// Copyright (c) <1995-1999> Microsoft Corporation #include "shellprv.h" #pragma hdrstop #include #include #include // for TreeResetNamedSecurityInfo #include "shlwapip.h" // for SHGlobalCounterDecrement #include "ynlist.h" #define INTERNAL_COPY_ENGINE #include "copy.h" #include "shell32p.h" #include "control.h" #include "cdburn.h" #include "propsht.h" #include "prshtcpp.h" #define REG_VAL_GENERAL_RENAMEHTMLFILE TEXT("RenameHtmlFile") #define TF_DEBUGCOPY 0x00800000 #define VERBOSE_STATUS // REVIEW, we should tune this size down as small as we can // to get smoother multitasking (without effecting performance) #define COPYMAXBUFFERSIZE 0x10000 // 0xFFFF this is 32-bit code! #define MIN_MINTIME4FEEDBACK 5 // is it worth showing estimated time to completion feedback? #define MS_RUNAVG 10000 // ms, window for running average time to completion estimate #define MS_TIMESLICE 2000 // ms, (MUST be > 1000!) first average time to completion estimate #define MAXDIRDEPTH 128 // # of directories we will deal with recursivly #define SHOW_PROGRESS_TIMEOUT 1000 // 1 second #define MINSHOWTIME 1000 // 1 sec // progress dialog message #define PDM_SHUTDOWN WM_APP #define PDM_NOOP (WM_APP + 1) #define PDM_UPDATE (WM_APP + 2) #define OPER_MASK 0x0F00 #define OPER_ENTERDIR 0x0100 #define OPER_LEAVEDIR 0x0200 #define OPER_DOFILE 0x0300 #define OPER_ERROR 0x0400 #define FOFuncToStringID(wFunc) (IDS_UNDO_FILEOP + wFunc) // // The following is a list of folder suffixes in all international languages. This list is NOT // read from a resource because we do NOT want the strings in this list to be mistakenly localized. // This list will allow NT5 shell to operate on files created by any international version of // office 9. // This list is taken from "http://officeweb/specs/webclient/files.htm" // // WARNING: Do not localize the strings in this table. Do not make any changes to this table // without consulting AlanRa (Office9 PM) // static const LPCTSTR c_apszSuffixes[] = { TEXT(".files"), TEXT("_files"), TEXT("-Dateien"), TEXT("_fichiers"), TEXT("_bestanden"), TEXT("_file"), TEXT("_archivos"), TEXT("-filer"), TEXT("_tiedostot"), TEXT("_pliki"), TEXT("_soubory"), TEXT("_elemei"), TEXT("_ficheiros"), TEXT("_arquivos"), TEXT("_dosyalar"), TEXT("_datoteke"), TEXT("_fitxers"), TEXT("_failid"), TEXT("_fails"), TEXT("_bylos"), TEXT("_fajlovi"), TEXT("_fitxategiak"), }; // The reg value under HKCU\REGSTR_PATH_EXPLORER that specifies Connection ON/OFF switch #define REG_VALUE_NO_FILEFOLDER_CONNECTION TEXT("NoFileFolderConnection") //////////////////////////////////////////////////////////////////////////// ///// directory tree cache. // this is set if pdtnChild has not been traversed (as opposed to NULL which means // there are no children #define DTN_DELAYED ((PDIRTREENODE)-1) // DIRTREENODE is a node in a linked list/tree cache of the directory structure. // except for the top level (which is specified by the caller of the api), the order // are all files first, then all directories. typedef struct _dirtreenode { struct _dirtreenode *pdtnNext; // sibling struct _dirtreenode *pdtnChild; // head of children linked list struct _dirtreenode *pdtnParent; DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastWriteTime; DWORD nFileSizeLow; DWORD nFileSizeHigh; LARGE_INTEGER liFileSizeCopied; BOOL fNewRoot : 1; BOOL fDummy : 1; // this marks the node as a dummy node (a wildcard that didn't match anything) BOOL fConnectedElement : 1; // this marks the node as an element that was implicitly added // to the Move/Copy source list because of an office9 type of // connection established in the registry. //The following is a union because not all nodes need all the fields. union { // The following is valid only if fConnectedElement is FALSE. struct _dirtreenode *pdtnConnected; // The following structure is valid only if fConnectedElemet is TRUE. struct { LPTSTR pFromConnected; // if fNewRoot && fConnectedElement, then these two elements LPTSTR pToConnected; // have the pFrom and pTo. DWORD dwConfirmation; // The result of confirnation given by end-user } ConnectedInfo; }; TCHAR szShortName[14]; TCHAR szName[1]; // this struct is dynamic } DIRTREENODE, *PDIRTREENODE; typedef struct { BOOL fChanged; DWORD dwFiles; // number of files DWORD dwFolders; // number of folders LARGE_INTEGER liSize; // total size of all files } DIRTOTALS, *PDIRTOTALS; typedef struct { UINT oper; DIRTOTALS dtAll; // totals for all files DIRTOTALS dtDone; // totals of what's done BOOL fChangePosted; PDIRTREENODE pdtn; // first directory tree node PDIRTREENODE pdtnCurrent; PDIRTREENODE pdtnConnectedItems; //Pointer to the begining of connected elements node. TCHAR bDiskCheck[26]; // how much does each operation cost in the progress... int iFilePoints; int iFolderPoints; int iSizePoints; LPTSTR pTo; // this holds the top level target list LPTSTR pFrom; // this holds the top level source list BOOL fMultiDest; TCHAR szSrcPath[MAX_PATH]; TCHAR szDestPath[MAX_PATH]; // this is the current destination for pdtn and all it's children (not siblings) // lpszDestPath includes pdtn's first path component HDSA hdsaRenamePairs; } DIRTREEHEADER, *PDIRTREEHEADER; // We spend a lot of time creating simple PIDLs, so use this cache // to speed things up. typedef struct SIMPLEPIDLCACHE { IBindCtx *pbcFile; // Empty filesys bind context for files IBindCtx *pbcFolder; // Empty filesys bind context for folders IShellFolder *psfDesktop; // Desktop folder (for ParseDisplayName) int iInit; // 0 = not inited; 1 = inited; -1 = init failed IShellFolder *psf; // Current folder LPITEMIDLIST pidlFolder; // Current folder TCHAR szFolder[MAX_PATH]; // Current folder } SIMPLEPIDLCACHE, *PSIMPLEPIDLCACHE; typedef struct { int nRef; // struct reference count int nSourceFiles; LPTSTR lpCopyBuffer; // global file copy buffer UINT uSize; // size of this buffer FILEOP_FLAGS fFlags; // from SHFILEOPSTRUCT HWND hwndProgress; // dialog/progress window HWND hwndDlgParent; // parent window for message boxes CONFIRM_DATA cd; // confirmation stuff UNDOATOM *lpua; // the undo atom that this file operation will make BOOL fNoConfirmRecycle; BOOL bAbort; BOOL fMerge; // are we doing a merge of folders BOOL fDone; BOOL fProgressOk; BOOL fDTBuilt; BOOL fFromCDRom; // Clear readonly bits if copying from CDRom // folowing fields are used for giving estimated time for completion // feedback to the user during longer than MINTIME4FEEDBACK operations BOOL fFlushWrites; // Should we flush writes for destinations on slow links DWORD dwPreviousTime; // calculate transfer rate int iLastProgressPoints; // how many progress points we had the last time we updated the time est DWORD dwPointsPerSec; LPCTSTR lpszProgressTitle; LPSHFILEOPSTRUCT lpfo; DIRTREEHEADER dth; BOOL fInitialize; const WIN32_FIND_DATA* pfd; BOOL bStreamLossPossible; // Could stream loss happen in this directory? SIMPLEPIDLCACHE spc; } COPY_STATE, *LPCOPY_STATE; // we have a seperate struct that we pass off to the FOUIThread so that he can get to the pcs, // but since the FOUIThread can outlive the main thread (!!) in some cases, we can't let him have a // ref to pcs->lpfo since it is owned by SHFileOperations caller and we crash if we try to refrence // it after SHFileOperation returns and the caller has freed the memory. The only two things the // FOUIThread uses out of the pcs->lpfo are the wFunc and the lpszProgressTitle (to see if the // recycle bin was being emptied or not), so we make private copies of that info for the thread. typedef struct { COPY_STATE* pcs; UINT wFunc; BOOL bIsEmptyRBOp; } FOUITHREADINFO, *PFOUITHREADINFO; // Information to determine folder's movability to the recycle bin typedef struct { BOOL bProcessedRoot; // tells if we are the first call in the recursive chain and we need to do root-specific processing int cchBBDir; // count of characters in the recycle bin dir (eg "C:\Recycler\") int cchDelta; // count of characters that the path will increase (or decrease if negative) by when moved under the recycle bin directory ULONGLONG cbSize; // size of the folder TCHAR szNonDeletableFile[MAX_PATH]; // an output buffer that holds the name of the file that cannot be deleted, if one exists TCHAR szDir[MAX_PATH]; // input & scratch buffer for stack savings when recursing TCHAR szPath[MAX_PATH]; // scratch buffer for stack savings when recursing WIN32_FIND_DATA fd; // also for stack savings } FOLDERDELETEINFO; // function declarations void _ProcessNameMappings(LPTSTR pszTarget, UINT cchTarget, HDSA hdsaRenamePairs); int GetNameDialog(HWND hwnd, COPY_STATE *pcs, BOOL fMultiple,UINT wOp, LPTSTR pFrom, LPTSTR pTo); void AddRenamePairToHDSA(LPCTSTR pszOldPath, LPCTSTR pszNewPath, HDSA* phdsaRenamePairs); BOOL FOQueryAbort(COPY_STATE *pcs); UINT DTAllocConnectedItemNodes(PDIRTREEHEADER pdth, COPY_STATE *pcs, WIN32_FIND_DATA *pfd, LPTSTR pszPath, BOOL fRecurse, PDIRTREENODE *ppdtnConnectedItems); void CALLBACK FOUndo_Invoke(UNDOATOM *lpua); LONG CheckFolderSizeAndDeleteability(FOLDERDELETEINFO* pfdi, LPCOPY_STATE pcs); BOOL DeleteFileBB(LPTSTR pszFile, UINT cchFile, INT *piReturn, COPY_STATE *pcs, BOOL fIsDir, WIN32_FIND_DATA *pfd, HDPA *phdpaDeletedFiles); BOOL DTDiskCheck(PDIRTREEHEADER pdth, COPY_STATE *pcs, LPTSTR pszPath) { int iDrive = PathGetDriveNumber(pszPath); if (iDrive != -1) { if (!pdth->bDiskCheck[iDrive]) { HWND hwnd = pcs->hwndDlgParent; TCHAR szDrive[] = TEXT("A:\\"); szDrive[0] += (CHAR)iDrive; // Sometimes pszPath is a dir and sometimes it's a file. All we really care about is if the // drive is ready (inserted, formated, net path mapped, etc). We know that we don't have a // UNC path because PathGetDriveNumber would have failed and we are already busted in terms // of mounted volumes, again because we use PathGetDriveNumber, so we don't have to worry about // these two cases. As such we build the root path and use that instead. pdth->bDiskCheck[iDrive] = SUCCEEDED(SHPathPrepareForWrite(((pcs->fFlags & FOF_NOERRORUI) ? NULL : hwnd), NULL, szDrive, 0)); } return pdth->bDiskCheck[iDrive]; } return TRUE; // always succeed for net drives } //-------------------------------------------------------------------------------------------- // Simple pidl cache stuff //-------------------------------------------------------------------------------------------- void SimplePidlCache_Release(SIMPLEPIDLCACHE *pspc) { ATOMICRELEASE(pspc->pbcFile); ATOMICRELEASE(pspc->pbcFolder); ATOMICRELEASE(pspc->psfDesktop); ATOMICRELEASE(pspc->psf); ILFree(pspc->pidlFolder); } const WIN32_FIND_DATA c_fdFolder = { FILE_ATTRIBUTE_DIRECTORY }; BOOL SimplePidlCache_Init(SIMPLEPIDLCACHE *pspc) { ASSERT(pspc->iInit == 0); if (SUCCEEDED(SHCreateFileSysBindCtx(NULL, &pspc->pbcFile)) && SUCCEEDED(SHCreateFileSysBindCtx(&c_fdFolder, &pspc->pbcFolder)) && SUCCEEDED(SHGetDesktopFolder(&pspc->psfDesktop))) { pspc->psf = pspc->psfDesktop; pspc->psf->lpVtbl->AddRef(pspc->psf); // It's okay to leave pidlFolder as NULL; ILCombine won't barf pspc->iInit = 1; return TRUE; } else { pspc->iInit = -1; return FALSE; } } LPITEMIDLIST SimplePidlCache_GetFilePidl(SIMPLEPIDLCACHE *pspc, LPCTSTR pszFile) { LPITEMIDLIST pidlChild; LPITEMIDLIST pidlRet; LPTSTR pszFileName; TCHAR szFolder[MAX_PATH]; HRESULT hr; if (pspc->iInit < 0) return NULL; // Initialization failed if (!pspc->iInit && !SimplePidlCache_Init(pspc)) return NULL; // If this file is in a different folder from the one we cached, // need to dump the old one and get a new one. hr = StringCchCopy(szFolder, ARRAYSIZE(szFolder), pszFile); if (FAILED(hr)) return NULL; PathRemoveFileSpec(szFolder); // We use StrCmpC instead of lstrcmpi because the vast majority // of the time, the path will match even in case, and if we get // it wrong, it's no big whoop: we just don't use the cache. if (StrCmpC(pspc->szFolder, szFolder) != 0) { LPITEMIDLIST pidlFolder = NULL; // In case it's on the desktop IShellFolder *psf; if (szFolder[0]) // An actual folder { // Get a simple pidl to the folder. if (FAILED(pspc->psfDesktop->lpVtbl->ParseDisplayName(pspc->psfDesktop, NULL, pspc->pbcFolder, szFolder, NULL, &pidlFolder, NULL))) return NULL; } else // Going for the desktop { /* pidlFolder already preinitialized to NULL */ } // Bind to that folder if (FAILED(SHBindToObject(pspc->psfDesktop, IID_X_PPV_ARG(IShellFolder, pidlFolder, &psf)))) { ILFree(pidlFolder); return NULL; } hr = StringCchCopy(pspc->szFolder, ARRAYSIZE(pspc->szFolder), szFolder); if (FAILED(hr)) { ILFree(pidlFolder); ATOMICRELEASE(psf); return NULL; } // Woo-hoo, everybody is happy. Save the results into our cache. ATOMICRELEASE(pspc->psf); pspc->psf = psf; ILFree(pspc->pidlFolder); pspc->pidlFolder = pidlFolder; } // Get a simple pidl to the filename pszFileName = PathFindFileName(pszFile); // T2W is a macro with multiple evaluation if (FAILED(pspc->psf->lpVtbl->ParseDisplayName(pspc->psf, NULL, pspc->pbcFile, pszFileName, NULL, &pidlChild, NULL))) return NULL; // Combine it with the parent pidlRet = ILCombine(pspc->pidlFolder, pidlChild); ILFree(pidlChild); return pidlRet; } //-------------------------------------------------------------------------------------------- // ConvertToConnectedItemname: // Given a file/folder name, this function checks to see if it has any connection and if // there is a connection, then it will convert the given name to that of the connected element // and return length of the prefix. If no connection exists, it returns zero. // The fDirectory parameter specifies if the given filename is a FOLDER or not! // // dwBuffSize: The size of pszFileName buffer in CHARACTERS. // // Examples: // "foo.htm" => "foo*" (returns 3 because the prefix("foo") length is 3) // "foobar files" => "foobar.htm?" (returns 6 as the prefix length) // //-------------------------------------------------------------------------------------------- int ConvertToConnectedItemName(LPTSTR pszFileName, DWORD dwBuffSize, BOOL fDirectory) { LPTSTR pszDest, pszConnectedElemSuffix; int iPrefixLength; HRESULT hr; if (fDirectory) { // Look for a suffix which is one of the standard suffixes. if (!(pszDest = (LPTSTR)PathFindSuffixArray(pszFileName, c_apszSuffixes, ARRAYSIZE(c_apszSuffixes)))) return 0; // " files" suffix is found. Replace it with ".htm?" pszConnectedElemSuffix = TEXT(".htm?"); } else { // Look for the extension ".htm" or ".html" and replace it with "*". if (!(pszDest = PathFindExtension(pszFileName))) return 0; if (lstrcmpi(pszDest, TEXT(".htm")) && (lstrcmpi(pszDest, TEXT(".html")))) return 0; // Extension ".htm" or ".html" is found. Replace it with "*" pszConnectedElemSuffix = (LPTSTR)c_szStar; } iPrefixLength = (int)(pszDest - pszFileName); //Replace the source suffix with the connected element's suffix. hr = StringCchCopy(pszDest, dwBuffSize - iPrefixLength, pszConnectedElemSuffix); if (FAILED(hr)) return 0; return(iPrefixLength); } PDIRTREENODE DTAllocNode(PDIRTREEHEADER pdth, WIN32_FIND_DATA* pfd, PDIRTREENODE pdtnParent, PDIRTREENODE pdtnNext, BOOL fConnectedElement) { int iLen = pfd ? lstrlen(pfd->cFileName) * sizeof(TCHAR) : 0; PDIRTREENODE pdtn = (PDIRTREENODE)LocalAlloc(LPTR, sizeof(DIRTREENODE) + iLen); if (pdtn) { pdtn->fConnectedElement = fConnectedElement; // Initializing the following to NULL is not needed because of the LPTR (zero init) done // above. // if (fConnectedElement) //{ // pdtn->ConnectedInfo.pFromConnected = pdtn->ConnectedInfo.pToConnected = NULL; // pdtn->ConnectedInfo.dwConfirmation = 0; //} //else // pdtn->pdtnConnected = NULL; pdtn->pdtnParent = pdtnParent; pdtn->pdtnNext = pdtnNext; if (pfd) { HRESULT hr; BOOL fOk = TRUE; hr = StringCchCopy(pdtn->szShortName, ARRAYSIZE(pdtn->szShortName), pfd->cAlternateFileName); if (FAILED(hr)) { fOk = FALSE; } hr = StringCchCopy(pdtn->szName, iLen + 1, pfd->cFileName); if (FAILED(hr)) { fOk = FALSE; } if (fOk) { pdtn->dwFileAttributes = pfd->dwFileAttributes; pdtn->ftCreationTime = pfd->ftCreationTime; pdtn->ftLastWriteTime = pfd->ftLastWriteTime; pdtn->nFileSizeLow = pfd->nFileSizeLow; pdtn->nFileSizeHigh = pfd->nFileSizeHigh; // only the stuff we care about if (ISDIRFINDDATA(*pfd)) { pdth->dtAll.dwFolders++; pdtn->pdtnChild = DTN_DELAYED; } else { LARGE_INTEGER li; li.LowPart = pfd->nFileSizeLow; li.HighPart = pfd->nFileSizeHigh; pdth->dtAll.liSize.QuadPart += li.QuadPart; pdth->dtAll.dwFiles++; } // increment the header stats pdth->dtAll.fChanged = TRUE; } else { LocalFree(pdtn); pdtn = NULL; } } } return pdtn; } #if defined(DEBUG) /// && defined(DEBUGCOPY) void DebugDumpPDTN(PDIRTREENODE pdtn, LPTSTR ptext) { DebugMsg(TF_DEBUGCOPY, TEXT("***** PDTN %x (%s)"), pdtn, ptext); //Safe-guard against pdtn being NULL! if (pdtn) { DebugMsg(TF_DEBUGCOPY, TEXT("** %s %s"), pdtn->szShortName, pdtn->szName); DebugMsg(TF_DEBUGCOPY, TEXT("** %x %d"), pdtn->dwFileAttributes, pdtn->nFileSizeLow); DebugMsg(TF_DEBUGCOPY, TEXT("** %x %x %x"), pdtn->pdtnParent, pdtn->pdtnNext, pdtn->pdtnChild); DebugMsg(TF_DEBUGCOPY, TEXT("** NewRoot:%x, Connected:%x, Dummy:%x"), pdtn->fNewRoot, pdtn->fConnectedElement, pdtn->fDummy); if (pdtn->fConnectedElement) { DebugMsg(TF_DEBUGCOPY, TEXT("**** Connected: pFromConnected:%s, pToConnected:%s, dwConfirmation:%x"), pdtn->ConnectedInfo.pFromConnected, pdtn->ConnectedInfo.pToConnected, pdtn->ConnectedInfo.dwConfirmation); } else { DebugMsg(TF_DEBUGCOPY, TEXT("**** Origin: pdtnConnected:%x"), pdtn->pdtnConnected); } } else { DebugMsg(TF_DEBUGCOPY, TEXT("** NULL pointer(PDTN)")); } } #else #define DebugDumpPDTN(p, x) 0 #endif BOOL DoesSuffixMatch(LPTSTR lpSuffix, const LPCTSTR *apSuffixes, int iArraySize) { while (iArraySize--) { // Note: This must be a case sensitive compare, because we don't want to pickup // "Program Files". if (!lstrcmp(lpSuffix, *apSuffixes++)) return TRUE; } return FALSE; } //-------------------------------------------------------------------------------------------- // // DTPathToDTNode: // This function is used to build a list of nodes that correspond to the given pszPath. // This list is built under "ppdtn". If ppdtnConnectedItems is given, another list of nodes that // correspond to the connected elements(files/folders) of the nodes in the first list is also built // under "ppdtnConnectedItems". // // WARNING: This parties directly on pszPath and pfd so that it doesn't need to allocate // on the stack. This recurses, so we want to use as little stack as possible // // this will wack off one component from pszPath // // // ppdtn: Points to where the header of the list being built will be stored. // ppdtnConnectedItems: If this is NULL, then we are not interested in finding and building the // connected elements. If this is NOT null, it points to where the header of // the connected items list will be stored. // fConnectedElement: Each node being built under ppdtn needs to be marked with this bit. // iPrefixLength: This parameter is zero if fConnectedElement is FALSE. Otherwise, it contains the // Length of the prefix part of the file or foldername (path is NOT included). // For example, if "c:\windows\foo*" is passed in, iPrefixLength is 3 (length of "foo") // // dwFilesOrFolders parameter can specify if we need to look for only FILES or FOLDERs or BOTH. #define DTF_FILES_ONLY 0x00000001 //Operate only on Files. #define DTF_FOLDERS_ONLY 0x00000002 //Operate only on Folders. #define DTF_FILES_AND_FOLDERS (DTF_FILES_ONLY | DTF_FOLDERS_ONLY) //Operate on files AND folders. UINT DTPathToDTNode(PDIRTREEHEADER pdth, COPY_STATE *pcs, LPTSTR pszPath, BOOL fRecurse, DWORD dwFilesOrFolders, PDIRTREENODE* ppdtn, WIN32_FIND_DATA *pfd, PDIRTREENODE pdtnParent, PDIRTREENODE* ppdtnConnectedItems, BOOL fConnectedElement, int iPrefixLength) { int iError = 0; // this points to the var where all items are inserted. // folders are placed after it, files are placed before // keep the stack vars to a minimum because this is recursive PDIRTREENODE *ppdtnMiddle = ppdtn; BOOL fNeedToFindNext = TRUE; HANDLE hfind = FindFirstFile(pszPath, pfd); DebugMsg(TF_DEBUGCOPY, TEXT("DTPathToDTNode Entering %s"), pszPath); *ppdtnMiddle = NULL; // in case there are no children if (hfind == INVALID_HANDLE_VALUE) { // this is allowable only if the path is wild... // and the parent exists if (PathIsWild(pszPath)) { PathRemoveFileSpec(pszPath); if (PathFileExists(pszPath)) { return 0; } } return OPER_ERROR | ERROR_FILE_NOT_FOUND; } //Remove the filespec before passing it onto DTAllocConnectedItemNodes. PathRemoveFileSpec(pszPath); do { // We skip the following files: // "." and ".." filenames // Folders when DTF_FILES_ONLY is set // Files when DTF_FOLDERS_ONLY is set if (!PathIsDotOrDotDot(pfd->cFileName) && (((dwFilesOrFolders & DTF_FILES_ONLY) && !ISDIRFINDDATA(*pfd)) || ((dwFilesOrFolders & DTF_FOLDERS_ONLY) && ISDIRFINDDATA(*pfd)))) { //Check if we are looking for connected elements if ((!pdtnParent) && fConnectedElement) { // We found what we are looking for. If we are looking for a top-level connected item and // if it is a folder, then we need to make sure that the suffix exactly matches one of the // suffixes in the array c_apszSuffixes[]. LPTSTR lpSuffix = (LPTSTR)(pfd->cFileName + iPrefixLength); if (ISDIRFINDDATA(*pfd)) { // What we found is a directory! // See if it has one of the standard suffixes for connected folders. if (!DoesSuffixMatch(lpSuffix, c_apszSuffixes, ARRAYSIZE(c_apszSuffixes))) continue; //This is not what we look for. So, find next. } else { // What we found is a file (i.e Not a directory) // See if it has one of the standard suffixes for html files. if (lstrcmpi(lpSuffix, TEXT(".htm")) && lstrcmpi(lpSuffix, TEXT(".html"))) continue; //This is not what we look for. So, find next. } // Now we know that we found the connected element that we looked for. // So, no need to FindNext again. We can get out of the loop after processing // it once. fNeedToFindNext = FALSE; } *ppdtnMiddle = DTAllocNode(pdth, pfd, pdtnParent, *ppdtnMiddle, fConnectedElement); if (!*ppdtnMiddle) { FindClose(hfind); return OPER_ERROR | ERROR_NOT_ENOUGH_MEMORY; } // make sure that the parent's pointer always points to the head of // this linked list if (*ppdtn == (*ppdtnMiddle)->pdtnNext) *ppdtn = (*ppdtnMiddle); DebugDumpPDTN(*ppdtnMiddle, TEXT("DTPathToDTNode, DTAllocNode")); //We need to check for Connected elements only for the top level items if ((!(pcs->fFlags & FOF_NO_CONNECTED_ELEMENTS)) && ppdtnConnectedItems) { //Make sure this is a top level item ASSERT(!pdtnParent); //Create a list of connected items and attach it to the head of the list. iError = DTAllocConnectedItemNodes(pdth, pcs, pfd, pszPath, fRecurse, ppdtnConnectedItems); DebugDumpPDTN(*ppdtnConnectedItems, TEXT("DTPathToDTNode, DTAllocConnectedNodes")); // It is possible that the connected files do not exist. That condition is not really // an error. So, we check for insufficient memory error condition alone here. if (iError == (OPER_ERROR | ERROR_NOT_ENOUGH_MEMORY)) { FindClose(hfind); return(iError); } //If a connected item exists, then make the origin item point to this connected item. if (*ppdtnConnectedItems) { (*ppdtnMiddle)->pdtnConnected = *ppdtnConnectedItems; // Also by default, set the Confirmation result to NO so that the connected element // will not be copied/moved etc., in case of a conflict. However, if the origin had // a conflict, we would put up a confirmation dlg and the result of that dlg will // over-write this value. (*ppdtnConnectedItems)->ConnectedInfo.dwConfirmation = IDNO; } //Move to the last node in the connected items list. while (*ppdtnConnectedItems) ppdtnConnectedItems = &((*ppdtnConnectedItems)->pdtnNext); } else { // This should have been initialized to zero during allocation, but lets be paranoid ASSERT(NULL == (*ppdtnMiddle)->pdtnConnected); } // if this is not a directory, move the ppdtnMiddle up one if (!ISDIRFINDDATA(*pfd)) { ppdtnMiddle = &(*ppdtnMiddle)->pdtnNext; } } } while (fNeedToFindNext && !FOQueryAbort(pcs) && FindNextFile(hfind, pfd)); iError = 0; //It is possible that iError contains other errors value now! So, reset it! FindClose(hfind); // now go and recurse into folders (if desired) // we don't have to check to see if these pdtn's are dirs, because the // way we inserted them above ensures that everything in from of // ppdtnMiddle are folders // we're going to tack on a specific child // then add the *.* after that while (!FOQueryAbort(pcs) && *ppdtnMiddle) { BOOL fRecurseThisItem = fRecurse; if ((*ppdtnMiddle)->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { // Recurse into reparse points unless they asked not to if (pcs->fFlags & FOF_NORECURSEREPARSE) { fRecurseThisItem = FALSE; } } if (fRecurseThisItem) { if (PathAppend(pszPath, (*ppdtnMiddle)->szName)) { if (PathAppend(pszPath, c_szStarDotStar)) { // NULL indicates that we do not want to get the connected elements. // This is because we want the connected elements only for the top-level items. iError = DTPathToDTNode(pdth, pcs, pszPath, TRUE, DTF_FILES_AND_FOLDERS, &((*ppdtnMiddle)->pdtnChild), pfd, *ppdtnMiddle, NULL, fConnectedElement, 0); } else { iError = OPER_ERROR | DE_INVALIDFILES; } PathRemoveFileSpec(pszPath); } else { iError = OPER_ERROR | DE_INVALIDFILES; } } else { // if we don't want to recurse, just mark them all as having no children (*ppdtnMiddle)->pdtnChild = NULL; } if (iError) { return iError; } ppdtnMiddle = &(*ppdtnMiddle)->pdtnNext; } return 0; } UINT DTAllocConnectedItemNodes(PDIRTREEHEADER pdth, COPY_STATE *pcs, WIN32_FIND_DATA *pfd, LPTSTR pszPath, BOOL fRecurse, PDIRTREENODE *ppdtnConnectedItems) { // Since DTAllocConnectedItemNodes() gets called only for the top-level items in the src list, // there is no danger of this function getting called recursively. Hence, I didn't worry about // allocating the following on the stack. // If "too-much-stack-is-used" problem arises, we can optimize the stack usage by splitting // the following function into two such that the most common case (of no connection) // doesn't use much stack. DWORD dwFileOrFolder; TCHAR szFullPath[MAX_PATH]; TCHAR szFileName[MAX_PATH]; WIN32_FIND_DATA fd; int iPrefixLength; //This is the length of "foo" if the filename is "foo.htm" or "foo files" HRESULT hr; //Make a copy of the filename; This copy will get munged by ConvertToConnectedItemName(). hr = StringCchCopy(szFileName, ARRAYSIZE(szFileName), pfd->cFileName); if (FAILED(hr)) return 0; // No connection exist for too big names // Convert the given file/foder name into the connected item's name with wild card characters. iPrefixLength = ConvertToConnectedItemName(szFileName, ARRAYSIZE(szFileName), ISDIRFINDDATA(*pfd)); if (iPrefixLength == 0) return 0; //No connections exist for the given folder/file. // Now szFileName has the name of connected element with wildcard character. // If the given element is a directory, we want to look for connected FILES only and // if the given element is a file, we want to look for connected FOLDERS only. dwFileOrFolder = ISDIRFINDDATA(*pfd) ? DTF_FILES_ONLY : DTF_FOLDERS_ONLY; // Form the file/folder name with the complete path! hr = StringCchCopy(szFullPath, ARRAYSIZE(szFullPath), pszPath); if (FAILED(hr)) return 0; if (!PathAppend(szFullPath, szFileName)) return 0; // The file-element has some "connected" items. DebugMsg(TF_DEBUGCOPY, TEXT("DTAllocConnectedItemNodes Looking for %s"), szFullPath); return(DTPathToDTNode(pdth, pcs, szFullPath, fRecurse, dwFileOrFolder, ppdtnConnectedItems, &fd, NULL, NULL, TRUE, iPrefixLength)); } void DTInitProgressPoints(PDIRTREEHEADER pdth, COPY_STATE *pcs) { pdth->iFilePoints = 1; pdth->iFolderPoints = 1; switch (pcs->lpfo->wFunc) { case FO_RENAME: case FO_DELETE: pdth->iSizePoints = 0; break; case FO_COPY: pdth->iSizePoints = 1; break; case FO_MOVE: if (PathIsSameRoot(pcs->lpfo->pFrom, pcs->lpfo->pTo)) { pdth->iSizePoints = 0; } else { // if it's across volumes, these points increase // because we need to nuke the source as well as // create the target... // whereas we don't need to nuke the "size" of the source pdth->iFilePoints = 2; pdth->iFolderPoints = 2; pdth->iSizePoints = 1; } break; } } UINT DTBuild(COPY_STATE* pcs) { PDIRTREEHEADER pdth = &pcs->dth; WIN32_FIND_DATA fd; TCHAR szPath[MAX_PATH]; PDIRTREENODE *ppdtn; PDIRTREENODE *ppdtnConnectedItems; int iError = 0; HRESULT hr; pcs->dth.pFrom = (LPTSTR)pcs->lpfo->pFrom; pcs->dth.pTo = (LPTSTR)pcs->lpfo->pTo; // A tree of original items will be built under ppdtn. ppdtn = &pdth->pdtn; // A tree of items "connected" to the orginal items will be built under ppdtnConnectedItems. ppdtnConnectedItems = &pdth->pdtnConnectedItems; DTInitProgressPoints(pdth, pcs); while (!FOQueryAbort(pcs) && *pdth->pFrom) { BOOL fRecurse = TRUE; switch (pcs->lpfo->wFunc) { case FO_MOVE: // The move operation doesn't need to recurse if we are moving from and to the same // volume. In this case we know that we don't need to display any warnings for // things like LFN to 8.3 filename conversion or stream loss. Instead, we can do // the operation with one single win32 file operation that just does a rename. // NTRAID89119-2000/02/25-toddb // This is only true if we don't cross a mount point! If we cross // a mount point then we might have to warn about these things. if ((pcs->fFlags & FOF_NORECURSION) || PathIsSameRoot(pdth->pFrom, pdth->pTo)) { fRecurse = FALSE; } break; case FO_COPY: // For a copy we always recurse unless we're told not to. if (pcs->fFlags & FOF_NORECURSION) { fRecurse = FALSE; } break; case FO_RENAME: // for a rename we never recurse fRecurse = FALSE; break; case FO_DELETE: // for a delete we don't need to recurse IF the recycle bin will be able to handle // the given item. If the recycle bin handles the delete then we can undo from // the recycle bin if we need to. if ((pcs->fFlags & FOF_ALLOWUNDO) && BBWillRecycle(pdth->pFrom, NULL)) { fRecurse = FALSE; } break; } hr = StringCchCopy(szPath, ARRAYSIZE(szPath), pdth->pFrom); if (FAILED(hr)) { iError = OPER_ERROR | DE_INVALIDFILES; break; } DebugMsg(TF_DEBUGCOPY, TEXT("DTBuild: %s"), szPath); // If the file is on removable media, we need to check for media in the drive. // Prompt the user to insert the media if it's missing. if (!DTDiskCheck(pdth, pcs, szPath)) { iError = ERROR_CANCELLED; break; } iError = DTPathToDTNode(pdth, pcs, szPath, fRecurse, ((PathIsWild(pdth->pFrom) && (pcs->lpfo->fFlags & FOF_FILESONLY)) ? DTF_FILES_ONLY : DTF_FILES_AND_FOLDERS), ppdtn,&fd, NULL, ppdtnConnectedItems, FALSE, 0); DebugMsg(TF_DEBUGCOPY, TEXT("DTBuild: returned %d"), iError); // FEATURE: If an error occured we should allow the user to skip the file that caused the error. That way // if one of the source files doesn't exists the rest will still get copied. Do this only in the multi- // source case, blah blah blah. This helps in the case where one of the source files cannot be moved or // copied (usually due to Access Denied, could be insuffecent permissions or file is in use, etc). if (iError) break; if (!(*ppdtn) && PathIsWild(pdth->pFrom)) { // no files are associated with this path... this // can happen when we have wildcards... // alloc a dummy node *ppdtn = DTAllocNode(pdth, NULL, NULL, NULL, FALSE); if (*ppdtn) { (*ppdtn)->fDummy = TRUE; } } if (*ppdtn) { // mark this as the start of a root spec... this is // necessary in case we have several wild specs (*ppdtn)->fNewRoot = TRUE; } if (*ppdtnConnectedItems) { // Mark this as the start of a root spec. (*ppdtnConnectedItems)->fNewRoot = TRUE; // For connected items, we need to remember the path. (*ppdtnConnectedItems)->ConnectedInfo.pFromConnected = pdth->pFrom; (*ppdtnConnectedItems)->ConnectedInfo.pToConnected = pdth->pTo; } while (*ppdtn) { ppdtn = &(*ppdtn)->pdtnNext; } while (*ppdtnConnectedItems) { ppdtnConnectedItems = &(*ppdtnConnectedItems)->pdtnNext; } pdth->pFrom += lstrlen(pdth->pFrom) + 1; if (pcs->lpfo->wFunc != FO_DELETE && (pcs->lpfo->fFlags & FOF_MULTIDESTFILES)) { pdth->pTo += lstrlen(pdth->pTo) + 1; } } //Attach the "ConnectedElements" Tree to the end of the source element tree. *ppdtn = pcs->dth.pdtnConnectedItems; pcs->dth.pFrom = (LPTSTR)pcs->lpfo->pFrom; pcs->dth.pTo = (LPTSTR)pcs->lpfo->pTo; pcs->fDTBuilt = TRUE; // set up the initial time information pcs->dwPreviousTime = GetTickCount(); pcs->dwPointsPerSec = 0; pcs->iLastProgressPoints = 0; return iError; } #define DTNIsRootNode(pdtn) ((pdtn)->pdtnParent == NULL) #define DTNIsDirectory(pdtn) (pdtn->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) // This macro determines if the given node is an "Origin" of a connection. i.e. Does this node // point to a connected element that needs to be moved/copied etc., along with it? // For example, if "foo.htm" is moved, "foo files" is also moved. // Here, "foo.htm" is the "Connect origin" (fConnectedElement = FALSE; pdtnConnected is valid) // and "foo files" is the "connected element". (fConnectedElement = TRUE;) #define DTNIsConnectOrigin(pdtn) ((!pdtn->fConnectedElement) && (pdtn->pdtnConnected != NULL)) #define DTNIsConnected(pdtn) (pdtn && (pdtn->fConnectedElement)) // UINT DTEnumChildren(PDIRTREEHEADER pdth, COPY_STATE *pcs, BOOL fRecurse, DWORD dwFileOrFolder) { int iError = 0; if (pdth->pdtnCurrent->pdtnChild == DTN_DELAYED) { WIN32_FIND_DATA fd; // fill in all the children and update the stats in pdth TCHAR szPath[MAX_PATH]; if (PathCombine(szPath, pdth->szSrcPath, c_szStarDotStar)) { iError = DTPathToDTNode(pdth, pcs, szPath, fRecurse, dwFileOrFolder, &pdth->pdtnCurrent->pdtnChild, &fd, pdth->pdtnCurrent, NULL, pdth->pdtnCurrent->fConnectedElement, 0); } else { iError = OPER_ERROR | DE_INVALIDFILES; } // If we get "File Not Found" Error now and if it is a connected item, then this item // must have already been moved/renamed/deleted etc., So, this is not really an error. // All this means is that this connected item was also explicitly selected and hence appeared // as or "Origin" item earlier in the list and it had already been operated upon. // So, reset the error here. // (Example: If end-user selects "foo.htm" AND "foo files" folder and moves them, then we // will get a file-not-found error when we attempt to move the connected items. To avoid // this error dialog, we reset the error here.) if (DTNIsConnected(pdth->pdtnCurrent) && (iError == (OPER_ERROR | ERROR_FILE_NOT_FOUND))) iError = 0; } return iError; } // // DTNGetConfirmationResult: // When a file("foo.htm") is moved/copied, we may put up a confirmation dialog in case // of a conflict and the end-user might have responded saying "Yes", "no" etc., When the // corresponding connected element ("foo files") is also moved/copied etc., we should NOT put up // a confirmation dialog again. We must simply store the answer to the original confirmation and // use it later. // This function retries the result of the original confirmation from the top-level connected // element. int DTNGetConfirmationResult(PDIRTREENODE pdtn) { //Confirmation results are saved only for Connected items; Not for Connection Origins. if (!pdtn || !DTNIsConnected(pdtn)) return 0; //Confirmation results are stored only at the top-level node. So, go there. while (pdtn->pdtnParent) pdtn = pdtn->pdtnParent; return(pdtn->ConnectedInfo.dwConfirmation); } BOOL DTGetWin32FindData(PDIRTREENODE pdtn, WIN32_FIND_DATA* pfd) { HRESULT hr; BOOL fOk = TRUE; // only the stuff we care about hr = StringCchCopy(pfd->cAlternateFileName, ARRAYSIZE(pfd->cAlternateFileName), pdtn->szShortName); if (FAILED(hr)) { fOk = FALSE; } hr = StringCchCopy(pfd->cFileName, ARRAYSIZE(pfd->cFileName), pdtn->szName); if (FAILED(hr)) { fOk = FALSE; } pfd->dwFileAttributes = pdtn->dwFileAttributes; pfd->ftCreationTime = pdtn->ftCreationTime; pfd->ftLastWriteTime = pdtn->ftLastWriteTime; pfd->nFileSizeLow = pdtn->nFileSizeLow; pfd->nFileSizeHigh = pdtn->nFileSizeHigh; return fOk; } void DTSetFileCopyProgress(PDIRTREEHEADER pdth, LARGE_INTEGER liRead) { LARGE_INTEGER liDelta; liDelta.QuadPart = (liRead.QuadPart - pdth->pdtnCurrent->liFileSizeCopied.QuadPart); DebugMsg(TF_DEBUGCOPY, TEXT("DTSetFileCopyProgress %d %d %d"), liDelta.LowPart, liRead.LowPart, pdth->dtDone.liSize.QuadPart); pdth->pdtnCurrent->liFileSizeCopied.QuadPart += liDelta.QuadPart; pdth->dtDone.liSize.QuadPart += liDelta.QuadPart; DebugMsg(TF_DEBUGCOPY, TEXT("DTSetFileCopyProgress %d %d"), liDelta.LowPart, pdth->dtDone.liSize.LowPart); pdth->dtDone.fChanged = TRUE; } void DTFreeNode(PDIRTREEHEADER pdth, PDIRTREENODE pdtn) { if (pdth) { ASSERT(pdtn->pdtnChild == NULL || pdtn->pdtnChild == DTN_DELAYED); // we're done with this node.. update the header totals if (DTNIsDirectory(pdtn)) { pdth->dtDone.dwFolders++; } else { LARGE_INTEGER li; li.LowPart = pdtn->nFileSizeLow; li.HighPart = pdtn->nFileSizeHigh; pdth->dtDone.dwFiles++; pdth->dtDone.liSize.QuadPart += (li.QuadPart - pdtn->liFileSizeCopied.QuadPart); } pdth->dtDone.fChanged = TRUE; // repoint parent pointer if (!pdtn->pdtnParent) { // no parent... must be a root type thing ASSERT(pdth->pdtn == pdtn); pdth->pdtn = pdtn->pdtnNext; } else { ASSERT(pdtn->pdtnParent->pdtnChild == pdtn); if (pdtn->pdtnParent->pdtnChild == pdtn) { // if my parent was pointing to me, point him to my sib pdtn->pdtnParent->pdtnChild = pdtn->pdtnNext; } } } LocalFree(pdtn); } // this frees all children of (but NOT including) the current node. // it doesn' free the current node because it's assumed that // DTGoToNextNode will be called right afterwards, and that will // free the current node void DTFreeChildrenNodes(PDIRTREEHEADER pdth, PDIRTREENODE pdtn) { PDIRTREENODE pdtnChild = pdtn->pdtnChild; while (pdtnChild && pdtnChild != DTN_DELAYED) { PDIRTREENODE pdtnNext = pdtnChild->pdtnNext; // recurse and free these children if (DTNIsDirectory(pdtnChild)) { DTFreeChildrenNodes(pdth, pdtnChild); } DTFreeNode(pdth, pdtnChild); pdtnChild = pdtnNext; } pdtn->pdtnChild = NULL; } void DTForceEnumChildren(PDIRTREEHEADER pdth) { if (!pdth->pdtnCurrent->pdtnChild) pdth->pdtnCurrent->pdtnChild = DTN_DELAYED; } void DTAbortCurrentNode(PDIRTREEHEADER pdth) { DTFreeChildrenNodes((pdth), (pdth)->pdtnCurrent); if (pdth->oper == OPER_ENTERDIR) pdth->oper = OPER_LEAVEDIR; } void DTCleanup(PDIRTREEHEADER pdth) { PDIRTREENODE pdtn; while (pdth->pdtnCurrent && pdth->pdtnCurrent->pdtnParent) { // in case we bailed deep in a tree pdth->pdtnCurrent = pdth->pdtnCurrent->pdtnParent; } while (pdth->pdtnCurrent) { pdtn = pdth->pdtnCurrent; pdth->pdtnCurrent = pdtn->pdtnNext; DTFreeChildrenNodes(NULL, pdtn); DTFreeNode(NULL, pdtn); } } BOOL DTInitializePaths(PDIRTREEHEADER pdth, COPY_STATE *pcs) { HRESULT hr; TCHAR szTemp[MAX_PATH]; ASSERT(pdth->pdtnCurrent); // If we have no current node then how can we Initialize its paths? hr = StringCchCopy(szTemp, ARRAYSIZE(szTemp), pdth->pFrom); if (FAILED(hr)) { return FALSE; } // For the "Origins" we need to do this only if a wild card exists. However, for connected elements, // we need to do this everytime because connected elements may not exist for every "Origins" if (PathIsWild(pdth->pFrom) || (pdth->pdtnCurrent->fNewRoot && DTNIsConnected(pdth->pdtnCurrent))) { PathRemoveFileSpec(szTemp); if (!PathAppend(szTemp, pdth->pdtnCurrent->szName)) return FALSE; } hr = StringCchCopy(pdth->szSrcPath, ARRAYSIZE(pdth->szSrcPath), szTemp); if (FAILED(hr)) { // This should never fail because pdth->szSrcPath is the same size (MAX_PATH) as szTemp return FALSE; } if (!pdth->pTo) { // no dest, make it the same as the source and we're done hr = StringCchCopy(pdth->szDestPath, ARRAYSIZE(pdth->szDestPath), pdth->szSrcPath); if (FAILED(hr)) { return FALSE; } return TRUE; } hr = StringCchCopy(szTemp, ARRAYSIZE(szTemp), pdth->pTo); if (FAILED(hr)) { return FALSE; } if (!pdth->fMultiDest) { if (!PathAppend(szTemp, pdth->pdtnCurrent->szName)) return FALSE; } else { //When undo of a move operation is done, fMultiDest is set. // When fMultiDest is set, we need to strip out the filename given by pTo and // append the current filename. // For RENAME operations, the source and destination names are different. This is handled // seperately below. So, we handle only other operations here where source and dest names are the same. if ((pcs->lpfo->wFunc != FO_RENAME) && pdth->pdtnCurrent->fNewRoot && DTNIsConnected(pdth->pdtnCurrent)) { PathRemoveFileSpec(szTemp); if (!PathAppend(szTemp, pdth->pdtnCurrent->szName)) return FALSE; } } hr = StringCchCopy(pdth->szDestPath, ARRAYSIZE(pdth->szDestPath), szTemp); if (FAILED(hr)) { // This should never fail because pdth->szDestPath is the same size (MAX_PATH) as szTemp return FALSE; } //We will never try to rename a connected element! Make sure we don't hit this! ASSERT(!((pcs->lpfo->wFunc == FO_RENAME) && DTNIsConnected(pdth->pdtnCurrent))); return TRUE; } UINT DTValidatePathNames(PDIRTREEHEADER pdth, UINT operation, COPY_STATE * pcs) { if (pcs->lpfo->wFunc != FO_DELETE) { // Why process name mappings? Here's why. If we are asked to copy directory "c:\foo" and // file "c:\foo\file" to another directory (say "d:\") we might have a name confilct when // we copy "c:\foo" so instead we create "d:\Copy Of foo". Later, we walk to the second // dirtree node and we are asked to copy "c:\foo\file" to "d:\foo", all of which is valid. // HOWEVER, it's not what we want to do. We use _ProccessNameMappings to convert // "d:\foo\file" into "d:\Copy of foo\file". _ProcessNameMappings(pdth->szDestPath, ARRAYSIZE(pdth->szDestPath), pdth->hdsaRenamePairs); // REVIEW, do we need to do the name mapping here or just let the // VFAT do it? if vfat does it we need to rip out all of the GetNameDialog() stuff. if ((operation != OPER_LEAVEDIR) && !IsLFNDrive(pdth->szDestPath) && PathIsLFNFileSpec(PathFindFileName(pdth->szSrcPath)) && PathIsLFNFileSpec(PathFindFileName(pdth->szDestPath))) { int iRet; TCHAR szOldDest[MAX_PATH]; HRESULT hr; hr = StringCchCopy(szOldDest, ARRAYSIZE(szOldDest), pdth->szDestPath); if (FAILED(hr)) { return OPER_ERROR | DE_INVALIDFILES; } iRet = GetNameDialog(pcs->hwndDlgParent, pcs, (pcs->nSourceFiles != 1) || !DTNIsRootNode(pdth->pdtnCurrent), // if we're entering a dir, multiple spec, or not at root operation, pdth->szSrcPath, pdth->szDestPath); switch (iRet) { case IDNO: case IDCANCEL: return iRet; default: AddRenamePairToHDSA(szOldDest, pdth->szDestPath, &pcs->dth.hdsaRenamePairs); break; } } if (operation == OPER_ENTERDIR) { // Make sure the new directory is not a subdir of the original... int cchFrom = lstrlen(pdth->szSrcPath); // NTRAID89511-2000/02/25-KishoreP // Shouldn't we get the short names for both these directories and compair those? // Otherwise I can copy "C:\Long Directory Name" to "C:\LongDi~1\foo" without error. if (!(pcs->fFlags & FOF_RENAMEONCOLLISION) && !StrCmpNI(pdth->szSrcPath, pdth->szDestPath, cchFrom)) { TCHAR chNext = pdth->szDestPath[cchFrom]; // Get the next char in the dest. if (!chNext) { return OPER_ERROR | DE_DESTSAMETREE; } else if (chNext == TEXT('\\')) { // The two fully qualified strings are equal up to the end // of the source directory ==> the destination is a subdir. // Must return an error. // if, stripping the last file name and the backslash give the same length, they are the // same file/folder if ((PathFindFileName(pdth->szDestPath) - pdth->szDestPath - 1) == lstrlen(pdth->szSrcPath)) { return OPER_ERROR | DE_DESTSAMETREE; } else { return OPER_ERROR | DE_DESTSUBTREE; } } } } } return 0; } // this moves to the next node (child, sib, parent) and sets up the // directory path info and oper state UINT DTGoToNextNode(PDIRTREEHEADER pdth, COPY_STATE *pcs) { UINT oper = OPER_ENTERDIR; // the default int iError; if (!pdth->pdtnCurrent) { pdth->pdtnCurrent = pdth->pdtn; if (pdth->pdtnCurrent) { if (pdth->pdtnCurrent->fDummy) { // if this is just a placeholder... go on to the next one return DTGoToNextNode(pdth, pcs); } if (!DTInitializePaths(pdth, pcs)) { return OPER_ERROR | DE_INVALIDFILES; } } else { // Our tree is completely empty. // REVIEW: What do we do here? If pdtnCurrent is still NULL then our list is completely empty. // Is that a bug or what? My hunch is that we should return an error code here, most likely // OPER_ERROR | DE_INVALIDFILES. If we do nothing here then we will fail silently. return OPER_ERROR | DE_INVALIDFILES; } } else { UINT iError; BOOL fFreeLastNode = TRUE; PDIRTREENODE pdtnLastCurrent = pdth->pdtnCurrent; TCHAR szTemp[MAX_PATH]; HRESULT hr; if (iError = DTEnumChildren(pdth, pcs, FALSE, DTF_FILES_AND_FOLDERS)) return iError; if (pdth->pdtnCurrent->pdtnChild) { fFreeLastNode = FALSE; pdth->pdtnCurrent = pdth->pdtnCurrent->pdtnChild; // if the source long name is too long, try the short name if (!PathCombine(szTemp, pdth->szSrcPath, pdth->pdtnCurrent->szName)) { if (!PathCombine(szTemp, pdth->szSrcPath, pdth->pdtnCurrent->szShortName)) { return OPER_ERROR | DE_INVALIDFILES; } } hr = StringCchCopy(pdth->szSrcPath, ARRAYSIZE(pdth->szSrcPath), szTemp); if (FAILED(hr)) { return OPER_ERROR | DE_INVALIDFILES; } // if the dest long name is too long, try the short name if (!PathCombine(szTemp, pdth->szDestPath, pdth->pdtnCurrent->szName)) { if (!PathCombine(szTemp, pdth->szDestPath, pdth->pdtnCurrent->szShortName)) { return OPER_ERROR | DE_INVALIDFILES; } } hr = StringCchCopy(pdth->szDestPath, ARRAYSIZE(pdth->szDestPath), szTemp); if (FAILED(hr)) { return OPER_ERROR | DE_INVALIDFILES; } } else if (pdth->oper == OPER_ENTERDIR) { // if the last operation was an enterdir and it has no children // (because it failed the above test // then we should do a leave dir on it now oper = OPER_LEAVEDIR; fFreeLastNode = FALSE; } else if (pdth->pdtnCurrent->pdtnNext) { pdth->pdtnCurrent = pdth->pdtnCurrent->pdtnNext; if (!pdth->pdtnCurrent->pdtnParent) { // if this was the top, we need to build the next path info // from scratch if (pdth->pdtnCurrent->fNewRoot) { if (pdth->pdtnCurrent->fConnectedElement) { // Since this is a new root in a Connected list, the pFrom and pTo are // stored in the node itself. This is needed because Connected elements may // not exist for every item in the source list and we do not want to create dummy // nodes for each one of them. So, pFrom and pTo are stored for every NewRoot of // connected elements and we use these here. pdth->pFrom = pdth->pdtnCurrent->ConnectedInfo.pFromConnected; pdth->pTo = pdth->pdtnCurrent->ConnectedInfo.pToConnected; } else { // go to the next path pair pdth->pFrom += lstrlen(pdth->pFrom) + 1; if (pdth->pTo) { if (pdth->fMultiDest) { pdth->pTo += lstrlen(pdth->pTo) + 1; } } } } if (pdth->pdtnCurrent->fDummy) { // if this is just a placeholder... go on to the next one if (fFreeLastNode) { DTFreeNode(pdth, pdtnLastCurrent); } return DTGoToNextNode(pdth, pcs); } DTInitializePaths(pdth, pcs); } else { PathRemoveFileSpec(pdth->szSrcPath); PathRemoveFileSpec(pdth->szDestPath); // if the source long name is too long, try the short name if (!PathCombine(szTemp, pdth->szSrcPath, pdth->pdtnCurrent->szName)) { if (!PathCombine(szTemp, pdth->szSrcPath, pdth->pdtnCurrent->szShortName)) { return OPER_ERROR | DE_INVALIDFILES; } } hr = StringCchCopy(pdth->szSrcPath, ARRAYSIZE(pdth->szSrcPath), szTemp); if (FAILED(hr)) { return OPER_ERROR | DE_INVALIDFILES; } // if the dest long name is too long, try the short name if (!PathCombine(szTemp, pdth->szDestPath, pdth->pdtnCurrent->szName)) { if (!PathCombine(szTemp, pdth->szDestPath, pdth->pdtnCurrent->szShortName)) { return OPER_ERROR | DE_INVALIDFILES; } } hr = StringCchCopy(pdth->szDestPath, ARRAYSIZE(pdth->szDestPath), szTemp); if (FAILED(hr)) { return OPER_ERROR | DE_INVALIDFILES; } } } else { oper = OPER_LEAVEDIR; PathRemoveFileSpec(pdth->szSrcPath); PathRemoveFileSpec(pdth->szDestPath); pdth->pdtnCurrent = pdth->pdtnCurrent->pdtnParent; } if (fFreeLastNode) { DTFreeNode(pdth, pdtnLastCurrent); } } if (!pdth->pdtnCurrent) { // no more! we're done! return 0; } DebugDumpPDTN(pdth->pdtnCurrent, TEXT("PDTNCurrent")); if (oper == OPER_ENTERDIR) { if (pcs->lpfo->wFunc == FO_RENAME || !DTNIsDirectory(pdth->pdtnCurrent)) { oper = OPER_DOFILE; } } if (DTNIsRootNode(pdth->pdtnCurrent)) { // we need to diskcheck the source and target because this might // be the first time we've seen this drive if (!DTDiskCheck(pdth, pcs, pdth->szSrcPath) || !DTDiskCheck(pdth, pcs, pdth->szDestPath)) { pcs->bAbort = TRUE; return 0; } } iError = DTValidatePathNames(pdth, oper, pcs); if (iError) { if (iError & OPER_ERROR) { //For connected nodes, ignore the error and silently abort the node! if (DTNIsConnected(pdth->pdtnCurrent)) { DTAbortCurrentNode(pdth); return DTGoToNextNode(pdth, pcs); } else return iError; } else { switch (iError) { case IDNO: DTAbortCurrentNode(pdth); pcs->lpfo->fAnyOperationsAborted = TRUE; return DTGoToNextNode(pdth, pcs); case IDCANCEL: // User cancelled the operation pcs->bAbort = TRUE; return 0; } } } pdth->oper = oper; return oper; } int CopyMoveRetry(COPY_STATE *pcs, LPCTSTR pszDest, int error, ULARGE_INTEGER* puliFileSize); void CopyError(LPCOPY_STATE, LPCTSTR, LPCTSTR, int, UINT, int); void SetProgressTime(COPY_STATE *pcs); void SetProgressText(COPY_STATE *pcs, LPCTSTR pszFrom, LPCTSTR pszTo); void FOUndo_AddInfo(UNDOATOM *lpua, LPTSTR lpszSrc, LPTSTR lpszDest, DWORD dwAttributes); void CALLBACK FOUndo_Release(UNDOATOM *lpua); void FOUndo_FileReallyDeleted(LPTSTR lpszFile); void AddRenamePairToHDSA(LPCTSTR pszOldPath, LPCTSTR pszNewPath, HDSA* phdsaRenamePairs); BOOL_PTR CALLBACK FOFProgressDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam); typedef struct { LPTSTR lpszName; DWORD dwAttributes; } FOUNDO_DELETEDFILEINFO, *LPFOUNDO_DELETEDFILEINFO; typedef struct { HDPA hdpa; HDSA hdsa; } FOUNDODATA, *LPFOUNDODATA; void ReleasePCS(COPY_STATE *pcs) { ASSERT( 0 != pcs->nRef ); if (0 == InterlockedDecrement(&pcs->nRef)) { SimplePidlCache_Release(&pcs->spc); LocalFree(pcs); } } DWORD CALLBACK AddRefPCS(COPY_STATE *pcs) { return InterlockedIncrement(&pcs->nRef); } DWORD CALLBACK FOUIThreadProc(COPY_STATE *pcs) { DebugMsg(TF_DEBUGCOPY, TEXT("FOUIThreadProc -- Begin")); Sleep(SHOW_PROGRESS_TIMEOUT); if (!pcs->fDone) { HWND hwndParent; FOUITHREADINFO fouiti = {0}; ENTERCRITICAL; if (!pcs->fDone) { // need to check again within the critsec to make sure that pcs->lpfo is still valid fouiti.pcs = pcs; fouiti.wFunc = pcs->lpfo->wFunc; fouiti.bIsEmptyRBOp = ((pcs->lpfo->lpszProgressTitle == MAKEINTRESOURCE(IDS_BB_EMPTYINGWASTEBASKET)) || (pcs->lpfo->lpszProgressTitle == MAKEINTRESOURCE(IDS_BB_DELETINGWASTEBASKETFILES))); hwndParent = pcs->lpfo->hwnd; } LEAVECRITICAL; if (fouiti.pcs) { HWND hwnd = CreateDialogParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_MOVECOPYPROGRESS), hwndParent, FOFProgressDlgProc, (LPARAM)&fouiti); if (hwnd) { MSG msg; DWORD dwShowTime; int iShowTimeLeft; // crit section to sync with main thread termination ENTERCRITICAL; if (!pcs->fDone) { pcs->hwndProgress = hwnd; } LEAVECRITICAL; dwShowTime = GetTickCount(); while (!pcs->fDone && GetMessage(&msg, NULL, 0, 0)) { if (!pcs->fDone && !IsDialogMessage(pcs->hwndProgress, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } // if we've put it up, we need to keep it up for at least some minimal amount of time iShowTimeLeft = MINSHOWTIME - (GetTickCount() - dwShowTime); if (iShowTimeLeft > 0) { DebugMsg(TF_DEBUGCOPY, TEXT("FOUIThreadProc -- doing an extra sleep")); Sleep(iShowTimeLeft); } // Keep us from doing this while other thread processing... ENTERCRITICAL; pcs->hwndProgress = NULL; LEAVECRITICAL; DestroyWindow(hwnd); } } else { // main thread must have finished ASSERT(pcs->fDone); } } ReleasePCS(pcs); DebugMsg(TF_DEBUGCOPY, TEXT("FOUIThreadProc -- End . Completed")); return 0; } // this queries the progress dialog for a cancel and yields. // it also will show the progress dialog if a certain amount of time has passed // // returns: // TRUE cacnel was pressed, abort the operation // FALSE continue BOOL FOQueryAbort(COPY_STATE *pcs) { if (!pcs->bAbort && pcs->hwndProgress) { if (pcs->hwndProgress != pcs->hwndDlgParent) { // do this here rather than on the FOUIThreadProc so that we don't have // synchronization problems with this thread popping up a dialog on // hwndDlgParent then the progress dialog coming up afterwards on top. pcs->hwndDlgParent = pcs->hwndProgress; ShowWindow(pcs->hwndProgress, SW_SHOW); SetForegroundWindow(pcs->hwndProgress); SetFocus(GetDlgItem(pcs->hwndProgress, IDCANCEL)); SetProgressText(pcs, pcs->dth.szSrcPath, pcs->lpfo->wFunc == FO_DELETE ? NULL : pcs->dth.szDestPath); } else { MSG msg; // win95 handled messages in here. // we need to do the same in order to flush the input queue as well as // for backwards compatability. // we need to flush the input queue now because hwndProgress is // on a different thread... which means it has attached thread inputs // inorder to unlock the attached threads, we need to remove some // sort of message until there's none left... any type of message.. while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (!IsDialogMessage(pcs->hwndProgress, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } } if (pcs->dth.dtAll.fChanged || pcs->dth.dtDone.fChanged) { if (!pcs->dth.fChangePosted) { // set the flag first because with async threads // the progress window could handle it and clear the // bit before we set it.. then we'd lose further messages // thinking that one was still pending pcs->dth.fChangePosted = TRUE; if (!PostMessage(pcs->hwndProgress, PDM_UPDATE, 0, 0)) pcs->dth.fChangePosted = FALSE; } } } return pcs->bAbort; } typedef struct _confdlg_data { LPCTSTR pFileDest; LPCTSTR pFileSource; LPCTSTR pStreamNames; const WIN32_FIND_DATA *pfdDest; const WIN32_FIND_DATA *pfdSource; BOOL bShowCancel; // allow cancel out of this operation BOOL bShowDates; // use date/size info in message UINT uDeleteWarning; // warn that the delete's not going to the wastebasket BOOL bFireIcon; BOOL bShrinkDialog; // should we move the buttons up to the text? int nSourceFiles; // if != 1 used to build "n files" string int idText; // if != 0 use to override string in dlg template CONFIRM_FLAG fConfirm; // we will confirm things set here CONFIRM_FLAG fYesMask; // these bits are cleared in fConfirm on "yes" // Only use fYesMask for things that should be confirmed once per operation CONFIRM_FLAG fYesToAllMask; // these bits are cleared in fConfirm on "yes to all" //COPY_STATE *pcs; CONFIRM_DATA *pcd; void (*InitConfirmDlg)(HWND hDlg, struct _confdlg_data *pcd); // routine to initialize dialog BOOL bARPWarning; } CONFDLG_DATA; BOOL BuildDateLine(LPTSTR pszDateLine, UINT cchDateLine, const WIN32_FIND_DATA *pFind, LPCTSTR pFileName) { TCHAR szTemplate[64]; TCHAR szNum[32], szTmp[64]; WIN32_FIND_DATA fd; ULARGE_INTEGER liFileSize; if (!pFind) { HANDLE hfind = FindFirstFile(pFileName, &fd); ASSERT(hfind != INVALID_HANDLE_VALUE); FindClose(hfind); pFind = &fd; } liFileSize.LowPart = pFind->nFileSizeLow; liFileSize.HighPart = pFind->nFileSizeHigh; // There are cases where the date is 0, this is especially true when the // source is from a file contents... if (pFind->ftLastWriteTime.dwLowDateTime || pFind->ftLastWriteTime.dwHighDateTime) { DWORD dwFlags = FDTF_LONGDATE | FDTF_RELATIVE | FDTF_LONGTIME; SHFormatDateTime(&pFind->ftLastWriteTime, &dwFlags, szTmp, SIZECHARS(szTmp)); LoadString(HINST_THISDLL, IDS_DATESIZELINE, szTemplate, ARRAYSIZE(szTemplate)); StringCchPrintf(pszDateLine, cchDateLine, szTemplate, StrFormatByteSize64(liFileSize.QuadPart, szNum, ARRAYSIZE(szNum)), szTmp); } else { // Simpy output the number to the string StrFormatByteSize64(liFileSize.QuadPart, pszDateLine, cchDateLine); if (liFileSize.QuadPart == 0) return FALSE; } return TRUE; // valid data in the strings } // hide the cancel button and move "Yes" and "No" over to the right positions. // // "Yes" is IDYES // "No" is IDNO // #define HideYesToAllAndCancel(hdlg) HideConfirmButtons(hdlg, IDCANCEL) #define HideYesToAllAndNo(hdlg) HideConfirmButtons(hdlg, IDNO) void HideConfirmButtons(HWND hdlg, int idHide) { HWND hwndCancel = GetDlgItem(hdlg, IDCANCEL); HWND hwndYesToAll = GetDlgItem(hdlg, IDD_YESTOALL); if (hwndCancel) { RECT rcCancel; HWND hwndNo; GetWindowRect(hwndCancel, &rcCancel); hwndNo = GetDlgItem(hdlg, IDNO); if (hwndNo) { RECT rcNo; HWND hwndYes; GetWindowRect(hwndNo, &rcNo); MapWindowRect(NULL, hdlg, &rcCancel); SetWindowPos(hwndNo, NULL, rcCancel.left, rcCancel.top, 0, 0, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE); hwndYes = GetDlgItem(hdlg, IDYES); if (hwndYes) { MapWindowRect(NULL, hdlg, &rcNo); SetWindowPos(hwndYes, NULL, rcNo.left, rcNo.top, 0, 0, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE); } } // Although the function is called "Hide", we actually destroy // the windows, because keyboard accelerators for hidden windows // are still active! if (hwndYesToAll) DestroyWindow(hwndYesToAll); DestroyWindow(GetDlgItem(hdlg, idHide)); } } int MoveDlgItem(HWND hDlg, UINT id, int y) { RECT rc; HWND hwnd = GetDlgItem(hDlg, id); if (hwnd) { GetWindowRect(hwnd, &rc); MapWindowRect(NULL, hDlg, &rc); SetWindowPos(hwnd, NULL, rc.left, y, 0,0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); return rc.top - y; // return how much it moved } return 0; } void ShrinkDialog(HWND hDlg, UINT idText) { RECT rc; int y; HWND hwnd; hwnd = GetDlgItem(hDlg, idText); ASSERT(hwnd); GetWindowRect(hwnd, &rc); MapWindowRect(NULL, hDlg, &rc); y = rc.bottom + 12; // move all the buttons MoveDlgItem(hDlg, IDNO, y); MoveDlgItem(hDlg, IDCANCEL, y); MoveDlgItem(hDlg, IDD_YESTOALL, y); y = MoveDlgItem(hDlg, IDYES, y); // now resize the entire dialog GetWindowRect(hDlg, &rc); SetWindowPos(hDlg, NULL, 0, 0, rc.right - rc.left, rc.bottom - y - rc.top, SWP_NOMOVE | SWP_NOZORDER |SWP_NOACTIVATE); } void InitConfirmDlg(HWND hDlg, CONFDLG_DATA *pcd) { TCHAR szMessage[255]; TCHAR szDeleteWarning[80]; TCHAR szSrc[32]; TCHAR szFriendlyName[MAX_PATH]; SHFILEINFO sfi; SHFILEINFO sfiDest; LPTSTR pszFileDest = NULL; LPTSTR pszMsg, pszSource; int i; int cxWidth; RECT rc; HDC hdc; HFONT hfont; HFONT hfontSave; SIZE size; BOOL bIsARPWarning = pcd->bARPWarning; ASSERT((bIsARPWarning && (pcd->nSourceFiles == 1)) || (!bIsARPWarning)); hdc = GetDC(hDlg); hfont = (HFONT)SendMessage(hDlg, WM_GETFONT, 0, 0); hfontSave = (HFONT)SelectObject(hdc, hfont); // get the size of the text boxes GetWindowRect(GetDlgItem(hDlg, pcd->idText), &rc); cxWidth = rc.right - rc.left; // // There are cases where, if the filename has no spaces, the static text // control will put the entire file name with our quote character down // on the 2nd line. To account for this, we subtract off the width of a // the quote character. Since the quote character comes from the resource // string, it could really be just about an character, with just about // any width. So we assume its about the width of the letter 0, which // should be more than wide enough. size.cx = 0; GetTextExtentPoint(hdc, TEXT("0"), 1, &size); cxWidth -= size.cx * 2; if (!bIsARPWarning && !pcd->bShowCancel) HideYesToAllAndCancel(hDlg); switch (pcd->nSourceFiles) { case -1: LoadString(HINST_THISDLL, IDS_SELECTEDFILES, szSrc, ARRAYSIZE(szSrc)); pszSource = szSrc; break; case 1: if (bIsARPWarning) { TCHAR szTarget[MAX_PATH]; DWORD cchFriendlyName = ARRAYSIZE(szFriendlyName); HRESULT hres = GetPathFromLinkFile(pcd->pFileSource, szTarget, ARRAYSIZE(szTarget)); if (S_OK == hres) { if (SUCCEEDED(AssocQueryString(ASSOCF_VERIFY | ASSOCF_OPEN_BYEXENAME, ASSOCSTR_FRIENDLYAPPNAME, szTarget, NULL, szFriendlyName, &cchFriendlyName))) { pszSource = szFriendlyName; } else { pszSource = PathFindFileName(szTarget); } } else if (S_FALSE == hres) { TCHAR szProductCode[MAX_PATH]; szProductCode[0] = 0; if ((ERROR_SUCCESS == MsiDecomposeDescriptor(szTarget, szProductCode, NULL, NULL, NULL)) && (ERROR_SUCCESS == MsiGetProductInfo(szProductCode, INSTALLPROPERTY_PRODUCTNAME, szFriendlyName, &cchFriendlyName))) { pszSource = szFriendlyName; } else goto UNKNOWNAPP; } else { UNKNOWNAPP: LoadString(HINST_THISDLL, IDS_UNKNOWNAPPLICATION, szSrc, ARRAYSIZE(szSrc)); pszSource = szSrc; } } else { SHGetFileInfo(pcd->pFileSource, (pcd->fConfirm==CONFIRM_DELETE_FOLDER || pcd->fConfirm==CONFIRM_WONT_RECYCLE_FOLDER)? FILE_ATTRIBUTE_DIRECTORY : 0, &sfi, sizeof(sfi), SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES); pszSource = sfi.szDisplayName; PathCompactPath(hdc, pszSource, cxWidth); } break; default: pszSource = AddCommas(pcd->nSourceFiles, szSrc, ARRAYSIZE(szSrc)); break; } // if we're supposed to show the date info, grab the icons and format the date string if (pcd->bShowDates) { SHFILEINFO sfi2; TCHAR szDateSrc[64], szDateDest[64]; BuildDateLine(szDateSrc, ARRAYSIZE(szDateSrc), pcd->pfdSource, pcd->pFileSource); SetDlgItemText(hDlg, IDD_FILEINFO_NEW, szDateSrc); BuildDateLine(szDateDest, ARRAYSIZE(szDateSrc), pcd->pfdDest, pcd->pFileDest); SetDlgItemText(hDlg, IDD_FILEINFO_OLD, szDateDest); SHGetFileInfo(pcd->pFileDest, pcd->pfdDest ? pcd->pfdDest->dwFileAttributes : 0, &sfi2, sizeof(sfi2), pcd->pfdDest ? (SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_LARGEICON) : (SHGFI_ICON|SHGFI_LARGEICON)); ReplaceDlgIcon(hDlg, IDD_ICON_OLD, sfi2.hIcon); SHGetFileInfo(pcd->pFileSource, pcd->pfdSource ? pcd->pfdSource->dwFileAttributes : 0, &sfi2, sizeof(sfi2), pcd->pfdSource ? (SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_LARGEICON) : (SHGFI_ICON|SHGFI_LARGEICON)); ReplaceDlgIcon(hDlg, IDD_ICON_NEW, sfi2.hIcon); } if (!bIsARPWarning) { // there are multiple controls: // IDD_TEXT contains regular text (normal file/folder) // IDD_TEXT1 - IDD_TEXT4 contain optional secondary text for (i = IDD_TEXT; i <= IDD_TEXT4; i++) { if (i == pcd->idText) { szMessage[0] = 0; GetDlgItemText(hDlg, i, szMessage, ARRAYSIZE(szMessage)); } else { HWND hwndCtl = GetDlgItem(hDlg, i); if (hwndCtl) ShowWindow(hwndCtl, SW_HIDE); } } } else { GetDlgItemText(hDlg, IDD_ARPWARNINGTEXT, szMessage, ARRAYSIZE(szMessage)); } // REVIEW Is there some better way? The code above always hides // this control, and I don't see a way around this if (pcd->pStreamNames) { SetDlgItemText(hDlg, IDD_TEXT1, pcd->pStreamNames); ShowWindow(GetDlgItem(hDlg, IDD_TEXT1), SW_SHOW); } if (pcd->bShrinkDialog) ShrinkDialog(hDlg, pcd->idText); if (pcd->pFileDest) { SHGetFileInfo(pcd->pFileDest, 0, &sfiDest, sizeof(sfiDest), SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES); pszFileDest = sfiDest.szDisplayName; PathCompactPath(hdc, pszFileDest, cxWidth); } if (pcd->uDeleteWarning) { LPITEMIDLIST pidl; if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_BITBUCKET, &pidl))) { SHFILEINFO fi; if (SHGetFileInfo((LPCTSTR)pidl, 0, &fi, sizeof(fi), SHGFI_PIDL | SHGFI_ICON |SHGFI_LARGEICON)) { ReplaceDlgIcon(hDlg, IDD_ICON_WASTEBASKET, fi.hIcon); } ILFree(pidl); } LoadString(HINST_THISDLL, pcd->uDeleteWarning, szDeleteWarning, ARRAYSIZE(szDeleteWarning)); } else szDeleteWarning[0] = 0; if (pcd->bFireIcon) { ReplaceDlgIcon(hDlg, IDD_ICON_WASTEBASKET, LoadImage(HINST_THISDLL, MAKEINTRESOURCE(IDI_NUKEFILE), IMAGE_ICON, 0, 0, LR_LOADMAP3DCOLORS)); } pszMsg = ShellConstructMessageString(HINST_THISDLL, szMessage, pszSource, pszFileDest, szDeleteWarning); if (pszMsg) { SetDlgItemText(hDlg, pcd->idText, pszMsg); LocalFree(pszMsg); } SelectObject(hdc, hfontSave); ReleaseDC(hDlg, hdc); } BOOL_PTR CALLBACK ConfirmDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam) { CONFDLG_DATA *pcd = (CONFDLG_DATA *)GetWindowLongPtr(hDlg, DWLP_USER); switch (wMsg) { case WM_INITDIALOG: SetWindowLongPtr(hDlg, DWLP_USER, lParam); pcd = (CONFDLG_DATA *)lParam; pcd->InitConfirmDlg(hDlg, pcd); break; case WM_DESTROY: // Handle case where the allocation of the PCD failed. if (!pcd) break; if (pcd->bShowDates) { ReplaceDlgIcon(hDlg, IDD_ICON_NEW, NULL); ReplaceDlgIcon(hDlg, IDD_ICON_OLD, NULL); } ReplaceDlgIcon(hDlg, IDD_ICON_WASTEBASKET, NULL); break; case WM_COMMAND: if (!pcd) break; switch (GET_WM_COMMAND_ID(wParam, lParam)) { case IDNO: if (GetKeyState(VK_SHIFT) < 0) // force NOTOALL { // I use the fYesToAllMask here. There used to be a fNoToAllMask but I // removed it. When you select "No To All" what you are saying is that // anything I would be saying yes to all for I am actually saying "no to // all" for. I feel that it is confusing and unnecessary to have both. pcd->pcd->fNoToAll |= pcd->fYesToAllMask; } EndDialog(hDlg, IDNO); break; case IDD_YESTOALL: // pcd is the confirmation data for just this file/folder. pcd->pcd is the // confirm data for the entire copy operation. When we get a Yes To All we // remove the coresponding bits from the entire operation. pcd->pcd->fConfirm &= ~pcd->fYesToAllMask; EndDialog(hDlg, IDYES); break; case IDYES: // There are some messages that we only want to tell the use once even if they // select Yes instead of Yes To All. As such we sometimes remove bits from the // global confirm state even on a simple Yes. This mask is usually zero. pcd->pcd->fConfirm &= ~pcd->fYesMask; EndDialog(hDlg, IDYES); break; case IDCANCEL: EndDialog(hDlg, IDCANCEL); break; } break; case WM_NOTIFY: switch (((LPNMHDR)lParam)->code) { case NM_RETURN: case NM_CLICK: { TCHAR szModule[MAX_PATH]; if (GetSystemDirectory(szModule, ARRAYSIZE(szModule))) { if (PathAppend(szModule, TEXT("appwiz.cpl"))) { TCHAR szParam[1 + MAX_PATH + 2 + MAX_CCH_CPLNAME]; // See MakeCPLCommandLine function TCHAR szAppwiz[64]; LoadString(g_hinst, IDS_APPWIZCPL, szAppwiz, SIZECHARS(szAppwiz)); MakeCPLCommandLine(szModule, szAppwiz, szParam, ARRAYSIZE(szParam)); SHRunControlPanelEx(szParam, NULL, FALSE); } } EndDialog(hDlg, IDNO); } break; } break; default: return FALSE; } return TRUE; } void SetConfirmMaskAndText(CONFDLG_DATA *pcd, DWORD dwFileAttributes, LPCTSTR pszFile) { if (IS_SYSTEM_HIDDEN(dwFileAttributes) && !ShowSuperHidden()) { dwFileAttributes &= ~FILE_ATTRIBUTE_SUPERHIDDEN; } // we used to have a desktop.ini "ConfirmFileOp" flag that was set // to avoid this case, but there are no folders that are marked READONLY // or SYSTEM for a reason other than the shell, so don't consider any as such if ((dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY)) && (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { dwFileAttributes &= ~(FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY); } if (dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) { pcd->fConfirm = CONFIRM_SYSTEM_FILE; pcd->fYesToAllMask |= CONFIRM_SYSTEM_FILE; pcd->idText = IDD_TEXT2; } else if (dwFileAttributes & FILE_ATTRIBUTE_READONLY) { pcd->fConfirm = CONFIRM_READONLY_FILE; pcd->fYesToAllMask |= CONFIRM_READONLY_FILE; pcd->idText = IDD_TEXT1; } else if (pszFile && ((dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) && PathIsRegisteredProgram(pszFile)) { pcd->fConfirm = CONFIRM_PROGRAM_FILE; pcd->fYesToAllMask |= CONFIRM_PROGRAM_FILE; pcd->idText = IDD_TEXT3; } } void PauseAnimation(COPY_STATE *pcs, BOOL bStop) { // only called from within the hwndProgress wndproc so assum it's there if (bStop) Animate_Stop(GetDlgItem(pcs->hwndProgress, IDD_ANIMATE)); else Animate_Play(GetDlgItem(pcs->hwndProgress, IDD_ANIMATE), -1, -1, -1); } // confirm a file operation UI. // // this routine uses the CONFIRM_DATA in the copy state structure to // decide if it needs to put up a dailog to confirm the given file operation. // // in: // pcs current copy state (confirm flags, hwnd) // fConfirm only one bit may be set! (operation to confirm) // pFileSource source file // pFileDest optional destination file // pfdSource // pfdDest find data describing the destination // // returns: // IDYES // IDNO // IDCANCEL // ERROR_ (DE_) error codes (DE_MEMORY) // int ConfirmFileOp(HWND hwnd, COPY_STATE *pcs, CONFIRM_DATA *pcd, int nSourceFiles, int cDepth, CONFIRM_FLAG fConfirm, LPCTSTR pFileSource, const WIN32_FIND_DATA *pfdSource, LPCTSTR pFileDest, const WIN32_FIND_DATA *pfdDest, LPCTSTR pStreamNames) { int dlg; int ret; CONFDLG_DATA cdd; CONFIRM_FLAG fConfirmType; if (pcs) nSourceFiles = pcs->nSourceFiles; cdd.pfdSource = pfdSource; cdd.pfdDest = NULL; // pfdDest is only partially filed in cdd.pFileSource = pFileSource; cdd.pFileDest = pFileDest; cdd.pcd = pcd; cdd.fConfirm = fConfirm; // default, changed below cdd.fYesMask = 0; cdd.fYesToAllMask = 0; cdd.nSourceFiles = 1; // default to individual file names in message cdd.idText = IDD_TEXT; // default string from the dlg template cdd.bShowCancel = ((nSourceFiles != 1) || cDepth); cdd.uDeleteWarning = 0; cdd.bFireIcon = FALSE; cdd.bShowDates = FALSE; cdd.bShrinkDialog = FALSE; cdd.InitConfirmDlg = InitConfirmDlg; cdd.pStreamNames = NULL; cdd.bARPWarning = FALSE; fConfirmType = fConfirm & CONFIRM_FLAG_TYPE_MASK; switch (fConfirmType) { case CONFIRM_DELETE_FILE: case CONFIRM_DELETE_FOLDER: { BOOL bIsFolderShortcut = FALSE; cdd.bShrinkDialog = TRUE; // find data for source is in pdfDest if ((nSourceFiles != 1) && (pcd->fConfirm & CONFIRM_MULTIPLE)) { // this is the special CONFIRM_MULTIPLE case (usuall SHIFT+DELETE, or // SHIFT+DRAG to Recycle Bin). if the user says yes to this, they // basically get no more warnings. cdd.nSourceFiles = nSourceFiles; if ((fConfirm & CONFIRM_WASTEBASKET_PURGE) || (!pcs || !(pcs->fFlags & FOF_ALLOWUNDO)) || !BBWillRecycle(cdd.pFileSource, NULL)) { // have the fire icon and the REALLY delete warning cdd.uDeleteWarning = IDS_FOLDERDELETEWARNING; cdd.bFireIcon = TRUE; if (pcs) pcs->fFlags &= ~FOF_ALLOWUNDO; if (nSourceFiles == -1) { // -1 indicates that there were >= MAX_EMPTY_FILES files, so we stoped counting // them all up for perf. We use the more generic message in this case. cdd.idText = IDD_TEXT3; } else { // use the "are you sure you want to nuke XX files?" message cdd.idText = IDD_TEXT4; } } else { // uDeleteWarning must be set for the proper recycle icon to be loaded. cdd.uDeleteWarning = IDS_FOLDERDELETEWARNING; } if (!pcs || !pcs->fNoConfirmRecycle) { POINT ptInvoke; HWND hwndPos = NULL; if ((GetNumberOfMonitors() > 1) && GetCursorPos(&ptInvoke)) { HMONITOR hMon = MonitorFromPoint(ptInvoke, MONITOR_DEFAULTTONULL); if (hMon) { hwndPos = _CreateStubWindow(&ptInvoke, hwnd); } } ret = (int)DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_DELETE_MULTIPLE), hwndPos ? hwndPos : hwnd, ConfirmDlgProc, (LPARAM)&cdd); if (hwndPos) { DestroyWindow(hwndPos); } if (ret != IDYES) { return IDCANCEL; } } // clear all other possible warnings pcd->fConfirm &= ~(CONFIRM_MULTIPLE | CONFIRM_DELETE_FILE | CONFIRM_DELETE_FOLDER); cdd.fConfirm &= ~(CONFIRM_DELETE_FILE | CONFIRM_DELETE_FOLDER); cdd.nSourceFiles = 1; // use individual file name } SetConfirmMaskAndText(&cdd, pfdDest->dwFileAttributes, cdd.pFileSource); if ((pfdDest->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && PathIsShortcut(cdd.pFileSource, pfdDest->dwFileAttributes)) { // Its a folder and its a shortcut... must be a FolderShortcut! bIsFolderShortcut = TRUE; // since its a folder, we need to clear out all of these warnings cdd.fYesMask |= CONFIRM_DELETE_FILE | CONFIRM_DELETE_FOLDER | CONFIRM_MULTIPLE; cdd.fYesToAllMask |= CONFIRM_DELETE_FILE | CONFIRM_DELETE_FOLDER | CONFIRM_MULTIPLE; } // we want to treat FolderShortcuts as "files" instead of folders. We do this so we don't display dialogs // that say stuff like "do you want to delete this and all of its contents" when to the user, this looks like // an item instead of a folder (eg nethood shortcut). if ((fConfirmType == CONFIRM_DELETE_FILE) || bIsFolderShortcut) { dlg = DLG_DELETE_FILE; if ((nSourceFiles == 1) && PathIsShortcutToProgram(cdd.pFileSource)) { dlg = DLG_DELETE_FILE_ARP; cdd.idText = IDD_ARPWARNINGTEXT; cdd.bShrinkDialog = FALSE; cdd.bARPWarning = TRUE; } if ((fConfirm & CONFIRM_WASTEBASKET_PURGE) || (!pcs || !(pcs->fFlags & FOF_ALLOWUNDO)) || !BBWillRecycle(cdd.pFileSource, NULL)) { // we are really nuking it, so show the appropriate icon/dialog cdd.bFireIcon = TRUE; if (pcs) { pcs->fFlags &= ~FOF_ALLOWUNDO; } cdd.uDeleteWarning = IDS_FILEDELETEWARNING; if (cdd.idText == IDD_TEXT) { cdd.idText = IDD_TEXT4; } } else { // we are recycling it cdd.uDeleteWarning = IDS_FILERECYCLEWARNING; } } else { // fConfirmType == CONFIRM_DELETE_FOLDER if (pcs) { // show cancel on NEXT confirm dialog pcs->nSourceFiles = -1; } cdd.fYesMask |= CONFIRM_DELETE_FILE | CONFIRM_DELETE_FOLDER | CONFIRM_MULTIPLE; cdd.fYesToAllMask |= CONFIRM_DELETE_FILE | CONFIRM_DELETE_FOLDER | CONFIRM_MULTIPLE; dlg = DLG_DELETE_FOLDER; if ((fConfirm & CONFIRM_WASTEBASKET_PURGE) || (!pcs || !(pcs->fFlags & FOF_ALLOWUNDO)) || !BBWillRecycle(cdd.pFileSource, NULL)) { // we are really nuking it, so show the appropriate icon/dialog cdd.bFireIcon = TRUE; if (pcs) { pcs->fFlags &= ~FOF_ALLOWUNDO; } cdd.uDeleteWarning = IDS_FOLDERDELETEWARNING; } else { // we are recycling it cdd.uDeleteWarning = IDS_FOLDERRECYCLEWARNING; } } // // NTRAID#NTBUG9-100335-2001/01/03-jeffreys // See also #128485 in the OSR v 4.1 database // // The fix for 128485 added the BBWillRecycle check below, but this // caused NTBUG9-100335. These 2 bugs say the opposite things. // We've had several customer complaints (see dupes of 100335) // so I'm putting it back to the way it worked in Windows 2000. // if (pcs && pcs->fNoConfirmRecycle /*&& BBWillRecycle(cdd.pFileSource, NULL)*/) { cdd.fConfirm = 0; } } break; case CONFIRM_WONT_RECYCLE_FILE: case CONFIRM_WONT_RECYCLE_FOLDER: cdd.bShrinkDialog = TRUE; cdd.nSourceFiles = 1; cdd.bFireIcon = TRUE; cdd.idText = IDD_TEXT; cdd.fYesMask = CONFIRM_MULTIPLE; cdd.fConfirm = fConfirmType; cdd.fYesToAllMask = fConfirmType | CONFIRM_MULTIPLE; // set the dialog to be file or folder if (fConfirmType == CONFIRM_WONT_RECYCLE_FOLDER) { dlg = DLG_WONT_RECYCLE_FOLDER; } else { dlg = DLG_WONT_RECYCLE_FILE; } break; case CONFIRM_PATH_TOO_LONG: cdd.bShrinkDialog = TRUE; cdd.nSourceFiles = 1; cdd.bFireIcon = TRUE; cdd.idText = IDD_TEXT; cdd.fYesMask = CONFIRM_MULTIPLE; cdd.fConfirm = CONFIRM_PATH_TOO_LONG; cdd.fYesToAllMask = CONFIRM_PATH_TOO_LONG | CONFIRM_MULTIPLE; dlg = DLG_PATH_TOO_LONG; break; case CONFIRM_WONT_RECYCLE_OFFLINE: cdd.bShrinkDialog = TRUE; cdd.nSourceFiles = 1; cdd.bFireIcon = TRUE; cdd.idText = IDD_TEXT; cdd.fYesMask = CONFIRM_MULTIPLE; cdd.fConfirm = fConfirmType; cdd.fYesToAllMask = fConfirmType | CONFIRM_MULTIPLE; dlg = DLG_WONT_RECYCLE_OFFLINE; break; case CONFIRM_STREAMLOSS: cdd.bShrinkDialog = FALSE; cdd.nSourceFiles = 1; cdd.idText = IDD_TEXT; cdd.fConfirm = CONFIRM_STREAMLOSS; cdd.fYesToAllMask = CONFIRM_STREAMLOSS; cdd.pStreamNames = pStreamNames; dlg = DLG_STREAMLOSS_ON_COPY; break; case CONFIRM_FAILED_ENCRYPT: cdd.bShrinkDialog = FALSE; cdd.nSourceFiles = 1; cdd.idText = IDD_TEXT; cdd.bShowCancel = TRUE; cdd.fConfirm = CONFIRM_FAILED_ENCRYPT; cdd.fYesToAllMask = CONFIRM_FAILED_ENCRYPT; dlg = DLG_FAILED_ENCRYPT; break; case CONFIRM_LOST_ENCRYPT_FILE: case CONFIRM_LOST_ENCRYPT_FOLDER: cdd.bShrinkDialog = FALSE; cdd.nSourceFiles = 1; cdd.idText = IDD_TEXT; cdd.bShowCancel = TRUE; cdd.fConfirm = CONFIRM_LOST_ENCRYPT_FILE | CONFIRM_LOST_ENCRYPT_FOLDER; cdd.fYesToAllMask = CONFIRM_LOST_ENCRYPT_FILE | CONFIRM_LOST_ENCRYPT_FOLDER; if (fConfirmType == CONFIRM_LOST_ENCRYPT_FILE) { dlg = DLG_LOST_ENCRYPT_FILE; } else { dlg = DLG_LOST_ENCRYPT_FOLDER; } break; case CONFIRM_REPLACE_FILE: cdd.bShowDates = TRUE; cdd.fYesToAllMask = CONFIRM_REPLACE_FILE; SetConfirmMaskAndText(&cdd, pfdDest->dwFileAttributes, NULL); dlg = DLG_REPLACE_FILE; break; case CONFIRM_REPLACE_FOLDER: cdd.bShowCancel = TRUE; if (pcs) pcs->nSourceFiles = -1; // show cancel on NEXT confirm dialog // this implies operations on the files cdd.fYesMask = CONFIRM_REPLACE_FILE; cdd.fYesToAllMask = CONFIRM_REPLACE_FILE | CONFIRM_REPLACE_FOLDER; dlg = DLG_REPLACE_FOLDER; break; case CONFIRM_MOVE_FILE: cdd.fYesToAllMask = CONFIRM_MOVE_FILE; SetConfirmMaskAndText(&cdd, pfdSource->dwFileAttributes, NULL); dlg = DLG_MOVE_FILE; break; case CONFIRM_MOVE_FOLDER: cdd.bShowCancel = TRUE; cdd.fYesToAllMask = CONFIRM_MOVE_FOLDER; SetConfirmMaskAndText(&cdd, pfdSource->dwFileAttributes, cdd.pFileSource); dlg = DLG_MOVE_FOLDER; break; case CONFIRM_RENAME_FILE: SetConfirmMaskAndText(&cdd, pfdSource->dwFileAttributes, NULL); dlg = DLG_RENAME_FILE; break; case CONFIRM_RENAME_FOLDER: cdd.bShowCancel = TRUE; if (pcs) pcs->nSourceFiles = -1; // show cancel on NEXT confirm dialog SetConfirmMaskAndText(&cdd, pfdSource->dwFileAttributes, cdd.pFileSource); dlg = DLG_RENAME_FOLDER; break; default: DebugMsg(DM_WARNING, TEXT("bogus confirm option")); return IDCANCEL; } // Does this operation need to be confirmed? if (pcd->fConfirm & cdd.fConfirm) { // Has the user already said "No To All" for this operation? if ((pcd->fNoToAll & cdd.fConfirm) == cdd.fConfirm) { ret = IDNO; } else { // HACK for multimon, make sure the file operation dialog box comes // up on the correct monitor POINT ptInvoke; HWND hwndPos = NULL; if ((GetNumberOfMonitors() > 1) && GetCursorPos(&ptInvoke)) { HMONITOR hMon = MonitorFromPoint(ptInvoke, MONITOR_DEFAULTTONULL); if (hMon) { hwndPos = _CreateStubWindow(&ptInvoke, hwnd); } } ret = (int)DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(dlg), (hwndPos ? hwndPos : hwnd), ConfirmDlgProc, (LPARAM)&cdd); if (hwndPos) DestroyWindow(hwndPos); if (ret == -1) ret = ERROR_NOT_ENOUGH_MEMORY; } } else { ret = IDYES; } return ret; } // // DTNIsParentConnectOrigin() // // When a folder ("c:\foo files") is moved to a different drive ("a:\"), the source and the // destinations have different roots, and therefore the "fRecursive" flag is turned ON by default. // This results in confirmations obtained for the individual files ("c:\foo files\aaa.gif") // rather than the folder itself. We need to first find the parent and then save the confirmation // in the connected element of it's parent. This function gets the top-most parent and then // checks to see if it is a connect origin and if so returns that parent pointer. // PDIRTREENODE DTNGetConnectOrigin(PDIRTREENODE pdtn) { PDIRTREENODE pdtnParent = pdtn; //Get the top-level parent of the given node. while (pdtn) { pdtnParent = pdtn; pdtn = pdtn->pdtnParent; } //Now check if the parent is a connect origin. if (pdtnParent && DTNIsConnectOrigin(pdtnParent)) return pdtnParent; //If so, return him. else return NULL; } // // CachedConfirmFileOp() // // When a file("foo.htm") is moved/copied, we may put up a confirmation dialog in case // of a conflict and the end-user might have responded saying "Yes", "no" etc., When the // corresponding connected element ("foo files") is also moved/copied etc., we should NOT put up // a confirmation dialog again. We must simply store the answer to the original confirmation and // use it later. // // What this function does is: if the given node is a connected element, it simply retrieves the // confirmation for the original operation and returns. If the given element is NOT a connected // element, then this function calls the ConfirmFileOp and stores the confirmation result in // it's connected element sothat, it later it can be used by the connected element. // int CachedConfirmFileOp(HWND hwnd, COPY_STATE *pcs, CONFIRM_DATA *pcd, int nSourceFiles, int cDepth, CONFIRM_FLAG fConfirm, LPCTSTR pFileSource, const WIN32_FIND_DATA *pfdSource, LPCTSTR pFileDest, const WIN32_FIND_DATA *pfdDest, LPCTSTR pStreamNames) { int result; //See if this is a connected item. if (DTNIsConnected(pcs->dth.pdtnCurrent)) { // Since this is a connected item, the confirmation must already have been obtained from // the user and get it from the cache! result = DTNGetConfirmationResult(pcs->dth.pdtnCurrent); } else { PDIRTREENODE pdtnConnectOrigin; result = ConfirmFileOp(hwnd, pcs, pcd, nSourceFiles, cDepth, fConfirm, pFileSource, pfdSource, pFileDest, pfdDest, pStreamNames); //Check if this node has a connection. if (pdtnConnectOrigin = DTNGetConnectOrigin(pcs->dth.pdtnCurrent)) { pdtnConnectOrigin->pdtnConnected->ConnectedInfo.dwConfirmation = result; // PERF: Can we check for the result to be IDCANCEL or IDNO and if so make the // connected node a Dummy? Currently this won't work because current code assumes // that dummy nodes do not have children. This connected node might have some children. // if ((result == IDCANCEL) || (result == IDNO)) // pdtnConnectOrigin->pdtnConnected->fDummy = TRUE; } } return result; } void GuessAShortName(LPCTSTR p, LPTSTR szT) { int i, j, fDot, cMax; for (i = j = fDot = 0, cMax = 8; *p; p++) { if (*p == TEXT('.')) { // if there was a previous dot, step back to it // this way, we get the last extension if (fDot) i -= j+1; // set number of chars to 0, put the dot in j = 0; szT[i++] = TEXT('.'); // remember we saw a dot and set max 3 chars. fDot = TRUE; cMax = 3; } else if (j < cMax && (PathGetCharType(*p) & GCT_SHORTCHAR)) { // if *p is a lead byte, we move forward one more if (IsDBCSLeadByte(*p)) { szT[i] = *p++; if (++j >= cMax) continue; ++i; } j++; szT[i++] = *p; } } szT[i] = 0; } /* GetNameDialog * * Runs the dialog box to prompt the user for a new filename when copying * or moving from HPFS to FAT. */ typedef struct { LPTSTR pszDialogFrom; LPTSTR pszDialogTo; BOOL bShowCancel; } GETNAME_DATA; BOOL_PTR CALLBACK GetNameDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam) { TCHAR szT[14]; TCHAR szTo[MAX_PATH]; GETNAME_DATA * pgn = (GETNAME_DATA *)GetWindowLongPtr(hDlg, DWLP_USER); HRESULT hr; BOOL fOk; switch (wMsg) { case WM_INITDIALOG: SetWindowLongPtr(hDlg, DWLP_USER, lParam); pgn = (GETNAME_DATA *)lParam; // inform the user of the old name PathSetDlgItemPath(hDlg, IDD_FROM, pgn->pszDialogFrom); // directory the file will go into PathRemoveFileSpec(pgn->pszDialogTo); PathSetDlgItemPath(hDlg, IDD_DIR, pgn->pszDialogTo); // generate a guess for the new name GuessAShortName(PathFindFileName(pgn->pszDialogFrom), szT); fOk = FALSE; hr = StringCchCopy(szTo, ARRAYSIZE(szTo), pgn->pszDialogTo); if (SUCCEEDED(hr)) { if (PathAppend(szTo, szT)) { // make sure that name is unique if (PathYetAnotherMakeUniqueName(szTo, szTo, NULL, NULL)) { fOk = TRUE; } } } SetDlgItemText(hDlg, IDD_TO, fOk ? PathFindFileName(szTo) : TEXT("")); SendDlgItemMessage(hDlg, IDD_TO, EM_LIMITTEXT, 13, 0L); SHAutoComplete(GetDlgItem(hDlg, IDD_TO), 0); if (!pgn->bShowCancel) HideYesToAllAndNo(hDlg); break; case WM_COMMAND: switch (GET_WM_COMMAND_ID(wParam, lParam)) { case IDD_YESTOALL: case IDYES: GetDlgItemText(hDlg, IDD_TO, szT, ARRAYSIZE(szT)); if (szT[0] == TEXT('\0') || !PathCombine(szTo, pgn->pszDialogTo, szT)) { // ignore the button press, since we can't use the name break; } hr = StringCchCopy(pgn->pszDialogTo, MAX_PATH, szTo); if (FAILED(hr)) { // This should never fail because pdth->pszDestPath (what is passed to GetNameDialog as the pTo parameter) // is the same size (MAX_PATH) as szTo break; } PathQualify(pgn->pszDialogTo); // fall through case IDNO: case IDCANCEL: EndDialog(hDlg,GET_WM_COMMAND_ID(wParam, lParam)); break; case IDD_TO: { LPCTSTR p; GetDlgItemText(hDlg, IDD_TO, szT, ARRAYSIZE(szT)); for (p = szT; *p; p = CharNext(p)) { if (!(PathGetCharType(*p) & GCT_SHORTCHAR)) break; } EnableWindow(GetDlgItem(hDlg,IDYES), ((!*p) && (p != szT))); } break; default: return FALSE; } break; default: return FALSE; } return TRUE; } int GetNameDialog(HWND hwnd, COPY_STATE *pcs, BOOL fMultiple,UINT wOp, LPTSTR pFrom, LPTSTR pTo) { int iRet; // if we don't want to confirm this, just mock up a string and return ok if (!(pcs->cd.fConfirm & CONFIRM_LFNTOFAT)) { TCHAR szTemp[MAX_PATH]; TCHAR szTo[MAX_PATH]; HRESULT hr; GuessAShortName(PathFindFileName(pFrom), szTemp); hr = StringCchCopy(szTo, ARRAYSIZE(szTo), pTo); if (SUCCEEDED(hr)) { PathRemoveFileSpec(szTo); if (PathAppend(szTo, szTemp)) { HRESULT hr; // make sure that name is unique PathYetAnotherMakeUniqueName(szTo, szTo, NULL, NULL); iRet = IDYES; hr = StringCchCopy(pTo, MAX_PATH, szTo); if (FAILED(hr)) { // This should never fail because pdth->szDestPath (what is passed to GetNameDialog as the pTo parameter) // is the same size (MAX_PATH) as szTo iRet = IDCANCEL; } } else { // can't do this operation on a long path, cancel the operation iRet = IDCANCEL; } } else { iRet = IDCANCEL; } } else { GETNAME_DATA gn; gn.pszDialogFrom = pFrom; gn.pszDialogTo = pTo; gn.bShowCancel = fMultiple; iRet = (int)DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_LFNTOFAT), hwnd, GetNameDlgProc, (LPARAM)(GETNAME_DATA *)&gn); if (iRet == IDD_YESTOALL) pcs->cd.fConfirm &= ~CONFIRM_LFNTOFAT; } return iRet; } STDAPI_(void) SHFreeNameMappings(void *hNameMappings) { HDSA hdsaRenamePairs = (HDSA)hNameMappings; int i; if (!hdsaRenamePairs) return; i = DSA_GetItemCount(hdsaRenamePairs) - 1; for (; i >= 0; i--) { SHNAMEMAPPING FAR* prp = DSA_GetItemPtr(hdsaRenamePairs, i); LocalFree(prp->pszOldPath); LocalFree(prp->pszNewPath); } DSA_Destroy(hdsaRenamePairs); } void _ProcessNameMappings(LPTSTR pszTarget, UINT cchTarget, HDSA hdsaRenamePairs) { int i; if (!hdsaRenamePairs) return; for (i = DSA_GetItemCount(hdsaRenamePairs) - 1; i >= 0; i--) { TCHAR cTemp; SHNAMEMAPPING FAR* prp = DSA_GetItemPtr(hdsaRenamePairs, i); // I don't call StrCmpNI 'cause I already know cchOldPath, and // it has to do a couple of lstrlen()s to calculate it. cTemp = pszTarget[prp->cchOldPath]; pszTarget[prp->cchOldPath] = 0; // Does the target match this collision renaming entry? // NOTE: We are trying to compare a path to a path. prp->pszOldPath // does not have a trailing "\" character, so this isn't covered // by the lstrcmpi below. As such, cTemp had best be the path // seperator character to ensure that the modified pszTarget is actually // a path and not a filename or a longer path name that doesn't match // but happens to start with the same characters as prp->pszOldPath. if ((cTemp == TEXT('\\')) && !lstrcmpi(pszTarget, prp->pszOldPath)) { // Get subtree string of the target. TCHAR *pszSubTree = &(pszTarget[prp->cchOldPath + 1]); TCHAR szNewTarget[MAX_PATH]; // Generate the new target path. if (PathCombine(szNewTarget, prp->pszNewPath, pszSubTree)) { StringCchCopy(pszTarget, cchTarget, szNewTarget); // Ok should never truncate } break; } else { // Restore the trounced character. pszTarget[prp->cchOldPath] = cTemp; } } } /* Sets the status dialog item in the modeless status dialog box. */ // used for both the drag drop status dialogs and the manual user // entry dialogs so be careful what you change void SetProgressText(COPY_STATE *pcs, LPCTSTR pszFrom, LPCTSTR pszTo) { HWND hwndProgress = pcs->hwndProgress; if (hwndProgress && !(pcs->fFlags & FOF_SIMPLEPROGRESS)) { TCHAR szFrom[MAX_PATH], szTo[MAX_PATH]; LPTSTR pszMsg = NULL; HDC hdc; HFONT hfont; HFONT hfontSave; RECT rc; int cxWidth; SIZE size; HRESULT hr; // // Compute the size we can use for our file names (REVIEW: Cache this result?) // hdc = GetDC(hwndProgress); hfont = (HFONT)SendMessage(hwndProgress, WM_GETFONT, 0, 0); hfontSave = (HFONT)SelectObject(hdc, hfont); GetWindowRect(GetDlgItem(hwndProgress, IDD_TONAME), &rc); cxWidth = rc.right - rc.left; if (NULL != pszTo && pcs->fFlags & FOF_MULTIDESTFILES) { hr = StringCchCopy(szFrom, ARRAYSIZE(szFrom), pszTo); } else { hr = StringCchCopy(szFrom, ARRAYSIZE(szFrom), pszFrom); } if (SUCCEEDED(hr) || hr == STRSAFE_E_INSUFFICIENT_BUFFER) { PathStripPath(szFrom); PathCompactPath(hdc, szFrom, cxWidth); } else { szFrom[0] = TEXT('\0'); } SetDlgItemText(hwndProgress, IDD_NAME, szFrom); hr = StringCchCopy(szFrom, ARRAYSIZE(szFrom), pszFrom); if (SUCCEEDED(hr) && szFrom[0] != TEXT('\0')) { LPTSTR pszResource = MAKEINTRESOURCE(IDS_FROM); LPTSTR pszToUsable = NULL; szTo[0] = TEXT('\0'); if (pszTo) { pszToUsable = szTo; pszResource = MAKEINTRESOURCE(IDS_FROMTO); } pszMsg = ShellConstructMessageString(HINST_THISDLL, pszResource, "", pszToUsable); if (NULL != pszMsg) { GetTextExtentPoint(hdc, pszMsg, lstrlen(pszMsg), &size); cxWidth -= size.cx; LocalFree(pszMsg); } // // Now build the file names // PathRemoveFileSpec(szFrom); PathStripPath(szFrom); if (pszTo) { PathCompactPath(hdc, szFrom, cxWidth/2); hr = StringCchCopy(szTo, ARRAYSIZE(szTo), pszTo); if (SUCCEEDED(hr) || hr == STRSAFE_E_INSUFFICIENT_BUFFER) { PathRemoveFileSpec(szTo); PathStripPath(szTo); PathCompactPath(hdc, szTo, cxWidth/2); } else { szTo[0] = TEXT('\0'); } } else { PathCompactPath(hdc, szFrom, cxWidth); } // // Now create the real message // pszMsg = ShellConstructMessageString(HINST_THISDLL, pszResource, szFrom, pszToUsable); } else if (!pcs->fDTBuilt) { TCHAR szFunc[80]; if (LoadString(HINST_THISDLL, FOFuncToStringID(pcs->lpfo->wFunc), szFunc, ARRAYSIZE(szFunc))) { pszMsg = ShellConstructMessageString(HINST_THISDLL, MAKEINTRESOURCE(IDS_PREPARINGTO), szFunc); } } if (pszMsg) { SetDlgItemText(hwndProgress, IDD_TONAME, pszMsg); LocalFree(pszMsg); } SelectObject(hdc, hfontSave); ReleaseDC(hwndProgress, hdc); } } void SetProgressTimeEst(COPY_STATE *pcs, DWORD dwTimeLeft) { TCHAR szFmt[60]; TCHAR szOut[70]; DWORD dwTime; if (pcs->hwndProgress) { if (dwTimeLeft > 4*60*60) // 4 hours and over you get no text { szFmt[0] = TEXT('\0'); } else if (dwTimeLeft > 60) { // Note that dwTime is at least 2, so we only need a plural form LoadString(HINST_THISDLL, IDS_TIMEEST_MINUTES, szFmt, ARRAYSIZE(szFmt)); dwTime = (dwTimeLeft / 60) + 1; } else { LoadString(HINST_THISDLL, IDS_TIMEEST_SECONDS, szFmt, ARRAYSIZE(szFmt)); // Round up to 5 seconds so it doesn't look so random dwTime = ((dwTimeLeft+4) / 5) * 5; } StringCchPrintf(szOut, ARRAYSIZE(szOut), szFmt, dwTime); SetDlgItemText(pcs->hwndProgress, IDD_TIMEEST, szOut); } } // this updates the animation, which could change because we could switch between // doing a move to recycle bin and really nuke if the file/folder was bigger that // the allowable size of the recycle bin. void UpdateProgressAnimation(COPY_STATE *pcs) { if (pcs->hwndProgress && pcs->lpfo) { INT_PTR idAni, idAniCurrent; HWND hwndAnimation; switch (pcs->lpfo->wFunc) { case FO_DELETE: if ((pcs->lpfo->lpszProgressTitle == MAKEINTRESOURCE(IDS_BB_EMPTYINGWASTEBASKET)) || (pcs->lpfo->lpszProgressTitle == MAKEINTRESOURCE(IDS_BB_DELETINGWASTEBASKETFILES))) { idAni = IDA_FILENUKE; break; } else if (!(pcs->fFlags & FOF_ALLOWUNDO)) { idAni = IDA_FILEDELREAL; break; } // else fall through to default default: idAni = (IDA_FILEMOVE + (int)pcs->lpfo->wFunc - FO_MOVE); } hwndAnimation = GetDlgItem(pcs->hwndProgress,IDD_ANIMATE); idAniCurrent = (INT_PTR) GetProp(hwndAnimation, TEXT("AnimationID")); if (idAni != idAniCurrent) { // the one we should be using is different from the one we have, // so update it // close the old clip Animate_Close(hwndAnimation); // open the new one Animate_Open(hwndAnimation, idAni); // if the window is enabled, start the new animation playing if (IsWindowEnabled(pcs->hwndProgress)) Animate_Play(hwndAnimation, -1, -1, -1); // set the current idAni SetProp(hwndAnimation, TEXT("AnimationID"), (HANDLE)idAni); // at the same time we update the animation, we also update the text, // so that the two will always be in sync SetProgressText(pcs, pcs->dth.szSrcPath, pcs->lpfo->wFunc == FO_DELETE ? NULL : pcs->dth.szDestPath); } } } void SendProgressMessage(COPY_STATE *pcs, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (pcs->hwndProgress) SendDlgItemMessage(pcs->hwndProgress, IDD_PROBAR, uMsg, wParam, lParam); } // // creates folder and all parts of the path if necessary (parent does not need // to exists) and verifies that the contents of the folder will be visibile. // // in: // hwnd hwnd to post UI on // pszPath full path to create // psa security attributes // // returns: // ERROR_SUCCESS (0) success // ERROR_ failure // STDAPI_(int) SHCreateDirectoryEx(HWND hwnd, LPCTSTR pszPath, SECURITY_ATTRIBUTES *psa) { int ret = ERROR_SUCCESS; if (PathIsRelative(pszPath)) { // if not a "full" path bail // to ensure that we dont create a dir in the current working directory SetLastError(ERROR_BAD_PATHNAME); return ERROR_BAD_PATHNAME; } if (!Win32CreateDirectory(pszPath, psa)) { TCHAR *pEnd, *pSlash, szTemp[MAX_PATH]; HRESULT hr; ret = GetLastError(); // There are certain error codes that we should bail out here // before going through and walking up the tree... switch (ret) { case ERROR_FILENAME_EXCED_RANGE: case ERROR_FILE_EXISTS: case ERROR_ALREADY_EXISTS: return ret; } hr = StringCchCopy(szTemp, ARRAYSIZE(szTemp), pszPath); if (FAILED(hr)) { return ERROR_FILENAME_EXCED_RANGE; } pEnd = PathAddBackslash(szTemp); // for the loop below if (pEnd == NULL) { return ERROR_FILENAME_EXCED_RANGE; } // assume we have 'X:\' to start this should even work // on UNC names because will will ignore the first error pSlash = szTemp + 3; // create each part of the dir in order while (*pSlash) { while (*pSlash && *pSlash != TEXT('\\')) pSlash = CharNext(pSlash); if (*pSlash) { ASSERT(*pSlash == TEXT('\\')); *pSlash = 0; // terminate path at seperator ret = Win32CreateDirectory(szTemp, pSlash + 1 == pEnd ? psa : NULL) ? ERROR_SUCCESS : GetLastError(); } *pSlash++ = TEXT('\\'); // put the seperator back } } if (ERROR_SUCCESS != ret) { // We failed, so let's try to display error UI. if (hwnd && ERROR_CANCELLED != ret) { SHSysErrorMessageBox(hwnd, NULL, IDS_CANNOTCREATEFOLDER, ret, pszPath ? PathFindFileName(pszPath) : NULL, MB_OK | MB_ICONEXCLAMATION); ret = ERROR_CANCELLED; // Indicate we already displayed Error UI. } } return ret; } STDAPI_(int) SHCreateDirectory(HWND hwnd, LPCTSTR pszPath) { return SHCreateDirectoryEx(hwnd, pszPath, NULL); } #ifdef UNICODE STDAPI_(int) SHCreateDirectoryExA(HWND hwnd, LPCSTR pszPath, SECURITY_ATTRIBUTES *psa) { WCHAR wsz[MAX_PATH]; SHAnsiToUnicode(pszPath, wsz, SIZECHARS(wsz)); return SHCreateDirectoryEx(hwnd, wsz, psa); } #else STDAPI_(int) SHCreateDirectoryExW(HWND hwnd, LPCWSTR pszPath, SECURITY_ATTRIBUTES *psa) { char sz[MAX_PATH]; SHUnicodeToAnsi(pszPath, sz, SIZECHARS(sz)); return SHCreateDirectoryEx(hwnd, sz, psa); } #endif // this function will move a file by copying it and then deleting it // with proper error propagation and cleanup BOOL MoveFileAsCopyAndDelete(LPCTSTR pszSource, LPCTSTR pszDest, LPPROGRESS_ROUTINE lpProgressRoutine, void *lpData, BOOL *pbCancel, DWORD dwCopyFlags) { BOOL bRet = FALSE; if (CopyFileEx(pszSource, pszDest, lpProgressRoutine, lpData, pbCancel, dwCopyFlags)) { if (DeleteFile(pszSource)) { // all is well in the world bRet = TRUE; } else { // couldn't delete the source - save the current GLE value, delete the dest, and return FALSE int iGLE = GetLastError(); DeleteFile(pszDest); // if this fails, life is hopeless SetLastError(iGLE); } } return bRet; } // call MPR to find out the speed of a given path // // returns // 0 for unknown // 144 for 14.4 modems // 96 for 9600 // 24 for 2400 // // if the device does not return a speed we return 0 // DWORD GetPathSpeed(LPCTSTR pszPath) { NETCONNECTINFOSTRUCT nci; NETRESOURCE nr; TCHAR szPath[MAX_PATH]; HRESULT hr; hr = StringCchCopy(szPath, ARRAYSIZE(szPath), pszPath); if (FAILED(hr)) { return 0; } PathStripToRoot(szPath); // get a root to this path memset(&nci, 0, sizeof(nci)); nci.cbStructure = sizeof(nci); memset(&nr, 0, sizeof(nr)); if (PathIsUNC(szPath)) nr.lpRemoteName = szPath; else { // Don't bother for local drives if (!IsRemoteDrive(DRIVEID(szPath))) return 0; // we are passing in a local drive and MPR does not like us to pass a // local name as Z:\ but only wants Z: szPath[2] = 0; // Strip off after character and : nr.lpLocalName = szPath; } // dwSpeed is returned by MultinetGetConnectionPerformance MultinetGetConnectionPerformance(&nr, &nci); return nci.dwSpeed; } DWORD CopyCallbackProc(LARGE_INTEGER liTotSize, LARGE_INTEGER liBytes, LARGE_INTEGER liStreamSize, LARGE_INTEGER liStreamBytes, DWORD dwStream, DWORD dwCallback, HANDLE hSource, HANDLE hDest, void *pv) { COPY_STATE *pcs = (COPY_STATE *)pv; DebugMsg(DM_TRACE, TEXT("CopyCallbackProc[%08lX], totsize=%08lX, bytes=%08lX"), dwCallback, liTotSize.LowPart, liBytes.LowPart); if (FOQueryAbort(pcs)) return PROGRESS_CANCEL; DTSetFileCopyProgress(&pcs->dth, liBytes); if (pcs->fInitialize) { // preserve the create date when moving across volumes, otherwise use the // create date the file system picked when we did the CreateFile() // always preserve modified date (ftLastWriteTime) // bummer is we loose accuracy when going to VFAT compared to NT servers SetFileTime((HANDLE)hDest, (pcs->lpfo->wFunc == FO_MOVE) ? &pcs->pfd->ftCreationTime : NULL, NULL, &pcs->pfd->ftLastWriteTime); pcs->fInitialize = FALSE; } switch (dwCallback) { case CALLBACK_STREAM_SWITCH: break; case CALLBACK_CHUNK_FINISHED: break; default: break; } return PROGRESS_CONTINUE; } // copy the SECURITY_DESCRIPTOR for two files // // in: // pszSource fully qualified source path // pszDest fully qualified destination path // // returns: // 0 ERROR_SUCCESS // WIN32 error codes // DWORD CopyFileSecurity(LPCTSTR pszSource, LPCTSTR pszDest) { DWORD err = ERROR_SUCCESS; BOOL fRet = TRUE; BYTE buf[512]; // arbitrarily saying do everything we can // except SACL_SECURITY_INFORMATION because SECURITY_INFORMATION si = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; PSECURITY_DESCRIPTOR psd = (PSECURITY_DESCRIPTOR) buf; DWORD cbPsd = sizeof(buf); if (!SHRestricted(REST_FORCECOPYACLWITHFILE)) { // shell restriction so return access denied? return ERROR_ACCESS_DENIED; } fRet = GetFileSecurity(pszSource, si, psd, cbPsd, &cbPsd); if (!fRet) { err = GetLastError(); if (ERROR_INSUFFICIENT_BUFFER == err) { // just need to resize the buffer and try again psd = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, cbPsd); if (psd) { fRet = GetFileSecurity(pszSource, si, psd, cbPsd, &cbPsd); } else { err = ERROR_NOT_ENOUGH_MEMORY; } } } if (fRet) { fRet = SetFileSecurity(pszDest, si, psd); if (!fRet) err = GetLastError(); } if (psd && psd != buf) LocalFree(psd); if (fRet) return ERROR_SUCCESS; return err; } // reset the SECURITY_DESCRIPTOR on a file or directory // // in: // pszDest fully qualified destination path // // returns: // 0 ERROR_SUCCESS // WIN32 error codes // DWORD ResetFileSecurity(LPCTSTR pszDest) { DWORD err = ERROR_SUCCESS; if (!SHRestricted(REST_FORCECOPYACLWITHFILE)) { ACL acl; InitializeAcl(&acl, sizeof(acl), ACL_REVISION); // TreeResetNamedSecurityInfo has a callback mechanism, but // we currently don't use it. Note that the paths passed to // the callback look like // "\Device\HarddiskVolume1\dir\name" err = TreeResetNamedSecurityInfo((LPTSTR)pszDest, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION, NULL, NULL, &acl, NULL, FALSE, // KeepExplicit (perms on children) NULL, ProgressInvokeNever, NULL); } return err; } // // in: // hwnd Window to report things to. // pszSource fully qualified source path // pszDest fully qualified destination path // pfd source file find data (size/date/time/attribs) // // returns: // ERROR_SUCCESS (0) // other Win32 ERROR_ codes // UINT FileCopy(COPY_STATE *pcs, LPCTSTR pszSource, LPCTSTR pszDest, const WIN32_FIND_DATA *pfd, BOOL fCreateAlways) { UINT iRet = ERROR_CANCELLED; BOOL fRetryPath = FALSE; BOOL fRetryAttr = FALSE; BOOL fCopyOrMoveSucceeded = FALSE; BOOL fSecurityObtained = FALSE; DWORD dwCopyFlags; BOOL fLostEncryptOk = FALSE; // Buffers for security info BYTE rgbSecurityDescriptor[512]; SECURITY_INFORMATION si = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; PSECURITY_DESCRIPTOR psd = (PSECURITY_DESCRIPTOR) rgbSecurityDescriptor; DWORD cbPsd = sizeof(rgbSecurityDescriptor); // Make sure we can start if (FOQueryAbort(pcs)) return ERROR_CANCELLED; // // Now do the file copy/move // // Get the security info from the source file. If there is a problem // (e.g. the file is on FAT) we ignore it and proceed with the copy/move. if (!(pcs->fFlags & FOF_NOCOPYSECURITYATTRIBS)) { if (SHRestricted(REST_FORCECOPYACLWITHFILE)) { if (GetFileSecurity(pszSource, si, psd, cbPsd, &cbPsd)) { fSecurityObtained = TRUE; } else { if (ERROR_INSUFFICIENT_BUFFER == GetLastError()) { psd = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, cbPsd); if (psd) { if (GetFileSecurity(pszSource, si, psd, cbPsd, &cbPsd)) { fSecurityObtained = TRUE; } } } } } } TryCopyAgain: pcs->fInitialize = TRUE; pcs->pfd = pfd; SetProgressText(pcs, pszSource, pszDest); dwCopyFlags = 0; if (fLostEncryptOk) { dwCopyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION; } if (!fCreateAlways) { dwCopyFlags |= COPY_FILE_FAIL_IF_EXISTS; } if (FO_MOVE == pcs->lpfo->wFunc) { fCopyOrMoveSucceeded = MoveFileWithProgress(pszSource, pszDest, CopyCallbackProc, pcs, MOVEFILE_COPY_ALLOWED | (fCreateAlways ? MOVEFILE_REPLACE_EXISTING : 0)); if (!fCopyOrMoveSucceeded && (dwCopyFlags & COPY_FILE_ALLOW_DECRYPTED_DESTINATION) && // this flag will only be set if we've already come through here once, then accepted the prompt below (GetLastError() == ERROR_ENCRYPTION_FAILED)) { fCopyOrMoveSucceeded = MoveFileAsCopyAndDelete(pszSource, pszDest, CopyCallbackProc, pcs, &pcs->bAbort, dwCopyFlags); } } else { fCopyOrMoveSucceeded = CopyFileEx(pszSource, pszDest, CopyCallbackProc, pcs, &pcs->bAbort, dwCopyFlags); } if (!fCopyOrMoveSucceeded) { int iLastError = (int)GetLastError(); DebugMsg(TF_DEBUGCOPY, TEXT("FileCopy() failed, get last error returned 0x%08x"), iLastError); switch (iLastError) { // Let the caller handle this one case ERROR_FILE_EXISTS: case ERROR_ALREADY_EXISTS: // nt5 221893 CopyFileEx now returns this for some reason... iRet = ERROR_FILE_EXISTS; goto Exit; case ERROR_DISK_FULL: if (PathIsUNC(pszDest) || !IsRemovableDrive(DRIVEID(pszDest)) || PathIsSameRoot(pszDest,pszSource)) { break; } iLastError = ERROR_DISK_FULL; // Fall through case ERROR_PATH_NOT_FOUND: if (!fRetryPath) { // ask the user to stick in another disk or empty wastebasket ULARGE_INTEGER ulFileSize; ulFileSize.LowPart = pfd->nFileSizeLow; ulFileSize.HighPart = pfd->nFileSizeHigh; iLastError = CopyMoveRetry(pcs, pszDest, iLastError, &ulFileSize); if (!iLastError) { fRetryPath = TRUE; goto TryCopyAgain; } CopyError(pcs, pszSource, pszDest, (UINT)iLastError | ERRORONDEST, FO_COPY, OPER_DOFILE); iRet = ERROR_CANCELLED; goto Exit; } break; case ERROR_ENCRYPTION_FAILED: if (pfd->dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED && FALSE == fLostEncryptOk) { int result; result = CachedConfirmFileOp(pcs->hwndDlgParent, pcs, &pcs->cd, pcs->nSourceFiles, FALSE, CONFIRM_LOST_ENCRYPT_FILE, pszSource, pfd, pszDest, NULL, NULL); switch (result) { case IDYES: fLostEncryptOk = TRUE; goto TryCopyAgain; case IDNO: case IDCANCEL: pcs->bAbort = TRUE; iRet = result; break; default: iRet = result; break; } } break; case ERROR_ACCESS_DENIED: // check if the filename is too long if (lstrlen(PathFindFileName(pszSource)) + lstrlen(pszDest) >= MAX_PATH) { iLastError = DE_FILENAMETOOLONG; } else if (!fRetryAttr) { // If the file is readonly, reset the readonly attribute // and have another go at it DWORD dwAttributes = GetFileAttributes(pszDest); if (0xFFFFFFFF != dwAttributes) { dwAttributes &= ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM); if (SetFileAttributes(pszDest, dwAttributes)) { fRetryAttr = TRUE; goto TryCopyAgain; } } // GetFileAttributes() 10 lines above clobers GetLastError() and CopyError() // needs it. SetLastError(iLastError); } break; } if (!pcs->bAbort) { CopyError(pcs, pszSource, pszDest, iLastError, FO_COPY, OPER_DOFILE); } iRet = ERROR_CANCELLED; // error already reported goto Exit; } // If copying from a CDRom - clear the read-only bit if (pcs->fFromCDRom) { SetFileAttributes(pszDest, pfd->dwFileAttributes & ~FILE_ATTRIBUTE_READONLY); } // Set the source's security on the destination, ignoring any error. if (fSecurityObtained) { SetFileSecurity(pszDest, si, psd); } SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, pszDest, NULL); if (FO_MOVE == pcs->lpfo->wFunc) { // Let windows waiting on notifications of Source know of change. We have to check // to see if the file is actually gone in order to tell if it actually moved or not. if (!PathFileExists(pszSource)) SHChangeNotify(SHCNE_DELETE, SHCNF_PATH, pszSource, NULL); } else if (0 == StrCmpIC(pfd->cFileName, TEXT("desktop.ini"))) { // clean out stuff from the desktop.ini WritePrivateProfileSection(TEXT("DeleteOnCopy"), NULL, pszDest); } iRet = ERROR_SUCCESS; // 0 Exit: // If we had to alloc a buffer for the security descriptor, // free it now. if (psd && (rgbSecurityDescriptor != psd)) LocalFree(psd); return iRet; } // note: this is a very slow call DWORD GetFreeClusters(LPCTSTR szPath) { DWORD dwFreeClus; DWORD dwTemp; if (GetDiskFreeSpace(szPath, &dwTemp, &dwTemp, &dwFreeClus, &dwTemp)) return dwFreeClus; else return (DWORD)-1; } // note: this is a very slow call BOOL TotalCapacity(LPCTSTR szPath, ULARGE_INTEGER *puliDiskSize) { int idDrive = PathGetDriveNumber(szPath); if (idDrive != -1) { TCHAR szDrive[5]; ULARGE_INTEGER ullDiskFreeForUser; PathBuildRoot(szDrive, idDrive); return GetDiskFreeSpaceEx(szDrive, &ullDiskFreeForUser, puliDiskSize, NULL); } return FALSE; } typedef struct { LPTSTR pszTitle; LPTSTR pszText; } DISKERRORPARAM; BOOL_PTR CALLBACK DiskErrDlgProc(HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam) { switch (uMessage) { case WM_INITDIALOG: { DISKERRORPARAM *pDiskError = (DISKERRORPARAM *) lParam; if (pDiskError) { SetWindowText(hDlg, pDiskError->pszTitle); SetDlgItemText(hDlg, IDC_DISKERR_EXPLAIN, pDiskError->pszText); } Static_SetIcon(GetDlgItem(hDlg, IDC_DISKERR_STOPICON), LoadIcon(NULL, IDI_HAND)); } break; case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: case IDCANCEL: case IDC_DISKERR_LAUNCHCLEANUP: EndDialog (hDlg, LOWORD(wParam)); break; default: return FALSE; } break; default: return FALSE; } return TRUE; } void DisplayFileOperationError(HWND hParent, int idVerb, int wFunc, int nError, LPCTSTR pszReason, LPCTSTR pszPath, LPCTSTR pszDest) { TCHAR szBuffer[80]; DISKERRORPARAM diskparams; // Grab title from resource if (LoadString(HINST_THISDLL, IDS_FILEERROR + wFunc, szBuffer, ARRAYSIZE(szBuffer))) { diskparams.pszTitle = szBuffer; } else { diskparams.pszTitle = NULL; } // Build Message to display diskparams.pszText = ShellConstructMessageString(HINST_THISDLL, MAKEINTRESOURCE(idVerb), pszReason, PathFindFileName(pszPath)); if (diskparams.pszText) { int idDrive = DriveIDFromBBPath(pszDest); //if we want to show Disk cleanup do our stuff, otherwise do MessageBox if (nError == ERROR_DISK_FULL && IsBitBucketableDrive(idDrive) && !PathIsUNC(pszDest) && GetDiskCleanupPath(NULL, 0)) { if (DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_DISKERR), hParent, DiskErrDlgProc, (LPARAM)&diskparams) == IDC_DISKERR_LAUNCHCLEANUP) { LaunchDiskCleanup(hParent, idDrive, DISKCLEANUP_NOFLAG); } } else { MessageBox(hParent, diskparams.pszText, diskparams.pszTitle, MB_OK | MB_ICONSTOP | MB_SETFOREGROUND); } LocalFree(diskparams.pszText); } } /***********************************************************************\ DESCRIPTION: We received an SHARINGVIOLATION or ACCESSDENIED error. We want to generate the most accruate error message for the user to inform them better. These are the cases we care about: ERROR_ACCESS_DENIED: This is the legacy case with the message: "Access is denied. The source file may be in use." DE_DEST_IS_CDROM: This is displayed in case the user copies a file to their cd-rom drive. DE_DEST_IS_CDRECORD: user deletes from CD recordable drive, we need an error message that isn't so scary about "cant copy files to CD". DE_DEST_IS_DVD: This is displayed in case the user copies a file to their DVD drive DE_SHARING_VIOLATION: The file can't be copied because it's open by someone who doesn't allow others to read the file while they use it. DE_PERMISSIONDENIED: This should be displayed if the user doesn't have the ACLs (security permissions) to read/copy the file. \***********************************************************************/ int GenAccessDeniedError(LPCTSTR pszSource, LPCTSTR pszDest, int nError) { int nErrorMsg = ERROR_ACCESS_DENIED; int iDrive = PathGetDriveNumber(pszDest); if (iDrive != -1) { if (IsCDRomDrive(iDrive)) { WCHAR szDrive[4]; // check if user is deleting from cd-r drive. error message saying "cant copy or move files to cd drive" // 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. if (SUCCEEDED(CDBurn_GetRecorderDriveLetter(szDrive, ARRAYSIZE(szDrive))) && (DRIVEID(szDrive) == iDrive)) { nErrorMsg = DE_DEST_IS_CDRECORD; } else { nErrorMsg = DE_DEST_IS_CDROM; } } if (DriveIsDVD(iDrive)) nErrorMsg = DE_DEST_IS_DVD; } // TODO: DE_SHARING_VIOLATION, DE_PERMISSIONDENIED return nErrorMsg; } // // The following function reports errors for the copy engine // // Parameters // pszSource source file name // pszDest destination file name // nError dos (or our exteneded) error code // 0xFFFF for special case NET error // wFunc FO_* values // nOper OPER_* values, operation being performed // void CopyError(LPCOPY_STATE pcs, LPCTSTR pszSource, LPCTSTR pszDest, int nError, UINT wFunc, int nOper) { TCHAR szReason[200]; TCHAR szFile[MAX_PATH]; int idVerb; BOOL bDest; BOOL fSysError = FALSE; DWORD dwError = GetLastError(); // get Extended error now before we blow it away. HRESULT hr; if (!pcs || (pcs->fFlags & FOF_NOERRORUI)) return; // caller doesn't want to report errors bDest = nError & ERRORONDEST; // was dest file cause of error nError &= ~ERRORONDEST; // clear the dest bit // We also may need to remap some new error codes into old error codes // if (nError == ERROR_BAD_PATHNAME) nError = DE_INVALIDFILES; if (nError == ERROR_CANCELLED) // user abort return; hr = StringCchCopy(szFile, ARRAYSIZE(szFile), bDest ? pszDest : pszSource); if (FAILED(hr) || szFile[0] == TEXT('\0')) { LoadString(HINST_THISDLL, IDS_FILE, szFile, ARRAYSIZE(szFile)); } else { // make the path fits on the screen RECT rcMonitor; HWND hwnd = pcs->hwndProgress ? pcs->hwndProgress : pcs->hwndDlgParent; GetMonitorRect(MonitorFromWindow(hwnd, TRUE), &rcMonitor); PathCompactPath(NULL, szFile, (rcMonitor.right - rcMonitor.left) / 3); } // get the verb string // since we now recycle folders as well as files, added OPER_ENTERDIR check here if ((nOper == OPER_DOFILE) || (nOper == OPER_ENTERDIR) || (nOper == 0)) { if ((nError != -1) && bDest) { idVerb = IDS_REPLACING; } else { idVerb = IDS_VERBS + wFunc; } } else { idVerb = IDS_ACTIONS + (nOper >> 8); } // get the reason string if (nError == 0xFFFF) { DWORD dw; WNetGetLastError(&dw, szReason, ARRAYSIZE(szReason), NULL, 0); } else { // transform some error cases if (bDest) { // This caseing of error codes is error prone.. it would // be better to find the explicit ones we wish to map to // this one instead of trying to guess all the ones // we don't want to map... if ((nError == ERROR_DISK_FULL) || ((nError != ERROR_ACCESS_DENIED) && (nError != ERROR_NETWORK_ACCESS_DENIED) && (nError != ERROR_WRITE_PROTECT) && (nError != ERROR_BAD_NET_NAME) && (GetFreeClusters(pszDest) == 0L))) { nError = ERROR_DISK_FULL; } else if (dwError == ERROR_WRITE_FAULT) { nError = ERROR_WRITE_FAULT; } else if (dwError == ERROR_INVALID_NAME) { nError = ERROR_INVALID_NAME; } } else { if (nError == ERROR_ACCESS_DENIED) { // Check the extended error for more info about the error... // We just map these errors to something generic that // tells the user something weird is going on. switch (dwError) { case ERROR_CRC: case ERROR_SEEK: case ERROR_SECTOR_NOT_FOUND: case ERROR_READ_FAULT: case ERROR_GEN_FAILURE: nError = ERROR_GEN_FAILURE; break; // We can't test for ERROR_FILE_NOT_FOUND because in the case where we copy to // a write-protected dest we check to see if the reason we got access denied was // because there's already a read-only file there. If there isn't _that_ test is // going to SetLastError() to ERROR_FILE_NOT_FOUND and that's what we're going to // report as an error. [davepl] // // case ERROR_FILE_NOT_FOUND: // nError = ERROR_GEN_FAILURE; // break; case ERROR_SHARING_VIOLATION: case ERROR_ACCESS_DENIED: nError = GenAccessDeniedError(pszSource, pszDest, nError); break; default: TraceMsg(TF_WARNING, "CopyEngine: hit error %x , not currently special cased", dwError); break; } } else { // This error occures when a user drags & drops a file from point a to // point b twice. The second time fails because the first time hasn't finished. if (nError == (OPER_ERROR | ERROR_FILE_NOT_FOUND)) { nError = ERROR_GEN_FAILURE; } } } } // the error munging above is in several places, but there are some errors that we // know for SURE the user will never want to see so zap them to generic failures. // this whole thing needs a redesign... we shouldnt depend generally on errors getting // UI ("There is not enough space on the disk.") because then we get crap like this. // but everybody already knows that. switch (nError) { case ERROR_SWAPERROR: // Error performing inpage operation. nError = ERROR_GEN_FAILURE; break; } if (nError <= DE_ERROR_MAX) { BOOL fOverridden = FALSE; if (nError == ERROR_SHARING_VIOLATION) { // in the sharing violation case we can try to be a little better in the error UI by // going through the running object table and seeing if the file is registered in there. // if it's not in there, no biggie, just use our normal handling. PWSTR pszApp; if (SUCCEEDED(FindAppForFileInUse(bDest ? pszDest : pszSource, &pszApp))) { PWSTR pszMessage = ShellConstructMessageString(HINST_THISDLL, MAKEINTRESOURCE(IDS_SHAREVIOLATION_HINT), pszApp); if (pszMessage) { StringCchCopy(szReason, ARRAYSIZE(szReason), pszMessage); // ok to truncate, for display only fOverridden = TRUE; LocalFree(pszMessage); } LocalFree(pszApp); } } if (!fOverridden) { fSysError = !LoadString(HINST_THISDLL, IDS_REASONS + nError, szReason, ARRAYSIZE(szReason)); } } if (nOper == OPER_DOFILE) { PathRemoveExtension(szFile); } if (fSysError) { SHSysErrorMessageBox(pcs->hwndDlgParent, MAKEINTRESOURCE(IDS_FILEERROR + wFunc), idVerb, nError, PathFindFileName(szFile), MB_OK | MB_ICONSTOP | MB_SETFOREGROUND); } else { if (nError > DE_ERROR_MAX && 0 == FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, nError, 0, szReason, ARRAYSIZE(szReason), NULL)) { szReason[0] = 0; } DisplayFileOperationError(pcs->hwndDlgParent, idVerb, wFunc, nError, szReason, szFile, pszDest); } } // // The following function is used to retry failed move/copy operations // due to out of disk situations or path not found errors // on the destination. // // parameters: // pszDest Fully qualified path to destination file (ANSI) // nError type of error: ERROR_DISK_FULL or ERROR_PATH_NOT_FOUND // dwFileSize amount of space needed for this file if ERROR_DISK_FULL // // returns: // 0 success (destination path has been created) // != 0 dos error code including ERROR_CANCELLED // int CopyMoveRetry(COPY_STATE *pcs, LPCTSTR pszDest, int nError, ULARGE_INTEGER* pulFileSize) { UINT wFlags; int result; LPCTSTR wID; TCHAR szTemp[MAX_PATH]; BOOL fFirstRetry = TRUE; HRESULT hr; if (pcs->fFlags & FOF_NOERRORUI) { result = ERROR_CANCELLED; goto ErrorExit; } hr = StringCchCopy(szTemp, ARRAYSIZE(szTemp), pszDest); if (SUCCEEDED(hr)) { PathRemoveFileSpec(szTemp); } else { szTemp[0] = TEXT('\0'); } do { // until the destination path has been created if (nError == ERROR_PATH_NOT_FOUND) { if (!(pcs->fFlags & FOF_NOCONFIRMMKDIR)) { wID = MAKEINTRESOURCE(IDS_PATHNOTTHERE); wFlags = MB_ICONEXCLAMATION | MB_YESNO; } else { wID = 0; } } else // ERROR_DISK_FULL { ULARGE_INTEGER ulDiskSize; wFlags = MB_ICONEXCLAMATION | MB_RETRYCANCEL; if (pulFileSize && TotalCapacity(pszDest, &ulDiskSize) && pulFileSize->QuadPart > ulDiskSize.QuadPart) { wID = MAKEINTRESOURCE(IDS_FILEWONTFIT); } else { wID = MAKEINTRESOURCE(IDS_DESTFULL); } } if (wID) { // szTemp will be ignored if there's no %1%s in the string. result = ShellMessageBox(HINST_THISDLL, pcs->hwndDlgParent, wID, MAKEINTRESOURCE(IDS_UNDO_FILEOP + pcs->lpfo->wFunc), wFlags, (LPTSTR)szTemp); } else { result = IDYES; } if (result == IDRETRY || result == IDYES) { TCHAR szDrive[5]; int idDrive; // Allow the disk to be formatted // REVIEW, could this be FO_MOVE as well? if (FAILED(SHPathPrepareForWrite(((pcs->fFlags & FOF_NOERRORUI) ? NULL : pcs->hwndDlgParent), NULL, szTemp, SHPPFW_DEFAULT))) return ERROR_CANCELLED; idDrive = PathGetDriveNumber(szTemp); if (idDrive != -1) PathBuildRoot(szDrive, idDrive); else szDrive[0] = 0; // if we're not copying to the root if (lstrcmpi(szTemp, szDrive)) { result = SHCreateDirectory(pcs->hwndDlgParent, szTemp); if (result == ERROR_CANCELLED) goto ErrorExit; if (result == ERROR_ALREADY_EXISTS) { // if SHPathPrepareForWrite created the directory we shouldn't treat this as an error result = 0; } else if (result && (nError == ERROR_PATH_NOT_FOUND)) { result |= ERRORONDEST; // We try twice to allow the recyclebin to be flushed. if (fFirstRetry) fFirstRetry = FALSE; else goto ErrorExit; } } else { result = 0; } } else { result = ERROR_CANCELLED; goto ErrorExit; } } while (result); ErrorExit: return result; // success } BOOL ValidFilenames(LPCTSTR pList) { if (!*pList) return FALSE; for (; *pList; pList += lstrlen(pList) + 1) { if (PathIsInvalid(pList)) { return FALSE; } } return TRUE; } void AddRenamePairToHDSA(LPCTSTR pszOldPath, LPCTSTR pszNewPath, HDSA* phdsaRenamePairs) { // // Update our collision mapping table // if (!*phdsaRenamePairs) *phdsaRenamePairs = DSA_Create(sizeof(SHNAMEMAPPING), 4); if (*phdsaRenamePairs) { SHNAMEMAPPING rp; rp.cchOldPath = lstrlen(pszOldPath); rp.cchNewPath = lstrlen(pszNewPath); rp.pszOldPath = StrDup(pszOldPath); if (rp.pszOldPath) { rp.pszNewPath = StrDup(pszNewPath); if (rp.pszNewPath) { if (DSA_AppendItem(*phdsaRenamePairs, &rp) == -1) { LocalFree(rp.pszOldPath); LocalFree(rp.pszNewPath); } } else { LocalFree(rp.pszOldPath); } } } } BOOL _HandleRename(LPCTSTR pszSource, LPTSTR pszDest, UINT cchDest, FILEOP_FLAGS fFlags, COPY_STATE * pcs) { TCHAR *pszConflictingName = PathFindFileName(pszSource); TCHAR szTemp[MAX_PATH]; TCHAR szTemplate[MAX_PATH]; LPTSTR lpszLongPlate; PathRemoveFileSpec(pszDest); if (LoadString(HINST_THISDLL, IDS_COPYLONGPLATE, szTemplate, ARRAYSIZE(szTemplate))) { LPTSTR lpsz; lpsz = pszConflictingName; lpszLongPlate = szTemplate; // see if the first part of the template is the same as the name "Copy #" while (*lpsz && *lpszLongPlate && *lpsz == *lpszLongPlate && *lpszLongPlate != TEXT('(')) { lpsz++; lpszLongPlate++; } if (*lpsz == TEXT('(') && *lpszLongPlate == TEXT('(')) { // conflicting name already in the template, use it instead lpszLongPlate = pszConflictingName; } else { // otherwise build our own // We need to make sure not to overflow a max buffer. int ichFixed = lstrlen(szTemplate) + lstrlen(pszDest) + 5; lpszLongPlate = szTemplate; if ((ichFixed + lstrlen(pszConflictingName)) <= MAX_PATH) { StringCchCat(szTemplate, ARRAYSIZE(szTemplate), pszConflictingName); } else { // Need to remove some of the name LPTSTR pszExt = StrRChr(pszConflictingName, NULL, TEXT('.')); if (pszExt) { // ok to truncate here StringCchCat(szTemplate, ARRAYSIZE(szTemplate) - lstrlen(pszExt), pszConflictingName); // use as much of the buffer as possible StringCchCat(szTemplate, ARRAYSIZE(szTemplate), pszExt); } else { StringCchCat(szTemplate, ARRAYSIZE(szTemplate), pszConflictingName); } } } } else { lpszLongPlate = NULL; } if (PathYetAnotherMakeUniqueName(szTemp, pszDest, pszConflictingName, lpszLongPlate)) { // // If there are any other files in the queue which are to // be copied into a subtree of pszDest, we must update them // as well. // // Put the new (renamed) target in pszDest. HRESULT hr = StringCchCopy(pszDest, cchDest, szTemp); if (SUCCEEDED(hr)) { // Rebuild the old dest name and put it in szTemp. // I'm going for minimum stack usage here, so I don't want more // than one MAX_PATH lying around. PathRemoveFileSpec(szTemp); if (PathAppend(szTemp, pszConflictingName)) { AddRenamePairToHDSA(szTemp, pszDest, &pcs->dth.hdsaRenamePairs); } return TRUE; } } return FALSE; } // test input for "multiple" filespec // // examples: // 1 foo.bar (single non directory file) // -1 *.exe (wild card on any of the files) // n foo.bar bletch.txt (number of files) // int CountFiles(LPCTSTR pInput) { int count; for (count = 0; *pInput; pInput += lstrlen(pInput) + 1, count++) { // wild cards imply multiple files if (PathIsWild(pInput)) return -1; } return count; } #define ISDIGIT(c) ((c) >= TEXT('0') && (c) <= TEXT('9')) BOOL IsCompressedVolume(LPCTSTR pszSource, DWORD dwAttributes) { int i; LPTSTR pszFileName, pszExtension; TCHAR szPath[MAX_PATH]; HRESULT hr; // must be marked system and hidden if (!IS_SYSTEM_HIDDEN(dwAttributes)) return FALSE; hr = StringCchCopy(szPath, ARRAYSIZE(szPath), pszSource); if (FAILED(hr)) return FALSE; pszFileName = PathFindFileName(szPath); pszExtension = PathFindExtension(pszFileName); // make sure the extension is a 3 digit number if (!*pszExtension) return FALSE; // no extension for (i = 1; i < 4; i++) { if (!pszExtension[i] || !ISDIGIT(pszExtension[i])) return FALSE; } // make sure it's null terminated here if (pszExtension[4]) return FALSE; // now knock off the extension and make sure the stem matches *pszExtension = 0; if (lstrcmpi(pszFileName, TEXT("DRVSPACE")) && lstrcmpi(pszFileName, TEXT("DBLSPACE"))) { return FALSE; } // make sure it's in the root PathRemoveFileSpec(szPath); if (!PathIsRoot(szPath)) { return FALSE; } return TRUE; // passed all tests! } void _DeferMoveDlgItem(HDWP hdwp, HWND hDlg, int nItem, int x, int y) { RECT rc; HWND hwnd = GetDlgItem(hDlg, nItem); GetClientRect(hwnd, &rc); MapWindowPoints(hwnd, hDlg, (LPPOINT) &rc, 2); DeferWindowPos(hdwp, hwnd, 0, rc.left + x, rc.top + y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE); } void _RecalcWindowHeight(HWND hWnd, LPTSTR lpszText) { HDC hdc = GetDC(hWnd); RECT rc; HWND hwndText = GetDlgItem(hWnd,IDC_MBC_TEXT); HDWP hdwp; int iHeightDelta, cx; // Get the starting rect of the text area (for the width) GetClientRect(hwndText, &rc); MapWindowPoints(hwndText, hWnd, (LPPOINT) &rc, 2); // Calc how high the static text area needs to be, given the above width iHeightDelta = RECTHEIGHT(rc); cx = RECTWIDTH(rc); DrawText(hdc, lpszText, -1, &rc, DT_CALCRECT | DT_WORDBREAK | DT_LEFT | DT_INTERNAL | DT_EDITCONTROL); iHeightDelta = RECTHEIGHT(rc) - iHeightDelta; cx = RECTWIDTH(rc) - cx; // Should only change for really long words w/o spaces if (cx < 0) cx = 0; ReleaseDC(hWnd, hdc); hdwp = BeginDeferWindowPos(4); if (hdwp) { hdwp = DeferWindowPos(hdwp, hwndText, 0, rc.left, rc.top, RECTWIDTH(rc), RECTHEIGHT(rc), SWP_NOZORDER | SWP_NOACTIVATE); if (hdwp) { _DeferMoveDlgItem(hdwp, hWnd, IDC_MESSAGEBOXCHECKEX, 0, iHeightDelta); _DeferMoveDlgItem(hdwp, hWnd, IDYES, cx, iHeightDelta); _DeferMoveDlgItem(hdwp, hWnd, IDNO, cx, iHeightDelta); EndDeferWindowPos(hdwp); } } GetWindowRect(hWnd, &rc); SetWindowPos(hWnd, 0, rc.left - (cx/2), rc.top - (iHeightDelta/2), RECTWIDTH(rc)+cx, RECTHEIGHT(rc)+iHeightDelta, SWP_NOZORDER | SWP_NOACTIVATE); return; } BOOL_PTR CALLBACK RenameMsgBoxCheckDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { // we only handle the WM_INITDIALOG so that we can resize the dialog // approprately and to set the default button to IDNO case WM_INITDIALOG: { HWND hwndNO = GetDlgItem(hDlg, IDNO); _RecalcWindowHeight(hDlg, (LPTSTR)lParam); SetDlgItemText(hDlg,IDC_MBC_TEXT,(LPTSTR)lParam); SendMessage(hDlg, DM_SETDEFID, IDNO, 0); SetFocus(hwndNO); return FALSE; // we set the focus, so return false } } // didnt handle this message return FALSE; } int ConfirmRenameOfConnectedItem(COPY_STATE *pcs, WIN32_FIND_DATA *pfd, LPTSTR szSource) { int result = IDYES; //For non-connected elements, the default is IDYES! LPTSTR pszMessage; LPTSTR lpConnectedItem, lpConnectOrigin; LPTSTR lpStringID; //Check if this item being renamed has a connected item. if (DTNIsConnectOrigin(pcs->dth.pdtnCurrent)) { //Yes! It has a connected element! Form the strings to create the confirmation dialog! //Get the name of the connected element lpConnectedItem = PathFindFileName(pcs->dth.pdtnCurrent->pdtnConnected->szName); lpConnectOrigin = PathFindFileName(pcs->dth.pFrom); // Mark the connected item as dummy as this will never get renamed. // (Note that this connected node could be a folder. It is still OK to mark it as // dummy because for rename operation, a folder is treated just like a file in // DTGotoNextNode()). pcs->dth.pdtnCurrent->pdtnConnected->fDummy = TRUE; if (pfd && (pfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) lpStringID = MAKEINTRESOURCE(IDS_HTML_FOLDER_RENAME); else lpStringID = MAKEINTRESOURCE(IDS_HTML_FILE_RENAME); //Load the confirmation message and format it! pszMessage = ShellConstructMessageString(HINST_THISDLL, lpStringID, lpConnectedItem, lpConnectOrigin); if (pszMessage) { //Get the confirmation from the end-user; result = SHMessageBoxCheckEx(pcs->hwndDlgParent, HINST_THISDLL, MAKEINTRESOURCE(DLG_RENAME_MESSAGEBOXCHECK), RenameMsgBoxCheckDlgProc, (void *)pszMessage, IDYES, REG_VAL_GENERAL_RENAMEHTMLFILE); //It is possible we get IDCANCEL if the "X" in the caption is clicked to clost // the dialog. The following code makes sure we get one of the return code that we want. if ((result != IDYES) && (result != IDNO)) result = IDNO; SHFree(pszMessage); } else result = IDNO; //For connected elements, the default is "Don't rename"; } else { if (DTNIsConnected(pcs->dth.pdtnCurrent)) result = IDNO; //Connected elements, do not get renamed. } return result; } int AllConfirmations(COPY_STATE *pcs, WIN32_FIND_DATA *pfd, UINT oper, UINT wFunc, LPTSTR szSource, LPTSTR szDest, BOOL bTimeToUpdate, WIN32_FIND_DATA *pfdDest, LPINT lpret) { int result = IDYES; LPTSTR p; LPTSTR pszStatusDest = NULL; CONFIRM_FLAG fConfirm; WIN32_FIND_DATA *pfdUse1 = NULL; WIN32_FIND_DATA *pfdUse2; BOOL fSetProgress = FALSE; BOOL fShowConfirm = FALSE; switch (oper | wFunc) { case OPER_ENTERDIR | FO_MOVE: if (PathIsSameRoot(szSource, szDest)) { fConfirm = CONFIRM_MOVE_FOLDER; pfdUse1 = pfd; pfdUse2 = pfdDest; fShowConfirm = TRUE; } break; case OPER_ENTERDIR | FO_DELETE: // Confirm removal of directory on this pass. The directories // are actually removed on the OPER_LEAVEDIR pass if (DTNIsRootNode(pcs->dth.pdtnCurrent)) fSetProgress = TRUE; if (!PathIsRoot(szSource)) { fShowConfirm = TRUE; pfdUse2 = pfd; fConfirm = CONFIRM_DELETE_FOLDER; szDest = NULL; } break; case OPER_DOFILE | FO_RENAME: // pszStatusDest = szDest; fSetProgress = TRUE; p = PathFindFileName(szSource); if (!IntlStrEqNI(szSource, szDest, (int)(p - szSource))) { result = DE_DIFFDIR; } else { if (pfd && (pfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) fConfirm = CONFIRM_RENAME_FOLDER; else fConfirm = CONFIRM_RENAME_FILE; if (PathIsRoot(szSource) || (PathIsRoot(szDest))) { result = DE_ROOTDIR | ERRORONDEST; } else { // We need to bring up a special confirmation dialog if this file/folder being // renamed has a connected element (if "foo.htm" or "foo files" is renamed that // will break the links). result = ConfirmRenameOfConnectedItem(pcs, pfd, szSource); if (result != IDNO) { fShowConfirm = TRUE; pfdUse2 = pfdDest; pfdUse1 = pfd; } } } break; case OPER_DOFILE | FO_MOVE: fSetProgress = TRUE; pszStatusDest = szDest; if (PathIsRoot(szSource)) { result = DE_ROOTDIR; } else if (PathIsRoot(szDest)) { result = DE_ROOTDIR | ERRORONDEST; } else { fConfirm = CONFIRM_MOVE_FILE; fShowConfirm = TRUE; pfdUse2 = pfdDest; pfdUse1 = pfd; } break; case OPER_DOFILE | FO_DELETE: fSetProgress = TRUE; if (IsCompressedVolume(szSource, pfd->dwFileAttributes)) { CopyError(pcs, szSource, szDest, DE_COMPRESSEDVOLUME, wFunc, oper); result = IDNO; } else { fShowConfirm = TRUE; szDest = NULL; pfdUse2 = pfd; fConfirm = CONFIRM_DELETE_FILE; } break; } if (fShowConfirm) { result = CachedConfirmFileOp(pcs->hwndDlgParent, pcs, &pcs->cd, pcs->nSourceFiles, !DTNIsRootNode(pcs->dth.pdtnCurrent), fConfirm, szSource, pfdUse1, szDest, pfdUse2, NULL); } if (oper == OPER_DOFILE || oper == OPER_ENTERDIR) { if ((wFunc == FO_MOVE) || (wFunc == FO_COPY)) { if ((result != IDNO) && (result != IDCANCEL)) { LPTSTR pszDataToBeLost; WCHAR wszDestDir[MAX_PATH]; BOOL bNoStreamLossThisDir = FALSE; HRESULT hr = StringCchCopy(wszDestDir, ARRAYSIZE(wszDestDir), szDest); if (SUCCEEDED(hr)) { PathRemoveFileSpec(wszDestDir); // Files with multiple streams will suffer stream loss on a downlevel // copy, but CopyFile special-cases native structure storage. pszDataToBeLost = GetDownlevelCopyDataLossText(szSource, wszDestDir, (oper == OPER_ENTERDIR), &bNoStreamLossThisDir); if (pszDataToBeLost) { fConfirm = CONFIRM_STREAMLOSS; pfdUse2 = pfd; result = CachedConfirmFileOp(pcs->hwndDlgParent, pcs, &pcs->cd, pcs->nSourceFiles, !DTNIsRootNode(pcs->dth.pdtnCurrent), fConfirm, szSource, pfdUse1, szDest, pfdUse2, pszDataToBeLost); LocalFree(pszDataToBeLost); } else if (bNoStreamLossThisDir) { // pcs->bStreamLossPossible = FALSE; } } } } } // We only really care about OPER_ENTERDIR when deleting and // OPER_DOFILE when renaming, but I guess the hook will figure it out if ((result == IDYES) && ISDIRFINDDATA(*pfd) && (oper==OPER_ENTERDIR || oper==OPER_DOFILE)) { result = CallFileCopyHooks(pcs->hwndDlgParent, wFunc, pcs->fFlags, szSource, pfd->dwFileAttributes, szDest, pfdDest->dwFileAttributes); } if ((result != IDCANCEL) && (result != IDNO) && fSetProgress && bTimeToUpdate) SetProgressText(pcs, szSource, pszStatusDest); return result; } // return TRUE if they're the same file // assumes that given two file specs, the short name will // be identical (except case) BOOL SameFile(LPTSTR pszSource, LPTSTR pszDest) { TCHAR szShortSrc[MAX_PATH]; if (GetShortPathName(pszSource, szShortSrc, ARRAYSIZE(szShortSrc))) { TCHAR szShortDest[MAX_PATH]; if (GetShortPathName(pszDest, szShortDest, ARRAYSIZE(szShortDest))) return !lstrcmpi(szShortSrc, szShortDest); } return FALSE; } // make sure we aren't operating on the current dir to avoid // ERROR_CURRENT_DIRECTORY kinda errors void AvoidCurrentDirectory(LPCTSTR p) { TCHAR szTemp[MAX_PATH]; GetCurrentDirectory(ARRAYSIZE(szTemp), szTemp); if (lstrcmpi(szTemp, p) == 0) { DebugMsg(TF_DEBUGCOPY, TEXT("operating on current dir(%s), cd .."), p); PathRemoveFileSpec(szTemp); SetCurrentDirectory(szTemp); } } // this resolves short/long name collisions such as moving // "NewFolde" onto a dir with "New Folder" whose short name is "NEWFOLDE" // // we resolve this by renaming "New Folder" to a unique short name (like TMP1) // // making a temporary file of name "NEWFOLDE" // // renaming TMP1 back to "New Folder" (at which point it will have a new short // name like "NEWFOL~1" // PERF: it'd be faster if we didn't make the temporary file, but that // would require that we rename the file back to the long name at the // end of the operation.. which would mean we'd need to queue them all up.. // too much for right now. BOOL ResolveShortNameCollisions(LPCTSTR lpszDest, WIN32_FIND_DATA *pfd) { BOOL fRet = FALSE; // first verify that we're in the name collision. // we are if lpszDest is the same as the pfd's short name which is different // than it's long name. if (!lstrcmpi(PathFindFileName(lpszDest), pfd->cAlternateFileName) && lstrcmpi(pfd->cAlternateFileName, pfd->cFileName)) { // yes... do the renaming TCHAR szTemp[MAX_PATH]; TCHAR szLongName[MAX_PATH]; HRESULT hr = StringCchCopy(szTemp, ARRAYSIZE(szTemp), lpszDest); if (SUCCEEDED(hr)) { PathRemoveFileSpec(szTemp); // build the original long name hr = StringCchCopy(szLongName, ARRAYSIZE(szLongName), szTemp); if (SUCCEEDED(hr)) { if (PathAppend(szLongName, pfd->cFileName)) { GetTempFileName(szTemp, c_szNULL, 1, szTemp); DebugMsg(TF_DEBUGCOPY, TEXT("Got %s as a temp file"), szTemp); // rename "New Folder" to "tmp1" if (Win32MoveFile(szLongName, szTemp, ISDIRFINDDATA(*pfd))) { // make a temporary "NewFolde" fRet = CreateWriteCloseFile(NULL, lpszDest, NULL, 0); ASSERT(fRet); // move it back... if (!Win32MoveFile(szTemp, szLongName, ISDIRFINDDATA(*pfd))) { // // Can't move it back, so delete the empty dir and then // move it back. Return FALSE to denote failure. // DeleteFile(lpszDest); Win32MoveFile(szTemp, szLongName, ISDIRFINDDATA(*pfd)); fRet = FALSE; } else { // send this out because we could have confused views // with this swapping files around... by the time they get the first // move file notification, the temp file is likely gone // so they could blow that off.. which would mess up the rest of this. SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, szLongName, NULL); // // We've now created an empty dir entry of this name type. // Win32DeleteFile(lpszDest); } DebugMsg(TF_DEBUGCOPY, TEXT("ResolveShortNameCollision: %s = original, %s = destination,\n %s = temp file, %d = return"), szLongName, lpszDest, szTemp, fRet); } } } } } return fRet; } typedef struct { LPTSTR szFilename; int iResult; } RENAMEEXEMPTIONINFO; RENAMEEXEMPTIONINFO g_rgExemptions[] = { { TEXT("thumbs.db"), IDYES } }; // return values. // // IDCANCEL = bail out of all operations // IDNO = skip this one // IDRETRY = try operation again // IDUNKNOWN = this (collision) is not the problem #define IDUNKNOWN IDOK int CheckForRenameCollision(COPY_STATE *pcs, UINT oper, LPTSTR pszSource, LPTSTR pszDest, UINT cchDest, WIN32_FIND_DATA *pfdDest, WIN32_FIND_DATA* pfd) { int iRet = IDUNKNOWN; ASSERT((pcs->lpfo->wFunc != FO_DELETE) && (oper != OPER_LEAVEDIR)); /* Check to see if we are overwriting an existing file or directory. If so, better confirm */ if ((oper == OPER_DOFILE) || ((oper == OPER_ENTERDIR) && (pcs->fFlags & FOF_RENAMEONCOLLISION))) { HANDLE hfindT; // REVIEW this slows things down checking for the dest file if ((hfindT = FindFirstFile(pszDest, pfdDest)) != INVALID_HANDLE_VALUE) { FindClose(hfindT); iRet = IDCANCEL; if (pcs->lpfo->wFunc != FO_RENAME || !SameFile(pszSource, pszDest)) { if (!ResolveShortNameCollisions(pszDest, pfdDest)) { if (pcs->fFlags & FOF_RENAMEONCOLLISION) { // The client wants us to generate a new name for the // source file to avoid a collision at the destination // dir. Must also update the current queue and the // copy root. _HandleRename(pszSource, pszDest, cchDest, pcs->fFlags, pcs); iRet = IDRETRY; } else { int result = IDRETRY; if (pcs->lpfo->wFunc == FO_RENAME) { return ERROR_ALREADY_EXISTS; } // Is this a super-hidden file we don't want to prompt the // user regarding? if (IS_SYSTEM_HIDDEN(pfd->dwFileAttributes) && IS_SYSTEM_HIDDEN(pfdDest->dwFileAttributes) && !ShowSuperHidden()) { int cExempt = 0; for (; cExempt < ARRAYSIZE(g_rgExemptions); cExempt++) { if (0 == StrCmpI(g_rgExemptions[cExempt].szFilename, PathFindFileName(pszSource))) { result = g_rgExemptions[cExempt].iResult; break; } } } // REVIEW, if the destination file we are copying over // is actually a directory we are doomed. we can // try to remove the dir but that will fail if there // are files there. we probably need a special error message // for this case. if (result == IDRETRY) { result = CachedConfirmFileOp(pcs->hwndDlgParent, pcs, &pcs->cd, pcs->nSourceFiles, !DTNIsRootNode(pcs->dth.pdtnCurrent), CONFIRM_REPLACE_FILE, pszSource, pfd, pszDest, pfdDest, NULL); } switch (result) { case IDYES: if ((pcs->lpfo->wFunc == FO_MOVE) && (PathIsSameRoot(pszSource, pszDest))) { int ret; // For FO_MOVE we need to delete the // destination first. Do that now. // FEATURE this replace options should be undable ret = Win32DeleteFile(pszDest) ? 0 : GetLastError(); if (ret) { ret |= ERRORONDEST; result = ret; } } if (pcs->lpua) FOUndo_Release(pcs->lpua); iRet = IDRETRY; break; case IDNO: case IDCANCEL: pcs->lpfo->fAnyOperationsAborted = TRUE; iRet = result; break; default: iRet = result; break; } } } else { iRet = IDRETRY; } } } } return iRet; } int LeaveDir_Delete(COPY_STATE *pcs, LPTSTR pszSource) { int ret; if (PathIsRoot(pszSource)) return 0; AvoidCurrentDirectory(pszSource); // We already confirmed the delete at MKDIR time, so attempt // to delete the directory ret = Win32RemoveDirectory(pszSource) ? 0 : GetLastError(); if (!ret) { FOUndo_FileReallyDeleted(pszSource); } return ret; } int EnterDir_Copy(COPY_STATE* pcs, LPTSTR pszSource, LPTSTR pszDest, UINT cchDest, WIN32_FIND_DATA *pfd, WIN32_FIND_DATA * pfdDest, BOOL fRenameTried, BOOL fLostEncryptOk) { int ret; int result; BOOL fSetDestAttributes = FALSE; DWORD dwDesiredAttributes = pfd->dwFileAttributes; BOOL fWithoutTemplate = FALSE; // Whenever we enter a directory, we need to reset the bStreamLossPossible flag, // since we could have stepped out from an NTFS->NTFS to NTFS->FAT scenario via // a junction point pcs->bStreamLossPossible = TRUE; TryCreateAgain: // SHMoveFile restricts the based on path length. To be consistent, we make the same // restricton on Copy directory also. if (IsDirPathTooLongForCreateDir(pszDest)) { ret = ERROR_FILENAME_EXCED_RANGE; } else { if (fLostEncryptOk) { dwDesiredAttributes &= ~FILE_ATTRIBUTE_ENCRYPTED; // Pretend its not encrypted fSetDestAttributes = TRUE; fWithoutTemplate = TRUE; } if (pfd->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { if (!(pcs->fFlags & FOF_NORECURSEREPARSE)) { dwDesiredAttributes &= ~FILE_ATTRIBUTE_REPARSE_POINT; // Pretend like its just a folder fSetDestAttributes = TRUE; fWithoutTemplate = TRUE; } } if (fWithoutTemplate) { ret = (CreateDirectory(pszDest, NULL) ? 0 : GetLastError()); // Since we didn't call CreateDirectoryEx, we need to manually // propogate the attributes to the dest directory. fSetDestAttributes = TRUE; } else { ret = (CreateDirectoryEx(pszSource, pszDest, NULL) ? 0 : GetLastError()); } if (ret == ERROR_SUCCESS) { SHChangeNotify(SHCNE_MKDIR, SHCNF_PATH, pszDest, NULL); } } switch (ret) { case 0: // successful folder creation (or it already exists) // propogate the attributes (if there are any) if (pcs->fFromCDRom) { // Don't propogate read-only from CDRoms dwDesiredAttributes &= ~FILE_ATTRIBUTE_READONLY; fSetDestAttributes = TRUE; } if (fSetDestAttributes) { // Avoid setting FILE_ATTRIBUTE_DIRECTORY, since its // already a directory, and is less error prone. SetFileAttributes(pszDest, dwDesiredAttributes); } // we should set the security ACLs here on NT // we ignore any kind of failure though, is that OK? // CopyFileSecurity(pszSource, pszDest); // add to the undo atom if (pcs->lpua) { if (DTNIsRootNode(pcs->dth.pdtnCurrent) && !DTNIsConnected(pcs->dth.pdtnCurrent)) FOUndo_AddInfo(pcs->lpua, pszSource, pszDest, 0); } break; case ERROR_ALREADY_EXISTS: case ERROR_DISK_FULL: case ERROR_ACCESS_DENIED: case ERROR_INVALID_NAME: { DWORD dwFileAttributes; if (!fRenameTried) { int result = CheckForRenameCollision(pcs, OPER_ENTERDIR, pszSource, pszDest, cchDest, pfdDest, pfd); switch (result) { case IDUNKNOWN: break; case IDRETRY: return EnterDir_Copy(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, TRUE, fLostEncryptOk); case IDCANCEL: pcs->bAbort = TRUE; return result; case IDNO: return result; default: return result; } } dwFileAttributes = GetFileAttributes(pszDest); if (dwFileAttributes == (DWORD)-1) { // The dir does not exist, so it looks like a problem // with a read-only drive or disk full if (ret == ERROR_DISK_FULL && IsRemovableDrive(DRIVEID(pszDest)) && !PathIsSameRoot(pszDest, pszSource)) { ret = CopyMoveRetry(pcs, pszDest, ERROR_DISK_FULL, NULL); if (!ret) { return EnterDir_Copy(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, fRenameTried, fLostEncryptOk); } else { pcs->bAbort = TRUE; return ret; } } // Maybe its an encrypted folder thats losing its encryption? // If fLostEncryptOk is TRUE then we are already trying to recover from an Encrypted Folder, so // don't recursively try again if ((fLostEncryptOk == FALSE) && (pfd->dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED)) { int result; result = CachedConfirmFileOp(pcs->hwndDlgParent, pcs, &pcs->cd, pcs->nSourceFiles, FALSE, CONFIRM_LOST_ENCRYPT_FOLDER, pszSource, pfd, pszDest, NULL, NULL); switch (result) { case IDYES: fLostEncryptOk = TRUE; return EnterDir_Copy(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, fRenameTried, fLostEncryptOk); case IDNO: case IDCANCEL: pcs->bAbort = TRUE; ret = result; break; default: ret = result; break; } return ret; } CopyError(pcs, pszSource, pszDest, ERROR_ACCESS_DENIED | ERRORONDEST, FO_COPY, OPER_DOFILE); pcs->bAbort = TRUE; return ret; } if (!(dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { // A file with this name already exists CopyError(pcs, pszSource, pszDest, DE_FLDDESTISFILE | ERRORONDEST, FO_COPY, OPER_DOFILE); pcs->bAbort = TRUE; return ret; } result = CachedConfirmFileOp(pcs->hwndDlgParent, pcs, &pcs->cd, pcs->nSourceFiles, !DTNIsRootNode(pcs->dth.pdtnCurrent), CONFIRM_REPLACE_FOLDER, pszSource, pfd, pszDest, pfdDest, NULL); switch (result) { case IDYES: ret = 0; // convert to no error pcs->fMerge = TRUE; if (pcs->lpua) FOUndo_Release(pcs->lpua); break; case IDNO: DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder pcs->lpfo->fAnyOperationsAborted = TRUE; ret = IDNO; // Don't put up error message on this one... // Since the end-user cancelled the copy operation on this folder, we can cancel the // copy operation on the corresponding connected file too! if (DTNIsConnectOrigin(pcs->dth.pdtnCurrent)) pcs->dth.pdtnCurrent->pdtnConnected->fDummy = TRUE; break; case IDCANCEL: pcs->lpfo->fAnyOperationsAborted = TRUE; pcs->bAbort = TRUE; // Since the end-user cancelled the copy operation on this folder, we can cancel the // copy operation on the corresponding connected file too! if (DTNIsConnectOrigin(pcs->dth.pdtnCurrent)) pcs->dth.pdtnCurrent->pdtnConnected->fDummy = TRUE; break; default: result = ret; break; } break; } case ERROR_CANCELLED: pcs->bAbort = TRUE; break; case ERROR_FILENAME_EXCED_RANGE: DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder break; case ERROR_EAS_NOT_SUPPORTED: case ERROR_NOT_SUPPORTED: // Directories with EAs are identified here. if (!fWithoutTemplate) { fWithoutTemplate = TRUE; goto TryCreateAgain; } // fall through default: // ret != 0 (dos error code) ret |= ERRORONDEST; break; } return ret; } int EnterDir_Move(COPY_STATE* pcs, LPTSTR pszSource, LPTSTR pszDest, UINT cchDest, WIN32_FIND_DATA *pfd, WIN32_FIND_DATA * pfdDest, BOOL fRenameTried) { int ret; // Whenever we enter a directory, we need to reset the bStreamLossPossible flag, // since we could have stepped out from an NTFS->NTFS to NTFS->FAT scenario via // a junction point pcs->bStreamLossPossible = TRUE; // if these are in the same drive, try using MoveFile on it. // if that fails then fail through to the copy if (PathIsSameRoot(pszSource, pszDest)) { AvoidCurrentDirectory(pszSource); ret = Win32MoveFile(pszSource, pszDest, TRUE) ? 0 : GetLastError(); switch (ret) { case 0: DebugMsg(TF_DEBUGCOPY, TEXT("Move Folder worked!")); DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder // add to the undo atom if (pcs->lpua && DTNIsRootNode(pcs->dth.pdtnCurrent) && !DTNIsConnected(pcs->dth.pdtnCurrent)) FOUndo_AddInfo(pcs->lpua, pszSource, pszDest, 0); if (!SHRestricted(REST_NOENCRYPTONMOVE) && !(pfd->dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED)) { TCHAR szDestDir[MAX_PATH]; DWORD dwAttribs; if (SUCCEEDED(StringCchCopy(szDestDir, ARRAYSIZE(szDestDir), pszDest))) { PathRemoveFileSpec(szDestDir); dwAttribs = GetFileAttributes(szDestDir); if ((dwAttribs != -1) && (dwAttribs & FILE_ATTRIBUTE_ENCRYPTED)) { // Encrypt the directory by pretending we are the // property sheet properties->Advanced. Fill in the fake // information and call the helper. FILEPROPSHEETPAGE fpsp; FOLDERCONTENTSINFO fci; fci.fIsCompressionAvailable = FALSE; fci.fMultipleFiles = TRUE; ZeroMemory(&fpsp, SIZEOF(fpsp)); fpsp.hDlg = GetWindow(pcs->hwndDlgParent, GW_CHILD); fpsp.fRecursive = TRUE; fpsp.fIsDirectory = TRUE; fpsp.pfci = &fci; // As long as asInitial.* == asCurrent.* it won't be changed fpsp.asInitial.fReadOnly = BST_INDETERMINATE; fpsp.asInitial.fHidden = BST_INDETERMINATE; fpsp.asInitial.fIndex = BST_INDETERMINATE; fpsp.asInitial.fArchive = BST_INDETERMINATE; fpsp.asInitial.fCompress = BST_INDETERMINATE; fpsp.asInitial.fEncrypt = BST_UNCHECKED; // Not encrypted yet fpsp.asInitial.fRecordingEnabled = BST_INDETERMINATE; fpsp.asCurrent.fReadOnly = BST_INDETERMINATE; fpsp.asCurrent.fHidden = BST_INDETERMINATE; fpsp.asCurrent.fIndex = BST_INDETERMINATE; fpsp.asCurrent.fArchive = BST_INDETERMINATE; fpsp.asCurrent.fCompress = BST_INDETERMINATE; fpsp.asCurrent.fEncrypt = BST_CHECKED; // Now encrypt fpsp.asCurrent.fRecordingEnabled = BST_INDETERMINATE; ApplyRecursiveFolderAttribs(pszDest, &fpsp); } } } // Win32MoveFile on a single-volume leaves the original ACL // intact. If necessary, pick up perms from the destination. if (pcs->fFlags & FOF_NOCOPYSECURITYATTRIBS) { ResetFileSecurity(pszDest); } return 0; case ERROR_PATH_NOT_FOUND: ret = CopyMoveRetry(pcs, pszDest, ret, NULL); if (!ret) return EnterDir_Move(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, fRenameTried); return ret; case ERROR_ALREADY_EXISTS: case ERROR_FILE_EXISTS: if (!fRenameTried) { int result = CheckForRenameCollision(pcs, OPER_ENTERDIR, pszSource, pszDest, cchDest, pfdDest, pfd); switch (result) { case IDUNKNOWN: break; case IDRETRY: return EnterDir_Move(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, TRUE); case IDCANCEL: pcs->bAbort = TRUE; return result; case IDNO: return result; default: return result; } } break; case ERROR_FILENAME_EXCED_RANGE: case ERROR_ONLY_IF_CONNECTED: DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder return ret; } } // we're going to recurse in.... if we've not enumerated the children for // this folder, set it for delayed enumeration now. if (!pcs->dth.pdtnCurrent->pdtnChild) { pcs->dth.pdtnCurrent->pdtnChild = DTN_DELAYED; } if (DTNIsConnected(pcs->dth.pdtnCurrent) && !PathFileExists(pszSource)) { // This can happen if the end-user moved "foo.htm" AND "foo files" together. // As a result the connected element "foo files" has already been moved. DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder return(0); //No error! This connected element seems to have been moved. } return EnterDir_Copy(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, FALSE, FALSE); } int EnterDir_Delete(COPY_STATE * pcs, WIN32_FIND_DATA *pfdSrc, LPTSTR pszSource, UINT cchSource, HDPA *phdpaDeletedFiles) { int iRet = 0; if (!DTNIsRootNode(pcs->dth.pdtnCurrent)) { // we are not at a root node... when doing a delete this can only mean // that we are really nuking the folder. we dont need to enum children // because we already did a non-lazy enum at the root node. return iRet; } else if (!pcs->lpua) { NukeFolder: // we are at a root node and we have no undo atom, this means that we // really want to nuke this whole dir, so enum the children DTForceEnumChildren(&pcs->dth); // do a non-layz enum of the children to prevent the progress // bar from going back and forth as we recurse down into any subdirs. DTEnumChildren(&pcs->dth, pcs, TRUE, DTF_FILES_AND_FOLDERS); return iRet; } if (DeleteFileBB(pszSource, cchSource, &iRet, pcs, TRUE, pfdSrc, phdpaDeletedFiles)) { DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder } else { // DeleteFileBB failed, check iRet to find out why switch (iRet) { case BBDELETE_PATH_TOO_LONG: case BBDELETE_SIZE_TOO_BIG: case BBDELETE_NUKE_OFFLINE: { // This is the case where the folder is too big to fit in the Recycle Bin or the folder // is offline. We have no choice but to really nuke it, but we warn the user first since // they may have thought that it was being sent to the recycle bin. int result = CachedConfirmFileOp(pcs->hwndDlgParent, pcs, &pcs->cd, pcs->nSourceFiles, FALSE, (iRet == BBDELETE_SIZE_TOO_BIG) ? CONFIRM_WONT_RECYCLE_FOLDER : ((iRet == BBDELETE_NUKE_OFFLINE) ? CONFIRM_WONT_RECYCLE_OFFLINE : CONFIRM_PATH_TOO_LONG), pszSource, pfdSrc, NULL, NULL, NULL); switch (result) { case IDNO: // user said "please dont really nuke the file" DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder pcs->lpfo->fAnyOperationsAborted = TRUE; iRet = IDNO; // Don't put up error message for this case //Because the Delete on this FOLDER is aborted, we can cancel the "Delete" // on the corresponding FILE too! if (DTNIsConnectOrigin(pcs->dth.pdtnCurrent)) { pcs->dth.pdtnCurrent->pdtnConnected->fDummy = TRUE; } break; case IDCANCEL: // user canceled the operation pcs->lpfo->fAnyOperationsAborted = TRUE; pcs->bAbort = TRUE; //Because the Delete on this FOLDER is cancelled, we can cancel the "Delete" // on the corresponding FILE too! if (DTNIsConnectOrigin(pcs->dth.pdtnCurrent)) { pcs->dth.pdtnCurrent->pdtnConnected->fDummy = TRUE; } break; case IDYES: default: // user said "please nuke the file" // assume noerror iRet = 0; // set this so the is correct progress animation is displayed if (pcs) { pcs->fFlags &= ~FOF_ALLOWUNDO; } // dont allow undo since we are really nuking it (cant bring it back...) if (pcs->lpua) { FOUndo_Release(pcs->lpua); } UpdateProgressAnimation(pcs); goto NukeFolder; break; } } break; case BBDELETE_CANNOT_DELETE: { // This is the non-deletable file case. Note: this is an NT only case, and // it could be caused by acls or the fact that the file is currently in use. // We attemt to really delete the file (which should fail) so we can generate // the proper error value DWORD dwAttributes = GetFileAttributes(pszSource); if (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) { iRet = Win32RemoveDirectory(pszSource); } else { iRet = Win32DeleteFile(pszSource); } if (!iRet) { // indeed, the file/folder could not be deleted. // Get last error to find out why iRet = GetLastError(); } else { // DeleteFileBB said that it couldn't be deleted, but we just nuked it. We will // end up falling into this case when we hit things like Mounted Volumes. // As Obi-Wan would say: "You don't need to see his identification... these aren't // the droids you are looking for... He can go about his business... Move along." iRet = ERROR_SUCCESS; DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder // dont allow undo since we reall nuked it (cant bring it back...) if (pcs->lpua) { FOUndo_Release(pcs->lpua); } } } break; case BBDELETE_FORCE_NUKE: { // This is the catch-all case. If iRet = BDETETE_FORCE_NUKE, then we just nuke the // file without warning. // return noerror so we recurse into this dir and nuke it iRet = ERROR_SUCCESS; // set this so the is correct progress animation is displayed if (pcs) { pcs->fFlags &= ~FOF_ALLOWUNDO; } // dont allow undo since we are really nuking it (cant bring it back...) if (pcs->lpua) { FOUndo_Release(pcs->lpua); } UpdateProgressAnimation(pcs); goto NukeFolder; } break; case BBDELETE_CANCELLED: { // user canceled the operation pcs->lpfo->fAnyOperationsAborted = TRUE; pcs->bAbort = TRUE; //Because the Delete on this FOLDER is cancelled, we can cancel the "Delete" // on the corresponding FILE too! if (DTNIsConnectOrigin(pcs->dth.pdtnCurrent)) { pcs->dth.pdtnCurrent->pdtnConnected->fDummy = TRUE; } } case BBDELETE_UNKNOWN_ERROR: default: { iRet = GetLastError(); ASSERT(iRet != ERROR_SUCCESS); } break; } } // DeleteFileBB return iRet; } BOOL DoFile_Win32DeleteFileWithPidl(LPCTSTR pszFile, SIMPLEPIDLCACHE *pspc) { LPITEMIDLIST pidlFile = NULL; int iRet; if (pspc) { pidlFile = SimplePidlCache_GetFilePidl(pspc, pszFile); } iRet = Win32DeleteFilePidl(pszFile, pidlFile); ILFree(pidlFile); return iRet; } int DoFile_Delete(COPY_STATE* pcs, WIN32_FIND_DATA *pfdSrc, LPTSTR pszSource, UINT cchSource, HDPA *phdpaDeletedFiles, BOOL fShouldSuspendEvents) { int iRet = 0; // if we dont have an undo atom or this isint a root node or if this is a network file // then we need to really nuke it if (!pcs->lpua || !DTNIsRootNode(pcs->dth.pdtnCurrent) || IsNetDrive(PathGetDriveNumber(pszSource))) { iRet = DoFile_Win32DeleteFileWithPidl(pszSource, fShouldSuspendEvents ? NULL : &pcs->spc) ? 0 : GetLastError(); if (!iRet) { FOUndo_FileReallyDeleted(pszSource); } } else if (!DeleteFileBB(pszSource, cchSource, &iRet, pcs, FALSE, pfdSrc, phdpaDeletedFiles)) { // DeleteFileBB failed, check iRet to find out why switch (iRet) { case BBDELETE_SIZE_TOO_BIG: case BBDELETE_NUKE_OFFLINE: { // This is the case where the file is too big to fit in the Recycle Bin. We have no // choice but to really nuke it, but we warn the user first since they may have thought // that it was being sent to the recycle bin. int result = CachedConfirmFileOp(pcs->hwndDlgParent, pcs, &pcs->cd, pcs->nSourceFiles, FALSE, (iRet == BBDELETE_SIZE_TOO_BIG) ? CONFIRM_WONT_RECYCLE_FOLDER : CONFIRM_WONT_RECYCLE_OFFLINE, pszSource, pfdSrc, NULL, NULL, NULL); switch (result) { case IDNO: // user said "please dont really nuke the file" pcs->lpfo->fAnyOperationsAborted = TRUE; iRet = IDNO; // Don't put up error message for this case // WARNING: It is tempting to mark the corresponding connected folder as dummy here. // But, this will not work because currently folders (nodes with children) can not be // marked as dummy. break; case IDCANCEL: // user canceled the operation pcs->lpfo->fAnyOperationsAborted = TRUE; pcs->bAbort = TRUE; // WARNING: It is tempting to mark the corresponding connected folder as dummy here. // But, this will not work because currently folders (nodes with children) can not be // marked as dummy. break; case IDYES: default: // user said "please nuke the file" // set this so the is correct progress animation is displayed if (pcs) { pcs->fFlags &= ~FOF_ALLOWUNDO; } // dont allow undo since we are really nuking it if (pcs->lpua) { FOUndo_Release(pcs->lpua); } UpdateProgressAnimation(pcs); iRet = DoFile_Win32DeleteFileWithPidl(pszSource, &pcs->spc) ? 0 : GetLastError(); break; } } break; case BBDELETE_CANNOT_DELETE: { // This is the non-deletable file case. Note: this is an NT only case, and // it could be caused by acls or the fact that the file is currently in use. // We attemt to really delete the file (which should fail) so we can generate // the proper error value iRet = Win32DeleteFile(pszSource); if (!iRet) { // indeed, the file/folder could not be deleted. // Get last error to find out why iRet = GetLastError(); } else { // DeleteFileBB said that it couldn't be deleted, but we just nuked it. We will // end up falling into this case when we hit things like Mounted Volumes and other // reparse points that we can't "recycle". // As Obi-Wan would say: "You don't need to see his identification... these aren't // the droids you are looking for... He can go about his business... Move along." iRet = ERROR_SUCCESS; DTAbortCurrentNode(&pcs->dth); // so we don't recurse down this folder // dont allow undo since we really nuked it (cant bring it back...) if (pcs->lpua) { FOUndo_Release(pcs->lpua); } } } break; case BBDELETE_FORCE_NUKE: { // This is the catch-all case. If iRet = BDETETE_FORCE_NUKE, then we just nuke the // file without warning. // set this so the is correct progress animation is displayed if (pcs) { pcs->fFlags &= ~FOF_ALLOWUNDO; } // dont allow undo since we are going to nuke this file if (pcs->lpua) { FOUndo_Release(pcs->lpua); } UpdateProgressAnimation(pcs); iRet = DoFile_Win32DeleteFileWithPidl(pszSource, &pcs->spc) ? 0 : GetLastError(); } break; case BBDELETE_CANCELLED: { // user canceled the operation pcs->lpfo->fAnyOperationsAborted = TRUE; pcs->bAbort = TRUE; } break; case BBDELETE_UNKNOWN_ERROR: default: { iRet = GetLastError(); ASSERT(iRet != ERROR_SUCCESS); } break; } } // !DeleteFileBB return iRet; } int DoFile_Copy(COPY_STATE* pcs, LPTSTR pszSource, LPTSTR pszDest, UINT cchDest, WIN32_FIND_DATA *pfd, WIN32_FIND_DATA * pfdDest, BOOL fRenameTried) { /* Now try to copy the file. Do extra error processing only in 2 cases: 1) If a removeable drive is full let the user stick in a new disk 2) If the path doesn't exist (the user typed in and explicit path that doesn't exits) ask if we should create it for him. */ int ret = FileCopy(pcs, pszSource, pszDest, pfd, fRenameTried); if (ret == ERROR_CANCELLED) { pcs->bAbort = TRUE; return ret; } if ((ret & ~ERRORONDEST) == ERROR_FILE_EXISTS) { if (!fRenameTried) { int result = CheckForRenameCollision(pcs, OPER_DOFILE, pszSource, pszDest, cchDest, pfdDest, pfd); switch (result) { case IDUNKNOWN: break; case IDRETRY: return DoFile_Copy(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, TRUE); case IDCANCEL: pcs->bAbort = TRUE; return result; case IDNO: return result; default: return result; } } } if ((((ret & ~ERRORONDEST) == ERROR_DISK_FULL) && IsRemovableDrive(DRIVEID(pszDest))) || ((ret & ~ERRORONDEST) == ERROR_PATH_NOT_FOUND)) { ULARGE_INTEGER ulFileSize; ulFileSize.LowPart = pfd->nFileSizeLow; ulFileSize.HighPart = pfd->nFileSizeHigh; ret = CopyMoveRetry(pcs, pszDest, ret & ~ERRORONDEST, &ulFileSize); if (!ret) { return DoFile_Copy(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, fRenameTried); } else { pcs->bAbort = TRUE; return ret; } } if (!ret) { // add to the undo atom // if we're doing a copy, only keep track of the highest most // level.. unless we're doing a merge sort of copy if (pcs->lpua) { if (DTNIsRootNode(pcs->dth.pdtnCurrent) && !DTNIsConnected(pcs->dth.pdtnCurrent)) FOUndo_AddInfo(pcs->lpua, pszSource, pszDest, 0); } // if we copied in a new desktop ini, send out an update event for the paretn if (!lstrcmpi(PathFindFileName(pszDest), c_szDesktopIni)) { TCHAR szDest[MAX_PATH]; HRESULT hr = StringCchCopy(szDest, ARRAYSIZE(szDest), pszDest); if (SUCCEEDED(hr)) { PathRemoveFileSpec(szDest); SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, szDest, NULL); } } } return ret; } int DoFile_Move(COPY_STATE* pcs, LPTSTR pszSource, LPTSTR pszDest, UINT cchDest, WIN32_FIND_DATA *pfd, WIN32_FIND_DATA * pfdDest, BOOL fRenameTried) { int ret = 0; if (PathIsRoot(pszSource)) { return DE_ROOTDIR; } if (PathIsRoot(pszDest)) { return DE_ROOTDIR | ERRORONDEST; } AvoidCurrentDirectory(pszSource); if (PathIsSameRoot(pszSource, pszDest)) { TryAgain: ret = Win32MoveFile(pszSource, pszDest, ISDIRFINDDATA(*pfd)) ? 0 : GetLastError(); // try to create the destination if it is not there if (ret == ERROR_PATH_NOT_FOUND) { ret = CopyMoveRetry(pcs, pszDest, ret, NULL); if (!ret) { goto TryAgain; } } if (ret == ERROR_ALREADY_EXISTS) { if (!fRenameTried) { int result = CheckForRenameCollision(pcs, OPER_DOFILE, pszSource, pszDest, cchDest, pfdDest, pfd); switch (result) { case IDUNKNOWN: break; case IDRETRY: fRenameTried = TRUE; goto TryAgain; case IDCANCEL: pcs->bAbort = TRUE; return result; case IDNO: return result; default: return result; } } } if ((ret == ERROR_SUCCESS) && !SHRestricted(REST_NOENCRYPTONMOVE) && !(pfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !(pfd->dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED)) { TCHAR szDestDir[MAX_PATH]; DWORD dwAttribs; // We are moving a file that is NOT encrypted. On Win2k, we need to check to see if this was a move to // an encrypted folder. If so, we automatically encrypt the file. HRESULT hr = StringCchCopy(szDestDir, ARRAYSIZE(szDestDir), pszDest); if (SUCCEEDED(hr)) { PathRemoveFileSpec(szDestDir); dwAttribs = GetFileAttributes(szDestDir); if ((dwAttribs != -1) && (dwAttribs & FILE_ATTRIBUTE_ENCRYPTED)) { // sanity check ASSERT(dwAttribs & FILE_ATTRIBUTE_DIRECTORY); // attempt to encrypt the file if (!SHEncryptFile(pszDest, TRUE)) { int result = CachedConfirmFileOp(pcs->hwndDlgParent, pcs, &pcs->cd, pcs->nSourceFiles, FALSE, CONFIRM_FAILED_ENCRYPT, pszDest, pfd, // since we just moved it, the attibs should be the same as the src NULL, NULL, NULL); switch (result) { case IDCANCEL: // user canceled the operation pcs->lpfo->fAnyOperationsAborted = TRUE; pcs->bAbort = TRUE; break; case IDNO: // user choose to "restore" the file to its original location ret = Win32MoveFile(pszDest, pszSource, ISDIRFINDDATA(*pfd)) ? 0 : GetLastError(); case IDYES: default: // user ignored the error break; } } } } } if (ret == ERROR_SUCCESS) { if (pcs->lpua && DTNIsRootNode(pcs->dth.pdtnCurrent) && !DTNIsConnected(pcs->dth.pdtnCurrent)) { // add to the undo atom FOUndo_AddInfo(pcs->lpua, pszSource, pszDest, 0); } // Win32MoveFile on a single-volume leaves the original ACL // intact. If necessary, pick up perms from the destination. if (pcs->fFlags & FOF_NOCOPYSECURITYATTRIBS) { ResetFileSecurity(pszDest); } // if we copied in a new desktop ini, send out an update event for the paretn if (!lstrcmpi(PathFindFileName(pszDest), c_szDesktopIni)) { TCHAR szDest[MAX_PATH]; HRESULT hr = StringCchCopy(szDest, ARRAYSIZE(szDest), pszDest); if (SUCCEEDED(hr)) { PathRemoveFileSpec(szDest); SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, szDest, NULL); } } } } else { // we must force all copies to go through // straight so we can remove the source if (DTNIsConnected(pcs->dth.pdtnCurrent) && !PathFileExists(pszSource)) { //This can happen if "foo.htm" and "foo files" were moved by the end-user. // The connected file had already been moved and hence this is not an error! ret = 0; //No error! That file has been moved already! } else { ret = DoFile_Copy(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, FALSE); } } return ret; } int DoFile_Rename(COPY_STATE* pcs, LPTSTR pszSource, LPTSTR pszDest, UINT cchDest, WIN32_FIND_DATA *pfd, WIN32_FIND_DATA * pfdDest, BOOL fRenameTried) { LPTSTR p = PathFindFileName(pszSource); /* Get raw source and dest paths. Check to make sure the paths are the same */ int ret = !IntlStrEqNI(pszSource, pszDest, (int)(p - pszSource)); if (ret) { return DE_DIFFDIR; } return DoFile_Move(pcs, pszSource, pszDest, cchDest, pfd, pfdDest, fRenameTried); } int MoveCopyInitPCS(COPY_STATE * pcs) { BOOL fMultiDest = FALSE; int ret = 0; LPTSTR p = NULL; TCHAR szDestPath[MAX_PATH]; pcs->nSourceFiles = CountFiles(pcs->lpfo->pFrom); // multiple source files? pcs->fProgressOk = TRUE; // skip destination processing if we are deleting files if (pcs->lpfo->wFunc != FO_DELETE) { HRESULT hr = S_OK; if (pcs->lpfo->pTo == NULL) { szDestPath[0] = 0; } else { hr = StringCchCopy(szDestPath, ARRAYSIZE(szDestPath), pcs->lpfo->pTo); } if (SUCCEEDED(hr)) { if (!szDestPath[0]) // NULL dest is same as "." { szDestPath[0] = TEXT('.'); szDestPath[1] = 0; } } if (FAILED(hr) || PathIsInvalid(szDestPath)) { CopyError(pcs, c_szNULL, c_szNULL, DE_INVALIDFILES | ERRORONDEST, pcs->lpfo->wFunc, 0); return ERROR_ACCESS_DENIED; } if (pcs->lpfo->wFunc == FO_RENAME) { // don't let them rename multiple files to one single file if ((pcs->nSourceFiles != 1) && !PathIsWild(szDestPath)) { CopyError(pcs, c_szNULL, c_szNULL, DE_MANYSRC1DEST, pcs->lpfo->wFunc, 0); return DE_MANYSRC1DEST; } fMultiDest = TRUE; } else // FO_COPY or FO_MOVE at this point { fMultiDest = ((pcs->fFlags & FOF_MULTIDESTFILES) && (pcs->nSourceFiles == CountFiles(pcs->lpfo->pTo))); if (!fMultiDest) { // for backwards compat. // copy c:\foo.bar c:\folder\foo.bar means // multi dest if foo.bar doesn't exist. // Hack if it is a root we special case this for the offline // floppy case... if (pcs->nSourceFiles == 1 && !PathIsRoot(szDestPath) && !PathIsDirectory(szDestPath)) { fMultiDest = TRUE; } } } } pcs->dth.fMultiDest = fMultiDest; return 0; } DWORD g_dwStopWatchMode = 0xffffffff; // Shell performance mode // actually this does move/copy/rename/delete int MoveCopyDriver(COPY_STATE *pcs) { int ret; WIN32_FIND_DATA fdSrc; WIN32_FIND_DATA fdDest; HDPA hdpaDeletedFiles = NULL; LPSHFILEOPSTRUCT lpfo = pcs->lpfo; TCHAR szText[28]; BOOL bInitialAllowUndo = FALSE; DWORD dwLastUpdateTime = 0; BOOL fShouldSuspendEvents = FALSE; HANDLE hEventRunning; if (g_dwStopWatchMode) { if (g_dwStopWatchMode == 0xffffffff) { g_dwStopWatchMode = StopWatchMode(); // Since the stopwatch funcs live in shdocvw, delay this call so we don't load shdocvw until we need to } if (g_dwStopWatchMode) { StringCchCopy(szText, ARRAYSIZE(szText), TEXT("Shell ")); switch (lpfo->wFunc) { case FO_COPY: StringCchCat(szText, ARRAYSIZE(szText), TEXT("Copy ")); break; case FO_MOVE: StringCchCat(szText, ARRAYSIZE(szText), TEXT("Move ")); break; case FO_DELETE: StringCchCat(szText, ARRAYSIZE(szText), TEXT("Delete")); break; case FO_RENAME: StringCchCat(szText, ARRAYSIZE(szText), TEXT("Rename")); break; default: StringCchCat(szText, ARRAYSIZE(szText), TEXT("Copy? ")); break; } StringCchCat(szText, ARRAYSIZE(szText), TEXT(": Start")); StopWatch_Start(SWID_COPY, (LPCTSTR)szText, SPMODE_SHELL | SPMODE_DEBUGOUT); } } // start by assuming an error. Non-zero means an error has occured. If we don't // start with this assumption then we will return success if MoveCopyInitPCS fails. ret = ERROR_GEN_FAILURE; if (!ValidFilenames(lpfo->pFrom)) { CopyError(pcs, c_szNULL, c_szNULL, DE_INVALIDFILES, lpfo->wFunc, 0); return ERROR_ACCESS_DENIED; } StartCopyEngine(&hEventRunning); // Check the pcs destination directory to make sure it is valid for the given source file list if (MoveCopyInitPCS(pcs)) { goto ExitLoop; // Destination is invalid so we bail out } // Build a tree where each node is a source file, a dest file, and an operation to perform ret = DTBuild(pcs); if (ret) { goto ShowMessageBox; } // Speed optimization: for a delete, sending all FSNotifies really bogs down the system, // so we skip it and rely on the file system notifies. if (((lpfo->wFunc == FO_DELETE) || (lpfo->wFunc == FO_MOVE)) && (pcs->dth.dtAll.dwFiles > 100)) { // Only suspend notifies for local moves if (lpfo->wFunc == FO_MOVE) { if (lpfo->pTo) { int idDrive = PathGetDriveNumber(lpfo->pFrom); if (idDrive == PathGetDriveNumber(lpfo->pTo) && !IsNetDrive(idDrive)) { fShouldSuspendEvents = TRUE; } } } else { fShouldSuspendEvents = TRUE; } } if (fShouldSuspendEvents) { // SuspendSHNotify can fail if another thread is using it. Only one thread at a time can suspend notify. fShouldSuspendEvents = SuspendSHNotify(); } // save off the initial state of the allowundo flag if (pcs->fFlags & FOF_ALLOWUNDO) { bInitialAllowUndo = TRUE; } // When first starting, we assume that stream loss is possible until we prove // otherwise for the current directory. This gets reset to true each time we // enter a new dir via EnterDir_Move or EnterDir_Copy pcs->bStreamLossPossible = TRUE; for (;;) { BOOL bUpdateAnimation = FALSE; int result; DWORD dwTickCount; BOOL bTimeToUpdate = FALSE; BOOL fOk; pcs->dth.oper = DTGoToNextNode(&pcs->dth,pcs); dwTickCount = GetTickCount(); if ((dwTickCount - dwLastUpdateTime) > 10) { dwLastUpdateTime = dwTickCount; bTimeToUpdate = TRUE; } if ((pcs->dth.oper & OPER_MASK) == OPER_ERROR) { CopyError(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, LOBYTE(pcs->dth.oper), pcs->lpfo->wFunc, OPER_DOFILE); // If the directory is copied but a file inside that directory could not // be copied because of long filename, check to see if this is // a connected element. If so, invoke undo, sothat we getback the orginal html file // in the same place as the associated folder. if ((pcs->dth.oper == (OPER_ERROR | DE_INVALIDFILES)) && (DTNIsConnected(pcs->dth.pdtnCurrent))) { if (pcs->lpua) { pcs->lpua->foFlags |= FOF_NOCONFIRMATION; FOUndo_Invoke(pcs->lpua); pcs->lpua = NULL; } } break; } if (!pcs->dth.oper || pcs->bAbort) // all done? { break; } if (DTNIsRootNode(pcs->dth.pdtnCurrent) && (pcs->dth.oper != OPER_LEAVEDIR)) { int iDrive; // check to see if we switched between doing a move to // recycle bin and a true delete (this would happen when // there was an object that was too big for the recycle bin) if (!(pcs->fFlags & FOF_ALLOWUNDO) && bInitialAllowUndo) { // reset the allowundo flag since we have a new root node, and we // want to attempt to send it to the recycle bin pcs->fFlags |= FOF_ALLOWUNDO; // we delay to update the progress animation till we are basically // done, which allows us to keep the progress and animation in sync bUpdateAnimation = TRUE; } pcs->fMerge = FALSE; pcs->fFromCDRom = FALSE; // Check source for being a CDRom iDrive = PathGetDriveNumber(pcs->dth.szSrcPath); if (-1 != iDrive) { TCHAR szDrive[4]; if (DRIVE_CDROM == GetDriveType(PathBuildRoot(szDrive, iDrive))) { pcs->fFromCDRom = TRUE; } } } fOk = DTGetWin32FindData(pcs->dth.pdtnCurrent, &fdSrc); if (!fOk) { ret = ERROR_FILENAME_EXCED_RANGE; goto ShowMessageBox; } fdDest.dwFileAttributes = 0; DebugMsg(TF_DEBUGCOPY, TEXT("MoveCopyDriver(): Oper %x From(%s) To(%s)"), pcs->dth.oper, (LPCTSTR)pcs->dth.szSrcPath, (LPCTSTR)pcs->dth.szDestPath); // some operation that may effect the destination (have a collision) if ((pcs->lpfo->wFunc != FO_DELETE) && (pcs->dth.oper != OPER_LEAVEDIR)) { // this compare needs to be case sensitive, and locale insensitive if (!StrCmpC(pcs->dth.szSrcPath, pcs->dth.szDestPath) && !(pcs->fFlags & FOF_RENAMEONCOLLISION)) { // Source and dest are the same file, and name collision // resolution is not turned on, so we just return an error. // TODO: Show the error dialog here and allow for SKIP ret = DE_SAMEFILE; goto ShowMessageBox; } } result = AllConfirmations(pcs, &fdSrc, pcs->dth.oper, pcs->lpfo->wFunc, pcs->dth.szSrcPath, pcs->dth.szDestPath, bTimeToUpdate, &fdDest, &ret); switch (result) { case IDNO: DTAbortCurrentNode(&pcs->dth); lpfo->fAnyOperationsAborted = TRUE; continue; case IDCANCEL: pcs->bAbort = TRUE; goto ExitLoop; case IDYES: break; default: ret = result; goto ShowMessageBox; } /* Now determine which operation to perform */ switch (pcs->dth.oper | pcs->lpfo->wFunc) { // Note that ENTERDIR is not done for a root, even though LEAVEDIR is case OPER_ENTERDIR | FO_MOVE: // Create dest, verify source delete ret = EnterDir_Move(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, ARRAYSIZE(pcs->dth.szDestPath), &fdSrc, &fdDest, FALSE); break; case OPER_ENTERDIR | FO_COPY: // Create destination directory ret = EnterDir_Copy(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, ARRAYSIZE(pcs->dth.szDestPath), &fdSrc, &fdDest, FALSE, FALSE); break; case OPER_LEAVEDIR | FO_MOVE: case OPER_LEAVEDIR | FO_DELETE: ret = LeaveDir_Delete(pcs, pcs->dth.szSrcPath); break; case OPER_LEAVEDIR | FO_COPY: break; case OPER_DOFILE | FO_COPY: ret = DoFile_Copy(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, ARRAYSIZE(pcs->dth.szDestPath), &fdSrc, &fdDest, FALSE); break; case OPER_DOFILE | FO_RENAME: ret = DoFile_Rename(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, ARRAYSIZE(pcs->dth.szDestPath), &fdSrc, &fdDest, FALSE); break; case OPER_DOFILE | FO_MOVE: ret = DoFile_Move(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, ARRAYSIZE(pcs->dth.szDestPath), &fdSrc, &fdDest, FALSE); break; case OPER_ENTERDIR | FO_DELETE: ret = EnterDir_Delete(pcs, &fdSrc, pcs->dth.szSrcPath, ARRAYSIZE(pcs->dth.szSrcPath), &hdpaDeletedFiles); break; case OPER_DOFILE | FO_DELETE: ret = DoFile_Delete(pcs, &fdSrc, pcs->dth.szSrcPath, ARRAYSIZE(pcs->dth.szSrcPath), &hdpaDeletedFiles, fShouldSuspendEvents); break; default: DebugMsg(DM_ERROR, TEXT("Invalid file operation")); ret = 0; // internal error break; } // switch (pcs->dth.oper | pcs->lpfo->wFunc) if (pcs->bAbort) break; if (ret == IDNO) { pcs->lpfo->fAnyOperationsAborted = TRUE; } else if (ret) { // any errors? ShowMessageBox: // If source file is a connected item and is not found, that means that // we have already moved/deleted/renamed it. So, don't report that as error! if ((!pcs->dth.pdtnCurrent) || (!pcs->dth.pdtnCurrent->fConnectedElement) || ((ret != ERROR_FILE_NOT_FOUND) && (ret != ERROR_PATH_NOT_FOUND))) { CopyError(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, ret, pcs->lpfo->wFunc, pcs->dth.oper); // If the directory is copied but a file inside that directory could not // be copied because of long filename, check to see if this is // a connected element. If so, invoke undo, sothat we getback the orginal html file // in the same place as the associated folder. if ((ret == ERROR_FILENAME_EXCED_RANGE) && (DTNIsConnected(pcs->dth.pdtnCurrent))) { if (pcs->lpua) { pcs->lpua->foFlags |= FOF_NOCONFIRMATION; FOUndo_Invoke(pcs->lpua); pcs->lpua = NULL; } } break; } } if (bTimeToUpdate) { // perform the delayed update of the dialog if (bUpdateAnimation) { UpdateProgressAnimation(pcs); bUpdateAnimation = FALSE; } // We check to see if we are finished here (instead of at the // start) since we want to keep the progress a step behind what // we are doing to ensure we have the correct progress animation // and text (since FOQueryAbort updates the progress text) if (FOQueryAbort(pcs)) break; } } ExitLoop: // this happens in error cases where we broke out of the pcr loop // without hitting the end lpfo->hNameMappings = pcs->dth.hdsaRenamePairs; DTCleanup(&pcs->dth); BBFinishDelete(hdpaDeletedFiles); if (fShouldSuspendEvents) { ResumeSHNotify(); if (lpfo->wFunc == FO_DELETE) { TCHAR szNotifyPath[MAX_PATH]; int iDrive; // Since we probably blew away any chance at having the FSNotify work, make sure // we update dir for this path... we can send the message on any drive since // the bitbucket listens for changes on all drives. iDrive = DriveIDFromBBPath(lpfo->pFrom); if ((iDrive == -1) || !DriveIDToBBPath(iDrive, szNotifyPath)) { StringCchCopy(szNotifyPath, ARRAYSIZE(szNotifyPath), lpfo->pFrom); // failure ok since only use for changenotify PathRemoveFileSpec(szNotifyPath); } SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH, szNotifyPath, NULL); } } if (g_dwStopWatchMode) { StringCchCopy(&szText[12], ARRAYSIZE(szText)-12, TEXT(": Stop ")); StopWatch_Stop(SWID_COPY, (LPCTSTR)szText, SPMODE_SHELL | SPMODE_DEBUGOUT); } EndCopyEngine(hEventRunning); return ret; } void SetWindowTextFromRes(HWND hwnd, int id) { TCHAR szTemp[80]; LoadString(HINST_THISDLL, id, szTemp, ARRAYSIZE(szTemp)); SetWindowText(hwnd, szTemp); } int CountProgressPoints(COPY_STATE *pcs, PDIRTOTALS pdt) { // point value for each item int iTotal = 0; UINT uSize = pcs->uSize; if (!uSize) { uSize = 32*1024; } // now add it up. iTotal += (UINT)((pdt->liSize.QuadPart/uSize) * pcs->dth.iSizePoints); iTotal += pdt->dwFiles * pcs->dth.iFilePoints; iTotal += pdt->dwFolders * pcs->dth.iFolderPoints; return iTotal; } void UpdateProgressDialog(COPY_STATE* pcs) { int iRange; // from 0 to iRange int iPos; // how much is done. if (pcs->fProgressOk) { if (pcs->dth.dtAll.fChanged) { pcs->dth.dtAll.fChanged = FALSE; iRange = CountProgressPoints(pcs, &pcs->dth.dtAll); SendProgressMessage(pcs, PBM_SETRANGE32, 0, iRange); DebugMsg(TF_DEBUGCOPY, TEXT("UpdateProgressDialog iRange = %d "), iRange); } if (pcs->dth.dtDone.fChanged) { pcs->dth.dtDone.fChanged = FALSE; iPos = CountProgressPoints(pcs, &pcs->dth.dtDone); SendProgressMessage(pcs, PBM_SETPOS, iPos, 0); DebugMsg(TF_DEBUGCOPY, TEXT("UpdateProgressDialog iPos = %d "), iPos); } } } // NOTE: !! do NOT refrence pcs->lpfo anywhere in this dialog proc !! // // It can be freed while we are still running. If you need to get information from it, // add a new member to the FOUITHREADINFO struct and copy the value from the pcs->lpfo // into the member (while holding the critsec) right before we create this dlg. BOOL_PTR CALLBACK FOFProgressDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam) { FOUITHREADINFO* pfouiti = (FOUITHREADINFO*)GetWindowLongPtr(hDlg, DWLP_USER); COPY_STATE *pcs = (pfouiti ? pfouiti->pcs : NULL); if (WM_INITDIALOG == wMsg) { SetWindowLongPtr(hDlg, DWLP_USER, lParam); pfouiti = (FOUITHREADINFO*)lParam; pcs = pfouiti->pcs; SetWindowTextFromRes(hDlg, IDS_ACTIONTITLE + pfouiti->wFunc); if (pcs->fFlags & FOF_SIMPLEPROGRESS) { TCHAR szFrom[MAX_PATH]; if (pcs->lpszProgressTitle) { if (IS_INTRESOURCE(pcs->lpszProgressTitle)) { LoadString(HINST_THISDLL, PtrToUlong(pcs->lpszProgressTitle), szFrom, ARRAYSIZE(szFrom)); pcs->lpszProgressTitle = szFrom; } SetDlgItemText(hDlg, IDD_NAME, pcs->lpszProgressTitle); // null it so we only set it once pcs->lpszProgressTitle = NULL; } } return FALSE; } if (pcs) { switch (wMsg) { case WM_TIMER: if (IsWindowEnabled(hDlg)) SetProgressTime(pcs); break; case WM_SHOWWINDOW: if (wParam) { int idAni; HWND hwndAnimation; ASSERT(pfouiti->wFunc >= FO_MOVE && pfouiti->wFunc <= FO_DELETE); ASSERT(FO_COPY==FO_MOVE+1); ASSERT(FO_DELETE==FO_COPY+1); ASSERT(IDA_FILECOPY==IDA_FILEMOVE+1); ASSERT(IDA_FILEDEL ==IDA_FILECOPY+1); switch (pfouiti->wFunc) { case FO_DELETE: if (pfouiti->bIsEmptyRBOp) { idAni = IDA_FILENUKE; break; } else if (!(pcs->fFlags & FOF_ALLOWUNDO)) { idAni = IDA_FILEDELREAL; break; } // else fall through default: idAni = (IDA_FILEMOVE + (int)pfouiti->wFunc - FO_MOVE); } hwndAnimation = GetDlgItem(hDlg,IDD_ANIMATE); Animate_Open(hwndAnimation, IntToPtr(idAni)); SetProp(hwndAnimation, TEXT("AnimationID"), IntToPtr(idAni)); // a timer every MS_TIMESLICE seconds to update the progress time estimate SetTimer(hDlg, 1, MS_TIMESLICE, NULL); } break; case WM_ENABLE: if (wParam) { if (pcs->dwPreviousTime) { // if we're enabling it, set the previous time to now // because no action has happened while we were disabled pcs->dwPreviousTime = GetTickCount(); } } else { SetProgressTime(pcs); } PauseAnimation(pcs, wParam == 0); break; case WM_COMMAND: switch (GET_WM_COMMAND_ID(wParam, lParam)) { case IDCANCEL: pcs->bAbort = TRUE; ShowWindow(hDlg, SW_HIDE); break; } break; case PDM_SHUTDOWN: // Make sure this window is shown before telling the user there // is a problem // ignore FOF_NOERRORUI here because of the nature of the situation ShellMessageBox(HINST_THISDLL, hDlg, MAKEINTRESOURCE(IDS_CANTSHUTDOWN), NULL, MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND); break; case PDM_NOOP: // a dummy id that we can take so that folks can post to us and make // us go through the main loop break; case PDM_UPDATE: pcs->dth.fChangePosted = FALSE; UpdateProgressDialog(pcs); break; case WM_QUERYENDSESSION: // Post a message telling the dialog to show the "We can't shutdown now" // dialog and return to USER right away, so we don't have to worry about // the user not clicking the OK button before USER puts up its "this // app didn't respond" dialog PostMessage(hDlg, PDM_SHUTDOWN, 0, 0); // Make sure the dialog box procedure returns FALSE SetWindowLongPtr(hDlg, DWLP_MSGRESULT, FALSE); return TRUE; default: return FALSE; } } return TRUE; } int CALLBACK FOUndo_FileReallyDeletedCallback(UNDOATOM *lpua, LPARAM lParam) { LPTSTR * ppsz = (LPTSTR*)lParam; // this is our signal to nuke the rest if (!*ppsz) return EUA_DELETE; switch (lpua->uType) { case IDS_RENAME: case IDS_COPY: case IDS_MOVE: case IDS_DELETE: { LPFOUNDODATA lpud = (LPFOUNDODATA)lpua->lpData; HDPA hdpa = lpud->hdpa; // only the destinations matter. int i, iMax = DPA_GetPtrCount(hdpa); for (i = 1; i <= iMax; i += 2) { LPTSTR lpsz = DPA_GetPtr(hdpa, i); if (lstrcmpi(lpsz, *ppsz) == 0) { *ppsz = NULL; break; } } } break; } // this is our signal to nuke the rest if (!*ppsz) return EUA_DELETE; else return EUA_DONOTHING; } // someone really really deleted a file. make sure we no longer have // any undo information pointing to it. void FOUndo_FileReallyDeleted(LPTSTR lpszFile) { EnumUndoAtoms(FOUndo_FileReallyDeletedCallback, (LPARAM)&lpszFile); } int CALLBACK FOUndo_FileRestoredCallback(UNDOATOM *lpua, LPARAM lParam) { LPTSTR psz = (LPTSTR)lParam; switch (lpua->uType) { case IDS_DELETE: { LPFOUNDODATA lpud = (LPFOUNDODATA)lpua->lpData; HDPA hdpa = lpud->hdpa; LPTSTR lpsz; int i, iMax; ASSERT(hdpa); // only the destinations matter. iMax = DPA_GetPtrCount(hdpa); for (i = 1; i <= iMax; i += 2) { lpsz = DPA_GetPtr(hdpa, i); if (lstrcmpi(lpsz, psz) == 0) { ENTERCRITICAL; Str_SetPtr(&lpsz, NULL); lpsz = DPA_GetPtr(hdpa, i - 1); Str_SetPtr(&lpsz, NULL); DPA_DeletePtr(hdpa, i); DPA_DeletePtr(hdpa, i - 1); LEAVECRITICAL; if (DPA_GetPtrCount(hdpa)) return EUA_ABORT; else return EUA_DELETEABORT; } } } break; } return EUA_DONOTHING; } // this means someone restored a file (via ui in the bitbucket) // so we need to clean up the undo info. void FOUndo_FileRestored(LPCTSTR lpszFile) { EnumUndoAtoms(FOUndo_FileRestoredCallback, (LPARAM)lpszFile); } void FOUndo_AddInfo(UNDOATOM *lpua, LPTSTR lpszSrc, LPTSTR lpszDest, DWORD dwAttributes) { HDPA hdpa; LPTSTR lpsz = NULL; int i; LPFOUNDODATA lpud; if (lpua->lpData == (void *)-1) return; if (!lpua->lpData) { lpua->lpData = LocalAlloc(LPTR, sizeof(FOUNDODATA)); if (!lpua->lpData) return; ((LPFOUNDODATA)lpua->lpData)->hdpa = (void *)DPA_Create(4); } lpud = lpua->lpData; hdpa = lpud->hdpa; if (!hdpa) return; // if it's a directory that got deleted, we're just going to save it's // attributes so that we can recreate it later. // directories do NOT get moved into the wastebasket if ((lpua->uType == IDS_DELETE) && (dwAttributes & FILE_ATTRIBUTE_DIRECTORY)) { FOUNDO_DELETEDFILEINFO dfi; if (!lpud->hdsa) { lpud->hdsa = DSA_Create(sizeof(FOUNDO_DELETEDFILEINFO), 4); if (!lpud->hdsa) return; } Str_SetPtr(&lpsz, lpszSrc); dfi.lpszName = lpsz; dfi.dwAttributes = dwAttributes; DSA_AppendItem(lpud->hdsa, &dfi); } else { Str_SetPtr(&lpsz, lpszSrc); if (!lpsz) return; if ((i = DPA_AppendPtr(hdpa, lpsz)) == -1) { return; } lpsz = NULL; Str_SetPtr(&lpsz, lpszDest); if (!lpsz || DPA_AppendPtr(hdpa, lpsz) == -1) { DPA_DeletePtr(hdpa, i); } } } LPTSTR DPA_ToFileList(HDPA hdpa, int iStart, int iEnd, int iIncr) { LPTSTR lpsz; LPTSTR lpszReturn; int ichSize; int ichTemp; int i; // undo copy by deleting destinations lpszReturn = (LPTSTR)LocalAlloc(LPTR, 1); if (!lpszReturn) { return NULL; } ichSize = 1; // build the NULL separated file list // go from the end to the front.. restore in reverse order! for (i = iEnd; i >= iStart ; i -= iIncr) { LPTSTR psz; HRESULT hr; UINT cchLen; lpsz = DPA_GetPtr(hdpa, i); ASSERT(lpsz); ichTemp = ichSize - 1; cchLen = lstrlen(lpsz); ichSize += (cchLen + 1); psz = (LPTSTR)LocalReAlloc((HLOCAL)lpszReturn, ichSize * sizeof(TCHAR), LMEM_MOVEABLE|LMEM_ZEROINIT); if (!psz) { break; } lpszReturn = psz; hr = StringCchCopyN(lpszReturn + ichTemp, ichSize - ichTemp, lpsz, cchLen); if (FAILED(hr)) { break; } } if ((i + iIncr) != iStart) { LocalFree((HLOCAL)lpszReturn); lpszReturn = NULL; } return lpszReturn; } // from dpa to: // 'file 1', 'file 2' and 'file 3' LPTSTR DPA_ToQuotedFileList(HDPA hdpa, int iStart, int iEnd, int iIncr) { LPTSTR lpsz; LPTSTR lpszReturn; TCHAR szFile[MAX_PATH]; int ichSize; int ichTemp; int i; SHELLSTATE ss; // undo copy by deleting destinations lpszReturn = (LPTSTR)(void*)LocalAlloc(LPTR, 1); if (!lpszReturn) { return NULL; } SHGetSetSettings(&ss, SSF_SHOWEXTENSIONS|SSF_SHOWALLOBJECTS, FALSE); ichSize = 1; // build the quoted file list for (i = iStart; i < iEnd ; i += iIncr) { LPTSTR psz; HRESULT hr; ichTemp = ichSize - 1; // get the name (filename only without extension) lpsz = DPA_GetPtr(hdpa, i); hr = StringCchCopy(szFile, ARRAYSIZE(szFile), PathFindFileName(lpsz)); if (FAILED(hr)) { LocalFree(lpszReturn); lpszReturn = NULL; break; } if (!ss.fShowExtensions) { PathRemoveExtension(szFile); } // grow the buffer and add it in ichSize += lstrlen(szFile) + 2; psz = (LPTSTR)LocalReAlloc((HLOCAL)lpszReturn, ichSize * sizeof(TCHAR), LMEM_MOVEABLE|LMEM_ZEROINIT); if (!psz) { LocalFree(lpszReturn); lpszReturn = NULL; break; } lpszReturn = psz; // is it too long? if (ichSize >= MAX_PATH) { StringCchCat(lpszReturn, ichSize, c_szEllipses); return lpszReturn; } else { StringCchCat(lpszReturn, ichSize, TEXT("'")); //A single quote BEFORE the filename. - should fit because of realloc above StringCchCat(lpszReturn, ichSize, szFile); // should fit because of realloc above StringCchCat(lpszReturn, ichSize, TEXT("'")); //A single quote AFTER the filename. - should fit because of realloc above } ASSERT(ichSize == ichTemp + (lstrlen(lpszReturn + ichTemp) + 1)); ichTemp = ichSize - 1; // check to see if we need the "and" if ((i + iIncr) < iEnd) { TCHAR szTemp[40]; int id; ichSize += 40; if ((i + (iIncr*2)) >= iEnd) { id = IDS_SPACEANDSPACE; } else { id = IDS_COMMASPACE; } psz = (LPTSTR)LocalReAlloc((HLOCAL)lpszReturn, ichSize * sizeof(TCHAR), LMEM_MOVEABLE|LMEM_ZEROINIT); if (!psz) { LocalFree(lpszReturn); lpszReturn = NULL; break; } lpszReturn = psz; LoadString(HINST_THISDLL, id, szTemp, ARRAYSIZE(szTemp)); StringCchCat(lpszReturn, ichSize, szTemp); // should fit because of realloc above ichSize = ichTemp + (lstrlen(lpszReturn + ichTemp) + 1); } } return lpszReturn; } void CALLBACK FOUndo_GetText(UNDOATOM *lpua, TCHAR * buffer, UINT cchBuffer, int type) { LPFOUNDODATA lpud = (LPFOUNDODATA)lpua->lpData; HDPA hdpa = lpud->hdpa; if (type == UNDO_MENUTEXT) { LoadString(HINST_THISDLL, lpua->uType, buffer, MAX_PATH); } else { TCHAR szTemplate[80]; // thank god for growable stacks.. TCHAR szFile1[MAX_PATH]; TCHAR szFile2[MAX_PATH]; TCHAR szFile1Short[30]; TCHAR szFile2Short[30]; TCHAR *lpszFile1; TCHAR *lpszFile2; // get the template LoadString(HINST_THISDLL, lpua->uType + (IDS_UNDO_FILEOPHELP - IDS_UNDO_FILEOP), szTemplate, ARRAYSIZE(szTemplate)); if (lpua->uType == IDS_RENAME) { SHELLSTATE ss; LPTSTR pszTemp; HRESULT hr; // fill in the file names lpszFile1 = DPA_GetPtr(hdpa, 0); lpszFile2 = DPA_GetPtr(hdpa, 1); hr = StringCchCopy(szFile1, ARRAYSIZE(szFile1), PathFindFileName(lpszFile1)); if (FAILED(hr)) { szFile1[0] = TEXT('\0'); } hr = StringCchCopy(szFile2, ARRAYSIZE(szFile2), PathFindFileName(lpszFile2)); if (FAILED(hr)) { szFile2[0] = TEXT('\0'); } SHGetSetSettings(&ss, SSF_SHOWEXTENSIONS, FALSE); if (!ss.fShowExtensions) { PathRemoveExtension(szFile1); PathRemoveExtension(szFile2); } // length sanity check // don't just whack "..." at 30 bytes into szFile1 since that may be a dbcs character... PathCompactPathEx(szFile1Short, szFile1, ARRAYSIZE(szFile1Short), 0); PathCompactPathEx(szFile2Short, szFile2, ARRAYSIZE(szFile2Short), 0); pszTemp = ShellConstructMessageString(HINST_THISDLL, szTemplate, szFile1Short, szFile2Short); if (pszTemp) { hr = StringCchCopy(buffer, cchBuffer, pszTemp); // ok to truncate, just a message LocalFree(pszTemp); } } else { TCHAR *lpszFile1; HDPA hdpaFull = hdpa; // in the case of delete (where ther's an hdsa) // we need to add in the names of folders deleted // we do this by cloning the hdpa and tacking on our names. if (lpud->hdsa) { hdpaFull = DPA_Clone(hdpa, NULL); if (hdpaFull) { int iMax; int i; LPFOUNDO_DELETEDFILEINFO lpdfi; iMax = DSA_GetItemCount(lpud->hdsa); for (i = 0; i < iMax; i++) { lpdfi = DSA_GetItemPtr(lpud->hdsa, i); DPA_AppendPtr(hdpaFull, lpdfi->lpszName); DPA_AppendPtr(hdpaFull, lpdfi->lpszName); } } else { hdpaFull = hdpa; } } lpszFile1 = DPA_ToQuotedFileList(hdpaFull, 0, DPA_GetPtrCount(hdpaFull), 2); StringCchPrintf(buffer, cchBuffer, szTemplate, lpszFile1); // ok to truncate, just a message LocalFree((HLOCAL)lpszFile1); if (hdpaFull != hdpa) { DPA_Destroy(hdpaFull); } } } } void CALLBACK FOUndo_Release(UNDOATOM *lpua) { LPFOUNDODATA lpud = (LPFOUNDODATA)lpua->lpData; int i; LPTSTR lpsz; if (lpud && (lpud != (void *)-1)) { HDPA hdpa = lpud->hdpa; HDSA hdsa = lpud->hdsa; if (hdpa) { i = DPA_GetPtrCount(hdpa) - 1; for (; i >= 0; i--) { lpsz = DPA_FastGetPtr(hdpa, i); Str_SetPtr(&lpsz, NULL); } DPA_Destroy(hdpa); } if (hdsa) { LPFOUNDO_DELETEDFILEINFO lpdfi; i = DSA_GetItemCount(hdsa) - 1; for (; i >= 0 ; i--) { lpdfi = DSA_GetItemPtr(hdsa, i); Str_SetPtr(&lpdfi->lpszName, NULL); } DSA_Destroy(hdsa); } LocalFree(lpud); lpua->lpData = (void *)-1; } } DWORD WINAPI FOUndo_InvokeThreadInit(UNDOATOM *lpua) { LPFOUNDODATA lpud = (LPFOUNDODATA)lpua->lpData; HDPA hdpa = lpud->hdpa; HWND hwnd = lpua->hwnd; BOOL fNukeAtom = TRUE; SHFILEOPSTRUCT sFileOp = { hwnd, 0, NULL, NULL, 0, } ; int iMax; SuspendUndo(TRUE); iMax = DPA_GetPtrCount(hdpa); switch (lpua->uType) { case IDS_RENAME: { TCHAR szFromPath[MAX_PATH + 1]; if (iMax < 2) goto Exit; sFileOp.wFunc = FO_RENAME; sFileOp.pFrom = DPA_GetPtr(hdpa, 1); sFileOp.pTo = DPA_GetPtr(hdpa, 0); if (sFileOp.pFrom && sFileOp.pTo) { HRESULT hr = StringCchCopy(szFromPath, ARRAYSIZE(szFromPath), sFileOp.pFrom); if (SUCCEEDED(hr)) { szFromPath[lstrlen(sFileOp.pFrom) + 1] = 0; sFileOp.pFrom = szFromPath; SHFileOperation(&sFileOp); if (sFileOp.fAnyOperationsAborted) { fNukeAtom = FALSE; } } } // In the rename case the DPA owns these pointers, in all other cases // they must be freed below. To prevent freeing these during a rename // we NULL them out when we're done with them sFileOp.pFrom = NULL; sFileOp.pTo = NULL; } break; case IDS_COPY: sFileOp.pFrom = DPA_ToFileList(hdpa, 1, iMax - 1, 2); if (!sFileOp.pFrom) goto Exit; sFileOp.wFunc = FO_DELETE; // // If this delete is occuring because of an automatic undo caused by // connected files, then do not ask for confirmation. // if (lpua->foFlags & FOF_NOCONFIRMATION) sFileOp.fFlags |= FOF_NOCONFIRMATION; SHFileOperation(&sFileOp); if (sFileOp.fAnyOperationsAborted) { fNukeAtom = FALSE; } break; case IDS_MOVE: sFileOp.pFrom = DPA_ToFileList(hdpa, 1, iMax-1, 2); sFileOp.pTo = DPA_ToFileList(hdpa, 0, iMax-2, 2); if (!sFileOp.pFrom || !sFileOp.pTo) goto Exit; sFileOp.wFunc = FO_MOVE; sFileOp.fFlags = FOF_MULTIDESTFILES; if (lpua->foFlags & FOF_NOCOPYSECURITYATTRIBS) { sFileOp.fFlags |= FOF_NOCOPYSECURITYATTRIBS; } SHFileOperation(&sFileOp); if (sFileOp.fAnyOperationsAborted) { fNukeAtom = FALSE; } break; case IDS_DELETE: { // first create any directories if (lpud->hdsa) { HDSA hdsa = lpud->hdsa; int i; // do it in reverse order to get the parentage right for (i = DSA_GetItemCount(hdsa) - 1; i >= 0; i--) { LPFOUNDO_DELETEDFILEINFO lpdfi = DSA_GetItemPtr(hdsa, i); if (lpdfi) { if (Win32CreateDirectory(lpdfi->lpszName, NULL)) { SetFileAttributes(lpdfi->lpszName, lpdfi->dwAttributes & ~FILE_ATTRIBUTE_DIRECTORY); } } } } if (iMax) { sFileOp.pFrom = DPA_ToFileList(hdpa, 1, iMax-1, 2); sFileOp.pTo = DPA_ToFileList(hdpa, 0, iMax-2, 2); if (!sFileOp.pFrom || !sFileOp.pTo) goto Exit; UndoBBFileDelete(sFileOp.pTo, sFileOp.pFrom); } break; } } SHChangeNotify(0, SHCNF_FLUSH | SHCNF_FLUSHNOWAIT, NULL, NULL); Exit: if (sFileOp.pFrom) LocalFree((HLOCAL)sFileOp.pFrom); if (sFileOp.pTo) LocalFree((HLOCAL)sFileOp.pTo); SuspendUndo(FALSE); if (fNukeAtom) NukeUndoAtom(lpua); return 1; } void CALLBACK FOUndo_Invoke(UNDOATOM *lpua) { DWORD idThread; HANDLE hthread = CreateThread(NULL, 0, FOUndo_InvokeThreadInit, lpua, 0, &idThread); if (hthread) CloseHandle(hthread); } UNDOATOM *FOAllocUndoAtom(LPSHFILEOPSTRUCT lpfo) { UNDOATOM *lpua = (UNDOATOM *)LocalAlloc(LPTR, sizeof(*lpua)); if (lpua) { lpua->uType = FOFuncToStringID(lpfo->wFunc); lpua->GetText = FOUndo_GetText; lpua->Invoke = FOUndo_Invoke; lpua->Release = FOUndo_Release; lpua->foFlags = 0; if (lpfo->fFlags & FOF_NOCOPYSECURITYATTRIBS) { lpua->foFlags |= FOF_NOCOPYSECURITYATTRIBS; } } return lpua; } //============================================================================ // // The following function is the mainline function for COPYing, RENAMEing, // DELETEing, and MOVEing single or multiple files. // // in: // hwnd the parent to create the progress dialog from if FOF_CREATEPROGRESSDLG is set. // // // wFunc operation to be performed: // FO_DELETE - Delete files in pFrom (pTo unused) // FO_RENAME - Rename files // FO_MOVE - Move files in pFrom to pTo // FO_COPY - Copy files in pFrom to pTo // // pFrom list of source file specs either qualified or // unqualified. unqualified names will be qualified based on the current // global current directories. examples include // "foo.txt bar.txt *.bak ..\*.old dir_name" // // pTo destination file spec. // // fFlags flags that control the operation // // returns: // 0 indicates success // != 0 is the DE_ (dos error code) of last failed operation // // //=========================================================================== int WINAPI SHFileOperation(LPSHFILEOPSTRUCT lpfo) { int ret; BOOL bRecycledStuff = FALSE; COPY_STATE *pcs; if (!lpfo || !lpfo->pFrom) { // return an error instead of waiting to AV return ERROR_INVALID_PARAMETER; } lpfo->fAnyOperationsAborted = FALSE; lpfo->hNameMappings = NULL; if (lpfo->wFunc < FO_MOVE || lpfo->wFunc > FO_RENAME) // validate { // NOTE: We used to return 0 here (win95gold -> IE401). // // If we run into app compat bugs because they were relying on the old // buggy return value, then add an app hack here. // // this is not a DE_ error, and I don't care! return ERROR_INVALID_PARAMETER; } pcs = (COPY_STATE*)LocalAlloc(LPTR, sizeof(COPY_STATE)); if (!pcs) { return ERROR_NOT_ENOUGH_MEMORY; } pcs->nRef = 1; // // REVIEW: We want to allow copying of a file within a given directory // by having default renaming on collisions within a directory. // if (!(lpfo->fFlags & FOF_NOCONFIRMATION)) { pcs->cd.fConfirm = CONFIRM_DELETE_FILE | CONFIRM_DELETE_FOLDER | CONFIRM_REPLACE_FILE | CONFIRM_REPLACE_FOLDER | CONFIRM_WONT_RECYCLE_FILE | CONFIRM_WONT_RECYCLE_FOLDER | CONFIRM_PATH_TOO_LONG | // CONFIRM_MOVE_FILE | // CONFIRM_MOVE_FOLDER | // CONFIRM_RENAME_FILE | // CONFIRM_RENAME_FOLDER | CONFIRM_SYSTEM_FILE | CONFIRM_READONLY_FILE | CONFIRM_MULTIPLE | CONFIRM_PROGRAM_FILE | CONFIRM_STREAMLOSS | CONFIRM_FAILED_ENCRYPT | CONFIRM_LFNTOFAT | CONFIRM_WONT_RECYCLE_OFFLINE | CONFIRM_LOST_ENCRYPT_FILE | CONFIRM_LOST_ENCRYPT_FOLDER; } if (lpfo->fFlags & FOF_WANTNUKEWARNING) { // We will warn the user that the thing they thought was going to be recycled is // now really going to be nuked. (eg drag-drop folder on recycle bin, but it turns // out that the folder is too big for the bitbucket, so we confirm on the wont-recycle // cases). // // Also, we keep the system file / readonly file / progran file warnings around for good // measure. pcs->cd.fConfirm |= CONFIRM_WONT_RECYCLE_FILE | CONFIRM_WONT_RECYCLE_FOLDER | CONFIRM_PATH_TOO_LONG | CONFIRM_SYSTEM_FILE | CONFIRM_READONLY_FILE | CONFIRM_PROGRAM_FILE | CONFIRM_WONT_RECYCLE_OFFLINE; } pcs->fFlags = lpfo->fFlags; // duplicate some stuff here pcs->lpszProgressTitle = lpfo->lpszProgressTitle; pcs->lpfo = lpfo; // Check to see if we need to operate on the "connected" files and folders too! if (!(pcs->fFlags & FOF_NO_CONNECTED_ELEMENTS)) { DWORD dwFileFolderConnection = 0; DWORD dwSize = sizeof(dwFileFolderConnection); DWORD dwType = REG_DWORD; if (SHGetValue(HKEY_CURRENT_USER, REGSTR_PATH_EXPLORER, REG_VALUE_NO_FILEFOLDER_CONNECTION, &dwType, &dwFileFolderConnection, &dwSize) == ERROR_SUCCESS) { //If the registry says "No connection", then set the flags accordingly. if (dwFileFolderConnection == 1) { pcs->fFlags = pcs->fFlags | FOF_NO_CONNECTED_ELEMENTS; } } } // Always create a progress dialog // Note that it will be created invisible, and will be shown if the // operation takes longer than a second to perform // Note the parent of this window is NULL so it will get the QUERYENDSESSION // message if (!(pcs->fFlags & FOF_SILENT)) { SHCreateThread(FOUIThreadProc, pcs, 0, AddRefPCS); } else { // To be compatible with Win95 semantics... if (!lpfo->hwnd) { pcs->fFlags |= FOF_NOERRORUI; } } if (lpfo->hwnd) { // The caller will be disabled if we ever show the progress window // We need to make sure this is not disabled now because if it is and // another dialog uses this as its parent, USER code will tell this // window it got the focus while it is still disabled, which keeps it // from passing that focus down to its children // EnableWindow(lpfo->hwnd, FALSE); pcs->hwndDlgParent = lpfo->hwnd; } // do this always.. even if this is not an undoable op, we could be // affecting something that is. SuspendUndo(TRUE); if (lpfo->fFlags & FOF_ALLOWUNDO) { pcs->lpua = FOAllocUndoAtom(lpfo); if (lpfo->wFunc == FO_DELETE) { // We check the shell state to see if the user has turned on the // "Don't confirm deleting recycle bin contents" flag. If yes, // then we store this flag and check against it if this case occures. // Review: Not a super common case, why not just check when the // flag is actually needed? SHELLSTATE ss; SHGetSetSettings(&ss, SSF_NOCONFIRMRECYCLE, FALSE); pcs->fNoConfirmRecycle = ss.fNoConfirmRecycle; if (InitBBGlobals()) { // since we are going to be recycling stuff, we add ourselves to the // global list of threads who are recycling SHGlobalCounterIncrement(g_hgcNumDeleters); bRecycledStuff = TRUE; } else { // this shouldnt happen, but if it does we can't send stuff to the Recycle // Bin, instead we remove the undo flag so that everything is really nuked. lpfo->fFlags &= ~FOF_ALLOWUNDO; LocalFree(pcs->lpua); pcs->lpua = NULL; } } } // While doing the file operation, tell PnP not to suspend // the machine. Otherwise, you could be copying a lot of files // over the network and have the laptop suddenly hibernate on you // because PnP thought you were idle. // // Indicate that we only need the system. It's okay if the display // goes into low-power mode, as long as we can keep copying. // SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED); ret = MoveCopyDriver(pcs); SetThreadExecutionState(ES_CONTINUOUS); if (pcs->bAbort) { ASSERT(pcs->lpfo == lpfo); lpfo->fAnyOperationsAborted = TRUE; } if (bRecycledStuff) { SHUpdateRecycleBinIcon(); if (0 == SHGlobalCounterDecrement(g_hgcNumDeleters)) { // We were the last guy who was deleting stuff. Thus, we need to // check to see if any of the bitbuckets info files neeed compacting or purging CheckCompactAndPurge(); } } if (pcs->lpCopyBuffer) { LocalFree((HLOCAL)pcs->lpCopyBuffer); pcs->lpCopyBuffer = NULL; } if (pcs->lpua) { if (pcs->lpua->lpData && (pcs->lpua->lpData != (void *)-1)) { AddUndoAtom(pcs->lpua); } else { FOUndo_Release(pcs->lpua); NukeUndoAtom(pcs->lpua); } } // NTRAID89119 (toddb): This code is totally busted in respect to mounted volumes. // We will send a change notify for the drive on which your volume is mounted // instead of on the volume which actually had a free space change. We need // to update PathGetDriveNumber to handle mounted volumes // notify of freespace changes // rename doesn't change drive usage if (lpfo->wFunc != FO_RENAME) { int idDriveSrc; int idDriveDest = -1; DWORD dwDrives = 0; // bitfield for drives if (lpfo->wFunc == FO_COPY) { // nothing changes on the source idDriveSrc = -1; } else { idDriveSrc = PathGetDriveNumber(lpfo->pFrom); } if (lpfo->pTo) { idDriveDest = PathGetDriveNumber(lpfo->pTo); } if ((lpfo->wFunc == FO_MOVE) && (idDriveDest == idDriveSrc)) { // no freespace nothing changes idDriveSrc = -1; idDriveDest = -1; } // NTRAID89119: What if idDriveSrc or idDriveDest are > 32? This is totally // possible under NT by using mounted volumes. SHChangeNotify is busted // in this respect. if (idDriveSrc != -1) { dwDrives |= (1 << idDriveSrc); } if (idDriveDest != -1) { dwDrives |= (1 << idDriveDest); } if (dwDrives) { SHChangeNotify(SHCNE_FREESPACE, SHCNF_DWORD, IntToPtr(dwDrives), 0); } } SuspendUndo(FALSE); if (!(lpfo->fFlags & FOF_WANTMAPPINGHANDLE)) { SHFreeNameMappings(lpfo->hNameMappings); lpfo->hNameMappings = NULL; } // shut down the progress dialog // // this is necessary so that the ui thread won't block pcs->fProgressOk = TRUE; ENTERCRITICAL; // need to take critsec to sync w/ the UI thread pcs->fDone = TRUE; if (pcs->hwndProgress) { PostMessage(pcs->hwndProgress, PDM_NOOP, 0, 0); } LEAVECRITICAL; if (lpfo->hwnd) { EnableWindow(lpfo->hwnd, TRUE); } ReleasePCS(pcs); return ret; } #ifdef UNICODE int WINAPI SHFileOperationA(LPSHFILEOPSTRUCTA lpfo) { int iResult; UINT uTotalSize; UINT uSize; UINT uSizeTitle; UINT uSizeW; SHFILEOPSTRUCTW shop; LPCSTR lpAnsi; LPWSTR lpBuffer; LPWSTR lpTemp; COMPILETIME_ASSERT(sizeof(SHFILEOPSTRUCTW) == sizeof(SHFILEOPSTRUCTA)); hmemcpy(&shop, lpfo, sizeof(SHFILEOPSTRUCTW)); // // Thunk the strings as appropriate // uTotalSize = 0; if (lpfo->pFrom) { lpAnsi = lpfo->pFrom; do { uSize = lstrlenA(lpAnsi) + 1; uTotalSize += uSize; lpAnsi += uSize; } while (uSize != 1); } if (lpfo->pTo) { lpAnsi = lpfo->pTo; do { uSize = lstrlenA(lpAnsi) + 1; uTotalSize += uSize; lpAnsi += uSize; } while (uSize != 1); } if ((lpfo->fFlags & FOF_SIMPLEPROGRESS) && lpfo->lpszProgressTitle != NULL) { uSizeTitle = lstrlenA(lpfo->lpszProgressTitle) + 1; uTotalSize += uSizeTitle; } if (uTotalSize != 0) { lpTemp = lpBuffer = LocalAlloc(LPTR, uTotalSize*sizeof(WCHAR)); if (!lpBuffer) { SetLastError(ERROR_OUTOFMEMORY); return ERROR_OUTOFMEMORY; } } else { lpBuffer = NULL; } // // Now convert the strings // if (lpfo->pFrom) { shop.pFrom = lpTemp; lpAnsi = lpfo->pFrom; do { uSize = lstrlenA(lpAnsi) + 1; uSizeW = MultiByteToWideChar(CP_ACP, 0, lpAnsi, uSize, lpTemp, uSize); lpAnsi += uSize; lpTemp += uSizeW; } while (uSize != 1); } else { shop.pFrom = NULL; } if (lpfo->pTo) { shop.pTo = lpTemp; lpAnsi = lpfo->pTo; do { uSize = lstrlenA(lpAnsi) + 1; uSizeW = MultiByteToWideChar(CP_ACP, 0, lpAnsi, uSize, lpTemp, uSize); lpAnsi += uSize; lpTemp += uSizeW; } while (uSize != 1); } else { shop.pTo = NULL; } if ((lpfo->fFlags & FOF_SIMPLEPROGRESS) && lpfo->lpszProgressTitle != NULL) { shop.lpszProgressTitle = lpTemp; MultiByteToWideChar(CP_ACP, 0, lpfo->lpszProgressTitle, uSizeTitle, lpTemp, uSizeTitle); } else { shop.lpszProgressTitle = NULL; } iResult = SHFileOperationW(&shop); // link up the two things in the SHFILEOPSTRUCT that could have changed lpfo->fAnyOperationsAborted = shop.fAnyOperationsAborted; lpfo->hNameMappings = shop.hNameMappings; if (lpBuffer) LocalFree(lpBuffer); return iResult; } #else int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpfo) { return E_NOTIMPL; } #endif // In: // pcs: copy_state structure containing the state of the copy // // feedback: If the estimated time to copmplete a copy is larger than // MINTIME4FEEDBACK, the user is given a time to completion estimate in minutes. // The estimate is calculated using a MS_RUNAVG seconds running average. The // initial estimate is done after MS_TIMESLICE void SetProgressTime(COPY_STATE *pcs) { DWORD dwNow = GetTickCount(); if (pcs->dwPreviousTime) { int iPointsTotal = CountProgressPoints(pcs, &pcs->dth.dtAll); int iPointsDone = CountProgressPoints(pcs, &pcs->dth.dtDone); int iPointsDelta = iPointsDone - pcs->iLastProgressPoints; DWORD dwTimeLeft; // // A couple of times the shell has reported bad time remaining // we need to find out why. // ASSERT(iPointsTotal >= 0); ASSERT(iPointsDone >= 0); ASSERT(iPointsTotal >= iPointsDone); ASSERT(iPointsDelta >= 0); // has enough time elapsed to update the display // We do this every 10 seconds, but we'll do the first one after // only a few seconds if (iPointsDelta && (iPointsDone > 0) && (dwNow - pcs->dwPreviousTime)) { DWORD dwPointsPerSec; DWORD dwTime; // how many tenths of a second have gone by // We take 10 times the number of Points and divide by the number of // tenths of a second to minimize both overflow and roundoff dwTime = (dwNow - pcs->dwPreviousTime)/100; if (dwTime == 0) dwTime = 1; dwPointsPerSec = iPointsDelta * 10 / dwTime; if (!dwPointsPerSec) { // This could happen if the net went to sleep for a couple // minutes while trying to copy a small (512 byte) buffer dwPointsPerSec = 1; } // if we didn't have enough time to get a good sample, // don't use this last bit as a time estimater if ((dwNow - pcs->dwPreviousTime) < (MS_TIMESLICE/2)) { dwPointsPerSec = pcs->dwPointsPerSec; } if (pcs->dwPointsPerSec) { // Take a weighted average of the current transfer rate and the // previously computed one, just to try to smooth out // some random fluctuations dwPointsPerSec = (dwPointsPerSec + (pcs->dwPointsPerSec * 2)) / 3; } // never allow 0 points per second.. just tack it on to next time if (dwPointsPerSec) { pcs->dwPointsPerSec = dwPointsPerSec; // Calculate time remaining (round up by adding 1) // We only get here every 10 seconds, so always update dwTimeLeft = ((iPointsTotal - iPointsDone) / dwPointsPerSec) + 1; // It would be odd to show "1 second left" and then immediately // clear it if (dwTimeLeft >= MIN_MINTIME4FEEDBACK) { // display new estimate of time left SetProgressTimeEst(pcs, dwTimeLeft); } } } // Reset previous time and # of Points read pcs->dwPreviousTime = dwNow; pcs->iLastProgressPoints = iPointsDone; } } void InitClipConfirmDlg(HWND hDlg, CONFDLG_DATA *pcd) { TCHAR szMessage[255]; TCHAR szDeleteWarning[80]; SHFILEINFO sfiDest; LPTSTR pszFileDest = NULL; LPTSTR pszMsg, pszSource; int i; int cxWidth; RECT rc; // get the size of the text boxes GetWindowRect(GetDlgItem(hDlg, pcd->idText), &rc); cxWidth = rc.right - rc.left; // get the source display name pszSource = PathFindFileName(pcd->pFileSource); PathCompactPath(NULL, pszSource, cxWidth); // get the dest display name SHGetFileInfo(pcd->pFileDest, 0, &sfiDest, sizeof(sfiDest), SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES); pszFileDest = sfiDest.szDisplayName; PathCompactPath(NULL, pszFileDest, cxWidth); // if we're supposed to show the date info, grab the icons and format the date string if (pcd->bShowDates) { SHFILEINFO sfi2; TCHAR szDateSrc[64], szDateDest[64]; // likely that this data may be incomplete... leave it saying "Unknown date and size" if (BuildDateLine(szDateSrc, ARRAYSIZE(szDateSrc), pcd->pfdSource, pcd->pFileSource)) SetDlgItemText(hDlg, IDD_FILEINFO_NEW, szDateSrc); BuildDateLine(szDateDest, ARRAYSIZE(szDateSrc), pcd->pfdDest, pcd->pFileDest); SetDlgItemText(hDlg, IDD_FILEINFO_OLD, szDateDest); SHGetFileInfo(pcd->pFileSource, pcd->pfdSource ? pcd->pfdSource->dwFileAttributes : 0, &sfi2, sizeof(sfi2), pcd->pfdSource ? (SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_LARGEICON) : (SHGFI_ICON|SHGFI_LARGEICON)); ReplaceDlgIcon(hDlg, IDD_ICON_NEW, sfi2.hIcon); SHGetFileInfo(pcd->pFileDest, pcd->pfdDest ? pcd->pfdDest->dwFileAttributes : 0, &sfi2, sizeof(sfi2), pcd->pfdDest ? (SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_LARGEICON) : (SHGFI_ICON|SHGFI_LARGEICON)); ReplaceDlgIcon(hDlg, IDD_ICON_OLD, sfi2.hIcon); } // there are 5 controls: // IDD_TEXT contains regular text (normal file/folder) // IDD_TEXT1 through IDD_TEXT4 contain optional secondary text for (i = IDD_TEXT; i <= IDD_TEXT4; i++) { if (i == pcd->idText) { szMessage[0] = 0; GetDlgItemText(hDlg, i, szMessage, ARRAYSIZE(szMessage)); } else { HWND hwndCtl = GetDlgItem(hDlg, i); if (hwndCtl) { ShowWindow(hwndCtl, SW_HIDE); } } } szDeleteWarning[0] = 0; pszMsg = ShellConstructMessageString(HINST_THISDLL, szMessage, pszSource, pszFileDest, szDeleteWarning); if (pszMsg) { SetDlgItemText(hDlg, pcd->idText, pszMsg); LocalFree(pszMsg); } } HRESULT FileDescToWin32FileData(LPFILEDESCRIPTOR pfdsc, LPWIN32_FIND_DATA pwfd) { ZeroMemory(pwfd, sizeof(*pwfd)); if (pfdsc->dwFlags & FD_ATTRIBUTES) pwfd->dwFileAttributes = pfdsc->dwFileAttributes; if (pfdsc->dwFlags & FD_CREATETIME) hmemcpy(&pwfd->ftCreationTime, &pfdsc->ftCreationTime, sizeof(FILETIME)); if (pfdsc->dwFlags & FD_ACCESSTIME) hmemcpy(&pwfd->ftLastAccessTime, &pfdsc->ftLastAccessTime, sizeof(FILETIME)); if (pfdsc->dwFlags & FD_WRITESTIME) hmemcpy(&pwfd->ftLastWriteTime, &pfdsc->ftLastWriteTime, sizeof(FILETIME)); if (pfdsc->dwFlags & FD_FILESIZE) { pwfd->nFileSizeHigh = pfdsc->nFileSizeHigh; pwfd->nFileSizeLow = pfdsc->nFileSizeLow; } return StringCchCopy(pwfd->cFileName, ARRAYSIZE(pwfd->cFileName), pfdsc->cFileName); } INT_PTR ValidateCreateFileFromClip(HWND hwnd, LPFILEDESCRIPTOR pfdscSrc, TCHAR *pszPathDest, PYNLIST pynl) { WIN32_FIND_DATA wfdSrc, wfdDest; CONFDLG_DATA cdd; CONFIRM_DATA cd; COPY_STATE cs; INT_PTR result; HRESULT hr; // // If the destination does not exist, we are done. // HANDLE hff = FindFirstFile(pszPathDest, &wfdDest); if (hff == INVALID_HANDLE_VALUE) { return IDYES; } FindClose(hff); // // Maybe this was just a short name collision and // we can quickly get out of here. // if (ResolveShortNameCollisions(pszPathDest, &wfdDest)) { return IDYES; } // // Most of the helper functions want a WIN32_FILE_DATA // and not a FILEDESCRIPTOR, so we create wfd for the // source file on the fly. // hr = FileDescToWin32FileData(pfdscSrc, &wfdSrc); if (FAILED(hr)) { return IDNO; } // // Take care of the easy cases - can't copy a file to a dir // or a dir to a file. // if ((wfdDest.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && ((wfdSrc.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)) { ZeroMemory(&cs, sizeof(cs)); cs.hwndDlgParent = hwnd; CopyError(&cs, wfdSrc.cFileName, pszPathDest, DE_FILEDESTISFLD | ERRORONDEST, FO_COPY, OPER_DOFILE); return IDNO; } else if (((wfdDest.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) && (wfdSrc.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { ZeroMemory(&cs, sizeof(cs)); cs.hwndDlgParent = hwnd; CopyError(&cs, wfdSrc.cFileName, pszPathDest, DE_FLDDESTISFILE | ERRORONDEST, FO_COPY, OPER_DOFILE); AddToNoList(pynl, pszPathDest); return IDNO; } // // We need a confirmation dialog. Fill in the // ConfirmDialogData (cdd) here. // ZeroMemory(&cdd, sizeof(cdd)); cdd.InitConfirmDlg = InitClipConfirmDlg; cdd.idText = IDD_TEXT; cdd.pFileSource = pfdscSrc->cFileName; cdd.pfdSource = &wfdSrc; cdd.pFileDest = pszPathDest; cdd.pfdDest = &wfdDest; cdd.bShowDates = FALSE; cdd.pcd = &cd; ZeroMemory(&cd, sizeof(cd)); cd.fConfirm = CONFIRM_REPLACE_FILE; cdd.fYesToAllMask = CONFIRM_REPLACE_FILE; if (((wfdDest.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) && (wfdDest.dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY))) { if (wfdDest.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) { cdd.idText = IDD_TEXT2; } else if (wfdDest.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { cdd.idText = IDD_TEXT1; } } // // What we do now depends on whether we are processing a directory // or a file. // if (wfdDest.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { // // If this directory is already in the yes list, // the parent directory must have already conflicted // and the user said "yes, move the dir contents over". // if (IsInYesList(pynl, pszPathDest)) { result = IDYES; } else { // // Copying directory to a destination with the same directory. // result = DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_REPLACE_FOLDER), hwnd, ConfirmDlgProc, (LPARAM)&cdd); if (result == IDYES) { if (cd.fConfirm & CONFIRM_REPLACE_FILE) { AddToYesList(pynl, pszPathDest); } else { SetYesToAll(pynl); } } else if (result == IDNO) { AddToNoList(pynl, pszPathDest); } } } else { if (IsInYesList(pynl, pszPathDest)) { result = IDYES; } else { // // Copying a file to a destination with the same file. // cdd.bShowDates = TRUE; result = DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_REPLACE_FILE), hwnd, ConfirmDlgProc, (LPARAM)&cdd); if (result == IDYES) { if ((cd.fConfirm & CONFIRM_REPLACE_FILE) == 0) { SetYesToAll(pynl); } } } } return result; } // We can get transient file locks for moving files, for example extracting a thumbnail on a background task // so we wrap our single check in a loop of multiple checks with a short nap between. We'd expect // to get the ERROR_SHARING_VIOLATION, but in practice we also say ERROR_ACCESS_DENIED, so we'll try that // as well #define MAX_DELETE_ATTEMPTS 5 #define SLEEP_DELETE_ATTEMPT 1000 BOOL _IsFileDeletable(LPCTSTR pszFile) { int iAttempt = 0; BOOL bRet; while (!(bRet = IsFileDeletable(pszFile)) && (iAttempt < MAX_DELETE_ATTEMPTS)) { DWORD dwError = GetLastError(); if ((dwError == ERROR_ACCESS_DENIED) || (dwError == ERROR_SHARING_VIOLATION)) { iAttempt++; Sleep(SLEEP_DELETE_ATTEMPT); } else { break; } } return (bRet); } BOOL _IsDirectoryDeletable(LPCTSTR pszDir) { int iAttempt = 0; BOOL bRet; while (!(bRet = IsDirectoryDeletable(pszDir)) && (iAttempt < MAX_DELETE_ATTEMPTS)) { DWORD dwError = GetLastError(); if ((dwError == ERROR_ACCESS_DENIED) || (dwError == ERROR_SHARING_VIOLATION)) { iAttempt++; Sleep(SLEEP_DELETE_ATTEMPT); } else { break; } } return (bRet); } // This function adds up the sizes of the files in pszDir, and // also makes sure that all those files are "delete-able" // // return: ERROR_SUCCESS - everything is fine, all the files in the dir are deleteable // else - the dir cant be deleted because something inside is non-deletable // // NOTE: other in-out params are in the pfdi // LONG CheckFolderSizeAndDeleteability(FOLDERDELETEINFO* pfdi, LPCOPY_STATE pcs) { LONG lRet = ERROR_SUCCESS; // keep stack to a minimum as this is a recursive function! BOOL bHasChildren = FALSE; if (FOQueryAbort(pcs)) return ERROR_CANCELLED; // do the root specific processing if (!pfdi->bProcessedRoot) { // since the the destination folder could be something like "DC100000.oldext", calculate how many characters are // going to be in the new destination directory: "C:\recycler\sid" + "\" + "DC100000.oldext" == the new root directory length pfdi->cchDelta = (pfdi->cchBBDir + 1 + 8 + 1 + lstrlen(PathFindExtension(pfdi->szDir))) - lstrlen(pfdi->szDir); // set this so that we only do the above processing for the root folder pfdi->bProcessedRoot = TRUE; } if (PathCombine(pfdi->szPath, pfdi->szDir, c_szStarDotStar)) { HANDLE hfind = FindFirstFile(pfdi->szPath, &pfdi->fd); if (hfind != INVALID_HANDLE_VALUE) { do { if (!PathIsDotOrDotDot(pfdi->fd.cFileName)) { bHasChildren = TRUE; // append the subfile/subfolder to the parent path if (!PathCombine(pfdi->szPath, pfdi->szDir, pfdi->fd.cFileName)) { // PathAppend failed, try to append the short name if (!pfdi->fd.cAlternateFileName[0] || !PathCombine(pfdi->szPath, pfdi->szDir, pfdi->fd.cAlternateFileName)) { // no alternate name or we failed to append that as well, assume we failed because the path is too long lRet = ERROR_FILENAME_EXCED_RANGE; // pass back the name of the non-deleteable file/folder in pfdi->szNonDeletableFile StringCchCopy(pfdi->szNonDeletableFile, ARRAYSIZE(pfdi->szNonDeletableFile), pfdi->szPath); // truncation ok } } if (lRet == ERROR_SUCCESS) { // we have to check to see if the path will exceed MAX_PATH if we were to move this file to the recycle // bin (C:\Recycler\). the increase in path length due to the recycle bin dir could be enough to // put us over MAX_PATH and we would have problems later. if ((lstrlen(pfdi->szPath) + pfdi->cchDelta + 1) > MAX_PATH) // +1 for NULL { TraceMsg(TF_BITBUCKET, "CheckFolderSizeAndDeleteability: path '%s' would exceed MAX_PATH if moved to the recycle bin!", pfdi->szPath); lRet = ERROR_FILENAME_EXCED_RANGE; // pass back the name of the non-deleteable file/folder in pfdi->szNonDeletableFile StringCchCopy(pfdi->szNonDeletableFile, ARRAYSIZE(pfdi->szNonDeletableFile), pfdi->szPath); // truncation ok } else { if (pfdi->fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { HRESULT hr = StringCchCopy(pfdi->szDir, ARRAYSIZE(pfdi->szDir), pfdi->szPath); if (SUCCEEDED(hr)) { // its a directory, so recurse lRet = CheckFolderSizeAndDeleteability(pfdi, pcs); PathRemoveFileSpec(pfdi->szDir); } else { lRet = ERROR_FILENAME_EXCED_RANGE; StringCchCopy(pfdi->szNonDeletableFile, ARRAYSIZE(pfdi->szNonDeletableFile), pfdi->szPath); // truncation ok } } else { // its a file. ULARGE_INTEGER ulTemp; if (!_IsFileDeletable(pfdi->szPath)) { // we cant delete this file, find out why lRet = GetLastError(); ASSERT(lRet != ERROR_SUCCESS); // pass back the name of the non-deleteable file in pfdi->szNonDeletableFile StringCchCopy(pfdi->szNonDeletableFile, ARRAYSIZE(pfdi->szNonDeletableFile), pfdi->szPath); // truncation ok } ulTemp.LowPart = pfdi->fd.nFileSizeLow; ulTemp.HighPart = pfdi->fd.nFileSizeHigh; pfdi->cbSize += ulTemp.QuadPart; } } } } } while ((lRet == ERROR_SUCCESS) && FindNextFile(hfind, &pfdi->fd)); FindClose(hfind); // if this dir has no children, see if we can simply delete it if (!bHasChildren && !_IsDirectoryDeletable(pfdi->szDir)) { lRet = GetLastError(); ASSERT(lRet != ERROR_SUCCESS); // pass back the name of the non-deleteable file in pfdi->szNonDeletableFile StringCchCopy(pfdi->szNonDeletableFile, ARRAYSIZE(pfdi->szNonDeletableFile), pfdi->szDir); // truncation ok } } else { // if FindFirstFile fails, check to see if the directory itself is deleteable if (!_IsDirectoryDeletable(pfdi->szDir)) { lRet = GetLastError(); ASSERT(lRet != ERROR_SUCCESS); // pass back the name of the non-deleteable file in pfdi->szNonDeletableFile StringCchCopy(pfdi->szNonDeletableFile, ARRAYSIZE(pfdi->szNonDeletableFile), pfdi->szDir); // truncation ok } } } else { // if PathCombine fails, assume its because the path is too long lRet = ERROR_FILENAME_EXCED_RANGE; // pass back the name of the non-deleteable file in pfdi->szNonDeletableFile StringCchCopy(pfdi->szNonDeletableFile, ARRAYSIZE(pfdi->szNonDeletableFile), pfdi->szDir); // truncation ok } return lRet; } // This takes over for what BBDeleteFile used to do (init, check, delete)... but does it with the ability to cancel BOOL DeleteFileBB(LPTSTR pszFile, UINT cchFile, INT *piRet, COPY_STATE *pcs, BOOL fIsDir, WIN32_FIND_DATA *pfd, HDPA *phdpaDeletedFiles) { ULARGE_INTEGER ulSize; int idDrive = DriveIDFromBBPath(pszFile); // Init if (!BBDeleteFileInit(pszFile, piRet)) return FALSE; // Check if we can delete this properly if (fIsDir) { DWORD dwError; HRESULT hr; FOLDERDELETEINFO fdi = {0}; fdi.cchBBDir = BBRecyclePathLength(idDrive); hr = StringCchCopy(fdi.szDir, ARRAYSIZE(fdi.szDir), pszFile); if (FAILED(hr)) { *piRet = BBDELETE_PATH_TOO_LONG; return FALSE; } dwError = CheckFolderSizeAndDeleteability(&fdi, pcs); if (dwError != ERROR_SUCCESS) { // if CheckFolderSizeAndDeleteability can fail if a file cant be recycled. // In this case, it appends the name of the file to pszFile, so we know who // the undeletable file is. if ((dwError == ERROR_FILENAME_EXCED_RANGE) || (dwError == ERROR_BUFFER_OVERFLOW)) { // it failed because a new path would be to long after being moveed under the "C:\recycler\sid" directory *piRet = BBDELETE_PATH_TOO_LONG; } else if (dwError == ERROR_CANCELLED) { // user hit the cancel button *piRet = BBDELETE_CANCELLED; } else { // must be a non-deletable directory, so set piRet = BBDELETE_CANNOT_DELETE so our caller // can detect this case, also pass the name of the non-deletable file back out so we can give // a better error message to the user *piRet = BBDELETE_CANNOT_DELETE; ASSERT(*fdi.szPath); StringCchCopy(pszFile, cchFile, fdi.szNonDeletableFile); // truncation ok } TraceMsg(TF_BITBUCKET, "DeleteFileBB : early error (%x) on file (%s)", dwError, pszFile); return FALSE; } ulSize.QuadPart = fdi.cbSize; } else { if (!_IsFileDeletable(pszFile)) { // We set piRet = BBDELETE_CANNOT_DELETE so our caller can detect // that this file cant be recycled. *piRet = BBDELETE_CANNOT_DELETE; return FALSE; } ulSize.LowPart = pfd->nFileSizeLow; ulSize.HighPart = pfd->nFileSizeHigh; } // check to make sure it's not bigger than the allowed wastebasket.. if (!BBCheckDeleteFileSize(idDrive, ulSize)) { // we set piRet = BBDELETE_SIZE_TOO_BIG so our caller can // detect the "file/folder too big" case *piRet = BBDELETE_SIZE_TOO_BIG; return FALSE; } return BBDeleteFile(pszFile, piRet, pcs->lpua, fIsDir, phdpaDeletedFiles, ulSize); } void StartCopyEngine(HANDLE *phEventRunning) { SECURITY_ATTRIBUTES* psa = SHGetAllAccessSA(); if (psa) { *phEventRunning = CreateEvent(psa, TRUE, FALSE, L"ShellCopyEngineRunning"); if (*phEventRunning) { SetEvent(*phEventRunning); } } } void EndCopyEngine(HANDLE hEventRunning) { // signal that we're done. this will always trigger so there will be some weirdness if the // user does simultaneous copies, but it's not worth making a semaphore to keep track. SECURITY_ATTRIBUTES* psa = SHGetAllAccessSA(); if (psa) { HANDLE hEventFinished = CreateEvent(psa, TRUE, FALSE, L"ShellCopyEngineFinished"); if (hEventFinished) { SetEvent(hEventFinished); CloseHandle(hEventFinished); } } if (hEventRunning) { // close out the event that says we're running. ResetEvent(hEventRunning); CloseHandle(hEventRunning); } } BOOL IsCopyEngineRunning() { BOOL bRet=FALSE; SECURITY_ATTRIBUTES* psa = SHGetAllAccessSA(); if (psa) { HANDLE hEventCopyRunning = OpenEvent(SYNCHRONIZE, FALSE, L"ShellCopyEngineRunning"); if (hEventCopyRunning) { // probe the event with a wait, if it times out the copy engine isn't running so we're done. bRet = (WAIT_OBJECT_0 == WaitForSingleObject(hEventCopyRunning, 0)); CloseHandle(hEventCopyRunning); } } return bRet; }