VirtualBox

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

Last change on this file since 73368 was 73003, checked in by vboxsync, 6 years ago

Main: Use setErrorBoth when we've got a VBox status code handy. (The COM status codes aren't too specfic and this may help us decode error messages and provide an alternative to strstr for API clients. setErrorBoth isn't new, btw.)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 34.0 KB
Line 
1/* $Id: ProgressImpl.cpp 73003 2018-07-09 11:09:32Z 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, tr("Result code is not available, operation is still in progress"));
711
712 *aResultCode = mResultCode;
713
714 return S_OK;
715}
716
717HRESULT Progress::getErrorInfo(ComPtr<IVirtualBoxErrorInfo> &aErrorInfo)
718{
719 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
720
721 if (!mCompleted)
722 return setError(E_FAIL, tr("Error info is not available, operation is still in progress"));
723
724 mErrorInfo.queryInterfaceTo(aErrorInfo.asOutParam());
725
726 return S_OK;
727}
728
729HRESULT Progress::getOperationCount(ULONG *aOperationCount)
730{
731 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
732
733 *aOperationCount = m_cOperations;
734
735 return S_OK;
736}
737
738HRESULT Progress::getOperation(ULONG *aOperation)
739{
740 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
741
742 *aOperation = m_ulCurrentOperation;
743
744 return S_OK;
745}
746
747HRESULT Progress::getOperationDescription(com::Utf8Str &aOperationDescription)
748{
749 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
750
751 aOperationDescription = m_operationDescription;
752
753 return S_OK;
754}
755
756HRESULT Progress::getOperationPercent(ULONG *aOperationPercent)
757{
758 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
759
760 if (mCompleted && SUCCEEDED(mResultCode))
761 *aOperationPercent = 100;
762 else
763 *aOperationPercent = m_ulOperationPercent;
764
765 return S_OK;
766}
767
768HRESULT Progress::getOperationWeight(ULONG *aOperationWeight)
769{
770 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
771
772 *aOperationWeight = m_ulCurrentOperationWeight;
773
774 return S_OK;
775}
776
777HRESULT Progress::getTimeout(ULONG *aTimeout)
778{
779 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
780
781 *aTimeout = m_cMsTimeout;
782
783 return S_OK;
784}
785
786HRESULT Progress::setTimeout(ULONG aTimeout)
787{
788 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
789
790 if (!mCancelable)
791 return setError(VBOX_E_INVALID_OBJECT_STATE, tr("Operation cannot be canceled"));
792 m_cMsTimeout = aTimeout;
793
794 return S_OK;
795}
796
797
798// IProgress methods
799/////////////////////////////////////////////////////////////////////////////
800
801/**
802 * Updates the percentage value of the current operation.
803 *
804 * @param aPercent New percentage value of the operation in progress
805 * (in range [0, 100]).
806 */
807HRESULT Progress::setCurrentOperationProgress(ULONG aPercent)
808{
809 AssertMsgReturn(aPercent <= 100, ("%u\n", aPercent), E_INVALIDARG);
810
811 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
812
813 i_checkForAutomaticTimeout();
814 if (mCancelable && mCanceled)
815 AssertReturn(!mCompleted, E_FAIL);
816 AssertReturn(!mCompleted && !mCanceled, E_FAIL);
817
818 if (m_ulOperationPercent != aPercent)
819 {
820 m_ulOperationPercent = aPercent;
821 ULONG actualPercent = 0;
822 getPercent(&actualPercent);
823 fireProgressPercentageChangedEvent(pEventSource, mId.toUtf16().raw(), actualPercent);
824 }
825
826 return S_OK;
827}
828
829/**
830 * Signals that the current operation is successfully completed and advances to
831 * the next operation. The operation percentage is reset to 0.
832 *
833 * @param aNextOperationDescription Description of the next operation.
834 * @param aNextOperationsWeight Weight of the next operation.
835 *
836 * @note The current operation must not be the last one.
837 */
838HRESULT Progress::setNextOperation(const com::Utf8Str &aNextOperationDescription, ULONG aNextOperationsWeight)
839{
840 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
841
842 if (mCanceled)
843 return E_FAIL;
844 AssertReturn(!mCompleted, E_FAIL);
845 AssertReturn(m_ulCurrentOperation + 1 < m_cOperations, E_FAIL);
846
847 ++m_ulCurrentOperation;
848 m_ulOperationsCompletedWeight += m_ulCurrentOperationWeight;
849
850 m_operationDescription = aNextOperationDescription;
851 m_ulCurrentOperationWeight = aNextOperationsWeight;
852 m_ulOperationPercent = 0;
853
854 LogThisFunc(("%s: aNextOperationsWeight = %d; m_ulCurrentOperation is now %d, m_ulOperationsCompletedWeight is now %d\n",
855 m_operationDescription.c_str(), aNextOperationsWeight, m_ulCurrentOperation, m_ulOperationsCompletedWeight));
856
857 /* wake up all waiting threads */
858 if (mWaitersCount > 0)
859 RTSemEventMultiSignal(mCompletedSem);
860
861 ULONG actualPercent = 0;
862 getPercent(&actualPercent);
863 fireProgressPercentageChangedEvent(pEventSource, mId.toUtf16().raw(), actualPercent);
864
865 return S_OK;
866}
867
868/**
869 * @note XPCOM: when this method is not called on the main XPCOM thread, it
870 * simply blocks the thread until mCompletedSem is signalled. If the
871 * thread has its own event queue (hmm, what for?) that it must run, then
872 * calling this method will definitely freeze event processing.
873 */
874HRESULT Progress::waitForCompletion(LONG aTimeout)
875{
876 LogFlowThisFuncEnter();
877 LogFlowThisFunc(("aTimeout=%d\n", aTimeout));
878
879 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
880
881 /* if we're already completed, take a shortcut */
882 if (!mCompleted)
883 {
884 int vrc = VINF_SUCCESS;
885 bool fForever = aTimeout < 0;
886 int64_t timeLeft = aTimeout;
887 int64_t lastTime = RTTimeMilliTS();
888
889 while (!mCompleted && (fForever || timeLeft > 0))
890 {
891 mWaitersCount++;
892 alock.release();
893 vrc = RTSemEventMultiWait(mCompletedSem,
894 fForever ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)timeLeft);
895 alock.acquire();
896 mWaitersCount--;
897
898 /* the last waiter resets the semaphore */
899 if (mWaitersCount == 0)
900 RTSemEventMultiReset(mCompletedSem);
901
902 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
903 break;
904
905 if (!fForever)
906 {
907 int64_t now = RTTimeMilliTS();
908 timeLeft -= now - lastTime;
909 lastTime = now;
910 }
911 }
912
913 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
914 return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Failed to wait for the task completion (%Rrc)"), vrc);
915 }
916
917 LogFlowThisFuncLeave();
918
919 return S_OK;
920}
921
922/**
923 * @note XPCOM: when this method is not called on the main XPCOM thread, it
924 * simply blocks the thread until mCompletedSem is signalled. If the
925 * thread has its own event queue (hmm, what for?) that it must run, then
926 * calling this method will definitely freeze event processing.
927 */
928HRESULT Progress::waitForOperationCompletion(ULONG aOperation, LONG aTimeout)
929
930{
931 LogFlowThisFuncEnter();
932 LogFlowThisFunc(("aOperation=%d, aTimeout=%d\n", aOperation, aTimeout));
933
934 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
935
936 CheckComArgExpr(aOperation, aOperation < m_cOperations);
937
938 /* if we're already completed or if the given operation is already done,
939 * then take a shortcut */
940 if ( !mCompleted
941 && aOperation >= m_ulCurrentOperation)
942 {
943 int vrc = VINF_SUCCESS;
944 bool fForever = aTimeout < 0;
945 int64_t timeLeft = aTimeout;
946 int64_t lastTime = RTTimeMilliTS();
947
948 while ( !mCompleted && aOperation >= m_ulCurrentOperation
949 && (fForever || timeLeft > 0))
950 {
951 mWaitersCount ++;
952 alock.release();
953 vrc = RTSemEventMultiWait(mCompletedSem,
954 fForever ? RT_INDEFINITE_WAIT : (unsigned) timeLeft);
955 alock.acquire();
956 mWaitersCount--;
957
958 /* the last waiter resets the semaphore */
959 if (mWaitersCount == 0)
960 RTSemEventMultiReset(mCompletedSem);
961
962 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
963 break;
964
965 if (!fForever)
966 {
967 int64_t now = RTTimeMilliTS();
968 timeLeft -= now - lastTime;
969 lastTime = now;
970 }
971 }
972
973 if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
974 return setErrorBoth(E_FAIL, vrc, tr("Failed to wait for the operation completion (%Rrc)"), vrc);
975 }
976
977 LogFlowThisFuncLeave();
978
979 return S_OK;
980}
981
982HRESULT Progress::waitForAsyncProgressCompletion(const ComPtr<IProgress> &aPProgressAsync)
983{
984 LogFlowThisFuncEnter();
985
986 /* Note: we don't lock here, cause we just using public methods. */
987
988 HRESULT rc = S_OK;
989 BOOL fCancelable = FALSE;
990 BOOL fCompleted = FALSE;
991 BOOL fCanceled = FALSE;
992 ULONG prevPercent = UINT32_MAX;
993 ULONG currentPercent = 0;
994 ULONG cOp = 0;
995 /* Is the async process cancelable? */
996 rc = aPProgressAsync->COMGETTER(Cancelable)(&fCancelable);
997 if (FAILED(rc)) return rc;
998 /* Loop as long as the sync process isn't completed. */
999 while (SUCCEEDED(aPProgressAsync->COMGETTER(Completed(&fCompleted))))
1000 {
1001 /* We can forward any cancel request to the async process only when
1002 * it is cancelable. */
1003 if (fCancelable)
1004 {
1005 rc = COMGETTER(Canceled)(&fCanceled);
1006 if (FAILED(rc)) return rc;
1007 if (fCanceled)
1008 {
1009 rc = aPProgressAsync->Cancel();
1010 if (FAILED(rc)) return rc;
1011 }
1012 }
1013 /* Even if the user canceled the process, we have to wait until the
1014 async task has finished his work (cleanup and such). Otherwise there
1015 will be sync trouble (still wrong state, dead locks, ...) on the
1016 used objects. So just do nothing, but wait for the complete
1017 notification. */
1018 if (!fCanceled)
1019 {
1020 /* Check if the current operation has changed. It is also possible that
1021 * in the meantime more than one async operation was finished. So we
1022 * have to loop as long as we reached the same operation count. */
1023 ULONG curOp;
1024 for (;;)
1025 {
1026 rc = aPProgressAsync->COMGETTER(Operation(&curOp));
1027 if (FAILED(rc)) return rc;
1028 if (cOp != curOp)
1029 {
1030 Bstr bstr;
1031 ULONG currentWeight;
1032 rc = aPProgressAsync->COMGETTER(OperationDescription(bstr.asOutParam()));
1033 if (FAILED(rc)) return rc;
1034 rc = aPProgressAsync->COMGETTER(OperationWeight(&currentWeight));
1035 if (FAILED(rc)) return rc;
1036 rc = SetNextOperation(bstr.raw(), currentWeight);
1037 if (FAILED(rc)) return rc;
1038 ++cOp;
1039 }
1040 else
1041 break;
1042 }
1043
1044 rc = aPProgressAsync->COMGETTER(OperationPercent(&currentPercent));
1045 if (FAILED(rc)) return rc;
1046 if (currentPercent != prevPercent)
1047 {
1048 prevPercent = currentPercent;
1049 rc = SetCurrentOperationProgress(currentPercent);
1050 if (FAILED(rc)) return rc;
1051 }
1052 }
1053 if (fCompleted)
1054 break;
1055
1056 /* Make sure the loop is not too tight */
1057 rc = aPProgressAsync->WaitForCompletion(100);
1058 if (FAILED(rc)) return rc;
1059 }
1060
1061 LogFlowThisFuncLeave();
1062
1063 return rc;
1064}
1065
1066HRESULT Progress::cancel()
1067{
1068 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1069
1070 if (!mCancelable)
1071 return setError(VBOX_E_INVALID_OBJECT_STATE, tr("Operation cannot be canceled"));
1072
1073 if (!mCanceled)
1074 {
1075 LogThisFunc(("Canceling\n"));
1076 mCanceled = TRUE;
1077 if (m_pfnCancelCallback)
1078 m_pfnCancelCallback(m_pvCancelUserArg);
1079
1080 }
1081 else
1082 LogThisFunc(("Already canceled\n"));
1083
1084 return S_OK;
1085}
1086
1087HRESULT Progress::getEventSource(ComPtr<IEventSource> &aEventSource)
1088{
1089 /* event source is const, no need to lock */
1090 pEventSource.queryInterfaceTo(aEventSource.asOutParam());
1091 return S_OK;
1092}
1093
1094// private internal helpers
1095/////////////////////////////////////////////////////////////////////////////
1096
1097/**
1098 * Internal helper to compute the total percent value based on the member values and
1099 * returns it as a "double". This is used both by GetPercent (which returns it as a
1100 * rounded ULONG) and GetTimeRemaining().
1101 *
1102 * Requires locking by the caller!
1103 *
1104 * @return fractional percentage as a double value.
1105 */
1106double Progress::i_calcTotalPercent()
1107{
1108 // avoid division by zero
1109 if (m_ulTotalOperationsWeight == 0)
1110 return 0.0;
1111
1112 double dPercent = ( (double)m_ulOperationsCompletedWeight // weight of operations that have been completed
1113 + ((double)m_ulOperationPercent *
1114 (double)m_ulCurrentOperationWeight / 100.0) // plus partial weight of the current operation
1115 ) * 100.0 / (double)m_ulTotalOperationsWeight;
1116
1117 return dPercent;
1118}
1119
1120/**
1121 * Internal helper for automatically timing out the operation.
1122 *
1123 * The caller must hold the object write lock.
1124 */
1125void Progress::i_checkForAutomaticTimeout(void)
1126{
1127 AssertReturnVoid(isWriteLockOnCurrentThread());
1128
1129 if ( m_cMsTimeout
1130 && mCancelable
1131 && !mCanceled
1132 && RTTimeMilliTS() - m_ullTimestamp > m_cMsTimeout)
1133 Cancel();
1134}
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