VirtualBox

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

Last change on this file since 62772 was 62485, checked in by vboxsync, 8 years ago

(C) 2016

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