VirtualBox

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

Last change on this file since 51441 was 51441, checked in by vboxsync, 11 years ago

Main: code formatting.

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