//+------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1999. // // File: Stg2StgX.cpp // // Contents: Wrapper object that takes an IStorage and makes it act like and ITransferDest // // History: 18-July-2000 ToddB // //-------------------------------------------------------------------------- #include "shellprv.h" #include "ids.h" #pragma hdrstop #include "isproc.h" #include "ConfirmationUI.h" #include "clsobj.h" class CShellItem2TransferDest : public ITransferDest { public: // IUnknown STDMETHOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release)(); STDMETHOD(QueryInterface)(REFIID riid, void **ppvObj); // ITransferDest STDMETHOD(Advise)(ITransferAdviseSink *pAdvise, DWORD *pdwCookie); STDMETHOD(Unadvise)(DWORD dwCookie); STDMETHOD(OpenElement)( const WCHAR *pwcsName, STGXMODE grfMode, DWORD *pdwType, REFIID riid, void **ppunk); STDMETHOD(CreateElement)( const WCHAR *pwcsName, IShellItem *psiTemplate, STGXMODE grfMode, DWORD dwType, REFIID riid, void **ppunk); STDMETHOD(MoveElement)( IShellItem *psiItem, WCHAR *pwcsNewName, // Pointer to new name of element in destination STGXMOVE grfOptions); // Options (STGMOVEEX_ enum) STDMETHOD(DestroyElement)( const WCHAR *pwcsName, STGXDESTROY grfOptions); // commented out in the interface declaration STDMETHOD(RenameElement)( const WCHAR *pwcsOldName, const WCHAR *pwcsNewName); // CShellItem2TransferDest CShellItem2TransferDest(); STDMETHOD(Init)(IShellItem *psi, IStorageProcessor *pEngine); protected: ULONG _cRef; IShellItem *_psi; ITransferAdviseSink *_ptas; IStorageProcessor *_pEngine; BOOL _fWebFolders; ~CShellItem2TransferDest(); HRESULT _OpenHelper(const WCHAR *pwcsName, DWORD grfMode, DWORD *pdwType, REFIID riid, void **ppunk); HRESULT _CreateHelper(const WCHAR *pwcsName, DWORD grfMode, DWORD dwType, REFIID riid, void **ppunk); HRESULT _GetItemType(IShellItem *psi, DWORD *pdwType); HRESULT _BindToHandlerWithMode(IShellItem *psi, STGXMODE grfMode, REFIID riid, void **ppv); BOOL _CanHardLink(LPCWSTR pszSourceName, LPCWSTR pszDestName); HRESULT _CopyStreamHardLink(IShellItem *psiSource, IShellItem *psiDest, LPCWSTR pszName); HRESULT _CopyStreamBits(IShellItem *psiSource, IShellItem *psiDest); HRESULT _CopyStreamWithOptions(IShellItem *psiSource, IShellItem *psiDest, LPCWSTR pszName, STGXMOVE grfOptions); BOOL _HasMultipleStreams(IShellItem *psiItem); }; STDAPI CreateStg2StgExWrapper(IShellItem *psi, IStorageProcessor *pEngine, ITransferDest **pptd) { if (!psi || !pptd) return E_INVALIDARG; *pptd = NULL; CShellItem2TransferDest *pobj = new CShellItem2TransferDest(); if (!pobj) return E_OUTOFMEMORY; HRESULT hr = pobj->Init(psi, pEngine); if (SUCCEEDED(hr)) { hr = pobj->QueryInterface(IID_PPV_ARG(ITransferDest, pptd)); } pobj->Release(); return hr; } CShellItem2TransferDest::CShellItem2TransferDest() : _cRef(1) { } CShellItem2TransferDest::~CShellItem2TransferDest() { if (_psi) _psi->Release(); if (_pEngine) _pEngine->Release(); if (_ptas) _ptas->Release(); } HRESULT CShellItem2TransferDest::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CShellItem2TransferDest, ITransferDest), { 0 }, }; return QISearch(this, qit, riid, ppv); } STDMETHODIMP_(ULONG) CShellItem2TransferDest::AddRef() { return InterlockedIncrement((LONG *)&_cRef); } STDMETHODIMP_(ULONG) CShellItem2TransferDest::Release() { if (InterlockedDecrement((LONG *)&_cRef)) return _cRef; delete this; return 0; } BOOL _IsWebfolders(IShellItem *psi); STDMETHODIMP CShellItem2TransferDest::Init(IShellItem *psi, IStorageProcessor *pEngine) { if (!psi) return E_INVALIDARG; if (_psi) return E_FAIL; _psi = psi; _psi->AddRef(); _fWebFolders = _IsWebfolders(_psi); if (pEngine) { _pEngine = pEngine; _pEngine->AddRef(); } return S_OK; } // ITransferDest STDMETHODIMP CShellItem2TransferDest::Advise(ITransferAdviseSink *pAdvise, DWORD *pdwCookie) { if (!pAdvise || !pdwCookie) return E_INVALIDARG; if (_ptas) return E_FAIL; _ptas = pAdvise; *pdwCookie = 1; _ptas->AddRef(); return S_OK; } STDMETHODIMP CShellItem2TransferDest::Unadvise(DWORD dwCookie) { if (dwCookie != 1) return E_INVALIDARG; ATOMICRELEASE(_ptas); return S_OK; } HRESULT CShellItem2TransferDest::_GetItemType(IShellItem *psi, DWORD *pdwType) { *pdwType = STGX_TYPE_ANY; SFGAOF flags = SFGAO_STORAGE | SFGAO_STREAM; if (SUCCEEDED(psi->GetAttributes(flags, &flags)) && (flags & (SFGAO_STORAGE | SFGAO_STREAM))) *pdwType = flags & SFGAO_STREAM ? STGX_TYPE_STREAM : STGX_TYPE_STORAGE; return S_OK; } HRESULT CShellItem2TransferDest::_OpenHelper(const WCHAR *pwcsName, DWORD grfMode, DWORD *pdwType, REFIID riid, void **ppunk) { *ppunk = NULL; IShellItem *psiTemp = NULL; HRESULT hr = SHCreateShellItemFromParent(_psi, pwcsName, &psiTemp); if (SUCCEEDED(hr)) { // make sure this actually exists SFGAOF flags = SFGAO_VALIDATE; hr = psiTemp->GetAttributes(flags, &flags); } if (SUCCEEDED(hr)) { DWORD dwTemp; if (!pdwType) pdwType = &dwTemp; _GetItemType(psiTemp, pdwType); hr = psiTemp->QueryInterface(riid, ppunk); if (FAILED(hr)) { hr = _BindToHandlerWithMode(psiTemp, grfMode, riid, ppunk); if (FAILED(hr) && IsEqualIID(riid, IID_ITransferDest) && *pdwType == STGX_TYPE_STORAGE) hr = CreateStg2StgExWrapper(psiTemp, _pEngine, (ITransferDest**)ppunk); } } if (psiTemp) psiTemp->Release(); return hr; } HRESULT CShellItem2TransferDest::_CreateHelper(const WCHAR *pwcsName, DWORD grfMode, DWORD dwType, REFIID riid, void **ppunk) { *ppunk = NULL; IStorage *pstg; HRESULT hr = _BindToHandlerWithMode(_psi, grfMode, IID_PPV_ARG(IStorage, &pstg)); if (SUCCEEDED(hr)) { if (STGX_TYPE_STORAGE == dwType) { IStorage *pstgTemp; hr = pstg->CreateStorage(pwcsName, grfMode, 0, 0, &pstgTemp); if (SUCCEEDED(hr)) { hr = pstgTemp->Commit(STGC_DEFAULT); if (SUCCEEDED(hr)) { hr = pstgTemp->QueryInterface(riid, ppunk); ATOMICRELEASE(pstgTemp); //need to close first in case someone has exclusive lock. Do we need to worry about delete on release? if (FAILED(hr)) hr = _OpenHelper(pwcsName, grfMode, &dwType, riid, ppunk); } if (pstgTemp) pstgTemp->Release(); } } else if (STGX_TYPE_STREAM == dwType) { IStream *pstm; hr = pstg->CreateStream(pwcsName, grfMode, 0, 0, &pstm); if (SUCCEEDED(hr)) { hr = pstm->Commit(STGC_DEFAULT); if (SUCCEEDED(hr)) { hr = pstm->QueryInterface(riid, ppunk); ATOMICRELEASE(pstm); //need to close first in case someone has exclusive lock. Do we need to worry about delete on release? if (FAILED(hr)) hr = _OpenHelper(pwcsName, grfMode, &dwType, riid, ppunk); } if (pstm) pstm->Release(); } } pstg->Release(); } return hr; } STDMETHODIMP CShellItem2TransferDest::OpenElement(const WCHAR *pwcsName, STGXMODE grfMode, DWORD *pdwType, REFIID riid, void **ppunk) { if (!pwcsName || !pdwType || !ppunk) return E_INVALIDARG; if (!_psi) return E_FAIL; DWORD dwFlags = grfMode & ~(STGX_MODE_CREATIONMASK); return _OpenHelper(pwcsName, dwFlags, pdwType, riid, ppunk); } STDMETHODIMP CShellItem2TransferDest::CreateElement(const WCHAR *pwcsName, IShellItem *psiTemplate, STGXMODE grfMode, DWORD dwType, REFIID riid, void **ppunk) { if (!ppunk) return E_INVALIDARG; *ppunk = NULL; if (!pwcsName) return E_INVALIDARG; if (!_psi) return E_FAIL; DWORD dwFlags = grfMode & ~(STGX_MODE_CREATIONMASK); DWORD dwExistingType = STGX_TYPE_ANY; IShellItem *psi; HRESULT hr = _OpenHelper(pwcsName, dwFlags, &dwExistingType, IID_PPV_ARG(IShellItem, &psi)); if (grfMode & STGX_MODE_FAILIFTHERE) dwFlags |= STGM_FAILIFTHERE; else dwFlags |= STGM_CREATE; if (SUCCEEDED(hr)) { if (grfMode & STGX_MODE_OPENEXISTING) { ATOMICRELEASE(psi); hr = _OpenHelper(pwcsName, dwFlags, &dwType, riid, ppunk); if (FAILED(hr)) hr = STGX_E_INCORRECTTYPE; } else if (grfMode & STGX_MODE_FAILIFTHERE) { hr = STG_E_FILEALREADYEXISTS; } else { // release the open handle on the element ATOMICRELEASE(psi); // destroy the element DestroyElement(pwcsName, grfMode & STGX_MODE_FORCE ? STGX_DESTROY_FORCE : 0); // dont keep hr from destroyelement because in certain storages (mergedfolder // for cd burning) the destroy will try to delete the one on the cd, that'll // fail, but the create will still succeed in the staging area. at this point // we're already committed to overwriting the element so if _CreateHelper can // succeed with the STGM_CREATE flag if destroy fails, then more power to it. hr = _CreateHelper(pwcsName, dwFlags, dwType, riid, ppunk); } if (psi) psi->Release(); } else { hr = _CreateHelper(pwcsName, dwFlags, dwType, riid, ppunk); } return hr; } HRESULT CShellItem2TransferDest::_BindToHandlerWithMode(IShellItem *psi, STGXMODE grfMode, REFIID riid, void **ppv) { IBindCtx *pbc; HRESULT hr = BindCtx_CreateWithMode(grfMode, &pbc); // need to translate mode flags? if (SUCCEEDED(hr)) { GUID bhid; if (IsEqualGUID(riid, IID_IStorage)) bhid = BHID_Storage; else if (IsEqualGUID(riid, IID_IStream)) bhid = BHID_Stream; else bhid = BHID_SFObject; hr = psi->BindToHandler(pbc, bhid, riid, ppv); pbc->Release(); } return hr; } #define NT_FAILED(x) NT_ERROR(x) // More consistent name for this macro BOOL CShellItem2TransferDest::_HasMultipleStreams(IShellItem *psiItem) { BOOL fReturn = FALSE; LPWSTR pszPath; if (SUCCEEDED(psiItem->GetDisplayName(SIGDN_FILESYSPATH, &pszPath))) { DWORD dwType; _GetItemType(psiItem, &dwType); BOOL fIsADir = (STGX_TYPE_STORAGE == dwType); // Covert the conventional paths to UnicodePath descriptors UNICODE_STRING UnicodeSrcObject; RtlInitUnicodeString(&UnicodeSrcObject, pszPath); if (RtlDosPathNameToNtPathName_U(pszPath, &UnicodeSrcObject, NULL, NULL)) { // Build an NT object descriptor from the UnicodeSrcObject OBJECT_ATTRIBUTES SrcObjectAttributes; InitializeObjectAttributes(&SrcObjectAttributes, &UnicodeSrcObject, OBJ_CASE_INSENSITIVE, NULL, NULL); // Open the file for generic read, and the dest path for attribute read IO_STATUS_BLOCK IoStatusBlock; HANDLE SrcObjectHandle = INVALID_HANDLE_VALUE; NTSTATUS NtStatus = NtOpenFile(&SrcObjectHandle, FILE_GENERIC_READ, &SrcObjectAttributes, &IoStatusBlock, FILE_SHARE_READ, (fIsADir ? FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE)); if (NT_SUCCESS(NtStatus)) { // pAttributeInfo will point to enough stack to hold the // FILE_FS_ATTRIBUTE_INFORMATION and worst-case filesystem name size_t cbAttributeInfo = sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + MAX_PATH * sizeof(TCHAR); PFILE_FS_ATTRIBUTE_INFORMATION pAttributeInfo = (PFILE_FS_ATTRIBUTE_INFORMATION) _alloca(cbAttributeInfo); NtStatus = NtQueryVolumeInformationFile( SrcObjectHandle, &IoStatusBlock, (BYTE *) pAttributeInfo, cbAttributeInfo, FileFsAttributeInformation ); if (NT_SUCCESS(NtStatus)) { // If the source filesystem isn't NTFS, we can just bail now pAttributeInfo->FileSystemName[ (pAttributeInfo->FileSystemNameLength / sizeof(WCHAR)) ] = L'\0'; if (0 != StrStrIW(pAttributeInfo->FileSystemName, L"NTFS")) { // Incrementally try allocation sizes for the ObjectStreamInformation, // then retrieve the actual stream info size_t cbBuffer = sizeof(FILE_STREAM_INFORMATION) + MAX_PATH * sizeof(WCHAR); BYTE *pBuffer = (BYTE *) LocalAlloc(LPTR, cbBuffer); if (pBuffer) { NtStatus = STATUS_BUFFER_OVERFLOW; while (STATUS_BUFFER_OVERFLOW == NtStatus) { BYTE * pOldBuffer = pBuffer; pBuffer = (BYTE *) LocalReAlloc(pBuffer, cbBuffer, LMEM_MOVEABLE); if (NULL == pBuffer) { pBuffer = pOldBuffer; //we will free it at the end of the function break; } NtStatus = NtQueryInformationFile(SrcObjectHandle, &IoStatusBlock, pBuffer, cbBuffer, FileStreamInformation); cbBuffer *= 2; } if (NT_SUCCESS(NtStatus)) { FILE_STREAM_INFORMATION * pStreamInfo = (FILE_STREAM_INFORMATION *) pBuffer; if (fIsADir) { // From experimentation, it seems that if there's only one stream on a directory and // it has a zero-length name, its a vanilla directory fReturn = ((0 != pStreamInfo->NextEntryOffset) && (0 == pStreamInfo->StreamNameLength)); } else // File { // Single stream only if first stream has no next offset fReturn = ((0 != pStreamInfo->NextEntryOffset) && (pBuffer == (BYTE *) pStreamInfo)); } } LocalFree(pBuffer); } } } NtClose(SrcObjectHandle); } RtlFreeHeap(RtlProcessHeap(), 0, UnicodeSrcObject.Buffer); } CoTaskMemFree(pszPath); } return fReturn; } // needs to implement new name functionality STDMETHODIMP CShellItem2TransferDest::MoveElement(IShellItem *psiItem, WCHAR *pwcsNewName, STGXMOVE grfOptions) { if (!psiItem) return E_INVALIDARG; if (!_psi) return E_FAIL; HRESULT hr = STRESPONSE_CONTINUE; DWORD dwType; _GetItemType(psiItem, &dwType); if (_HasMultipleStreams(psiItem) && _ptas) { hr = _ptas->ConfirmOperation(psiItem, NULL, (STGX_TYPE_STORAGE == dwType) ? STCONFIRM_STREAM_LOSS_STORAGE : STCONFIRM_STREAM_LOSS_STREAM, NULL); } if (STRESPONSE_CONTINUE == hr) { LPWSTR pszOldName; hr = psiItem->GetDisplayName(SIGDN_PARENTRELATIVEFORADDRESSBAR, &pszOldName); if (SUCCEEDED(hr)) { // we want to merge folders and replace files STGXMODE grfMode = STGX_TYPE_STORAGE == dwType ? STGX_MODE_WRITE | STGX_MODE_OPENEXISTING : STGX_MODE_WRITE | STGX_MODE_FAILIFTHERE; LPWSTR pszName = pwcsNewName ? pwcsNewName : pszOldName; BOOL fRepeat; do { fRepeat = FALSE; IShellItem *psiTarget; hr = CreateElement(pszName, psiItem, grfMode, dwType, IID_PPV_ARG(IShellItem, &psiTarget)); if (SUCCEEDED(hr)) { if (STGX_TYPE_STORAGE == dwType) { if (!(grfOptions & STGX_MOVE_NORECURSION)) { if (_pEngine) { IEnumShellItems *penum; hr = psiItem->BindToHandler(NULL, BHID_StorageEnum, IID_PPV_ARG(IEnumShellItems, &penum)); if (SUCCEEDED(hr)) { STGOP stgop; if (grfOptions & STGX_MOVE_PREFERHARDLINK) { stgop = STGOP_COPY_PREFERHARDLINK; } else { stgop = (grfOptions & STGX_MOVE_COPY) ? STGOP_COPY : STGOP_MOVE; } hr = _pEngine->Run(penum, psiTarget, stgop, STOPT_NOSTATS); penum->Release(); } } else { hr = STGX_E_CANNOTRECURSE; } } } else if (STGX_TYPE_STREAM == dwType) { // this one is easy, create the destination stream and then call our stream copy helper function // Use the stream copy helper that gives us progress hr = _CopyStreamWithOptions(psiItem, psiTarget, pszName, grfOptions); } else { hr = E_FAIL; } } if (SUCCEEDED(hr) && !(grfOptions & STGX_MOVE_COPY)) { // in order to do a move we "copy" and then "delete" IShellItem *psiSource; hr = psiItem->GetParent(&psiSource); if (SUCCEEDED(hr)) { IStorage *pstgSource; hr = _BindToHandlerWithMode(psiSource, STGX_MODE_WRITE, IID_PPV_ARG(IStorage, &pstgSource)); if (SUCCEEDED(hr)) { hr = pstgSource->DestroyElement(pszName); pstgSource->Release(); } psiSource->Release(); } } if (FAILED(hr) && _ptas) { HRESULT hrConfirm = E_FAIL; CUSTOMCONFIRMATION cc = {sizeof(cc)}; STGTRANSCONFIRMATION stc = GUID_NULL; UINT idDesc = 0, idTitle = 0; BOOL fConfirm = FALSE; switch (hr) { case STG_E_FILEALREADYEXISTS: ASSERT(STGX_TYPE_STREAM == dwType); hrConfirm = _OpenHelper(pszName, STGX_MODE_READ, NULL, IID_PPV_ARG(IShellItem, &psiTarget)); if (SUCCEEDED(hrConfirm)) { hrConfirm = _ptas->ConfirmOperation(psiItem, psiTarget, STCONFIRM_REPLACE_STREAM, NULL); } break; case STRESPONSE_CANCEL: break; case STG_E_MEDIUMFULL: fConfirm = TRUE; cc.dwButtons = CCB_OK; idDesc = IDS_REASONS_NODISKSPACE; break; // this is just for CD burning case case HRESULT_FROM_WIN32(E_ACCESSDENIED): case STG_E_ACCESSDENIED: stc = STCONFIRM_ACCESS_DENIED; // fall through, so that we can have some kind of error in non CD case default: fConfirm = TRUE; cc.dwFlags |= CCF_SHOW_SOURCE_INFO; cc.dwButtons = CCB_RETRY_SKIP_CANCEL; idTitle = (grfOptions & STGX_MOVE_COPY ? IDS_UNKNOWN_COPY_TITLE : IDS_UNKNOWN_MOVE_TITLE); if (STGX_TYPE_STORAGE == dwType) { if (grfOptions & STGX_MOVE_COPY) { idDesc = IDS_UNKNOWN_COPY_FOLDER; } else { idDesc = IDS_UNKNOWN_MOVE_FOLDER; } } else { if (grfOptions & STGX_MOVE_COPY) { idDesc = IDS_UNKNOWN_COPY_FILE; } else { idDesc = IDS_UNKNOWN_MOVE_FILE; } } break; } if (fConfirm) { if (idTitle == 0) idTitle = IDS_DEFAULTTITLE; ASSERT(idDesc != 0); cc.pwszDescription = ResourceCStrToStr(g_hinst, (LPCWSTR)(UINT_PTR)idDesc); if (cc.pwszDescription) { cc.pwszTitle = ResourceCStrToStr(g_hinst, (LPCWSTR)(UINT_PTR)idTitle); if (cc.pwszTitle) { cc.dwFlags |= CCF_USE_DEFAULT_ICON; hrConfirm = _ptas->ConfirmOperation(psiItem, psiTarget, stc, &cc); LocalFree(cc.pwszTitle); } LocalFree(cc.pwszDescription); } } switch (hrConfirm) { case STRESPONSE_CONTINUE: case STRESPONSE_RETRY: if (STRESPONSE_RETRY == hrConfirm || STG_E_FILEALREADYEXISTS == hr) { grfMode = STGX_MODE_WRITE | STGX_MODE_FORCE; fRepeat = TRUE; } break; case STRESPONSE_SKIP: hr = S_FALSE; break; default: // let hr propagate out of the function break; } } if (psiTarget) psiTarget->Release(); } while (fRepeat); CoTaskMemFree(pszOldName); } } return hr; } STDMETHODIMP CShellItem2TransferDest::DestroyElement(const WCHAR *pwcsName, STGXDESTROY grfOptions) { if (!_psi) return E_FAIL; // TODO: Pre and post op, confirmations HRESULT hr = STRESPONSE_CONTINUE; if (!(grfOptions & STGX_DESTROY_FORCE) && _ptas) { DWORD dwType = STGX_TYPE_ANY; IShellItem *psi; hr = _OpenHelper(pwcsName, STGX_MODE_READ, &dwType, IID_PPV_ARG(IShellItem, &psi)); if (SUCCEEDED(hr)) { hr = _ptas->ConfirmOperation(psi, NULL, (STGX_TYPE_STORAGE == dwType) ? STCONFIRM_DELETE_STORAGE : STCONFIRM_DELETE_STREAM, NULL); psi->Release(); } } if (STRESPONSE_CONTINUE == hr) { IStorage *pstg; hr = _BindToHandlerWithMode(_psi, STGX_MODE_WRITE, IID_PPV_ARG(IStorage, &pstg)); if (SUCCEEDED(hr)) { hr = pstg->DestroyElement(pwcsName); pstg->Release(); } } return hr; } STDMETHODIMP CShellItem2TransferDest::RenameElement(const WCHAR *pwcsOldName, const WCHAR *pwcsNewName) { if (!_psi) return E_FAIL; // TODO: Pre and post op, confirmations IStorage *pstg; HRESULT hr = _BindToHandlerWithMode(_psi, STGX_MODE_WRITE, IID_PPV_ARG(IStorage, &pstg)); if (SUCCEEDED(hr)) { hr = pstg->RenameElement(pwcsOldName, pwcsNewName); pstg->Release(); } return hr; } STDAPI_(BOOL) IsFileDeletable(LPCTSTR pszFile); // bitbuck.c BOOL CShellItem2TransferDest::_CanHardLink(LPCWSTR pszSourceName, LPCWSTR pszDestName) { // this is not intended to catch invalid situations where we could be hard linking -- // CreateHardLink already takes care of all removable media, non-NTFS, etc. // this is just to do a quick check before taking the cost of destroying and // recreating the file. // unfortunately due to architecture cleanliness we can't keep state of whether hard // links are possible for the whole copy, so we check on each element. BOOL fRet = FALSE; if (PathGetDriveNumber(pszSourceName) == PathGetDriveNumber(pszDestName)) { TCHAR szRoot[MAX_PATH]; lstrcpyn(szRoot, pszSourceName, ARRAYSIZE(szRoot)); TCHAR szFileSystem[20]; if (PathStripToRoot(szRoot) && GetVolumeInformation(szRoot, NULL, 0, NULL, NULL, NULL, szFileSystem, ARRAYSIZE(szFileSystem))) { if (lstrcmpi(szFileSystem, TEXT("NTFS")) == 0) { // check if we have delete access on the file. this will aid the user later // if they want to manage the files in the staging area for cd burning. // if not, then make a normal copy. if (IsFileDeletable(pszSourceName)) { fRet = TRUE; } } } } return fRet; } HRESULT CShellItem2TransferDest::_CopyStreamHardLink(IShellItem *psiSource, IShellItem *psiDest, LPCWSTR pszName) { // sell out and go to filesystem LPWSTR pszSourceName; HRESULT hr = psiSource->GetDisplayName(SIGDN_FILESYSPATH, &pszSourceName); if (SUCCEEDED(hr)) { LPWSTR pszDestName; hr = psiDest->GetDisplayName(SIGDN_FILESYSPATH, &pszDestName); if (SUCCEEDED(hr)) { if (_CanHardLink(pszSourceName, pszDestName)) { // need to destroy the 0-byte file we created during our confirm overwrite probing DestroyElement(pszName, STGX_DESTROY_FORCE); hr = CreateHardLink(pszDestName, pszSourceName, NULL) ? S_OK : E_FAIL; if (SUCCEEDED(hr)) { SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, pszDestName, NULL); _ptas->OperationProgress(STGOP_COPY, psiSource, psiDest, 1, 1); } else { // we deleted it above and need to recreate it for the fallback of doing a normal copy IUnknown *punkDummy; if (SUCCEEDED(_CreateHelper(pszName, STGX_MODE_WRITE | STGX_MODE_FORCE, STGX_TYPE_STREAM, IID_PPV_ARG(IUnknown, &punkDummy)))) { punkDummy->Release(); } } } else { hr = E_FAIL; } CoTaskMemFree(pszDestName); } CoTaskMemFree(pszSourceName); } return hr; } HRESULT CShellItem2TransferDest::_CopyStreamWithOptions(IShellItem *psiSource, IShellItem *psiDest, LPCWSTR pszName, STGXMOVE grfOptions) { HRESULT hr = E_FAIL; if (grfOptions & STGX_MOVE_PREFERHARDLINK) { hr = _CopyStreamHardLink(psiSource, psiDest, pszName); } if (FAILED(hr)) { hr = _CopyStreamBits(psiSource, psiDest); } return hr; } HRESULT CShellItem2TransferDest::_CopyStreamBits(IShellItem *psiSource, IShellItem *psiDest) { const ULONG maxbuf = 1024*1024; // max size we will ever use for a buffer const ULONG minbuf = 1024; // smallest buffer we will use void *pv = LocalAlloc(LPTR, minbuf); if (!pv) return E_OUTOFMEMORY; IStream *pstrmSource; HRESULT hr = _BindToHandlerWithMode(psiSource, STGM_READ | STGM_SHARE_DENY_WRITE, IID_PPV_ARG(IStream, &pstrmSource)); if (SUCCEEDED(hr)) { IStream *pstrmDest; hr = _BindToHandlerWithMode(psiDest, STGM_READWRITE, IID_PPV_ARG(IStream, &pstrmDest)); if (SUCCEEDED(hr)) { // we need the source size info so we can show progress STATSTG statsrc; hr = pstrmSource->Stat(&statsrc, STATFLAG_NONAME); if (SUCCEEDED(hr)) { ULONG cbSizeToAlloc = minbuf; ULONG cbSizeAlloced = 0; ULONG cbToRead = 0; ULONGLONG ullCurr = 0; const ULONG maxms = 2500; // max time, in ms, we'd like between progress updates const ULONG minms = 750; // min time we'd like to be doing work between updates cbSizeAlloced = cbSizeToAlloc; cbToRead = cbSizeAlloced; DWORD dwmsBefore = GetTickCount(); // Read from source, write to dest, and update progress. We start doing 1K at a time, and // so long as its taking us less than (minms) milliseconds per pass, we'll double the buffer // size. If we go longer than (maxms) milliseconds, we'll cut our work in half. ULONG cbRead; ULONGLONG ullCur = 0; while (SUCCEEDED(hr = pstrmSource->Read(pv, cbToRead, &cbRead)) && cbRead) { // Update the progress based on the bytes read so far ullCur += cbRead; hr = _ptas->OperationProgress(STGOP_COPY, psiSource, psiDest, statsrc.cbSize.QuadPart, ullCur); if (FAILED(hr)) break; // Write the bytes to the output stream ULONG cbWritten = 0; hr = pstrmDest->Write(pv, cbRead, &cbWritten); if (FAILED(hr)) break; DWORD dwmsAfter = GetTickCount(); // If we're going to fast or too slow, adjust the size of the buffer. If we paused for user // intervention we'll think we're slow, but we'll correct next pass if (dwmsAfter - dwmsBefore < minms && cbSizeAlloced < maxbuf) { // We completed really quickly, so we should try to do more work next time. // Try to grow the buffer. If it fails, just go with the existing buffer. if (cbToRead < cbSizeAlloced) { // Buffer already larger than work we're doing, so just bump up scheduled work cbToRead = __min(cbToRead *2, cbSizeAlloced); } else { // Buffer maxed by current scheduled work, so increase its size void *pvOld = pv; cbSizeToAlloc = __min(cbSizeAlloced *2, maxbuf); pv = LocalReAlloc((HLOCAL)pv, cbSizeToAlloc, LPTR); if (!pv) pv = pvOld; // Old pointer still valid else cbSizeAlloced = cbSizeToAlloc; cbToRead = cbSizeAlloced; } } else if (dwmsAfter - dwmsBefore > maxms && cbToRead > minbuf) { cbToRead = __max(cbToRead / 2, minbuf); } dwmsBefore = GetTickCount(); } } if (SUCCEEDED(hr)) hr = pstrmDest->Commit(STGC_DEFAULT); pstrmDest->Release(); } pstrmSource->Release(); } LocalFree(pv); // eventually we will read to the end of the file and get an S_FALSE, return S_OK if (S_FALSE == hr) { hr = S_OK; } return hr; }