VirtualBox

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

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

header (C) fixes

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