VirtualBox

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

Last change on this file since 41105 was 40257, checked in by vboxsync, 13 years ago

Main/Medium: rework locking scheme to solve lock order violations and long GUI start up time caused by too much locking
Main/all: Remove the enter and leave methods from write locks, they cause hard to find locking problems. Better solve them explicitly.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 55.6 KB
Line 
1/* $Id: ProgressImpl.cpp 40257 2012-02-27 09:25:12Z 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#if defined(VBOX_WITH_XPCOM)
22#include <nsIServiceManager.h>
23#include <nsIExceptionService.h>
24#include <nsCOMPtr.h>
25#endif /* defined(VBOX_WITH_XPCOM) */
26
27#include "ProgressCombinedImpl.h"
28
29#include "VirtualBoxImpl.h"
30#include "VirtualBoxErrorInfoImpl.h"
31
32#include "Logging.h"
33
34#include <iprt/time.h>
35#include <iprt/semaphore.h>
36#include <iprt/cpp/utils.h>
37
38#include <VBox/err.h>
39
40////////////////////////////////////////////////////////////////////////////////
41// ProgressBase class
42////////////////////////////////////////////////////////////////////////////////
43
44// constructor / destructor
45////////////////////////////////////////////////////////////////////////////////
46
47ProgressBase::ProgressBase()
48#if !defined(VBOX_COM_INPROC)
49 : mParent(NULL)
50#endif
51{
52}
53
54ProgressBase::~ProgressBase()
55{
56}
57
58
59/**
60 * Subclasses must call this method from their FinalConstruct() implementations.
61 */
62HRESULT ProgressBase::FinalConstruct()
63{
64 mCancelable = FALSE;
65 mCompleted = FALSE;
66 mCanceled = FALSE;
67 mResultCode = S_OK;
68
69 m_cOperations
70 = m_ulTotalOperationsWeight
71 = m_ulOperationsCompletedWeight
72 = m_ulCurrentOperation
73 = m_ulCurrentOperationWeight
74 = m_ulOperationPercent
75 = m_cMsTimeout
76 = 0;
77
78 // get creation timestamp
79 m_ullTimestamp = RTTimeMilliTS();
80
81 m_pfnCancelCallback = NULL;
82 m_pvCancelUserArg = NULL;
83
84 return BaseFinalConstruct();
85}
86
87// protected initializer/uninitializer for internal purposes only
88////////////////////////////////////////////////////////////////////////////////
89
90/**
91 * Initializes the progress base object.
92 *
93 * Subclasses should call this or any other #protectedInit() method from their
94 * init() implementations.
95 *
96 * @param aAutoInitSpan AutoInitSpan object instantiated by a subclass.
97 * @param aParent Parent object (only for server-side Progress objects).
98 * @param aInitiator Initiator of the task (for server-side objects. Can be
99 * NULL which means initiator = parent, otherwise must not
100 * be NULL).
101 * @param aDescription ask description.
102 * @param aID Address of result GUID structure (optional).
103 *
104 * @return COM result indicator.
105 */
106HRESULT ProgressBase::protectedInit(AutoInitSpan &aAutoInitSpan,
107#if !defined(VBOX_COM_INPROC)
108 VirtualBox *aParent,
109#endif
110 IUnknown *aInitiator,
111 CBSTR aDescription,
112 OUT_GUID aId /* = NULL */)
113{
114 /* Guarantees subclasses call this method at the proper time */
115 NOREF(aAutoInitSpan);
116
117 AutoCaller autoCaller(this);
118 AssertReturn(autoCaller.state() == InInit, E_FAIL);
119
120#if !defined(VBOX_COM_INPROC)
121 AssertReturn(aParent, E_INVALIDARG);
122#else
123 AssertReturn(aInitiator, E_INVALIDARG);
124#endif
125
126 AssertReturn(aDescription, E_INVALIDARG);
127
128#if !defined(VBOX_COM_INPROC)
129 /* share parent weakly */
130 unconst(mParent) = aParent;
131#endif
132
133#if !defined(VBOX_COM_INPROC)
134 /* assign (and therefore addref) initiator only if it is not VirtualBox
135 * (to avoid cycling); otherwise mInitiator will remain null which means
136 * that it is the same as the parent */
137 if (aInitiator)
138 {
139 ComObjPtr<VirtualBox> pVirtualBox(mParent);
140 if (!(pVirtualBox == aInitiator))
141 unconst(mInitiator) = aInitiator;
142 }
143#else
144 unconst(mInitiator) = aInitiator;
145#endif
146
147 unconst(mId).create();
148 if (aId)
149 mId.cloneTo(aId);
150
151#if !defined(VBOX_COM_INPROC)
152 /* add to the global collection of progress operations (note: after
153 * creating mId) */
154 mParent->addProgress(this);
155#endif
156
157 unconst(mDescription) = aDescription;
158
159 return S_OK;
160}
161
162/**
163 * Initializes the progress base object.
164 *
165 * This is a special initializer that doesn't initialize any field. Used by one
166 * of the Progress::init() forms to create sub-progress operations combined
167 * together using a CombinedProgress instance, so it doesn't require the parent,
168 * initiator, description and doesn't create an ID.
169 *
170 * Subclasses should call this or any other #protectedInit() method from their
171 * init() implementations.
172 *
173 * @param aAutoInitSpan AutoInitSpan object instantiated by a subclass.
174 */
175HRESULT ProgressBase::protectedInit(AutoInitSpan &aAutoInitSpan)
176{
177 /* Guarantees subclasses call this method at the proper time */
178 NOREF(aAutoInitSpan);
179
180 return S_OK;
181}
182
183/**
184 * Uninitializes the instance.
185 *
186 * Subclasses should call this from their uninit() implementations.
187 *
188 * @param aAutoUninitSpan AutoUninitSpan object instantiated by a subclass.
189 *
190 * @note Using the mParent member after this method returns is forbidden.
191 */
192void ProgressBase::protectedUninit(AutoUninitSpan &aAutoUninitSpan)
193{
194 /* release initiator (effective only if mInitiator has been assigned in
195 * init()) */
196 unconst(mInitiator).setNull();
197
198#if !defined(VBOX_COM_INPROC)
199 if (mParent)
200 {
201 /* remove the added progress on failure to complete the initialization */
202 if (aAutoUninitSpan.initFailed() && !mId.isEmpty())
203 mParent->removeProgress(mId.ref());
204
205 unconst(mParent) = NULL;
206 }
207#endif
208}
209
210// IProgress properties
211/////////////////////////////////////////////////////////////////////////////
212
213STDMETHODIMP ProgressBase::COMGETTER(Id)(BSTR *aId)
214{
215 CheckComArgOutPointerValid(aId);
216
217 AutoCaller autoCaller(this);
218 if (FAILED(autoCaller.rc())) return autoCaller.rc();
219
220 /* mId is constant during life time, no need to lock */
221 mId.toUtf16().cloneTo(aId);
222
223 return S_OK;
224}
225
226STDMETHODIMP ProgressBase::COMGETTER(Description)(BSTR *aDescription)
227{
228 CheckComArgOutPointerValid(aDescription);
229
230 AutoCaller autoCaller(this);
231 if (FAILED(autoCaller.rc())) return autoCaller.rc();
232
233 /* mDescription is constant during life time, no need to lock */
234 mDescription.cloneTo(aDescription);
235
236 return S_OK;
237}
238
239STDMETHODIMP ProgressBase::COMGETTER(Initiator)(IUnknown **aInitiator)
240{
241 CheckComArgOutPointerValid(aInitiator);
242
243 AutoCaller autoCaller(this);
244 if (FAILED(autoCaller.rc())) return autoCaller.rc();
245
246 /* mInitiator/mParent are constant during life time, no need to lock */
247
248#if !defined(VBOX_COM_INPROC)
249 if (mInitiator)
250 mInitiator.queryInterfaceTo(aInitiator);
251 else
252 {
253 ComObjPtr<VirtualBox> pVirtualBox(mParent);
254 pVirtualBox.queryInterfaceTo(aInitiator);
255 }
256#else
257 mInitiator.queryInterfaceTo(aInitiator);
258#endif
259
260 return S_OK;
261}
262
263STDMETHODIMP ProgressBase::COMGETTER(Cancelable)(BOOL *aCancelable)
264{
265 CheckComArgOutPointerValid(aCancelable);
266
267 AutoCaller autoCaller(this);
268 if (FAILED(autoCaller.rc())) return autoCaller.rc();
269
270 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
271
272 *aCancelable = mCancelable;
273
274 return S_OK;
275}
276
277/**
278 * Internal helper to compute the total percent value based on the member values and
279 * returns it as a "double". This is used both by GetPercent (which returns it as a
280 * rounded ULONG) and GetTimeRemaining().
281 *
282 * Requires locking by the caller!
283 *
284 * @return fractional percentage as a double value.
285 */
286double ProgressBase::calcTotalPercent()
287{
288 // avoid division by zero
289 if (m_ulTotalOperationsWeight == 0)
290 return 0;
291
292 double dPercent = ( (double)m_ulOperationsCompletedWeight // weight of operations that have been completed
293 + ((double)m_ulOperationPercent * (double)m_ulCurrentOperationWeight / (double)100) // plus partial weight of the current operation
294 ) * (double)100 / (double)m_ulTotalOperationsWeight;
295
296 return dPercent;
297}
298
299/**
300 * Internal helper for automatically timing out the operation.
301 *
302 * The caller should hold the object write lock.
303 */
304void ProgressBase::checkForAutomaticTimeout(void)
305{
306 if ( m_cMsTimeout
307 && mCancelable
308 && !mCanceled
309 && RTTimeMilliTS() - m_ullTimestamp > m_cMsTimeout
310 )
311 Cancel();
312}
313
314
315STDMETHODIMP ProgressBase::COMGETTER(TimeRemaining)(LONG *aTimeRemaining)
316{
317 CheckComArgOutPointerValid(aTimeRemaining);
318
319 AutoCaller autoCaller(this);
320 if (FAILED(autoCaller.rc())) return autoCaller.rc();
321
322 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
323
324 if (mCompleted)
325 *aTimeRemaining = 0;
326 else
327 {
328 double dPercentDone = calcTotalPercent();
329 if (dPercentDone < 1)
330 *aTimeRemaining = -1; // unreliable, or avoid division by 0 below
331 else
332 {
333 uint64_t ullTimeNow = RTTimeMilliTS();
334 uint64_t ullTimeElapsed = ullTimeNow - m_ullTimestamp;
335 uint64_t ullTimeTotal = (uint64_t)(ullTimeElapsed * 100 / dPercentDone);
336 uint64_t ullTimeRemaining = ullTimeTotal - ullTimeElapsed;
337
338// Log(("ProgressBase::GetTimeRemaining: dPercentDone %RI32, ullTimeNow = %RI64, ullTimeElapsed = %RI64, ullTimeTotal = %RI64, ullTimeRemaining = %RI64\n",
339// (uint32_t)dPercentDone, ullTimeNow, ullTimeElapsed, ullTimeTotal, ullTimeRemaining));
340
341 *aTimeRemaining = (LONG)(ullTimeRemaining / 1000);
342 }
343 }
344
345 return S_OK;
346}
347
348STDMETHODIMP ProgressBase::COMGETTER(Percent)(ULONG *aPercent)
349{
350 CheckComArgOutPointerValid(aPercent);
351
352 AutoCaller autoCaller(this);
353 if (FAILED(autoCaller.rc())) return autoCaller.rc();
354
355 checkForAutomaticTimeout();
356
357 /* checkForAutomaticTimeout requires a write lock. */
358 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
359
360 if (mCompleted && SUCCEEDED(mResultCode))
361 *aPercent = 100;
362 else
363 {
364 ULONG ulPercent = (ULONG)calcTotalPercent();
365 // do not report 100% until we're really really done with everything as the Qt GUI dismisses progress dialogs in that case
366 if ( ulPercent == 100
367 && ( m_ulOperationPercent < 100
368 || (m_ulCurrentOperation < m_cOperations -1)
369 )
370 )
371 *aPercent = 99;
372 else
373 *aPercent = ulPercent;
374 }
375
376 checkForAutomaticTimeout();
377
378 return S_OK;
379}
380
381STDMETHODIMP ProgressBase::COMGETTER(Completed)(BOOL *aCompleted)
382{
383 CheckComArgOutPointerValid(aCompleted);
384
385 AutoCaller autoCaller(this);
386 if (FAILED(autoCaller.rc())) return autoCaller.rc();
387
388 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
389
390 *aCompleted = mCompleted;
391
392 return S_OK;
393}
394
395STDMETHODIMP ProgressBase::COMGETTER(Canceled)(BOOL *aCanceled)
396{
397 CheckComArgOutPointerValid(aCanceled);
398
399 AutoCaller autoCaller(this);
400 if (FAILED(autoCaller.rc())) return autoCaller.rc();
401
402 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
403
404 *aCanceled = mCanceled;
405
406 return S_OK;
407}
408
409STDMETHODIMP ProgressBase::COMGETTER(ResultCode)(LONG *aResultCode)
410{
411 CheckComArgOutPointerValid(aResultCode);
412
413 AutoCaller autoCaller(this);
414 if (FAILED(autoCaller.rc())) return autoCaller.rc();
415
416 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
417
418 if (!mCompleted)
419 return setError(E_FAIL,
420 tr("Result code is not available, operation is still in progress"));
421
422 *aResultCode = mResultCode;
423
424 return S_OK;
425}
426
427STDMETHODIMP ProgressBase::COMGETTER(ErrorInfo)(IVirtualBoxErrorInfo **aErrorInfo)
428{
429 CheckComArgOutPointerValid(aErrorInfo);
430
431 AutoCaller autoCaller(this);
432 if (FAILED(autoCaller.rc())) return autoCaller.rc();
433
434 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
435
436 if (!mCompleted)
437 return setError(E_FAIL,
438 tr("Error info is not available, operation is still in progress"));
439
440 mErrorInfo.queryInterfaceTo(aErrorInfo);
441
442 return S_OK;
443}
444
445STDMETHODIMP ProgressBase::COMGETTER(OperationCount)(ULONG *aOperationCount)
446{
447 CheckComArgOutPointerValid(aOperationCount);
448
449 AutoCaller autoCaller(this);
450 if (FAILED(autoCaller.rc())) return autoCaller.rc();
451
452 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
453
454 *aOperationCount = m_cOperations;
455
456 return S_OK;
457}
458
459STDMETHODIMP ProgressBase::COMGETTER(Operation)(ULONG *aOperation)
460{
461 CheckComArgOutPointerValid(aOperation);
462
463 AutoCaller autoCaller(this);
464 if (FAILED(autoCaller.rc())) return autoCaller.rc();
465
466 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
467
468 *aOperation = m_ulCurrentOperation;
469
470 return S_OK;
471}
472
473STDMETHODIMP ProgressBase::COMGETTER(OperationDescription)(BSTR *aOperationDescription)
474{
475 CheckComArgOutPointerValid(aOperationDescription);
476
477 AutoCaller autoCaller(this);
478 if (FAILED(autoCaller.rc())) return autoCaller.rc();
479
480 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
481
482 m_bstrOperationDescription.cloneTo(aOperationDescription);
483
484 return S_OK;
485}
486
487STDMETHODIMP ProgressBase::COMGETTER(OperationPercent)(ULONG *aOperationPercent)
488{
489 CheckComArgOutPointerValid(aOperationPercent);
490
491 AutoCaller autoCaller(this);
492 if (FAILED(autoCaller.rc())) return autoCaller.rc();
493
494 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
495
496 if (mCompleted && SUCCEEDED(mResultCode))
497 *aOperationPercent = 100;
498 else
499 *aOperationPercent = m_ulOperationPercent;
500
501 return S_OK;
502}
503
504STDMETHODIMP ProgressBase::COMGETTER(OperationWeight)(ULONG *aOperationWeight)
505{
506 CheckComArgOutPointerValid(aOperationWeight);
507
508 AutoCaller autoCaller(this);
509 if (FAILED(autoCaller.rc())) return autoCaller.rc();
510
511 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
512
513 *aOperationWeight = m_ulCurrentOperationWeight;
514
515 return S_OK;
516}
517
518STDMETHODIMP ProgressBase::COMSETTER(Timeout)(ULONG aTimeout)
519{
520 AutoCaller autoCaller(this);
521 if (FAILED(autoCaller.rc())) return autoCaller.rc();
522
523 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
524
525 if (!mCancelable)
526 return setError(VBOX_E_INVALID_OBJECT_STATE,
527 tr("Operation cannot be canceled"));
528
529 LogThisFunc(("%#x => %#x\n", m_cMsTimeout, aTimeout));
530 m_cMsTimeout = aTimeout;
531 return S_OK;
532}
533
534STDMETHODIMP ProgressBase::COMGETTER(Timeout)(ULONG *aTimeout)
535{
536 CheckComArgOutPointerValid(aTimeout);
537
538 AutoCaller autoCaller(this);
539 if (FAILED(autoCaller.rc())) return autoCaller.rc();
540
541 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
542
543 *aTimeout = m_cMsTimeout;
544 return S_OK;
545}
546
547// public methods only for internal purposes
548////////////////////////////////////////////////////////////////////////////////
549
550/**
551 * Sets the cancelation callback, checking for cancelation first.
552 *
553 * @returns Success indicator.
554 * @retval true on success.
555 * @retval false if the progress object has already been canceled or is in an
556 * invalid state
557 *
558 * @param pfnCallback The function to be called upon cancelation.
559 * @param pvUser The callback argument.
560 */
561bool ProgressBase::setCancelCallback(void (*pfnCallback)(void *), void *pvUser)
562{
563 AutoCaller autoCaller(this);
564 AssertComRCReturn(autoCaller.rc(), false);
565
566 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
567
568 checkForAutomaticTimeout();
569 if (mCanceled)
570 return false;
571
572 m_pvCancelUserArg = pvUser;
573 m_pfnCancelCallback = pfnCallback;
574 return true;
575}
576
577////////////////////////////////////////////////////////////////////////////////
578// Progress class
579////////////////////////////////////////////////////////////////////////////////
580
581HRESULT Progress::FinalConstruct()
582{
583 HRESULT rc = ProgressBase::FinalConstruct();
584 if (FAILED(rc)) return rc;
585
586 mCompletedSem = NIL_RTSEMEVENTMULTI;
587 mWaitersCount = 0;
588
589 return S_OK;
590}
591
592void Progress::FinalRelease()
593{
594 uninit();
595 BaseFinalRelease();
596}
597
598// public initializer/uninitializer for internal purposes only
599////////////////////////////////////////////////////////////////////////////////
600
601/**
602 * Initializes the normal progress object. With this variant, one can have
603 * an arbitrary number of sub-operation which IProgress can analyze to
604 * have a weighted progress computed.
605 *
606 * For example, say that one IProgress is supposed to track the cloning
607 * of two hard disk images, which are 100 MB and 1000 MB in size, respectively,
608 * and each of these hard disks should be one sub-operation of the IProgress.
609 *
610 * Obviously the progress would be misleading if the progress displayed 50%
611 * after the smaller image was cloned and would then take much longer for
612 * the second half.
613 *
614 * With weighted progress, one can invoke the following calls:
615 *
616 * 1) create progress object with cOperations = 2 and ulTotalOperationsWeight =
617 * 1100 (100 MB plus 1100, but really the weights can be any ULONG); pass
618 * in ulFirstOperationWeight = 100 for the first sub-operation
619 *
620 * 2) Then keep calling setCurrentOperationProgress() with a percentage
621 * for the first image; the total progress will increase up to a value
622 * of 9% (100MB / 1100MB * 100%).
623 *
624 * 3) Then call setNextOperation with the second weight (1000 for the megabytes
625 * of the second disk).
626 *
627 * 4) Then keep calling setCurrentOperationProgress() with a percentage for
628 * the second image, where 100% of the operation will then yield a 100%
629 * progress of the entire task.
630 *
631 * Weighting is optional; you can simply assign a weight of 1 to each operation
632 * and pass ulTotalOperationsWeight == cOperations to this constructor (but
633 * for that variant and for backwards-compatibility a simpler constructor exists
634 * in ProgressImpl.h as well).
635 *
636 * Even simpler, if you need no sub-operations at all, pass in cOperations =
637 * ulTotalOperationsWeight = ulFirstOperationWeight = 1.
638 *
639 * @param aParent See ProgressBase::init().
640 * @param aInitiator See ProgressBase::init().
641 * @param aDescription See ProgressBase::init().
642 * @param aCancelable Flag whether the task maybe canceled.
643 * @param cOperations Number of operations within this task (at least 1).
644 * @param ulTotalOperationsWeight Total weight of operations; must be the sum of ulFirstOperationWeight and
645 * what is later passed with each subsequent setNextOperation() call.
646 * @param bstrFirstOperationDescription Description of the first operation.
647 * @param ulFirstOperationWeight Weight of first sub-operation.
648 * @param aId See ProgressBase::init().
649 */
650HRESULT Progress::init(
651#if !defined(VBOX_COM_INPROC)
652 VirtualBox *aParent,
653#endif
654 IUnknown *aInitiator,
655 CBSTR aDescription,
656 BOOL aCancelable,
657 ULONG cOperations,
658 ULONG ulTotalOperationsWeight,
659 CBSTR bstrFirstOperationDescription,
660 ULONG ulFirstOperationWeight,
661 OUT_GUID aId /* = NULL */)
662{
663 LogFlowThisFunc(("aDescription=\"%ls\", cOperations=%d, ulTotalOperationsWeight=%d, bstrFirstOperationDescription=\"%ls\", ulFirstOperationWeight=%d\n",
664 aDescription,
665 cOperations,
666 ulTotalOperationsWeight,
667 bstrFirstOperationDescription,
668 ulFirstOperationWeight));
669
670 AssertReturn(bstrFirstOperationDescription, E_INVALIDARG);
671 AssertReturn(ulTotalOperationsWeight >= 1, E_INVALIDARG);
672
673 /* Enclose the state transition NotReady->InInit->Ready */
674 AutoInitSpan autoInitSpan(this);
675 AssertReturn(autoInitSpan.isOk(), E_FAIL);
676
677 HRESULT rc = S_OK;
678
679 rc = ProgressBase::protectedInit(autoInitSpan,
680#if !defined(VBOX_COM_INPROC)
681 aParent,
682#endif
683 aInitiator, aDescription, aId);
684 if (FAILED(rc)) return rc;
685
686 mCancelable = aCancelable;
687
688 m_cOperations = cOperations;
689 m_ulTotalOperationsWeight = ulTotalOperationsWeight;
690 m_ulOperationsCompletedWeight = 0;
691 m_ulCurrentOperation = 0;
692 m_bstrOperationDescription = bstrFirstOperationDescription;
693 m_ulCurrentOperationWeight = ulFirstOperationWeight;
694 m_ulOperationPercent = 0;
695
696 int vrc = RTSemEventMultiCreate(&mCompletedSem);
697 ComAssertRCRet(vrc, E_FAIL);
698
699 RTSemEventMultiReset(mCompletedSem);
700
701 /* Confirm a successful initialization when it's the case */
702 if (SUCCEEDED(rc))
703 autoInitSpan.setSucceeded();
704
705 return rc;
706}
707
708/**
709 * Initializes the sub-progress object that represents a specific operation of
710 * the whole task.
711 *
712 * Objects initialized with this method are then combined together into the
713 * single task using a CombinedProgress instance, so it doesn't require the
714 * parent, initiator, description and doesn't create an ID. Note that calling
715 * respective getter methods on an object initialized with this method is
716 * useless. Such objects are used only to provide a separate wait semaphore and
717 * store individual operation descriptions.
718 *
719 * @param aCancelable Flag whether the task maybe canceled.
720 * @param aOperationCount Number of sub-operations within this task (at least 1).
721 * @param aOperationDescription Description of the individual operation.
722 */
723HRESULT Progress::init(BOOL aCancelable,
724 ULONG aOperationCount,
725 CBSTR aOperationDescription)
726{
727 LogFlowThisFunc(("aOperationDescription=\"%ls\"\n", aOperationDescription));
728
729 /* Enclose the state transition NotReady->InInit->Ready */
730 AutoInitSpan autoInitSpan(this);
731 AssertReturn(autoInitSpan.isOk(), E_FAIL);
732
733 HRESULT rc = S_OK;
734
735 rc = ProgressBase::protectedInit(autoInitSpan);
736 if (FAILED(rc)) return rc;
737
738 mCancelable = aCancelable;
739
740 // for this variant we assume for now that all operations are weighed "1"
741 // and equal total weight = operation count
742 m_cOperations = aOperationCount;
743 m_ulTotalOperationsWeight = aOperationCount;
744 m_ulOperationsCompletedWeight = 0;
745 m_ulCurrentOperation = 0;
746 m_bstrOperationDescription = aOperationDescription;
747 m_ulCurrentOperationWeight = 1;
748 m_ulOperationPercent = 0;
749
750 int vrc = RTSemEventMultiCreate(&mCompletedSem);
751 ComAssertRCRet(vrc, E_FAIL);
752
753 RTSemEventMultiReset(mCompletedSem);
754
755 /* Confirm a successful initialization when it's the case */
756 if (SUCCEEDED(rc))
757 autoInitSpan.setSucceeded();
758
759 return rc;
760}
761
762/**
763 * Uninitializes the instance and sets the ready flag to FALSE.
764 *
765 * Called either from FinalRelease() or by the parent when it gets destroyed.
766 */
767void Progress::uninit()
768{
769 LogFlowThisFunc(("\n"));
770
771 /* Enclose the state transition Ready->InUninit->NotReady */
772 AutoUninitSpan autoUninitSpan(this);
773 if (autoUninitSpan.uninitDone())
774 return;
775
776 /* wake up all threads still waiting on occasion */
777 if (mWaitersCount > 0)
778 {
779 LogFlow(("WARNING: There are still %d threads waiting for '%ls' completion!\n",
780 mWaitersCount, mDescription.raw()));
781 RTSemEventMultiSignal(mCompletedSem);
782 }
783
784 RTSemEventMultiDestroy(mCompletedSem);
785
786 ProgressBase::protectedUninit(autoUninitSpan);
787}
788
789// IProgress properties
790/////////////////////////////////////////////////////////////////////////////
791
792// IProgress methods
793/////////////////////////////////////////////////////////////////////////////
794
795/**
796 * @note XPCOM: when this method is not called on the main XPCOM thread, it
797 * simply blocks the thread until mCompletedSem is signalled. If the
798 * thread has its own event queue (hmm, what for?) that it must run, then
799 * calling this method will definitely freeze event processing.
800 */
801STDMETHODIMP Progress::WaitForCompletion(LONG aTimeout)
802{
803 LogFlowThisFuncEnter();
804 LogFlowThisFunc(("aTimeout=%d\n", aTimeout));
805
806 AutoCaller autoCaller(this);
807 if (FAILED(autoCaller.rc())) return autoCaller.rc();
808
809 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
810
811 /* if we're already completed, take a shortcut */
812 if (!mCompleted)
813 {
814 int vrc = VINF_SUCCESS;
815 bool fForever = aTimeout < 0;
816 int64_t timeLeft = aTimeout;
817 int64_t lastTime = RTTimeMilliTS();
818
819 while (!mCompleted && (fForever || timeLeft > 0))
820 {
821 mWaitersCount++;
822 alock.release();
823 vrc = RTSemEventMultiWait(mCompletedSem,
824 fForever ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)timeLeft);
825 alock.acquire();
826 mWaitersCount--;
827
828 /* the last waiter resets the semaphore */
829 if (mWaitersCount == 0)
830 RTSemEventMultiReset(mCompletedSem);
831
832 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
833 break;
834
835 if (!fForever)
836 {
837 int64_t now = RTTimeMilliTS();
838 timeLeft -= now - lastTime;
839 lastTime = now;
840 }
841 }
842
843 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
844 return setError(VBOX_E_IPRT_ERROR,
845 tr("Failed to wait for the task completion (%Rrc)"),
846 vrc);
847 }
848
849 LogFlowThisFuncLeave();
850
851 return S_OK;
852}
853
854/**
855 * @note XPCOM: when this method is not called on the main XPCOM thread, it
856 * simply blocks the thread until mCompletedSem is signalled. If the
857 * thread has its own event queue (hmm, what for?) that it must run, then
858 * calling this method will definitely freeze event processing.
859 */
860STDMETHODIMP Progress::WaitForOperationCompletion(ULONG aOperation, LONG aTimeout)
861{
862 LogFlowThisFuncEnter();
863 LogFlowThisFunc(("aOperation=%d, aTimeout=%d\n", aOperation, aTimeout));
864
865 AutoCaller autoCaller(this);
866 if (FAILED(autoCaller.rc())) return autoCaller.rc();
867
868 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
869
870 CheckComArgExpr(aOperation, aOperation < m_cOperations);
871
872 /* if we're already completed or if the given operation is already done,
873 * then take a shortcut */
874 if ( !mCompleted
875 && aOperation >= m_ulCurrentOperation)
876 {
877 int vrc = VINF_SUCCESS;
878 bool fForever = aTimeout < 0;
879 int64_t timeLeft = aTimeout;
880 int64_t lastTime = RTTimeMilliTS();
881
882 while ( !mCompleted && aOperation >= m_ulCurrentOperation
883 && (fForever || timeLeft > 0))
884 {
885 mWaitersCount ++;
886 alock.release();
887 vrc = RTSemEventMultiWait(mCompletedSem,
888 fForever ? RT_INDEFINITE_WAIT : (unsigned) timeLeft);
889 alock.acquire();
890 mWaitersCount--;
891
892 /* the last waiter resets the semaphore */
893 if (mWaitersCount == 0)
894 RTSemEventMultiReset(mCompletedSem);
895
896 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
897 break;
898
899 if (!fForever)
900 {
901 int64_t now = RTTimeMilliTS();
902 timeLeft -= now - lastTime;
903 lastTime = now;
904 }
905 }
906
907 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
908 return setError(E_FAIL,
909 tr("Failed to wait for the operation completion (%Rrc)"),
910 vrc);
911 }
912
913 LogFlowThisFuncLeave();
914
915 return S_OK;
916}
917
918STDMETHODIMP Progress::WaitForAsyncProgressCompletion(IProgress *pProgressAsync)
919{
920 LogFlowThisFuncEnter();
921
922 CheckComArgNotNull(pProgressAsync);
923
924 AutoCaller autoCaller(this);
925 if (FAILED(autoCaller.rc())) return autoCaller.rc();
926
927 /* Note: we don't lock here, cause we just using public methods. */
928
929 HRESULT rc = S_OK;
930 BOOL fCancelable = FALSE;
931 BOOL fCompleted = FALSE;
932 BOOL fCanceled = FALSE;
933 ULONG prevPercent = UINT32_MAX;
934 ULONG currentPercent = 0;
935 ULONG cOp = 0;
936 /* Is the async process cancelable? */
937 rc = pProgressAsync->COMGETTER(Cancelable)(&fCancelable);
938 if (FAILED(rc)) return rc;
939 /* Loop as long as the sync process isn't completed. */
940 while (SUCCEEDED(pProgressAsync->COMGETTER(Completed(&fCompleted))))
941 {
942 /* We can forward any cancel request to the async process only when
943 * it is cancelable. */
944 if (fCancelable)
945 {
946 rc = COMGETTER(Canceled)(&fCanceled);
947 if (FAILED(rc)) return rc;
948 if (fCanceled)
949 {
950 rc = pProgressAsync->Cancel();
951 if (FAILED(rc)) return rc;
952 }
953 }
954 /* Even if the user canceled the process, we have to wait until the
955 async task has finished his work (cleanup and such). Otherwise there
956 will be sync trouble (still wrong state, dead locks, ...) on the
957 used objects. So just do nothing, but wait for the complete
958 notification. */
959 if (!fCanceled)
960 {
961 /* Check if the current operation has changed. It is also possible that
962 * in the meantime more than one async operation was finished. So we
963 * have to loop as long as we reached the same operation count. */
964 ULONG curOp;
965 for(;;)
966 {
967 rc = pProgressAsync->COMGETTER(Operation(&curOp));
968 if (FAILED(rc)) return rc;
969 if (cOp != curOp)
970 {
971 Bstr bstr;
972 ULONG currentWeight;
973 rc = pProgressAsync->COMGETTER(OperationDescription(bstr.asOutParam()));
974 if (FAILED(rc)) return rc;
975 rc = pProgressAsync->COMGETTER(OperationWeight(&currentWeight));
976 if (FAILED(rc)) return rc;
977 rc = SetNextOperation(bstr.raw(), currentWeight);
978 if (FAILED(rc)) return rc;
979 ++cOp;
980 }
981 else
982 break;
983 }
984
985 rc = pProgressAsync->COMGETTER(OperationPercent(&currentPercent));
986 if (FAILED(rc)) return rc;
987 if (currentPercent != prevPercent)
988 {
989 prevPercent = currentPercent;
990 rc = SetCurrentOperationProgress(currentPercent);
991 if (FAILED(rc)) return rc;
992 }
993 }
994 if (fCompleted)
995 break;
996
997 /* Make sure the loop is not too tight */
998 rc = pProgressAsync->WaitForCompletion(100);
999 if (FAILED(rc)) return rc;
1000 }
1001
1002 LogFlowThisFuncLeave();
1003
1004 return rc;
1005}
1006
1007STDMETHODIMP Progress::Cancel()
1008{
1009 AutoCaller autoCaller(this);
1010 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1011
1012 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1013
1014 if (!mCancelable)
1015 return setError(VBOX_E_INVALID_OBJECT_STATE,
1016 tr("Operation cannot be canceled"));
1017
1018 if (!mCanceled)
1019 {
1020 LogThisFunc(("Canceling\n"));
1021 mCanceled = TRUE;
1022 if (m_pfnCancelCallback)
1023 m_pfnCancelCallback(m_pvCancelUserArg);
1024
1025 }
1026 else
1027 LogThisFunc(("Already canceled\n"));
1028
1029 return S_OK;
1030}
1031
1032/**
1033 * Updates the percentage value of the current operation.
1034 *
1035 * @param aPercent New percentage value of the operation in progress
1036 * (in range [0, 100]).
1037 */
1038STDMETHODIMP Progress::SetCurrentOperationProgress(ULONG aPercent)
1039{
1040 AutoCaller autoCaller(this);
1041 AssertComRCReturnRC(autoCaller.rc());
1042
1043 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1044
1045 AssertMsgReturn(aPercent <= 100, ("%u\n", aPercent), E_INVALIDARG);
1046
1047 checkForAutomaticTimeout();
1048 if (mCancelable && mCanceled)
1049 {
1050 Assert(!mCompleted);
1051 return E_FAIL;
1052 }
1053 AssertReturn(!mCompleted && !mCanceled, E_FAIL);
1054
1055 m_ulOperationPercent = aPercent;
1056
1057 return S_OK;
1058}
1059
1060/**
1061 * Signals that the current operation is successfully completed and advances to
1062 * the next operation. The operation percentage is reset to 0.
1063 *
1064 * @param aOperationDescription Description of the next operation.
1065 *
1066 * @note The current operation must not be the last one.
1067 */
1068STDMETHODIMP Progress::SetNextOperation(IN_BSTR bstrNextOperationDescription, ULONG ulNextOperationsWeight)
1069{
1070 AssertReturn(bstrNextOperationDescription, E_INVALIDARG);
1071
1072 AutoCaller autoCaller(this);
1073 AssertComRCReturnRC(autoCaller.rc());
1074
1075 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1076
1077 if (mCanceled)
1078 return E_FAIL;
1079 AssertReturn(!mCompleted, E_FAIL);
1080 AssertReturn(m_ulCurrentOperation + 1 < m_cOperations, E_FAIL);
1081
1082 ++m_ulCurrentOperation;
1083 m_ulOperationsCompletedWeight += m_ulCurrentOperationWeight;
1084
1085 m_bstrOperationDescription = bstrNextOperationDescription;
1086 m_ulCurrentOperationWeight = ulNextOperationsWeight;
1087 m_ulOperationPercent = 0;
1088
1089 Log(("Progress::setNextOperation(%ls): ulNextOperationsWeight = %d; m_ulCurrentOperation is now %d, m_ulOperationsCompletedWeight is now %d\n",
1090 m_bstrOperationDescription.raw(), ulNextOperationsWeight, m_ulCurrentOperation, m_ulOperationsCompletedWeight));
1091
1092 /* wake up all waiting threads */
1093 if (mWaitersCount > 0)
1094 RTSemEventMultiSignal(mCompletedSem);
1095
1096 return S_OK;
1097}
1098
1099// public methods only for internal purposes
1100/////////////////////////////////////////////////////////////////////////////
1101
1102/**
1103 * Sets the internal result code and attempts to retrieve additional error
1104 * info from the current thread. Gets called from Progress::notifyComplete(),
1105 * but can be called again to override a previous result set with
1106 * notifyComplete().
1107 *
1108 * @param aResultCode
1109 */
1110HRESULT Progress::setResultCode(HRESULT aResultCode)
1111{
1112 AutoCaller autoCaller(this);
1113 AssertComRCReturnRC(autoCaller.rc());
1114
1115 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1116
1117 mResultCode = aResultCode;
1118
1119 HRESULT rc = S_OK;
1120
1121 if (FAILED(aResultCode))
1122 {
1123 /* try to import error info from the current thread */
1124
1125#if !defined(VBOX_WITH_XPCOM)
1126
1127 ComPtr<IErrorInfo> err;
1128 rc = ::GetErrorInfo(0, err.asOutParam());
1129 if (rc == S_OK && err)
1130 {
1131 rc = err.queryInterfaceTo(mErrorInfo.asOutParam());
1132 if (SUCCEEDED(rc) && !mErrorInfo)
1133 rc = E_FAIL;
1134 }
1135
1136#else /* !defined(VBOX_WITH_XPCOM) */
1137
1138 nsCOMPtr<nsIExceptionService> es;
1139 es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &rc);
1140 if (NS_SUCCEEDED(rc))
1141 {
1142 nsCOMPtr <nsIExceptionManager> em;
1143 rc = es->GetCurrentExceptionManager(getter_AddRefs(em));
1144 if (NS_SUCCEEDED(rc))
1145 {
1146 ComPtr<nsIException> ex;
1147 rc = em->GetCurrentException(ex.asOutParam());
1148 if (NS_SUCCEEDED(rc) && ex)
1149 {
1150 rc = ex.queryInterfaceTo(mErrorInfo.asOutParam());
1151 if (NS_SUCCEEDED(rc) && !mErrorInfo)
1152 rc = E_FAIL;
1153 }
1154 }
1155 }
1156#endif /* !defined(VBOX_WITH_XPCOM) */
1157
1158 AssertMsg(rc == S_OK, ("Couldn't get error info (rc=%08X) while trying to set a failed result (%08X)!\n",
1159 rc, aResultCode));
1160 }
1161
1162 return rc;
1163}
1164
1165/**
1166 * Marks the whole task as complete and sets the result code.
1167 *
1168 * If the result code indicates a failure (|FAILED(@a aResultCode)|) then this
1169 * method will import the error info from the current thread and assign it to
1170 * the errorInfo attribute (it will return an error if no info is available in
1171 * such case).
1172 *
1173 * If the result code indicates a success (|SUCCEEDED(@a aResultCode)|) then
1174 * the current operation is set to the last.
1175 *
1176 * Note that this method may be called only once for the given Progress object.
1177 * Subsequent calls will assert.
1178 *
1179 * @param aResultCode Operation result code.
1180 */
1181HRESULT Progress::notifyComplete(HRESULT aResultCode)
1182{
1183 AutoCaller autoCaller(this);
1184 AssertComRCReturnRC(autoCaller.rc());
1185
1186 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1187
1188 AssertReturn(mCompleted == FALSE, E_FAIL);
1189
1190 LogFunc(("aResultCode=%d\n", aResultCode));
1191
1192 if (mCanceled && SUCCEEDED(aResultCode))
1193 aResultCode = E_FAIL;
1194
1195 HRESULT rc = setResultCode(aResultCode);
1196
1197 mCompleted = TRUE;
1198
1199 if (!FAILED(aResultCode))
1200 {
1201 m_ulCurrentOperation = m_cOperations - 1; /* last operation */
1202 m_ulOperationPercent = 100;
1203 }
1204
1205#if !defined VBOX_COM_INPROC
1206 /* remove from the global collection of pending progress operations */
1207 if (mParent)
1208 mParent->removeProgress(mId.ref());
1209#endif
1210
1211 /* wake up all waiting threads */
1212 if (mWaitersCount > 0)
1213 RTSemEventMultiSignal(mCompletedSem);
1214
1215 return rc;
1216}
1217
1218/**
1219 * Wrapper around Progress:notifyCompleteV.
1220 */
1221HRESULT Progress::notifyComplete(HRESULT aResultCode,
1222 const GUID &aIID,
1223 const char *pcszComponent,
1224 const char *aText,
1225 ...)
1226{
1227 va_list va;
1228 va_start(va, aText);
1229 HRESULT hrc = notifyCompleteV(aResultCode, aIID, pcszComponent, aText, va);
1230 va_end(va);
1231 return hrc;
1232}
1233
1234/**
1235 * Marks the operation as complete and attaches full error info.
1236 *
1237 * See com::SupportErrorInfoImpl::setError(HRESULT, const GUID &, const wchar_t
1238 * *, const char *, ...) for more info.
1239 *
1240 * @param aResultCode Operation result (error) code, must not be S_OK.
1241 * @param aIID IID of the interface that defines the error.
1242 * @param aComponent Name of the component that generates the error.
1243 * @param aText Error message (must not be null), an RTStrPrintf-like
1244 * format string in UTF-8 encoding.
1245 * @param va List of arguments for the format string.
1246 */
1247HRESULT Progress::notifyCompleteV(HRESULT aResultCode,
1248 const GUID &aIID,
1249 const char *pcszComponent,
1250 const char *aText,
1251 va_list va)
1252{
1253 Utf8Str text(aText, va);
1254
1255 AutoCaller autoCaller(this);
1256 AssertComRCReturnRC(autoCaller.rc());
1257
1258 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1259
1260 AssertReturn(mCompleted == FALSE, E_FAIL);
1261
1262 if (mCanceled && SUCCEEDED(aResultCode))
1263 aResultCode = E_FAIL;
1264
1265 mCompleted = TRUE;
1266 mResultCode = aResultCode;
1267
1268 AssertReturn(FAILED(aResultCode), E_FAIL);
1269
1270 ComObjPtr<VirtualBoxErrorInfo> errorInfo;
1271 HRESULT rc = errorInfo.createObject();
1272 AssertComRC(rc);
1273 if (SUCCEEDED(rc))
1274 {
1275 errorInfo->init(aResultCode, aIID, pcszComponent, text);
1276 errorInfo.queryInterfaceTo(mErrorInfo.asOutParam());
1277 }
1278
1279#if !defined VBOX_COM_INPROC
1280 /* remove from the global collection of pending progress operations */
1281 if (mParent)
1282 mParent->removeProgress(mId.ref());
1283#endif
1284
1285 /* wake up all waiting threads */
1286 if (mWaitersCount > 0)
1287 RTSemEventMultiSignal(mCompletedSem);
1288
1289 return rc;
1290}
1291
1292/**
1293 * Notify the progress object that we're almost at the point of no return.
1294 *
1295 * This atomically checks for and disables cancelation. Calls to
1296 * IProgress::Cancel() made after a successful call to this method will fail
1297 * and the user can be told. While this isn't entirely clean behavior, it
1298 * prevents issues with an irreversible actually operation succeeding while the
1299 * user believe it was rolled back.
1300 *
1301 * @returns Success indicator.
1302 * @retval true on success.
1303 * @retval false if the progress object has already been canceled or is in an
1304 * invalid state
1305 */
1306bool Progress::notifyPointOfNoReturn(void)
1307{
1308 AutoCaller autoCaller(this);
1309 AssertComRCReturn(autoCaller.rc(), false);
1310
1311 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1312
1313 if (mCanceled)
1314 {
1315 LogThisFunc(("returns false\n"));
1316 return false;
1317 }
1318
1319 mCancelable = FALSE;
1320 LogThisFunc(("returns true\n"));
1321 return true;
1322}
1323
1324////////////////////////////////////////////////////////////////////////////////
1325// CombinedProgress class
1326////////////////////////////////////////////////////////////////////////////////
1327
1328HRESULT CombinedProgress::FinalConstruct()
1329{
1330 HRESULT rc = ProgressBase::FinalConstruct();
1331 if (FAILED(rc)) return rc;
1332
1333 mProgress = 0;
1334 mCompletedOperations = 0;
1335
1336 return BaseFinalConstruct();
1337}
1338
1339void CombinedProgress::FinalRelease()
1340{
1341 uninit();
1342 BaseFinalRelease();
1343}
1344
1345// public initializer/uninitializer for internal purposes only
1346////////////////////////////////////////////////////////////////////////////////
1347
1348/**
1349 * Initializes this object based on individual combined progresses.
1350 * Must be called only from #init()!
1351 *
1352 * @param aAutoInitSpan AutoInitSpan object instantiated by a subclass.
1353 * @param aParent See ProgressBase::init().
1354 * @param aInitiator See ProgressBase::init().
1355 * @param aDescription See ProgressBase::init().
1356 * @param aId See ProgressBase::init().
1357 */
1358HRESULT CombinedProgress::protectedInit(AutoInitSpan &aAutoInitSpan,
1359#if !defined(VBOX_COM_INPROC)
1360 VirtualBox *aParent,
1361#endif
1362 IUnknown *aInitiator,
1363 CBSTR aDescription, OUT_GUID aId)
1364{
1365 LogFlowThisFunc(("aDescription={%ls} mProgresses.size()=%d\n",
1366 aDescription, mProgresses.size()));
1367
1368 HRESULT rc = S_OK;
1369
1370 rc = ProgressBase::protectedInit(aAutoInitSpan,
1371#if !defined(VBOX_COM_INPROC)
1372 aParent,
1373#endif
1374 aInitiator, aDescription, aId);
1375 if (FAILED(rc)) return rc;
1376
1377 mProgress = 0; /* the first object */
1378 mCompletedOperations = 0;
1379
1380 mCompleted = FALSE;
1381 mCancelable = TRUE; /* until any progress returns FALSE */
1382 mCanceled = FALSE;
1383
1384 m_cOperations = 0; /* will be calculated later */
1385
1386 m_ulCurrentOperation = 0;
1387 rc = mProgresses[0]->COMGETTER(OperationDescription)(m_bstrOperationDescription.asOutParam());
1388 if (FAILED(rc)) return rc;
1389
1390 for (size_t i = 0; i < mProgresses.size(); i ++)
1391 {
1392 if (mCancelable)
1393 {
1394 BOOL cancelable = FALSE;
1395 rc = mProgresses[i]->COMGETTER(Cancelable)(&cancelable);
1396 if (FAILED(rc)) return rc;
1397
1398 if (!cancelable)
1399 mCancelable = FALSE;
1400 }
1401
1402 {
1403 ULONG opCount = 0;
1404 rc = mProgresses[i]->COMGETTER(OperationCount)(&opCount);
1405 if (FAILED(rc)) return rc;
1406
1407 m_cOperations += opCount;
1408 }
1409 }
1410
1411 rc = checkProgress();
1412 if (FAILED(rc)) return rc;
1413
1414 return rc;
1415}
1416
1417/**
1418 * Initializes the combined progress object given two normal progress
1419 * objects.
1420 *
1421 * @param aParent See ProgressBase::init().
1422 * @param aInitiator See ProgressBase::init().
1423 * @param aDescription See ProgressBase::init().
1424 * @param aProgress1 First normal progress object.
1425 * @param aProgress2 Second normal progress object.
1426 * @param aId See ProgressBase::init().
1427 */
1428HRESULT CombinedProgress::init(
1429#if !defined(VBOX_COM_INPROC)
1430 VirtualBox *aParent,
1431#endif
1432 IUnknown *aInitiator,
1433 CBSTR aDescription,
1434 IProgress *aProgress1,
1435 IProgress *aProgress2,
1436 OUT_GUID aId /* = NULL */)
1437{
1438 /* Enclose the state transition NotReady->InInit->Ready */
1439 AutoInitSpan autoInitSpan(this);
1440 AssertReturn(autoInitSpan.isOk(), E_FAIL);
1441
1442 mProgresses.resize(2);
1443 mProgresses[0] = aProgress1;
1444 mProgresses[1] = aProgress2;
1445
1446 HRESULT rc = protectedInit(autoInitSpan,
1447#if !defined(VBOX_COM_INPROC)
1448 aParent,
1449#endif
1450 aInitiator,
1451 aDescription,
1452 aId);
1453
1454 /* Confirm a successful initialization when it's the case */
1455 if (SUCCEEDED(rc))
1456 autoInitSpan.setSucceeded();
1457
1458 return rc;
1459}
1460
1461/**
1462 * Uninitializes the instance and sets the ready flag to FALSE.
1463 *
1464 * Called either from FinalRelease() or by the parent when it gets destroyed.
1465 */
1466void CombinedProgress::uninit()
1467{
1468 LogFlowThisFunc(("\n"));
1469
1470 /* Enclose the state transition Ready->InUninit->NotReady */
1471 AutoUninitSpan autoUninitSpan(this);
1472 if (autoUninitSpan.uninitDone())
1473 return;
1474
1475 mProgress = 0;
1476 mProgresses.clear();
1477
1478 ProgressBase::protectedUninit(autoUninitSpan);
1479}
1480
1481// IProgress properties
1482////////////////////////////////////////////////////////////////////////////////
1483
1484STDMETHODIMP CombinedProgress::COMGETTER(Percent)(ULONG *aPercent)
1485{
1486 CheckComArgOutPointerValid(aPercent);
1487
1488 AutoCaller autoCaller(this);
1489 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1490
1491 /* checkProgress needs a write lock */
1492 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1493
1494 if (mCompleted && SUCCEEDED(mResultCode))
1495 *aPercent = 100;
1496 else
1497 {
1498 HRESULT rc = checkProgress();
1499 if (FAILED(rc)) return rc;
1500
1501 /* global percent =
1502 * (100 / m_cOperations) * mOperation +
1503 * ((100 / m_cOperations) / 100) * m_ulOperationPercent */
1504 *aPercent = (100 * m_ulCurrentOperation + m_ulOperationPercent) / m_cOperations;
1505 }
1506
1507 return S_OK;
1508}
1509
1510STDMETHODIMP CombinedProgress::COMGETTER(Completed)(BOOL *aCompleted)
1511{
1512 CheckComArgOutPointerValid(aCompleted);
1513
1514 AutoCaller autoCaller(this);
1515 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1516
1517 /* checkProgress needs a write lock */
1518 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1519
1520 HRESULT rc = checkProgress();
1521 if (FAILED(rc)) return rc;
1522
1523 return ProgressBase::COMGETTER(Completed)(aCompleted);
1524}
1525
1526STDMETHODIMP CombinedProgress::COMGETTER(Canceled)(BOOL *aCanceled)
1527{
1528 CheckComArgOutPointerValid(aCanceled);
1529
1530 AutoCaller autoCaller(this);
1531 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1532
1533 /* checkProgress needs a write lock */
1534 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1535
1536 HRESULT rc = checkProgress();
1537 if (FAILED(rc)) return rc;
1538
1539 return ProgressBase::COMGETTER(Canceled)(aCanceled);
1540}
1541
1542STDMETHODIMP CombinedProgress::COMGETTER(ResultCode)(LONG *aResultCode)
1543{
1544 CheckComArgOutPointerValid(aResultCode);
1545
1546 AutoCaller autoCaller(this);
1547 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1548
1549 /* checkProgress needs a write lock */
1550 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1551
1552 HRESULT rc = checkProgress();
1553 if (FAILED(rc)) return rc;
1554
1555 return ProgressBase::COMGETTER(ResultCode)(aResultCode);
1556}
1557
1558STDMETHODIMP CombinedProgress::COMGETTER(ErrorInfo)(IVirtualBoxErrorInfo **aErrorInfo)
1559{
1560 CheckComArgOutPointerValid(aErrorInfo);
1561
1562 AutoCaller autoCaller(this);
1563 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1564
1565 /* checkProgress needs a write lock */
1566 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1567
1568 HRESULT rc = checkProgress();
1569 if (FAILED(rc)) return rc;
1570
1571 return ProgressBase::COMGETTER(ErrorInfo)(aErrorInfo);
1572}
1573
1574STDMETHODIMP CombinedProgress::COMGETTER(Operation)(ULONG *aOperation)
1575{
1576 CheckComArgOutPointerValid(aOperation);
1577
1578 AutoCaller autoCaller(this);
1579 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1580
1581 /* checkProgress needs a write lock */
1582 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1583
1584 HRESULT rc = checkProgress();
1585 if (FAILED(rc)) return rc;
1586
1587 return ProgressBase::COMGETTER(Operation)(aOperation);
1588}
1589
1590STDMETHODIMP CombinedProgress::COMGETTER(OperationDescription)(BSTR *aOperationDescription)
1591{
1592 CheckComArgOutPointerValid(aOperationDescription);
1593
1594 AutoCaller autoCaller(this);
1595 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1596
1597 /* checkProgress needs a write lock */
1598 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1599
1600 HRESULT rc = checkProgress();
1601 if (FAILED(rc)) return rc;
1602
1603 return ProgressBase::COMGETTER(OperationDescription)(aOperationDescription);
1604}
1605
1606STDMETHODIMP CombinedProgress::COMGETTER(OperationPercent)(ULONG *aOperationPercent)
1607{
1608 CheckComArgOutPointerValid(aOperationPercent);
1609
1610 AutoCaller autoCaller(this);
1611 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1612
1613 /* checkProgress needs a write lock */
1614 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1615
1616 HRESULT rc = checkProgress();
1617 if (FAILED(rc)) return rc;
1618
1619 return ProgressBase::COMGETTER(OperationPercent)(aOperationPercent);
1620}
1621
1622STDMETHODIMP CombinedProgress::COMSETTER(Timeout)(ULONG aTimeout)
1623{
1624 NOREF(aTimeout);
1625 AssertFailed();
1626 return E_NOTIMPL;
1627}
1628
1629STDMETHODIMP CombinedProgress::COMGETTER(Timeout)(ULONG *aTimeout)
1630{
1631 CheckComArgOutPointerValid(aTimeout);
1632
1633 AssertFailed();
1634 return E_NOTIMPL;
1635}
1636
1637// IProgress methods
1638/////////////////////////////////////////////////////////////////////////////
1639
1640/**
1641 * @note XPCOM: when this method is called not on the main XPCOM thread, it
1642 * simply blocks the thread until mCompletedSem is signalled. If the
1643 * thread has its own event queue (hmm, what for?) that it must run, then
1644 * calling this method will definitely freeze event processing.
1645 */
1646STDMETHODIMP CombinedProgress::WaitForCompletion(LONG aTimeout)
1647{
1648 LogFlowThisFuncEnter();
1649 LogFlowThisFunc(("aTtimeout=%d\n", aTimeout));
1650
1651 AutoCaller autoCaller(this);
1652 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1653
1654 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1655
1656 /* if we're already completed, take a shortcut */
1657 if (!mCompleted)
1658 {
1659 HRESULT rc = S_OK;
1660 bool forever = aTimeout < 0;
1661 int64_t timeLeft = aTimeout;
1662 int64_t lastTime = RTTimeMilliTS();
1663
1664 while (!mCompleted && (forever || timeLeft > 0))
1665 {
1666 alock.release();
1667 rc = mProgresses.back()->WaitForCompletion(forever ? -1 : (LONG) timeLeft);
1668 alock.acquire();
1669
1670 if (SUCCEEDED(rc))
1671 rc = checkProgress();
1672
1673 if (FAILED(rc)) break;
1674
1675 if (!forever)
1676 {
1677 int64_t now = RTTimeMilliTS();
1678 timeLeft -= now - lastTime;
1679 lastTime = now;
1680 }
1681 }
1682
1683 if (FAILED(rc)) return rc;
1684 }
1685
1686 LogFlowThisFuncLeave();
1687
1688 return S_OK;
1689}
1690
1691/**
1692 * @note XPCOM: when this method is called not on the main XPCOM thread, it
1693 * simply blocks the thread until mCompletedSem is signalled. If the
1694 * thread has its own event queue (hmm, what for?) that it must run, then
1695 * calling this method will definitely freeze event processing.
1696 */
1697STDMETHODIMP CombinedProgress::WaitForOperationCompletion(ULONG aOperation, LONG aTimeout)
1698{
1699 LogFlowThisFuncEnter();
1700 LogFlowThisFunc(("aOperation=%d, aTimeout=%d\n", aOperation, aTimeout));
1701
1702 AutoCaller autoCaller(this);
1703 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1704
1705 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1706
1707 if (aOperation >= m_cOperations)
1708 return setError(E_FAIL,
1709 tr("Operation number must be in range [0, %d]"), m_ulCurrentOperation - 1);
1710
1711 /* if we're already completed or if the given operation is already done,
1712 * then take a shortcut */
1713 if (!mCompleted && aOperation >= m_ulCurrentOperation)
1714 {
1715 HRESULT rc = S_OK;
1716
1717 /* find the right progress object to wait for */
1718 size_t progress = mProgress;
1719 ULONG operation = 0, completedOps = mCompletedOperations;
1720 do
1721 {
1722 ULONG opCount = 0;
1723 rc = mProgresses[progress]->COMGETTER(OperationCount)(&opCount);
1724 if (FAILED(rc))
1725 return rc;
1726
1727 if (completedOps + opCount > aOperation)
1728 {
1729 /* found the right progress object */
1730 operation = aOperation - completedOps;
1731 break;
1732 }
1733
1734 completedOps += opCount;
1735 progress ++;
1736 ComAssertRet(progress < mProgresses.size(), E_FAIL);
1737 }
1738 while (1);
1739
1740 LogFlowThisFunc(("will wait for mProgresses [%d] (%d)\n",
1741 progress, operation));
1742
1743 bool forever = aTimeout < 0;
1744 int64_t timeLeft = aTimeout;
1745 int64_t lastTime = RTTimeMilliTS();
1746
1747 while (!mCompleted && aOperation >= m_ulCurrentOperation &&
1748 (forever || timeLeft > 0))
1749 {
1750 alock.release();
1751 /* wait for the appropriate progress operation completion */
1752 rc = mProgresses[progress]-> WaitForOperationCompletion(operation,
1753 forever ? -1 : (LONG) timeLeft);
1754 alock.acquire();
1755
1756 if (SUCCEEDED(rc))
1757 rc = checkProgress();
1758
1759 if (FAILED(rc)) break;
1760
1761 if (!forever)
1762 {
1763 int64_t now = RTTimeMilliTS();
1764 timeLeft -= now - lastTime;
1765 lastTime = now;
1766 }
1767 }
1768
1769 if (FAILED(rc)) return rc;
1770 }
1771
1772 LogFlowThisFuncLeave();
1773
1774 return S_OK;
1775}
1776
1777STDMETHODIMP CombinedProgress::Cancel()
1778{
1779 AutoCaller autoCaller(this);
1780 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1781
1782 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1783
1784 if (!mCancelable)
1785 return setError(E_FAIL, tr("Operation cannot be canceled"));
1786
1787 if (!mCanceled)
1788 {
1789 LogThisFunc(("Canceling\n"));
1790 mCanceled = TRUE;
1791/** @todo Teleportation: Shouldn't this be propagated to mProgresses? If
1792 * powerUp creates passes a combined progress object to the client, I
1793 * won't get called back since I'm only getting the powerupProgress ...
1794 * Or what? */
1795 if (m_pfnCancelCallback)
1796 m_pfnCancelCallback(m_pvCancelUserArg);
1797
1798 }
1799 else
1800 LogThisFunc(("Already canceled\n"));
1801
1802 return S_OK;
1803}
1804
1805// private methods
1806////////////////////////////////////////////////////////////////////////////////
1807
1808/**
1809 * Fetches the properties of the current progress object and, if it is
1810 * successfully completed, advances to the next uncompleted or unsuccessfully
1811 * completed object in the vector of combined progress objects.
1812 *
1813 * @note Must be called from under this object's write lock!
1814 */
1815HRESULT CombinedProgress::checkProgress()
1816{
1817 /* do nothing if we're already marked ourselves as completed */
1818 if (mCompleted)
1819 return S_OK;
1820
1821 AssertReturn(mProgress < mProgresses.size(), E_FAIL);
1822
1823 ComPtr<IProgress> progress = mProgresses[mProgress];
1824 ComAssertRet(!progress.isNull(), E_FAIL);
1825
1826 HRESULT rc = S_OK;
1827 BOOL fCompleted = FALSE;
1828
1829 do
1830 {
1831 rc = progress->COMGETTER(Completed)(&fCompleted);
1832 if (FAILED(rc))
1833 return rc;
1834
1835 if (fCompleted)
1836 {
1837 rc = progress->COMGETTER(Canceled)(&mCanceled);
1838 if (FAILED(rc))
1839 return rc;
1840
1841 LONG iRc;
1842 rc = progress->COMGETTER(ResultCode)(&iRc);
1843 if (FAILED(rc))
1844 return rc;
1845 mResultCode = iRc;
1846
1847 if (FAILED(mResultCode))
1848 {
1849 rc = progress->COMGETTER(ErrorInfo)(mErrorInfo.asOutParam());
1850 if (FAILED(rc))
1851 return rc;
1852 }
1853
1854 if (FAILED(mResultCode) || mCanceled)
1855 {
1856 mCompleted = TRUE;
1857 }
1858 else
1859 {
1860 ULONG opCount = 0;
1861 rc = progress->COMGETTER(OperationCount)(&opCount);
1862 if (FAILED(rc))
1863 return rc;
1864
1865 mCompletedOperations += opCount;
1866 mProgress ++;
1867
1868 if (mProgress < mProgresses.size())
1869 progress = mProgresses[mProgress];
1870 else
1871 mCompleted = TRUE;
1872 }
1873 }
1874 }
1875 while (fCompleted && !mCompleted);
1876
1877 rc = progress->COMGETTER(OperationPercent)(&m_ulOperationPercent);
1878 if (SUCCEEDED(rc))
1879 {
1880 ULONG operation = 0;
1881 rc = progress->COMGETTER(Operation)(&operation);
1882 if (SUCCEEDED(rc) && mCompletedOperations + operation > m_ulCurrentOperation)
1883 {
1884 m_ulCurrentOperation = mCompletedOperations + operation;
1885 rc = progress->COMGETTER(OperationDescription)(m_bstrOperationDescription.asOutParam());
1886 }
1887 }
1888
1889 return rc;
1890}
1891/* vi: set tabstop=4 shiftwidth=4 expandtab: */
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