Leaked source code of windows server 2003
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

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