8443 lines
281 KiB
C
8443 lines
281 KiB
C
// 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 fLostEncryptOk;
|
|
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 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, 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(LPCTSTR pszDir, FOLDERDELETEINFO* pfdi, LPCOPY_STATE pcs);
|
|
BOOL DeleteFileBB(LPTSTR pszFile, 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];
|
|
USES_CONVERSION;
|
|
|
|
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.
|
|
|
|
lstrcpyn(szFolder, pszFile, ARRAYSIZE(szFolder));
|
|
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, T2W(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;
|
|
}
|
|
|
|
// Woo-hoo, everybody is happy. Save the results into our cache.
|
|
|
|
ATOMICRELEASE(pspc->psf);
|
|
pspc->psf = psf;
|
|
|
|
ILFree(pspc->pidlFolder);
|
|
pspc->pidlFolder = pidlFolder;
|
|
|
|
lstrcpyn(pspc->szFolder, szFolder, ARRAYSIZE(pspc->szFolder));
|
|
|
|
}
|
|
|
|
// 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,
|
|
T2W(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;
|
|
|
|
if (fDirectory)
|
|
{
|
|
// Look for a suffix which is one of the standard suffixes.
|
|
if (!(pszDest = (LPTSTR)PathFindSuffixArray(pszFileName, c_apszSuffixes, ARRAYSIZE(c_apszSuffixes))))
|
|
return FALSE;
|
|
|
|
// " 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 FALSE;
|
|
|
|
if (lstrcmpi(pszDest, TEXT(".htm")) && (lstrcmpi(pszDest, TEXT(".html"))))
|
|
return FALSE;
|
|
|
|
// Extension ".htm" or ".html" is found. Replace it with "*"
|
|
pszConnectedElemSuffix = (LPTSTR)c_szStar;
|
|
}
|
|
|
|
iPrefixLength = (int)(pszDest - pszFileName);
|
|
|
|
//Check if the input buffer is big enough to over-write the suffix in-place
|
|
if ((((int)dwBuffSize - iPrefixLength) - 1) < lstrlen(pszConnectedElemSuffix))
|
|
return 0;
|
|
|
|
//Replace the source suffix with the connected element's suffix.
|
|
lstrcpy(pszDest, pszConnectedElemSuffix);
|
|
|
|
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)
|
|
{
|
|
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
|
|
lstrcpy(pdtn->szShortName, pfd->cAlternateFileName);
|
|
lstrcpy(pdtn->szName, pfd->cFileName);
|
|
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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"
|
|
|
|
//Make a copy of the filename; This copy will get munged by ConvertToConnectedItemName().
|
|
lstrcpy(szFileName, pfd->cFileName);
|
|
// Convert the given file/foder name into the connected item's name with wild card characters.
|
|
if (!(iPrefixLength = ConvertToConnectedItemName(szFileName, ARRAYSIZE(szFileName), ISDIRFINDDATA(*pfd))))
|
|
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!
|
|
lstrcpy(szFullPath, pszPath);
|
|
PathAppend(szFullPath, szFileName);
|
|
|
|
// 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;
|
|
|
|
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;
|
|
}
|
|
|
|
lstrcpy(szPath, pdth->pFrom);
|
|
|
|
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
|
|
if (PathAppend(pdth->szSrcPath, c_szStarDotStar))
|
|
{
|
|
iError = DTPathToDTNode(pdth, pcs, pdth->szSrcPath, 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);
|
|
}
|
|
|
|
void DTGetWin32FindData(PDIRTREENODE pdtn, WIN32_FIND_DATA* pfd)
|
|
{
|
|
// only the stuff we care about
|
|
lstrcpy(pfd->cAlternateFileName, pdtn->szShortName);
|
|
lstrcpy(pfd->cFileName, pdtn->szName);
|
|
|
|
pfd->dwFileAttributes = pdtn->dwFileAttributes;
|
|
pfd->ftCreationTime = pdtn->ftCreationTime;
|
|
pfd->ftLastWriteTime = pdtn->ftLastWriteTime;
|
|
pfd->nFileSizeLow = pdtn->nFileSizeLow;
|
|
pfd->nFileSizeHigh = pdtn->nFileSizeHigh;
|
|
}
|
|
|
|
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)
|
|
{
|
|
ASSERT(pdth->pdtnCurrent); // If we have no current node then how can we Initialize its paths?
|
|
|
|
lstrcpyn(pdth->szSrcPath, pdth->pFrom, ARRAYSIZE(pdth->szSrcPath));
|
|
|
|
// 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(pdth->szSrcPath);
|
|
if (!PathAppend(pdth->szSrcPath, pdth->pdtnCurrent->szName))
|
|
return FALSE;
|
|
}
|
|
|
|
if (!pdth->pTo)
|
|
{
|
|
// no dest, make it the same as the source and we're done
|
|
lstrcpyn(pdth->szDestPath, pdth->szSrcPath, ARRAYSIZE(pdth->szSrcPath));
|
|
return TRUE;
|
|
}
|
|
|
|
if (pdth->pTo)
|
|
{
|
|
lstrcpyn(pdth->szDestPath, pdth->pTo, ARRAYSIZE(pdth->szSrcPath));
|
|
}
|
|
|
|
if (!pdth->fMultiDest)
|
|
{
|
|
if (!PathAppend(pdth->szDestPath, 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(pdth->szDestPath);
|
|
if (!PathAppend(pdth->szDestPath, pdth->pdtnCurrent->szName))
|
|
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, 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];
|
|
|
|
lstrcpyn(szOldDest, pdth->szDestPath, ARRAYSIZE(szOldDest));
|
|
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];
|
|
|
|
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;
|
|
}
|
|
}
|
|
StrCpyN(pdth->szSrcPath, szTemp, ARRAYSIZE(pdth->szSrcPath));
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
StrCpyN(pdth->szDestPath, szTemp, ARRAYSIZE(pdth->szDestPath));
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
StrCpyN(pdth->szSrcPath, szTemp, ARRAYSIZE(pdth->szSrcPath));
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
StrCpyN(pdth->szDestPath, szTemp, ARRAYSIZE(pdth->szDestPath));
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
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, 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));
|
|
wsprintf(pszDateLine, szTemplate, StrFormatByteSize64(liFileSize.QuadPart, szNum, ARRAYSIZE(szNum)),
|
|
szTmp);
|
|
}
|
|
else
|
|
{
|
|
// Simpy output the number to the string
|
|
StrFormatByteSize64(liFileSize.QuadPart, pszDateLine, 64);
|
|
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, pcd->pfdSource, pcd->pFileSource);
|
|
SetDlgItemText(hDlg, IDD_FILEINFO_NEW, szDateSrc);
|
|
|
|
BuildDateLine(szDateDest, 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);
|
|
|
|
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);
|
|
|
|
lstrcpy(szTo, pgn->pszDialogTo);
|
|
PathAppend(szTo, szT);
|
|
// make sure that name is unique
|
|
PathYetAnotherMakeUniqueName(szTo, szTo, NULL, NULL);
|
|
SetDlgItemText(hDlg, IDD_TO, PathFindFileName(szTo));
|
|
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));
|
|
PathAppend(pgn->pszDialogTo, szT);
|
|
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];
|
|
GuessAShortName(PathFindFileName(pFrom), szTemp);
|
|
PathRemoveFileSpec(pTo);
|
|
PathAppend(pTo, szTemp);
|
|
// make sure that name is unique
|
|
PathYetAnotherMakeUniqueName(pTo, pTo, NULL, NULL);
|
|
iRet = IDYES;
|
|
}
|
|
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, 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]);
|
|
|
|
// Generate the new target path.
|
|
PathCombine(pszTarget, prp->pszNewPath, pszSubTree);
|
|
|
|
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;
|
|
|
|
//
|
|
// 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)
|
|
{
|
|
lstrcpy(szFrom, pszTo);
|
|
}
|
|
else
|
|
{
|
|
lstrcpy(szFrom, pszFrom);
|
|
}
|
|
PathStripPath(szFrom);
|
|
PathCompactPath(hdc, szFrom, cxWidth);
|
|
SetDlgItemText(hwndProgress, IDD_NAME, szFrom);
|
|
|
|
lstrcpy(szFrom, pszFrom);
|
|
if (szFrom[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)
|
|
{
|
|
lstrcpy(szTo, pszTo);
|
|
PathRemoveFileSpec(szTo);
|
|
PathStripPath(szTo);
|
|
|
|
PathCompactPath(hdc, szFrom, cxWidth/2);
|
|
PathCompactPath(hdc, szTo, cxWidth/2);
|
|
}
|
|
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;
|
|
}
|
|
|
|
wsprintf(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];
|
|
|
|
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;
|
|
}
|
|
|
|
lstrcpyn(szTemp, pszPath, ARRAYSIZE(szTemp));
|
|
pEnd = PathAddBackslash(szTemp); // for the loop below
|
|
|
|
// 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
|
|
|
|
|
|
// 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];
|
|
|
|
lstrcpyn(szPath, pszPath, ARRAYSIZE(szPath));
|
|
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;
|
|
|
|
// 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 (pcs->fLostEncryptOk)
|
|
{
|
|
dwCopyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION;
|
|
}
|
|
|
|
if (FO_MOVE == pcs->lpfo->wFunc)
|
|
{
|
|
fCopyOrMoveSucceeded = MoveFileWithProgress(pszSource, pszDest, CopyCallbackProc, pcs, MOVEFILE_COPY_ALLOWED | (fCreateAlways ? MOVEFILE_REPLACE_EXISTING : 0));
|
|
}
|
|
else
|
|
{
|
|
if (!fCreateAlways)
|
|
{
|
|
dwCopyFlags |= COPY_FILE_FAIL_IF_EXISTS;
|
|
}
|
|
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 == pcs->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:
|
|
pcs->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.
|
|
|
|
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;
|
|
|
|
lstrcpyn(szFile, bDest ? pszDest : pszSource, ARRAYSIZE(szFile));
|
|
if (!szFile[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)
|
|
{
|
|
StrCpyN(szReason, pszMessage, ARRAYSIZE(szReason));
|
|
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,
|
|
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;
|
|
|
|
if (pcs->fFlags & FOF_NOERRORUI)
|
|
{
|
|
result = ERROR_CANCELLED;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
lstrcpyn(szTemp, pszDest, ARRAYSIZE(szTemp));
|
|
PathRemoveFileSpec(szTemp);
|
|
|
|
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, 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)
|
|
{
|
|
lstrcat(lpszLongPlate, pszConflictingName);
|
|
}
|
|
else
|
|
{
|
|
// Need to remove some of the name
|
|
LPTSTR pszExt = StrRChr(pszConflictingName, NULL, TEXT('.'));
|
|
if (pszExt)
|
|
{
|
|
int ichTemplate = lstrlen(lpszLongPlate);
|
|
lstrcpyn(lpszLongPlate + ichTemplate,
|
|
pszConflictingName,
|
|
max(MAX_PATH - ichFixed - lstrlen(pszExt), 0));
|
|
|
|
// use as much of the buffer as possible
|
|
StrCatBuff(lpszLongPlate, pszExt, max(MAX_PATH - ichFixed + ichTemplate, 0));
|
|
}
|
|
else
|
|
{
|
|
lstrcpyn(lpszLongPlate + lstrlen(lpszLongPlate),
|
|
pszConflictingName,
|
|
MAX_PATH - ichFixed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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.
|
|
lstrcpy(pszDest, szTemp);
|
|
|
|
// 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);
|
|
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];
|
|
|
|
// must be marked system and hidden
|
|
if (!IS_SYSTEM_HIDDEN(dwAttributes))
|
|
return FALSE;
|
|
|
|
lstrcpy(szPath, pszSource);
|
|
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;
|
|
|
|
lstrcpy(wszDestDir, szDest);
|
|
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];
|
|
|
|
lstrcpy(szTemp, lpszDest);
|
|
PathRemoveFileSpec(szTemp);
|
|
|
|
// build the original long name
|
|
lstrcpy(szLongName, szTemp);
|
|
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,
|
|
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, 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,
|
|
WIN32_FIND_DATA *pfd, WIN32_FIND_DATA * pfdDest, BOOL fRenameTried)
|
|
{
|
|
int ret;
|
|
int result;
|
|
BOOL fSetDestAttributes = FALSE;
|
|
DWORD dwDesiredAttributes = pfd->dwFileAttributes;
|
|
|
|
// 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;
|
|
|
|
// 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
|
|
{
|
|
BOOL fWithoutTemplate = FALSE;
|
|
|
|
if (pcs->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, pfdDest, pfd);
|
|
switch (result)
|
|
{
|
|
case IDUNKNOWN:
|
|
break;
|
|
|
|
case IDRETRY:
|
|
return EnterDir_Copy(pcs, pszSource, pszDest, pfd, pfdDest, TRUE);
|
|
|
|
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, pfd, pfdDest, fRenameTried);
|
|
}
|
|
else
|
|
{
|
|
pcs->bAbort = TRUE;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
// Maybe its an encrypted folder thats losing its encryption?
|
|
if (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:
|
|
pcs->fLostEncryptOk = TRUE;
|
|
return EnterDir_Copy(pcs, pszSource, pszDest, pfd, pfdDest, fRenameTried);
|
|
|
|
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;
|
|
|
|
default: // ret != 0 (dos error code)
|
|
ret |= ERRORONDEST;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int EnterDir_Move(COPY_STATE* pcs, LPTSTR pszSource, LPTSTR pszDest,
|
|
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;
|
|
|
|
StrCpyN(szDestDir, pszDest, ARRAYSIZE(szDestDir));
|
|
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, pfd, pfdDest, fRenameTried);
|
|
return ret;
|
|
|
|
case ERROR_ALREADY_EXISTS:
|
|
case ERROR_FILE_EXISTS:
|
|
if (!fRenameTried)
|
|
{
|
|
int result = CheckForRenameCollision(pcs, OPER_ENTERDIR, pszSource, pszDest, pfdDest, pfd);
|
|
switch (result)
|
|
{
|
|
case IDUNKNOWN:
|
|
break;
|
|
case IDRETRY:
|
|
return EnterDir_Move(pcs, pszSource, pszDest, 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, pfd, pfdDest, FALSE);
|
|
}
|
|
|
|
int EnterDir_Delete(COPY_STATE * pcs, WIN32_FIND_DATA *pfdSrc, LPTSTR pszSource, 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, &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, 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, &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,
|
|
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, pfdDest, pfd);
|
|
switch (result)
|
|
{
|
|
case IDUNKNOWN:
|
|
break;
|
|
|
|
case IDRETRY:
|
|
return DoFile_Copy(pcs, pszSource, pszDest, 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, 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];
|
|
lstrcpyn(szDest, pszDest, ARRAYSIZE(szDest));
|
|
PathRemoveFileSpec(szDest);
|
|
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, szDest, NULL);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int DoFile_Move(COPY_STATE* pcs, LPTSTR pszSource, LPTSTR pszDest,
|
|
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, 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.
|
|
|
|
lstrcpyn(szDestDir, pszDest, ARRAYSIZE(szDestDir));
|
|
PathRemoveFileSpec(szDestDir);
|
|
dwAttribs = GetFileAttributes(szDestDir);
|
|
|
|
if ((dwAttribs != -1) && (dwAttribs & FILE_ATTRIBUTE_ENCRYPTED))
|
|
{
|
|
// sainity 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];
|
|
lstrcpyn(szDest, pszDest, ARRAYSIZE(szDest));
|
|
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, pfd, pfdDest, FALSE);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int DoFile_Rename(COPY_STATE* pcs, LPTSTR pszSource, LPTSTR pszDest,
|
|
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, 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)
|
|
{
|
|
lstrcpyn(szDestPath, pcs->lpfo->pTo, ARRAYSIZE(szDestPath));
|
|
if (!szDestPath[0]) // NULL dest is same as "."
|
|
{
|
|
szDestPath[0] = TEXT('.');
|
|
szDestPath[1] = 0;
|
|
}
|
|
|
|
if (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)
|
|
{
|
|
lstrcpy((LPTSTR)szText, TEXT("Shell "));
|
|
switch (lpfo->wFunc)
|
|
{
|
|
case FO_COPY:
|
|
lstrcat((LPTSTR)szText, TEXT("Copy "));
|
|
break;
|
|
case FO_MOVE:
|
|
lstrcat((LPTSTR)szText, TEXT("Move "));
|
|
break;
|
|
case FO_DELETE:
|
|
lstrcat((LPTSTR)szText, TEXT("Delete"));
|
|
break;
|
|
case FO_RENAME:
|
|
lstrcat((LPTSTR)szText, TEXT("Rename"));
|
|
break;
|
|
default:
|
|
lstrcat((LPTSTR)szText, TEXT("Copy? "));
|
|
break;
|
|
}
|
|
lstrcat((LPTSTR)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;
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
DTGetWin32FindData(pcs->dth.pdtnCurrent, &fdSrc);
|
|
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, &fdSrc, &fdDest, FALSE);
|
|
break;
|
|
|
|
case OPER_ENTERDIR | FO_COPY: // Create destination directory
|
|
ret = EnterDir_Copy(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, &fdSrc, &fdDest, 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, &fdSrc, &fdDest, FALSE);
|
|
break;
|
|
|
|
case OPER_DOFILE | FO_RENAME:
|
|
ret = DoFile_Rename(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, &fdSrc, &fdDest, FALSE);
|
|
break;
|
|
|
|
case OPER_DOFILE | FO_MOVE:
|
|
ret = DoFile_Move(pcs, pcs->dth.szSrcPath, pcs->dth.szDestPath, &fdSrc, &fdDest, FALSE);
|
|
break;
|
|
|
|
case OPER_ENTERDIR | FO_DELETE:
|
|
ret = EnterDir_Delete(pcs, &fdSrc, pcs->dth.szSrcPath, &hdpaDeletedFiles);
|
|
break;
|
|
|
|
case OPER_DOFILE | FO_DELETE:
|
|
ret = DoFile_Delete(pcs, &fdSrc, 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 szBBPath[MAX_PATH];
|
|
|
|
// Since we probably blew away any chance at having the FSNotify work, make sure
|
|
// we update item for this path... we can send the message on any drive since
|
|
// the bitbucket listens for changes on all drives.
|
|
DriveIDToBBPath(DriveIDFromBBPath(lpfo->pFrom), szBBPath);
|
|
SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH, szBBPath, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
if (g_dwStopWatchMode)
|
|
{
|
|
lstrcpy((LPTSTR)&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)(void*)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;
|
|
|
|
lpsz = DPA_GetPtr(hdpa, i);
|
|
ASSERT(lpsz);
|
|
|
|
ichTemp = ichSize - 1;
|
|
|
|
ichSize += (lstrlen(lpsz) + 1);
|
|
psz = (LPTSTR)(void*)LocalReAlloc((HLOCAL)lpszReturn, ichSize * sizeof(TCHAR),
|
|
LMEM_MOVEABLE|LMEM_ZEROINIT);
|
|
if (!psz)
|
|
{
|
|
break;
|
|
}
|
|
lpszReturn = psz;
|
|
lstrcpy(lpszReturn + ichTemp, lpsz);
|
|
}
|
|
|
|
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;
|
|
|
|
ichTemp = ichSize - 1;
|
|
|
|
// get the name (filename only without extension)
|
|
lpsz = DPA_GetPtr(hdpa, i);
|
|
lstrcpy(szFile, PathFindFileName(lpsz));
|
|
if (!ss.fShowExtensions)
|
|
{
|
|
PathRemoveExtension(szFile);
|
|
}
|
|
|
|
// grow the buffer and add it in
|
|
ichSize += lstrlen(szFile) + 2;
|
|
psz = (LPTSTR)(void*)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)
|
|
{
|
|
lstrcat(lpszReturn, c_szEllipses);
|
|
return lpszReturn;
|
|
}
|
|
else
|
|
{
|
|
StrCatBuff(lpszReturn, TEXT("'"), ichSize); //A single quote BEFORE the filename.
|
|
StrCatBuff(lpszReturn, szFile, ichSize);
|
|
StrCatBuff(lpszReturn, TEXT("'"), ichSize); //A single quote AFTER the filename.
|
|
}
|
|
|
|
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));
|
|
StrCatBuff(lpszReturn, szTemp, ichSize);
|
|
ichSize = ichTemp + (lstrlen(lpszReturn + ichTemp) + 1);
|
|
}
|
|
}
|
|
return lpszReturn;
|
|
}
|
|
|
|
|
|
void CALLBACK FOUndo_GetText(UNDOATOM *lpua, TCHAR * buffer, 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;
|
|
|
|
// fill in the file names
|
|
lpszFile1 = DPA_GetPtr(hdpa, 0);
|
|
lpszFile2 = DPA_GetPtr(hdpa, 1);
|
|
lstrcpy(szFile1, PathFindFileName(lpszFile1));
|
|
lstrcpy(szFile2, PathFindFileName(lpszFile2));
|
|
|
|
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)
|
|
{
|
|
StrCpyN(buffer, pszTemp, MAX_PATH);
|
|
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);
|
|
wnsprintf(buffer, MAX_PATH, szTemplate, lpszFile1);
|
|
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)
|
|
{
|
|
lstrcpy(szFromPath, sFileOp.pFrom);
|
|
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, pcd->pfdSource, pcd->pFileSource))
|
|
SetDlgItemText(hDlg, IDD_FILEINFO_NEW, szDateSrc);
|
|
|
|
BuildDateLine(szDateDest, 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);
|
|
}
|
|
}
|
|
|
|
void 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;
|
|
}
|
|
lstrcpy(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;
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
FileDescToWin32FileData(pfdscSrc, &wfdSrc);
|
|
|
|
//
|
|
// 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(LPCTSTR pszDir, 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;
|
|
|
|
if (NULL == pszDir)
|
|
return E_INVALIDARG;
|
|
|
|
// 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(pszDir))) - lstrlen(pszDir);
|
|
|
|
// set this so that we only do the above processing for the root folder
|
|
pfdi->bProcessedRoot = TRUE;
|
|
}
|
|
|
|
if (PathCombine(pfdi->szPath, pszDir, c_szStarDotStar))
|
|
{
|
|
HANDLE hfind = FindFirstFile(pfdi->szPath, &pfdi->fd);
|
|
|
|
// remove the *.* we added, because we will be recycling this buffer
|
|
PathRemoveFileSpec(pfdi->szPath);
|
|
|
|
if (hfind != INVALID_HANDLE_VALUE)
|
|
{
|
|
do
|
|
{
|
|
if (!PathIsDotOrDotDot(pfdi->fd.cFileName))
|
|
{
|
|
bHasChildren = TRUE;
|
|
|
|
// append the subfile/subfolder to the parent path
|
|
if (!PathAppend(pfdi->szPath, pfdi->fd.cFileName))
|
|
{
|
|
// PathAppend failed, try to append the short name
|
|
if (!pfdi->fd.cAlternateFileName[0] || !PathAppend(pfdi->szPath, 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
|
|
lstrcpyn(pfdi->szNonDeletableFile, pfdi->szPath, ARRAYSIZE(pfdi->szNonDeletableFile));
|
|
}
|
|
}
|
|
|
|
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
|
|
lstrcpyn(pfdi->szNonDeletableFile, pfdi->szPath, ARRAYSIZE(pfdi->szNonDeletableFile));
|
|
}
|
|
else
|
|
{
|
|
if (pfdi->fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
// its a directory, so recurse
|
|
lRet = CheckFolderSizeAndDeleteability(pfdi->szPath, pfdi, pcs);
|
|
}
|
|
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
|
|
lstrcpyn(pfdi->szNonDeletableFile, pfdi->szPath, ARRAYSIZE(pfdi->szNonDeletableFile));
|
|
}
|
|
|
|
ulTemp.LowPart = pfdi->fd.nFileSizeLow;
|
|
ulTemp.HighPart = pfdi->fd.nFileSizeHigh;
|
|
pfdi->cbSize += ulTemp.QuadPart;
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove the file/dir name we appended
|
|
PathRemoveFileSpec(pfdi->szPath);
|
|
}
|
|
|
|
} 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(pszDir))
|
|
{
|
|
lRet = GetLastError();
|
|
ASSERT(lRet != ERROR_SUCCESS);
|
|
|
|
// pass back the name of the non-deleteable file in pfdi->szNonDeletableFile
|
|
lstrcpyn(pfdi->szNonDeletableFile, pszDir, ARRAYSIZE(pfdi->szNonDeletableFile));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if FindFirstFile fails, check to see if the directory itself is deleteable
|
|
if (!_IsDirectoryDeletable(pszDir))
|
|
{
|
|
lRet = GetLastError();
|
|
ASSERT(lRet != ERROR_SUCCESS);
|
|
|
|
// pass back the name of the non-deleteable file in pfdi->szNonDeletableFile
|
|
lstrcpyn(pfdi->szNonDeletableFile, pszDir, ARRAYSIZE(pfdi->szNonDeletableFile));
|
|
}
|
|
}
|
|
}
|
|
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
|
|
lstrcpyn(pfdi->szNonDeletableFile, pszDir, ARRAYSIZE(pfdi->szNonDeletableFile));
|
|
}
|
|
|
|
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, 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;
|
|
FOLDERDELETEINFO fdi = {0};
|
|
|
|
fdi.cchBBDir = BBRecyclePathLength(idDrive);
|
|
|
|
dwError = CheckFolderSizeAndDeleteability(pszFile, &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);
|
|
lstrcpy(pszFile, fdi.szNonDeletableFile);
|
|
}
|
|
|
|
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)
|
|
{
|
|
*phEventRunning = CreateEvent(CreateAllAccessSecurityAttributes(NULL, NULL, NULL), 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.
|
|
HANDLE hEventFinished = CreateEvent(CreateAllAccessSecurityAttributes(NULL, NULL, NULL), 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;
|
|
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;
|
|
}
|