VirtualBox

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

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

Main: do not include VirtualBoxImpl.h from code ending in VBoxC (causes unnecessary rebuilds), and make sure that the code still builds with VBOX_WITH_RESOURCE_USAGE_API unset

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