ProgressProxyImpl.cpp revision e45ccb294fc1f6b4078d058eaff86100361a7358
/* $Id$ */
/** @file
* IProgress implementation for Machine::openRemoteSession in VBoxSVC.
*/
/*
* Copyright (C) 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;
* you can redistribute it and/or modify it under the terms of the GNU
* 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.
*/
#include <iprt/types.h>
#if defined (VBOX_WITH_XPCOM)
#include <nsIServiceManager.h>
#include <nsIExceptionService.h>
#include <nsCOMPtr.h>
#endif /* defined (VBOX_WITH_XPCOM) */
#include "ProgressProxyImpl.h"
#include "VirtualBoxImpl.h"
#include "VirtualBoxErrorInfoImpl.h"
#include "Logging.h"
#include <iprt/time.h>
#include <iprt/semaphore.h>
#include <VBox/err.h>
////////////////////////////////////////////////////////////////////////////////
// ProgressProxy class
////////////////////////////////////////////////////////////////////////////////
// constructor / destructor / uninitializer
////////////////////////////////////////////////////////////////////////////////
HRESULT ProgressProxy::FinalConstruct()
{
mfMultiOperation = false;
muOtherProgressStartWeight = 0;
muOtherProgressWeight = 0;
muOtherProgressStartOperation = 0;
HRESULT rc = Progress::FinalConstruct();
return rc;
}
/**
* Initialize it as a one operation Progress object.
*
* This is used by SessionMachine::OnSessionEnd.
*/
HRESULT ProgressProxy::init(
#if !defined (VBOX_COM_INPROC)
VirtualBox *pParent,
#endif
IUnknown *pInitiator,
CBSTR bstrDescription,
BOOL fCancelable)
{
mfMultiOperation = false;
muOtherProgressStartWeight = 1;
muOtherProgressWeight = 1;
muOtherProgressStartOperation = 1;
return Progress::init(
#if !defined (VBOX_COM_INPROC)
pParent,
#endif
pInitiator,
bstrDescription,
fCancelable,
1 /* cOperations */,
1 /* ulTotalOperationsWeight */,
bstrDescription /* bstrFirstOperationDescription */,
1 /* ulFirstOperationWeight */,
NULL /* pId */);
}
/**
* Initialize for proxying one other progress object.
*
* This is tailored explicitly for the openRemoteSession code, so we start out
* with one operation where we don't have any remote object (powerUp). Then a
* remote object is added and stays with us till the end.
*
* The user must do normal completion notification or risk leave the threads
* waiting forever!
*/
HRESULT ProgressProxy::init(
#if !defined (VBOX_COM_INPROC)
VirtualBox *pParent,
#endif
IUnknown *pInitiator,
CBSTR bstrDescription,
BOOL fCancelable,
ULONG uTotalOperationsWeight,
CBSTR bstrFirstOperationDescription,
ULONG uFirstOperationWeight,
ULONG cOtherProgressObjectOperations)
{
mfMultiOperation = false;
muOtherProgressStartWeight = uFirstOperationWeight;
muOtherProgressWeight = uTotalOperationsWeight - uFirstOperationWeight;
muOtherProgressStartOperation = 1;
return Progress::init(
#if !defined (VBOX_COM_INPROC)
pParent,
#endif
pInitiator,
bstrDescription,
fCancelable,
1 + cOtherProgressObjectOperations /* cOperations */,
uTotalOperationsWeight,
bstrFirstOperationDescription,
uFirstOperationWeight,
NULL);
}
void ProgressProxy::FinalRelease()
{
uninit();
mfMultiOperation = false;
muOtherProgressStartWeight = 0;
muOtherProgressWeight = 0;
muOtherProgressStartOperation = 0;
}
void ProgressProxy::uninit()
{
LogFlowThisFunc(("\n"));
mptrOtherProgress.setNull();
Progress::uninit();
}
// Public methods
////////////////////////////////////////////////////////////////////////////////
/** Just a wrapper so we can automatically do the handover before setting
* the result locally. */
HRESULT ProgressProxy::notifyComplete(HRESULT aResultCode)
{
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
clearOtherProgressObjectInternal(true /* fEarly */);
HRESULT hrc = S_OK;
if (!mCompleted)
hrc = Progress::notifyComplete(aResultCode);
return hrc;
}
/** Just a wrapper so we can automatically do the handover before setting
* the result locally. */
HRESULT ProgressProxy::notifyComplete(HRESULT aResultCode,
const GUID &aIID,
const char *pcszComponent,
const char *aText,
...)
{
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
clearOtherProgressObjectInternal(true /* fEarly */);
HRESULT hrc = S_OK;
if (!mCompleted)
{
va_list va;
va_start(va, aText);
hrc = Progress::notifyCompleteV(aResultCode, aIID, pcszComponent, aText, va);
va_end(va);
}
return hrc;
}
/**
* Sets the other progress object unless the operation has been completed /
* canceled already.
*
* @returns false if failed/canceled, true if not.
* @param pOtherProgress The other progress object. Must not be NULL.
*/
bool ProgressProxy::setOtherProgressObject(IProgress *pOtherProgress)
{
LogFlowThisFunc(("setOtherProgressObject: %p\n", pOtherProgress));
ComPtr<IProgress> ptrOtherProgress = pOtherProgress;
/*
* Query information from the other progress object before we grab the
* lock.
*/
ULONG cOperations;
HRESULT hrc = pOtherProgress->COMGETTER(OperationCount)(&cOperations);
if (FAILED(hrc))
cOperations = 1;
Bstr bstrOperationDescription;
hrc = pOtherProgress->COMGETTER(Description)(bstrOperationDescription.asOutParam());
if (FAILED(hrc))
bstrOperationDescription = "oops";
/*
* Take the lock and check for cancelation, cancel the other object if
* we've been canceled already.
*/
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
BOOL fCompletedOrCanceled = mCompleted || mCanceled;
if (!fCompletedOrCanceled)
{
/*
* Advance to the next object and operation. If the other object has
* more operations than anticipated, adjust our internal count.
*/
mptrOtherProgress = ptrOtherProgress;
mfMultiOperation = cOperations > 1;
muOtherProgressStartWeight = m_ulOperationsCompletedWeight + m_ulCurrentOperationWeight;
muOtherProgressWeight = m_ulTotalOperationsWeight - muOtherProgressStartWeight;
Progress::SetNextOperation(bstrOperationDescription.raw(), muOtherProgressWeight);
muOtherProgressStartOperation = m_ulCurrentOperation;
m_cOperations = cOperations + m_ulCurrentOperation;
/*
* Check for cancelation and completion.
*/
BOOL f;
hrc = ptrOtherProgress->COMGETTER(Completed)(&f);
fCompletedOrCanceled = FAILED(hrc) || f;
if (!fCompletedOrCanceled)
{
hrc = ptrOtherProgress->COMGETTER(Canceled)(&f);
fCompletedOrCanceled = SUCCEEDED(hrc) && f;
}
if (fCompletedOrCanceled)
{
LogFlowThisFunc(("Other object completed or canceled, clearing...\n"));
clearOtherProgressObjectInternal(false /*fEarly*/);
}
else
{
/*
* Finally, mirror the cancelable property.
* Note! Note necessary if we do passthru!
*/
if (mCancelable)
{
hrc = ptrOtherProgress->COMGETTER(Cancelable)(&f);
if (SUCCEEDED(hrc) && !f)
{
LogFlowThisFunc(("The other progress object is not cancelable\n"));
mCancelable = FALSE;
}
}
}
}
else
{
LogFlowThisFunc(("mCompleted=%RTbool mCanceled=%RTbool - Canceling the other progress object!\n",
mCompleted, mCanceled));
hrc = ptrOtherProgress->Cancel();
LogFlowThisFunc(("Cancel -> %Rhrc", hrc));
}
LogFlowThisFunc(("Returns %RTbool\n", !fCompletedOrCanceled));
return !fCompletedOrCanceled;
}
// Internal methods.
////////////////////////////////////////////////////////////////////////////////
/**
* Clear the other progress object reference, first copying over its state.
*
* This is used internally when completion is signalled one way or another.
*
* @param fEarly Early clearing or not.
*/
void ProgressProxy::clearOtherProgressObjectInternal(bool fEarly)
{
if (!mptrOtherProgress.isNull())
{
ComPtr<IProgress> ptrOtherProgress = mptrOtherProgress;
mptrOtherProgress.setNull();
copyProgressInfo(ptrOtherProgress, fEarly);
}
}
/**
* Called to copy over the progress information from @a pOtherProgress.
*
* @param pOtherProgress The source of the information.
* @param fEarly Early copy.
*
* @note The caller owns the write lock and as cleared mptrOtherProgress
* already (or we might recurse forever)!
*/
void ProgressProxy::copyProgressInfo(IProgress *pOtherProgress, bool fEarly)
{
HRESULT hrc;
LogFlowThisFunc(("\n"));
NOREF(fEarly);
/*
* No point in doing this if the progress object was canceled already.
*/
if (!mCanceled)
{
/* Detect if the other progress object was canceled. */
BOOL fCanceled;
hrc = pOtherProgress->COMGETTER(Canceled)(&fCanceled);
if (FAILED(hrc))
fCanceled = FALSE;
if (fCanceled)
{
LogFlowThisFunc(("Canceled\n"));
mCanceled = TRUE;
if (m_pfnCancelCallback)
m_pfnCancelCallback(m_pvCancelUserArg);
}
else
{
/* Has it completed? */
BOOL fCompleted;
hrc = pOtherProgress->COMGETTER(Completed)(&fCompleted);
if (FAILED(hrc))
fCompleted = TRUE;
Assert(fCompleted || fEarly);
if (fCompleted)
{
/* Check the result. */
LONG hrcResult;
hrc = pOtherProgress->COMGETTER(ResultCode)(&hrcResult);
if (FAILED(hrc))
hrcResult = hrc;
if (SUCCEEDED((HRESULT)hrcResult))
LogFlowThisFunc(("Succeeded\n"));
else
{
/* Get the error information. */
ComPtr<IVirtualBoxErrorInfo> ptrErrorInfo;
hrc = pOtherProgress->COMGETTER(ErrorInfo)(ptrErrorInfo.asOutParam());
if (SUCCEEDED(hrc))
{
Bstr bstrIID;
hrc = ptrErrorInfo->COMGETTER(InterfaceID)(bstrIID.asOutParam()); AssertComRC(hrc);
if (FAILED(hrc))
bstrIID.setNull();
Bstr bstrComponent;
hrc = ptrErrorInfo->COMGETTER(Component)(bstrComponent.asOutParam()); AssertComRC(hrc);
if (FAILED(hrc))
bstrComponent = "failed";
Bstr bstrText;
hrc = ptrErrorInfo->COMGETTER(Text)(bstrText.asOutParam()); AssertComRC(hrc);
if (FAILED(hrc))
bstrText = "<failed>";
Utf8Str strText(bstrText);
LogFlowThisFunc(("Got ErrorInfo(%s); hrcResult=%Rhrc\n", strText.c_str(), hrcResult));
Progress::notifyComplete((HRESULT)hrcResult,
Guid(bstrIID).ref(),
Utf8Str(bstrComponent).c_str(),
"%s", strText.c_str());
}
else
{
LogFlowThisFunc(("ErrorInfo failed with hrc=%Rhrc; hrcResult=%Rhrc\n", hrc, hrcResult));
Progress::notifyComplete((HRESULT)hrcResult,
COM_IIDOF(IProgress),
"ProgressProxy",
tr("No error info"));
}
}
}
else
LogFlowThisFunc(("Not completed\n"));
}
}
else
LogFlowThisFunc(("Already canceled\n"));
/*
* Did cancelable state change (point of no return)?
*/
if (mCancelable && !mCompleted && !mCanceled)
{
BOOL fCancelable;
hrc = pOtherProgress->COMGETTER(Cancelable)(&fCancelable); AssertComRC(hrc);
if (SUCCEEDED(hrc) && !fCancelable)
{
LogFlowThisFunc(("point-of-no-return reached\n"));
mCancelable = FALSE;
}
}
}
// IProgress properties
////////////////////////////////////////////////////////////////////////////////
STDMETHODIMP ProgressProxy::COMGETTER(Cancelable)(BOOL *aCancelable)
{
CheckComArgOutPointerValid(aCancelable);
AutoCaller autoCaller(this);
HRESULT hrc = autoCaller.rc();
if (SUCCEEDED(hrc))
{
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
/* ASSUME: The cancelable property can only change to FALSE. */
if (!mCancelable || mptrOtherProgress.isNull())
*aCancelable = mCancelable;
else
{
hrc = mptrOtherProgress->COMGETTER(Cancelable)(aCancelable);
if (SUCCEEDED(hrc) && !*aCancelable)
{
LogFlowThisFunc(("point-of-no-return reached\n"));
mCancelable = FALSE;
}
}
}
return hrc;
}
STDMETHODIMP ProgressProxy::COMGETTER(Percent)(ULONG *aPercent)
{
CheckComArgOutPointerValid(aPercent);
AutoCaller autoCaller(this);
HRESULT hrc = autoCaller.rc();
if (SUCCEEDED(hrc))
{
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
if (mptrOtherProgress.isNull())
hrc = Progress::COMGETTER(Percent)(aPercent);
else
{
/*
* Get the overall percent of the other object and adjust it with
* the weighting given to the period before proxying started.
*/
ULONG uPct;
hrc = mptrOtherProgress->COMGETTER(Percent)(&uPct);
if (SUCCEEDED(hrc))
{
double rdPercent = ((double)uPct / 100 * muOtherProgressWeight + muOtherProgressStartWeight)
/ m_ulTotalOperationsWeight * 100;
*aPercent = RT_MIN((ULONG)rdPercent, 99); /* mptrOtherProgress is cleared when its completed, so we can never return 100%. */
}
}
}
return hrc;
}
STDMETHODIMP ProgressProxy::COMGETTER(TimeRemaining)(LONG *aTimeRemaining)
{
CheckComArgOutPointerValid(aTimeRemaining);
AutoCaller autoCaller(this);
HRESULT hrc = autoCaller.rc();
if (SUCCEEDED(hrc))
{
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
if (mptrOtherProgress.isNull())
hrc = Progress::COMGETTER(TimeRemaining)(aTimeRemaining);
else
hrc = mptrOtherProgress->COMGETTER(TimeRemaining)(aTimeRemaining);
}
return hrc;
}
STDMETHODIMP ProgressProxy::COMGETTER(Completed)(BOOL *aCompleted)
{
/* Not proxied since we EXPECT a normal completion notification call. */
return Progress::COMGETTER(Completed)(aCompleted);
}
STDMETHODIMP ProgressProxy::COMGETTER(Canceled)(BOOL *aCanceled)
{
CheckComArgOutPointerValid(aCanceled);
AutoCaller autoCaller(this);
HRESULT hrc = autoCaller.rc();
if (SUCCEEDED(hrc))
{
/* Check the local data first, then the other object. */
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
hrc = Progress::COMGETTER(Canceled)(aCanceled);
if ( SUCCEEDED(hrc)
&& !*aCanceled
&& !mptrOtherProgress.isNull()
&& mCancelable)
{
hrc = mptrOtherProgress->COMGETTER(Canceled)(aCanceled);
if (SUCCEEDED(hrc) && *aCanceled)
/* This will not complete the object, only mark it as canceled. */
clearOtherProgressObjectInternal(false /*fEarly*/);
}
}
return hrc;
}
STDMETHODIMP ProgressProxy::COMGETTER(ResultCode)(LONG *aResultCode)
{
/* Not proxied since we EXPECT a normal completion notification call. */
return Progress::COMGETTER(ResultCode)(aResultCode);
}
STDMETHODIMP ProgressProxy::COMGETTER(ErrorInfo)(IVirtualBoxErrorInfo **aErrorInfo)
{
/* Not proxied since we EXPECT a normal completion notification call. */
return Progress::COMGETTER(ErrorInfo)(aErrorInfo);
}
STDMETHODIMP ProgressProxy::COMGETTER(Operation)(ULONG *aOperation)
{
CheckComArgOutPointerValid(aOperation);
AutoCaller autoCaller(this);
HRESULT hrc = autoCaller.rc();
if (SUCCEEDED(hrc))
{
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
if (mptrOtherProgress.isNull())
hrc = Progress::COMGETTER(Operation)(aOperation);
else
{
ULONG uCurOtherOperation;
hrc = mptrOtherProgress->COMGETTER(Operation)(&uCurOtherOperation);
if (SUCCEEDED(hrc))
*aOperation = uCurOtherOperation + muOtherProgressStartOperation;
}
}
return hrc;
}
STDMETHODIMP ProgressProxy::COMGETTER(OperationDescription)(BSTR *aOperationDescription)
{
CheckComArgOutPointerValid(aOperationDescription);
AutoCaller autoCaller(this);
HRESULT hrc = autoCaller.rc();
if (SUCCEEDED(hrc))
{
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
if (mptrOtherProgress.isNull() || !mfMultiOperation)
hrc = Progress::COMGETTER(OperationDescription)(aOperationDescription);
else
hrc = mptrOtherProgress->COMGETTER(OperationDescription)(aOperationDescription);
}
return hrc;
}
STDMETHODIMP ProgressProxy::COMGETTER(OperationPercent)(ULONG *aOperationPercent)
{
CheckComArgOutPointerValid(aOperationPercent);
AutoCaller autoCaller(this);
HRESULT hrc = autoCaller.rc();
if (SUCCEEDED(hrc))
{
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
if (mptrOtherProgress.isNull() || !mfMultiOperation)
hrc = Progress::COMGETTER(OperationPercent)(aOperationPercent);
else
hrc = mptrOtherProgress->COMGETTER(OperationPercent)(aOperationPercent);
}
return hrc;
}
STDMETHODIMP ProgressProxy::COMSETTER(Timeout)(ULONG aTimeout)
{
/* Not currently supported. */
NOREF(aTimeout);
AssertFailed();
return E_NOTIMPL;
}
STDMETHODIMP ProgressProxy::COMGETTER(Timeout)(ULONG *aTimeout)
{
/* Not currently supported. */
CheckComArgOutPointerValid(aTimeout);
AssertFailed();
return E_NOTIMPL;
}
// IProgress methods
/////////////////////////////////////////////////////////////////////////////
STDMETHODIMP ProgressProxy::WaitForCompletion(LONG aTimeout)
{
HRESULT hrc;
LogFlowThisFuncEnter();
LogFlowThisFunc(("aTimeout=%d\n", aTimeout));
/* No need to wait on the proxied object for these since we'll get the
normal completion notifications. */
hrc = Progress::WaitForCompletion(aTimeout);
LogFlowThisFuncLeave();
return hrc;
}
STDMETHODIMP ProgressProxy::WaitForOperationCompletion(ULONG aOperation, LONG aTimeout)
{
LogFlowThisFuncEnter();
LogFlowThisFunc(("aOperation=%d aTimeout=%d\n", aOperation, aTimeout));
AutoCaller autoCaller(this);
HRESULT hrc = autoCaller.rc();
if (SUCCEEDED(hrc))
{
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
CheckComArgExpr(aOperation, aOperation < m_cOperations);
/*
* Check if we can wait locally.
*/
if ( aOperation + 1 == m_cOperations /* final operation */
|| mptrOtherProgress.isNull())
{
/* ASSUMES that Progress::WaitForOperationCompletion is using
AutoWriteLock::leave() as it saves us from duplicating the code! */
hrc = Progress::WaitForOperationCompletion(aOperation, aTimeout);
}
else
{
LogFlowThisFunc(("calling the other object...\n"));
ComPtr<IProgress> ptrOtherProgress = mptrOtherProgress;
alock.release();
hrc = ptrOtherProgress->WaitForOperationCompletion(aOperation, aTimeout);
}
}
LogFlowThisFuncLeave();
return hrc;
}
STDMETHODIMP ProgressProxy::Cancel()
{
LogFlowThisFunc(("\n"));
AutoCaller autoCaller(this);
HRESULT hrc = autoCaller.rc();
if (SUCCEEDED(hrc))
{
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
if (mptrOtherProgress.isNull() || !mCancelable)
hrc = Progress::Cancel();
else
{
hrc = mptrOtherProgress->Cancel();
if (SUCCEEDED(hrc))
clearOtherProgressObjectInternal(false /*fEarly*/);
}
}
LogFlowThisFunc(("returns %Rhrc\n", hrc));
return hrc;
}
STDMETHODIMP ProgressProxy::SetCurrentOperationProgress(ULONG aPercent)
{
/* Not supported - why do we actually expose this? */
NOREF(aPercent);
return E_NOTIMPL;
}
STDMETHODIMP ProgressProxy::SetNextOperation(IN_BSTR bstrNextOperationDescription, ULONG ulNextOperationsWeight)
{
/* Not supported - why do we actually expose this? */
NOREF(bstrNextOperationDescription);
NOREF(ulNextOperationsWeight);
return E_NOTIMPL;
}
/* vi: set tabstop=4 shiftwidth=4 expandtab: */