/** @file * * VirtualBox COM class implementation */ /* * Copyright (C) 2006-2007 innotek GmbH * * 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 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 you received this file as part of a commercial VirtualBox * distribution, then only the terms of your commercial VirtualBox * license agreement apply instead of the previous paragraph. */ #if defined (VBOX_WITH_XPCOM) #include #include #include #endif // defined (VBOX_WITH_XPCOM) #include "ProgressImpl.h" #include "VirtualBoxImpl.h" #include "VirtualBoxErrorInfoImpl.h" #include "Logging.h" #include #include #include //////////////////////////////////////////////////////////////////////////////// // ProgressBase class //////////////////////////////////////////////////////////////////////////////// // constructor / destructor //////////////////////////////////////////////////////////////////////////////// /** Subclasses must call this method from their FinalConstruct() implementations */ HRESULT ProgressBase::FinalConstruct() { mCancelable = FALSE; mCompleted = FALSE; mCanceled = FALSE; mResultCode = S_OK; mOperationCount = 0; mOperation = 0; mOperationPercent = 0; return S_OK; } // public initializer/uninitializer for internal purposes only //////////////////////////////////////////////////////////////////////////////// /** * Initializes the progress base object. * * Subclasses should call this or any other #init() method from their * init() implementations. * * @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) * * @note * This method doesn't do |isReady()| check and doesn't call * |setReady (true)| on success! * @note * This method must be called from under the object's lock! */ HRESULT ProgressBase::protectedInit ( #if !defined (VBOX_COM_INPROC) VirtualBox *aParent, #endif IUnknown *aInitiator, const BSTR aDescription, GUIDPARAMOUT aId /* = NULL */) { #if !defined (VBOX_COM_INPROC) ComAssertRet (aParent, E_POINTER); #else ComAssertRet (aInitiator, E_POINTER); #endif ComAssertRet (aDescription, E_INVALIDARG); #if !defined (VBOX_COM_INPROC) mParent = aParent; #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 && !mParent.equalsTo (aInitiator)) mInitiator = aInitiator; #else mInitiator = aInitiator; #endif mDescription = aDescription; mId.create(); if (aId) mId.cloneTo (aId); #if !defined (VBOX_COM_INPROC) // add to the global colleciton of progess operations mParent->addProgress (this); // cause #uninit() to be called automatically upon VirtualBox uninit mParent->addDependentChild (this); #endif return S_OK; } /** * Initializes the progress base object. * This is a special initializator for progress objects that are combined * within 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 constructor will hit an * assertion. * * Subclasses should call this or any other #init() method from their * init() implementations. * * @note * This method doesn't do |isReady()| check and doesn't call * |setReady (true)| on success! * @note * This method must be called from under the object's lock! */ HRESULT ProgressBase::protectedInit() { return S_OK; } /** * Uninitializes the instance. * Subclasses should call this from their uninit() implementations. * The readiness flag must be true on input and will be set to false * on output. * * @param alock this object's autolock (with lock level = 1!) * * @note * Using mParent member after this method returns is forbidden. */ void ProgressBase::protectedUninit (AutoLock &alock) { LogFlowMember (("ProgressBase::protectedUninit()\n")); Assert (alock.belongsTo (this) && alock.level() == 1); Assert (isReady()); /* * release initiator * (effective only if mInitiator has been assigned in init()) */ mInitiator.setNull(); setReady (false); #if !defined (VBOX_COM_INPROC) if (mParent) { alock.leave(); mParent->removeDependentChild (this); alock.enter(); } mParent.setNull(); #endif } // IProgress properties ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP ProgressBase::COMGETTER(Id) (GUIDPARAMOUT aId) { if (!aId) return E_POINTER; AutoLock lock (this); CHECK_READY(); ComAssertRet (!mId.isEmpty(), E_FAIL); mId.cloneTo (aId); return S_OK; } STDMETHODIMP ProgressBase::COMGETTER(Description) (BSTR *aDescription) { if (!aDescription) return E_POINTER; AutoLock lock (this); CHECK_READY(); ComAssertRet (!mDescription.isNull(), E_FAIL); mDescription.cloneTo (aDescription); return S_OK; } STDMETHODIMP ProgressBase::COMGETTER(Initiator) (IUnknown **aInitiator) { if (!aInitiator) return E_POINTER; AutoLock lock(this); CHECK_READY(); #if !defined (VBOX_COM_INPROC) ComAssertRet (!mInitiator.isNull() || !mParent.isNull(), E_FAIL); if (mInitiator) mInitiator.queryInterfaceTo (aInitiator); else mParent.queryInterfaceTo (aInitiator); #else ComAssertRet (!mInitiator.isNull(), E_FAIL); mInitiator.queryInterfaceTo (aInitiator); #endif return S_OK; } STDMETHODIMP ProgressBase::COMGETTER(Cancelable) (BOOL *aCancelable) { if (!aCancelable) return E_POINTER; AutoLock lock(this); CHECK_READY(); *aCancelable = mCancelable; return S_OK; } STDMETHODIMP ProgressBase::COMGETTER(Percent) (LONG *aPercent) { if (!aPercent) return E_POINTER; AutoLock lock(this); CHECK_READY(); if (mCompleted && SUCCEEDED (mResultCode)) *aPercent = 100; else { // global percent = (100 / mOperationCount) * mOperation + // ((100 / mOperationCount) / 100) * mOperationPercent *aPercent = (100 * mOperation + mOperationPercent) / mOperationCount; } return S_OK; } STDMETHODIMP ProgressBase::COMGETTER(Completed) (BOOL *aCompleted) { if (!aCompleted) return E_POINTER; AutoLock lock(this); CHECK_READY(); *aCompleted = mCompleted; return S_OK; } STDMETHODIMP ProgressBase::COMGETTER(Canceled) (BOOL *aCanceled) { if (!aCanceled) return E_POINTER; AutoLock lock(this); CHECK_READY(); *aCanceled = mCanceled; return S_OK; } STDMETHODIMP ProgressBase::COMGETTER(ResultCode) (HRESULT *aResultCode) { if (!aResultCode) return E_POINTER; AutoLock lock(this); CHECK_READY(); if (!mCompleted) return setError (E_FAIL, tr ("Result code is not available, operation is still in progress")); *aResultCode = mResultCode; return S_OK; } STDMETHODIMP ProgressBase::COMGETTER(ErrorInfo) (IVirtualBoxErrorInfo **aErrorInfo) { if (!aErrorInfo) return E_POINTER; AutoLock lock(this); CHECK_READY(); if (!mCompleted) return setError (E_FAIL, tr ("Error info is not available, operation is still in progress")); mErrorInfo.queryInterfaceTo (aErrorInfo); return S_OK; } STDMETHODIMP ProgressBase::COMGETTER(OperationCount) (ULONG *aOperationCount) { if (!aOperationCount) return E_POINTER; AutoLock lock(this); CHECK_READY(); *aOperationCount = mOperationCount; return S_OK; } STDMETHODIMP ProgressBase::COMGETTER(Operation) (ULONG *aOperation) { if (!aOperation) return E_POINTER; AutoLock lock(this); CHECK_READY(); *aOperation = mOperation; return S_OK; } STDMETHODIMP ProgressBase::COMGETTER(OperationDescription) (BSTR *aOperationDescription) { if (!aOperationDescription) return E_POINTER; AutoLock lock(this); CHECK_READY(); mOperationDescription.cloneTo (aOperationDescription); return S_OK; } STDMETHODIMP ProgressBase::COMGETTER(OperationPercent) (LONG *aOperationPercent) { if (!aOperationPercent) return E_POINTER; AutoLock lock(this); CHECK_READY(); if (mCompleted && SUCCEEDED (mResultCode)) *aOperationPercent = 100; else *aOperationPercent = mOperationPercent; return S_OK; } //////////////////////////////////////////////////////////////////////////////// // Progress class //////////////////////////////////////////////////////////////////////////////// HRESULT Progress::FinalConstruct() { HRESULT rc = ProgressBase::FinalConstruct(); if (FAILED (rc)) return rc; mCompletedSem = NIL_RTSEMEVENTMULTI; mWaitersCount = 0; return S_OK; } void Progress::FinalRelease() { uninit(); } // public initializer/uninitializer for internal purposes only //////////////////////////////////////////////////////////////////////////////// /** * Initializes the 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() */ HRESULT Progress::init ( #if !defined (VBOX_COM_INPROC) VirtualBox *aParent, #endif IUnknown *aInitiator, const BSTR aDescription, BOOL aCancelable, ULONG aOperationCount, const BSTR aOperationDescription, GUIDPARAMOUT aId /* = NULL */) { LogFlowMember(("Progress::init(): aDescription={%ls}\n", aDescription)); ComAssertRet (aOperationDescription, E_INVALIDARG); ComAssertRet (aOperationCount >= 1, E_INVALIDARG); AutoLock lock(this); ComAssertRet (!isReady(), E_UNEXPECTED); HRESULT rc = S_OK; do { rc = ProgressBase::protectedInit ( #if !defined (VBOX_COM_INPROC) aParent, #endif aInitiator, aDescription, aId); CheckComRCBreakRC (rc); // set ready to let protectedUninit() be called on failure setReady (true); mCancelable = aCancelable; mOperationCount = aOperationCount; mOperation = 0; // the first operation mOperationDescription = aOperationDescription; int vrc = RTSemEventMultiCreate (&mCompletedSem); ComAssertRCBreak (vrc, rc = E_FAIL); RTSemEventMultiReset (mCompletedSem); } while (0); if (FAILED (rc)) uninit(); return rc; } HRESULT Progress::init (BOOL aCancelable, ULONG aOperationCount, const BSTR aOperationDescription) { LogFlowMember(("Progress::init(): \n")); AutoLock lock(this); ComAssertRet (!isReady(), E_UNEXPECTED); HRESULT rc = S_OK; do { rc = ProgressBase::protectedInit(); CheckComRCBreakRC (rc); // set ready to let protectedUninit() be called on failure setReady (true); mCancelable = aCancelable; mOperationCount = aOperationCount; mOperation = 0; // the first operation mOperationDescription = aOperationDescription; int vrc = RTSemEventMultiCreate (&mCompletedSem); ComAssertRCBreak (vrc, rc = E_FAIL); RTSemEventMultiReset (mCompletedSem); } while (0); if (FAILED (rc)) uninit(); 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 Progress::uninit() { LogFlowMember (("Progress::uninit()\n")); AutoLock alock (this); LogFlowMember (("Progress::uninit(): isReady=%d\n", isReady())); if (!isReady()) return; // wake up all threads still waiting by occasion if (mWaitersCount > 0) { LogFlow (("WARNING: There are still %d threads waiting for '%ls' completion!\n", mWaitersCount, mDescription.raw())); RTSemEventMultiSignal (mCompletedSem); } RTSemEventMultiDestroy (mCompletedSem); ProgressBase::protectedUninit (alock); } // 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. */ STDMETHODIMP Progress::WaitForCompletion (LONG aTimeout) { LogFlowMember(("Progress::WaitForCompletion: BEGIN: timeout=%d\n", aTimeout)); AutoLock lock(this); CHECK_READY(); // if we're already completed, take a shortcut if (!mCompleted) { RTTIMESPEC time; RTTimeNow (&time); int vrc = VINF_SUCCESS; bool forever = aTimeout < 0; int64_t timeLeft = aTimeout; int64_t lastTime = RTTimeSpecGetMilli (&time); while (!mCompleted && (forever || timeLeft > 0)) { mWaitersCount ++; lock.unlock(); int vrc = RTSemEventMultiWait (mCompletedSem, forever ? RT_INDEFINITE_WAIT : (unsigned) timeLeft); lock.lock(); mWaitersCount --; // the progress might have been uninitialized if (!isReady()) break; // the last waiter resets the semaphore if (mWaitersCount == 0) RTSemEventMultiReset (mCompletedSem); if (VBOX_FAILURE (vrc) && vrc != VERR_TIMEOUT) break; if (!forever) { RTTimeNow (&time); timeLeft -= RTTimeSpecGetMilli (&time) - lastTime; lastTime = RTTimeSpecGetMilli (&time); } } if (VBOX_FAILURE (vrc) && vrc != VERR_TIMEOUT) return setError (E_FAIL, tr ("Failed to wait for the task completion (%Vrc)"), vrc); } LogFlowMember(("Progress::WaitForCompletion: END\n")); 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. */ STDMETHODIMP Progress::WaitForOperationCompletion (ULONG aOperation, LONG aTimeout) { LogFlowMember(("Progress::WaitForOperationCompletion: BEGIN: " "operation=%d, timeout=%d\n", aOperation, aTimeout)); AutoLock lock(this); CHECK_READY(); if (aOperation >= mOperationCount) return setError (E_FAIL, tr ("Operation number must be in range [0, %d]"), mOperation - 1); // if we're already completed or if the given operation is already done, // then take a shortcut if (!mCompleted && aOperation >= mOperation) { RTTIMESPEC time; RTTimeNow (&time); int vrc = VINF_SUCCESS; bool forever = aTimeout < 0; int64_t timeLeft = aTimeout; int64_t lastTime = RTTimeSpecGetMilli (&time); while (!mCompleted && aOperation >= mOperation && (forever || timeLeft > 0)) { mWaitersCount ++; lock.unlock(); int vrc = RTSemEventMultiWait (mCompletedSem, forever ? RT_INDEFINITE_WAIT : (unsigned) timeLeft); lock.lock(); mWaitersCount --; // the progress might have been uninitialized if (!isReady()) break; // the last waiter resets the semaphore if (mWaitersCount == 0) RTSemEventMultiReset (mCompletedSem); if (VBOX_FAILURE (vrc) && vrc != VERR_TIMEOUT) break; if (!forever) { RTTimeNow (&time); timeLeft -= RTTimeSpecGetMilli (&time) - lastTime; lastTime = RTTimeSpecGetMilli (&time); } } if (VBOX_FAILURE (vrc) && vrc != VERR_TIMEOUT) return setError (E_FAIL, tr ("Failed to wait for the operation completion (%Vrc)"), vrc); } LogFlowMember(("Progress::WaitForOperationCompletion: END\n")); return S_OK; } STDMETHODIMP Progress::Cancel() { AutoLock lock(this); CHECK_READY(); if (!mCancelable) return setError (E_FAIL, tr ("Operation cannot be cancelled")); /// @todo (dmik): implement operation cancellation! // mCompleted = TRUE; // mCanceled = TRUE; // return S_OK; ComAssertMsgFailed (("Not implemented!")); return E_NOTIMPL; } // 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]). */ HRESULT Progress::notifyProgress (LONG aPercent) { AutoLock lock (this); AssertReturn (isReady(), E_UNEXPECTED); AssertReturn (!mCompleted && !mCanceled, E_FAIL); AssertReturn (aPercent >= 0 && aPercent <= 100, E_INVALIDARG); mOperationPercent = aPercent; 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. */ HRESULT Progress::advanceOperation (const BSTR aOperationDescription) { AssertReturn (aOperationDescription, E_INVALIDARG); AutoLock lock (this); AssertReturn (isReady(), E_UNEXPECTED); AssertReturn (!mCompleted && !mCanceled, E_FAIL); AssertReturn (mOperation + 1 < mOperationCount, E_FAIL); mOperation ++; mOperationDescription = aOperationDescription; mOperationPercent = 0; // wake up all waiting threads if (mWaitersCount > 0) RTSemEventMultiSignal (mCompletedSem); 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 * * @param aResultCode Operation result code */ HRESULT Progress::notifyComplete (HRESULT aResultCode) { AutoLock lock (this); AssertReturn (isReady(), E_FAIL); mCompleted = TRUE; mResultCode = aResultCode; HRESULT rc = S_OK; if (FAILED (aResultCode)) { /* try to import error info from the current thread */ #if !defined (VBOX_WITH_XPCOM) #if defined (RT_OS_WINDOWS) ComPtr err; rc = ::GetErrorInfo (0, err.asOutParam()); if (rc == S_OK && err) { rc = err.queryInterfaceTo (mErrorInfo.asOutParam()); if (SUCCEEDED (rc) && !mErrorInfo) rc = E_FAIL; } #endif // !defined (RT_OS_WINDOWS) #else // !defined (VBOX_WITH_XPCOM) nsCOMPtr es; es = do_GetService (NS_EXCEPTIONSERVICE_CONTRACTID, &rc); if (NS_SUCCEEDED (rc)) { nsCOMPtr em; rc = es->GetCurrentExceptionManager (getter_AddRefs (em)); if (NS_SUCCEEDED (rc)) { ComPtr ex; rc = em->GetCurrentException (ex.asOutParam()); if (NS_SUCCEEDED (rc) && ex) { rc = ex.queryInterfaceTo (mErrorInfo.asOutParam()); if (NS_SUCCEEDED (rc) && !mErrorInfo) rc = E_FAIL; } } } #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)); } else { mOperation = mOperationCount - 1; /* last operation */ mOperationPercent = 100; } #if !defined VBOX_COM_INPROC /* remove from the global collection of pending progress operations */ if (mParent) mParent->removeProgress (mId); #endif /* wake up all waiting threads */ if (mWaitersCount > 0) RTSemEventMultiSignal (mCompletedSem); return rc; } /** * Marks the operation as complete and attaches full error info. * See VirtualBoxSupportErrorInfoImpl::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 */ HRESULT Progress::notifyComplete (HRESULT aResultCode, const GUID &aIID, const Bstr &aComponent, const char *aText, ...) { va_list args; va_start (args, aText); Bstr text = Utf8StrFmt (aText, args); va_end (args); return notifyCompleteBstr (aResultCode, aIID, aComponent, text); } /** * Marks the operation as complete and attaches full error info. * See VirtualBoxSupportErrorInfoImpl::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) */ HRESULT Progress::notifyCompleteBstr (HRESULT aResultCode, const GUID &aIID, const Bstr &aComponent, const Bstr &aText) { AutoLock lock (this); AssertReturn (isReady(), E_UNEXPECTED); mCompleted = TRUE; mResultCode = aResultCode; AssertReturn (FAILED (aResultCode), E_FAIL); ComObjPtr errorInfo; HRESULT rc = errorInfo.createObject(); AssertComRC (rc); if (SUCCEEDED (rc)) { errorInfo->init (aResultCode, aIID, aComponent, aText); errorInfo.queryInterfaceTo (mErrorInfo.asOutParam()); } #if !defined VBOX_COM_INPROC /* remove from the global collection of pending progress operations */ if (mParent) mParent->removeProgress (mId); #endif /* wake up all waiting threads */ if (mWaitersCount > 0) RTSemEventMultiSignal (mCompletedSem); return rc; } //////////////////////////////////////////////////////////////////////////////// // CombinedProgress class //////////////////////////////////////////////////////////////////////////////// HRESULT CombinedProgress::FinalConstruct() { HRESULT rc = ProgressBase::FinalConstruct(); if (FAILED (rc)) return rc; 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 aParent see ProgressBase::init() * @param aInitiator see ProgressBase::init() * @param aDescription see ProgressBase::init() * @param aId see ProgressBase::init() */ HRESULT CombinedProgress::protectedInit ( #if !defined (VBOX_COM_INPROC) VirtualBox *aParent, #endif IUnknown *aInitiator, const BSTR aDescription, GUIDPARAMOUT aId) { LogFlowMember (("CombinedProgress::protectedInit(): " "aDescription={%ls} mProgresses.size()=%d\n", aDescription, mProgresses.size())); HRESULT rc = S_OK; do { rc = ProgressBase::protectedInit ( #if !defined (VBOX_COM_INPROC) aParent, #endif aInitiator, aDescription, aId); CheckComRCBreakRC (rc); // set ready to let protectedUninit() be called on failure setReady (true); mProgress = 0; // the first object mCompletedOperations = 0; mCompleted = FALSE; mCancelable = TRUE; // until any progress returns FALSE mCanceled = FALSE; mOperationCount = 0; // will be calculated later mOperation = 0; rc = mProgresses [0]->COMGETTER(OperationDescription) ( mOperationDescription.asOutParam()); CheckComRCBreakRC (rc); for (size_t i = 0; i < mProgresses.size(); i ++) { if (mCancelable) { BOOL cancelable = FALSE; rc = mProgresses [i]->COMGETTER(Cancelable) (&cancelable); if (FAILED (rc)) return rc; if (!cancelable) mCancelable = FALSE; } { ULONG opCount = 0; rc = mProgresses [i]->COMGETTER(OperationCount) (&opCount); if (FAILED (rc)) return rc; mOperationCount += opCount; } } rc = checkProgress(); CheckComRCBreakRC (rc); } while (0); if (FAILED (rc)) uninit(); 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() { LogFlowMember (("CombinedProgress::uninit()\n")); AutoLock alock (this); LogFlowMember (("CombinedProgress::uninit(): isReady=%d\n", isReady())); if (!isReady()) return; mProgress = 0; mProgresses.clear(); ProgressBase::protectedUninit (alock); } // IProgress properties //////////////////////////////////////////////////////////////////////////////// STDMETHODIMP CombinedProgress::COMGETTER(Percent) (LONG *aPercent) { if (!aPercent) return E_POINTER; AutoLock lock(this); CHECK_READY(); if (mCompleted && SUCCEEDED (mResultCode)) *aPercent = 100; else { HRESULT rc = checkProgress(); if (FAILED (rc)) return rc; // global percent = (100 / mOperationCount) * mOperation + // ((100 / mOperationCount) / 100) * mOperationPercent *aPercent = (100 * mOperation + mOperationPercent) / mOperationCount; } return S_OK; } STDMETHODIMP CombinedProgress::COMGETTER(Completed) (BOOL *aCompleted) { if (!aCompleted) return E_POINTER; AutoLock lock(this); CHECK_READY(); HRESULT rc = checkProgress(); if (FAILED (rc)) return rc; return ProgressBase::COMGETTER(Completed) (aCompleted); } STDMETHODIMP CombinedProgress::COMGETTER(Canceled) (BOOL *aCanceled) { if (!aCanceled) return E_POINTER; AutoLock lock(this); CHECK_READY(); HRESULT rc = checkProgress(); if (FAILED (rc)) return rc; return ProgressBase::COMGETTER(Canceled) (aCanceled); } STDMETHODIMP CombinedProgress::COMGETTER(ResultCode) (HRESULT *aResultCode) { if (!aResultCode) return E_POINTER; AutoLock lock(this); CHECK_READY(); HRESULT rc = checkProgress(); if (FAILED (rc)) return rc; return ProgressBase::COMGETTER(ResultCode) (aResultCode); } STDMETHODIMP CombinedProgress::COMGETTER(ErrorInfo) (IVirtualBoxErrorInfo **aErrorInfo) { if (!aErrorInfo) return E_POINTER; AutoLock lock(this); CHECK_READY(); HRESULT rc = checkProgress(); if (FAILED (rc)) return rc; return ProgressBase::COMGETTER(ErrorInfo) (aErrorInfo); } STDMETHODIMP CombinedProgress::COMGETTER(Operation) (ULONG *aOperation) { if (!aOperation) return E_POINTER; AutoLock lock(this); CHECK_READY(); HRESULT rc = checkProgress(); if (FAILED (rc)) return rc; return ProgressBase::COMGETTER(Operation) (aOperation); } STDMETHODIMP CombinedProgress::COMGETTER(OperationDescription) (BSTR *aOperationDescription) { if (!aOperationDescription) return E_POINTER; AutoLock lock(this); CHECK_READY(); HRESULT rc = checkProgress(); if (FAILED (rc)) return rc; return ProgressBase::COMGETTER(OperationDescription) (aOperationDescription); } STDMETHODIMP CombinedProgress::COMGETTER(OperationPercent) (LONG *aOperationPercent) { if (!aOperationPercent) return E_POINTER; AutoLock lock(this); CHECK_READY(); HRESULT rc = checkProgress(); if (FAILED (rc)) return rc; return ProgressBase::COMGETTER(OperationPercent) (aOperationPercent); } // 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. */ STDMETHODIMP CombinedProgress::WaitForCompletion (LONG aTimeout) { LogFlowMember (("CombinedProgress::WaitForCompletion: BEGIN: timeout=%d\n", aTimeout)); AutoLock lock (this); CHECK_READY(); // if we're already completed, take a shortcut if (!mCompleted) { RTTIMESPEC time; RTTimeNow (&time); HRESULT rc = S_OK; bool forever = aTimeout < 0; int64_t timeLeft = aTimeout; int64_t lastTime = RTTimeSpecGetMilli (&time); while (!mCompleted && (forever || timeLeft > 0)) { lock.unlock(); rc = mProgresses.back()->WaitForCompletion ( forever ? -1 : (LONG) timeLeft); lock.lock(); // the progress might have been uninitialized if (!isReady()) break; if (SUCCEEDED (rc)) rc = checkProgress(); if (FAILED (rc)) break; if (!forever) { RTTimeNow (&time); timeLeft -= RTTimeSpecGetMilli (&time) - lastTime; lastTime = RTTimeSpecGetMilli (&time); } } if (FAILED (rc)) return rc; } LogFlowMember(("CombinedProgress::WaitForCompletion: END\n")); 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. */ STDMETHODIMP CombinedProgress::WaitForOperationCompletion (ULONG aOperation, LONG aTimeout) { LogFlowMember(("CombinedProgress::WaitForOperationCompletion: BEGIN: " "operation=%d, timeout=%d\n", aOperation, aTimeout)); AutoLock lock(this); CHECK_READY(); if (aOperation >= mOperationCount) return setError (E_FAIL, tr ("Operation number must be in range [0, %d]"), mOperation - 1); // if we're already completed or if the given operation is already done, // then take a shortcut if (!mCompleted && aOperation >= mOperation) { HRESULT rc = S_OK; // find the right progress object to wait for size_t progress = mProgress; ULONG operation = 0, completedOps = mCompletedOperations; do { ULONG opCount = 0; rc = mProgresses [progress]->COMGETTER(OperationCount) (&opCount); if (FAILED (rc)) return rc; if (completedOps + opCount > aOperation) { // found the right progress object operation = aOperation - completedOps; break; } completedOps += opCount; progress ++; ComAssertRet (progress < mProgresses.size(), E_FAIL); } while (1); LogFlowMember (("CombinedProgress::WaitForOperationCompletion(): " "will wait for mProgresses [%d] (%d)\n", progress, operation)); RTTIMESPEC time; RTTimeNow (&time); bool forever = aTimeout < 0; int64_t timeLeft = aTimeout; int64_t lastTime = RTTimeSpecGetMilli (&time); while (!mCompleted && aOperation >= mOperation && (forever || timeLeft > 0)) { lock.unlock(); // wait for the appropriate progress operation completion rc = mProgresses [progress]-> WaitForOperationCompletion ( operation, forever ? -1 : (LONG) timeLeft); lock.lock(); // the progress might have been uninitialized if (!isReady()) break; if (SUCCEEDED (rc)) rc = checkProgress(); if (FAILED (rc)) break; if (!forever) { RTTimeNow (&time); timeLeft -= RTTimeSpecGetMilli (&time) - lastTime; lastTime = RTTimeSpecGetMilli (&time); } } if (FAILED (rc)) return rc; } LogFlowMember(("CombinedProgress::WaitForOperationCompletion: END\n")); return S_OK; } STDMETHODIMP CombinedProgress::Cancel() { AutoLock lock(this); CHECK_READY(); if (!mCancelable) return setError (E_FAIL, tr ("Operation cannot be cancelled")); /// @todo (dmik): implement operation cancellation! // mCompleted = TRUE; // mCanceled = TRUE; // return S_OK; return setError (E_NOTIMPL, ("Not implemented!")); } // 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 the object's lock! */ HRESULT CombinedProgress::checkProgress() { // do nothing if we're already marked ourselves as completed if (mCompleted) return S_OK; ComAssertRet (mProgress < mProgresses.size(), E_FAIL); ComPtr progress = mProgresses [mProgress]; ComAssertRet (!progress.isNull(), E_FAIL); HRESULT rc = S_OK; BOOL completed = FALSE; do { rc = progress->COMGETTER(Completed) (&completed); if (FAILED (rc)) return rc; if (completed) { rc = progress->COMGETTER(Canceled) (&mCanceled); if (FAILED (rc)) return rc; rc = progress->COMGETTER(ResultCode) (&mResultCode); if (FAILED (rc)) return rc; if (FAILED (mResultCode)) { rc = progress->COMGETTER(ErrorInfo) (mErrorInfo.asOutParam()); if (FAILED (rc)) return rc; } if (FAILED (mResultCode) || mCanceled) { mCompleted = TRUE; } else { ULONG opCount = 0; rc = progress->COMGETTER(OperationCount) (&opCount); if (FAILED (rc)) return rc; mCompletedOperations += opCount; mProgress ++; if (mProgress < mProgresses.size()) progress = mProgresses [mProgress]; else mCompleted = TRUE; } } } while (completed && !mCompleted); rc = progress->COMGETTER(OperationPercent) (&mOperationPercent); if (SUCCEEDED (rc)) { ULONG operation = 0; rc = progress->COMGETTER(Operation) (&operation); if (SUCCEEDED (rc) && mCompletedOperations + operation > mOperation) { mOperation = mCompletedOperations + operation; rc = progress->COMGETTER(OperationDescription) ( mOperationDescription.asOutParam()); } } return rc; }