VirtualBox

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

Last change on this file since 35650 was 35638, checked in by vboxsync, 14 years ago

Main. QT/FE: fix long standing COM issue

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 52.5 KB
Line 
1/* $Id: ProgressImpl.cpp 35638 2011-01-19 19:10:49Z vboxsync $ */
2/** @file
3 *
4 * VirtualBox Progress COM class implementation
5 */
6
7/*
8 * Copyright (C) 2006-2010 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.leave();
823 vrc = RTSemEventMultiWait(mCompletedSem,
824 fForever ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)timeLeft);
825 alock.enter();
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.leave();
887 vrc = RTSemEventMultiWait(mCompletedSem,
888 fForever ? RT_INDEFINITE_WAIT : (unsigned) timeLeft);
889 alock.enter();
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::Cancel()
919{
920 AutoCaller autoCaller(this);
921 if (FAILED(autoCaller.rc())) return autoCaller.rc();
922
923 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
924
925 if (!mCancelable)
926 return setError(VBOX_E_INVALID_OBJECT_STATE,
927 tr("Operation cannot be canceled"));
928
929 if (!mCanceled)
930 {
931 LogThisFunc(("Canceling\n"));
932 mCanceled = TRUE;
933 if (m_pfnCancelCallback)
934 m_pfnCancelCallback(m_pvCancelUserArg);
935
936 }
937 else
938 LogThisFunc(("Already canceled\n"));
939
940 return S_OK;
941}
942
943/**
944 * Updates the percentage value of the current operation.
945 *
946 * @param aPercent New percentage value of the operation in progress
947 * (in range [0, 100]).
948 */
949STDMETHODIMP Progress::SetCurrentOperationProgress(ULONG aPercent)
950{
951 AutoCaller autoCaller(this);
952 AssertComRCReturnRC(autoCaller.rc());
953
954 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
955
956 AssertMsgReturn(aPercent <= 100, ("%u\n", aPercent), E_INVALIDARG);
957
958 checkForAutomaticTimeout();
959 if (mCancelable && mCanceled)
960 {
961 Assert(!mCompleted);
962 return E_FAIL;
963 }
964 AssertReturn(!mCompleted && !mCanceled, E_FAIL);
965
966 m_ulOperationPercent = aPercent;
967
968 return S_OK;
969}
970
971/**
972 * Signals that the current operation is successfully completed and advances to
973 * the next operation. The operation percentage is reset to 0.
974 *
975 * @param aOperationDescription Description of the next operation.
976 *
977 * @note The current operation must not be the last one.
978 */
979STDMETHODIMP Progress::SetNextOperation(IN_BSTR bstrNextOperationDescription, ULONG ulNextOperationsWeight)
980{
981 AssertReturn(bstrNextOperationDescription, E_INVALIDARG);
982
983 AutoCaller autoCaller(this);
984 AssertComRCReturnRC(autoCaller.rc());
985
986 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
987
988 if (mCanceled)
989 return E_FAIL;
990 AssertReturn(!mCompleted, E_FAIL);
991 AssertReturn(m_ulCurrentOperation + 1 < m_cOperations, E_FAIL);
992
993 ++m_ulCurrentOperation;
994 m_ulOperationsCompletedWeight += m_ulCurrentOperationWeight;
995
996 m_bstrOperationDescription = bstrNextOperationDescription;
997 m_ulCurrentOperationWeight = ulNextOperationsWeight;
998 m_ulOperationPercent = 0;
999
1000 Log(("Progress::setNextOperation(%ls): ulNextOperationsWeight = %d; m_ulCurrentOperation is now %d, m_ulOperationsCompletedWeight is now %d\n",
1001 m_bstrOperationDescription.raw(), ulNextOperationsWeight, m_ulCurrentOperation, m_ulOperationsCompletedWeight));
1002
1003 /* wake up all waiting threads */
1004 if (mWaitersCount > 0)
1005 RTSemEventMultiSignal(mCompletedSem);
1006
1007 return S_OK;
1008}
1009
1010// public methods only for internal purposes
1011/////////////////////////////////////////////////////////////////////////////
1012
1013/**
1014 * Sets the internal result code and attempts to retrieve additional error
1015 * info from the current thread. Gets called from Progress::notifyComplete(),
1016 * but can be called again to override a previous result set with
1017 * notifyComplete().
1018 *
1019 * @param aResultCode
1020 */
1021HRESULT Progress::setResultCode(HRESULT aResultCode)
1022{
1023 AutoCaller autoCaller(this);
1024 AssertComRCReturnRC(autoCaller.rc());
1025
1026 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1027
1028 mResultCode = aResultCode;
1029
1030 HRESULT rc = S_OK;
1031
1032 if (FAILED(aResultCode))
1033 {
1034 /* try to import error info from the current thread */
1035
1036#if !defined(VBOX_WITH_XPCOM)
1037
1038 ComPtr<IErrorInfo> err;
1039 rc = ::GetErrorInfo(0, err.asOutParam());
1040 if (rc == S_OK && err)
1041 {
1042 rc = err.queryInterfaceTo(mErrorInfo.asOutParam());
1043 if (SUCCEEDED(rc) && !mErrorInfo)
1044 rc = E_FAIL;
1045 }
1046
1047#else /* !defined(VBOX_WITH_XPCOM) */
1048
1049 nsCOMPtr<nsIExceptionService> es;
1050 es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &rc);
1051 if (NS_SUCCEEDED(rc))
1052 {
1053 nsCOMPtr <nsIExceptionManager> em;
1054 rc = es->GetCurrentExceptionManager(getter_AddRefs(em));
1055 if (NS_SUCCEEDED(rc))
1056 {
1057 ComPtr<nsIException> ex;
1058 rc = em->GetCurrentException(ex.asOutParam());
1059 if (NS_SUCCEEDED(rc) && ex)
1060 {
1061 rc = ex.queryInterfaceTo(mErrorInfo.asOutParam());
1062 if (NS_SUCCEEDED(rc) && !mErrorInfo)
1063 rc = E_FAIL;
1064 }
1065 }
1066 }
1067#endif /* !defined(VBOX_WITH_XPCOM) */
1068
1069 AssertMsg(rc == S_OK, ("Couldn't get error info (rc=%08X) while trying to set a failed result (%08X)!\n",
1070 rc, aResultCode));
1071 }
1072
1073 return rc;
1074}
1075
1076/**
1077 * Marks the whole task as complete and sets the result code.
1078 *
1079 * If the result code indicates a failure (|FAILED(@a aResultCode)|) then this
1080 * method will import the error info from the current thread and assign it to
1081 * the errorInfo attribute (it will return an error if no info is available in
1082 * such case).
1083 *
1084 * If the result code indicates a success (|SUCCEEDED(@a aResultCode)|) then
1085 * the current operation is set to the last.
1086 *
1087 * Note that this method may be called only once for the given Progress object.
1088 * Subsequent calls will assert.
1089 *
1090 * @param aResultCode Operation result code.
1091 */
1092HRESULT Progress::notifyComplete(HRESULT aResultCode)
1093{
1094 AutoCaller autoCaller(this);
1095 AssertComRCReturnRC(autoCaller.rc());
1096
1097 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1098
1099 AssertReturn(mCompleted == FALSE, E_FAIL);
1100
1101 LogFunc(("aResultCode=%d\n", aResultCode));
1102
1103 if (mCanceled && SUCCEEDED(aResultCode))
1104 aResultCode = E_FAIL;
1105
1106 HRESULT rc = setResultCode(aResultCode);
1107
1108 mCompleted = TRUE;
1109
1110 if (!FAILED(aResultCode))
1111 {
1112 m_ulCurrentOperation = m_cOperations - 1; /* last operation */
1113 m_ulOperationPercent = 100;
1114 }
1115
1116#if !defined VBOX_COM_INPROC
1117 /* remove from the global collection of pending progress operations */
1118 if (mParent)
1119 mParent->removeProgress(mId.ref());
1120#endif
1121
1122 /* wake up all waiting threads */
1123 if (mWaitersCount > 0)
1124 RTSemEventMultiSignal(mCompletedSem);
1125
1126 return rc;
1127}
1128
1129/**
1130 * Wrapper around Progress:notifyCompleteV.
1131 */
1132HRESULT Progress::notifyComplete(HRESULT aResultCode,
1133 const GUID &aIID,
1134 const char *pcszComponent,
1135 const char *aText,
1136 ...)
1137{
1138 va_list va;
1139 va_start(va, aText);
1140 HRESULT hrc = notifyCompleteV(aResultCode, aIID, pcszComponent, aText, va);
1141 va_end(va);
1142 return hrc;
1143}
1144
1145/**
1146 * Marks the operation as complete and attaches full error info.
1147 *
1148 * See com::SupportErrorInfoImpl::setError(HRESULT, const GUID &, const wchar_t
1149 * *, const char *, ...) for more info.
1150 *
1151 * @param aResultCode Operation result (error) code, must not be S_OK.
1152 * @param aIID IID of the interface that defines the error.
1153 * @param aComponent Name of the component that generates the error.
1154 * @param aText Error message (must not be null), an RTStrPrintf-like
1155 * format string in UTF-8 encoding.
1156 * @param va List of arguments for the format string.
1157 */
1158HRESULT Progress::notifyCompleteV(HRESULT aResultCode,
1159 const GUID &aIID,
1160 const char *pcszComponent,
1161 const char *aText,
1162 va_list va)
1163{
1164 Utf8Str text(aText, va);
1165
1166 AutoCaller autoCaller(this);
1167 AssertComRCReturnRC(autoCaller.rc());
1168
1169 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1170
1171 AssertReturn(mCompleted == FALSE, E_FAIL);
1172
1173 if (mCanceled && SUCCEEDED(aResultCode))
1174 aResultCode = E_FAIL;
1175
1176 mCompleted = TRUE;
1177 mResultCode = aResultCode;
1178
1179 AssertReturn(FAILED(aResultCode), E_FAIL);
1180
1181 ComObjPtr<VirtualBoxErrorInfo> errorInfo;
1182 HRESULT rc = errorInfo.createObject();
1183 AssertComRC(rc);
1184 if (SUCCEEDED(rc))
1185 {
1186 errorInfo->init(aResultCode, aIID, pcszComponent, text);
1187 errorInfo.queryInterfaceTo(mErrorInfo.asOutParam());
1188 }
1189
1190#if !defined VBOX_COM_INPROC
1191 /* remove from the global collection of pending progress operations */
1192 if (mParent)
1193 mParent->removeProgress(mId.ref());
1194#endif
1195
1196 /* wake up all waiting threads */
1197 if (mWaitersCount > 0)
1198 RTSemEventMultiSignal(mCompletedSem);
1199
1200 return rc;
1201}
1202
1203/**
1204 * Notify the progress object that we're almost at the point of no return.
1205 *
1206 * This atomically checks for and disables cancelation. Calls to
1207 * IProgress::Cancel() made after a successful call to this method will fail
1208 * and the user can be told. While this isn't entirely clean behavior, it
1209 * prevents issues with an irreversible actually operation succeeding while the
1210 * user believe it was rolled back.
1211 *
1212 * @returns Success indicator.
1213 * @retval true on success.
1214 * @retval false if the progress object has already been canceled or is in an
1215 * invalid state
1216 */
1217bool Progress::notifyPointOfNoReturn(void)
1218{
1219 AutoCaller autoCaller(this);
1220 AssertComRCReturn(autoCaller.rc(), false);
1221
1222 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1223
1224 if (mCanceled)
1225 {
1226 LogThisFunc(("returns false\n"));
1227 return false;
1228 }
1229
1230 mCancelable = FALSE;
1231 LogThisFunc(("returns true\n"));
1232 return true;
1233}
1234
1235////////////////////////////////////////////////////////////////////////////////
1236// CombinedProgress class
1237////////////////////////////////////////////////////////////////////////////////
1238
1239HRESULT CombinedProgress::FinalConstruct()
1240{
1241 HRESULT rc = ProgressBase::FinalConstruct();
1242 if (FAILED(rc)) return rc;
1243
1244 mProgress = 0;
1245 mCompletedOperations = 0;
1246
1247 return BaseFinalConstruct();
1248}
1249
1250void CombinedProgress::FinalRelease()
1251{
1252 uninit();
1253 BaseFinalRelease();
1254}
1255
1256// public initializer/uninitializer for internal purposes only
1257////////////////////////////////////////////////////////////////////////////////
1258
1259/**
1260 * Initializes this object based on individual combined progresses.
1261 * Must be called only from #init()!
1262 *
1263 * @param aAutoInitSpan AutoInitSpan object instantiated by a subclass.
1264 * @param aParent See ProgressBase::init().
1265 * @param aInitiator See ProgressBase::init().
1266 * @param aDescription See ProgressBase::init().
1267 * @param aId See ProgressBase::init().
1268 */
1269HRESULT CombinedProgress::protectedInit(AutoInitSpan &aAutoInitSpan,
1270#if !defined(VBOX_COM_INPROC)
1271 VirtualBox *aParent,
1272#endif
1273 IUnknown *aInitiator,
1274 CBSTR aDescription, OUT_GUID aId)
1275{
1276 LogFlowThisFunc(("aDescription={%ls} mProgresses.size()=%d\n",
1277 aDescription, mProgresses.size()));
1278
1279 HRESULT rc = S_OK;
1280
1281 rc = ProgressBase::protectedInit(aAutoInitSpan,
1282#if !defined(VBOX_COM_INPROC)
1283 aParent,
1284#endif
1285 aInitiator, aDescription, aId);
1286 if (FAILED(rc)) return rc;
1287
1288 mProgress = 0; /* the first object */
1289 mCompletedOperations = 0;
1290
1291 mCompleted = FALSE;
1292 mCancelable = TRUE; /* until any progress returns FALSE */
1293 mCanceled = FALSE;
1294
1295 m_cOperations = 0; /* will be calculated later */
1296
1297 m_ulCurrentOperation = 0;
1298 rc = mProgresses[0]->COMGETTER(OperationDescription)(m_bstrOperationDescription.asOutParam());
1299 if (FAILED(rc)) return rc;
1300
1301 for (size_t i = 0; i < mProgresses.size(); i ++)
1302 {
1303 if (mCancelable)
1304 {
1305 BOOL cancelable = FALSE;
1306 rc = mProgresses[i]->COMGETTER(Cancelable)(&cancelable);
1307 if (FAILED(rc)) return rc;
1308
1309 if (!cancelable)
1310 mCancelable = FALSE;
1311 }
1312
1313 {
1314 ULONG opCount = 0;
1315 rc = mProgresses[i]->COMGETTER(OperationCount)(&opCount);
1316 if (FAILED(rc)) return rc;
1317
1318 m_cOperations += opCount;
1319 }
1320 }
1321
1322 rc = checkProgress();
1323 if (FAILED(rc)) return rc;
1324
1325 return rc;
1326}
1327
1328/**
1329 * Initializes the combined progress object given two normal progress
1330 * objects.
1331 *
1332 * @param aParent See ProgressBase::init().
1333 * @param aInitiator See ProgressBase::init().
1334 * @param aDescription See ProgressBase::init().
1335 * @param aProgress1 First normal progress object.
1336 * @param aProgress2 Second normal progress object.
1337 * @param aId See ProgressBase::init().
1338 */
1339HRESULT CombinedProgress::init(
1340#if !defined(VBOX_COM_INPROC)
1341 VirtualBox *aParent,
1342#endif
1343 IUnknown *aInitiator,
1344 CBSTR aDescription,
1345 IProgress *aProgress1,
1346 IProgress *aProgress2,
1347 OUT_GUID aId /* = NULL */)
1348{
1349 /* Enclose the state transition NotReady->InInit->Ready */
1350 AutoInitSpan autoInitSpan(this);
1351 AssertReturn(autoInitSpan.isOk(), E_FAIL);
1352
1353 mProgresses.resize(2);
1354 mProgresses[0] = aProgress1;
1355 mProgresses[1] = aProgress2;
1356
1357 HRESULT rc = protectedInit(autoInitSpan,
1358#if !defined(VBOX_COM_INPROC)
1359 aParent,
1360#endif
1361 aInitiator,
1362 aDescription,
1363 aId);
1364
1365 /* Confirm a successful initialization when it's the case */
1366 if (SUCCEEDED(rc))
1367 autoInitSpan.setSucceeded();
1368
1369 return rc;
1370}
1371
1372/**
1373 * Uninitializes the instance and sets the ready flag to FALSE.
1374 *
1375 * Called either from FinalRelease() or by the parent when it gets destroyed.
1376 */
1377void CombinedProgress::uninit()
1378{
1379 LogFlowThisFunc(("\n"));
1380
1381 /* Enclose the state transition Ready->InUninit->NotReady */
1382 AutoUninitSpan autoUninitSpan(this);
1383 if (autoUninitSpan.uninitDone())
1384 return;
1385
1386 mProgress = 0;
1387 mProgresses.clear();
1388
1389 ProgressBase::protectedUninit(autoUninitSpan);
1390}
1391
1392// IProgress properties
1393////////////////////////////////////////////////////////////////////////////////
1394
1395STDMETHODIMP CombinedProgress::COMGETTER(Percent)(ULONG *aPercent)
1396{
1397 CheckComArgOutPointerValid(aPercent);
1398
1399 AutoCaller autoCaller(this);
1400 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1401
1402 /* checkProgress needs a write lock */
1403 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1404
1405 if (mCompleted && SUCCEEDED(mResultCode))
1406 *aPercent = 100;
1407 else
1408 {
1409 HRESULT rc = checkProgress();
1410 if (FAILED(rc)) return rc;
1411
1412 /* global percent =
1413 * (100 / m_cOperations) * mOperation +
1414 * ((100 / m_cOperations) / 100) * m_ulOperationPercent */
1415 *aPercent = (100 * m_ulCurrentOperation + m_ulOperationPercent) / m_cOperations;
1416 }
1417
1418 return S_OK;
1419}
1420
1421STDMETHODIMP CombinedProgress::COMGETTER(Completed)(BOOL *aCompleted)
1422{
1423 CheckComArgOutPointerValid(aCompleted);
1424
1425 AutoCaller autoCaller(this);
1426 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1427
1428 /* checkProgress needs a write lock */
1429 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1430
1431 HRESULT rc = checkProgress();
1432 if (FAILED(rc)) return rc;
1433
1434 return ProgressBase::COMGETTER(Completed)(aCompleted);
1435}
1436
1437STDMETHODIMP CombinedProgress::COMGETTER(Canceled)(BOOL *aCanceled)
1438{
1439 CheckComArgOutPointerValid(aCanceled);
1440
1441 AutoCaller autoCaller(this);
1442 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1443
1444 /* checkProgress needs a write lock */
1445 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1446
1447 HRESULT rc = checkProgress();
1448 if (FAILED(rc)) return rc;
1449
1450 return ProgressBase::COMGETTER(Canceled)(aCanceled);
1451}
1452
1453STDMETHODIMP CombinedProgress::COMGETTER(ResultCode)(LONG *aResultCode)
1454{
1455 CheckComArgOutPointerValid(aResultCode);
1456
1457 AutoCaller autoCaller(this);
1458 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1459
1460 /* checkProgress needs a write lock */
1461 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1462
1463 HRESULT rc = checkProgress();
1464 if (FAILED(rc)) return rc;
1465
1466 return ProgressBase::COMGETTER(ResultCode)(aResultCode);
1467}
1468
1469STDMETHODIMP CombinedProgress::COMGETTER(ErrorInfo)(IVirtualBoxErrorInfo **aErrorInfo)
1470{
1471 CheckComArgOutPointerValid(aErrorInfo);
1472
1473 AutoCaller autoCaller(this);
1474 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1475
1476 /* checkProgress needs a write lock */
1477 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1478
1479 HRESULT rc = checkProgress();
1480 if (FAILED(rc)) return rc;
1481
1482 return ProgressBase::COMGETTER(ErrorInfo)(aErrorInfo);
1483}
1484
1485STDMETHODIMP CombinedProgress::COMGETTER(Operation)(ULONG *aOperation)
1486{
1487 CheckComArgOutPointerValid(aOperation);
1488
1489 AutoCaller autoCaller(this);
1490 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1491
1492 /* checkProgress needs a write lock */
1493 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1494
1495 HRESULT rc = checkProgress();
1496 if (FAILED(rc)) return rc;
1497
1498 return ProgressBase::COMGETTER(Operation)(aOperation);
1499}
1500
1501STDMETHODIMP CombinedProgress::COMGETTER(OperationDescription)(BSTR *aOperationDescription)
1502{
1503 CheckComArgOutPointerValid(aOperationDescription);
1504
1505 AutoCaller autoCaller(this);
1506 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1507
1508 /* checkProgress needs a write lock */
1509 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1510
1511 HRESULT rc = checkProgress();
1512 if (FAILED(rc)) return rc;
1513
1514 return ProgressBase::COMGETTER(OperationDescription)(aOperationDescription);
1515}
1516
1517STDMETHODIMP CombinedProgress::COMGETTER(OperationPercent)(ULONG *aOperationPercent)
1518{
1519 CheckComArgOutPointerValid(aOperationPercent);
1520
1521 AutoCaller autoCaller(this);
1522 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1523
1524 /* checkProgress needs a write lock */
1525 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1526
1527 HRESULT rc = checkProgress();
1528 if (FAILED(rc)) return rc;
1529
1530 return ProgressBase::COMGETTER(OperationPercent)(aOperationPercent);
1531}
1532
1533STDMETHODIMP CombinedProgress::COMSETTER(Timeout)(ULONG aTimeout)
1534{
1535 NOREF(aTimeout);
1536 AssertFailed();
1537 return E_NOTIMPL;
1538}
1539
1540STDMETHODIMP CombinedProgress::COMGETTER(Timeout)(ULONG *aTimeout)
1541{
1542 CheckComArgOutPointerValid(aTimeout);
1543
1544 AssertFailed();
1545 return E_NOTIMPL;
1546}
1547
1548// IProgress methods
1549/////////////////////////////////////////////////////////////////////////////
1550
1551/**
1552 * @note XPCOM: when this method is called not on the main XPCOM thread, it
1553 * simply blocks the thread until mCompletedSem is signalled. If the
1554 * thread has its own event queue (hmm, what for?) that it must run, then
1555 * calling this method will definitely freeze event processing.
1556 */
1557STDMETHODIMP CombinedProgress::WaitForCompletion(LONG aTimeout)
1558{
1559 LogFlowThisFuncEnter();
1560 LogFlowThisFunc(("aTtimeout=%d\n", aTimeout));
1561
1562 AutoCaller autoCaller(this);
1563 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1564
1565 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1566
1567 /* if we're already completed, take a shortcut */
1568 if (!mCompleted)
1569 {
1570 HRESULT rc = S_OK;
1571 bool forever = aTimeout < 0;
1572 int64_t timeLeft = aTimeout;
1573 int64_t lastTime = RTTimeMilliTS();
1574
1575 while (!mCompleted && (forever || timeLeft > 0))
1576 {
1577 alock.leave();
1578 rc = mProgresses.back()->WaitForCompletion(forever ? -1 : (LONG) timeLeft);
1579 alock.enter();
1580
1581 if (SUCCEEDED(rc))
1582 rc = checkProgress();
1583
1584 if (FAILED(rc)) break;
1585
1586 if (!forever)
1587 {
1588 int64_t now = RTTimeMilliTS();
1589 timeLeft -= now - lastTime;
1590 lastTime = now;
1591 }
1592 }
1593
1594 if (FAILED(rc)) return rc;
1595 }
1596
1597 LogFlowThisFuncLeave();
1598
1599 return S_OK;
1600}
1601
1602/**
1603 * @note XPCOM: when this method is called not on the main XPCOM thread, it
1604 * simply blocks the thread until mCompletedSem is signalled. If the
1605 * thread has its own event queue (hmm, what for?) that it must run, then
1606 * calling this method will definitely freeze event processing.
1607 */
1608STDMETHODIMP CombinedProgress::WaitForOperationCompletion(ULONG aOperation, LONG aTimeout)
1609{
1610 LogFlowThisFuncEnter();
1611 LogFlowThisFunc(("aOperation=%d, aTimeout=%d\n", aOperation, aTimeout));
1612
1613 AutoCaller autoCaller(this);
1614 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1615
1616 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1617
1618 if (aOperation >= m_cOperations)
1619 return setError(E_FAIL,
1620 tr("Operation number must be in range [0, %d]"), m_ulCurrentOperation - 1);
1621
1622 /* if we're already completed or if the given operation is already done,
1623 * then take a shortcut */
1624 if (!mCompleted && aOperation >= m_ulCurrentOperation)
1625 {
1626 HRESULT rc = S_OK;
1627
1628 /* find the right progress object to wait for */
1629 size_t progress = mProgress;
1630 ULONG operation = 0, completedOps = mCompletedOperations;
1631 do
1632 {
1633 ULONG opCount = 0;
1634 rc = mProgresses[progress]->COMGETTER(OperationCount)(&opCount);
1635 if (FAILED(rc))
1636 return rc;
1637
1638 if (completedOps + opCount > aOperation)
1639 {
1640 /* found the right progress object */
1641 operation = aOperation - completedOps;
1642 break;
1643 }
1644
1645 completedOps += opCount;
1646 progress ++;
1647 ComAssertRet(progress < mProgresses.size(), E_FAIL);
1648 }
1649 while (1);
1650
1651 LogFlowThisFunc(("will wait for mProgresses [%d] (%d)\n",
1652 progress, operation));
1653
1654 bool forever = aTimeout < 0;
1655 int64_t timeLeft = aTimeout;
1656 int64_t lastTime = RTTimeMilliTS();
1657
1658 while (!mCompleted && aOperation >= m_ulCurrentOperation &&
1659 (forever || timeLeft > 0))
1660 {
1661 alock.leave();
1662 /* wait for the appropriate progress operation completion */
1663 rc = mProgresses[progress]-> WaitForOperationCompletion(operation,
1664 forever ? -1 : (LONG) timeLeft);
1665 alock.enter();
1666
1667 if (SUCCEEDED(rc))
1668 rc = checkProgress();
1669
1670 if (FAILED(rc)) break;
1671
1672 if (!forever)
1673 {
1674 int64_t now = RTTimeMilliTS();
1675 timeLeft -= now - lastTime;
1676 lastTime = now;
1677 }
1678 }
1679
1680 if (FAILED(rc)) return rc;
1681 }
1682
1683 LogFlowThisFuncLeave();
1684
1685 return S_OK;
1686}
1687
1688STDMETHODIMP CombinedProgress::Cancel()
1689{
1690 AutoCaller autoCaller(this);
1691 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1692
1693 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1694
1695 if (!mCancelable)
1696 return setError(E_FAIL, tr("Operation cannot be canceled"));
1697
1698 if (!mCanceled)
1699 {
1700 LogThisFunc(("Canceling\n"));
1701 mCanceled = TRUE;
1702/** @todo Teleportation: Shouldn't this be propagated to mProgresses? If
1703 * powerUp creates passes a combined progress object to the client, I
1704 * won't get called back since I'm only getting the powerupProgress ...
1705 * Or what? */
1706 if (m_pfnCancelCallback)
1707 m_pfnCancelCallback(m_pvCancelUserArg);
1708
1709 }
1710 else
1711 LogThisFunc(("Already canceled\n"));
1712
1713 return S_OK;
1714}
1715
1716// private methods
1717////////////////////////////////////////////////////////////////////////////////
1718
1719/**
1720 * Fetches the properties of the current progress object and, if it is
1721 * successfully completed, advances to the next uncompleted or unsuccessfully
1722 * completed object in the vector of combined progress objects.
1723 *
1724 * @note Must be called from under this object's write lock!
1725 */
1726HRESULT CombinedProgress::checkProgress()
1727{
1728 /* do nothing if we're already marked ourselves as completed */
1729 if (mCompleted)
1730 return S_OK;
1731
1732 AssertReturn(mProgress < mProgresses.size(), E_FAIL);
1733
1734 ComPtr<IProgress> progress = mProgresses[mProgress];
1735 ComAssertRet(!progress.isNull(), E_FAIL);
1736
1737 HRESULT rc = S_OK;
1738 BOOL fCompleted = FALSE;
1739
1740 do
1741 {
1742 rc = progress->COMGETTER(Completed)(&fCompleted);
1743 if (FAILED(rc))
1744 return rc;
1745
1746 if (fCompleted)
1747 {
1748 rc = progress->COMGETTER(Canceled)(&mCanceled);
1749 if (FAILED(rc))
1750 return rc;
1751
1752 LONG iRc;
1753 rc = progress->COMGETTER(ResultCode)(&iRc);
1754 if (FAILED(rc))
1755 return rc;
1756 mResultCode = iRc;
1757
1758 if (FAILED(mResultCode))
1759 {
1760 rc = progress->COMGETTER(ErrorInfo)(mErrorInfo.asOutParam());
1761 if (FAILED(rc))
1762 return rc;
1763 }
1764
1765 if (FAILED(mResultCode) || mCanceled)
1766 {
1767 mCompleted = TRUE;
1768 }
1769 else
1770 {
1771 ULONG opCount = 0;
1772 rc = progress->COMGETTER(OperationCount)(&opCount);
1773 if (FAILED(rc))
1774 return rc;
1775
1776 mCompletedOperations += opCount;
1777 mProgress ++;
1778
1779 if (mProgress < mProgresses.size())
1780 progress = mProgresses[mProgress];
1781 else
1782 mCompleted = TRUE;
1783 }
1784 }
1785 }
1786 while (fCompleted && !mCompleted);
1787
1788 rc = progress->COMGETTER(OperationPercent)(&m_ulOperationPercent);
1789 if (SUCCEEDED(rc))
1790 {
1791 ULONG operation = 0;
1792 rc = progress->COMGETTER(Operation)(&operation);
1793 if (SUCCEEDED(rc) && mCompletedOperations + operation > m_ulCurrentOperation)
1794 {
1795 m_ulCurrentOperation = mCompletedOperations + operation;
1796 rc = progress->COMGETTER(OperationDescription)(m_bstrOperationDescription.asOutParam());
1797 }
1798 }
1799
1800 return rc;
1801}
1802/* 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