ProgressImpl.cpp revision 1c898140fdfb6f3d207b0066f4fc8988226da7d4
/* $Id$ */
/** @file
*
* VirtualBox Progress COM class implementation
*/
/*
* Copyright (C) 2006-2009 Sun Microsystems, Inc.
*
* 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.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
#if defined (VBOX_WITH_XPCOM)
#include <nsIServiceManager.h>
#include <nsIExceptionService.h>
#include <nsCOMPtr.h>
#endif /* defined (VBOX_WITH_XPCOM) */
#include "ProgressImpl.h"
#include "VirtualBoxImpl.h"
#include "VirtualBoxErrorInfoImpl.h"
#include "Logging.h"
#include <iprt/semaphore.h>
////////////////////////////////////////////////////////////////////////////////
// ProgressBase class
////////////////////////////////////////////////////////////////////////////////
// constructor / destructor
////////////////////////////////////////////////////////////////////////////////
/**
* Subclasses must call this method from their FinalConstruct() implementations.
*/
{
mCancelable = FALSE;
mCompleted = FALSE;
mResultCode = S_OK;
mOperationCount = 0;
mOperation = 0;
mOperationPercent = 0;
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 Task 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 */
/* register with parent early, since uninit() will unconditionally
* unregister on failure */
mParent->addDependentChild (this);
#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 */
#else
#endif
if (aId)
#if !defined (VBOX_COM_INPROC)
/* add to the global colleciton of progess 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 */
mParent->removeDependentChild (this);
}
#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);
AutoReadLock alock (this);
return S_OK;
}
{
AutoCaller autoCaller (this);
AutoReadLock alock (this);
*aPercent = 100;
else
{
/* global percent =
* (100 / mOperationCount) * mOperation +
* ((100 / mOperationCount) / 100) * mOperationPercent */
}
return S_OK;
}
{
AutoCaller autoCaller (this);
AutoReadLock alock (this);
*aCompleted = mCompleted;
return S_OK;
}
{
AutoCaller autoCaller (this);
AutoReadLock alock (this);
return S_OK;
}
{
AutoCaller autoCaller (this);
AutoReadLock alock (this);
if (!mCompleted)
tr ("Result code is not available, operation is still in progress"));
return S_OK;
}
{
AutoCaller autoCaller (this);
AutoReadLock alock (this);
if (!mCompleted)
tr ("Error info is not available, operation is still in progress"));
return S_OK;
}
{
AutoCaller autoCaller (this);
AutoReadLock alock (this);
return S_OK;
}
{
AutoCaller autoCaller (this);
AutoReadLock alock (this);
*aOperation = mOperation;
return S_OK;
}
{
AutoCaller autoCaller (this);
AutoReadLock alock (this);
return S_OK;
}
{
AutoCaller autoCaller (this);
AutoReadLock alock (this);
*aOperationPercent = 100;
else
return S_OK;
}
// public methods only for internal purposes
////////////////////////////////////////////////////////////////////////////////
/**
* Sets the error info stored in the given progress object as the error info on
* the current thread.
*
* This method is useful if some other COM method uses IProgress to wait for
* something and then wants to return a failed result of the operation it was
* waiting for as its own result retaining the extended error info.
*
* If the operation tracked by this progress object is completed successfully
* and returned S_OK, this method does nothing but returns S_OK. Otherwise, the
* failed warning or error result code specified at progress completion is
* returned and the extended error info object (if any) is set on the current
* thread.
*
* Note that the given progress object must be completed, otherwise this method
* will assert and fail.
*/
/* static */
{
if (resultCode == S_OK)
return resultCode;
return resultCode;
}
////////////////////////////////////////////////////////////////////////////////
// Progress class
////////////////////////////////////////////////////////////////////////////////
{
mWaitersCount = 0;
return S_OK;
}
void Progress::FinalRelease()
{
uninit();
}
// public initializer/uninitializer for internal purposes only
////////////////////////////////////////////////////////////////////////////////
/**
* Initializes the normal progress object.
*
* @param aParent See ProgressBase::init().
* @param aInitiator See ProgressBase::init().
* @param aDescription See ProgressBase::init().
* @param aCancelable Flag whether the task maybe canceled.
* @param aOperationCount Number of operations within this task (at least 1).
* @param aOperationDescription Description of the first operation.
* @param aId See ProgressBase::init().
*/
#if !defined (VBOX_COM_INPROC)
#endif
{
/* Enclose the state transition NotReady->InInit->Ready */
AutoInitSpan autoInitSpan (this);
#if !defined (VBOX_COM_INPROC)
#endif
mOperation = 0; /* the first operation */
/* 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);
mOperation = 0; /* the first operation */
/* 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 called not on the main XPCOM thread, it 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 definitey freese event processing.
*/
{
AutoCaller autoCaller (this);
AutoWriteLock alock (this);
/* if we're already completed, take a shortcut */
if (!mCompleted)
{
int vrc = VINF_SUCCESS;
{
mWaitersCount ++;
mWaitersCount --;
/* the last waiter resets the semaphore */
if (mWaitersCount == 0)
break;
if (!forever)
{
}
}
return setError (VBOX_E_IPRT_ERROR,
}
return S_OK;
}
/**
* @note XPCOM: when this method is called not on the main XPCOM thread, it 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 definitey freese event processing.
*/
{
AutoCaller autoCaller (this);
AutoWriteLock alock (this);
/* if we're already completed or if the given operation is already done,
* then take a shortcut */
{
int vrc = VINF_SUCCESS;
{
mWaitersCount ++;
: (unsigned) timeLeft);
mWaitersCount --;
/* the last waiter resets the semaphore */
if (mWaitersCount == 0)
break;
if (!forever)
{
}
}
}
return S_OK;
}
{
AutoCaller autoCaller (this);
AutoWriteLock alock (this);
if (!mCancelable)
return setError (VBOX_E_INVALID_OBJECT_STATE,
tr ("Operation cannot be canceled"));
return S_OK;
}
// public methods only for internal purposes
/////////////////////////////////////////////////////////////////////////////
/**
* 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);
AutoWriteLock alock (this);
if (mCancelable && mCanceled)
{
Assert(!mCompleted);
return E_FAIL;
}
else
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.
*/
{
AutoCaller autoCaller (this);
AutoWriteLock alock (this);
mOperation ++;
mOperationPercent = 0;
/* wake up all waiting threads */
if (mWaitersCount > 0)
return S_OK;
}
/**
* 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);
AutoWriteLock alock (this);
mCompleted = TRUE;
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) */
}
else
{
mOperationPercent = 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;
}
/**
* 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 intrface 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 ... List of arguments for the format string.
*/
const Bstr &aComponent,
const char *aText, ...)
{
}
/**
* 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.
*
* This method is preferred iy you have a ready (translated and formatted) Bstr
* string, because it omits an extra conversion Utf8Str -> Bstr.
*
* @param aResultCode Operation result (error) code, must not be S_OK.
* @param aIID IID of the intrface that defines the error.
* @param aComponent Name of the component that generates the error.
* @param aText Error message (must not be null).
*/
{
AutoCaller autoCaller (this);
AutoWriteLock alock (this);
mCompleted = TRUE;
AssertComRC (rc);
{
}
#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;
}
////////////////////////////////////////////////////////////////////////////////
// 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;
mOperationCount = 0; /* will be calculated later */
mOperation = 0;
{
if (mCancelable)
{
if (!cancelable)
mCancelable = FALSE;
}
{
}
}
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
/* 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 */
AutoWriteLock alock (this);
*aPercent = 100;
else
{
/* global percent =
* (100 / mOperationCount) * mOperation +
* ((100 / mOperationCount) / 100) * mOperationPercent */
}
return S_OK;
}
{
AutoCaller autoCaller (this);
/* checkProgress needs a write lock */
AutoWriteLock alock (this);
}
{
AutoCaller autoCaller (this);
/* checkProgress needs a write lock */
AutoWriteLock alock (this);
}
{
AutoCaller autoCaller (this);
/* checkProgress needs a write lock */
AutoWriteLock alock (this);
}
{
AutoCaller autoCaller (this);
/* checkProgress needs a write lock */
AutoWriteLock alock (this);
}
{
AutoCaller autoCaller (this);
/* checkProgress needs a write lock */
AutoWriteLock alock (this);
}
{
AutoCaller autoCaller (this);
/* checkProgress needs a write lock */
AutoWriteLock alock (this);
}
{
AutoCaller autoCaller (this);
/* checkProgress needs a write lock */
AutoWriteLock alock (this);
}
// IProgress methods
/////////////////////////////////////////////////////////////////////////////
/**
* @note XPCOM: when this method is called not on the main XPCOM thread, it 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 definitey freese event processing.
*/
{
AutoCaller autoCaller (this);
AutoWriteLock alock (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 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 definitey freese event processing.
*/
{
AutoCaller autoCaller (this);
AutoWriteLock alock (this);
if (aOperation >= mOperationCount)
/* 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);
AutoWriteLock alock (this);
if (!mCancelable)
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 unsucessfully
* 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 (completed)
{
return rc;
return rc;
if (FAILED (mResultCode))
{
return rc;
}
{
mCompleted = TRUE;
}
else
{
return rc;
mProgress ++;
else
mCompleted = TRUE;
}
}
}
while (completed && !mCompleted);
{
{
}
}
return rc;
}
/* vi: set tabstop=4 shiftwidth=4 expandtab: */