VirtualBox

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

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

Main/ProgresImpl: Added a callback method for VD as well (i_vdProgressCallback). Identical to the IPRT one, except the parameters are switched around.

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