Source code of Windows XP (NT5)
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.

1086 lines
31 KiB

  1. #include "shellprv.h"
  2. #include "ids.h"
  3. #pragma hdrstop
  4. #include "isproc.h"
  5. #include "ConfirmationUI.h"
  6. #include "clsobj.h"
  7. #include "datautil.h"
  8. #include "prop.h" // SCID_SIZE
  9. BOOL _HasAttributes(IShellItem *psi, SFGAOF flags);
  10. STDAPI CStorageProcessor_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
  11. {
  12. CComObject<CStorageProcessor> *pObj = NULL;
  13. HRESULT hr = CComObject<CStorageProcessor>::CreateInstance(&pObj);
  14. if (SUCCEEDED(hr))
  15. {
  16. // ATL creates the object with no refcount, but this initial QI will give it one
  17. hr = pObj->QueryInterface(riid, ppv);
  18. if (SUCCEEDED(hr))
  19. return hr;
  20. else
  21. delete pObj;
  22. }
  23. *ppv = NULL;
  24. return hr;
  25. }
  26. //
  27. // These operators allow me to mix int64 types with the old LARGE_INTEGER
  28. // unions without messing with the QuadPart members in the code.
  29. //
  30. inline ULONGLONG operator + (const ULARGE_INTEGER i, const ULARGE_INTEGER j)
  31. {
  32. return i.QuadPart + j.QuadPart;
  33. }
  34. inline ULONGLONG operator + (const ULONGLONG i, const ULARGE_INTEGER j)
  35. {
  36. return i + j.QuadPart;
  37. }
  38. bool operator<(const GUID & lh, const GUID & rh)
  39. {
  40. return (memcmp(&lh, &rh, sizeof(GUID))<0)?TRUE:FALSE;
  41. }
  42. //
  43. // Progress dialog text while gathering stats. Unordered, unsorted lookup table.
  44. //
  45. #define OPDETAIL(op, title, prep, action) {op, title, prep, action}
  46. const STGOP_DETAIL s_opdetail[] =
  47. {
  48. OPDETAIL(STGOP_STATS, IDS_GATHERINGSTATS, IDS_SCANNING, SPACTION_CALCULATING),
  49. OPDETAIL(STGOP_COPY, IDS_ACTIONTITLECOPY, IDS_PREPARINGTOCOPY, SPACTION_COPYING),
  50. OPDETAIL(STGOP_COPY_PREFERHARDLINK, IDS_ACTIONTITLECOPY, IDS_PREPARINGTOCOPY, SPACTION_COPYING),
  51. OPDETAIL(STGOP_MOVE, IDS_ACTIONTITLEMOVE, IDS_PREPARINGTOMOVE, SPACTION_MOVING),
  52. };
  53. CStorageProcessor::CStorageProcessor() : _clsidLinkFactory(CLSID_ShellLink)
  54. {
  55. ASSERT(!_msTicksLast);
  56. ASSERT(!_msStarted);
  57. ASSERT(!_pstatSrc);
  58. ASSERT(!_ptc);
  59. }
  60. CStorageProcessor::~CStorageProcessor()
  61. {
  62. ATOMICRELEASE(_ptc);
  63. if (_dsaConfirmationResponses)
  64. _dsaConfirmationResponses.Destroy();
  65. }
  66. HRESULT CStorageProcessor::GetWindow(HWND * phwnd)
  67. {
  68. return IUnknown_GetWindow(_spProgress, phwnd);
  69. }
  70. // Placeholder. If I move to an exception model, I'll add errorinfo support,
  71. // but not in the current implementation
  72. STDMETHODIMP CStorageProcessor::InterfaceSupportsErrorInfo(REFIID riid)
  73. {
  74. return S_FALSE;
  75. }
  76. // Allows clients to register an advise sink
  77. STDMETHODIMP CStorageProcessor::Advise(ITransferAdviseSink *pAdvise, DWORD *pdwCookie)
  78. {
  79. *pdwCookie = 0;
  80. for (DWORD i = 0; i < ARRAYSIZE(_aspSinks); i++)
  81. {
  82. if (!_aspSinks[i])
  83. {
  84. _aspSinks[i] = pAdvise; // smart pointer, do not call pAdvise->AddRef();
  85. *pdwCookie = i+1; // Make it 1-based so 0 is not valid
  86. return S_OK;
  87. }
  88. }
  89. return E_OUTOFMEMORY; // No empty slots
  90. }
  91. // Allows clients to register an advise sink
  92. STDMETHODIMP CStorageProcessor::Unadvise(DWORD dwCookie)
  93. {
  94. // Remember dwCookie == slot + 1, to be 1-based
  95. if (!dwCookie || dwCookie > ARRAYSIZE(_aspSinks))
  96. return E_INVALIDARG;
  97. if (!_aspSinks[dwCookie-1])
  98. return E_INVALIDARG;
  99. _aspSinks[dwCookie-1] = NULL; // smart pointer, no need to release
  100. return S_OK;
  101. }
  102. // Computes stats (if requested) and launches the actual storage operation
  103. STDMETHODIMP CStorageProcessor::Run(IEnumShellItems *penum, IShellItem *psiDest, STGOP dwOperation, DWORD dwOptions)
  104. {
  105. if (!penum || !psiDest)
  106. return E_INVALIDARG;
  107. ITransferDest *ptdDest;
  108. HRESULT hr = _BindToHandlerWithMode(psiDest, STGX_MODE_READWRITE, IID_PPV_ARG(ITransferDest, &ptdDest));
  109. if (SUCCEEDED(hr))
  110. {
  111. hr = _Run(penum, psiDest, ptdDest, dwOperation, dwOptions);
  112. ptdDest->Release();
  113. }
  114. return hr;
  115. }
  116. // defined in copy.c
  117. EXTERN_C void DisplayFileOperationError(HWND hParent, int idVerb, int wFunc, int nError, LPCTSTR szReason, LPCTSTR szPath, LPCTSTR szDest);
  118. STDMETHODIMP CStorageProcessor::_Run(IEnumShellItems *penum, IShellItem *psiDest, ITransferDest *ptdDest, STGOP dwOperation, DWORD dwOptions)
  119. {
  120. switch (dwOperation)
  121. {
  122. case STGOP_MOVE:
  123. case STGOP_COPY:
  124. case STGOP_STATS:
  125. case STGOP_REMOVE:
  126. case STGOP_COPY_PREFERHARDLINK:
  127. // parameter validation done in ::Run
  128. break;
  129. // not yet implemented
  130. case STGOP_RENAME:
  131. case STGOP_DIFF:
  132. case STGOP_SYNC:
  133. return E_NOTIMPL;
  134. // any other value is an invalid operation
  135. default:
  136. return E_INVALIDARG;
  137. }
  138. const STGOP_DETAIL *popd = NULL;
  139. for (int i=0; i < ARRAYSIZE(s_opdetail); i++)
  140. {
  141. if (s_opdetail[i].stgop == dwOperation)
  142. {
  143. popd = &s_opdetail[i];
  144. break;
  145. }
  146. }
  147. if (!_dsaConfirmationResponses)
  148. {
  149. // If we don't have a confirmation array yet, make one
  150. _dsaConfirmationResponses.Create(4);
  151. }
  152. else
  153. {
  154. // well, no one currently reuses the engine for multiple operations
  155. // but, move operation reenters the engine (for recursive move)
  156. // so we need to preserve the answers, so comment this out
  157. // If we do have one then it's got left over confirmations from the previous call
  158. // to run so we should delete all those.
  159. //_dsaConfirmationResponses.DeleteAllItems();
  160. }
  161. if (popd)
  162. {
  163. PreOperation(dwOperation, NULL, NULL);
  164. HRESULT hr = S_FALSE;
  165. if (IsFlagClear(dwOptions, STOPT_NOSTATS))
  166. {
  167. if (IsFlagClear(dwOptions, STOPT_NOPROGRESSUI))
  168. _StartProgressDialog(popd);
  169. if (_spProgress)
  170. {
  171. // Put the "Preparing to Whatever" text in the dialog
  172. WCHAR szText[MAX_PATH];
  173. LoadStringW(_Module.GetModuleInstance(), popd->idPrep, szText, ARRAYSIZE(szText));
  174. _spProgress->UpdateText(SPTEXT_ACTIONDETAIL, szText, TRUE);
  175. }
  176. // Compute the stats we need
  177. _dwOperation = STGOP_STATS;
  178. _dwOptions = STOPT_NOCONFIRMATIONS;
  179. HRESULT hrProgressBegin;
  180. if (_spProgress)
  181. hrProgressBegin = _spProgress->Begin(SPACTION_SEARCHING_FILES, SPBEGINF_MARQUEEPROGRESS);
  182. penum->Reset();
  183. hr = _WalkStorage(penum, psiDest, ptdDest);
  184. if (_spProgress && SUCCEEDED(hrProgressBegin))
  185. {
  186. _spProgress->End();
  187. // Remove the "Preparing to Whatever" text from the dialog
  188. _spProgress->UpdateText(SPTEXT_ACTIONDETAIL, L"", FALSE);
  189. }
  190. }
  191. if (SUCCEEDED(hr))
  192. {
  193. _dwOperation = (STGOP) dwOperation;
  194. _dwOptions = dwOptions;
  195. HRESULT hrProgressBegin;
  196. if (_spProgress)
  197. hrProgressBegin = _spProgress->Begin(popd->spa, SPBEGINF_AUTOTIME);
  198. penum->Reset();
  199. hr = _WalkStorage(penum, psiDest, ptdDest);
  200. if (_spProgress && SUCCEEDED(hrProgressBegin))
  201. _spProgress->End();
  202. }
  203. if (IsFlagClear(dwOptions, STOPT_NOSTATS) && _spProgress)
  204. {
  205. // this should only be called if we called the matching FlagClear-NOSTATS above.
  206. // smartpointers NULL on .Release();
  207. _spProgress.Release();
  208. if (_spShellProgress)
  209. {
  210. _spShellProgress->Stop();
  211. _spShellProgress.Release();
  212. }
  213. }
  214. SHChangeNotifyHandleEvents();
  215. PostOperation(dwOperation, NULL, NULL, hr);
  216. return hr;
  217. }
  218. else
  219. {
  220. AssertMsg(0, TEXT("A valid operation is missing from the s_opdetail array, was a new operation added? (%d)"), dwOperation);
  221. }
  222. return E_INVALIDARG;
  223. }
  224. // Does a depth-first walk of the storage performing the requested
  225. // operation.
  226. HRESULT CStorageProcessor::_WalkStorage(IShellItem *psi, IShellItem *psiDest, ITransferDest *ptdDest)
  227. {
  228. HRESULT hr = S_FALSE;
  229. if (_ShouldWalk(psi))
  230. {
  231. IEnumShellItems *penum;
  232. hr = psi->BindToHandler(NULL, BHID_StorageEnum, IID_PPV_ARG(IEnumShellItems, &penum));
  233. if (SUCCEEDED(hr))
  234. {
  235. hr = _WalkStorage(penum, psiDest, ptdDest);
  236. penum->Release();
  237. }
  238. }
  239. return hr;
  240. }
  241. HRESULT CStorageProcessor::_WalkStorage(IEnumShellItems *penum, IShellItem *psiDest, ITransferDest *ptdDest)
  242. {
  243. DWORD dwCookie;
  244. if (ptdDest)
  245. ptdDest->Advise(static_cast<ITransferAdviseSink*>(this), &dwCookie);
  246. HRESULT hr;
  247. IShellItem *psi;
  248. while (S_OK == (hr = penum->Next(1, &psi, NULL)))
  249. {
  250. // skip anything we can't work with
  251. if (_HasAttributes(psi, SFGAO_STORAGE | SFGAO_STREAM))
  252. {
  253. if (_spProgress)
  254. {
  255. // We don't show filenames while collecting stats
  256. if (_dwOperation != STGOP_STATS)
  257. {
  258. LPWSTR pszName;
  259. if (SUCCEEDED(psi->GetDisplayName(SIGDN_PARENTRELATIVEFORADDRESSBAR, &pszName)))
  260. {
  261. _spProgress->UpdateText(SPTEXT_ACTIONDETAIL, pszName, TRUE);
  262. CoTaskMemFree(pszName);
  263. }
  264. }
  265. }
  266. if (_dwOperation != STGOP_STATS)
  267. _UpdateProgress(0, 0);
  268. DWORD dwFlagsExtra = 0;
  269. switch (_dwOperation)
  270. {
  271. case STGOP_STATS:
  272. hr = _DoStats(psi);
  273. break;
  274. case STGOP_COPY_PREFERHARDLINK:
  275. dwFlagsExtra = STGX_MOVE_PREFERHARDLINK;
  276. // fall through
  277. case STGOP_COPY:
  278. hr = _DoCopy(psi, psiDest, ptdDest, dwFlagsExtra);
  279. break;
  280. case STGOP_MOVE:
  281. hr = _DoMove(psi, psiDest, ptdDest);
  282. break;
  283. case STGOP_REMOVE:
  284. hr = _DoRemove(psi, psiDest, ptdDest);
  285. break;
  286. case STGOP_RENAME:
  287. case STGOP_DIFF:
  288. case STGOP_SYNC:
  289. hr = E_NOTIMPL;
  290. break;
  291. default:
  292. hr = E_UNEXPECTED;
  293. break;
  294. }
  295. if (S_OK != QueryContinue())
  296. hr = STRESPONSE_CANCEL;
  297. }
  298. else if (STGOP_COPY_PREFERHARDLINK == _dwOperation || STGOP_COPY == _dwOperation || STGOP_MOVE == _dwOperation)
  299. {
  300. CUSTOMCONFIRMATION cc = {sizeof(cc)};
  301. cc.dwButtons = CCB_OK;
  302. cc.dwFlags = CCF_SHOW_SOURCE_INFO | CCF_USE_DEFAULT_ICON;
  303. UINT idDesc = (STGOP_MOVE == _dwOperation ? IDS_NO_STORAGE_MOVE : IDS_NO_STORAGE_COPY);
  304. cc.pwszDescription = ResourceCStrToStr(g_hinst, (LPCWSTR)(UINT_PTR)idDesc);
  305. if (cc.pwszDescription)
  306. {
  307. UINT idTitle = (STGOP_MOVE == _dwOperation ? IDS_UNKNOWN_MOVE_TITLE : IDS_UNKNOWN_COPY_TITLE);
  308. cc.pwszTitle = ResourceCStrToStr(g_hinst, (LPCWSTR)(UINT_PTR)idTitle);
  309. if (cc.pwszTitle)
  310. {
  311. ConfirmOperation(psi, NULL, GUID_NULL, &cc);
  312. LocalFree(cc.pwszTitle);
  313. }
  314. LocalFree(cc.pwszDescription);
  315. }
  316. }
  317. psi->Release();
  318. if (FAILED(hr) && STRESPONSE_SKIP != hr)
  319. break;
  320. }
  321. // We'll always get to the "no more Streams" stage, so this is meaningless
  322. if (S_FALSE == hr)
  323. hr = S_OK;
  324. if (ptdDest)
  325. ptdDest->Unadvise(dwCookie);
  326. return hr;
  327. }
  328. HRESULT CStorageProcessor::_DoConfirmations(STGTRANSCONFIRMATION stc, CUSTOMCONFIRMATION *pcc, IShellItem *psiItem, IShellItem *psiDest)
  329. {
  330. CONFIRMATIONRESPONSE crResponse = (CONFIRMATIONRESPONSE)E_FAIL;
  331. HRESULT hr = _GetDefaultResponse(stc, &crResponse);
  332. if (FAILED(hr))
  333. {
  334. // If we don't have a default answer, then call the confirmation UI, it will return the repsonse
  335. hr = S_OK;
  336. // should be able to supply the CLSID of an alternate implementation and we should CoCreate the object.
  337. if (!_ptc)
  338. hr = CTransferConfirmation_CreateInstance(NULL, IID_PPV_ARG(ITransferConfirmation, &_ptc));
  339. if (SUCCEEDED(hr))
  340. {
  341. BOOL bAll;
  342. CONFIRMOP cop;
  343. cop.dwOperation = _dwOperation;
  344. cop.stc = stc;
  345. cop.pcc = pcc;
  346. cop.cRemaining = _StreamsToDo() + _StoragesToDo();
  347. cop.psiItem = psiItem;
  348. cop.psiDest = psiDest;
  349. cop.pwszRenameTo = NULL;
  350. cop.punkSite = SAFECAST(this, IStorageProcessor*);
  351. hr = _ptc->Confirm(&cop, &crResponse, &bAll);
  352. if (SUCCEEDED(hr))
  353. {
  354. if (bAll)
  355. {
  356. // if the confirmation UI says "do for all" then add hrResponse to the default response map.
  357. STC_CR_PAIR scp(stc, crResponse);
  358. _dsaConfirmationResponses.AppendItem(&scp);
  359. }
  360. }
  361. else
  362. {
  363. // TODO: What do we do if we fail to ask for confirmation?
  364. }
  365. }
  366. }
  367. // TODO: Get rid of CONFIRMATIONRESPONSE and make these the same
  368. if (SUCCEEDED(hr))
  369. {
  370. switch (crResponse)
  371. {
  372. case CONFRES_CONTINUE:
  373. hr = STRESPONSE_CONTINUE;
  374. break;
  375. case CONFRES_SKIP:
  376. hr = STRESPONSE_SKIP;
  377. break;
  378. case CONFRES_RETRY:
  379. hr = STRESPONSE_RETRY;
  380. break;
  381. case CONFRES_RENAME:
  382. hr = STRESPONSE_RENAME;
  383. break;
  384. case CONFRES_CANCEL:
  385. case CONFRES_UNDO:
  386. hr = STRESPONSE_CANCEL;
  387. break;
  388. }
  389. }
  390. return hr;
  391. }
  392. // Based on how the user has responded to previous confirmations figure
  393. // out what flags we should be passing to CreateStorage
  394. DWORD CStorageProcessor::_GetCreateStorageFlags()
  395. {
  396. // Check to see if the user has ok'd all storage overwrites
  397. CONFIRMATIONRESPONSE hrResponse;
  398. if (SUCCEEDED(_GetDefaultResponse(STCONFIRM_REPLACE_STORAGE, &hrResponse) && (CONFRES_CONTINUE == hrResponse)))
  399. return STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT;
  400. else
  401. return STGM_FAILIFTHERE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT;
  402. }
  403. // Based on how the user has responded to previous confirmations figure
  404. // out what flags we should be passing to Open
  405. DWORD CStorageProcessor::_GetOpenStorageFlags()
  406. {
  407. return STGM_DIRECT | STGM_READ | STGM_SHARE_EXCLUSIVE;
  408. }
  409. HRESULT CStorageProcessor::_GetDefaultResponse(STGTRANSCONFIRMATION stc, LPCONFIRMATIONRESPONSE pcrResponse)
  410. {
  411. // Look in our map to see if there's already been a default response
  412. // set for this condition
  413. for (int i=0; i<_dsaConfirmationResponses.GetItemCount(); i++)
  414. {
  415. STC_CR_PAIR *pscp = _dsaConfirmationResponses.GetItemPtr(i);
  416. if (*pscp == stc)
  417. {
  418. *pcrResponse = pscp->cr;
  419. return S_OK;
  420. }
  421. }
  422. return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
  423. }
  424. HRESULT CStorageProcessor::_BindToHandlerWithMode(IShellItem *psi, STGXMODE grfMode, REFIID riid, void **ppv)
  425. {
  426. HRESULT hr = S_OK;
  427. IBindCtx *pbc = NULL;
  428. if (grfMode)
  429. hr = BindCtx_CreateWithMode(grfMode, &pbc); // need to translate mode flags?
  430. if (SUCCEEDED(hr))
  431. {
  432. GUID bhid;
  433. if (IsEqualGUID(riid, IID_IStorage))
  434. bhid = BHID_Storage;
  435. else if (IsEqualGUID(riid, IID_IStream))
  436. bhid = BHID_Stream;
  437. else
  438. bhid = BHID_SFObject;
  439. hr = psi->BindToHandler(pbc, bhid, riid, ppv);
  440. if (FAILED(hr) && IsEqualGUID(riid, IID_ITransferDest))
  441. hr = CreateStg2StgExWrapper(psi, this, (ITransferDest **)ppv);
  442. if (pbc)
  443. pbc->Release();
  444. }
  445. return hr;
  446. }
  447. BOOL _HasAttributes(IShellItem *psi, SFGAOF flags)
  448. {
  449. BOOL fReturn = FALSE;
  450. SFGAOF flagsOut;
  451. if (SUCCEEDED(psi->GetAttributes(flags, &flagsOut)) && (flags & flagsOut))
  452. fReturn = TRUE;
  453. return fReturn;
  454. }
  455. BOOL CStorageProcessor::_IsStream(IShellItem *psi)
  456. {
  457. return _HasAttributes(psi, SFGAO_STREAM);
  458. }
  459. BOOL CStorageProcessor::_ShouldWalk(IShellItem *psi)
  460. {
  461. return _HasAttributes(psi, SFGAO_STORAGE);
  462. }
  463. ULONGLONG CStorageProcessor::_GetSize(IShellItem *psi)
  464. {
  465. ULONGLONG ullReturn = 0;
  466. // first, try to get size from the pidl, so we don't hit the disk
  467. IParentAndItem *ppai;
  468. HRESULT hr = psi->QueryInterface(IID_PPV_ARG(IParentAndItem, &ppai));
  469. if (SUCCEEDED(hr))
  470. {
  471. IShellFolder *psf;
  472. LPITEMIDLIST pidlChild;
  473. hr = ppai->GetParentAndItem(NULL, &psf, &pidlChild);
  474. if (SUCCEEDED(hr))
  475. {
  476. IShellFolder2 *psf2;
  477. hr = psf->QueryInterface(IID_PPV_ARG(IShellFolder2, &psf2));
  478. if (SUCCEEDED(hr))
  479. {
  480. hr = GetLongProperty(psf2, pidlChild, &SCID_SIZE, &ullReturn);
  481. psf2->Release();
  482. }
  483. psf->Release();
  484. ILFree(pidlChild);
  485. }
  486. ppai->Release();
  487. }
  488. // if it failed, try the stream
  489. if (FAILED(hr))
  490. {
  491. //this should ask for IPropertySetStorage instead of stream...
  492. IStream *pstrm;
  493. if (SUCCEEDED(_BindToHandlerWithMode(psi, STGX_MODE_READ, IID_PPV_ARG(IStream, &pstrm))))
  494. {
  495. STATSTG stat;
  496. if (SUCCEEDED(pstrm->Stat(&stat, STATFLAG_NONAME)))
  497. ullReturn = stat.cbSize.QuadPart;
  498. pstrm->Release();
  499. }
  500. }
  501. return ullReturn;
  502. }
  503. HRESULT CStorageProcessor::_DoStats(IShellItem *psi)
  504. {
  505. HRESULT hr = PreOperation(STGOP_STATS, psi, NULL);
  506. if (FAILED(hr))
  507. return hr;
  508. if (!_IsStream(psi))
  509. {
  510. _statsTodo.AddStorage();
  511. hr = _WalkStorage(psi, NULL, NULL);
  512. }
  513. else
  514. {
  515. _statsTodo.AddStream(_GetSize(psi));
  516. }
  517. PostOperation(STGOP_STATS, psi, NULL, hr);
  518. return hr;
  519. }
  520. HRESULT CStorageProcessor::_DoCopy(IShellItem *psi, IShellItem *psiDest, ITransferDest *ptdDest, DWORD dwStgXFlags)
  521. {
  522. HRESULT hr = PreOperation(STGOP_COPY, psi, psiDest);
  523. if (FAILED(hr))
  524. return hr;
  525. LPWSTR pszNewName;
  526. hr = AutoCreateName(psiDest, psi, &pszNewName);
  527. if (SUCCEEDED(hr))
  528. {
  529. do
  530. {
  531. hr = ptdDest->MoveElement(psi, pszNewName, STGX_MOVE_COPY | STGX_MOVE_NORECURSION | dwStgXFlags);
  532. }
  533. while (STRESPONSE_RETRY == hr);
  534. if (SUCCEEDED(hr))
  535. {
  536. if (!_IsStream(psi))
  537. {
  538. _statsDone.AddStorage();
  539. // Open the source
  540. IShellItem *psiNewDest;
  541. hr = SHCreateShellItemFromParent(psiDest, pszNewName, &psiNewDest);
  542. if (SUCCEEDED(hr))
  543. {
  544. ITransferDest *ptdNewDest;
  545. hr = _BindToHandlerWithMode(psiNewDest, STGX_MODE_READWRITE, IID_PPV_ARG(ITransferDest, &ptdNewDest));
  546. if (SUCCEEDED(hr))
  547. {
  548. // And copy everything underneath
  549. hr = _WalkStorage(psi, psiNewDest, ptdNewDest);
  550. ptdNewDest->Release();
  551. }
  552. psiNewDest->Release();
  553. }
  554. }
  555. else
  556. {
  557. _statsDone.AddStream(_GetSize(psi));
  558. }
  559. }
  560. CoTaskMemFree(pszNewName);
  561. }
  562. PostOperation(STGOP_COPY, psi, psiDest, hr);
  563. return hr;
  564. }
  565. HRESULT CStorageProcessor::_DoMove(IShellItem *psi, IShellItem *psiDest, ITransferDest *ptdDest)
  566. {
  567. HRESULT hr = PreOperation(STGOP_MOVE, psi, psiDest);
  568. if (FAILED(hr))
  569. return hr;
  570. LPWSTR pszNewName;
  571. hr = AutoCreateName(psiDest, psi, &pszNewName);
  572. if (SUCCEEDED(hr))
  573. {
  574. do
  575. {
  576. hr = ptdDest->MoveElement(psi, pszNewName, STGX_MOVE_MOVE);
  577. }
  578. while (STRESPONSE_RETRY == hr);
  579. if (SUCCEEDED(hr))
  580. {
  581. if (!_IsStream(psi))
  582. {
  583. _statsDone.AddStorage();
  584. }
  585. else
  586. {
  587. _statsDone.AddStream(_GetSize(psi));
  588. }
  589. }
  590. CoTaskMemFree(pszNewName);
  591. }
  592. PostOperation(STGOP_MOVE, psi, psiDest, hr);
  593. return hr;
  594. }
  595. HRESULT CStorageProcessor::_DoRemove(IShellItem *psi, IShellItem *psiDest, ITransferDest *ptdDest)
  596. {
  597. HRESULT hr = PreOperation(STGOP_REMOVE, psi, NULL);
  598. if (FAILED(hr))
  599. return hr;
  600. LPWSTR pszName;
  601. hr = psi->GetDisplayName(SIGDN_PARENTRELATIVEFORADDRESSBAR, &pszName);
  602. if (SUCCEEDED(hr))
  603. {
  604. BOOL fStorage = !_IsStream(psi);
  605. ULONGLONG ullSize;
  606. if (!fStorage)
  607. ullSize = _GetSize(psi);
  608. // try to delete the entire storage in one operation
  609. do
  610. {
  611. hr = ptdDest->DestroyElement(pszName, 0);
  612. }
  613. while (STRESPONSE_RETRY == hr);
  614. if (FAILED(hr) && STRESPONSE_SKIP != hr && fStorage)
  615. {
  616. // if we fail then walk down deleting the contents
  617. hr = _WalkStorage(psi, psiDest, ptdDest);
  618. if (SUCCEEDED(hr))
  619. {
  620. // see if we can delete the storage now that it's empty
  621. do
  622. {
  623. hr = ptdDest->DestroyElement(pszName, 0);
  624. }
  625. while (STRESPONSE_RETRY == hr);
  626. }
  627. }
  628. if (SUCCEEDED(hr))
  629. {
  630. if (fStorage)
  631. {
  632. _statsDone.AddStorage();
  633. }
  634. else
  635. {
  636. _statsDone.AddStream(ullSize);
  637. }
  638. }
  639. CoTaskMemFree(pszName);
  640. }
  641. PostOperation(STGOP_REMOVE, psi, NULL, hr);
  642. return hr;
  643. }
  644. // Recomputes the amount of estimated time remaining, and if progress
  645. // is being displayed, updates the dialog as well
  646. // TODO: This doesn't take into account any items that are skipped. Skipped items
  647. // will still be considered undone which means the operation will finish before the
  648. // progress bar reaches the end. To accurately remove the skipped items we would need
  649. // to either:
  650. // 1.) Walk a storage if it is skipped, counting the bytes
  651. // 2.) Remember the counts in a tree when we first walked the storage
  652. //
  653. // Of these options I like #1 better since its simpler and #2 would waste memory to hold
  654. // a bunch of information we can recalculate (we're already doing a sloooow operation anyway).
  655. #define MINIMUM_UPDATE_INTERVAL 1000
  656. #define HISTORICAL_POINT_WEIGHTING 50
  657. #define TIME_BEFORE_SHOWING_ESTIMATE 5000
  658. void CStorageProcessor::_UpdateProgress(ULONGLONG ullCurrentComplete, ULONGLONG ullCurrentTotal)
  659. {
  660. // Ensure at least N ms has elapsed since last update
  661. DWORD msNow = GetTickCount();
  662. if ((msNow - _msTicksLast) >= MINIMUM_UPDATE_INTERVAL)
  663. {
  664. // Calc the estimated total cost to finish and work done so far
  665. ULONGLONG ullTotal = _statsTodo.Cost(_dwOperation, 0);
  666. if (ullTotal)
  667. {
  668. ULONGLONG cbExtra = ullCurrentTotal ? (_cbCurrentSize / ullCurrentTotal) * ullCurrentComplete : 0;
  669. ULONGLONG ullDone = _statsDone.Cost(_dwOperation, cbExtra);
  670. // Regardless of whether we update the text, update the status bar
  671. if (_spProgress)
  672. _spProgress->UpdateProgress(ullDone, ullTotal);
  673. for (int i = 0; i < ARRAYSIZE(_aspSinks); i++)
  674. {
  675. if (_aspSinks[i])
  676. {
  677. HRESULT hr = _aspSinks[i]->OperationProgress(_dwOperation, NULL, NULL, ullTotal, ullDone);
  678. if (FAILED(hr))
  679. break;
  680. }
  681. }
  682. }
  683. _msTicksLast = msNow;
  684. }
  685. }
  686. DWORD CStorageProcessor::CStgStatistics::AddStream(ULONGLONG cbSize)
  687. {
  688. _cbSize += cbSize;
  689. return ++_cStreams;
  690. }
  691. DWORD CStorageProcessor::CStgStatistics::AddStorage()
  692. {
  693. return ++_cStorages;
  694. }
  695. // Computes the total time cost of performing the storage operation
  696. // after the stats have been collected
  697. #define COST_PER_DELETE 1
  698. #define COST_PER_CREATE 1
  699. ULONGLONG CStorageProcessor::CStgStatistics::Cost(DWORD op, ULONGLONG cbExtra) const
  700. {
  701. ULONGLONG ullTotalCost = 0;
  702. // Copy and Move both need to create the target and move the bits
  703. if (op == STGOP_COPY || op == STGOP_MOVE || op == STGOP_COPY_PREFERHARDLINK)
  704. {
  705. ullTotalCost += Bytes() + cbExtra;
  706. ullTotalCost += (Streams() + Storages()) * COST_PER_CREATE;
  707. }
  708. // Move and Remove need to delete the originals
  709. if (op == STGOP_MOVE || op == STGOP_REMOVE)
  710. {
  711. ullTotalCost += (Streams() + Storages()) * COST_PER_DELETE;
  712. }
  713. return ullTotalCost;
  714. }
  715. // Figures out what animation and title text should be displayed in
  716. // the progress UI, and starts it
  717. HRESULT CStorageProcessor::_StartProgressDialog(const STGOP_DETAIL *popd)
  718. {
  719. HRESULT hr = S_OK;
  720. if (!_spProgress)
  721. {
  722. hr = CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IActionProgressDialog, &_spShellProgress));
  723. if (SUCCEEDED(hr))
  724. {
  725. //
  726. // Map the requested action to the appropriate strings (like "Preparing to Copy")
  727. //
  728. ASSERT(popd);
  729. WCHAR szText[MAX_PATH];
  730. LoadStringW(_Module.GetModuleInstance(), popd->idTitle, szText, ARRAYSIZE(szText));
  731. hr = _spShellProgress->Initialize(SPINITF_MODAL, szText, NULL);
  732. if (SUCCEEDED(hr))
  733. hr = _spShellProgress->QueryInterface(IID_PPV_ARG(IActionProgress, &_spProgress));
  734. }
  735. }
  736. return hr;
  737. }
  738. HRESULT CStorageProcessor::SetProgress(IActionProgress *pspaProgress)
  739. {
  740. HRESULT hr = E_FAIL;
  741. if (!_spProgress)
  742. {
  743. hr = E_INVALIDARG;
  744. if (pspaProgress)
  745. {
  746. _spProgress = pspaProgress;
  747. hr = S_OK;
  748. }
  749. }
  750. return hr;
  751. }
  752. STDMETHODIMP CStorageProcessor::SetLinkFactory(REFCLSID clsid)
  753. {
  754. _clsidLinkFactory = clsid;
  755. return S_OK;
  756. }
  757. // Runs through the list of registered sinks and gives each of them a shot
  758. // at cancelling or skipping this operation
  759. STDMETHODIMP CStorageProcessor::PreOperation(const STGOP op, IShellItem *psiItem, IShellItem *psiDest)
  760. {
  761. if (psiItem)
  762. {
  763. _cbCurrentSize = _IsStream(psiItem) ? _GetSize(psiItem) : 0;
  764. }
  765. for (int i = 0; i < ARRAYSIZE(_aspSinks); i++)
  766. {
  767. if (_aspSinks[i])
  768. {
  769. HRESULT hr = _aspSinks[i]->PreOperation(op, psiItem, psiDest);
  770. if (FAILED(hr))
  771. return hr;
  772. }
  773. }
  774. return S_OK;
  775. }
  776. // Allow each of the sinks to confirm the operation if they'd like
  777. STDMETHODIMP CStorageProcessor::ConfirmOperation(IShellItem *psiSource, IShellItem *psiDest, STGTRANSCONFIRMATION stc, LPCUSTOMCONFIRMATION pcc)
  778. {
  779. // TODO: map the confirmation (stc) based on _dwOperation memeber varaible
  780. HRESULT hr = STRESPONSE_CONTINUE;
  781. for (int i = 0; i < ARRAYSIZE(_aspSinks); i++)
  782. {
  783. if (_aspSinks[i])
  784. {
  785. hr = _aspSinks[i]->ConfirmOperation(psiSource, psiDest, stc, pcc);
  786. if (FAILED(hr) || hr == STRESPONSE_RENAME)
  787. break;
  788. }
  789. }
  790. // Question: How do we know if one of the above handlers displayed UI already? If the
  791. // hr is anything other than STRESPONSE_CONTINUE then obviously the confirmation has been
  792. // handled already, but one of the handlers might have diplayed UI and then returned
  793. // STRESPONSE_CONTINUE as the users response.
  794. if (STRESPONSE_CONTINUE == hr)
  795. {
  796. // show default UI
  797. hr = _DoConfirmations(stc, pcc, psiSource, psiDest);
  798. }
  799. return hr;
  800. }
  801. // Apprise each of the sinks as to our current progress
  802. STDMETHODIMP CStorageProcessor::OperationProgress(const STGOP op, IShellItem *psiItem, IShellItem *psiDest, ULONGLONG ullTotal, ULONGLONG ullComplete)
  803. {
  804. for (int i = 0; i < ARRAYSIZE(_aspSinks); i++)
  805. {
  806. if (_aspSinks[i])
  807. {
  808. HRESULT hr = _aspSinks[i]->OperationProgress(op, psiItem, psiDest, ullTotal, ullComplete);
  809. if (FAILED(hr))
  810. return hr;
  811. }
  812. }
  813. _UpdateProgress(ullComplete, ullTotal);
  814. return S_OK;
  815. }
  816. // When the operation is successfully complete, let the advises know
  817. STDMETHODIMP CStorageProcessor::PostOperation(const STGOP op, IShellItem *psiItem, IShellItem *psiDest, HRESULT hrResult)
  818. {
  819. _cbCurrentSize = 0;
  820. HRESULT hr = S_OK;
  821. for (int i = 0; (S_OK == hr) && (i < ARRAYSIZE(_aspSinks)); i++)
  822. {
  823. if (_aspSinks[i])
  824. {
  825. hr = _aspSinks[i]->PostOperation(op, psiItem, psiDest, hrResult);
  826. }
  827. }
  828. return hr;
  829. }
  830. HRESULT CStorageProcessor::QueryContinue()
  831. {
  832. HRESULT hr = S_OK;
  833. for (int i = 0; S_OK == hr && i < ARRAYSIZE(_aspSinks); i++)
  834. {
  835. if (_aspSinks[i])
  836. hr = _aspSinks[i]->QueryContinue();
  837. }
  838. if (S_OK == hr && _spProgress)
  839. {
  840. BOOL fCanceled;
  841. if (SUCCEEDED(_spProgress->QueryCancel(&fCanceled)) && fCanceled)
  842. hr = S_FALSE;
  843. }
  844. return hr;
  845. }
  846. HRESULT EnumShellItemsFromHIDADataObject(IDataObject *pdtobj, IEnumShellItems **ppenum)
  847. {
  848. *ppenum = NULL;
  849. HRESULT hr = E_FAIL;
  850. STGMEDIUM medium;
  851. LPIDA pida = DataObj_GetHIDA(pdtobj, &medium);
  852. if (pida)
  853. {
  854. LPCITEMIDLIST pidlSource = IDA_GetIDListPtr(pida, -1);
  855. if (pidlSource)
  856. {
  857. IDynamicStorage *pdstg;
  858. hr = CoCreateInstance(CLSID_DynamicStorage, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IDynamicStorage, &pdstg));
  859. if (SUCCEEDED(hr))
  860. {
  861. LPCITEMIDLIST pidl;
  862. for (UINT i = 0; SUCCEEDED(hr) && (pidl = IDA_GetIDListPtr(pida, i)); i++)
  863. {
  864. LPITEMIDLIST pidlFull;
  865. hr = SHILCombine(pidlSource, pidl, &pidlFull);
  866. if (SUCCEEDED(hr))
  867. {
  868. hr = pdstg->AddIDList(1, &pidlFull, DSTGF_ALLOWDUP);
  869. ILFree(pidlFull);
  870. }
  871. }
  872. if (SUCCEEDED(hr))
  873. {
  874. hr = pdstg->EnumItems(ppenum);
  875. }
  876. pdstg->Release();
  877. }
  878. }
  879. HIDA_ReleaseStgMedium(pida, &medium);
  880. }
  881. return hr;
  882. }
  883. HRESULT TransferDataObject(IDataObject *pdoSource, IShellItem *psiDest, STGOP dwOperation, DWORD dwOptions, ITransferAdviseSink *ptas)
  884. {
  885. IEnumShellItems *penum;
  886. HRESULT hr = EnumShellItemsFromHIDADataObject(pdoSource, &penum);
  887. if (SUCCEEDED(hr))
  888. {
  889. IStorageProcessor *psp;
  890. hr = CStorageProcessor_CreateInstance(NULL, IID_PPV_ARG(IStorageProcessor, &psp));
  891. if (SUCCEEDED(hr))
  892. {
  893. DWORD dwCookie;
  894. HRESULT hrAdvise;
  895. if (ptas)
  896. {
  897. hrAdvise = psp->Advise(ptas, &dwCookie);
  898. }
  899. hr = psp->Run(penum, psiDest, dwOperation, dwOptions);
  900. if (ptas && SUCCEEDED(hrAdvise))
  901. {
  902. psp->Unadvise(dwCookie);
  903. }
  904. psp->Release();
  905. }
  906. penum->Release();
  907. }
  908. return hr;
  909. }