windows-nt/Source/XPSP1/NT/shell/ext/ftp/ftpobj.cpp
2020-09-26 16:20:57 +08:00

2075 lines
65 KiB
C++

/*****************************************************************************
*
* ftpobj.cpp - IDataObject interface
*
*****************************************************************************/
#include "priv.h"
#include "ftpobj.h"
#include "ftpurl.h"
#include <shlwapi.h>
// CLSIDs
// {299D0193-6DAA-11d2-B679-006097DF5BD4}
const GUID CLSID_FtpDataObject = { 0x299d0193, 0x6daa, 0x11d2, 0xb6, 0x79, 0x0, 0x60, 0x97, 0xdf, 0x5b, 0xd4 };
/*****************************************************************************
*
* g_dropTypes conveniently mirrors our FORMATETCs.
*
* Hardly coincidence, of course. Enum_Fe did the real work.
*
*****************************************************************************/
/*****************************************************************************
*
* Preinitialized global data.
*
*****************************************************************************/
FORMATETC g_formatEtcOffsets;
FORMATETC g_formatPasteSucceeded;
CLIPFORMAT g_cfTargetCLSID;
FORMATETC g_dropTypes[] =
{
{ 0, 0, DVASPECT_CONTENT, -1, TYMED_ISTREAM }, // DROP_FCont
{ 0, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, // DROP_FGDW
{ 0, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, // DROP_FGDA
{ 0, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, // DROP_IDList
{ 0, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, // DROP_URL
// { 0, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, // DROP_Offsets
{ 0, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, // DROP_PrefDe
{ 0, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, // DROP_PerfDe
{ 0, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, // DROP_FTP_PRIVATE
{ 0, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, // DROP_OLEPERSIST - see _RenderOlePersist() for desc.
{ CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, // DROP_Hdrop
{ 0, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, // DROP_FNMA
{ 0, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL } // DROP_FNMW
};
/*****************************************************************************\
GLOBAL: c_stgInit
DESCRIPTION:
Mostly straightforward. The only major weirdness is that cfURL
is delay-rendered iff the m_pflHfpl contains only one object. Otherwise,
cfURL is not supported. (URLs can refer to only one object at a time.)
\*****************************************************************************/
STGMEDIUM c_stgInit[] =
{
{ 0, 0, 0 }, // DROP_FCont
{ TYMED_HGLOBAL, 0, 0 }, // DROP_FGDW - delay-rendered
{ TYMED_HGLOBAL, 0, 0 }, // DROP_FGDA - delay-rendered
{ TYMED_HGLOBAL, 0, 0 }, // DROP_IDList - delay-rendered
{ 0, 0, 0 }, // DROP_URL - opt delay-rendered
// { 0, 0, 0 }, // DROP_Offsets
{ TYMED_HGLOBAL, 0, 0 }, // DROP_PrefDe - delay-rendered
{ 0, 0, 0 }, // DROP_PerfDe
{ TYMED_HGLOBAL, 0, 0 }, // DROP_FTP_PRIVATE
{ TYMED_HGLOBAL, 0, 0 }, // DROP_OLEPERSIST - see _RenderOlePersist() for desc.
{ 0, 0, 0 }, // DROP_Hdrop
{ 0, 0, 0 }, // DROP_FNMA
{ 0, 0, 0 } // DROP_FNMW
};
/*****************************************************************************\
FUNCTION: TraceMsgWithFormatEtc
DESCRIPTION:
\*****************************************************************************/
void TraceMsgWithFormat(DWORD dwFlags, LPCSTR pszBefore, LPFORMATETC pFormatEtc, LPCSTR pszAfter, HRESULT hr)
{
#ifdef DEBUG
TCHAR szFormatName[MAX_PATH];
TCHAR szMedium[MAX_PATH];
szFormatName[0] = 0;
szMedium[0] = 0;
if (pFormatEtc)
{
// This may fail if it's a basic format.
if (!GetClipboardFormatName(pFormatEtc->cfFormat, szFormatName, ARRAYSIZE(szFormatName)))
wnsprintf(szFormatName, ARRAYSIZE(szFormatName), TEXT("Pre-defined=%d"), pFormatEtc->cfFormat);
switch (pFormatEtc->tymed)
{
case TYMED_HGLOBAL: StrCpyN(szMedium, TEXT("HGLOBAL"), ARRAYSIZE(szMedium)); break;
case TYMED_FILE: StrCpyN(szMedium, TEXT("File"), ARRAYSIZE(szMedium)); break;
case TYMED_GDI: StrCpyN(szMedium, TEXT("GDI"), ARRAYSIZE(szMedium)); break;
case TYMED_MFPICT: StrCpyN(szMedium, TEXT("MFPICT"), ARRAYSIZE(szMedium)); break;
case TYMED_ENHMF: StrCpyN(szMedium, TEXT("ENHMF"), ARRAYSIZE(szMedium)); break;
case TYMED_ISTORAGE: StrCpyN(szMedium, TEXT("ISTORAGE"), ARRAYSIZE(szMedium)); break;
case TYMED_ISTREAM: StrCpyN(szMedium, TEXT("ISTREAM"), ARRAYSIZE(szMedium)); break;
}
}
else
{
szMedium[0] = 0;
}
TraceMsg(dwFlags, "%hs [FRMTETC: %ls, lndx: %d, %ls] hr=%#08lx, %hs", pszBefore, szFormatName, pFormatEtc->lindex, szMedium, hr, pszAfter);
#endif // DEBUG
}
/*****************************************************************************\
FUNCTION: _IsLindexOkay
DESCRIPTION:
If ife != DROP_FCont, then pfeWant->lindex must be -1.
If ife == DROP_FCont, then pfeWant->lindex must be in the range
0 ... m_pflHfpl->GetCount() - 1
\*****************************************************************************/
BOOL CFtpObj::_IsLindexOkay(int ife, FORMATETC *pfeWant)
{
BOOL fResult;
if (ife != DROP_FCont)
fResult = pfeWant->lindex == -1;
else
fResult = (LONG)pfeWant->lindex < m_pflHfpl->GetCount();
return fResult;
}
/*****************************************************************************\
FUNCTION: _FindData
DESCRIPTION:
Locate our FORMATETC/STGMEDIUM given a FORMATETC from somebody else.
On success, stores the index found into *piOut.
We do not allow clients to change the TYMED of a FORMATETC, so
in fact checking the TYMED is what we want, even on a SetData.
\*****************************************************************************/
HRESULT CFtpObj::_FindData(FORMATETC *pfe, PINT piOut)
{
int nIndex;
HRESULT hres = DV_E_FORMATETC;
*piOut = 0;
for (nIndex = DROP_FCont; nIndex < DROP_OFFERMAX; nIndex++)
{
ASSERT(0 == (g_dropTypes[nIndex]).ptd);
ASSERT(g_dropTypes[nIndex].dwAspect == DVASPECT_CONTENT);
if ((pfe->cfFormat == g_dropTypes[nIndex].cfFormat) && !ShouldSkipDropFormat(nIndex))
{
if (EVAL(g_dropTypes[nIndex].ptd == NULL))
{
if (EVAL(pfe->dwAspect == DVASPECT_CONTENT))
{
if (EVAL(g_dropTypes[nIndex].tymed & pfe->tymed))
{
if (EVAL(_IsLindexOkay(nIndex, pfe)))
{
*piOut = nIndex;
hres = S_OK;
}
else
hres = DV_E_LINDEX;
}
else
hres = DV_E_TYMED;
}
else
hres = DV_E_DVASPECT;
}
else
hres = DV_E_DVTARGETDEVICE;
break;
}
}
return hres;
}
/*****************************************************************************\
FUNCTION: _FindDataForGet
DESCRIPTION:
Locate our FORMATETC/STGMEDIUM given a FORMATETC from somebody else.
On success, stores the index found into *piOut. Unlike _FindData, we will
fail the call if the data object doesn't currently have the clipboard format.
(Delayed render counts as "currently having it". What we are filtering out
are formats for which GetData will necessarily fail.)
\*****************************************************************************/
HRESULT CFtpObj::_FindDataForGet(FORMATETC *pfe, PINT piOut)
{
HRESULT hr = _FindData(pfe, piOut);
// TODO: g_cfHIDA should return an array of pidls for each folder.
// If we do this, the caller will support creating Shortcuts
// (LNK files) that point to these pidls. We may want to do
// that later.
if (SUCCEEDED(hr))
{
if (*piOut != DROP_FCont)
{
if (m_stgCache[*piOut].tymed)
{
// Do we have data at all?
// (possibly delay-rendered)
}
else
hr = DV_E_FORMATETC; // I guess not
}
else
{
// File contents always okay
}
}
#ifdef DEBUG
if (FAILED(hr))
{
//TraceMsg(TF_FTPDRAGDROP, "CFtpObj::_FindDataForGet(FORMATETC.cfFormat=%d) Failed.", pfe->cfFormat);
*piOut = 0xBAADF00D;
}
#endif
return hr;
}
// The following are used to enumerate sub directories when creating a list of pidls for
// a directory download (Ftp->FileSys).
typedef struct tagGENPIDLLIST
{
CFtpPidlList * ppidlList;
IMalloc * pm;
IProgressDialog * ppd;
CWireEncoding * pwe;
} GENPIDLLIST;
/*****************************************************************************\
FUNCTION: ProcessItemCB
DESCRIPTION:
This function will add the specified pidl to the list. It will then
detect if it's a folder and if so, will call EnumFolder() to recursively
enum it's contents and call ProcessItemCB() for each one.
PARAMETERS:
\*****************************************************************************/
HRESULT ProcessItemCB(LPVOID pvFuncCB, HINTERNET hint, LPCITEMIDLIST pidlFull, BOOL * pfValidhinst, LPVOID pvData)
{
GENPIDLLIST * pGenPidlList = (GENPIDLLIST *) pvData;
HRESULT hr = S_OK;
// Does the user want to cancel?
if (pGenPidlList->ppd && pGenPidlList->ppd->HasUserCancelled())
{
EVAL(SUCCEEDED(pGenPidlList->ppd->StopProgressDialog()));
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
}
if (SUCCEEDED(hr))
{
// No, don't cancel so continue...
// Add everything except SoftLinks.
// This is because dir SoftLinks may cause infinite recurion.
// Someday, we may want to upload a shortcut but
// that's too much work for now.
if (0 != FtpPidl_GetAttributes(pidlFull))
{
// We exist to do this:
pGenPidlList->ppidlList->InsertSorted(pidlFull);
}
// Is this a dir/folder that we need to recurse into?
if (SUCCEEDED(hr) && (FILE_ATTRIBUTE_DIRECTORY & FtpPidl_GetAttributes(pidlFull)))
{
hr = EnumFolder((LPFNPROCESSITEMCB) pvFuncCB, hint, pidlFull, pGenPidlList->pwe, pfValidhinst, pvData);
}
}
return hr;
}
/*****************************************************************************\
FUNCTION: _ExpandPidlListRecursively
DESCRIPTION:
This function will take the pidl list (ppidlListSrc) and call into it
to enumerate. It will provide ProcessItemCB as the callback function.
This function will help it create a new CFtpPidlList which will not only
contain the pidls in a base folder, but also all the pidls in any subfolders
that are in the original list.
Delay-render a file group descriptor.
\*****************************************************************************/
CFtpPidlList * CFtpObj::_ExpandPidlListRecursively(CFtpPidlList * ppidlListSrc)
{
GENPIDLLIST pep = {0};
pep.ppidlList = NULL;
pep.ppd = m_ppd;
pep.pwe = m_pff->GetCWireEncoding();
if (SUCCEEDED(CFtpPidlList_Create(0, NULL, &pep.ppidlList)))
{
m_pff->GetItemAllocator(&pep.pm);
if (EVAL(m_pfd) && EVAL(pep.pm))
{
HINTERNET hint;
if (SUCCEEDED(m_pfd->GetHint(NULL, NULL, &hint, NULL, m_pff)))
{
LPITEMIDLIST pidlRoot = ILClone(m_pfd->GetPidlReference());
if (pidlRoot)
{
HRESULT hr = ppidlListSrc->RecursiveEnum(pidlRoot, ProcessItemCB, hint, (LPVOID) &pep);
if (m_ppd)
EVAL(SUCCEEDED(m_ppd->StopProgressDialog()));
if (FAILED(hr) && (HRESULT_FROM_WIN32(ERROR_CANCELLED) != hr) && !m_fErrAlreadyDisplayed)
{
pep.ppidlList->Release();
pep.ppidlList = NULL;
// Oh, I want a real hwnd, but where or where can I get one?
DisplayWininetErrorEx(NULL, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_DROPFAIL, IDS_FTPERR_WININET, MB_OK, NULL, NULL);
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); // Wrong permissions
// We need to suppress subsequent error dlgs from this location
// because callers like to ask for FILEGROUPDESCRIPTORA and
// if that fails, ask for FILEGROUPDESCRIPTORW and we don't
// want an error dialog for each.
m_fErrAlreadyDisplayed = TRUE;
}
ILFree(pidlRoot);
}
m_pfd->ReleaseHint(hint);
pep.pm->Release();
}
}
}
return pep.ppidlList;
}
/*****************************************************************************\
FUNCTION: _DelayRender_FGD
DESCRIPTION:
Delay-render a file group descriptor
\*****************************************************************************/
HGLOBAL CFtpObj::_DelayRender_FGD(BOOL fUnicode)
{
HGLOBAL hGlobal = NULL;
if (m_fCheckSecurity &&
ZoneCheckPidlAction(SAFECAST(this, IInternetSecurityMgrSite *), URLACTION_SHELL_FILE_DOWNLOAD, m_pff->GetPrivatePidlReference(), (PUAF_DEFAULT | PUAF_WARN_IF_DENIED)))
{
m_pflHfpl->TraceDump(m_pff->GetPrivatePidlReference(), TEXT("_DelayRender_FGD() TraceDump before"));
CFtpPidlList * pPidlList;
if (!m_fFGDRendered)
{
pPidlList = _ExpandPidlListRecursively(m_pflHfpl);
if (pPidlList)
{
// We succeeded so now it's expanded.
m_fFGDRendered = TRUE;
}
}
else
{
m_pflHfpl->AddRef();
pPidlList = m_pflHfpl;
}
if (pPidlList)
{
hGlobal = Misc_HFGD_Create(pPidlList, m_pff->GetPrivatePidlReference(), fUnicode);
IUnknown_Set(&m_pflHfpl, pPidlList);
m_pflHfpl->TraceDump(m_pff->GetPrivatePidlReference(), TEXT("_DelayRender_FGD() TraceDump after"));
pPidlList->Release();
}
}
else
{
// Suppress future UI. We don't need to check any more
// because our pidl won't change. We could not pass PUAF_WARN_IF_DENIED
// but that won't suppress the UI in the prompt case. (Only admins can
// turn on the prompt case).
m_fCheckSecurity = FALSE;
}
return hGlobal;
}
/*****************************************************************************\
FUNCTION: _DelayRender_IDList
DESCRIPTION:
Delay-render an ID List Array (HIDA)
\*****************************************************************************/
HRESULT CFtpObj::_DelayRender_IDList(STGMEDIUM * pStgMedium)
{
pStgMedium->hGlobal = Misc_HIDA_Create(m_pff->GetPublicRootPidlReference(), m_pflHfpl);
ASSERT(pStgMedium->hGlobal);
return S_OK;
}
/*****************************************************************************\
FUNCTION: _DelayRender_URL
DESCRIPTION:
The caller wants an URL in an Ansi String
\*****************************************************************************/
HRESULT CFtpObj::_DelayRender_URL(STGMEDIUM * pStgMedium)
{
LPSTR pszUrl = NULL;
LPITEMIDLIST pidlFull = NULL;
LPITEMIDLIST pidl = m_pflHfpl->GetPidl(0);
ASSERT(pidl); // We need this
// Sometimes m_pflHfpl->GetPidl(0) is fully qualified and
// sometimes it's not.
if (!FtpID_IsServerItemID(pidl))
{
pidlFull = ILCombine(m_pfd->GetPidlReference(), pidl);
pidl = pidlFull;
}
ASSERT(m_pflHfpl->GetCount() == 1); // How do we give them more than 1 URL?
if (pidl)
{
TCHAR szUrl[MAX_URL_STRING];
if (EVAL(SUCCEEDED(UrlCreateFromPidl(pidl, SHGDN_FORADDRESSBAR, szUrl, ARRAYSIZE(szUrl), (ICU_ESCAPE | ICU_USERNAME), TRUE))))
{
DWORD cchSize = (lstrlen(szUrl) + 1);
pszUrl = (LPSTR) LocalAlloc(LPTR, (cchSize * sizeof(CHAR)));
if (pszUrl)
SHTCharToAnsi(szUrl, pszUrl, cchSize);
}
ILFree(pidlFull);
}
pStgMedium->hGlobal = (HGLOBAL) pszUrl;
return S_OK;
}
#pragma BEGIN_CONST_DATA
DROPEFFECT c_deCopyLink = DROPEFFECT_COPY | DROPEFFECT_LINK;
DROPEFFECT c_deLink = DROPEFFECT_LINK;
#pragma END_CONST_DATA
/*****************************************************************************\
FUNCTION: _DelayRender_PrefDe
DESCRIPTION:
Delay-render a preferred drop effect.
The preferred drop effect is DROPEFFECT_COPY (with DROPEFFECT_LINK as fallback),
unless you are dragging an FTP site, in which case it's just DROPEFFECT_LINK.
DROPEFFECT_MOVE is never preferred. We can do it; it just isn't preferred.
NOTES: About DROPEFFECT_MOVE
We cannot support Move on platforms before NT5 because of a Recycle Bin bug
were it would clain to have succeeded with the copy but it actually didn't
copy anything. On NT5, the Recycle Bin drop target will call pDataObject->SetData()
with a data type of "Dropped On" and the data being the CLSID of the drop
target in addition to really copying the files to the recycle bin. This will
let us delete the files knowing they are in the recycle bin.
\*****************************************************************************/
HRESULT CFtpObj::_DelayRender_PrefDe(STGMEDIUM * pStgMedium)
{
DROPEFFECT * pde;
if (!m_pfd->IsRoot())
pde = &c_deCopyLink;
else
pde = &c_deLink;
return Misc_CreateHglob(sizeof(*pde), pde, &pStgMedium->hGlobal);
}
/*****************************************************************************\
FUNCTION: _RenderOlePersist
DESCRIPTION:
When the copy source goes away (the process shuts down), it calls
OleFlushClipboard. OLE will then copy our data, release us, and then
give out our data later. This works for most things except for:
1. When lindex needs to very. This doesn't work because ole doesn't know
how to ask us how may lindexs they need to copy.
2. If this object has a private interface OLE doesn't know about. For us,
it's IAsyncOperation.
To get around this problem, we want OLE to recreate us when some possible
paste target calls OleGetClipboard. We want OLE to call OleLoadFromStream()
to have us CoCreated and reload our persisted data via IPersistStream.
OLE doesn't want to do this by default or they may have backward compat
problems so they want a sign from the heavens, or at least from us, that
we will work. They ping our "OleClipboardPersistOnFlush" clipboard format
to ask this.
\*****************************************************************************/
HRESULT CFtpObj::_RenderOlePersist(STGMEDIUM * pStgMedium)
{
// The actual cookie value is opaque to the outside world. Since
// we don't use it either, we just leave it at zero in case we use
// it in the future. It's mere existence will cause OLE to do the
// use our IPersistStream, which is what we want.
DWORD dwCookie = 0;
return Misc_CreateHglob(sizeof(dwCookie), &dwCookie, &pStgMedium->hGlobal);
}
/*****************************************************************************\
FUNCTION: _RenderFGD
DESCRIPTION:
\*****************************************************************************/
HRESULT CFtpObj::_RenderFGD(int nIndex, STGMEDIUM * pStgMedium)
{
HRESULT hr = _DoProgressForLegacySystemsPre();
if (SUCCEEDED(hr))
pStgMedium->hGlobal = _DelayRender_FGD((DROP_FGDW == nIndex) ? TRUE : FALSE);
if (!pStgMedium->hGlobal)
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); // Probably failed because of Zones check.
return hr;
}
/*****************************************************************************\
FUNCTION: _ForceRender
DESCRIPTION:
We previously delayed rendering the data for perf reasons. This function
was called, so we now need to render the data.
\*****************************************************************************/
HRESULT CFtpObj::_ForceRender(int nIndex)
{
HRESULT hr = S_OK;
// We only support HGLOBALs here, but the caller may be valid
// to ask for something we don't support or an extended data.
// ASSERT((m_stgCache[nIndex].tymed) == TYMED_HGLOBAL);
if (!m_stgCache[nIndex].hGlobal)
{
STGMEDIUM medium = {TYMED_HGLOBAL, 0, NULL};
switch (nIndex)
{
case DROP_FCont:
ASSERT(0);
break;
case DROP_FGDW:
case DROP_FGDA:
hr = _RenderFGD(nIndex, &medium);
break;
case DROP_IDList:
hr = _DelayRender_IDList(&medium);
break;
/* Nuke
case DROP_Offsets:
ASSERT(0);
// hglob = _DelayRender_Offsets();
break;
*/
case DROP_PrefDe:
hr = _DelayRender_PrefDe(&medium);
break;
case DROP_PerfDe:
ASSERT(0);
// hglob = _DelayRender_PerfDe();
break;
case DROP_FTP_PRIVATE:
hr = DV_E_FORMATETC;
break;
case DROP_OLEPERSIST:
hr = _RenderOlePersist(&medium);
break;
case DROP_Hdrop:
ASSERT(0);
// hglob = _DelayRender_Hdrop();
break;
case DROP_FNMA:
ASSERT(0);
// hglob = _DelayRender_FNM();
break;
case DROP_FNMW:
ASSERT(0);
// hglob = _DelayRender_FNM();
break;
case DROP_URL:
hr = _DelayRender_URL(&medium);
break;
default:
ASSERT(0); // Should never hit.
break;
}
if (medium.hGlobal) // Will fail if the Zones Security Check Fails.
{
m_stgCache[nIndex].pUnkForRelease = NULL;
m_stgCache[nIndex].hGlobal = medium.hGlobal;
}
else
{
if (S_OK == hr)
hr = E_OUTOFMEMORY;
}
}
if (FAILED(hr))
TraceMsg(TF_FTPDRAGDROP, "CFtpObj::_ForceRender() FAILED. hres=%#08lx", hr);
return hr;
}
/*****************************************************************************\
FUNCTION: _DoProgressForLegacySystemsPre
DESCRIPTION:
Shell's pre-NT5 didn't do progress on the File Contents drop, so we
will do it here. This function will display a progress dialog while we
walk the server and expand the pidls that are needed to be copied.
Later,
\*****************************************************************************/
HRESULT CFtpObj::_DoProgressForLegacySystemsPre(void)
{
HRESULT hr = S_OK;
if (DEBUG_LEGACY_PROGRESS || (SHELL_VERSION_NT5 > GetShellVersion()))
{
TraceMsg(TF_ALWAYS, "CFtpObj::_DoProgressForLegacySystemsPre() going to do the Legacy dialogs.");
// Do we need to initialize the list?
if (!m_ppd && (-1 == m_nStartIndex))
{
// Yes, so create the create the dialog and find the sizes of the list.
if (m_ppd)
_CloseProgressDialog();
m_uliCompleted.QuadPart = 0;
m_uliTotal.QuadPart = 0;
m_ppd = CProgressDialog_CreateInstance(IDS_COPY_TITLE, IDA_FTPDOWNLOAD);
if (m_ppd)
{
WCHAR wzProgressDialogStr[MAX_PATH];
// Tell the user we are calculating how long it will take.
if (EVAL(LoadStringW(HINST_THISDLL, IDS_PROGRESS_DOWNLOADTIMECALC, wzProgressDialogStr, ARRAYSIZE(wzProgressDialogStr))))
EVAL(SUCCEEDED(m_ppd->SetLine(2, wzProgressDialogStr, FALSE, NULL)));
// We give a NULL punkEnableModless because we don't want to go modal.
EVAL(SUCCEEDED(m_ppd->StartProgressDialog(NULL, NULL, PROGDLG_AUTOTIME, NULL)));
}
}
}
return hr;
}
/*****************************************************************************\
FUNCTION: _DoProgressForLegacySystemsStart
DESCRIPTION:
Shell's pre-NT5 didn't do progress on the File Contents drop, so we
will do it here. Only return FAILED(hr) if IProgressDialog::HasUserCancelled().
\*****************************************************************************/
HRESULT CFtpObj::_DoProgressForLegacySystemsStart(LPCITEMIDLIST pidl, int nIndex)
{
HRESULT hr = S_OK;
if (DEBUG_LEGACY_PROGRESS || (SHELL_VERSION_NT5 > GetShellVersion()))
{
TraceMsg(TF_ALWAYS, "CFtpObj::_DoProgressForLegacySystemsStart() going to do the Legacy dialogs.");
// Do we need to initialize the list?
if (-1 == m_nStartIndex)
hr = _SetProgressDialogValues(nIndex); // Yes, so do so.
if (EVAL(m_ppd))
{
WCHAR wzTemplate[MAX_PATH];
WCHAR wzPath[MAX_PATH];
WCHAR wzStatusText[MAX_PATH];
WCHAR wzTemp[MAX_PATH];
LPITEMIDLIST pidlBase = (LPITEMIDLIST) pidl;
EVAL(SUCCEEDED(m_ppd->StartProgressDialog(NULL, NULL, PROGDLG_AUTOTIME, NULL)));
// Generate the string "Downloading <FileName>..." status string
EVAL(LoadStringW(HINST_THISDLL, IDS_DOWNLOADING, wzTemplate, ARRAYSIZE(wzTemplate)));
FtpPidl_GetLastItemDisplayName(pidl, wzTemp, ARRAYSIZE(wzTemp));
wnsprintfW(wzStatusText, ARRAYSIZE(wzStatusText), wzTemplate, wzTemp);
EVAL(SUCCEEDED(m_ppd->SetLine(1, wzStatusText, FALSE, NULL)));
if (FtpPidl_IsDirectory(pidl, FALSE))
{
pidlBase = ILClone(pidl);
ILRemoveLastID(pidlBase);
}
// Generate the string "From <SrcFileDir>" status string
GetDisplayPathFromPidl(pidlBase, wzPath, ARRAYSIZE(wzPath), TRUE);
EVAL(LoadStringW(HINST_THISDLL, IDS_DL_SRC_DIR, wzTemplate, ARRAYSIZE(wzTemplate)));
wnsprintfW(wzStatusText, ARRAYSIZE(wzStatusText), wzTemplate, wzPath);
EVAL(SUCCEEDED(m_ppd->SetLine(2, wzStatusText, FALSE, NULL)));
EVAL(SUCCEEDED(m_ppd->SetProgress64(m_uliCompleted.QuadPart, m_uliTotal.QuadPart)));
TraceMsg(TF_ALWAYS, "CFtpObj::_DoProgressForLegacySystemsStart() SetProgress64(%#08lx, %#08lx)", m_uliCompleted.LowPart, m_uliTotal.LowPart);
if (m_ppd->HasUserCancelled())
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
if (pidlBase != pidl) // Did we allocated it?
ILFree(pidlBase);
}
}
return hr;
}
/*****************************************************************************\
FUNCTION: _DoProgressForLegacySystemsPost
DESCRIPTION:
Shell's pre-NT5 didn't do progress on the File Contents drop, so we
will do it here. Only return FAILED(hr) if IProgressDialog::HasUserCancelled().
\*****************************************************************************/
HRESULT CFtpObj::_DoProgressForLegacySystemsPost(LPCITEMIDLIST pidl, BOOL fLast)
{
HRESULT hr = S_OK;
if ((DEBUG_LEGACY_PROGRESS || (SHELL_VERSION_NT5 > GetShellVersion())) && EVAL(m_ppd))
{
if (pidl)
{
// Add the file size to the Completed.
m_uliCompleted.QuadPart += FtpPidl_GetFileSize(pidl);
}
TraceMsg(TF_ALWAYS, "CFtpObj::_DoProgressForLegacySystemsPost() Closing DLG");
if (fLast)
IUnknown_Set((IUnknown **)&m_ppd, NULL); // The stream will close the dialog and release it.
}
return hr;
}
HRESULT CFtpObj::_SetProgressDialogValues(int nIndex)
{
HRESULT hr = S_OK;
m_nStartIndex = nIndex;
if (EVAL(m_ppd))
{
// Calculate m_nEndIndex
while (nIndex < m_pflHfpl->GetCount())
{
if (!FtpPidl_IsDirectory(m_pflHfpl->GetPidl(nIndex), FALSE))
m_nEndIndex = nIndex;
nIndex++;
}
for (nIndex = 0; nIndex < m_pflHfpl->GetCount(); nIndex++)
{
LPCITEMIDLIST pidl = m_pflHfpl->GetPidl(nIndex);
m_uliTotal.QuadPart += FtpPidl_GetFileSize(pidl);
}
// Reset because the above for loop can take a long time and the estimated time
// is based on the time between ::StartProgressDialog() and the first
// ::SetProgress() call.
EVAL(SUCCEEDED(m_ppd->Timer(PDTIMER_RESET, NULL)));
}
return hr;
}
HRESULT CFtpObj::_CloseProgressDialog(void)
{
m_nStartIndex = -1; // Indicate we haven't inited yet.
if (m_ppd)
{
EVAL(SUCCEEDED(m_ppd->StopProgressDialog()));
IUnknown_Set((IUnknown **)&m_ppd, NULL);
}
return S_OK;
}
HRESULT CFtpObj::_RefThread(void)
{
if (NULL == m_punkThreadRef)
{
// This is valid to fail from some hosts who won't go away,
// so they don't need to support ref counting threads.
SHGetThreadRef(&m_punkThreadRef);
}
return S_OK;
}
HRESULT CFtpObj::_RenderFileContents(LPFORMATETC pfe, LPSTGMEDIUM pstg)
{
HRESULT hr = E_INVALIDARG;
// callers have a bad habit of asking for lindex == -1 because
// that means 'all' data. But how can you hand out one IStream* for
// all files?
if (-1 != pfe->lindex)
{
LPITEMIDLIST pidl = m_pflHfpl->GetPidl(pfe->lindex);
// FileContents are always regenerated afresh.
pstg->pUnkForRelease = 0;
pstg->tymed = TYMED_ISTREAM;
if (pidl)
{
hr = _DoProgressForLegacySystemsStart(pidl, pfe->lindex);
if (SUCCEEDED(hr))
{
// Is it a directory?
if (FtpPidl_IsDirectory(pidl, FALSE))
{
// Yes, so pack the name and attributes
hr = DV_E_LINDEX;
AssertMsg(0, TEXT("Someone is asking for a FILECONTENTs for a directory item."));
}
else
{
// No, so give them the stream.
// shell32 v5 will display progress dialogs, but we need to
// display progress dialogs for shell32 v3 or v4. We do this
// by creating the progress dialog when the caller asks for the
// first stream. We then need to find out when they call for
// the last stream and then hand off the IProgressDialog to the
// CFtpStm. The CFtpStm will then close down the dialog when the
// caller closes it.
hr = CFtpStm_Create(m_pfd, pidl, GENERIC_READ, &pstg->pstm, m_uliCompleted, m_uliTotal, m_ppd, (pfe->lindex == m_nEndIndex));
EVAL(SUCCEEDED(_DoProgressForLegacySystemsPost(pidl, (pfe->lindex == m_nEndIndex))));
}
}
else
{
// The user may have cancelled
ASSERT(HRESULT_FROM_WIN32(ERROR_CANCELLED) == hr);
}
}
if (FAILED(hr))
_CloseProgressDialog();
}
//TraceMsg(TF_FTPDRAGDROP, "CFtpObj::GetData() CFtpStm_Create() returned hr=%#08lx", hr);
return hr;
}
/*****************************************************************************\
FUNCTION: IsEqualFORMATETC
DESCRIPTION:
The two fields of a FORMATETC that need to match to be equivalent are:
cfFormat and lindex.
\*****************************************************************************/
BOOL IsEqualFORMATETC(FORMATETC * pfe1, FORMATETC * pfe2)
{
BOOL fIsEqual = FALSE;
if ((pfe1->cfFormat == pfe2->cfFormat) && (pfe1->lindex == pfe2->lindex))
{
fIsEqual = TRUE;
}
return fIsEqual;
}
/*****************************************************************************\
FUNCTION: _FreeExtraData
DESCRIPTION:
\*****************************************************************************/
int CFtpObj::_DSA_FreeCB(LPVOID pvItem, LPVOID pvlparam)
{
FORMATETC_STGMEDIUM * pfs = (FORMATETC_STGMEDIUM *) pvItem;
if (EVAL(pfs))
ReleaseStgMedium(&(pfs->medium));
return 1;
}
/*****************************************************************************\
FUNCTION: _FindSetDataIndex
DESCRIPTION:
\*****************************************************************************/
int CFtpObj::_FindExtraDataIndex(FORMATETC *pfe)
{
int nIndex;
for (nIndex = (DSA_GetItemCount(m_hdsaSetData) - 1); nIndex >= 0; nIndex--)
{
FORMATETC_STGMEDIUM * pfs = (FORMATETC_STGMEDIUM *) DSA_GetItemPtr(m_hdsaSetData, nIndex);
if (IsEqualFORMATETC(pfe, &pfs->formatEtc))
{
return nIndex;
}
}
return -1;
}
/*****************************************************************************\
FUNCTION: _SetExtraData
DESCRIPTION:
We don't render the data, but we will carry it because someone may need
or want it. This is the case with the drag source's defview pushing in
the icon points via CFSTR_SHELLIDLISTOFFSET for the drop target.
\*****************************************************************************/
HRESULT CFtpObj::_SetExtraData(FORMATETC *pfe, STGMEDIUM *pstg, BOOL fRelease)
{
HRESULT hr;
int nIndex = _FindExtraDataIndex(pfe);
// Do we already have someone's copy?
if (-1 == nIndex)
{
FORMATETC_STGMEDIUM fs;
fs.formatEtc = *pfe;
// If there is a pointer, copy the data because we can't maintain the lifetime
// of the pointer.
if (fs.formatEtc.ptd)
{
fs.dvTargetDevice = *(pfe->ptd);
fs.formatEtc.ptd = &fs.dvTargetDevice;
}
hr = CopyStgMediumWrap(pstg, &fs.medium);
if (EVAL(SUCCEEDED(hr)))
{
// No, so just append it to the end.
DSA_AppendItem(m_hdsaSetData, &fs);
}
}
else
{
FORMATETC_STGMEDIUM fs;
DSA_GetItem(m_hdsaSetData, nIndex, &fs);
// Free the previous guy.
ReleaseStgMedium(&fs.medium);
// Yes, so Replace it.
hr = CopyStgMediumWrap(pstg, &fs.medium);
if (EVAL(SUCCEEDED(hr)))
{
// Replace the data.
DSA_SetItem(m_hdsaSetData, nIndex, &fs);
}
}
return hr;
}
typedef struct
{
DWORD dwVersion;
DWORD dwExtraSize; // After pidl list
BOOL fFGDRendered;
DWORD dwReserved1;
DWORD dwReserved2;
} FTPDATAOBJ_PERSISTSTRUCT;
/*****************************************************************************\
FUNCTION: FormatEtcSaveToStream
DESCRIPTION:
\*****************************************************************************/
HRESULT FormatEtcSaveToStream(IStream *pStm, FORMATETC * pFormatEtc)
{
HRESULT hr = E_INVALIDARG;
if (pStm)
{
// We don't support ptd because where would the allocation be
// on the load?
if (EVAL(NULL == pFormatEtc->ptd))
{
WCHAR szFormatName[MAX_PATH];
if (EVAL(GetClipboardFormatNameW(pFormatEtc->cfFormat, szFormatName, ARRAYSIZE(szFormatName))))
{
DWORD cbFormatNameSize = ((lstrlenW(szFormatName) + 1) * sizeof(szFormatName[0]));
hr = pStm->Write(pFormatEtc, SIZEOF(*pFormatEtc), NULL);
if (SUCCEEDED(hr))
{
hr = pStm->Write(&cbFormatNameSize, SIZEOF(cbFormatNameSize), NULL);
if (SUCCEEDED(hr))
{
hr = pStm->Write(szFormatName, cbFormatNameSize, NULL);
}
}
}
else
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
return hr;
}
/*****************************************************************************\
FUNCTION: FormatEtcLoadFromStream
DESCRIPTION:
\*****************************************************************************/
HRESULT FormatEtcLoadFromStream(IStream *pStm, FORMATETC * pFormatEtc)
{
HRESULT hr = E_INVALIDARG;
if (pStm)
{
hr = pStm->Read(pFormatEtc, SIZEOF(*pFormatEtc), NULL);
ASSERT(NULL == pFormatEtc->ptd); // We don't support this.
if (SUCCEEDED(hr))
{
DWORD cbFormatNameSize;
hr = pStm->Read(&cbFormatNameSize, SIZEOF(cbFormatNameSize), NULL);
if (SUCCEEDED(hr))
{
WCHAR szFormatName[MAX_PATH];
hr = pStm->Read(szFormatName, cbFormatNameSize, NULL);
if (SUCCEEDED(hr))
{
pFormatEtc->cfFormat = (CLIPFORMAT)RegisterClipboardFormatW(szFormatName);
}
}
}
else
hr = HRESULT_FROM_WIN32(GetLastError());
}
return hr;
}
typedef struct
{
DWORD dwVersion;
DWORD dwExtraSize; // After this struct
DWORD dwTymed; // What type of data is stored?
BOOL fUnkForRelease; // Did we save the object after this?
DWORD dwReserved1; //
DWORD dwReserved2; //
} STGMEDIUM_PERSISTSTRUCT;
/*****************************************************************************\
FUNCTION: StgMediumSaveToStream
DESCRIPTION:
\*****************************************************************************/
HRESULT StgMediumSaveToStream(IStream *pStm, STGMEDIUM * pMedium)
{
HRESULT hr = E_INVALIDARG;
if (pStm)
{
STGMEDIUM_PERSISTSTRUCT smps = {0};
smps.dwVersion = 1;
smps.dwTymed = pMedium->tymed;
switch (pMedium->tymed)
{
case TYMED_HGLOBAL:
{
IStream * pstmHGlobal;
hr = CreateStreamOnHGlobal(pMedium->hGlobal, FALSE, &pstmHGlobal);
if (SUCCEEDED(hr))
{
STATSTG statStg;
hr = pstmHGlobal->Stat(&statStg, STATFLAG_NONAME);
if (SUCCEEDED(hr))
{
ASSERT(!statStg.cbSize.HighPart);
smps.dwExtraSize = statStg.cbSize.LowPart;
hr = pStm->Write(&smps, SIZEOF(smps), NULL);
if (SUCCEEDED(hr))
hr = pstmHGlobal->CopyTo(pStm, statStg.cbSize, NULL, NULL);
}
pstmHGlobal->Release();
}
}
break;
case TYMED_FILE:
smps.dwExtraSize = ((lstrlenW(pMedium->lpszFileName) + 1) * sizeof(WCHAR));
hr = pStm->Write(&smps, SIZEOF(smps), NULL);
if (SUCCEEDED(hr))
{
hr = pStm->Write(pMedium->lpszFileName, smps.dwExtraSize, NULL);
ASSERT(SUCCEEDED(hr));
}
break;
case TYMED_GDI:
case TYMED_MFPICT:
case TYMED_ENHMF:
case TYMED_ISTORAGE:
case TYMED_ISTREAM:
default:
ASSERT(0); // What are you doing? Impl this if you need it.
hr = E_NOTIMPL;
break;
}
}
return hr;
}
LPWSTR OLESTRAlloc(DWORD cchSize)
{
return (LPWSTR) new WCHAR [cchSize + 1];
}
/*****************************************************************************\
FUNCTION: StgMediumLoadFromStream
DESCRIPTION:
\*****************************************************************************/
HRESULT StgMediumLoadFromStream(IStream *pStm, STGMEDIUM * pMedium)
{
HRESULT hr = E_INVALIDARG;
if (pStm && pMedium)
{
STGMEDIUM_PERSISTSTRUCT smps;
pMedium->pUnkForRelease = NULL;
hr = pStm->Read(&smps, SIZEOF(smps), NULL);
if (SUCCEEDED(hr))
{
pMedium->tymed = smps.dwTymed;
ASSERT(!pMedium->pUnkForRelease);
switch (pMedium->tymed)
{
case TYMED_HGLOBAL:
{
IStream * pstmTemp;
hr = CreateStreamOnHGlobal(NULL, FALSE, &pstmTemp);
if (SUCCEEDED(hr))
{
ULARGE_INTEGER uli = {0};
uli.LowPart = smps.dwExtraSize;
hr = pStm->CopyTo(pstmTemp, uli, NULL, NULL);
if (SUCCEEDED(hr))
{
hr = GetHGlobalFromStream(pstmTemp, &pMedium->hGlobal);
}
pstmTemp->Release();
}
}
break;
case TYMED_FILE:
pMedium->lpszFileName = OLESTRAlloc(smps.dwExtraSize / sizeof(WCHAR));
if (pMedium->lpszFileName)
hr = pStm->Read(pMedium->lpszFileName, smps.dwExtraSize, NULL);
else
hr = E_OUTOFMEMORY;
break;
case TYMED_GDI:
case TYMED_MFPICT:
case TYMED_ENHMF:
case TYMED_ISTORAGE:
case TYMED_ISTREAM:
default:
ASSERT(0); // What are you doing? Impl this if you need it.
// Some future version must have done the save, so skip the
// data so we don't leave unread data.
if (0 != smps.dwExtraSize)
{
LARGE_INTEGER li = {0};
li.LowPart = smps.dwExtraSize;
EVAL(SUCCEEDED(pStm->Seek(li, STREAM_SEEK_CUR, NULL)));
}
hr = E_NOTIMPL;
break;
}
}
}
return hr;
}
/*****************************************************************************\
FUNCTION: FORMATETC_STGMEDIUMSaveToStream
DESCRIPTION:
\*****************************************************************************/
HRESULT FORMATETC_STGMEDIUMSaveToStream(IStream *pStm, FORMATETC_STGMEDIUM * pfdops)
{
HRESULT hr = E_INVALIDARG;
if (pStm)
{
hr = FormatEtcSaveToStream(pStm, &pfdops->formatEtc);
if (SUCCEEDED(hr))
hr = StgMediumSaveToStream(pStm, &pfdops->medium);
}
return hr;
}
/*****************************************************************************\
FUNCTION: FORMATETC_STGMEDIUMLoadFromStream
DESCRIPTION:
\*****************************************************************************/
HRESULT FORMATETC_STGMEDIUMLoadFromStream(IStream *pStm, FORMATETC_STGMEDIUM * pfdops)
{
HRESULT hr = E_INVALIDARG;
if (pStm)
{
hr = FormatEtcLoadFromStream(pStm, &pfdops->formatEtc);
if (SUCCEEDED(hr))
hr = StgMediumLoadFromStream(pStm, &pfdops->medium);
}
return hr;
}
/////////////////////////////////
////// IAsynchDataObject Impl
/////////////////////////////////
/*****************************************************************************\
FUNCTION: IAsyncOperation::GetAsyncMode
DESCRIPTION:
\*****************************************************************************/
HRESULT CFtpObj::GetAsyncMode(BOOL * pfIsOpAsync)
{
*pfIsOpAsync = TRUE;
return S_OK;
}
/*****************************************************************************\
FUNCTION: IAsyncOperation::StartOperation
DESCRIPTION:
\*****************************************************************************/
HRESULT CFtpObj::StartOperation(IBindCtx * pbcReserved)
{
ASSERT(!pbcReserved);
m_fDidAsynchStart = TRUE;
return S_OK;
}
/*****************************************************************************\
FUNCTION: IAsyncOperation::InOperation
DESCRIPTION:
\*****************************************************************************/
HRESULT CFtpObj::InOperation(BOOL * pfInAsyncOp)
{
if (m_fDidAsynchStart)
*pfInAsyncOp = TRUE;
else
*pfInAsyncOp = FALSE;
return S_OK;
}
/*****************************************************************************\
FUNCTION: IAsyncOperation::EndOperation
DESCRIPTION:
\*****************************************************************************/
HRESULT CFtpObj::EndOperation(HRESULT hResult, IBindCtx * pbcReserved, DWORD dwEffects)
{
if (SUCCEEDED(hResult) &&
(DROPEFFECT_MOVE == dwEffects))
{
CFtpPidlList * pPidlListNew = CreateRelativePidlList(m_pff, m_pflHfpl);
if (pPidlListNew)
{
Misc_DeleteHfpl(m_pff, GetDesktopWindow(), pPidlListNew);
pPidlListNew->Release();
}
}
m_fDidAsynchStart = FALSE;
return S_OK;
}
/////////////////////////////////
////// IPersistStream Impl
/////////////////////////////////
/*****************************************************************************\
FUNCTION: IPersistStream::Load
DESCRIPTION:
See IPersistStream::Save() for the layout of the stream.
\*****************************************************************************/
HRESULT CFtpObj::Load(IStream *pStm)
{
HRESULT hr = E_INVALIDARG;
if (pStm)
{
FTPDATAOBJ_PERSISTSTRUCT fdoss;
DWORD dwNumPidls;
DWORD dwNumStgMedium;
hr = pStm->Read(&fdoss, SIZEOF(fdoss), NULL); // #1
// If we rev the version, read it now (fdoss.dwVersion)
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidl = NULL; // ILLoadFromStream frees the param
ASSERT(!m_pff);
m_fFGDRendered = fdoss.fFGDRendered;
hr = ILLoadFromStream(pStm, &pidl); // #2
if (SUCCEEDED(hr))
{
hr = SHBindToIDList(pidl, NULL, IID_CFtpFolder, (void **)&m_pff);
if (SUCCEEDED(hr))
m_pfd = m_pff->GetFtpDir();
ASSERT(m_pfd);
ILFree(pidl);
}
}
if (SUCCEEDED(hr))
{
hr = pStm->Read(&dwNumPidls, SIZEOF(dwNumPidls), NULL); // #3
if (SUCCEEDED(hr))
hr = CFtpPidlList_Create(0, NULL, &m_pflHfpl);
}
if (SUCCEEDED(hr))
{
for (int nIndex = 0; (nIndex < (int)dwNumPidls) && SUCCEEDED(hr); nIndex++)
{
LPITEMIDLIST pidl = NULL; // ILLoadFromStream frees the param
hr = ILLoadFromStream(pStm, &pidl); // #4
if (SUCCEEDED(hr))
{
hr = m_pflHfpl->InsertSorted(pidl);
ILFree(pidl);
}
}
}
if (SUCCEEDED(hr))
hr = pStm->Read(&dwNumStgMedium, SIZEOF(dwNumStgMedium), NULL); // #5
if (SUCCEEDED(hr))
{
for (int nIndex = 0; (nIndex < (int)dwNumStgMedium) && SUCCEEDED(hr); nIndex++)
{
FORMATETC_STGMEDIUM fs;
hr = FORMATETC_STGMEDIUMLoadFromStream(pStm, &fs); // #6
if (SUCCEEDED(hr))
DSA_AppendItem(m_hdsaSetData, &fs);
}
}
if (SUCCEEDED(hr))
{
// We may be reading a version newer than us, so skip their data.
if (0 != fdoss.dwExtraSize)
{
LARGE_INTEGER li = {0};
li.LowPart = fdoss.dwExtraSize;
hr = pStm->Seek(li, STREAM_SEEK_CUR, NULL);
}
}
}
return hr;
}
/*****************************************************************************\
FUNCTION: IPersistStream::Save
DESCRIPTION:
The stream will be layed out in the following way:
Version 1:
1. FTPDATAOBJ_PERSISTSTRUCT - Constant sized data.
<PidlList BEGIN>
2. PIDL pidl - Pidl for m_pff. It will be a public pidl (fully qualified
from the shell root)
3. DWORD dwNumPidls - Number of pidls coming.
4. PIDL pidl(n) - Pidl in slot (n) of m_pflHfpl
<PidlList END>
5. DWORD dwNumStgMedium - Number of FORMATETC_STGMEDIUMs coming
6. FORMATETC_STGMEDIUM fmtstg(n) - dwNumStgMedium FORMATETC_STGMEDIUMs.
\*****************************************************************************/
HRESULT CFtpObj::Save(IStream *pStm, BOOL fClearDirty)
{
HRESULT hr = E_INVALIDARG;
if (pStm)
{
FTPDATAOBJ_PERSISTSTRUCT fdoss = {0};
DWORD dwNumPidls = m_pflHfpl->GetCount();
DWORD dwNumStgMedium = DSA_GetItemCount(m_hdsaSetData);
fdoss.dwVersion = 1;
fdoss.fFGDRendered = m_fFGDRendered;
hr = pStm->Write(&fdoss, SIZEOF(fdoss), NULL); // #1
if (SUCCEEDED(hr))
{
ASSERT(m_pff);
hr = ILSaveToStream(pStm, m_pff->GetPublicRootPidlReference()); // #2
}
if (SUCCEEDED(hr))
hr = pStm->Write(&dwNumPidls, SIZEOF(dwNumPidls), NULL); // #3
if (SUCCEEDED(hr))
{
for (int nIndex = 0; (nIndex < (int)dwNumPidls) && SUCCEEDED(hr); nIndex++)
{
LPITEMIDLIST pidlCur = m_pflHfpl->GetPidl(nIndex);
ASSERT(pidlCur);
hr = ILSaveToStream(pStm, pidlCur); // #4
}
}
if (SUCCEEDED(hr))
hr = pStm->Write(&dwNumStgMedium, SIZEOF(dwNumStgMedium), NULL); // #5
if (SUCCEEDED(hr))
{
for (int nIndex = 0; (nIndex < (int)dwNumStgMedium) && SUCCEEDED(hr); nIndex++)
{
FORMATETC_STGMEDIUM fs;
DSA_GetItem(m_hdsaSetData, nIndex, &fs);
hr = FORMATETC_STGMEDIUMSaveToStream(pStm, &fs); // #6
}
}
}
return hr;
}
#define MAX_STREAM_SIZE (500 * 1024) // 500k
/*****************************************************************************\
FUNCTION: IPersistStream::GetSizeMax
DESCRIPTION:
Now this is tough. I can't calculate the real value because I don't know
how big the hglobals are going to be for the user provided data. I will
assume everything fits in
\*****************************************************************************/
HRESULT CFtpObj::GetSizeMax(ULARGE_INTEGER * pcbSize)
{
if (pcbSize)
{
pcbSize->HighPart = 0;
pcbSize->LowPart = MAX_STREAM_SIZE;
}
return E_NOTIMPL;
}
/////////////////////////////////
////// IDataObject Impl
/////////////////////////////////
/*****************************************************************************\
FUNCTION: IDataObject::GetData
DESCRIPTION:
Render the data in the requested format and put it into the
STGMEDIUM structure.
\*****************************************************************************/
HRESULT CFtpObj::GetData(LPFORMATETC pfe, LPSTGMEDIUM pstg)
{
int ife;
HRESULT hr;
hr = _FindDataForGet(pfe, &ife);
if (SUCCEEDED(hr))
{
if (ife == DROP_FCont)
hr = _RenderFileContents(pfe, pstg);
else
{
hr = _ForceRender(ife);
if (SUCCEEDED(hr)) // May not succeed for security reasons.
{
ASSERT(m_stgCache[ife].hGlobal);
// It's possible to use the hacking STGMEDIUM.pUnkForRelease to give away
// pointers to our data, but we then need massive amounts of code to babysite
// the lifetime of those pointers. This becomes more work when ::SetData() can
// replace that data, so we just take the hit of the memcpy for less code.
hr = CopyStgMediumWrap(&m_stgCache[ife], pstg);
ASSERT(SUCCEEDED(hr));
ASSERT(NULL == pstg->pUnkForRelease);
//TraceMsg(TF_FTPDRAGDROP, "CFtpObj::GetData() pstg->hGlobal=%#08lx. pstg->pUnkForRelease=%#08lx.", pstg->hGlobal, pstg->pUnkForRelease);
}
}
TraceMsgWithFormat(TF_FTPDRAGDROP, "CFtpObj::GetData()", pfe, "Format in static list", hr);
}
else
{
int nIndex = _FindExtraDataIndex(pfe);
if (-1 == nIndex)
hr = E_FAIL;
else
{
FORMATETC_STGMEDIUM fs;
DSA_GetItem(m_hdsaSetData, nIndex, &fs);
hr = CopyStgMediumWrap(&fs.medium, pstg);
}
TraceMsgWithFormat(TF_FTPDRAGDROP, "CFtpObj::GetData()", pfe, "Looking in dyn list", hr);
}
return hr;
}
/*****************************************************************************\
IDataObject::GetDataHere
Render the data in the requested format and put it into the
object provided by the caller.
\*****************************************************************************/
HRESULT CFtpObj::GetDataHere(FORMATETC *pfe, STGMEDIUM *pstg)
{
TraceMsg(TF_FTPDRAGDROP, "CFtpObj::GetDataHere() pfe->cfFormat=%d.", pfe->cfFormat);
return E_NOTIMPL;
}
/*****************************************************************************\
FUNCTION: IDataObject::QueryGetData
DESCRIPTION:
Indicate whether we could provide data in the requested format.
\*****************************************************************************/
HRESULT CFtpObj::QueryGetData(FORMATETC *pfe)
{
int ife;
HRESULT hr = _FindDataForGet(pfe, &ife);
if (FAILED(hr))
{
// If it wasn't one of the types we offer, see if it was given to us via
// IDataObject::SetData().
int nIndex = _FindExtraDataIndex(pfe);
if (-1 != nIndex)
hr = S_OK;
}
TraceMsgWithFormat(TF_FTPDRAGDROP, "CFtpObj::QueryGetData()", pfe, "", hr);
return hr;
}
/*****************************************************************************\
FUNCTION: IDataObject::GetCanonicalFormatEtc
DESCRIPTION:
Our data are not sensitive to device-specific renderings,
so we do what the book tells us to do.
Or we *try* to do what the book tells us to do.
OLE random documentation of the day:
IDataObject::GetCanonicalFormatEtc.
Turns out that the man page contradicts itself within sentences:
DATA_S_SAMEFORMATETC - The FORMATETC structures are the same
and NULL is returned in pfeOut.
If the data object never provides device-specific renderings,
the implementation of IDataObject::GetCanonicalFormatEtc
simply copies the input FORMATETC to the output FORMATETC,
stores a null in the ptd field, and returns DATA_S_SAMEFORMATETC.
And it turns out that the shell doesn't do *either* of these things.
It just returns DATA_S_SAMEFORMATETC and doesn't touch pfeOut.
The book is even more confused. Under pfeOut, it says
The value is NULL if the method returns DATA_S_SAMEFORMATETC.
This makes no sense. The caller provides the value of pfeOut.
How can the caller possibly know that the method is going to return
DATA_S_SAMEFORMATETC before it calls it? If you expect the
method to write "pfeOut = 0" before returning, you're nuts. That
communicates nothing to the caller.
I'll just do what the shell does.
\*****************************************************************************/
HRESULT CFtpObj::GetCanonicalFormatEtc(FORMATETC *pfeIn, FORMATETC *pfeOut)
{
return DATA_S_SAMEFORMATETC;
}
/*****************************************************************************\
FUNCTION: IDataObject::SetData
DESCRIPTION:
We let people change TYMED_HGLOBAL gizmos, but nothing else.
We need to do a careful two-step when replacing the HGLOBAL.
If the user gave us a plain HGLOBAL without a pUnkForRelease,
we need to invent our own pUnkForRelease to track it. But we
don't want to release the old STGMEDIUM until we're sure we
can accept the new one.
fRelease == 0 makes life doubly interesting, because we also
have to clone the HGLOBAL (and remember to free the clone on the
error path).
_SOMEDAY_/TODO -- Need to support PerformedDropEffect so we can
clean up stuff on a cut/paste.
\*****************************************************************************/
HRESULT CFtpObj::SetData(FORMATETC *pfe, STGMEDIUM *pstg, BOOL fRelease)
{
int ife;
HRESULT hr;
hr = _FindData(pfe, &ife);
if (SUCCEEDED(hr))
{
if (ife == DROP_FCont)
{
TraceMsg(TF_FTPDRAGDROP, "CFtpObj::SetData(FORMATETC.cfFormat=%d) ife == DROP_FCont", pfe->cfFormat);
hr = DV_E_FORMATETC;
}
else
{
ASSERT(g_dropTypes[ife].tymed == TYMED_HGLOBAL);
ASSERT(pstg->tymed == TYMED_HGLOBAL);
if (pstg->hGlobal)
{
STGMEDIUM stg = {0};
hr = CopyStgMediumWrap(pstg, &stg);
if (SUCCEEDED(hr))
{
ReleaseStgMedium(&m_stgCache[ife]);
m_stgCache[ife] = stg;
}
}
else
{ // Tried to SetData a _DelayRender
hr = DV_E_STGMEDIUM;
}
}
TraceMsgWithFormat(TF_FTPDRAGDROP, "CFtpObj::SetData()", pfe, "in static list", hr);
}
else
{
hr = _SetExtraData(pfe, pstg, fRelease);
TraceMsgWithFormat(TF_FTPDRAGDROP, "CFtpObj::SetData()", pfe, "in dyn list", hr);
}
return hr;
}
/*****************************************************************************\
FUNCTION: IDataObject::EnumFormatEtc
DESCRIPTION:
_UNDOCUMENTED_: If you drag something from a DefView, it will
check the data object to see if it has a hida. If so, then it
will cook up a CFSTR_SHELLIDLISTOFFSET *for you* and SetData
the information into the data object. So in order to get
position-aware drag/drop working, you must allow DefView to change
your CFSTR_SHELLIDLISTOFFSET.
We allow all FORMATETCs to be modified except for FileContents.
\*****************************************************************************/
HRESULT CFtpObj::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppenum)
{
HRESULT hres;
switch (dwDirection)
{
case DATADIR_GET:
hres = CFtpEfe_Create(DROP_OFFERMAX - DROP_FCont, &g_dropTypes[DROP_FCont],
&m_stgCache[DROP_FCont], this, ppenum);
TraceMsg(TF_FTPDRAGDROP, "CFtpObj::EnumFormatEtc(DATADIR_GET) CFtpEfe_Create() returned hres=%#08lx", hres);
break;
case DATADIR_SET:
hres = CFtpEfe_Create(DROP_OFFERMAX - DROP_OFFERMIN, &g_dropTypes[DROP_OFFERMIN],
&m_stgCache[DROP_OFFERMIN], NULL, ppenum);
TraceMsg(TF_FTPDRAGDROP, "CFtpObj::EnumFormatEtc(DATADIR_SET) CFtpEfe_Create() returned hres=%#08lx", hres);
break;
default:
ASSERT(0);
hres = E_NOTIMPL;
break;
}
return hres;
}
/*****************************************************************************\
FUNCTION: IDataObject::DAdvise
DESCRIPTION:
\*****************************************************************************/
HRESULT CFtpObj::DAdvise(FORMATETC *pfe, DWORD advfl, IAdviseSink *padv, DWORD *pdwConnection)
{
return OLE_E_ADVISENOTSUPPORTED;
}
/*****************************************************************************\
FUNCTION: IDataObject::DUnadvise
DESCRIPTION:
\*****************************************************************************/
HRESULT CFtpObj::DUnadvise(DWORD dwConnection)
{
return OLE_E_ADVISENOTSUPPORTED;
}
/*****************************************************************************\
FUNCTION: IDataObject::EnumDAdvise
DESCRIPTION:
\*****************************************************************************/
HRESULT CFtpObj::EnumDAdvise(IEnumSTATDATA **ppeadv)
{
return OLE_E_ADVISENOTSUPPORTED;
}
/*****************************************************************************\
FUNCTION: CFtpObj_Create
DESCRIPTION:
\*****************************************************************************/
HRESULT CFtpObj_Create(CFtpFolder * pff, CFtpPidlList * pflHfpl, REFIID riid, LPVOID * ppvObj)
{
HRESULT hres;
CFtpObj * pfo;
*ppvObj = NULL;
hres = CFtpObj_Create(pff, pflHfpl, &pfo);
if (SUCCEEDED(hres))
{
pfo->QueryInterface(riid, ppvObj);
pfo->Release();
}
return hres;
}
/*****************************************************************************\
FUNCTION: CFtpObj_Create
DESCRIPTION:
\*****************************************************************************/
HRESULT CFtpObj_Create(CFtpFolder * pff, CFtpPidlList * pflHfpl, CFtpObj ** ppfo)
{
HRESULT hres = S_OK;
if (EVAL(pflHfpl->GetCount()))
{
*ppfo = new CFtpObj();
if (*ppfo)
{
CFtpObj * pfo = *ppfo;
pfo->m_pfd = pff->GetFtpDir();
if (pfo->m_pfd)
{
pfo->m_pff = pff;
if (pff)
pff->AddRef();
IUnknown_Set(&pfo->m_pflHfpl, pflHfpl);
if (pfo->m_pflHfpl->GetCount() == 1)
{
pfo->m_stgCache[DROP_URL].tymed = TYMED_HGLOBAL;
}
}
else
{
hres = E_FAIL;
(*ppfo)->Release();
*ppfo = NULL;
}
}
else
hres = E_OUTOFMEMORY;
}
else
{
*ppfo = NULL;
hres = E_INVALIDARG; /* Trying to get UI object of nil? */
}
return hres;
}
/*****************************************************************************\
FUNCTION: CFtpObj_Create
DESCRIPTION:
This will be called by the Class Factory when the IDataObject gets
persisted and then wants to be recreated in a new process. (Happens
after the original thread/process calls OleFlushClipboard.
\*****************************************************************************/
HRESULT CFtpObj_Create(REFIID riid, void ** ppvObj)
{
HRESULT hr = E_OUTOFMEMORY;
CFtpObj * pfo = new CFtpObj();
*ppvObj = NULL;
if (pfo)
{
hr = pfo->QueryInterface(riid, ppvObj);
pfo->Release();
}
return hr;
}
#define SETDATA_GROWSIZE 3
/****************************************************\
Constructor
\****************************************************/
CFtpObj::CFtpObj() : m_cRef(1)
{
DllAddRef();
// This needs to be allocated in Zero Inited Memory.
// Assert that all Member Variables are inited to Zero.
ASSERT(!m_pff);
ASSERT(!m_pfd);
ASSERT(!m_pflHfpl);
ASSERT(!m_fDidAsynchStart);
// NT #245306: If the user drags files from an FTP window (Thread 1)
// to a shell window (Thread 2), the shell window will do
// the drop on a background thread (thread 3). Since the
// UI thread is no longer blocked, the user can now close
// the window. The problem is that OLE is using Thread 2
// for marshalling. In order to solve this problem, we
// ref count the thread for items that rely on it.
// This include FTP, normal Download, and other things
// in the future.
// SHIncrementThreadModelessCount();
m_nStartIndex = -1; // -1 means we don't know the start.
m_fFGDRendered = FALSE;
m_fCheckSecurity = TRUE; // We need to keep checking.
m_hdsaSetData = DSA_Create(sizeof(FORMATETC_STGMEDIUM), SETDATA_GROWSIZE);
for (int nIndex = 0; nIndex < ARRAYSIZE(c_stgInit); nIndex++)
{
ASSERT(nIndex < ARRAYSIZE(m_stgCache));
m_stgCache[nIndex] = c_stgInit[nIndex];
}
_RefThread();
// The receiver may use us in the background, so make sure that our thread
// doesn't go away.
LEAK_ADDREF(LEAK_CFtpObj);
}
/****************************************************\
Destructor
\****************************************************/
CFtpObj::~CFtpObj()
{
int ife;
_CloseProgressDialog();
for (ife = DROP_OFFERMIN; ife < DROP_OFFERMAX; ife++)
{
ReleaseStgMedium(&m_stgCache[ife]);
}
if (m_ppd)
m_ppd->StopProgressDialog();
IUnknown_Set((IUnknown **)&m_ppd, NULL);
IUnknown_Set(&m_pff, NULL);
IUnknown_Set(&m_pfd, NULL);
IUnknown_Set(&m_pflHfpl, NULL);
DSA_DestroyCallback(m_hdsaSetData, &_DSA_FreeCB, NULL);
// NT #245306: If the user drags files from an FTP window (Thread 1)
// to a shell window (Thread 2), the shell window will do
// the drop on a background thread (thread 3). Since the
// UI thread is no longer blocked, the user can now close
// the window. The problem is that OLE is using Thread 2
// for marshalling. In order to solve this problem, we
// ref count the thread for items that rely on it.
// This include FTP, normal Download, and other things
// in the future.
ATOMICRELEASE(m_punkThreadRef);
DllRelease();
LEAK_DELREF(LEAK_CFtpObj);
}
//===========================
// *** IUnknown Interface ***
//===========================
ULONG CFtpObj::AddRef()
{
m_cRef++;
return m_cRef;
}
ULONG CFtpObj::Release()
{
ASSERT(m_cRef > 0);
m_cRef--;
if (m_cRef > 0)
return m_cRef;
delete this;
return 0;
}
HRESULT CFtpObj::QueryInterface(REFIID riid, void **ppvObj)
{
static const QITAB qit[] = {
QITABENT(CFtpObj, IDataObject),
QITABENT(CFtpObj, IInternetSecurityMgrSite),
QITABENT(CFtpObj, IPersist),
QITABENT(CFtpObj, IPersistStream),
QITABENT(CFtpObj, IAsyncOperation),
{ 0 },
};
return QISearch(this, qit, riid, ppvObj);
}