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.

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