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
978 lines
35 KiB
//+-------------------------------------------------------------------------
|
|
//
|
|
// 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:
|
|
LONG _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(&_cRef);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CShellItem2TransferDest::Release()
|
|
{
|
|
ASSERT( 0 != _cRef );
|
|
ULONG cRef = InterlockedDecrement(&_cRef);
|
|
if ( 0 == cRef )
|
|
{
|
|
delete this;
|
|
}
|
|
return cRef;
|
|
}
|
|
|
|
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;
|
|
if (NT_SUCCESS(RtlInitUnicodeStringEx(&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);
|
|
|
|
// in the failure case, delete the file we just created (it might be 0 bytes or incomplete).
|
|
// if we moved a tree of files, leave it since we're just worried about incomplete streams.
|
|
if (FAILED(hr))
|
|
{
|
|
DestroyElement(pszName, STGX_DESTROY_FORCE);
|
|
}
|
|
}
|
|
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];
|
|
StrCpyN(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;
|
|
}
|