VirtualBox

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

Last change on this file since 72057 was 69500, checked in by vboxsync, 7 years ago

*: scm --update-copyright-year

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