You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1069 lines
32 KiB
1069 lines
32 KiB
#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<CStorageProcessor> *pObj = NULL;
|
|
HRESULT hr = CComObject<CStorageProcessor>::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<ITransferAdviseSink*>(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;
|
|
}
|