547 lines
17 KiB
C++
547 lines
17 KiB
C++
#include "shellprv.h"
|
|
#include "datautil.h"
|
|
|
|
#include "idlcomm.h"
|
|
|
|
STDAPI DataObj_SetDropTarget(IDataObject *pdtobj, const CLSID *pclsid)
|
|
{
|
|
return DataObj_SetBlob(pdtobj, g_cfTargetCLSID, pclsid, sizeof(*pclsid));
|
|
}
|
|
|
|
STDAPI DataObj_GetDropTarget(IDataObject *pdtobj, CLSID *pclsid)
|
|
{
|
|
return DataObj_GetBlob(pdtobj, g_cfTargetCLSID, pclsid, sizeof(*pclsid));
|
|
}
|
|
|
|
STDAPI_(UINT) DataObj_GetHIDACount(IDataObject *pdtobj)
|
|
{
|
|
STGMEDIUM medium = {0};
|
|
LPIDA pida = DataObj_GetHIDA(pdtobj, &medium);
|
|
if (pida)
|
|
{
|
|
UINT count = pida->cidl;
|
|
|
|
ASSERT(pida->cidl == HIDA_GetCount(medium.hGlobal));
|
|
|
|
HIDA_ReleaseStgMedium(pida, &medium);
|
|
return count;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// PERFPERF
|
|
// This routine used to copy 512 bytes at a time, but that had a major negative perf impact.
|
|
// I have measured a 2-3x speedup in copy times by increasing this buffer size to 16k.
|
|
// Yes, its a lot of stack, but it is memory well spent. -saml
|
|
#define STREAM_COPY_BUF_SIZE 16384
|
|
#define STREAM_PROGRESS_INTERVAL (100*1024/STREAM_COPY_BUF_SIZE) // display progress after this many blocks
|
|
|
|
HRESULT StreamCopyWithProgress(IStream *pstmFrom, IStream *pstmTo, ULARGE_INTEGER cb, PROGRESSINFO * ppi)
|
|
{
|
|
BYTE buf[STREAM_COPY_BUF_SIZE];
|
|
ULONG cbRead;
|
|
HRESULT hr = S_OK;
|
|
ULARGE_INTEGER uliNewCompleted;
|
|
DWORD dwLastTickCount = 0;
|
|
|
|
if (ppi)
|
|
{
|
|
uliNewCompleted.QuadPart = ppi->uliBytesCompleted.QuadPart;
|
|
}
|
|
|
|
while (cb.QuadPart)
|
|
{
|
|
if (ppi && ppi->ppd)
|
|
{
|
|
DWORD dwTickCount = GetTickCount();
|
|
|
|
if ((dwTickCount - dwLastTickCount) > 1000)
|
|
{
|
|
EVAL(SUCCEEDED(ppi->ppd->SetProgress64(uliNewCompleted.QuadPart, ppi->uliBytesTotal.QuadPart)));
|
|
|
|
if (ppi->ppd->HasUserCancelled())
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
|
|
break;
|
|
}
|
|
|
|
dwLastTickCount = dwTickCount;
|
|
}
|
|
}
|
|
|
|
hr = pstmFrom->Read(buf, min(cb.LowPart, sizeof(buf)), &cbRead);
|
|
if (FAILED(hr) || (cbRead == 0))
|
|
{
|
|
// sometimes we are just done.
|
|
if (SUCCEEDED(hr))
|
|
hr = S_OK;
|
|
break;
|
|
}
|
|
|
|
|
|
if (ppi)
|
|
{
|
|
uliNewCompleted.QuadPart += (ULONGLONG) cbRead;
|
|
}
|
|
|
|
cb.QuadPart -= cbRead;
|
|
|
|
hr = pstmTo->Write(buf, cbRead, &cbRead);
|
|
if (FAILED(hr) || (cbRead == 0))
|
|
break;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// APP COMPAT! Prior versions of the shell used IStream::CopyTo to copy
|
|
// the stream. New versions of the shell use IStream::Read to copy the
|
|
// stream so we can put up progress UI. WebFerret 3.0000 implements both
|
|
// IStream::Read and IStream::CopyTo, but their implementation of
|
|
// IStream::Read hangs the system. So we need to sniff at the data object
|
|
// and stream to see if it is WebFerret.
|
|
//
|
|
// WebFerret doesn't implement IPersist (so IPersist::GetClassID won't
|
|
// help) and they don't fill in the CLSID in the FILEDESCRIPTOR
|
|
// and it's an out-of-proc data object, so we have to go completely
|
|
// on circumstantial evidence.
|
|
//
|
|
|
|
STDAPI_(BOOL) IUnknown_SupportsInterface(IUnknown *punk, REFIID riid)
|
|
{
|
|
IUnknown *punkOut;
|
|
if (SUCCEEDED(punk->QueryInterface(riid, (void **)&punkOut)))
|
|
{
|
|
punkOut->Release();
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
STDAPI_(BOOL) DataObj_ShouldCopyWithProgress(IDataObject *pdtobj, IStream *pstm, PROGRESSINFO * ppi)
|
|
{
|
|
//
|
|
// Optimization: If there is no progress info, then don't waste your
|
|
// time with progress UI.
|
|
//
|
|
if (!ppi) return FALSE;
|
|
|
|
//
|
|
// How to detect a WebFerret IDataObject:
|
|
//
|
|
// The filegroup descriptor gives all objects as size zero.
|
|
// (Check this first since it is cheap and usually false)
|
|
// WebFerret app is running (look for their tooltip window).
|
|
// Their IDataObject doesn't support anything other than IUnknown
|
|
// (so we use IID_IAsyncOperation to detect shell data objects
|
|
// and IPersist to allow ISVs to override).
|
|
// Their IStream doesn't support IStream::Stat.
|
|
//
|
|
|
|
STATSTG stat;
|
|
|
|
if (ppi->uliBytesTotal.QuadPart == 0 &&
|
|
FindWindow(TEXT("VslToolTipWindow"), NULL) &&
|
|
!IUnknown_SupportsInterface(pdtobj, IID_IAsyncOperation) &&
|
|
!IUnknown_SupportsInterface(pdtobj, IID_IPersist) &&
|
|
pstm->Stat(&stat, STATFLAG_NONAME) == E_NOTIMPL)
|
|
{
|
|
return FALSE; // WebFerret!
|
|
}
|
|
|
|
// All test passed; go ahead and copy with progress UI
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
STDAPI DataObj_SaveToFile(IDataObject *pdtobj, UINT cf, LONG lindex, LPCTSTR pszFile, FILEDESCRIPTOR *pfd, PROGRESSINFO * ppi)
|
|
{
|
|
STGMEDIUM medium = {0};
|
|
FORMATETC fmte;
|
|
HRESULT hr;
|
|
|
|
fmte.cfFormat = (CLIPFORMAT) cf;
|
|
fmte.ptd = NULL;
|
|
fmte.dwAspect = DVASPECT_CONTENT;
|
|
fmte.lindex = lindex;
|
|
fmte.tymed = TYMED_HGLOBAL | TYMED_ISTREAM | TYMED_ISTORAGE;
|
|
|
|
hr = pdtobj->GetData(&fmte, &medium);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// if the destination file is system or read-only,
|
|
// clear those bits out so we can write anew.
|
|
//
|
|
DWORD dwTargetFileAttributes = GetFileAttributes(pszFile);
|
|
if (dwTargetFileAttributes != -1)
|
|
{
|
|
if (dwTargetFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY))
|
|
{
|
|
SetFileAttributes(pszFile, dwTargetFileAttributes & ~(FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY));
|
|
}
|
|
}
|
|
|
|
DWORD dwSrcFileAttributes = 0;
|
|
if (pfd->dwFlags & FD_ATTRIBUTES)
|
|
{
|
|
// store the rest of the attributes if passed...
|
|
dwSrcFileAttributes = (pfd->dwFileAttributes & ~FILE_ATTRIBUTE_DIRECTORY);
|
|
}
|
|
|
|
switch (medium.tymed) {
|
|
case TYMED_HGLOBAL:
|
|
{
|
|
HANDLE hfile = CreateFile(pszFile, GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, dwSrcFileAttributes, NULL);
|
|
|
|
if (hfile != INVALID_HANDLE_VALUE)
|
|
{
|
|
DWORD dwWrite;
|
|
// NTRAID89561-2000/02/25-raymondc: what about writes greater than 4 GB?
|
|
if (!WriteFile(hfile, GlobalLock(medium.hGlobal), (pfd->dwFlags & FD_FILESIZE) ? pfd->nFileSizeLow : (DWORD) GlobalSize(medium.hGlobal), &dwWrite, NULL))
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
GlobalUnlock(medium.hGlobal);
|
|
|
|
if (pfd->dwFlags & (FD_CREATETIME | FD_ACCESSTIME | FD_WRITESTIME))
|
|
{
|
|
SetFileTime(hfile,
|
|
pfd->dwFlags & FD_CREATETIME ? &pfd->ftCreationTime : NULL,
|
|
pfd->dwFlags & FD_ACCESSTIME ? &pfd->ftLastAccessTime : NULL,
|
|
pfd->dwFlags & FD_WRITESTIME ? &pfd->ftLastWriteTime : NULL);
|
|
}
|
|
|
|
CloseHandle(hfile);
|
|
|
|
if (FAILED(hr))
|
|
EVAL(DeleteFile(pszFile));
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
break;
|
|
}
|
|
|
|
case TYMED_ISTREAM:
|
|
{
|
|
IStream *pstm;
|
|
hr = SHCreateStreamOnFile(pszFile, STGM_CREATE | STGM_WRITE | STGM_SHARE_DENY_WRITE, &pstm);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// Per the SDK, IDataObject::GetData leaves the stream ptr at
|
|
// the end of the data in the stream. To copy the stream we
|
|
// first must reposition the stream ptr to the begining.
|
|
// We restore the stream ptr to it's original location when we're done.
|
|
//
|
|
// NOTE: In case the source stream doesn't support Seek(),
|
|
// attempt the copy even if the seek operation fails.
|
|
//
|
|
const LARGE_INTEGER ofsBegin = {0, 0};
|
|
ULARGE_INTEGER ofsOriginal = {0, 0};
|
|
HRESULT hrSeek = medium.pstm->Seek(ofsBegin, STREAM_SEEK_CUR, &ofsOriginal);
|
|
if (SUCCEEDED(hrSeek))
|
|
{
|
|
hrSeek = medium.pstm->Seek(ofsBegin, STREAM_SEEK_SET, NULL);
|
|
}
|
|
|
|
const ULARGE_INTEGER ul = {(UINT)-1, (UINT)-1}; // the whole thing
|
|
|
|
if (DataObj_ShouldCopyWithProgress(pdtobj, medium.pstm, ppi))
|
|
{
|
|
hr = StreamCopyWithProgress(medium.pstm, pstm, ul, ppi);
|
|
}
|
|
else
|
|
{
|
|
hr = medium.pstm->CopyTo(pstm, ul, NULL, NULL);
|
|
}
|
|
if (SUCCEEDED(hrSeek))
|
|
{
|
|
//
|
|
// Restore stream ptr in source to it's original location.
|
|
//
|
|
const LARGE_INTEGER ofs = { ofsOriginal.LowPart, (LONG)ofsOriginal.HighPart };
|
|
medium.pstm->Seek(ofs, STREAM_SEEK_SET, NULL);
|
|
}
|
|
|
|
pstm->Release();
|
|
|
|
if (FAILED(hr))
|
|
EVAL(DeleteFile(pszFile));
|
|
|
|
DebugMsg(TF_FSTREE, TEXT("IStream::CopyTo() -> %x"), hr);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case TYMED_ISTORAGE:
|
|
{
|
|
WCHAR wszNewFile[MAX_PATH];
|
|
IStorage *pstg;
|
|
|
|
DebugMsg(TF_FSTREE, TEXT("got IStorage"));
|
|
|
|
SHTCharToUnicode(pszFile, wszNewFile, ARRAYSIZE(wszNewFile));
|
|
hr = StgCreateDocfile(wszNewFile,
|
|
STGM_DIRECT | STGM_READWRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE,
|
|
0, &pstg);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = medium.pstg->CopyTo(0, NULL, NULL, pstg);
|
|
|
|
DebugMsg(TF_FSTREE, TEXT("IStorage::CopyTo() -> %x"), hr);
|
|
|
|
pstg->Commit(STGC_OVERWRITE);
|
|
pstg->Release();
|
|
|
|
if (FAILED(hr))
|
|
EVAL(DeleteFile(pszFile));
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
AssertMsg(FALSE, TEXT("got tymed that I didn't ask for %d"), medium.tymed);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// in the HGLOBAL case we could take some shortcuts, so the attributes and
|
|
// file times were set earlier in the case statement.
|
|
// otherwise, we need to set the file times and attributes now.
|
|
if (medium.tymed != TYMED_HGLOBAL)
|
|
{
|
|
if (pfd->dwFlags & (FD_CREATETIME | FD_ACCESSTIME | FD_WRITESTIME))
|
|
{
|
|
// open with GENERIC_WRITE to let us set the file times,
|
|
// everybody else can open with SHARE_READ.
|
|
HANDLE hFile = CreateFile(pszFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
SetFileTime(hFile,
|
|
pfd->dwFlags & FD_CREATETIME ? &pfd->ftCreationTime : NULL,
|
|
pfd->dwFlags & FD_ACCESSTIME ? &pfd->ftLastAccessTime : NULL,
|
|
pfd->dwFlags & FD_WRITESTIME ? &pfd->ftLastWriteTime : NULL);
|
|
CloseHandle(hFile);
|
|
}
|
|
}
|
|
|
|
if (dwSrcFileAttributes)
|
|
{
|
|
SetFileAttributes(pszFile, dwSrcFileAttributes);
|
|
}
|
|
}
|
|
|
|
SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, pszFile, NULL);
|
|
SHChangeNotify(SHCNE_FREESPACE, SHCNF_PATH, pszFile, NULL);
|
|
}
|
|
|
|
ReleaseStgMedium(&medium);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
STDAPI DataObj_GetShellURL(IDataObject *pdtobj, STGMEDIUM *pmedium, LPCSTR *ppszURL)
|
|
{
|
|
FORMATETC fmte = {g_cfShellURL, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
HRESULT hr;
|
|
|
|
if (pmedium)
|
|
{
|
|
hr = pdtobj->GetData(&fmte, pmedium);
|
|
if (SUCCEEDED(hr))
|
|
*ppszURL = (LPCSTR)GlobalLock(pmedium->hGlobal);
|
|
}
|
|
else
|
|
hr = pdtobj->QueryGetData(&fmte); // query only
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDAPI DataObj_GetOFFSETs(IDataObject *pdtobj, POINT *ppt)
|
|
{
|
|
STGMEDIUM medium = {0};
|
|
|
|
IDLData_InitializeClipboardFormats( );
|
|
ASSERT(g_cfOFFSETS);
|
|
|
|
FORMATETC fmt = {g_cfOFFSETS, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
|
|
ASSERT(ppt);
|
|
ppt->x = ppt->y = 0;
|
|
HRESULT hr = pdtobj->GetData(&fmt, &medium);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
POINT * pptTemp = (POINT *)GlobalLock(medium.hGlobal);
|
|
if (pptTemp)
|
|
{
|
|
*ppt = *pptTemp;
|
|
GlobalUnlock(medium.hGlobal);
|
|
}
|
|
else
|
|
hr = E_UNEXPECTED;
|
|
ReleaseStgMedium(&medium);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
STDAPI_(BOOL) DataObj_CanGoAsync(IDataObject *pdtobj)
|
|
{
|
|
BOOL fDoOpAsynch = FALSE;
|
|
IAsyncOperation * pao;
|
|
|
|
if (SUCCEEDED(pdtobj->QueryInterface(IID_PPV_ARG(IAsyncOperation, &pao))))
|
|
{
|
|
BOOL fIsOpAsync;
|
|
if (SUCCEEDED(pao->GetAsyncMode(&fIsOpAsync)) && fIsOpAsync)
|
|
{
|
|
fDoOpAsynch = SUCCEEDED(pao->StartOperation(NULL));
|
|
}
|
|
pao->Release();
|
|
}
|
|
return fDoOpAsynch;
|
|
}
|
|
|
|
//
|
|
// HACKHACK: (reinerf) - We used to always do async drag/drop operations on NT4 by cloning the
|
|
// dataobject. Some apps (WS_FTP 6.0) rely on the async nature in order for drag/drop to work since
|
|
// they stash the return value from DoDragDrop and look at it later when their copy hook is invoked
|
|
// by SHFileOperation(). So, we sniff the HDROP and if it has one path that contains "WS_FTPE\Notify"
|
|
// in it, then we do the operation async.
|
|
//
|
|
STDAPI_(BOOL) DataObj_GoAsyncForCompat(IDataObject *pdtobj)
|
|
{
|
|
BOOL bRet = FALSE;
|
|
STGMEDIUM medium;
|
|
FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
|
|
if (SUCCEEDED(pdtobj->GetData(&fmte, &medium)))
|
|
{
|
|
// is there only one path in the hdrop?
|
|
if (DragQueryFile((HDROP)medium.hGlobal, (UINT)-1, NULL, 0) == 1)
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
|
|
// is it the magical WS_FTP path ("%temp%\WS_FTPE\Notify") that WS_FTP sniffs
|
|
// for in their copy hook?
|
|
if (DragQueryFile((HDROP)medium.hGlobal, 0, szPath, ARRAYSIZE(szPath)) &&
|
|
StrStrI(szPath, TEXT("WS_FTPE\\Notify")))
|
|
{
|
|
// yes, we have to do an async operation for app compat
|
|
TraceMsg(TF_WARNING, "DataObj_GoAsyncForCompat: found WS_FTP HDROP, doing async drag-drop");
|
|
bRet = TRUE;
|
|
}
|
|
}
|
|
|
|
ReleaseStgMedium(&medium);
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
// use GlobalFree() to free the handle returned here
|
|
STDAPI DataObj_CopyHIDA(IDataObject *pdtobj, HIDA *phida)
|
|
{
|
|
*phida = NULL;
|
|
|
|
IDLData_InitializeClipboardFormats();
|
|
|
|
STGMEDIUM medium;
|
|
FORMATETC fmte = {g_cfHIDA, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
HRESULT hr = pdtobj->GetData(&fmte, &medium);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
SIZE_T cb = GlobalSize(medium.hGlobal);
|
|
*phida = (HIDA)GlobalAlloc(GPTR, cb);
|
|
if (*phida)
|
|
{
|
|
void *pv = GlobalLock(medium.hGlobal);
|
|
CopyMemory((void *)*phida, pv, cb);
|
|
GlobalUnlock(medium.hGlobal);
|
|
}
|
|
else
|
|
hr = E_OUTOFMEMORY;
|
|
ReleaseStgMedium(&medium);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
// Returns an IShellItem for the FIRST item in the data object
|
|
HRESULT DataObj_GetIShellItem(IDataObject *pdtobj, IShellItem** ppsi)
|
|
{
|
|
LPITEMIDLIST pidl;
|
|
HRESULT hr = PidlFromDataObject(pdtobj, &pidl);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// at shome point should find out who is calling this
|
|
// can see if caller already as the info to create the ShellItem
|
|
hr = SHCreateShellItem(NULL, NULL, pidl, ppsi);
|
|
ILFree(pidl);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
STDAPI PathFromDataObject(IDataObject *pdtobj, LPTSTR pszPath, UINT cchPath)
|
|
{
|
|
FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
STGMEDIUM medium;
|
|
|
|
HRESULT hr = pdtobj->GetData(&fmte, &medium);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (DragQueryFile((HDROP)medium.hGlobal, 0, pszPath, cchPath))
|
|
hr = S_OK;
|
|
else
|
|
hr = E_FAIL;
|
|
|
|
ReleaseStgMedium(&medium);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDAPI PidlFromDataObject(IDataObject *pdtobj, LPITEMIDLIST *ppidlTarget)
|
|
{
|
|
HRESULT hr;
|
|
|
|
*ppidlTarget = NULL;
|
|
|
|
// If the data object has a HIDA, then use it. This allows us to
|
|
// access pidls inside data objects that aren't filesystem objects.
|
|
// (It's also faster than extracting the path and converting it back
|
|
// to a pidl. Difference: pidls for files on the desktop
|
|
// are returned in original form instead of being converted to
|
|
// a CSIDL_DESKTOPDIRECTORY-relative pidl. I think this is a good thing.)
|
|
|
|
STGMEDIUM medium;
|
|
LPIDA pida = DataObj_GetHIDA(pdtobj, &medium);
|
|
|
|
if (pida)
|
|
{
|
|
*ppidlTarget = HIDA_ILClone(pida, 0);
|
|
HIDA_ReleaseStgMedium(pida, &medium);
|
|
hr = *ppidlTarget ? S_OK : E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
// No HIDA available; go for a filename
|
|
|
|
// This string is also used to store an URL in case it's an URL file
|
|
TCHAR szPath[MAX_URL_STRING];
|
|
|
|
hr = PathFromDataObject(pdtobj, szPath, ARRAYSIZE(szPath));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*ppidlTarget = ILCreateFromPath(szPath);
|
|
hr = *ppidlTarget ? S_OK : E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|