VirtualBox

source: vbox/trunk/src/VBox/Main/ProgressImpl.cpp@ 35273

Last change on this file since 35273 was 33621, checked in by vboxsync, 14 years ago

Utf8Str: Use printfV in Utf8StrFmt, eliminated Utf8StrFmtVA by replacing it by a Utf8Str(str,va) constructor.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 52.4 KB
Line 
1/* $Id: ProgressImpl.cpp 33621 2010-10-29 16:15:40Z 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 S_OK;
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}
596
597// public initializer/uninitializer for internal purposes only
598////////////////////////////////////////////////////////////////////////////////
599
600/**
601 * Initializes the normal progress object. With this variant, one can have
602 * an arbitrary number of sub-operation which IProgress can analyze to
603 * have a weighted progress computed.
604 *
605 * For example, say that one IProgress is supposed to track the cloning
606 * of two hard disk images, which are 100 MB and 1000 MB in size, respectively,
607 * and each of these hard disks should be one sub-operation of the IProgress.
608 *
609 * Obviously the progress would be misleading if the progress displayed 50%
610 * after the smaller image was cloned and would then take much longer for
611 * the second half.
612 *
613 * With weighted progress, one can invoke the following calls:
614 *
615 * 1) create progress object with cOperations = 2 and ulTotalOperationsWeight =
616 * 1100 (100 MB plus 1100, but really the weights can be any ULONG); pass
617 * in ulFirstOperationWeight = 100 for the first sub-operation
618 *
619 * 2) Then keep calling setCurrentOperationProgress() with a percentage
620 * for the first image; the total progress will increase up to a value
621 * of 9% (100MB / 1100MB * 100%).
622 *
623 * 3) Then call setNextOperation with the second weight (1000 for the megabytes
624 * of the second disk).
625 *
626 * 4) Then keep calling setCurrentOperationProgress() with a percentage for
627 * the second image, where 100% of the operation will then yield a 100%
628 * progress of the entire task.
629 *
630 * Weighting is optional; you can simply assign a weight of 1 to each operation
631 * and pass ulTotalOperationsWeight == cOperations to this constructor (but
632 * for that variant and for backwards-compatibility a simpler constructor exists
633 * in ProgressImpl.h as well).
634 *
635 * Even simpler, if you need no sub-operations at all, pass in cOperations =
636 * ulTotalOperationsWeight = ulFirstOperationWeight = 1.
637 *
638 * @param aParent See ProgressBase::init().
639 * @param aInitiator See ProgressBase::init().
640 * @param aDescription See ProgressBase::init().
641 * @param aCancelable Flag whether the task maybe canceled.
642 * @param cOperations Number of operations within this task (at least 1).
643 * @param ulTotalOperationsWeight Total weight of operations; must be the sum of ulFirstOperationWeight and
644 * what is later passed with each subsequent setNextOperation() call.
645 * @param bstrFirstOperationDescription Description of the first operation.
646 * @param ulFirstOperationWeight Weight of first sub-operation.
647 * @param aId See ProgressBase::init().
648 */
649HRESULT Progress::init(
650#if !defined(VBOX_COM_INPROC)
651 VirtualBox *aParent,
652#endif
653 IUnknown *aInitiator,
654 CBSTR aDescription,
655 BOOL aCancelable,
656 ULONG cOperations,
657 ULONG ulTotalOperationsWeight,
658 CBSTR bstrFirstOperationDescription,
659 ULONG ulFirstOperationWeight,
660 OUT_GUID aId /* = NULL */)
661{
662 LogFlowThisFunc(("aDescription=\"%ls\", cOperations=%d, ulTotalOperationsWeight=%d, bstrFirstOperationDescription=\"%ls\", ulFirstOperationWeight=%d\n",
663 aDescription,
664 cOperations,
665 ulTotalOperationsWeight,
666 bstrFirstOperationDescription,
667 ulFirstOperationWeight));
668
669 AssertReturn(bstrFirstOperationDescription, E_INVALIDARG);
670 AssertReturn(ulTotalOperationsWeight >= 1, E_INVALIDARG);
671
672 /* Enclose the state transition NotReady->InInit->Ready */
673 AutoInitSpan autoInitSpan(this);
674 AssertReturn(autoInitSpan.isOk(), E_FAIL);
675
676 HRESULT rc = S_OK;
677
678 rc = ProgressBase::protectedInit(autoInitSpan,
679#if !defined(VBOX_COM_INPROC)
680 aParent,
681#endif
682 aInitiator, aDescription, aId);
683 if (FAILED(rc)) return rc;
684
685 mCancelable = aCancelable;
686
687 m_cOperations = cOperations;
688 m_ulTotalOperationsWeight = ulTotalOperationsWeight;
689 m_ulOperationsCompletedWeight = 0;
690 m_ulCurrentOperation = 0;
691 m_bstrOperationDescription = bstrFirstOperationDescription;
692 m_ulCurrentOperationWeight = ulFirstOperationWeight;
693 m_ulOperationPercent = 0;
694
695 int vrc = RTSemEventMultiCreate(&mCompletedSem);
696 ComAssertRCRet(vrc, E_FAIL);
697
698 RTSemEventMultiReset(mCompletedSem);
699
700 /* Confirm a successful initialization when it's the case */
701 if (SUCCEEDED(rc))
702 autoInitSpan.setSucceeded();
703
704 return rc;
705}
706
707/**
708 * Initializes the sub-progress object that represents a specific operation of
709 * the whole task.
710 *
711 * Objects initialized with this method are then combined together into the
712 * single task using a CombinedProgress instance, so it doesn't require the
713 * parent, initiator, description and doesn't create an ID. Note that calling
714 * respective getter methods on an object initialized with this method is
715 * useless. Such objects are used only to provide a separate wait semaphore and
716 * store individual operation descriptions.
717 *
718 * @param aCancelable Flag whether the task maybe canceled.
719 * @param aOperationCount Number of sub-operations within this task (at least 1).
720 * @param aOperationDescription Description of the individual operation.
721 */
722HRESULT Progress::init(BOOL aCancelable,
723 ULONG aOperationCount,
724 CBSTR aOperationDescription)
725{
726 LogFlowThisFunc(("aOperationDescription=\"%ls\"\n", aOperationDescription));
727
728 /* Enclose the state transition NotReady->InInit->Ready */
729 AutoInitSpan autoInitSpan(this);
730 AssertReturn(autoInitSpan.isOk(), E_FAIL);
731
732 HRESULT rc = S_OK;
733
734 rc = ProgressBase::protectedInit(autoInitSpan);
735 if (FAILED(rc)) return rc;
736
737 mCancelable = aCancelable;
738
739 // for this variant we assume for now that all operations are weighed "1"
740 // and equal total weight = operation count
741 m_cOperations = aOperationCount;
742 m_ulTotalOperationsWeight = aOperationCount;
743 m_ulOperationsCompletedWeight = 0;
744 m_ulCurrentOperation = 0;
745 m_bstrOperationDescription = aOperationDescription;
746 m_ulCurrentOperationWeight = 1;
747 m_ulOperationPercent = 0;
748
749 int vrc = RTSemEventMultiCreate(&mCompletedSem);
750 ComAssertRCRet(vrc, E_FAIL);
751
752 RTSemEventMultiReset(mCompletedSem);
753
754 /* Confirm a successful initialization when it's the case */
755 if (SUCCEEDED(rc))
756 autoInitSpan.setSucceeded();
757
758 return rc;
759}
760
761/**
762 * Uninitializes the instance and sets the ready flag to FALSE.
763 *
764 * Called either from FinalRelease() or by the parent when it gets destroyed.
765 */
766void Progress::uninit()
767{
768 LogFlowThisFunc(("\n"));
769
770 /* Enclose the state transition Ready->InUninit->NotReady */
771 AutoUninitSpan autoUninitSpan(this);
772 if (autoUninitSpan.uninitDone())
773 return;
774
775 /* wake up all threads still waiting on occasion */
776 if (mWaitersCount > 0)
777 {
778 LogFlow(("WARNING: There are still %d threads waiting for '%ls' completion!\n",
779 mWaitersCount, mDescription.raw()));
780 RTSemEventMultiSignal(mCompletedSem);
781 }
782
783 RTSemEventMultiDestroy(mCompletedSem);
784
785 ProgressBase::protectedUninit(autoUninitSpan);
786}
787
788// IProgress properties
789/////////////////////////////////////////////////////////////////////////////
790
791// IProgress methods
792/////////////////////////////////////////////////////////////////////////////
793
794/**
795 * @note XPCOM: when this method is not called on the main XPCOM thread, it
796 * simply blocks the thread until mCompletedSem is signalled. If the
797 * thread has its own event queue (hmm, what for?) that it must run, then
798 * calling this method will definitely freeze event processing.
799 */
800STDMETHODIMP Progress::WaitForCompletion(LONG aTimeout)
801{
802 LogFlowThisFuncEnter();
803 LogFlowThisFunc(("aTimeout=%d\n", aTimeout));
804
805 AutoCaller autoCaller(this);
806 if (FAILED(autoCaller.rc())) return autoCaller.rc();
807
808 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
809
810 /* if we're already completed, take a shortcut */
811 if (!mCompleted)
812 {
813 int vrc = VINF_SUCCESS;
814 bool fForever = aTimeout < 0;
815 int64_t timeLeft = aTimeout;
816 int64_t lastTime = RTTimeMilliTS();
817
818 while (!mCompleted && (fForever || timeLeft > 0))
819 {
820 mWaitersCount++;
821 alock.leave();
822 vrc = RTSemEventMultiWait(mCompletedSem,
823 fForever ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)timeLeft);
824 alock.enter();
825 mWaitersCount--;
826
827 /* the last waiter resets the semaphore */
828 if (mWaitersCount == 0)
829 RTSemEventMultiReset(mCompletedSem);
830
831 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
832 break;
833
834 if (!fForever)
835 {
836 int64_t now = RTTimeMilliTS();
837 timeLeft -= now - lastTime;
838 lastTime = now;
839 }
840 }
841
842 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
843 return setError(VBOX_E_IPRT_ERROR,
844 tr("Failed to wait for the task completion (%Rrc)"),
845 vrc);
846 }
847
848 LogFlowThisFuncLeave();
849
850 return S_OK;
851}
852
853/**
854 * @note XPCOM: when this method is not called on the main XPCOM thread, it
855 * simply blocks the thread until mCompletedSem is signalled. If the
856 * thread has its own event queue (hmm, what for?) that it must run, then
857 * calling this method will definitely freeze event processing.
858 */
859STDMETHODIMP Progress::WaitForOperationCompletion(ULONG aOperation, LONG aTimeout)
860{
861 LogFlowThisFuncEnter();
862 LogFlowThisFunc(("aOperation=%d, aTimeout=%d\n", aOperation, aTimeout));
863
864 AutoCaller autoCaller(this);
865 if (FAILED(autoCaller.rc())) return autoCaller.rc();
866
867 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
868
869 CheckComArgExpr(aOperation, aOperation < m_cOperations);
870
871 /* if we're already completed or if the given operation is already done,
872 * then take a shortcut */
873 if ( !mCompleted
874 && aOperation >= m_ulCurrentOperation)
875 {
876 int vrc = VINF_SUCCESS;
877 bool fForever = aTimeout < 0;
878 int64_t timeLeft = aTimeout;
879 int64_t lastTime = RTTimeMilliTS();
880
881 while ( !mCompleted && aOperation >= m_ulCurrentOperation
882 && (fForever || timeLeft > 0))
883 {
884 mWaitersCount ++;
885 alock.leave();
886 vrc = RTSemEventMultiWait(mCompletedSem,
887 fForever ? RT_INDEFINITE_WAIT : (unsigned) timeLeft);
888 alock.enter();
889 mWaitersCount--;
890
891 /* the last waiter resets the semaphore */
892 if (mWaitersCount == 0)
893 RTSemEventMultiReset(mCompletedSem);
894
895 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
896 break;
897
898 if (!fForever)
899 {
900 int64_t now = RTTimeMilliTS();
901 timeLeft -= now - lastTime;
902 lastTime = now;
903 }
904 }
905
906 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
907 return setError(E_FAIL,
908 tr("Failed to wait for the operation completion (%Rrc)"),
909 vrc);
910 }
911
912 LogFlowThisFuncLeave();
913
914 return S_OK;
915}
916
917STDMETHODIMP Progress::Cancel()
918{
919 AutoCaller autoCaller(this);
920 if (FAILED(autoCaller.rc())) return autoCaller.rc();
921
922 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
923
924 if (!mCancelable)
925 return setError(VBOX_E_INVALID_OBJECT_STATE,
926 tr("Operation cannot be canceled"));
927
928 if (!mCanceled)
929 {
930 LogThisFunc(("Canceling\n"));
931 mCanceled = TRUE;
932 if (m_pfnCancelCallback)
933 m_pfnCancelCallback(m_pvCancelUserArg);
934
935 }
936 else
937 LogThisFunc(("Already canceled\n"));
938
939 return S_OK;
940}
941
942/**
943 * Updates the percentage value of the current operation.
944 *
945 * @param aPercent New percentage value of the operation in progress
946 * (in range [0, 100]).
947 */
948STDMETHODIMP Progress::SetCurrentOperationProgress(ULONG aPercent)
949{
950 AutoCaller autoCaller(this);
951 AssertComRCReturnRC(autoCaller.rc());
952
953 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
954
955 AssertMsgReturn(aPercent <= 100, ("%u\n", aPercent), E_INVALIDARG);
956
957 checkForAutomaticTimeout();
958 if (mCancelable && mCanceled)
959 {
960 Assert(!mCompleted);
961 return E_FAIL;
962 }
963 AssertReturn(!mCompleted && !mCanceled, E_FAIL);
964
965 m_ulOperationPercent = aPercent;
966
967 return S_OK;
968}
969
970/**
971 * Signals that the current operation is successfully completed and advances to
972 * the next operation. The operation percentage is reset to 0.
973 *
974 * @param aOperationDescription Description of the next operation.
975 *
976 * @note The current operation must not be the last one.
977 */
978STDMETHODIMP Progress::SetNextOperation(IN_BSTR bstrNextOperationDescription, ULONG ulNextOperationsWeight)
979{
980 AssertReturn(bstrNextOperationDescription, E_INVALIDARG);
981
982 AutoCaller autoCaller(this);
983 AssertComRCReturnRC(autoCaller.rc());
984
985 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
986
987 if (mCanceled)
988 return E_FAIL;
989 AssertReturn(!mCompleted, E_FAIL);
990 AssertReturn(m_ulCurrentOperation + 1 < m_cOperations, E_FAIL);
991
992 ++m_ulCurrentOperation;
993 m_ulOperationsCompletedWeight += m_ulCurrentOperationWeight;
994
995 m_bstrOperationDescription = bstrNextOperationDescription;
996 m_ulCurrentOperationWeight = ulNextOperationsWeight;
997 m_ulOperationPercent = 0;
998
999 Log(("Progress::setNextOperation(%ls): ulNextOperationsWeight = %d; m_ulCurrentOperation is now %d, m_ulOperationsCompletedWeight is now %d\n",
1000 m_bstrOperationDescription.raw(), ulNextOperationsWeight, m_ulCurrentOperation, m_ulOperationsCompletedWeight));
1001
1002 /* wake up all waiting threads */
1003 if (mWaitersCount > 0)
1004 RTSemEventMultiSignal(mCompletedSem);
1005
1006 return S_OK;
1007}
1008
1009// public methods only for internal purposes
1010/////////////////////////////////////////////////////////////////////////////
1011
1012/**
1013 * Sets the internal result code and attempts to retrieve additional error
1014 * info from the current thread. Gets called from Progress::notifyComplete(),
1015 * but can be called again to override a previous result set with
1016 * notifyComplete().
1017 *
1018 * @param aResultCode
1019 */
1020HRESULT Progress::setResultCode(HRESULT aResultCode)
1021{
1022 AutoCaller autoCaller(this);
1023 AssertComRCReturnRC(autoCaller.rc());
1024
1025 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1026
1027 mResultCode = aResultCode;
1028
1029 HRESULT rc = S_OK;
1030
1031 if (FAILED(aResultCode))
1032 {
1033 /* try to import error info from the current thread */
1034
1035#if !defined(VBOX_WITH_XPCOM)
1036
1037 ComPtr<IErrorInfo> err;
1038 rc = ::GetErrorInfo(0, err.asOutParam());
1039 if (rc == S_OK && err)
1040 {
1041 rc = err.queryInterfaceTo(mErrorInfo.asOutParam());
1042 if (SUCCEEDED(rc) && !mErrorInfo)
1043 rc = E_FAIL;
1044 }
1045
1046#else /* !defined(VBOX_WITH_XPCOM) */
1047
1048 nsCOMPtr<nsIExceptionService> es;
1049 es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &rc);
1050 if (NS_SUCCEEDED(rc))
1051 {
1052 nsCOMPtr <nsIExceptionManager> em;
1053 rc = es->GetCurrentExceptionManager(getter_AddRefs(em));
1054 if (NS_SUCCEEDED(rc))
1055 {
1056 ComPtr<nsIException> ex;
1057 rc = em->GetCurrentException(ex.asOutParam());
1058 if (NS_SUCCEEDED(rc) && ex)
1059 {
1060 rc = ex.queryInterfaceTo(mErrorInfo.asOutParam());
1061 if (NS_SUCCEEDED(rc) && !mErrorInfo)
1062 rc = E_FAIL;
1063 }
1064 }
1065 }
1066#endif /* !defined(VBOX_WITH_XPCOM) */
1067
1068 AssertMsg(rc == S_OK, ("Couldn't get error info (rc=%08X) while trying to set a failed result (%08X)!\n",
1069 rc, aResultCode));
1070 }
1071
1072 return rc;
1073}
1074
1075/**
1076 * Marks the whole task as complete and sets the result code.
1077 *
1078 * If the result code indicates a failure (|FAILED(@a aResultCode)|) then this
1079 * method will import the error info from the current thread and assign it to
1080 * the errorInfo attribute (it will return an error if no info is available in
1081 * such case).
1082 *
1083 * If the result code indicates a success (|SUCCEEDED(@a aResultCode)|) then
1084 * the current operation is set to the last.
1085 *
1086 * Note that this method may be called only once for the given Progress object.
1087 * Subsequent calls will assert.
1088 *
1089 * @param aResultCode Operation result code.
1090 */
1091HRESULT Progress::notifyComplete(HRESULT aResultCode)
1092{
1093 AutoCaller autoCaller(this);
1094 AssertComRCReturnRC(autoCaller.rc());
1095
1096 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1097
1098 AssertReturn(mCompleted == FALSE, E_FAIL);
1099
1100 LogFunc(("aResultCode=%d\n", aResultCode));
1101
1102 if (mCanceled && SUCCEEDED(aResultCode))
1103 aResultCode = E_FAIL;
1104
1105 HRESULT rc = setResultCode(aResultCode);
1106
1107 mCompleted = TRUE;
1108
1109 if (!FAILED(aResultCode))
1110 {
1111 m_ulCurrentOperation = m_cOperations - 1; /* last operation */
1112 m_ulOperationPercent = 100;
1113 }
1114
1115#if !defined VBOX_COM_INPROC
1116 /* remove from the global collection of pending progress operations */
1117 if (mParent)
1118 mParent->removeProgress(mId.ref());
1119#endif
1120
1121 /* wake up all waiting threads */
1122 if (mWaitersCount > 0)
1123 RTSemEventMultiSignal(mCompletedSem);
1124
1125 return rc;
1126}
1127
1128/**
1129 * Wrapper around Progress:notifyCompleteV.
1130 */
1131HRESULT Progress::notifyComplete(HRESULT aResultCode,
1132 const GUID &aIID,
1133 const char *pcszComponent,
1134 const char *aText,
1135 ...)
1136{
1137 va_list va;
1138 va_start(va, aText);
1139 HRESULT hrc = notifyCompleteV(aResultCode, aIID, pcszComponent, aText, va);
1140 va_end(va);
1141 return hrc;
1142}
1143
1144/**
1145 * Marks the operation as complete and attaches full error info.
1146 *
1147 * See com::SupportErrorInfoImpl::setError(HRESULT, const GUID &, const wchar_t
1148 * *, const char *, ...) for more info.
1149 *
1150 * @param aResultCode Operation result (error) code, must not be S_OK.
1151 * @param aIID IID of the interface that defines the error.
1152 * @param aComponent Name of the component that generates the error.
1153 * @param aText Error message (must not be null), an RTStrPrintf-like
1154 * format string in UTF-8 encoding.
1155 * @param va List of arguments for the format string.
1156 */
1157HRESULT Progress::notifyCompleteV(HRESULT aResultCode,
1158 const GUID &aIID,
1159 const char *pcszComponent,
1160 const char *aText,
1161 va_list va)
1162{
1163 Utf8Str text(aText, va);
1164
1165 AutoCaller autoCaller(this);
1166 AssertComRCReturnRC(autoCaller.rc());
1167
1168 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1169
1170 AssertReturn(mCompleted == FALSE, E_FAIL);
1171
1172 if (mCanceled && SUCCEEDED(aResultCode))
1173 aResultCode = E_FAIL;
1174
1175 mCompleted = TRUE;
1176 mResultCode = aResultCode;
1177
1178 AssertReturn(FAILED(aResultCode), E_FAIL);
1179
1180 ComObjPtr<VirtualBoxErrorInfo> errorInfo;
1181 HRESULT rc = errorInfo.createObject();
1182 AssertComRC(rc);
1183 if (SUCCEEDED(rc))
1184 {
1185 errorInfo->init(aResultCode, aIID, pcszComponent, text);
1186 errorInfo.queryInterfaceTo(mErrorInfo.asOutParam());
1187 }
1188
1189#if !defined VBOX_COM_INPROC
1190 /* remove from the global collection of pending progress operations */
1191 if (mParent)
1192 mParent->removeProgress(mId.ref());
1193#endif
1194
1195 /* wake up all waiting threads */
1196 if (mWaitersCount > 0)
1197 RTSemEventMultiSignal(mCompletedSem);
1198
1199 return rc;
1200}
1201
1202/**
1203 * Notify the progress object that we're almost at the point of no return.
1204 *
1205 * This atomically checks for and disables cancelation. Calls to
1206 * IProgress::Cancel() made after a successful call to this method will fail
1207 * and the user can be told. While this isn't entirely clean behavior, it
1208 * prevents issues with an irreversible actually operation succeeding while the
1209 * user believe it was rolled back.
1210 *
1211 * @returns Success indicator.
1212 * @retval true on success.
1213 * @retval false if the progress object has already been canceled or is in an
1214 * invalid state
1215 */
1216bool Progress::notifyPointOfNoReturn(void)
1217{
1218 AutoCaller autoCaller(this);
1219 AssertComRCReturn(autoCaller.rc(), false);
1220
1221 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1222
1223 if (mCanceled)
1224 {
1225 LogThisFunc(("returns false\n"));
1226 return false;
1227 }
1228
1229 mCancelable = FALSE;
1230 LogThisFunc(("returns true\n"));
1231 return true;
1232}
1233
1234////////////////////////////////////////////////////////////////////////////////
1235// CombinedProgress class
1236////////////////////////////////////////////////////////////////////////////////
1237
1238HRESULT CombinedProgress::FinalConstruct()
1239{
1240 HRESULT rc = ProgressBase::FinalConstruct();
1241 if (FAILED(rc)) return rc;
1242
1243 mProgress = 0;
1244 mCompletedOperations = 0;
1245
1246 return S_OK;
1247}
1248
1249void CombinedProgress::FinalRelease()
1250{
1251 uninit();
1252}
1253
1254// public initializer/uninitializer for internal purposes only
1255////////////////////////////////////////////////////////////////////////////////
1256
1257/**
1258 * Initializes this object based on individual combined progresses.
1259 * Must be called only from #init()!
1260 *
1261 * @param aAutoInitSpan AutoInitSpan object instantiated by a subclass.
1262 * @param aParent See ProgressBase::init().
1263 * @param aInitiator See ProgressBase::init().
1264 * @param aDescription See ProgressBase::init().
1265 * @param aId See ProgressBase::init().
1266 */
1267HRESULT CombinedProgress::protectedInit(AutoInitSpan &aAutoInitSpan,
1268#if !defined(VBOX_COM_INPROC)
1269 VirtualBox *aParent,
1270#endif
1271 IUnknown *aInitiator,
1272 CBSTR aDescription, OUT_GUID aId)
1273{
1274 LogFlowThisFunc(("aDescription={%ls} mProgresses.size()=%d\n",
1275 aDescription, mProgresses.size()));
1276
1277 HRESULT rc = S_OK;
1278
1279 rc = ProgressBase::protectedInit(aAutoInitSpan,
1280#if !defined(VBOX_COM_INPROC)
1281 aParent,
1282#endif
1283 aInitiator, aDescription, aId);
1284 if (FAILED(rc)) return rc;
1285
1286 mProgress = 0; /* the first object */
1287 mCompletedOperations = 0;
1288
1289 mCompleted = FALSE;
1290 mCancelable = TRUE; /* until any progress returns FALSE */
1291 mCanceled = FALSE;
1292
1293 m_cOperations = 0; /* will be calculated later */
1294
1295 m_ulCurrentOperation = 0;
1296 rc = mProgresses[0]->COMGETTER(OperationDescription)(m_bstrOperationDescription.asOutParam());
1297 if (FAILED(rc)) return rc;
1298
1299 for (size_t i = 0; i < mProgresses.size(); i ++)
1300 {
1301 if (mCancelable)
1302 {
1303 BOOL cancelable = FALSE;
1304 rc = mProgresses[i]->COMGETTER(Cancelable)(&cancelable);
1305 if (FAILED(rc)) return rc;
1306
1307 if (!cancelable)
1308 mCancelable = FALSE;
1309 }
1310
1311 {
1312 ULONG opCount = 0;
1313 rc = mProgresses[i]->COMGETTER(OperationCount)(&opCount);
1314 if (FAILED(rc)) return rc;
1315
1316 m_cOperations += opCount;
1317 }
1318 }
1319
1320 rc = checkProgress();
1321 if (FAILED(rc)) return rc;
1322
1323 return rc;
1324}
1325
1326/**
1327 * Initializes the combined progress object given two normal progress
1328 * objects.
1329 *
1330 * @param aParent See ProgressBase::init().
1331 * @param aInitiator See ProgressBase::init().
1332 * @param aDescription See ProgressBase::init().
1333 * @param aProgress1 First normal progress object.
1334 * @param aProgress2 Second normal progress object.
1335 * @param aId See ProgressBase::init().
1336 */
1337HRESULT CombinedProgress::init(
1338#if !defined(VBOX_COM_INPROC)
1339 VirtualBox *aParent,
1340#endif
1341 IUnknown *aInitiator,
1342 CBSTR aDescription,
1343 IProgress *aProgress1,
1344 IProgress *aProgress2,
1345 OUT_GUID aId /* = NULL */)
1346{
1347 /* Enclose the state transition NotReady->InInit->Ready */
1348 AutoInitSpan autoInitSpan(this);
1349 AssertReturn(autoInitSpan.isOk(), E_FAIL);
1350
1351 mProgresses.resize(2);
1352 mProgresses[0] = aProgress1;
1353 mProgresses[1] = aProgress2;
1354
1355 HRESULT rc = protectedInit(autoInitSpan,
1356#if !defined(VBOX_COM_INPROC)
1357 aParent,
1358#endif
1359 aInitiator,
1360 aDescription,
1361 aId);
1362
1363 /* Confirm a successful initialization when it's the case */
1364 if (SUCCEEDED(rc))
1365 autoInitSpan.setSucceeded();
1366
1367 return rc;
1368}
1369
1370/**
1371 * Uninitializes the instance and sets the ready flag to FALSE.
1372 *
1373 * Called either from FinalRelease() or by the parent when it gets destroyed.
1374 */
1375void CombinedProgress::uninit()
1376{
1377 LogFlowThisFunc(("\n"));
1378
1379 /* Enclose the state transition Ready->InUninit->NotReady */
1380 AutoUninitSpan autoUninitSpan(this);
1381 if (autoUninitSpan.uninitDone())
1382 return;
1383
1384 mProgress = 0;
1385 mProgresses.clear();
1386
1387 ProgressBase::protectedUninit(autoUninitSpan);
1388}
1389
1390// IProgress properties
1391////////////////////////////////////////////////////////////////////////////////
1392
1393STDMETHODIMP CombinedProgress::COMGETTER(Percent)(ULONG *aPercent)
1394{
1395 CheckComArgOutPointerValid(aPercent);
1396
1397 AutoCaller autoCaller(this);
1398 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1399
1400 /* checkProgress needs a write lock */
1401 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1402
1403 if (mCompleted && SUCCEEDED(mResultCode))
1404 *aPercent = 100;
1405 else
1406 {
1407 HRESULT rc = checkProgress();
1408 if (FAILED(rc)) return rc;
1409
1410 /* global percent =
1411 * (100 / m_cOperations) * mOperation +
1412 * ((100 / m_cOperations) / 100) * m_ulOperationPercent */
1413 *aPercent = (100 * m_ulCurrentOperation + m_ulOperationPercent) / m_cOperations;
1414 }
1415
1416 return S_OK;
1417}
1418
1419STDMETHODIMP CombinedProgress::COMGETTER(Completed)(BOOL *aCompleted)
1420{
1421 CheckComArgOutPointerValid(aCompleted);
1422
1423 AutoCaller autoCaller(this);
1424 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1425
1426 /* checkProgress needs a write lock */
1427 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1428
1429 HRESULT rc = checkProgress();
1430 if (FAILED(rc)) return rc;
1431
1432 return ProgressBase::COMGETTER(Completed)(aCompleted);
1433}
1434
1435STDMETHODIMP CombinedProgress::COMGETTER(Canceled)(BOOL *aCanceled)
1436{
1437 CheckComArgOutPointerValid(aCanceled);
1438
1439 AutoCaller autoCaller(this);
1440 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1441
1442 /* checkProgress needs a write lock */
1443 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1444
1445 HRESULT rc = checkProgress();
1446 if (FAILED(rc)) return rc;
1447
1448 return ProgressBase::COMGETTER(Canceled)(aCanceled);
1449}
1450
1451STDMETHODIMP CombinedProgress::COMGETTER(ResultCode)(LONG *aResultCode)
1452{
1453 CheckComArgOutPointerValid(aResultCode);
1454
1455 AutoCaller autoCaller(this);
1456 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1457
1458 /* checkProgress needs a write lock */
1459 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1460
1461 HRESULT rc = checkProgress();
1462 if (FAILED(rc)) return rc;
1463
1464 return ProgressBase::COMGETTER(ResultCode)(aResultCode);
1465}
1466
1467STDMETHODIMP CombinedProgress::COMGETTER(ErrorInfo)(IVirtualBoxErrorInfo **aErrorInfo)
1468{
1469 CheckComArgOutPointerValid(aErrorInfo);
1470
1471 AutoCaller autoCaller(this);
1472 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1473
1474 /* checkProgress needs a write lock */
1475 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1476
1477 HRESULT rc = checkProgress();
1478 if (FAILED(rc)) return rc;
1479
1480 return ProgressBase::COMGETTER(ErrorInfo)(aErrorInfo);
1481}
1482
1483STDMETHODIMP CombinedProgress::COMGETTER(Operation)(ULONG *aOperation)
1484{
1485 CheckComArgOutPointerValid(aOperation);
1486
1487 AutoCaller autoCaller(this);
1488 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1489
1490 /* checkProgress needs a write lock */
1491 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1492
1493 HRESULT rc = checkProgress();
1494 if (FAILED(rc)) return rc;
1495
1496 return ProgressBase::COMGETTER(Operation)(aOperation);
1497}
1498
1499STDMETHODIMP CombinedProgress::COMGETTER(OperationDescription)(BSTR *aOperationDescription)
1500{
1501 CheckComArgOutPointerValid(aOperationDescription);
1502
1503 AutoCaller autoCaller(this);
1504 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1505
1506 /* checkProgress needs a write lock */
1507 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1508
1509 HRESULT rc = checkProgress();
1510 if (FAILED(rc)) return rc;
1511
1512 return ProgressBase::COMGETTER(OperationDescription)(aOperationDescription);
1513}
1514
1515STDMETHODIMP CombinedProgress::COMGETTER(OperationPercent)(ULONG *aOperationPercent)
1516{
1517 CheckComArgOutPointerValid(aOperationPercent);
1518
1519 AutoCaller autoCaller(this);
1520 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1521
1522 /* checkProgress needs a write lock */
1523 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1524
1525 HRESULT rc = checkProgress();
1526 if (FAILED(rc)) return rc;
1527
1528 return ProgressBase::COMGETTER(OperationPercent)(aOperationPercent);
1529}
1530
1531STDMETHODIMP CombinedProgress::COMSETTER(Timeout)(ULONG aTimeout)
1532{
1533 NOREF(aTimeout);
1534 AssertFailed();
1535 return E_NOTIMPL;
1536}
1537
1538STDMETHODIMP CombinedProgress::COMGETTER(Timeout)(ULONG *aTimeout)
1539{
1540 CheckComArgOutPointerValid(aTimeout);
1541
1542 AssertFailed();
1543 return E_NOTIMPL;
1544}
1545
1546// IProgress methods
1547/////////////////////////////////////////////////////////////////////////////
1548
1549/**
1550 * @note XPCOM: when this method is called not on the main XPCOM thread, it
1551 * simply blocks the thread until mCompletedSem is signalled. If the
1552 * thread has its own event queue (hmm, what for?) that it must run, then
1553 * calling this method will definitely freeze event processing.
1554 */
1555STDMETHODIMP CombinedProgress::WaitForCompletion(LONG aTimeout)
1556{
1557 LogFlowThisFuncEnter();
1558 LogFlowThisFunc(("aTtimeout=%d\n", aTimeout));
1559
1560 AutoCaller autoCaller(this);
1561 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1562
1563 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1564
1565 /* if we're already completed, take a shortcut */
1566 if (!mCompleted)
1567 {
1568 HRESULT rc = S_OK;
1569 bool forever = aTimeout < 0;
1570 int64_t timeLeft = aTimeout;
1571 int64_t lastTime = RTTimeMilliTS();
1572
1573 while (!mCompleted && (forever || timeLeft > 0))
1574 {
1575 alock.leave();
1576 rc = mProgresses.back()->WaitForCompletion(forever ? -1 : (LONG) timeLeft);
1577 alock.enter();
1578
1579 if (SUCCEEDED(rc))
1580 rc = checkProgress();
1581
1582 if (FAILED(rc)) break;
1583
1584 if (!forever)
1585 {
1586 int64_t now = RTTimeMilliTS();
1587 timeLeft -= now - lastTime;
1588 lastTime = now;
1589 }
1590 }
1591
1592 if (FAILED(rc)) return rc;
1593 }
1594
1595 LogFlowThisFuncLeave();
1596
1597 return S_OK;
1598}
1599
1600/**
1601 * @note XPCOM: when this method is called not on the main XPCOM thread, it
1602 * simply blocks the thread until mCompletedSem is signalled. If the
1603 * thread has its own event queue (hmm, what for?) that it must run, then
1604 * calling this method will definitely freeze event processing.
1605 */
1606STDMETHODIMP CombinedProgress::WaitForOperationCompletion(ULONG aOperation, LONG aTimeout)
1607{
1608 LogFlowThisFuncEnter();
1609 LogFlowThisFunc(("aOperation=%d, aTimeout=%d\n", aOperation, aTimeout));
1610
1611 AutoCaller autoCaller(this);
1612 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1613
1614 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1615
1616 if (aOperation >= m_cOperations)
1617 return setError(E_FAIL,
1618 tr("Operation number must be in range [0, %d]"), m_ulCurrentOperation - 1);
1619
1620 /* if we're already completed or if the given operation is already done,
1621 * then take a shortcut */
1622 if (!mCompleted && aOperation >= m_ulCurrentOperation)
1623 {
1624 HRESULT rc = S_OK;
1625
1626 /* find the right progress object to wait for */
1627 size_t progress = mProgress;
1628 ULONG operation = 0, completedOps = mCompletedOperations;
1629 do
1630 {
1631 ULONG opCount = 0;
1632 rc = mProgresses[progress]->COMGETTER(OperationCount)(&opCount);
1633 if (FAILED(rc))
1634 return rc;
1635
1636 if (completedOps + opCount > aOperation)
1637 {
1638 /* found the right progress object */
1639 operation = aOperation - completedOps;
1640 break;
1641 }
1642
1643 completedOps += opCount;
1644 progress ++;
1645 ComAssertRet(progress < mProgresses.size(), E_FAIL);
1646 }
1647 while (1);
1648
1649 LogFlowThisFunc(("will wait for mProgresses [%d] (%d)\n",
1650 progress, operation));
1651
1652 bool forever = aTimeout < 0;
1653 int64_t timeLeft = aTimeout;
1654 int64_t lastTime = RTTimeMilliTS();
1655
1656 while (!mCompleted && aOperation >= m_ulCurrentOperation &&
1657 (forever || timeLeft > 0))
1658 {
1659 alock.leave();
1660 /* wait for the appropriate progress operation completion */
1661 rc = mProgresses[progress]-> WaitForOperationCompletion(operation,
1662 forever ? -1 : (LONG) timeLeft);
1663 alock.enter();
1664
1665 if (SUCCEEDED(rc))
1666 rc = checkProgress();
1667
1668 if (FAILED(rc)) break;
1669
1670 if (!forever)
1671 {
1672 int64_t now = RTTimeMilliTS();
1673 timeLeft -= now - lastTime;
1674 lastTime = now;
1675 }
1676 }
1677
1678 if (FAILED(rc)) return rc;
1679 }
1680
1681 LogFlowThisFuncLeave();
1682
1683 return S_OK;
1684}
1685
1686STDMETHODIMP CombinedProgress::Cancel()
1687{
1688 AutoCaller autoCaller(this);
1689 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1690
1691 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1692
1693 if (!mCancelable)
1694 return setError(E_FAIL, tr("Operation cannot be canceled"));
1695
1696 if (!mCanceled)
1697 {
1698 LogThisFunc(("Canceling\n"));
1699 mCanceled = TRUE;
1700/** @todo Teleportation: Shouldn't this be propagated to mProgresses? If
1701 * powerUp creates passes a combined progress object to the client, I
1702 * won't get called back since I'm only getting the powerupProgress ...
1703 * Or what? */
1704 if (m_pfnCancelCallback)
1705 m_pfnCancelCallback(m_pvCancelUserArg);
1706
1707 }
1708 else
1709 LogThisFunc(("Already canceled\n"));
1710
1711 return S_OK;
1712}
1713
1714// private methods
1715////////////////////////////////////////////////////////////////////////////////
1716
1717/**
1718 * Fetches the properties of the current progress object and, if it is
1719 * successfully completed, advances to the next uncompleted or unsuccessfully
1720 * completed object in the vector of combined progress objects.
1721 *
1722 * @note Must be called from under this object's write lock!
1723 */
1724HRESULT CombinedProgress::checkProgress()
1725{
1726 /* do nothing if we're already marked ourselves as completed */
1727 if (mCompleted)
1728 return S_OK;
1729
1730 AssertReturn(mProgress < mProgresses.size(), E_FAIL);
1731
1732 ComPtr<IProgress> progress = mProgresses[mProgress];
1733 ComAssertRet(!progress.isNull(), E_FAIL);
1734
1735 HRESULT rc = S_OK;
1736 BOOL fCompleted = FALSE;
1737
1738 do
1739 {
1740 rc = progress->COMGETTER(Completed)(&fCompleted);
1741 if (FAILED(rc))
1742 return rc;
1743
1744 if (fCompleted)
1745 {
1746 rc = progress->COMGETTER(Canceled)(&mCanceled);
1747 if (FAILED(rc))
1748 return rc;
1749
1750 LONG iRc;
1751 rc = progress->COMGETTER(ResultCode)(&iRc);
1752 if (FAILED(rc))
1753 return rc;
1754 mResultCode = iRc;
1755
1756 if (FAILED(mResultCode))
1757 {
1758 rc = progress->COMGETTER(ErrorInfo)(mErrorInfo.asOutParam());
1759 if (FAILED(rc))
1760 return rc;
1761 }
1762
1763 if (FAILED(mResultCode) || mCanceled)
1764 {
1765 mCompleted = TRUE;
1766 }
1767 else
1768 {
1769 ULONG opCount = 0;
1770 rc = progress->COMGETTER(OperationCount)(&opCount);
1771 if (FAILED(rc))
1772 return rc;
1773
1774 mCompletedOperations += opCount;
1775 mProgress ++;
1776
1777 if (mProgress < mProgresses.size())
1778 progress = mProgresses[mProgress];
1779 else
1780 mCompleted = TRUE;
1781 }
1782 }
1783 }
1784 while (fCompleted && !mCompleted);
1785
1786 rc = progress->COMGETTER(OperationPercent)(&m_ulOperationPercent);
1787 if (SUCCEEDED(rc))
1788 {
1789 ULONG operation = 0;
1790 rc = progress->COMGETTER(Operation)(&operation);
1791 if (SUCCEEDED(rc) && mCompletedOperations + operation > m_ulCurrentOperation)
1792 {
1793 m_ulCurrentOperation = mCompletedOperations + operation;
1794 rc = progress->COMGETTER(OperationDescription)(m_bstrOperationDescription.asOutParam());
1795 }
1796 }
1797
1798 return rc;
1799}
1800/* vi: set tabstop=4 shiftwidth=4 expandtab: */
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use