ProgressImpl.cpp revision 939774a6cffc83ff3407a4678b56df359df8db28
/* $Id$ */
/** @file
*
* VirtualBox Progress COM class implementation
*/
/*
* Copyright (C) 2006-2010 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
#if defined(VBOX_WITH_XPCOM)
#include <nsIServiceManager.h>
#include <nsIExceptionService.h>
#include <nsCOMPtr.h>
#endif /* defined(VBOX_WITH_XPCOM) */
#include "ProgressCombinedImpl.h"
#include "VirtualBoxImpl.h"
#include "VirtualBoxErrorInfoImpl.h"
#include "Logging.h"
#include <iprt/semaphore.h>
////////////////////////////////////////////////////////////////////////////////
// ProgressBase class
////////////////////////////////////////////////////////////////////////////////
// constructor / destructor
////////////////////////////////////////////////////////////////////////////////
#if !defined(VBOX_COM_INPROC)
#endif
{
}
{
}
/**
* Subclasses must call this method from their FinalConstruct() implementations.
*/
{
mCancelable = FALSE;
mCompleted = FALSE;
mResultCode = S_OK;
= 0;
// get creation timestamp
return S_OK;
}
// protected initializer/uninitializer for internal purposes only
////////////////////////////////////////////////////////////////////////////////
/**
* Initializes the progress base object.
*
* Subclasses should call this or any other #protectedInit() method from their
* init() implementations.
*
* @param aAutoInitSpan AutoInitSpan object instantiated by a subclass.
* @param aParent Parent object (only for server-side Progress objects).
* @param aInitiator Initiator of the task (for server-side objects. Can be
* NULL which means initiator = parent, otherwise must not
* be NULL).
* @param aDescription ask description.
* @param aID Address of result GUID structure (optional).
*
* @return COM result indicator.
*/
#if !defined(VBOX_COM_INPROC)
#endif
{
/* Guarantees subclasses call this method at the proper time */
AutoCaller autoCaller(this);
#if !defined(VBOX_COM_INPROC)
#else
#endif
#if !defined(VBOX_COM_INPROC)
/* share parent weakly */
#endif
#if !defined(VBOX_COM_INPROC)
/* assign (and therefore addref) initiator only if it is not VirtualBox
* (to avoid cycling); otherwise mInitiator will remain null which means
* that it is the same as the parent */
if (aInitiator)
{
if (!(pVirtualBox == aInitiator))
}
#else
#endif
if (aId)
#if !defined(VBOX_COM_INPROC)
/* add to the global collection of progress operations (note: after
* creating mId) */
mParent->addProgress(this);
#endif
return S_OK;
}
/**
* Initializes the progress base object.
*
* This is a special initializer that doesn't initialize any field. Used by one
* of the Progress::init() forms to create sub-progress operations combined
* together using a CombinedProgress instance, so it doesn't require the parent,
* initiator, description and doesn't create an ID.
*
* Subclasses should call this or any other #protectedInit() method from their
* init() implementations.
*
* @param aAutoInitSpan AutoInitSpan object instantiated by a subclass.
*/
{
/* Guarantees subclasses call this method at the proper time */
return S_OK;
}
/**
* Uninitializes the instance.
*
* Subclasses should call this from their uninit() implementations.
*
* @param aAutoUninitSpan AutoUninitSpan object instantiated by a subclass.
*
* @note Using the mParent member after this method returns is forbidden.
*/
{
/* release initiator (effective only if mInitiator has been assigned in
* init()) */
#if !defined(VBOX_COM_INPROC)
if (mParent)
{
/* remove the added progress on failure to complete the initialization */
}
#endif
}
// IProgress properties
/////////////////////////////////////////////////////////////////////////////
{
AutoCaller autoCaller(this);
/* mId is constant during life time, no need to lock */
return S_OK;
}
{
AutoCaller autoCaller(this);
/* mDescription is constant during life time, no need to lock */
return S_OK;
}
{
AutoCaller autoCaller(this);
/* mInitiator/mParent are constant during life time, no need to lock */
#if !defined(VBOX_COM_INPROC)
if (mInitiator)
else
{
}
#else
#endif
return S_OK;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
/**
* Internal helper to compute the total percent value based on the member values and
* returns it as a "double". This is used both by GetPercent (which returns it as a
* rounded ULONG) and GetTimeRemaining().
*
* Requires locking by the caller!
*
* @return fractional percentage as a double value.
*/
double ProgressBase::calcTotalPercent()
{
// avoid division by zero
if (m_ulTotalOperationsWeight == 0)
return 0;
double dPercent = ( (double)m_ulOperationsCompletedWeight // weight of operations that have been completed
+ ((double)m_ulOperationPercent * (double)m_ulCurrentOperationWeight / (double)100) // plus partial weight of the current operation
) * (double)100 / (double)m_ulTotalOperationsWeight;
return dPercent;
}
/**
* Internal helper for automatically timing out the operation.
*
* The caller should hold the object write lock.
*/
void ProgressBase::checkForAutomaticTimeout(void)
{
if ( m_cMsTimeout
&& mCancelable
&& !mCanceled
)
Cancel();
}
{
AutoCaller autoCaller(this);
if (mCompleted)
*aTimeRemaining = 0;
else
{
double dPercentDone = calcTotalPercent();
if (dPercentDone < 1)
else
{
// Log(("ProgressBase::GetTimeRemaining: dPercentDone %RI32, ullTimeNow = %RI64, ullTimeElapsed = %RI64, ullTimeTotal = %RI64, ullTimeRemaining = %RI64\n",
// (uint32_t)dPercentDone, ullTimeNow, ullTimeElapsed, ullTimeTotal, ullTimeRemaining));
}
}
return S_OK;
}
{
AutoCaller autoCaller(this);
/* checkForAutomaticTimeout requires a write lock. */
*aPercent = 100;
else
{
// do not report 100% until we're really really done with everything as the Qt GUI dismisses progress dialogs in that case
if ( ulPercent == 100
&& ( m_ulOperationPercent < 100
)
)
*aPercent = 99;
else
}
return S_OK;
}
{
AutoCaller autoCaller(this);
*aCompleted = mCompleted;
return S_OK;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
if (!mCompleted)
tr("Result code is not available, operation is still in progress"));
return S_OK;
}
{
AutoCaller autoCaller(this);
if (!mCompleted)
tr("Error info is not available, operation is still in progress"));
return S_OK;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
*aOperationPercent = 100;
else
return S_OK;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
if (!mCancelable)
return setError(VBOX_E_INVALID_OBJECT_STATE,
tr("Operation cannot be canceled"));
return S_OK;
}
{
AutoCaller autoCaller(this);
*aTimeout = m_cMsTimeout;
return S_OK;
}
// public methods only for internal purposes
////////////////////////////////////////////////////////////////////////////////
/**
* Sets the cancelation callback, checking for cancelation first.
*
* @returns Success indicator.
* @retval true on success.
* @retval false if the progress object has already been canceled or is in an
* invalid state
*
* @param pfnCallback The function to be called upon cancelation.
* @param pvUser The callback argument.
*/
{
AutoCaller autoCaller(this);
if (mCanceled)
return false;
return true;
}
////////////////////////////////////////////////////////////////////////////////
// Progress class
////////////////////////////////////////////////////////////////////////////////
{
mWaitersCount = 0;
return S_OK;
}
void Progress::FinalRelease()
{
uninit();
}
// public initializer/uninitializer for internal purposes only
////////////////////////////////////////////////////////////////////////////////
/**
* Initializes the normal progress object. With this variant, one can have
* an arbitrary number of sub-operation which IProgress can analyze to
* have a weighted progress computed.
*
* For example, say that one IProgress is supposed to track the cloning
* of two hard disk images, which are 100 MB and 1000 MB in size, respectively,
* and each of these hard disks should be one sub-operation of the IProgress.
*
* Obviously the progress would be misleading if the progress displayed 50%
* after the smaller image was cloned and would then take much longer for
* the second half.
*
* With weighted progress, one can invoke the following calls:
*
* 1) create progress object with cOperations = 2 and ulTotalOperationsWeight =
* 1100 (100 MB plus 1100, but really the weights can be any ULONG); pass
* in ulFirstOperationWeight = 100 for the first sub-operation
*
* 2) Then keep calling setCurrentOperationProgress() with a percentage
* for the first image; the total progress will increase up to a value
* of 9% (100MB / 1100MB * 100%).
*
* 3) Then call setNextOperation with the second weight (1000 for the megabytes
* of the second disk).
*
* 4) Then keep calling setCurrentOperationProgress() with a percentage for
* the second image, where 100% of the operation will then yield a 100%
* progress of the entire task.
*
* Weighting is optional; you can simply assign a weight of 1 to each operation
* and pass ulTotalOperationsWeight == cOperations to this constructor (but
* for that variant and for backwards-compatibility a simpler constructor exists
* in ProgressImpl.h as well).
*
* Even simpler, if you need no sub-operations at all, pass in cOperations =
* ulTotalOperationsWeight = ulFirstOperationWeight = 1.
*
* @param aParent See ProgressBase::init().
* @param aInitiator See ProgressBase::init().
* @param aDescription See ProgressBase::init().
* @param aCancelable Flag whether the task maybe canceled.
* @param cOperations Number of operations within this task (at least 1).
* @param ulTotalOperationsWeight Total weight of operations; must be the sum of ulFirstOperationWeight and
* what is later passed with each subsequent setNextOperation() call.
* @param bstrFirstOperationDescription Description of the first operation.
* @param ulFirstOperationWeight Weight of first sub-operation.
* @param aId See ProgressBase::init().
*/
#if !defined(VBOX_COM_INPROC)
#endif
{
LogFlowThisFunc(("aDescription=\"%ls\", cOperations=%d, ulTotalOperationsWeight=%d, bstrFirstOperationDescription=\"%ls\", ulFirstOperationWeight=%d\n",
/* Enclose the state transition NotReady->InInit->Ready */
AutoInitSpan autoInitSpan(this);
#if !defined(VBOX_COM_INPROC)
#endif
m_ulCurrentOperation = 0;
m_ulOperationPercent = 0;
/* Confirm a successful initialization when it's the case */
return rc;
}
/**
* Initializes the sub-progress object that represents a specific operation of
* the whole task.
*
* Objects initialized with this method are then combined together into the
* single task using a CombinedProgress instance, so it doesn't require the
* parent, initiator, description and doesn't create an ID. Note that calling
* respective getter methods on an object initialized with this method is
* useless. Such objects are used only to provide a separate wait semaphore and
* store individual operation descriptions.
*
* @param aCancelable Flag whether the task maybe canceled.
* @param aOperationCount Number of sub-operations within this task (at least 1).
* @param aOperationDescription Description of the individual operation.
*/
{
/* Enclose the state transition NotReady->InInit->Ready */
AutoInitSpan autoInitSpan(this);
// for this variant we assume for now that all operations are weighed "1"
// and equal total weight = operation count
m_ulCurrentOperation = 0;
m_ulOperationPercent = 0;
/* Confirm a successful initialization when it's the case */
return rc;
}
/**
* Uninitializes the instance and sets the ready flag to FALSE.
*
* Called either from FinalRelease() or by the parent when it gets destroyed.
*/
{
LogFlowThisFunc(("\n"));
/* Enclose the state transition Ready->InUninit->NotReady */
AutoUninitSpan autoUninitSpan(this);
if (autoUninitSpan.uninitDone())
return;
/* wake up all threads still waiting on occasion */
if (mWaitersCount > 0)
{
LogFlow(("WARNING: There are still %d threads waiting for '%ls' completion!\n",
}
}
// IProgress properties
/////////////////////////////////////////////////////////////////////////////
// IProgress methods
/////////////////////////////////////////////////////////////////////////////
/**
* @note XPCOM: when this method is not called on the main XPCOM thread, it
* simply blocks the thread until mCompletedSem is signalled. If the
* thread has its own event queue (hmm, what for?) that it must run, then
* calling this method will definitely freeze event processing.
*/
{
AutoCaller autoCaller(this);
/* if we're already completed, take a shortcut */
if (!mCompleted)
{
int vrc = VINF_SUCCESS;
{
/* the last waiter resets the semaphore */
if (mWaitersCount == 0)
break;
if (!fForever)
{
}
}
return setError(VBOX_E_IPRT_ERROR,
tr("Failed to wait for the task completion (%Rrc)"),
vrc);
}
return S_OK;
}
/**
* @note XPCOM: when this method is not called on the main XPCOM thread, it
* simply blocks the thread until mCompletedSem is signalled. If the
* thread has its own event queue (hmm, what for?) that it must run, then
* calling this method will definitely freeze event processing.
*/
{
AutoCaller autoCaller(this);
/* if we're already completed or if the given operation is already done,
* then take a shortcut */
if ( !mCompleted
&& aOperation >= m_ulCurrentOperation)
{
int vrc = VINF_SUCCESS;
{
mWaitersCount ++;
/* the last waiter resets the semaphore */
if (mWaitersCount == 0)
break;
if (!fForever)
{
}
}
tr("Failed to wait for the operation completion (%Rrc)"),
vrc);
}
return S_OK;
}
{
AutoCaller autoCaller(this);
if (!mCancelable)
return setError(VBOX_E_INVALID_OBJECT_STATE,
tr("Operation cannot be canceled"));
if (!mCanceled)
{
LogThisFunc(("Canceling\n"));
if (m_pfnCancelCallback)
}
else
LogThisFunc(("Already canceled\n"));
return S_OK;
}
/**
* Updates the percentage value of the current operation.
*
* @param aPercent New percentage value of the operation in progress
* (in range [0, 100]).
*/
{
AutoCaller autoCaller(this);
if (mCancelable && mCanceled)
{
Assert(!mCompleted);
return E_FAIL;
}
return S_OK;
}
/**
* Signals that the current operation is successfully completed and advances to
* the next operation. The operation percentage is reset to 0.
*
* @param aOperationDescription Description of the next operation.
*
* @note The current operation must not be the last one.
*/
STDMETHODIMP Progress::SetNextOperation(IN_BSTR bstrNextOperationDescription, ULONG ulNextOperationsWeight)
{
AutoCaller autoCaller(this);
if (mCanceled)
return E_FAIL;
m_ulOperationPercent = 0;
Log(("Progress::setNextOperation(%ls): ulNextOperationsWeight = %d; m_ulCurrentOperation is now %d, m_ulOperationsCompletedWeight is now %d\n",
m_bstrOperationDescription.raw(), ulNextOperationsWeight, m_ulCurrentOperation, m_ulOperationsCompletedWeight));
/* wake up all waiting threads */
if (mWaitersCount > 0)
return S_OK;
}
// public methods only for internal purposes
/////////////////////////////////////////////////////////////////////////////
/**
* Sets the internal result code and attempts to retrieve additional error
* info from the current thread. Gets called from Progress::notifyComplete(),
* but can be called again to override a previous result set with
* notifyComplete().
*
* @param aResultCode
*/
{
AutoCaller autoCaller(this);
if (FAILED(aResultCode))
{
/* try to import error info from the current thread */
#if !defined(VBOX_WITH_XPCOM)
{
}
#else /* !defined(VBOX_WITH_XPCOM) */
if (NS_SUCCEEDED(rc))
{
if (NS_SUCCEEDED(rc))
{
{
}
}
}
#endif /* !defined(VBOX_WITH_XPCOM) */
AssertMsg(rc == S_OK, ("Couldn't get error info (rc=%08X) while trying to set a failed result (%08X)!\n",
rc, aResultCode));
}
return rc;
}
/**
* Marks the whole task as complete and sets the result code.
*
* If the result code indicates a failure (|FAILED(@a aResultCode)|) then this
* method will import the error info from the current thread and assign it to
* the errorInfo attribute (it will return an error if no info is available in
* such case).
*
* If the result code indicates a success (|SUCCEEDED(@a aResultCode)|) then
* the current operation is set to the last.
*
* Note that this method may be called only once for the given Progress object.
* Subsequent calls will assert.
*
* @param aResultCode Operation result code.
*/
{
AutoCaller autoCaller(this);
mCompleted = TRUE;
if (!FAILED(aResultCode))
{
m_ulOperationPercent = 100;
}
#if !defined VBOX_COM_INPROC
/* remove from the global collection of pending progress operations */
if (mParent)
#endif
/* wake up all waiting threads */
if (mWaitersCount > 0)
return rc;
}
/**
* Wrapper around Progress:notifyCompleteV.
*/
const char *pcszComponent,
const char *aText,
...)
{
return hrc;
}
/**
* Marks the operation as complete and attaches full error info.
*
* See com::SupportErrorInfoImpl::setError(HRESULT, const GUID &, const wchar_t
* *, const char *, ...) for more info.
*
* @param aResultCode Operation result (error) code, must not be S_OK.
* @param aIID IID of the interface that defines the error.
* @param aComponent Name of the component that generates the error.
* @param aText Error message (must not be null), an RTStrPrintf-like
* format string in UTF-8 encoding.
* @param va List of arguments for the format string.
*/
const char *pcszComponent,
const char *aText,
{
AutoCaller autoCaller(this);
mCompleted = TRUE;
{
}
#if !defined VBOX_COM_INPROC
/* remove from the global collection of pending progress operations */
if (mParent)
#endif
/* wake up all waiting threads */
if (mWaitersCount > 0)
return rc;
}
/**
* Notify the progress object that we're almost at the point of no return.
*
* This atomically checks for and disables cancelation. Calls to
* IProgress::Cancel() made after a successfull call to this method will fail
* and the user can be told. While this isn't entirely clean behavior, it
* prevents issues with an irreversible actually operation succeeding while the
* user belive it was rolled back.
*
* @returns Success indicator.
* @retval true on success.
* @retval false if the progress object has already been canceled or is in an
* invalid state
*/
bool Progress::notifyPointOfNoReturn(void)
{
AutoCaller autoCaller(this);
if (mCanceled)
{
LogThisFunc(("returns false\n"));
return false;
}
mCancelable = FALSE;
LogThisFunc(("returns true\n"));
return true;
}
////////////////////////////////////////////////////////////////////////////////
// CombinedProgress class
////////////////////////////////////////////////////////////////////////////////
{
mProgress = 0;
mCompletedOperations = 0;
return S_OK;
}
void CombinedProgress::FinalRelease()
{
uninit();
}
// public initializer/uninitializer for internal purposes only
////////////////////////////////////////////////////////////////////////////////
/**
* Initializes this object based on individual combined progresses.
* Must be called only from #init()!
*
* @param aAutoInitSpan AutoInitSpan object instantiated by a subclass.
* @param aParent See ProgressBase::init().
* @param aInitiator See ProgressBase::init().
* @param aDescription See ProgressBase::init().
* @param aId See ProgressBase::init().
*/
#if !defined(VBOX_COM_INPROC)
#endif
{
LogFlowThisFunc(("aDescription={%ls} mProgresses.size()=%d\n",
#if !defined(VBOX_COM_INPROC)
#endif
mProgress = 0; /* the first object */
mCompletedOperations = 0;
mCompleted = FALSE;
m_cOperations = 0; /* will be calculated later */
m_ulCurrentOperation = 0;
{
if (mCancelable)
{
if (!cancelable)
mCancelable = FALSE;
}
{
m_cOperations += opCount;
}
}
rc = checkProgress();
return rc;
}
/**
* Initializes the combined progress object given two normal progress
* objects.
*
* @param aParent See ProgressBase::init().
* @param aInitiator See ProgressBase::init().
* @param aDescription See ProgressBase::init().
* @param aProgress1 First normal progress object.
* @param aProgress2 Second normal progress object.
* @param aId See ProgressBase::init().
*/
#if !defined(VBOX_COM_INPROC)
#endif
{
/* Enclose the state transition NotReady->InInit->Ready */
AutoInitSpan autoInitSpan(this);
mProgresses[0] = aProgress1;
#if !defined(VBOX_COM_INPROC)
#endif
aId);
/* Confirm a successful initialization when it's the case */
return rc;
}
/**
* Uninitializes the instance and sets the ready flag to FALSE.
*
* Called either from FinalRelease() or by the parent when it gets destroyed.
*/
void CombinedProgress::uninit()
{
LogFlowThisFunc(("\n"));
/* Enclose the state transition Ready->InUninit->NotReady */
AutoUninitSpan autoUninitSpan(this);
if (autoUninitSpan.uninitDone())
return;
mProgress = 0;
mProgresses.clear();
}
// IProgress properties
////////////////////////////////////////////////////////////////////////////////
{
AutoCaller autoCaller(this);
/* checkProgress needs a write lock */
*aPercent = 100;
else
{
/* global percent =
* (100 / m_cOperations) * mOperation +
* ((100 / m_cOperations) / 100) * m_ulOperationPercent */
}
return S_OK;
}
{
AutoCaller autoCaller(this);
/* checkProgress needs a write lock */
}
{
AutoCaller autoCaller(this);
/* checkProgress needs a write lock */
}
{
AutoCaller autoCaller(this);
/* checkProgress needs a write lock */
}
{
AutoCaller autoCaller(this);
/* checkProgress needs a write lock */
}
{
AutoCaller autoCaller(this);
/* checkProgress needs a write lock */
}
{
AutoCaller autoCaller(this);
/* checkProgress needs a write lock */
}
{
AutoCaller autoCaller(this);
/* checkProgress needs a write lock */
}
{
AssertFailed();
return E_NOTIMPL;
}
{
AssertFailed();
return E_NOTIMPL;
}
// IProgress methods
/////////////////////////////////////////////////////////////////////////////
/**
* @note XPCOM: when this method is called not on the main XPCOM thread, it
* simply blocks the thread until mCompletedSem is signalled. If the
* thread has its own event queue (hmm, what for?) that it must run, then
* calling this method will definitely freeze event processing.
*/
{
AutoCaller autoCaller(this);
/* if we're already completed, take a shortcut */
if (!mCompleted)
{
{
rc = checkProgress();
if (!forever)
{
}
}
}
return S_OK;
}
/**
* @note XPCOM: when this method is called not on the main XPCOM thread, it
* simply blocks the thread until mCompletedSem is signalled. If the
* thread has its own event queue (hmm, what for?) that it must run, then
* calling this method will definitely freeze event processing.
*/
{
AutoCaller autoCaller(this);
if (aOperation >= m_cOperations)
/* if we're already completed or if the given operation is already done,
* then take a shortcut */
{
/* find the right progress object to wait for */
do
{
return rc;
{
/* found the right progress object */
break;
}
completedOps += opCount;
progress ++;
}
while (1);
LogFlowThisFunc(("will wait for mProgresses [%d] (%d)\n",
{
/* wait for the appropriate progress operation completion */
rc = checkProgress();
if (!forever)
{
}
}
}
return S_OK;
}
{
AutoCaller autoCaller(this);
if (!mCancelable)
if (!mCanceled)
{
LogThisFunc(("Canceling\n"));
/** @todo Teleportation: Shouldn't this be propagated to mProgresses? If
* powerUp creates passes a combined progress object to the client, I
* won't get called back since I'm only getting the powerupProgress ...
* Or what? */
if (m_pfnCancelCallback)
}
else
LogThisFunc(("Already canceled\n"));
return S_OK;
}
// private methods
////////////////////////////////////////////////////////////////////////////////
/**
* Fetches the properties of the current progress object and, if it is
* successfully completed, advances to the next uncompleted or unsuccessfully
* completed object in the vector of combined progress objects.
*
* @note Must be called from under this object's write lock!
*/
{
/* do nothing if we're already marked ourselves as completed */
if (mCompleted)
return S_OK;
do
{
return rc;
if (fCompleted)
{
return rc;
return rc;
mResultCode = iRc;
if (FAILED(mResultCode))
{
return rc;
}
{
mCompleted = TRUE;
}
else
{
return rc;
mProgress ++;
else
mCompleted = TRUE;
}
}
}
while (fCompleted && !mCompleted);
{
{
}
}
return rc;
}
/* vi: set tabstop=4 shiftwidth=4 expandtab: */