VirtualBox

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

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

Main/Progress: eliminate useless copying of Utf8Str for method calls

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