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.

969 lines
34 KiB

  1. //+-------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 1999.
  5. //
  6. // File: Stg2StgX.cpp
  7. //
  8. // Contents: Wrapper object that takes an IStorage and makes it act like and ITransferDest
  9. //
  10. // History: 18-July-2000 ToddB
  11. //
  12. //--------------------------------------------------------------------------
  13. #include "shellprv.h"
  14. #include "ids.h"
  15. #pragma hdrstop
  16. #include "isproc.h"
  17. #include "ConfirmationUI.h"
  18. #include "clsobj.h"
  19. class CShellItem2TransferDest : public ITransferDest
  20. {
  21. public:
  22. // IUnknown
  23. STDMETHOD_(ULONG, AddRef)();
  24. STDMETHOD_(ULONG, Release)();
  25. STDMETHOD(QueryInterface)(REFIID riid, void **ppvObj);
  26. // ITransferDest
  27. STDMETHOD(Advise)(ITransferAdviseSink *pAdvise, DWORD *pdwCookie);
  28. STDMETHOD(Unadvise)(DWORD dwCookie);
  29. STDMETHOD(OpenElement)(
  30. const WCHAR *pwcsName,
  31. STGXMODE grfMode,
  32. DWORD *pdwType,
  33. REFIID riid,
  34. void **ppunk);
  35. STDMETHOD(CreateElement)(
  36. const WCHAR *pwcsName,
  37. IShellItem *psiTemplate,
  38. STGXMODE grfMode,
  39. DWORD dwType,
  40. REFIID riid,
  41. void **ppunk);
  42. STDMETHOD(MoveElement)(
  43. IShellItem *psiItem,
  44. WCHAR *pwcsNewName, // Pointer to new name of element in destination
  45. STGXMOVE grfOptions); // Options (STGMOVEEX_ enum)
  46. STDMETHOD(DestroyElement)(
  47. const WCHAR *pwcsName,
  48. STGXDESTROY grfOptions);
  49. // commented out in the interface declaration
  50. STDMETHOD(RenameElement)(
  51. const WCHAR *pwcsOldName,
  52. const WCHAR *pwcsNewName);
  53. // CShellItem2TransferDest
  54. CShellItem2TransferDest();
  55. STDMETHOD(Init)(IShellItem *psi, IStorageProcessor *pEngine);
  56. protected:
  57. ULONG _cRef;
  58. IShellItem *_psi;
  59. ITransferAdviseSink *_ptas;
  60. IStorageProcessor *_pEngine;
  61. BOOL _fWebFolders;
  62. ~CShellItem2TransferDest();
  63. HRESULT _OpenHelper(const WCHAR *pwcsName, DWORD grfMode, DWORD *pdwType, REFIID riid, void **ppunk);
  64. HRESULT _CreateHelper(const WCHAR *pwcsName, DWORD grfMode, DWORD dwType, REFIID riid, void **ppunk);
  65. HRESULT _GetItemType(IShellItem *psi, DWORD *pdwType);
  66. HRESULT _BindToHandlerWithMode(IShellItem *psi, STGXMODE grfMode, REFIID riid, void **ppv);
  67. BOOL _CanHardLink(LPCWSTR pszSourceName, LPCWSTR pszDestName);
  68. HRESULT _CopyStreamHardLink(IShellItem *psiSource, IShellItem *psiDest, LPCWSTR pszName);
  69. HRESULT _CopyStreamBits(IShellItem *psiSource, IShellItem *psiDest);
  70. HRESULT _CopyStreamWithOptions(IShellItem *psiSource, IShellItem *psiDest, LPCWSTR pszName, STGXMOVE grfOptions);
  71. BOOL _HasMultipleStreams(IShellItem *psiItem);
  72. };
  73. STDAPI CreateStg2StgExWrapper(IShellItem *psi, IStorageProcessor *pEngine, ITransferDest **pptd)
  74. {
  75. if (!psi || !pptd)
  76. return E_INVALIDARG;
  77. *pptd = NULL;
  78. CShellItem2TransferDest *pobj = new CShellItem2TransferDest();
  79. if (!pobj)
  80. return E_OUTOFMEMORY;
  81. HRESULT hr = pobj->Init(psi, pEngine);
  82. if (SUCCEEDED(hr))
  83. {
  84. hr = pobj->QueryInterface(IID_PPV_ARG(ITransferDest, pptd));
  85. }
  86. pobj->Release();
  87. return hr;
  88. }
  89. CShellItem2TransferDest::CShellItem2TransferDest() : _cRef(1)
  90. {
  91. }
  92. CShellItem2TransferDest::~CShellItem2TransferDest()
  93. {
  94. if (_psi)
  95. _psi->Release();
  96. if (_pEngine)
  97. _pEngine->Release();
  98. if (_ptas)
  99. _ptas->Release();
  100. }
  101. HRESULT CShellItem2TransferDest::QueryInterface(REFIID riid, void **ppv)
  102. {
  103. static const QITAB qit[] =
  104. {
  105. QITABENT(CShellItem2TransferDest, ITransferDest),
  106. { 0 },
  107. };
  108. return QISearch(this, qit, riid, ppv);
  109. }
  110. STDMETHODIMP_(ULONG) CShellItem2TransferDest::AddRef()
  111. {
  112. return InterlockedIncrement((LONG *)&_cRef);
  113. }
  114. STDMETHODIMP_(ULONG) CShellItem2TransferDest::Release()
  115. {
  116. if (InterlockedDecrement((LONG *)&_cRef))
  117. return _cRef;
  118. delete this;
  119. return 0;
  120. }
  121. BOOL _IsWebfolders(IShellItem *psi);
  122. STDMETHODIMP CShellItem2TransferDest::Init(IShellItem *psi, IStorageProcessor *pEngine)
  123. {
  124. if (!psi)
  125. return E_INVALIDARG;
  126. if (_psi)
  127. return E_FAIL;
  128. _psi = psi;
  129. _psi->AddRef();
  130. _fWebFolders = _IsWebfolders(_psi);
  131. if (pEngine)
  132. {
  133. _pEngine = pEngine;
  134. _pEngine->AddRef();
  135. }
  136. return S_OK;
  137. }
  138. // ITransferDest
  139. STDMETHODIMP CShellItem2TransferDest::Advise(ITransferAdviseSink *pAdvise, DWORD *pdwCookie)
  140. {
  141. if (!pAdvise || !pdwCookie)
  142. return E_INVALIDARG;
  143. if (_ptas)
  144. return E_FAIL;
  145. _ptas = pAdvise;
  146. *pdwCookie = 1;
  147. _ptas->AddRef();
  148. return S_OK;
  149. }
  150. STDMETHODIMP CShellItem2TransferDest::Unadvise(DWORD dwCookie)
  151. {
  152. if (dwCookie != 1)
  153. return E_INVALIDARG;
  154. ATOMICRELEASE(_ptas);
  155. return S_OK;
  156. }
  157. HRESULT CShellItem2TransferDest::_GetItemType(IShellItem *psi, DWORD *pdwType)
  158. {
  159. *pdwType = STGX_TYPE_ANY;
  160. SFGAOF flags = SFGAO_STORAGE | SFGAO_STREAM;
  161. if (SUCCEEDED(psi->GetAttributes(flags, &flags)) && (flags & (SFGAO_STORAGE | SFGAO_STREAM)))
  162. *pdwType = flags & SFGAO_STREAM ? STGX_TYPE_STREAM : STGX_TYPE_STORAGE;
  163. return S_OK;
  164. }
  165. HRESULT CShellItem2TransferDest::_OpenHelper(const WCHAR *pwcsName, DWORD grfMode, DWORD *pdwType, REFIID riid, void **ppunk)
  166. {
  167. *ppunk = NULL;
  168. IShellItem *psiTemp = NULL;
  169. HRESULT hr = SHCreateShellItemFromParent(_psi, pwcsName, &psiTemp);
  170. if (SUCCEEDED(hr))
  171. {
  172. // make sure this actually exists
  173. SFGAOF flags = SFGAO_VALIDATE;
  174. hr = psiTemp->GetAttributes(flags, &flags);
  175. }
  176. if (SUCCEEDED(hr))
  177. {
  178. DWORD dwTemp;
  179. if (!pdwType)
  180. pdwType = &dwTemp;
  181. _GetItemType(psiTemp, pdwType);
  182. hr = psiTemp->QueryInterface(riid, ppunk);
  183. if (FAILED(hr))
  184. {
  185. hr = _BindToHandlerWithMode(psiTemp, grfMode, riid, ppunk);
  186. if (FAILED(hr) && IsEqualIID(riid, IID_ITransferDest) && *pdwType == STGX_TYPE_STORAGE)
  187. hr = CreateStg2StgExWrapper(psiTemp, _pEngine, (ITransferDest**)ppunk);
  188. }
  189. }
  190. if (psiTemp)
  191. psiTemp->Release();
  192. return hr;
  193. }
  194. HRESULT CShellItem2TransferDest::_CreateHelper(const WCHAR *pwcsName, DWORD grfMode, DWORD dwType, REFIID riid, void **ppunk)
  195. {
  196. *ppunk = NULL;
  197. IStorage *pstg;
  198. HRESULT hr = _BindToHandlerWithMode(_psi, grfMode, IID_PPV_ARG(IStorage, &pstg));
  199. if (SUCCEEDED(hr))
  200. {
  201. if (STGX_TYPE_STORAGE == dwType)
  202. {
  203. IStorage *pstgTemp;
  204. hr = pstg->CreateStorage(pwcsName, grfMode, 0, 0, &pstgTemp);
  205. if (SUCCEEDED(hr))
  206. {
  207. hr = pstgTemp->Commit(STGC_DEFAULT);
  208. if (SUCCEEDED(hr))
  209. {
  210. hr = pstgTemp->QueryInterface(riid, ppunk);
  211. ATOMICRELEASE(pstgTemp); //need to close first in case someone has exclusive lock. Do we need to worry about delete on release?
  212. if (FAILED(hr))
  213. hr = _OpenHelper(pwcsName, grfMode, &dwType, riid, ppunk);
  214. }
  215. if (pstgTemp)
  216. pstgTemp->Release();
  217. }
  218. }
  219. else if (STGX_TYPE_STREAM == dwType)
  220. {
  221. IStream *pstm;
  222. hr = pstg->CreateStream(pwcsName, grfMode, 0, 0, &pstm);
  223. if (SUCCEEDED(hr))
  224. {
  225. hr = pstm->Commit(STGC_DEFAULT);
  226. if (SUCCEEDED(hr))
  227. {
  228. hr = pstm->QueryInterface(riid, ppunk);
  229. ATOMICRELEASE(pstm); //need to close first in case someone has exclusive lock. Do we need to worry about delete on release?
  230. if (FAILED(hr))
  231. hr = _OpenHelper(pwcsName, grfMode, &dwType, riid, ppunk);
  232. }
  233. if (pstm)
  234. pstm->Release();
  235. }
  236. }
  237. pstg->Release();
  238. }
  239. return hr;
  240. }
  241. STDMETHODIMP CShellItem2TransferDest::OpenElement(const WCHAR *pwcsName, STGXMODE grfMode, DWORD *pdwType, REFIID riid, void **ppunk)
  242. {
  243. if (!pwcsName || !pdwType || !ppunk)
  244. return E_INVALIDARG;
  245. if (!_psi)
  246. return E_FAIL;
  247. DWORD dwFlags = grfMode & ~(STGX_MODE_CREATIONMASK);
  248. return _OpenHelper(pwcsName, dwFlags, pdwType, riid, ppunk);
  249. }
  250. STDMETHODIMP CShellItem2TransferDest::CreateElement(const WCHAR *pwcsName, IShellItem *psiTemplate, STGXMODE grfMode, DWORD dwType, REFIID riid, void **ppunk)
  251. {
  252. if (!ppunk)
  253. return E_INVALIDARG;
  254. *ppunk = NULL;
  255. if (!pwcsName)
  256. return E_INVALIDARG;
  257. if (!_psi)
  258. return E_FAIL;
  259. DWORD dwFlags = grfMode & ~(STGX_MODE_CREATIONMASK);
  260. DWORD dwExistingType = STGX_TYPE_ANY;
  261. IShellItem *psi;
  262. HRESULT hr = _OpenHelper(pwcsName, dwFlags, &dwExistingType, IID_PPV_ARG(IShellItem, &psi));
  263. if (grfMode & STGX_MODE_FAILIFTHERE)
  264. dwFlags |= STGM_FAILIFTHERE;
  265. else
  266. dwFlags |= STGM_CREATE;
  267. if (SUCCEEDED(hr))
  268. {
  269. if (grfMode & STGX_MODE_OPENEXISTING)
  270. {
  271. ATOMICRELEASE(psi);
  272. hr = _OpenHelper(pwcsName, dwFlags, &dwType, riid, ppunk);
  273. if (FAILED(hr))
  274. hr = STGX_E_INCORRECTTYPE;
  275. }
  276. else if (grfMode & STGX_MODE_FAILIFTHERE)
  277. {
  278. hr = STG_E_FILEALREADYEXISTS;
  279. }
  280. else
  281. {
  282. // release the open handle on the element
  283. ATOMICRELEASE(psi);
  284. // destroy the element
  285. DestroyElement(pwcsName, grfMode & STGX_MODE_FORCE ? STGX_DESTROY_FORCE : 0);
  286. // dont keep hr from destroyelement because in certain storages (mergedfolder
  287. // for cd burning) the destroy will try to delete the one on the cd, that'll
  288. // fail, but the create will still succeed in the staging area. at this point
  289. // we're already committed to overwriting the element so if _CreateHelper can
  290. // succeed with the STGM_CREATE flag if destroy fails, then more power to it.
  291. hr = _CreateHelper(pwcsName, dwFlags, dwType, riid, ppunk);
  292. }
  293. if (psi)
  294. psi->Release();
  295. }
  296. else
  297. {
  298. hr = _CreateHelper(pwcsName, dwFlags, dwType, riid, ppunk);
  299. }
  300. return hr;
  301. }
  302. HRESULT CShellItem2TransferDest::_BindToHandlerWithMode(IShellItem *psi, STGXMODE grfMode, REFIID riid, void **ppv)
  303. {
  304. IBindCtx *pbc;
  305. HRESULT hr = BindCtx_CreateWithMode(grfMode, &pbc); // need to translate mode flags?
  306. if (SUCCEEDED(hr))
  307. {
  308. GUID bhid;
  309. if (IsEqualGUID(riid, IID_IStorage))
  310. bhid = BHID_Storage;
  311. else if (IsEqualGUID(riid, IID_IStream))
  312. bhid = BHID_Stream;
  313. else
  314. bhid = BHID_SFObject;
  315. hr = psi->BindToHandler(pbc, bhid, riid, ppv);
  316. pbc->Release();
  317. }
  318. return hr;
  319. }
  320. #define NT_FAILED(x) NT_ERROR(x) // More consistent name for this macro
  321. BOOL CShellItem2TransferDest::_HasMultipleStreams(IShellItem *psiItem)
  322. {
  323. BOOL fReturn = FALSE;
  324. LPWSTR pszPath;
  325. if (SUCCEEDED(psiItem->GetDisplayName(SIGDN_FILESYSPATH, &pszPath)))
  326. {
  327. DWORD dwType;
  328. _GetItemType(psiItem, &dwType);
  329. BOOL fIsADir = (STGX_TYPE_STORAGE == dwType);
  330. // Covert the conventional paths to UnicodePath descriptors
  331. UNICODE_STRING UnicodeSrcObject;
  332. RtlInitUnicodeString(&UnicodeSrcObject, pszPath);
  333. if (RtlDosPathNameToNtPathName_U(pszPath, &UnicodeSrcObject, NULL, NULL))
  334. {
  335. // Build an NT object descriptor from the UnicodeSrcObject
  336. OBJECT_ATTRIBUTES SrcObjectAttributes;
  337. InitializeObjectAttributes(&SrcObjectAttributes, &UnicodeSrcObject, OBJ_CASE_INSENSITIVE, NULL, NULL);
  338. // Open the file for generic read, and the dest path for attribute read
  339. IO_STATUS_BLOCK IoStatusBlock;
  340. HANDLE SrcObjectHandle = INVALID_HANDLE_VALUE;
  341. NTSTATUS NtStatus = NtOpenFile(&SrcObjectHandle, FILE_GENERIC_READ, &SrcObjectAttributes,
  342. &IoStatusBlock, FILE_SHARE_READ, (fIsADir ? FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE));
  343. if (NT_SUCCESS(NtStatus))
  344. {
  345. // pAttributeInfo will point to enough stack to hold the
  346. // FILE_FS_ATTRIBUTE_INFORMATION and worst-case filesystem name
  347. size_t cbAttributeInfo = sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + MAX_PATH * sizeof(TCHAR);
  348. PFILE_FS_ATTRIBUTE_INFORMATION pAttributeInfo = (PFILE_FS_ATTRIBUTE_INFORMATION) _alloca(cbAttributeInfo);
  349. NtStatus = NtQueryVolumeInformationFile(
  350. SrcObjectHandle,
  351. &IoStatusBlock,
  352. (BYTE *) pAttributeInfo,
  353. cbAttributeInfo,
  354. FileFsAttributeInformation
  355. );
  356. if (NT_SUCCESS(NtStatus))
  357. {
  358. // If the source filesystem isn't NTFS, we can just bail now
  359. pAttributeInfo->FileSystemName[ (pAttributeInfo->FileSystemNameLength / sizeof(WCHAR)) ] = L'\0';
  360. if (0 != StrStrIW(pAttributeInfo->FileSystemName, L"NTFS"))
  361. {
  362. // Incrementally try allocation sizes for the ObjectStreamInformation,
  363. // then retrieve the actual stream info
  364. size_t cbBuffer = sizeof(FILE_STREAM_INFORMATION) + MAX_PATH * sizeof(WCHAR);
  365. BYTE *pBuffer = (BYTE *) LocalAlloc(LPTR, cbBuffer);
  366. if (pBuffer)
  367. {
  368. NtStatus = STATUS_BUFFER_OVERFLOW;
  369. while (STATUS_BUFFER_OVERFLOW == NtStatus)
  370. {
  371. BYTE * pOldBuffer = pBuffer;
  372. pBuffer = (BYTE *) LocalReAlloc(pBuffer, cbBuffer, LMEM_MOVEABLE);
  373. if (NULL == pBuffer)
  374. {
  375. pBuffer = pOldBuffer; //we will free it at the end of the function
  376. break;
  377. }
  378. NtStatus = NtQueryInformationFile(SrcObjectHandle, &IoStatusBlock, pBuffer, cbBuffer, FileStreamInformation);
  379. cbBuffer *= 2;
  380. }
  381. if (NT_SUCCESS(NtStatus))
  382. {
  383. FILE_STREAM_INFORMATION * pStreamInfo = (FILE_STREAM_INFORMATION *) pBuffer;
  384. if (fIsADir)
  385. {
  386. // From experimentation, it seems that if there's only one stream on a directory and
  387. // it has a zero-length name, its a vanilla directory
  388. fReturn = ((0 != pStreamInfo->NextEntryOffset) && (0 == pStreamInfo->StreamNameLength));
  389. }
  390. else // File
  391. {
  392. // Single stream only if first stream has no next offset
  393. fReturn = ((0 != pStreamInfo->NextEntryOffset) && (pBuffer == (BYTE *) pStreamInfo));
  394. }
  395. }
  396. LocalFree(pBuffer);
  397. }
  398. }
  399. }
  400. NtClose(SrcObjectHandle);
  401. }
  402. RtlFreeHeap(RtlProcessHeap(), 0, UnicodeSrcObject.Buffer);
  403. }
  404. CoTaskMemFree(pszPath);
  405. }
  406. return fReturn;
  407. }
  408. // needs to implement new name functionality
  409. STDMETHODIMP CShellItem2TransferDest::MoveElement(IShellItem *psiItem, WCHAR *pwcsNewName, STGXMOVE grfOptions)
  410. {
  411. if (!psiItem)
  412. return E_INVALIDARG;
  413. if (!_psi)
  414. return E_FAIL;
  415. HRESULT hr = STRESPONSE_CONTINUE;
  416. DWORD dwType;
  417. _GetItemType(psiItem, &dwType);
  418. if (_HasMultipleStreams(psiItem) && _ptas)
  419. {
  420. hr = _ptas->ConfirmOperation(psiItem, NULL, (STGX_TYPE_STORAGE == dwType) ? STCONFIRM_STREAM_LOSS_STORAGE : STCONFIRM_STREAM_LOSS_STREAM, NULL);
  421. }
  422. if (STRESPONSE_CONTINUE == hr)
  423. {
  424. LPWSTR pszOldName;
  425. hr = psiItem->GetDisplayName(SIGDN_PARENTRELATIVEFORADDRESSBAR, &pszOldName);
  426. if (SUCCEEDED(hr))
  427. {
  428. // we want to merge folders and replace files
  429. STGXMODE grfMode = STGX_TYPE_STORAGE == dwType ? STGX_MODE_WRITE | STGX_MODE_OPENEXISTING : STGX_MODE_WRITE | STGX_MODE_FAILIFTHERE;
  430. LPWSTR pszName = pwcsNewName ? pwcsNewName : pszOldName;
  431. BOOL fRepeat;
  432. do
  433. {
  434. fRepeat = FALSE;
  435. IShellItem *psiTarget;
  436. hr = CreateElement(pszName, psiItem, grfMode, dwType, IID_PPV_ARG(IShellItem, &psiTarget));
  437. if (SUCCEEDED(hr))
  438. {
  439. if (STGX_TYPE_STORAGE == dwType)
  440. {
  441. if (!(grfOptions & STGX_MOVE_NORECURSION))
  442. {
  443. if (_pEngine)
  444. {
  445. IEnumShellItems *penum;
  446. hr = psiItem->BindToHandler(NULL, BHID_StorageEnum, IID_PPV_ARG(IEnumShellItems, &penum));
  447. if (SUCCEEDED(hr))
  448. {
  449. STGOP stgop;
  450. if (grfOptions & STGX_MOVE_PREFERHARDLINK)
  451. {
  452. stgop = STGOP_COPY_PREFERHARDLINK;
  453. }
  454. else
  455. {
  456. stgop = (grfOptions & STGX_MOVE_COPY) ? STGOP_COPY : STGOP_MOVE;
  457. }
  458. hr = _pEngine->Run(penum, psiTarget, stgop, STOPT_NOSTATS);
  459. penum->Release();
  460. }
  461. }
  462. else
  463. {
  464. hr = STGX_E_CANNOTRECURSE;
  465. }
  466. }
  467. }
  468. else if (STGX_TYPE_STREAM == dwType)
  469. {
  470. // this one is easy, create the destination stream and then call our stream copy helper function
  471. // Use the stream copy helper that gives us progress
  472. hr = _CopyStreamWithOptions(psiItem, psiTarget, pszName, grfOptions);
  473. }
  474. else
  475. {
  476. hr = E_FAIL;
  477. }
  478. }
  479. if (SUCCEEDED(hr) && !(grfOptions & STGX_MOVE_COPY))
  480. {
  481. // in order to do a move we "copy" and then "delete"
  482. IShellItem *psiSource;
  483. hr = psiItem->GetParent(&psiSource);
  484. if (SUCCEEDED(hr))
  485. {
  486. IStorage *pstgSource;
  487. hr = _BindToHandlerWithMode(psiSource, STGX_MODE_WRITE, IID_PPV_ARG(IStorage, &pstgSource));
  488. if (SUCCEEDED(hr))
  489. {
  490. hr = pstgSource->DestroyElement(pszName);
  491. pstgSource->Release();
  492. }
  493. psiSource->Release();
  494. }
  495. }
  496. if (FAILED(hr) && _ptas)
  497. {
  498. HRESULT hrConfirm = E_FAIL;
  499. CUSTOMCONFIRMATION cc = {sizeof(cc)};
  500. STGTRANSCONFIRMATION stc = GUID_NULL;
  501. UINT idDesc = 0, idTitle = 0;
  502. BOOL fConfirm = FALSE;
  503. switch (hr)
  504. {
  505. case STG_E_FILEALREADYEXISTS:
  506. ASSERT(STGX_TYPE_STREAM == dwType);
  507. hrConfirm = _OpenHelper(pszName, STGX_MODE_READ, NULL, IID_PPV_ARG(IShellItem, &psiTarget));
  508. if (SUCCEEDED(hrConfirm))
  509. {
  510. hrConfirm = _ptas->ConfirmOperation(psiItem, psiTarget, STCONFIRM_REPLACE_STREAM, NULL);
  511. }
  512. break;
  513. case STRESPONSE_CANCEL:
  514. break;
  515. case STG_E_MEDIUMFULL:
  516. fConfirm = TRUE;
  517. cc.dwButtons = CCB_OK;
  518. idDesc = IDS_REASONS_NODISKSPACE;
  519. break;
  520. // this is just for CD burning case
  521. case HRESULT_FROM_WIN32(E_ACCESSDENIED):
  522. case STG_E_ACCESSDENIED:
  523. stc = STCONFIRM_ACCESS_DENIED;
  524. // fall through, so that we can have some kind of error in non CD case
  525. default:
  526. fConfirm = TRUE;
  527. cc.dwFlags |= CCF_SHOW_SOURCE_INFO;
  528. cc.dwButtons = CCB_RETRY_SKIP_CANCEL;
  529. idTitle = (grfOptions & STGX_MOVE_COPY ? IDS_UNKNOWN_COPY_TITLE : IDS_UNKNOWN_MOVE_TITLE);
  530. if (STGX_TYPE_STORAGE == dwType)
  531. {
  532. if (grfOptions & STGX_MOVE_COPY)
  533. {
  534. idDesc = IDS_UNKNOWN_COPY_FOLDER;
  535. }
  536. else
  537. {
  538. idDesc = IDS_UNKNOWN_MOVE_FOLDER;
  539. }
  540. }
  541. else
  542. {
  543. if (grfOptions & STGX_MOVE_COPY)
  544. {
  545. idDesc = IDS_UNKNOWN_COPY_FILE;
  546. }
  547. else
  548. {
  549. idDesc = IDS_UNKNOWN_MOVE_FILE;
  550. }
  551. }
  552. break;
  553. }
  554. if (fConfirm)
  555. {
  556. if (idTitle == 0)
  557. idTitle = IDS_DEFAULTTITLE;
  558. ASSERT(idDesc != 0);
  559. cc.pwszDescription = ResourceCStrToStr(g_hinst, (LPCWSTR)(UINT_PTR)idDesc);
  560. if (cc.pwszDescription)
  561. {
  562. cc.pwszTitle = ResourceCStrToStr(g_hinst, (LPCWSTR)(UINT_PTR)idTitle);
  563. if (cc.pwszTitle)
  564. {
  565. cc.dwFlags |= CCF_USE_DEFAULT_ICON;
  566. hrConfirm = _ptas->ConfirmOperation(psiItem, psiTarget, stc, &cc);
  567. LocalFree(cc.pwszTitle);
  568. }
  569. LocalFree(cc.pwszDescription);
  570. }
  571. }
  572. switch (hrConfirm)
  573. {
  574. case STRESPONSE_CONTINUE:
  575. case STRESPONSE_RETRY:
  576. if (STRESPONSE_RETRY == hrConfirm || STG_E_FILEALREADYEXISTS == hr)
  577. {
  578. grfMode = STGX_MODE_WRITE | STGX_MODE_FORCE;
  579. fRepeat = TRUE;
  580. }
  581. break;
  582. case STRESPONSE_SKIP:
  583. hr = S_FALSE;
  584. break;
  585. default:
  586. // let hr propagate out of the function
  587. break;
  588. }
  589. }
  590. if (psiTarget)
  591. psiTarget->Release();
  592. }
  593. while (fRepeat);
  594. CoTaskMemFree(pszOldName);
  595. }
  596. }
  597. return hr;
  598. }
  599. STDMETHODIMP CShellItem2TransferDest::DestroyElement(const WCHAR *pwcsName, STGXDESTROY grfOptions)
  600. {
  601. if (!_psi)
  602. return E_FAIL;
  603. // TODO: Pre and post op, confirmations
  604. HRESULT hr = STRESPONSE_CONTINUE;
  605. if (!(grfOptions & STGX_DESTROY_FORCE) && _ptas)
  606. {
  607. DWORD dwType = STGX_TYPE_ANY;
  608. IShellItem *psi;
  609. hr = _OpenHelper(pwcsName, STGX_MODE_READ, &dwType, IID_PPV_ARG(IShellItem, &psi));
  610. if (SUCCEEDED(hr))
  611. {
  612. hr = _ptas->ConfirmOperation(psi, NULL,
  613. (STGX_TYPE_STORAGE == dwType) ? STCONFIRM_DELETE_STORAGE : STCONFIRM_DELETE_STREAM,
  614. NULL);
  615. psi->Release();
  616. }
  617. }
  618. if (STRESPONSE_CONTINUE == hr)
  619. {
  620. IStorage *pstg;
  621. hr = _BindToHandlerWithMode(_psi, STGX_MODE_WRITE, IID_PPV_ARG(IStorage, &pstg));
  622. if (SUCCEEDED(hr))
  623. {
  624. hr = pstg->DestroyElement(pwcsName);
  625. pstg->Release();
  626. }
  627. }
  628. return hr;
  629. }
  630. STDMETHODIMP CShellItem2TransferDest::RenameElement(const WCHAR *pwcsOldName, const WCHAR *pwcsNewName)
  631. {
  632. if (!_psi)
  633. return E_FAIL;
  634. // TODO: Pre and post op, confirmations
  635. IStorage *pstg;
  636. HRESULT hr = _BindToHandlerWithMode(_psi, STGX_MODE_WRITE, IID_PPV_ARG(IStorage, &pstg));
  637. if (SUCCEEDED(hr))
  638. {
  639. hr = pstg->RenameElement(pwcsOldName, pwcsNewName);
  640. pstg->Release();
  641. }
  642. return hr;
  643. }
  644. STDAPI_(BOOL) IsFileDeletable(LPCTSTR pszFile); // bitbuck.c
  645. BOOL CShellItem2TransferDest::_CanHardLink(LPCWSTR pszSourceName, LPCWSTR pszDestName)
  646. {
  647. // this is not intended to catch invalid situations where we could be hard linking --
  648. // CreateHardLink already takes care of all removable media, non-NTFS, etc.
  649. // this is just to do a quick check before taking the cost of destroying and
  650. // recreating the file.
  651. // unfortunately due to architecture cleanliness we can't keep state of whether hard
  652. // links are possible for the whole copy, so we check on each element.
  653. BOOL fRet = FALSE;
  654. if (PathGetDriveNumber(pszSourceName) == PathGetDriveNumber(pszDestName))
  655. {
  656. TCHAR szRoot[MAX_PATH];
  657. lstrcpyn(szRoot, pszSourceName, ARRAYSIZE(szRoot));
  658. TCHAR szFileSystem[20];
  659. if (PathStripToRoot(szRoot) &&
  660. GetVolumeInformation(szRoot, NULL, 0, NULL, NULL, NULL, szFileSystem, ARRAYSIZE(szFileSystem)))
  661. {
  662. if (lstrcmpi(szFileSystem, TEXT("NTFS")) == 0)
  663. {
  664. // check if we have delete access on the file. this will aid the user later
  665. // if they want to manage the files in the staging area for cd burning.
  666. // if not, then make a normal copy.
  667. if (IsFileDeletable(pszSourceName))
  668. {
  669. fRet = TRUE;
  670. }
  671. }
  672. }
  673. }
  674. return fRet;
  675. }
  676. HRESULT CShellItem2TransferDest::_CopyStreamHardLink(IShellItem *psiSource, IShellItem *psiDest, LPCWSTR pszName)
  677. {
  678. // sell out and go to filesystem
  679. LPWSTR pszSourceName;
  680. HRESULT hr = psiSource->GetDisplayName(SIGDN_FILESYSPATH, &pszSourceName);
  681. if (SUCCEEDED(hr))
  682. {
  683. LPWSTR pszDestName;
  684. hr = psiDest->GetDisplayName(SIGDN_FILESYSPATH, &pszDestName);
  685. if (SUCCEEDED(hr))
  686. {
  687. if (_CanHardLink(pszSourceName, pszDestName))
  688. {
  689. // need to destroy the 0-byte file we created during our confirm overwrite probing
  690. DestroyElement(pszName, STGX_DESTROY_FORCE);
  691. hr = CreateHardLink(pszDestName, pszSourceName, NULL) ? S_OK : E_FAIL;
  692. if (SUCCEEDED(hr))
  693. {
  694. SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, pszDestName, NULL);
  695. _ptas->OperationProgress(STGOP_COPY, psiSource, psiDest, 1, 1);
  696. }
  697. else
  698. {
  699. // we deleted it above and need to recreate it for the fallback of doing a normal copy
  700. IUnknown *punkDummy;
  701. if (SUCCEEDED(_CreateHelper(pszName, STGX_MODE_WRITE | STGX_MODE_FORCE, STGX_TYPE_STREAM, IID_PPV_ARG(IUnknown, &punkDummy))))
  702. {
  703. punkDummy->Release();
  704. }
  705. }
  706. }
  707. else
  708. {
  709. hr = E_FAIL;
  710. }
  711. CoTaskMemFree(pszDestName);
  712. }
  713. CoTaskMemFree(pszSourceName);
  714. }
  715. return hr;
  716. }
  717. HRESULT CShellItem2TransferDest::_CopyStreamWithOptions(IShellItem *psiSource, IShellItem *psiDest, LPCWSTR pszName, STGXMOVE grfOptions)
  718. {
  719. HRESULT hr = E_FAIL;
  720. if (grfOptions & STGX_MOVE_PREFERHARDLINK)
  721. {
  722. hr = _CopyStreamHardLink(psiSource, psiDest, pszName);
  723. }
  724. if (FAILED(hr))
  725. {
  726. hr = _CopyStreamBits(psiSource, psiDest);
  727. }
  728. return hr;
  729. }
  730. HRESULT CShellItem2TransferDest::_CopyStreamBits(IShellItem *psiSource, IShellItem *psiDest)
  731. {
  732. const ULONG maxbuf = 1024*1024; // max size we will ever use for a buffer
  733. const ULONG minbuf = 1024; // smallest buffer we will use
  734. void *pv = LocalAlloc(LPTR, minbuf);
  735. if (!pv)
  736. return E_OUTOFMEMORY;
  737. IStream *pstrmSource;
  738. HRESULT hr = _BindToHandlerWithMode(psiSource, STGM_READ | STGM_SHARE_DENY_WRITE, IID_PPV_ARG(IStream, &pstrmSource));
  739. if (SUCCEEDED(hr))
  740. {
  741. IStream *pstrmDest;
  742. hr = _BindToHandlerWithMode(psiDest, STGM_READWRITE, IID_PPV_ARG(IStream, &pstrmDest));
  743. if (SUCCEEDED(hr))
  744. {
  745. // we need the source size info so we can show progress
  746. STATSTG statsrc;
  747. hr = pstrmSource->Stat(&statsrc, STATFLAG_NONAME);
  748. if (SUCCEEDED(hr))
  749. {
  750. ULONG cbSizeToAlloc = minbuf;
  751. ULONG cbSizeAlloced = 0;
  752. ULONG cbToRead = 0;
  753. ULONGLONG ullCurr = 0;
  754. const ULONG maxms = 2500; // max time, in ms, we'd like between progress updates
  755. const ULONG minms = 750; // min time we'd like to be doing work between updates
  756. cbSizeAlloced = cbSizeToAlloc;
  757. cbToRead = cbSizeAlloced;
  758. DWORD dwmsBefore = GetTickCount();
  759. // Read from source, write to dest, and update progress. We start doing 1K at a time, and
  760. // so long as its taking us less than (minms) milliseconds per pass, we'll double the buffer
  761. // size. If we go longer than (maxms) milliseconds, we'll cut our work in half.
  762. ULONG cbRead;
  763. ULONGLONG ullCur = 0;
  764. while (SUCCEEDED(hr = pstrmSource->Read(pv, cbToRead, &cbRead)) && cbRead)
  765. {
  766. // Update the progress based on the bytes read so far
  767. ullCur += cbRead;
  768. hr = _ptas->OperationProgress(STGOP_COPY, psiSource, psiDest, statsrc.cbSize.QuadPart, ullCur);
  769. if (FAILED(hr))
  770. break;
  771. // Write the bytes to the output stream
  772. ULONG cbWritten = 0;
  773. hr = pstrmDest->Write(pv, cbRead, &cbWritten);
  774. if (FAILED(hr))
  775. break;
  776. DWORD dwmsAfter = GetTickCount();
  777. // If we're going to fast or too slow, adjust the size of the buffer. If we paused for user
  778. // intervention we'll think we're slow, but we'll correct next pass
  779. if (dwmsAfter - dwmsBefore < minms && cbSizeAlloced < maxbuf)
  780. {
  781. // We completed really quickly, so we should try to do more work next time.
  782. // Try to grow the buffer. If it fails, just go with the existing buffer.
  783. if (cbToRead < cbSizeAlloced)
  784. {
  785. // Buffer already larger than work we're doing, so just bump up scheduled work
  786. cbToRead = __min(cbToRead *2, cbSizeAlloced);
  787. }
  788. else
  789. {
  790. // Buffer maxed by current scheduled work, so increase its size
  791. void *pvOld = pv;
  792. cbSizeToAlloc = __min(cbSizeAlloced *2, maxbuf);
  793. pv = LocalReAlloc((HLOCAL)pv, cbSizeToAlloc, LPTR);
  794. if (!pv)
  795. pv = pvOld; // Old pointer still valid
  796. else
  797. cbSizeAlloced = cbSizeToAlloc;
  798. cbToRead = cbSizeAlloced;
  799. }
  800. }
  801. else if (dwmsAfter - dwmsBefore > maxms && cbToRead > minbuf)
  802. {
  803. cbToRead = __max(cbToRead / 2, minbuf);
  804. }
  805. dwmsBefore = GetTickCount();
  806. }
  807. }
  808. if (SUCCEEDED(hr))
  809. hr = pstrmDest->Commit(STGC_DEFAULT);
  810. pstrmDest->Release();
  811. }
  812. pstrmSource->Release();
  813. }
  814. LocalFree(pv);
  815. // eventually we will read to the end of the file and get an S_FALSE, return S_OK
  816. if (S_FALSE == hr)
  817. {
  818. hr = S_OK;
  819. }
  820. return hr;
  821. }