ftpdrop.cpp
Upload User: xhy777
Upload Date: 2007-02-14
Package Size: 24088k
Code Size: 81k
Category:

Windows Kernel

Development Platform:

Visual C++

  1. /*****************************************************************************
  2.     FILE: ftpdrop.cpp - IDropTarget interface
  3.     Remarks:
  4.     Note that you cannot create a shortcut on an FTP site.  Although
  5.     there's nothing technically preventing it, it's not done because
  6.     the shortcut won't be of much use on an FTP site.  (It points to
  7.     your local machine, which doesn't help much for people not on the
  8.     same network!)
  9.     If you really want to put a shortcut file on an FTP site, create
  10.     it on the desktop, then drag the shortcut onto the FTP site.
  11.     The default verb for FTP sites is always "Copy".  This is true
  12.     even if an intra-site drag-drop is being done.
  13.     DESCRIPTION:
  14.         DefView will cache the IDropTarget pointer (CFtpDrop) for a shell extension.
  15.     When it calls CFtpDrop::Drop(), the work needs to be done on a background
  16.     thread in order to not block the UI thread.  The problem is that if the user
  17.     does another drag to the same Ftp Window, CFtpDrop::Drop() will be called again.
  18.     For this reasons, CFtpDrop::Drop() cannot have any state after it returns.
  19.     In order to accomplish this with the asynch background thread, we have
  20.     CFtpDrop::Drop() call CDropOperation_Create(), and then CDropOperation->DoOperation().
  21.     And then it will orphan (call Release()) the CDropOperation.  The CDropOperation
  22.     will then destroy itself when the copy is finishes.  This enables subsequent calls
  23.     to CFtpDrop::Drop() to spawn separate CDropOperation objects so each can maintain
  24.     the state for that specifc operation and CFtpDrop remains stateless.
  25. *****************************************************************************/
  26. #include "priv.h"
  27. #include "ftpdrop.h"
  28. #include "ftpurl.h"
  29. #include "statusbr.h"
  30. #include "newmenu.h"
  31. class CDropOperation;
  32. HRESULT CDropOperation_Create(CFtpFolder * pff, HWND hwnd, LPCTSTR pszzFSSource, LPCTSTR pszzFtpDest, CDropOperation ** ppfdt, DROPEFFECT de, OPS ops, int cobj);
  33. HRESULT ConfirmCopy(LPCWSTR pszLocal, LPCWSTR pszFtpName, OPS * pOps, HWND hwnd, CFtpFolder * pff, CFtpDir * pfd, DROPEFFECT * pde, int nObjs, BOOL * pfFireChangeNotify);
  34. // Declared because of recusion
  35. HRESULT FtpCopyDirectory(HINTERNET hint, HINTPROCINFO * phpi, LPCOPYONEHDROPINFO pcohi);
  36. HRESULT FtpCopyFile(HINTERNET hint, HINTPROCINFO * phpi, LPCOPYONEHDROPINFO pcohi);
  37. HRESULT UpdateCopyFileName(LPCOPYONEHDROPINFO pcohi)
  38. {
  39.     HRESULT hr = S_OK;
  40.     static WCHAR wzCopyTemplate[MAX_PATH] = L"";
  41.     WCHAR wzLine1[MAX_PATH];
  42.     if (!wzCopyTemplate[0])
  43.         LoadStringW(HINST_THISDLL, IDS_COPYING, wzCopyTemplate, ARRAYSIZE(wzCopyTemplate));
  44.     wnsprintfW(wzLine1, ARRAYSIZE(wzLine1), wzCopyTemplate, pcohi->pszFtpDest);
  45.     EVAL(SUCCEEDED(pcohi->progInfo.ppd->SetLine(1, wzLine1, FALSE, NULL)));
  46.     return hr;
  47. }
  48. HRESULT UpdateSrcDestDirs(LPCOPYONEHDROPINFO pcohi)
  49. {
  50.     HRESULT hr = S_OK;
  51.     WCHAR wzFrom[MAX_PATH];
  52.     WCHAR wzStatusStr[MAX_PATH];
  53.     StrCpyN(wzFrom, pcohi->pszFSSource, ARRAYSIZE(wzFrom));
  54.     PathRemoveFileSpecW(wzFrom);
  55.     if (EVAL(SUCCEEDED(hr = CreateFromToStr(wzStatusStr, ARRAYSIZE(wzStatusStr), wzFrom, pcohi->pszDir))))
  56.         EVAL(SUCCEEDED(hr = pcohi->progInfo.ppd->SetLine(2, wzStatusStr, FALSE, NULL)));    // Line one is the file being copied.
  57.     return hr;
  58. }
  59. HRESULT DeleteOneFileCB(HINTERNET hint, HINTPROCINFO * phpi, LPVOID pv, BOOL * pfReleaseHint)
  60. {
  61.     LPCOPYONEHDROPINFO pcohi = (LPCOPYONEHDROPINFO) pv;
  62.     WIRECHAR wFtpPath[MAX_PATH];
  63.     phpi->pfd->GetFtpSite()->GetCWireEncoding()->UnicodeToWireBytes(pcohi->pmlc, pcohi->pszFtpDest, (phpi->pfd->IsUTF8Supported() ? WIREENC_USE_UTF8 : WIREENC_NONE), wFtpPath, ARRAYSIZE(wFtpPath));
  64.     return FtpDeleteFileWrap(hint, TRUE, wFtpPath);
  65. }
  66. HRESULT UpdateProgressDialogStr(LPCOPYONEHDROPINFO pcohi)
  67. {
  68.     EVAL(SUCCEEDED(UpdateCopyFileName(pcohi)));
  69.     EVAL(SUCCEEDED(UpdateSrcDestDirs(pcohi)));
  70.     return S_OK;
  71. }
  72. /*****************************************************************************
  73.     CopyFileSysItem
  74.     This function may cause recursion.
  75. *****************************************************************************/
  76. HRESULT CopyFileSysItem(HINTERNET hint, HINTPROCINFO * phpi, LPCOPYONEHDROPINFO pcohi)
  77. {
  78.     HRESULT hr = S_OK;
  79.     // Check if the user canceled.
  80.     if (pcohi->progInfo.ppd)
  81.     {
  82.         if (pcohi->progInfo.ppd->HasUserCancelled())
  83.             return HRESULT_FROM_WIN32(ERROR_CANCELLED);
  84.         if (pcohi->dwOperation != COHDI_FILESIZE_COUNT)
  85.             UpdateProgressDialogStr(pcohi);
  86.     }
  87.     if (PathIsDirectory(pcohi->pszFSSource))
  88.     {
  89.         hr = FtpCopyDirectory(hint, phpi, pcohi);
  90.         if (SUCCEEDED(hr) && (pcohi->dwOperation != COHDI_FILESIZE_COUNT))
  91.         {
  92.             /*
  93.             WIN32_FIND_DATA wfd;
  94.             HANDLE handle = FindFirstFile(pcohi->pszFSSource, &wfd);
  95.             // BUGBUG: The date is wrong doing it this way, but it's faster.  We should
  96.             //         find out if FtpCreateDirectory always stamps the directory with
  97.             //         the current date, and then update wfd with the current time/date.
  98.             //         This will simulate the server entry w/o the perf hit.
  99.             if (handle != INVALID_HANDLE_VALUE)
  100.             {
  101.                 // If we are the root, then we need to notify the shell that
  102.                 // a folder was created so the view needs to be updated.
  103.                 // We fire the FtpChangeNotify() call for SHCNE_MKDIR in FtpCreateDirectoryWithCN().
  104.                 // FtpChangeNotify(SHCNE_MKDIR) is fired in FtpCreateDirectoryWithCN
  105.                 FindClose(handle);
  106.             }
  107.             */
  108.         }
  109.     }
  110.     else
  111.         hr = FtpCopyFile(hint, phpi, pcohi);
  112.     return hr;
  113. }
  114. HRESULT FtpCopyItem(HINTERNET hint, HINTPROCINFO * phpi, LPCOPYONEHDROPINFO pcohi, LPWIN32_FIND_DATA pwfd, LPCWIRESTR pwCurrentDir)
  115. {
  116.     HRESULT hr = S_OK;
  117.     TCHAR szFrom[MAX_PATH];
  118.     WCHAR wzDestDir[MAX_PATH];
  119.     TCHAR szServer[INTERNET_MAX_HOST_NAME_LENGTH];
  120.     COPYONEHDROPINFO cohi = {pcohi->pff, szFrom, pwfd->cFileName, wzDestDir, pcohi->dwOperation, pcohi->ops, FALSE, pcohi->pmlc, pcohi->pidlServer, pcohi->fFireChangeNotify, NULL};
  121.     CFtpDir * pfd = phpi->pfd;
  122.     BOOL fSkipCurrentFile = FALSE;
  123.     CWireEncoding * pwe = phpi->pfd->GetFtpSite()->GetCWireEncoding();
  124.     cohi.progInfo.ppd = pcohi->progInfo.ppd;
  125.     cohi.progInfo.hint = pcohi->progInfo.hint;
  126.     cohi.progInfo.uliBytesCompleted.QuadPart = pcohi->progInfo.uliBytesCompleted.QuadPart;
  127.     cohi.progInfo.uliBytesTotal.QuadPart = pcohi->progInfo.uliBytesTotal.QuadPart;
  128.     EVAL(SUCCEEDED(pwe->WireBytesToUnicode(pcohi->pmlc, pwCurrentDir, (pfd->IsUTF8Supported() ? WIREENC_USE_UTF8 : WIREENC_NONE), wzDestDir, ARRAYSIZE(wzDestDir))));
  129.     DisplayPathAppend(wzDestDir, ARRAYSIZE(wzDestDir), pcohi->pszFtpDest);
  130.     if (EVAL(SUCCEEDED(pfd->GetFtpSite()->GetServer(szServer, ARRAYSIZE(szServer)))) &&
  131.         SUCCEEDED(pfd->GetFtpSite()->GetFtpDir(szServer, wzDestDir, &(phpi->pfd))))
  132.     {
  133.         ASSERT(phpi->hwnd);
  134.         // Make sure the user thinks it's ok to replace.  We don't care about replacing directories
  135.         if ((pcohi->dwOperation != COHDI_FILESIZE_COUNT) &&
  136.             !(FILE_ATTRIBUTE_DIRECTORY & pwfd->dwFileAttributes))
  137.         {
  138.             TCHAR szSourceFile[MAX_PATH];
  139.             StrCpyN(szSourceFile, pcohi->pszFSSource, ARRAYSIZE(szSourceFile));
  140.             if (PathAppend(szSourceFile, pwfd->cFileName))
  141.             {
  142.                 // PERF: We should do the Confirm copy only if the upload fails because it's
  143.                 //       so costly.
  144.                 hr = ConfirmCopy(szSourceFile, pwfd->cFileName, &(cohi.ops), phpi->hwnd, pcohi->pff, phpi->pfd, NULL, 1, &cohi.fFireChangeNotify);
  145.                 if (S_FALSE == hr)
  146.                 {
  147.                     // S_FALSE from ConfirmCopy() means doen't replace this specific file, but continue
  148.                     // copying.  We need to return S_OK or we will cancel copying all the files.
  149.                     fSkipCurrentFile = TRUE;
  150.                     hr = S_OK;
  151.                 }
  152.             }
  153.             else
  154.                 hr = HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);    // Path too long, probably.
  155.         }
  156.         if (!fSkipCurrentFile && (S_OK == hr) && IS_VALID_FILE(pwfd->cFileName))
  157.         {
  158.             StrCpyN(szFrom, pcohi->pszFSSource, ARRAYSIZE(szFrom));     // Set the source directory.
  159.             // Specify the file/dir in that directory to copy.
  160.             if (PathAppend(szFrom, pwfd->cFileName))
  161.             {
  162.                 // 5. Call CopyFileSysItem() to get it copied (maybe recursively)
  163.                 //TraceMsg(TF_FTPOPERATION, "FtpCopyDirectory() calling CopyFileSysItem(From=%s. To=%s)", szFrom, pwfd->cFileName);
  164.                 hr = CopyFileSysItem(hint, phpi, &cohi);
  165.                 if (FAILED(hr) && (HRESULT_FROM_WIN32(ERROR_CANCELLED) != hr) &&
  166.                     (pcohi->dwOperation != COHDI_FILESIZE_COUNT))
  167.                 {
  168.                     int nResult = DisplayWininetError(phpi->hwnd, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_FILECOPY, IDS_FTPERR_WININET, MB_OK, pcohi->progInfo.ppd);
  169.                     hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  170.                 }
  171.             }
  172.             else
  173.                 hr = HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);    // Path too long, probably.
  174.         }
  175.         pcohi->progInfo.hint = cohi.progInfo.hint;    // Maybe the user cancelled.
  176.         pcohi->progInfo.uliBytesCompleted.QuadPart = cohi.progInfo.uliBytesCompleted.QuadPart;
  177.         pcohi->progInfo.uliBytesTotal.QuadPart = cohi.progInfo.uliBytesTotal.QuadPart;
  178.         pcohi->ops = cohi.ops;
  179.         phpi->pfd->Release();
  180.     }
  181.     phpi->pfd = pfd;
  182.     return hr;
  183. }
  184. HRESULT _FtpSetCurrentDirectory(HINTERNET hint, HINTPROCINFO * phpi, LPCWSTR pwzFtpPath)
  185. {
  186.     HRESULT hr;
  187.     WIRECHAR wFtpPath[MAX_PATH];
  188.     CWireEncoding * pwe = phpi->pfd->GetFtpSite()->GetCWireEncoding();
  189.     hr = pwe->UnicodeToWireBytes(NULL, pwzFtpPath, (phpi->pfd->IsUTF8Supported() ? WIREENC_USE_UTF8 : WIREENC_NONE), wFtpPath, ARRAYSIZE(wFtpPath));
  190.     if (SUCCEEDED(hr))
  191.         hr = FtpSetCurrentDirectoryWrap(hint, TRUE, wFtpPath);
  192.     return hr;
  193. }
  194. /*****************************************************************************
  195.      FtpCopyDirectory
  196.  
  197.     DESCRIPTION:
  198.         This function will need to copy all the items in the directory to the
  199.     FTP server if the item is a folder, it will need to recurse.
  200.     Recursion algorithm:
  201.     // 1. Create Directory
  202.     // 2. Get Current Directory (To save for later).
  203.     // 3. Change Directory Into new Directory.
  204.     // 4. Find Next item (file/dir) in file system
  205.     // 5. Call CopyFileSysItem() to get it copied (maybe recursively)
  206.     // 6. Go to Step 4 if there are any left.
  207.     // 7. Go back to original directory (Step 2)
  208. *****************************************************************************/
  209. HRESULT FtpCopyDirectory(HINTERNET hint, HINTPROCINFO * phpi, LPCOPYONEHDROPINFO pcohi)
  210. {
  211.     HRESULT hr = S_OK;
  212.     if (phpi->psb && (pcohi->dwOperation != COHDI_FILESIZE_COUNT))
  213.         phpi->psb->SetStatusMessage(IDS_COPYING, pcohi->pszFSSource);
  214.     //TraceMsg(TF_FTPOPERATION, "FtpCopyDirectory() calling FtpCreateDirectoryA(%s)", pcohi->pszFSSource);
  215.     // Create the directories on the first pass when we calculate file sizes.
  216.     // We then skip creating them on the copy pass.
  217.     if (pcohi->dwOperation == COHDI_FILESIZE_COUNT)
  218.     {
  219.         hr = FtpSafeCreateDirectory(phpi->hwnd, hint, pcohi->pmlc, pcohi->pff, phpi->pfd, pcohi->progInfo.ppd, pcohi->pszFtpDest, pcohi->fIsRoot);
  220.     }
  221.     // 1. Create Directory
  222.     if (SUCCEEDED(hr))
  223.     {
  224.         WIRECHAR wCurrentDir[MAX_PATH];
  225.         hr = FtpGetCurrentDirectoryWrap(hint, TRUE, wCurrentDir, ARRAYSIZE(wCurrentDir));
  226.         if (EVAL(SUCCEEDED(hr)))
  227.         {
  228.             // NOTE: At this point, pcohi->pszFSSource is the DIRECTORY on the local
  229.             //       file system that is being copied.
  230.             hr = _FtpSetCurrentDirectory(hint, phpi, pcohi->pszFtpDest);
  231.             if (SUCCEEDED(hr))
  232.             {
  233.                 WCHAR szSearchStr[MAX_PATH*2];
  234.                 WIN32_FIND_DATA wfd;
  235.                 HANDLE handle = NULL;
  236.                 StrCpyN(szSearchStr, pcohi->pszFSSource, ARRAYSIZE(szSearchStr));
  237.                 // We need to copy the entire directory.
  238.                 if (PathAppend(szSearchStr, SZ_ALL_FILES))
  239.                 {
  240.                     // 4. Find Next item (file/dir) in file system
  241.                     handle = FindFirstFile(szSearchStr, &wfd);
  242.                     if (handle != INVALID_HANDLE_VALUE)
  243.                     {
  244.                         do
  245.                         {
  246.                             //TraceMsg(TF_WININET_DEBUG, "FindFirstFileNext() returned %s", wfd.cFileName);
  247.                             hr = FtpCopyItem(hint, phpi, pcohi, &wfd, wCurrentDir);
  248.                             // 6. Check if the user canceled.
  249.                             if ((pcohi->progInfo.ppd) && (pcohi->progInfo.ppd->HasUserCancelled()))
  250.                             {
  251.                                 hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  252.                                 break;
  253.                             }
  254.                             // 7. Repeat if there are any left and it wasn't cancelled (S_FALSE)
  255.                         }
  256.                         while ((S_OK == hr) && FindNextFile(handle, &wfd));
  257.                         FindClose(handle);
  258.                     }
  259.                 }
  260.                 else
  261.                     hr = HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);    // Path too long, probably.
  262.             }
  263.             // 7. Go back to original directory (from Step 2)
  264.             // The only time we don't want to return to the original directory is if
  265.             // the hinst was freed in an wininet callback function.  We may cache the hinst
  266.             // so we need the directory to be valid later.
  267.             if (pcohi->progInfo.hint)
  268.             {
  269.                 EVAL(SUCCEEDED(FtpSetCurrentDirectoryWrap(hint, TRUE, wCurrentDir)));
  270.             }
  271.         }
  272.     }
  273.     else
  274.     {
  275.         if (HRESULT_FROM_WIN32(ERROR_CANCELLED) != hr)
  276.         {
  277.             DisplayWininetError(phpi->hwnd, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_DIRCOPY, IDS_FTPERR_WININET, MB_OK, pcohi->progInfo.ppd);
  278.             hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  279.         }
  280.     }
  281.     return hr;
  282. }
  283. HRESULT UpdateCopyProgressInfo(IProgressDialog * ppd, LPCTSTR pszFileName)
  284. {
  285.     HRESULT hr = E_FAIL;
  286.     TCHAR szTemplate[MAX_PATH];
  287.     if (EVAL(LoadString(HINST_THISDLL, IDS_COPYING, szTemplate, ARRAYSIZE(szTemplate))))
  288.     {
  289.         TCHAR szStatusStr[MAX_PATH];
  290.         WCHAR wzStatusStr[MAX_PATH];
  291.         wnsprintf(szStatusStr, ARRAYSIZE(szStatusStr), szTemplate, pszFileName);
  292.         SHTCharToUnicode(szStatusStr, wzStatusStr, ARRAYSIZE(wzStatusStr));
  293.         EVAL(SUCCEEDED(hr = ppd->SetLine(1, wzStatusStr, FALSE, NULL)));
  294.     }
  295.     return hr;
  296. }
  297. /*****************************************************************************
  298.     FUNCTION: _FireChangeNotify
  299.     DESCRIPTION:
  300.         asd
  301. *****************************************************************************/
  302. HRESULT _FireChangeNotify(HINTPROCINFO * phpi, LPCOPYONEHDROPINFO pcohi)
  303. {
  304.     HRESULT hr = S_OK;
  305.     WIN32_FIND_DATA wfd;
  306.     HANDLE handle = FindFirstFile(pcohi->pszFSSource, &wfd);
  307.     TraceMsg(TF_WININET_DEBUG, "_FireChangeNotify() FtpPutFileEx(%s -> %s) succeeded", pcohi->pszFSSource, pcohi->pszFtpDest);
  308.     if (handle != INVALID_HANDLE_VALUE)
  309.     {
  310.         ULARGE_INTEGER uliFileSize;
  311.         FTP_FIND_DATA ffd;
  312.         CWireEncoding * pwe = pcohi->pff->GetCWireEncoding();
  313.         uliFileSize.LowPart = wfd.nFileSizeLow;
  314.         uliFileSize.HighPart = wfd.nFileSizeHigh;
  315.         pcohi->progInfo.uliBytesCompleted.QuadPart += uliFileSize.QuadPart;
  316.         hr = pwe->UnicodeToWireBytes(pcohi->pmlc, wfd.cFileName, (phpi->pfd->IsUTF8Supported() ? WIREENC_USE_UTF8 : WIREENC_NONE), ffd.cFileName, ARRAYSIZE(ffd.cFileName));
  317.         if (EVAL(SUCCEEDED(hr)))
  318.         {
  319.             LPITEMIDLIST pidlFtpFile;
  320.             SYSTEMTIME st;
  321.             FILETIME ftUTC;
  322.             ffd.dwFileAttributes = wfd.dwFileAttributes;
  323.             ffd.dwReserved0 = wfd.dwReserved0;
  324.             ffd.dwReserved1 = wfd.dwReserved1;
  325.             ffd.nFileSizeHigh = wfd.nFileSizeHigh;
  326.             ffd.nFileSizeLow = wfd.nFileSizeLow;
  327.             // wfd.ft*Time is in UTF and FtpItemID_CreateReal wants
  328.             // it in LocalTime, so we need to convert here.
  329.             GetSystemTime(&st);
  330.             SystemTimeToFileTime(&st, &ftUTC);
  331.             FileTimeToLocalFileTime(&ftUTC, &ffd.ftLastWriteTime);   // UTC->LocalTime
  332.             ffd.ftCreationTime = ffd.ftLastWriteTime;
  333.             ffd.ftLastAccessTime = ffd.ftLastWriteTime;
  334.             hr = FtpItemID_CreateReal(&ffd, pcohi->pszFtpDest, &pidlFtpFile);
  335.             if (SUCCEEDED(hr))
  336.             {
  337.                 // Note that we created the mapped name
  338.                 // PERF: Note that we give the time/date stamp to SHChangeNotify that comes from the source
  339.                 //       file, not from the FTP server, so it may be inforrect.  However, it's perf prohibitive
  340.                 //       to do the right thing.
  341.                 FtpChangeNotify(phpi->hwnd, SHCNE_CREATE, pcohi->pff, phpi->pfd, pidlFtpFile, NULL, pcohi->fIsRoot);
  342.                 ILFree(pidlFtpFile);
  343.             }
  344.         }
  345.         FindClose(handle);
  346.     }
  347.     return hr;
  348. }
  349. #define CCH_SIZE_ERROR_MESSAGE  6*1024
  350. /*****************************************************************************
  351.     FtpCopyFile
  352.     Callback procedure that copies a single hdrop / map.
  353.     Should I try to make the name unique in case of collision?
  354.     Naah, just prompt, but! no way to tell if destination is case-sensitive...
  355. *****************************************************************************/
  356. HRESULT FtpCopyFile(HINTERNET hint, HINTPROCINFO * phpi, LPCOPYONEHDROPINFO pcohi)
  357. {
  358.     HRESULT hr = S_OK;
  359.     if (pcohi->dwOperation != COHDI_FILESIZE_COUNT)
  360.     {
  361.         WIRECHAR wWireName[MAX_PATH];
  362.         EVAL(SUCCEEDED(pcohi->pff->GetCWireEncoding()->UnicodeToWireBytes(pcohi->pmlc, pcohi->pszFtpDest, (pcohi->pff->IsUTF8Supported() ? WIREENC_USE_UTF8 : WIREENC_NONE), wWireName, ARRAYSIZE(wWireName))));
  363.         if (phpi->psb)
  364.             phpi->psb->SetStatusMessage(IDS_COPYING, pcohi->pszFSSource);
  365.         if (pcohi->progInfo.ppd)
  366.         {
  367.             EVAL(SUCCEEDED(UpdateCopyProgressInfo(pcohi->progInfo.ppd, pcohi->pszFtpDest)));
  368.             EVAL(SUCCEEDED(pcohi->progInfo.ppd->SetProgress64(pcohi->progInfo.uliBytesCompleted.QuadPart, pcohi->progInfo.uliBytesTotal.QuadPart)));
  369.         }
  370.         pcohi->progInfo.dwCompletedInCurFile = 0;
  371.         pcohi->progInfo.dwLastDisplayed = 0;
  372.         // BUGBUG: We need to pass the FTP_TRANSFER_TYPE (_ASCII vs. _BINARY)
  373.         hr = FtpPutFileExWrap(hint, TRUE, pcohi->pszFSSource, wWireName, FTP_TRANSFER_TYPE_UNKNOWN, (DWORD_PTR)&(pcohi->progInfo));
  374.         if (SUCCEEDED(hr))
  375.         {
  376.             // We don't fire change notify on browser only if we
  377.             // are replacing a file because ChangeNotify really
  378.             // just hacks ListView and doen't know how to handle
  379.             // duplicates (file replace).
  380.             if (pcohi->fFireChangeNotify)
  381.                 hr = _FireChangeNotify(phpi, pcohi);
  382.         }
  383.         else
  384.         {
  385.             if (HRESULT_FROM_WIN32(ERROR_INTERNET_OPERATION_CANCELLED) == hr)
  386.             {
  387.                 // Clean up the file.
  388.                 EVAL(SUCCEEDED(phpi->pfd->WithHint(NULL, phpi->hwnd, DeleteOneFileCB, pcohi, NULL, pcohi->pff)));
  389.                 hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  390.             }
  391.             else
  392.             {
  393.                 // We still want to delete the file, but we need to save the error message
  394.                 // so the dialog is correct.
  395.                 CHAR szErrorMsg[CCH_SIZE_ERROR_MESSAGE];
  396.                 WCHAR wzErrorMsg[CCH_SIZE_ERROR_MESSAGE];
  397.                 DWORD cchSize = ARRAYSIZE(szErrorMsg);
  398.                 InternetGetLastResponseInfoWrap(TRUE, NULL, szErrorMsg, &cchSize);
  399.                 HRESULT hrOrig = hr;
  400.                 CWireEncoding * pwe = phpi->pfd->GetFtpSite()->GetCWireEncoding();
  401.                 pwe->WireBytesToUnicode(NULL, szErrorMsg, WIREENC_NONE, wzErrorMsg, ARRAYSIZE(wzErrorMsg));
  402.                 // Does it already exist?  This may fail.
  403.                 SUCCEEDED(phpi->pfd->WithHint(NULL, phpi->hwnd, DeleteOneFileCB, pcohi, NULL, pcohi->pff));
  404.                 // No, so it was a real error, now display the error message with the original
  405.                 // server response.
  406.                 DisplayWininetErrorEx(phpi->hwnd, TRUE, HRESULT_CODE(hrOrig), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_FILECOPY, IDS_FTPERR_WININET, MB_OK, pcohi->progInfo.ppd, wzErrorMsg);
  407.                 hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  408.             }
  409.         }
  410.     }
  411.     else
  412.     {
  413.         // Just get the file size.   
  414.         WIN32_FIND_DATA wfd;
  415.         HANDLE handle = FindFirstFile(pcohi->pszFSSource, &wfd);
  416.         if (handle && (handle != INVALID_HANDLE_VALUE))
  417.         {
  418.             ULARGE_INTEGER uliFileSize;
  419.             uliFileSize.LowPart = wfd.nFileSizeLow;
  420.             uliFileSize.HighPart = wfd.nFileSizeHigh;
  421.             pcohi->progInfo.uliBytesTotal.QuadPart += uliFileSize.QuadPart;
  422.             FindClose(handle);
  423.         }
  424.     }
  425.     //TraceMsg(TF_FTPOPERATION, "FtpPutFileA(From=%ls, To=%s) hr=%#08lX", pcohi->pszFSSource, pcohi->pszFtpDest, hr);
  426.     return hr;
  427. }
  428. /*****************************************************************************
  429.     _EnumOneHdropW
  430.     Handle one hdrop and corresponding filemap.
  431.     This is annoying because we need to convert from UNICODE to ANSI.
  432. *****************************************************************************/
  433. #define OleStrToStrA(a, b) OleStrToStrN(a, ARRAYSIZE(a), b, -1)
  434. HRESULT _EnumOneHdropW(LPCWSTR * ppwzzFSSources, LPCWSTR * ppwzzFtpDest, LPTSTR pszFSSourceOut, DWORD cchFSSourceOut, LPTSTR pszFtpDestOut, DWORD cchFtpDestOut)
  435. {
  436.     HRESULT hres;
  437.     int cwch;
  438.     if (*ppwzzFSSources && (*ppwzzFSSources)[0])
  439.     {
  440.         cwch = SHUnicodeToTChar(*ppwzzFSSources, pszFSSourceOut, cchFSSourceOut);
  441.         if (EVAL(cwch))
  442.         {
  443.             *ppwzzFSSources += cwch;
  444.             if (EVAL((*ppwzzFtpDest)[0]))
  445.             {
  446.                 cwch = SHUnicodeToTChar(*ppwzzFtpDest, pszFtpDestOut, cchFtpDestOut);
  447.                 if (EVAL(cwch))
  448.                 {
  449.                     *ppwzzFtpDest += cwch;
  450.                     hres = S_OK;    // Both strings converted okay
  451.                 }
  452.                 else
  453.                     hres = E_UNEXPECTED; // File name too long
  454.             }
  455.             else
  456.                 hres = E_UNEXPECTED;    // Premature EOF in map
  457.         }
  458.         else
  459.             hres = E_UNEXPECTED;    // File name too long
  460.     }
  461.     else
  462.         hres = S_FALSE;            // End of buffer
  463.     return hres;
  464. }
  465. /*****************************************************************************
  466.     _EnumOneHdropA
  467.     Handle one hdrop and corresponding filemap.
  468. *****************************************************************************/
  469. HRESULT _EnumOneHdropA(LPCSTR * ppszzFSSource, LPCSTR * ppszzFtpDest, LPTSTR pszFSSourceOut, DWORD cchFSSourceOut, LPTSTR pszFtpDestOut, DWORD cchFtpDestOut)
  470. {
  471.     HRESULT hres;
  472.     if ((*ppszzFSSource)[0])
  473.     {
  474.         SHAnsiToTChar(*ppszzFSSource, pszFSSourceOut, cchFSSourceOut);
  475.         *ppszzFSSource += lstrlenA(*ppszzFSSource) + 1;
  476.         if (EVAL((*ppszzFtpDest)[0]))
  477.         {
  478.             SHAnsiToTChar(*ppszzFtpDest, pszFtpDestOut, cchFtpDestOut);
  479.             *ppszzFtpDest += lstrlenA(*ppszzFtpDest) + 1;
  480.             hres = S_OK;        // No problemo
  481.         }
  482.         else
  483.             hres = E_UNEXPECTED;    // Premature EOF in map
  484.     }
  485.     else
  486.         hres = S_FALSE;            // No more files
  487.     return hres;
  488. }
  489. /*****************************************************************************
  490.     ConfirmCopy
  491.     Callback procedure that checks if this file really ought to be
  492.     copied.
  493.     Returns S_OK if the file should be copied.
  494.     Returns S_FALSE if the file should not be copied.
  495.     - If the user cancelled, then say S_FALSE from now on.
  496.     - If the user said Yes to All, then say S_OK.
  497.     - If there is no conflict, then say S_OK.
  498.     - If the user said No to All, then say S_FALSE.
  499.     - Else, ask the user what to do.
  500.     Note that the order of the tests above means that if you say
  501.     "Yes to All", then we don't waste our time doing overwrite checks.
  502.     _GROSS_:  NOTE! that we don't try to uniquify the name, because
  503.     WinINet doesn't support the STOU (store unique) command, and
  504.     there is no way to know what filenames are valid on the server.
  505. *****************************************************************************/
  506. HRESULT ConfirmCopy(LPCWSTR pszLocal, LPCWSTR pszFtpName, OPS * pOps, HWND hwnd, CFtpFolder * pff, CFtpDir * pfd, DROPEFFECT * pde, int nObjs, BOOL * pfFireChangeNotify)
  507. {
  508.     HRESULT hr = S_OK;
  509.     *pfFireChangeNotify = TRUE;
  510.     if (*pOps == opsCancel)
  511.         hr = S_FALSE;
  512.     else 
  513.     {
  514.         HANDLE hfind;
  515.         WIN32_FIND_DATA wfdSrc;
  516.         hfind = FindFirstFile(pszLocal, &wfdSrc);
  517.         if (hfind != INVALID_HANDLE_VALUE)
  518.         {
  519.             FindClose(hfind);
  520.             // Is it a file?  We don't care about confirming the replacement
  521.             // of directories.
  522.             if (!(FILE_ATTRIBUTE_DIRECTORY & wfdSrc.dwFileAttributes))
  523.             {
  524.                 FTP_FIND_DATA wfd;
  525.                 hr = pfd->GetFindDataForDisplayPath(hwnd, pszFtpName, &wfd, pff);
  526.                 if (*pOps == opsYesToAll)
  527.                 {
  528.                     // If the file exists (S_OK) and it's browser only, 
  529.                     // then don't fire the change notify.
  530.                     if ((S_OK == hr) && (SHELL_VERSION_NT5 != GetShellVersion()))
  531.                         *pfFireChangeNotify = FALSE;
  532.                     hr = S_OK;
  533.                 }
  534.                 else
  535.                 {
  536.                     switch (hr)
  537.                     {
  538.                     case S_OK:            // File exists; worry
  539.                         if (*pOps == opsNoToAll)
  540.                             hr = S_FALSE;
  541.                         else
  542.                         {
  543.                             FILETIME ftUTC = wfdSrc.ftLastWriteTime;
  544.     
  545.                             FileTimeToLocalFileTime(&ftUTC, &wfdSrc.ftLastWriteTime);   // UTC->LocalTime
  546.                             // BUGBUG/TODO: Do we need to set modal?
  547.                             switch (FtpConfirmReplaceDialog(hwnd, &wfdSrc, &wfd, nObjs, pff))
  548.                             {
  549.                             case IDC_REPLACE_YESTOALL:
  550.                                 *pOps = opsYesToAll;
  551.                                 // FALLTHROUGH
  552.                             case IDC_REPLACE_YES:
  553.                                 // pre-NT5 doesn't work 
  554.                                 if (SHELL_VERSION_NT5 != GetShellVersion())
  555.                                     *pfFireChangeNotify = FALSE;
  556.                                 hr = S_OK;
  557.                                 break;
  558.                             case IDC_REPLACE_NOTOALL:
  559.                                 *pOps = opsNoToAll;
  560.                                 // FALLTHROUGH
  561.                             case IDC_REPLACE_NO:
  562.                                 hr = S_FALSE;
  563.                                 break;
  564.                             default:
  565.                                 ASSERT(0);        // Huh?
  566.                                 // FALLTHROUGH
  567.                             case IDC_REPLACE_CANCEL:
  568.                                 if (pde)
  569.                                     *pde = 0;
  570.                                 *pOps = opsCancel;
  571.                                 hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  572.                                 break;
  573.                             }
  574.                         }
  575.                         break;
  576.                     case S_FALSE:
  577.                     default:
  578.                         // Assume the file doesn't exist; no problemo
  579.                         hr = S_OK;
  580.                         break;
  581.                     }
  582.                 }
  583.             }
  584.         }
  585.         else
  586.         {                   // File doesn't exist
  587.             hr = S_OK;    // The open will raise the error
  588.         }
  589.     }
  590.     //TraceMsg(TF_FTPDRAGDROP, "ConfirmCopy(%s) -> %08x", pszFtpName, hr);
  591.     return hr;
  592. }
  593. /*****************************************************************************
  594.     CLASS: CDropOperation
  595.     DESCRIPTION:
  596.         DefView will cache the IDropTarget pointer (CFtpDrop) for a shell extension.
  597.     When it calls CFtpDrop::Drop(), the work needs to be done on a background
  598.     thread in order to not block the UI thread.  The problem is that if the user
  599.     does another drag to the same Ftp Window, CFtpDrop::Drop() will be called again.
  600.     For this reasons, CFtpDrop::Drop() cannot have any state after it returns.
  601.     In order to accomplish this with the asynch background thread, we have
  602.     CFtpDrop::Drop() call CDropOperation_Create(), and then CDropOperation->DoOperation().
  603.     And then it will orphan (call Release()) the CDropOperation.  The CDropOperation
  604.     will then destroy itself when the copy is finishes.  This enables subsequent calls
  605.     to CFtpDrop::Drop() to spawn separate CDropOperation objects so each can maintain
  606.     the state for that specifc operation and CFtpDrop remains stateless.
  607. *****************************************************************************/
  608. class CDropOperation          : public IUnknown
  609. {
  610. public:
  611.     //////////////////////////////////////////////////////
  612.     // Public Interfaces
  613.     //////////////////////////////////////////////////////
  614.     
  615.     // *** IUnknown ***
  616.     virtual STDMETHODIMP_(ULONG) AddRef(void);
  617.     virtual STDMETHODIMP_(ULONG) Release(void);
  618.     virtual STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj);
  619. public:
  620.     CDropOperation();
  621.     ~CDropOperation(void);
  622.     // Public Member Functions
  623.     HRESULT DoOperation(BOOL fAsync);
  624.     static HRESULT CopyCB(HINTERNET hint, HINTPROCINFO * phpi, LPVOID pv, BOOL * pfReleaseHint);
  625.     // Friend Functions
  626.     friend HRESULT CDropOperation_Create(CFtpFolder * pff, HWND hwnd, LPCTSTR pszzFSSource, LPCTSTR pszzFtpDest, CDropOperation ** ppfdt, DROPEFFECT de, OPS ops, int cobj);
  627. protected:
  628.     // Protected Member Variables
  629.     int                     m_cRef;
  630.     CFtpFolder *            m_pff;          // The owner
  631.     CFtpDir *               m_pfd;          // The FtpDir of the owner
  632.     HWND                    m_hwnd;         // The window being drug over
  633.     DROPEFFECT              m_de;           // Effect being performed
  634.     OPS                     m_ops;          // Overwrite prompting state
  635.     int                     m_cobj;         // Number of objects being dropped
  636.     ULARGE_INTEGER          m_uliBytesCompleted;
  637.     ULARGE_INTEGER          m_uliBytesTotal;
  638.     // Private Member Functions
  639.     HRESULT _ConfirmCopy(LPCWSTR pszLocal, LPCWSTR psz, BOOL * pfFireChangeNotify);
  640.     HRESULT _CalcSizeOneHdrop(LPCWSTR pszFSSource, LPCWSTR pszFtpDest, IProgressDialog * ppd);
  641.     HRESULT _ThreadProcCB(void);
  642.     HRESULT _CopyOneHdrop(LPCWSTR pszFSSource, LPCWSTR pszFtpDest);
  643.     HRESULT _StartBackgroundInteration(void);
  644.     HRESULT _DoCopyIteration(void);
  645.     HRESULT _CalcUploadProgress(void);
  646. private:
  647.     // Private Member Variables
  648.     IProgressDialog *       m_ppd;
  649.     LPCWSTR                 m_pszzFSSource;            // Paths
  650.     LPCWSTR                 m_pszzFtpDest;              // Map
  651.     CMultiLanguageCache     m_mlc;          // Cache for fast str thunking.
  652.     static DWORD CALLBACK _ThreadProc(LPVOID pThis) {return ((CDropOperation *)pThis)->_ThreadProcCB();};
  653. };
  654. HRESULT CDropOperation_Create(CFtpFolder * pff, HWND hwnd, LPCTSTR pszzFSSource, LPCTSTR pszzFtpDest, CDropOperation ** ppfdt, 
  655.                               DROPEFFECT de, OPS ops, int cobj)
  656. {
  657.     HRESULT hr = E_OUTOFMEMORY;
  658.     CDropOperation * pfdt = new CDropOperation();
  659.     *ppfdt = pfdt;
  660.     if (pfdt)
  661.     {
  662.         pfdt->m_hwnd = hwnd;
  663.         // Copy the CFtpFolder * value
  664.         pfdt->m_pff = pff;
  665.         if (pff)
  666.             pff->AddRef();
  667.         // Copy the CFtpDir * value
  668.         ASSERT(!pfdt->m_pfd);
  669.         pfdt->m_pfd = pff->GetFtpDir();
  670.         ASSERT(pfdt->m_pfd);
  671.         ASSERT(!pfdt->m_pszzFSSource);
  672.         pfdt->m_pszzFSSource = pszzFSSource;
  673.         ASSERT(!pfdt->m_pszzFtpDest);
  674.         pfdt->m_pszzFtpDest = pszzFtpDest;
  675.         pfdt->m_de = de;           // Effect being performed
  676.         pfdt->m_ops = ops;          // Overwrite prompting state
  677.         pfdt->m_cobj = cobj;         // Number of objects being dropped
  678.         hr = S_OK;
  679.     }
  680.     ASSERT_POINTER_MATCHES_HRESULT(*ppfdt, hr);
  681.     return hr;
  682. }
  683. /****************************************************
  684.     Constructor
  685. ****************************************************/
  686. CDropOperation::CDropOperation() : m_cRef(1)
  687. {
  688.     DllAddRef();
  689.     // This needs to be allocated in Zero Inited Memory.
  690.     // Assert that all Member Variables are inited to Zero.
  691.     ASSERT(!m_pff);
  692.     ASSERT(!m_pfd);
  693.     ASSERT(!m_hwnd);
  694.     ASSERT(!m_cobj);
  695.     LEAK_ADDREF(LEAK_CDropOperation);
  696. }
  697. /****************************************************
  698.     Destructor
  699. ****************************************************/
  700. CDropOperation::~CDropOperation()
  701. {
  702.     // use ATOMICRELEASE
  703.     IUnknown_Set(&m_pff, NULL);
  704.     IUnknown_Set(&m_pfd, NULL);
  705.     IUnknown_Set((IUnknown **)&m_ppd, NULL);
  706.     Str_SetPtr((LPTSTR *) &m_pszzFSSource, NULL);
  707.     Str_SetPtr((LPTSTR *) &m_pszzFtpDest, NULL);
  708.     DllRelease();
  709.     LEAK_DELREF(LEAK_CDropOperation);
  710. }
  711. //===========================
  712. // *** IUnknown Interface ***
  713. //===========================
  714. ULONG CDropOperation::AddRef()
  715. {
  716.     m_cRef++;
  717.     return m_cRef;
  718. }
  719. ULONG CDropOperation::Release()
  720. {
  721.     ASSERT(m_cRef > 0);
  722.     m_cRef--;
  723.     if (m_cRef > 0)
  724.         return m_cRef;
  725.     delete this;
  726.     return 0;
  727. }
  728. HRESULT CDropOperation::QueryInterface(REFIID riid, void **ppvObj)
  729. {
  730.     if (IsEqualIID(riid, IID_IUnknown))
  731.     {
  732.         *ppvObj = SAFECAST(this, IUnknown*);
  733.     }
  734.     else
  735.     {
  736.         TraceMsg(TF_FTPQI, "CDropOperation::QueryInterface() failed.");
  737.         *ppvObj = NULL;
  738.         return E_NOINTERFACE;
  739.     }
  740.     AddRef();
  741.     return S_OK;
  742. }
  743. /****************************************************
  744.     FUNCTION: _ThreadProcCB
  745.     DESCRIPTION:
  746. ****************************************************/
  747. HRESULT CDropOperation::_ThreadProcCB(void)
  748. {
  749.     HRESULT hr;
  750.     HRESULT hrOleInit = SHCoInitialize();
  751.     
  752.     // WARNING: Init OLE if you plan to do COM.
  753.     m_ppd = CProgressDialog_CreateInstance(IDS_COPY_TITLE, IDA_FTPUPLOAD);
  754.     if (EVAL(m_ppd))
  755.     {
  756.         ASSERT(m_hwnd);
  757.         // We give a NULL punkEnableModless because we don't want to go modal.
  758.         EVAL(SUCCEEDED(m_ppd->StartProgressDialog(m_hwnd, NULL, PROGDLG_AUTOTIME, NULL)));
  759.     }
  760.     hr = _CalcUploadProgress();
  761.     // Did we succeed creating the directories and counting the
  762.     // size we need to copy?
  763.     if (SUCCEEDED(hr))
  764.     {
  765.         if (m_ppd)
  766.         {
  767.             EVAL(SUCCEEDED(m_ppd->SetProgress64(m_uliBytesCompleted.QuadPart, m_uliBytesTotal.QuadPart)));
  768.             // Reset because _CalcUploadProgress() can take a long time and the estimated time
  769.             // is based on the time between ::StartProgressDialog() and the first
  770.             // ::SetProgress() call.
  771.             EVAL(SUCCEEDED(m_ppd->Timer(PDTIMER_RESET, NULL)));
  772.         }
  773.         hr = _DoCopyIteration();
  774.     }
  775.     if (m_ppd)
  776.     {
  777.         EVAL(SUCCEEDED(m_ppd->StopProgressDialog()));
  778.         ATOMICRELEASE(m_ppd);
  779.     }
  780.     SHCoUninitialize(hrOleInit);
  781.     Release();
  782.     return hr;
  783. }
  784. HRESULT CDropOperation::DoOperation(BOOL fAsync)
  785. {
  786.     HRESULT hr = S_OK;
  787.     AddRef();
  788.     if (fAsync)
  789.     {
  790.         HANDLE hThread;
  791.         DWORD dwThreadId;
  792.         hThread = CreateThread(NULL, 0, CDropOperation::_ThreadProc, this, 0, &dwThreadId);
  793.         if (hThread)
  794.             CloseHandle(hThread);
  795.         else
  796.         {
  797.             TraceMsg(TF_ERROR, "CDropOperation::DoOperation() CreateThread() failed and GetLastError()=%lu.", GetLastError());
  798.             Release();
  799.         }
  800.     }
  801.     else
  802.         hr = _ThreadProcCB();
  803.     return hr;
  804. }
  805. /****************************************************
  806.     FUNCTION: _CalcUploadProgress
  807.     DESCRIPTION:
  808. ****************************************************/
  809. HRESULT CDropOperation::_CalcUploadProgress(void)
  810. {
  811.     HRESULT hr = S_OK;
  812.     LPCWSTR pszzFSSource = m_pszzFSSource;
  813.     LPCWSTR pszzFtpDest = m_pszzFtpDest;
  814.     WCHAR wzProgressDialogStr[MAX_PATH];
  815.     m_uliBytesCompleted.QuadPart = 0;
  816.     m_uliBytesTotal.QuadPart = 0;
  817.     
  818.     // Tell the user we are calculating how long it will take.
  819.     if (EVAL(LoadStringW(HINST_THISDLL, IDS_PROGRESS_UPLOADTIMECALC, wzProgressDialogStr, ARRAYSIZE(wzProgressDialogStr))))
  820.         EVAL(SUCCEEDED(m_ppd->SetLine(2, wzProgressDialogStr, FALSE, NULL)));
  821.     while (S_OK == hr)
  822.     {
  823.         WCHAR szFSSource[MAX_PATH];
  824.         WCHAR szFtpDest[MAX_PATH];
  825.         hr = _EnumOneHdrop(&pszzFSSource, &pszzFtpDest, szFSSource, ARRAYSIZE(szFSSource), szFtpDest, ARRAYSIZE(szFtpDest));
  826.         if (S_OK == hr)
  827.             hr = _CalcSizeOneHdrop(szFSSource, szFtpDest, m_ppd);
  828.     }
  829.     if (FAILED(hr))
  830.         TraceMsg(TF_ALWAYS, "CDropOperation::_CalcUploadProgress() Calculating the upload time failed, but oh well.");
  831.     return hr;
  832. }
  833. HRESULT CDropOperation::_CalcSizeOneHdrop(LPCWSTR pszFSSource, LPCWSTR pszFtpDest, IProgressDialog * ppd)
  834. {
  835.     HRESULT hr;
  836.     WCHAR wzTo[MAX_PATH];
  837.     EVAL(SUCCEEDED(m_pfd->GetDisplayPath(wzTo, ARRAYSIZE(wzTo))));
  838.     pszFtpDest = PathFindFileName(pszFtpDest);
  839.     COPYONEHDROPINFO cohi = {0};
  840.     cohi.pff = m_pff;
  841.     cohi.pszFSSource = pszFSSource;
  842.     cohi.pszFtpDest = pszFtpDest;
  843.     cohi.pszDir = wzTo;
  844.     cohi.dwOperation = COHDI_FILESIZE_COUNT;
  845.     cohi.ops = opsPrompt;
  846.     cohi.fIsRoot = TRUE;
  847.     cohi.pmlc = &m_mlc;
  848.     cohi.pidlServer = FtpCloneServerID(m_pff->GetPrivatePidlReference());
  849.     cohi.progInfo.ppd = ppd;
  850.     cohi.fFireChangeNotify = TRUE;
  851.     cohi.progInfo.uliBytesCompleted.QuadPart = m_uliBytesCompleted.QuadPart;
  852.     cohi.progInfo.uliBytesTotal.QuadPart = m_uliBytesTotal.QuadPart;
  853.     hr = m_pfd->WithHint(NULL, m_hwnd, CopyCB, &cohi, NULL, m_pff);
  854.     if (SUCCEEDED(hr))
  855.     {
  856.         m_uliBytesCompleted = cohi.progInfo.uliBytesCompleted;
  857.         m_uliBytesTotal = cohi.progInfo.uliBytesTotal;
  858.     }
  859.     ILFree(cohi.pidlServer);
  860.     return hr;
  861. }
  862. /****************************************************
  863.     FUNCTION: CDropOperation
  864.     DESCRIPTION:
  865. ****************************************************/
  866. HRESULT CDropOperation::_DoCopyIteration()
  867. {
  868.     HRESULT hr = S_OK;
  869.     LPCTSTR pszzFSSource = m_pszzFSSource;
  870.     LPCTSTR pszzFtpDest = m_pszzFtpDest;
  871.     m_ops = opsPrompt;
  872.     while (S_OK == hr)
  873.     {
  874.         WCHAR szFSSource[MAX_PATH];
  875.         WCHAR szFtpDest[MAX_PATH];
  876.         hr = _EnumOneHdrop(&pszzFSSource, &pszzFtpDest, szFSSource, ARRAYSIZE(szFSSource), szFtpDest, ARRAYSIZE(szFtpDest));
  877.         if (S_OK == hr)
  878.         {
  879.             szFSSource[lstrlenW(szFSSource)+1] = 0;   // Double terminate for SHFileOperation(Delete) in move case
  880.             hr = _CopyOneHdrop(szFSSource, szFtpDest);
  881.             if (EVAL(m_ppd))
  882.                 EVAL(SUCCEEDED(m_ppd->SetProgress64(m_uliBytesCompleted.QuadPart, m_uliBytesTotal.QuadPart)));
  883.             // Did we fail to copy the file?
  884.             if (FAILED(hr) && (HRESULT_FROM_WIN32(ERROR_CANCELLED) != hr))
  885.             {
  886.                 if (!IsValidFtpAnsiFileName(szFSSource) || !IsValidFtpAnsiFileName(szFtpDest))
  887.                     int nResult = DisplayWininetError(m_hwnd, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_INVALIDFTPNAME, IDS_FTPERR_WININET, MB_OK, m_ppd);
  888.                 else
  889.                     int nResult = DisplayWininetError(m_hwnd, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_FILECOPY, IDS_FTPERR_WININET, MB_OK, m_ppd);
  890.                 hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  891.             }
  892.             if (S_FALSE == hr)
  893.             {
  894.                 // _CopyOneHdrop() returning S_FALSE means we hit the end of the iteration,
  895.                 // in this case, _ConfirmCopy() only meant to skip this one file, so
  896.                 // change to S_OK to continue with the rest of the files.
  897.                 hr = S_OK;
  898.             }
  899.         }
  900.     }
  901.     Str_SetPtr((LPTSTR *) &m_pszzFSSource, NULL);
  902.     Str_SetPtr((LPTSTR *) &m_pszzFtpDest, NULL);
  903.     return hr;
  904. }
  905. HRESULT CDropOperation::_ConfirmCopy(LPCWSTR pszLocal, LPCWSTR pszFtpName, BOOL * pfFireChangeNotify)
  906. {
  907.     return ConfirmCopy(pszLocal, pszFtpName, &m_ops, m_hwnd, m_pff, m_pfd, NULL, m_cobj, pfFireChangeNotify);
  908. }
  909. /*****************************************************************************
  910.     CopyCB
  911.     Callback procedure that copies a single hdrop / map.
  912. *****************************************************************************/
  913. HRESULT CDropOperation::CopyCB(HINTERNET hint, HINTPROCINFO * phpi, LPVOID pv, BOOL * pfReleaseHint)
  914. {
  915.     LPCOPYONEHDROPINFO pcohi = (LPCOPYONEHDROPINFO) pv;
  916.     pcohi->progInfo.hint = hint;
  917.     HRESULT hr;
  918.     InternetSetStatusCallbackWrap(hint, TRUE, FtpProgressInternetStatusCB);
  919.     hr = CopyFileSysItem(hint, phpi, pcohi);
  920.     if (!pcohi->progInfo.hint)
  921.         *pfReleaseHint = FALSE;     // We had to close hint to get the cancel.
  922.     return hr;
  923. }
  924. HRESULT CDropOperation::_CopyOneHdrop(LPCWSTR pszFSSource, LPCWSTR pszFtpDest)
  925. {
  926.     HRESULT hr;
  927.     BOOL fFireChangeNotify = TRUE;
  928.     pszFtpDest = PathFindFileName(pszFtpDest);
  929.     hr = _ConfirmCopy(pszFSSource, pszFtpDest, &fFireChangeNotify);
  930.     if (S_OK == hr)
  931.     {
  932.         WCHAR wzTo[MAX_PATH];
  933.         COPYONEHDROPINFO cohi = {0};
  934.         cohi.pff = m_pff;
  935.         cohi.pszFSSource = pszFSSource;
  936.         cohi.pszFtpDest = pszFtpDest;
  937.         cohi.pszDir = wzTo;
  938.         cohi.dwOperation = COHDI_COPY_FILES;
  939.         cohi.ops = m_ops;
  940.         cohi.fIsRoot = TRUE;
  941.         cohi.pmlc = &m_mlc;
  942.         cohi.pidlServer = FtpCloneServerID(m_pff->GetPrivatePidlReference());
  943.         cohi.fFireChangeNotify = fFireChangeNotify;
  944.         cohi.progInfo.ppd = m_ppd;
  945.         cohi.progInfo.uliBytesCompleted.QuadPart = m_uliBytesCompleted.QuadPart;
  946.         cohi.progInfo.uliBytesTotal.QuadPart = m_uliBytesTotal.QuadPart;
  947.         EVAL(SUCCEEDED(m_pfd->GetDisplayPath(wzTo, ARRAYSIZE(wzTo))));
  948.         // TODO: have CopyCB also update the dialog.
  949.         hr = m_pfd->WithHint(NULL, m_hwnd, CopyCB, &cohi, NULL, m_pff);
  950.         if (SUCCEEDED(hr) && (m_de == DROPEFFECT_MOVE))
  951.         {
  952.             //  We delete the file with SHFileOperation to keep the
  953.             //  disk free space statistics up to date.
  954.             //
  955.             //  BUGBUG -- If coming from a file name map, maybe it's
  956.             //  being dragged from the recycle bin, in which case, doing
  957.             //  an FO_DELETE will put it back in!
  958.             SHFILEOPSTRUCT sfo = {0};
  959.             
  960.             sfo.hwnd = NULL,                // No HWND so NO UI.
  961.             sfo.wFunc  = FO_DELETE;
  962.             sfo.pFrom  = pszFSSource;       // Multiple files in list.
  963.             sfo.fFlags = (FOF_SILENT | FOF_NOCONFIRMATION /*| FOF_MULTIDESTFILES*/);  // No HWND so NO UI.
  964.             int nResult = SHFileOperation(&sfo);
  965.             if (0 != nResult)
  966.                 TraceMsg(TF_ALWAYS, "In CDropOperation::_CopyOneHdrop() and caller wanted MOVE but we couldn't delete the files after the copy.");
  967.         }
  968.         m_uliBytesCompleted = cohi.progInfo.uliBytesCompleted;
  969.         m_uliBytesTotal = cohi.progInfo.uliBytesTotal;
  970.         m_ops = cohi.ops;
  971.     }
  972.     else
  973.     {
  974.         if (S_FALSE == hr)
  975.         {
  976.             // _CopyOneHdrop() returning S_FALSE means we hit the end of the iteration,
  977.             // in this case, _ConfirmCopy() only meant to skip this one file, so
  978.             // change to S_OK to continue with the rest of the files.
  979.             hr = S_OK;
  980.         }
  981.     }
  982.     return hr;
  983. }
  984. /*****************************************************************************
  985.     FUNCTION: SetEffect
  986.     DESCRIPTION:
  987.         Set the appropriate drop effect feedback.
  988.     In the absence of keyboard modifiers, use CTRL (copy), unless
  989.     DROPEFFECT_COPY is not available, in which case we use SHIFT (move).
  990.     If anything else is set, then panic out to DROPEFFECT_NONE.
  991.     Note that we do *not* use g_cfPreferredDe.  The only things
  992.     we support are DROPEFFECT_COPY and DROPEFFECT_MOVE, and we always prefer DROPEFFECT_COPY.
  993.     BUGBUG -- ignoring g_cfPreferredDe messes up cut/paste, though.
  994.  *****************************************************************************/
  995. HRESULT CFtpDrop::SetEffect(DROPEFFECT * pde)
  996. {
  997.     DWORD de;            // Preferred drop effect
  998.     // Don't even think about effects that we don't support
  999.     *pde &= m_grfksAvail;
  1000.     switch (m_grfks & (MK_SHIFT | MK_CONTROL))
  1001.     {
  1002.     case 0:            // No modifier, use COPY if possible
  1003.         if (*pde & DROPEFFECT_COPY)
  1004.         {
  1005.     case MK_CONTROL:
  1006.             de = DROPEFFECT_COPY;
  1007.         }
  1008.         else
  1009.         {
  1010.     case MK_SHIFT:
  1011.             de = DROPEFFECT_MOVE;
  1012.         }
  1013.         break;
  1014.     default:
  1015.         de = 0;
  1016.         break;        // Cannot link
  1017.     }
  1018.     *pde &= de;
  1019.     TraceMsg(TF_FTPDRAGDROP, "CFtpDrop::SetEffect(DROPEFFECT=%08x) m_grfksAvail=%08x", *pde, m_grfksAvail);
  1020.     return S_OK;
  1021. }
  1022. BOOL CFtpDrop::_IsFTPOperationAllowed(IDataObject * pdto)
  1023. {
  1024. #ifdef FEATURE_FTP_TO_FTP_COPY
  1025.     BOOL fIsFTPOperationAllowed = TRUE;
  1026.     // There are a few things we don't allow.
  1027.     // Is the Drop FTP Location the same
  1028.     // folder that the dragged items are already in?
  1029.     if (0)
  1030.     {
  1031.         // TODO:
  1032.     }
  1033.     
  1034.     return fIsFTPOperationAllowed;
  1035. #else // FEATURE_FTP_TO_FTP_COPY
  1036.     // Disallow all FTP Operations
  1037.     return !_HasData(pdto, &g_dropTypes[DROP_FTP_PRIVATE]);
  1038. #endif // FEATURE_FTP_TO_FTP_COPY
  1039. }
  1040. /*****************************************************************************
  1041.     GetEffectsAvail
  1042.     Look at the object to see what drop effects are available.
  1043.     If we have a file group descriptor or an HDROP,
  1044.     then file contents are available.  (We assume that if you have
  1045.     a FGD, then a Contents isn't far behind.)
  1046.     In a perfect world, we would also validate the contents of
  1047.     each file in the group descriptor, to ensure that the contents
  1048.     are droppable.  We skimp on that because it's too expensive.
  1049. *****************************************************************************/
  1050. DWORD CFtpDrop::GetEffectsAvail(IDataObject * pdto)
  1051. {
  1052.     DWORD grfksAvail = 0;
  1053.     // Is this from an Ftp Shell Extension?
  1054.     if (_IsFTPOperationAllowed(pdto))
  1055.     {
  1056.         // No or it's allowed, then we will accept it.  We reject everything
  1057.         // else because we can't do Ftp1->Ftp2 copying without
  1058.         // using the local machine as a temp location. (Ftp1->Local->Ftp2)
  1059.         if (_HasData(pdto, &g_dropTypes[DROP_Hdrop]) ||
  1060.             _HasData(pdto, &g_dropTypes[DROP_FGDW]) ||
  1061.             _HasData(pdto, &g_dropTypes[DROP_FGDA]))
  1062.         {
  1063.             TraceMsg(TF_FTPDRAGDROP, "CFtpDrop::GetEffectsAvail() SUCCEEDED");
  1064.             grfksAvail = DROPEFFECT_COPY + DROPEFFECT_MOVE;
  1065.         }
  1066.         else
  1067.         {
  1068.             TraceMsg(TF_FTPDRAGDROP, "CFtpDrop::GetEffectsAvail() FAILED");
  1069. #ifdef DEBUG
  1070.             STGMEDIUM sm;
  1071.             HRESULT hres = pdto->GetData(&g_dropTypes[DROP_URL], &sm);
  1072.             if (SUCCEEDED(hres))
  1073.             {
  1074.                 TraceMsg(TF_FTPDRAGDROP, "CFtpDrop::GetEffectsAvail(%08x) URL: %hs", pdto, GlobalLock(sm.hGlobal));
  1075.                 GlobalUnlock(sm.hGlobal);
  1076.                 ReleaseStgMedium(&sm);
  1077.             }
  1078.             else
  1079.             {
  1080.                 TraceMsg(TF_FTPDRAGDROP, "CFtpDrop::GetEffectsAvail(%08x) No URL", pdto);
  1081.             }
  1082. #endif // DEBUG
  1083.         }
  1084.     }
  1085.     return grfksAvail;
  1086. }
  1087. /*****************************************************************************
  1088.     GetEffect
  1089.     Return the drop effect to use.
  1090.     If this is a nondefault drag/drop, then put up a menu.  Else,
  1091.     just go with the default.
  1092.     m_de = default effect
  1093.     m_pde -> possible effects (and receives result)
  1094. *****************************************************************************/
  1095. DROPEFFECT CFtpDrop::GetEffect(POINTL pt)
  1096. {
  1097.     TraceMsg(TF_FTPDRAGDROP, "CFtpDrop::GetEffect() m_de=%08x. m_grfks=%08x", m_de, m_grfks);
  1098.     if (m_de && (m_grfks & MK_RBUTTON))
  1099.     {
  1100.         HMENU hmenuMain = LoadMenu(g_hinst, MAKEINTRESOURCE(IDM_DROPCONTEXT));
  1101.         HMENU hmenu = GetSubMenu(hmenuMain, 0);
  1102.         DROPEFFECT de;
  1103.         ASSERT(*m_pde & m_de);
  1104.         SetMenuDefaultItem(hmenu, m_de, 0);
  1105.         if (!(*m_pde & DROPEFFECT_COPY))
  1106.             DeleteMenu(hmenu, DROPEFFECT_COPY, MF_BYCOMMAND);
  1107.         if (!(*m_pde & DROPEFFECT_MOVE))
  1108.             DeleteMenu(hmenu, DROPEFFECT_MOVE, MF_BYCOMMAND);
  1109.         // _UNOBVIOUS_:  Defview is incestuous with itself.
  1110.         // If the drop target originated from Shell32.dll, then
  1111.         // it leaves the image of the dropped object on the screen
  1112.         // while the menu is up, which is nice.  Otherwise, it removes
  1113.         // the image of the dropped object before the drop target
  1114.         // receives its IDropTarget::Drop.
  1115.         // Which means that outside shell extensions can't take
  1116.         // advantage of the "pretty drop UI" feature.
  1117.         // _UNOBVIOUS_:  Have to force foregroundness, else the input
  1118.         // gets screwed up.
  1119.         if (m_hwnd)
  1120.             SetForegroundWindow(m_hwnd);
  1121.         de = TrackPopupMenuEx(hmenu,
  1122.                       TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_VERTICAL |
  1123.                       TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y,
  1124.                       m_hwnd, 0);
  1125.         DestroyMenu(hmenuMain);
  1126.         m_de = de;
  1127.     }
  1128.     *m_pde = m_de;
  1129.     TraceMsg(TF_FTPDRAGDROP, "CFtpDrop::GetEffect(%08x) -> %08x", this, m_de);
  1130.     return m_de;
  1131. }
  1132. /****************************************************
  1133.     FUNCTION: _StartBackgroundInteration
  1134.     DESCRIPTION:
  1135. ****************************************************/
  1136. HRESULT CFtpDrop::_StartBackgroundInteration(void)
  1137. {
  1138.     CDropOperation * pDropOperation;
  1139.     HRESULT hr = CDropOperation_Create(m_pff, m_hwnd, m_pszzFSSource, m_pszzFtpDest, &pDropOperation, m_de, m_ops, m_cobj);
  1140.     
  1141.     // Did it succeed?
  1142.     if (EVAL(SUCCEEDED(hr)))
  1143.     {
  1144.         // Yes, so NULL out m_pszzFSSource, m_pszzFtpDest because we gave them our copies.
  1145.         //  Ugly but allocation is uglier.
  1146.         m_pszzFSSource = NULL;
  1147.         m_pszzFtpDest = NULL;
  1148.         EVAL(SUCCEEDED(hr = pDropOperation->DoOperation(TRUE)));
  1149.         pDropOperation->Release();
  1150.     }
  1151.     return hr;
  1152. }
  1153. /****************************************************
  1154.     FUNCTION: _DoCountIteration
  1155.     DESCRIPTION:
  1156. ****************************************************/
  1157. HRESULT CFtpDrop::_DoCountIteration(void)
  1158. {
  1159.     HRESULT hr = S_OK;
  1160.     LPCTSTR pszzFSSource = m_pszzFSSource;
  1161.     LPCTSTR pszzFtpDest = m_pszzFtpDest;
  1162.     while (S_OK == hr)
  1163.     {
  1164.         TCHAR szFSSource[MAX_PATH];
  1165.         TCHAR szFtpDest[MAX_PATH];
  1166.         hr = _EnumOneHdrop(&pszzFSSource, &pszzFtpDest, szFSSource, ARRAYSIZE(szFSSource), szFtpDest, ARRAYSIZE(szFtpDest));
  1167.         if (S_OK == hr)
  1168.             m_cobj++;
  1169.     }
  1170.     if (hr == S_FALSE)
  1171.         hr = S_OK;        // Enumerated to completion
  1172.     return hr;
  1173. }
  1174. /****************************************************
  1175.     FUNCTION: _GetFSSourcePaths
  1176.     DESCRIPTION:
  1177. ****************************************************/
  1178. HRESULT CFtpDrop::_GetFSSourcePaths(HGLOBAL hdrop, BOOL * pfAnsi)
  1179. {
  1180.     LPDROPFILES pdrop = (LPDROPFILES) GlobalLock(hdrop);
  1181.     HRESULT hr = E_INVALIDARG;
  1182.     *pfAnsi = TRUE;
  1183.     if (EVAL(pdrop))
  1184.     {
  1185.         //  Now to decide whether it is an old-style drop or a new-style
  1186.         // drop.  And if it's a new-style drop, to get the character set.
  1187.         if (LOWORD(pdrop->pFiles) == sizeof(DROPFILES16))
  1188.         {
  1189.             // Old style
  1190.             Str_StrAndThunkA((LPTSTR *) &m_pszzFSSource, (LPCSTR) pvByteIndexCb(pdrop, LOWORD(pdrop->pFiles)), TRUE);
  1191.         }
  1192.         else
  1193.         {
  1194.             if (pdrop->fWide)
  1195.             {
  1196.                 Str_StrAndThunkW((LPTSTR *) &m_pszzFSSource, (LPCWSTR) pvByteIndexCb(pdrop, pdrop->pFiles), TRUE);
  1197.                 *pfAnsi = FALSE;
  1198.             }
  1199.             else
  1200.                 Str_StrAndThunkA((LPTSTR *) &m_pszzFSSource, (LPCSTR) pvByteIndexCb(pdrop, pdrop->pFiles), TRUE);
  1201.         }
  1202.         GlobalUnlock(pdrop);
  1203.         hr = S_OK;
  1204.     }
  1205.     return hr;
  1206. }
  1207. /****************************************************
  1208.     FUNCTION: _GetFtpDestPaths
  1209.     DESCRIPTION:
  1210. ****************************************************/
  1211. HRESULT CFtpDrop::_GetFtpDestPaths(HGLOBAL hmap, BOOL fAnsi)
  1212. {
  1213.     HRESULT hr = E_INVALIDARG;
  1214.     LPVOID pmap = NULL;
  1215.     //  If we can't get a map, then just use the source file names.
  1216.     ASSERT(!m_pszzFtpDest);
  1217.     if (hmap)
  1218.     {
  1219.         pmap = GlobalLock(hmap);
  1220.         if (pmap)
  1221.         {
  1222.             if (fAnsi)
  1223.                 Str_StrAndThunkA((LPTSTR *) &m_pszzFtpDest, (LPCSTR) pmap, TRUE);
  1224.             else
  1225.                 Str_StrAndThunkW((LPTSTR *) &m_pszzFtpDest, (LPCWSTR) pmap, TRUE);
  1226.             GlobalUnlock(pmap);
  1227.         }
  1228.     }
  1229.     if (!m_pszzFtpDest)
  1230.     {
  1231.         // Just copy the Paths
  1232.         Str_StrAndThunk((LPTSTR *) &m_pszzFtpDest, m_pszzFSSource, TRUE);
  1233.     }
  1234.     if (m_pszzFtpDest)
  1235.         hr = S_OK;
  1236.     return hr;
  1237. }
  1238. /*****************************************************************************
  1239.     CopyHdrop
  1240.     Copy an HDROP data object.
  1241.     Note also that when we use HDROP, we must also consult the
  1242.     FileNameMap otherwise dragging out of the recycle bin directly
  1243.     into an FTP folder will create files with the wrong name!
  1244.     Note further that the returned effect of an HDROP is always
  1245.     DROPEFFECT_COPY, because we will do the work of deleting the
  1246.     source files when finished.
  1247. *****************************************************************************/
  1248. HRESULT CFtpDrop::CopyHdrop(IDataObject * pdto, STGMEDIUM *psm)
  1249. {
  1250.     BOOL fAnsi;
  1251.     HRESULT hr = _GetFSSourcePaths(psm->hGlobal, &fAnsi);
  1252.     if (EVAL(SUCCEEDED(hr)))
  1253.     {
  1254.         STGMEDIUM sm;
  1255.         // ZIP fails this.
  1256.         // Get the File name map, too, if one exists
  1257.         if (fAnsi)
  1258.             hr = pdto->GetData(&g_dropTypes[DROP_FNMA], &sm);
  1259.         else
  1260.             hr = pdto->GetData(&g_dropTypes[DROP_FNMW], &sm);
  1261.         if (FAILED(hr))       // Failure is ok
  1262.             sm.hGlobal = 0;
  1263.         hr = _GetFtpDestPaths(sm.hGlobal, fAnsi);
  1264.         if (EVAL(SUCCEEDED(hr)))
  1265.         {
  1266.             *m_pde = DROPEFFECT_COPY;
  1267.             // Count up how many things there are in the hdrop,
  1268.             // so that our confirmation dialog knows what the deal is.
  1269.             // We can ignore the error; it'll show up again when we copy.
  1270.             m_cobj = 0;
  1271.             hr = _DoCountIteration();
  1272.             ASSERT(SUCCEEDED(hr));
  1273.             TraceMsg(TF_FTPDRAGDROP, "CFtpDrop_CopyHdrop: %d file(s)", m_cobj);
  1274.             //  Now walk the lists with the appropriate enumerator.
  1275.             hr = _StartBackgroundInteration();
  1276.             ASSERT(SUCCEEDED(hr));
  1277.         }
  1278.         if (sm.hGlobal)
  1279.             ReleaseStgMedium(&sm);
  1280.     }
  1281.     return hr;
  1282. }
  1283. /*****************************************************************************
  1284.     _CopyHglobal
  1285.     Copy a file contents received as an hglobal.
  1286.     If a FD_FILESIZE is provided, use it.  Otherwise, just use the size
  1287.     of the hglobal.
  1288. *****************************************************************************/
  1289. HRESULT CFtpDrop::_CopyHglobal(IStream * pstm, DWORD dwFlags, DWORD dwFileSizeHigh, DWORD dwFileSizeLow, LPVOID pvSrc, ULARGE_INTEGER *pqw)
  1290. {
  1291.     LPVOID pv;
  1292.     HGLOBAL hglob = pvSrc;
  1293.     HRESULT hres;
  1294.     pqw->HighPart = 0;
  1295.     pv = GlobalLock(hglob);
  1296.     if (EVAL(pv))
  1297.     {
  1298.         UINT cb = (UINT) GlobalSize(hglob);
  1299.         if (dwFlags & FD_FILESIZE)
  1300.         {
  1301.             if (cb > dwFileSizeLow)
  1302.                 cb = dwFileSizeHigh;
  1303.         }
  1304.         hres = pstm->Write(pv, cb, &pqw->LowPart);
  1305.         if (SUCCEEDED(hres))
  1306.         {
  1307.             if (pqw->LowPart != cb)
  1308.                 hres = STG_E_MEDIUMFULL;
  1309.         }
  1310.         GlobalUnlock(pv);
  1311.     }
  1312.     else
  1313.         hres = E_INVALIDARG;
  1314.     return hres;
  1315. }
  1316. /*****************************************************************************
  1317.     FUNCTION: _GetRelativePidl
  1318.     DESCRIPTION:
  1319.         pszFullPath may come in this format: "dir1dir2dir3file.txt".  We
  1320.     need to create *ppidl such that it will contain 4 itemIDs in this case and
  1321.     the last one (file.txt) will have the correct attributes and file size.
  1322. *****************************************************************************/
  1323. CFtpDir * CFtpDrop::_GetRelativePidl(LPCWSTR pszFullPath, DWORD dwFileAttributes, DWORD dwFileSizeHigh, DWORD dwFileSizeLow, LPITEMIDLIST * ppidl)
  1324. {
  1325.     HRESULT hr = S_OK;
  1326.     WCHAR szFullPath[MAX_PATH];
  1327.     LPWSTR pszFileName;
  1328.     LPITEMIDLIST pidlFull;
  1329.     CFtpDir * pfd = m_pfd;  // Assume the Dir to create isn't in a subdir.
  1330.     // Find the File Name
  1331.     StrCpyNW(szFullPath, pszFullPath, ARRAYSIZE(szFullPath));   // Make a copy because the caller's is read only.
  1332.     pszFileName = PathFindFileName(szFullPath);                 // Find where the file begins.
  1333.     FilePathToUrlPathW(szFullPath);                             // Convert from "dir1dir2file.txt" to "dir1/dir2/file.txt"
  1334.     *ppidl = NULL;
  1335.     hr = CreateFtpPidlFromDisplayPath(szFullPath, m_pff->GetCWireEncoding(), NULL, &pidlFull, TRUE, FALSE);
  1336.     if (SUCCEEDED(hr))
  1337.     {
  1338.         LPITEMIDLIST pidlFile = ILFindLastID(pidlFull);
  1339.         SYSTEMTIME st;
  1340.         FILETIME ft;
  1341.         GetSystemTime(&st);
  1342.         SystemTimeToFileTime(&st, &ft);
  1343.         FtpPidl_SetAttributes(pidlFile, dwFileAttributes);
  1344.         FtpPidl_SetFileSize(pidlFile, dwFileSizeHigh, dwFileSizeLow);
  1345.         FtpItemID_SetFileTime(pidlFile, ft);
  1346.         // Is the file in a subdir?
  1347.         if (!ILIsEmpty(pidlFull) && !ILIsEmpty(_ILNext(pidlFull)))
  1348.         {
  1349.             // Yes, so generate a CFtpDir to the subdir.
  1350.             LPITEMIDLIST pidlPath = ILClone(pidlFull);
  1351.             if (pidlPath)
  1352.             {
  1353.                 ILRemoveLastID(pidlPath);
  1354.                 pfd = m_pfd->GetSubFtpDir(m_pff, pidlPath, FALSE);
  1355.                 ILFree(pidlPath);
  1356.             }
  1357.         }
  1358.         if (pfd)
  1359.             *ppidl = ILClone(pidlFile);
  1360.         ILFree(pidlFull);
  1361.     }
  1362.     return pfd;
  1363. }
  1364. /*****************************************************************************
  1365.     FUNCTION: CopyAsStream
  1366.     DESCRIPTION:
  1367.         Copy a file contents received as a <mumble> to a stream.
  1368. *****************************************************************************/
  1369. HRESULT CFtpDrop::CopyAsStream(LPCWSTR pszName, DWORD dwFileAttributes, DWORD dwFlags, DWORD dwFileSizeHigh, DWORD dwFileSizeLow, STREAMCOPYPROC pfn, LPVOID pv)
  1370. {
  1371.     BOOL fFireChangeNotify;
  1372.     HRESULT hr = ConfirmCopy(pszName, pszName, &m_ops, m_hwnd, m_pff, m_pfd, m_pde, m_cobj, &fFireChangeNotify);
  1373.     if (EVAL(SUCCEEDED(hr)))
  1374.     {
  1375.         LPITEMIDLIST pidlRelative;
  1376.         CFtpDir * pfd = _GetRelativePidl(pszName, dwFileAttributes, dwFileSizeHigh, dwFileSizeLow, &pidlRelative);
  1377.         if (EVAL(pfd))
  1378.         {
  1379.             LPITEMIDLIST pidlFull = ILCombine(pfd->GetPidlReference(), pidlRelative);
  1380.             if (pidlFull)
  1381.             {
  1382.                 IStream * pstm;
  1383.                 ULARGE_INTEGER uliTemp = {0};
  1384.                 hr = CFtpStm_Create(pfd, pidlFull, GENERIC_WRITE, &pstm, uliTemp, uliTemp, NULL, FALSE);
  1385.                 if (SUCCEEDED(hr))
  1386.                 {
  1387.                     ULARGE_INTEGER uli = {dwFileSizeLow, dwFileSizeHigh};
  1388.                     hr = pfn(pstm, dwFlags, dwFileSizeHigh, dwFileSizeLow, pv, &uli);
  1389.                     if (SUCCEEDED(hr))
  1390.                     {
  1391.                         // Only fire change notify if we didn't replace a file on 
  1392.                         // browser only. (Because we hack the defview and it doesn't
  1393.                         // check for duplicates)
  1394.                         if (fFireChangeNotify)
  1395.                         {
  1396.                             FtpPidl_SetFileSize(pidlRelative, uli.HighPart, uli.LowPart);
  1397.                             // This time date stamp may be incorrect.
  1398.                             FtpChangeNotify(m_hwnd, SHCNE_CREATE, m_pff, pfd, pidlRelative, NULL, TRUE);
  1399.                         }
  1400.                     }
  1401.                     else
  1402.                     {
  1403.                         ASSERT(0);      // BUGBUG - Is there an orphaned file we need to delete?
  1404.                         DisplayWininetError(m_hwnd, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_DROPFAIL, IDS_FTPERR_WININET, MB_OK, NULL);
  1405.                         hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  1406.                     }
  1407.                     pstm->Release();
  1408.                 }
  1409.                 else
  1410.                 {
  1411.                     DisplayWininetError(m_hwnd, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_DROPFAIL, IDS_FTPERR_WININET, MB_OK, NULL);
  1412.                     hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  1413.                 }
  1414.                 ILFree(pidlFull);
  1415.             }
  1416.             else
  1417.                 hr = E_OUTOFMEMORY;
  1418.             if (pfd != m_pfd)
  1419.                 pfd->Release();
  1420.             ILFree(pidlRelative);
  1421.         }
  1422.         else
  1423.             hr = E_FAIL;
  1424.     }
  1425.     else
  1426.     {
  1427.         // BUGBUG -- need to stat the file to generate a local WFDA
  1428.         // BUGBUG -- check the return value and do something
  1429.         ASSERT(0);      // Handle appropriately.
  1430.     }
  1431.     return hr;
  1432. }
  1433. /*****************************************************************************
  1434.     CopyStream
  1435.     Copy a file contents received as a stream.
  1436.     We ignore the file size in the fgd.
  1437. *****************************************************************************/
  1438. HRESULT CFtpDrop::CopyStream(IStream * pstm, DWORD dwFlags, DWORD dwFileSizeHigh, DWORD dwFileSizeLow, LPVOID pvSrc, ULARGE_INTEGER *pqw)
  1439. {
  1440.     IStream * pstmSrc = (IStream *) pvSrc;
  1441.     ULARGE_INTEGER qwMax = {0xFFFFFFFF, 0xFFFFFFFF};
  1442.     HRESULT hres;
  1443.     hres = pstmSrc->CopyTo(pstm, qwMax, 0, pqw);
  1444.     ASSERT(SUCCEEDED(hres));
  1445.     return hres;
  1446. }
  1447. /*****************************************************************************
  1448.     FUNCTION: CFtpDrop::CopyStorage
  1449.     DESCRIPTION:
  1450.         Copy a file contents provided as an IStorage.  Gack.
  1451.     We have to do this only because Exchange is a moron.
  1452.     Since there is no way to tell OLE to create a .doc file
  1453.     into an existing stream, we need to create the .doc file
  1454.     on disk, and then copy the file into the stream, then delete
  1455.     the .doc file.
  1456.     Note that CDropOperation::DoOperation() (_CopyOneHdrop) will do the ConfirmCopy
  1457.     and the FtpDropNotifyCreate(), too!  However, we want to fake
  1458.     it out and fool it into thinking we are doing a DROPEFFECT_COPY,
  1459.     so that it doesn't delete the "source" file.  *We* will delete
  1460.     the source file, because we created it.  (No need to tell the
  1461.     shell about disk size changes that don't affect it.)
  1462. *****************************************************************************/
  1463. HRESULT CFtpDrop::CopyStorage(LPCWSTR pszFile, IStorage * pstgIn)
  1464. {
  1465.     IStorage * pstgOut;
  1466.     HRESULT hr = StgCreateDocfile(0, (STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_CREATE), 0, &pstgOut);
  1467.     if (EVAL(SUCCEEDED(hr)))
  1468.     {
  1469.         STATSTG stat;
  1470.         hr = pstgOut->Stat(&stat, STATFLAG_DEFAULT);
  1471.         if (EVAL(SUCCEEDED(hr)))
  1472.         {
  1473.             TCHAR szFSSource[MAX_PATH+3];
  1474.             TCHAR szFtpDest[MAX_PATH+3];
  1475.             SHUnicodeToTChar(stat.pwcsName, szFSSource, ARRAYSIZE(szFSSource));
  1476.             StrCpyN(szFtpDest, pszFile, ARRAYSIZE(szFtpDest));
  1477.             szFSSource[lstrlen(szFSSource)+1] = 0;    // Add the termination of the list of strings.
  1478.             szFtpDest[lstrlen(szFtpDest)+1] = 0;    // Add the termination of the list of strings.
  1479.             hr = pstgIn->CopyTo(0, 0, 0, pstgOut);
  1480.             pstgOut->Commit(STGC_OVERWRITE);
  1481.             pstgOut->Release();     // Must release before copying
  1482.             pstgOut = NULL;
  1483.             if (EVAL(SUCCEEDED(hr)))
  1484.             {
  1485.                 DROPEFFECT deTrue = m_de;
  1486.                 m_de = DROPEFFECT_COPY;
  1487.                 CDropOperation * pDropOperation;
  1488.                 hr = CDropOperation_Create(m_pff, m_hwnd, szFSSource, szFtpDest, &pDropOperation, m_de, m_ops, m_ops);
  1489.     
  1490.                 // Did it succeed?
  1491.                 if (EVAL(SUCCEEDED(hr)))
  1492.                 {
  1493.                     // Do the operation asynchroniously because the caller may call
  1494.                     // this over an over.
  1495.                     EVAL(SUCCEEDED(hr = pDropOperation->DoOperation(FALSE)));
  1496.                     pDropOperation->Release();
  1497.                 }
  1498.                 // Did an error occure and no UI has been displayed yet?
  1499.                 if (FAILED(hr) && (HRESULT_FROM_WIN32(ERROR_CANCELLED) != hr))
  1500.                 {
  1501.                     DisplayWininetError(m_hwnd, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_FILECOPY, IDS_FTPERR_WININET, MB_OK, NULL);
  1502.                 }
  1503.                 m_de = deTrue;
  1504.                 DeleteFile(szFSSource);
  1505.             }
  1506.             else
  1507.                 DisplayWininetError(m_hwnd, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_DROPFAIL, IDS_FTPERR_WININET, MB_OK, NULL);
  1508.             SHFree(stat.pwcsName);
  1509.         }
  1510.         else
  1511.             DisplayWininetError(m_hwnd, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_DROPFAIL, IDS_FTPERR_WININET, MB_OK, NULL);
  1512.     }
  1513.     else
  1514.         DisplayWininetError(m_hwnd, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_DROPFAIL, IDS_FTPERR_WININET, MB_OK, NULL);
  1515.     return hr;
  1516. }
  1517. /*****************************************************************************
  1518.     CopyFCont
  1519.     Copy a file contents.
  1520. *****************************************************************************/
  1521. HRESULT CFtpDrop::CopyFCont(LPCWSTR pszName, DWORD dwFileAttributes, DWORD dwFlags, DWORD dwFileSizeHigh, DWORD dwFileSizeLow, STGMEDIUM *psm)
  1522. {
  1523.     HRESULT hres;
  1524.     switch (psm->tymed)
  1525.     {
  1526.     case TYMED_HGLOBAL:
  1527.         hres = CopyAsStream(pszName, dwFileAttributes, dwFlags, dwFileSizeHigh, dwFileSizeLow, _CopyHglobal, psm->hGlobal);
  1528.         break;
  1529.     case TYMED_ISTREAM:
  1530.         hres = CopyAsStream(pszName, dwFileAttributes, dwFlags, dwFileSizeHigh, dwFileSizeLow, CopyStream, psm->pstm);
  1531.         break;
  1532.     case TYMED_ISTORAGE:        // Stupid Exchange
  1533.         hres = CopyStorage(pszName, psm->pstg);
  1534.         break;
  1535.     default:
  1536.         ASSERT(0);
  1537.         // Shouldn't have gotten this - BUGBUG -- UI?
  1538.         hres = E_INVALIDARG;
  1539.         break;
  1540.     }
  1541.     return hres;
  1542. }
  1543. HRESULT CFtpDrop::_GetFileDescriptor(LONG nIndex, LPFILEGROUPDESCRIPTORW pfgdW, LPFILEGROUPDESCRIPTORA pfgdA, BOOL fUnicode, LPFILEDESCRIPTOR pfd)
  1544. {
  1545.     if (fUnicode)
  1546.     {
  1547.         LPFILEDESCRIPTORW pfdW = &pfgdW->fgd[nIndex];
  1548.     
  1549.         CopyMemory(pfd, pfdW, (sizeof(*pfdW) - sizeof(pfdW->cFileName)));   // Copy Everything except the name.
  1550.         SHUnicodeToTChar(pfdW->cFileName, pfd->cFileName, ARRAYSIZE(pfd->cFileName));
  1551.     }
  1552.     else
  1553.     {
  1554.         LPFILEDESCRIPTORA pfdA = &pfgdA->fgd[nIndex];
  1555.         
  1556.         CopyMemory(pfd, pfdA, (sizeof(*pfdA) - sizeof(pfdA->cFileName)));   // Copy Everything except the name.
  1557.         SHAnsiToTChar(pfdA->cFileName, pfd->cFileName, ARRAYSIZE(pfd->cFileName));
  1558.     }
  1559.     return S_OK;
  1560. }
  1561. HRESULT CFtpDrop::_CreateFGDDirectory(LPFILEDESCRIPTOR pFileDesc)
  1562. {
  1563.     HRESULT hr = S_OK;
  1564.     WCHAR szDirName[MAX_PATH];
  1565.     LPTSTR pszDirToCreate = PathFindFileName(pFileDesc->cFileName);
  1566.     FTPCREATEFOLDERSTRUCT fcfs = {szDirName, m_pff};
  1567.     CFtpDir * pfd = m_pfd;  // Assume the Dir to create isn't in a subdir.
  1568.     SHTCharToUnicode(pszDirToCreate, szDirName, ARRAYSIZE(szDirName));
  1569.     pszDirToCreate[0] = 0;  // Separate Dir to create from SubDir where to create it.
  1570.     // Is the dir to create in subdir?
  1571.     if (pFileDesc->cFileName[0])
  1572.     {
  1573.         // Yes, so let's get that CFtpDir pointer so WithHint below will get us there.
  1574.         LPITEMIDLIST pidlPath;
  1575.         
  1576.         FilePathToUrlPathW(pFileDesc->cFileName);
  1577.         hr = CreateFtpPidlFromDisplayPath(pFileDesc->cFileName, m_pff->GetCWireEncoding(), NULL, &pidlPath, TRUE, TRUE);
  1578.         if (SUCCEEDED(hr))
  1579.         {
  1580.             pfd = m_pfd->GetSubFtpDir(m_pff, pidlPath, FALSE);
  1581.             ILFree(pidlPath);
  1582.         }
  1583.     }
  1584.     
  1585.     if (SUCCEEDED(hr))
  1586.     {
  1587.         hr = pfd->WithHint(NULL, m_hwnd, CreateNewFolderCB, (LPVOID) &fcfs, NULL, m_pff);
  1588.         if (SUCCEEDED(hr))
  1589.         {
  1590.         }
  1591.         else
  1592.         {
  1593.             // TODO: Display error UI?
  1594.         }
  1595.     }
  1596.     if (m_pfd != pfd)
  1597.     {
  1598.         // We allocated pfd, so now let's free it.
  1599.         pfd->Release();
  1600.     }
  1601.     return hr;
  1602. }
  1603. /*****************************************************************************
  1604.     CopyFGD
  1605.     Copy a file group descriptor.
  1606.     File group descriptors are used to source gizmos that are file-like
  1607.     but aren't stored on disk as such.  E.g., an embedded file in a
  1608.     mail message, a GIF image in a web page, an OLE scrap, or a file
  1609.     on a remote FTP site.
  1610.     _UNOBVIOUS_:  If you do a GetData on TYMED_HGLOBAL | TYMED_ISTREAM,
  1611.     Exchange will nonetheless give you a TYMED_ISTORAGE even though
  1612.     you didn't ask for it.  So we need to support IStorage in order
  1613.     to make Exchange look less broken.  (Maybe I shouldn't cover for
  1614.     them.  Or maybe I should send them a bill.)
  1615. *****************************************************************************/
  1616. HRESULT CFtpDrop::CopyFGD(IDataObject * pdto, STGMEDIUM *psm, BOOL fUnicode)
  1617. {
  1618.     LPFILEGROUPDESCRIPTORA pfgdA = NULL;
  1619.     LPFILEGROUPDESCRIPTORW pfgdW = NULL;
  1620.     HRESULT hr = E_INVALIDARG;
  1621.     // WARNING:
  1622.     //      shell32.dll from Win95, WinNT 4, IE 3, IE 4, and IE 4.01 have
  1623.     //      a bug that cause recursive file download not to work for
  1624.     //      subdirectories on WinNT unless we implement FILEGROUPDESCRIPTORW.
  1625.     if (fUnicode)
  1626.         pfgdW = (LPFILEGROUPDESCRIPTORW) GlobalLock((LPFILEGROUPDESCRIPTORW *) psm->hGlobal);
  1627.     else
  1628.         pfgdA = (LPFILEGROUPDESCRIPTORA) GlobalLock((FILEGROUPDESCRIPTORA *) psm->hGlobal);
  1629.     if (EVAL(pfgdA || pfgdW))
  1630.     {
  1631.         FORMATETC fe = {g_dropTypes[DROP_FCont].cfFormat, 0, DVASPECT_CONTENT, 0, (TYMED_ISTREAM | TYMED_HGLOBAL | TYMED_ISTORAGE)};
  1632.         
  1633.         // Stupid Exchange
  1634.         DWORD dwSize = m_cobj = (pfgdW ? pfgdW->cItems : pfgdA->cItems);
  1635.         TraceMsg(TF_FTPDRAGDROP, "CFtpDrop::CopyFGD: %d files", m_cobj);
  1636.         hr = S_OK;        // Watch out for vacuous null case
  1637.         for (; ((UINT)fe.lindex < dwSize); fe.lindex++)
  1638.         {
  1639.             FILEDESCRIPTOR fileDescriptor = {0};
  1640.             if (EVAL(SUCCEEDED(_GetFileDescriptor(fe.lindex, pfgdW, pfgdA, fUnicode, &fileDescriptor))))
  1641.             {
  1642.                 // Is this a folder?
  1643.                 if ((FD_ATTRIBUTES & fileDescriptor.dwFlags) &&
  1644.                     FILE_ATTRIBUTE_DIRECTORY & fileDescriptor.dwFileAttributes)
  1645.                 {
  1646.                     // Yes, so let's create it.  We currently don't copy folder
  1647.                     // info. (ACLs or other attributes)
  1648.                     hr = _CreateFGDDirectory(&fileDescriptor);
  1649.                 }
  1650.                 else
  1651.                 {
  1652.                     // No, so it's a file.  Let's get the stream and then upload that to the FTP server.
  1653.                     STGMEDIUM sm;
  1654.                     
  1655.                     hr = pdto->GetData(&fe, &sm);
  1656.                     if (SUCCEEDED(hr))
  1657.                     {
  1658.                         hr = CopyFCont(fileDescriptor.cFileName, fileDescriptor.dwFileAttributes, fileDescriptor.dwFlags, fileDescriptor.nFileSizeHigh, fileDescriptor.nFileSizeLow, &sm);
  1659.                         ReleaseStgMedium(&sm);
  1660.                         if (FAILED(hr))
  1661.                         {
  1662.                             break;
  1663.                         }
  1664.                     }
  1665.                     else
  1666.                     {
  1667.                         ASSERT(0);
  1668.                         break;
  1669.                     }
  1670.                 }
  1671.             }
  1672.         }
  1673.         if (pfgdW)
  1674.             GlobalUnlock(pfgdW);
  1675.         if (pfgdA)
  1676.             GlobalUnlock(pfgdA);
  1677.     }
  1678.     return hr;
  1679. }
  1680. /*****************************************************************************
  1681.     _Copy
  1682.     Copy the data object into the shell folder.
  1683.     HDROPs are preferred, because we can use FtpPutFile to shove
  1684.     them onto the FTP site without getting our hands dirty.
  1685.     Failing that, we use FileGroupDescriptor, which lets us
  1686.     get at pseudo-files.
  1687.     Note also that if you use HDROP, you need to support FileNameMap
  1688.     otherwise dragging out of the recycle bin directly into an FTP
  1689.     folder will create files with the wrong name!
  1690.     BUGBUG -- Ask FrancisH how to handle the multi-drag case + move.
  1691.     If a single file is cancelled, should I return DROPEFFECT_NONE?
  1692. *****************************************************************************/
  1693. HRESULT CFtpDrop::_Copy(IDataObject * pdto)
  1694. {
  1695.     STGMEDIUM sm;
  1696.     HRESULT hr;
  1697.     if (SUCCEEDED(hr = pdto->GetData(&g_dropTypes[DROP_Hdrop], &sm)))
  1698.     {
  1699.         hr = CopyHdrop(pdto, &sm);
  1700.         ReleaseStgMedium(&sm);
  1701.     }
  1702.     else
  1703.     {
  1704.         BOOL fSupportsUnicode = SUCCEEDED(hr = pdto->GetData(&g_dropTypes[DROP_FGDW], &sm));
  1705.         if (fSupportsUnicode || EVAL(SUCCEEDED(hr = pdto->GetData(&g_dropTypes[DROP_FGDA], &sm))))
  1706.         {
  1707.             hr = CopyFGD(pdto, &sm, fSupportsUnicode);
  1708.             ReleaseStgMedium(&sm);
  1709.         }
  1710.     }
  1711.     // Normally we would set the PASTESUCCEEDED info back into 
  1712.     // the IDataObject but we don't because we do an optimized
  1713.     // MOVE by doing a DELETE after the COPY operation.
  1714.     // We do this because we do the operation on a background thread
  1715.     // in order to be asynch and we don't want to extend the lifetime
  1716.     // of the IDataObject that long.  Therefore we push
  1717.     // DROPEFFECT_COPY back into the caller to tell them that
  1718.     // we did an optimized move and to not delete the items.
  1719.     //
  1720.     // TODO: We need to test the CopyFGD() code above and
  1721.     //       maybe use PasteSucceeded(DROPEFFECT_MOVE) in
  1722.     //       that case.
  1723.     if (SUCCEEDED(hr) && (m_de == DROPEFFECT_MOVE))
  1724.     {
  1725.         // Always set "Copy" because we did an optimized move
  1726.         // because we deleted the files our selfs.
  1727.         DataObj_SetPasteSucceeded(pdto, DROPEFFECT_COPY);
  1728.     }
  1729.     return hr;
  1730. }
  1731. //===========================
  1732. // *** IDropTarget Interface ***
  1733. //===========================
  1734. /*****************************************************************************
  1735.     IDropTarget::DragEnter
  1736.  *****************************************************************************/
  1737. HRESULT CFtpDrop::DragEnter(IDataObject * pdto, DWORD grfKeyState, POINTL pt, DROPEFFECT * pde)
  1738. {
  1739.     HRESULT hr;
  1740.     m_grfks = grfKeyState;    // Remember last key state
  1741.     m_grfksAvail = GetEffectsAvail(pdto);
  1742.     hr = SetEffect(pde);
  1743.     ASSERT(SUCCEEDED(hr));
  1744.     TraceMsg(TF_FTPDRAGDROP, "CFtpDrop::DragEnter(grfKeyState=%08x, DROPEFFECT=%08x) m_grfks=%08x. m_grfksAvail=%08x hres=%#08lx", grfKeyState, *pde, m_grfks, m_grfksAvail, hr);
  1745.     return hr;
  1746. }
  1747. /*****************************************************************************
  1748.     IDropTarget::DragOver
  1749.  *****************************************************************************/
  1750. HRESULT CFtpDrop::DragOver(DWORD grfKeyState, POINTL pt, DROPEFFECT * pde)
  1751. {
  1752.     HRESULT hr;
  1753.     m_grfks = grfKeyState;    // Remember last key state
  1754.     hr = SetEffect(pde);
  1755.     ASSERT(SUCCEEDED(hr));
  1756.     TraceMsg(TF_FTPDRAGDROP, "CFtpDrop::DragOver(grfKeyState=%08x, DROPEFFECT=%08x) m_grfks=%08x. SetEffect() returned hres=%#08lx", grfKeyState, *pde, m_grfks, hr);
  1757.     return hr;
  1758. }
  1759. /*****************************************************************************
  1760.     IDropTarget::DragLeave
  1761.  *****************************************************************************/
  1762. HRESULT CFtpDrop::DragLeave(void)
  1763. {
  1764.     TraceMsg(TF_FTPDRAGDROP, "CFtpDrop::DragLeave() ");
  1765.     return S_OK;
  1766. }
  1767. /*****************************************************************************
  1768.     IDropTarget::Drop 
  1769.     Note that the incoming pdto is not necessarily the same as the
  1770.     one we saw on DragEnter.  OLE will first give us a "preliminary"
  1771.     data object to play with, but on the drop, it will give us a
  1772.     fully marshalled object.
  1773.     Fortunately, we don't care, because we didn't cache the object.
  1774.     Note that we don't pass the real pde to SetEffect, because
  1775.     we don't want to lose the list of all possible effects before
  1776.     GetEffect uses it.
  1777. *****************************************************************************/
  1778. HRESULT CFtpDrop::Drop(IDataObject * pdo, DWORD grfKeyState, POINTL pt, DROPEFFECT * pde)
  1779. {
  1780.     HRESULT hr;
  1781.     m_ops = opsPrompt;        // Start out in prompt mode
  1782.     m_grfksAvail = GetEffectsAvail(pdo);
  1783.     
  1784.     m_pde = pde;
  1785.     m_de = *pde;
  1786.     hr = SetEffect(&m_de);
  1787.     TraceMsg(TF_FTPDRAGDROP, "CFtpDrop::Drop(grfKeyState=%08x, DROPEFFECT=%08x) m_grfksAvail=%08x. m_de=%08x. SetEffect() returned hres=%#08lx", grfKeyState, *pde, m_grfksAvail, m_de, hr);
  1788.     if (EVAL(SUCCEEDED(hr)))
  1789.     {
  1790.         if (GetEffect(pt))
  1791.         {
  1792.             hr = _Copy(pdo);
  1793.         }
  1794.         else
  1795.             hr = S_FALSE;   // Indicate cancel.
  1796.     }
  1797.     if (!(SUCCEEDED(hr)))
  1798.     {
  1799.         // Error message already has been displayed.
  1800.         *pde = 0;
  1801.     }
  1802.     return hr;
  1803. }
  1804. /*****************************************************************************
  1805.     CFtpDrop_Create
  1806. *****************************************************************************/
  1807. HRESULT CFtpDrop_Create(CFtpFolder * pff, HWND hwnd, CFtpDrop ** ppfdt)
  1808. {
  1809.     HRESULT hres = E_OUTOFMEMORY;
  1810.     CFtpDrop * pfdt = new CFtpDrop();
  1811.     *ppfdt = pfdt;
  1812.     if (EVAL(pfdt))
  1813.     {
  1814.         pfdt->m_hwnd = hwnd;
  1815.         // Copy the CFtpFolder * value
  1816.         pfdt->m_pff = pff;
  1817.         if (pff)
  1818.             pff->AddRef();
  1819.         // Copy the CFtpDir * value
  1820.         ASSERT(!pfdt->m_pfd);
  1821.         pfdt->m_pfd = pff->GetFtpDir();
  1822.         hres = pfdt->m_pfd ? S_OK : E_FAIL;
  1823.         if (FAILED(hres))   // Will fail if the caller is CFtpMenu::_RemoveContextMenuItems() and it's OK.
  1824.             ATOMICRELEASE(*ppfdt);
  1825.     }
  1826.     ASSERT_POINTER_MATCHES_HRESULT(*ppfdt, hres);
  1827.     return hres;
  1828. }
  1829. /****************************************************
  1830.     Constructor
  1831. ****************************************************/
  1832. CFtpDrop::CFtpDrop() : m_cRef(1)
  1833. {
  1834.     DllAddRef();
  1835.     // This needs to be allocated in Zero Inited Memory.
  1836.     // Assert that all Member Variables are inited to Zero.
  1837.     ASSERT(!m_pff);
  1838.     ASSERT(!m_pfd);
  1839.     ASSERT(!m_hwnd);
  1840.     ASSERT(!m_grfks);
  1841.     ASSERT(!m_grfksAvail);
  1842.     ASSERT(!m_pde);
  1843.     ASSERT(!m_cobj);
  1844.     LEAK_ADDREF(LEAK_CFtpDrop);
  1845. }
  1846. /****************************************************
  1847.     Destructor
  1848. ****************************************************/
  1849. CFtpDrop::~CFtpDrop()
  1850. {
  1851.     IUnknown_Set(&m_pff, NULL);
  1852.     IUnknown_Set(&m_pfd, NULL);
  1853.     DllRelease();
  1854.     LEAK_DELREF(LEAK_CFtpDrop);
  1855. }
  1856. //===========================
  1857. // *** IUnknown Interface ***
  1858. //===========================
  1859. ULONG CFtpDrop::AddRef()
  1860. {
  1861.     m_cRef++;
  1862.     return m_cRef;
  1863. }
  1864. ULONG CFtpDrop::Release()
  1865. {
  1866.     ASSERT(m_cRef > 0);
  1867.     m_cRef--;
  1868.     if (m_cRef > 0)
  1869.         return m_cRef;
  1870.     delete this;
  1871.     return 0;
  1872. }
  1873. HRESULT CFtpDrop::QueryInterface(REFIID riid, void **ppvObj)
  1874. {
  1875.     if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IDropTarget))
  1876.     {
  1877.         *ppvObj = SAFECAST(this, IDropTarget*);
  1878.     }
  1879.     else
  1880.     {
  1881.         TraceMsg(TF_FTPQI, "CFtpDrop::QueryInterface() failed.");
  1882.         *ppvObj = NULL;
  1883.         return E_NOINTERFACE;
  1884.     }
  1885.     AddRef();
  1886.     return S_OK;
  1887. }