VirtualBox

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

Last change on this file since 83321 was 82968, checked in by vboxsync, 5 years ago

Copyright year updates by scm.

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