Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2168 lines
78 KiB

  1. /*****************************************************************************\
  2. FILE: ftpcm.cpp - IContextMenu interface
  3. \*****************************************************************************/
  4. #include "priv.h"
  5. #include "ftpcm.h"
  6. #include "util.h"
  7. #include "ftpprop.h"
  8. #include "ftpurl.h"
  9. #include "dialogs.h"
  10. #include "statusbr.h"
  11. #include "newmenu.h"
  12. #include "view.h"
  13. #include "resource.h"
  14. /*****************************************************************************\
  15. *
  16. * VERBINFO, c_rgvi
  17. *
  18. * Information about which capabilities correspond to which verbs.
  19. *
  20. * If the item ID is in the range 0 ... IDC_ITEM_MAX, then it is
  21. * relative to the base address.
  22. *
  23. \*****************************************************************************/
  24. #pragma BEGIN_CONST_DATA
  25. #define CMDSTR_LOGINASA "Login As"
  26. struct VERBINFO
  27. {
  28. UINT idc;
  29. DWORD sfgao;
  30. LPCTSTR ptszCmd;
  31. } c_rgvi[] = {
  32. /* If you edit anything below this comment, make sure to update below */
  33. { IDM_SHARED_EDIT_COPY, SFGAO_CANCOPY, TEXT("copy"), },
  34. #ifdef FEATURE_CUT_MOVE
  35. { IDM_SHARED_EDIT_CUT, SFGAO_CANMOVE, TEXT("cut"), },
  36. #endif // FEATURE_CUT_MOVE
  37. { IDM_SHARED_FILE_LINK, SFGAO_CANLINK, TEXT("link"), },
  38. { IDM_SHARED_FILE_RENAME, SFGAO_CANRENAME, TEXT("rename"), },
  39. { IDM_SHARED_FILE_DELETE, SFGAO_CANDELETE, TEXT("delete"), },
  40. { IDM_SHARED_FILE_PROP, SFGAO_HASPROPSHEET, TEXT("properties"), },
  41. { IDM_SHARED_EDIT_PASTE, SFGAO_DROPTARGET, TEXT("paste"), },
  42. /* CVI_NONREQ is the number of items in c_rgvi up to this point */
  43. /* The following entries must be in IDC_ITEM_* order */
  44. { IDC_ITEM_OPEN, SFGAO_FOLDER, TEXT("open"), },
  45. { IDC_ITEM_EXPLORE, SFGAO_FOLDER, TEXT("explore"),},
  46. { IDC_ITEM_DOWNLOAD, SFGAO_CANCOPY, TEXT("download"),},
  47. { IDC_ITEM_BKGNDPROP, 0, TEXT("backgroundproperties"),},
  48. { IDC_LOGIN_AS, 0, TEXT(CMDSTR_LOGINASA),},
  49. { IDC_ITEM_NEWFOLDER, 0, CMDSTR_NEWFOLDER,},
  50. /* The preceding entries must be in IDC_ITEM_* order */
  51. /* If you edit anything above this comment, make sure to update below */
  52. };
  53. #ifdef FEATURE_CUT_MOVE
  54. #define CVI_NONREQ 7 /* See remarks above */
  55. #else // FEATURE_CUT_MOVE
  56. #define CVI_NONREQ 6 /* See remarks above */
  57. #endif // FEATURE_CUT_MOVE
  58. #define IVI_REQ CVI_NONREQ /* First required verb */
  59. #define IVI_MAX ARRAYSIZE(c_rgvi) /* One past last value index */
  60. #pragma END_CONST_DATA
  61. /*****************************************************************************\
  62. FUNCTION: _RemoveContextMenuItems
  63. Remove context menu items based on attribute flags.
  64. If we have a drop target, ping it to see if the object on the
  65. clipboard is pasteable. If not, then disable Paste. (Shell UI
  66. says that you don't remove Paste, merely disable it.)
  67. Return the number of items removed.
  68. \*****************************************************************************/
  69. int CFtpMenu::_RemoveContextMenuItems(HMENU hmenu, UINT idCmdFirst, DWORD sfgao)
  70. {
  71. int ivi;
  72. int nItemRemoved = 0;
  73. for (ivi = 0; ivi < CVI_NONREQ; ivi++)
  74. {
  75. if (!(sfgao & c_rgvi[ivi].sfgao))
  76. {
  77. EnableMenuItem(hmenu, (c_rgvi[ivi].idc + idCmdFirst), MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
  78. nItemRemoved++;
  79. }
  80. }
  81. // See if the clipboard format is supported
  82. if (sfgao & SFGAO_DROPTARGET)
  83. {
  84. IDataObject *pdto;
  85. DWORD grflEffects = 0; // Clipboard not available
  86. if (SUCCEEDED(OleGetClipboard(&pdto)))
  87. {
  88. CFtpDrop * pfdrop;
  89. if (SUCCEEDED(CFtpDrop_Create(m_pff, m_hwnd, &pfdrop)))
  90. {
  91. grflEffects = pfdrop->GetEffectsAvail(pdto);
  92. pfdrop->Release();
  93. }
  94. pdto->Release();
  95. }
  96. if (!(grflEffects & (DROPEFFECT_COPY | DROPEFFECT_MOVE)))
  97. {
  98. EnableMenuItem(hmenu, (IDM_SHARED_EDIT_PASTE + idCmdFirst),
  99. MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
  100. nItemRemoved++;
  101. }
  102. #ifdef _SOMEDAY_PASTESHORTCUT
  103. if (!(grflEffects & DROPEFFECT_LINK))
  104. {
  105. EnableMenuItem(hmenu, (IDM_SHARED_EDIT_PASTE_SHORTCUT + idCmdFirst),
  106. MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
  107. nItemRemoved++;
  108. }
  109. #endif
  110. }
  111. return nItemRemoved;
  112. }
  113. /*****************************************************************************\
  114. FUNCTION: _AddToRecentDocs
  115. DESCRIPTION:
  116. This method will add the item to the Recent Docs MRU. The pidl parameter
  117. is a fully qualified pidl all the way to the root of the public shell name space
  118. (desktop).
  119. \*****************************************************************************/
  120. HRESULT CFtpMenu::_AddToRecentDocs(LPCITEMIDLIST pidl)
  121. {
  122. // We may want to filter on verb.
  123. SHAddToRecentDocs(SHARD_PIDL, (LPCVOID) pidl);
  124. return S_OK;
  125. }
  126. typedef struct
  127. {
  128. LPCWIRESTR pwSoftLink;
  129. LPWIRESTR pwFtpPath;
  130. DWORD cchSize;
  131. } SOFTLINKDESTCBSTRUCT;
  132. HRESULT CFtpMenu::_SoftLinkDestCB(HINTERNET hint, HINTPROCINFO * phpi, LPVOID pvsldcbs, BOOL * pfReleaseHint)
  133. {
  134. HRESULT hr = S_OK;
  135. WIRECHAR wFrom[MAX_PATH];
  136. SOFTLINKDESTCBSTRUCT * psldcbs = (SOFTLINKDESTCBSTRUCT *) pvsldcbs;
  137. DWORD cchSize = ARRAYSIZE(wFrom);
  138. // Normally, I hate hard coding the buffer size, but passing structs to callbacks is such a pain
  139. // and this won't change.
  140. hr = FtpGetCurrentDirectoryWrap(hint, TRUE, wFrom, cchSize);
  141. if (SUCCEEDED(hr))
  142. {
  143. hr = FtpSetCurrentDirectoryWrap(hint, TRUE, psldcbs->pwSoftLink);
  144. if (SUCCEEDED(hr))
  145. {
  146. hr = FtpGetCurrentDirectoryWrap(hint, TRUE, psldcbs->pwFtpPath, psldcbs->cchSize);
  147. if (SUCCEEDED(hr))
  148. {
  149. hr = FtpSetCurrentDirectoryWrap(hint, TRUE, wFrom);
  150. }
  151. }
  152. }
  153. return hr;
  154. }
  155. LPITEMIDLIST CFtpMenu::GetSoftLinkDestination(LPCITEMIDLIST pidlToSoftLink)
  156. {
  157. LPITEMIDLIST pidlToDest = NULL;
  158. WIRECHAR wSoftLinkName[MAX_PATH];
  159. WIRECHAR wFtpPath[MAX_PATH];
  160. SOFTLINKDESTCBSTRUCT sldcbs = {wSoftLinkName, wFtpPath, ARRAYSIZE(wFtpPath)};
  161. LPCWIRESTR pszName = FtpPidl_GetLastItemWireName(pidlToSoftLink);
  162. StrCpyNA(wSoftLinkName, (pszName ? pszName : ""), ARRAYSIZE(wSoftLinkName));
  163. StrCpyNA(wFtpPath, (pszName ? pszName : ""), ARRAYSIZE(wFtpPath));
  164. // NULL hwnd because I don't want UI.
  165. if (SUCCEEDED(m_pfd->WithHint(NULL, NULL, _SoftLinkDestCB, (LPVOID) &sldcbs, _punkSite, m_pff)))
  166. {
  167. CreateFtpPidlFromUrlPathAndPidl(pidlToSoftLink, m_pff->GetCWireEncoding(), wFtpPath, &pidlToDest);
  168. }
  169. return pidlToDest;
  170. }
  171. // Someday maybe add: (SEE_MASK_UNICODE | SEE_MASK_FLAG_TITLE)
  172. #define SEE_MASK_SHARED (SEE_MASK_FLAG_NO_UI | SEE_MASK_HOTKEY | SEE_MASK_NO_CONSOLE)
  173. #define FILEATTRIB_DIRSOFTLINK (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)
  174. /*****************************************************************************\
  175. FUNCTION: _ApplyOne
  176. DESCRIPTION:
  177. This function will ShellExec() the pidl.
  178. SECURITY ISSUES:
  179. We don't need to worry about the 'Open' verb on folders because that
  180. is always safe. The 'Open' verb on files is safe because we later
  181. redirect the functionality to the original URLMON ftp support which
  182. goes through code download. This displays dialogs, checks certs, and
  183. does all the zones checking and admin policies.
  184. \*****************************************************************************/
  185. HRESULT CFtpMenu::_ApplyOne(CFtpMenu * pfcm, LPCMINVOKECOMMANDINFO pici, LPCTSTR pszCmd, LPCITEMIDLIST pidl)
  186. {
  187. HRESULT hr;
  188. SHELLEXECUTEINFO sei;
  189. LPITEMIDLIST pidlFullPriv = pfcm->m_pff->CreateFullPrivatePidl(pidl);
  190. ZeroMemory(&sei, sizeof(sei));
  191. sei.cbSize = sizeof(sei);
  192. LPITEMIDLIST pidlFullPub = NULL;
  193. // It would be nice to see if the pidl is a SoftLink (FtpPidl_IsSoftLink)
  194. // and if so, step into the directory, get the directory path and then create
  195. // a pidl from that path so we end up showing the user the real destination
  196. // of the softlink. However, we will indefinitely postpone that work
  197. // since it's optinal and has a very low user impact.
  198. if (FILEATTRIB_DIRSOFTLINK == (FILEATTRIB_DIRSOFTLINK & FtpPidl_GetAttributes(pidlFullPriv)))
  199. {
  200. LPITEMIDLIST pidlNew = pfcm->GetSoftLinkDestination(pidlFullPriv);
  201. // Switch pidls if it worked, otherwise, using the original pidl isn't that bad, so it
  202. // will be the fall back case if things don't work out.
  203. if (pidlNew)
  204. {
  205. ILFree(pidlFullPriv);
  206. pidlFullPriv = pidlNew;
  207. }
  208. pidlFullPub = pfcm->m_pff->CreateFullPublicPidl(pidlFullPriv);
  209. }
  210. else
  211. {
  212. // Yes, so we need to use it in the pidl we pass to ShellExecute.
  213. pidlFullPub = ILCombine(pfcm->m_pff->GetPublicRootPidlReference(), pidl);
  214. }
  215. // Titles are excluded because there is no lpTitle in the sei.
  216. // Unicode is excluded because we don't do UNICODE; in fact,
  217. // we filter it out up front!
  218. ASSERT(SEE_MASK_FLAG_NO_UI == CMIC_MASK_FLAG_NO_UI);
  219. ASSERT(SEE_MASK_HOTKEY == CMIC_MASK_HOTKEY);
  220. ASSERT(SEE_MASK_NO_CONSOLE == CMIC_MASK_NO_CONSOLE);
  221. sei.fMask |= SEE_MASK_IDLIST | (pici->fMask & SEE_MASK_SHARED);
  222. sei.hwnd = pici->hwnd;
  223. sei.nShow = pici->nShow;
  224. sei.dwHotKey = pici->dwHotKey;
  225. sei.hIcon = pici->hIcon;
  226. sei.lpIDList = (void *) pidlFullPub;
  227. if (sei.lpIDList)
  228. {
  229. TCHAR szParameters[MAX_URL_STRING];
  230. TCHAR szDirectory[MAX_PATH];
  231. if (pici->lpParameters)
  232. SHAnsiToTChar(pici->lpParameters, szParameters, ARRAYSIZE(szParameters));
  233. if (pici->lpDirectory)
  234. SHAnsiToTChar(pici->lpDirectory, szDirectory, ARRAYSIZE(szDirectory));
  235. sei.lpVerb = pszCmd;
  236. sei.lpParameters = (pici->lpParameters ? szParameters : NULL);
  237. sei.lpDirectory = (pici->lpDirectory ? szDirectory : NULL);
  238. if (ShellExecuteEx(&sei))
  239. {
  240. // Yes, so we need to use it in the pidl we pass to ShellExecute.
  241. LPITEMIDLIST pidlFullPubTarget = ILCombine(pfcm->m_pff->GetPublicTargetPidlReference(), pidl);
  242. if (pidlFullPubTarget)
  243. {
  244. EVAL(SUCCEEDED(pfcm->_AddToRecentDocs(pidlFullPubTarget))); // We don't care if AddToRecent works or not.
  245. ILFree(pidlFullPubTarget);
  246. hr = S_OK;
  247. }
  248. else
  249. hr = E_OUTOFMEMORY;
  250. }
  251. else
  252. hr = HRESULT_FROM_WIN32(GetLastError());
  253. }
  254. else
  255. hr = E_OUTOFMEMORY;
  256. if (pidlFullPub)
  257. ILFree(pidlFullPub);
  258. if (pidlFullPriv)
  259. ILFree(pidlFullPriv);
  260. return hr;
  261. }
  262. /*****************************************************************************\
  263. *
  264. * _InvokeOneCB
  265. *
  266. * Invoke the command on the single pidl.
  267. *
  268. \*****************************************************************************/
  269. int CFtpMenu::_InvokeOneCB(LPVOID pvPidl, LPVOID pv)
  270. {
  271. LPCITEMIDLIST pidl = (LPCITEMIDLIST) pvPidl;
  272. PEII peii = (PEII) pv;
  273. ASSERT(peii && peii->pfcm);
  274. return peii->pfcm->_InvokeOne(pidl, peii);
  275. }
  276. int CFtpMenu::_InvokeOne(LPCITEMIDLIST pidl, PEII peii)
  277. {
  278. ASSERT(ILIsSimple(pidl));
  279. if (GetAsyncKeyState(VK_ESCAPE) >= 0)
  280. {
  281. if (EVAL(SUCCEEDED(peii->hres)))
  282. peii->hres = peii->pfn(peii->pfcm, peii->pici, peii->ptszCmd, pidl);
  283. }
  284. else
  285. peii->hres = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  286. return SUCCEEDED(peii->hres);
  287. }
  288. /*****************************************************************************\
  289. *
  290. * _EnumInvoke
  291. *
  292. * Invoke the command on each object in the list, assuming that
  293. * permissions are properly set. (We need to check the permissions
  294. * in case somebody randomly threw the verb at us.)
  295. *
  296. \*****************************************************************************/
  297. STDMETHODIMP CFtpMenu::_EnumInvoke(LPCMINVOKECOMMANDINFO pici, INVOKEPROC pfn, LPCTSTR pszCmd)
  298. {
  299. EII eii;
  300. eii.pfcm = this;
  301. eii.pici = pici;
  302. eii.pfn = pfn;
  303. eii.ptszCmd = pszCmd;
  304. eii.hres = S_OK;
  305. if (m_pflHfpl->GetCount())
  306. m_pflHfpl->Enum(_InvokeOneCB, (LPVOID) &eii);
  307. else
  308. _InvokeOne(c_pidlNil, &eii);
  309. return eii.hres;
  310. }
  311. /*****************************************************************************\
  312. *
  313. * _InvokeRename
  314. *
  315. * Rename the object to the indicated name.
  316. *
  317. * The rename verb should have been enabled only if the pidl list
  318. * is singleton. Of course, that doesn't prevent some random person
  319. * from throwing the word "rename" at us from out of the blue, so
  320. * we need to remain on guard.
  321. *
  322. * _UNOBVIOUS_: If the user does an in-place rename, we don't get
  323. * a "rename" command invoked against our context menu. Instead,
  324. * the shell goes straight for the SetNameOf method in the ShellFolder.
  325. * Which means that we cannot put UI in the context menu (which is the
  326. * obvious place for it, because it has a CMIC_MASK_FLAG_NO_UI bit);
  327. * we must put it into SetNameOf, which is annoying because it means
  328. * there is no way to programmatically perform a SetNameOf without UI.
  329. *
  330. * _SOMEDAY_
  331. * We fix this unobvious-ness by passing the CMIC_MASK_FLAG_NO_UI bit
  332. * through to our SetNameOf backdoor, so you can programmatically
  333. * rename a file without UI by going through the IContextMenu.
  334. *
  335. \*****************************************************************************/
  336. HRESULT CFtpMenu::_InvokeRename(LPCMINVOKECOMMANDINFO pici)
  337. {
  338. HRESULT hr;
  339. if (EVAL((m_sfgao & SFGAO_CANRENAME) && m_pfd))
  340. {
  341. ASSERT(m_pflHfpl->GetCount() == 1);
  342. if (EVAL(pici->lpParameters))
  343. {
  344. TCHAR szParams[MAX_URL_STRING];
  345. ASSERT(pici->hwnd);
  346. SHAnsiToTChar(pici->lpParameters, szParams, ARRAYSIZE(szParams));
  347. hr = m_pfd->SetNameOf(m_pff, pici->hwnd, m_pflHfpl->GetPidl(0), szParams, SHGDN_INFOLDER, 0);
  348. }
  349. else
  350. hr = E_INVALIDARG; // Arguments required
  351. }
  352. else
  353. hr = E_ACCESSDENIED; // Can't rename this
  354. return hr;
  355. }
  356. /*****************************************************************************\
  357. * _InvokeCutCopy
  358. *
  359. * Cut or copy the selection to the OLE clipboard. No big deal.
  360. *
  361. * Note that GetUIObjectOfHfpl(IID_IDataObject) will fail if we
  362. * are talking about ourself. Maybe it shouldn't but it does today.
  363. \*****************************************************************************/
  364. HRESULT CFtpMenu::_InvokeCutCopy(UINT_PTR id, LPCMINVOKECOMMANDINFO pici)
  365. {
  366. IDataObject * pdo;
  367. HRESULT hr;
  368. hr = m_pff->GetUIObjectOfHfpl(pici->hwnd, m_pflHfpl, IID_IDataObject, (LPVOID *)&pdo, m_fBackground);
  369. if (SUCCEEDED(hr))
  370. {
  371. DWORD dwEffect = ((DFM_CMD_COPY == id) ? DROPEFFECT_COPY : DROPEFFECT_MOVE);
  372. EVAL(SUCCEEDED(DataObj_SetPreferredEffect(pdo, dwEffect)));
  373. ShellFolderView_SetPoints(m_hwnd, pdo);
  374. hr = OleSetClipboard(pdo); // Will do its own AddRef
  375. ShellFolderView_SetClipboard(m_hwnd, id);
  376. if (pdo)
  377. pdo->Release();
  378. }
  379. else
  380. {
  381. // This will only happen in out of memory cases, so we are assuming the calling code
  382. // display error UI.
  383. }
  384. return hr;
  385. }
  386. /*****************************************************************************\
  387. FUNCTION: _DoDrop
  388. DESCRIPTION:
  389. The user just did a Paste on FTP so we want to do the operation.
  390. We will use our Drag & Drop code to carry out the operation. We don't
  391. currently support optimized FTP operations but a lot could be done if
  392. we did.
  393. First we need to find out if the caller did "Cut" or "Copy" to create
  394. the IDataObject. We can find out by asking the IDataObject for the
  395. CFSTR_PREFERREDDROPEFFECT.
  396. \*****************************************************************************/
  397. HRESULT CFtpMenu::_DoDrop(IDropTarget * pdt, IDataObject * pdo)
  398. {
  399. POINTL pt = {0, 0};
  400. DWORD dwEffect = DROPEFFECT_COPY; // Default
  401. HRESULT hr = DataObj_GetDWORD(pdo, g_dropTypes[DROP_PrefDe].cfFormat, &dwEffect);
  402. #ifndef FEATURE_CUT_MOVE
  403. dwEffect = DROPEFFECT_COPY; // Forcibly remove the MOVE effect
  404. #endif // FEATURE_CUT_MOVE
  405. hr = pdt->DragEnter(pdo, MK_LBUTTON, pt, &dwEffect);
  406. if (EVAL(SUCCEEDED(hr)) && dwEffect)
  407. {
  408. #ifndef FEATURE_CUT_MOVE
  409. dwEffect = DROPEFFECT_COPY; // Forcibly remove the MOVE effect
  410. #endif // FEATURE_CUT_MOVE
  411. hr = pdt->Drop(pdo, MK_LBUTTON, pt, &dwEffect);
  412. }
  413. else
  414. pdt->DragLeave();
  415. return hr;
  416. }
  417. /*****************************************************************************\
  418. *
  419. * _InvokePaste
  420. *
  421. * Copy from the OLE clipboard into the selcted folder (which might
  422. * be ourselves).
  423. *
  424. \*****************************************************************************/
  425. HRESULT CFtpMenu::_InvokePaste(LPCMINVOKECOMMANDINFO pici)
  426. {
  427. HRESULT hres = E_FAIL;
  428. // The code that enables/disables "Paste" in the context menu
  429. // should prevent getting this far. The only other callers
  430. // could possibly be buggy code callers. So we ignore them.
  431. if (EVAL(m_sfgao & SFGAO_DROPTARGET))
  432. {
  433. IDataObject *pdto;
  434. hres = OleGetClipboard(&pdto);
  435. if (SUCCEEDED(hres))
  436. {
  437. IDropTarget *pdt;
  438. hres = m_pff->GetUIObjectOfHfpl(pici->hwnd, m_pflHfpl, IID_IDropTarget, (LPVOID *)&pdt, m_fBackground);
  439. if (SUCCEEDED(hres))
  440. {
  441. hres = _DoDrop(pdt, pdto);
  442. if (pdt)
  443. pdt->Release();
  444. }
  445. else
  446. {
  447. // This will only happen in out of memory cases so we are assuming the calling
  448. // code will display error UI.
  449. }
  450. if (pdto)
  451. pdto->Release();
  452. }
  453. else
  454. {
  455. // We expect calling code to display out of memory err UI. The only other err could be
  456. // internal clipboard state err, which the user wouldn't understand even if we explained
  457. // it to them.
  458. }
  459. }
  460. return hres;
  461. }
  462. //===========================
  463. // *** IContextMenu Interface ***
  464. //===========================
  465. /*****************************************************************************\
  466. FUNCTION: _ContainsForgroundItems
  467. DESCRIPTION:
  468. We want to know if the user selected items in the view and then invoked
  469. some menu (Context Menu, File Menu, CaptionBar icon menu, etc.). Normally
  470. this is as simple as seeing if (0 == m_pflHfpl->GetCount()). However,
  471. there is one other nasty case where (1 == m_pflHfpl->GetCount()) and
  472. the user still didn't select anything. This case happens when the user
  473. is at the root of a FTP share and the CaptionBar menu is dropped down.
  474. In that case, the single pidl is the pidl to the ftp root.
  475. \*****************************************************************************/
  476. BOOL CFtpMenu::_ContainsForgroundItems(void)
  477. {
  478. BOOL fIsForground = (0 != m_pflHfpl->GetCount());
  479. if (fIsForground && (1 == m_pflHfpl->GetCount()))
  480. {
  481. LPITEMIDLIST pidl = m_pflHfpl->GetPidl(0);
  482. if (FtpID_IsServerItemID(pidl) && ILIsEmpty(_ILNext(pidl)))
  483. {
  484. if (!m_pfd)
  485. {
  486. CFtpSite * pfs;
  487. // In this strange case, our m_pfd is NULL, so we need
  488. // to create it from pidl.
  489. if (SUCCEEDED(SiteCache_PidlLookup(pidl, FALSE, m_pff->GetItemAllocatorDirect(), &pfs)))
  490. {
  491. pfs->GetFtpDir(pidl, &m_pfd);
  492. pfs->Release();
  493. }
  494. }
  495. fIsForground = FALSE;
  496. }
  497. }
  498. return fIsForground;
  499. }
  500. BOOL CFtpMenu::_IsCallerCaptionBar(UINT indexMenu, UINT uFlags)
  501. {
  502. BOOL fFromCaptionBar;
  503. if ((0 == uFlags) && (1 == indexMenu))
  504. fFromCaptionBar = TRUE;
  505. else
  506. fFromCaptionBar = FALSE;
  507. return fFromCaptionBar;
  508. }
  509. /*****************************************************************************\
  510. FUNCTION: IContextMenu::QueryContextMenu
  511. DESCRIPTION:
  512. Given an existing context menu hmenu, insert new context menu
  513. items at location indexMenu (indexMenu = index to menu indexMenu), returning the
  514. number of menu items added.
  515. The incoming flags control how much goop we add to the menu.
  516. It is important not to add "Delete", "Rename", etc., to context
  517. menus that come from shortcuts, else the user gets hit with
  518. two "Delete" verbs, one to delete the object from the FTP site,
  519. and the other to delete the shortcut. How confusing...
  520. hmenu - destination menu
  521. indexMenu - location at which menu items should be inserted
  522. idCmdFirst - first available menu identifier
  523. idCmdLast - first unavailable menu identifier
  524. _UNDOCUMENTED_: The "shared" menu items are not documented.
  525. Particularly gruesome, because the "shared" menu items are the
  526. only way to get Rename, Delete, etc. to work. You can't roll
  527. your own, because those magics are handled partly in the
  528. enclosing shell view.
  529. _UNOBVIOUS_: The context menu for the folder itself is
  530. extremely squirly. It's not like a normal context menu.
  531. Rather, you add the "New" verb, and any custom verbs, but
  532. none of the standard folder verbs.
  533. PARAMS:
  534. Often, we need to key off strange parameter heiristicts to
  535. determine who our caller is so we don't enable certain items.
  536. "Rename" from the from CaptionBar is one example. Here are what
  537. we are passed in the different situations:
  538. CaptionBar:
  539. QCM(hmenu, 1, idCmdFirst, idCmdLast, 0) m_pflHfpl contains 1
  540. FileMenu w/1 Selected:
  541. QCM(hmenu, 0, idCmdFirst, idCmdLast, CMF_DVFILE | CMF_NODEFAULT) m_pflHfpl contains 1
  542. 0 Items Selected:
  543. QCM(hmenu, -1, idCmdFirst, idCmdLast, 0) m_pflHfpl contains 0
  544. 1 Items Selected:
  545. QCM(hmenu, 0, idCmdFirst, idCmdLast, CMF_CANRENAME) m_pflHfpl contains 1
  546. 2 Items Selected:
  547. QCM(hmenu, 0, idCmdFirst, idCmdLast, CMF_CANRENAME) m_pflHfpl contains 2
  548. \*****************************************************************************/
  549. HRESULT CFtpMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
  550. {
  551. HRESULT hr = S_OK;
  552. // HACK: I assume that they are querying during a WM_INITMENUPOPUP or equivelant
  553. GetCursorPos(&m_ptNewItem);
  554. m_uFlags = uFlags;
  555. if (!m_fBackground)
  556. {
  557. BOOL fAllFolders = m_pflHfpl->AreAllFolders();
  558. // _UNDOCUMENTED_: CMF_DVFILE is not a documented flag.
  559. if (!(uFlags & (CMF_DVFILE | CMF_VERBSONLY)))
  560. {
  561. DWORD sfgao = m_sfgao;
  562. // We don't support Delete or Rename from the Caption Bar
  563. if (_IsCallerCaptionBar(indexMenu, uFlags))
  564. sfgao &= ~(SFGAO_CANDELETE | SFGAO_CANRENAME); // Clear these two.
  565. // Not on the "File" menu, and not from a shortcut.
  566. // Add the "Delete", "Rename", etc. stuff, then go
  567. // enable/disable them as needed.
  568. AddToPopupMenu(hmenu, IDM_ITEMCONTEXT, IDM_M_SHAREDVERBS, indexMenu, idCmdFirst, idCmdLast, MM_ADDSEPARATOR);
  569. _RemoveContextMenuItems(hmenu, idCmdFirst, sfgao);
  570. }
  571. // Add Download if there is anything inside.
  572. // The assertion makes sure that idCmdLast is set properly.
  573. ASSERT(IDC_ITEM_DOWNLOAD > IDC_ITEM_OPEN);
  574. if (!_IsCallerCaptionBar(indexMenu, uFlags))
  575. {
  576. // Don't add "Copy To Folder" in the caption bar because it doesn't work for the root of
  577. // an ftp server. We aren't going to support it in subdirectories.
  578. AddToPopupMenu(hmenu, IDM_ITEMCONTEXT, IDM_M_VERBS, indexMenu, idCmdFirst, idCmdLast, MM_ADDSEPARATOR);
  579. }
  580. if (!(uFlags & CMF_NODEFAULT))
  581. SetMenuDefaultItem(hmenu, IDC_ITEM_DOWNLOAD + idCmdFirst, MM_ADDSEPARATOR);
  582. AddToPopupMenu(hmenu, IDM_ITEMCONTEXT, (fAllFolders ? IDM_M_FOLDERVERBS : IDM_M_FILEVERBS), indexMenu, idCmdFirst,
  583. idCmdLast, (_IsCallerCaptionBar(indexMenu, uFlags) ? 0 : MM_ADDSEPARATOR));
  584. if (fAllFolders && (SHELL_VERSION_W95NT4 == GetShellVersion()))
  585. {
  586. // On shell32 v3 (Win95 & NT4) I remove the 'Explore' verb because the shell has bugs
  587. // that aren't fixable are easy to fix.
  588. EVAL(DeleteMenu(hmenu, (IDC_ITEM_EXPLORE + idCmdFirst), MF_BYCOMMAND));
  589. TraceMsg(TF_FTPOPERATION, "QueryContextMenu() Removing 'Explorer' because it's shell v3");
  590. SetMenuDefaultItem(hmenu, idCmdFirst + IDC_ITEM_OPEN, 0);
  591. }
  592. else if (!(uFlags & CMF_NODEFAULT))
  593. SetMenuDefaultItem(hmenu, idCmdFirst + (((uFlags & CMF_EXPLORE) && fAllFolders)? IDC_ITEM_EXPLORE : IDC_ITEM_OPEN), 0);
  594. }
  595. else
  596. { // Folder background menu
  597. AddToPopupMenu(hmenu, IDM_ITEMCONTEXT, IDM_M_BACKGROUNDVERBS, indexMenu, idCmdFirst, idCmdLast, MM_ADDSEPARATOR);
  598. // Did the menu come from the file menu?
  599. if (CMF_DVFILE == (CMF_DVFILE & uFlags))
  600. {
  601. // Yes, then we want to delete the "Properties" background menu item because one
  602. // was already merged in for the selected files. The other Properties will
  603. // be there but grayed out if nothing was selected.
  604. EVAL(DeleteMenu(hmenu, (IDC_ITEM_BKGNDPROP + idCmdFirst), MF_BYCOMMAND));
  605. }
  606. MergeInToPopupMenu(hmenu, IDM_M_BACKGROUND_POPUPMERGE, indexMenu, idCmdFirst, idCmdLast, MM_ADDSEPARATOR);
  607. }
  608. if (EVAL(SUCCEEDED(hr)))
  609. hr = ResultFromShort(IDC_ITEM_MAX);
  610. _SHPrettyMenu(hmenu);
  611. return hr;
  612. }
  613. /*****************************************************************************\
  614. *
  615. * IContextMenu::GetCommandString
  616. *
  617. * Somebody wants to convert a command id into a string of some sort.
  618. *
  619. \*****************************************************************************/
  620. HRESULT CFtpMenu::GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT *pwRsv, LPSTR pszName, UINT cchMax)
  621. {
  622. HRESULT hr = E_FAIL;
  623. BOOL fUnicode = FALSE;
  624. if (idCmd < IDC_ITEM_MAX)
  625. {
  626. switch (uFlags)
  627. {
  628. case GCS_HELPTEXTW:
  629. fUnicode = TRUE;
  630. // Fall thru...
  631. case GCS_HELPTEXTA:
  632. GetHelpText:
  633. if (EVAL(cchMax))
  634. {
  635. BOOL fResult;
  636. pszName[0] = '\0';
  637. if (fUnicode)
  638. fResult = LoadStringW(HINST_THISDLL, IDS_ITEM_HELP((UINT)idCmd), (LPWSTR)pszName, cchMax);
  639. else
  640. fResult = LoadStringA(HINST_THISDLL, IDS_ITEM_HELP((UINT)idCmd), pszName, cchMax);
  641. if (EVAL(fResult))
  642. hr = S_OK;
  643. else
  644. hr = E_INVALIDARG;
  645. }
  646. else
  647. hr = E_INVALIDARG;
  648. break;
  649. case GCS_VALIDATEW:
  650. case GCS_VALIDATEA:
  651. hr = S_OK;
  652. break;
  653. case GCS_VERBW:
  654. fUnicode = TRUE;
  655. // Fall thru...
  656. case GCS_VERBA:
  657. {
  658. int ivi;
  659. for (ivi = 0; ivi < IVI_MAX; ivi++)
  660. {
  661. if (c_rgvi[ivi].idc == idCmd)
  662. {
  663. if (fUnicode)
  664. SHTCharToUnicode(c_rgvi[ivi].ptszCmd, (LPWSTR)pszName, cchMax);
  665. else
  666. SHTCharToAnsi(c_rgvi[ivi].ptszCmd, pszName, cchMax);
  667. hr = S_OK;
  668. break;
  669. }
  670. }
  671. if (!EVAL(ivi < IVI_MAX))
  672. hr = E_INVALIDARG;
  673. break;
  674. }
  675. default:
  676. hr = E_NOTIMPL;
  677. break;
  678. }
  679. }
  680. else
  681. {
  682. // _UNOBVIOUS_: Another place where PASTE rears its ugly head.
  683. // We must generate the help text for it ourselves, even though
  684. // the menu item "sort of" belongs to the shell.
  685. if ((idCmd == SHARED_EDIT_PASTE) &&
  686. ((uFlags == GCS_HELPTEXTW) || (uFlags == GCS_HELPTEXTA)))
  687. {
  688. goto GetHelpText;
  689. }
  690. hr = E_INVALIDARG;
  691. }
  692. return hr;
  693. }
  694. HRESULT UpdateDeleteProgressStr(IProgressDialog * ppd, LPCTSTR pszFileName)
  695. {
  696. HRESULT hr = E_FAIL;
  697. TCHAR szTemplate[MAX_PATH];
  698. if (EVAL(LoadString(HINST_THISDLL, IDS_DELETING, szTemplate, ARRAYSIZE(szTemplate))))
  699. {
  700. TCHAR szStatusStr[MAX_PATH];
  701. WCHAR wzStatusStr[MAX_PATH];
  702. wnsprintf(szStatusStr, ARRAYSIZE(szStatusStr), szTemplate, pszFileName);
  703. SHTCharToUnicode(szStatusStr, wzStatusStr, ARRAYSIZE(wzStatusStr));
  704. EVAL(SUCCEEDED(hr = ppd->SetLine(2, wzStatusStr, FALSE, NULL)));
  705. }
  706. return hr;
  707. }
  708. HRESULT FtpChangeNotifyDirPatch(HWND hwnd, LONG wEventId, CFtpFolder * pff, LPCITEMIDLIST pidlFull, LPCITEMIDLIST pidl2, BOOL fTopLevel)
  709. {
  710. HRESULT hr = S_OK;
  711. LPITEMIDLIST pidlParent = ILClone(pidlFull);
  712. if (pidlParent)
  713. {
  714. ILRemoveLastID(pidlParent);
  715. CFtpDir * pfd = pff->GetFtpDirFromPidl(pidlParent);
  716. if (pfd)
  717. {
  718. FtpChangeNotify(hwnd, wEventId, pff, pfd, ILFindLastID(pidlFull), pidl2, fTopLevel);
  719. pfd->Release();
  720. }
  721. ILFree(pidlParent);
  722. }
  723. return hr;
  724. }
  725. // The following struct is used when recursively downloading
  726. // files/dirs from the FTP server after a "Download" verb.
  727. typedef struct tagDELETESTRUCT
  728. {
  729. LPCITEMIDLIST pidlRoot; // Base URL of the Download Source
  730. CFtpFolder * pff; // Allocator to create temp pidls.
  731. IMalloc * pm; // Allocator to create temp pidls.
  732. LPCMINVOKECOMMANDINFO pdoi; // Our call.
  733. HWND hwnd; // HWND for UI
  734. CStatusBar * psb; // Used to display info during the delete
  735. IProgressDialog * ppd; // Used to display progress during the delete.
  736. DWORD dwTotalFiles; // How many files are there to delete total.
  737. DWORD dwDeletedFiles; // How many files have already been deleted.
  738. BOOL fInDeletePass; // Are we in the 'Count Files to Delete' or 'Delete Files' pass?
  739. } DELETESTRUCT;
  740. /*****************************************************************************\
  741. FUNCTION: DeleteItemCB
  742. DESCRIPTION:
  743. This function will download the specified item and it's contents if it
  744. is a directory.
  745. \*****************************************************************************/
  746. HRESULT _DeleteItemPrep(HINTERNET hint, LPCITEMIDLIST pidlFull, BOOL fIsTopLevel, DELETESTRUCT * pDelete)
  747. {
  748. HRESULT hr = S_OK;
  749. if (pDelete->ppd && pDelete->ppd->HasUserCancelled())
  750. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  751. if (SUCCEEDED(hr)) // May have been cancelled
  752. {
  753. DWORD dwError = 0;
  754. TCHAR szStatus[MAX_PATH];
  755. FtpPidl_GetLastFileDisplayName(pidlFull, szStatus, ARRAYSIZE(szStatus));
  756. if (pDelete->fInDeletePass && pDelete->psb)
  757. {
  758. pDelete->psb->SetStatusMessage(IDS_DELETING, szStatus);
  759. }
  760. if (pDelete->fInDeletePass && pDelete->ppd)
  761. {
  762. EVAL(SUCCEEDED(UpdateDeleteProgressStr(pDelete->ppd, szStatus)));
  763. }
  764. // Is this a dir/folder that we need to recurse into? OR
  765. // Is this a SoftLink?
  766. if ((FILE_ATTRIBUTE_DIRECTORY & FtpPidl_GetAttributes(pidlFull)) ||
  767. (0 == FtpPidl_GetAttributes(pidlFull)))
  768. {
  769. // This is the head of the recursion. We will do nothing now and we will
  770. // wait to delete the dir in the recursion tail because we need to wait
  771. // until all the files are gone.
  772. // Don't delete softlinks because of the recursion problem.
  773. }
  774. else
  775. {
  776. if (pDelete->fInDeletePass)
  777. {
  778. if (pDelete->ppd)
  779. EVAL(SUCCEEDED(pDelete->ppd->SetProgress(pDelete->dwDeletedFiles, pDelete->dwTotalFiles)));
  780. // Contemplate adding a callback function in order to feed the status bar.
  781. hr = FtpDeleteFileWrap(hint, TRUE, FtpPidl_GetLastItemWireName(pidlFull));
  782. if (FAILED(hr))
  783. {
  784. // We need to display the error now while the extended error info is still valid.
  785. // This is because as we walk out of the resursive call, we will be calling
  786. // FtpSetCurrentDirectory() which will wipe clean the extended error msg.
  787. if (FAILED(hr) && (HRESULT_FROM_WIN32(ERROR_CANCELLED) != hr))
  788. {
  789. DisplayWininetError(pDelete->hwnd, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_DELETE, IDS_FTPERR_WININET, MB_OK, pDelete->ppd);
  790. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); // Wrong permissions
  791. }
  792. }
  793. else
  794. FtpChangeNotifyDirPatch(pDelete->hwnd, SHCNE_DELETE, pDelete->pff, pidlFull, NULL, fIsTopLevel);
  795. pDelete->dwDeletedFiles++;
  796. // TraceMsg(TF_FTPOPERATION, "DeleteItemCB() FtpDeleteFileA() returned dwError=%#08lx. File=%s", dwError, FtpPidl_GetLastFileDisplayName(pidlFull));
  797. }
  798. else
  799. pDelete->dwTotalFiles++;
  800. }
  801. }
  802. return hr;
  803. }
  804. HRESULT _DeleteItemCleanUp(HRESULT hr, DELETESTRUCT * pDelete)
  805. {
  806. if (pDelete->ppd && pDelete->ppd->HasUserCancelled())
  807. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  808. if (pDelete->fInDeletePass) // Only display errors and fire ChangeNotify if in Delete pass.
  809. {
  810. if ((FAILED(hr)) && (HRESULT_FROM_WIN32(ERROR_CANCELLED) != hr))
  811. {
  812. int nResult = DisplayWininetError(pDelete->hwnd, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_DELETE, IDS_FTPERR_WININET, MB_OK, pDelete->ppd);
  813. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); // Don't display any more error dialogs.
  814. }
  815. }
  816. return hr;
  817. }
  818. HRESULT FtpRemoveDirectoryWithCN(HWND hwnd, HINTERNET hint, CFtpFolder * pff, LPCITEMIDLIST pidlFull, BOOL fIsTopLevel)
  819. {
  820. HRESULT hr = S_OK;
  821. hr = FtpRemoveDirectoryWrap(hint, TRUE, FtpPidl_GetLastItemWireName(pidlFull));
  822. if (SUCCEEDED(hr))
  823. {
  824. hr = FtpChangeNotifyDirPatch(hwnd, SHCNE_RMDIR, pff, pidlFull, NULL, fIsTopLevel);
  825. TraceMsg(TF_WININET_DEBUG, "FtpRemoveDirectoryWithCN() FtpRemoveDirectory(%hs) returned %#08lx", FtpPidl_GetLastItemWireName(pidlFull), hr);
  826. }
  827. return hr;
  828. }
  829. INT ILCountItemIDs(LPCITEMIDLIST pidl)
  830. {
  831. INT nCount = 0;
  832. if (pidl)
  833. {
  834. while (!ILIsEmpty(pidl))
  835. {
  836. pidl = _ILNext(pidl);
  837. nCount++;
  838. }
  839. }
  840. return nCount;
  841. }
  842. /*****************************************************************************\
  843. FUNCTION: _IsTopLevel
  844. DESCRIPTION:
  845. \*****************************************************************************/
  846. BOOL _IsTopLevel(LPCITEMIDLIST pidlRoot, LPCITEMIDLIST pidlCurrent)
  847. {
  848. INT nRoot = ILCountItemIDs(pidlRoot);
  849. INT nCurrent = ILCountItemIDs(pidlCurrent);
  850. // It is the root if nCurrent has no more than 1 more than nRoot
  851. return (((nRoot + 1) >= nCurrent) ? TRUE : FALSE);
  852. }
  853. /*****************************************************************************\
  854. FUNCTION: DeleteItemCB
  855. DESCRIPTION:
  856. This function will download the specified item and it's contents if it
  857. is a directory. Since this is in the line of recursion, we need to have the
  858. stack be as small as possible. Therefore, we call _DeleteItemPrep() to use
  859. as much stack as needed to do the majority of the work and the clean up the
  860. stack before we do the recursion. The only information we need from it is
  861. pszUrlPath which we put on the stack and heap and clean up our selves.
  862. \*****************************************************************************/
  863. HRESULT DeleteItemCB(LPVOID pvFuncCB, HINTERNET hint, LPCITEMIDLIST pidlFull, BOOL * pfValidhinst, LPVOID pvData)
  864. {
  865. DELETESTRUCT * pDelete = (DELETESTRUCT *) pvData;
  866. BOOL fIsTopLevel = _IsTopLevel(pDelete->pidlRoot, pidlFull);
  867. HRESULT hr = _DeleteItemPrep(hint, pidlFull, fIsTopLevel, pDelete);
  868. if (SUCCEEDED(hr) && (FILE_ATTRIBUTE_DIRECTORY & FtpPidl_GetAttributes(pidlFull)))
  869. {
  870. hr = EnumFolder((LPFNPROCESSITEMCB) pvFuncCB, hint, pidlFull, pDelete->pff->GetCWireEncoding(), pfValidhinst, pvData);
  871. if (SUCCEEDED(hr))
  872. {
  873. if (pDelete->fInDeletePass)
  874. {
  875. hr = FtpRemoveDirectoryWithCN(pDelete->hwnd, hint, pDelete->pff, pidlFull, fIsTopLevel);
  876. // TraceMsg(TF_FTPOPERATION, "DeleteItemCB() FtpRemoveDirectoryA() returned hr=%#08lx.", hr);
  877. pDelete->dwDeletedFiles++;
  878. }
  879. else
  880. pDelete->dwTotalFiles++;
  881. }
  882. }
  883. hr = _DeleteItemCleanUp(hr, pDelete);
  884. return hr;
  885. }
  886. /*****************************************************************************\
  887. FUNCTION: _InvokeLoginAsVerb
  888. DESCRIPTION:
  889. \*****************************************************************************/
  890. HRESULT CFtpMenu::_InvokeLoginAsVerb(LPCMINVOKECOMMANDINFO pici)
  891. {
  892. HRESULT hr = E_FAIL;
  893. if (EVAL(m_pfd))
  894. hr = LoginAs(pici->hwnd, m_pff, m_pfd, _punkSite);
  895. return hr;
  896. }
  897. /*****************************************************************************\
  898. FUNCTION: _InvokeNewFolderVerb
  899. DESCRIPTION:
  900. The user just selected "New Folder", so we need to create a new folder.
  901. \*****************************************************************************/
  902. HRESULT CFtpMenu::_InvokeNewFolderVerb(LPCMINVOKECOMMANDINFO pici)
  903. {
  904. HRESULT hr = E_FAIL;
  905. if (m_pfd)
  906. hr = CreateNewFolder(m_hwnd, m_pff, m_pfd, _punkSite, (m_uFlags & CMF_DVFILE), m_ptNewItem);
  907. return hr;
  908. }
  909. /*****************************************************************************\
  910. FUNCTION: _InvokeDeleteVerb
  911. DESCRIPTION:
  912. The user just selected file(s) and/or folder(s) and selected the
  913. "download" verb. We need to:
  914. 1. Display UI to ask the user for the destination directory.
  915. 2. Download each item (pidl) into that directory.
  916. \*****************************************************************************/
  917. HRESULT CFtpMenu::_InvokeDeleteVerb(LPCMINVOKECOMMANDINFO pici)
  918. {
  919. HRESULT hr = S_OK;
  920. if (EVAL(m_pfd))
  921. {
  922. if (m_sfgao & SFGAO_CANDELETE)
  923. {
  924. if (!(pici->fMask & CMIC_MASK_FLAG_NO_UI))
  925. {
  926. ASSERT(pici->hwnd);
  927. switch (FtpConfirmDeleteDialog(ChooseWindow(pici->hwnd, m_hwnd), m_pflHfpl, m_pff))
  928. {
  929. case IDC_REPLACE_YES:
  930. hr = S_OK;
  931. break;
  932. default:
  933. // FALLTHROUGH
  934. case IDC_REPLACE_CANCEL:
  935. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); // Cancel all copies.
  936. break;
  937. case IDC_REPLACE_NO:
  938. hr = S_FALSE;
  939. break;
  940. }
  941. }
  942. else
  943. hr = S_OK;
  944. if (hr == S_OK)
  945. {
  946. CStatusBar * psb = _GetStatusBar();
  947. IProgressDialog * ppd = CProgressDialog_CreateInstance(IDS_DELETE_TITLE, IDA_FTPDELETE);
  948. LPITEMIDLIST pidlRoot = ILClone(m_pfd->GetPidlReference());
  949. DELETESTRUCT delStruct = {pidlRoot, m_pff, m_pff->m_pm, pici, ChooseWindow(pici->hwnd, m_hwnd), psb, ppd, 0, 0, FALSE};
  950. if (EVAL(SUCCEEDED(hr)))
  951. {
  952. HINTERNET hint;
  953. m_pfd->GetHint(NULL, NULL, &hint, _punkSite, m_pff);
  954. if (hint)
  955. {
  956. HCURSOR hCursorOld = SetCursor(LoadCursor(NULL, IDC_WAIT));
  957. if (EVAL(ppd))
  958. {
  959. WCHAR wzProgressDialogStr[MAX_PATH];
  960. HWND hwndParent = NULL;
  961. // DefView (defview.cpp CDefView::QueryInterface()) doesn't support IOleWindow so our
  962. // progress dialog isn't correctly parented.
  963. // If the caller was nice enough to SetSite() with their punk, I will be nice enough to make
  964. // their window may progress dialog's parent window.
  965. IUnknown_GetWindow(_punkSite, &hwndParent);
  966. if (!hwndParent)
  967. hwndParent = m_hwnd;
  968. // Normally we always want UI, but in one case we don't. If the
  969. // user does a DROPEFFECT_MOVE, it really is a DROPEFFECT_COPY
  970. // and then a IContextMenu::InvokeCommand(SZ_VERB_DELETEA).
  971. // The progress was done in the copy thread and isn't needed
  972. // in the delete thread.
  973. // ASSERT(hwndParent);
  974. // We give a NULL punkEnableModless because we don't want to go modal.
  975. EVAL(SUCCEEDED(ppd->StartProgressDialog(hwndParent, NULL, PROGDLG_AUTOTIME, NULL)));
  976. // Tell the user we are calculating how long it will take.
  977. if (EVAL(LoadStringW(HINST_THISDLL, IDS_PROGRESS_DELETETIMECALC, wzProgressDialogStr, ARRAYSIZE(wzProgressDialogStr))))
  978. EVAL(SUCCEEDED(ppd->SetLine(2, wzProgressDialogStr, FALSE, NULL)));
  979. }
  980. // Tell the user we are calculating how long it will take.
  981. hr = m_pflHfpl->RecursiveEnum(pidlRoot, DeleteItemCB, hint, (LPVOID) &delStruct);
  982. if (ppd)
  983. {
  984. // Reset because RecursiveEnum(DeleteItemCB) can take a long time and the estimated time
  985. // is based on the time between ::StartProgressDialog() and the first
  986. // ::SetProgress() call.
  987. EVAL(SUCCEEDED(ppd->Timer(PDTIMER_RESET, NULL)));
  988. }
  989. delStruct.fInDeletePass = TRUE;
  990. if (HRESULT_FROM_WIN32(ERROR_CANCELLED) != hr) // This is the only error we care about.
  991. {
  992. m_pflHfpl->UseCachedDirListings(TRUE); // Get the perf advantage now because we just updated the cache a few lines up.
  993. hr = m_pflHfpl->RecursiveEnum(pidlRoot, DeleteItemCB, hint, (LPVOID) &delStruct);
  994. }
  995. if (FAILED(hr) && (HRESULT_FROM_WIN32(ERROR_CANCELLED) != hr))
  996. {
  997. DisplayWininetError(pici->hwnd, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_DELETE, IDS_FTPERR_WININET, MB_OK, ppd);
  998. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); // Wrong permissions
  999. }
  1000. if (psb)
  1001. psb->SetStatusMessage(IDS_EMPTY, NULL);
  1002. if (ppd)
  1003. {
  1004. ppd->StopProgressDialog();
  1005. ppd->Release();
  1006. }
  1007. ILFree(pidlRoot);
  1008. SetCursor(hCursorOld); // Restore old cursor.
  1009. }
  1010. m_pfd->ReleaseHint(hint);
  1011. }
  1012. }
  1013. }
  1014. else
  1015. {
  1016. DisplayWininetError(pici->hwnd, TRUE, ResultFromScode(E_ACCESSDENIED), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_DELETE, IDS_FTPERR_WININET, MB_OK, NULL);
  1017. hr = E_ACCESSDENIED; // Wrong permissions
  1018. }
  1019. }
  1020. return hr;
  1021. }
  1022. /*****************************************************************************\
  1023. FUNCTION: _GetStatusBar
  1024. DESCRIPTION:
  1025. \*****************************************************************************/
  1026. CStatusBar * CFtpMenu::_GetStatusBar(void)
  1027. {
  1028. return GetCStatusBarFromDefViewSite(_punkSite);
  1029. }
  1030. /*****************************************************************************\
  1031. FUNCTION: FileSizeCountItemCB
  1032. DESCRIPTION:
  1033. This function will download the specified item and it's contents if it
  1034. is a directory.
  1035. \*****************************************************************************/
  1036. HRESULT FileSizeCountItemCB(LPVOID pvFuncCB, HINTERNET hint, LPCITEMIDLIST pidlFull, BOOL * pfValidhinst, LPVOID pvData)
  1037. {
  1038. PROGRESSINFO * pProgInfo = (PROGRESSINFO *) pvData;
  1039. HRESULT hr = S_OK;
  1040. if (pProgInfo->ppd && pProgInfo->ppd->HasUserCancelled())
  1041. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  1042. if (SUCCEEDED(hr))
  1043. {
  1044. // Is this a dir/folder that we need to recurse into?
  1045. if (FILE_ATTRIBUTE_DIRECTORY & FtpPidl_GetAttributes(pidlFull))
  1046. hr = EnumFolder((LPFNPROCESSITEMCB) pvFuncCB, hint, pidlFull, NULL, pfValidhinst, pvData);
  1047. else
  1048. pProgInfo->uliBytesTotal.QuadPart += FtpPidl_GetFileSize(pidlFull);
  1049. }
  1050. return hr;
  1051. }
  1052. HRESULT UpdateDownloadProgress(PROGRESSINFO * pProgInfo, LPCITEMIDLIST pidlFull, LPCWSTR pwzTo, LPCWSTR pwzFileName)
  1053. {
  1054. HRESULT hr;
  1055. WCHAR wzTemplate[MAX_PATH];
  1056. WCHAR wzStatusText[MAX_PATH];
  1057. WCHAR wzFrom[MAX_PATH];
  1058. LPITEMIDLIST pidlParent = ILClone(pidlFull);
  1059. if (pidlParent)
  1060. {
  1061. ILRemoveLastID(pidlParent);
  1062. FtpPidl_GetDisplayName(pidlParent, wzFrom, ARRAYSIZE(wzFrom));
  1063. ILFree(pidlParent);
  1064. }
  1065. // Give the directories some weight because the user may be copying tons of empty directories.
  1066. EVAL(SUCCEEDED(pProgInfo->ppd->SetProgress64(pProgInfo->uliBytesCompleted.QuadPart, pProgInfo->uliBytesTotal.QuadPart)));
  1067. // Generate the string "Downloading <FileName>..." status string
  1068. EVAL(LoadStringW(HINST_THISDLL, IDS_DOWNLOADING, wzTemplate, ARRAYSIZE(wzTemplate)));
  1069. wnsprintfW(wzStatusText, ARRAYSIZE(wzStatusText), wzTemplate, pwzFileName);
  1070. EVAL(SUCCEEDED(pProgInfo->ppd->SetLine(1, wzStatusText, FALSE, NULL)));
  1071. // Generate the string "From <SrcFtpUrlDir> to <DestFileDir>" status string
  1072. if (EVAL(SUCCEEDED(hr = CreateFromToStr(wzStatusText, ARRAYSIZE(wzStatusText), wzFrom, pwzTo))))
  1073. EVAL(SUCCEEDED(hr = pProgInfo->ppd->SetLine(2, wzStatusText, FALSE, NULL))); // Line one is the file being copied.
  1074. return hr;
  1075. }
  1076. /*****************************************************************************\
  1077. ConfirmDownloadReplace
  1078. Callback procedure that checks if this file really ought to be
  1079. copied.
  1080. Returns S_OK if the file should be copied.
  1081. Returns S_FALSE if the file should not be copied.
  1082. - If the user cancelled, then say S_FALSE from now on.
  1083. - If the user said Yes to All, then say S_OK.
  1084. - If there is no conflict, then say S_OK.
  1085. - If the user said No to All, then say S_FALSE.
  1086. - Else, ask the user what to do.
  1087. Note that the order of the tests above means that if you say
  1088. "Yes to All", then we don't waste our time doing overwrite checks.
  1089. _GROSS_: NOTE! that we don't try to uniquify the name, because
  1090. WinINet doesn't support the STOU (store unique) command, and
  1091. there is no way to know what filenames are valid on the server.
  1092. \*****************************************************************************/
  1093. HRESULT ConfirmDownloadReplace(LPCWSTR pwzDestPath, LPCITEMIDLIST pidlSrcFTP, OPS * pOps, HWND hwnd, CFtpFolder * pff, CFtpDir * pfd, int nObjs, BOOL * pfDeleteRequired)
  1094. {
  1095. HRESULT hr = S_OK;
  1096. ASSERT(hwnd);
  1097. *pfDeleteRequired = FALSE;
  1098. if (*pOps == opsCancel)
  1099. hr = S_FALSE;
  1100. else if (*pOps == opsYesToAll)
  1101. {
  1102. *pfDeleteRequired = PathFileExistsW(pwzDestPath);
  1103. hr = S_OK;
  1104. }
  1105. else
  1106. {
  1107. if (PathFileExistsW(pwzDestPath))
  1108. {
  1109. // It exists, so worry.
  1110. if (*pOps == opsNoToAll)
  1111. hr = S_FALSE;
  1112. else
  1113. {
  1114. FTP_FIND_DATA wfdSrc;
  1115. WIN32_FIND_DATA wfdDest;
  1116. HANDLE hfindDest;
  1117. FILETIME ftUTC;
  1118. *pfDeleteRequired = TRUE;
  1119. hfindDest = FindFirstFileW(pwzDestPath, &wfdDest);
  1120. ftUTC = wfdDest.ftLastWriteTime;
  1121. FileTimeToLocalFileTime(&ftUTC, &wfdDest.ftLastWriteTime); // UTC->LocalTime
  1122. EVAL(S_OK == Win32FindDataFromPidl(pidlSrcFTP, (LPWIN32_FIND_DATA)&wfdSrc, FALSE, FALSE));
  1123. if (EVAL(hfindDest != INVALID_HANDLE_VALUE))
  1124. {
  1125. // If we ever have modal problems, we should enter a modal state here. This normally
  1126. // isn't needed because we do the download on a background worker thread.
  1127. switch (FtpConfirmReplaceDialog(hwnd, &wfdSrc, &wfdDest, nObjs, pff))
  1128. {
  1129. case IDC_REPLACE_YESTOALL:
  1130. *pOps = opsYesToAll;
  1131. // FALLTHROUGH
  1132. case IDC_REPLACE_YES:
  1133. hr = S_OK;
  1134. break;
  1135. case IDC_REPLACE_NOTOALL:
  1136. *pOps = opsNoToAll;
  1137. // FALLTHROUGH
  1138. case IDC_REPLACE_NO:
  1139. hr = S_FALSE;
  1140. break;
  1141. default:
  1142. ASSERT(0); // Huh?
  1143. // FALLTHROUGH
  1144. case IDC_REPLACE_CANCEL:
  1145. *pOps = opsCancel;
  1146. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  1147. break;
  1148. }
  1149. FindClose(hfindDest);
  1150. }
  1151. }
  1152. }
  1153. }
  1154. return hr;
  1155. }
  1156. // The following struct is used when recursively downloading
  1157. // files/dirs from the FTP server after a "Download" verb.
  1158. typedef struct tagDOWNLOADSTRUCT
  1159. {
  1160. LPCWSTR pwzDestRootPath; // Dir on FileSys of the Download Destination
  1161. LPCITEMIDLIST pidlRoot; // Base URL of the Download Source
  1162. DWORD dwInternetFlags; // Binary, ASCII, AutoDetect?
  1163. HWND hwndParent; // hwnd for Confirm UI
  1164. OPS ops; // Do we cancel?
  1165. CFtpFolder * pff;
  1166. CFtpDir * pfd;
  1167. // Progress
  1168. PROGRESSINFO progInfo;
  1169. } DOWNLOADSTRUCT;
  1170. /*****************************************************************************\
  1171. FUNCTION: _CalcDestName
  1172. DESCRIPTION:
  1173. This recursive function starts at pwzDestDir as the dest FS path and
  1174. pidlRoot as the src ftp path. We need to construct pwzDestPath which
  1175. is the current path. This will be done by adding the relative path
  1176. (pidlFull - pidlRoot) to pwzDestDir. pidlFull can point to either a file
  1177. or a directory.
  1178. PARAMETERS: (Example. "C:\dir1\dir2\dir3\file.txt")
  1179. pwzDestParentPath: "C:\dir1\dir2\dir3"
  1180. pwzDestDir: "C:\dir1\dir2\dir3\file.txt"
  1181. pwzDestFileName: "file.txt"
  1182. Example. "C:\dir1\dir2\dir3\"
  1183. pwzDestParentPath: "C:\dir1\dir2"
  1184. pwzDestDir: "C:\dir1\dir2\dir3"
  1185. pwzDestFileName: "dir3"
  1186. \*****************************************************************************/
  1187. HRESULT _CalcDestName(LPCWSTR pwzDestDir, LPCITEMIDLIST pidlRoot, LPCITEMIDLIST pidlFull, LPWSTR pwzDestParentPath, DWORD cchDestParentPathSize,
  1188. LPWSTR pwzDestPath, DWORD cchDestPathSize, LPWSTR pwzDestFileName, DWORD cchDestFileNameSize)
  1189. {
  1190. HRESULT hr = S_OK;
  1191. WCHAR wzFtpPathTemp[MAX_PATH];
  1192. WCHAR wzFSPathTemp[MAX_PATH];
  1193. LPITEMIDLIST pidlRootIterate = (LPITEMIDLIST) pidlRoot; // I promise to iterate only
  1194. LPITEMIDLIST pidlFullIterate = (LPITEMIDLIST) pidlFull; // I promise to iterate only
  1195. // This one is easy.
  1196. FtpPidl_GetLastFileDisplayName(pidlFull, pwzDestFileName, cchDestFileNameSize); // The dest filename is easy.
  1197. // Let's find the relative path between pidlRoot and pidlFull.
  1198. while (!ILIsEmpty(pidlRootIterate) && !ILIsEmpty(pidlFullIterate) && FtpItemID_IsEqual(pidlRootIterate, pidlFullIterate))
  1199. {
  1200. pidlFullIterate = _ILNext(pidlFullIterate);
  1201. pidlRootIterate = _ILNext(pidlRootIterate);
  1202. }
  1203. ASSERT(ILIsEmpty(pidlRootIterate) && !ILIsEmpty(pidlFullIterate)); // Asure pidlFull is a superset of pidlRoot
  1204. LPITEMIDLIST pidlParent = ILClone(pidlFullIterate);
  1205. if (pidlParent)
  1206. {
  1207. ILRemoveLastID(pidlParent); // Remove the item that will be created (file or dir)
  1208. GetDisplayPathFromPidl(pidlParent, wzFtpPathTemp, ARRAYSIZE(wzFtpPathTemp), TRUE); // Full path w/o last item.
  1209. StrCpyNW(pwzDestParentPath, pwzDestDir, cchDestParentPathSize); // Put the base dest.
  1210. UrlPathToFilePath(wzFtpPathTemp, wzFSPathTemp, ARRAYSIZE(wzFSPathTemp));
  1211. if (!PathAppendW(pwzDestParentPath, wzFSPathTemp))
  1212. hr = HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW); // Path too long, probably.
  1213. ILFree(pidlParent);
  1214. }
  1215. if (SUCCEEDED(hr))
  1216. {
  1217. GetDisplayPathFromPidl(pidlFullIterate, wzFtpPathTemp, ARRAYSIZE(wzFSPathTemp), FALSE); // Full Path including item.
  1218. StrCpyNW(pwzDestPath, pwzDestDir, cchDestParentPathSize); // Put the base dest.
  1219. UrlPathToFilePath(wzFtpPathTemp, wzFSPathTemp, ARRAYSIZE(wzFSPathTemp));
  1220. if (!PathAppendW(pwzDestPath, wzFSPathTemp))
  1221. hr = HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW); // Path too long, probably.
  1222. }
  1223. return hr;
  1224. }
  1225. // This defines the size of a directory measured by the amount of time it would take compared to a file.
  1226. #define VIRTUAL_DIR_SIZE 1000 // about 1k.
  1227. /*****************************************************************************\
  1228. FUNCTION: DownloadItemStackPig
  1229. DESCRIPTION:
  1230. This function will download the specified item and it's contents if it
  1231. is a directory.
  1232. \*****************************************************************************/
  1233. HRESULT DownloadItemStackPig(HINTERNET hint, LPCITEMIDLIST pidlFull, BOOL * pfValidhinst, DOWNLOADSTRUCT * pDownLoad, CFtpDir ** ppfd)
  1234. {
  1235. HRESULT hr;
  1236. WCHAR wzDestParentPath[MAX_PATH]; // If item is "C:\dir1\dir2copy\", the this is "C:\dir1"
  1237. WCHAR wzDestPath[MAX_PATH]; // This is "C:\dir1\dir2copy\"
  1238. WCHAR wzDestFileName[MAX_PATH]; // This is "dir2copy"
  1239. hr = _CalcDestName(pDownLoad->pwzDestRootPath, pDownLoad->pidlRoot, pidlFull, wzDestParentPath, ARRAYSIZE(wzDestParentPath), wzDestPath, ARRAYSIZE(wzDestPath), wzDestFileName, ARRAYSIZE(wzDestFileName));
  1240. if (SUCCEEDED(hr))
  1241. {
  1242. if (pDownLoad->progInfo.ppd)
  1243. EVAL(SUCCEEDED(UpdateDownloadProgress(&(pDownLoad->progInfo), pidlFull, wzDestParentPath, wzDestFileName)));
  1244. if (pDownLoad->progInfo.ppd && pDownLoad->progInfo.ppd->HasUserCancelled())
  1245. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  1246. else
  1247. {
  1248. // Is this a dir/folder that we need to recurse into?
  1249. if (FILE_ATTRIBUTE_DIRECTORY & FtpPidl_GetAttributes(pidlFull))
  1250. {
  1251. // Yes, so let's go...
  1252. if (EVAL((PathFileExistsW(wzDestPath) && PathIsDirectoryW(wzDestPath)) ||
  1253. CreateDirectoryW(wzDestPath, NULL)))
  1254. {
  1255. EVAL(SetFileAttributes(wzDestPath, FtpPidl_GetAttributes(pidlFull)));
  1256. hr = pDownLoad->pfd->GetFtpSite()->GetFtpDir(pidlFull, ppfd);
  1257. if (!SUCCEEDED(hr))
  1258. TraceMsg(TF_ERROR, "DownloadItemStackPig() GetFtpDir failed hr=%#08lx", hr);
  1259. }
  1260. else
  1261. {
  1262. hr = E_FAIL;
  1263. TraceMsg(TF_ERROR, "DownloadItemStackPig() CreateDirectory or PathFileExists failed hr=%#08lx", hr);
  1264. }
  1265. }
  1266. else
  1267. {
  1268. BOOL fDeleteRequired;
  1269. ULARGE_INTEGER uliFileSize;
  1270. pDownLoad->progInfo.dwCompletedInCurFile = 0;
  1271. pDownLoad->progInfo.dwLastDisplayed = 0;
  1272. hr = ConfirmDownloadReplace(wzDestPath, pidlFull, &(pDownLoad->ops), GetProgressHWnd(pDownLoad->progInfo.ppd, pDownLoad->hwndParent), pDownLoad->pff, pDownLoad->pfd, 1, &fDeleteRequired);
  1273. if (S_OK == hr)
  1274. {
  1275. if (fDeleteRequired)
  1276. {
  1277. if (!DeleteFileW(wzDestPath))
  1278. hr = HRESULT_FROM_WIN32(GetLastError());
  1279. }
  1280. // Don't copy the file if it's a SoftLink because of the possible
  1281. // recursion case.
  1282. if (SUCCEEDED(hr) && (0 != FtpPidl_GetAttributes(pidlFull)))
  1283. {
  1284. // Contemplate adding a callback function in order to feed the status bar.
  1285. hr = FtpGetFileExPidlWrap(hint, TRUE, pidlFull, wzDestPath, TRUE, FtpPidl_GetAttributes(pidlFull), pDownLoad->dwInternetFlags, (DWORD_PTR)&(pDownLoad->progInfo));
  1286. if (FAILED(hr))
  1287. {
  1288. if (HRESULT_FROM_WIN32(ERROR_CANCELLED) != hr)
  1289. {
  1290. // We need to display the error now while the extended error info is still valid.
  1291. // This is because as we walk out of the resursive call, we will be calling
  1292. // FtpSetCurrentDirectory() which will wipe clean the extended error msg.
  1293. DisplayWininetError(pDownLoad->hwndParent, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_DOWNLOADING, IDS_FTPERR_WININET, MB_OK, pDownLoad->progInfo.ppd);
  1294. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); // Don't display any more error dialogs.
  1295. }
  1296. }
  1297. else
  1298. {
  1299. // The docs imply that (FILE_SHARE_READ | FILE_SHARE_WRITE) means that other callers need both, but
  1300. // I want them to be able to use either.
  1301. HANDLE hFile = CreateFileW(wzDestPath, GENERIC_WRITE, (FILE_SHARE_READ | FILE_SHARE_WRITE), NULL, OPEN_EXISTING, FtpPidl_GetAttributes(pidlFull), NULL);
  1302. // FtpGetFile() won't set the time/date correctly, so we will.
  1303. if (INVALID_HANDLE_VALUE != hFile)
  1304. {
  1305. FILETIME ftLastWriteTime = FtpPidl_GetFileTime(ILFindLastID(pidlFull));
  1306. // Since the file time on the disk is stored in a time zone independent way (UTC)
  1307. // we have a problem because FTP WIN32_FIND_DATA is in the local time zone. So we
  1308. // need to convert the FTP local time to UTC when we set the file.
  1309. // Note that we are using an optimization that uses the fact that FTP always
  1310. // has the same time for LastAccessTime, LastWriteTime, and CreationTime.
  1311. // ASSERT(pwfd->ftCreationTime.dwLowDateTime = pwfd->ftLastAccessTime.dwLowDateTime = pwfd->ftLastWriteTime.dwLowDateTime);
  1312. // ASSERT(pwfd->ftCreationTime.dwHighDateTime = pwfd->ftLastAccessTime.dwHighDateTime = pwfd->ftLastWriteTime.dwHighDateTime);
  1313. // priv.h has notes on how time works.
  1314. SetFileTime(hFile, &ftLastWriteTime, &ftLastWriteTime, &ftLastWriteTime);
  1315. CloseHandle(hFile);
  1316. }
  1317. SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, wzDestPath, NULL);
  1318. }
  1319. }
  1320. }
  1321. uliFileSize.QuadPart = FtpPidl_GetFileSize(pidlFull);
  1322. pDownLoad->progInfo.uliBytesCompleted.QuadPart += uliFileSize.QuadPart;
  1323. }
  1324. }
  1325. }
  1326. if (pfValidhinst)
  1327. *pfValidhinst = (pDownLoad->progInfo.hint ? TRUE : FALSE);
  1328. return hr;
  1329. }
  1330. /*****************************************************************************\
  1331. FUNCTION: DownloadItemCB
  1332. DESCRIPTION:
  1333. This function will download the specified item and it's contents if it
  1334. is a directory.
  1335. \*****************************************************************************/
  1336. HRESULT DownloadItemCB(LPVOID pvFuncCB, HINTERNET hint, LPCITEMIDLIST pidlFull, BOOL * pfValidhinst, LPVOID pvData)
  1337. {
  1338. DOWNLOADSTRUCT * pDownLoad = (DOWNLOADSTRUCT *) pvData;
  1339. LPFNPROCESSITEMCB pfnProcessItemCB = (LPFNPROCESSITEMCB) pvFuncCB;
  1340. CFtpDir * pfdNew = NULL;
  1341. HRESULT hr = DownloadItemStackPig(hint, pidlFull, pfValidhinst, pDownLoad, &pfdNew);
  1342. if (SUCCEEDED(hr) && pfdNew) // pfdNew Maybe NULL if cancelled
  1343. {
  1344. CFtpDir * pfdOriginal = pDownLoad->pfd;
  1345. pDownLoad->pfd = pfdNew;
  1346. hr = EnumFolder(pfnProcessItemCB, hint, pidlFull, pDownLoad->pff->GetCWireEncoding(), pfValidhinst, pvData);
  1347. pDownLoad->pfd = pfdOriginal;
  1348. pfdNew->Release();
  1349. }
  1350. return hr;
  1351. }
  1352. // If in the future, we want to add more support to downloading to non-File System
  1353. // locations, we would accept pidls instead of pszPath. We would then bind via
  1354. // IStorages and reuse some of the drag & drop code. Examples of non-File System
  1355. // locations are CAB View, ZIP Folders, WEB Folders, etc.
  1356. HRESULT ShowDownloadDialog(HWND hwnd, LPTSTR pszPath, DWORD cchSize)
  1357. {
  1358. TCHAR szMessage[MAX_URL_STRING];
  1359. HRESULT hr;
  1360. LPITEMIDLIST pidlDefault = NULL;
  1361. LPITEMIDLIST pidlFolder = NULL;
  1362. HKEY hkey = NULL;
  1363. IStream * pstrm = NULL;
  1364. if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, SZ_REGKEY_MICROSOFTSOFTWARE, 0, (KEY_READ | KEY_WRITE), &hkey))
  1365. {
  1366. pstrm = SHOpenRegStream(hkey, SZ_REGKEY_FTPCLASS, SZ_REGVALUE_DOWNLOAD_DIR, STGM_READWRITE);
  1367. if (pstrm)
  1368. ILLoadFromStream(pstrm, &pidlDefault); // Will return (NULL == pidlDefault) if the reg value is empty.
  1369. }
  1370. if (!pidlDefault && (SHELL_VERSION_W95NT4 == GetShellVersion())) // If reg key is empty.
  1371. pidlDefault = SHCloneSpecialIDList(NULL, CSIDL_PERSONAL, TRUE);
  1372. EVAL(LoadString(HINST_THISDLL, IDS_DLG_DOWNLOAD_TITLE, szMessage, ARRAYSIZE(szMessage)));
  1373. hr = BrowseForDir(hwnd, szMessage, pidlDefault, &pidlFolder);
  1374. if (pstrm)
  1375. {
  1376. // Do we want to save the new pidl?
  1377. if (S_OK == hr)
  1378. {
  1379. LARGE_INTEGER li = {0};
  1380. ULARGE_INTEGER uli = {0};
  1381. // rewind the stream to the beginning so that when we
  1382. // add a new pidl it does not get appended to the first one
  1383. pstrm->Seek(li, STREAM_SEEK_SET, &uli);
  1384. ILSaveToStream(pstrm, pidlFolder);
  1385. }
  1386. pstrm->Release();
  1387. }
  1388. if (S_OK == hr)
  1389. {
  1390. ASSERT(cchSize >= MAX_PATH); // This is an assumption SHGetPathFromIDList makes.
  1391. hr = (SHGetPathFromIDList(pidlFolder, pszPath) ? S_OK : E_FAIL);
  1392. }
  1393. if (hkey)
  1394. RegCloseKey(hkey);
  1395. if (pidlDefault)
  1396. ILFree(pidlDefault);
  1397. if (pidlFolder)
  1398. ILFree(pidlFolder);
  1399. return hr;
  1400. }
  1401. /*****************************************************************************\
  1402. FUNCTION: _InvokeDownloadVerb
  1403. DESCRIPTION:
  1404. The user just selected file(s) and/or folder(s) and selected the
  1405. "download" verb. We need to:
  1406. 1. Display UI to ask the user for the destination directory.
  1407. 2. Download each item (pidl) into that directory.
  1408. \*****************************************************************************/
  1409. HRESULT CFtpMenu::_InvokeDownloadVerb(LPCMINVOKECOMMANDINFO pici)
  1410. {
  1411. if (ZoneCheckPidlAction(_punkSite, URLACTION_SHELL_FILE_DOWNLOAD, m_pff->GetPrivatePidlReference(), (PUAF_DEFAULT | PUAF_WARN_IF_DENIED)))
  1412. {
  1413. TCHAR szDestDir[MAX_PATH];
  1414. // DWORD dwDownloadType;
  1415. HRESULT hr = ShowDownloadDialog(pici->hwnd, szDestDir, ARRAYSIZE(szDestDir));
  1416. if (S_OK == hr)
  1417. {
  1418. HANDLE hThread;
  1419. while (m_pszDownloadDir)
  1420. Sleep(0); // Wait until the other thread is done.
  1421. Str_SetPtr(&m_pszDownloadDir, szDestDir);
  1422. // m_dwDownloadType = dwDownloadType;
  1423. AddRef(); // The thread will hold a ref.
  1424. DWORD dwHack; // Win95 fails CreateThread() if pdwThreadID is NULL.
  1425. hThread = CreateThread(NULL, 0, CFtpMenu::DownloadThreadProc, this, 0, &dwHack);
  1426. if (!hThread)
  1427. {
  1428. // Failed to create the thread.
  1429. Release(); // The thread will hold a ref.
  1430. Str_SetPtr(&m_pszDownloadDir, NULL); // Clear this value so other thread an use it.
  1431. }
  1432. else
  1433. Sleep(100); // Give the thread a second to copy the variables.
  1434. }
  1435. }
  1436. return S_OK;
  1437. }
  1438. /*****************************************************************************\
  1439. FUNCTION: _DownloadThreadProc
  1440. DESCRIPTION:
  1441. \*****************************************************************************/
  1442. DWORD CFtpMenu::_DownloadThreadProc(void)
  1443. {
  1444. if (EVAL(m_pfd))
  1445. {
  1446. TCHAR szUrl[MAX_URL_STRING];
  1447. WCHAR wzDestDir[MAX_PATH];
  1448. HINTERNET hint;
  1449. LPITEMIDLIST pidlRoot = ILClone(m_pfd->GetPidlReference());
  1450. DOWNLOADSTRUCT downloadStruct = {wzDestDir, pidlRoot, m_dwDownloadType, m_hwnd, opsPrompt, m_pff, m_pfd, 0, 0, 0, 0, 0};
  1451. CFtpPidlList * pflHfpl = NULL; // We need a copy because the caller may select other files and execute a verb during the download.
  1452. HRESULT hrOleInit = SHCoInitialize();
  1453. HRESULT hr = HRESULT_FROM_WIN32(ERROR_INTERNET_CANNOT_CONNECT);
  1454. IUnknown_Set(&pflHfpl, m_pflHfpl);
  1455. StrCpyNW(wzDestDir, m_pszDownloadDir, ARRAYSIZE(wzDestDir));
  1456. Str_SetPtr(&m_pszDownloadDir, NULL); // Clear this value so other thread an use it.
  1457. m_pfd->GetHint(NULL, NULL, &hint, _punkSite, m_pff);
  1458. if (hint)
  1459. {
  1460. BOOL fReleaseHint = TRUE;
  1461. // If we find that we need to make the browser model during this UI, we would do that here.
  1462. // However, since we are on a background thread (to be async), that isn't needed here.
  1463. // An example is that we test closing the browser during our async navigation and make sure
  1464. // the process hangs around (we don't need the calling thread).
  1465. // Is the disk ready? (Floppy, CD, net share)
  1466. if (SUCCEEDED(SHPathPrepareForWriteWrapW(m_hwnd, NULL, wzDestDir, FO_COPY, SHPPFW_DEFAULT))) // Check and prompt if necessary.
  1467. {
  1468. hr = UrlCreateFromPidl(pidlRoot, SHGDN_FORPARSING, szUrl, ARRAYSIZE(szUrl), ICU_ESCAPE | ICU_USERNAME, FALSE);
  1469. if (EVAL(SUCCEEDED(hr)))
  1470. {
  1471. PROGRESSINFO progInfo;
  1472. progInfo.uliBytesCompleted.QuadPart = 0;
  1473. progInfo.uliBytesTotal.QuadPart = 0;
  1474. downloadStruct.progInfo.hint = hint;
  1475. downloadStruct.progInfo.ppd = CProgressDialog_CreateInstance(IDS_COPY_TITLE, IDA_FTPDOWNLOAD);
  1476. if (downloadStruct.progInfo.ppd)
  1477. {
  1478. HWND hwndParent = NULL;
  1479. WCHAR wzProgressDialogStr[MAX_PATH];
  1480. // If the caller was nice enough to SetSite() with their punk, I will be nice enough to make
  1481. // their window may progress dialog's parent window.
  1482. IUnknown_GetWindow(_punkSite, &hwndParent);
  1483. if (!hwndParent)
  1484. hwndParent = m_hwnd;
  1485. // We give a NULL punkEnableModless because we don't want to go modal.
  1486. downloadStruct.progInfo.ppd->StartProgressDialog(hwndParent, NULL, PROGDLG_AUTOTIME, NULL);
  1487. // Tell the user we are calculating how long it will take.
  1488. if (EVAL(LoadStringW(HINST_THISDLL, IDS_PROGRESS_DOWNLOADTIMECALC, wzProgressDialogStr, ARRAYSIZE(wzProgressDialogStr))))
  1489. EVAL(SUCCEEDED(downloadStruct.progInfo.ppd->SetLine(2, wzProgressDialogStr, FALSE, NULL)));
  1490. InternetSetStatusCallbackWrap(hint, TRUE, FtpProgressInternetStatusCB);
  1491. progInfo.ppd = downloadStruct.progInfo.ppd;
  1492. }
  1493. hr = pflHfpl->RecursiveEnum(pidlRoot, FileSizeCountItemCB, hint, (LPVOID) &progInfo);
  1494. if (downloadStruct.progInfo.ppd)
  1495. {
  1496. // Reset because RecursiveEnum(FileSizeCountItemCB) can take a long time and the estimated time
  1497. // is based on the time between ::StartProgressDialog() and the first
  1498. // ::SetProgress() call.
  1499. EVAL(SUCCEEDED(downloadStruct.progInfo.ppd->Timer(PDTIMER_RESET, NULL)));
  1500. }
  1501. if (SUCCEEDED(hr))
  1502. {
  1503. downloadStruct.progInfo.uliBytesCompleted.QuadPart = progInfo.uliBytesCompleted.QuadPart;
  1504. downloadStruct.progInfo.uliBytesTotal.QuadPart = progInfo.uliBytesTotal.QuadPart;
  1505. pflHfpl->UseCachedDirListings(TRUE); // Get the perf advantage now because we just updated the cache a few lines up.
  1506. hr = pflHfpl->RecursiveEnum(pidlRoot, DownloadItemCB, hint, (LPVOID) &downloadStruct);
  1507. }
  1508. if (downloadStruct.progInfo.ppd)
  1509. {
  1510. EVAL(SUCCEEDED(downloadStruct.progInfo.ppd->StopProgressDialog()));
  1511. downloadStruct.progInfo.ppd->Release();
  1512. }
  1513. if (!downloadStruct.progInfo.hint)
  1514. fReleaseHint = FALSE;
  1515. }
  1516. }
  1517. else
  1518. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); // Err msg already displayed
  1519. if (fReleaseHint)
  1520. m_pfd->ReleaseHint(hint);
  1521. }
  1522. if (FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_CANCELLED)))
  1523. {
  1524. int nResult = DisplayWininetError(m_hwnd, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_DOWNLOADING, IDS_FTPERR_WININET, MB_OK, NULL);
  1525. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); // Don't display any more error dialogs.
  1526. }
  1527. ILFree(pidlRoot);
  1528. IUnknown_Set(&pflHfpl, NULL);
  1529. SHCoUninitialize(hrOleInit);
  1530. }
  1531. Release(); // This thread is holding a ref.
  1532. return 0;
  1533. }
  1534. HRESULT CFtpMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
  1535. {
  1536. UINT idc;
  1537. HRESULT hres = E_FAIL;
  1538. if (pici->cbSize < sizeof(*pici))
  1539. return E_INVALIDARG;
  1540. if (HIWORD(pici->lpVerb))
  1541. {
  1542. int ivi;
  1543. idc = (UINT)-1;
  1544. for (ivi = 0; ivi < IVI_MAX; ivi++)
  1545. {
  1546. TCHAR szVerb[MAX_PATH];
  1547. SHAnsiToTChar(pici->lpVerb, szVerb, ARRAYSIZE(szVerb));
  1548. if (!StrCmpI(c_rgvi[ivi].ptszCmd, szVerb))
  1549. {
  1550. // Yes, the command is equal to the verb str, so this is the one.
  1551. idc = c_rgvi[ivi].idc;
  1552. break;
  1553. }
  1554. }
  1555. }
  1556. else
  1557. idc = LOWORD(pici->lpVerb);
  1558. switch (idc)
  1559. {
  1560. case IDC_ITEM_NEWFOLDER:
  1561. hres = _InvokeNewFolderVerb(pici);
  1562. break;
  1563. case IDC_LOGIN_AS:
  1564. hres = _InvokeLoginAsVerb(pici);
  1565. break;
  1566. case IDC_ITEM_OPEN:
  1567. case IDC_ITEM_EXPLORE:
  1568. hres = _EnumInvoke(pici, _ApplyOne, c_rgvi[IVI_REQ + idc].ptszCmd);
  1569. break;
  1570. case IDC_ITEM_DOWNLOAD:
  1571. hres = _InvokeDownloadVerb(pici);
  1572. break;
  1573. case IDM_SHARED_FILE_DELETE: // SFVIDM_FILE_DELETE
  1574. hres = _InvokeDeleteVerb(pici);
  1575. break;
  1576. case IDM_SHARED_FILE_RENAME: // SFVIDM_FILE_RENAME
  1577. hres = _InvokeRename(pici);
  1578. break;
  1579. case IDM_SHARED_EDIT_COPY: // SFVIDM_EDIT_COPY
  1580. hres = _InvokeCutCopy(DFM_CMD_COPY, pici);
  1581. break;
  1582. case IDM_SHARED_EDIT_CUT: // SFVIDM_EDIT_CUT
  1583. hres = _InvokeCutCopy(DFM_CMD_MOVE, pici);
  1584. break;
  1585. // _UNOBVIOUS_: Yes, this is not a typo. You might think I
  1586. // should have written SFVIDM_EDIT_PASTE, but you would be wrong.
  1587. case SHARED_EDIT_PASTE:
  1588. // What's more annoying is that I also have to list
  1589. // IDM_SHARED_EDIT_PASTE, as a hack, because the "convert a
  1590. // name to an ID" loop above will cook up IDM_SHARED_EDIT_PASTE
  1591. // as the matching ID.
  1592. case IDM_SHARED_EDIT_PASTE:
  1593. hres = _InvokePaste(pici);
  1594. break;
  1595. case IDC_ITEM_BKGNDPROP: // Properties for the background folder.
  1596. case IDM_SHARED_FILE_PROP: // Same as SFVIDM_FILE_PROPERTIES
  1597. TraceMsg(TF_FTP_OTHER, "Properties!");
  1598. hres = CFtpProp_DoProp(m_pflHfpl, m_pff, m_hwnd);
  1599. break;
  1600. case IDM_SORTBYNAME:
  1601. case IDM_SORTBYSIZE:
  1602. case IDM_SORTBYTYPE:
  1603. case IDM_SORTBYDATE:
  1604. ASSERT(m_hwnd);
  1605. ShellFolderView_ReArrange(m_hwnd, CONVERT_IDMID_TO_COLNAME(idc));
  1606. hres = S_OK;
  1607. break;
  1608. default:
  1609. TraceMsg(TF_FTP_OTHER, "InvokeCommand");
  1610. hres = E_INVALIDARG;
  1611. break;
  1612. }
  1613. return hres;
  1614. }
  1615. /*****************************************************************************
  1616. *
  1617. * CFtpMenu_Create
  1618. *
  1619. \*****************************************************************************/
  1620. HRESULT CFtpMenu_Create(CFtpFolder * pff, CFtpPidlList * pflHfpl, HWND hwnd, REFIID riid, LPVOID * ppvObj, BOOL fFromCreateViewObject)
  1621. {
  1622. HRESULT hr;
  1623. CFtpMenu * pfm;
  1624. *ppvObj = NULL;
  1625. hr = CFtpMenu_Create(pff, pflHfpl, hwnd, fFromCreateViewObject, &pfm);
  1626. if (SUCCEEDED(hr))
  1627. {
  1628. hr = pfm->QueryInterface(riid, ppvObj);
  1629. pfm->Release();
  1630. }
  1631. ASSERT_POINTER_MATCHES_HRESULT(*ppvObj, hr);
  1632. return hr;
  1633. }
  1634. /**********************************************************************\
  1635. FUNCTION: GetFtpDirFromFtpFolder
  1636. DESCRIPTION:
  1637. If an ftp folder is opened to an ftp server root (ftp://wired/),
  1638. and the user clicks on the icon in the caption bar, pff will have an
  1639. empty pidl. This will cause us to return NULL.
  1640. \**********************************************************************/
  1641. CFtpDir * GetFtpDirFromFtpFolder(CFtpFolder * pff, CFtpPidlList * pflHfpl)
  1642. {
  1643. LPCITEMIDLIST pidl = pff->GetPrivatePidlReference();
  1644. if (!pidl || ILIsEmpty(pidl))
  1645. return NULL;
  1646. return pff->GetFtpDirFromPidl(pidl);
  1647. }
  1648. BOOL CanRenameAndDelete(CFtpFolder * pff, CFtpPidlList * pidlList, DWORD * pdwSFGAO)
  1649. {
  1650. BOOL fResult = TRUE;
  1651. // If talking about yourself, you can't delete or rename.
  1652. // (Rename isn't allowed because SetNameOf doesn't like "self".)
  1653. if (pidlList->GetCount() == 0)
  1654. fResult = FALSE;
  1655. else if (pidlList->GetCount() == 1)
  1656. {
  1657. LPITEMIDLIST pidl = GetPidlFromFtpFolderAndPidlList(pff, pidlList);
  1658. // We can't rename or delete FTP servers, so check to see if it is one.
  1659. if (FtpID_IsServerItemID(FtpID_GetLastIDReferense(pidl)))
  1660. fResult = FALSE;
  1661. ILFree(pidl);
  1662. }
  1663. return fResult;
  1664. }
  1665. /*****************************************************************************
  1666. *
  1667. * CFtpMenu_Create
  1668. *
  1669. \*****************************************************************************/
  1670. HRESULT CFtpMenu_Create(CFtpFolder * pff, CFtpPidlList * pidlList, HWND hwnd, BOOL fFromCreateViewObject, CFtpMenu ** ppfcm)
  1671. {
  1672. HRESULT hr = E_FAIL;
  1673. // It's ok if this is NULL
  1674. CFtpDir * pfd = GetFtpDirFromFtpFolder(pff, pidlList);
  1675. ASSERT(ppfcm);
  1676. *ppfcm = new CFtpMenu();
  1677. if (*ppfcm)
  1678. {
  1679. // We must AddRef the moment we copy them, else
  1680. // Finalize will get extremely upset.
  1681. //
  1682. // NOTE! that we rely on the fact that GetAttributesOf
  1683. // will barf on complex pidls!
  1684. (*ppfcm)->m_pff = pff;
  1685. if (pff)
  1686. pff->AddRef();
  1687. IUnknown_Set(&(*ppfcm)->m_pflHfpl, pidlList);
  1688. IUnknown_Set(&(*ppfcm)->m_pfd, pfd);
  1689. (*ppfcm)->m_sfgao = SFGAO_CAPABILITYMASK | SFGAO_FOLDER;
  1690. (*ppfcm)->m_hwnd = hwnd;
  1691. (*ppfcm)->m_fBackground = fFromCreateViewObject;
  1692. if (!CanRenameAndDelete(pff, pidlList, &((*ppfcm)->m_sfgao)))
  1693. (*ppfcm)->m_sfgao &= ~(SFGAO_CANDELETE | SFGAO_CANRENAME); // Clear those two bits.
  1694. if ((*ppfcm)->m_pflHfpl)
  1695. {
  1696. LPCITEMIDLIST * ppidl = (*ppfcm)->m_pflHfpl->GetPidlList();
  1697. if (ppidl)
  1698. {
  1699. hr = (*ppfcm)->m_pff->GetAttributesOf((*ppfcm)->m_pflHfpl->GetCount(), ppidl, &(*ppfcm)->m_sfgao);
  1700. (*ppfcm)->m_pflHfpl->FreePidlList(ppidl);
  1701. }
  1702. }
  1703. if (FAILED(hr))
  1704. {
  1705. IUnknown_Set(ppfcm, NULL); // Unable to get attributes
  1706. }
  1707. }
  1708. else
  1709. {
  1710. hr = E_OUTOFMEMORY;
  1711. }
  1712. if (pfd)
  1713. pfd->Release();
  1714. ASSERT_POINTER_MATCHES_HRESULT(*ppfcm, hr);
  1715. return hr;
  1716. }
  1717. /****************************************************\
  1718. Constructor
  1719. \****************************************************/
  1720. CFtpMenu::CFtpMenu() : m_cRef(1)
  1721. {
  1722. DllAddRef();
  1723. // This needs to be allocated in Zero Inited Memory.
  1724. // Assert that all Member Variables are inited to Zero.
  1725. ASSERT(!m_pflHfpl);
  1726. ASSERT(!m_pff);
  1727. ASSERT(!m_pfd);
  1728. ASSERT(!m_sfgao);
  1729. ASSERT(!m_hwnd);
  1730. LEAK_ADDREF(LEAK_CFtpContextMenu);
  1731. }
  1732. /****************************************************\
  1733. Destructor
  1734. \****************************************************/
  1735. CFtpMenu::~CFtpMenu()
  1736. {
  1737. IUnknown_Set(&m_pflHfpl, NULL);
  1738. IUnknown_Set(&m_pff, NULL);
  1739. IUnknown_Set(&m_pfd, NULL);
  1740. IUnknown_Set(&_punkSite, NULL);
  1741. DllRelease();
  1742. LEAK_DELREF(LEAK_CFtpContextMenu);
  1743. }
  1744. //===========================
  1745. // *** IUnknown Interface ***
  1746. //===========================
  1747. ULONG CFtpMenu::AddRef()
  1748. {
  1749. m_cRef++;
  1750. return m_cRef;
  1751. }
  1752. ULONG CFtpMenu::Release()
  1753. {
  1754. ASSERT(m_cRef > 0);
  1755. m_cRef--;
  1756. if (m_cRef > 0)
  1757. return m_cRef;
  1758. delete this;
  1759. return 0;
  1760. }
  1761. HRESULT CFtpMenu::QueryInterface(REFIID riid, void **ppvObj)
  1762. {
  1763. if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IContextMenu))
  1764. {
  1765. *ppvObj = SAFECAST(this, IContextMenu*);
  1766. }
  1767. else if (IsEqualIID(riid, IID_IObjectWithSite))
  1768. {
  1769. *ppvObj = SAFECAST(this, IObjectWithSite*);
  1770. }
  1771. else
  1772. {
  1773. TraceMsg(TF_FTPQI, "CFtpMenu::QueryInterface() failed.");
  1774. *ppvObj = NULL;
  1775. return E_NOINTERFACE;
  1776. }
  1777. AddRef();
  1778. return S_OK;
  1779. }