Leaked source code of windows server 2003
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.

8792 lines
303 KiB

  1. // Copyright (c) <1995-1999> Microsoft Corporation
  2. #include "shellprv.h"
  3. #pragma hdrstop
  4. #include <msi.h>
  5. #include <msip.h>
  6. #include <aclapi.h> // for TreeResetNamedSecurityInfo
  7. #include "shlwapip.h" // for SHGlobalCounterDecrement
  8. #include "ynlist.h"
  9. #define INTERNAL_COPY_ENGINE
  10. #include "copy.h"
  11. #include "shell32p.h"
  12. #include "control.h"
  13. #include "cdburn.h"
  14. #include "propsht.h"
  15. #include "prshtcpp.h"
  16. #define REG_VAL_GENERAL_RENAMEHTMLFILE TEXT("RenameHtmlFile")
  17. #define TF_DEBUGCOPY 0x00800000
  18. #define VERBOSE_STATUS
  19. // REVIEW, we should tune this size down as small as we can
  20. // to get smoother multitasking (without effecting performance)
  21. #define COPYMAXBUFFERSIZE 0x10000 // 0xFFFF this is 32-bit code!
  22. #define MIN_MINTIME4FEEDBACK 5 // is it worth showing estimated time to completion feedback?
  23. #define MS_RUNAVG 10000 // ms, window for running average time to completion estimate
  24. #define MS_TIMESLICE 2000 // ms, (MUST be > 1000!) first average time to completion estimate
  25. #define MAXDIRDEPTH 128 // # of directories we will deal with recursivly
  26. #define SHOW_PROGRESS_TIMEOUT 1000 // 1 second
  27. #define MINSHOWTIME 1000 // 1 sec
  28. // progress dialog message
  29. #define PDM_SHUTDOWN WM_APP
  30. #define PDM_NOOP (WM_APP + 1)
  31. #define PDM_UPDATE (WM_APP + 2)
  32. #define OPER_MASK 0x0F00
  33. #define OPER_ENTERDIR 0x0100
  34. #define OPER_LEAVEDIR 0x0200
  35. #define OPER_DOFILE 0x0300
  36. #define OPER_ERROR 0x0400
  37. #define FOFuncToStringID(wFunc) (IDS_UNDO_FILEOP + wFunc)
  38. //
  39. // The following is a list of folder suffixes in all international languages. This list is NOT
  40. // read from a resource because we do NOT want the strings in this list to be mistakenly localized.
  41. // This list will allow NT5 shell to operate on files created by any international version of
  42. // office 9.
  43. // This list is taken from "http://officeweb/specs/webclient/files.htm"
  44. //
  45. // WARNING: Do not localize the strings in this table. Do not make any changes to this table
  46. // without consulting AlanRa (Office9 PM)
  47. //
  48. static const LPCTSTR c_apszSuffixes[] =
  49. {
  50. TEXT(".files"),
  51. TEXT("_files"),
  52. TEXT("-Dateien"),
  53. TEXT("_fichiers"),
  54. TEXT("_bestanden"),
  55. TEXT("_file"),
  56. TEXT("_archivos"),
  57. TEXT("-filer"),
  58. TEXT("_tiedostot"),
  59. TEXT("_pliki"),
  60. TEXT("_soubory"),
  61. TEXT("_elemei"),
  62. TEXT("_ficheiros"),
  63. TEXT("_arquivos"),
  64. TEXT("_dosyalar"),
  65. TEXT("_datoteke"),
  66. TEXT("_fitxers"),
  67. TEXT("_failid"),
  68. TEXT("_fails"),
  69. TEXT("_bylos"),
  70. TEXT("_fajlovi"),
  71. TEXT("_fitxategiak"),
  72. };
  73. // The reg value under HKCU\REGSTR_PATH_EXPLORER that specifies Connection ON/OFF switch
  74. #define REG_VALUE_NO_FILEFOLDER_CONNECTION TEXT("NoFileFolderConnection")
  75. ////////////////////////////////////////////////////////////////////////////
  76. ///// directory tree cache.
  77. // this is set if pdtnChild has not been traversed (as opposed to NULL which means
  78. // there are no children
  79. #define DTN_DELAYED ((PDIRTREENODE)-1)
  80. // DIRTREENODE is a node in a linked list/tree cache of the directory structure.
  81. // except for the top level (which is specified by the caller of the api), the order
  82. // are all files first, then all directories.
  83. typedef struct _dirtreenode {
  84. struct _dirtreenode *pdtnNext; // sibling
  85. struct _dirtreenode *pdtnChild; // head of children linked list
  86. struct _dirtreenode *pdtnParent;
  87. DWORD dwFileAttributes;
  88. FILETIME ftCreationTime;
  89. FILETIME ftLastWriteTime;
  90. DWORD nFileSizeLow;
  91. DWORD nFileSizeHigh;
  92. LARGE_INTEGER liFileSizeCopied;
  93. BOOL fNewRoot : 1;
  94. BOOL fDummy : 1; // this marks the node as a dummy node (a wildcard that didn't match anything)
  95. BOOL fConnectedElement : 1; // this marks the node as an element that was implicitly added
  96. // to the Move/Copy source list because of an office9 type of
  97. // connection established in the registry.
  98. //The following is a union because not all nodes need all the fields.
  99. union {
  100. // The following is valid only if fConnectedElement is FALSE.
  101. struct _dirtreenode *pdtnConnected;
  102. // The following structure is valid only if fConnectedElemet is TRUE.
  103. struct {
  104. LPTSTR pFromConnected; // if fNewRoot && fConnectedElement, then these two elements
  105. LPTSTR pToConnected; // have the pFrom and pTo.
  106. DWORD dwConfirmation; // The result of confirnation given by end-user
  107. } ConnectedInfo;
  108. };
  109. TCHAR szShortName[14];
  110. TCHAR szName[1]; // this struct is dynamic
  111. } DIRTREENODE, *PDIRTREENODE;
  112. typedef struct {
  113. BOOL fChanged;
  114. DWORD dwFiles; // number of files
  115. DWORD dwFolders; // number of folders
  116. LARGE_INTEGER liSize; // total size of all files
  117. } DIRTOTALS, *PDIRTOTALS;
  118. typedef struct {
  119. UINT oper;
  120. DIRTOTALS dtAll; // totals for all files
  121. DIRTOTALS dtDone; // totals of what's done
  122. BOOL fChangePosted;
  123. PDIRTREENODE pdtn; // first directory tree node
  124. PDIRTREENODE pdtnCurrent;
  125. PDIRTREENODE pdtnConnectedItems; //Pointer to the begining of connected elements node.
  126. TCHAR bDiskCheck[26];
  127. // how much does each operation cost in the progress...
  128. int iFilePoints;
  129. int iFolderPoints;
  130. int iSizePoints;
  131. LPTSTR pTo; // this holds the top level target list
  132. LPTSTR pFrom; // this holds the top level source list
  133. BOOL fMultiDest;
  134. TCHAR szSrcPath[MAX_PATH];
  135. TCHAR szDestPath[MAX_PATH]; // this is the current destination for pdtn and all it's children (not siblings)
  136. // lpszDestPath includes pdtn's first path component
  137. HDSA hdsaRenamePairs;
  138. } DIRTREEHEADER, *PDIRTREEHEADER;
  139. // We spend a lot of time creating simple PIDLs, so use this cache
  140. // to speed things up.
  141. typedef struct SIMPLEPIDLCACHE {
  142. IBindCtx *pbcFile; // Empty filesys bind context for files
  143. IBindCtx *pbcFolder; // Empty filesys bind context for folders
  144. IShellFolder *psfDesktop; // Desktop folder (for ParseDisplayName)
  145. int iInit; // 0 = not inited; 1 = inited; -1 = init failed
  146. IShellFolder *psf; // Current folder
  147. LPITEMIDLIST pidlFolder; // Current folder
  148. TCHAR szFolder[MAX_PATH]; // Current folder
  149. } SIMPLEPIDLCACHE, *PSIMPLEPIDLCACHE;
  150. typedef struct {
  151. int nRef; // struct reference count
  152. int nSourceFiles;
  153. LPTSTR lpCopyBuffer; // global file copy buffer
  154. UINT uSize; // size of this buffer
  155. FILEOP_FLAGS fFlags; // from SHFILEOPSTRUCT
  156. HWND hwndProgress; // dialog/progress window
  157. HWND hwndDlgParent; // parent window for message boxes
  158. CONFIRM_DATA cd; // confirmation stuff
  159. UNDOATOM *lpua; // the undo atom that this file operation will make
  160. BOOL fNoConfirmRecycle;
  161. BOOL bAbort;
  162. BOOL fMerge; // are we doing a merge of folders
  163. BOOL fDone;
  164. BOOL fProgressOk;
  165. BOOL fDTBuilt;
  166. BOOL fFromCDRom; // Clear readonly bits if copying from CDRom
  167. // folowing fields are used for giving estimated time for completion
  168. // feedback to the user during longer than MINTIME4FEEDBACK operations
  169. BOOL fFlushWrites; // Should we flush writes for destinations on slow links
  170. DWORD dwPreviousTime; // calculate transfer rate
  171. int iLastProgressPoints; // how many progress points we had the last time we updated the time est
  172. DWORD dwPointsPerSec;
  173. LPCTSTR lpszProgressTitle;
  174. LPSHFILEOPSTRUCT lpfo;
  175. DIRTREEHEADER dth;
  176. BOOL fInitialize;
  177. const WIN32_FIND_DATA* pfd;
  178. BOOL bStreamLossPossible; // Could stream loss happen in this directory?
  179. SIMPLEPIDLCACHE spc;
  180. } COPY_STATE, *LPCOPY_STATE;
  181. // we have a seperate struct that we pass off to the FOUIThread so that he can get to the pcs,
  182. // but since the FOUIThread can outlive the main thread (!!) in some cases, we can't let him have a
  183. // ref to pcs->lpfo since it is owned by SHFileOperations caller and we crash if we try to refrence
  184. // it after SHFileOperation returns and the caller has freed the memory. The only two things the
  185. // FOUIThread uses out of the pcs->lpfo are the wFunc and the lpszProgressTitle (to see if the
  186. // recycle bin was being emptied or not), so we make private copies of that info for the thread.
  187. typedef struct {
  188. COPY_STATE* pcs;
  189. UINT wFunc;
  190. BOOL bIsEmptyRBOp;
  191. } FOUITHREADINFO, *PFOUITHREADINFO;
  192. // Information to determine folder's movability to the recycle bin
  193. typedef struct {
  194. BOOL bProcessedRoot; // tells if we are the first call in the recursive chain and we need to do root-specific processing
  195. int cchBBDir; // count of characters in the recycle bin dir (eg "C:\Recycler\<sid>")
  196. int cchDelta; // count of characters that the path will increase (or decrease if negative) by when moved under the recycle bin directory
  197. ULONGLONG cbSize; // size of the folder
  198. TCHAR szNonDeletableFile[MAX_PATH]; // an output buffer that holds the name of the file that cannot be deleted, if one exists
  199. TCHAR szDir[MAX_PATH]; // input & scratch buffer for stack savings when recursing
  200. TCHAR szPath[MAX_PATH]; // scratch buffer for stack savings when recursing
  201. WIN32_FIND_DATA fd; // also for stack savings
  202. } FOLDERDELETEINFO;
  203. // function declarations
  204. void _ProcessNameMappings(LPTSTR pszTarget, UINT cchTarget, HDSA hdsaRenamePairs);
  205. int GetNameDialog(HWND hwnd, COPY_STATE *pcs, BOOL fMultiple,UINT wOp, LPTSTR pFrom, LPTSTR pTo);
  206. void AddRenamePairToHDSA(LPCTSTR pszOldPath, LPCTSTR pszNewPath, HDSA* phdsaRenamePairs);
  207. BOOL FOQueryAbort(COPY_STATE *pcs);
  208. UINT DTAllocConnectedItemNodes(PDIRTREEHEADER pdth, COPY_STATE *pcs, WIN32_FIND_DATA *pfd, LPTSTR pszPath, BOOL fRecurse, PDIRTREENODE *ppdtnConnectedItems);
  209. void CALLBACK FOUndo_Invoke(UNDOATOM *lpua);
  210. LONG CheckFolderSizeAndDeleteability(FOLDERDELETEINFO* pfdi, LPCOPY_STATE pcs);
  211. BOOL DeleteFileBB(LPTSTR pszFile, UINT cchFile, INT *piReturn, COPY_STATE *pcs, BOOL fIsDir, WIN32_FIND_DATA *pfd, HDPA *phdpaDeletedFiles);
  212. BOOL DTDiskCheck(PDIRTREEHEADER pdth, COPY_STATE *pcs, LPTSTR pszPath)
  213. {
  214. int iDrive = PathGetDriveNumber(pszPath);
  215. if (iDrive != -1)
  216. {
  217. if (!pdth->bDiskCheck[iDrive])
  218. {
  219. HWND hwnd = pcs->hwndDlgParent;
  220. TCHAR szDrive[] = TEXT("A:\\");
  221. szDrive[0] += (CHAR)iDrive;
  222. // Sometimes pszPath is a dir and sometimes it's a file. All we really care about is if the
  223. // drive is ready (inserted, formated, net path mapped, etc). We know that we don't have a
  224. // UNC path because PathGetDriveNumber would have failed and we are already busted in terms
  225. // of mounted volumes, again because we use PathGetDriveNumber, so we don't have to worry about
  226. // these two cases. As such we build the root path and use that instead.
  227. pdth->bDiskCheck[iDrive] = SUCCEEDED(SHPathPrepareForWrite(((pcs->fFlags & FOF_NOERRORUI) ? NULL : hwnd), NULL, szDrive, 0));
  228. }
  229. return pdth->bDiskCheck[iDrive];
  230. }
  231. return TRUE; // always succeed for net drives
  232. }
  233. //--------------------------------------------------------------------------------------------
  234. // Simple pidl cache stuff
  235. //--------------------------------------------------------------------------------------------
  236. void SimplePidlCache_Release(SIMPLEPIDLCACHE *pspc)
  237. {
  238. ATOMICRELEASE(pspc->pbcFile);
  239. ATOMICRELEASE(pspc->pbcFolder);
  240. ATOMICRELEASE(pspc->psfDesktop);
  241. ATOMICRELEASE(pspc->psf);
  242. ILFree(pspc->pidlFolder);
  243. }
  244. const WIN32_FIND_DATA c_fdFolder = { FILE_ATTRIBUTE_DIRECTORY };
  245. BOOL SimplePidlCache_Init(SIMPLEPIDLCACHE *pspc)
  246. {
  247. ASSERT(pspc->iInit == 0);
  248. if (SUCCEEDED(SHCreateFileSysBindCtx(NULL, &pspc->pbcFile)) &&
  249. SUCCEEDED(SHCreateFileSysBindCtx(&c_fdFolder, &pspc->pbcFolder)) &&
  250. SUCCEEDED(SHGetDesktopFolder(&pspc->psfDesktop)))
  251. {
  252. pspc->psf = pspc->psfDesktop;
  253. pspc->psf->lpVtbl->AddRef(pspc->psf);
  254. // It's okay to leave pidlFolder as NULL; ILCombine won't barf
  255. pspc->iInit = 1;
  256. return TRUE;
  257. }
  258. else
  259. {
  260. pspc->iInit = -1;
  261. return FALSE;
  262. }
  263. }
  264. LPITEMIDLIST SimplePidlCache_GetFilePidl(SIMPLEPIDLCACHE *pspc, LPCTSTR pszFile)
  265. {
  266. LPITEMIDLIST pidlChild;
  267. LPITEMIDLIST pidlRet;
  268. LPTSTR pszFileName;
  269. TCHAR szFolder[MAX_PATH];
  270. HRESULT hr;
  271. if (pspc->iInit < 0)
  272. return NULL; // Initialization failed
  273. if (!pspc->iInit && !SimplePidlCache_Init(pspc))
  274. return NULL;
  275. // If this file is in a different folder from the one we cached,
  276. // need to dump the old one and get a new one.
  277. hr = StringCchCopy(szFolder, ARRAYSIZE(szFolder), pszFile);
  278. if (FAILED(hr))
  279. return NULL;
  280. PathRemoveFileSpec(szFolder);
  281. // We use StrCmpC instead of lstrcmpi because the vast majority
  282. // of the time, the path will match even in case, and if we get
  283. // it wrong, it's no big whoop: we just don't use the cache.
  284. if (StrCmpC(pspc->szFolder, szFolder) != 0)
  285. {
  286. LPITEMIDLIST pidlFolder = NULL; // In case it's on the desktop
  287. IShellFolder *psf;
  288. if (szFolder[0]) // An actual folder
  289. {
  290. // Get a simple pidl to the folder.
  291. if (FAILED(pspc->psfDesktop->lpVtbl->ParseDisplayName(pspc->psfDesktop, NULL,
  292. pspc->pbcFolder, szFolder, NULL, &pidlFolder, NULL)))
  293. return NULL;
  294. }
  295. else // Going for the desktop
  296. {
  297. /* pidlFolder already preinitialized to NULL */
  298. }
  299. // Bind to that folder
  300. if (FAILED(SHBindToObject(pspc->psfDesktop, IID_X_PPV_ARG(IShellFolder, pidlFolder, &psf))))
  301. {
  302. ILFree(pidlFolder);
  303. return NULL;
  304. }
  305. hr = StringCchCopy(pspc->szFolder, ARRAYSIZE(pspc->szFolder), szFolder);
  306. if (FAILED(hr))
  307. {
  308. ILFree(pidlFolder);
  309. ATOMICRELEASE(psf);
  310. return NULL;
  311. }
  312. // Woo-hoo, everybody is happy. Save the results into our cache.
  313. ATOMICRELEASE(pspc->psf);
  314. pspc->psf = psf;
  315. ILFree(pspc->pidlFolder);
  316. pspc->pidlFolder = pidlFolder;
  317. }
  318. // Get a simple pidl to the filename
  319. pszFileName = PathFindFileName(pszFile); // T2W is a macro with multiple evaluation
  320. if (FAILED(pspc->psf->lpVtbl->ParseDisplayName(pspc->psf, NULL, pspc->pbcFile,
  321. pszFileName, NULL, &pidlChild, NULL)))
  322. return NULL;
  323. // Combine it with the parent
  324. pidlRet = ILCombine(pspc->pidlFolder, pidlChild);
  325. ILFree(pidlChild);
  326. return pidlRet;
  327. }
  328. //--------------------------------------------------------------------------------------------
  329. // ConvertToConnectedItemname:
  330. // Given a file/folder name, this function checks to see if it has any connection and if
  331. // there is a connection, then it will convert the given name to that of the connected element
  332. // and return length of the prefix. If no connection exists, it returns zero.
  333. // The fDirectory parameter specifies if the given filename is a FOLDER or not!
  334. //
  335. // dwBuffSize: The size of pszFileName buffer in CHARACTERS.
  336. //
  337. // Examples:
  338. // "foo.htm" => "foo*" (returns 3 because the prefix("foo") length is 3)
  339. // "foobar files" => "foobar.htm?" (returns 6 as the prefix length)
  340. //
  341. //--------------------------------------------------------------------------------------------
  342. int ConvertToConnectedItemName(LPTSTR pszFileName, DWORD dwBuffSize, BOOL fDirectory)
  343. {
  344. LPTSTR pszDest, pszConnectedElemSuffix;
  345. int iPrefixLength;
  346. HRESULT hr;
  347. if (fDirectory)
  348. {
  349. // Look for a suffix which is one of the standard suffixes.
  350. if (!(pszDest = (LPTSTR)PathFindSuffixArray(pszFileName, c_apszSuffixes, ARRAYSIZE(c_apszSuffixes))))
  351. return 0;
  352. // " files" suffix is found. Replace it with ".htm?"
  353. pszConnectedElemSuffix = TEXT(".htm?");
  354. }
  355. else
  356. {
  357. // Look for the extension ".htm" or ".html" and replace it with "*".
  358. if (!(pszDest = PathFindExtension(pszFileName)))
  359. return 0;
  360. if (lstrcmpi(pszDest, TEXT(".htm")) && (lstrcmpi(pszDest, TEXT(".html"))))
  361. return 0;
  362. // Extension ".htm" or ".html" is found. Replace it with "*"
  363. pszConnectedElemSuffix = (LPTSTR)c_szStar;
  364. }
  365. iPrefixLength = (int)(pszDest - pszFileName);
  366. //Replace the source suffix with the connected element's suffix.
  367. hr = StringCchCopy(pszDest, dwBuffSize - iPrefixLength, pszConnectedElemSuffix);
  368. if (FAILED(hr))
  369. return 0;
  370. return(iPrefixLength);
  371. }
  372. PDIRTREENODE DTAllocNode(PDIRTREEHEADER pdth, WIN32_FIND_DATA* pfd, PDIRTREENODE pdtnParent, PDIRTREENODE pdtnNext, BOOL fConnectedElement)
  373. {
  374. int iLen = pfd ? lstrlen(pfd->cFileName) * sizeof(TCHAR) : 0;
  375. PDIRTREENODE pdtn = (PDIRTREENODE)LocalAlloc(LPTR, sizeof(DIRTREENODE) + iLen);
  376. if (pdtn)
  377. {
  378. pdtn->fConnectedElement = fConnectedElement;
  379. // Initializing the following to NULL is not needed because of the LPTR (zero init) done
  380. // above.
  381. // if (fConnectedElement)
  382. //{
  383. // pdtn->ConnectedInfo.pFromConnected = pdtn->ConnectedInfo.pToConnected = NULL;
  384. // pdtn->ConnectedInfo.dwConfirmation = 0;
  385. //}
  386. //else
  387. // pdtn->pdtnConnected = NULL;
  388. pdtn->pdtnParent = pdtnParent;
  389. pdtn->pdtnNext = pdtnNext;
  390. if (pfd)
  391. {
  392. HRESULT hr;
  393. BOOL fOk = TRUE;
  394. hr = StringCchCopy(pdtn->szShortName, ARRAYSIZE(pdtn->szShortName), pfd->cAlternateFileName);
  395. if (FAILED(hr))
  396. {
  397. fOk = FALSE;
  398. }
  399. hr = StringCchCopy(pdtn->szName, iLen + 1, pfd->cFileName);
  400. if (FAILED(hr))
  401. {
  402. fOk = FALSE;
  403. }
  404. if (fOk)
  405. {
  406. pdtn->dwFileAttributes = pfd->dwFileAttributes;
  407. pdtn->ftCreationTime = pfd->ftCreationTime;
  408. pdtn->ftLastWriteTime = pfd->ftLastWriteTime;
  409. pdtn->nFileSizeLow = pfd->nFileSizeLow;
  410. pdtn->nFileSizeHigh = pfd->nFileSizeHigh;
  411. // only the stuff we care about
  412. if (ISDIRFINDDATA(*pfd))
  413. {
  414. pdth->dtAll.dwFolders++;
  415. pdtn->pdtnChild = DTN_DELAYED;
  416. }
  417. else
  418. {
  419. LARGE_INTEGER li;
  420. li.LowPart = pfd->nFileSizeLow;
  421. li.HighPart = pfd->nFileSizeHigh;
  422. pdth->dtAll.liSize.QuadPart += li.QuadPart;
  423. pdth->dtAll.dwFiles++;
  424. }
  425. // increment the header stats
  426. pdth->dtAll.fChanged = TRUE;
  427. }
  428. else
  429. {
  430. LocalFree(pdtn);
  431. pdtn = NULL;
  432. }
  433. }
  434. }
  435. return pdtn;
  436. }
  437. #if defined(DEBUG) /// && defined(DEBUGCOPY)
  438. void DebugDumpPDTN(PDIRTREENODE pdtn, LPTSTR ptext)
  439. {
  440. DebugMsg(TF_DEBUGCOPY, TEXT("***** PDTN %x (%s)"), pdtn, ptext);
  441. //Safe-guard against pdtn being NULL!
  442. if (pdtn)
  443. {
  444. DebugMsg(TF_DEBUGCOPY, TEXT("** %s %s"), pdtn->szShortName, pdtn->szName);
  445. DebugMsg(TF_DEBUGCOPY, TEXT("** %x %d"), pdtn->dwFileAttributes, pdtn->nFileSizeLow);
  446. DebugMsg(TF_DEBUGCOPY, TEXT("** %x %x %x"), pdtn->pdtnParent, pdtn->pdtnNext, pdtn->pdtnChild);
  447. DebugMsg(TF_DEBUGCOPY, TEXT("** NewRoot:%x, Connected:%x, Dummy:%x"), pdtn->fNewRoot, pdtn->fConnectedElement, pdtn->fDummy);
  448. if (pdtn->fConnectedElement)
  449. {
  450. DebugMsg(TF_DEBUGCOPY, TEXT("**** Connected: pFromConnected:%s, pToConnected:%s, dwConfirmation:%x"), pdtn->ConnectedInfo.pFromConnected,
  451. pdtn->ConnectedInfo.pToConnected, pdtn->ConnectedInfo.dwConfirmation);
  452. }
  453. else
  454. {
  455. DebugMsg(TF_DEBUGCOPY, TEXT("**** Origin: pdtnConnected:%x"), pdtn->pdtnConnected);
  456. }
  457. }
  458. else
  459. {
  460. DebugMsg(TF_DEBUGCOPY, TEXT("** NULL pointer(PDTN)"));
  461. }
  462. }
  463. #else
  464. #define DebugDumpPDTN(p, x) 0
  465. #endif
  466. BOOL DoesSuffixMatch(LPTSTR lpSuffix, const LPCTSTR *apSuffixes, int iArraySize)
  467. {
  468. while (iArraySize--)
  469. {
  470. // Note: This must be a case sensitive compare, because we don't want to pickup
  471. // "Program Files".
  472. if (!lstrcmp(lpSuffix, *apSuffixes++))
  473. return TRUE;
  474. }
  475. return FALSE;
  476. }
  477. //--------------------------------------------------------------------------------------------
  478. //
  479. // DTPathToDTNode:
  480. // This function is used to build a list of nodes that correspond to the given pszPath.
  481. // This list is built under "ppdtn". If ppdtnConnectedItems is given, another list of nodes that
  482. // correspond to the connected elements(files/folders) of the nodes in the first list is also built
  483. // under "ppdtnConnectedItems".
  484. //
  485. // WARNING: This parties directly on pszPath and pfd so that it doesn't need to allocate
  486. // on the stack. This recurses, so we want to use as little stack as possible
  487. //
  488. // this will wack off one component from pszPath
  489. //
  490. //
  491. // ppdtn: Points to where the header of the list being built will be stored.
  492. // ppdtnConnectedItems: If this is NULL, then we are not interested in finding and building the
  493. // connected elements. If this is NOT null, it points to where the header of
  494. // the connected items list will be stored.
  495. // fConnectedElement: Each node being built under ppdtn needs to be marked with this bit.
  496. // iPrefixLength: This parameter is zero if fConnectedElement is FALSE. Otherwise, it contains the
  497. // Length of the prefix part of the file or foldername (path is NOT included).
  498. // For example, if "c:\windows\foo*" is passed in, iPrefixLength is 3 (length of "foo")
  499. //
  500. // dwFilesOrFolders parameter can specify if we need to look for only FILES or FOLDERs or BOTH.
  501. #define DTF_FILES_ONLY 0x00000001 //Operate only on Files.
  502. #define DTF_FOLDERS_ONLY 0x00000002 //Operate only on Folders.
  503. #define DTF_FILES_AND_FOLDERS (DTF_FILES_ONLY | DTF_FOLDERS_ONLY) //Operate on files AND folders.
  504. UINT DTPathToDTNode(PDIRTREEHEADER pdth, COPY_STATE *pcs, LPTSTR pszPath, BOOL fRecurse,
  505. DWORD dwFilesOrFolders, PDIRTREENODE* ppdtn, WIN32_FIND_DATA *pfd,
  506. PDIRTREENODE pdtnParent, PDIRTREENODE* ppdtnConnectedItems, BOOL fConnectedElement,
  507. int iPrefixLength)
  508. {
  509. int iError = 0;
  510. // this points to the var where all items are inserted.
  511. // folders are placed after it, files are placed before
  512. // keep the stack vars to a minimum because this is recursive
  513. PDIRTREENODE *ppdtnMiddle = ppdtn;
  514. BOOL fNeedToFindNext = TRUE;
  515. HANDLE hfind = FindFirstFile(pszPath, pfd);
  516. DebugMsg(TF_DEBUGCOPY, TEXT("DTPathToDTNode Entering %s"), pszPath);
  517. *ppdtnMiddle = NULL; // in case there are no children
  518. if (hfind == INVALID_HANDLE_VALUE)
  519. {
  520. // this is allowable only if the path is wild...
  521. // and the parent exists
  522. if (PathIsWild(pszPath))
  523. {
  524. PathRemoveFileSpec(pszPath);
  525. if (PathFileExists(pszPath))
  526. {
  527. return 0;
  528. }
  529. }
  530. return OPER_ERROR | ERROR_FILE_NOT_FOUND;
  531. }
  532. //Remove the filespec before passing it onto DTAllocConnectedItemNodes.
  533. PathRemoveFileSpec(pszPath);
  534. do
  535. {
  536. // We skip the following files:
  537. // "." and ".." filenames
  538. // Folders when DTF_FILES_ONLY is set
  539. // Files when DTF_FOLDERS_ONLY is set
  540. if (!PathIsDotOrDotDot(pfd->cFileName) &&
  541. (((dwFilesOrFolders & DTF_FILES_ONLY) && !ISDIRFINDDATA(*pfd)) ||
  542. ((dwFilesOrFolders & DTF_FOLDERS_ONLY) && ISDIRFINDDATA(*pfd))))
  543. {
  544. //Check if we are looking for connected elements
  545. if ((!pdtnParent) && fConnectedElement)
  546. {
  547. // We found what we are looking for. If we are looking for a top-level connected item and
  548. // if it is a folder, then we need to make sure that the suffix exactly matches one of the
  549. // suffixes in the array c_apszSuffixes[].
  550. LPTSTR lpSuffix = (LPTSTR)(pfd->cFileName + iPrefixLength);
  551. if (ISDIRFINDDATA(*pfd))
  552. {
  553. // What we found is a directory!
  554. // See if it has one of the standard suffixes for connected folders.
  555. if (!DoesSuffixMatch(lpSuffix, c_apszSuffixes, ARRAYSIZE(c_apszSuffixes)))
  556. continue; //This is not what we look for. So, find next.
  557. }
  558. else
  559. {
  560. // What we found is a file (i.e Not a directory)
  561. // See if it has one of the standard suffixes for html files.
  562. if (lstrcmpi(lpSuffix, TEXT(".htm")) && lstrcmpi(lpSuffix, TEXT(".html")))
  563. continue; //This is not what we look for. So, find next.
  564. }
  565. // Now we know that we found the connected element that we looked for.
  566. // So, no need to FindNext again. We can get out of the loop after processing
  567. // it once.
  568. fNeedToFindNext = FALSE;
  569. }
  570. *ppdtnMiddle = DTAllocNode(pdth, pfd, pdtnParent, *ppdtnMiddle, fConnectedElement);
  571. if (!*ppdtnMiddle)
  572. {
  573. FindClose(hfind);
  574. return OPER_ERROR | ERROR_NOT_ENOUGH_MEMORY;
  575. }
  576. // make sure that the parent's pointer always points to the head of
  577. // this linked list
  578. if (*ppdtn == (*ppdtnMiddle)->pdtnNext)
  579. *ppdtn = (*ppdtnMiddle);
  580. DebugDumpPDTN(*ppdtnMiddle, TEXT("DTPathToDTNode, DTAllocNode"));
  581. //We need to check for Connected elements only for the top level items
  582. if ((!(pcs->fFlags & FOF_NO_CONNECTED_ELEMENTS)) && ppdtnConnectedItems)
  583. {
  584. //Make sure this is a top level item
  585. ASSERT(!pdtnParent);
  586. //Create a list of connected items and attach it to the head of the list.
  587. iError = DTAllocConnectedItemNodes(pdth, pcs, pfd, pszPath, fRecurse, ppdtnConnectedItems);
  588. DebugDumpPDTN(*ppdtnConnectedItems, TEXT("DTPathToDTNode, DTAllocConnectedNodes"));
  589. // It is possible that the connected files do not exist. That condition is not really
  590. // an error. So, we check for insufficient memory error condition alone here.
  591. if (iError == (OPER_ERROR | ERROR_NOT_ENOUGH_MEMORY))
  592. {
  593. FindClose(hfind);
  594. return(iError);
  595. }
  596. //If a connected item exists, then make the origin item point to this connected item.
  597. if (*ppdtnConnectedItems)
  598. {
  599. (*ppdtnMiddle)->pdtnConnected = *ppdtnConnectedItems;
  600. // Also by default, set the Confirmation result to NO so that the connected element
  601. // will not be copied/moved etc., in case of a conflict. However, if the origin had
  602. // a conflict, we would put up a confirmation dlg and the result of that dlg will
  603. // over-write this value.
  604. (*ppdtnConnectedItems)->ConnectedInfo.dwConfirmation = IDNO;
  605. }
  606. //Move to the last node in the connected items list.
  607. while (*ppdtnConnectedItems)
  608. ppdtnConnectedItems = &((*ppdtnConnectedItems)->pdtnNext);
  609. }
  610. else
  611. {
  612. // This should have been initialized to zero during allocation, but lets be paranoid
  613. ASSERT(NULL == (*ppdtnMiddle)->pdtnConnected);
  614. }
  615. // if this is not a directory, move the ppdtnMiddle up one
  616. if (!ISDIRFINDDATA(*pfd))
  617. {
  618. ppdtnMiddle = &(*ppdtnMiddle)->pdtnNext;
  619. }
  620. }
  621. } while (fNeedToFindNext && !FOQueryAbort(pcs) && FindNextFile(hfind, pfd));
  622. iError = 0; //It is possible that iError contains other errors value now! So, reset it!
  623. FindClose(hfind);
  624. // now go and recurse into folders (if desired)
  625. // we don't have to check to see if these pdtn's are dirs, because the
  626. // way we inserted them above ensures that everything in from of
  627. // ppdtnMiddle are folders
  628. // we're going to tack on a specific child
  629. // then add the *.* after that
  630. while (!FOQueryAbort(pcs) && *ppdtnMiddle)
  631. {
  632. BOOL fRecurseThisItem = fRecurse;
  633. if ((*ppdtnMiddle)->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
  634. {
  635. // Recurse into reparse points unless they asked not to
  636. if (pcs->fFlags & FOF_NORECURSEREPARSE)
  637. {
  638. fRecurseThisItem = FALSE;
  639. }
  640. }
  641. if (fRecurseThisItem)
  642. {
  643. if (PathAppend(pszPath, (*ppdtnMiddle)->szName))
  644. {
  645. if (PathAppend(pszPath, c_szStarDotStar))
  646. {
  647. // NULL indicates that we do not want to get the connected elements.
  648. // This is because we want the connected elements only for the top-level items.
  649. iError = DTPathToDTNode(pdth, pcs, pszPath, TRUE, DTF_FILES_AND_FOLDERS,
  650. &((*ppdtnMiddle)->pdtnChild), pfd, *ppdtnMiddle, NULL, fConnectedElement, 0);
  651. }
  652. else
  653. {
  654. iError = OPER_ERROR | DE_INVALIDFILES;
  655. }
  656. PathRemoveFileSpec(pszPath);
  657. }
  658. else
  659. {
  660. iError = OPER_ERROR | DE_INVALIDFILES;
  661. }
  662. }
  663. else
  664. {
  665. // if we don't want to recurse, just mark them all as having no children
  666. (*ppdtnMiddle)->pdtnChild = NULL;
  667. }
  668. if (iError)
  669. {
  670. return iError;
  671. }
  672. ppdtnMiddle = &(*ppdtnMiddle)->pdtnNext;
  673. }
  674. return 0;
  675. }
  676. UINT DTAllocConnectedItemNodes(PDIRTREEHEADER pdth, COPY_STATE *pcs, WIN32_FIND_DATA *pfd, LPTSTR pszPath, BOOL fRecurse, PDIRTREENODE *ppdtnConnectedItems)
  677. {
  678. // Since DTAllocConnectedItemNodes() gets called only for the top-level items in the src list,
  679. // there is no danger of this function getting called recursively. Hence, I didn't worry about
  680. // allocating the following on the stack.
  681. // If "too-much-stack-is-used" problem arises, we can optimize the stack usage by splitting
  682. // the following function into two such that the most common case (of no connection)
  683. // doesn't use much stack.
  684. DWORD dwFileOrFolder;
  685. TCHAR szFullPath[MAX_PATH];
  686. TCHAR szFileName[MAX_PATH];
  687. WIN32_FIND_DATA fd;
  688. int iPrefixLength; //This is the length of "foo" if the filename is "foo.htm" or "foo files"
  689. HRESULT hr;
  690. //Make a copy of the filename; This copy will get munged by ConvertToConnectedItemName().
  691. hr = StringCchCopy(szFileName, ARRAYSIZE(szFileName), pfd->cFileName);
  692. if (FAILED(hr))
  693. return 0; // No connection exist for too big names
  694. // Convert the given file/foder name into the connected item's name with wild card characters.
  695. iPrefixLength = ConvertToConnectedItemName(szFileName, ARRAYSIZE(szFileName), ISDIRFINDDATA(*pfd));
  696. if (iPrefixLength == 0)
  697. return 0; //No connections exist for the given folder/file.
  698. // Now szFileName has the name of connected element with wildcard character.
  699. // If the given element is a directory, we want to look for connected FILES only and
  700. // if the given element is a file, we want to look for connected FOLDERS only.
  701. dwFileOrFolder = ISDIRFINDDATA(*pfd) ? DTF_FILES_ONLY : DTF_FOLDERS_ONLY;
  702. // Form the file/folder name with the complete path!
  703. hr = StringCchCopy(szFullPath, ARRAYSIZE(szFullPath), pszPath);
  704. if (FAILED(hr))
  705. return 0;
  706. if (!PathAppend(szFullPath, szFileName))
  707. return 0;
  708. // The file-element has some "connected" items.
  709. DebugMsg(TF_DEBUGCOPY, TEXT("DTAllocConnectedItemNodes Looking for %s"), szFullPath);
  710. return(DTPathToDTNode(pdth, pcs, szFullPath, fRecurse, dwFileOrFolder, ppdtnConnectedItems, &fd, NULL, NULL, TRUE, iPrefixLength));
  711. }
  712. void DTInitProgressPoints(PDIRTREEHEADER pdth, COPY_STATE *pcs)
  713. {
  714. pdth->iFilePoints = 1;
  715. pdth->iFolderPoints = 1;
  716. switch (pcs->lpfo->wFunc)
  717. {
  718. case FO_RENAME:
  719. case FO_DELETE:
  720. pdth->iSizePoints = 0;
  721. break;
  722. case FO_COPY:
  723. pdth->iSizePoints = 1;
  724. break;
  725. case FO_MOVE:
  726. if (PathIsSameRoot(pcs->lpfo->pFrom, pcs->lpfo->pTo))
  727. {
  728. pdth->iSizePoints = 0;
  729. }
  730. else
  731. {
  732. // if it's across volumes, these points increase
  733. // because we need to nuke the source as well as
  734. // create the target...
  735. // whereas we don't need to nuke the "size" of the source
  736. pdth->iFilePoints = 2;
  737. pdth->iFolderPoints = 2;
  738. pdth->iSizePoints = 1;
  739. }
  740. break;
  741. }
  742. }
  743. UINT DTBuild(COPY_STATE* pcs)
  744. {
  745. PDIRTREEHEADER pdth = &pcs->dth;
  746. WIN32_FIND_DATA fd;
  747. TCHAR szPath[MAX_PATH];
  748. PDIRTREENODE *ppdtn;
  749. PDIRTREENODE *ppdtnConnectedItems;
  750. int iError = 0;
  751. HRESULT hr;
  752. pcs->dth.pFrom = (LPTSTR)pcs->lpfo->pFrom;
  753. pcs->dth.pTo = (LPTSTR)pcs->lpfo->pTo;
  754. // A tree of original items will be built under ppdtn.
  755. ppdtn = &pdth->pdtn;
  756. // A tree of items "connected" to the orginal items will be built under ppdtnConnectedItems.
  757. ppdtnConnectedItems = &pdth->pdtnConnectedItems;
  758. DTInitProgressPoints(pdth, pcs);
  759. while (!FOQueryAbort(pcs) && *pdth->pFrom)
  760. {
  761. BOOL fRecurse = TRUE;
  762. switch (pcs->lpfo->wFunc)
  763. {
  764. case FO_MOVE:
  765. // The move operation doesn't need to recurse if we are moving from and to the same
  766. // volume. In this case we know that we don't need to display any warnings for
  767. // things like LFN to 8.3 filename conversion or stream loss. Instead, we can do
  768. // the operation with one single win32 file operation that just does a rename.
  769. // NTRAID89119-2000/02/25-toddb
  770. // This is only true if we don't cross a mount point! If we cross
  771. // a mount point then we might have to warn about these things.
  772. if ((pcs->fFlags & FOF_NORECURSION) || PathIsSameRoot(pdth->pFrom, pdth->pTo))
  773. {
  774. fRecurse = FALSE;
  775. }
  776. break;
  777. case FO_COPY:
  778. // For a copy we always recurse unless we're told not to.
  779. if (pcs->fFlags & FOF_NORECURSION)
  780. {
  781. fRecurse = FALSE;
  782. }
  783. break;
  784. case FO_RENAME:
  785. // for a rename we never recurse
  786. fRecurse = FALSE;
  787. break;
  788. case FO_DELETE:
  789. // for a delete we don't need to recurse IF the recycle bin will be able to handle
  790. // the given item. If the recycle bin handles the delete then we can undo from
  791. // the recycle bin if we need to.
  792. if ((pcs->fFlags & FOF_ALLOWUNDO) && BBWillRecycle(pdth->pFrom, NULL))
  793. {
  794. fRecurse = FALSE;
  795. }
  796. break;
  797. }
  798. hr = StringCchCopy(szPath, ARRAYSIZE(szPath), pdth->pFrom);
  799. if (FAILED(hr))
  800. {
  801. iError = OPER_ERROR | DE_INVALIDFILES;
  802. break;
  803. }
  804. DebugMsg(TF_DEBUGCOPY, TEXT("DTBuild: %s"), szPath);
  805. // If the file is on removable media, we need to check for media in the drive.
  806. // Prompt the user to insert the media if it's missing.
  807. if (!DTDiskCheck(pdth, pcs, szPath))
  808. {
  809. iError = ERROR_CANCELLED;
  810. break;
  811. }
  812. iError = DTPathToDTNode(pdth, pcs, szPath, fRecurse,
  813. ((PathIsWild(pdth->pFrom) && (pcs->lpfo->fFlags & FOF_FILESONLY)) ? DTF_FILES_ONLY : DTF_FILES_AND_FOLDERS),
  814. ppdtn,&fd, NULL, ppdtnConnectedItems, FALSE, 0);
  815. DebugMsg(TF_DEBUGCOPY, TEXT("DTBuild: returned %d"), iError);
  816. // FEATURE: If an error occured we should allow the user to skip the file that caused the error. That way
  817. // if one of the source files doesn't exists the rest will still get copied. Do this only in the multi-
  818. // source case, blah blah blah. This helps in the case where one of the source files cannot be moved or
  819. // copied (usually due to Access Denied, could be insuffecent permissions or file is in use, etc).
  820. if (iError)
  821. break;
  822. if (!(*ppdtn) && PathIsWild(pdth->pFrom))
  823. {
  824. // no files are associated with this path... this
  825. // can happen when we have wildcards...
  826. // alloc a dummy node
  827. *ppdtn = DTAllocNode(pdth, NULL, NULL, NULL, FALSE);
  828. if (*ppdtn)
  829. {
  830. (*ppdtn)->fDummy = TRUE;
  831. }
  832. }
  833. if (*ppdtn)
  834. {
  835. // mark this as the start of a root spec... this is
  836. // necessary in case we have several wild specs
  837. (*ppdtn)->fNewRoot = TRUE;
  838. }
  839. if (*ppdtnConnectedItems)
  840. {
  841. // Mark this as the start of a root spec.
  842. (*ppdtnConnectedItems)->fNewRoot = TRUE;
  843. // For connected items, we need to remember the path.
  844. (*ppdtnConnectedItems)->ConnectedInfo.pFromConnected = pdth->pFrom;
  845. (*ppdtnConnectedItems)->ConnectedInfo.pToConnected = pdth->pTo;
  846. }
  847. while (*ppdtn)
  848. {
  849. ppdtn = &(*ppdtn)->pdtnNext;
  850. }
  851. while (*ppdtnConnectedItems)
  852. {
  853. ppdtnConnectedItems = &(*ppdtnConnectedItems)->pdtnNext;
  854. }
  855. pdth->pFrom += lstrlen(pdth->pFrom) + 1;
  856. if (pcs->lpfo->wFunc != FO_DELETE && (pcs->lpfo->fFlags & FOF_MULTIDESTFILES))
  857. {
  858. pdth->pTo += lstrlen(pdth->pTo) + 1;
  859. }
  860. }
  861. //Attach the "ConnectedElements" Tree to the end of the source element tree.
  862. *ppdtn = pcs->dth.pdtnConnectedItems;
  863. pcs->dth.pFrom = (LPTSTR)pcs->lpfo->pFrom;
  864. pcs->dth.pTo = (LPTSTR)pcs->lpfo->pTo;
  865. pcs->fDTBuilt = TRUE;
  866. // set up the initial time information
  867. pcs->dwPreviousTime = GetTickCount();
  868. pcs->dwPointsPerSec = 0;
  869. pcs->iLastProgressPoints = 0;
  870. return iError;
  871. }
  872. #define DTNIsRootNode(pdtn) ((pdtn)->pdtnParent == NULL)
  873. #define DTNIsDirectory(pdtn) (pdtn->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  874. // This macro determines if the given node is an "Origin" of a connection. i.e. Does this node
  875. // point to a connected element that needs to be moved/copied etc., along with it?
  876. // For example, if "foo.htm" is moved, "foo files" is also moved.
  877. // Here, "foo.htm" is the "Connect origin" (fConnectedElement = FALSE; pdtnConnected is valid)
  878. // and "foo files" is the "connected element". (fConnectedElement = TRUE;)
  879. #define DTNIsConnectOrigin(pdtn) ((!pdtn->fConnectedElement) && (pdtn->pdtnConnected != NULL))
  880. #define DTNIsConnected(pdtn) (pdtn && (pdtn->fConnectedElement))
  881. //
  882. UINT DTEnumChildren(PDIRTREEHEADER pdth, COPY_STATE *pcs, BOOL fRecurse, DWORD dwFileOrFolder)
  883. {
  884. int iError = 0;
  885. if (pdth->pdtnCurrent->pdtnChild == DTN_DELAYED)
  886. {
  887. WIN32_FIND_DATA fd;
  888. // fill in all the children and update the stats in pdth
  889. TCHAR szPath[MAX_PATH];
  890. if (PathCombine(szPath, pdth->szSrcPath, c_szStarDotStar))
  891. {
  892. iError = DTPathToDTNode(pdth, pcs, szPath, fRecurse, dwFileOrFolder,
  893. &pdth->pdtnCurrent->pdtnChild, &fd, pdth->pdtnCurrent, NULL, pdth->pdtnCurrent->fConnectedElement, 0);
  894. }
  895. else
  896. {
  897. iError = OPER_ERROR | DE_INVALIDFILES;
  898. }
  899. // If we get "File Not Found" Error now and if it is a connected item, then this item
  900. // must have already been moved/renamed/deleted etc., So, this is not really an error.
  901. // All this means is that this connected item was also explicitly selected and hence appeared
  902. // as or "Origin" item earlier in the list and it had already been operated upon.
  903. // So, reset the error here.
  904. // (Example: If end-user selects "foo.htm" AND "foo files" folder and moves them, then we
  905. // will get a file-not-found error when we attempt to move the connected items. To avoid
  906. // this error dialog, we reset the error here.)
  907. if (DTNIsConnected(pdth->pdtnCurrent) && (iError == (OPER_ERROR | ERROR_FILE_NOT_FOUND)))
  908. iError = 0;
  909. }
  910. return iError;
  911. }
  912. //
  913. // DTNGetConfirmationResult:
  914. // When a file("foo.htm") is moved/copied, we may put up a confirmation dialog in case
  915. // of a conflict and the end-user might have responded saying "Yes", "no" etc., When the
  916. // corresponding connected element ("foo files") is also moved/copied etc., we should NOT put up
  917. // a confirmation dialog again. We must simply store the answer to the original confirmation and
  918. // use it later.
  919. // This function retries the result of the original confirmation from the top-level connected
  920. // element.
  921. int DTNGetConfirmationResult(PDIRTREENODE pdtn)
  922. {
  923. //Confirmation results are saved only for Connected items; Not for Connection Origins.
  924. if (!pdtn || !DTNIsConnected(pdtn))
  925. return 0;
  926. //Confirmation results are stored only at the top-level node. So, go there.
  927. while (pdtn->pdtnParent)
  928. pdtn = pdtn->pdtnParent;
  929. return(pdtn->ConnectedInfo.dwConfirmation);
  930. }
  931. BOOL DTGetWin32FindData(PDIRTREENODE pdtn, WIN32_FIND_DATA* pfd)
  932. {
  933. HRESULT hr;
  934. BOOL fOk = TRUE;
  935. // only the stuff we care about
  936. hr = StringCchCopy(pfd->cAlternateFileName, ARRAYSIZE(pfd->cAlternateFileName), pdtn->szShortName);
  937. if (FAILED(hr))
  938. {
  939. fOk = FALSE;
  940. }
  941. hr = StringCchCopy(pfd->cFileName, ARRAYSIZE(pfd->cFileName), pdtn->szName);
  942. if (FAILED(hr))
  943. {
  944. fOk = FALSE;
  945. }
  946. pfd->dwFileAttributes = pdtn->dwFileAttributes;
  947. pfd->ftCreationTime = pdtn->ftCreationTime;
  948. pfd->ftLastWriteTime = pdtn->ftLastWriteTime;
  949. pfd->nFileSizeLow = pdtn->nFileSizeLow;
  950. pfd->nFileSizeHigh = pdtn->nFileSizeHigh;
  951. return fOk;
  952. }
  953. void DTSetFileCopyProgress(PDIRTREEHEADER pdth, LARGE_INTEGER liRead)
  954. {
  955. LARGE_INTEGER liDelta;
  956. liDelta.QuadPart = (liRead.QuadPart - pdth->pdtnCurrent->liFileSizeCopied.QuadPart);
  957. DebugMsg(TF_DEBUGCOPY, TEXT("DTSetFileCopyProgress %d %d %d"), liDelta.LowPart, liRead.LowPart, pdth->dtDone.liSize.QuadPart);
  958. pdth->pdtnCurrent->liFileSizeCopied.QuadPart += liDelta.QuadPart;
  959. pdth->dtDone.liSize.QuadPart += liDelta.QuadPart;
  960. DebugMsg(TF_DEBUGCOPY, TEXT("DTSetFileCopyProgress %d %d"), liDelta.LowPart, pdth->dtDone.liSize.LowPart);
  961. pdth->dtDone.fChanged = TRUE;
  962. }
  963. void DTFreeNode(PDIRTREEHEADER pdth, PDIRTREENODE pdtn)
  964. {
  965. if (pdth)
  966. {
  967. ASSERT(pdtn->pdtnChild == NULL || pdtn->pdtnChild == DTN_DELAYED);
  968. // we're done with this node.. update the header totals
  969. if (DTNIsDirectory(pdtn))
  970. {
  971. pdth->dtDone.dwFolders++;
  972. }
  973. else
  974. {
  975. LARGE_INTEGER li;
  976. li.LowPart = pdtn->nFileSizeLow;
  977. li.HighPart = pdtn->nFileSizeHigh;
  978. pdth->dtDone.dwFiles++;
  979. pdth->dtDone.liSize.QuadPart += (li.QuadPart - pdtn->liFileSizeCopied.QuadPart);
  980. }
  981. pdth->dtDone.fChanged = TRUE;
  982. // repoint parent pointer
  983. if (!pdtn->pdtnParent)
  984. {
  985. // no parent... must be a root type thing
  986. ASSERT(pdth->pdtn == pdtn);
  987. pdth->pdtn = pdtn->pdtnNext;
  988. }
  989. else
  990. {
  991. ASSERT(pdtn->pdtnParent->pdtnChild == pdtn);
  992. if (pdtn->pdtnParent->pdtnChild == pdtn)
  993. {
  994. // if my parent was pointing to me, point him to my sib
  995. pdtn->pdtnParent->pdtnChild = pdtn->pdtnNext;
  996. }
  997. }
  998. }
  999. LocalFree(pdtn);
  1000. }
  1001. // this frees all children of (but NOT including) the current node.
  1002. // it doesn' free the current node because it's assumed that
  1003. // DTGoToNextNode will be called right afterwards, and that will
  1004. // free the current node
  1005. void DTFreeChildrenNodes(PDIRTREEHEADER pdth, PDIRTREENODE pdtn)
  1006. {
  1007. PDIRTREENODE pdtnChild = pdtn->pdtnChild;
  1008. while (pdtnChild && pdtnChild != DTN_DELAYED)
  1009. {
  1010. PDIRTREENODE pdtnNext = pdtnChild->pdtnNext;
  1011. // recurse and free these children
  1012. if (DTNIsDirectory(pdtnChild))
  1013. {
  1014. DTFreeChildrenNodes(pdth, pdtnChild);
  1015. }
  1016. DTFreeNode(pdth, pdtnChild);
  1017. pdtnChild = pdtnNext;
  1018. }
  1019. pdtn->pdtnChild = NULL;
  1020. }
  1021. void DTForceEnumChildren(PDIRTREEHEADER pdth)
  1022. {
  1023. if (!pdth->pdtnCurrent->pdtnChild)
  1024. pdth->pdtnCurrent->pdtnChild = DTN_DELAYED;
  1025. }
  1026. void DTAbortCurrentNode(PDIRTREEHEADER pdth)
  1027. {
  1028. DTFreeChildrenNodes((pdth), (pdth)->pdtnCurrent);
  1029. if (pdth->oper == OPER_ENTERDIR)
  1030. pdth->oper = OPER_LEAVEDIR;
  1031. }
  1032. void DTCleanup(PDIRTREEHEADER pdth)
  1033. {
  1034. PDIRTREENODE pdtn;
  1035. while (pdth->pdtnCurrent && pdth->pdtnCurrent->pdtnParent)
  1036. {
  1037. // in case we bailed deep in a tree
  1038. pdth->pdtnCurrent = pdth->pdtnCurrent->pdtnParent;
  1039. }
  1040. while (pdth->pdtnCurrent)
  1041. {
  1042. pdtn = pdth->pdtnCurrent;
  1043. pdth->pdtnCurrent = pdtn->pdtnNext;
  1044. DTFreeChildrenNodes(NULL, pdtn);
  1045. DTFreeNode(NULL, pdtn);
  1046. }
  1047. }
  1048. BOOL DTInitializePaths(PDIRTREEHEADER pdth, COPY_STATE *pcs)
  1049. {
  1050. HRESULT hr;
  1051. TCHAR szTemp[MAX_PATH];
  1052. ASSERT(pdth->pdtnCurrent); // If we have no current node then how can we Initialize its paths?
  1053. hr = StringCchCopy(szTemp, ARRAYSIZE(szTemp), pdth->pFrom);
  1054. if (FAILED(hr))
  1055. {
  1056. return FALSE;
  1057. }
  1058. // For the "Origins" we need to do this only if a wild card exists. However, for connected elements,
  1059. // we need to do this everytime because connected elements may not exist for every "Origins"
  1060. if (PathIsWild(pdth->pFrom) || (pdth->pdtnCurrent->fNewRoot && DTNIsConnected(pdth->pdtnCurrent)))
  1061. {
  1062. PathRemoveFileSpec(szTemp);
  1063. if (!PathAppend(szTemp, pdth->pdtnCurrent->szName))
  1064. return FALSE;
  1065. }
  1066. hr = StringCchCopy(pdth->szSrcPath, ARRAYSIZE(pdth->szSrcPath), szTemp);
  1067. if (FAILED(hr))
  1068. {
  1069. // This should never fail because pdth->szSrcPath is the same size (MAX_PATH) as szTemp
  1070. return FALSE;
  1071. }
  1072. if (!pdth->pTo)
  1073. {
  1074. // no dest, make it the same as the source and we're done
  1075. hr = StringCchCopy(pdth->szDestPath, ARRAYSIZE(pdth->szDestPath), pdth->szSrcPath);
  1076. if (FAILED(hr))
  1077. {
  1078. return FALSE;
  1079. }
  1080. return TRUE;
  1081. }
  1082. hr = StringCchCopy(szTemp, ARRAYSIZE(szTemp), pdth->pTo);
  1083. if (FAILED(hr))
  1084. {
  1085. return FALSE;
  1086. }
  1087. if (!pdth->fMultiDest)
  1088. {
  1089. if (!PathAppend(szTemp, pdth->pdtnCurrent->szName))
  1090. return FALSE;
  1091. }
  1092. else
  1093. {
  1094. //When undo of a move operation is done, fMultiDest is set.
  1095. // When fMultiDest is set, we need to strip out the filename given by pTo and
  1096. // append the current filename.
  1097. // For RENAME operations, the source and destination names are different. This is handled
  1098. // seperately below. So, we handle only other operations here where source and dest names are the same.
  1099. if ((pcs->lpfo->wFunc != FO_RENAME) && pdth->pdtnCurrent->fNewRoot && DTNIsConnected(pdth->pdtnCurrent))
  1100. {
  1101. PathRemoveFileSpec(szTemp);
  1102. if (!PathAppend(szTemp, pdth->pdtnCurrent->szName))
  1103. return FALSE;
  1104. }
  1105. }
  1106. hr = StringCchCopy(pdth->szDestPath, ARRAYSIZE(pdth->szDestPath), szTemp);
  1107. if (FAILED(hr))
  1108. {
  1109. // This should never fail because pdth->szDestPath is the same size (MAX_PATH) as szTemp
  1110. return FALSE;
  1111. }
  1112. //We will never try to rename a connected element! Make sure we don't hit this!
  1113. ASSERT(!((pcs->lpfo->wFunc == FO_RENAME) && DTNIsConnected(pdth->pdtnCurrent)));
  1114. return TRUE;
  1115. }
  1116. UINT DTValidatePathNames(PDIRTREEHEADER pdth, UINT operation, COPY_STATE * pcs)
  1117. {
  1118. if (pcs->lpfo->wFunc != FO_DELETE)
  1119. {
  1120. // Why process name mappings? Here's why. If we are asked to copy directory "c:\foo" and
  1121. // file "c:\foo\file" to another directory (say "d:\") we might have a name confilct when
  1122. // we copy "c:\foo" so instead we create "d:\Copy Of foo". Later, we walk to the second
  1123. // dirtree node and we are asked to copy "c:\foo\file" to "d:\foo", all of which is valid.
  1124. // HOWEVER, it's not what we want to do. We use _ProccessNameMappings to convert
  1125. // "d:\foo\file" into "d:\Copy of foo\file".
  1126. _ProcessNameMappings(pdth->szDestPath, ARRAYSIZE(pdth->szDestPath), pdth->hdsaRenamePairs);
  1127. // REVIEW, do we need to do the name mapping here or just let the
  1128. // VFAT do it? if vfat does it we need to rip out all of the GetNameDialog() stuff.
  1129. if ((operation != OPER_LEAVEDIR) &&
  1130. !IsLFNDrive(pdth->szDestPath) &&
  1131. PathIsLFNFileSpec(PathFindFileName(pdth->szSrcPath)) &&
  1132. PathIsLFNFileSpec(PathFindFileName(pdth->szDestPath)))
  1133. {
  1134. int iRet;
  1135. TCHAR szOldDest[MAX_PATH];
  1136. HRESULT hr;
  1137. hr = StringCchCopy(szOldDest, ARRAYSIZE(szOldDest), pdth->szDestPath);
  1138. if (FAILED(hr))
  1139. {
  1140. return OPER_ERROR | DE_INVALIDFILES;
  1141. }
  1142. iRet = GetNameDialog(pcs->hwndDlgParent, pcs,
  1143. (pcs->nSourceFiles != 1) || !DTNIsRootNode(pdth->pdtnCurrent), // if we're entering a dir, multiple spec, or not at root
  1144. operation, pdth->szSrcPath, pdth->szDestPath);
  1145. switch (iRet)
  1146. {
  1147. case IDNO:
  1148. case IDCANCEL:
  1149. return iRet;
  1150. default:
  1151. AddRenamePairToHDSA(szOldDest, pdth->szDestPath, &pcs->dth.hdsaRenamePairs);
  1152. break;
  1153. }
  1154. }
  1155. if (operation == OPER_ENTERDIR)
  1156. {
  1157. // Make sure the new directory is not a subdir of the original...
  1158. int cchFrom = lstrlen(pdth->szSrcPath);
  1159. // NTRAID89511-2000/02/25-KishoreP
  1160. // Shouldn't we get the short names for both these directories and compair those?
  1161. // Otherwise I can copy "C:\Long Directory Name" to "C:\LongDi~1\foo" without error.
  1162. if (!(pcs->fFlags & FOF_RENAMEONCOLLISION) &&
  1163. !StrCmpNI(pdth->szSrcPath, pdth->szDestPath, cchFrom))
  1164. {
  1165. TCHAR chNext = pdth->szDestPath[cchFrom]; // Get the next char in the dest.
  1166. if (!chNext)
  1167. {
  1168. return OPER_ERROR | DE_DESTSAMETREE;
  1169. }
  1170. else if (chNext == TEXT('\\'))
  1171. {
  1172. // The two fully qualified strings are equal up to the end
  1173. // of the source directory ==> the destination is a subdir.
  1174. // Must return an error.
  1175. // if, stripping the last file name and the backslash give the same length, they are the
  1176. // same file/folder
  1177. if ((PathFindFileName(pdth->szDestPath) - pdth->szDestPath - 1) ==
  1178. lstrlen(pdth->szSrcPath))
  1179. {
  1180. return OPER_ERROR | DE_DESTSAMETREE;
  1181. }
  1182. else
  1183. {
  1184. return OPER_ERROR | DE_DESTSUBTREE;
  1185. }
  1186. }
  1187. }
  1188. }
  1189. }
  1190. return 0;
  1191. }
  1192. // this moves to the next node (child, sib, parent) and sets up the
  1193. // directory path info and oper state
  1194. UINT DTGoToNextNode(PDIRTREEHEADER pdth, COPY_STATE *pcs)
  1195. {
  1196. UINT oper = OPER_ENTERDIR; // the default
  1197. int iError;
  1198. if (!pdth->pdtnCurrent)
  1199. {
  1200. pdth->pdtnCurrent = pdth->pdtn;
  1201. if (pdth->pdtnCurrent)
  1202. {
  1203. if (pdth->pdtnCurrent->fDummy)
  1204. {
  1205. // if this is just a placeholder... go on to the next one
  1206. return DTGoToNextNode(pdth, pcs);
  1207. }
  1208. if (!DTInitializePaths(pdth, pcs))
  1209. {
  1210. return OPER_ERROR | DE_INVALIDFILES;
  1211. }
  1212. }
  1213. else
  1214. {
  1215. // Our tree is completely empty.
  1216. // REVIEW: What do we do here? If pdtnCurrent is still NULL then our list is completely empty.
  1217. // Is that a bug or what? My hunch is that we should return an error code here, most likely
  1218. // OPER_ERROR | DE_INVALIDFILES. If we do nothing here then we will fail silently.
  1219. return OPER_ERROR | DE_INVALIDFILES;
  1220. }
  1221. }
  1222. else
  1223. {
  1224. UINT iError;
  1225. BOOL fFreeLastNode = TRUE;
  1226. PDIRTREENODE pdtnLastCurrent = pdth->pdtnCurrent;
  1227. TCHAR szTemp[MAX_PATH];
  1228. HRESULT hr;
  1229. if (iError = DTEnumChildren(pdth, pcs, FALSE, DTF_FILES_AND_FOLDERS))
  1230. return iError;
  1231. if (pdth->pdtnCurrent->pdtnChild)
  1232. {
  1233. fFreeLastNode = FALSE;
  1234. pdth->pdtnCurrent = pdth->pdtnCurrent->pdtnChild;
  1235. // if the source long name is too long, try the short name
  1236. if (!PathCombine(szTemp, pdth->szSrcPath, pdth->pdtnCurrent->szName))
  1237. {
  1238. if (!PathCombine(szTemp, pdth->szSrcPath, pdth->pdtnCurrent->szShortName))
  1239. {
  1240. return OPER_ERROR | DE_INVALIDFILES;
  1241. }
  1242. }
  1243. hr = StringCchCopy(pdth->szSrcPath, ARRAYSIZE(pdth->szSrcPath), szTemp);
  1244. if (FAILED(hr))
  1245. {
  1246. return OPER_ERROR | DE_INVALIDFILES;
  1247. }
  1248. // if the dest long name is too long, try the short name
  1249. if (!PathCombine(szTemp, pdth->szDestPath, pdth->pdtnCurrent->szName))
  1250. {
  1251. if (!PathCombine(szTemp, pdth->szDestPath, pdth->pdtnCurrent->szShortName))
  1252. {
  1253. return OPER_ERROR | DE_INVALIDFILES;
  1254. }
  1255. }
  1256. hr = StringCchCopy(pdth->szDestPath, ARRAYSIZE(pdth->szDestPath), szTemp);
  1257. if (FAILED(hr))
  1258. {
  1259. return OPER_ERROR | DE_INVALIDFILES;
  1260. }
  1261. }
  1262. else if (pdth->oper == OPER_ENTERDIR)
  1263. {
  1264. // if the last operation was an enterdir and it has no children
  1265. // (because it failed the above test
  1266. // then we should do a leave dir on it now
  1267. oper = OPER_LEAVEDIR;
  1268. fFreeLastNode = FALSE;
  1269. }
  1270. else if (pdth->pdtnCurrent->pdtnNext)
  1271. {
  1272. pdth->pdtnCurrent = pdth->pdtnCurrent->pdtnNext;
  1273. if (!pdth->pdtnCurrent->pdtnParent)
  1274. {
  1275. // if this was the top, we need to build the next path info
  1276. // from scratch
  1277. if (pdth->pdtnCurrent->fNewRoot)
  1278. {
  1279. if (pdth->pdtnCurrent->fConnectedElement)
  1280. {
  1281. // Since this is a new root in a Connected list, the pFrom and pTo are
  1282. // stored in the node itself. This is needed because Connected elements may
  1283. // not exist for every item in the source list and we do not want to create dummy
  1284. // nodes for each one of them. So, pFrom and pTo are stored for every NewRoot of
  1285. // connected elements and we use these here.
  1286. pdth->pFrom = pdth->pdtnCurrent->ConnectedInfo.pFromConnected;
  1287. pdth->pTo = pdth->pdtnCurrent->ConnectedInfo.pToConnected;
  1288. }
  1289. else
  1290. {
  1291. // go to the next path pair
  1292. pdth->pFrom += lstrlen(pdth->pFrom) + 1;
  1293. if (pdth->pTo)
  1294. {
  1295. if (pdth->fMultiDest)
  1296. {
  1297. pdth->pTo += lstrlen(pdth->pTo) + 1;
  1298. }
  1299. }
  1300. }
  1301. }
  1302. if (pdth->pdtnCurrent->fDummy)
  1303. {
  1304. // if this is just a placeholder... go on to the next one
  1305. if (fFreeLastNode)
  1306. {
  1307. DTFreeNode(pdth, pdtnLastCurrent);
  1308. }
  1309. return DTGoToNextNode(pdth, pcs);
  1310. }
  1311. DTInitializePaths(pdth, pcs);
  1312. }
  1313. else
  1314. {
  1315. PathRemoveFileSpec(pdth->szSrcPath);
  1316. PathRemoveFileSpec(pdth->szDestPath);
  1317. // if the source long name is too long, try the short name
  1318. if (!PathCombine(szTemp, pdth->szSrcPath, pdth->pdtnCurrent->szName))
  1319. {
  1320. if (!PathCombine(szTemp, pdth->szSrcPath, pdth->pdtnCurrent->szShortName))
  1321. {
  1322. return OPER_ERROR | DE_INVALIDFILES;
  1323. }
  1324. }
  1325. hr = StringCchCopy(pdth->szSrcPath, ARRAYSIZE(pdth->szSrcPath), szTemp);
  1326. if (FAILED(hr))
  1327. {
  1328. return OPER_ERROR | DE_INVALIDFILES;
  1329. }
  1330. // if the dest long name is too long, try the short name
  1331. if (!PathCombine(szTemp, pdth->szDestPath, pdth->pdtnCurrent->szName))
  1332. {
  1333. if (!PathCombine(szTemp, pdth->szDestPath, pdth->pdtnCurrent->szShortName))
  1334. {
  1335. return OPER_ERROR | DE_INVALIDFILES;
  1336. }
  1337. }
  1338. hr = StringCchCopy(pdth->szDestPath, ARRAYSIZE(pdth->szDestPath), szTemp);
  1339. if (FAILED(hr))
  1340. {
  1341. return OPER_ERROR | DE_INVALIDFILES;
  1342. }
  1343. }
  1344. }
  1345. else
  1346. {
  1347. oper = OPER_LEAVEDIR;
  1348. PathRemoveFileSpec(pdth->szSrcPath);
  1349. PathRemoveFileSpec(pdth->szDestPath);
  1350. pdth->pdtnCurrent = pdth->pdtnCurrent->pdtnParent;
  1351. }
  1352. if (fFreeLastNode)
  1353. {
  1354. DTFreeNode(pdth, pdtnLastCurrent);
  1355. }
  1356. }
  1357. if (!pdth->pdtnCurrent)
  1358. {
  1359. // no more! we're done!
  1360. return 0;
  1361. }
  1362. DebugDumpPDTN(pdth->pdtnCurrent, TEXT("PDTNCurrent"));
  1363. if (oper == OPER_ENTERDIR)
  1364. {
  1365. if (pcs->lpfo->wFunc == FO_RENAME || !DTNIsDirectory(pdth->pdtnCurrent))
  1366. {
  1367. oper = OPER_DOFILE;
  1368. }
  1369. }
  1370. if (DTNIsRootNode(pdth->pdtnCurrent))
  1371. {
  1372. // we need to diskcheck the source and target because this might
  1373. // be the first time we've seen this drive
  1374. if (!DTDiskCheck(pdth, pcs, pdth->szSrcPath) ||
  1375. !DTDiskCheck(pdth, pcs, pdth->szDestPath))
  1376. {
  1377. pcs->bAbort = TRUE;
  1378. return 0;
  1379. }
  1380. }
  1381. iError = DTValidatePathNames(pdth, oper, pcs);
  1382. if (iError)
  1383. {
  1384. if (iError & OPER_ERROR)
  1385. {
  1386. //For connected nodes, ignore the error and silently abort the node!
  1387. if (DTNIsConnected(pdth->pdtnCurrent))
  1388. {
  1389. DTAbortCurrentNode(pdth);
  1390. return DTGoToNextNode(pdth, pcs);
  1391. }
  1392. else
  1393. return iError;
  1394. }
  1395. else
  1396. {
  1397. switch (iError)
  1398. {
  1399. case IDNO:
  1400. DTAbortCurrentNode(pdth);
  1401. pcs->lpfo->fAnyOperationsAborted = TRUE;
  1402. return DTGoToNextNode(pdth, pcs);
  1403. case IDCANCEL:
  1404. // User cancelled the operation
  1405. pcs->bAbort = TRUE;
  1406. return 0;
  1407. }
  1408. }
  1409. }
  1410. pdth->oper = oper;
  1411. return oper;
  1412. }
  1413. int CopyMoveRetry(COPY_STATE *pcs, LPCTSTR pszDest, int error, ULARGE_INTEGER* puliFileSize);
  1414. void CopyError(LPCOPY_STATE, LPCTSTR, LPCTSTR, int, UINT, int);
  1415. void SetProgressTime(COPY_STATE *pcs);
  1416. void SetProgressText(COPY_STATE *pcs, LPCTSTR pszFrom, LPCTSTR pszTo);
  1417. void FOUndo_AddInfo(UNDOATOM *lpua, LPTSTR lpszSrc, LPTSTR lpszDest, DWORD dwAttributes);
  1418. void CALLBACK FOUndo_Release(UNDOATOM *lpua);
  1419. void FOUndo_FileReallyDeleted(LPTSTR lpszFile);
  1420. void AddRenamePairToHDSA(LPCTSTR pszOldPath, LPCTSTR pszNewPath, HDSA* phdsaRenamePairs);
  1421. BOOL_PTR CALLBACK FOFProgressDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam);
  1422. typedef struct {
  1423. LPTSTR lpszName;
  1424. DWORD dwAttributes;
  1425. } FOUNDO_DELETEDFILEINFO, *LPFOUNDO_DELETEDFILEINFO;
  1426. typedef struct {
  1427. HDPA hdpa;
  1428. HDSA hdsa;
  1429. } FOUNDODATA, *LPFOUNDODATA;
  1430. void ReleasePCS(COPY_STATE *pcs)
  1431. {
  1432. ASSERT( 0 != pcs->nRef );
  1433. if (0 == InterlockedDecrement(&pcs->nRef))
  1434. {
  1435. SimplePidlCache_Release(&pcs->spc);
  1436. LocalFree(pcs);
  1437. }
  1438. }
  1439. DWORD CALLBACK AddRefPCS(COPY_STATE *pcs)
  1440. {
  1441. return InterlockedIncrement(&pcs->nRef);
  1442. }
  1443. DWORD CALLBACK FOUIThreadProc(COPY_STATE *pcs)
  1444. {
  1445. DebugMsg(TF_DEBUGCOPY, TEXT("FOUIThreadProc -- Begin"));
  1446. Sleep(SHOW_PROGRESS_TIMEOUT);
  1447. if (!pcs->fDone)
  1448. {
  1449. HWND hwndParent;
  1450. FOUITHREADINFO fouiti = {0};
  1451. ENTERCRITICAL;
  1452. if (!pcs->fDone)
  1453. {
  1454. // need to check again within the critsec to make sure that pcs->lpfo is still valid
  1455. fouiti.pcs = pcs;
  1456. fouiti.wFunc = pcs->lpfo->wFunc;
  1457. fouiti.bIsEmptyRBOp = ((pcs->lpfo->lpszProgressTitle == MAKEINTRESOURCE(IDS_BB_EMPTYINGWASTEBASKET)) ||
  1458. (pcs->lpfo->lpszProgressTitle == MAKEINTRESOURCE(IDS_BB_DELETINGWASTEBASKETFILES)));
  1459. hwndParent = pcs->lpfo->hwnd;
  1460. }
  1461. LEAVECRITICAL;
  1462. if (fouiti.pcs)
  1463. {
  1464. HWND hwnd = CreateDialogParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_MOVECOPYPROGRESS),
  1465. hwndParent, FOFProgressDlgProc, (LPARAM)&fouiti);
  1466. if (hwnd)
  1467. {
  1468. MSG msg;
  1469. DWORD dwShowTime;
  1470. int iShowTimeLeft;
  1471. // crit section to sync with main thread termination
  1472. ENTERCRITICAL;
  1473. if (!pcs->fDone)
  1474. {
  1475. pcs->hwndProgress = hwnd;
  1476. }
  1477. LEAVECRITICAL;
  1478. dwShowTime = GetTickCount();
  1479. while (!pcs->fDone && GetMessage(&msg, NULL, 0, 0))
  1480. {
  1481. if (!pcs->fDone && !IsDialogMessage(pcs->hwndProgress, &msg))
  1482. {
  1483. TranslateMessage(&msg);
  1484. DispatchMessage(&msg);
  1485. }
  1486. }
  1487. // if we've put it up, we need to keep it up for at least some minimal amount of time
  1488. iShowTimeLeft = MINSHOWTIME - (GetTickCount() - dwShowTime);
  1489. if (iShowTimeLeft > 0)
  1490. {
  1491. DebugMsg(TF_DEBUGCOPY, TEXT("FOUIThreadProc -- doing an extra sleep"));
  1492. Sleep(iShowTimeLeft);
  1493. }
  1494. // Keep us from doing this while other thread processing...
  1495. ENTERCRITICAL;
  1496. pcs->hwndProgress = NULL;
  1497. LEAVECRITICAL;
  1498. DestroyWindow(hwnd);
  1499. }
  1500. }
  1501. else
  1502. {
  1503. // main thread must have finished
  1504. ASSERT(pcs->fDone);
  1505. }
  1506. }
  1507. ReleasePCS(pcs);
  1508. DebugMsg(TF_DEBUGCOPY, TEXT("FOUIThreadProc -- End . Completed"));
  1509. return 0;
  1510. }
  1511. // this queries the progress dialog for a cancel and yields.
  1512. // it also will show the progress dialog if a certain amount of time has passed
  1513. //
  1514. // returns:
  1515. // TRUE cacnel was pressed, abort the operation
  1516. // FALSE continue
  1517. BOOL FOQueryAbort(COPY_STATE *pcs)
  1518. {
  1519. if (!pcs->bAbort && pcs->hwndProgress)
  1520. {
  1521. if (pcs->hwndProgress != pcs->hwndDlgParent)
  1522. {
  1523. // do this here rather than on the FOUIThreadProc so that we don't have
  1524. // synchronization problems with this thread popping up a dialog on
  1525. // hwndDlgParent then the progress dialog coming up afterwards on top.
  1526. pcs->hwndDlgParent = pcs->hwndProgress;
  1527. ShowWindow(pcs->hwndProgress, SW_SHOW);
  1528. SetForegroundWindow(pcs->hwndProgress);
  1529. SetFocus(GetDlgItem(pcs->hwndProgress, IDCANCEL));
  1530. SetProgressText(pcs, pcs->dth.szSrcPath,
  1531. pcs->lpfo->wFunc == FO_DELETE ? NULL : pcs->dth.szDestPath);
  1532. }
  1533. else
  1534. {
  1535. MSG msg;
  1536. // win95 handled messages in here.
  1537. // we need to do the same in order to flush the input queue as well as
  1538. // for backwards compatability.
  1539. // we need to flush the input queue now because hwndProgress is
  1540. // on a different thread... which means it has attached thread inputs
  1541. // inorder to unlock the attached threads, we need to remove some
  1542. // sort of message until there's none left... any type of message..
  1543. while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
  1544. {
  1545. if (!IsDialogMessage(pcs->hwndProgress, &msg))
  1546. {
  1547. TranslateMessage(&msg);
  1548. DispatchMessage(&msg);
  1549. }
  1550. }
  1551. }
  1552. if (pcs->dth.dtAll.fChanged || pcs->dth.dtDone.fChanged)
  1553. {
  1554. if (!pcs->dth.fChangePosted)
  1555. {
  1556. // set the flag first because with async threads
  1557. // the progress window could handle it and clear the
  1558. // bit before we set it.. then we'd lose further messages
  1559. // thinking that one was still pending
  1560. pcs->dth.fChangePosted = TRUE;
  1561. if (!PostMessage(pcs->hwndProgress, PDM_UPDATE, 0, 0))
  1562. pcs->dth.fChangePosted = FALSE;
  1563. }
  1564. }
  1565. }
  1566. return pcs->bAbort;
  1567. }
  1568. typedef struct _confdlg_data {
  1569. LPCTSTR pFileDest;
  1570. LPCTSTR pFileSource;
  1571. LPCTSTR pStreamNames;
  1572. const WIN32_FIND_DATA *pfdDest;
  1573. const WIN32_FIND_DATA *pfdSource;
  1574. BOOL bShowCancel; // allow cancel out of this operation
  1575. BOOL bShowDates; // use date/size info in message
  1576. UINT uDeleteWarning; // warn that the delete's not going to the wastebasket
  1577. BOOL bFireIcon;
  1578. BOOL bShrinkDialog; // should we move the buttons up to the text?
  1579. int nSourceFiles; // if != 1 used to build "n files" string
  1580. int idText; // if != 0 use to override string in dlg template
  1581. CONFIRM_FLAG fConfirm; // we will confirm things set here
  1582. CONFIRM_FLAG fYesMask; // these bits are cleared in fConfirm on "yes"
  1583. // Only use fYesMask for things that should be confirmed once per operation
  1584. CONFIRM_FLAG fYesToAllMask; // these bits are cleared in fConfirm on "yes to all"
  1585. //COPY_STATE *pcs;
  1586. CONFIRM_DATA *pcd;
  1587. void (*InitConfirmDlg)(HWND hDlg, struct _confdlg_data *pcd); // routine to initialize dialog
  1588. BOOL bARPWarning;
  1589. } CONFDLG_DATA;
  1590. BOOL BuildDateLine(LPTSTR pszDateLine, UINT cchDateLine, const WIN32_FIND_DATA *pFind, LPCTSTR pFileName)
  1591. {
  1592. TCHAR szTemplate[64];
  1593. TCHAR szNum[32], szTmp[64];
  1594. WIN32_FIND_DATA fd;
  1595. ULARGE_INTEGER liFileSize;
  1596. if (!pFind)
  1597. {
  1598. HANDLE hfind = FindFirstFile(pFileName, &fd);
  1599. ASSERT(hfind != INVALID_HANDLE_VALUE);
  1600. FindClose(hfind);
  1601. pFind = &fd;
  1602. }
  1603. liFileSize.LowPart = pFind->nFileSizeLow;
  1604. liFileSize.HighPart = pFind->nFileSizeHigh;
  1605. // There are cases where the date is 0, this is especially true when the
  1606. // source is from a file contents...
  1607. if (pFind->ftLastWriteTime.dwLowDateTime || pFind->ftLastWriteTime.dwHighDateTime)
  1608. {
  1609. DWORD dwFlags = FDTF_LONGDATE | FDTF_RELATIVE | FDTF_LONGTIME;
  1610. SHFormatDateTime(&pFind->ftLastWriteTime, &dwFlags, szTmp, SIZECHARS(szTmp));
  1611. LoadString(HINST_THISDLL, IDS_DATESIZELINE, szTemplate, ARRAYSIZE(szTemplate));
  1612. StringCchPrintf(pszDateLine, cchDateLine, szTemplate, StrFormatByteSize64(liFileSize.QuadPart, szNum, ARRAYSIZE(szNum)), szTmp);
  1613. }
  1614. else
  1615. {
  1616. // Simpy output the number to the string
  1617. StrFormatByteSize64(liFileSize.QuadPart, pszDateLine, cchDateLine);
  1618. if (liFileSize.QuadPart == 0)
  1619. return FALSE;
  1620. }
  1621. return TRUE; // valid data in the strings
  1622. }
  1623. // hide the cancel button and move "Yes" and "No" over to the right positions.
  1624. //
  1625. // "Yes" is IDYES
  1626. // "No" is IDNO
  1627. //
  1628. #define HideYesToAllAndCancel(hdlg) HideConfirmButtons(hdlg, IDCANCEL)
  1629. #define HideYesToAllAndNo(hdlg) HideConfirmButtons(hdlg, IDNO)
  1630. void HideConfirmButtons(HWND hdlg, int idHide)
  1631. {
  1632. HWND hwndCancel = GetDlgItem(hdlg, IDCANCEL);
  1633. HWND hwndYesToAll = GetDlgItem(hdlg, IDD_YESTOALL);
  1634. if (hwndCancel)
  1635. {
  1636. RECT rcCancel;
  1637. HWND hwndNo;
  1638. GetWindowRect(hwndCancel, &rcCancel);
  1639. hwndNo = GetDlgItem(hdlg, IDNO);
  1640. if (hwndNo)
  1641. {
  1642. RECT rcNo;
  1643. HWND hwndYes;
  1644. GetWindowRect(hwndNo, &rcNo);
  1645. MapWindowRect(NULL, hdlg, &rcCancel);
  1646. SetWindowPos(hwndNo, NULL, rcCancel.left, rcCancel.top,
  1647. 0, 0, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE);
  1648. hwndYes = GetDlgItem(hdlg, IDYES);
  1649. if (hwndYes)
  1650. {
  1651. MapWindowRect(NULL, hdlg, &rcNo);
  1652. SetWindowPos(hwndYes, NULL, rcNo.left, rcNo.top,
  1653. 0, 0, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE);
  1654. }
  1655. }
  1656. // Although the function is called "Hide", we actually destroy
  1657. // the windows, because keyboard accelerators for hidden windows
  1658. // are still active!
  1659. if (hwndYesToAll)
  1660. DestroyWindow(hwndYesToAll);
  1661. DestroyWindow(GetDlgItem(hdlg, idHide));
  1662. }
  1663. }
  1664. int MoveDlgItem(HWND hDlg, UINT id, int y)
  1665. {
  1666. RECT rc;
  1667. HWND hwnd = GetDlgItem(hDlg, id);
  1668. if (hwnd)
  1669. {
  1670. GetWindowRect(hwnd, &rc);
  1671. MapWindowRect(NULL, hDlg, &rc);
  1672. SetWindowPos(hwnd, NULL, rc.left, y, 0,0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
  1673. return rc.top - y; // return how much it moved
  1674. }
  1675. return 0;
  1676. }
  1677. void ShrinkDialog(HWND hDlg, UINT idText)
  1678. {
  1679. RECT rc;
  1680. int y;
  1681. HWND hwnd;
  1682. hwnd = GetDlgItem(hDlg, idText);
  1683. ASSERT(hwnd);
  1684. GetWindowRect(hwnd, &rc);
  1685. MapWindowRect(NULL, hDlg, &rc);
  1686. y = rc.bottom + 12;
  1687. // move all the buttons
  1688. MoveDlgItem(hDlg, IDNO, y);
  1689. MoveDlgItem(hDlg, IDCANCEL, y);
  1690. MoveDlgItem(hDlg, IDD_YESTOALL, y);
  1691. y = MoveDlgItem(hDlg, IDYES, y);
  1692. // now resize the entire dialog
  1693. GetWindowRect(hDlg, &rc);
  1694. SetWindowPos(hDlg, NULL, 0, 0, rc.right - rc.left, rc.bottom - y - rc.top, SWP_NOMOVE | SWP_NOZORDER |SWP_NOACTIVATE);
  1695. }
  1696. void InitConfirmDlg(HWND hDlg, CONFDLG_DATA *pcd)
  1697. {
  1698. TCHAR szMessage[255];
  1699. TCHAR szDeleteWarning[80];
  1700. TCHAR szSrc[32];
  1701. TCHAR szFriendlyName[MAX_PATH];
  1702. SHFILEINFO sfi;
  1703. SHFILEINFO sfiDest;
  1704. LPTSTR pszFileDest = NULL;
  1705. LPTSTR pszMsg, pszSource;
  1706. int i;
  1707. int cxWidth;
  1708. RECT rc;
  1709. HDC hdc;
  1710. HFONT hfont;
  1711. HFONT hfontSave;
  1712. SIZE size;
  1713. BOOL bIsARPWarning = pcd->bARPWarning;
  1714. ASSERT((bIsARPWarning && (pcd->nSourceFiles == 1)) || (!bIsARPWarning));
  1715. hdc = GetDC(hDlg);
  1716. hfont = (HFONT)SendMessage(hDlg, WM_GETFONT, 0, 0);
  1717. hfontSave = (HFONT)SelectObject(hdc, hfont);
  1718. // get the size of the text boxes
  1719. GetWindowRect(GetDlgItem(hDlg, pcd->idText), &rc);
  1720. cxWidth = rc.right - rc.left;
  1721. //
  1722. // There are cases where, if the filename has no spaces, the static text
  1723. // control will put the entire file name with our quote character down
  1724. // on the 2nd line. To account for this, we subtract off the width of a
  1725. // the quote character. Since the quote character comes from the resource
  1726. // string, it could really be just about an character, with just about
  1727. // any width. So we assume its about the width of the letter 0, which
  1728. // should be more than wide enough.
  1729. size.cx = 0;
  1730. GetTextExtentPoint(hdc, TEXT("0"), 1, &size);
  1731. cxWidth -= size.cx * 2;
  1732. if (!bIsARPWarning && !pcd->bShowCancel)
  1733. HideYesToAllAndCancel(hDlg);
  1734. switch (pcd->nSourceFiles)
  1735. {
  1736. case -1:
  1737. LoadString(HINST_THISDLL, IDS_SELECTEDFILES, szSrc, ARRAYSIZE(szSrc));
  1738. pszSource = szSrc;
  1739. break;
  1740. case 1:
  1741. if (bIsARPWarning)
  1742. {
  1743. TCHAR szTarget[MAX_PATH];
  1744. DWORD cchFriendlyName = ARRAYSIZE(szFriendlyName);
  1745. HRESULT hres = GetPathFromLinkFile(pcd->pFileSource, szTarget, ARRAYSIZE(szTarget));
  1746. if (S_OK == hres)
  1747. {
  1748. if (SUCCEEDED(AssocQueryString(ASSOCF_VERIFY | ASSOCF_OPEN_BYEXENAME, ASSOCSTR_FRIENDLYAPPNAME,
  1749. szTarget, NULL, szFriendlyName, &cchFriendlyName)))
  1750. {
  1751. pszSource = szFriendlyName;
  1752. }
  1753. else
  1754. {
  1755. pszSource = PathFindFileName(szTarget);
  1756. }
  1757. }
  1758. else if (S_FALSE == hres)
  1759. {
  1760. TCHAR szProductCode[MAX_PATH];
  1761. szProductCode[0] = 0;
  1762. if ((ERROR_SUCCESS == MsiDecomposeDescriptor(szTarget, szProductCode, NULL, NULL, NULL)) &&
  1763. (ERROR_SUCCESS == MsiGetProductInfo(szProductCode, INSTALLPROPERTY_PRODUCTNAME, szFriendlyName, &cchFriendlyName)))
  1764. {
  1765. pszSource = szFriendlyName;
  1766. }
  1767. else
  1768. goto UNKNOWNAPP;
  1769. }
  1770. else
  1771. {
  1772. UNKNOWNAPP:
  1773. LoadString(HINST_THISDLL, IDS_UNKNOWNAPPLICATION, szSrc, ARRAYSIZE(szSrc));
  1774. pszSource = szSrc;
  1775. }
  1776. }
  1777. else
  1778. {
  1779. SHGetFileInfo(pcd->pFileSource,
  1780. (pcd->fConfirm==CONFIRM_DELETE_FOLDER || pcd->fConfirm==CONFIRM_WONT_RECYCLE_FOLDER)? FILE_ATTRIBUTE_DIRECTORY : 0,
  1781. &sfi, sizeof(sfi), SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES);
  1782. pszSource = sfi.szDisplayName;
  1783. PathCompactPath(hdc, pszSource, cxWidth);
  1784. }
  1785. break;
  1786. default:
  1787. pszSource = AddCommas(pcd->nSourceFiles, szSrc, ARRAYSIZE(szSrc));
  1788. break;
  1789. }
  1790. // if we're supposed to show the date info, grab the icons and format the date string
  1791. if (pcd->bShowDates)
  1792. {
  1793. SHFILEINFO sfi2;
  1794. TCHAR szDateSrc[64], szDateDest[64];
  1795. BuildDateLine(szDateSrc, ARRAYSIZE(szDateSrc), pcd->pfdSource, pcd->pFileSource);
  1796. SetDlgItemText(hDlg, IDD_FILEINFO_NEW, szDateSrc);
  1797. BuildDateLine(szDateDest, ARRAYSIZE(szDateSrc), pcd->pfdDest, pcd->pFileDest);
  1798. SetDlgItemText(hDlg, IDD_FILEINFO_OLD, szDateDest);
  1799. SHGetFileInfo(pcd->pFileDest, pcd->pfdDest ? pcd->pfdDest->dwFileAttributes : 0, &sfi2, sizeof(sfi2),
  1800. pcd->pfdDest ? (SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_LARGEICON) : (SHGFI_ICON|SHGFI_LARGEICON));
  1801. ReplaceDlgIcon(hDlg, IDD_ICON_OLD, sfi2.hIcon);
  1802. SHGetFileInfo(pcd->pFileSource, pcd->pfdSource ? pcd->pfdSource->dwFileAttributes : 0, &sfi2, sizeof(sfi2),
  1803. pcd->pfdSource ? (SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_LARGEICON) : (SHGFI_ICON|SHGFI_LARGEICON));
  1804. ReplaceDlgIcon(hDlg, IDD_ICON_NEW, sfi2.hIcon);
  1805. }
  1806. if (!bIsARPWarning)
  1807. {
  1808. // there are multiple controls:
  1809. // IDD_TEXT contains regular text (normal file/folder)
  1810. // IDD_TEXT1 - IDD_TEXT4 contain optional secondary text
  1811. for (i = IDD_TEXT; i <= IDD_TEXT4; i++)
  1812. {
  1813. if (i == pcd->idText)
  1814. {
  1815. szMessage[0] = 0;
  1816. GetDlgItemText(hDlg, i, szMessage, ARRAYSIZE(szMessage));
  1817. }
  1818. else
  1819. {
  1820. HWND hwndCtl = GetDlgItem(hDlg, i);
  1821. if (hwndCtl)
  1822. ShowWindow(hwndCtl, SW_HIDE);
  1823. }
  1824. }
  1825. }
  1826. else
  1827. {
  1828. GetDlgItemText(hDlg, IDD_ARPWARNINGTEXT, szMessage, ARRAYSIZE(szMessage));
  1829. }
  1830. // REVIEW Is there some better way? The code above always hides
  1831. // this control, and I don't see a way around this
  1832. if (pcd->pStreamNames)
  1833. {
  1834. SetDlgItemText(hDlg, IDD_TEXT1, pcd->pStreamNames);
  1835. ShowWindow(GetDlgItem(hDlg, IDD_TEXT1), SW_SHOW);
  1836. }
  1837. if (pcd->bShrinkDialog)
  1838. ShrinkDialog(hDlg, pcd->idText);
  1839. if (pcd->pFileDest)
  1840. {
  1841. SHGetFileInfo(pcd->pFileDest, 0,
  1842. &sfiDest, sizeof(sfiDest), SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES);
  1843. pszFileDest = sfiDest.szDisplayName;
  1844. PathCompactPath(hdc, pszFileDest, cxWidth);
  1845. }
  1846. if (pcd->uDeleteWarning)
  1847. {
  1848. LPITEMIDLIST pidl;
  1849. if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_BITBUCKET, &pidl)))
  1850. {
  1851. SHFILEINFO fi;
  1852. if (SHGetFileInfo((LPCTSTR)pidl, 0, &fi, sizeof(fi), SHGFI_PIDL | SHGFI_ICON |SHGFI_LARGEICON))
  1853. {
  1854. ReplaceDlgIcon(hDlg, IDD_ICON_WASTEBASKET, fi.hIcon);
  1855. }
  1856. ILFree(pidl);
  1857. }
  1858. LoadString(HINST_THISDLL, pcd->uDeleteWarning, szDeleteWarning, ARRAYSIZE(szDeleteWarning));
  1859. }
  1860. else
  1861. szDeleteWarning[0] = 0;
  1862. if (pcd->bFireIcon)
  1863. {
  1864. ReplaceDlgIcon(hDlg, IDD_ICON_WASTEBASKET, LoadImage(HINST_THISDLL, MAKEINTRESOURCE(IDI_NUKEFILE), IMAGE_ICON, 0, 0, LR_LOADMAP3DCOLORS));
  1865. }
  1866. pszMsg = ShellConstructMessageString(HINST_THISDLL, szMessage,
  1867. pszSource, pszFileDest, szDeleteWarning);
  1868. if (pszMsg)
  1869. {
  1870. SetDlgItemText(hDlg, pcd->idText, pszMsg);
  1871. LocalFree(pszMsg);
  1872. }
  1873. SelectObject(hdc, hfontSave);
  1874. ReleaseDC(hDlg, hdc);
  1875. }
  1876. BOOL_PTR CALLBACK ConfirmDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
  1877. {
  1878. CONFDLG_DATA *pcd = (CONFDLG_DATA *)GetWindowLongPtr(hDlg, DWLP_USER);
  1879. switch (wMsg)
  1880. {
  1881. case WM_INITDIALOG:
  1882. SetWindowLongPtr(hDlg, DWLP_USER, lParam);
  1883. pcd = (CONFDLG_DATA *)lParam;
  1884. pcd->InitConfirmDlg(hDlg, pcd);
  1885. break;
  1886. case WM_DESTROY:
  1887. // Handle case where the allocation of the PCD failed.
  1888. if (!pcd)
  1889. break;
  1890. if (pcd->bShowDates)
  1891. {
  1892. ReplaceDlgIcon(hDlg, IDD_ICON_NEW, NULL);
  1893. ReplaceDlgIcon(hDlg, IDD_ICON_OLD, NULL);
  1894. }
  1895. ReplaceDlgIcon(hDlg, IDD_ICON_WASTEBASKET, NULL);
  1896. break;
  1897. case WM_COMMAND:
  1898. if (!pcd)
  1899. break;
  1900. switch (GET_WM_COMMAND_ID(wParam, lParam))
  1901. {
  1902. case IDNO:
  1903. if (GetKeyState(VK_SHIFT) < 0) // force NOTOALL
  1904. {
  1905. // I use the fYesToAllMask here. There used to be a fNoToAllMask but I
  1906. // removed it. When you select "No To All" what you are saying is that
  1907. // anything I would be saying yes to all for I am actually saying "no to
  1908. // all" for. I feel that it is confusing and unnecessary to have both.
  1909. pcd->pcd->fNoToAll |= pcd->fYesToAllMask;
  1910. }
  1911. EndDialog(hDlg, IDNO);
  1912. break;
  1913. case IDD_YESTOALL:
  1914. // pcd is the confirmation data for just this file/folder. pcd->pcd is the
  1915. // confirm data for the entire copy operation. When we get a Yes To All we
  1916. // remove the coresponding bits from the entire operation.
  1917. pcd->pcd->fConfirm &= ~pcd->fYesToAllMask;
  1918. EndDialog(hDlg, IDYES);
  1919. break;
  1920. case IDYES:
  1921. // There are some messages that we only want to tell the use once even if they
  1922. // select Yes instead of Yes To All. As such we sometimes remove bits from the
  1923. // global confirm state even on a simple Yes. This mask is usually zero.
  1924. pcd->pcd->fConfirm &= ~pcd->fYesMask;
  1925. EndDialog(hDlg, IDYES);
  1926. break;
  1927. case IDCANCEL:
  1928. EndDialog(hDlg, IDCANCEL);
  1929. break;
  1930. }
  1931. break;
  1932. case WM_NOTIFY:
  1933. switch (((LPNMHDR)lParam)->code)
  1934. {
  1935. case NM_RETURN:
  1936. case NM_CLICK:
  1937. {
  1938. TCHAR szModule[MAX_PATH];
  1939. if (GetSystemDirectory(szModule, ARRAYSIZE(szModule)))
  1940. {
  1941. if (PathAppend(szModule, TEXT("appwiz.cpl")))
  1942. {
  1943. TCHAR szParam[1 + MAX_PATH + 2 + MAX_CCH_CPLNAME]; // See MakeCPLCommandLine function
  1944. TCHAR szAppwiz[64];
  1945. LoadString(g_hinst, IDS_APPWIZCPL, szAppwiz, SIZECHARS(szAppwiz));
  1946. MakeCPLCommandLine(szModule, szAppwiz, szParam, ARRAYSIZE(szParam));
  1947. SHRunControlPanelEx(szParam, NULL, FALSE);
  1948. }
  1949. }
  1950. EndDialog(hDlg, IDNO);
  1951. }
  1952. break;
  1953. }
  1954. break;
  1955. default:
  1956. return FALSE;
  1957. }
  1958. return TRUE;
  1959. }
  1960. void SetConfirmMaskAndText(CONFDLG_DATA *pcd, DWORD dwFileAttributes, LPCTSTR pszFile)
  1961. {
  1962. if (IS_SYSTEM_HIDDEN(dwFileAttributes) && !ShowSuperHidden())
  1963. {
  1964. dwFileAttributes &= ~FILE_ATTRIBUTE_SUPERHIDDEN;
  1965. }
  1966. // we used to have a desktop.ini "ConfirmFileOp" flag that was set
  1967. // to avoid this case, but there are no folders that are marked READONLY
  1968. // or SYSTEM for a reason other than the shell, so don't consider any as such
  1969. if ((dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY)) &&
  1970. (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  1971. {
  1972. dwFileAttributes &= ~(FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY);
  1973. }
  1974. if (dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
  1975. {
  1976. pcd->fConfirm = CONFIRM_SYSTEM_FILE;
  1977. pcd->fYesToAllMask |= CONFIRM_SYSTEM_FILE;
  1978. pcd->idText = IDD_TEXT2;
  1979. }
  1980. else if (dwFileAttributes & FILE_ATTRIBUTE_READONLY)
  1981. {
  1982. pcd->fConfirm = CONFIRM_READONLY_FILE;
  1983. pcd->fYesToAllMask |= CONFIRM_READONLY_FILE;
  1984. pcd->idText = IDD_TEXT1;
  1985. }
  1986. else if (pszFile && ((dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) &&
  1987. PathIsRegisteredProgram(pszFile))
  1988. {
  1989. pcd->fConfirm = CONFIRM_PROGRAM_FILE;
  1990. pcd->fYesToAllMask |= CONFIRM_PROGRAM_FILE;
  1991. pcd->idText = IDD_TEXT3;
  1992. }
  1993. }
  1994. void PauseAnimation(COPY_STATE *pcs, BOOL bStop)
  1995. {
  1996. // only called from within the hwndProgress wndproc so assum it's there
  1997. if (bStop)
  1998. Animate_Stop(GetDlgItem(pcs->hwndProgress, IDD_ANIMATE));
  1999. else
  2000. Animate_Play(GetDlgItem(pcs->hwndProgress, IDD_ANIMATE), -1, -1, -1);
  2001. }
  2002. // confirm a file operation UI.
  2003. //
  2004. // this routine uses the CONFIRM_DATA in the copy state structure to
  2005. // decide if it needs to put up a dailog to confirm the given file operation.
  2006. //
  2007. // in:
  2008. // pcs current copy state (confirm flags, hwnd)
  2009. // fConfirm only one bit may be set! (operation to confirm)
  2010. // pFileSource source file
  2011. // pFileDest optional destination file
  2012. // pfdSource
  2013. // pfdDest find data describing the destination
  2014. //
  2015. // returns:
  2016. // IDYES
  2017. // IDNO
  2018. // IDCANCEL
  2019. // ERROR_ (DE_) error codes (DE_MEMORY)
  2020. //
  2021. int ConfirmFileOp(HWND hwnd, COPY_STATE *pcs, CONFIRM_DATA *pcd,
  2022. int nSourceFiles, int cDepth, CONFIRM_FLAG fConfirm,
  2023. LPCTSTR pFileSource, const WIN32_FIND_DATA *pfdSource,
  2024. LPCTSTR pFileDest, const WIN32_FIND_DATA *pfdDest,
  2025. LPCTSTR pStreamNames)
  2026. {
  2027. int dlg;
  2028. int ret;
  2029. CONFDLG_DATA cdd;
  2030. CONFIRM_FLAG fConfirmType;
  2031. if (pcs)
  2032. nSourceFiles = pcs->nSourceFiles;
  2033. cdd.pfdSource = pfdSource;
  2034. cdd.pfdDest = NULL; // pfdDest is only partially filed in
  2035. cdd.pFileSource = pFileSource;
  2036. cdd.pFileDest = pFileDest;
  2037. cdd.pcd = pcd;
  2038. cdd.fConfirm = fConfirm; // default, changed below
  2039. cdd.fYesMask = 0;
  2040. cdd.fYesToAllMask = 0;
  2041. cdd.nSourceFiles = 1; // default to individual file names in message
  2042. cdd.idText = IDD_TEXT; // default string from the dlg template
  2043. cdd.bShowCancel = ((nSourceFiles != 1) || cDepth);
  2044. cdd.uDeleteWarning = 0;
  2045. cdd.bFireIcon = FALSE;
  2046. cdd.bShowDates = FALSE;
  2047. cdd.bShrinkDialog = FALSE;
  2048. cdd.InitConfirmDlg = InitConfirmDlg;
  2049. cdd.pStreamNames = NULL;
  2050. cdd.bARPWarning = FALSE;
  2051. fConfirmType = fConfirm & CONFIRM_FLAG_TYPE_MASK;
  2052. switch (fConfirmType)
  2053. {
  2054. case CONFIRM_DELETE_FILE:
  2055. case CONFIRM_DELETE_FOLDER:
  2056. {
  2057. BOOL bIsFolderShortcut = FALSE;
  2058. cdd.bShrinkDialog = TRUE;
  2059. // find data for source is in pdfDest
  2060. if ((nSourceFiles != 1) && (pcd->fConfirm & CONFIRM_MULTIPLE))
  2061. {
  2062. // this is the special CONFIRM_MULTIPLE case (usuall SHIFT+DELETE, or
  2063. // SHIFT+DRAG to Recycle Bin). if the user says yes to this, they
  2064. // basically get no more warnings.
  2065. cdd.nSourceFiles = nSourceFiles;
  2066. if ((fConfirm & CONFIRM_WASTEBASKET_PURGE) ||
  2067. (!pcs || !(pcs->fFlags & FOF_ALLOWUNDO)) ||
  2068. !BBWillRecycle(cdd.pFileSource, NULL))
  2069. {
  2070. // have the fire icon and the REALLY delete warning
  2071. cdd.uDeleteWarning = IDS_FOLDERDELETEWARNING;
  2072. cdd.bFireIcon = TRUE;
  2073. if (pcs)
  2074. pcs->fFlags &= ~FOF_ALLOWUNDO;
  2075. if (nSourceFiles == -1)
  2076. {
  2077. // -1 indicates that there were >= MAX_EMPTY_FILES files, so we stoped counting
  2078. // them all up for perf. We use the more generic message in this case.
  2079. cdd.idText = IDD_TEXT3;
  2080. }
  2081. else
  2082. {
  2083. // use the "are you sure you want to nuke XX files?" message
  2084. cdd.idText = IDD_TEXT4;
  2085. }
  2086. }
  2087. else
  2088. {
  2089. // uDeleteWarning must be set for the proper recycle icon to be loaded.
  2090. cdd.uDeleteWarning = IDS_FOLDERDELETEWARNING;
  2091. }
  2092. if (!pcs || !pcs->fNoConfirmRecycle)
  2093. {
  2094. POINT ptInvoke;
  2095. HWND hwndPos = NULL;
  2096. if ((GetNumberOfMonitors() > 1) && GetCursorPos(&ptInvoke))
  2097. {
  2098. HMONITOR hMon = MonitorFromPoint(ptInvoke, MONITOR_DEFAULTTONULL);
  2099. if (hMon)
  2100. {
  2101. hwndPos = _CreateStubWindow(&ptInvoke, hwnd);
  2102. }
  2103. }
  2104. ret = (int)DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_DELETE_MULTIPLE), hwndPos ? hwndPos : hwnd, ConfirmDlgProc, (LPARAM)&cdd);
  2105. if (hwndPos)
  2106. {
  2107. DestroyWindow(hwndPos);
  2108. }
  2109. if (ret != IDYES)
  2110. {
  2111. return IDCANCEL;
  2112. }
  2113. }
  2114. // clear all other possible warnings
  2115. pcd->fConfirm &= ~(CONFIRM_MULTIPLE | CONFIRM_DELETE_FILE | CONFIRM_DELETE_FOLDER);
  2116. cdd.fConfirm &= ~(CONFIRM_DELETE_FILE | CONFIRM_DELETE_FOLDER);
  2117. cdd.nSourceFiles = 1; // use individual file name
  2118. }
  2119. SetConfirmMaskAndText(&cdd, pfdDest->dwFileAttributes, cdd.pFileSource);
  2120. if ((pfdDest->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
  2121. PathIsShortcut(cdd.pFileSource, pfdDest->dwFileAttributes))
  2122. {
  2123. // Its a folder and its a shortcut... must be a FolderShortcut!
  2124. bIsFolderShortcut = TRUE;
  2125. // since its a folder, we need to clear out all of these warnings
  2126. cdd.fYesMask |= CONFIRM_DELETE_FILE | CONFIRM_DELETE_FOLDER | CONFIRM_MULTIPLE;
  2127. cdd.fYesToAllMask |= CONFIRM_DELETE_FILE | CONFIRM_DELETE_FOLDER | CONFIRM_MULTIPLE;
  2128. }
  2129. // we want to treat FolderShortcuts as "files" instead of folders. We do this so we don't display dialogs
  2130. // that say stuff like "do you want to delete this and all of its contents" when to the user, this looks like
  2131. // an item instead of a folder (eg nethood shortcut).
  2132. if ((fConfirmType == CONFIRM_DELETE_FILE) || bIsFolderShortcut)
  2133. {
  2134. dlg = DLG_DELETE_FILE;
  2135. if ((nSourceFiles == 1) && PathIsShortcutToProgram(cdd.pFileSource))
  2136. {
  2137. dlg = DLG_DELETE_FILE_ARP;
  2138. cdd.idText = IDD_ARPWARNINGTEXT;
  2139. cdd.bShrinkDialog = FALSE;
  2140. cdd.bARPWarning = TRUE;
  2141. }
  2142. if ((fConfirm & CONFIRM_WASTEBASKET_PURGE) ||
  2143. (!pcs || !(pcs->fFlags & FOF_ALLOWUNDO)) ||
  2144. !BBWillRecycle(cdd.pFileSource, NULL))
  2145. {
  2146. // we are really nuking it, so show the appropriate icon/dialog
  2147. cdd.bFireIcon = TRUE;
  2148. if (pcs)
  2149. {
  2150. pcs->fFlags &= ~FOF_ALLOWUNDO;
  2151. }
  2152. cdd.uDeleteWarning = IDS_FILEDELETEWARNING;
  2153. if (cdd.idText == IDD_TEXT)
  2154. {
  2155. cdd.idText = IDD_TEXT4;
  2156. }
  2157. }
  2158. else
  2159. {
  2160. // we are recycling it
  2161. cdd.uDeleteWarning = IDS_FILERECYCLEWARNING;
  2162. }
  2163. }
  2164. else
  2165. {
  2166. // fConfirmType == CONFIRM_DELETE_FOLDER
  2167. if (pcs)
  2168. {
  2169. // show cancel on NEXT confirm dialog
  2170. pcs->nSourceFiles = -1;
  2171. }
  2172. cdd.fYesMask |= CONFIRM_DELETE_FILE | CONFIRM_DELETE_FOLDER | CONFIRM_MULTIPLE;
  2173. cdd.fYesToAllMask |= CONFIRM_DELETE_FILE | CONFIRM_DELETE_FOLDER | CONFIRM_MULTIPLE;
  2174. dlg = DLG_DELETE_FOLDER;
  2175. if ((fConfirm & CONFIRM_WASTEBASKET_PURGE) ||
  2176. (!pcs || !(pcs->fFlags & FOF_ALLOWUNDO)) ||
  2177. !BBWillRecycle(cdd.pFileSource, NULL))
  2178. {
  2179. // we are really nuking it, so show the appropriate icon/dialog
  2180. cdd.bFireIcon = TRUE;
  2181. if (pcs)
  2182. {
  2183. pcs->fFlags &= ~FOF_ALLOWUNDO;
  2184. }
  2185. cdd.uDeleteWarning = IDS_FOLDERDELETEWARNING;
  2186. }
  2187. else
  2188. {
  2189. // we are recycling it
  2190. cdd.uDeleteWarning = IDS_FOLDERRECYCLEWARNING;
  2191. }
  2192. }
  2193. //
  2194. // NTRAID#NTBUG9-100335-2001/01/03-jeffreys
  2195. // See also #128485 in the OSR v 4.1 database
  2196. //
  2197. // The fix for 128485 added the BBWillRecycle check below, but this
  2198. // caused NTBUG9-100335. These 2 bugs say the opposite things.
  2199. // We've had several customer complaints (see dupes of 100335)
  2200. // so I'm putting it back to the way it worked in Windows 2000.
  2201. //
  2202. if (pcs && pcs->fNoConfirmRecycle /*&& BBWillRecycle(cdd.pFileSource, NULL)*/)
  2203. {
  2204. cdd.fConfirm = 0;
  2205. }
  2206. }
  2207. break;
  2208. case CONFIRM_WONT_RECYCLE_FILE:
  2209. case CONFIRM_WONT_RECYCLE_FOLDER:
  2210. cdd.bShrinkDialog = TRUE;
  2211. cdd.nSourceFiles = 1;
  2212. cdd.bFireIcon = TRUE;
  2213. cdd.idText = IDD_TEXT;
  2214. cdd.fYesMask = CONFIRM_MULTIPLE;
  2215. cdd.fConfirm = fConfirmType;
  2216. cdd.fYesToAllMask = fConfirmType | CONFIRM_MULTIPLE;
  2217. // set the dialog to be file or folder
  2218. if (fConfirmType == CONFIRM_WONT_RECYCLE_FOLDER)
  2219. {
  2220. dlg = DLG_WONT_RECYCLE_FOLDER;
  2221. }
  2222. else
  2223. {
  2224. dlg = DLG_WONT_RECYCLE_FILE;
  2225. }
  2226. break;
  2227. case CONFIRM_PATH_TOO_LONG:
  2228. cdd.bShrinkDialog = TRUE;
  2229. cdd.nSourceFiles = 1;
  2230. cdd.bFireIcon = TRUE;
  2231. cdd.idText = IDD_TEXT;
  2232. cdd.fYesMask = CONFIRM_MULTIPLE;
  2233. cdd.fConfirm = CONFIRM_PATH_TOO_LONG;
  2234. cdd.fYesToAllMask = CONFIRM_PATH_TOO_LONG | CONFIRM_MULTIPLE;
  2235. dlg = DLG_PATH_TOO_LONG;
  2236. break;
  2237. case CONFIRM_WONT_RECYCLE_OFFLINE:
  2238. cdd.bShrinkDialog = TRUE;
  2239. cdd.nSourceFiles = 1;
  2240. cdd.bFireIcon = TRUE;
  2241. cdd.idText = IDD_TEXT;
  2242. cdd.fYesMask = CONFIRM_MULTIPLE;
  2243. cdd.fConfirm = fConfirmType;
  2244. cdd.fYesToAllMask = fConfirmType | CONFIRM_MULTIPLE;
  2245. dlg = DLG_WONT_RECYCLE_OFFLINE;
  2246. break;
  2247. case CONFIRM_STREAMLOSS:
  2248. cdd.bShrinkDialog = FALSE;
  2249. cdd.nSourceFiles = 1;
  2250. cdd.idText = IDD_TEXT;
  2251. cdd.fConfirm = CONFIRM_STREAMLOSS;
  2252. cdd.fYesToAllMask = CONFIRM_STREAMLOSS;
  2253. cdd.pStreamNames = pStreamNames;
  2254. dlg = DLG_STREAMLOSS_ON_COPY;
  2255. break;
  2256. case CONFIRM_FAILED_ENCRYPT:
  2257. cdd.bShrinkDialog = FALSE;
  2258. cdd.nSourceFiles = 1;
  2259. cdd.idText = IDD_TEXT;
  2260. cdd.bShowCancel = TRUE;
  2261. cdd.fConfirm = CONFIRM_FAILED_ENCRYPT;
  2262. cdd.fYesToAllMask = CONFIRM_FAILED_ENCRYPT;
  2263. dlg = DLG_FAILED_ENCRYPT;
  2264. break;
  2265. case CONFIRM_LOST_ENCRYPT_FILE:
  2266. case CONFIRM_LOST_ENCRYPT_FOLDER:
  2267. cdd.bShrinkDialog = FALSE;
  2268. cdd.nSourceFiles = 1;
  2269. cdd.idText = IDD_TEXT;
  2270. cdd.bShowCancel = TRUE;
  2271. cdd.fConfirm = CONFIRM_LOST_ENCRYPT_FILE | CONFIRM_LOST_ENCRYPT_FOLDER;
  2272. cdd.fYesToAllMask = CONFIRM_LOST_ENCRYPT_FILE | CONFIRM_LOST_ENCRYPT_FOLDER;
  2273. if (fConfirmType == CONFIRM_LOST_ENCRYPT_FILE)
  2274. {
  2275. dlg = DLG_LOST_ENCRYPT_FILE;
  2276. }
  2277. else
  2278. {
  2279. dlg = DLG_LOST_ENCRYPT_FOLDER;
  2280. }
  2281. break;
  2282. case CONFIRM_REPLACE_FILE:
  2283. cdd.bShowDates = TRUE;
  2284. cdd.fYesToAllMask = CONFIRM_REPLACE_FILE;
  2285. SetConfirmMaskAndText(&cdd, pfdDest->dwFileAttributes, NULL);
  2286. dlg = DLG_REPLACE_FILE;
  2287. break;
  2288. case CONFIRM_REPLACE_FOLDER:
  2289. cdd.bShowCancel = TRUE;
  2290. if (pcs) pcs->nSourceFiles = -1; // show cancel on NEXT confirm dialog
  2291. // this implies operations on the files
  2292. cdd.fYesMask = CONFIRM_REPLACE_FILE;
  2293. cdd.fYesToAllMask = CONFIRM_REPLACE_FILE | CONFIRM_REPLACE_FOLDER;
  2294. dlg = DLG_REPLACE_FOLDER;
  2295. break;
  2296. case CONFIRM_MOVE_FILE:
  2297. cdd.fYesToAllMask = CONFIRM_MOVE_FILE;
  2298. SetConfirmMaskAndText(&cdd, pfdSource->dwFileAttributes, NULL);
  2299. dlg = DLG_MOVE_FILE;
  2300. break;
  2301. case CONFIRM_MOVE_FOLDER:
  2302. cdd.bShowCancel = TRUE;
  2303. cdd.fYesToAllMask = CONFIRM_MOVE_FOLDER;
  2304. SetConfirmMaskAndText(&cdd, pfdSource->dwFileAttributes, cdd.pFileSource);
  2305. dlg = DLG_MOVE_FOLDER;
  2306. break;
  2307. case CONFIRM_RENAME_FILE:
  2308. SetConfirmMaskAndText(&cdd, pfdSource->dwFileAttributes, NULL);
  2309. dlg = DLG_RENAME_FILE;
  2310. break;
  2311. case CONFIRM_RENAME_FOLDER:
  2312. cdd.bShowCancel = TRUE;
  2313. if (pcs) pcs->nSourceFiles = -1; // show cancel on NEXT confirm dialog
  2314. SetConfirmMaskAndText(&cdd, pfdSource->dwFileAttributes, cdd.pFileSource);
  2315. dlg = DLG_RENAME_FOLDER;
  2316. break;
  2317. default:
  2318. DebugMsg(DM_WARNING, TEXT("bogus confirm option"));
  2319. return IDCANCEL;
  2320. }
  2321. // Does this operation need to be confirmed?
  2322. if (pcd->fConfirm & cdd.fConfirm)
  2323. {
  2324. // Has the user already said "No To All" for this operation?
  2325. if ((pcd->fNoToAll & cdd.fConfirm) == cdd.fConfirm)
  2326. {
  2327. ret = IDNO;
  2328. }
  2329. else
  2330. {
  2331. // HACK for multimon, make sure the file operation dialog box comes
  2332. // up on the correct monitor
  2333. POINT ptInvoke;
  2334. HWND hwndPos = NULL;
  2335. if ((GetNumberOfMonitors() > 1) && GetCursorPos(&ptInvoke))
  2336. {
  2337. HMONITOR hMon = MonitorFromPoint(ptInvoke, MONITOR_DEFAULTTONULL);
  2338. if (hMon)
  2339. {
  2340. hwndPos = _CreateStubWindow(&ptInvoke, hwnd);
  2341. }
  2342. }
  2343. ret = (int)DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(dlg), (hwndPos ? hwndPos : hwnd), ConfirmDlgProc, (LPARAM)&cdd);
  2344. if (hwndPos)
  2345. DestroyWindow(hwndPos);
  2346. if (ret == -1)
  2347. ret = ERROR_NOT_ENOUGH_MEMORY;
  2348. }
  2349. }
  2350. else
  2351. {
  2352. ret = IDYES;
  2353. }
  2354. return ret;
  2355. }
  2356. //
  2357. // DTNIsParentConnectOrigin()
  2358. //
  2359. // When a folder ("c:\foo files") is moved to a different drive ("a:\"), the source and the
  2360. // destinations have different roots, and therefore the "fRecursive" flag is turned ON by default.
  2361. // This results in confirmations obtained for the individual files ("c:\foo files\aaa.gif")
  2362. // rather than the folder itself. We need to first find the parent and then save the confirmation
  2363. // in the connected element of it's parent. This function gets the top-most parent and then
  2364. // checks to see if it is a connect origin and if so returns that parent pointer.
  2365. //
  2366. PDIRTREENODE DTNGetConnectOrigin(PDIRTREENODE pdtn)
  2367. {
  2368. PDIRTREENODE pdtnParent = pdtn;
  2369. //Get the top-level parent of the given node.
  2370. while (pdtn)
  2371. {
  2372. pdtnParent = pdtn;
  2373. pdtn = pdtn->pdtnParent;
  2374. }
  2375. //Now check if the parent is a connect origin.
  2376. if (pdtnParent && DTNIsConnectOrigin(pdtnParent))
  2377. return pdtnParent; //If so, return him.
  2378. else
  2379. return NULL;
  2380. }
  2381. //
  2382. // CachedConfirmFileOp()
  2383. //
  2384. // When a file("foo.htm") is moved/copied, we may put up a confirmation dialog in case
  2385. // of a conflict and the end-user might have responded saying "Yes", "no" etc., When the
  2386. // corresponding connected element ("foo files") is also moved/copied etc., we should NOT put up
  2387. // a confirmation dialog again. We must simply store the answer to the original confirmation and
  2388. // use it later.
  2389. //
  2390. // What this function does is: if the given node is a connected element, it simply retrieves the
  2391. // confirmation for the original operation and returns. If the given element is NOT a connected
  2392. // element, then this function calls the ConfirmFileOp and stores the confirmation result in
  2393. // it's connected element sothat, it later it can be used by the connected element.
  2394. //
  2395. int CachedConfirmFileOp(HWND hwnd, COPY_STATE *pcs, CONFIRM_DATA *pcd,
  2396. int nSourceFiles, int cDepth, CONFIRM_FLAG fConfirm,
  2397. LPCTSTR pFileSource, const WIN32_FIND_DATA *pfdSource,
  2398. LPCTSTR pFileDest, const WIN32_FIND_DATA *pfdDest,
  2399. LPCTSTR pStreamNames)
  2400. {
  2401. int result;
  2402. //See if this is a connected item.
  2403. if (DTNIsConnected(pcs->dth.pdtnCurrent))
  2404. {
  2405. // Since this is a connected item, the confirmation must already have been obtained from
  2406. // the user and get it from the cache!
  2407. result = DTNGetConfirmationResult(pcs->dth.pdtnCurrent);
  2408. }
  2409. else
  2410. {
  2411. PDIRTREENODE pdtnConnectOrigin;
  2412. result = ConfirmFileOp(hwnd, pcs, pcd, nSourceFiles, cDepth, fConfirm, pFileSource,
  2413. pfdSource, pFileDest, pfdDest, pStreamNames);
  2414. //Check if this node has a connection.
  2415. if (pdtnConnectOrigin = DTNGetConnectOrigin(pcs->dth.pdtnCurrent))
  2416. {
  2417. pdtnConnectOrigin->pdtnConnected->ConnectedInfo.dwConfirmation = result;
  2418. // PERF: Can we check for the result to be IDCANCEL or IDNO and if so make the
  2419. // connected node a Dummy? Currently this won't work because current code assumes
  2420. // that dummy nodes do not have children. This connected node might have some children.
  2421. // if ((result == IDCANCEL) || (result == IDNO))
  2422. // pdtnConnectOrigin->pdtnConnected->fDummy = TRUE;
  2423. }
  2424. }
  2425. return result;
  2426. }
  2427. void GuessAShortName(LPCTSTR p, LPTSTR szT)
  2428. {
  2429. int i, j, fDot, cMax;
  2430. for (i = j = fDot = 0, cMax = 8; *p; p++)
  2431. {
  2432. if (*p == TEXT('.'))
  2433. {
  2434. // if there was a previous dot, step back to it
  2435. // this way, we get the last extension
  2436. if (fDot)
  2437. i -= j+1;
  2438. // set number of chars to 0, put the dot in
  2439. j = 0;
  2440. szT[i++] = TEXT('.');
  2441. // remember we saw a dot and set max 3 chars.
  2442. fDot = TRUE;
  2443. cMax = 3;
  2444. }
  2445. else if (j < cMax && (PathGetCharType(*p) & GCT_SHORTCHAR))
  2446. {
  2447. // if *p is a lead byte, we move forward one more
  2448. if (IsDBCSLeadByte(*p))
  2449. {
  2450. szT[i] = *p++;
  2451. if (++j >= cMax)
  2452. continue;
  2453. ++i;
  2454. }
  2455. j++;
  2456. szT[i++] = *p;
  2457. }
  2458. }
  2459. szT[i] = 0;
  2460. }
  2461. /* GetNameDialog
  2462. *
  2463. * Runs the dialog box to prompt the user for a new filename when copying
  2464. * or moving from HPFS to FAT.
  2465. */
  2466. typedef struct {
  2467. LPTSTR pszDialogFrom;
  2468. LPTSTR pszDialogTo;
  2469. BOOL bShowCancel;
  2470. } GETNAME_DATA;
  2471. BOOL_PTR CALLBACK GetNameDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
  2472. {
  2473. TCHAR szT[14];
  2474. TCHAR szTo[MAX_PATH];
  2475. GETNAME_DATA * pgn = (GETNAME_DATA *)GetWindowLongPtr(hDlg, DWLP_USER);
  2476. HRESULT hr;
  2477. BOOL fOk;
  2478. switch (wMsg)
  2479. {
  2480. case WM_INITDIALOG:
  2481. SetWindowLongPtr(hDlg, DWLP_USER, lParam);
  2482. pgn = (GETNAME_DATA *)lParam;
  2483. // inform the user of the old name
  2484. PathSetDlgItemPath(hDlg, IDD_FROM, pgn->pszDialogFrom);
  2485. // directory the file will go into
  2486. PathRemoveFileSpec(pgn->pszDialogTo);
  2487. PathSetDlgItemPath(hDlg, IDD_DIR, pgn->pszDialogTo);
  2488. // generate a guess for the new name
  2489. GuessAShortName(PathFindFileName(pgn->pszDialogFrom), szT);
  2490. fOk = FALSE;
  2491. hr = StringCchCopy(szTo, ARRAYSIZE(szTo), pgn->pszDialogTo);
  2492. if (SUCCEEDED(hr))
  2493. {
  2494. if (PathAppend(szTo, szT))
  2495. {
  2496. // make sure that name is unique
  2497. if (PathYetAnotherMakeUniqueName(szTo, szTo, NULL, NULL))
  2498. {
  2499. fOk = TRUE;
  2500. }
  2501. }
  2502. }
  2503. SetDlgItemText(hDlg, IDD_TO, fOk ? PathFindFileName(szTo) : TEXT(""));
  2504. SendDlgItemMessage(hDlg, IDD_TO, EM_LIMITTEXT, 13, 0L);
  2505. SHAutoComplete(GetDlgItem(hDlg, IDD_TO), 0);
  2506. if (!pgn->bShowCancel)
  2507. HideYesToAllAndNo(hDlg);
  2508. break;
  2509. case WM_COMMAND:
  2510. switch (GET_WM_COMMAND_ID(wParam, lParam))
  2511. {
  2512. case IDD_YESTOALL:
  2513. case IDYES:
  2514. GetDlgItemText(hDlg, IDD_TO, szT, ARRAYSIZE(szT));
  2515. if (szT[0] == TEXT('\0') || !PathCombine(szTo, pgn->pszDialogTo, szT))
  2516. {
  2517. // ignore the button press, since we can't use the name
  2518. break;
  2519. }
  2520. hr = StringCchCopy(pgn->pszDialogTo, MAX_PATH, szTo);
  2521. if (FAILED(hr))
  2522. {
  2523. // This should never fail because pdth->pszDestPath (what is passed to GetNameDialog as the pTo parameter)
  2524. // is the same size (MAX_PATH) as szTo
  2525. break;
  2526. }
  2527. PathQualify(pgn->pszDialogTo);
  2528. // fall through
  2529. case IDNO:
  2530. case IDCANCEL:
  2531. EndDialog(hDlg,GET_WM_COMMAND_ID(wParam, lParam));
  2532. break;
  2533. case IDD_TO:
  2534. {
  2535. LPCTSTR p;
  2536. GetDlgItemText(hDlg, IDD_TO, szT, ARRAYSIZE(szT));
  2537. for (p = szT; *p; p = CharNext(p))
  2538. {
  2539. if (!(PathGetCharType(*p) & GCT_SHORTCHAR))
  2540. break;
  2541. }
  2542. EnableWindow(GetDlgItem(hDlg,IDYES), ((!*p) && (p != szT)));
  2543. }
  2544. break;
  2545. default:
  2546. return FALSE;
  2547. }
  2548. break;
  2549. default:
  2550. return FALSE;
  2551. }
  2552. return TRUE;
  2553. }
  2554. int GetNameDialog(HWND hwnd, COPY_STATE *pcs, BOOL fMultiple,UINT wOp, LPTSTR pFrom, LPTSTR pTo)
  2555. {
  2556. int iRet;
  2557. // if we don't want to confirm this, just mock up a string and return ok
  2558. if (!(pcs->cd.fConfirm & CONFIRM_LFNTOFAT))
  2559. {
  2560. TCHAR szTemp[MAX_PATH];
  2561. TCHAR szTo[MAX_PATH];
  2562. HRESULT hr;
  2563. GuessAShortName(PathFindFileName(pFrom), szTemp);
  2564. hr = StringCchCopy(szTo, ARRAYSIZE(szTo), pTo);
  2565. if (SUCCEEDED(hr))
  2566. {
  2567. PathRemoveFileSpec(szTo);
  2568. if (PathAppend(szTo, szTemp))
  2569. {
  2570. HRESULT hr;
  2571. // make sure that name is unique
  2572. PathYetAnotherMakeUniqueName(szTo, szTo, NULL, NULL);
  2573. iRet = IDYES;
  2574. hr = StringCchCopy(pTo, MAX_PATH, szTo);
  2575. if (FAILED(hr))
  2576. {
  2577. // This should never fail because pdth->szDestPath (what is passed to GetNameDialog as the pTo parameter)
  2578. // is the same size (MAX_PATH) as szTo
  2579. iRet = IDCANCEL;
  2580. }
  2581. }
  2582. else
  2583. {
  2584. // can't do this operation on a long path, cancel the operation
  2585. iRet = IDCANCEL;
  2586. }
  2587. }
  2588. else
  2589. {
  2590. iRet = IDCANCEL;
  2591. }
  2592. }
  2593. else
  2594. {
  2595. GETNAME_DATA gn;
  2596. gn.pszDialogFrom = pFrom;
  2597. gn.pszDialogTo = pTo;
  2598. gn.bShowCancel = fMultiple;
  2599. iRet = (int)DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_LFNTOFAT), hwnd, GetNameDlgProc, (LPARAM)(GETNAME_DATA *)&gn);
  2600. if (iRet == IDD_YESTOALL)
  2601. pcs->cd.fConfirm &= ~CONFIRM_LFNTOFAT;
  2602. }
  2603. return iRet;
  2604. }
  2605. STDAPI_(void) SHFreeNameMappings(void *hNameMappings)
  2606. {
  2607. HDSA hdsaRenamePairs = (HDSA)hNameMappings;
  2608. int i;
  2609. if (!hdsaRenamePairs)
  2610. return;
  2611. i = DSA_GetItemCount(hdsaRenamePairs) - 1;
  2612. for (; i >= 0; i--)
  2613. {
  2614. SHNAMEMAPPING FAR* prp = DSA_GetItemPtr(hdsaRenamePairs, i);
  2615. LocalFree(prp->pszOldPath);
  2616. LocalFree(prp->pszNewPath);
  2617. }
  2618. DSA_Destroy(hdsaRenamePairs);
  2619. }
  2620. void _ProcessNameMappings(LPTSTR pszTarget, UINT cchTarget, HDSA hdsaRenamePairs)
  2621. {
  2622. int i;
  2623. if (!hdsaRenamePairs)
  2624. return;
  2625. for (i = DSA_GetItemCount(hdsaRenamePairs) - 1; i >= 0; i--)
  2626. {
  2627. TCHAR cTemp;
  2628. SHNAMEMAPPING FAR* prp = DSA_GetItemPtr(hdsaRenamePairs, i);
  2629. // I don't call StrCmpNI 'cause I already know cchOldPath, and
  2630. // it has to do a couple of lstrlen()s to calculate it.
  2631. cTemp = pszTarget[prp->cchOldPath];
  2632. pszTarget[prp->cchOldPath] = 0;
  2633. // Does the target match this collision renaming entry?
  2634. // NOTE: We are trying to compare a path to a path. prp->pszOldPath
  2635. // does not have a trailing "\" character, so this isn't covered
  2636. // by the lstrcmpi below. As such, cTemp had best be the path
  2637. // seperator character to ensure that the modified pszTarget is actually
  2638. // a path and not a filename or a longer path name that doesn't match
  2639. // but happens to start with the same characters as prp->pszOldPath.
  2640. if ((cTemp == TEXT('\\')) && !lstrcmpi(pszTarget, prp->pszOldPath))
  2641. {
  2642. // Get subtree string of the target.
  2643. TCHAR *pszSubTree = &(pszTarget[prp->cchOldPath + 1]);
  2644. TCHAR szNewTarget[MAX_PATH];
  2645. // Generate the new target path.
  2646. if (PathCombine(szNewTarget, prp->pszNewPath, pszSubTree))
  2647. {
  2648. StringCchCopy(pszTarget, cchTarget, szNewTarget); // Ok should never truncate
  2649. }
  2650. break;
  2651. }
  2652. else
  2653. {
  2654. // Restore the trounced character.
  2655. pszTarget[prp->cchOldPath] = cTemp;
  2656. }
  2657. }
  2658. }
  2659. /* Sets the status dialog item in the modeless status dialog box. */
  2660. // used for both the drag drop status dialogs and the manual user
  2661. // entry dialogs so be careful what you change
  2662. void SetProgressText(COPY_STATE *pcs, LPCTSTR pszFrom, LPCTSTR pszTo)
  2663. {
  2664. HWND hwndProgress = pcs->hwndProgress;
  2665. if (hwndProgress && !(pcs->fFlags & FOF_SIMPLEPROGRESS))
  2666. {
  2667. TCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
  2668. LPTSTR pszMsg = NULL;
  2669. HDC hdc;
  2670. HFONT hfont;
  2671. HFONT hfontSave;
  2672. RECT rc;
  2673. int cxWidth;
  2674. SIZE size;
  2675. HRESULT hr;
  2676. //
  2677. // Compute the size we can use for our file names (REVIEW: Cache this result?)
  2678. //
  2679. hdc = GetDC(hwndProgress);
  2680. hfont = (HFONT)SendMessage(hwndProgress, WM_GETFONT, 0, 0);
  2681. hfontSave = (HFONT)SelectObject(hdc, hfont);
  2682. GetWindowRect(GetDlgItem(hwndProgress, IDD_TONAME), &rc);
  2683. cxWidth = rc.right - rc.left;
  2684. if (NULL != pszTo && pcs->fFlags & FOF_MULTIDESTFILES)
  2685. {
  2686. hr = StringCchCopy(szFrom, ARRAYSIZE(szFrom), pszTo);
  2687. }
  2688. else
  2689. {
  2690. hr = StringCchCopy(szFrom, ARRAYSIZE(szFrom), pszFrom);
  2691. }
  2692. if (SUCCEEDED(hr) || hr == STRSAFE_E_INSUFFICIENT_BUFFER)
  2693. {
  2694. PathStripPath(szFrom);
  2695. PathCompactPath(hdc, szFrom, cxWidth);
  2696. }
  2697. else
  2698. {
  2699. szFrom[0] = TEXT('\0');
  2700. }
  2701. SetDlgItemText(hwndProgress, IDD_NAME, szFrom);
  2702. hr = StringCchCopy(szFrom, ARRAYSIZE(szFrom), pszFrom);
  2703. if (SUCCEEDED(hr) && szFrom[0] != TEXT('\0'))
  2704. {
  2705. LPTSTR pszResource = MAKEINTRESOURCE(IDS_FROM);
  2706. LPTSTR pszToUsable = NULL;
  2707. szTo[0] = TEXT('\0');
  2708. if (pszTo)
  2709. {
  2710. pszToUsable = szTo;
  2711. pszResource = MAKEINTRESOURCE(IDS_FROMTO);
  2712. }
  2713. pszMsg = ShellConstructMessageString(HINST_THISDLL,
  2714. pszResource, "", pszToUsable);
  2715. if (NULL != pszMsg)
  2716. {
  2717. GetTextExtentPoint(hdc, pszMsg, lstrlen(pszMsg), &size);
  2718. cxWidth -= size.cx;
  2719. LocalFree(pszMsg);
  2720. }
  2721. //
  2722. // Now build the file names
  2723. //
  2724. PathRemoveFileSpec(szFrom);
  2725. PathStripPath(szFrom);
  2726. if (pszTo)
  2727. {
  2728. PathCompactPath(hdc, szFrom, cxWidth/2);
  2729. hr = StringCchCopy(szTo, ARRAYSIZE(szTo), pszTo);
  2730. if (SUCCEEDED(hr) || hr == STRSAFE_E_INSUFFICIENT_BUFFER)
  2731. {
  2732. PathRemoveFileSpec(szTo);
  2733. PathStripPath(szTo);
  2734. PathCompactPath(hdc, szTo, cxWidth/2);
  2735. }
  2736. else
  2737. {
  2738. szTo[0] = TEXT('\0');
  2739. }
  2740. }
  2741. else
  2742. {
  2743. PathCompactPath(hdc, szFrom, cxWidth);
  2744. }
  2745. //
  2746. // Now create the real message
  2747. //
  2748. pszMsg = ShellConstructMessageString(HINST_THISDLL,
  2749. pszResource, szFrom, pszToUsable);
  2750. }
  2751. else if (!pcs->fDTBuilt)
  2752. {
  2753. TCHAR szFunc[80];
  2754. if (LoadString(HINST_THISDLL, FOFuncToStringID(pcs->lpfo->wFunc),
  2755. szFunc, ARRAYSIZE(szFunc)))
  2756. {
  2757. pszMsg = ShellConstructMessageString(HINST_THISDLL,
  2758. MAKEINTRESOURCE(IDS_PREPARINGTO), szFunc);
  2759. }
  2760. }
  2761. if (pszMsg)
  2762. {
  2763. SetDlgItemText(hwndProgress, IDD_TONAME, pszMsg);
  2764. LocalFree(pszMsg);
  2765. }
  2766. SelectObject(hdc, hfontSave);
  2767. ReleaseDC(hwndProgress, hdc);
  2768. }
  2769. }
  2770. void SetProgressTimeEst(COPY_STATE *pcs, DWORD dwTimeLeft)
  2771. {
  2772. TCHAR szFmt[60];
  2773. TCHAR szOut[70];
  2774. DWORD dwTime;
  2775. if (pcs->hwndProgress)
  2776. {
  2777. if (dwTimeLeft > 4*60*60) // 4 hours and over you get no text
  2778. {
  2779. szFmt[0] = TEXT('\0');
  2780. }
  2781. else if (dwTimeLeft > 60)
  2782. {
  2783. // Note that dwTime is at least 2, so we only need a plural form
  2784. LoadString(HINST_THISDLL, IDS_TIMEEST_MINUTES, szFmt, ARRAYSIZE(szFmt));
  2785. dwTime = (dwTimeLeft / 60) + 1;
  2786. }
  2787. else
  2788. {
  2789. LoadString(HINST_THISDLL, IDS_TIMEEST_SECONDS, szFmt, ARRAYSIZE(szFmt));
  2790. // Round up to 5 seconds so it doesn't look so random
  2791. dwTime = ((dwTimeLeft+4) / 5) * 5;
  2792. }
  2793. StringCchPrintf(szOut, ARRAYSIZE(szOut), szFmt, dwTime);
  2794. SetDlgItemText(pcs->hwndProgress, IDD_TIMEEST, szOut);
  2795. }
  2796. }
  2797. // this updates the animation, which could change because we could switch between
  2798. // doing a move to recycle bin and really nuke if the file/folder was bigger that
  2799. // the allowable size of the recycle bin.
  2800. void UpdateProgressAnimation(COPY_STATE *pcs)
  2801. {
  2802. if (pcs->hwndProgress && pcs->lpfo)
  2803. {
  2804. INT_PTR idAni, idAniCurrent;
  2805. HWND hwndAnimation;
  2806. switch (pcs->lpfo->wFunc)
  2807. {
  2808. case FO_DELETE:
  2809. if ((pcs->lpfo->lpszProgressTitle == MAKEINTRESOURCE(IDS_BB_EMPTYINGWASTEBASKET)) ||
  2810. (pcs->lpfo->lpszProgressTitle == MAKEINTRESOURCE(IDS_BB_DELETINGWASTEBASKETFILES)))
  2811. {
  2812. idAni = IDA_FILENUKE;
  2813. break;
  2814. }
  2815. else if (!(pcs->fFlags & FOF_ALLOWUNDO))
  2816. {
  2817. idAni = IDA_FILEDELREAL;
  2818. break;
  2819. } // else fall through to default
  2820. default:
  2821. idAni = (IDA_FILEMOVE + (int)pcs->lpfo->wFunc - FO_MOVE);
  2822. }
  2823. hwndAnimation = GetDlgItem(pcs->hwndProgress,IDD_ANIMATE);
  2824. idAniCurrent = (INT_PTR) GetProp(hwndAnimation, TEXT("AnimationID"));
  2825. if (idAni != idAniCurrent)
  2826. {
  2827. // the one we should be using is different from the one we have,
  2828. // so update it
  2829. // close the old clip
  2830. Animate_Close(hwndAnimation);
  2831. // open the new one
  2832. Animate_Open(hwndAnimation, idAni);
  2833. // if the window is enabled, start the new animation playing
  2834. if (IsWindowEnabled(pcs->hwndProgress))
  2835. Animate_Play(hwndAnimation, -1, -1, -1);
  2836. // set the current idAni
  2837. SetProp(hwndAnimation, TEXT("AnimationID"), (HANDLE)idAni);
  2838. // at the same time we update the animation, we also update the text,
  2839. // so that the two will always be in sync
  2840. SetProgressText(pcs, pcs->dth.szSrcPath, pcs->lpfo->wFunc == FO_DELETE ? NULL : pcs->dth.szDestPath);
  2841. }
  2842. }
  2843. }
  2844. void SendProgressMessage(COPY_STATE *pcs, UINT uMsg, WPARAM wParam, LPARAM lParam)
  2845. {
  2846. if (pcs->hwndProgress)
  2847. SendDlgItemMessage(pcs->hwndProgress, IDD_PROBAR, uMsg, wParam, lParam);
  2848. }
  2849. //
  2850. // creates folder and all parts of the path if necessary (parent does not need
  2851. // to exists) and verifies that the contents of the folder will be visibile.
  2852. //
  2853. // in:
  2854. // hwnd hwnd to post UI on
  2855. // pszPath full path to create
  2856. // psa security attributes
  2857. //
  2858. // returns:
  2859. // ERROR_SUCCESS (0) success
  2860. // ERROR_ failure
  2861. //
  2862. STDAPI_(int) SHCreateDirectoryEx(HWND hwnd, LPCTSTR pszPath, SECURITY_ATTRIBUTES *psa)
  2863. {
  2864. int ret = ERROR_SUCCESS;
  2865. if (PathIsRelative(pszPath))
  2866. {
  2867. // if not a "full" path bail
  2868. // to ensure that we dont create a dir in the current working directory
  2869. SetLastError(ERROR_BAD_PATHNAME);
  2870. return ERROR_BAD_PATHNAME;
  2871. }
  2872. if (!Win32CreateDirectory(pszPath, psa))
  2873. {
  2874. TCHAR *pEnd, *pSlash, szTemp[MAX_PATH];
  2875. HRESULT hr;
  2876. ret = GetLastError();
  2877. // There are certain error codes that we should bail out here
  2878. // before going through and walking up the tree...
  2879. switch (ret)
  2880. {
  2881. case ERROR_FILENAME_EXCED_RANGE:
  2882. case ERROR_FILE_EXISTS:
  2883. case ERROR_ALREADY_EXISTS:
  2884. return ret;
  2885. }
  2886. hr = StringCchCopy(szTemp, ARRAYSIZE(szTemp), pszPath);
  2887. if (FAILED(hr))
  2888. {
  2889. return ERROR_FILENAME_EXCED_RANGE;
  2890. }
  2891. pEnd = PathAddBackslash(szTemp); // for the loop below
  2892. if (pEnd == NULL)
  2893. {
  2894. return ERROR_FILENAME_EXCED_RANGE;
  2895. }
  2896. // assume we have 'X:\' to start this should even work
  2897. // on UNC names because will will ignore the first error
  2898. pSlash = szTemp + 3;
  2899. // create each part of the dir in order
  2900. while (*pSlash)
  2901. {
  2902. while (*pSlash && *pSlash != TEXT('\\'))
  2903. pSlash = CharNext(pSlash);
  2904. if (*pSlash)
  2905. {
  2906. ASSERT(*pSlash == TEXT('\\'));
  2907. *pSlash = 0; // terminate path at seperator
  2908. ret = Win32CreateDirectory(szTemp, pSlash + 1 == pEnd ? psa : NULL) ? ERROR_SUCCESS : GetLastError();
  2909. }
  2910. *pSlash++ = TEXT('\\'); // put the seperator back
  2911. }
  2912. }
  2913. if (ERROR_SUCCESS != ret)
  2914. {
  2915. // We failed, so let's try to display error UI.
  2916. if (hwnd && ERROR_CANCELLED != ret)
  2917. {
  2918. SHSysErrorMessageBox(hwnd, NULL, IDS_CANNOTCREATEFOLDER, ret,
  2919. pszPath ? PathFindFileName(pszPath) : NULL,
  2920. MB_OK | MB_ICONEXCLAMATION);
  2921. ret = ERROR_CANCELLED; // Indicate we already displayed Error UI.
  2922. }
  2923. }
  2924. return ret;
  2925. }
  2926. STDAPI_(int) SHCreateDirectory(HWND hwnd, LPCTSTR pszPath)
  2927. {
  2928. return SHCreateDirectoryEx(hwnd, pszPath, NULL);
  2929. }
  2930. #ifdef UNICODE
  2931. STDAPI_(int) SHCreateDirectoryExA(HWND hwnd, LPCSTR pszPath, SECURITY_ATTRIBUTES *psa)
  2932. {
  2933. WCHAR wsz[MAX_PATH];
  2934. SHAnsiToUnicode(pszPath, wsz, SIZECHARS(wsz));
  2935. return SHCreateDirectoryEx(hwnd, wsz, psa);
  2936. }
  2937. #else
  2938. STDAPI_(int) SHCreateDirectoryExW(HWND hwnd, LPCWSTR pszPath, SECURITY_ATTRIBUTES *psa)
  2939. {
  2940. char sz[MAX_PATH];
  2941. SHUnicodeToAnsi(pszPath, sz, SIZECHARS(sz));
  2942. return SHCreateDirectoryEx(hwnd, sz, psa);
  2943. }
  2944. #endif
  2945. // this function will move a file by copying it and then deleting it
  2946. // with proper error propagation and cleanup
  2947. BOOL MoveFileAsCopyAndDelete(LPCTSTR pszSource, LPCTSTR pszDest, LPPROGRESS_ROUTINE lpProgressRoutine,
  2948. void *lpData, BOOL *pbCancel, DWORD dwCopyFlags)
  2949. {
  2950. BOOL bRet = FALSE;
  2951. if (CopyFileEx(pszSource, pszDest, lpProgressRoutine, lpData, pbCancel, dwCopyFlags))
  2952. {
  2953. if (DeleteFile(pszSource))
  2954. {
  2955. // all is well in the world
  2956. bRet = TRUE;
  2957. }
  2958. else
  2959. {
  2960. // couldn't delete the source - save the current GLE value, delete the dest, and return FALSE
  2961. int iGLE = GetLastError();
  2962. DeleteFile(pszDest); // if this fails, life is hopeless
  2963. SetLastError(iGLE);
  2964. }
  2965. }
  2966. return bRet;
  2967. }
  2968. // call MPR to find out the speed of a given path
  2969. //
  2970. // returns
  2971. // 0 for unknown
  2972. // 144 for 14.4 modems
  2973. // 96 for 9600
  2974. // 24 for 2400
  2975. //
  2976. // if the device does not return a speed we return 0
  2977. //
  2978. DWORD GetPathSpeed(LPCTSTR pszPath)
  2979. {
  2980. NETCONNECTINFOSTRUCT nci;
  2981. NETRESOURCE nr;
  2982. TCHAR szPath[MAX_PATH];
  2983. HRESULT hr;
  2984. hr = StringCchCopy(szPath, ARRAYSIZE(szPath), pszPath);
  2985. if (FAILED(hr))
  2986. {
  2987. return 0;
  2988. }
  2989. PathStripToRoot(szPath); // get a root to this path
  2990. memset(&nci, 0, sizeof(nci));
  2991. nci.cbStructure = sizeof(nci);
  2992. memset(&nr, 0, sizeof(nr));
  2993. if (PathIsUNC(szPath))
  2994. nr.lpRemoteName = szPath;
  2995. else
  2996. {
  2997. // Don't bother for local drives
  2998. if (!IsRemoteDrive(DRIVEID(szPath)))
  2999. return 0;
  3000. // we are passing in a local drive and MPR does not like us to pass a
  3001. // local name as Z:\ but only wants Z:
  3002. szPath[2] = 0; // Strip off after character and :
  3003. nr.lpLocalName = szPath;
  3004. }
  3005. // dwSpeed is returned by MultinetGetConnectionPerformance
  3006. MultinetGetConnectionPerformance(&nr, &nci);
  3007. return nci.dwSpeed;
  3008. }
  3009. DWORD CopyCallbackProc(LARGE_INTEGER liTotSize, LARGE_INTEGER liBytes,
  3010. LARGE_INTEGER liStreamSize, LARGE_INTEGER liStreamBytes,
  3011. DWORD dwStream, DWORD dwCallback,
  3012. HANDLE hSource, HANDLE hDest, void *pv)
  3013. {
  3014. COPY_STATE *pcs = (COPY_STATE *)pv;
  3015. DebugMsg(DM_TRACE, TEXT("CopyCallbackProc[%08lX], totsize=%08lX, bytes=%08lX"),
  3016. dwCallback, liTotSize.LowPart, liBytes.LowPart);
  3017. if (FOQueryAbort(pcs))
  3018. return PROGRESS_CANCEL;
  3019. DTSetFileCopyProgress(&pcs->dth, liBytes);
  3020. if (pcs->fInitialize)
  3021. {
  3022. // preserve the create date when moving across volumes, otherwise use the
  3023. // create date the file system picked when we did the CreateFile()
  3024. // always preserve modified date (ftLastWriteTime)
  3025. // bummer is we loose accuracy when going to VFAT compared to NT servers
  3026. SetFileTime((HANDLE)hDest, (pcs->lpfo->wFunc == FO_MOVE) ? &pcs->pfd->ftCreationTime : NULL,
  3027. NULL, &pcs->pfd->ftLastWriteTime);
  3028. pcs->fInitialize = FALSE;
  3029. }
  3030. switch (dwCallback)
  3031. {
  3032. case CALLBACK_STREAM_SWITCH:
  3033. break;
  3034. case CALLBACK_CHUNK_FINISHED:
  3035. break;
  3036. default:
  3037. break;
  3038. }
  3039. return PROGRESS_CONTINUE;
  3040. }
  3041. // copy the SECURITY_DESCRIPTOR for two files
  3042. //
  3043. // in:
  3044. // pszSource fully qualified source path
  3045. // pszDest fully qualified destination path
  3046. //
  3047. // returns:
  3048. // 0 ERROR_SUCCESS
  3049. // WIN32 error codes
  3050. //
  3051. DWORD
  3052. CopyFileSecurity(LPCTSTR pszSource, LPCTSTR pszDest)
  3053. {
  3054. DWORD err = ERROR_SUCCESS;
  3055. BOOL fRet = TRUE;
  3056. BYTE buf[512];
  3057. // arbitrarily saying do everything we can
  3058. // except SACL_SECURITY_INFORMATION because
  3059. SECURITY_INFORMATION si = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION;
  3060. PSECURITY_DESCRIPTOR psd = (PSECURITY_DESCRIPTOR) buf;
  3061. DWORD cbPsd = sizeof(buf);
  3062. if (!SHRestricted(REST_FORCECOPYACLWITHFILE))
  3063. {
  3064. // shell restriction so return access denied?
  3065. return ERROR_ACCESS_DENIED;
  3066. }
  3067. fRet = GetFileSecurity(pszSource, si, psd, cbPsd, &cbPsd);
  3068. if (!fRet)
  3069. {
  3070. err = GetLastError();
  3071. if (ERROR_INSUFFICIENT_BUFFER == err)
  3072. {
  3073. // just need to resize the buffer and try again
  3074. psd = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, cbPsd);
  3075. if (psd)
  3076. {
  3077. fRet = GetFileSecurity(pszSource, si, psd, cbPsd, &cbPsd);
  3078. }
  3079. else
  3080. {
  3081. err = ERROR_NOT_ENOUGH_MEMORY;
  3082. }
  3083. }
  3084. }
  3085. if (fRet)
  3086. {
  3087. fRet = SetFileSecurity(pszDest, si, psd);
  3088. if (!fRet)
  3089. err = GetLastError();
  3090. }
  3091. if (psd && psd != buf)
  3092. LocalFree(psd);
  3093. if (fRet)
  3094. return ERROR_SUCCESS;
  3095. return err;
  3096. }
  3097. // reset the SECURITY_DESCRIPTOR on a file or directory
  3098. //
  3099. // in:
  3100. // pszDest fully qualified destination path
  3101. //
  3102. // returns:
  3103. // 0 ERROR_SUCCESS
  3104. // WIN32 error codes
  3105. //
  3106. DWORD
  3107. ResetFileSecurity(LPCTSTR pszDest)
  3108. {
  3109. DWORD err = ERROR_SUCCESS;
  3110. if (!SHRestricted(REST_FORCECOPYACLWITHFILE))
  3111. {
  3112. ACL acl;
  3113. InitializeAcl(&acl, sizeof(acl), ACL_REVISION);
  3114. // TreeResetNamedSecurityInfo has a callback mechanism, but
  3115. // we currently don't use it. Note that the paths passed to
  3116. // the callback look like
  3117. // "\Device\HarddiskVolume1\dir\name"
  3118. err = TreeResetNamedSecurityInfo((LPTSTR)pszDest,
  3119. SE_FILE_OBJECT,
  3120. DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION,
  3121. NULL,
  3122. NULL,
  3123. &acl,
  3124. NULL,
  3125. FALSE, // KeepExplicit (perms on children)
  3126. NULL,
  3127. ProgressInvokeNever,
  3128. NULL);
  3129. }
  3130. return err;
  3131. }
  3132. //
  3133. // in:
  3134. // hwnd Window to report things to.
  3135. // pszSource fully qualified source path
  3136. // pszDest fully qualified destination path
  3137. // pfd source file find data (size/date/time/attribs)
  3138. //
  3139. // returns:
  3140. // ERROR_SUCCESS (0)
  3141. // other Win32 ERROR_ codes
  3142. //
  3143. UINT FileCopy(COPY_STATE *pcs, LPCTSTR pszSource, LPCTSTR pszDest, const WIN32_FIND_DATA *pfd, BOOL fCreateAlways)
  3144. {
  3145. UINT iRet = ERROR_CANCELLED;
  3146. BOOL fRetryPath = FALSE;
  3147. BOOL fRetryAttr = FALSE;
  3148. BOOL fCopyOrMoveSucceeded = FALSE;
  3149. BOOL fSecurityObtained = FALSE;
  3150. DWORD dwCopyFlags;
  3151. BOOL fLostEncryptOk = FALSE;
  3152. // Buffers for security info
  3153. BYTE rgbSecurityDescriptor[512];
  3154. SECURITY_INFORMATION si = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION;
  3155. PSECURITY_DESCRIPTOR psd = (PSECURITY_DESCRIPTOR) rgbSecurityDescriptor;
  3156. DWORD cbPsd = sizeof(rgbSecurityDescriptor);
  3157. // Make sure we can start
  3158. if (FOQueryAbort(pcs))
  3159. return ERROR_CANCELLED;
  3160. //
  3161. // Now do the file copy/move
  3162. //
  3163. // Get the security info from the source file. If there is a problem
  3164. // (e.g. the file is on FAT) we ignore it and proceed with the copy/move.
  3165. if (!(pcs->fFlags & FOF_NOCOPYSECURITYATTRIBS))
  3166. {
  3167. if (SHRestricted(REST_FORCECOPYACLWITHFILE))
  3168. {
  3169. if (GetFileSecurity(pszSource, si, psd, cbPsd, &cbPsd))
  3170. {
  3171. fSecurityObtained = TRUE;
  3172. }
  3173. else
  3174. {
  3175. if (ERROR_INSUFFICIENT_BUFFER == GetLastError())
  3176. {
  3177. psd = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, cbPsd);
  3178. if (psd)
  3179. {
  3180. if (GetFileSecurity(pszSource, si, psd, cbPsd, &cbPsd))
  3181. {
  3182. fSecurityObtained = TRUE;
  3183. }
  3184. }
  3185. }
  3186. }
  3187. }
  3188. }
  3189. TryCopyAgain:
  3190. pcs->fInitialize = TRUE;
  3191. pcs->pfd = pfd;
  3192. SetProgressText(pcs, pszSource, pszDest);
  3193. dwCopyFlags = 0;
  3194. if (fLostEncryptOk)
  3195. {
  3196. dwCopyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION;
  3197. }
  3198. if (!fCreateAlways)
  3199. {
  3200. dwCopyFlags |= COPY_FILE_FAIL_IF_EXISTS;
  3201. }
  3202. if (FO_MOVE == pcs->lpfo->wFunc)
  3203. {
  3204. fCopyOrMoveSucceeded = MoveFileWithProgress(pszSource, pszDest, CopyCallbackProc, pcs, MOVEFILE_COPY_ALLOWED | (fCreateAlways ? MOVEFILE_REPLACE_EXISTING : 0));
  3205. if (!fCopyOrMoveSucceeded &&
  3206. (dwCopyFlags & COPY_FILE_ALLOW_DECRYPTED_DESTINATION) && // this flag will only be set if we've already come through here once, then accepted the prompt below
  3207. (GetLastError() == ERROR_ENCRYPTION_FAILED))
  3208. {
  3209. fCopyOrMoveSucceeded =
  3210. MoveFileAsCopyAndDelete(pszSource, pszDest, CopyCallbackProc, pcs, &pcs->bAbort, dwCopyFlags);
  3211. }
  3212. }
  3213. else
  3214. {
  3215. fCopyOrMoveSucceeded = CopyFileEx(pszSource, pszDest, CopyCallbackProc, pcs, &pcs->bAbort, dwCopyFlags);
  3216. }
  3217. if (!fCopyOrMoveSucceeded)
  3218. {
  3219. int iLastError = (int)GetLastError();
  3220. DebugMsg(TF_DEBUGCOPY, TEXT("FileCopy() failed, get last error returned 0x%08x"), iLastError);
  3221. switch (iLastError)
  3222. {
  3223. // Let the caller handle this one
  3224. case ERROR_FILE_EXISTS:
  3225. case ERROR_ALREADY_EXISTS: // nt5 221893 CopyFileEx now returns this for some reason...
  3226. iRet = ERROR_FILE_EXISTS;
  3227. goto Exit;
  3228. case ERROR_DISK_FULL:
  3229. if (PathIsUNC(pszDest) || !IsRemovableDrive(DRIVEID(pszDest)) || PathIsSameRoot(pszDest,pszSource))
  3230. {
  3231. break;
  3232. }
  3233. iLastError = ERROR_DISK_FULL;
  3234. // Fall through
  3235. case ERROR_PATH_NOT_FOUND:
  3236. if (!fRetryPath)
  3237. {
  3238. // ask the user to stick in another disk or empty wastebasket
  3239. ULARGE_INTEGER ulFileSize;
  3240. ulFileSize.LowPart = pfd->nFileSizeLow;
  3241. ulFileSize.HighPart = pfd->nFileSizeHigh;
  3242. iLastError = CopyMoveRetry(pcs, pszDest, iLastError, &ulFileSize);
  3243. if (!iLastError)
  3244. {
  3245. fRetryPath = TRUE;
  3246. goto TryCopyAgain;
  3247. }
  3248. CopyError(pcs, pszSource, pszDest, (UINT)iLastError | ERRORONDEST, FO_COPY, OPER_DOFILE);
  3249. iRet = ERROR_CANCELLED;
  3250. goto Exit;
  3251. }
  3252. break;
  3253. case ERROR_ENCRYPTION_FAILED:
  3254. if (pfd->dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED && FALSE == fLostEncryptOk)
  3255. {
  3256. int result;
  3257. result = CachedConfirmFileOp(pcs->hwndDlgParent, pcs,
  3258. &pcs->cd, pcs->nSourceFiles,
  3259. FALSE,
  3260. CONFIRM_LOST_ENCRYPT_FILE,
  3261. pszSource, pfd, pszDest, NULL, NULL);
  3262. switch (result)
  3263. {
  3264. case IDYES:
  3265. fLostEncryptOk = TRUE;
  3266. goto TryCopyAgain;
  3267. case IDNO:
  3268. case IDCANCEL:
  3269. pcs->bAbort = TRUE;
  3270. iRet = result;
  3271. break;
  3272. default:
  3273. iRet = result;
  3274. break;
  3275. }
  3276. }
  3277. break;
  3278. case ERROR_ACCESS_DENIED:
  3279. // check if the filename is too long
  3280. if (lstrlen(PathFindFileName(pszSource)) + lstrlen(pszDest) >= MAX_PATH)
  3281. {
  3282. iLastError = DE_FILENAMETOOLONG;
  3283. }
  3284. else if (!fRetryAttr)
  3285. {
  3286. // If the file is readonly, reset the readonly attribute
  3287. // and have another go at it
  3288. DWORD dwAttributes = GetFileAttributes(pszDest);
  3289. if (0xFFFFFFFF != dwAttributes)
  3290. {
  3291. dwAttributes &= ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);
  3292. if (SetFileAttributes(pszDest, dwAttributes))
  3293. {
  3294. fRetryAttr = TRUE;
  3295. goto TryCopyAgain;
  3296. }
  3297. }
  3298. // GetFileAttributes() 10 lines above clobers GetLastError() and CopyError()
  3299. // needs it.
  3300. SetLastError(iLastError);
  3301. }
  3302. break;
  3303. }
  3304. if (!pcs->bAbort)
  3305. {
  3306. CopyError(pcs, pszSource, pszDest, iLastError, FO_COPY, OPER_DOFILE);
  3307. }
  3308. iRet = ERROR_CANCELLED; // error already reported
  3309. goto Exit;
  3310. }
  3311. // If copying from a CDRom - clear the read-only bit
  3312. if (pcs->fFromCDRom)
  3313. {
  3314. SetFileAttributes(pszDest, pfd->dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
  3315. }
  3316. // Set the source's security on the destination, ignoring any error.
  3317. if (fSecurityObtained)
  3318. {
  3319. SetFileSecurity(pszDest, si, psd);
  3320. }
  3321. SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, pszDest, NULL);
  3322. if (FO_MOVE == pcs->lpfo->wFunc)
  3323. {
  3324. // Let windows waiting on notifications of Source know of change. We have to check
  3325. // to see if the file is actually gone in order to tell if it actually moved or not.
  3326. if (!PathFileExists(pszSource))
  3327. SHChangeNotify(SHCNE_DELETE, SHCNF_PATH, pszSource, NULL);
  3328. }
  3329. else if (0 == StrCmpIC(pfd->cFileName, TEXT("desktop.ini")))
  3330. {
  3331. // clean out stuff from the desktop.ini
  3332. WritePrivateProfileSection(TEXT("DeleteOnCopy"), NULL, pszDest);
  3333. }
  3334. iRet = ERROR_SUCCESS; // 0
  3335. Exit:
  3336. // If we had to alloc a buffer for the security descriptor,
  3337. // free it now.
  3338. if (psd && (rgbSecurityDescriptor != psd))
  3339. LocalFree(psd);
  3340. return iRet;
  3341. }
  3342. // note: this is a very slow call
  3343. DWORD GetFreeClusters(LPCTSTR szPath)
  3344. {
  3345. DWORD dwFreeClus;
  3346. DWORD dwTemp;
  3347. if (GetDiskFreeSpace(szPath, &dwTemp, &dwTemp, &dwFreeClus, &dwTemp))
  3348. return dwFreeClus;
  3349. else
  3350. return (DWORD)-1;
  3351. }
  3352. // note: this is a very slow call
  3353. BOOL TotalCapacity(LPCTSTR szPath, ULARGE_INTEGER *puliDiskSize)
  3354. {
  3355. int idDrive = PathGetDriveNumber(szPath);
  3356. if (idDrive != -1)
  3357. {
  3358. TCHAR szDrive[5];
  3359. ULARGE_INTEGER ullDiskFreeForUser;
  3360. PathBuildRoot(szDrive, idDrive);
  3361. return GetDiskFreeSpaceEx(szDrive, &ullDiskFreeForUser, puliDiskSize, NULL);
  3362. }
  3363. return FALSE;
  3364. }
  3365. typedef struct
  3366. {
  3367. LPTSTR pszTitle;
  3368. LPTSTR pszText;
  3369. } DISKERRORPARAM;
  3370. BOOL_PTR CALLBACK DiskErrDlgProc(HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam)
  3371. {
  3372. switch (uMessage)
  3373. {
  3374. case WM_INITDIALOG:
  3375. {
  3376. DISKERRORPARAM *pDiskError = (DISKERRORPARAM *) lParam;
  3377. if (pDiskError)
  3378. {
  3379. SetWindowText(hDlg, pDiskError->pszTitle);
  3380. SetDlgItemText(hDlg, IDC_DISKERR_EXPLAIN, pDiskError->pszText);
  3381. }
  3382. Static_SetIcon(GetDlgItem(hDlg, IDC_DISKERR_STOPICON),
  3383. LoadIcon(NULL, IDI_HAND));
  3384. }
  3385. break;
  3386. case WM_COMMAND:
  3387. switch (LOWORD(wParam))
  3388. {
  3389. case IDOK:
  3390. case IDCANCEL:
  3391. case IDC_DISKERR_LAUNCHCLEANUP:
  3392. EndDialog (hDlg, LOWORD(wParam));
  3393. break;
  3394. default:
  3395. return FALSE;
  3396. }
  3397. break;
  3398. default:
  3399. return FALSE;
  3400. }
  3401. return TRUE;
  3402. }
  3403. void DisplayFileOperationError(HWND hParent, int idVerb, int wFunc, int nError, LPCTSTR pszReason, LPCTSTR pszPath, LPCTSTR pszDest)
  3404. {
  3405. TCHAR szBuffer[80];
  3406. DISKERRORPARAM diskparams;
  3407. // Grab title from resource
  3408. if (LoadString(HINST_THISDLL, IDS_FILEERROR + wFunc, szBuffer, ARRAYSIZE(szBuffer)))
  3409. {
  3410. diskparams.pszTitle = szBuffer;
  3411. }
  3412. else
  3413. {
  3414. diskparams.pszTitle = NULL;
  3415. }
  3416. // Build Message to display
  3417. diskparams.pszText = ShellConstructMessageString(HINST_THISDLL,
  3418. MAKEINTRESOURCE(idVerb), pszReason, PathFindFileName(pszPath));
  3419. if (diskparams.pszText)
  3420. {
  3421. int idDrive = DriveIDFromBBPath(pszDest);
  3422. //if we want to show Disk cleanup do our stuff, otherwise do MessageBox
  3423. if (nError == ERROR_DISK_FULL &&
  3424. IsBitBucketableDrive(idDrive) &&
  3425. !PathIsUNC(pszDest) &&
  3426. GetDiskCleanupPath(NULL, 0))
  3427. {
  3428. if (DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_DISKERR), hParent,
  3429. DiskErrDlgProc, (LPARAM)&diskparams) == IDC_DISKERR_LAUNCHCLEANUP)
  3430. {
  3431. LaunchDiskCleanup(hParent, idDrive, DISKCLEANUP_NOFLAG);
  3432. }
  3433. }
  3434. else
  3435. {
  3436. MessageBox(hParent, diskparams.pszText, diskparams.pszTitle,
  3437. MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
  3438. }
  3439. LocalFree(diskparams.pszText);
  3440. }
  3441. }
  3442. /***********************************************************************\
  3443. DESCRIPTION:
  3444. We received an SHARINGVIOLATION or ACCESSDENIED error. We want
  3445. to generate the most accruate error message for the user to inform
  3446. them better. These are the cases we care about:
  3447. ERROR_ACCESS_DENIED: This is the legacy case with the message:
  3448. "Access is denied. The source file may be in use."
  3449. DE_DEST_IS_CDROM: This is displayed in case the user copies a file to
  3450. their cd-rom drive.
  3451. DE_DEST_IS_CDRECORD: user deletes from CD recordable drive, we need an error
  3452. message that isn't so scary about "cant copy files to CD".
  3453. DE_DEST_IS_DVD: This is displayed in case the user copies a file to
  3454. their DVD drive
  3455. DE_SHARING_VIOLATION: The file can't be copied because it's open by someone
  3456. who doesn't allow others to read the file while they
  3457. use it.
  3458. DE_PERMISSIONDENIED: This should be displayed if the user doesn't have
  3459. the ACLs (security permissions) to read/copy the file.
  3460. \***********************************************************************/
  3461. int GenAccessDeniedError(LPCTSTR pszSource, LPCTSTR pszDest, int nError)
  3462. {
  3463. int nErrorMsg = ERROR_ACCESS_DENIED;
  3464. int iDrive = PathGetDriveNumber(pszDest);
  3465. if (iDrive != -1)
  3466. {
  3467. if (IsCDRomDrive(iDrive))
  3468. {
  3469. WCHAR szDrive[4];
  3470. // check if user is deleting from cd-r drive. error message saying "cant copy or move files to cd drive"
  3471. // doesn't apply. since we're about to put up ui its not like we have to be super fast here, call into cdburning code.
  3472. if (SUCCEEDED(CDBurn_GetRecorderDriveLetter(szDrive, ARRAYSIZE(szDrive))) &&
  3473. (DRIVEID(szDrive) == iDrive))
  3474. {
  3475. nErrorMsg = DE_DEST_IS_CDRECORD;
  3476. }
  3477. else
  3478. {
  3479. nErrorMsg = DE_DEST_IS_CDROM;
  3480. }
  3481. }
  3482. if (DriveIsDVD(iDrive))
  3483. nErrorMsg = DE_DEST_IS_DVD;
  3484. }
  3485. // TODO: DE_SHARING_VIOLATION, DE_PERMISSIONDENIED
  3486. return nErrorMsg;
  3487. }
  3488. //
  3489. // The following function reports errors for the copy engine
  3490. //
  3491. // Parameters
  3492. // pszSource source file name
  3493. // pszDest destination file name
  3494. // nError dos (or our exteneded) error code
  3495. // 0xFFFF for special case NET error
  3496. // wFunc FO_* values
  3497. // nOper OPER_* values, operation being performed
  3498. //
  3499. void CopyError(LPCOPY_STATE pcs, LPCTSTR pszSource, LPCTSTR pszDest, int nError, UINT wFunc, int nOper)
  3500. {
  3501. TCHAR szReason[200];
  3502. TCHAR szFile[MAX_PATH];
  3503. int idVerb;
  3504. BOOL bDest;
  3505. BOOL fSysError = FALSE;
  3506. DWORD dwError = GetLastError(); // get Extended error now before we blow it away.
  3507. HRESULT hr;
  3508. if (!pcs || (pcs->fFlags & FOF_NOERRORUI))
  3509. return; // caller doesn't want to report errors
  3510. bDest = nError & ERRORONDEST; // was dest file cause of error
  3511. nError &= ~ERRORONDEST; // clear the dest bit
  3512. // We also may need to remap some new error codes into old error codes
  3513. //
  3514. if (nError == ERROR_BAD_PATHNAME)
  3515. nError = DE_INVALIDFILES;
  3516. if (nError == ERROR_CANCELLED) // user abort
  3517. return;
  3518. hr = StringCchCopy(szFile, ARRAYSIZE(szFile), bDest ? pszDest : pszSource);
  3519. if (FAILED(hr) || szFile[0] == TEXT('\0'))
  3520. {
  3521. LoadString(HINST_THISDLL, IDS_FILE, szFile, ARRAYSIZE(szFile));
  3522. }
  3523. else
  3524. {
  3525. // make the path fits on the screen
  3526. RECT rcMonitor;
  3527. HWND hwnd = pcs->hwndProgress ? pcs->hwndProgress : pcs->hwndDlgParent;
  3528. GetMonitorRect(MonitorFromWindow(hwnd, TRUE), &rcMonitor);
  3529. PathCompactPath(NULL, szFile, (rcMonitor.right - rcMonitor.left) / 3);
  3530. }
  3531. // get the verb string
  3532. // since we now recycle folders as well as files, added OPER_ENTERDIR check here
  3533. if ((nOper == OPER_DOFILE) || (nOper == OPER_ENTERDIR) || (nOper == 0))
  3534. {
  3535. if ((nError != -1) && bDest)
  3536. {
  3537. idVerb = IDS_REPLACING;
  3538. }
  3539. else
  3540. {
  3541. idVerb = IDS_VERBS + wFunc;
  3542. }
  3543. }
  3544. else
  3545. {
  3546. idVerb = IDS_ACTIONS + (nOper >> 8);
  3547. }
  3548. // get the reason string
  3549. if (nError == 0xFFFF)
  3550. {
  3551. DWORD dw;
  3552. WNetGetLastError(&dw, szReason, ARRAYSIZE(szReason), NULL, 0);
  3553. }
  3554. else
  3555. {
  3556. // transform some error cases
  3557. if (bDest)
  3558. {
  3559. // This caseing of error codes is error prone.. it would
  3560. // be better to find the explicit ones we wish to map to
  3561. // this one instead of trying to guess all the ones
  3562. // we don't want to map...
  3563. if ((nError == ERROR_DISK_FULL) ||
  3564. ((nError != ERROR_ACCESS_DENIED) &&
  3565. (nError != ERROR_NETWORK_ACCESS_DENIED) &&
  3566. (nError != ERROR_WRITE_PROTECT) &&
  3567. (nError != ERROR_BAD_NET_NAME) &&
  3568. (GetFreeClusters(pszDest) == 0L)))
  3569. {
  3570. nError = ERROR_DISK_FULL;
  3571. }
  3572. else if (dwError == ERROR_WRITE_FAULT)
  3573. {
  3574. nError = ERROR_WRITE_FAULT;
  3575. }
  3576. else if (dwError == ERROR_INVALID_NAME)
  3577. {
  3578. nError = ERROR_INVALID_NAME;
  3579. }
  3580. }
  3581. else
  3582. {
  3583. if (nError == ERROR_ACCESS_DENIED)
  3584. {
  3585. // Check the extended error for more info about the error...
  3586. // We just map these errors to something generic that
  3587. // tells the user something weird is going on.
  3588. switch (dwError)
  3589. {
  3590. case ERROR_CRC:
  3591. case ERROR_SEEK:
  3592. case ERROR_SECTOR_NOT_FOUND:
  3593. case ERROR_READ_FAULT:
  3594. case ERROR_GEN_FAILURE:
  3595. nError = ERROR_GEN_FAILURE;
  3596. break;
  3597. // We can't test for ERROR_FILE_NOT_FOUND because in the case where we copy to
  3598. // a write-protected dest we check to see if the reason we got access denied was
  3599. // because there's already a read-only file there. If there isn't _that_ test is
  3600. // going to SetLastError() to ERROR_FILE_NOT_FOUND and that's what we're going to
  3601. // report as an error. [davepl]
  3602. //
  3603. // case ERROR_FILE_NOT_FOUND:
  3604. // nError = ERROR_GEN_FAILURE;
  3605. // break;
  3606. case ERROR_SHARING_VIOLATION:
  3607. case ERROR_ACCESS_DENIED:
  3608. nError = GenAccessDeniedError(pszSource, pszDest, nError);
  3609. break;
  3610. default:
  3611. TraceMsg(TF_WARNING, "CopyEngine: hit error %x , not currently special cased", dwError);
  3612. break;
  3613. }
  3614. }
  3615. else
  3616. {
  3617. // This error occures when a user drags & drops a file from point a to
  3618. // point b twice. The second time fails because the first time hasn't finished.
  3619. if (nError == (OPER_ERROR | ERROR_FILE_NOT_FOUND))
  3620. {
  3621. nError = ERROR_GEN_FAILURE;
  3622. }
  3623. }
  3624. }
  3625. }
  3626. // the error munging above is in several places, but there are some errors that we
  3627. // know for SURE the user will never want to see so zap them to generic failures.
  3628. // this whole thing needs a redesign... we shouldnt depend generally on errors getting
  3629. // UI ("There is not enough space on the disk.") because then we get crap like this.
  3630. // but everybody already knows that.
  3631. switch (nError)
  3632. {
  3633. case ERROR_SWAPERROR: // Error performing inpage operation.
  3634. nError = ERROR_GEN_FAILURE;
  3635. break;
  3636. }
  3637. if (nError <= DE_ERROR_MAX)
  3638. {
  3639. BOOL fOverridden = FALSE;
  3640. if (nError == ERROR_SHARING_VIOLATION)
  3641. {
  3642. // in the sharing violation case we can try to be a little better in the error UI by
  3643. // going through the running object table and seeing if the file is registered in there.
  3644. // if it's not in there, no biggie, just use our normal handling.
  3645. PWSTR pszApp;
  3646. if (SUCCEEDED(FindAppForFileInUse(bDest ? pszDest : pszSource, &pszApp)))
  3647. {
  3648. PWSTR pszMessage = ShellConstructMessageString(HINST_THISDLL, MAKEINTRESOURCE(IDS_SHAREVIOLATION_HINT), pszApp);
  3649. if (pszMessage)
  3650. {
  3651. StringCchCopy(szReason, ARRAYSIZE(szReason), pszMessage); // ok to truncate, for display only
  3652. fOverridden = TRUE;
  3653. LocalFree(pszMessage);
  3654. }
  3655. LocalFree(pszApp);
  3656. }
  3657. }
  3658. if (!fOverridden)
  3659. {
  3660. fSysError = !LoadString(HINST_THISDLL, IDS_REASONS + nError, szReason, ARRAYSIZE(szReason));
  3661. }
  3662. }
  3663. if (nOper == OPER_DOFILE)
  3664. {
  3665. PathRemoveExtension(szFile);
  3666. }
  3667. if (fSysError)
  3668. {
  3669. SHSysErrorMessageBox(pcs->hwndDlgParent, MAKEINTRESOURCE(IDS_FILEERROR + wFunc),
  3670. idVerb, nError, PathFindFileName(szFile),
  3671. MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
  3672. }
  3673. else
  3674. {
  3675. if (nError > DE_ERROR_MAX &&
  3676. 0 == FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
  3677. NULL,
  3678. nError,
  3679. 0,
  3680. szReason,
  3681. ARRAYSIZE(szReason),
  3682. NULL))
  3683. {
  3684. szReason[0] = 0;
  3685. }
  3686. DisplayFileOperationError(pcs->hwndDlgParent, idVerb, wFunc, nError, szReason, szFile, pszDest);
  3687. }
  3688. }
  3689. //
  3690. // The following function is used to retry failed move/copy operations
  3691. // due to out of disk situations or path not found errors
  3692. // on the destination.
  3693. //
  3694. // parameters:
  3695. // pszDest Fully qualified path to destination file (ANSI)
  3696. // nError type of error: ERROR_DISK_FULL or ERROR_PATH_NOT_FOUND
  3697. // dwFileSize amount of space needed for this file if ERROR_DISK_FULL
  3698. //
  3699. // returns:
  3700. // 0 success (destination path has been created)
  3701. // != 0 dos error code including ERROR_CANCELLED
  3702. //
  3703. int CopyMoveRetry(COPY_STATE *pcs, LPCTSTR pszDest, int nError, ULARGE_INTEGER* pulFileSize)
  3704. {
  3705. UINT wFlags;
  3706. int result;
  3707. LPCTSTR wID;
  3708. TCHAR szTemp[MAX_PATH];
  3709. BOOL fFirstRetry = TRUE;
  3710. HRESULT hr;
  3711. if (pcs->fFlags & FOF_NOERRORUI)
  3712. {
  3713. result = ERROR_CANCELLED;
  3714. goto ErrorExit;
  3715. }
  3716. hr = StringCchCopy(szTemp, ARRAYSIZE(szTemp), pszDest);
  3717. if (SUCCEEDED(hr))
  3718. {
  3719. PathRemoveFileSpec(szTemp);
  3720. }
  3721. else
  3722. {
  3723. szTemp[0] = TEXT('\0');
  3724. }
  3725. do
  3726. {
  3727. // until the destination path has been created
  3728. if (nError == ERROR_PATH_NOT_FOUND)
  3729. {
  3730. if (!(pcs->fFlags & FOF_NOCONFIRMMKDIR))
  3731. {
  3732. wID = MAKEINTRESOURCE(IDS_PATHNOTTHERE);
  3733. wFlags = MB_ICONEXCLAMATION | MB_YESNO;
  3734. }
  3735. else
  3736. {
  3737. wID = 0;
  3738. }
  3739. }
  3740. else // ERROR_DISK_FULL
  3741. {
  3742. ULARGE_INTEGER ulDiskSize;
  3743. wFlags = MB_ICONEXCLAMATION | MB_RETRYCANCEL;
  3744. if (pulFileSize && TotalCapacity(pszDest, &ulDiskSize) && pulFileSize->QuadPart > ulDiskSize.QuadPart)
  3745. {
  3746. wID = MAKEINTRESOURCE(IDS_FILEWONTFIT);
  3747. }
  3748. else
  3749. {
  3750. wID = MAKEINTRESOURCE(IDS_DESTFULL);
  3751. }
  3752. }
  3753. if (wID)
  3754. {
  3755. // szTemp will be ignored if there's no %1%s in the string.
  3756. result = ShellMessageBox(HINST_THISDLL, pcs->hwndDlgParent, wID, MAKEINTRESOURCE(IDS_UNDO_FILEOP + pcs->lpfo->wFunc), wFlags, (LPTSTR)szTemp);
  3757. }
  3758. else
  3759. {
  3760. result = IDYES;
  3761. }
  3762. if (result == IDRETRY || result == IDYES)
  3763. {
  3764. TCHAR szDrive[5];
  3765. int idDrive;
  3766. // Allow the disk to be formatted
  3767. // REVIEW, could this be FO_MOVE as well?
  3768. if (FAILED(SHPathPrepareForWrite(((pcs->fFlags & FOF_NOERRORUI) ? NULL : pcs->hwndDlgParent), NULL, szTemp, SHPPFW_DEFAULT)))
  3769. return ERROR_CANCELLED;
  3770. idDrive = PathGetDriveNumber(szTemp);
  3771. if (idDrive != -1)
  3772. PathBuildRoot(szDrive, idDrive);
  3773. else
  3774. szDrive[0] = 0;
  3775. // if we're not copying to the root
  3776. if (lstrcmpi(szTemp, szDrive))
  3777. {
  3778. result = SHCreateDirectory(pcs->hwndDlgParent, szTemp);
  3779. if (result == ERROR_CANCELLED)
  3780. goto ErrorExit;
  3781. if (result == ERROR_ALREADY_EXISTS)
  3782. {
  3783. // if SHPathPrepareForWrite created the directory we shouldn't treat this as an error
  3784. result = 0;
  3785. }
  3786. else if (result && (nError == ERROR_PATH_NOT_FOUND))
  3787. {
  3788. result |= ERRORONDEST;
  3789. // We try twice to allow the recyclebin to be flushed.
  3790. if (fFirstRetry)
  3791. fFirstRetry = FALSE;
  3792. else
  3793. goto ErrorExit;
  3794. }
  3795. }
  3796. else
  3797. {
  3798. result = 0;
  3799. }
  3800. }
  3801. else
  3802. {
  3803. result = ERROR_CANCELLED;
  3804. goto ErrorExit;
  3805. }
  3806. } while (result);
  3807. ErrorExit:
  3808. return result; // success
  3809. }
  3810. BOOL ValidFilenames(LPCTSTR pList)
  3811. {
  3812. if (!*pList)
  3813. return FALSE;
  3814. for (; *pList; pList += lstrlen(pList) + 1)
  3815. {
  3816. if (PathIsInvalid(pList))
  3817. {
  3818. return FALSE;
  3819. }
  3820. }
  3821. return TRUE;
  3822. }
  3823. void AddRenamePairToHDSA(LPCTSTR pszOldPath, LPCTSTR pszNewPath, HDSA* phdsaRenamePairs)
  3824. {
  3825. //
  3826. // Update our collision mapping table
  3827. //
  3828. if (!*phdsaRenamePairs)
  3829. *phdsaRenamePairs = DSA_Create(sizeof(SHNAMEMAPPING), 4);
  3830. if (*phdsaRenamePairs)
  3831. {
  3832. SHNAMEMAPPING rp;
  3833. rp.cchOldPath = lstrlen(pszOldPath);
  3834. rp.cchNewPath = lstrlen(pszNewPath);
  3835. rp.pszOldPath = StrDup(pszOldPath);
  3836. if (rp.pszOldPath)
  3837. {
  3838. rp.pszNewPath = StrDup(pszNewPath);
  3839. if (rp.pszNewPath)
  3840. {
  3841. if (DSA_AppendItem(*phdsaRenamePairs, &rp) == -1)
  3842. {
  3843. LocalFree(rp.pszOldPath);
  3844. LocalFree(rp.pszNewPath);
  3845. }
  3846. }
  3847. else
  3848. {
  3849. LocalFree(rp.pszOldPath);
  3850. }
  3851. }
  3852. }
  3853. }
  3854. BOOL _HandleRename(LPCTSTR pszSource, LPTSTR pszDest, UINT cchDest, FILEOP_FLAGS fFlags, COPY_STATE * pcs)
  3855. {
  3856. TCHAR *pszConflictingName = PathFindFileName(pszSource);
  3857. TCHAR szTemp[MAX_PATH];
  3858. TCHAR szTemplate[MAX_PATH];
  3859. LPTSTR lpszLongPlate;
  3860. PathRemoveFileSpec(pszDest);
  3861. if (LoadString(HINST_THISDLL, IDS_COPYLONGPLATE, szTemplate, ARRAYSIZE(szTemplate)))
  3862. {
  3863. LPTSTR lpsz;
  3864. lpsz = pszConflictingName;
  3865. lpszLongPlate = szTemplate;
  3866. // see if the first part of the template is the same as the name "Copy #"
  3867. while (*lpsz && *lpszLongPlate &&
  3868. *lpsz == *lpszLongPlate &&
  3869. *lpszLongPlate != TEXT('('))
  3870. {
  3871. lpsz++;
  3872. lpszLongPlate++;
  3873. }
  3874. if (*lpsz == TEXT('(') && *lpszLongPlate == TEXT('('))
  3875. {
  3876. // conflicting name already in the template, use it instead
  3877. lpszLongPlate = pszConflictingName;
  3878. }
  3879. else
  3880. {
  3881. // otherwise build our own
  3882. // We need to make sure not to overflow a max buffer.
  3883. int ichFixed = lstrlen(szTemplate) + lstrlen(pszDest) + 5;
  3884. lpszLongPlate = szTemplate;
  3885. if ((ichFixed + lstrlen(pszConflictingName)) <= MAX_PATH)
  3886. {
  3887. StringCchCat(szTemplate, ARRAYSIZE(szTemplate), pszConflictingName);
  3888. }
  3889. else
  3890. {
  3891. // Need to remove some of the name
  3892. LPTSTR pszExt = StrRChr(pszConflictingName, NULL, TEXT('.'));
  3893. if (pszExt)
  3894. {
  3895. // ok to truncate here
  3896. StringCchCat(szTemplate,
  3897. ARRAYSIZE(szTemplate) - lstrlen(pszExt),
  3898. pszConflictingName);
  3899. // use as much of the buffer as possible
  3900. StringCchCat(szTemplate, ARRAYSIZE(szTemplate), pszExt);
  3901. }
  3902. else
  3903. {
  3904. StringCchCat(szTemplate, ARRAYSIZE(szTemplate), pszConflictingName);
  3905. }
  3906. }
  3907. }
  3908. }
  3909. else
  3910. {
  3911. lpszLongPlate = NULL;
  3912. }
  3913. if (PathYetAnotherMakeUniqueName(szTemp, pszDest, pszConflictingName, lpszLongPlate))
  3914. {
  3915. //
  3916. // If there are any other files in the queue which are to
  3917. // be copied into a subtree of pszDest, we must update them
  3918. // as well.
  3919. //
  3920. // Put the new (renamed) target in pszDest.
  3921. HRESULT hr = StringCchCopy(pszDest, cchDest, szTemp);
  3922. if (SUCCEEDED(hr))
  3923. {
  3924. // Rebuild the old dest name and put it in szTemp.
  3925. // I'm going for minimum stack usage here, so I don't want more
  3926. // than one MAX_PATH lying around.
  3927. PathRemoveFileSpec(szTemp);
  3928. if (PathAppend(szTemp, pszConflictingName))
  3929. {
  3930. AddRenamePairToHDSA(szTemp, pszDest, &pcs->dth.hdsaRenamePairs);
  3931. }
  3932. return TRUE;
  3933. }
  3934. }
  3935. return FALSE;
  3936. }
  3937. // test input for "multiple" filespec
  3938. //
  3939. // examples:
  3940. // 1 foo.bar (single non directory file)
  3941. // -1 *.exe (wild card on any of the files)
  3942. // n foo.bar bletch.txt (number of files)
  3943. //
  3944. int CountFiles(LPCTSTR pInput)
  3945. {
  3946. int count;
  3947. for (count = 0; *pInput; pInput += lstrlen(pInput) + 1, count++)
  3948. {
  3949. // wild cards imply multiple files
  3950. if (PathIsWild(pInput))
  3951. return -1;
  3952. }
  3953. return count;
  3954. }
  3955. #define ISDIGIT(c) ((c) >= TEXT('0') && (c) <= TEXT('9'))
  3956. BOOL IsCompressedVolume(LPCTSTR pszSource, DWORD dwAttributes)
  3957. {
  3958. int i;
  3959. LPTSTR pszFileName, pszExtension;
  3960. TCHAR szPath[MAX_PATH];
  3961. HRESULT hr;
  3962. // must be marked system and hidden
  3963. if (!IS_SYSTEM_HIDDEN(dwAttributes))
  3964. return FALSE;
  3965. hr = StringCchCopy(szPath, ARRAYSIZE(szPath), pszSource);
  3966. if (FAILED(hr))
  3967. return FALSE;
  3968. pszFileName = PathFindFileName(szPath);
  3969. pszExtension = PathFindExtension(pszFileName);
  3970. // make sure the extension is a 3 digit number
  3971. if (!*pszExtension)
  3972. return FALSE; // no extension
  3973. for (i = 1; i < 4; i++)
  3974. {
  3975. if (!pszExtension[i] || !ISDIGIT(pszExtension[i]))
  3976. return FALSE;
  3977. }
  3978. // make sure it's null terminated here
  3979. if (pszExtension[4])
  3980. return FALSE;
  3981. // now knock off the extension and make sure the stem matches
  3982. *pszExtension = 0;
  3983. if (lstrcmpi(pszFileName, TEXT("DRVSPACE")) &&
  3984. lstrcmpi(pszFileName, TEXT("DBLSPACE")))
  3985. {
  3986. return FALSE;
  3987. }
  3988. // make sure it's in the root
  3989. PathRemoveFileSpec(szPath);
  3990. if (!PathIsRoot(szPath))
  3991. {
  3992. return FALSE;
  3993. }
  3994. return TRUE; // passed all tests!
  3995. }
  3996. void _DeferMoveDlgItem(HDWP hdwp, HWND hDlg, int nItem, int x, int y)
  3997. {
  3998. RECT rc;
  3999. HWND hwnd = GetDlgItem(hDlg, nItem);
  4000. GetClientRect(hwnd, &rc);
  4001. MapWindowPoints(hwnd, hDlg, (LPPOINT) &rc, 2);
  4002. DeferWindowPos(hdwp, hwnd, 0, rc.left + x, rc.top + y, 0, 0,
  4003. SWP_NOZORDER | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE);
  4004. }
  4005. void _RecalcWindowHeight(HWND hWnd, LPTSTR lpszText)
  4006. {
  4007. HDC hdc = GetDC(hWnd);
  4008. RECT rc;
  4009. HWND hwndText = GetDlgItem(hWnd,IDC_MBC_TEXT);
  4010. HDWP hdwp;
  4011. int iHeightDelta, cx;
  4012. // Get the starting rect of the text area (for the width)
  4013. GetClientRect(hwndText, &rc);
  4014. MapWindowPoints(hwndText, hWnd, (LPPOINT) &rc, 2);
  4015. // Calc how high the static text area needs to be, given the above width
  4016. iHeightDelta = RECTHEIGHT(rc);
  4017. cx = RECTWIDTH(rc);
  4018. DrawText(hdc, lpszText, -1, &rc, DT_CALCRECT | DT_WORDBREAK | DT_LEFT | DT_INTERNAL | DT_EDITCONTROL);
  4019. iHeightDelta = RECTHEIGHT(rc) - iHeightDelta;
  4020. cx = RECTWIDTH(rc) - cx; // Should only change for really long words w/o spaces
  4021. if (cx < 0)
  4022. cx = 0;
  4023. ReleaseDC(hWnd, hdc);
  4024. hdwp = BeginDeferWindowPos(4);
  4025. if (hdwp)
  4026. {
  4027. hdwp = DeferWindowPos(hdwp, hwndText, 0, rc.left, rc.top, RECTWIDTH(rc), RECTHEIGHT(rc), SWP_NOZORDER | SWP_NOACTIVATE);
  4028. if (hdwp)
  4029. {
  4030. _DeferMoveDlgItem(hdwp, hWnd, IDC_MESSAGEBOXCHECKEX, 0, iHeightDelta);
  4031. _DeferMoveDlgItem(hdwp, hWnd, IDYES, cx, iHeightDelta);
  4032. _DeferMoveDlgItem(hdwp, hWnd, IDNO, cx, iHeightDelta);
  4033. EndDeferWindowPos(hdwp);
  4034. }
  4035. }
  4036. GetWindowRect(hWnd, &rc);
  4037. SetWindowPos(hWnd, 0, rc.left - (cx/2), rc.top - (iHeightDelta/2), RECTWIDTH(rc)+cx, RECTHEIGHT(rc)+iHeightDelta, SWP_NOZORDER | SWP_NOACTIVATE);
  4038. return;
  4039. }
  4040. BOOL_PTR CALLBACK RenameMsgBoxCheckDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
  4041. {
  4042. switch (uMsg)
  4043. {
  4044. // we only handle the WM_INITDIALOG so that we can resize the dialog
  4045. // approprately and to set the default button to IDNO
  4046. case WM_INITDIALOG:
  4047. {
  4048. HWND hwndNO = GetDlgItem(hDlg, IDNO);
  4049. _RecalcWindowHeight(hDlg, (LPTSTR)lParam);
  4050. SetDlgItemText(hDlg,IDC_MBC_TEXT,(LPTSTR)lParam);
  4051. SendMessage(hDlg, DM_SETDEFID, IDNO, 0);
  4052. SetFocus(hwndNO);
  4053. return FALSE; // we set the focus, so return false
  4054. }
  4055. }
  4056. // didnt handle this message
  4057. return FALSE;
  4058. }
  4059. int ConfirmRenameOfConnectedItem(COPY_STATE *pcs, WIN32_FIND_DATA *pfd, LPTSTR szSource)
  4060. {
  4061. int result = IDYES; //For non-connected elements, the default is IDYES!
  4062. LPTSTR pszMessage;
  4063. LPTSTR lpConnectedItem, lpConnectOrigin;
  4064. LPTSTR lpStringID;
  4065. //Check if this item being renamed has a connected item.
  4066. if (DTNIsConnectOrigin(pcs->dth.pdtnCurrent))
  4067. {
  4068. //Yes! It has a connected element! Form the strings to create the confirmation dialog!
  4069. //Get the name of the connected element
  4070. lpConnectedItem = PathFindFileName(pcs->dth.pdtnCurrent->pdtnConnected->szName);
  4071. lpConnectOrigin = PathFindFileName(pcs->dth.pFrom);
  4072. // Mark the connected item as dummy as this will never get renamed.
  4073. // (Note that this connected node could be a folder. It is still OK to mark it as
  4074. // dummy because for rename operation, a folder is treated just like a file in
  4075. // DTGotoNextNode()).
  4076. pcs->dth.pdtnCurrent->pdtnConnected->fDummy = TRUE;
  4077. if (pfd && (pfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  4078. lpStringID = MAKEINTRESOURCE(IDS_HTML_FOLDER_RENAME);
  4079. else
  4080. lpStringID = MAKEINTRESOURCE(IDS_HTML_FILE_RENAME);
  4081. //Load the confirmation message and format it!
  4082. pszMessage = ShellConstructMessageString(HINST_THISDLL, lpStringID,
  4083. lpConnectedItem, lpConnectOrigin);
  4084. if (pszMessage)
  4085. {
  4086. //Get the confirmation from the end-user;
  4087. result = SHMessageBoxCheckEx(pcs->hwndDlgParent, HINST_THISDLL,
  4088. MAKEINTRESOURCE(DLG_RENAME_MESSAGEBOXCHECK),
  4089. RenameMsgBoxCheckDlgProc,
  4090. (void *)pszMessage,
  4091. IDYES,
  4092. REG_VAL_GENERAL_RENAMEHTMLFILE);
  4093. //It is possible we get IDCANCEL if the "X" in the caption is clicked to clost
  4094. // the dialog. The following code makes sure we get one of the return code that we want.
  4095. if ((result != IDYES) && (result != IDNO))
  4096. result = IDNO;
  4097. SHFree(pszMessage);
  4098. }
  4099. else
  4100. result = IDNO; //For connected elements, the default is "Don't rename";
  4101. }
  4102. else
  4103. {
  4104. if (DTNIsConnected(pcs->dth.pdtnCurrent))
  4105. result = IDNO; //Connected elements, do not get renamed.
  4106. }
  4107. return result;
  4108. }
  4109. int AllConfirmations(COPY_STATE *pcs, WIN32_FIND_DATA *pfd, UINT oper, UINT wFunc,
  4110. LPTSTR szSource, LPTSTR szDest, BOOL bTimeToUpdate,
  4111. WIN32_FIND_DATA *pfdDest, LPINT lpret)
  4112. {
  4113. int result = IDYES;
  4114. LPTSTR p;
  4115. LPTSTR pszStatusDest = NULL;
  4116. CONFIRM_FLAG fConfirm;
  4117. WIN32_FIND_DATA *pfdUse1 = NULL;
  4118. WIN32_FIND_DATA *pfdUse2;
  4119. BOOL fSetProgress = FALSE;
  4120. BOOL fShowConfirm = FALSE;
  4121. switch (oper | wFunc)
  4122. {
  4123. case OPER_ENTERDIR | FO_MOVE:
  4124. if (PathIsSameRoot(szSource, szDest))
  4125. {
  4126. fConfirm = CONFIRM_MOVE_FOLDER;
  4127. pfdUse1 = pfd;
  4128. pfdUse2 = pfdDest;
  4129. fShowConfirm = TRUE;
  4130. }
  4131. break;
  4132. case OPER_ENTERDIR | FO_DELETE:
  4133. // Confirm removal of directory on this pass. The directories
  4134. // are actually removed on the OPER_LEAVEDIR pass
  4135. if (DTNIsRootNode(pcs->dth.pdtnCurrent))
  4136. fSetProgress = TRUE;
  4137. if (!PathIsRoot(szSource))
  4138. {
  4139. fShowConfirm = TRUE;
  4140. pfdUse2 = pfd;
  4141. fConfirm = CONFIRM_DELETE_FOLDER;
  4142. szDest = NULL;
  4143. }
  4144. break;
  4145. case OPER_DOFILE | FO_RENAME:
  4146. // pszStatusDest = szDest;
  4147. fSetProgress = TRUE;
  4148. p = PathFindFileName(szSource);
  4149. if (!IntlStrEqNI(szSource, szDest, (int)(p - szSource)))
  4150. {
  4151. result = DE_DIFFDIR;
  4152. }
  4153. else
  4154. {
  4155. if (pfd && (pfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  4156. fConfirm = CONFIRM_RENAME_FOLDER;
  4157. else
  4158. fConfirm = CONFIRM_RENAME_FILE;
  4159. if (PathIsRoot(szSource) || (PathIsRoot(szDest)))
  4160. {
  4161. result = DE_ROOTDIR | ERRORONDEST;
  4162. }
  4163. else
  4164. {
  4165. // We need to bring up a special confirmation dialog if this file/folder being
  4166. // renamed has a connected element (if "foo.htm" or "foo files" is renamed that
  4167. // will break the links).
  4168. result = ConfirmRenameOfConnectedItem(pcs, pfd, szSource);
  4169. if (result != IDNO)
  4170. {
  4171. fShowConfirm = TRUE;
  4172. pfdUse2 = pfdDest;
  4173. pfdUse1 = pfd;
  4174. }
  4175. }
  4176. }
  4177. break;
  4178. case OPER_DOFILE | FO_MOVE:
  4179. fSetProgress = TRUE;
  4180. pszStatusDest = szDest;
  4181. if (PathIsRoot(szSource))
  4182. {
  4183. result = DE_ROOTDIR;
  4184. }
  4185. else if (PathIsRoot(szDest))
  4186. {
  4187. result = DE_ROOTDIR | ERRORONDEST;
  4188. }
  4189. else
  4190. {
  4191. fConfirm = CONFIRM_MOVE_FILE;
  4192. fShowConfirm = TRUE;
  4193. pfdUse2 = pfdDest;
  4194. pfdUse1 = pfd;
  4195. }
  4196. break;
  4197. case OPER_DOFILE | FO_DELETE:
  4198. fSetProgress = TRUE;
  4199. if (IsCompressedVolume(szSource, pfd->dwFileAttributes))
  4200. {
  4201. CopyError(pcs, szSource, szDest, DE_COMPRESSEDVOLUME, wFunc, oper);
  4202. result = IDNO;
  4203. }
  4204. else
  4205. {
  4206. fShowConfirm = TRUE;
  4207. szDest = NULL;
  4208. pfdUse2 = pfd;
  4209. fConfirm = CONFIRM_DELETE_FILE;
  4210. }
  4211. break;
  4212. }
  4213. if (fShowConfirm)
  4214. {
  4215. result = CachedConfirmFileOp(pcs->hwndDlgParent, pcs, &pcs->cd, pcs->nSourceFiles, !DTNIsRootNode(pcs->dth.pdtnCurrent), fConfirm,
  4216. szSource, pfdUse1, szDest, pfdUse2, NULL);
  4217. }
  4218. if (oper == OPER_DOFILE || oper == OPER_ENTERDIR)
  4219. {
  4220. if ((wFunc == FO_MOVE) || (wFunc == FO_COPY))
  4221. {
  4222. if ((result != IDNO) && (result != IDCANCEL))
  4223. {
  4224. LPTSTR pszDataToBeLost;
  4225. WCHAR wszDestDir[MAX_PATH];
  4226. BOOL bNoStreamLossThisDir = FALSE;
  4227. HRESULT hr = StringCchCopy(wszDestDir, ARRAYSIZE(wszDestDir), szDest);
  4228. if (SUCCEEDED(hr))
  4229. {
  4230. PathRemoveFileSpec(wszDestDir);
  4231. // Files with multiple streams will suffer stream loss on a downlevel
  4232. // copy, but CopyFile special-cases native structure storage.
  4233. pszDataToBeLost = GetDownlevelCopyDataLossText(szSource, wszDestDir, (oper == OPER_ENTERDIR), &bNoStreamLossThisDir);
  4234. if (pszDataToBeLost)
  4235. {
  4236. fConfirm = CONFIRM_STREAMLOSS;
  4237. pfdUse2 = pfd;
  4238. result = CachedConfirmFileOp(pcs->hwndDlgParent, pcs, &pcs->cd, pcs->nSourceFiles, !DTNIsRootNode(pcs->dth.pdtnCurrent), fConfirm,
  4239. szSource, pfdUse1, szDest, pfdUse2, pszDataToBeLost);
  4240. LocalFree(pszDataToBeLost);
  4241. }
  4242. else if (bNoStreamLossThisDir)
  4243. {
  4244. // pcs->bStreamLossPossible = FALSE;
  4245. }
  4246. }
  4247. }
  4248. }
  4249. }
  4250. // We only really care about OPER_ENTERDIR when deleting and
  4251. // OPER_DOFILE when renaming, but I guess the hook will figure it out
  4252. if ((result == IDYES) &&
  4253. ISDIRFINDDATA(*pfd) &&
  4254. (oper==OPER_ENTERDIR || oper==OPER_DOFILE))
  4255. {
  4256. result = CallFileCopyHooks(pcs->hwndDlgParent, wFunc, pcs->fFlags,
  4257. szSource, pfd->dwFileAttributes,
  4258. szDest, pfdDest->dwFileAttributes);
  4259. }
  4260. if ((result != IDCANCEL) && (result != IDNO) && fSetProgress && bTimeToUpdate)
  4261. SetProgressText(pcs, szSource, pszStatusDest);
  4262. return result;
  4263. }
  4264. // return TRUE if they're the same file
  4265. // assumes that given two file specs, the short name will
  4266. // be identical (except case)
  4267. BOOL SameFile(LPTSTR pszSource, LPTSTR pszDest)
  4268. {
  4269. TCHAR szShortSrc[MAX_PATH];
  4270. if (GetShortPathName(pszSource, szShortSrc, ARRAYSIZE(szShortSrc)))
  4271. {
  4272. TCHAR szShortDest[MAX_PATH];
  4273. if (GetShortPathName(pszDest, szShortDest, ARRAYSIZE(szShortDest)))
  4274. return !lstrcmpi(szShortSrc, szShortDest);
  4275. }
  4276. return FALSE;
  4277. }
  4278. // make sure we aren't operating on the current dir to avoid
  4279. // ERROR_CURRENT_DIRECTORY kinda errors
  4280. void AvoidCurrentDirectory(LPCTSTR p)
  4281. {
  4282. TCHAR szTemp[MAX_PATH];
  4283. GetCurrentDirectory(ARRAYSIZE(szTemp), szTemp);
  4284. if (lstrcmpi(szTemp, p) == 0)
  4285. {
  4286. DebugMsg(TF_DEBUGCOPY, TEXT("operating on current dir(%s), cd .."), p);
  4287. PathRemoveFileSpec(szTemp);
  4288. SetCurrentDirectory(szTemp);
  4289. }
  4290. }
  4291. // this resolves short/long name collisions such as moving
  4292. // "NewFolde" onto a dir with "New Folder" whose short name is "NEWFOLDE"
  4293. //
  4294. // we resolve this by renaming "New Folder" to a unique short name (like TMP1)
  4295. //
  4296. // making a temporary file of name "NEWFOLDE"
  4297. //
  4298. // renaming TMP1 back to "New Folder" (at which point it will have a new short
  4299. // name like "NEWFOL~1"
  4300. // PERF: it'd be faster if we didn't make the temporary file, but that
  4301. // would require that we rename the file back to the long name at the
  4302. // end of the operation.. which would mean we'd need to queue them all up..
  4303. // too much for right now.
  4304. BOOL ResolveShortNameCollisions(LPCTSTR lpszDest, WIN32_FIND_DATA *pfd)
  4305. {
  4306. BOOL fRet = FALSE;
  4307. // first verify that we're in the name collision.
  4308. // we are if lpszDest is the same as the pfd's short name which is different
  4309. // than it's long name.
  4310. if (!lstrcmpi(PathFindFileName(lpszDest), pfd->cAlternateFileName) &&
  4311. lstrcmpi(pfd->cAlternateFileName, pfd->cFileName))
  4312. {
  4313. // yes... do the renaming
  4314. TCHAR szTemp[MAX_PATH];
  4315. TCHAR szLongName[MAX_PATH];
  4316. HRESULT hr = StringCchCopy(szTemp, ARRAYSIZE(szTemp), lpszDest);
  4317. if (SUCCEEDED(hr))
  4318. {
  4319. PathRemoveFileSpec(szTemp);
  4320. // build the original long name
  4321. hr = StringCchCopy(szLongName, ARRAYSIZE(szLongName), szTemp);
  4322. if (SUCCEEDED(hr))
  4323. {
  4324. if (PathAppend(szLongName, pfd->cFileName))
  4325. {
  4326. GetTempFileName(szTemp, c_szNULL, 1, szTemp);
  4327. DebugMsg(TF_DEBUGCOPY, TEXT("Got %s as a temp file"), szTemp);
  4328. // rename "New Folder" to "tmp1"
  4329. if (Win32MoveFile(szLongName, szTemp, ISDIRFINDDATA(*pfd)))
  4330. {
  4331. // make a temporary "NewFolde"
  4332. fRet = CreateWriteCloseFile(NULL, lpszDest, NULL, 0);
  4333. ASSERT(fRet);
  4334. // move it back...
  4335. if (!Win32MoveFile(szTemp, szLongName, ISDIRFINDDATA(*pfd)))
  4336. {
  4337. //
  4338. // Can't move it back, so delete the empty dir and then
  4339. // move it back. Return FALSE to denote failure.
  4340. //
  4341. DeleteFile(lpszDest);
  4342. Win32MoveFile(szTemp, szLongName, ISDIRFINDDATA(*pfd));
  4343. fRet = FALSE;
  4344. }
  4345. else
  4346. {
  4347. // send this out because we could have confused views
  4348. // with this swapping files around... by the time they get the first
  4349. // move file notification, the temp file is likely gone
  4350. // so they could blow that off.. which would mess up the rest of this.
  4351. SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, szLongName, NULL);
  4352. //
  4353. // We've now created an empty dir entry of this name type.
  4354. //
  4355. Win32DeleteFile(lpszDest);
  4356. }
  4357. DebugMsg(TF_DEBUGCOPY, TEXT("ResolveShortNameCollision: %s = original, %s = destination,\n %s = temp file, %d = return"), szLongName, lpszDest, szTemp, fRet);
  4358. }
  4359. }
  4360. }
  4361. }
  4362. }
  4363. return fRet;
  4364. }
  4365. typedef struct { LPTSTR szFilename; int iResult; } RENAMEEXEMPTIONINFO;
  4366. RENAMEEXEMPTIONINFO g_rgExemptions[] = {
  4367. { TEXT("thumbs.db"), IDYES }
  4368. };
  4369. // return values.
  4370. //
  4371. // IDCANCEL = bail out of all operations
  4372. // IDNO = skip this one
  4373. // IDRETRY = try operation again
  4374. // IDUNKNOWN = this (collision) is not the problem
  4375. #define IDUNKNOWN IDOK
  4376. int CheckForRenameCollision(COPY_STATE *pcs, UINT oper, LPTSTR pszSource, LPTSTR pszDest, UINT cchDest,
  4377. WIN32_FIND_DATA *pfdDest, WIN32_FIND_DATA* pfd)
  4378. {
  4379. int iRet = IDUNKNOWN;
  4380. ASSERT((pcs->lpfo->wFunc != FO_DELETE) && (oper != OPER_LEAVEDIR));
  4381. /* Check to see if we are overwriting an existing file or
  4382. directory. If so, better confirm */
  4383. if ((oper == OPER_DOFILE) ||
  4384. ((oper == OPER_ENTERDIR) && (pcs->fFlags & FOF_RENAMEONCOLLISION)))
  4385. {
  4386. HANDLE hfindT;
  4387. // REVIEW this slows things down checking for the dest file
  4388. if ((hfindT = FindFirstFile(pszDest, pfdDest)) != INVALID_HANDLE_VALUE)
  4389. {
  4390. FindClose(hfindT);
  4391. iRet = IDCANCEL;
  4392. if (pcs->lpfo->wFunc != FO_RENAME || !SameFile(pszSource, pszDest))
  4393. {
  4394. if (!ResolveShortNameCollisions(pszDest, pfdDest))
  4395. {
  4396. if (pcs->fFlags & FOF_RENAMEONCOLLISION)
  4397. {
  4398. // The client wants us to generate a new name for the
  4399. // source file to avoid a collision at the destination
  4400. // dir. Must also update the current queue and the
  4401. // copy root.
  4402. _HandleRename(pszSource, pszDest, cchDest, pcs->fFlags, pcs);
  4403. iRet = IDRETRY;
  4404. }
  4405. else
  4406. {
  4407. int result = IDRETRY;
  4408. if (pcs->lpfo->wFunc == FO_RENAME)
  4409. {
  4410. return ERROR_ALREADY_EXISTS;
  4411. }
  4412. // Is this a super-hidden file we don't want to prompt the
  4413. // user regarding?
  4414. if (IS_SYSTEM_HIDDEN(pfd->dwFileAttributes) &&
  4415. IS_SYSTEM_HIDDEN(pfdDest->dwFileAttributes) &&
  4416. !ShowSuperHidden())
  4417. {
  4418. int cExempt = 0;
  4419. for (; cExempt < ARRAYSIZE(g_rgExemptions); cExempt++)
  4420. {
  4421. if (0 == StrCmpI(g_rgExemptions[cExempt].szFilename, PathFindFileName(pszSource)))
  4422. {
  4423. result = g_rgExemptions[cExempt].iResult;
  4424. break;
  4425. }
  4426. }
  4427. }
  4428. // REVIEW, if the destination file we are copying over
  4429. // is actually a directory we are doomed. we can
  4430. // try to remove the dir but that will fail if there
  4431. // are files there. we probably need a special error message
  4432. // for this case.
  4433. if (result == IDRETRY)
  4434. {
  4435. result = CachedConfirmFileOp(pcs->hwndDlgParent, pcs,
  4436. &pcs->cd, pcs->nSourceFiles,
  4437. !DTNIsRootNode(pcs->dth.pdtnCurrent),
  4438. CONFIRM_REPLACE_FILE,
  4439. pszSource, pfd, pszDest, pfdDest, NULL);
  4440. }
  4441. switch (result)
  4442. {
  4443. case IDYES:
  4444. if ((pcs->lpfo->wFunc == FO_MOVE) && (PathIsSameRoot(pszSource, pszDest)))
  4445. {
  4446. int ret;
  4447. // For FO_MOVE we need to delete the
  4448. // destination first. Do that now.
  4449. // FEATURE this replace options should be undable
  4450. ret = Win32DeleteFile(pszDest) ? 0 : GetLastError();
  4451. if (ret)
  4452. {
  4453. ret |= ERRORONDEST;
  4454. result = ret;
  4455. }
  4456. }
  4457. if (pcs->lpua)
  4458. FOUndo_Release(pcs->lpua);
  4459. iRet = IDRETRY;
  4460. break;
  4461. case IDNO:
  4462. case IDCANCEL:
  4463. pcs->lpfo->fAnyOperationsAborted = TRUE;
  4464. iRet = result;
  4465. break;
  4466. default:
  4467. iRet = result;
  4468. break;
  4469. }
  4470. }
  4471. }
  4472. else
  4473. {
  4474. iRet = IDRETRY;
  4475. }
  4476. }
  4477. }
  4478. }
  4479. return iRet;
  4480. }
  4481. int LeaveDir_Delete(COPY_STATE *pcs, LPTSTR pszSource)
  4482. {
  4483. int ret;
  4484. if (PathIsRoot(pszSource))
  4485. return 0;
  4486. AvoidCurrentDirectory(pszSource);
  4487. // We already confirmed the delete at MKDIR time, so attempt
  4488. // to delete the directory
  4489. ret = Win32RemoveDirectory(pszSource) ? 0 : GetLastError();
  4490. if (!ret)
  4491. {
  4492. FOUndo_FileReallyDeleted(pszSource);
  4493. }
  4494. return ret;
  4495. }
  4496. int EnterDir_Copy(COPY_STATE* pcs, LPTSTR pszSource, LPTSTR pszDest, UINT cchDest,
  4497. WIN32_FIND_DATA *pfd, WIN32_FIND_DATA * pfdDest, BOOL fRenameTried, BOOL fLostEncryptOk)
  4498. {
  4499. int ret;
  4500. int result;
  4501. BOOL fSetDestAttributes = FALSE;
  4502. DWORD dwDesiredAttributes = pfd->dwFileAttributes;
  4503. BOOL fWithoutTemplate = FALSE;
  4504. // Whenever we enter a directory, we need to reset the bStreamLossPossible flag,
  4505. // since we could have stepped out from an NTFS->NTFS to NTFS->FAT scenario via
  4506. // a junction point
  4507. pcs->bStreamLossPossible = TRUE;
  4508. TryCreateAgain:
  4509. // SHMoveFile restricts the based on path length. To be consistent, we make the same
  4510. // restricton on Copy directory also.
  4511. if (IsDirPathTooLongForCreateDir(pszDest))
  4512. {
  4513. ret = ERROR_FILENAME_EXCED_RANGE;
  4514. }
  4515. else
  4516. {
  4517. if (fLostEncryptOk)
  4518. {
  4519. dwDesiredAttributes &= ~FILE_ATTRIBUTE_ENCRYPTED; // Pretend its not encrypted
  4520. fSetDestAttributes = TRUE;
  4521. fWithoutTemplate = TRUE;
  4522. }
  4523. if (pfd->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
  4524. {
  4525. if (!(pcs->fFlags & FOF_NORECURSEREPARSE))
  4526. {
  4527. dwDesiredAttributes &= ~FILE_ATTRIBUTE_REPARSE_POINT; // Pretend like its just a folder
  4528. fSetDestAttributes = TRUE;
  4529. fWithoutTemplate = TRUE;
  4530. }
  4531. }
  4532. if (fWithoutTemplate)
  4533. {
  4534. ret = (CreateDirectory(pszDest, NULL) ? 0 : GetLastError());
  4535. // Since we didn't call CreateDirectoryEx, we need to manually
  4536. // propogate the attributes to the dest directory.
  4537. fSetDestAttributes = TRUE;
  4538. }
  4539. else
  4540. {
  4541. ret = (CreateDirectoryEx(pszSource, pszDest, NULL) ? 0 : GetLastError());
  4542. }
  4543. if (ret == ERROR_SUCCESS)
  4544. {
  4545. SHChangeNotify(SHCNE_MKDIR, SHCNF_PATH, pszDest, NULL);
  4546. }
  4547. }
  4548. switch (ret)
  4549. {
  4550. case 0: // successful folder creation (or it already exists)
  4551. // propogate the attributes (if there are any)
  4552. if (pcs->fFromCDRom)
  4553. {
  4554. // Don't propogate read-only from CDRoms
  4555. dwDesiredAttributes &= ~FILE_ATTRIBUTE_READONLY;
  4556. fSetDestAttributes = TRUE;
  4557. }
  4558. if (fSetDestAttributes)
  4559. {
  4560. // Avoid setting FILE_ATTRIBUTE_DIRECTORY, since its
  4561. // already a directory, and is less error prone.
  4562. SetFileAttributes(pszDest, dwDesiredAttributes);
  4563. }
  4564. // we should set the security ACLs here on NT
  4565. // we ignore any kind of failure though, is that OK?
  4566. //
  4567. CopyFileSecurity(pszSource, pszDest);
  4568. // add to the undo atom
  4569. if (pcs->lpua)
  4570. {
  4571. if (DTNIsRootNode(pcs->dth.pdtnCurrent) && !DTNIsConnected(pcs->dth.pdtnCurrent))
  4572. FOUndo_AddInfo(pcs->lpua, pszSource, pszDest, 0);
  4573. }
  4574. break;
  4575. case ERROR_ALREADY_EXISTS:
  4576. case ERROR_DISK_FULL:
  4577. case ERROR_ACCESS_DENIED:
  4578. case ERROR_INVALID_NAME:
  4579. {
  4580. DWORD dwFileAttributes;
  4581. if (!fRenameTried)
  4582. {
  4583. int result = CheckForRenameCollision(pcs, OPER_ENTERDIR, pszSource, pszDest, cchDest, pfdDest, pfd);
  4584. switch (result)
  4585. {
  4586. case IDUNKNOWN:
  4587. break;
  4588. case IDRETRY:
  4589. return EnterDir_Copy(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, TRUE, fLostEncryptOk);
  4590. case IDCANCEL:
  4591. pcs->bAbort = TRUE;
  4592. return result;
  4593. case IDNO:
  4594. return result;
  4595. default:
  4596. return result;
  4597. }
  4598. }
  4599. dwFileAttributes = GetFileAttributes(pszDest);
  4600. if (dwFileAttributes == (DWORD)-1)
  4601. {
  4602. // The dir does not exist, so it looks like a problem
  4603. // with a read-only drive or disk full
  4604. if (ret == ERROR_DISK_FULL &&
  4605. IsRemovableDrive(DRIVEID(pszDest)) &&
  4606. !PathIsSameRoot(pszDest, pszSource))
  4607. {
  4608. ret = CopyMoveRetry(pcs, pszDest, ERROR_DISK_FULL, NULL);
  4609. if (!ret)
  4610. {
  4611. return EnterDir_Copy(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, fRenameTried, fLostEncryptOk);
  4612. }
  4613. else
  4614. {
  4615. pcs->bAbort = TRUE;
  4616. return ret;
  4617. }
  4618. }
  4619. // Maybe its an encrypted folder thats losing its encryption?
  4620. // If fLostEncryptOk is TRUE then we are already trying to recover from an Encrypted Folder, so
  4621. // don't recursively try again
  4622. if ((fLostEncryptOk == FALSE) && (pfd->dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED))
  4623. {
  4624. int result;
  4625. result = CachedConfirmFileOp(pcs->hwndDlgParent, pcs,
  4626. &pcs->cd, pcs->nSourceFiles,
  4627. FALSE,
  4628. CONFIRM_LOST_ENCRYPT_FOLDER,
  4629. pszSource, pfd, pszDest, NULL, NULL);
  4630. switch (result)
  4631. {
  4632. case IDYES:
  4633. fLostEncryptOk = TRUE;
  4634. return EnterDir_Copy(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, fRenameTried, fLostEncryptOk);
  4635. case IDNO:
  4636. case IDCANCEL:
  4637. pcs->bAbort = TRUE;
  4638. ret = result;
  4639. break;
  4640. default:
  4641. ret = result;
  4642. break;
  4643. }
  4644. return ret;
  4645. }
  4646. CopyError(pcs, pszSource, pszDest, ERROR_ACCESS_DENIED | ERRORONDEST, FO_COPY, OPER_DOFILE);
  4647. pcs->bAbort = TRUE;
  4648. return ret;
  4649. }
  4650. if (!(dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  4651. {
  4652. // A file with this name already exists
  4653. CopyError(pcs, pszSource, pszDest, DE_FLDDESTISFILE | ERRORONDEST, FO_COPY, OPER_DOFILE);
  4654. pcs->bAbort = TRUE;
  4655. return ret;
  4656. }
  4657. result = CachedConfirmFileOp(pcs->hwndDlgParent, pcs, &pcs->cd,
  4658. pcs->nSourceFiles,
  4659. !DTNIsRootNode(pcs->dth.pdtnCurrent),
  4660. CONFIRM_REPLACE_FOLDER,
  4661. pszSource, pfd, pszDest, pfdDest, NULL);
  4662. switch (result)
  4663. {
  4664. case IDYES:
  4665. ret = 0; // convert to no error
  4666. pcs->fMerge = TRUE;
  4667. if (pcs->lpua)
  4668. FOUndo_Release(pcs->lpua);
  4669. break;
  4670. case IDNO:
  4671. DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder
  4672. pcs->lpfo->fAnyOperationsAborted = TRUE;
  4673. ret = IDNO; // Don't put up error message on this one...
  4674. // Since the end-user cancelled the copy operation on this folder, we can cancel the
  4675. // copy operation on the corresponding connected file too!
  4676. if (DTNIsConnectOrigin(pcs->dth.pdtnCurrent))
  4677. pcs->dth.pdtnCurrent->pdtnConnected->fDummy = TRUE;
  4678. break;
  4679. case IDCANCEL:
  4680. pcs->lpfo->fAnyOperationsAborted = TRUE;
  4681. pcs->bAbort = TRUE;
  4682. // Since the end-user cancelled the copy operation on this folder, we can cancel the
  4683. // copy operation on the corresponding connected file too!
  4684. if (DTNIsConnectOrigin(pcs->dth.pdtnCurrent))
  4685. pcs->dth.pdtnCurrent->pdtnConnected->fDummy = TRUE;
  4686. break;
  4687. default:
  4688. result = ret;
  4689. break;
  4690. }
  4691. break;
  4692. }
  4693. case ERROR_CANCELLED:
  4694. pcs->bAbort = TRUE;
  4695. break;
  4696. case ERROR_FILENAME_EXCED_RANGE:
  4697. DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder
  4698. break;
  4699. case ERROR_EAS_NOT_SUPPORTED:
  4700. case ERROR_NOT_SUPPORTED:
  4701. // Directories with EAs are identified here.
  4702. if (!fWithoutTemplate)
  4703. {
  4704. fWithoutTemplate = TRUE;
  4705. goto TryCreateAgain;
  4706. }
  4707. // fall through
  4708. default: // ret != 0 (dos error code)
  4709. ret |= ERRORONDEST;
  4710. break;
  4711. }
  4712. return ret;
  4713. }
  4714. int EnterDir_Move(COPY_STATE* pcs, LPTSTR pszSource, LPTSTR pszDest, UINT cchDest,
  4715. WIN32_FIND_DATA *pfd, WIN32_FIND_DATA * pfdDest, BOOL fRenameTried)
  4716. {
  4717. int ret;
  4718. // Whenever we enter a directory, we need to reset the bStreamLossPossible flag,
  4719. // since we could have stepped out from an NTFS->NTFS to NTFS->FAT scenario via
  4720. // a junction point
  4721. pcs->bStreamLossPossible = TRUE;
  4722. // if these are in the same drive, try using MoveFile on it.
  4723. // if that fails then fail through to the copy
  4724. if (PathIsSameRoot(pszSource, pszDest))
  4725. {
  4726. AvoidCurrentDirectory(pszSource);
  4727. ret = Win32MoveFile(pszSource, pszDest, TRUE) ? 0 : GetLastError();
  4728. switch (ret)
  4729. {
  4730. case 0:
  4731. DebugMsg(TF_DEBUGCOPY, TEXT("Move Folder worked!"));
  4732. DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder
  4733. // add to the undo atom
  4734. if (pcs->lpua && DTNIsRootNode(pcs->dth.pdtnCurrent) && !DTNIsConnected(pcs->dth.pdtnCurrent))
  4735. FOUndo_AddInfo(pcs->lpua, pszSource, pszDest, 0);
  4736. if (!SHRestricted(REST_NOENCRYPTONMOVE) &&
  4737. !(pfd->dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED))
  4738. {
  4739. TCHAR szDestDir[MAX_PATH];
  4740. DWORD dwAttribs;
  4741. if (SUCCEEDED(StringCchCopy(szDestDir, ARRAYSIZE(szDestDir), pszDest)))
  4742. {
  4743. PathRemoveFileSpec(szDestDir);
  4744. dwAttribs = GetFileAttributes(szDestDir);
  4745. if ((dwAttribs != -1) && (dwAttribs & FILE_ATTRIBUTE_ENCRYPTED))
  4746. {
  4747. // Encrypt the directory by pretending we are the
  4748. // property sheet properties->Advanced. Fill in the fake
  4749. // information and call the helper.
  4750. FILEPROPSHEETPAGE fpsp;
  4751. FOLDERCONTENTSINFO fci;
  4752. fci.fIsCompressionAvailable = FALSE;
  4753. fci.fMultipleFiles = TRUE;
  4754. ZeroMemory(&fpsp, SIZEOF(fpsp));
  4755. fpsp.hDlg = GetWindow(pcs->hwndDlgParent, GW_CHILD);
  4756. fpsp.fRecursive = TRUE;
  4757. fpsp.fIsDirectory = TRUE;
  4758. fpsp.pfci = &fci;
  4759. // As long as asInitial.* == asCurrent.* it won't be changed
  4760. fpsp.asInitial.fReadOnly = BST_INDETERMINATE;
  4761. fpsp.asInitial.fHidden = BST_INDETERMINATE;
  4762. fpsp.asInitial.fIndex = BST_INDETERMINATE;
  4763. fpsp.asInitial.fArchive = BST_INDETERMINATE;
  4764. fpsp.asInitial.fCompress = BST_INDETERMINATE;
  4765. fpsp.asInitial.fEncrypt = BST_UNCHECKED; // Not encrypted yet
  4766. fpsp.asInitial.fRecordingEnabled = BST_INDETERMINATE;
  4767. fpsp.asCurrent.fReadOnly = BST_INDETERMINATE;
  4768. fpsp.asCurrent.fHidden = BST_INDETERMINATE;
  4769. fpsp.asCurrent.fIndex = BST_INDETERMINATE;
  4770. fpsp.asCurrent.fArchive = BST_INDETERMINATE;
  4771. fpsp.asCurrent.fCompress = BST_INDETERMINATE;
  4772. fpsp.asCurrent.fEncrypt = BST_CHECKED; // Now encrypt
  4773. fpsp.asCurrent.fRecordingEnabled = BST_INDETERMINATE;
  4774. ApplyRecursiveFolderAttribs(pszDest, &fpsp);
  4775. }
  4776. }
  4777. }
  4778. // Win32MoveFile on a single-volume leaves the original ACL
  4779. // intact. If necessary, pick up perms from the destination.
  4780. if (pcs->fFlags & FOF_NOCOPYSECURITYATTRIBS)
  4781. {
  4782. ResetFileSecurity(pszDest);
  4783. }
  4784. return 0;
  4785. case ERROR_PATH_NOT_FOUND:
  4786. ret = CopyMoveRetry(pcs, pszDest, ret, NULL);
  4787. if (!ret)
  4788. return EnterDir_Move(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, fRenameTried);
  4789. return ret;
  4790. case ERROR_ALREADY_EXISTS:
  4791. case ERROR_FILE_EXISTS:
  4792. if (!fRenameTried)
  4793. {
  4794. int result = CheckForRenameCollision(pcs, OPER_ENTERDIR, pszSource, pszDest, cchDest, pfdDest, pfd);
  4795. switch (result)
  4796. {
  4797. case IDUNKNOWN:
  4798. break;
  4799. case IDRETRY:
  4800. return EnterDir_Move(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, TRUE);
  4801. case IDCANCEL:
  4802. pcs->bAbort = TRUE;
  4803. return result;
  4804. case IDNO:
  4805. return result;
  4806. default:
  4807. return result;
  4808. }
  4809. }
  4810. break;
  4811. case ERROR_FILENAME_EXCED_RANGE:
  4812. case ERROR_ONLY_IF_CONNECTED:
  4813. DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder
  4814. return ret;
  4815. }
  4816. }
  4817. // we're going to recurse in.... if we've not enumerated the children for
  4818. // this folder, set it for delayed enumeration now.
  4819. if (!pcs->dth.pdtnCurrent->pdtnChild)
  4820. {
  4821. pcs->dth.pdtnCurrent->pdtnChild = DTN_DELAYED;
  4822. }
  4823. if (DTNIsConnected(pcs->dth.pdtnCurrent) && !PathFileExists(pszSource))
  4824. {
  4825. // This can happen if the end-user moved "foo.htm" AND "foo files" together.
  4826. // As a result the connected element "foo files" has already been moved.
  4827. DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder
  4828. return(0); //No error! This connected element seems to have been moved.
  4829. }
  4830. return EnterDir_Copy(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, FALSE, FALSE);
  4831. }
  4832. int EnterDir_Delete(COPY_STATE * pcs, WIN32_FIND_DATA *pfdSrc, LPTSTR pszSource, UINT cchSource, HDPA *phdpaDeletedFiles)
  4833. {
  4834. int iRet = 0;
  4835. if (!DTNIsRootNode(pcs->dth.pdtnCurrent))
  4836. {
  4837. // we are not at a root node... when doing a delete this can only mean
  4838. // that we are really nuking the folder. we dont need to enum children
  4839. // because we already did a non-lazy enum at the root node.
  4840. return iRet;
  4841. }
  4842. else if (!pcs->lpua)
  4843. {
  4844. NukeFolder:
  4845. // we are at a root node and we have no undo atom, this means that we
  4846. // really want to nuke this whole dir, so enum the children
  4847. DTForceEnumChildren(&pcs->dth);
  4848. // do a non-layz enum of the children to prevent the progress
  4849. // bar from going back and forth as we recurse down into any subdirs.
  4850. DTEnumChildren(&pcs->dth, pcs, TRUE, DTF_FILES_AND_FOLDERS);
  4851. return iRet;
  4852. }
  4853. if (DeleteFileBB(pszSource, cchSource, &iRet, pcs, TRUE, pfdSrc, phdpaDeletedFiles))
  4854. {
  4855. DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder
  4856. }
  4857. else
  4858. {
  4859. // DeleteFileBB failed, check iRet to find out why
  4860. switch (iRet)
  4861. {
  4862. case BBDELETE_PATH_TOO_LONG:
  4863. case BBDELETE_SIZE_TOO_BIG:
  4864. case BBDELETE_NUKE_OFFLINE:
  4865. {
  4866. // This is the case where the folder is too big to fit in the Recycle Bin or the folder
  4867. // is offline. We have no choice but to really nuke it, but we warn the user first since
  4868. // they may have thought that it was being sent to the recycle bin.
  4869. int result = CachedConfirmFileOp(pcs->hwndDlgParent,
  4870. pcs,
  4871. &pcs->cd,
  4872. pcs->nSourceFiles,
  4873. FALSE,
  4874. (iRet == BBDELETE_SIZE_TOO_BIG) ?
  4875. CONFIRM_WONT_RECYCLE_FOLDER :
  4876. ((iRet == BBDELETE_NUKE_OFFLINE) ?
  4877. CONFIRM_WONT_RECYCLE_OFFLINE :
  4878. CONFIRM_PATH_TOO_LONG),
  4879. pszSource,
  4880. pfdSrc,
  4881. NULL,
  4882. NULL,
  4883. NULL);
  4884. switch (result)
  4885. {
  4886. case IDNO:
  4887. // user said "please dont really nuke the file"
  4888. DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder
  4889. pcs->lpfo->fAnyOperationsAborted = TRUE;
  4890. iRet = IDNO; // Don't put up error message for this case
  4891. //Because the Delete on this FOLDER is aborted, we can cancel the "Delete"
  4892. // on the corresponding FILE too!
  4893. if (DTNIsConnectOrigin(pcs->dth.pdtnCurrent))
  4894. {
  4895. pcs->dth.pdtnCurrent->pdtnConnected->fDummy = TRUE;
  4896. }
  4897. break;
  4898. case IDCANCEL:
  4899. // user canceled the operation
  4900. pcs->lpfo->fAnyOperationsAborted = TRUE;
  4901. pcs->bAbort = TRUE;
  4902. //Because the Delete on this FOLDER is cancelled, we can cancel the "Delete"
  4903. // on the corresponding FILE too!
  4904. if (DTNIsConnectOrigin(pcs->dth.pdtnCurrent))
  4905. {
  4906. pcs->dth.pdtnCurrent->pdtnConnected->fDummy = TRUE;
  4907. }
  4908. break;
  4909. case IDYES:
  4910. default:
  4911. // user said "please nuke the file"
  4912. // assume noerror
  4913. iRet = 0;
  4914. // set this so the is correct progress animation is displayed
  4915. if (pcs)
  4916. {
  4917. pcs->fFlags &= ~FOF_ALLOWUNDO;
  4918. }
  4919. // dont allow undo since we are really nuking it (cant bring it back...)
  4920. if (pcs->lpua)
  4921. {
  4922. FOUndo_Release(pcs->lpua);
  4923. }
  4924. UpdateProgressAnimation(pcs);
  4925. goto NukeFolder;
  4926. break;
  4927. }
  4928. }
  4929. break;
  4930. case BBDELETE_CANNOT_DELETE:
  4931. {
  4932. // This is the non-deletable file case. Note: this is an NT only case, and
  4933. // it could be caused by acls or the fact that the file is currently in use.
  4934. // We attemt to really delete the file (which should fail) so we can generate
  4935. // the proper error value
  4936. DWORD dwAttributes = GetFileAttributes(pszSource);
  4937. if (dwAttributes & FILE_ATTRIBUTE_DIRECTORY)
  4938. {
  4939. iRet = Win32RemoveDirectory(pszSource);
  4940. }
  4941. else
  4942. {
  4943. iRet = Win32DeleteFile(pszSource);
  4944. }
  4945. if (!iRet)
  4946. {
  4947. // indeed, the file/folder could not be deleted.
  4948. // Get last error to find out why
  4949. iRet = GetLastError();
  4950. }
  4951. else
  4952. {
  4953. // DeleteFileBB said that it couldn't be deleted, but we just nuked it. We will
  4954. // end up falling into this case when we hit things like Mounted Volumes.
  4955. // As Obi-Wan would say: "You don't need to see his identification... these aren't
  4956. // the droids you are looking for... He can go about his business... Move along."
  4957. iRet = ERROR_SUCCESS;
  4958. DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder
  4959. // dont allow undo since we reall nuked it (cant bring it back...)
  4960. if (pcs->lpua)
  4961. {
  4962. FOUndo_Release(pcs->lpua);
  4963. }
  4964. }
  4965. }
  4966. break;
  4967. case BBDELETE_FORCE_NUKE:
  4968. {
  4969. // This is the catch-all case. If iRet = BDETETE_FORCE_NUKE, then we just nuke the
  4970. // file without warning.
  4971. // return noerror so we recurse into this dir and nuke it
  4972. iRet = ERROR_SUCCESS;
  4973. // set this so the is correct progress animation is displayed
  4974. if (pcs)
  4975. {
  4976. pcs->fFlags &= ~FOF_ALLOWUNDO;
  4977. }
  4978. // dont allow undo since we are really nuking it (cant bring it back...)
  4979. if (pcs->lpua)
  4980. {
  4981. FOUndo_Release(pcs->lpua);
  4982. }
  4983. UpdateProgressAnimation(pcs);
  4984. goto NukeFolder;
  4985. }
  4986. break;
  4987. case BBDELETE_CANCELLED:
  4988. {
  4989. // user canceled the operation
  4990. pcs->lpfo->fAnyOperationsAborted = TRUE;
  4991. pcs->bAbort = TRUE;
  4992. //Because the Delete on this FOLDER is cancelled, we can cancel the "Delete"
  4993. // on the corresponding FILE too!
  4994. if (DTNIsConnectOrigin(pcs->dth.pdtnCurrent))
  4995. {
  4996. pcs->dth.pdtnCurrent->pdtnConnected->fDummy = TRUE;
  4997. }
  4998. }
  4999. case BBDELETE_UNKNOWN_ERROR:
  5000. default:
  5001. {
  5002. iRet = GetLastError();
  5003. ASSERT(iRet != ERROR_SUCCESS);
  5004. }
  5005. break;
  5006. }
  5007. } // DeleteFileBB
  5008. return iRet;
  5009. }
  5010. BOOL DoFile_Win32DeleteFileWithPidl(LPCTSTR pszFile, SIMPLEPIDLCACHE *pspc)
  5011. {
  5012. LPITEMIDLIST pidlFile = NULL;
  5013. int iRet;
  5014. if (pspc)
  5015. {
  5016. pidlFile = SimplePidlCache_GetFilePidl(pspc, pszFile);
  5017. }
  5018. iRet = Win32DeleteFilePidl(pszFile, pidlFile);
  5019. ILFree(pidlFile);
  5020. return iRet;
  5021. }
  5022. int DoFile_Delete(COPY_STATE* pcs, WIN32_FIND_DATA *pfdSrc, LPTSTR pszSource, UINT cchSource, HDPA *phdpaDeletedFiles, BOOL fShouldSuspendEvents)
  5023. {
  5024. int iRet = 0;
  5025. // if we dont have an undo atom or this isint a root node or if this is a network file
  5026. // then we need to really nuke it
  5027. if (!pcs->lpua || !DTNIsRootNode(pcs->dth.pdtnCurrent) || IsNetDrive(PathGetDriveNumber(pszSource)))
  5028. {
  5029. iRet = DoFile_Win32DeleteFileWithPidl(pszSource, fShouldSuspendEvents ? NULL : &pcs->spc) ? 0 : GetLastError();
  5030. if (!iRet)
  5031. {
  5032. FOUndo_FileReallyDeleted(pszSource);
  5033. }
  5034. }
  5035. else if (!DeleteFileBB(pszSource, cchSource, &iRet, pcs, FALSE, pfdSrc, phdpaDeletedFiles))
  5036. {
  5037. // DeleteFileBB failed, check iRet to find out why
  5038. switch (iRet)
  5039. {
  5040. case BBDELETE_SIZE_TOO_BIG:
  5041. case BBDELETE_NUKE_OFFLINE:
  5042. {
  5043. // This is the case where the file is too big to fit in the Recycle Bin. We have no
  5044. // choice but to really nuke it, but we warn the user first since they may have thought
  5045. // that it was being sent to the recycle bin.
  5046. int result = CachedConfirmFileOp(pcs->hwndDlgParent,
  5047. pcs,
  5048. &pcs->cd,
  5049. pcs->nSourceFiles,
  5050. FALSE,
  5051. (iRet == BBDELETE_SIZE_TOO_BIG) ?
  5052. CONFIRM_WONT_RECYCLE_FOLDER :
  5053. CONFIRM_WONT_RECYCLE_OFFLINE,
  5054. pszSource,
  5055. pfdSrc,
  5056. NULL,
  5057. NULL,
  5058. NULL);
  5059. switch (result)
  5060. {
  5061. case IDNO:
  5062. // user said "please dont really nuke the file"
  5063. pcs->lpfo->fAnyOperationsAborted = TRUE;
  5064. iRet = IDNO; // Don't put up error message for this case
  5065. // WARNING: It is tempting to mark the corresponding connected folder as dummy here.
  5066. // But, this will not work because currently folders (nodes with children) can not be
  5067. // marked as dummy.
  5068. break;
  5069. case IDCANCEL:
  5070. // user canceled the operation
  5071. pcs->lpfo->fAnyOperationsAborted = TRUE;
  5072. pcs->bAbort = TRUE;
  5073. // WARNING: It is tempting to mark the corresponding connected folder as dummy here.
  5074. // But, this will not work because currently folders (nodes with children) can not be
  5075. // marked as dummy.
  5076. break;
  5077. case IDYES:
  5078. default:
  5079. // user said "please nuke the file"
  5080. // set this so the is correct progress animation is displayed
  5081. if (pcs)
  5082. {
  5083. pcs->fFlags &= ~FOF_ALLOWUNDO;
  5084. }
  5085. // dont allow undo since we are really nuking it
  5086. if (pcs->lpua)
  5087. {
  5088. FOUndo_Release(pcs->lpua);
  5089. }
  5090. UpdateProgressAnimation(pcs);
  5091. iRet = DoFile_Win32DeleteFileWithPidl(pszSource, &pcs->spc) ? 0 : GetLastError();
  5092. break;
  5093. }
  5094. }
  5095. break;
  5096. case BBDELETE_CANNOT_DELETE:
  5097. {
  5098. // This is the non-deletable file case. Note: this is an NT only case, and
  5099. // it could be caused by acls or the fact that the file is currently in use.
  5100. // We attemt to really delete the file (which should fail) so we can generate
  5101. // the proper error value
  5102. iRet = Win32DeleteFile(pszSource);
  5103. if (!iRet)
  5104. {
  5105. // indeed, the file/folder could not be deleted.
  5106. // Get last error to find out why
  5107. iRet = GetLastError();
  5108. }
  5109. else
  5110. {
  5111. // DeleteFileBB said that it couldn't be deleted, but we just nuked it. We will
  5112. // end up falling into this case when we hit things like Mounted Volumes and other
  5113. // reparse points that we can't "recycle".
  5114. // As Obi-Wan would say: "You don't need to see his identification... these aren't
  5115. // the droids you are looking for... He can go about his business... Move along."
  5116. iRet = ERROR_SUCCESS;
  5117. DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder
  5118. // dont allow undo since we really nuked it (cant bring it back...)
  5119. if (pcs->lpua)
  5120. {
  5121. FOUndo_Release(pcs->lpua);
  5122. }
  5123. }
  5124. }
  5125. break;
  5126. case BBDELETE_FORCE_NUKE:
  5127. {
  5128. // This is the catch-all case. If iRet = BDETETE_FORCE_NUKE, then we just nuke the
  5129. // file without warning.
  5130. // set this so the is correct progress animation is displayed
  5131. if (pcs)
  5132. {
  5133. pcs->fFlags &= ~FOF_ALLOWUNDO;
  5134. }
  5135. // dont allow undo since we are going to nuke this file
  5136. if (pcs->lpua)
  5137. {
  5138. FOUndo_Release(pcs->lpua);
  5139. }
  5140. UpdateProgressAnimation(pcs);
  5141. iRet = DoFile_Win32DeleteFileWithPidl(pszSource, &pcs->spc) ? 0 : GetLastError();
  5142. }
  5143. break;
  5144. case BBDELETE_CANCELLED:
  5145. {
  5146. // user canceled the operation
  5147. pcs->lpfo->fAnyOperationsAborted = TRUE;
  5148. pcs->bAbort = TRUE;
  5149. }
  5150. break;
  5151. case BBDELETE_UNKNOWN_ERROR:
  5152. default:
  5153. {
  5154. iRet = GetLastError();
  5155. ASSERT(iRet != ERROR_SUCCESS);
  5156. }
  5157. break;
  5158. }
  5159. } // !DeleteFileBB
  5160. return iRet;
  5161. }
  5162. int DoFile_Copy(COPY_STATE* pcs, LPTSTR pszSource, LPTSTR pszDest, UINT cchDest,
  5163. WIN32_FIND_DATA *pfd, WIN32_FIND_DATA * pfdDest, BOOL fRenameTried)
  5164. {
  5165. /* Now try to copy the file. Do extra error processing only
  5166. in 2 cases:
  5167. 1) If a removeable drive is full let the user stick in a new disk
  5168. 2) If the path doesn't exist (the user typed in
  5169. and explicit path that doesn't exits) ask if
  5170. we should create it for him. */
  5171. int ret = FileCopy(pcs, pszSource, pszDest, pfd, fRenameTried);
  5172. if (ret == ERROR_CANCELLED)
  5173. {
  5174. pcs->bAbort = TRUE;
  5175. return ret;
  5176. }
  5177. if ((ret & ~ERRORONDEST) == ERROR_FILE_EXISTS)
  5178. {
  5179. if (!fRenameTried)
  5180. {
  5181. int result = CheckForRenameCollision(pcs, OPER_DOFILE, pszSource, pszDest, cchDest, pfdDest, pfd);
  5182. switch (result)
  5183. {
  5184. case IDUNKNOWN:
  5185. break;
  5186. case IDRETRY:
  5187. return DoFile_Copy(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, TRUE);
  5188. case IDCANCEL:
  5189. pcs->bAbort = TRUE;
  5190. return result;
  5191. case IDNO:
  5192. return result;
  5193. default:
  5194. return result;
  5195. }
  5196. }
  5197. }
  5198. if ((((ret & ~ERRORONDEST) == ERROR_DISK_FULL) &&
  5199. IsRemovableDrive(DRIVEID(pszDest))) ||
  5200. ((ret & ~ERRORONDEST) == ERROR_PATH_NOT_FOUND))
  5201. {
  5202. ULARGE_INTEGER ulFileSize;
  5203. ulFileSize.LowPart = pfd->nFileSizeLow;
  5204. ulFileSize.HighPart = pfd->nFileSizeHigh;
  5205. ret = CopyMoveRetry(pcs, pszDest, ret & ~ERRORONDEST, &ulFileSize);
  5206. if (!ret)
  5207. {
  5208. return DoFile_Copy(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, fRenameTried);
  5209. }
  5210. else
  5211. {
  5212. pcs->bAbort = TRUE;
  5213. return ret;
  5214. }
  5215. }
  5216. if (!ret)
  5217. {
  5218. // add to the undo atom
  5219. // if we're doing a copy, only keep track of the highest most
  5220. // level.. unless we're doing a merge sort of copy
  5221. if (pcs->lpua)
  5222. {
  5223. if (DTNIsRootNode(pcs->dth.pdtnCurrent) && !DTNIsConnected(pcs->dth.pdtnCurrent))
  5224. FOUndo_AddInfo(pcs->lpua, pszSource, pszDest, 0);
  5225. }
  5226. // if we copied in a new desktop ini, send out an update event for the paretn
  5227. if (!lstrcmpi(PathFindFileName(pszDest), c_szDesktopIni))
  5228. {
  5229. TCHAR szDest[MAX_PATH];
  5230. HRESULT hr = StringCchCopy(szDest, ARRAYSIZE(szDest), pszDest);
  5231. if (SUCCEEDED(hr))
  5232. {
  5233. PathRemoveFileSpec(szDest);
  5234. SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, szDest, NULL);
  5235. }
  5236. }
  5237. }
  5238. return ret;
  5239. }
  5240. int DoFile_Move(COPY_STATE* pcs, LPTSTR pszSource, LPTSTR pszDest, UINT cchDest,
  5241. WIN32_FIND_DATA *pfd, WIN32_FIND_DATA * pfdDest, BOOL fRenameTried)
  5242. {
  5243. int ret = 0;
  5244. if (PathIsRoot(pszSource))
  5245. {
  5246. return DE_ROOTDIR;
  5247. }
  5248. if (PathIsRoot(pszDest))
  5249. {
  5250. return DE_ROOTDIR | ERRORONDEST;
  5251. }
  5252. AvoidCurrentDirectory(pszSource);
  5253. if (PathIsSameRoot(pszSource, pszDest))
  5254. {
  5255. TryAgain:
  5256. ret = Win32MoveFile(pszSource, pszDest, ISDIRFINDDATA(*pfd)) ? 0 : GetLastError();
  5257. // try to create the destination if it is not there
  5258. if (ret == ERROR_PATH_NOT_FOUND)
  5259. {
  5260. ret = CopyMoveRetry(pcs, pszDest, ret, NULL);
  5261. if (!ret)
  5262. {
  5263. goto TryAgain;
  5264. }
  5265. }
  5266. if (ret == ERROR_ALREADY_EXISTS)
  5267. {
  5268. if (!fRenameTried)
  5269. {
  5270. int result = CheckForRenameCollision(pcs, OPER_DOFILE, pszSource, pszDest, cchDest, pfdDest, pfd);
  5271. switch (result)
  5272. {
  5273. case IDUNKNOWN:
  5274. break;
  5275. case IDRETRY:
  5276. fRenameTried = TRUE;
  5277. goto TryAgain;
  5278. case IDCANCEL:
  5279. pcs->bAbort = TRUE;
  5280. return result;
  5281. case IDNO:
  5282. return result;
  5283. default:
  5284. return result;
  5285. }
  5286. }
  5287. }
  5288. if ((ret == ERROR_SUCCESS) &&
  5289. !SHRestricted(REST_NOENCRYPTONMOVE) &&
  5290. !(pfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
  5291. !(pfd->dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED))
  5292. {
  5293. TCHAR szDestDir[MAX_PATH];
  5294. DWORD dwAttribs;
  5295. // We are moving a file that is NOT encrypted. On Win2k, we need to check to see if this was a move to
  5296. // an encrypted folder. If so, we automatically encrypt the file.
  5297. HRESULT hr = StringCchCopy(szDestDir, ARRAYSIZE(szDestDir), pszDest);
  5298. if (SUCCEEDED(hr))
  5299. {
  5300. PathRemoveFileSpec(szDestDir);
  5301. dwAttribs = GetFileAttributes(szDestDir);
  5302. if ((dwAttribs != -1) && (dwAttribs & FILE_ATTRIBUTE_ENCRYPTED))
  5303. {
  5304. // sanity check
  5305. ASSERT(dwAttribs & FILE_ATTRIBUTE_DIRECTORY);
  5306. // attempt to encrypt the file
  5307. if (!SHEncryptFile(pszDest, TRUE))
  5308. {
  5309. int result = CachedConfirmFileOp(pcs->hwndDlgParent,
  5310. pcs,
  5311. &pcs->cd,
  5312. pcs->nSourceFiles,
  5313. FALSE,
  5314. CONFIRM_FAILED_ENCRYPT,
  5315. pszDest,
  5316. pfd, // since we just moved it, the attibs should be the same as the src
  5317. NULL,
  5318. NULL,
  5319. NULL);
  5320. switch (result)
  5321. {
  5322. case IDCANCEL:
  5323. // user canceled the operation
  5324. pcs->lpfo->fAnyOperationsAborted = TRUE;
  5325. pcs->bAbort = TRUE;
  5326. break;
  5327. case IDNO:
  5328. // user choose to "restore" the file to its original location
  5329. ret = Win32MoveFile(pszDest, pszSource, ISDIRFINDDATA(*pfd)) ? 0 : GetLastError();
  5330. case IDYES:
  5331. default:
  5332. // user ignored the error
  5333. break;
  5334. }
  5335. }
  5336. }
  5337. }
  5338. }
  5339. if (ret == ERROR_SUCCESS)
  5340. {
  5341. if (pcs->lpua && DTNIsRootNode(pcs->dth.pdtnCurrent) && !DTNIsConnected(pcs->dth.pdtnCurrent))
  5342. {
  5343. // add to the undo atom
  5344. FOUndo_AddInfo(pcs->lpua, pszSource, pszDest, 0);
  5345. }
  5346. // Win32MoveFile on a single-volume leaves the original ACL
  5347. // intact. If necessary, pick up perms from the destination.
  5348. if (pcs->fFlags & FOF_NOCOPYSECURITYATTRIBS)
  5349. {
  5350. ResetFileSecurity(pszDest);
  5351. }
  5352. // if we copied in a new desktop ini, send out an update event for the paretn
  5353. if (!lstrcmpi(PathFindFileName(pszDest), c_szDesktopIni))
  5354. {
  5355. TCHAR szDest[MAX_PATH];
  5356. HRESULT hr = StringCchCopy(szDest, ARRAYSIZE(szDest), pszDest);
  5357. if (SUCCEEDED(hr))
  5358. {
  5359. PathRemoveFileSpec(szDest);
  5360. SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, szDest, NULL);
  5361. }
  5362. }
  5363. }
  5364. }
  5365. else
  5366. {
  5367. // we must force all copies to go through
  5368. // straight so we can remove the source
  5369. if (DTNIsConnected(pcs->dth.pdtnCurrent) && !PathFileExists(pszSource))
  5370. {
  5371. //This can happen if "foo.htm" and "foo files" were moved by the end-user.
  5372. // The connected file had already been moved and hence this is not an error!
  5373. ret = 0; //No error! That file has been moved already!
  5374. }
  5375. else
  5376. {
  5377. ret = DoFile_Copy(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, FALSE);
  5378. }
  5379. }
  5380. return ret;
  5381. }
  5382. int DoFile_Rename(COPY_STATE* pcs, LPTSTR pszSource, LPTSTR pszDest, UINT cchDest,
  5383. WIN32_FIND_DATA *pfd, WIN32_FIND_DATA * pfdDest, BOOL fRenameTried)
  5384. {
  5385. LPTSTR p = PathFindFileName(pszSource);
  5386. /* Get raw source and dest paths. Check to make sure the
  5387. paths are the same */
  5388. int ret = !IntlStrEqNI(pszSource, pszDest, (int)(p - pszSource));
  5389. if (ret)
  5390. {
  5391. return DE_DIFFDIR;
  5392. }
  5393. return DoFile_Move(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, fRenameTried);
  5394. }
  5395. int MoveCopyInitPCS(COPY_STATE * pcs)
  5396. {
  5397. BOOL fMultiDest = FALSE;
  5398. int ret = 0;
  5399. LPTSTR p = NULL;
  5400. TCHAR szDestPath[MAX_PATH];
  5401. pcs->nSourceFiles = CountFiles(pcs->lpfo->pFrom); // multiple source files?
  5402. pcs->fProgressOk = TRUE;
  5403. // skip destination processing if we are deleting files
  5404. if (pcs->lpfo->wFunc != FO_DELETE)
  5405. {
  5406. HRESULT hr = S_OK;
  5407. if (pcs->lpfo->pTo == NULL)
  5408. {
  5409. szDestPath[0] = 0;
  5410. }
  5411. else
  5412. {
  5413. hr = StringCchCopy(szDestPath, ARRAYSIZE(szDestPath), pcs->lpfo->pTo);
  5414. }
  5415. if (SUCCEEDED(hr))
  5416. {
  5417. if (!szDestPath[0]) // NULL dest is same as "."
  5418. {
  5419. szDestPath[0] = TEXT('.');
  5420. szDestPath[1] = 0;
  5421. }
  5422. }
  5423. if (FAILED(hr) || PathIsInvalid(szDestPath))
  5424. {
  5425. CopyError(pcs, c_szNULL, c_szNULL, DE_INVALIDFILES | ERRORONDEST, pcs->lpfo->wFunc, 0);
  5426. return ERROR_ACCESS_DENIED;
  5427. }
  5428. if (pcs->lpfo->wFunc == FO_RENAME)
  5429. {
  5430. // don't let them rename multiple files to one single file
  5431. if ((pcs->nSourceFiles != 1) && !PathIsWild(szDestPath))
  5432. {
  5433. CopyError(pcs, c_szNULL, c_szNULL, DE_MANYSRC1DEST, pcs->lpfo->wFunc, 0);
  5434. return DE_MANYSRC1DEST;
  5435. }
  5436. fMultiDest = TRUE;
  5437. }
  5438. else // FO_COPY or FO_MOVE at this point
  5439. {
  5440. fMultiDest = ((pcs->fFlags & FOF_MULTIDESTFILES) &&
  5441. (pcs->nSourceFiles == CountFiles(pcs->lpfo->pTo)));
  5442. if (!fMultiDest)
  5443. {
  5444. // for backwards compat.
  5445. // copy c:\foo.bar c:\folder\foo.bar means
  5446. // multi dest if foo.bar doesn't exist.
  5447. // Hack if it is a root we special case this for the offline
  5448. // floppy case...
  5449. if (pcs->nSourceFiles == 1 && !PathIsRoot(szDestPath) &&
  5450. !PathIsDirectory(szDestPath))
  5451. {
  5452. fMultiDest = TRUE;
  5453. }
  5454. }
  5455. }
  5456. }
  5457. pcs->dth.fMultiDest = fMultiDest;
  5458. return 0;
  5459. }
  5460. DWORD g_dwStopWatchMode = 0xffffffff; // Shell performance mode
  5461. // actually this does move/copy/rename/delete
  5462. int MoveCopyDriver(COPY_STATE *pcs)
  5463. {
  5464. int ret;
  5465. WIN32_FIND_DATA fdSrc;
  5466. WIN32_FIND_DATA fdDest;
  5467. HDPA hdpaDeletedFiles = NULL;
  5468. LPSHFILEOPSTRUCT lpfo = pcs->lpfo;
  5469. TCHAR szText[28];
  5470. BOOL bInitialAllowUndo = FALSE;
  5471. DWORD dwLastUpdateTime = 0;
  5472. BOOL fShouldSuspendEvents = FALSE;
  5473. HANDLE hEventRunning;
  5474. if (g_dwStopWatchMode)
  5475. {
  5476. if (g_dwStopWatchMode == 0xffffffff)
  5477. {
  5478. g_dwStopWatchMode = StopWatchMode(); // Since the stopwatch funcs live in shdocvw, delay this call so we don't load shdocvw until we need to
  5479. }
  5480. if (g_dwStopWatchMode)
  5481. {
  5482. StringCchCopy(szText, ARRAYSIZE(szText), TEXT("Shell "));
  5483. switch (lpfo->wFunc)
  5484. {
  5485. case FO_COPY:
  5486. StringCchCat(szText, ARRAYSIZE(szText), TEXT("Copy "));
  5487. break;
  5488. case FO_MOVE:
  5489. StringCchCat(szText, ARRAYSIZE(szText), TEXT("Move "));
  5490. break;
  5491. case FO_DELETE:
  5492. StringCchCat(szText, ARRAYSIZE(szText), TEXT("Delete"));
  5493. break;
  5494. case FO_RENAME:
  5495. StringCchCat(szText, ARRAYSIZE(szText), TEXT("Rename"));
  5496. break;
  5497. default:
  5498. StringCchCat(szText, ARRAYSIZE(szText), TEXT("Copy? "));
  5499. break;
  5500. }
  5501. StringCchCat(szText, ARRAYSIZE(szText), TEXT(": Start"));
  5502. StopWatch_Start(SWID_COPY, (LPCTSTR)szText, SPMODE_SHELL | SPMODE_DEBUGOUT);
  5503. }
  5504. }
  5505. // start by assuming an error. Non-zero means an error has occured. If we don't
  5506. // start with this assumption then we will return success if MoveCopyInitPCS fails.
  5507. ret = ERROR_GEN_FAILURE;
  5508. if (!ValidFilenames(lpfo->pFrom))
  5509. {
  5510. CopyError(pcs, c_szNULL, c_szNULL, DE_INVALIDFILES, lpfo->wFunc, 0);
  5511. return ERROR_ACCESS_DENIED;
  5512. }
  5513. StartCopyEngine(&hEventRunning);
  5514. // Check the pcs destination directory to make sure it is valid for the given source file list
  5515. if (MoveCopyInitPCS(pcs))
  5516. {
  5517. goto ExitLoop; // Destination is invalid so we bail out
  5518. }
  5519. // Build a tree where each node is a source file, a dest file, and an operation to perform
  5520. ret = DTBuild(pcs);
  5521. if (ret)
  5522. {
  5523. goto ShowMessageBox;
  5524. }
  5525. // Speed optimization: for a delete, sending all FSNotifies really bogs down the system,
  5526. // so we skip it and rely on the file system notifies.
  5527. if (((lpfo->wFunc == FO_DELETE) || (lpfo->wFunc == FO_MOVE)) && (pcs->dth.dtAll.dwFiles > 100))
  5528. {
  5529. // Only suspend notifies for local moves
  5530. if (lpfo->wFunc == FO_MOVE)
  5531. {
  5532. if (lpfo->pTo)
  5533. {
  5534. int idDrive = PathGetDriveNumber(lpfo->pFrom);
  5535. if (idDrive == PathGetDriveNumber(lpfo->pTo) && !IsNetDrive(idDrive))
  5536. {
  5537. fShouldSuspendEvents = TRUE;
  5538. }
  5539. }
  5540. }
  5541. else
  5542. {
  5543. fShouldSuspendEvents = TRUE;
  5544. }
  5545. }
  5546. if (fShouldSuspendEvents)
  5547. {
  5548. // SuspendSHNotify can fail if another thread is using it. Only one thread at a time can suspend notify.
  5549. fShouldSuspendEvents = SuspendSHNotify();
  5550. }
  5551. // save off the initial state of the allowundo flag
  5552. if (pcs->fFlags & FOF_ALLOWUNDO)
  5553. {
  5554. bInitialAllowUndo = TRUE;
  5555. }
  5556. // When first starting, we assume that stream loss is possible until we prove
  5557. // otherwise for the current directory. This gets reset to true each time we
  5558. // enter a new dir via EnterDir_Move or EnterDir_Copy
  5559. pcs->bStreamLossPossible = TRUE;
  5560. for (;;)
  5561. {
  5562. BOOL bUpdateAnimation = FALSE;
  5563. int result;
  5564. DWORD dwTickCount;
  5565. BOOL bTimeToUpdate = FALSE;
  5566. BOOL fOk;
  5567. pcs->dth.oper = DTGoToNextNode(&pcs->dth,pcs);
  5568. dwTickCount = GetTickCount();
  5569. if ((dwTickCount - dwLastUpdateTime) > 10)
  5570. {
  5571. dwLastUpdateTime = dwTickCount;
  5572. bTimeToUpdate = TRUE;
  5573. }
  5574. if ((pcs->dth.oper & OPER_MASK) == OPER_ERROR)
  5575. {
  5576. CopyError(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, LOBYTE(pcs->dth.oper), pcs->lpfo->wFunc, OPER_DOFILE);
  5577. // If the directory is copied but a file inside that directory could not
  5578. // be copied because of long filename, check to see if this is
  5579. // a connected element. If so, invoke undo, sothat we getback the orginal html file
  5580. // in the same place as the associated folder.
  5581. if ((pcs->dth.oper == (OPER_ERROR | DE_INVALIDFILES)) &&
  5582. (DTNIsConnected(pcs->dth.pdtnCurrent)))
  5583. {
  5584. if (pcs->lpua)
  5585. {
  5586. pcs->lpua->foFlags |= FOF_NOCONFIRMATION;
  5587. FOUndo_Invoke(pcs->lpua);
  5588. pcs->lpua = NULL;
  5589. }
  5590. }
  5591. break;
  5592. }
  5593. if (!pcs->dth.oper || pcs->bAbort) // all done?
  5594. {
  5595. break;
  5596. }
  5597. if (DTNIsRootNode(pcs->dth.pdtnCurrent) && (pcs->dth.oper != OPER_LEAVEDIR))
  5598. {
  5599. int iDrive;
  5600. // check to see if we switched between doing a move to
  5601. // recycle bin and a true delete (this would happen when
  5602. // there was an object that was too big for the recycle bin)
  5603. if (!(pcs->fFlags & FOF_ALLOWUNDO) && bInitialAllowUndo)
  5604. {
  5605. // reset the allowundo flag since we have a new root node, and we
  5606. // want to attempt to send it to the recycle bin
  5607. pcs->fFlags |= FOF_ALLOWUNDO;
  5608. // we delay to update the progress animation till we are basically
  5609. // done, which allows us to keep the progress and animation in sync
  5610. bUpdateAnimation = TRUE;
  5611. }
  5612. pcs->fMerge = FALSE;
  5613. pcs->fFromCDRom = FALSE;
  5614. // Check source for being a CDRom
  5615. iDrive = PathGetDriveNumber(pcs->dth.szSrcPath);
  5616. if (-1 != iDrive)
  5617. {
  5618. TCHAR szDrive[4];
  5619. if (DRIVE_CDROM == GetDriveType(PathBuildRoot(szDrive, iDrive)))
  5620. {
  5621. pcs->fFromCDRom = TRUE;
  5622. }
  5623. }
  5624. }
  5625. fOk = DTGetWin32FindData(pcs->dth.pdtnCurrent, &fdSrc);
  5626. if (!fOk)
  5627. {
  5628. ret = ERROR_FILENAME_EXCED_RANGE;
  5629. goto ShowMessageBox;
  5630. }
  5631. fdDest.dwFileAttributes = 0;
  5632. DebugMsg(TF_DEBUGCOPY, TEXT("MoveCopyDriver(): Oper %x From(%s) To(%s)"), pcs->dth.oper, (LPCTSTR)pcs->dth.szSrcPath, (LPCTSTR)pcs->dth.szDestPath);
  5633. // some operation that may effect the destination (have a collision)
  5634. if ((pcs->lpfo->wFunc != FO_DELETE) && (pcs->dth.oper != OPER_LEAVEDIR))
  5635. {
  5636. // this compare needs to be case sensitive, and locale insensitive
  5637. if (!StrCmpC(pcs->dth.szSrcPath, pcs->dth.szDestPath) &&
  5638. !(pcs->fFlags & FOF_RENAMEONCOLLISION))
  5639. {
  5640. // Source and dest are the same file, and name collision
  5641. // resolution is not turned on, so we just return an error.
  5642. // TODO: Show the error dialog here and allow for SKIP
  5643. ret = DE_SAMEFILE;
  5644. goto ShowMessageBox;
  5645. }
  5646. }
  5647. result = AllConfirmations(pcs, &fdSrc, pcs->dth.oper, pcs->lpfo->wFunc, pcs->dth.szSrcPath,
  5648. pcs->dth.szDestPath, bTimeToUpdate, &fdDest, &ret);
  5649. switch (result)
  5650. {
  5651. case IDNO:
  5652. DTAbortCurrentNode(&pcs->dth);
  5653. lpfo->fAnyOperationsAborted = TRUE;
  5654. continue;
  5655. case IDCANCEL:
  5656. pcs->bAbort = TRUE;
  5657. goto ExitLoop;
  5658. case IDYES:
  5659. break;
  5660. default:
  5661. ret = result;
  5662. goto ShowMessageBox;
  5663. }
  5664. /* Now determine which operation to perform */
  5665. switch (pcs->dth.oper | pcs->lpfo->wFunc)
  5666. {
  5667. // Note that ENTERDIR is not done for a root, even though LEAVEDIR is
  5668. case OPER_ENTERDIR | FO_MOVE: // Create dest, verify source delete
  5669. ret = EnterDir_Move(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, ARRAYSIZE(pcs->dth.szDestPath), &fdSrc, &fdDest, FALSE);
  5670. break;
  5671. case OPER_ENTERDIR | FO_COPY: // Create destination directory
  5672. ret = EnterDir_Copy(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, ARRAYSIZE(pcs->dth.szDestPath), &fdSrc, &fdDest, FALSE, FALSE);
  5673. break;
  5674. case OPER_LEAVEDIR | FO_MOVE:
  5675. case OPER_LEAVEDIR | FO_DELETE:
  5676. ret = LeaveDir_Delete(pcs, pcs->dth.szSrcPath);
  5677. break;
  5678. case OPER_LEAVEDIR | FO_COPY:
  5679. break;
  5680. case OPER_DOFILE | FO_COPY:
  5681. ret = DoFile_Copy(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, ARRAYSIZE(pcs->dth.szDestPath), &fdSrc, &fdDest, FALSE);
  5682. break;
  5683. case OPER_DOFILE | FO_RENAME:
  5684. ret = DoFile_Rename(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, ARRAYSIZE(pcs->dth.szDestPath), &fdSrc, &fdDest, FALSE);
  5685. break;
  5686. case OPER_DOFILE | FO_MOVE:
  5687. ret = DoFile_Move(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, ARRAYSIZE(pcs->dth.szDestPath), &fdSrc, &fdDest, FALSE);
  5688. break;
  5689. case OPER_ENTERDIR | FO_DELETE:
  5690. ret = EnterDir_Delete(pcs, &fdSrc, pcs->dth.szSrcPath, ARRAYSIZE(pcs->dth.szSrcPath), &hdpaDeletedFiles);
  5691. break;
  5692. case OPER_DOFILE | FO_DELETE:
  5693. ret = DoFile_Delete(pcs, &fdSrc, pcs->dth.szSrcPath, ARRAYSIZE(pcs->dth.szSrcPath), &hdpaDeletedFiles, fShouldSuspendEvents);
  5694. break;
  5695. default:
  5696. DebugMsg(DM_ERROR, TEXT("Invalid file operation"));
  5697. ret = 0; // internal error
  5698. break;
  5699. } // switch (pcs->dth.oper | pcs->lpfo->wFunc)
  5700. if (pcs->bAbort)
  5701. break;
  5702. if (ret == IDNO)
  5703. {
  5704. pcs->lpfo->fAnyOperationsAborted = TRUE;
  5705. }
  5706. else if (ret)
  5707. { // any errors?
  5708. ShowMessageBox:
  5709. // If source file is a connected item and is not found, that means that
  5710. // we have already moved/deleted/renamed it. So, don't report that as error!
  5711. if ((!pcs->dth.pdtnCurrent) || (!pcs->dth.pdtnCurrent->fConnectedElement) ||
  5712. ((ret != ERROR_FILE_NOT_FOUND) && (ret != ERROR_PATH_NOT_FOUND)))
  5713. {
  5714. CopyError(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, ret, pcs->lpfo->wFunc, pcs->dth.oper);
  5715. // If the directory is copied but a file inside that directory could not
  5716. // be copied because of long filename, check to see if this is
  5717. // a connected element. If so, invoke undo, sothat we getback the orginal html file
  5718. // in the same place as the associated folder.
  5719. if ((ret == ERROR_FILENAME_EXCED_RANGE) &&
  5720. (DTNIsConnected(pcs->dth.pdtnCurrent)))
  5721. {
  5722. if (pcs->lpua)
  5723. {
  5724. pcs->lpua->foFlags |= FOF_NOCONFIRMATION;
  5725. FOUndo_Invoke(pcs->lpua);
  5726. pcs->lpua = NULL;
  5727. }
  5728. }
  5729. break;
  5730. }
  5731. }
  5732. if (bTimeToUpdate)
  5733. {
  5734. // perform the delayed update of the dialog
  5735. if (bUpdateAnimation)
  5736. {
  5737. UpdateProgressAnimation(pcs);
  5738. bUpdateAnimation = FALSE;
  5739. }
  5740. // We check to see if we are finished here (instead of at the
  5741. // start) since we want to keep the progress a step behind what
  5742. // we are doing to ensure we have the correct progress animation
  5743. // and text (since FOQueryAbort updates the progress text)
  5744. if (FOQueryAbort(pcs))
  5745. break;
  5746. }
  5747. }
  5748. ExitLoop:
  5749. // this happens in error cases where we broke out of the pcr loop
  5750. // without hitting the end
  5751. lpfo->hNameMappings = pcs->dth.hdsaRenamePairs;
  5752. DTCleanup(&pcs->dth);
  5753. BBFinishDelete(hdpaDeletedFiles);
  5754. if (fShouldSuspendEvents)
  5755. {
  5756. ResumeSHNotify();
  5757. if (lpfo->wFunc == FO_DELETE)
  5758. {
  5759. TCHAR szNotifyPath[MAX_PATH];
  5760. int iDrive;
  5761. // Since we probably blew away any chance at having the FSNotify work, make sure
  5762. // we update dir for this path... we can send the message on any drive since
  5763. // the bitbucket listens for changes on all drives.
  5764. iDrive = DriveIDFromBBPath(lpfo->pFrom);
  5765. if ((iDrive == -1) || !DriveIDToBBPath(iDrive, szNotifyPath))
  5766. {
  5767. StringCchCopy(szNotifyPath, ARRAYSIZE(szNotifyPath), lpfo->pFrom); // failure ok since only use for changenotify
  5768. PathRemoveFileSpec(szNotifyPath);
  5769. }
  5770. SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH, szNotifyPath, NULL);
  5771. }
  5772. }
  5773. if (g_dwStopWatchMode)
  5774. {
  5775. StringCchCopy(&szText[12], ARRAYSIZE(szText)-12, TEXT(": Stop "));
  5776. StopWatch_Stop(SWID_COPY, (LPCTSTR)szText, SPMODE_SHELL | SPMODE_DEBUGOUT);
  5777. }
  5778. EndCopyEngine(hEventRunning);
  5779. return ret;
  5780. }
  5781. void SetWindowTextFromRes(HWND hwnd, int id)
  5782. {
  5783. TCHAR szTemp[80];
  5784. LoadString(HINST_THISDLL, id, szTemp, ARRAYSIZE(szTemp));
  5785. SetWindowText(hwnd, szTemp);
  5786. }
  5787. int CountProgressPoints(COPY_STATE *pcs, PDIRTOTALS pdt)
  5788. {
  5789. // point value for each item
  5790. int iTotal = 0;
  5791. UINT uSize = pcs->uSize;
  5792. if (!uSize)
  5793. {
  5794. uSize = 32*1024;
  5795. }
  5796. // now add it up.
  5797. iTotal += (UINT)((pdt->liSize.QuadPart/uSize) * pcs->dth.iSizePoints);
  5798. iTotal += pdt->dwFiles * pcs->dth.iFilePoints;
  5799. iTotal += pdt->dwFolders * pcs->dth.iFolderPoints;
  5800. return iTotal;
  5801. }
  5802. void UpdateProgressDialog(COPY_STATE* pcs)
  5803. {
  5804. int iRange; // from 0 to iRange
  5805. int iPos; // how much is done.
  5806. if (pcs->fProgressOk)
  5807. {
  5808. if (pcs->dth.dtAll.fChanged)
  5809. {
  5810. pcs->dth.dtAll.fChanged = FALSE;
  5811. iRange = CountProgressPoints(pcs, &pcs->dth.dtAll);
  5812. SendProgressMessage(pcs, PBM_SETRANGE32, 0, iRange);
  5813. DebugMsg(TF_DEBUGCOPY, TEXT("UpdateProgressDialog iRange = %d "), iRange);
  5814. }
  5815. if (pcs->dth.dtDone.fChanged)
  5816. {
  5817. pcs->dth.dtDone.fChanged = FALSE;
  5818. iPos = CountProgressPoints(pcs, &pcs->dth.dtDone);
  5819. SendProgressMessage(pcs, PBM_SETPOS, iPos, 0);
  5820. DebugMsg(TF_DEBUGCOPY, TEXT("UpdateProgressDialog iPos = %d "), iPos);
  5821. }
  5822. }
  5823. }
  5824. // NOTE: !! do NOT refrence pcs->lpfo anywhere in this dialog proc !!
  5825. //
  5826. // It can be freed while we are still running. If you need to get information from it,
  5827. // add a new member to the FOUITHREADINFO struct and copy the value from the pcs->lpfo
  5828. // into the member (while holding the critsec) right before we create this dlg.
  5829. BOOL_PTR CALLBACK FOFProgressDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
  5830. {
  5831. FOUITHREADINFO* pfouiti = (FOUITHREADINFO*)GetWindowLongPtr(hDlg, DWLP_USER);
  5832. COPY_STATE *pcs = (pfouiti ? pfouiti->pcs : NULL);
  5833. if (WM_INITDIALOG == wMsg)
  5834. {
  5835. SetWindowLongPtr(hDlg, DWLP_USER, lParam);
  5836. pfouiti = (FOUITHREADINFO*)lParam;
  5837. pcs = pfouiti->pcs;
  5838. SetWindowTextFromRes(hDlg, IDS_ACTIONTITLE + pfouiti->wFunc);
  5839. if (pcs->fFlags & FOF_SIMPLEPROGRESS)
  5840. {
  5841. TCHAR szFrom[MAX_PATH];
  5842. if (pcs->lpszProgressTitle)
  5843. {
  5844. if (IS_INTRESOURCE(pcs->lpszProgressTitle))
  5845. {
  5846. LoadString(HINST_THISDLL, PtrToUlong(pcs->lpszProgressTitle), szFrom, ARRAYSIZE(szFrom));
  5847. pcs->lpszProgressTitle = szFrom;
  5848. }
  5849. SetDlgItemText(hDlg, IDD_NAME, pcs->lpszProgressTitle);
  5850. // null it so we only set it once
  5851. pcs->lpszProgressTitle = NULL;
  5852. }
  5853. }
  5854. return FALSE;
  5855. }
  5856. if (pcs)
  5857. {
  5858. switch (wMsg)
  5859. {
  5860. case WM_TIMER:
  5861. if (IsWindowEnabled(hDlg))
  5862. SetProgressTime(pcs);
  5863. break;
  5864. case WM_SHOWWINDOW:
  5865. if (wParam)
  5866. {
  5867. int idAni;
  5868. HWND hwndAnimation;
  5869. ASSERT(pfouiti->wFunc >= FO_MOVE && pfouiti->wFunc <= FO_DELETE);
  5870. ASSERT(FO_COPY==FO_MOVE+1);
  5871. ASSERT(FO_DELETE==FO_COPY+1);
  5872. ASSERT(IDA_FILECOPY==IDA_FILEMOVE+1);
  5873. ASSERT(IDA_FILEDEL ==IDA_FILECOPY+1);
  5874. switch (pfouiti->wFunc)
  5875. {
  5876. case FO_DELETE:
  5877. if (pfouiti->bIsEmptyRBOp)
  5878. {
  5879. idAni = IDA_FILENUKE;
  5880. break;
  5881. }
  5882. else if (!(pcs->fFlags & FOF_ALLOWUNDO))
  5883. {
  5884. idAni = IDA_FILEDELREAL;
  5885. break;
  5886. }
  5887. // else fall through
  5888. default:
  5889. idAni = (IDA_FILEMOVE + (int)pfouiti->wFunc - FO_MOVE);
  5890. }
  5891. hwndAnimation = GetDlgItem(hDlg,IDD_ANIMATE);
  5892. Animate_Open(hwndAnimation, IntToPtr(idAni));
  5893. SetProp(hwndAnimation, TEXT("AnimationID"), IntToPtr(idAni));
  5894. // a timer every MS_TIMESLICE seconds to update the progress time estimate
  5895. SetTimer(hDlg, 1, MS_TIMESLICE, NULL);
  5896. }
  5897. break;
  5898. case WM_ENABLE:
  5899. if (wParam)
  5900. {
  5901. if (pcs->dwPreviousTime)
  5902. {
  5903. // if we're enabling it, set the previous time to now
  5904. // because no action has happened while we were disabled
  5905. pcs->dwPreviousTime = GetTickCount();
  5906. }
  5907. }
  5908. else
  5909. {
  5910. SetProgressTime(pcs);
  5911. }
  5912. PauseAnimation(pcs, wParam == 0);
  5913. break;
  5914. case WM_COMMAND:
  5915. switch (GET_WM_COMMAND_ID(wParam, lParam))
  5916. {
  5917. case IDCANCEL:
  5918. pcs->bAbort = TRUE;
  5919. ShowWindow(hDlg, SW_HIDE);
  5920. break;
  5921. }
  5922. break;
  5923. case PDM_SHUTDOWN:
  5924. // Make sure this window is shown before telling the user there
  5925. // is a problem
  5926. // ignore FOF_NOERRORUI here because of the nature of the situation
  5927. ShellMessageBox(HINST_THISDLL, hDlg, MAKEINTRESOURCE(IDS_CANTSHUTDOWN),
  5928. NULL, MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
  5929. break;
  5930. case PDM_NOOP:
  5931. // a dummy id that we can take so that folks can post to us and make
  5932. // us go through the main loop
  5933. break;
  5934. case PDM_UPDATE:
  5935. pcs->dth.fChangePosted = FALSE;
  5936. UpdateProgressDialog(pcs);
  5937. break;
  5938. case WM_QUERYENDSESSION:
  5939. // Post a message telling the dialog to show the "We can't shutdown now"
  5940. // dialog and return to USER right away, so we don't have to worry about
  5941. // the user not clicking the OK button before USER puts up its "this
  5942. // app didn't respond" dialog
  5943. PostMessage(hDlg, PDM_SHUTDOWN, 0, 0);
  5944. // Make sure the dialog box procedure returns FALSE
  5945. SetWindowLongPtr(hDlg, DWLP_MSGRESULT, FALSE);
  5946. return TRUE;
  5947. default:
  5948. return FALSE;
  5949. }
  5950. }
  5951. return TRUE;
  5952. }
  5953. int CALLBACK FOUndo_FileReallyDeletedCallback(UNDOATOM *lpua, LPARAM lParam)
  5954. {
  5955. LPTSTR * ppsz = (LPTSTR*)lParam;
  5956. // this is our signal to nuke the rest
  5957. if (!*ppsz)
  5958. return EUA_DELETE;
  5959. switch (lpua->uType)
  5960. {
  5961. case IDS_RENAME:
  5962. case IDS_COPY:
  5963. case IDS_MOVE:
  5964. case IDS_DELETE:
  5965. {
  5966. LPFOUNDODATA lpud = (LPFOUNDODATA)lpua->lpData;
  5967. HDPA hdpa = lpud->hdpa;
  5968. // only the destinations matter.
  5969. int i, iMax = DPA_GetPtrCount(hdpa);
  5970. for (i = 1; i <= iMax; i += 2)
  5971. {
  5972. LPTSTR lpsz = DPA_GetPtr(hdpa, i);
  5973. if (lstrcmpi(lpsz, *ppsz) == 0)
  5974. {
  5975. *ppsz = NULL;
  5976. break;
  5977. }
  5978. }
  5979. }
  5980. break;
  5981. }
  5982. // this is our signal to nuke the rest
  5983. if (!*ppsz)
  5984. return EUA_DELETE;
  5985. else
  5986. return EUA_DONOTHING;
  5987. }
  5988. // someone really really deleted a file. make sure we no longer have
  5989. // any undo information pointing to it.
  5990. void FOUndo_FileReallyDeleted(LPTSTR lpszFile)
  5991. {
  5992. EnumUndoAtoms(FOUndo_FileReallyDeletedCallback, (LPARAM)&lpszFile);
  5993. }
  5994. int CALLBACK FOUndo_FileRestoredCallback(UNDOATOM *lpua, LPARAM lParam)
  5995. {
  5996. LPTSTR psz = (LPTSTR)lParam;
  5997. switch (lpua->uType)
  5998. {
  5999. case IDS_DELETE:
  6000. {
  6001. LPFOUNDODATA lpud = (LPFOUNDODATA)lpua->lpData;
  6002. HDPA hdpa = lpud->hdpa;
  6003. LPTSTR lpsz;
  6004. int i, iMax;
  6005. ASSERT(hdpa);
  6006. // only the destinations matter.
  6007. iMax = DPA_GetPtrCount(hdpa);
  6008. for (i = 1; i <= iMax; i += 2)
  6009. {
  6010. lpsz = DPA_GetPtr(hdpa, i);
  6011. if (lstrcmpi(lpsz, psz) == 0)
  6012. {
  6013. ENTERCRITICAL;
  6014. Str_SetPtr(&lpsz, NULL);
  6015. lpsz = DPA_GetPtr(hdpa, i - 1);
  6016. Str_SetPtr(&lpsz, NULL);
  6017. DPA_DeletePtr(hdpa, i);
  6018. DPA_DeletePtr(hdpa, i - 1);
  6019. LEAVECRITICAL;
  6020. if (DPA_GetPtrCount(hdpa))
  6021. return EUA_ABORT;
  6022. else
  6023. return EUA_DELETEABORT;
  6024. }
  6025. }
  6026. }
  6027. break;
  6028. }
  6029. return EUA_DONOTHING;
  6030. }
  6031. // this means someone restored a file (via ui in the bitbucket)
  6032. // so we need to clean up the undo info.
  6033. void FOUndo_FileRestored(LPCTSTR lpszFile)
  6034. {
  6035. EnumUndoAtoms(FOUndo_FileRestoredCallback, (LPARAM)lpszFile);
  6036. }
  6037. void FOUndo_AddInfo(UNDOATOM *lpua, LPTSTR lpszSrc, LPTSTR lpszDest, DWORD dwAttributes)
  6038. {
  6039. HDPA hdpa;
  6040. LPTSTR lpsz = NULL;
  6041. int i;
  6042. LPFOUNDODATA lpud;
  6043. if (lpua->lpData == (void *)-1)
  6044. return;
  6045. if (!lpua->lpData)
  6046. {
  6047. lpua->lpData = LocalAlloc(LPTR, sizeof(FOUNDODATA));
  6048. if (!lpua->lpData)
  6049. return;
  6050. ((LPFOUNDODATA)lpua->lpData)->hdpa = (void *)DPA_Create(4);
  6051. }
  6052. lpud = lpua->lpData;
  6053. hdpa = lpud->hdpa;
  6054. if (!hdpa)
  6055. return;
  6056. // if it's a directory that got deleted, we're just going to save it's
  6057. // attributes so that we can recreate it later.
  6058. // directories do NOT get moved into the wastebasket
  6059. if ((lpua->uType == IDS_DELETE) && (dwAttributes & FILE_ATTRIBUTE_DIRECTORY))
  6060. {
  6061. FOUNDO_DELETEDFILEINFO dfi;
  6062. if (!lpud->hdsa)
  6063. {
  6064. lpud->hdsa = DSA_Create(sizeof(FOUNDO_DELETEDFILEINFO), 4);
  6065. if (!lpud->hdsa)
  6066. return;
  6067. }
  6068. Str_SetPtr(&lpsz, lpszSrc);
  6069. dfi.lpszName = lpsz;
  6070. dfi.dwAttributes = dwAttributes;
  6071. DSA_AppendItem(lpud->hdsa, &dfi);
  6072. }
  6073. else
  6074. {
  6075. Str_SetPtr(&lpsz, lpszSrc);
  6076. if (!lpsz)
  6077. return;
  6078. if ((i = DPA_AppendPtr(hdpa, lpsz)) == -1)
  6079. {
  6080. return;
  6081. }
  6082. lpsz = NULL;
  6083. Str_SetPtr(&lpsz, lpszDest);
  6084. if (!lpsz ||
  6085. DPA_AppendPtr(hdpa, lpsz) == -1)
  6086. {
  6087. DPA_DeletePtr(hdpa, i);
  6088. }
  6089. }
  6090. }
  6091. LPTSTR DPA_ToFileList(HDPA hdpa, int iStart, int iEnd, int iIncr)
  6092. {
  6093. LPTSTR lpsz;
  6094. LPTSTR lpszReturn;
  6095. int ichSize;
  6096. int ichTemp;
  6097. int i;
  6098. // undo copy by deleting destinations
  6099. lpszReturn = (LPTSTR)LocalAlloc(LPTR, 1);
  6100. if (!lpszReturn)
  6101. {
  6102. return NULL;
  6103. }
  6104. ichSize = 1;
  6105. // build the NULL separated file list
  6106. // go from the end to the front.. restore in reverse order!
  6107. for (i = iEnd; i >= iStart ; i -= iIncr)
  6108. {
  6109. LPTSTR psz;
  6110. HRESULT hr;
  6111. UINT cchLen;
  6112. lpsz = DPA_GetPtr(hdpa, i);
  6113. ASSERT(lpsz);
  6114. ichTemp = ichSize - 1;
  6115. cchLen = lstrlen(lpsz);
  6116. ichSize += (cchLen + 1);
  6117. psz = (LPTSTR)LocalReAlloc((HLOCAL)lpszReturn, ichSize * sizeof(TCHAR),
  6118. LMEM_MOVEABLE|LMEM_ZEROINIT);
  6119. if (!psz)
  6120. {
  6121. break;
  6122. }
  6123. lpszReturn = psz;
  6124. hr = StringCchCopyN(lpszReturn + ichTemp, ichSize - ichTemp, lpsz, cchLen);
  6125. if (FAILED(hr))
  6126. {
  6127. break;
  6128. }
  6129. }
  6130. if ((i + iIncr) != iStart)
  6131. {
  6132. LocalFree((HLOCAL)lpszReturn);
  6133. lpszReturn = NULL;
  6134. }
  6135. return lpszReturn;
  6136. }
  6137. // from dpa to:
  6138. // 'file 1', 'file 2' and 'file 3'
  6139. LPTSTR DPA_ToQuotedFileList(HDPA hdpa, int iStart, int iEnd, int iIncr)
  6140. {
  6141. LPTSTR lpsz;
  6142. LPTSTR lpszReturn;
  6143. TCHAR szFile[MAX_PATH];
  6144. int ichSize;
  6145. int ichTemp;
  6146. int i;
  6147. SHELLSTATE ss;
  6148. // undo copy by deleting destinations
  6149. lpszReturn = (LPTSTR)(void*)LocalAlloc(LPTR, 1);
  6150. if (!lpszReturn)
  6151. {
  6152. return NULL;
  6153. }
  6154. SHGetSetSettings(&ss, SSF_SHOWEXTENSIONS|SSF_SHOWALLOBJECTS, FALSE);
  6155. ichSize = 1;
  6156. // build the quoted file list
  6157. for (i = iStart; i < iEnd ; i += iIncr)
  6158. {
  6159. LPTSTR psz;
  6160. HRESULT hr;
  6161. ichTemp = ichSize - 1;
  6162. // get the name (filename only without extension)
  6163. lpsz = DPA_GetPtr(hdpa, i);
  6164. hr = StringCchCopy(szFile, ARRAYSIZE(szFile), PathFindFileName(lpsz));
  6165. if (FAILED(hr))
  6166. {
  6167. LocalFree(lpszReturn);
  6168. lpszReturn = NULL;
  6169. break;
  6170. }
  6171. if (!ss.fShowExtensions)
  6172. {
  6173. PathRemoveExtension(szFile);
  6174. }
  6175. // grow the buffer and add it in
  6176. ichSize += lstrlen(szFile) + 2;
  6177. psz = (LPTSTR)LocalReAlloc((HLOCAL)lpszReturn, ichSize * sizeof(TCHAR),
  6178. LMEM_MOVEABLE|LMEM_ZEROINIT);
  6179. if (!psz)
  6180. {
  6181. LocalFree(lpszReturn);
  6182. lpszReturn = NULL;
  6183. break;
  6184. }
  6185. lpszReturn = psz;
  6186. // is it too long?
  6187. if (ichSize >= MAX_PATH)
  6188. {
  6189. StringCchCat(lpszReturn, ichSize, c_szEllipses);
  6190. return lpszReturn;
  6191. }
  6192. else
  6193. {
  6194. StringCchCat(lpszReturn, ichSize, TEXT("'")); //A single quote BEFORE the filename. - should fit because of realloc above
  6195. StringCchCat(lpszReturn, ichSize, szFile); // should fit because of realloc above
  6196. StringCchCat(lpszReturn, ichSize, TEXT("'")); //A single quote AFTER the filename. - should fit because of realloc above
  6197. }
  6198. ASSERT(ichSize == ichTemp + (lstrlen(lpszReturn + ichTemp) + 1));
  6199. ichTemp = ichSize - 1;
  6200. // check to see if we need the "and"
  6201. if ((i + iIncr) < iEnd)
  6202. {
  6203. TCHAR szTemp[40];
  6204. int id;
  6205. ichSize += 40;
  6206. if ((i + (iIncr*2)) >= iEnd)
  6207. {
  6208. id = IDS_SPACEANDSPACE;
  6209. }
  6210. else
  6211. {
  6212. id = IDS_COMMASPACE;
  6213. }
  6214. psz = (LPTSTR)LocalReAlloc((HLOCAL)lpszReturn, ichSize * sizeof(TCHAR),
  6215. LMEM_MOVEABLE|LMEM_ZEROINIT);
  6216. if (!psz)
  6217. {
  6218. LocalFree(lpszReturn);
  6219. lpszReturn = NULL;
  6220. break;
  6221. }
  6222. lpszReturn = psz;
  6223. LoadString(HINST_THISDLL, id, szTemp, ARRAYSIZE(szTemp));
  6224. StringCchCat(lpszReturn, ichSize, szTemp); // should fit because of realloc above
  6225. ichSize = ichTemp + (lstrlen(lpszReturn + ichTemp) + 1);
  6226. }
  6227. }
  6228. return lpszReturn;
  6229. }
  6230. void CALLBACK FOUndo_GetText(UNDOATOM *lpua, TCHAR * buffer, UINT cchBuffer, int type)
  6231. {
  6232. LPFOUNDODATA lpud = (LPFOUNDODATA)lpua->lpData;
  6233. HDPA hdpa = lpud->hdpa;
  6234. if (type == UNDO_MENUTEXT)
  6235. {
  6236. LoadString(HINST_THISDLL, lpua->uType, buffer, MAX_PATH);
  6237. }
  6238. else
  6239. {
  6240. TCHAR szTemplate[80];
  6241. // thank god for growable stacks..
  6242. TCHAR szFile1[MAX_PATH];
  6243. TCHAR szFile2[MAX_PATH];
  6244. TCHAR szFile1Short[30];
  6245. TCHAR szFile2Short[30];
  6246. TCHAR *lpszFile1;
  6247. TCHAR *lpszFile2;
  6248. // get the template
  6249. LoadString(HINST_THISDLL, lpua->uType + (IDS_UNDO_FILEOPHELP - IDS_UNDO_FILEOP), szTemplate, ARRAYSIZE(szTemplate));
  6250. if (lpua->uType == IDS_RENAME)
  6251. {
  6252. SHELLSTATE ss;
  6253. LPTSTR pszTemp;
  6254. HRESULT hr;
  6255. // fill in the file names
  6256. lpszFile1 = DPA_GetPtr(hdpa, 0);
  6257. lpszFile2 = DPA_GetPtr(hdpa, 1);
  6258. hr = StringCchCopy(szFile1, ARRAYSIZE(szFile1), PathFindFileName(lpszFile1));
  6259. if (FAILED(hr))
  6260. {
  6261. szFile1[0] = TEXT('\0');
  6262. }
  6263. hr = StringCchCopy(szFile2, ARRAYSIZE(szFile2), PathFindFileName(lpszFile2));
  6264. if (FAILED(hr))
  6265. {
  6266. szFile2[0] = TEXT('\0');
  6267. }
  6268. SHGetSetSettings(&ss, SSF_SHOWEXTENSIONS, FALSE);
  6269. if (!ss.fShowExtensions)
  6270. {
  6271. PathRemoveExtension(szFile1);
  6272. PathRemoveExtension(szFile2);
  6273. }
  6274. // length sanity check
  6275. // don't just whack "..." at 30 bytes into szFile1 since that may be a dbcs character...
  6276. PathCompactPathEx(szFile1Short, szFile1, ARRAYSIZE(szFile1Short), 0);
  6277. PathCompactPathEx(szFile2Short, szFile2, ARRAYSIZE(szFile2Short), 0);
  6278. pszTemp = ShellConstructMessageString(HINST_THISDLL, szTemplate, szFile1Short, szFile2Short);
  6279. if (pszTemp)
  6280. {
  6281. hr = StringCchCopy(buffer, cchBuffer, pszTemp); // ok to truncate, just a message
  6282. LocalFree(pszTemp);
  6283. }
  6284. }
  6285. else
  6286. {
  6287. TCHAR *lpszFile1;
  6288. HDPA hdpaFull = hdpa;
  6289. // in the case of delete (where ther's an hdsa)
  6290. // we need to add in the names of folders deleted
  6291. // we do this by cloning the hdpa and tacking on our names.
  6292. if (lpud->hdsa)
  6293. {
  6294. hdpaFull = DPA_Clone(hdpa, NULL);
  6295. if (hdpaFull)
  6296. {
  6297. int iMax;
  6298. int i;
  6299. LPFOUNDO_DELETEDFILEINFO lpdfi;
  6300. iMax = DSA_GetItemCount(lpud->hdsa);
  6301. for (i = 0; i < iMax; i++)
  6302. {
  6303. lpdfi = DSA_GetItemPtr(lpud->hdsa, i);
  6304. DPA_AppendPtr(hdpaFull, lpdfi->lpszName);
  6305. DPA_AppendPtr(hdpaFull, lpdfi->lpszName);
  6306. }
  6307. }
  6308. else
  6309. {
  6310. hdpaFull = hdpa;
  6311. }
  6312. }
  6313. lpszFile1 = DPA_ToQuotedFileList(hdpaFull, 0, DPA_GetPtrCount(hdpaFull), 2);
  6314. StringCchPrintf(buffer, cchBuffer, szTemplate, lpszFile1); // ok to truncate, just a message
  6315. LocalFree((HLOCAL)lpszFile1);
  6316. if (hdpaFull != hdpa)
  6317. {
  6318. DPA_Destroy(hdpaFull);
  6319. }
  6320. }
  6321. }
  6322. }
  6323. void CALLBACK FOUndo_Release(UNDOATOM *lpua)
  6324. {
  6325. LPFOUNDODATA lpud = (LPFOUNDODATA)lpua->lpData;
  6326. int i;
  6327. LPTSTR lpsz;
  6328. if (lpud && (lpud != (void *)-1))
  6329. {
  6330. HDPA hdpa = lpud->hdpa;
  6331. HDSA hdsa = lpud->hdsa;
  6332. if (hdpa)
  6333. {
  6334. i = DPA_GetPtrCount(hdpa) - 1;
  6335. for (; i >= 0; i--)
  6336. {
  6337. lpsz = DPA_FastGetPtr(hdpa, i);
  6338. Str_SetPtr(&lpsz, NULL);
  6339. }
  6340. DPA_Destroy(hdpa);
  6341. }
  6342. if (hdsa)
  6343. {
  6344. LPFOUNDO_DELETEDFILEINFO lpdfi;
  6345. i = DSA_GetItemCount(hdsa) - 1;
  6346. for (; i >= 0 ; i--)
  6347. {
  6348. lpdfi = DSA_GetItemPtr(hdsa, i);
  6349. Str_SetPtr(&lpdfi->lpszName, NULL);
  6350. }
  6351. DSA_Destroy(hdsa);
  6352. }
  6353. LocalFree(lpud);
  6354. lpua->lpData = (void *)-1;
  6355. }
  6356. }
  6357. DWORD WINAPI FOUndo_InvokeThreadInit(UNDOATOM *lpua)
  6358. {
  6359. LPFOUNDODATA lpud = (LPFOUNDODATA)lpua->lpData;
  6360. HDPA hdpa = lpud->hdpa;
  6361. HWND hwnd = lpua->hwnd;
  6362. BOOL fNukeAtom = TRUE;
  6363. SHFILEOPSTRUCT sFileOp =
  6364. {
  6365. hwnd,
  6366. 0,
  6367. NULL,
  6368. NULL,
  6369. 0,
  6370. } ;
  6371. int iMax;
  6372. SuspendUndo(TRUE);
  6373. iMax = DPA_GetPtrCount(hdpa);
  6374. switch (lpua->uType)
  6375. {
  6376. case IDS_RENAME:
  6377. {
  6378. TCHAR szFromPath[MAX_PATH + 1];
  6379. if (iMax < 2)
  6380. goto Exit;
  6381. sFileOp.wFunc = FO_RENAME;
  6382. sFileOp.pFrom = DPA_GetPtr(hdpa, 1);
  6383. sFileOp.pTo = DPA_GetPtr(hdpa, 0);
  6384. if (sFileOp.pFrom && sFileOp.pTo)
  6385. {
  6386. HRESULT hr = StringCchCopy(szFromPath, ARRAYSIZE(szFromPath), sFileOp.pFrom);
  6387. if (SUCCEEDED(hr))
  6388. {
  6389. szFromPath[lstrlen(sFileOp.pFrom) + 1] = 0;
  6390. sFileOp.pFrom = szFromPath;
  6391. SHFileOperation(&sFileOp);
  6392. if (sFileOp.fAnyOperationsAborted)
  6393. {
  6394. fNukeAtom = FALSE;
  6395. }
  6396. }
  6397. }
  6398. // In the rename case the DPA owns these pointers, in all other cases
  6399. // they must be freed below. To prevent freeing these during a rename
  6400. // we NULL them out when we're done with them
  6401. sFileOp.pFrom = NULL;
  6402. sFileOp.pTo = NULL;
  6403. }
  6404. break;
  6405. case IDS_COPY:
  6406. sFileOp.pFrom = DPA_ToFileList(hdpa, 1, iMax - 1, 2);
  6407. if (!sFileOp.pFrom)
  6408. goto Exit;
  6409. sFileOp.wFunc = FO_DELETE;
  6410. //
  6411. // If this delete is occuring because of an automatic undo caused by
  6412. // connected files, then do not ask for confirmation.
  6413. //
  6414. if (lpua->foFlags & FOF_NOCONFIRMATION)
  6415. sFileOp.fFlags |= FOF_NOCONFIRMATION;
  6416. SHFileOperation(&sFileOp);
  6417. if (sFileOp.fAnyOperationsAborted)
  6418. {
  6419. fNukeAtom = FALSE;
  6420. }
  6421. break;
  6422. case IDS_MOVE:
  6423. sFileOp.pFrom = DPA_ToFileList(hdpa, 1, iMax-1, 2);
  6424. sFileOp.pTo = DPA_ToFileList(hdpa, 0, iMax-2, 2);
  6425. if (!sFileOp.pFrom || !sFileOp.pTo)
  6426. goto Exit;
  6427. sFileOp.wFunc = FO_MOVE;
  6428. sFileOp.fFlags = FOF_MULTIDESTFILES;
  6429. if (lpua->foFlags & FOF_NOCOPYSECURITYATTRIBS)
  6430. {
  6431. sFileOp.fFlags |= FOF_NOCOPYSECURITYATTRIBS;
  6432. }
  6433. SHFileOperation(&sFileOp);
  6434. if (sFileOp.fAnyOperationsAborted)
  6435. {
  6436. fNukeAtom = FALSE;
  6437. }
  6438. break;
  6439. case IDS_DELETE:
  6440. {
  6441. // first create any directories
  6442. if (lpud->hdsa)
  6443. {
  6444. HDSA hdsa = lpud->hdsa;
  6445. int i;
  6446. // do it in reverse order to get the parentage right
  6447. for (i = DSA_GetItemCount(hdsa) - 1; i >= 0; i--)
  6448. {
  6449. LPFOUNDO_DELETEDFILEINFO lpdfi = DSA_GetItemPtr(hdsa, i);
  6450. if (lpdfi)
  6451. {
  6452. if (Win32CreateDirectory(lpdfi->lpszName, NULL))
  6453. {
  6454. SetFileAttributes(lpdfi->lpszName, lpdfi->dwAttributes & ~FILE_ATTRIBUTE_DIRECTORY);
  6455. }
  6456. }
  6457. }
  6458. }
  6459. if (iMax)
  6460. {
  6461. sFileOp.pFrom = DPA_ToFileList(hdpa, 1, iMax-1, 2);
  6462. sFileOp.pTo = DPA_ToFileList(hdpa, 0, iMax-2, 2);
  6463. if (!sFileOp.pFrom || !sFileOp.pTo)
  6464. goto Exit;
  6465. UndoBBFileDelete(sFileOp.pTo, sFileOp.pFrom);
  6466. }
  6467. break;
  6468. }
  6469. }
  6470. SHChangeNotify(0, SHCNF_FLUSH | SHCNF_FLUSHNOWAIT, NULL, NULL);
  6471. Exit:
  6472. if (sFileOp.pFrom)
  6473. LocalFree((HLOCAL)sFileOp.pFrom);
  6474. if (sFileOp.pTo)
  6475. LocalFree((HLOCAL)sFileOp.pTo);
  6476. SuspendUndo(FALSE);
  6477. if (fNukeAtom)
  6478. NukeUndoAtom(lpua);
  6479. return 1;
  6480. }
  6481. void CALLBACK FOUndo_Invoke(UNDOATOM *lpua)
  6482. {
  6483. DWORD idThread;
  6484. HANDLE hthread = CreateThread(NULL, 0, FOUndo_InvokeThreadInit, lpua, 0, &idThread);
  6485. if (hthread)
  6486. CloseHandle(hthread);
  6487. }
  6488. UNDOATOM *FOAllocUndoAtom(LPSHFILEOPSTRUCT lpfo)
  6489. {
  6490. UNDOATOM *lpua = (UNDOATOM *)LocalAlloc(LPTR, sizeof(*lpua));
  6491. if (lpua)
  6492. {
  6493. lpua->uType = FOFuncToStringID(lpfo->wFunc);
  6494. lpua->GetText = FOUndo_GetText;
  6495. lpua->Invoke = FOUndo_Invoke;
  6496. lpua->Release = FOUndo_Release;
  6497. lpua->foFlags = 0;
  6498. if (lpfo->fFlags & FOF_NOCOPYSECURITYATTRIBS)
  6499. {
  6500. lpua->foFlags |= FOF_NOCOPYSECURITYATTRIBS;
  6501. }
  6502. }
  6503. return lpua;
  6504. }
  6505. //============================================================================
  6506. //
  6507. // The following function is the mainline function for COPYing, RENAMEing,
  6508. // DELETEing, and MOVEing single or multiple files.
  6509. //
  6510. // in:
  6511. // hwnd the parent to create the progress dialog from if FOF_CREATEPROGRESSDLG is set.
  6512. //
  6513. //
  6514. // wFunc operation to be performed:
  6515. // FO_DELETE - Delete files in pFrom (pTo unused)
  6516. // FO_RENAME - Rename files
  6517. // FO_MOVE - Move files in pFrom to pTo
  6518. // FO_COPY - Copy files in pFrom to pTo
  6519. //
  6520. // pFrom list of source file specs either qualified or
  6521. // unqualified. unqualified names will be qualified based on the current
  6522. // global current directories. examples include
  6523. // "foo.txt bar.txt *.bak ..\*.old dir_name"
  6524. //
  6525. // pTo destination file spec.
  6526. //
  6527. // fFlags flags that control the operation
  6528. //
  6529. // returns:
  6530. // 0 indicates success
  6531. // != 0 is the DE_ (dos error code) of last failed operation
  6532. //
  6533. //
  6534. //===========================================================================
  6535. int WINAPI SHFileOperation(LPSHFILEOPSTRUCT lpfo)
  6536. {
  6537. int ret;
  6538. BOOL bRecycledStuff = FALSE;
  6539. COPY_STATE *pcs;
  6540. if (!lpfo || !lpfo->pFrom)
  6541. {
  6542. // return an error instead of waiting to AV
  6543. return ERROR_INVALID_PARAMETER;
  6544. }
  6545. lpfo->fAnyOperationsAborted = FALSE;
  6546. lpfo->hNameMappings = NULL;
  6547. if (lpfo->wFunc < FO_MOVE || lpfo->wFunc > FO_RENAME) // validate
  6548. {
  6549. // NOTE: We used to return 0 here (win95gold -> IE401).
  6550. //
  6551. // If we run into app compat bugs because they were relying on the old
  6552. // buggy return value, then add an app hack here.
  6553. //
  6554. // this is not a DE_ error, and I don't care!
  6555. return ERROR_INVALID_PARAMETER;
  6556. }
  6557. pcs = (COPY_STATE*)LocalAlloc(LPTR, sizeof(COPY_STATE));
  6558. if (!pcs)
  6559. {
  6560. return ERROR_NOT_ENOUGH_MEMORY;
  6561. }
  6562. pcs->nRef = 1;
  6563. //
  6564. // REVIEW: We want to allow copying of a file within a given directory
  6565. // by having default renaming on collisions within a directory.
  6566. //
  6567. if (!(lpfo->fFlags & FOF_NOCONFIRMATION))
  6568. {
  6569. pcs->cd.fConfirm =
  6570. CONFIRM_DELETE_FILE |
  6571. CONFIRM_DELETE_FOLDER |
  6572. CONFIRM_REPLACE_FILE |
  6573. CONFIRM_REPLACE_FOLDER |
  6574. CONFIRM_WONT_RECYCLE_FILE |
  6575. CONFIRM_WONT_RECYCLE_FOLDER |
  6576. CONFIRM_PATH_TOO_LONG |
  6577. // CONFIRM_MOVE_FILE |
  6578. // CONFIRM_MOVE_FOLDER |
  6579. // CONFIRM_RENAME_FILE |
  6580. // CONFIRM_RENAME_FOLDER |
  6581. CONFIRM_SYSTEM_FILE |
  6582. CONFIRM_READONLY_FILE |
  6583. CONFIRM_MULTIPLE |
  6584. CONFIRM_PROGRAM_FILE |
  6585. CONFIRM_STREAMLOSS |
  6586. CONFIRM_FAILED_ENCRYPT |
  6587. CONFIRM_LFNTOFAT |
  6588. CONFIRM_WONT_RECYCLE_OFFLINE |
  6589. CONFIRM_LOST_ENCRYPT_FILE |
  6590. CONFIRM_LOST_ENCRYPT_FOLDER;
  6591. }
  6592. if (lpfo->fFlags & FOF_WANTNUKEWARNING)
  6593. {
  6594. // We will warn the user that the thing they thought was going to be recycled is
  6595. // now really going to be nuked. (eg drag-drop folder on recycle bin, but it turns
  6596. // out that the folder is too big for the bitbucket, so we confirm on the wont-recycle
  6597. // cases).
  6598. //
  6599. // Also, we keep the system file / readonly file / progran file warnings around for good
  6600. // measure.
  6601. pcs->cd.fConfirm |= CONFIRM_WONT_RECYCLE_FILE |
  6602. CONFIRM_WONT_RECYCLE_FOLDER |
  6603. CONFIRM_PATH_TOO_LONG |
  6604. CONFIRM_SYSTEM_FILE |
  6605. CONFIRM_READONLY_FILE |
  6606. CONFIRM_PROGRAM_FILE |
  6607. CONFIRM_WONT_RECYCLE_OFFLINE;
  6608. }
  6609. pcs->fFlags = lpfo->fFlags; // duplicate some stuff here
  6610. pcs->lpszProgressTitle = lpfo->lpszProgressTitle;
  6611. pcs->lpfo = lpfo;
  6612. // Check to see if we need to operate on the "connected" files and folders too!
  6613. if (!(pcs->fFlags & FOF_NO_CONNECTED_ELEMENTS))
  6614. {
  6615. DWORD dwFileFolderConnection = 0;
  6616. DWORD dwSize = sizeof(dwFileFolderConnection);
  6617. DWORD dwType = REG_DWORD;
  6618. if (SHGetValue(HKEY_CURRENT_USER, REGSTR_PATH_EXPLORER,
  6619. REG_VALUE_NO_FILEFOLDER_CONNECTION, &dwType, &dwFileFolderConnection,
  6620. &dwSize) == ERROR_SUCCESS)
  6621. {
  6622. //If the registry says "No connection", then set the flags accordingly.
  6623. if (dwFileFolderConnection == 1)
  6624. {
  6625. pcs->fFlags = pcs->fFlags | FOF_NO_CONNECTED_ELEMENTS;
  6626. }
  6627. }
  6628. }
  6629. // Always create a progress dialog
  6630. // Note that it will be created invisible, and will be shown if the
  6631. // operation takes longer than a second to perform
  6632. // Note the parent of this window is NULL so it will get the QUERYENDSESSION
  6633. // message
  6634. if (!(pcs->fFlags & FOF_SILENT))
  6635. {
  6636. SHCreateThread(FOUIThreadProc, pcs, 0, AddRefPCS);
  6637. }
  6638. else
  6639. {
  6640. // To be compatible with Win95 semantics...
  6641. if (!lpfo->hwnd)
  6642. {
  6643. pcs->fFlags |= FOF_NOERRORUI;
  6644. }
  6645. }
  6646. if (lpfo->hwnd)
  6647. {
  6648. // The caller will be disabled if we ever show the progress window
  6649. // We need to make sure this is not disabled now because if it is and
  6650. // another dialog uses this as its parent, USER code will tell this
  6651. // window it got the focus while it is still disabled, which keeps it
  6652. // from passing that focus down to its children
  6653. // EnableWindow(lpfo->hwnd, FALSE);
  6654. pcs->hwndDlgParent = lpfo->hwnd;
  6655. }
  6656. // do this always.. even if this is not an undoable op, we could be
  6657. // affecting something that is.
  6658. SuspendUndo(TRUE);
  6659. if (lpfo->fFlags & FOF_ALLOWUNDO)
  6660. {
  6661. pcs->lpua = FOAllocUndoAtom(lpfo);
  6662. if (lpfo->wFunc == FO_DELETE)
  6663. {
  6664. // We check the shell state to see if the user has turned on the
  6665. // "Don't confirm deleting recycle bin contents" flag. If yes,
  6666. // then we store this flag and check against it if this case occures.
  6667. // Review: Not a super common case, why not just check when the
  6668. // flag is actually needed?
  6669. SHELLSTATE ss;
  6670. SHGetSetSettings(&ss, SSF_NOCONFIRMRECYCLE, FALSE);
  6671. pcs->fNoConfirmRecycle = ss.fNoConfirmRecycle;
  6672. if (InitBBGlobals())
  6673. {
  6674. // since we are going to be recycling stuff, we add ourselves to the
  6675. // global list of threads who are recycling
  6676. SHGlobalCounterIncrement(g_hgcNumDeleters);
  6677. bRecycledStuff = TRUE;
  6678. }
  6679. else
  6680. {
  6681. // this shouldnt happen, but if it does we can't send stuff to the Recycle
  6682. // Bin, instead we remove the undo flag so that everything is really nuked.
  6683. lpfo->fFlags &= ~FOF_ALLOWUNDO;
  6684. LocalFree(pcs->lpua);
  6685. pcs->lpua = NULL;
  6686. }
  6687. }
  6688. }
  6689. // While doing the file operation, tell PnP not to suspend
  6690. // the machine. Otherwise, you could be copying a lot of files
  6691. // over the network and have the laptop suddenly hibernate on you
  6692. // because PnP thought you were idle.
  6693. //
  6694. // Indicate that we only need the system. It's okay if the display
  6695. // goes into low-power mode, as long as we can keep copying.
  6696. //
  6697. SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
  6698. ret = MoveCopyDriver(pcs);
  6699. SetThreadExecutionState(ES_CONTINUOUS);
  6700. if (pcs->bAbort)
  6701. {
  6702. ASSERT(pcs->lpfo == lpfo);
  6703. lpfo->fAnyOperationsAborted = TRUE;
  6704. }
  6705. if (bRecycledStuff)
  6706. {
  6707. SHUpdateRecycleBinIcon();
  6708. if (0 == SHGlobalCounterDecrement(g_hgcNumDeleters))
  6709. {
  6710. // We were the last guy who was deleting stuff. Thus, we need to
  6711. // check to see if any of the bitbuckets info files neeed compacting or purging
  6712. CheckCompactAndPurge();
  6713. }
  6714. }
  6715. if (pcs->lpCopyBuffer)
  6716. {
  6717. LocalFree((HLOCAL)pcs->lpCopyBuffer);
  6718. pcs->lpCopyBuffer = NULL;
  6719. }
  6720. if (pcs->lpua)
  6721. {
  6722. if (pcs->lpua->lpData && (pcs->lpua->lpData != (void *)-1))
  6723. {
  6724. AddUndoAtom(pcs->lpua);
  6725. }
  6726. else
  6727. {
  6728. FOUndo_Release(pcs->lpua);
  6729. NukeUndoAtom(pcs->lpua);
  6730. }
  6731. }
  6732. // NTRAID89119 (toddb): This code is totally busted in respect to mounted volumes.
  6733. // We will send a change notify for the drive on which your volume is mounted
  6734. // instead of on the volume which actually had a free space change. We need
  6735. // to update PathGetDriveNumber to handle mounted volumes
  6736. // notify of freespace changes
  6737. // rename doesn't change drive usage
  6738. if (lpfo->wFunc != FO_RENAME)
  6739. {
  6740. int idDriveSrc;
  6741. int idDriveDest = -1;
  6742. DWORD dwDrives = 0; // bitfield for drives
  6743. if (lpfo->wFunc == FO_COPY)
  6744. {
  6745. // nothing changes on the source
  6746. idDriveSrc = -1;
  6747. }
  6748. else
  6749. {
  6750. idDriveSrc = PathGetDriveNumber(lpfo->pFrom);
  6751. }
  6752. if (lpfo->pTo)
  6753. {
  6754. idDriveDest = PathGetDriveNumber(lpfo->pTo);
  6755. }
  6756. if ((lpfo->wFunc == FO_MOVE) && (idDriveDest == idDriveSrc))
  6757. {
  6758. // no freespace nothing changes
  6759. idDriveSrc = -1;
  6760. idDriveDest = -1;
  6761. }
  6762. // NTRAID89119: What if idDriveSrc or idDriveDest are > 32? This is totally
  6763. // possible under NT by using mounted volumes. SHChangeNotify is busted
  6764. // in this respect.
  6765. if (idDriveSrc != -1)
  6766. {
  6767. dwDrives |= (1 << idDriveSrc);
  6768. }
  6769. if (idDriveDest != -1)
  6770. {
  6771. dwDrives |= (1 << idDriveDest);
  6772. }
  6773. if (dwDrives)
  6774. {
  6775. SHChangeNotify(SHCNE_FREESPACE, SHCNF_DWORD, IntToPtr(dwDrives), 0);
  6776. }
  6777. }
  6778. SuspendUndo(FALSE);
  6779. if (!(lpfo->fFlags & FOF_WANTMAPPINGHANDLE))
  6780. {
  6781. SHFreeNameMappings(lpfo->hNameMappings);
  6782. lpfo->hNameMappings = NULL;
  6783. }
  6784. // shut down the progress dialog
  6785. //
  6786. // this is necessary so that the ui thread won't block
  6787. pcs->fProgressOk = TRUE;
  6788. ENTERCRITICAL; // need to take critsec to sync w/ the UI thread
  6789. pcs->fDone = TRUE;
  6790. if (pcs->hwndProgress)
  6791. {
  6792. PostMessage(pcs->hwndProgress, PDM_NOOP, 0, 0);
  6793. }
  6794. LEAVECRITICAL;
  6795. if (lpfo->hwnd)
  6796. {
  6797. EnableWindow(lpfo->hwnd, TRUE);
  6798. }
  6799. ReleasePCS(pcs);
  6800. return ret;
  6801. }
  6802. #ifdef UNICODE
  6803. int WINAPI SHFileOperationA(LPSHFILEOPSTRUCTA lpfo)
  6804. {
  6805. int iResult;
  6806. UINT uTotalSize;
  6807. UINT uSize;
  6808. UINT uSizeTitle;
  6809. UINT uSizeW;
  6810. SHFILEOPSTRUCTW shop;
  6811. LPCSTR lpAnsi;
  6812. LPWSTR lpBuffer;
  6813. LPWSTR lpTemp;
  6814. COMPILETIME_ASSERT(sizeof(SHFILEOPSTRUCTW) == sizeof(SHFILEOPSTRUCTA));
  6815. hmemcpy(&shop, lpfo, sizeof(SHFILEOPSTRUCTW));
  6816. //
  6817. // Thunk the strings as appropriate
  6818. //
  6819. uTotalSize = 0;
  6820. if (lpfo->pFrom)
  6821. {
  6822. lpAnsi = lpfo->pFrom;
  6823. do {
  6824. uSize = lstrlenA(lpAnsi) + 1;
  6825. uTotalSize += uSize;
  6826. lpAnsi += uSize;
  6827. } while (uSize != 1);
  6828. }
  6829. if (lpfo->pTo)
  6830. {
  6831. lpAnsi = lpfo->pTo;
  6832. do {
  6833. uSize = lstrlenA(lpAnsi) + 1;
  6834. uTotalSize += uSize;
  6835. lpAnsi += uSize;
  6836. } while (uSize != 1);
  6837. }
  6838. if ((lpfo->fFlags & FOF_SIMPLEPROGRESS) && lpfo->lpszProgressTitle != NULL)
  6839. {
  6840. uSizeTitle = lstrlenA(lpfo->lpszProgressTitle) + 1;
  6841. uTotalSize += uSizeTitle;
  6842. }
  6843. if (uTotalSize != 0)
  6844. {
  6845. lpTemp = lpBuffer = LocalAlloc(LPTR, uTotalSize*sizeof(WCHAR));
  6846. if (!lpBuffer)
  6847. {
  6848. SetLastError(ERROR_OUTOFMEMORY);
  6849. return ERROR_OUTOFMEMORY;
  6850. }
  6851. }
  6852. else
  6853. {
  6854. lpBuffer = NULL;
  6855. }
  6856. //
  6857. // Now convert the strings
  6858. //
  6859. if (lpfo->pFrom)
  6860. {
  6861. shop.pFrom = lpTemp;
  6862. lpAnsi = lpfo->pFrom;
  6863. do
  6864. {
  6865. uSize = lstrlenA(lpAnsi) + 1;
  6866. uSizeW = MultiByteToWideChar(CP_ACP, 0,
  6867. lpAnsi, uSize,
  6868. lpTemp, uSize);
  6869. lpAnsi += uSize;
  6870. lpTemp += uSizeW;
  6871. } while (uSize != 1);
  6872. }
  6873. else
  6874. {
  6875. shop.pFrom = NULL;
  6876. }
  6877. if (lpfo->pTo)
  6878. {
  6879. shop.pTo = lpTemp;
  6880. lpAnsi = lpfo->pTo;
  6881. do
  6882. {
  6883. uSize = lstrlenA(lpAnsi) + 1;
  6884. uSizeW = MultiByteToWideChar(CP_ACP, 0,
  6885. lpAnsi, uSize,
  6886. lpTemp, uSize);
  6887. lpAnsi += uSize;
  6888. lpTemp += uSizeW;
  6889. } while (uSize != 1);
  6890. }
  6891. else
  6892. {
  6893. shop.pTo = NULL;
  6894. }
  6895. if ((lpfo->fFlags & FOF_SIMPLEPROGRESS) && lpfo->lpszProgressTitle != NULL)
  6896. {
  6897. shop.lpszProgressTitle = lpTemp;
  6898. MultiByteToWideChar(CP_ACP, 0,
  6899. lpfo->lpszProgressTitle, uSizeTitle,
  6900. lpTemp, uSizeTitle);
  6901. }
  6902. else
  6903. {
  6904. shop.lpszProgressTitle = NULL;
  6905. }
  6906. iResult = SHFileOperationW(&shop);
  6907. // link up the two things in the SHFILEOPSTRUCT that could have changed
  6908. lpfo->fAnyOperationsAborted = shop.fAnyOperationsAborted;
  6909. lpfo->hNameMappings = shop.hNameMappings;
  6910. if (lpBuffer)
  6911. LocalFree(lpBuffer);
  6912. return iResult;
  6913. }
  6914. #else
  6915. int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpfo)
  6916. {
  6917. return E_NOTIMPL;
  6918. }
  6919. #endif
  6920. // In:
  6921. // pcs: copy_state structure containing the state of the copy
  6922. //
  6923. // feedback: If the estimated time to copmplete a copy is larger than
  6924. // MINTIME4FEEDBACK, the user is given a time to completion estimate in minutes.
  6925. // The estimate is calculated using a MS_RUNAVG seconds running average. The
  6926. // initial estimate is done after MS_TIMESLICE
  6927. void SetProgressTime(COPY_STATE *pcs)
  6928. {
  6929. DWORD dwNow = GetTickCount();
  6930. if (pcs->dwPreviousTime)
  6931. {
  6932. int iPointsTotal = CountProgressPoints(pcs, &pcs->dth.dtAll);
  6933. int iPointsDone = CountProgressPoints(pcs, &pcs->dth.dtDone);
  6934. int iPointsDelta = iPointsDone - pcs->iLastProgressPoints;
  6935. DWORD dwTimeLeft;
  6936. //
  6937. // A couple of times the shell has reported bad time remaining
  6938. // we need to find out why.
  6939. //
  6940. ASSERT(iPointsTotal >= 0);
  6941. ASSERT(iPointsDone >= 0);
  6942. ASSERT(iPointsTotal >= iPointsDone);
  6943. ASSERT(iPointsDelta >= 0);
  6944. // has enough time elapsed to update the display
  6945. // We do this every 10 seconds, but we'll do the first one after
  6946. // only a few seconds
  6947. if (iPointsDelta && (iPointsDone > 0) && (dwNow - pcs->dwPreviousTime))
  6948. {
  6949. DWORD dwPointsPerSec;
  6950. DWORD dwTime; // how many tenths of a second have gone by
  6951. // We take 10 times the number of Points and divide by the number of
  6952. // tenths of a second to minimize both overflow and roundoff
  6953. dwTime = (dwNow - pcs->dwPreviousTime)/100;
  6954. if (dwTime == 0)
  6955. dwTime = 1;
  6956. dwPointsPerSec = iPointsDelta * 10 / dwTime;
  6957. if (!dwPointsPerSec)
  6958. {
  6959. // This could happen if the net went to sleep for a couple
  6960. // minutes while trying to copy a small (512 byte) buffer
  6961. dwPointsPerSec = 1;
  6962. }
  6963. // if we didn't have enough time to get a good sample,
  6964. // don't use this last bit as a time estimater
  6965. if ((dwNow - pcs->dwPreviousTime) < (MS_TIMESLICE/2))
  6966. {
  6967. dwPointsPerSec = pcs->dwPointsPerSec;
  6968. }
  6969. if (pcs->dwPointsPerSec)
  6970. {
  6971. // Take a weighted average of the current transfer rate and the
  6972. // previously computed one, just to try to smooth out
  6973. // some random fluctuations
  6974. dwPointsPerSec = (dwPointsPerSec + (pcs->dwPointsPerSec * 2)) / 3;
  6975. }
  6976. // never allow 0 points per second.. just tack it on to next time
  6977. if (dwPointsPerSec)
  6978. {
  6979. pcs->dwPointsPerSec = dwPointsPerSec;
  6980. // Calculate time remaining (round up by adding 1)
  6981. // We only get here every 10 seconds, so always update
  6982. dwTimeLeft = ((iPointsTotal - iPointsDone) / dwPointsPerSec) + 1;
  6983. // It would be odd to show "1 second left" and then immediately
  6984. // clear it
  6985. if (dwTimeLeft >= MIN_MINTIME4FEEDBACK)
  6986. {
  6987. // display new estimate of time left
  6988. SetProgressTimeEst(pcs, dwTimeLeft);
  6989. }
  6990. }
  6991. }
  6992. // Reset previous time and # of Points read
  6993. pcs->dwPreviousTime = dwNow;
  6994. pcs->iLastProgressPoints = iPointsDone;
  6995. }
  6996. }
  6997. void InitClipConfirmDlg(HWND hDlg, CONFDLG_DATA *pcd)
  6998. {
  6999. TCHAR szMessage[255];
  7000. TCHAR szDeleteWarning[80];
  7001. SHFILEINFO sfiDest;
  7002. LPTSTR pszFileDest = NULL;
  7003. LPTSTR pszMsg, pszSource;
  7004. int i;
  7005. int cxWidth;
  7006. RECT rc;
  7007. // get the size of the text boxes
  7008. GetWindowRect(GetDlgItem(hDlg, pcd->idText), &rc);
  7009. cxWidth = rc.right - rc.left;
  7010. // get the source display name
  7011. pszSource = PathFindFileName(pcd->pFileSource);
  7012. PathCompactPath(NULL, pszSource, cxWidth);
  7013. // get the dest display name
  7014. SHGetFileInfo(pcd->pFileDest, 0,
  7015. &sfiDest, sizeof(sfiDest), SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES);
  7016. pszFileDest = sfiDest.szDisplayName;
  7017. PathCompactPath(NULL, pszFileDest, cxWidth);
  7018. // if we're supposed to show the date info, grab the icons and format the date string
  7019. if (pcd->bShowDates)
  7020. {
  7021. SHFILEINFO sfi2;
  7022. TCHAR szDateSrc[64], szDateDest[64];
  7023. // likely that this data may be incomplete... leave it saying "Unknown date and size"
  7024. if (BuildDateLine(szDateSrc, ARRAYSIZE(szDateSrc), pcd->pfdSource, pcd->pFileSource))
  7025. SetDlgItemText(hDlg, IDD_FILEINFO_NEW, szDateSrc);
  7026. BuildDateLine(szDateDest, ARRAYSIZE(szDateSrc), pcd->pfdDest, pcd->pFileDest);
  7027. SetDlgItemText(hDlg, IDD_FILEINFO_OLD, szDateDest);
  7028. SHGetFileInfo(pcd->pFileSource, pcd->pfdSource ? pcd->pfdSource->dwFileAttributes : 0, &sfi2, sizeof(sfi2),
  7029. pcd->pfdSource ? (SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_LARGEICON) : (SHGFI_ICON|SHGFI_LARGEICON));
  7030. ReplaceDlgIcon(hDlg, IDD_ICON_NEW, sfi2.hIcon);
  7031. SHGetFileInfo(pcd->pFileDest, pcd->pfdDest ? pcd->pfdDest->dwFileAttributes : 0, &sfi2, sizeof(sfi2),
  7032. pcd->pfdDest ? (SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_LARGEICON) : (SHGFI_ICON|SHGFI_LARGEICON));
  7033. ReplaceDlgIcon(hDlg, IDD_ICON_OLD, sfi2.hIcon);
  7034. }
  7035. // there are 5 controls:
  7036. // IDD_TEXT contains regular text (normal file/folder)
  7037. // IDD_TEXT1 through IDD_TEXT4 contain optional secondary text
  7038. for (i = IDD_TEXT; i <= IDD_TEXT4; i++)
  7039. {
  7040. if (i == pcd->idText)
  7041. {
  7042. szMessage[0] = 0;
  7043. GetDlgItemText(hDlg, i, szMessage, ARRAYSIZE(szMessage));
  7044. }
  7045. else
  7046. {
  7047. HWND hwndCtl = GetDlgItem(hDlg, i);
  7048. if (hwndCtl)
  7049. {
  7050. ShowWindow(hwndCtl, SW_HIDE);
  7051. }
  7052. }
  7053. }
  7054. szDeleteWarning[0] = 0;
  7055. pszMsg = ShellConstructMessageString(HINST_THISDLL, szMessage,
  7056. pszSource, pszFileDest, szDeleteWarning);
  7057. if (pszMsg)
  7058. {
  7059. SetDlgItemText(hDlg, pcd->idText, pszMsg);
  7060. LocalFree(pszMsg);
  7061. }
  7062. }
  7063. HRESULT FileDescToWin32FileData(LPFILEDESCRIPTOR pfdsc, LPWIN32_FIND_DATA pwfd)
  7064. {
  7065. ZeroMemory(pwfd, sizeof(*pwfd));
  7066. if (pfdsc->dwFlags & FD_ATTRIBUTES)
  7067. pwfd->dwFileAttributes = pfdsc->dwFileAttributes;
  7068. if (pfdsc->dwFlags & FD_CREATETIME)
  7069. hmemcpy(&pwfd->ftCreationTime, &pfdsc->ftCreationTime, sizeof(FILETIME));
  7070. if (pfdsc->dwFlags & FD_ACCESSTIME)
  7071. hmemcpy(&pwfd->ftLastAccessTime, &pfdsc->ftLastAccessTime, sizeof(FILETIME));
  7072. if (pfdsc->dwFlags & FD_WRITESTIME)
  7073. hmemcpy(&pwfd->ftLastWriteTime, &pfdsc->ftLastWriteTime, sizeof(FILETIME));
  7074. if (pfdsc->dwFlags & FD_FILESIZE)
  7075. {
  7076. pwfd->nFileSizeHigh = pfdsc->nFileSizeHigh;
  7077. pwfd->nFileSizeLow = pfdsc->nFileSizeLow;
  7078. }
  7079. return StringCchCopy(pwfd->cFileName, ARRAYSIZE(pwfd->cFileName), pfdsc->cFileName);
  7080. }
  7081. INT_PTR ValidateCreateFileFromClip(HWND hwnd, LPFILEDESCRIPTOR pfdscSrc, TCHAR *pszPathDest, PYNLIST pynl)
  7082. {
  7083. WIN32_FIND_DATA wfdSrc, wfdDest;
  7084. CONFDLG_DATA cdd;
  7085. CONFIRM_DATA cd;
  7086. COPY_STATE cs;
  7087. INT_PTR result;
  7088. HRESULT hr;
  7089. //
  7090. // If the destination does not exist, we are done.
  7091. //
  7092. HANDLE hff = FindFirstFile(pszPathDest, &wfdDest);
  7093. if (hff == INVALID_HANDLE_VALUE)
  7094. {
  7095. return IDYES;
  7096. }
  7097. FindClose(hff);
  7098. //
  7099. // Maybe this was just a short name collision and
  7100. // we can quickly get out of here.
  7101. //
  7102. if (ResolveShortNameCollisions(pszPathDest, &wfdDest))
  7103. {
  7104. return IDYES;
  7105. }
  7106. //
  7107. // Most of the helper functions want a WIN32_FILE_DATA
  7108. // and not a FILEDESCRIPTOR, so we create wfd for the
  7109. // source file on the fly.
  7110. //
  7111. hr = FileDescToWin32FileData(pfdscSrc, &wfdSrc);
  7112. if (FAILED(hr))
  7113. {
  7114. return IDNO;
  7115. }
  7116. //
  7117. // Take care of the easy cases - can't copy a file to a dir
  7118. // or a dir to a file.
  7119. //
  7120. if ((wfdDest.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
  7121. ((wfdSrc.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0))
  7122. {
  7123. ZeroMemory(&cs, sizeof(cs));
  7124. cs.hwndDlgParent = hwnd;
  7125. CopyError(&cs, wfdSrc.cFileName, pszPathDest, DE_FILEDESTISFLD | ERRORONDEST, FO_COPY, OPER_DOFILE);
  7126. return IDNO;
  7127. }
  7128. else if (((wfdDest.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) &&
  7129. (wfdSrc.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  7130. {
  7131. ZeroMemory(&cs, sizeof(cs));
  7132. cs.hwndDlgParent = hwnd;
  7133. CopyError(&cs, wfdSrc.cFileName, pszPathDest, DE_FLDDESTISFILE | ERRORONDEST, FO_COPY, OPER_DOFILE);
  7134. AddToNoList(pynl, pszPathDest);
  7135. return IDNO;
  7136. }
  7137. //
  7138. // We need a confirmation dialog. Fill in the
  7139. // ConfirmDialogData (cdd) here.
  7140. //
  7141. ZeroMemory(&cdd, sizeof(cdd));
  7142. cdd.InitConfirmDlg = InitClipConfirmDlg;
  7143. cdd.idText = IDD_TEXT;
  7144. cdd.pFileSource = pfdscSrc->cFileName;
  7145. cdd.pfdSource = &wfdSrc;
  7146. cdd.pFileDest = pszPathDest;
  7147. cdd.pfdDest = &wfdDest;
  7148. cdd.bShowDates = FALSE;
  7149. cdd.pcd = &cd;
  7150. ZeroMemory(&cd, sizeof(cd));
  7151. cd.fConfirm = CONFIRM_REPLACE_FILE;
  7152. cdd.fYesToAllMask = CONFIRM_REPLACE_FILE;
  7153. if (((wfdDest.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) &&
  7154. (wfdDest.dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY)))
  7155. {
  7156. if (wfdDest.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
  7157. {
  7158. cdd.idText = IDD_TEXT2;
  7159. }
  7160. else if (wfdDest.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
  7161. {
  7162. cdd.idText = IDD_TEXT1;
  7163. }
  7164. }
  7165. //
  7166. // What we do now depends on whether we are processing a directory
  7167. // or a file.
  7168. //
  7169. if (wfdDest.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  7170. {
  7171. //
  7172. // If this directory is already in the yes list,
  7173. // the parent directory must have already conflicted
  7174. // and the user said "yes, move the dir contents over".
  7175. //
  7176. if (IsInYesList(pynl, pszPathDest))
  7177. {
  7178. result = IDYES;
  7179. }
  7180. else
  7181. {
  7182. //
  7183. // Copying directory to a destination with the same directory.
  7184. //
  7185. result = DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_REPLACE_FOLDER), hwnd, ConfirmDlgProc, (LPARAM)&cdd);
  7186. if (result == IDYES)
  7187. {
  7188. if (cd.fConfirm & CONFIRM_REPLACE_FILE)
  7189. {
  7190. AddToYesList(pynl, pszPathDest);
  7191. }
  7192. else
  7193. {
  7194. SetYesToAll(pynl);
  7195. }
  7196. }
  7197. else if (result == IDNO)
  7198. {
  7199. AddToNoList(pynl, pszPathDest);
  7200. }
  7201. }
  7202. }
  7203. else
  7204. {
  7205. if (IsInYesList(pynl, pszPathDest))
  7206. {
  7207. result = IDYES;
  7208. }
  7209. else
  7210. {
  7211. //
  7212. // Copying a file to a destination with the same file.
  7213. //
  7214. cdd.bShowDates = TRUE;
  7215. result = DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_REPLACE_FILE), hwnd, ConfirmDlgProc, (LPARAM)&cdd);
  7216. if (result == IDYES)
  7217. {
  7218. if ((cd.fConfirm & CONFIRM_REPLACE_FILE) == 0)
  7219. {
  7220. SetYesToAll(pynl);
  7221. }
  7222. }
  7223. }
  7224. }
  7225. return result;
  7226. }
  7227. // We can get transient file locks for moving files, for example extracting a thumbnail on a background task
  7228. // so we wrap our single check in a loop of multiple checks with a short nap between. We'd expect
  7229. // to get the ERROR_SHARING_VIOLATION, but in practice we also say ERROR_ACCESS_DENIED, so we'll try that
  7230. // as well
  7231. #define MAX_DELETE_ATTEMPTS 5
  7232. #define SLEEP_DELETE_ATTEMPT 1000
  7233. BOOL _IsFileDeletable(LPCTSTR pszFile)
  7234. {
  7235. int iAttempt = 0;
  7236. BOOL bRet;
  7237. while (!(bRet = IsFileDeletable(pszFile)) && (iAttempt < MAX_DELETE_ATTEMPTS))
  7238. {
  7239. DWORD dwError = GetLastError();
  7240. if ((dwError == ERROR_ACCESS_DENIED) || (dwError == ERROR_SHARING_VIOLATION))
  7241. {
  7242. iAttempt++;
  7243. Sleep(SLEEP_DELETE_ATTEMPT);
  7244. }
  7245. else
  7246. {
  7247. break;
  7248. }
  7249. }
  7250. return (bRet);
  7251. }
  7252. BOOL _IsDirectoryDeletable(LPCTSTR pszDir)
  7253. {
  7254. int iAttempt = 0;
  7255. BOOL bRet;
  7256. while (!(bRet = IsDirectoryDeletable(pszDir)) && (iAttempt < MAX_DELETE_ATTEMPTS))
  7257. {
  7258. DWORD dwError = GetLastError();
  7259. if ((dwError == ERROR_ACCESS_DENIED) || (dwError == ERROR_SHARING_VIOLATION))
  7260. {
  7261. iAttempt++;
  7262. Sleep(SLEEP_DELETE_ATTEMPT);
  7263. }
  7264. else
  7265. {
  7266. break;
  7267. }
  7268. }
  7269. return (bRet);
  7270. }
  7271. // This function adds up the sizes of the files in pszDir, and
  7272. // also makes sure that all those files are "delete-able"
  7273. //
  7274. // return: ERROR_SUCCESS - everything is fine, all the files in the dir are deleteable
  7275. // else - the dir cant be deleted because something inside is non-deletable
  7276. //
  7277. // NOTE: other in-out params are in the pfdi
  7278. //
  7279. LONG CheckFolderSizeAndDeleteability(FOLDERDELETEINFO* pfdi, LPCOPY_STATE pcs)
  7280. {
  7281. LONG lRet = ERROR_SUCCESS; // keep stack to a minimum as this is a recursive function!
  7282. BOOL bHasChildren = FALSE;
  7283. if (FOQueryAbort(pcs))
  7284. return ERROR_CANCELLED;
  7285. // do the root specific processing
  7286. if (!pfdi->bProcessedRoot)
  7287. {
  7288. // since the the destination folder could be something like "DC100000.oldext", calculate how many characters are
  7289. // going to be in the new destination directory: "C:\recycler\sid" + "\" + "DC100000.oldext" == the new root directory length
  7290. pfdi->cchDelta = (pfdi->cchBBDir + 1 + 8 + 1 + lstrlen(PathFindExtension(pfdi->szDir))) - lstrlen(pfdi->szDir);
  7291. // set this so that we only do the above processing for the root folder
  7292. pfdi->bProcessedRoot = TRUE;
  7293. }
  7294. if (PathCombine(pfdi->szPath, pfdi->szDir, c_szStarDotStar))
  7295. {
  7296. HANDLE hfind = FindFirstFile(pfdi->szPath, &pfdi->fd);
  7297. if (hfind != INVALID_HANDLE_VALUE)
  7298. {
  7299. do
  7300. {
  7301. if (!PathIsDotOrDotDot(pfdi->fd.cFileName))
  7302. {
  7303. bHasChildren = TRUE;
  7304. // append the subfile/subfolder to the parent path
  7305. if (!PathCombine(pfdi->szPath, pfdi->szDir, pfdi->fd.cFileName))
  7306. {
  7307. // PathAppend failed, try to append the short name
  7308. if (!pfdi->fd.cAlternateFileName[0] || !PathCombine(pfdi->szPath, pfdi->szDir, pfdi->fd.cAlternateFileName))
  7309. {
  7310. // no alternate name or we failed to append that as well, assume we failed because the path is too long
  7311. lRet = ERROR_FILENAME_EXCED_RANGE;
  7312. // pass back the name of the non-deleteable file/folder in pfdi->szNonDeletableFile
  7313. StringCchCopy(pfdi->szNonDeletableFile, ARRAYSIZE(pfdi->szNonDeletableFile), pfdi->szPath); // truncation ok
  7314. }
  7315. }
  7316. if (lRet == ERROR_SUCCESS)
  7317. {
  7318. // we have to check to see if the path will exceed MAX_PATH if we were to move this file to the recycle
  7319. // bin (C:\Recycler\<sid>). the increase in path length due to the recycle bin dir could be enough to
  7320. // put us over MAX_PATH and we would have problems later.
  7321. if ((lstrlen(pfdi->szPath) + pfdi->cchDelta + 1) > MAX_PATH) // +1 for NULL
  7322. {
  7323. TraceMsg(TF_BITBUCKET, "CheckFolderSizeAndDeleteability: path '%s' would exceed MAX_PATH if moved to the recycle bin!", pfdi->szPath);
  7324. lRet = ERROR_FILENAME_EXCED_RANGE;
  7325. // pass back the name of the non-deleteable file/folder in pfdi->szNonDeletableFile
  7326. StringCchCopy(pfdi->szNonDeletableFile, ARRAYSIZE(pfdi->szNonDeletableFile), pfdi->szPath); // truncation ok
  7327. }
  7328. else
  7329. {
  7330. if (pfdi->fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  7331. {
  7332. HRESULT hr = StringCchCopy(pfdi->szDir, ARRAYSIZE(pfdi->szDir), pfdi->szPath);
  7333. if (SUCCEEDED(hr))
  7334. {
  7335. // its a directory, so recurse
  7336. lRet = CheckFolderSizeAndDeleteability(pfdi, pcs);
  7337. PathRemoveFileSpec(pfdi->szDir);
  7338. }
  7339. else
  7340. {
  7341. lRet = ERROR_FILENAME_EXCED_RANGE;
  7342. StringCchCopy(pfdi->szNonDeletableFile, ARRAYSIZE(pfdi->szNonDeletableFile), pfdi->szPath); // truncation ok
  7343. }
  7344. }
  7345. else
  7346. {
  7347. // its a file.
  7348. ULARGE_INTEGER ulTemp;
  7349. if (!_IsFileDeletable(pfdi->szPath))
  7350. {
  7351. // we cant delete this file, find out why
  7352. lRet = GetLastError();
  7353. ASSERT(lRet != ERROR_SUCCESS);
  7354. // pass back the name of the non-deleteable file in pfdi->szNonDeletableFile
  7355. StringCchCopy(pfdi->szNonDeletableFile, ARRAYSIZE(pfdi->szNonDeletableFile), pfdi->szPath); // truncation ok
  7356. }
  7357. ulTemp.LowPart = pfdi->fd.nFileSizeLow;
  7358. ulTemp.HighPart = pfdi->fd.nFileSizeHigh;
  7359. pfdi->cbSize += ulTemp.QuadPart;
  7360. }
  7361. }
  7362. }
  7363. }
  7364. } while ((lRet == ERROR_SUCCESS) && FindNextFile(hfind, &pfdi->fd));
  7365. FindClose(hfind);
  7366. // if this dir has no children, see if we can simply delete it
  7367. if (!bHasChildren && !_IsDirectoryDeletable(pfdi->szDir))
  7368. {
  7369. lRet = GetLastError();
  7370. ASSERT(lRet != ERROR_SUCCESS);
  7371. // pass back the name of the non-deleteable file in pfdi->szNonDeletableFile
  7372. StringCchCopy(pfdi->szNonDeletableFile, ARRAYSIZE(pfdi->szNonDeletableFile), pfdi->szDir); // truncation ok
  7373. }
  7374. }
  7375. else
  7376. {
  7377. // if FindFirstFile fails, check to see if the directory itself is deleteable
  7378. if (!_IsDirectoryDeletable(pfdi->szDir))
  7379. {
  7380. lRet = GetLastError();
  7381. ASSERT(lRet != ERROR_SUCCESS);
  7382. // pass back the name of the non-deleteable file in pfdi->szNonDeletableFile
  7383. StringCchCopy(pfdi->szNonDeletableFile, ARRAYSIZE(pfdi->szNonDeletableFile), pfdi->szDir); // truncation ok
  7384. }
  7385. }
  7386. }
  7387. else
  7388. {
  7389. // if PathCombine fails, assume its because the path is too long
  7390. lRet = ERROR_FILENAME_EXCED_RANGE;
  7391. // pass back the name of the non-deleteable file in pfdi->szNonDeletableFile
  7392. StringCchCopy(pfdi->szNonDeletableFile, ARRAYSIZE(pfdi->szNonDeletableFile), pfdi->szDir); // truncation ok
  7393. }
  7394. return lRet;
  7395. }
  7396. // This takes over for what BBDeleteFile used to do (init, check, delete)... but does it with the ability to cancel
  7397. BOOL DeleteFileBB(LPTSTR pszFile, UINT cchFile, INT *piRet, COPY_STATE *pcs, BOOL fIsDir, WIN32_FIND_DATA *pfd, HDPA *phdpaDeletedFiles)
  7398. {
  7399. ULARGE_INTEGER ulSize;
  7400. int idDrive = DriveIDFromBBPath(pszFile);
  7401. // Init
  7402. if (!BBDeleteFileInit(pszFile, piRet))
  7403. return FALSE;
  7404. // Check if we can delete this properly
  7405. if (fIsDir)
  7406. {
  7407. DWORD dwError;
  7408. HRESULT hr;
  7409. FOLDERDELETEINFO fdi = {0};
  7410. fdi.cchBBDir = BBRecyclePathLength(idDrive);
  7411. hr = StringCchCopy(fdi.szDir, ARRAYSIZE(fdi.szDir), pszFile);
  7412. if (FAILED(hr))
  7413. {
  7414. *piRet = BBDELETE_PATH_TOO_LONG;
  7415. return FALSE;
  7416. }
  7417. dwError = CheckFolderSizeAndDeleteability(&fdi, pcs);
  7418. if (dwError != ERROR_SUCCESS)
  7419. {
  7420. // if CheckFolderSizeAndDeleteability can fail if a file cant be recycled.
  7421. // In this case, it appends the name of the file to pszFile, so we know who
  7422. // the undeletable file is.
  7423. if ((dwError == ERROR_FILENAME_EXCED_RANGE) ||
  7424. (dwError == ERROR_BUFFER_OVERFLOW))
  7425. {
  7426. // it failed because a new path would be to long after being moveed under the "C:\recycler\sid" directory
  7427. *piRet = BBDELETE_PATH_TOO_LONG;
  7428. }
  7429. else if (dwError == ERROR_CANCELLED)
  7430. {
  7431. // user hit the cancel button
  7432. *piRet = BBDELETE_CANCELLED;
  7433. }
  7434. else
  7435. {
  7436. // must be a non-deletable directory, so set piRet = BBDELETE_CANNOT_DELETE so our caller
  7437. // can detect this case, also pass the name of the non-deletable file back out so we can give
  7438. // a better error message to the user
  7439. *piRet = BBDELETE_CANNOT_DELETE;
  7440. ASSERT(*fdi.szPath);
  7441. StringCchCopy(pszFile, cchFile, fdi.szNonDeletableFile); // truncation ok
  7442. }
  7443. TraceMsg(TF_BITBUCKET, "DeleteFileBB : early error (%x) on file (%s)", dwError, pszFile);
  7444. return FALSE;
  7445. }
  7446. ulSize.QuadPart = fdi.cbSize;
  7447. }
  7448. else
  7449. {
  7450. if (!_IsFileDeletable(pszFile))
  7451. {
  7452. // We set piRet = BBDELETE_CANNOT_DELETE so our caller can detect
  7453. // that this file cant be recycled.
  7454. *piRet = BBDELETE_CANNOT_DELETE;
  7455. return FALSE;
  7456. }
  7457. ulSize.LowPart = pfd->nFileSizeLow;
  7458. ulSize.HighPart = pfd->nFileSizeHigh;
  7459. }
  7460. // check to make sure it's not bigger than the allowed wastebasket..
  7461. if (!BBCheckDeleteFileSize(idDrive, ulSize))
  7462. {
  7463. // we set piRet = BBDELETE_SIZE_TOO_BIG so our caller can
  7464. // detect the "file/folder too big" case
  7465. *piRet = BBDELETE_SIZE_TOO_BIG;
  7466. return FALSE;
  7467. }
  7468. return BBDeleteFile(pszFile, piRet, pcs->lpua, fIsDir, phdpaDeletedFiles, ulSize);
  7469. }
  7470. void StartCopyEngine(HANDLE *phEventRunning)
  7471. {
  7472. SECURITY_ATTRIBUTES* psa = SHGetAllAccessSA();
  7473. if (psa)
  7474. {
  7475. *phEventRunning = CreateEvent(psa, TRUE, FALSE, L"ShellCopyEngineRunning");
  7476. if (*phEventRunning)
  7477. {
  7478. SetEvent(*phEventRunning);
  7479. }
  7480. }
  7481. }
  7482. void EndCopyEngine(HANDLE hEventRunning)
  7483. {
  7484. // signal that we're done. this will always trigger so there will be some weirdness if the
  7485. // user does simultaneous copies, but it's not worth making a semaphore to keep track.
  7486. SECURITY_ATTRIBUTES* psa = SHGetAllAccessSA();
  7487. if (psa)
  7488. {
  7489. HANDLE hEventFinished = CreateEvent(psa, TRUE, FALSE, L"ShellCopyEngineFinished");
  7490. if (hEventFinished)
  7491. {
  7492. SetEvent(hEventFinished);
  7493. CloseHandle(hEventFinished);
  7494. }
  7495. }
  7496. if (hEventRunning)
  7497. {
  7498. // close out the event that says we're running.
  7499. ResetEvent(hEventRunning);
  7500. CloseHandle(hEventRunning);
  7501. }
  7502. }
  7503. BOOL IsCopyEngineRunning()
  7504. {
  7505. BOOL bRet=FALSE;
  7506. SECURITY_ATTRIBUTES* psa = SHGetAllAccessSA();
  7507. if (psa)
  7508. {
  7509. HANDLE hEventCopyRunning = OpenEvent(SYNCHRONIZE, FALSE, L"ShellCopyEngineRunning");
  7510. if (hEventCopyRunning)
  7511. {
  7512. // probe the event with a wait, if it times out the copy engine isn't running so we're done.
  7513. bRet = (WAIT_OBJECT_0 == WaitForSingleObject(hEventCopyRunning, 0));
  7514. CloseHandle(hEventCopyRunning);
  7515. }
  7516. }
  7517. return bRet;
  7518. }