VirtualBox

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

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

bugref:8674. Added 2 new events into the Progress - "percentage changed" and "task completed".

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 34.3 KB
Line 
1/* $Id: ProgressImpl.cpp 69002 2017-10-06 12:49:53Z vboxsync $ */
2/** @file
3 *
4 * VirtualBox Progress COM class implementation
5 */
6
7/*
8 * Copyright (C) 2006-2016 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->m_ulOperationPercent = RT_MIN(uPercentage, 100);
567 }
568 else
569 {
570 Assert(pThis->mCancelable);
571 vrc = VERR_CANCELLED;
572 }
573 ULONG actualPercent = 0;
574 pThis->getPercent(&actualPercent);
575 fireProgressPercentageChangedEvent(pThis->pEventSource, pThis->mId.toUtf16().raw(), actualPercent);
576 }
577 /* else ignored */
578 return vrc;
579}
580
581/**
582 * @callback_method_impl{FNVDPROGRESS,
583 * Progress::i_iprtProgressCallback with parameters switched around.}
584 */
585/*static*/ DECLCALLBACK(int) Progress::i_vdProgressCallback(void *pvUser, unsigned uPercentage)
586{
587 return i_iprtProgressCallback(uPercentage, pvUser);
588}
589
590// IProgress properties
591/////////////////////////////////////////////////////////////////////////////
592
593HRESULT Progress::getId(com::Guid &aId)
594{
595 /* mId is constant during life time, no need to lock */
596 aId = mId;
597
598 return S_OK;
599}
600
601HRESULT Progress::getDescription(com::Utf8Str &aDescription)
602{
603 /* mDescription is constant during life time, no need to lock */
604 aDescription = mDescription;
605
606 return S_OK;
607}
608HRESULT Progress::getInitiator(ComPtr<IUnknown> &aInitiator)
609{
610 /* mInitiator/mParent are constant during life time, no need to lock */
611#if !defined(VBOX_COM_INPROC)
612 if (mInitiator)
613 mInitiator.queryInterfaceTo(aInitiator.asOutParam());
614 else
615 {
616 ComObjPtr<VirtualBox> pVirtualBox(mParent);
617 pVirtualBox.queryInterfaceTo(aInitiator.asOutParam());
618 }
619#else
620 mInitiator.queryInterfaceTo(aInitiator.asOutParam());
621#endif
622
623 return S_OK;
624}
625
626HRESULT Progress::getCancelable(BOOL *aCancelable)
627{
628 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
629
630 *aCancelable = mCancelable;
631
632 return S_OK;
633}
634
635HRESULT Progress::getPercent(ULONG *aPercent)
636{
637 /* i_checkForAutomaticTimeout requires a write lock. */
638 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
639
640 if (mCompleted && SUCCEEDED(mResultCode))
641 *aPercent = 100;
642 else
643 {
644 ULONG ulPercent = (ULONG)i_calcTotalPercent();
645 // do not report 100% until we're really really done with everything
646 // as the Qt GUI dismisses progress dialogs in that case
647 if ( ulPercent == 100
648 && ( m_ulOperationPercent < 100
649 || (m_ulCurrentOperation < m_cOperations -1)
650 )
651 )
652 *aPercent = 99;
653 else
654 *aPercent = ulPercent;
655 }
656
657 i_checkForAutomaticTimeout();
658
659 return S_OK;
660}
661
662HRESULT Progress::getTimeRemaining(LONG *aTimeRemaining)
663{
664 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
665
666 if (mCompleted)
667 *aTimeRemaining = 0;
668 else
669 {
670 double dPercentDone = i_calcTotalPercent();
671 if (dPercentDone < 1)
672 *aTimeRemaining = -1; // unreliable, or avoid division by 0 below
673 else
674 {
675 uint64_t ullTimeNow = RTTimeMilliTS();
676 uint64_t ullTimeElapsed = ullTimeNow - m_ullTimestamp;
677 uint64_t ullTimeTotal = (uint64_t)((double)ullTimeElapsed * 100 / dPercentDone);
678 uint64_t ullTimeRemaining = ullTimeTotal - ullTimeElapsed;
679
680// LogFunc(("dPercentDone = %RI32, ullTimeNow = %RI64, ullTimeElapsed = %RI64, ullTimeTotal = %RI64, ullTimeRemaining = %RI64\n",
681// (uint32_t)dPercentDone, ullTimeNow, ullTimeElapsed, ullTimeTotal, ullTimeRemaining));
682
683 *aTimeRemaining = (LONG)(ullTimeRemaining / 1000);
684 }
685 }
686
687 return S_OK;
688}
689
690HRESULT Progress::getCompleted(BOOL *aCompleted)
691{
692 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
693
694 *aCompleted = mCompleted;
695
696 return S_OK;
697}
698
699HRESULT Progress::getCanceled(BOOL *aCanceled)
700{
701 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
702
703 *aCanceled = mCanceled;
704
705 return S_OK;
706}
707
708HRESULT Progress::getResultCode(LONG *aResultCode)
709{
710 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
711
712 if (!mCompleted)
713 return setError(E_FAIL,
714 tr("Result code is not available, operation is still in progress"));
715
716 *aResultCode = mResultCode;
717
718 return S_OK;
719}
720
721HRESULT Progress::getErrorInfo(ComPtr<IVirtualBoxErrorInfo> &aErrorInfo)
722{
723 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
724
725 if (!mCompleted)
726 return setError(E_FAIL,
727 tr("Error info is not available, operation is still in progress"));
728
729 mErrorInfo.queryInterfaceTo(aErrorInfo.asOutParam());
730
731 return S_OK;
732}
733
734HRESULT Progress::getOperationCount(ULONG *aOperationCount)
735{
736 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
737
738 *aOperationCount = m_cOperations;
739
740 return S_OK;
741}
742
743HRESULT Progress::getOperation(ULONG *aOperation)
744{
745 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
746
747 *aOperation = m_ulCurrentOperation;
748
749 return S_OK;
750}
751
752HRESULT Progress::getOperationDescription(com::Utf8Str &aOperationDescription)
753{
754 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
755
756 aOperationDescription = m_operationDescription;
757
758 return S_OK;
759}
760
761HRESULT Progress::getOperationPercent(ULONG *aOperationPercent)
762{
763 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
764
765 if (mCompleted && SUCCEEDED(mResultCode))
766 *aOperationPercent = 100;
767 else
768 *aOperationPercent = m_ulOperationPercent;
769
770 return S_OK;
771}
772
773HRESULT Progress::getOperationWeight(ULONG *aOperationWeight)
774{
775 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
776
777 *aOperationWeight = m_ulCurrentOperationWeight;
778
779 return S_OK;
780}
781
782HRESULT Progress::getTimeout(ULONG *aTimeout)
783{
784 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
785
786 *aTimeout = m_cMsTimeout;
787
788 return S_OK;
789}
790
791HRESULT Progress::setTimeout(ULONG aTimeout)
792{
793 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
794
795 if (!mCancelable)
796 return setError(VBOX_E_INVALID_OBJECT_STATE,
797 tr("Operation cannot be canceled"));
798 m_cMsTimeout = aTimeout;
799
800 return S_OK;
801}
802
803
804// IProgress methods
805/////////////////////////////////////////////////////////////////////////////
806
807/**
808 * Updates the percentage value of the current operation.
809 *
810 * @param aPercent New percentage value of the operation in progress
811 * (in range [0, 100]).
812 */
813HRESULT Progress::setCurrentOperationProgress(ULONG aPercent)
814{
815 AssertMsgReturn(aPercent <= 100, ("%u\n", aPercent), E_INVALIDARG);
816
817 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
818
819 i_checkForAutomaticTimeout();
820 if (mCancelable && mCanceled)
821 AssertReturn(!mCompleted, E_FAIL);
822 AssertReturn(!mCompleted && !mCanceled, E_FAIL);
823
824 m_ulOperationPercent = aPercent;
825
826 ULONG actualPercent = 0;
827 getPercent(&actualPercent);
828 fireProgressPercentageChangedEvent(pEventSource, mId.toUtf16().raw(), actualPercent);
829
830 return S_OK;
831}
832
833/**
834 * Signals that the current operation is successfully completed and advances to
835 * the next operation. The operation percentage is reset to 0.
836 *
837 * @param aNextOperationDescription Description of the next operation.
838 * @param aNextOperationsWeight Weight of the next operation.
839 *
840 * @note The current operation must not be the last one.
841 */
842HRESULT Progress::setNextOperation(const com::Utf8Str &aNextOperationDescription, ULONG aNextOperationsWeight)
843{
844 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
845
846 if (mCanceled)
847 return E_FAIL;
848 AssertReturn(!mCompleted, E_FAIL);
849 AssertReturn(m_ulCurrentOperation + 1 < m_cOperations, E_FAIL);
850
851 ++m_ulCurrentOperation;
852 m_ulOperationsCompletedWeight += m_ulCurrentOperationWeight;
853
854 m_operationDescription = aNextOperationDescription;
855 m_ulCurrentOperationWeight = aNextOperationsWeight;
856 m_ulOperationPercent = 0;
857
858 LogThisFunc(("%s: aNextOperationsWeight = %d; m_ulCurrentOperation is now %d, m_ulOperationsCompletedWeight is now %d\n",
859 m_operationDescription.c_str(), aNextOperationsWeight, m_ulCurrentOperation, m_ulOperationsCompletedWeight));
860
861 /* wake up all waiting threads */
862 if (mWaitersCount > 0)
863 RTSemEventMultiSignal(mCompletedSem);
864
865 ULONG actualPercent = 0;
866 getPercent(&actualPercent);
867 fireProgressPercentageChangedEvent(pEventSource, mId.toUtf16().raw(), actualPercent);
868
869 return S_OK;
870}
871
872/**
873 * @note XPCOM: when this method is not called on the main XPCOM thread, it
874 * simply blocks the thread until mCompletedSem is signalled. If the
875 * thread has its own event queue (hmm, what for?) that it must run, then
876 * calling this method will definitely freeze event processing.
877 */
878HRESULT Progress::waitForCompletion(LONG aTimeout)
879{
880 LogFlowThisFuncEnter();
881 LogFlowThisFunc(("aTimeout=%d\n", aTimeout));
882
883 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
884
885 /* if we're already completed, take a shortcut */
886 if (!mCompleted)
887 {
888 int vrc = VINF_SUCCESS;
889 bool fForever = aTimeout < 0;
890 int64_t timeLeft = aTimeout;
891 int64_t lastTime = RTTimeMilliTS();
892
893 while (!mCompleted && (fForever || timeLeft > 0))
894 {
895 mWaitersCount++;
896 alock.release();
897 vrc = RTSemEventMultiWait(mCompletedSem,
898 fForever ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)timeLeft);
899 alock.acquire();
900 mWaitersCount--;
901
902 /* the last waiter resets the semaphore */
903 if (mWaitersCount == 0)
904 RTSemEventMultiReset(mCompletedSem);
905
906 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
907 break;
908
909 if (!fForever)
910 {
911 int64_t now = RTTimeMilliTS();
912 timeLeft -= now - lastTime;
913 lastTime = now;
914 }
915 }
916
917 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
918 return setError(VBOX_E_IPRT_ERROR,
919 tr("Failed to wait for the task completion (%Rrc)"),
920 vrc);
921 }
922
923 LogFlowThisFuncLeave();
924
925 return S_OK;
926}
927
928/**
929 * @note XPCOM: when this method is not called on the main XPCOM thread, it
930 * simply blocks the thread until mCompletedSem is signalled. If the
931 * thread has its own event queue (hmm, what for?) that it must run, then
932 * calling this method will definitely freeze event processing.
933 */
934HRESULT Progress::waitForOperationCompletion(ULONG aOperation, LONG aTimeout)
935
936{
937 LogFlowThisFuncEnter();
938 LogFlowThisFunc(("aOperation=%d, aTimeout=%d\n", aOperation, aTimeout));
939
940 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
941
942 CheckComArgExpr(aOperation, aOperation < m_cOperations);
943
944 /* if we're already completed or if the given operation is already done,
945 * then take a shortcut */
946 if ( !mCompleted
947 && aOperation >= m_ulCurrentOperation)
948 {
949 int vrc = VINF_SUCCESS;
950 bool fForever = aTimeout < 0;
951 int64_t timeLeft = aTimeout;
952 int64_t lastTime = RTTimeMilliTS();
953
954 while ( !mCompleted && aOperation >= m_ulCurrentOperation
955 && (fForever || timeLeft > 0))
956 {
957 mWaitersCount ++;
958 alock.release();
959 vrc = RTSemEventMultiWait(mCompletedSem,
960 fForever ? RT_INDEFINITE_WAIT : (unsigned) timeLeft);
961 alock.acquire();
962 mWaitersCount--;
963
964 /* the last waiter resets the semaphore */
965 if (mWaitersCount == 0)
966 RTSemEventMultiReset(mCompletedSem);
967
968 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
969 break;
970
971 if (!fForever)
972 {
973 int64_t now = RTTimeMilliTS();
974 timeLeft -= now - lastTime;
975 lastTime = now;
976 }
977 }
978
979 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
980 return setError(E_FAIL,
981 tr("Failed to wait for the operation completion (%Rrc)"),
982 vrc);
983 }
984
985 LogFlowThisFuncLeave();
986
987 return S_OK;
988}
989
990HRESULT Progress::waitForAsyncProgressCompletion(const ComPtr<IProgress> &aPProgressAsync)
991{
992 LogFlowThisFuncEnter();
993
994 /* Note: we don't lock here, cause we just using public methods. */
995
996 HRESULT rc = S_OK;
997 BOOL fCancelable = FALSE;
998 BOOL fCompleted = FALSE;
999 BOOL fCanceled = FALSE;
1000 ULONG prevPercent = UINT32_MAX;
1001 ULONG currentPercent = 0;
1002 ULONG cOp = 0;
1003 /* Is the async process cancelable? */
1004 rc = aPProgressAsync->COMGETTER(Cancelable)(&fCancelable);
1005 if (FAILED(rc)) return rc;
1006 /* Loop as long as the sync process isn't completed. */
1007 while (SUCCEEDED(aPProgressAsync->COMGETTER(Completed(&fCompleted))))
1008 {
1009 /* We can forward any cancel request to the async process only when
1010 * it is cancelable. */
1011 if (fCancelable)
1012 {
1013 rc = COMGETTER(Canceled)(&fCanceled);
1014 if (FAILED(rc)) return rc;
1015 if (fCanceled)
1016 {
1017 rc = aPProgressAsync->Cancel();
1018 if (FAILED(rc)) return rc;
1019 }
1020 }
1021 /* Even if the user canceled the process, we have to wait until the
1022 async task has finished his work (cleanup and such). Otherwise there
1023 will be sync trouble (still wrong state, dead locks, ...) on the
1024 used objects. So just do nothing, but wait for the complete
1025 notification. */
1026 if (!fCanceled)
1027 {
1028 /* Check if the current operation has changed. It is also possible that
1029 * in the meantime more than one async operation was finished. So we
1030 * have to loop as long as we reached the same operation count. */
1031 ULONG curOp;
1032 for (;;)
1033 {
1034 rc = aPProgressAsync->COMGETTER(Operation(&curOp));
1035 if (FAILED(rc)) return rc;
1036 if (cOp != curOp)
1037 {
1038 Bstr bstr;
1039 ULONG currentWeight;
1040 rc = aPProgressAsync->COMGETTER(OperationDescription(bstr.asOutParam()));
1041 if (FAILED(rc)) return rc;
1042 rc = aPProgressAsync->COMGETTER(OperationWeight(&currentWeight));
1043 if (FAILED(rc)) return rc;
1044 rc = SetNextOperation(bstr.raw(), currentWeight);
1045 if (FAILED(rc)) return rc;
1046 ++cOp;
1047 }
1048 else
1049 break;
1050 }
1051
1052 rc = aPProgressAsync->COMGETTER(OperationPercent(&currentPercent));
1053 if (FAILED(rc)) return rc;
1054 if (currentPercent != prevPercent)
1055 {
1056 prevPercent = currentPercent;
1057 rc = SetCurrentOperationProgress(currentPercent);
1058 if (FAILED(rc)) return rc;
1059 }
1060 }
1061 if (fCompleted)
1062 break;
1063
1064 /* Make sure the loop is not too tight */
1065 rc = aPProgressAsync->WaitForCompletion(100);
1066 if (FAILED(rc)) return rc;
1067 }
1068
1069 LogFlowThisFuncLeave();
1070
1071 return rc;
1072}
1073
1074HRESULT Progress::cancel()
1075{
1076 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1077
1078 if (!mCancelable)
1079 return setError(VBOX_E_INVALID_OBJECT_STATE,
1080 tr("Operation cannot be canceled"));
1081
1082 if (!mCanceled)
1083 {
1084 LogThisFunc(("Canceling\n"));
1085 mCanceled = TRUE;
1086 if (m_pfnCancelCallback)
1087 m_pfnCancelCallback(m_pvCancelUserArg);
1088
1089 }
1090 else
1091 LogThisFunc(("Already canceled\n"));
1092
1093 return S_OK;
1094}
1095
1096HRESULT Progress::getEventSource(ComPtr<IEventSource> &aEventSource)
1097{
1098 /* event source is const, no need to lock */
1099 pEventSource.queryInterfaceTo(aEventSource.asOutParam());
1100 return S_OK;
1101}
1102
1103// private internal helpers
1104/////////////////////////////////////////////////////////////////////////////
1105
1106/**
1107 * Internal helper to compute the total percent value based on the member values and
1108 * returns it as a "double". This is used both by GetPercent (which returns it as a
1109 * rounded ULONG) and GetTimeRemaining().
1110 *
1111 * Requires locking by the caller!
1112 *
1113 * @return fractional percentage as a double value.
1114 */
1115double Progress::i_calcTotalPercent()
1116{
1117 // avoid division by zero
1118 if (m_ulTotalOperationsWeight == 0)
1119 return 0.0;
1120
1121 double dPercent = ( (double)m_ulOperationsCompletedWeight // weight of operations that have been completed
1122 + ((double)m_ulOperationPercent *
1123 (double)m_ulCurrentOperationWeight / 100.0) // plus partial weight of the current operation
1124 ) * 100.0 / (double)m_ulTotalOperationsWeight;
1125
1126 return dPercent;
1127}
1128
1129/**
1130 * Internal helper for automatically timing out the operation.
1131 *
1132 * The caller must hold the object write lock.
1133 */
1134void Progress::i_checkForAutomaticTimeout(void)
1135{
1136 AssertReturnVoid(isWriteLockOnCurrentThread());
1137
1138 if ( m_cMsTimeout
1139 && mCancelable
1140 && !mCanceled
1141 && RTTimeMilliTS() - m_ullTimestamp > m_cMsTimeout)
1142 Cancel();
1143}
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