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

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