|
|
// Copyright (c) <1995-1999> Microsoft Corporation
#include "shellprv.h"
#pragma hdrstop
#include <msi.h>
#include <msip.h>
#include <aclapi.h> // 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\<sid>")
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\<sid>). 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; }
|