VirtualBox

source: vbox/trunk/src/VBox/Main/src-all/ProgressImpl.cpp@ 44124

Last change on this file since 44124 was 44124, checked in by vboxsync, 12 years ago

Eliminate last use of the CombinedProgess class - bugtracker id 6167

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 39.5 KB
Line 
1/* $Id: ProgressImpl.cpp 44124 2012-12-13 17:11:48Z vboxsync $ */
2/** @file
3 *
4 * VirtualBox Progress COM class implementation
5 */
6
7/*
8 * Copyright (C) 2006-2012 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19#include <iprt/types.h>
20
21
22#if defined(VBOX_WITH_XPCOM)
23#include <nsIServiceManager.h>
24#include <nsIExceptionService.h>
25#include <nsCOMPtr.h>
26#endif /* defined(VBOX_WITH_XPCOM) */
27
28#include "ProgressImpl.h"
29
30#include "VirtualBoxImpl.h"
31#include "VirtualBoxErrorInfoImpl.h"
32
33#include "Logging.h"
34
35#include <iprt/time.h>
36#include <iprt/semaphore.h>
37#include <iprt/cpp/utils.h>
38
39#include <VBox/err.h>
40#include "AutoCaller.h"
41
42////////////////////////////////////////////////////////////////////////////////
43// ProgressBase class
44////////////////////////////////////////////////////////////////////////////////
45
46// constructor / destructor
47////////////////////////////////////////////////////////////////////////////////
48
49ProgressBase::ProgressBase()
50#if !defined(VBOX_COM_INPROC)
51 : mParent(NULL)
52#endif
53{
54}
55
56ProgressBase::~ProgressBase()
57{
58}
59
60
61/**
62 * Subclasses must call this method from their FinalConstruct() implementations.
63 */
64HRESULT ProgressBase::FinalConstruct()
65{
66 mCancelable = FALSE;
67 mCompleted = FALSE;
68 mCanceled = FALSE;
69 mResultCode = S_OK;
70
71 m_cOperations
72 = m_ulTotalOperationsWeight
73 = m_ulOperationsCompletedWeight
74 = m_ulCurrentOperation
75 = m_ulCurrentOperationWeight
76 = m_ulOperationPercent
77 = m_cMsTimeout
78 = 0;
79
80 // get creation timestamp
81 m_ullTimestamp = RTTimeMilliTS();
82
83 m_pfnCancelCallback = NULL;
84 m_pvCancelUserArg = NULL;
85
86 return BaseFinalConstruct();
87}
88
89// protected initializer/uninitializer for internal purposes only
90////////////////////////////////////////////////////////////////////////////////
91
92/**
93 * Initializes the progress base object.
94 *
95 * Subclasses should call this or any other #protectedInit() method from their
96 * init() implementations.
97 *
98 * @param aAutoInitSpan AutoInitSpan object instantiated by a subclass.
99 * @param aParent Parent object (only for server-side Progress objects).
100 * @param aInitiator Initiator of the task (for server-side objects. Can be
101 * NULL which means initiator = parent, otherwise must not
102 * be NULL).
103 * @param aDescription ask description.
104 * @param aID Address of result GUID structure (optional).
105 *
106 * @return COM result indicator.
107 */
108HRESULT ProgressBase::protectedInit(AutoInitSpan &aAutoInitSpan,
109#if !defined(VBOX_COM_INPROC)
110 VirtualBox *aParent,
111#endif
112 IUnknown *aInitiator,
113 CBSTR aDescription,
114 OUT_GUID aId /* = NULL */)
115{
116 /* Guarantees subclasses call this method at the proper time */
117 NOREF(aAutoInitSpan);
118
119 AutoCaller autoCaller(this);
120 AssertReturn(autoCaller.state() == InInit, E_FAIL);
121
122#if !defined(VBOX_COM_INPROC)
123 AssertReturn(aParent, E_INVALIDARG);
124#else
125 AssertReturn(aInitiator, E_INVALIDARG);
126#endif
127
128 AssertReturn(aDescription, E_INVALIDARG);
129
130#if !defined(VBOX_COM_INPROC)
131 /* share parent weakly */
132 unconst(mParent) = aParent;
133#endif
134
135#if !defined(VBOX_COM_INPROC)
136 /* assign (and therefore addref) initiator only if it is not VirtualBox
137 * (to avoid cycling); otherwise mInitiator will remain null which means
138 * that it is the same as the parent */
139 if (aInitiator)
140 {
141 ComObjPtr<VirtualBox> pVirtualBox(mParent);
142 if (!(pVirtualBox == aInitiator))
143 unconst(mInitiator) = aInitiator;
144 }
145#else
146 unconst(mInitiator) = aInitiator;
147#endif
148
149 unconst(mId).create();
150 if (aId)
151 mId.cloneTo(aId);
152
153#if !defined(VBOX_COM_INPROC)
154 /* add to the global collection of progress operations (note: after
155 * creating mId) */
156 mParent->addProgress(this);
157#endif
158
159 unconst(mDescription) = aDescription;
160
161 return S_OK;
162}
163
164/**
165 * Initializes the progress base object.
166 *
167 * This is a special initializer that doesn't initialize any field. Used by one
168 * of the Progress::init() forms to create sub-progress operations combined
169 * together using a CombinedProgress instance, so it doesn't require the parent,
170 * initiator, description and doesn't create an ID.
171 *
172 * Subclasses should call this or any other #protectedInit() method from their
173 * init() implementations.
174 *
175 * @param aAutoInitSpan AutoInitSpan object instantiated by a subclass.
176 */
177HRESULT ProgressBase::protectedInit(AutoInitSpan &aAutoInitSpan)
178{
179 /* Guarantees subclasses call this method at the proper time */
180 NOREF(aAutoInitSpan);
181
182 return S_OK;
183}
184
185/**
186 * Uninitializes the instance.
187 *
188 * Subclasses should call this from their uninit() implementations.
189 *
190 * @param aAutoUninitSpan AutoUninitSpan object instantiated by a subclass.
191 *
192 * @note Using the mParent member after this method returns is forbidden.
193 */
194void ProgressBase::protectedUninit(AutoUninitSpan &aAutoUninitSpan)
195{
196 /* release initiator (effective only if mInitiator has been assigned in
197 * init()) */
198 unconst(mInitiator).setNull();
199
200#if !defined(VBOX_COM_INPROC)
201 if (mParent)
202 {
203 /* remove the added progress on failure to complete the initialization */
204 if (aAutoUninitSpan.initFailed() && mId.isValid() && !mId.isZero())
205 mParent->removeProgress(mId.ref());
206
207 unconst(mParent) = NULL;
208 }
209#endif
210}
211
212// IProgress properties
213/////////////////////////////////////////////////////////////////////////////
214
215STDMETHODIMP ProgressBase::COMGETTER(Id)(BSTR *aId)
216{
217 CheckComArgOutPointerValid(aId);
218
219 AutoCaller autoCaller(this);
220 if (FAILED(autoCaller.rc())) return autoCaller.rc();
221
222 /* mId is constant during life time, no need to lock */
223 mId.toUtf16().cloneTo(aId);
224
225 return S_OK;
226}
227
228STDMETHODIMP ProgressBase::COMGETTER(Description)(BSTR *aDescription)
229{
230 CheckComArgOutPointerValid(aDescription);
231
232 AutoCaller autoCaller(this);
233 if (FAILED(autoCaller.rc())) return autoCaller.rc();
234
235 /* mDescription is constant during life time, no need to lock */
236 mDescription.cloneTo(aDescription);
237
238 return S_OK;
239}
240
241STDMETHODIMP ProgressBase::COMGETTER(Initiator)(IUnknown **aInitiator)
242{
243 CheckComArgOutPointerValid(aInitiator);
244
245 AutoCaller autoCaller(this);
246 if (FAILED(autoCaller.rc())) return autoCaller.rc();
247
248 /* mInitiator/mParent are constant during life time, no need to lock */
249
250#if !defined(VBOX_COM_INPROC)
251 if (mInitiator)
252 mInitiator.queryInterfaceTo(aInitiator);
253 else
254 {
255 ComObjPtr<VirtualBox> pVirtualBox(mParent);
256 pVirtualBox.queryInterfaceTo(aInitiator);
257 }
258#else
259 mInitiator.queryInterfaceTo(aInitiator);
260#endif
261
262 return S_OK;
263}
264
265STDMETHODIMP ProgressBase::COMGETTER(Cancelable)(BOOL *aCancelable)
266{
267 CheckComArgOutPointerValid(aCancelable);
268
269 AutoCaller autoCaller(this);
270 if (FAILED(autoCaller.rc())) return autoCaller.rc();
271
272 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
273
274 *aCancelable = mCancelable;
275
276 return S_OK;
277}
278
279/**
280 * Internal helper to compute the total percent value based on the member values and
281 * returns it as a "double". This is used both by GetPercent (which returns it as a
282 * rounded ULONG) and GetTimeRemaining().
283 *
284 * Requires locking by the caller!
285 *
286 * @return fractional percentage as a double value.
287 */
288double ProgressBase::calcTotalPercent()
289{
290 // avoid division by zero
291 if (m_ulTotalOperationsWeight == 0)
292 return 0;
293
294 double dPercent = ( (double)m_ulOperationsCompletedWeight // weight of operations that have been completed
295 + ((double)m_ulOperationPercent * (double)m_ulCurrentOperationWeight / (double)100) // plus partial weight of the current operation
296 ) * (double)100 / (double)m_ulTotalOperationsWeight;
297
298 return dPercent;
299}
300
301/**
302 * Internal helper for automatically timing out the operation.
303 *
304 * The caller should hold the object write lock.
305 */
306void ProgressBase::checkForAutomaticTimeout(void)
307{
308 if ( m_cMsTimeout
309 && mCancelable
310 && !mCanceled
311 && RTTimeMilliTS() - m_ullTimestamp > m_cMsTimeout
312 )
313 Cancel();
314}
315
316
317STDMETHODIMP ProgressBase::COMGETTER(TimeRemaining)(LONG *aTimeRemaining)
318{
319 CheckComArgOutPointerValid(aTimeRemaining);
320
321 AutoCaller autoCaller(this);
322 if (FAILED(autoCaller.rc())) return autoCaller.rc();
323
324 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
325
326 if (mCompleted)
327 *aTimeRemaining = 0;
328 else
329 {
330 double dPercentDone = calcTotalPercent();
331 if (dPercentDone < 1)
332 *aTimeRemaining = -1; // unreliable, or avoid division by 0 below
333 else
334 {
335 uint64_t ullTimeNow = RTTimeMilliTS();
336 uint64_t ullTimeElapsed = ullTimeNow - m_ullTimestamp;
337 uint64_t ullTimeTotal = (uint64_t)(ullTimeElapsed * 100 / dPercentDone);
338 uint64_t ullTimeRemaining = ullTimeTotal - ullTimeElapsed;
339
340// Log(("ProgressBase::GetTimeRemaining: dPercentDone %RI32, ullTimeNow = %RI64, ullTimeElapsed = %RI64, ullTimeTotal = %RI64, ullTimeRemaining = %RI64\n",
341// (uint32_t)dPercentDone, ullTimeNow, ullTimeElapsed, ullTimeTotal, ullTimeRemaining));
342
343 *aTimeRemaining = (LONG)(ullTimeRemaining / 1000);
344 }
345 }
346
347 return S_OK;
348}
349
350STDMETHODIMP ProgressBase::COMGETTER(Percent)(ULONG *aPercent)
351{
352 CheckComArgOutPointerValid(aPercent);
353
354 AutoCaller autoCaller(this);
355 if (FAILED(autoCaller.rc())) return autoCaller.rc();
356
357 checkForAutomaticTimeout();
358
359 /* checkForAutomaticTimeout requires a write lock. */
360 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
361
362 if (mCompleted && SUCCEEDED(mResultCode))
363 *aPercent = 100;
364 else
365 {
366 ULONG ulPercent = (ULONG)calcTotalPercent();
367 // do not report 100% until we're really really done with everything as the Qt GUI dismisses progress dialogs in that case
368 if ( ulPercent == 100
369 && ( m_ulOperationPercent < 100
370 || (m_ulCurrentOperation < m_cOperations -1)
371 )
372 )
373 *aPercent = 99;
374 else
375 *aPercent = ulPercent;
376 }
377
378 checkForAutomaticTimeout();
379
380 return S_OK;
381}
382
383STDMETHODIMP ProgressBase::COMGETTER(Completed)(BOOL *aCompleted)
384{
385 CheckComArgOutPointerValid(aCompleted);
386
387 AutoCaller autoCaller(this);
388 if (FAILED(autoCaller.rc())) return autoCaller.rc();
389
390 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
391
392 *aCompleted = mCompleted;
393
394 return S_OK;
395}
396
397STDMETHODIMP ProgressBase::COMGETTER(Canceled)(BOOL *aCanceled)
398{
399 CheckComArgOutPointerValid(aCanceled);
400
401 AutoCaller autoCaller(this);
402 if (FAILED(autoCaller.rc())) return autoCaller.rc();
403
404 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
405
406 *aCanceled = mCanceled;
407
408 return S_OK;
409}
410
411STDMETHODIMP ProgressBase::COMGETTER(ResultCode)(LONG *aResultCode)
412{
413 CheckComArgOutPointerValid(aResultCode);
414
415 AutoCaller autoCaller(this);
416 if (FAILED(autoCaller.rc())) return autoCaller.rc();
417
418 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
419
420 if (!mCompleted)
421 return setError(E_FAIL,
422 tr("Result code is not available, operation is still in progress"));
423
424 *aResultCode = mResultCode;
425
426 return S_OK;
427}
428
429STDMETHODIMP ProgressBase::COMGETTER(ErrorInfo)(IVirtualBoxErrorInfo **aErrorInfo)
430{
431 CheckComArgOutPointerValid(aErrorInfo);
432
433 AutoCaller autoCaller(this);
434 if (FAILED(autoCaller.rc())) return autoCaller.rc();
435
436 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
437
438 if (!mCompleted)
439 return setError(E_FAIL,
440 tr("Error info is not available, operation is still in progress"));
441
442 mErrorInfo.queryInterfaceTo(aErrorInfo);
443
444 return S_OK;
445}
446
447STDMETHODIMP ProgressBase::COMGETTER(OperationCount)(ULONG *aOperationCount)
448{
449 CheckComArgOutPointerValid(aOperationCount);
450
451 AutoCaller autoCaller(this);
452 if (FAILED(autoCaller.rc())) return autoCaller.rc();
453
454 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
455
456 *aOperationCount = m_cOperations;
457
458 return S_OK;
459}
460
461STDMETHODIMP ProgressBase::COMGETTER(Operation)(ULONG *aOperation)
462{
463 CheckComArgOutPointerValid(aOperation);
464
465 AutoCaller autoCaller(this);
466 if (FAILED(autoCaller.rc())) return autoCaller.rc();
467
468 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
469
470 *aOperation = m_ulCurrentOperation;
471
472 return S_OK;
473}
474
475STDMETHODIMP ProgressBase::COMGETTER(OperationDescription)(BSTR *aOperationDescription)
476{
477 CheckComArgOutPointerValid(aOperationDescription);
478
479 AutoCaller autoCaller(this);
480 if (FAILED(autoCaller.rc())) return autoCaller.rc();
481
482 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
483
484 m_bstrOperationDescription.cloneTo(aOperationDescription);
485
486 return S_OK;
487}
488
489STDMETHODIMP ProgressBase::COMGETTER(OperationPercent)(ULONG *aOperationPercent)
490{
491 CheckComArgOutPointerValid(aOperationPercent);
492
493 AutoCaller autoCaller(this);
494 if (FAILED(autoCaller.rc())) return autoCaller.rc();
495
496 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
497
498 if (mCompleted && SUCCEEDED(mResultCode))
499 *aOperationPercent = 100;
500 else
501 *aOperationPercent = m_ulOperationPercent;
502
503 return S_OK;
504}
505
506STDMETHODIMP ProgressBase::COMGETTER(OperationWeight)(ULONG *aOperationWeight)
507{
508 CheckComArgOutPointerValid(aOperationWeight);
509
510 AutoCaller autoCaller(this);
511 if (FAILED(autoCaller.rc())) return autoCaller.rc();
512
513 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
514
515 *aOperationWeight = m_ulCurrentOperationWeight;
516
517 return S_OK;
518}
519
520STDMETHODIMP ProgressBase::COMSETTER(Timeout)(ULONG aTimeout)
521{
522 AutoCaller autoCaller(this);
523 if (FAILED(autoCaller.rc())) return autoCaller.rc();
524
525 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
526
527 if (!mCancelable)
528 return setError(VBOX_E_INVALID_OBJECT_STATE,
529 tr("Operation cannot be canceled"));
530
531 LogThisFunc(("%#x => %#x\n", m_cMsTimeout, aTimeout));
532 m_cMsTimeout = aTimeout;
533 return S_OK;
534}
535
536STDMETHODIMP ProgressBase::COMGETTER(Timeout)(ULONG *aTimeout)
537{
538 CheckComArgOutPointerValid(aTimeout);
539
540 AutoCaller autoCaller(this);
541 if (FAILED(autoCaller.rc())) return autoCaller.rc();
542
543 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
544
545 *aTimeout = m_cMsTimeout;
546 return S_OK;
547}
548
549// public methods only for internal purposes
550////////////////////////////////////////////////////////////////////////////////
551
552/**
553 * Sets the cancelation callback, checking for cancelation first.
554 *
555 * @returns Success indicator.
556 * @retval true on success.
557 * @retval false if the progress object has already been canceled or is in an
558 * invalid state
559 *
560 * @param pfnCallback The function to be called upon cancelation.
561 * @param pvUser The callback argument.
562 */
563bool ProgressBase::setCancelCallback(void (*pfnCallback)(void *), void *pvUser)
564{
565 AutoCaller autoCaller(this);
566 AssertComRCReturn(autoCaller.rc(), false);
567
568 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
569
570 checkForAutomaticTimeout();
571 if (mCanceled)
572 return false;
573
574 m_pvCancelUserArg = pvUser;
575 m_pfnCancelCallback = pfnCallback;
576 return true;
577}
578
579////////////////////////////////////////////////////////////////////////////////
580// Progress class
581////////////////////////////////////////////////////////////////////////////////
582
583HRESULT Progress::FinalConstruct()
584{
585 HRESULT rc = ProgressBase::FinalConstruct();
586 if (FAILED(rc)) return rc;
587
588 mCompletedSem = NIL_RTSEMEVENTMULTI;
589 mWaitersCount = 0;
590
591 return S_OK;
592}
593
594void Progress::FinalRelease()
595{
596 uninit();
597 BaseFinalRelease();
598}
599
600// public initializer/uninitializer for internal purposes only
601////////////////////////////////////////////////////////////////////////////////
602
603/**
604 * Initializes the normal progress object. With this variant, one can have
605 * an arbitrary number of sub-operation which IProgress can analyze to
606 * have a weighted progress computed.
607 *
608 * For example, say that one IProgress is supposed to track the cloning
609 * of two hard disk images, which are 100 MB and 1000 MB in size, respectively,
610 * and each of these hard disks should be one sub-operation of the IProgress.
611 *
612 * Obviously the progress would be misleading if the progress displayed 50%
613 * after the smaller image was cloned and would then take much longer for
614 * the second half.
615 *
616 * With weighted progress, one can invoke the following calls:
617 *
618 * 1) create progress object with cOperations = 2 and ulTotalOperationsWeight =
619 * 1100 (100 MB plus 1100, but really the weights can be any ULONG); pass
620 * in ulFirstOperationWeight = 100 for the first sub-operation
621 *
622 * 2) Then keep calling setCurrentOperationProgress() with a percentage
623 * for the first image; the total progress will increase up to a value
624 * of 9% (100MB / 1100MB * 100%).
625 *
626 * 3) Then call setNextOperation with the second weight (1000 for the megabytes
627 * of the second disk).
628 *
629 * 4) Then keep calling setCurrentOperationProgress() with a percentage for
630 * the second image, where 100% of the operation will then yield a 100%
631 * progress of the entire task.
632 *
633 * Weighting is optional; you can simply assign a weight of 1 to each operation
634 * and pass ulTotalOperationsWeight == cOperations to this constructor (but
635 * for that variant and for backwards-compatibility a simpler constructor exists
636 * in ProgressImpl.h as well).
637 *
638 * Even simpler, if you need no sub-operations at all, pass in cOperations =
639 * ulTotalOperationsWeight = ulFirstOperationWeight = 1.
640 *
641 * @param aParent See ProgressBase::init().
642 * @param aInitiator See ProgressBase::init().
643 * @param aDescription See ProgressBase::init().
644 * @param aCancelable Flag whether the task maybe canceled.
645 * @param cOperations Number of operations within this task (at least 1).
646 * @param ulTotalOperationsWeight Total weight of operations; must be the sum of ulFirstOperationWeight and
647 * what is later passed with each subsequent setNextOperation() call.
648 * @param bstrFirstOperationDescription Description of the first operation.
649 * @param ulFirstOperationWeight Weight of first sub-operation.
650 * @param aId See ProgressBase::init().
651 */
652HRESULT Progress::init(
653#if !defined(VBOX_COM_INPROC)
654 VirtualBox *aParent,
655#endif
656 IUnknown *aInitiator,
657 CBSTR aDescription,
658 BOOL aCancelable,
659 ULONG cOperations,
660 ULONG ulTotalOperationsWeight,
661 CBSTR bstrFirstOperationDescription,
662 ULONG ulFirstOperationWeight,
663 OUT_GUID aId /* = NULL */)
664{
665 LogFlowThisFunc(("aDescription=\"%ls\", cOperations=%d, ulTotalOperationsWeight=%d, bstrFirstOperationDescription=\"%ls\", ulFirstOperationWeight=%d\n",
666 aDescription,
667 cOperations,
668 ulTotalOperationsWeight,
669 bstrFirstOperationDescription,
670 ulFirstOperationWeight));
671
672 AssertReturn(bstrFirstOperationDescription, E_INVALIDARG);
673 AssertReturn(ulTotalOperationsWeight >= 1, E_INVALIDARG);
674
675 /* Enclose the state transition NotReady->InInit->Ready */
676 AutoInitSpan autoInitSpan(this);
677 AssertReturn(autoInitSpan.isOk(), E_FAIL);
678
679 HRESULT rc = S_OK;
680
681 rc = ProgressBase::protectedInit(autoInitSpan,
682#if !defined(VBOX_COM_INPROC)
683 aParent,
684#endif
685 aInitiator, aDescription, aId);
686 if (FAILED(rc)) return rc;
687
688 mCancelable = aCancelable;
689
690 m_cOperations = cOperations;
691 m_ulTotalOperationsWeight = ulTotalOperationsWeight;
692 m_ulOperationsCompletedWeight = 0;
693 m_ulCurrentOperation = 0;
694 m_bstrOperationDescription = bstrFirstOperationDescription;
695 m_ulCurrentOperationWeight = ulFirstOperationWeight;
696 m_ulOperationPercent = 0;
697
698 int vrc = RTSemEventMultiCreate(&mCompletedSem);
699 ComAssertRCRet(vrc, E_FAIL);
700
701 RTSemEventMultiReset(mCompletedSem);
702
703 /* Confirm a successful initialization when it's the case */
704 if (SUCCEEDED(rc))
705 autoInitSpan.setSucceeded();
706
707 return rc;
708}
709
710/**
711 * Initializes the sub-progress object that represents a specific operation of
712 * the whole task.
713 *
714 * Objects initialized with this method are then combined together into the
715 * single task using a CombinedProgress instance, so it doesn't require the
716 * parent, initiator, description and doesn't create an ID. Note that calling
717 * respective getter methods on an object initialized with this method is
718 * useless. Such objects are used only to provide a separate wait semaphore and
719 * store individual operation descriptions.
720 *
721 * @param aCancelable Flag whether the task maybe canceled.
722 * @param aOperationCount Number of sub-operations within this task (at least 1).
723 * @param aOperationDescription Description of the individual operation.
724 */
725HRESULT Progress::init(BOOL aCancelable,
726 ULONG aOperationCount,
727 CBSTR aOperationDescription)
728{
729 LogFlowThisFunc(("aOperationDescription=\"%ls\"\n", aOperationDescription));
730
731 /* Enclose the state transition NotReady->InInit->Ready */
732 AutoInitSpan autoInitSpan(this);
733 AssertReturn(autoInitSpan.isOk(), E_FAIL);
734
735 HRESULT rc = S_OK;
736
737 rc = ProgressBase::protectedInit(autoInitSpan);
738 if (FAILED(rc)) return rc;
739
740 mCancelable = aCancelable;
741
742 // for this variant we assume for now that all operations are weighed "1"
743 // and equal total weight = operation count
744 m_cOperations = aOperationCount;
745 m_ulTotalOperationsWeight = aOperationCount;
746 m_ulOperationsCompletedWeight = 0;
747 m_ulCurrentOperation = 0;
748 m_bstrOperationDescription = aOperationDescription;
749 m_ulCurrentOperationWeight = 1;
750 m_ulOperationPercent = 0;
751
752 int vrc = RTSemEventMultiCreate(&mCompletedSem);
753 ComAssertRCRet(vrc, E_FAIL);
754
755 RTSemEventMultiReset(mCompletedSem);
756
757 /* Confirm a successful initialization when it's the case */
758 if (SUCCEEDED(rc))
759 autoInitSpan.setSucceeded();
760
761 return rc;
762}
763
764/**
765 * Uninitializes the instance and sets the ready flag to FALSE.
766 *
767 * Called either from FinalRelease() or by the parent when it gets destroyed.
768 */
769void Progress::uninit()
770{
771 LogFlowThisFunc(("\n"));
772
773 /* Enclose the state transition Ready->InUninit->NotReady */
774 AutoUninitSpan autoUninitSpan(this);
775 if (autoUninitSpan.uninitDone())
776 return;
777
778 /* wake up all threads still waiting on occasion */
779 if (mWaitersCount > 0)
780 {
781 LogFlow(("WARNING: There are still %d threads waiting for '%ls' completion!\n",
782 mWaitersCount, mDescription.raw()));
783 RTSemEventMultiSignal(mCompletedSem);
784 }
785
786 RTSemEventMultiDestroy(mCompletedSem);
787
788 ProgressBase::protectedUninit(autoUninitSpan);
789}
790
791// IProgress properties
792/////////////////////////////////////////////////////////////////////////////
793
794// IProgress methods
795/////////////////////////////////////////////////////////////////////////////
796
797/**
798 * @note XPCOM: when this method is not called on the main XPCOM thread, it
799 * simply blocks the thread until mCompletedSem is signalled. If the
800 * thread has its own event queue (hmm, what for?) that it must run, then
801 * calling this method will definitely freeze event processing.
802 */
803STDMETHODIMP Progress::WaitForCompletion(LONG aTimeout)
804{
805 LogFlowThisFuncEnter();
806 LogFlowThisFunc(("aTimeout=%d\n", aTimeout));
807
808 AutoCaller autoCaller(this);
809 if (FAILED(autoCaller.rc())) return autoCaller.rc();
810
811 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
812
813 /* if we're already completed, take a shortcut */
814 if (!mCompleted)
815 {
816 int vrc = VINF_SUCCESS;
817 bool fForever = aTimeout < 0;
818 int64_t timeLeft = aTimeout;
819 int64_t lastTime = RTTimeMilliTS();
820
821 while (!mCompleted && (fForever || timeLeft > 0))
822 {
823 mWaitersCount++;
824 alock.release();
825 vrc = RTSemEventMultiWait(mCompletedSem,
826 fForever ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)timeLeft);
827 alock.acquire();
828 mWaitersCount--;
829
830 /* the last waiter resets the semaphore */
831 if (mWaitersCount == 0)
832 RTSemEventMultiReset(mCompletedSem);
833
834 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
835 break;
836
837 if (!fForever)
838 {
839 int64_t now = RTTimeMilliTS();
840 timeLeft -= now - lastTime;
841 lastTime = now;
842 }
843 }
844
845 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
846 return setError(VBOX_E_IPRT_ERROR,
847 tr("Failed to wait for the task completion (%Rrc)"),
848 vrc);
849 }
850
851 LogFlowThisFuncLeave();
852
853 return S_OK;
854}
855
856/**
857 * @note XPCOM: when this method is not called on the main XPCOM thread, it
858 * simply blocks the thread until mCompletedSem is signalled. If the
859 * thread has its own event queue (hmm, what for?) that it must run, then
860 * calling this method will definitely freeze event processing.
861 */
862STDMETHODIMP Progress::WaitForOperationCompletion(ULONG aOperation, LONG aTimeout)
863{
864 LogFlowThisFuncEnter();
865 LogFlowThisFunc(("aOperation=%d, aTimeout=%d\n", aOperation, aTimeout));
866
867 AutoCaller autoCaller(this);
868 if (FAILED(autoCaller.rc())) return autoCaller.rc();
869
870 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
871
872 CheckComArgExpr(aOperation, aOperation < m_cOperations);
873
874 /* if we're already completed or if the given operation is already done,
875 * then take a shortcut */
876 if ( !mCompleted
877 && aOperation >= m_ulCurrentOperation)
878 {
879 int vrc = VINF_SUCCESS;
880 bool fForever = aTimeout < 0;
881 int64_t timeLeft = aTimeout;
882 int64_t lastTime = RTTimeMilliTS();
883
884 while ( !mCompleted && aOperation >= m_ulCurrentOperation
885 && (fForever || timeLeft > 0))
886 {
887 mWaitersCount ++;
888 alock.release();
889 vrc = RTSemEventMultiWait(mCompletedSem,
890 fForever ? RT_INDEFINITE_WAIT : (unsigned) timeLeft);
891 alock.acquire();
892 mWaitersCount--;
893
894 /* the last waiter resets the semaphore */
895 if (mWaitersCount == 0)
896 RTSemEventMultiReset(mCompletedSem);
897
898 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
899 break;
900
901 if (!fForever)
902 {
903 int64_t now = RTTimeMilliTS();
904 timeLeft -= now - lastTime;
905 lastTime = now;
906 }
907 }
908
909 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
910 return setError(E_FAIL,
911 tr("Failed to wait for the operation completion (%Rrc)"),
912 vrc);
913 }
914
915 LogFlowThisFuncLeave();
916
917 return S_OK;
918}
919
920STDMETHODIMP Progress::WaitForAsyncProgressCompletion(IProgress *pProgressAsync)
921{
922 LogFlowThisFuncEnter();
923
924 CheckComArgNotNull(pProgressAsync);
925
926 AutoCaller autoCaller(this);
927 if (FAILED(autoCaller.rc())) return autoCaller.rc();
928
929 /* Note: we don't lock here, cause we just using public methods. */
930
931 HRESULT rc = S_OK;
932 BOOL fCancelable = FALSE;
933 BOOL fCompleted = FALSE;
934 BOOL fCanceled = FALSE;
935 ULONG prevPercent = UINT32_MAX;
936 ULONG currentPercent = 0;
937 ULONG cOp = 0;
938 /* Is the async process cancelable? */
939 rc = pProgressAsync->COMGETTER(Cancelable)(&fCancelable);
940 if (FAILED(rc)) return rc;
941 /* Loop as long as the sync process isn't completed. */
942 while (SUCCEEDED(pProgressAsync->COMGETTER(Completed(&fCompleted))))
943 {
944 /* We can forward any cancel request to the async process only when
945 * it is cancelable. */
946 if (fCancelable)
947 {
948 rc = COMGETTER(Canceled)(&fCanceled);
949 if (FAILED(rc)) return rc;
950 if (fCanceled)
951 {
952 rc = pProgressAsync->Cancel();
953 if (FAILED(rc)) return rc;
954 }
955 }
956 /* Even if the user canceled the process, we have to wait until the
957 async task has finished his work (cleanup and such). Otherwise there
958 will be sync trouble (still wrong state, dead locks, ...) on the
959 used objects. So just do nothing, but wait for the complete
960 notification. */
961 if (!fCanceled)
962 {
963 /* Check if the current operation has changed. It is also possible that
964 * in the meantime more than one async operation was finished. So we
965 * have to loop as long as we reached the same operation count. */
966 ULONG curOp;
967 for(;;)
968 {
969 rc = pProgressAsync->COMGETTER(Operation(&curOp));
970 if (FAILED(rc)) return rc;
971 if (cOp != curOp)
972 {
973 Bstr bstr;
974 ULONG currentWeight;
975 rc = pProgressAsync->COMGETTER(OperationDescription(bstr.asOutParam()));
976 if (FAILED(rc)) return rc;
977 rc = pProgressAsync->COMGETTER(OperationWeight(&currentWeight));
978 if (FAILED(rc)) return rc;
979 rc = SetNextOperation(bstr.raw(), currentWeight);
980 if (FAILED(rc)) return rc;
981 ++cOp;
982 }
983 else
984 break;
985 }
986
987 rc = pProgressAsync->COMGETTER(OperationPercent(&currentPercent));
988 if (FAILED(rc)) return rc;
989 if (currentPercent != prevPercent)
990 {
991 prevPercent = currentPercent;
992 rc = SetCurrentOperationProgress(currentPercent);
993 if (FAILED(rc)) return rc;
994 }
995 }
996 if (fCompleted)
997 break;
998
999 /* Make sure the loop is not too tight */
1000 rc = pProgressAsync->WaitForCompletion(100);
1001 if (FAILED(rc)) return rc;
1002 }
1003
1004 LogFlowThisFuncLeave();
1005
1006 return rc;
1007}
1008
1009STDMETHODIMP Progress::Cancel()
1010{
1011 AutoCaller autoCaller(this);
1012 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1013
1014 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1015
1016 if (!mCancelable)
1017 return setError(VBOX_E_INVALID_OBJECT_STATE,
1018 tr("Operation cannot be canceled"));
1019
1020 if (!mCanceled)
1021 {
1022 LogThisFunc(("Canceling\n"));
1023 mCanceled = TRUE;
1024 if (m_pfnCancelCallback)
1025 m_pfnCancelCallback(m_pvCancelUserArg);
1026
1027 }
1028 else
1029 LogThisFunc(("Already canceled\n"));
1030
1031 return S_OK;
1032}
1033
1034/**
1035 * Updates the percentage value of the current operation.
1036 *
1037 * @param aPercent New percentage value of the operation in progress
1038 * (in range [0, 100]).
1039 */
1040STDMETHODIMP Progress::SetCurrentOperationProgress(ULONG aPercent)
1041{
1042 AutoCaller autoCaller(this);
1043 AssertComRCReturnRC(autoCaller.rc());
1044
1045 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1046
1047 AssertMsgReturn(aPercent <= 100, ("%u\n", aPercent), E_INVALIDARG);
1048
1049 checkForAutomaticTimeout();
1050 if (mCancelable && mCanceled)
1051 {
1052 Assert(!mCompleted);
1053 return E_FAIL;
1054 }
1055 AssertReturn(!mCompleted && !mCanceled, E_FAIL);
1056
1057 m_ulOperationPercent = aPercent;
1058
1059 return S_OK;
1060}
1061
1062/**
1063 * Signals that the current operation is successfully completed and advances to
1064 * the next operation. The operation percentage is reset to 0.
1065 *
1066 * @param aOperationDescription Description of the next operation.
1067 *
1068 * @note The current operation must not be the last one.
1069 */
1070STDMETHODIMP Progress::SetNextOperation(IN_BSTR bstrNextOperationDescription, ULONG ulNextOperationsWeight)
1071{
1072 AssertReturn(bstrNextOperationDescription, E_INVALIDARG);
1073
1074 AutoCaller autoCaller(this);
1075 AssertComRCReturnRC(autoCaller.rc());
1076
1077 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1078
1079 if (mCanceled)
1080 return E_FAIL;
1081 AssertReturn(!mCompleted, E_FAIL);
1082 AssertReturn(m_ulCurrentOperation + 1 < m_cOperations, E_FAIL);
1083
1084 ++m_ulCurrentOperation;
1085 m_ulOperationsCompletedWeight += m_ulCurrentOperationWeight;
1086
1087 m_bstrOperationDescription = bstrNextOperationDescription;
1088 m_ulCurrentOperationWeight = ulNextOperationsWeight;
1089 m_ulOperationPercent = 0;
1090
1091 Log(("Progress::setNextOperation(%ls): ulNextOperationsWeight = %d; m_ulCurrentOperation is now %d, m_ulOperationsCompletedWeight is now %d\n",
1092 m_bstrOperationDescription.raw(), ulNextOperationsWeight, m_ulCurrentOperation, m_ulOperationsCompletedWeight));
1093
1094 /* wake up all waiting threads */
1095 if (mWaitersCount > 0)
1096 RTSemEventMultiSignal(mCompletedSem);
1097
1098 return S_OK;
1099}
1100
1101// public methods only for internal purposes
1102/////////////////////////////////////////////////////////////////////////////
1103
1104/**
1105 * Sets the internal result code and attempts to retrieve additional error
1106 * info from the current thread. Gets called from Progress::notifyComplete(),
1107 * but can be called again to override a previous result set with
1108 * notifyComplete().
1109 *
1110 * @param aResultCode
1111 */
1112HRESULT Progress::setResultCode(HRESULT aResultCode)
1113{
1114 AutoCaller autoCaller(this);
1115 AssertComRCReturnRC(autoCaller.rc());
1116
1117 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1118
1119 mResultCode = aResultCode;
1120
1121 HRESULT rc = S_OK;
1122
1123 if (FAILED(aResultCode))
1124 {
1125 /* try to import error info from the current thread */
1126
1127#if !defined(VBOX_WITH_XPCOM)
1128
1129 ComPtr<IErrorInfo> err;
1130 rc = ::GetErrorInfo(0, err.asOutParam());
1131 if (rc == S_OK && err)
1132 {
1133 rc = err.queryInterfaceTo(mErrorInfo.asOutParam());
1134 if (SUCCEEDED(rc) && !mErrorInfo)
1135 rc = E_FAIL;
1136 }
1137
1138#else /* !defined(VBOX_WITH_XPCOM) */
1139
1140 nsCOMPtr<nsIExceptionService> es;
1141 es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &rc);
1142 if (NS_SUCCEEDED(rc))
1143 {
1144 nsCOMPtr <nsIExceptionManager> em;
1145 rc = es->GetCurrentExceptionManager(getter_AddRefs(em));
1146 if (NS_SUCCEEDED(rc))
1147 {
1148 ComPtr<nsIException> ex;
1149 rc = em->GetCurrentException(ex.asOutParam());
1150 if (NS_SUCCEEDED(rc) && ex)
1151 {
1152 rc = ex.queryInterfaceTo(mErrorInfo.asOutParam());
1153 if (NS_SUCCEEDED(rc) && !mErrorInfo)
1154 rc = E_FAIL;
1155 }
1156 }
1157 }
1158#endif /* !defined(VBOX_WITH_XPCOM) */
1159
1160 AssertMsg(rc == S_OK, ("Couldn't get error info (rc=%08X) while trying to set a failed result (%08X)!\n",
1161 rc, aResultCode));
1162 }
1163
1164 return rc;
1165}
1166
1167/**
1168 * Marks the whole task as complete and sets the result code.
1169 *
1170 * If the result code indicates a failure (|FAILED(@a aResultCode)|) then this
1171 * method will import the error info from the current thread and assign it to
1172 * the errorInfo attribute (it will return an error if no info is available in
1173 * such case).
1174 *
1175 * If the result code indicates a success (|SUCCEEDED(@a aResultCode)|) then
1176 * the current operation is set to the last.
1177 *
1178 * Note that this method may be called only once for the given Progress object.
1179 * Subsequent calls will assert.
1180 *
1181 * @param aResultCode Operation result code.
1182 */
1183HRESULT Progress::notifyComplete(HRESULT aResultCode)
1184{
1185 AutoCaller autoCaller(this);
1186 AssertComRCReturnRC(autoCaller.rc());
1187
1188 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1189
1190 AssertReturn(mCompleted == FALSE, E_FAIL);
1191
1192 LogFunc(("aResultCode=%d\n", aResultCode));
1193
1194 if (mCanceled && SUCCEEDED(aResultCode))
1195 aResultCode = E_FAIL;
1196
1197 HRESULT rc = setResultCode(aResultCode);
1198
1199 mCompleted = TRUE;
1200
1201 if (!FAILED(aResultCode))
1202 {
1203 m_ulCurrentOperation = m_cOperations - 1; /* last operation */
1204 m_ulOperationPercent = 100;
1205 }
1206
1207#if !defined VBOX_COM_INPROC
1208 /* remove from the global collection of pending progress operations */
1209 if (mParent)
1210 mParent->removeProgress(mId.ref());
1211#endif
1212
1213 /* wake up all waiting threads */
1214 if (mWaitersCount > 0)
1215 RTSemEventMultiSignal(mCompletedSem);
1216
1217 return rc;
1218}
1219
1220/**
1221 * Wrapper around Progress:notifyCompleteV.
1222 */
1223HRESULT Progress::notifyComplete(HRESULT aResultCode,
1224 const GUID &aIID,
1225 const char *pcszComponent,
1226 const char *aText,
1227 ...)
1228{
1229 va_list va;
1230 va_start(va, aText);
1231 HRESULT hrc = notifyCompleteV(aResultCode, aIID, pcszComponent, aText, va);
1232 va_end(va);
1233 return hrc;
1234}
1235
1236/**
1237 * Marks the operation as complete and attaches full error info.
1238 *
1239 * See VirtualBoxBase::setError(HRESULT, const GUID &, const wchar_t
1240 * *, const char *, ...) for more info.
1241 *
1242 * @param aResultCode Operation result (error) code, must not be S_OK.
1243 * @param aIID IID of the interface that defines the error.
1244 * @param aComponent Name of the component that generates the error.
1245 * @param aText Error message (must not be null), an RTStrPrintf-like
1246 * format string in UTF-8 encoding.
1247 * @param va List of arguments for the format string.
1248 */
1249HRESULT Progress::notifyCompleteV(HRESULT aResultCode,
1250 const GUID &aIID,
1251 const char *pcszComponent,
1252 const char *aText,
1253 va_list va)
1254{
1255 Utf8Str text(aText, va);
1256
1257 AutoCaller autoCaller(this);
1258 AssertComRCReturnRC(autoCaller.rc());
1259
1260 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1261
1262 AssertReturn(mCompleted == FALSE, E_FAIL);
1263
1264 if (mCanceled && SUCCEEDED(aResultCode))
1265 aResultCode = E_FAIL;
1266
1267 mCompleted = TRUE;
1268 mResultCode = aResultCode;
1269
1270 AssertReturn(FAILED(aResultCode), E_FAIL);
1271
1272 ComObjPtr<VirtualBoxErrorInfo> errorInfo;
1273 HRESULT rc = errorInfo.createObject();
1274 AssertComRC(rc);
1275 if (SUCCEEDED(rc))
1276 {
1277 errorInfo->init(aResultCode, aIID, pcszComponent, text);
1278 errorInfo.queryInterfaceTo(mErrorInfo.asOutParam());
1279 }
1280
1281#if !defined VBOX_COM_INPROC
1282 /* remove from the global collection of pending progress operations */
1283 if (mParent)
1284 mParent->removeProgress(mId.ref());
1285#endif
1286
1287 /* wake up all waiting threads */
1288 if (mWaitersCount > 0)
1289 RTSemEventMultiSignal(mCompletedSem);
1290
1291 return rc;
1292}
1293
1294/**
1295 * Notify the progress object that we're almost at the point of no return.
1296 *
1297 * This atomically checks for and disables cancelation. Calls to
1298 * IProgress::Cancel() made after a successful call to this method will fail
1299 * and the user can be told. While this isn't entirely clean behavior, it
1300 * prevents issues with an irreversible actually operation succeeding while the
1301 * user believe it was rolled back.
1302 *
1303 * @returns Success indicator.
1304 * @retval true on success.
1305 * @retval false if the progress object has already been canceled or is in an
1306 * invalid state
1307 */
1308bool Progress::notifyPointOfNoReturn(void)
1309{
1310 AutoCaller autoCaller(this);
1311 AssertComRCReturn(autoCaller.rc(), false);
1312
1313 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1314
1315 if (mCanceled)
1316 {
1317 LogThisFunc(("returns false\n"));
1318 return false;
1319 }
1320
1321 mCancelable = FALSE;
1322 LogThisFunc(("returns true\n"));
1323 return true;
1324}
1325
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette