#include "shellprv.h" #include "ids.h" #pragma hdrstop #include "isproc.h" #include "ConfirmationUI.h" #include "clsobj.h" #include "datautil.h" #include "prop.h" // SCID_SIZE BOOL _HasAttributes(IShellItem *psi, SFGAOF flags); STDAPI CStorageProcessor_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) { CComObject *pObj = NULL; HRESULT hr = CComObject::CreateInstance(&pObj); if (SUCCEEDED(hr)) { // ATL creates the object with no refcount, but this initial QI will give it one hr = pObj->QueryInterface(riid, ppv); if (SUCCEEDED(hr)) return hr; else delete pObj; } *ppv = NULL; return hr; } // // These operators allow me to mix int64 types with the old LARGE_INTEGER // unions without messing with the QuadPart members in the code. // inline ULONGLONG operator + (const ULARGE_INTEGER i, const ULARGE_INTEGER j) { return i.QuadPart + j.QuadPart; } inline ULONGLONG operator + (const ULONGLONG i, const ULARGE_INTEGER j) { return i + j.QuadPart; } // // Progress dialog text while gathering stats. Unordered, unsorted lookup table. // #define OPDETAIL(op, title, prep, action) {op, title, prep, action} const STGOP_DETAIL s_opdetail[] = { OPDETAIL(STGOP_STATS, IDS_GATHERINGSTATS, IDS_SCANNING, SPACTION_CALCULATING), OPDETAIL(STGOP_COPY, IDS_ACTIONTITLECOPY, IDS_PREPARINGTOCOPY, SPACTION_COPYING), OPDETAIL(STGOP_COPY_PREFERHARDLINK, IDS_ACTIONTITLECOPY, IDS_PREPARINGTOCOPY, SPACTION_COPYING), OPDETAIL(STGOP_MOVE, IDS_ACTIONTITLEMOVE, IDS_PREPARINGTOMOVE, SPACTION_MOVING), }; CStorageProcessor::CStorageProcessor() : _clsidLinkFactory(CLSID_ShellLink) { ASSERT(!_msTicksLast); ASSERT(!_msStarted); ASSERT(!_pstatSrc); ASSERT(!_ptc); } CStorageProcessor::~CStorageProcessor() { ATOMICRELEASE(_ptc); if (_dsaConfirmationResponses) _dsaConfirmationResponses.Destroy(); } HRESULT CStorageProcessor::GetWindow(HWND * phwnd) { return IUnknown_GetWindow(_spProgress, phwnd); } // Placeholder. If I move to an exception model, I'll add errorinfo support, // but not in the current implementation STDMETHODIMP CStorageProcessor::InterfaceSupportsErrorInfo(REFIID riid) { return S_FALSE; } // Allows clients to register an advise sink STDMETHODIMP CStorageProcessor::Advise(ITransferAdviseSink *pAdvise, DWORD *pdwCookie) { *pdwCookie = 0; for (DWORD i = 0; i < ARRAYSIZE(_aspSinks); i++) { if (!_aspSinks[i]) { _aspSinks[i] = pAdvise; // smart pointer, do not call pAdvise->AddRef(); *pdwCookie = i+1; // Make it 1-based so 0 is not valid return S_OK; } } return E_OUTOFMEMORY; // No empty slots } // Allows clients to register an advise sink STDMETHODIMP CStorageProcessor::Unadvise(DWORD dwCookie) { // Remember dwCookie == slot + 1, to be 1-based if (!dwCookie || dwCookie > ARRAYSIZE(_aspSinks)) return E_INVALIDARG; if (!_aspSinks[dwCookie-1]) return E_INVALIDARG; _aspSinks[dwCookie-1] = NULL; // smart pointer, no need to release return S_OK; } // Computes stats (if requested) and launches the actual storage operation STDMETHODIMP CStorageProcessor::Run(IEnumShellItems *penum, IShellItem *psiDest, STGOP dwOperation, DWORD dwOptions) { if (!penum || !psiDest) return E_INVALIDARG; ITransferDest *ptdDest; HRESULT hr = _BindToHandlerWithMode(psiDest, STGX_MODE_READWRITE, IID_PPV_ARG(ITransferDest, &ptdDest)); if (SUCCEEDED(hr)) { hr = _Run(penum, psiDest, ptdDest, dwOperation, dwOptions); ptdDest->Release(); } return hr; } // defined in copy.c EXTERN_C void DisplayFileOperationError(HWND hParent, int idVerb, int wFunc, int nError, LPCTSTR szReason, LPCTSTR szPath, LPCTSTR szDest); STDMETHODIMP CStorageProcessor::_Run(IEnumShellItems *penum, IShellItem *psiDest, ITransferDest *ptdDest, STGOP dwOperation, DWORD dwOptions) { switch (dwOperation) { case STGOP_MOVE: case STGOP_COPY: case STGOP_STATS: case STGOP_REMOVE: case STGOP_COPY_PREFERHARDLINK: // parameter validation done in ::Run break; // not yet implemented case STGOP_RENAME: case STGOP_DIFF: case STGOP_SYNC: return E_NOTIMPL; // any other value is an invalid operation default: return E_INVALIDARG; } const STGOP_DETAIL *popd = NULL; for (int i=0; i < ARRAYSIZE(s_opdetail); i++) { if (s_opdetail[i].stgop == dwOperation) { popd = &s_opdetail[i]; break; } } if (!_dsaConfirmationResponses) { // If we don't have a confirmation array yet, make one _dsaConfirmationResponses.Create(4); } else { // well, no one currently reuses the engine for multiple operations // but, move operation reenters the engine (for recursive move) // so we need to preserve the answers, so comment this out // If we do have one then it's got left over confirmations from the previous call // to run so we should delete all those. //_dsaConfirmationResponses.DeleteAllItems(); } if (popd) { PreOperation(dwOperation, NULL, NULL); HRESULT hr = S_FALSE; if (IsFlagClear(dwOptions, STOPT_NOSTATS)) { if (IsFlagClear(dwOptions, STOPT_NOPROGRESSUI)) _StartProgressDialog(popd); if (_spProgress) { // Put the "Preparing to Whatever" text in the dialog WCHAR szText[MAX_PATH]; LoadStringW(_Module.GetModuleInstance(), popd->idPrep, szText, ARRAYSIZE(szText)); _spProgress->UpdateText(SPTEXT_ACTIONDETAIL, szText, TRUE); } // Compute the stats we need _dwOperation = STGOP_STATS; _dwOptions = STOPT_NOCONFIRMATIONS; HRESULT hrProgressBegin; if (_spProgress) hrProgressBegin = _spProgress->Begin(SPACTION_SEARCHING_FILES, SPBEGINF_MARQUEEPROGRESS); penum->Reset(); hr = _WalkStorage(penum, psiDest, ptdDest); if (_spProgress && SUCCEEDED(hrProgressBegin)) { _spProgress->End(); // Remove the "Preparing to Whatever" text from the dialog _spProgress->UpdateText(SPTEXT_ACTIONDETAIL, L"", FALSE); } } if (SUCCEEDED(hr)) { _dwOperation = (STGOP) dwOperation; _dwOptions = dwOptions; HRESULT hrProgressBegin; if (_spProgress) hrProgressBegin = _spProgress->Begin(popd->spa, SPBEGINF_AUTOTIME); penum->Reset(); hr = _WalkStorage(penum, psiDest, ptdDest); if (_spProgress && SUCCEEDED(hrProgressBegin)) _spProgress->End(); } if (IsFlagClear(dwOptions, STOPT_NOSTATS) && _spProgress) { // this should only be called if we called the matching FlagClear-NOSTATS above. // smartpointers NULL on .Release(); _spProgress.Release(); if (_spShellProgress) { _spShellProgress->Stop(); _spShellProgress.Release(); } } SHChangeNotifyHandleEvents(); PostOperation(dwOperation, NULL, NULL, hr); return hr; } else { AssertMsg(0, TEXT("A valid operation is missing from the s_opdetail array, was a new operation added? (%d)"), dwOperation); } return E_INVALIDARG; } // Does a depth-first walk of the storage performing the requested // operation. HRESULT CStorageProcessor::_WalkStorage(IShellItem *psi, IShellItem *psiDest, ITransferDest *ptdDest) { HRESULT hr = S_FALSE; if (_ShouldWalk(psi)) { IEnumShellItems *penum; hr = psi->BindToHandler(NULL, BHID_StorageEnum, IID_PPV_ARG(IEnumShellItems, &penum)); if (SUCCEEDED(hr)) { hr = _WalkStorage(penum, psiDest, ptdDest); penum->Release(); } } return hr; } HRESULT CStorageProcessor::_WalkStorage(IEnumShellItems *penum, IShellItem *psiDest, ITransferDest *ptdDest) { DWORD dwCookie; if (ptdDest) ptdDest->Advise(static_cast(this), &dwCookie); HRESULT hr; IShellItem *psi; while (S_OK == (hr = penum->Next(1, &psi, NULL))) { // skip anything we can't work with if (_HasAttributes(psi, SFGAO_STORAGE | SFGAO_STREAM)) { if (_spProgress) { // We don't show filenames while collecting stats if (_dwOperation != STGOP_STATS) { LPWSTR pszName; if (SUCCEEDED(psi->GetDisplayName(SIGDN_PARENTRELATIVEFORADDRESSBAR, &pszName))) { _spProgress->UpdateText(SPTEXT_ACTIONDETAIL, pszName, TRUE); CoTaskMemFree(pszName); } } } if (_dwOperation != STGOP_STATS) _UpdateProgress(0, 0); DWORD dwFlagsExtra = 0; switch (_dwOperation) { case STGOP_STATS: hr = _DoStats(psi); break; case STGOP_COPY_PREFERHARDLINK: dwFlagsExtra = STGX_MOVE_PREFERHARDLINK; // fall through case STGOP_COPY: hr = _DoCopy(psi, psiDest, ptdDest, dwFlagsExtra); break; case STGOP_MOVE: hr = _DoMove(psi, psiDest, ptdDest); break; case STGOP_REMOVE: hr = _DoRemove(psi, psiDest, ptdDest); break; case STGOP_RENAME: case STGOP_DIFF: case STGOP_SYNC: hr = E_NOTIMPL; break; default: hr = E_UNEXPECTED; break; } if (S_OK != QueryContinue()) hr = STRESPONSE_CANCEL; } else if (STGOP_COPY_PREFERHARDLINK == _dwOperation || STGOP_COPY == _dwOperation || STGOP_MOVE == _dwOperation) { CUSTOMCONFIRMATION cc = {sizeof(cc)}; cc.dwButtons = CCB_OK; cc.dwFlags = CCF_SHOW_SOURCE_INFO | CCF_USE_DEFAULT_ICON; UINT idDesc = (STGOP_MOVE == _dwOperation ? IDS_NO_STORAGE_MOVE : IDS_NO_STORAGE_COPY); cc.pwszDescription = ResourceCStrToStr(g_hinst, (LPCWSTR)(UINT_PTR)idDesc); if (cc.pwszDescription) { UINT idTitle = (STGOP_MOVE == _dwOperation ? IDS_UNKNOWN_MOVE_TITLE : IDS_UNKNOWN_COPY_TITLE); cc.pwszTitle = ResourceCStrToStr(g_hinst, (LPCWSTR)(UINT_PTR)idTitle); if (cc.pwszTitle) { ConfirmOperation(psi, NULL, GUID_NULL, &cc); LocalFree(cc.pwszTitle); } LocalFree(cc.pwszDescription); } } psi->Release(); if (FAILED(hr) && STRESPONSE_SKIP != hr) break; } // We'll always get to the "no more Streams" stage, so this is meaningless if (S_FALSE == hr) hr = S_OK; if (ptdDest) ptdDest->Unadvise(dwCookie); return hr; } HRESULT CStorageProcessor::_DoConfirmations(STGTRANSCONFIRMATION stc, CUSTOMCONFIRMATION *pcc, IShellItem *psiItem, IShellItem *psiDest) { CONFIRMATIONRESPONSE crResponse = (CONFIRMATIONRESPONSE)E_FAIL; HRESULT hr = _GetDefaultResponse(stc, &crResponse); if (FAILED(hr)) { // If we don't have a default answer, then call the confirmation UI, it will return the repsonse hr = S_OK; // should be able to supply the CLSID of an alternate implementation and we should CoCreate the object. if (!_ptc) hr = CTransferConfirmation_CreateInstance(NULL, IID_PPV_ARG(ITransferConfirmation, &_ptc)); if (SUCCEEDED(hr)) { BOOL bAll; CONFIRMOP cop; cop.dwOperation = _dwOperation; cop.stc = stc; cop.pcc = pcc; cop.cRemaining = _StreamsToDo() + _StoragesToDo(); cop.psiItem = psiItem; cop.psiDest = psiDest; cop.pwszRenameTo = NULL; cop.punkSite = SAFECAST(this, IStorageProcessor*); hr = _ptc->Confirm(&cop, &crResponse, &bAll); if (SUCCEEDED(hr)) { if (bAll) { // if the confirmation UI says "do for all" then add hrResponse to the default response map. STC_CR_PAIR scp(stc, crResponse); _dsaConfirmationResponses.AppendItem(&scp); } } else { // TODO: What do we do if we fail to ask for confirmation? } } } // TODO: Get rid of CONFIRMATIONRESPONSE and make these the same if (SUCCEEDED(hr)) { switch (crResponse) { case CONFRES_CONTINUE: hr = STRESPONSE_CONTINUE; break; case CONFRES_SKIP: hr = STRESPONSE_SKIP; break; case CONFRES_RETRY: hr = STRESPONSE_RETRY; break; case CONFRES_RENAME: hr = STRESPONSE_RENAME; break; case CONFRES_CANCEL: case CONFRES_UNDO: hr = STRESPONSE_CANCEL; break; } } return hr; } HRESULT CStorageProcessor::_GetDefaultResponse(STGTRANSCONFIRMATION stc, LPCONFIRMATIONRESPONSE pcrResponse) { // Look in our map to see if there's already been a default response // set for this condition for (int i=0; i<_dsaConfirmationResponses.GetItemCount(); i++) { STC_CR_PAIR *pscp = _dsaConfirmationResponses.GetItemPtr(i); if (*pscp == stc) { *pcrResponse = pscp->cr; return S_OK; } } return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); } HRESULT CStorageProcessor::_BindToHandlerWithMode(IShellItem *psi, STGXMODE grfMode, REFIID riid, void **ppv) { HRESULT hr = S_OK; IBindCtx *pbc = NULL; if (grfMode) hr = BindCtx_CreateWithMode(grfMode, &pbc); // need to translate mode flags? if (SUCCEEDED(hr)) { GUID bhid; if (IsEqualGUID(riid, IID_IStorage)) bhid = BHID_Storage; else if (IsEqualGUID(riid, IID_IStream)) bhid = BHID_Stream; else bhid = BHID_SFObject; hr = psi->BindToHandler(pbc, bhid, riid, ppv); if (FAILED(hr) && IsEqualGUID(riid, IID_ITransferDest)) hr = CreateStg2StgExWrapper(psi, this, (ITransferDest **)ppv); if (pbc) pbc->Release(); } return hr; } BOOL _HasAttributes(IShellItem *psi, SFGAOF flags) { BOOL fReturn = FALSE; SFGAOF flagsOut; if (SUCCEEDED(psi->GetAttributes(flags, &flagsOut)) && (flags & flagsOut)) fReturn = TRUE; return fReturn; } BOOL CStorageProcessor::_IsStream(IShellItem *psi) { return _HasAttributes(psi, SFGAO_STREAM); } BOOL CStorageProcessor::_ShouldWalk(IShellItem *psi) { return _HasAttributes(psi, SFGAO_STORAGE); } ULONGLONG CStorageProcessor::_GetSize(IShellItem *psi) { ULONGLONG ullReturn = 0; // first, try to get size from the pidl, so we don't hit the disk IParentAndItem *ppai; HRESULT hr = psi->QueryInterface(IID_PPV_ARG(IParentAndItem, &ppai)); if (SUCCEEDED(hr)) { IShellFolder *psf; LPITEMIDLIST pidlChild; hr = ppai->GetParentAndItem(NULL, &psf, &pidlChild); if (SUCCEEDED(hr)) { IShellFolder2 *psf2; hr = psf->QueryInterface(IID_PPV_ARG(IShellFolder2, &psf2)); if (SUCCEEDED(hr)) { hr = GetLongProperty(psf2, pidlChild, &SCID_SIZE, &ullReturn); psf2->Release(); } psf->Release(); ILFree(pidlChild); } ppai->Release(); } // if it failed, try the stream if (FAILED(hr)) { //this should ask for IPropertySetStorage instead of stream... IStream *pstrm; if (SUCCEEDED(_BindToHandlerWithMode(psi, STGX_MODE_READ, IID_PPV_ARG(IStream, &pstrm)))) { STATSTG stat; if (SUCCEEDED(pstrm->Stat(&stat, STATFLAG_NONAME))) ullReturn = stat.cbSize.QuadPart; pstrm->Release(); } } return ullReturn; } HRESULT CStorageProcessor::_DoStats(IShellItem *psi) { HRESULT hr = PreOperation(STGOP_STATS, psi, NULL); if (FAILED(hr)) return hr; if (!_IsStream(psi)) { _statsTodo.AddStorage(); hr = _WalkStorage(psi, NULL, NULL); } else { _statsTodo.AddStream(_GetSize(psi)); } PostOperation(STGOP_STATS, psi, NULL, hr); return hr; } HRESULT CStorageProcessor::_DoCopy(IShellItem *psi, IShellItem *psiDest, ITransferDest *ptdDest, DWORD dwStgXFlags) { HRESULT hr = PreOperation(STGOP_COPY, psi, psiDest); if (FAILED(hr)) return hr; LPWSTR pszNewName; hr = AutoCreateName(psiDest, psi, &pszNewName); if (SUCCEEDED(hr)) { do { hr = ptdDest->MoveElement(psi, pszNewName, STGX_MOVE_COPY | STGX_MOVE_NORECURSION | dwStgXFlags); } while (STRESPONSE_RETRY == hr); if (SUCCEEDED(hr)) { if (!_IsStream(psi)) { _statsDone.AddStorage(); // Open the source IShellItem *psiNewDest; hr = SHCreateShellItemFromParent(psiDest, pszNewName, &psiNewDest); if (SUCCEEDED(hr)) { ITransferDest *ptdNewDest; hr = _BindToHandlerWithMode(psiNewDest, STGX_MODE_READWRITE, IID_PPV_ARG(ITransferDest, &ptdNewDest)); if (SUCCEEDED(hr)) { // And copy everything underneath hr = _WalkStorage(psi, psiNewDest, ptdNewDest); ptdNewDest->Release(); } psiNewDest->Release(); } } else { _statsDone.AddStream(_GetSize(psi)); } } CoTaskMemFree(pszNewName); } PostOperation(STGOP_COPY, psi, psiDest, hr); return hr; } HRESULT CStorageProcessor::_DoMove(IShellItem *psi, IShellItem *psiDest, ITransferDest *ptdDest) { HRESULT hr = PreOperation(STGOP_MOVE, psi, psiDest); if (FAILED(hr)) return hr; LPWSTR pszNewName; hr = AutoCreateName(psiDest, psi, &pszNewName); if (SUCCEEDED(hr)) { do { hr = ptdDest->MoveElement(psi, pszNewName, STGX_MOVE_MOVE); } while (STRESPONSE_RETRY == hr); if (SUCCEEDED(hr)) { if (!_IsStream(psi)) { _statsDone.AddStorage(); } else { _statsDone.AddStream(_GetSize(psi)); } } CoTaskMemFree(pszNewName); } PostOperation(STGOP_MOVE, psi, psiDest, hr); return hr; } HRESULT CStorageProcessor::_DoRemove(IShellItem *psi, IShellItem *psiDest, ITransferDest *ptdDest) { HRESULT hr = PreOperation(STGOP_REMOVE, psi, NULL); if (FAILED(hr)) return hr; LPWSTR pszName; hr = psi->GetDisplayName(SIGDN_PARENTRELATIVEFORADDRESSBAR, &pszName); if (SUCCEEDED(hr)) { BOOL fStorage = !_IsStream(psi); ULONGLONG ullSize; if (!fStorage) ullSize = _GetSize(psi); // try to delete the entire storage in one operation do { hr = ptdDest->DestroyElement(pszName, 0); } while (STRESPONSE_RETRY == hr); if (FAILED(hr) && STRESPONSE_SKIP != hr && fStorage) { // if we fail then walk down deleting the contents hr = _WalkStorage(psi, psiDest, ptdDest); if (SUCCEEDED(hr)) { // see if we can delete the storage now that it's empty do { hr = ptdDest->DestroyElement(pszName, 0); } while (STRESPONSE_RETRY == hr); } } if (SUCCEEDED(hr)) { if (fStorage) { _statsDone.AddStorage(); } else { _statsDone.AddStream(ullSize); } } CoTaskMemFree(pszName); } PostOperation(STGOP_REMOVE, psi, NULL, hr); return hr; } // Recomputes the amount of estimated time remaining, and if progress // is being displayed, updates the dialog as well // TODO: This doesn't take into account any items that are skipped. Skipped items // will still be considered undone which means the operation will finish before the // progress bar reaches the end. To accurately remove the skipped items we would need // to either: // 1.) Walk a storage if it is skipped, counting the bytes // 2.) Remember the counts in a tree when we first walked the storage // // Of these options I like #1 better since its simpler and #2 would waste memory to hold // a bunch of information we can recalculate (we're already doing a sloooow operation anyway). #define MINIMUM_UPDATE_INTERVAL 1000 #define HISTORICAL_POINT_WEIGHTING 50 #define TIME_BEFORE_SHOWING_ESTIMATE 5000 void CStorageProcessor::_UpdateProgress(ULONGLONG ullCurrentComplete, ULONGLONG ullCurrentTotal) { // Ensure at least N ms has elapsed since last update DWORD msNow = GetTickCount(); if ((msNow - _msTicksLast) >= MINIMUM_UPDATE_INTERVAL) { // Calc the estimated total cost to finish and work done so far ULONGLONG ullTotal = _statsTodo.Cost(_dwOperation, 0); if (ullTotal) { ULONGLONG cbExtra = ullCurrentTotal ? (_cbCurrentSize / ullCurrentTotal) * ullCurrentComplete : 0; ULONGLONG ullDone = _statsDone.Cost(_dwOperation, cbExtra); // Regardless of whether we update the text, update the status bar if (_spProgress) _spProgress->UpdateProgress(ullDone, ullTotal); for (int i = 0; i < ARRAYSIZE(_aspSinks); i++) { if (_aspSinks[i]) { HRESULT hr = _aspSinks[i]->OperationProgress(_dwOperation, NULL, NULL, ullTotal, ullDone); if (FAILED(hr)) break; } } } _msTicksLast = msNow; } } DWORD CStorageProcessor::CStgStatistics::AddStream(ULONGLONG cbSize) { _cbSize += cbSize; return ++_cStreams; } DWORD CStorageProcessor::CStgStatistics::AddStorage() { return ++_cStorages; } // Computes the total time cost of performing the storage operation // after the stats have been collected #define COST_PER_DELETE 1 #define COST_PER_CREATE 1 ULONGLONG CStorageProcessor::CStgStatistics::Cost(DWORD op, ULONGLONG cbExtra) const { ULONGLONG ullTotalCost = 0; // Copy and Move both need to create the target and move the bits if (op == STGOP_COPY || op == STGOP_MOVE || op == STGOP_COPY_PREFERHARDLINK) { ullTotalCost += Bytes() + cbExtra; ullTotalCost += (Streams() + Storages()) * COST_PER_CREATE; } // Move and Remove need to delete the originals if (op == STGOP_MOVE || op == STGOP_REMOVE) { ullTotalCost += (Streams() + Storages()) * COST_PER_DELETE; } return ullTotalCost; } // Figures out what animation and title text should be displayed in // the progress UI, and starts it HRESULT CStorageProcessor::_StartProgressDialog(const STGOP_DETAIL *popd) { HRESULT hr = S_OK; if (!_spProgress) { hr = CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IActionProgressDialog, &_spShellProgress)); if (SUCCEEDED(hr)) { // // Map the requested action to the appropriate strings (like "Preparing to Copy") // ASSERT(popd); WCHAR szText[MAX_PATH]; LoadStringW(_Module.GetModuleInstance(), popd->idTitle, szText, ARRAYSIZE(szText)); hr = _spShellProgress->Initialize(SPINITF_MODAL, szText, NULL); if (SUCCEEDED(hr)) hr = _spShellProgress->QueryInterface(IID_PPV_ARG(IActionProgress, &_spProgress)); } } return hr; } HRESULT CStorageProcessor::SetProgress(IActionProgress *pspaProgress) { HRESULT hr = E_FAIL; if (!_spProgress) { hr = E_INVALIDARG; if (pspaProgress) { _spProgress = pspaProgress; hr = S_OK; } } return hr; } STDMETHODIMP CStorageProcessor::SetLinkFactory(REFCLSID clsid) { _clsidLinkFactory = clsid; return S_OK; } // Runs through the list of registered sinks and gives each of them a shot // at cancelling or skipping this operation STDMETHODIMP CStorageProcessor::PreOperation(const STGOP op, IShellItem *psiItem, IShellItem *psiDest) { if (psiItem) { _cbCurrentSize = _IsStream(psiItem) ? _GetSize(psiItem) : 0; } for (int i = 0; i < ARRAYSIZE(_aspSinks); i++) { if (_aspSinks[i]) { HRESULT hr = _aspSinks[i]->PreOperation(op, psiItem, psiDest); if (FAILED(hr)) return hr; } } return S_OK; } // Allow each of the sinks to confirm the operation if they'd like STDMETHODIMP CStorageProcessor::ConfirmOperation(IShellItem *psiSource, IShellItem *psiDest, STGTRANSCONFIRMATION stc, LPCUSTOMCONFIRMATION pcc) { // TODO: map the confirmation (stc) based on _dwOperation memeber varaible HRESULT hr = STRESPONSE_CONTINUE; for (int i = 0; i < ARRAYSIZE(_aspSinks); i++) { if (_aspSinks[i]) { hr = _aspSinks[i]->ConfirmOperation(psiSource, psiDest, stc, pcc); if (FAILED(hr) || hr == STRESPONSE_RENAME) break; } } // Question: How do we know if one of the above handlers displayed UI already? If the // hr is anything other than STRESPONSE_CONTINUE then obviously the confirmation has been // handled already, but one of the handlers might have diplayed UI and then returned // STRESPONSE_CONTINUE as the users response. if (STRESPONSE_CONTINUE == hr) { // show default UI hr = _DoConfirmations(stc, pcc, psiSource, psiDest); } return hr; } // Apprise each of the sinks as to our current progress STDMETHODIMP CStorageProcessor::OperationProgress(const STGOP op, IShellItem *psiItem, IShellItem *psiDest, ULONGLONG ullTotal, ULONGLONG ullComplete) { HRESULT hr = S_OK; for (int i = 0; SUCCEEDED(hr) && i < ARRAYSIZE(_aspSinks); i++) { if (_aspSinks[i]) { hr = _aspSinks[i]->OperationProgress(op, psiItem, psiDest, ullTotal, ullComplete); } } // CShellItem2TransferDest::_CopyStreamBits doesn't call QueryContinue to check if it should stop copying // so we do it here (because it does call OperationProgress) if (SUCCEEDED(hr)) { hr = QueryContinue(); if (S_FALSE == hr) hr = STRESPONSE_CANCEL; } if (SUCCEEDED(hr)) _UpdateProgress(ullComplete, ullTotal); return hr; } // When the operation is successfully complete, let the advises know STDMETHODIMP CStorageProcessor::PostOperation(const STGOP op, IShellItem *psiItem, IShellItem *psiDest, HRESULT hrResult) { _cbCurrentSize = 0; HRESULT hr = S_OK; for (int i = 0; (S_OK == hr) && (i < ARRAYSIZE(_aspSinks)); i++) { if (_aspSinks[i]) { hr = _aspSinks[i]->PostOperation(op, psiItem, psiDest, hrResult); } } return hr; } HRESULT CStorageProcessor::QueryContinue() { HRESULT hr = S_OK; for (int i = 0; S_OK == hr && i < ARRAYSIZE(_aspSinks); i++) { if (_aspSinks[i]) hr = _aspSinks[i]->QueryContinue(); } if (S_OK == hr && _spProgress) { BOOL fCanceled; if (SUCCEEDED(_spProgress->QueryCancel(&fCanceled)) && fCanceled) hr = S_FALSE; } return hr; } HRESULT EnumShellItemsFromHIDADataObject(IDataObject *pdtobj, IEnumShellItems **ppenum) { *ppenum = NULL; HRESULT hr = E_FAIL; STGMEDIUM medium; LPIDA pida = DataObj_GetHIDA(pdtobj, &medium); if (pida) { LPCITEMIDLIST pidlSource = IDA_GetIDListPtr(pida, -1); if (pidlSource) { IDynamicStorage *pdstg; hr = CoCreateInstance(CLSID_DynamicStorage, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IDynamicStorage, &pdstg)); if (SUCCEEDED(hr)) { LPCITEMIDLIST pidl; for (UINT i = 0; SUCCEEDED(hr) && (pidl = IDA_GetIDListPtr(pida, i)); i++) { LPITEMIDLIST pidlFull; hr = SHILCombine(pidlSource, pidl, &pidlFull); if (SUCCEEDED(hr)) { hr = pdstg->AddIDList(1, &pidlFull, DSTGF_ALLOWDUP); ILFree(pidlFull); } } if (SUCCEEDED(hr)) { hr = pdstg->EnumItems(ppenum); } pdstg->Release(); } } HIDA_ReleaseStgMedium(pida, &medium); } return hr; } HRESULT TransferDataObject(IDataObject *pdoSource, IShellItem *psiDest, STGOP dwOperation, DWORD dwOptions, ITransferAdviseSink *ptas) { IEnumShellItems *penum; HRESULT hr = EnumShellItemsFromHIDADataObject(pdoSource, &penum); if (SUCCEEDED(hr)) { IStorageProcessor *psp; hr = CStorageProcessor_CreateInstance(NULL, IID_PPV_ARG(IStorageProcessor, &psp)); if (SUCCEEDED(hr)) { DWORD dwCookie; HRESULT hrAdvise; if (ptas) { hrAdvise = psp->Advise(ptas, &dwCookie); } hr = psp->Run(penum, psiDest, dwOperation, dwOptions); if (ptas && SUCCEEDED(hrAdvise)) { psp->Unadvise(dwCookie); } psp->Release(); } penum->Release(); } return hr; }