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

4647 lines
144 KiB

#include "shellprv.h"
#pragma hdrstop
#define INTERNAL_COPY_ENGINE
#include "copy.h"
#include "shell32p.h"
#ifdef ENABLE_TRACK
#include <iofs.h>
#include "loaddll.h"
#endif
// BUGBUG - Might want this for Nashville too...
#ifdef WINNT
#define COPY_USE_COPYFILEEX
#endif
#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_FIRSTAVG 4000 // 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
// BUGBUG: merge COPY_STATE with COPYROOT
typedef struct {
int nSourceFiles;
LPTSTR lpCopyBuffer; // global file copy buffer
UINT uSize; // size of this buffer
FILEOP_FLAGS fFlags; // from SHFILEOPSTRUCT
UINT wFunc; // FO_ function type
HWND hwndProgress; // dialog/progress window
HWND hwndCaller; // window to do stuff on
HWND hwndDlgParent; // parent window for message boxes
CONFIRM_DATA cd; // confirmation stuff
DWORD dwStartTime; // start time to implement progress timeout
DWORD dwShowTime; // when did the dialog get shown
LPUNDOATOM lpua; // the undo atom that this file operation will make
BOOL fNoConfirmRecycle;
BOOL bAbort;
BOOL fNonCopyProgress;
BOOL fMerge; // are we doing a merge of folders
// folowing fields are used for giving estimated time for completion
// feedback to the user during longer than MINTIME4FEEDBACK operations
BOOL fShowTime; //
DWORD dwTimeLeft; // last reported time left, necessary for histerisis
DWORD dwBytesLeft;
DWORD dwPreviousTime; // calculate transfer rate
DWORD dwBytesRead; // Bytes read in the interval dwNewTime-dwPreviousTime
DWORD dwBytesPerSec;
LPCTSTR lpszProgressTitle;
LPSHFILEOPSTRUCT lpfo;
BOOL fMove;
BOOL fInitialize;
WIN32_FIND_DATA wfd;
} COPY_STATE, *LPCOPY_STATE;
typedef struct {
BOOL fRecurse;
BOOL fAbortRoot;
BOOL bGoIntoDir;
BOOL fMultiDest;
UINT cDepth;
UINT wFunc; // operation to be perfomed
LPCTSTR pSource; // list of source files and directories
LPCTSTR pDest; // list of dest files and dirs, usually just 1 path
LPTSTR pDestSpec; // destination file spec (*.*, *.bak, etc).
LPTSTR pDestPath; // root of destinaton
FILEOP_FLAGS fFlags; // from SHFILEOPSTRUCT
TCHAR bDiskCheck[26];
TCHAR szSource[MAX_PATH]; // current source root
TCHAR szDest[MAX_PATH]; // filespec part
WIN32_FIND_DATA finddata;
HANDLE hfindfiles[MAXDIRDEPTH];
} COPYROOT;
#define OPER_MASK 0x0F00
#define OPER_ENTERDIR 0x0100
#define OPER_LEAVEDIR 0x0200
#define OPER_DOFILE 0x0300
#define OPER_ERROR 0x0400
#ifdef CLOUDS
void CloudHookFileOperation(LPSHFILEOPSTRUCT *);
#endif
int CopyMoveRetry(COPY_STATE *pcs, LPCTSTR pszDest, int error, DWORD dwFileSize);
void CopyError(LPCOPY_STATE, LPCTSTR, LPCTSTR, int, UINT, int);
void SetProgressTime(COPY_STATE *pcs);
void FOUndo_AddInfo(LPUNDOATOM lpua, LPTSTR lpszSrc, LPTSTR lpszDest, DWORD dwAttributes);
void CALLBACK FOUndo_Release(LPUNDOATOM lpua);
void FOUndo_FileReallyDeleted(LPTSTR lpszFile);
void AddRenamePairToHDSA(LPCTSTR pszOldPath, LPCTSTR pszNewPath, HDSA* phdsaRenamePairs);
typedef struct _deletedfileinfo {
LPTSTR lpszName;
DWORD dwAttributes;
} FOUNDO_DELETEDFILEINFO, *LPFOUNDO_DELETEDFILEINFO;
typedef struct _FOUndoData {
HDPA hdpa;
HDSA hdsa;
} FOUNDODATA, *LPFOUNDODATA;
void ShowProgressWindow(COPY_STATE *pcs)
{
ShowWindow(pcs->hwndProgress, SW_SHOW);
SetFocus(GetDlgItem(pcs->hwndProgress, IDCANCEL));
if (pcs->hwndCaller != pcs->hwndProgress)
{
EnableWindow(pcs->hwndCaller, FALSE);
}
pcs->dwShowTime = GetTickCount();
pcs->hwndDlgParent = pcs->hwndProgress;
}
// 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)
{
MSG msg;
if (!(pcs->fFlags & FOF_SILENT)) {
if ((!pcs->bAbort) && (!pcs->dwShowTime) && ((GetTickCount() - pcs->dwStartTime) > SHOW_PROGRESS_TIMEOUT)) {
ShowProgressWindow(pcs);
UpdateWindow(pcs->hwndProgress); // make it show now
}
// don't do this peek message until we're visible. that way
// we don't eat up type ahead keys
if (pcs->dwShowTime) {
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (!IsDialogMessage(pcs->hwndProgress, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
}
return pcs->bAbort;
}
//
// note: this has the side effect of setting the
// current drive to the new disk if it is successful
//
BOOL DiskCheck(COPY_STATE *pcs, HWND hwnd, LPCTSTR pPath, UINT wFunc)
{
int drive;
TCHAR szDrive[10];
TCHAR szMessage[128];
DWORD dwError;
int iIsNet;
DebugMsg(DM_TRACE, TEXT("DiskCheck(%s)"), (LPTSTR)pPath);
if (pPath[1] == TEXT(':') && !IsDBCSLeadByte(*pPath))
drive = DRIVEID(pPath);
else
return TRUE;
Retry:
// BUGBUG, we need to do the find first here instead of GetCurrentDirectory()
// because redirected devices (network, cdrom) do not actually hit the disk
// on the GetCurrentDirectory() call (dos busted)
PathBuildRoot(szDrive, drive);
if (IsCDRomDrive(drive)) {
if (!PathFileExists(szDrive))
goto DriveNotIn;
} else if (FALSE != (iIsNet = IsNetDrive(drive))) {
if (iIsNet == 1)
return TRUE;
dwError = WNetRestoreConnection(hwnd, szDrive);
if (dwError != WN_SUCCESS) {
if (!(dwError == WN_CANCEL || dwError == ERROR_CONTINUE) &&
!(pcs->fFlags & FOF_NOERRORUI)) {
DWORD dw;
WNetGetLastError(&dw, szMessage, ARRAYSIZE(szMessage), NULL, 0);
ShellMessageBox(HINST_THISDLL, hwnd, szMessage, MAKEINTRESOURCE(IDS_FILEERROR + wFunc),
MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
}
}
return FALSE;
} else {
if (!PathFileExists(szDrive))
goto DiskNotThere;
}
return TRUE;
DiskNotThere:
dwError = GetLastError();
DebugMsg(DM_TRACE, TEXT("DOS Extended error %X"), dwError);
// BUGBUG, flash (ROM?) drives return a different error code here
// that we need to map to not formatted, talk to robwi...
if (dwError == ERROR_NOT_READY) {
// drive not ready (no disk in the drive)
DriveNotIn:
if (!(pcs->fFlags & FOF_NOERRORUI) &&
(ShellMessageBox(HINST_THISDLL, hwnd,
MAKEINTRESOURCE(IDS_DRIVENOTREADY),
MAKEINTRESOURCE(IDS_FILEERROR + wFunc),
MB_SETFOREGROUND | MB_ICONEXCLAMATION | MB_RETRYCANCEL,
(DWORD)(drive + TEXT('A'))) == IDRETRY))
{
goto Retry;
}
else
return FALSE;
} else if (dwError == ERROR_GEN_FAILURE) {
// general failue (disk not formatted)
if (!(pcs->fFlags & FOF_NOERRORUI) &&
(ShellMessageBox(HINST_THISDLL, hwnd,
MAKEINTRESOURCE(IDS_UNFORMATTED), MAKEINTRESOURCE(IDS_FILEERROR + wFunc),
MB_SETFOREGROUND | MB_ICONEXCLAMATION | MB_YESNO, (DWORD)(drive + TEXT('A'))) == IDYES)) {
switch (SHFormatDrive(hwnd, drive, SHFMT_ID_DEFAULT, 0)) {
case SHFMT_CANCEL:
return FALSE;
case SHFMT_ERROR:
case SHFMT_NOFORMAT:
if (!(pcs->fFlags & FOF_NOERRORUI))
{
ShellMessageBox(HINST_THISDLL, hwnd,
MAKEINTRESOURCE(IDS_NOFMT),
MAKEINTRESOURCE(IDS_FILEERROR + wFunc),
MB_SETFOREGROUND | MB_ICONEXCLAMATION | MB_OK,
(DWORD)(drive + TEXT('A')));
}
return FALSE;
default:
goto Retry; // Disk should now be formatted, verify
}
} else
return FALSE;
} else if (!(pcs->fFlags & FOF_NOERRORUI)) {
ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_NOSUCHDRIVE),
MAKEINTRESOURCE(IDS_FILEERROR + wFunc),
MB_SETFOREGROUND | MB_ICONHAND, (DWORD)(drive + TEXT('A')));
}
return FALSE;
}
BOOL PCRDiskCheck(COPYROOT *pcr, COPY_STATE *pcs, HWND hwnd, LPTSTR lpszFile, UINT wFunc)
{
int id = DRIVEID(lpszFile);
if (!pcr->bDiskCheck[id]) {
pcr->bDiskCheck[id] =
(TCHAR)DiskCheck(pcs, hwnd, lpszFile, wFunc);
}
return pcr->bDiskCheck[id];
}
void BuildDateLine(LPTSTR pszDateLine, const WIN32_FIND_DATA *pFind, LPCTSTR pFileName)
{
TCHAR szTemplate[64];
TCHAR szNum[32], szTmp[64], szTmp2[64];
SYSTEMTIME st;
FILETIME ftLocal;
WIN32_FIND_DATA fd;
ULARGE_INTEGER liFileSize;
if (!pFind) {
HANDLE hfind = FindFirstFile(pFileName, &fd);
Assert(hfind != INVALID_HANDLE_VALUE);
FindClose(hfind);
pFind = &fd;
}
LoadString(HINST_THISDLL, IDS_DATESIZELINE, szTemplate, ARRAYSIZE(szTemplate));
FileTimeToLocalFileTime(&pFind->ftLastWriteTime, &ftLocal);
FileTimeToSystemTime(&ftLocal, &st);
GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, szTmp, ARRAYSIZE(szTmp));
GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, szTmp2, ARRAYSIZE(szTmp2));
liFileSize.LowPart = pFind->nFileSizeLow;
liFileSize.HighPart = pFind->nFileSizeHigh;
wsprintf(pszDateLine, szTemplate, ShortSizeFormat64(liFileSize.QuadPart, szNum),
szTmp, szTmp2);
}
typedef struct {
LPCTSTR pFileDest;
LPCTSTR pFileSource;
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"
CONFIRM_FLAG fYesToAllMask; // these bits are cleared in fConfirm on "yes to all"
CONFIRM_FLAG fNoMask; // these bits are set on "no to all"
CONFIRM_FLAG fNoToAllMask; // these bits are set on "no to all"
//COPY_STATE *pcs;
CONFIRM_DATA *pcd;
} CONFDLG_DATA;
// 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);
}
}
if (hwndYesToAll)
ShowWindow(hwndYesToAll, SW_HIDE);
ShowWindow( GetDlgItem(hdlg, idHide), SW_HIDE);
}
}
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];
SHFILEINFO sfi;
SHFILEINFO sfiDest;
LPTSTR pszFileDest = NULL;
LPTSTR pszMsg, pszSource;
int i;
int cxWidth;
HICON hicon;
RECT rc;
// get the size of the text boxes
GetWindowRect(GetDlgItem(hDlg, pcd->idText), &rc);
cxWidth = rc.right - rc.left;
if (!pcd->bShowCancel)
HideYesToAllAndCancel(hDlg);
switch (pcd->nSourceFiles) {
case -1:
LoadString(HINST_THISDLL, IDS_SELECTEDFILES, szSrc, ARRAYSIZE(szSrc));
pszSource = szSrc;
break;
case 1:
// pszSource = PathFindFileName(pcd->pFileSource);
{
SHGetFileInfo(pcd->pFileSource,
(pcd->fConfirm==CONFIRM_DELETE_FOLDER)? FILE_ATTRIBUTE_DIRECTORY : 0,
&sfi, SIZEOF(sfi), SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES);
pszSource = sfi.szDisplayName;
PathCompactPath(NULL, pszSource, cxWidth);
break;
}
default:
pszSource = AddCommas(pcd->nSourceFiles, 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));
hicon = (HICON)SendDlgItemMessage(hDlg, IDD_ICON_OLD, STM_SETICON, (WPARAM)sfi2.hIcon, 0L);
if (hicon)
DestroyIcon(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));
hicon = (HICON)SendDlgItemMessage(hDlg, IDD_ICON_NEW, STM_SETICON, (WPARAM)sfi2.hIcon, 0L);
if (hicon)
DestroyIcon(hicon);
}
// there are 3 controls:
// IDD_TEXT contains regular text (normal file/folder)
// IDD_TEXT1 & IDD_TEXT2 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);
}
}
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(NULL, pszFileDest, cxWidth);
}
if (pcd->uDeleteWarning) {
LoadString(HINST_THISDLL, pcd->uDeleteWarning, szDeleteWarning, ARRAYSIZE(szDeleteWarning));
} else
szDeleteWarning[0] = 0;
if (pcd->bFireIcon) {
hicon = LoadImage(HINST_THISDLL, MAKEINTRESOURCE(IDI_NUKEFILE), IMAGE_ICON, 0,0, LR_LOADMAP3DCOLORS);
if (hicon) {
hicon = (HICON)SendDlgItemMessage(hDlg, IDD_ICON_WASTEBASKET, STM_SETICON, (WPARAM)hicon, 0L);
if (hicon)
DestroyIcon(hicon);
}
}
pszMsg = ShellConstructMessageString(HINST_THISDLL, szMessage,
pszSource, pszFileDest, szDeleteWarning);
if (pszMsg) {
SetDlgItemText(hDlg, pcd->idText, pszMsg);
SHFree(pszMsg);
}
}
int CALLBACK ConfirmDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
CONFDLG_DATA *pcd = (CONFDLG_DATA *)GetWindowLong(hDlg, DWL_USER);
switch (wMsg) {
case WM_INITDIALOG:
SetWindowLong(hDlg, DWL_USER, lParam);
InitConfirmDlg(hDlg, (CONFDLG_DATA *)lParam);
break;
case WM_DESTROY:
// Handle case where the allocation of the PCD failed.
if (!pcd)
break;
if (pcd->bShowDates) {
HICON hicon;
hicon = (HICON)SendDlgItemMessage(hDlg, IDD_ICON_NEW, STM_GETICON, 0, 0);
if (hicon)
DestroyIcon(hicon);
hicon = (HICON)SendDlgItemMessage(hDlg, IDD_ICON_OLD, STM_GETICON, 0, 0);
if (hicon)
DestroyIcon(hicon);
}
if (pcd->bFireIcon) {
HICON hicon;
hicon = (HICON)SendDlgItemMessage(hDlg, IDD_ICON_WASTEBASKET, STM_GETICON, 0, 0L);
if (hicon)
DestroyIcon(hicon);
}
break;
case WM_COMMAND:
if (!pcd)
break;
switch (GET_WM_COMMAND_ID(wParam, lParam)) {
case IDNO:
if (GetKeyState(VK_SHIFT) < 0) // force NOTOALL
pcd->pcd->fNoToAll |= pcd->fNoToAllMask;
else
pcd->pcd->fNoToAll |= pcd->fNoMask;
EndDialog(hDlg, IDNO);
break;
case IDD_YESTOALL:
pcd->pcd->fConfirm &= ~pcd->fYesToAllMask;
EndDialog(hDlg, IDYES);
break;
case IDYES:
pcd->pcd->fConfirm &= ~pcd->fYesMask;
EndDialog(hDlg, IDYES);
break;
case IDCANCEL:
EndDialog(hDlg, IDCANCEL);
break;
}
break;
default:
return FALSE;
}
return TRUE;
}
TCHAR const c_szConfirmFileOp[] = TEXT("ConfirmFileOp");
void SetConfirmMaskAndText(CONFDLG_DATA *pcd, DWORD dwFileAttributes, LPCTSTR lpszFileName)
{
if (dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY)) {
// if there's a desktop.ini with a .ShellClassInfo ConfirmFileOp=0 then don't add this bit
if (lpszFileName && (lstrlen(lpszFileName) + lstrlen(c_szDesktopIni) + 2 < MAX_PATH)) {
TCHAR szIniFile[MAX_PATH];
BOOL fConfirm;
lstrcpy(szIniFile, lpszFileName);
PathAppend(szIniFile, c_szDesktopIni);
fConfirm = GetPrivateProfileInt(c_szClassInfo, c_szConfirmFileOp, TRUE, szIniFile);
if (!fConfirm) {
SetConfirmMaskAndText(pcd, dwFileAttributes & ~(FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY), lpszFileName);
return;
}
}
}
if (dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
pcd->fConfirm = CONFIRM_SYSTEM_FILE;
pcd->fYesToAllMask |= CONFIRM_SYSTEM_FILE;
pcd->fNoToAllMask = CONFIRM_SYSTEM_FILE;
pcd->idText = IDD_TEXT2;
} else if (dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
pcd->fConfirm = CONFIRM_READONLY_FILE;
pcd->fYesToAllMask |= CONFIRM_READONLY_FILE;
pcd->fNoToAllMask = CONFIRM_READONLY_FILE;
pcd->idText = IDD_TEXT1;
} else if (lpszFileName && ((dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) &&
PathIsBinaryExe(lpszFileName)) {
pcd->fConfirm = CONFIRM_PROGRAM_FILE;
pcd->fYesToAllMask |= CONFIRM_PROGRAM_FILE;
pcd->fNoToAllMask = CONFIRM_PROGRAM_FILE;
pcd->idText = IDD_TEXT3;
}
}
void PauseAnimation(COPY_STATE *pcs, BOOL bStop)
{
if (pcs && !(pcs->fFlags & FOF_SILENT))
{
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)
{
int dlg, ret;
CONFDLG_DATA cdd;
CONFIRM_FLAG fConfirmType;
if (pcs) {
nSourceFiles = pcs->nSourceFiles;
}
cdd.pfdSource = pfdSource;
cdd.pfdDest = NULL; // pfdDest // BUGBUG: 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.fNoMask = 0;
cdd.fNoToAllMask = 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;
fConfirmType = fConfirm & CONFIRM_FLAG_TYPE_MASK;
switch (fConfirmType) {
case CONFIRM_DELETE_FILE:
case CONFIRM_DELETE_FOLDER:
cdd.bShrinkDialog = TRUE;
// find data for source is in pdfDest
if ((nSourceFiles != 1) && (pcd->fConfirm & CONFIRM_MULTIPLE)) {
cdd.nSourceFiles = nSourceFiles;
if ((fConfirm & CONFIRM_WASTEBASKET_PURGE) ||
(!pcs || !(pcs->fFlags & FOF_ALLOWUNDO)) ||
!BitBucketWillRecycle(cdd.pFileSource)) {
// have the fire icon and the REALLY delete warning
cdd.uDeleteWarning = IDS_FOLDERDELETEWARNING;
cdd.bFireIcon = TRUE;
if (pcs)
pcs->fFlags &= ~FOF_ALLOWUNDO;
cdd.idText = IDD_TEXT4;
}
if (cdd.bFireIcon || !pcs || !pcs->fNoConfirmRecycle) {
ret = DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_DELETE_MULTIPLE), hwnd, ConfirmDlgProc, (LPARAM)&cdd);
if (ret != IDYES)
return IDCANCEL;
}
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 (fConfirmType == CONFIRM_DELETE_FILE) {
dlg = DLG_DELETE_FILE;
if ((fConfirm & CONFIRM_WASTEBASKET_PURGE) ||
(!pcs || !(pcs->fFlags & FOF_ALLOWUNDO)) ||
!BitBucketWillRecycle(cdd.pFileSource))
{
cdd.bFireIcon = TRUE;
if (pcs)
pcs->fFlags &= ~FOF_ALLOWUNDO;
cdd.uDeleteWarning = IDS_FILEDELETEWARNING;
if (cdd.idText == IDD_TEXT)
cdd.idText = IDD_TEXT4;
} else {
cdd.uDeleteWarning = IDS_FILERECYCLEWARNING;
}
} else {
if (pcs) pcs->nSourceFiles = -1; // show cancel on NEXT confirm dialog
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)) ||
!BitBucketWillRecycle(cdd.pFileSource)) {
cdd.bFireIcon = TRUE;
if (pcs)
pcs->fFlags &= ~FOF_ALLOWUNDO;
cdd.uDeleteWarning = IDS_FOLDERDELETEWARNING;
} else {
cdd.uDeleteWarning = IDS_FOLDERRECYCLEWARNING;
}
}
if (!cdd.bFireIcon && (pcs && pcs->fNoConfirmRecycle)) {
cdd.fConfirm = 0;
}
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_TRACE, TEXT("bogus confirm option"));
return IDCANCEL;
}
if (pcd->fConfirm & cdd.fConfirm) {
if (pcd->fNoToAll & cdd.fConfirm) {
ret = IDNO;
} else {
ret = DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(dlg), hwnd, ConfirmDlgProc, (LPARAM)&cdd);
if (ret == -1)
ret = DE_INSMEM;
if (pcs) pcs->dwStartTime = GetTickCount(); // reset the timer after wait for UI
}
} else {
ret = IDYES;
}
// We used to clear the attributes of the file, but this would cause it to lose
// its attributes when it was restored. Instead, we now save and restore the
// attributes before doing the move/delete
/*
if ((ret == IDYES) && (!(pfdDest->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) &&(pfdDest->dwFileAttributes & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)))
SetFileAttributes(pFileDest ? pFileDest : pFileSource, pfdDest->dwFileAttributes & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM));
*/
return ret;
}
HANDLE * CurrentHANDLE(COPYROOT * pcr)
{
if (pcr->cDepth) {
return &pcr->hfindfiles[pcr->cDepth - 1];
} else {
return pcr->hfindfiles;
}
}
void GetNextCleanup(COPYROOT * pcr)
{
HANDLE * phf;
while (pcr->cDepth && (pcr->cDepth != (UINT)-1)) {
phf = CurrentHANDLE(pcr);
if (*phf) {
FindClose(*phf);
*phf = 0;
}
#ifdef DEBUG
else {
DebugMsg(DM_TRACE, TEXT("Bogus HANDLE"));
}
#endif
pcr->cDepth--;
}
}
void GuessAShortName(LPCTSTR p, LPTSTR szT)
{
int i, j, fDot, cMax;
// BUGBUG: use AnsiNext here?
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 {
// WORD wDialogOp;
LPTSTR pszDialogFrom;
LPTSTR pszDialogTo;
BOOL bShowCancel;
} GETNAME_DATA, *LPGETNAME_DATA;
BOOL CALLBACK GetNameDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
TCHAR szT[14];
TCHAR pTo[MAX_PATH];
LPCTSTR p;
LPGETNAME_DATA lpgn = (LPGETNAME_DATA)GetWindowLong(hDlg, DWL_USER);
switch (wMsg) {
case WM_INITDIALOG:
SetWindowLong(hDlg, DWL_USER, lParam);
lpgn = (LPGETNAME_DATA)lParam;
// inform the user of the old name
PathSetDlgItemPath(hDlg, IDD_FROM, lpgn->pszDialogFrom);
// directory the file will go into
PathRemoveFileSpec(lpgn->pszDialogTo);
PathSetDlgItemPath(hDlg, IDD_DIR, lpgn->pszDialogTo);
// generate a guess for the new name
GuessAShortName(PathFindFileName(lpgn->pszDialogFrom), szT);
lstrcpy(pTo, lpgn->pszDialogTo);
PathAppend(pTo, szT);
// make sure that name is unique
PathYetAnotherMakeUniqueName(pTo, pTo, NULL, NULL);
SetDlgItemText(hDlg, IDD_TO, PathFindFileName(pTo));
SendDlgItemMessage(hDlg, IDD_TO, EM_LIMITTEXT, 13, 0L);
if (!lpgn->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(lpgn->pszDialogTo, szT);
PathQualify(lpgn->pszDialogTo);
// fall through
case IDNO:
case IDCANCEL:
EndDialog(hDlg,GET_WM_COMMAND_ID(wParam, lParam));
break;
case IDD_TO:
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)
{
GETNAME_DATA gn;
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);
return IDYES;
} else {
// gn.wDialogOp = wOp;
gn.pszDialogFrom = pFrom;
gn.pszDialogTo = pTo;
gn.bShowCancel = fMultiple;
iRet = DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_LFNTOFAT), hwnd, GetNameDlgProc, (LPARAM)(LPGETNAME_DATA)&gn);
if (iRet == IDD_YESTOALL)
pcs->cd.fConfirm &= ~CONFIRM_LFNTOFAT;
return iRet;
}
}
void WINAPI SHFreeNameMappings(LPVOID 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);
Free(prp->pszOldPath);
Free(prp->pszNewPath);
}
DSA_DeleteAllItems(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?
if (!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;
}
}
}
// convenience routine to quit the current directory
// assumes the findfile has been closed already
void PuntCurrentDirPair(COPYROOT * pcr)
{
HANDLE * phfindfile = CurrentHANDLE(pcr);
if (*phfindfile) {
FindClose(*phfindfile);
*phfindfile = 0;
}
Assert(pcr->fRecurse && pcr->bGoIntoDir);
pcr->bGoIntoDir = FALSE;
/* Remove the child file spec. */
PathRemoveFileSpec(pcr->szSource);
PathRemoveFileSpec(pcr->szDest);
}
// this implenets an iterative tree walk for generating commands
// for moving/copying/renaming and deleting files. it is called
// repetativly until it returns 0. the commands returned indicate
// what type of operation should be perfomed. this includes
// creating and removing directories, or performing specific
// file operations (copy/move/delete/rename)
//
// in:
// pcr COPYROOT state structure that contains the
// list of source files to derive all dest files
// along with the destination spec and state for
// recursing.
// pcr->pSource list of source files and/or directories
// pcr->pDestPath destination path
// pcr->pDestSpec destination file spec to expand names into (*.*, *.bak, etc)
// pcr->wFunc FO_DELETE, FO_RENAME, FO_MOVE, FO_COPY
// operation to perform.
//
// out:
// pFrom source file or directory to operate on
// pToPath destination file or directory to operate on
//
// returns:
// operation to perform (on pFrom and pToPath)
// OPER_ERROR - Error processing filenames
// OPER_DOFILE - Go ahead and copy, rename, or delete file
// OPER_ENTERDIR - Make a directory specified in (pToPath)
// OPER_LEAVEDIR - Remove directory (pFrom)
// 0 - No more files left
UINT GetNextPair(COPYROOT * pcr, COPY_STATE *pcs, LPTSTR pFrom, LPTSTR pToPath,
HDSA * phdsaRenamePairs)
{
UINT operation; // Return value (operation to perform)
HANDLE *phfindfile;
*pFrom = 0;
*pToPath = 0;
/* Keep recursing directory structure until we get to the bottom */
while (TRUE) {
if (pcr->cDepth || pcr->bGoIntoDir) {
if (pcr->cDepth >= (MAXDIRDEPTH - 1)) { // reached the limit?
// BUGBUG, this is our internal depth, this should be large
// enough to handle very deep dirs...
operation = OPER_ERROR | DE_PATHTODEEP;
goto ReturnPair;
}
/* The directory we returned last call needs to be recursed. */
if (pcr->fRecurse && pcr->bGoIntoDir) {
pcr->cDepth++;
phfindfile = CurrentHANDLE(pcr);
pcr->bGoIntoDir = FALSE;
// This is rather gross, but if the path is up to
// max path, such that appending \* will fail, we
// need to detect this and not recurse
if (lstrlen(pcr->szSource) >= (MAX_PATH-2))
{
// See if we can get away with simply exiting...
pcr->cDepth--;
operation = OPER_LEAVEDIR;
goto ReturnPair;
}
/* Search for all subfiles in directory. */
PathAppend(pcr->szSource, c_szStar);
goto BeginSearch;
}
phfindfile = CurrentHANDLE(pcr); // use this finddata below
SkipThisFile:
/* Search for the next matching file. */
if (*phfindfile == (HANDLE)0) // Already punted
goto LeaveDirectory;
if (!FindNextFile(*phfindfile, &pcr->finddata)) {
FindClose(*phfindfile);
*phfindfile = 0;
LeaveDirectory:
pcr->cDepth--;
/* Remove the child file spec. */
PathRemoveFileSpec(pcr->szSource);
PathRemoveFileSpec(pcr->szDest);
if (pcr->fRecurse && (pcr->cDepth || !pcr->fAbortRoot)) {
/* Tell the move/copy driver it can now delete
the source directory if necessary. */
// REVIEW: we will do a LEAVEDIR for a root even though we did
// not do an ENTERDIR. This seems to work OK.
// .... or not...
operation = OPER_LEAVEDIR;
goto ReturnPair;
}
continue; /* Not recursing, get more stuff. */
}
ProcessSearchResult:
/* Got a file or dir which matches the wild card
originally passed in... */
if (ISDIRFINDDATA(pcr->finddata)) {
UINT iFileNameLen;
/* Ignore directories if we're not recursing. */
if (!pcr->fRecurse)
goto SkipThisFile;
/* Skip the current and parent directories. */
if (pcr->finddata.cFileName[0] == TEXT('.')) {
if (!pcr->finddata.cFileName[1] ||
(pcr->finddata.cFileName[1] == TEXT('.') && !pcr->finddata.cFileName[2]))
goto SkipThisFile;
}
/* We need to create this directory, and then begin searching
for subfiles. */
operation = OPER_ENTERDIR;
pcr->bGoIntoDir = TRUE;
PathRemoveFileSpec(pcr->szSource);
//
// Must have room for directory name + filename (8.3 = 12) within it
// 2 = \'s as in dir\sub-dir\filename, 1 = nul terminator
//
iFileNameLen = lstrlen(pcr->finddata.cFileName);
if (lstrlen(pcr->szSource) + iFileNameLen + 2 + 12 + 1 > MAX_PATH && pcr->wFunc == FO_DELETE) {
PathAppend(pcr->szSource, pcr->finddata.cAlternateFileName);
} else {
PathAppend(pcr->szSource, pcr->finddata.cFileName);
}
PathAppend(pcr->szDest, pcr->finddata.cFileName);
goto ReturnPair;
}
PathRemoveFileSpec(pcr->szSource); // remove the spec
if (!PathAppend(pcr->szSource, pcr->finddata.cFileName)) // replace it
PathAppend(pcr->szSource, pcr->finddata.cAlternateFileName);
/* If its a dir, tell the driver to create it
otherwise, tell the driver to "operate" on the file. */
operation = OPER_DOFILE;
goto ReturnPair;
} else { // !pcr->cDepth
/* Read the next source spec out of the raw source string. */
pcr->fRecurse = FALSE;
pcr->szDest[0] = 0;
if (*pcr->pSource == 0)
return 0; // done with all files
lstrcpyn(pcr->szSource, pcr->pSource, ARRAYSIZE(pcr->szSource));
pcr->pSource += lstrlen(pcr->pSource) + 1;
if (pcr->fMultiDest) {
// break szDestPath into path and spec parts
lstrcpyn(pcr->pDestPath, pcr->pDest, MAX_PATH);
if (!PCRDiskCheck(pcr, pcs, pcs->hwndDlgParent, pcr->pDestPath, pcr->wFunc))
return 0; // user cancel
pcr->pDestSpec = PathFindFileName(pcr->pDestPath);
*(pcr->pDestSpec - 1) = 0;
pcr->pDest += lstrlen(pcr->pDest) + 1;
}
/* Ensure the source disk really exists before doing anything.
(allow UNC names by checking for a valid drive letter)
Only call DiskCheck once for each drive letter. */
if (pcr->szSource[1] == TEXT(':')) {
if (!PCRDiskCheck(pcr, pcs, pcs->hwndDlgParent, pcr->szSource, pcr->wFunc))
return 0; // user cancel
}
/* Classify the input string. */
if (IsWild(pcr->szSource)) {
/* Wild card... operate on all matches but not recursively if FOF_FILESONLY set. */
if (!(pcr->fFlags & FOF_FILESONLY)) {
pcr->fRecurse = TRUE;
}
pcr->fAbortRoot = TRUE;
pcr->cDepth = 1;
phfindfile = pcr->hfindfiles;
BeginSearch:
*phfindfile = FindFirstFile(pcr->szSource, &pcr->finddata);
if (*phfindfile == INVALID_HANDLE_VALUE) {
if (pcr->fRecurse) {
/* We are inside a recursive directory delete, so
instead of erroring out, go back a level */
goto LeaveDirectory;
}
/* Back up as if we completed a search. */
PathRemoveFileSpec(pcr->szSource);
pcr->cDepth--;
/* Find First returned an error. Return FileNotFound. */
operation = OPER_ERROR | DE_FILENOTFOUND;
goto ReturnPair;
}
goto ProcessSearchResult;
} else { // !IsWild(pcr->szSource)
/* This could be a file or a directory. Fill in the finddata
structure for attrib check */
// REVIEW use get file attributes to see if
// this file exists and if it is a directory.
// or do the FindFirstFile first, then check for
// the root dir case if that fails and get rid
// of 3 PathIsRoot() calls
BOOL fRoot = PathIsRoot(pcr->szSource);
if (!fRoot) {
HANDLE hfind = FindFirstFile(pcr->szSource, &pcr->finddata);
if (hfind == INVALID_HANDLE_VALUE) {
operation = OPER_ERROR | DE_FILENOTFOUND;
goto ReturnPair;
}
FindClose(hfind);
}
/* Now determine if its a file or a directory */
if (fRoot || ISDIRFINDDATA(pcr->finddata)) {
/* Process directory */
if (pcr->wFunc == FO_RENAME) {
if (fRoot)
operation = OPER_ERROR | DE_ROOTDIR;
else
operation = OPER_DOFILE;
goto ReturnPair;
}
/* Directory: operation is recursive. */
pcr->fRecurse = TRUE;
pcr->bGoIntoDir = TRUE;
pcr->cDepth = 0;
if (fRoot)
{
*pcr->szDest = 0;
// ENTERDIR tries to create a dir, so do not send it; just
// go on to the next step
continue;
}
if (pcr->fMultiDest)
lstrcpy(pcr->szDest, pcr->pDestSpec);
else
lstrcpy(pcr->szDest, PathFindFileName(pcr->szSource));
operation = OPER_ENTERDIR;
goto ReturnPair;
} else { // we found a file, process it
operation = OPER_DOFILE;
goto ReturnPair;
}
}
}
} // while (TRUE)
ReturnPair:
// The source filespec has been derived into pcr->szSource
// that is copied to pFrom. pcr->szSource and pcr->szDestSpec are merged into pTo.
lstrcpy(pFrom, pcr->szSource);
if (pcr->wFunc != FO_DELETE) {
if (!PathCombine(pToPath, pcr->pDestPath, pcr->szDest))
{
goto CheckPathTooLong;
}
if (operation == OPER_ENTERDIR)
PathRemoveFileSpec(pToPath);
PathAppend(pToPath, pcr->pDestSpec);
_ProcessNameMappings(pToPath, *phdsaRenamePairs);
// 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_ENTERDIR || operation == OPER_DOFILE) &&
!IsLFNDrive(pToPath) &&
PathIsLFNFileSpec(PathFindFileName(pFrom)) &&
(IsWild(pcr->pDestSpec) || PathIsLFNFileSpec(pcr->pDestSpec))) {
switch (GetNameDialog(pcs->hwndDlgParent, pcs, (pcs->nSourceFiles != 1) || pcr->cDepth || (operation == OPER_ENTERDIR), operation, pFrom, pToPath)) {
case IDNO:
if (operation == OPER_ENTERDIR)
PuntCurrentDirPair(pcr);
pcs->lpfo->fAnyOperationsAborted = TRUE;
return GetNextPair(pcr, pcs, pFrom, pToPath, phdsaRenamePairs);
case IDCANCEL:
// User cancelled the operation
pcs->lpfo->fAnyOperationsAborted = TRUE;
return 0;
default:
AddRenamePairToHDSA(pFrom, pToPath, phdsaRenamePairs);
break;
}
// Update the "to" path with the FAT name chosen
if (operation == OPER_ENTERDIR) {
PathRemoveFileSpec(pcr->szDest);
PathAppend(pcr->szDest, PathFindFileName(pToPath));
}
} else {
CheckPathTooLong:
// Sanitity test here to see if
if (!PathMergePathName(pToPath, PathFindFileName(pFrom)))
{
// Failed to build path assume that this is because the
// path is to long.
operation = OPER_ERROR | DE_INVALIDFILES;
}
}
}
if (operation == OPER_ENTERDIR)
{
// Make sure the new directory is not a subdir of the original...
int cchFrom = lstrlen(pFrom);
if (!(pcr->fFlags & FOF_RENAMEONCOLLISION) && IntlStrEqNI(pFrom, pToPath, cchFrom))
{
TCHAR chNext = pToPath[cchFrom]; // Get the next char in the dest.
if (!chNext) {
operation = 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(pToPath) - pToPath - 1) == lstrlen(pFrom)) {
operation = OPER_ERROR | DE_DESTSAMETREE;
} else {
operation = OPER_ERROR | DE_DESTSUBTREE;
}
}
}
if (pcr->fMultiDest) {
// for all subitems (until we come back to the next item)
// we want to use the source name
// cast is safe
pcr->pDestSpec = (LPTSTR)c_szStarDotStar;
}
}
return operation;
}
/* 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)
{
TCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
LPTSTR pszMsg;
if (!(pcs->fFlags & (FOF_SILENT)))
{
if (pcs->fFlags & FOF_SIMPLEPROGRESS) {
if (pcs->lpszProgressTitle) {
if (!HIWORD(pcs->lpszProgressTitle)) {
LoadString(HINST_THISDLL, (UINT)pcs->lpszProgressTitle, szFrom, ARRAYSIZE(szFrom));
pcs->lpszProgressTitle = szFrom;
}
SetDlgItemText(pcs->hwndProgress, IDD_NAME, pcs->lpszProgressTitle);
// null it so we only set it once
pcs->lpszProgressTitle = NULL;
}
} else {
SetDlgItemText(pcs->hwndProgress, IDD_NAME,
PathFindFileName((pcs->fFlags & FOF_MULTIDESTFILES) ? pszTo : pszFrom));
lstrcpy(szFrom, pszFrom);
PathRemoveFileSpec(szFrom);
if (pszTo)
{
lstrcpy(szTo, pszTo);
PathRemoveFileSpec(szTo);
}
pszMsg = ShellConstructMessageString(HINST_THISDLL,
pszTo ? MAKEINTRESOURCE(IDS_FROMTO) : MAKEINTRESOURCE(IDS_FROM),
PathFindFileName(szFrom),
pszTo ? PathFindFileName(szTo) : NULL);
if (pszMsg)
{
SetDlgItemText(pcs->hwndProgress, IDD_TONAME, pszMsg);
SHFree(pszMsg);
}
}
}
}
void SetProgressTimeEst(COPY_STATE *pcs)
{
TCHAR szFmt[60];
TCHAR szOut[70];
DWORD dwTime;
if (!(pcs->fFlags & FOF_SILENT)) {
pcs->fShowTime = TRUE;
// BUGBUG: how well does this localize?
if (pcs->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 = (pcs->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 = ((pcs->dwTimeLeft+4) / 5) * 5;
}
wsprintf(szOut, szFmt, dwTime);
SetDlgItemText(pcs->hwndProgress, IDD_TIMEEST, szOut);
}
}
void SendProgressMessage(COPY_STATE *pcs, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (!(pcs->fFlags & FOF_SILENT))
SendDlgItemMessage(pcs->hwndProgress, IDD_PROBAR, uMsg, wParam, lParam);
}
// see if this file is loaded by kernel, thus something we don't
// want to fuck with.
//
// pszPath fully qualified path name
//
BOOL IsWindowsFileEx(LPCTSTR pszFile, BOOL bWin32)
{
LPCTSTR pszSpec = PathFindFileName(pszFile);
if (pszSpec)
{
HMODULE hMod = bWin32 ? GetModuleHandle(pszSpec)
: GetModuleHandle16(pszSpec);
if (hMod)
{
TCHAR szModule[MAX_PATH];
bWin32 ? GetModuleFileName(hMod, szModule, ARRAYSIZE(szModule))
: GetModuleFileName16(hMod, szModule, ARRAYSIZE(szModule));
return !lstrcmpi(pszFile, szModule);
}
}
return FALSE;
}
BOOL IsWindowsFile(LPCTSTR pszFile)
{
return(IsWindowsFileEx(pszFile, TRUE) || IsWindowsFileEx(pszFile, FALSE));
}
// verify that we can see the contents of a newly created folder
// this is to deal with the case where net drives have an unknown path
// limit.
//
// assumes:
// folder exists and is empty (newly created)
//
// returns:
// 0 everything is fine
// != 0 dos error or DE_OPCANCELLED
TCHAR const c_szTestFile[] = TEXT("TESTDIR.TMP"); // make this a short name
int VerifyFolderVisible(HWND hwnd, LPCTSTR pszPath)
{
int res = 0;
Assert(PathIsDirectory(pszPath)); // must exist and be a folder
if (PathIsUNC(pszPath) || IsRemoteDrive(DRIVEID(pszPath))) {
TCHAR szTest[MAX_PATH];
HFILE fh;
BOOL bFoundFile = FALSE;
PathCombine(szTest, pszPath, c_szTestFile);
#ifdef UNICODE
fh = (HFILE)CreateFile( szTest,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
0,
NULL);
#else
fh = _lcreat(szTest, 0);
#endif
if (fh != HFILE_ERROR) {
WIN32_FIND_DATA fd;
HANDLE hfind;
_lclose(fh);
PathRemoveFileSpec(szTest); // replace file with "*"
PathAppend(szTest, c_szStar);
hfind = FindFirstFile(szTest, &fd);
if (hfind != INVALID_HANDLE_VALUE) {
do {
if (!lstrcmpi(fd.cFileName, c_szTestFile)) {
bFoundFile = TRUE;
break;
}
} while (FindNextFile(hfind, &fd));
FindClose(hfind);
}
PathRemoveFileSpec(szTest); // rip off "*"
PathAppend(szTest, c_szTestFile); // rebuild the file name
DeleteFile(szTest);
}
if (!bFoundFile) {
PathRemoveFileSpec(szTest);
PathCompactPath(NULL, szTest, GetSystemMetrics(SM_CXSCREEN) / 3);
if (!hwnd ||
ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_CREATELONGDIR),
MAKEINTRESOURCE(IDS_CREATELONGDIRTITLE),
MB_SETFOREGROUND | MB_ICONHAND | MB_YESNO, (LPTSTR)szTest) != IDYES)
{
Win32RemoveDirectory(szTest);
res = DE_OPCANCELLED;
}
}
}
return res;
}
//
// 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
// lpsa security attributes
//
// returns:
// DE_OPCANCELED user canceled through UI
// DE_ last error code
// 0 success
//
int WINAPI SHCreateDirectoryEx(HWND hwnd, LPCTSTR pszPath, LPSECURITY_ATTRIBUTES lpsa)
{
int ret = 0;
if (!Win32CreateDirectory(pszPath, lpsa)) {
TCHAR *pSlash, szTemp[MAX_PATH + 1]; // +1 for PathAddBackslash()
TCHAR *pEnd;
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:
return(ret);
}
lstrcpyn(szTemp, pszPath, ARRAYSIZE(szTemp) - 1);
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
if (pSlash + 1 == pEnd)
ret = Win32CreateDirectory(szTemp, lpsa) ? 0 : GetLastError();
else
ret = Win32CreateDirectory(szTemp, NULL) ? 0 : GetLastError();
}
*pSlash++ = TEXT('\\'); // put the seperator back
}
}
if (ret == 0)
return VerifyFolderVisible(hwnd, pszPath);
else
return ret;
}
int WINAPI SHCreateDirectory(HWND hwnd, LPCTSTR pszPath)
{
return SHCreateDirectoryEx(hwnd, pszPath, NULL);
}
#ifndef COPY_USE_COPYFILEEX
// in:
//
// returns:
BOOL OpenDestFile(LPCTSTR pszDest, HFILE *phf, DWORD dwAttribs)
{
HFILE fh;
// NB Some networks will fail writes if you open the file readonly.
dwAttribs &= ~FILE_ATTRIBUTE_READONLY;
fh = (HFILE)CreateFile(pszDest, GENERIC_WRITE, FILE_SHARE_READ, 0L,
CREATE_ALWAYS, dwAttribs, NULL);
if (GetLastError() == ERROR_ACCESS_DENIED)
{
// 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;
if (SetFileAttributes(pszDest, dwAttributes))
{
fh = (HFILE)CreateFile(pszDest, GENERIC_WRITE, FILE_SHARE_READ, 0L, CREATE_ALWAYS, dwAttribs, NULL);
}
}
}
if (fh == HFILE_ERROR) {
*phf = (HFILE)GetLastError();
return FALSE;
}
*phf = fh;
return TRUE;
}
#endif // COPY_USE_COPYFILEEX
// 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
_fmemset(&nci, 0, SIZEOF(nci));
nci.cbStructure = SIZEOF(nci);
_fmemset(&nr, 0, SIZEOF(nr));
if (PathIsUNC(szPath))
nr.lpRemoteName = szPath;
else
{
// 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] = TEXT('\0'); // Strip off after character and :
nr.lpLocalName = szPath;
}
// dwSpeed is returned by MultinetGetConnectionPerformance
MultinetGetConnectionPerformance(&nr, &nci);
return nci.dwSpeed;
}
// This function determines the size of the copy buffer, depending
// on the speed of the connection. (for slow connections)
//
// in:
// pszSource fully qualified source path (ANSI)
// pszDest fully qualified destination path (ANSI)
//
// returns:
// optimal buffer size (optimized for approximately 1 sec bursts)
// with a maximum size of COPYMAXBUFFERSIZE
UINT SizeFromLinkSpeed(LPCTSTR pszSource, LPCTSTR pszDest)
{
DWORD dwSize, dwSpeed, dwSrc, dwDst;
dwSrc = GetPathSpeed(pszSource);
dwDst = GetPathSpeed(pszDest);
if ((dwSrc == 0) || (dwDst == 0))
{
dwSpeed = dwSrc == 0 ? dwDst : dwSrc;
}
else
{
dwSpeed = min(dwSrc, dwDst);
}
dwSize = (dwSpeed * 100 / 8); // convert 100 bps to bytes for 1 second
// round up to a sector size (512 == 0x200)
dwSize = (dwSize + 511) & ~511;
if (dwSize == 0 || dwSize > COPYMAXBUFFERSIZE)
dwSize = COPYMAXBUFFERSIZE;
DebugMsg(DM_TRACE, TEXT("Copy Size = %d, Copy Speed = %d"), dwSize, dwSpeed);
return dwSize;
}
DWORD CopyCallbackProc( LARGE_INTEGER liTotSize, LARGE_INTEGER liBytes,
LARGE_INTEGER liStreamSize, LARGE_INTEGER liStreamBytes,
DWORD dwStream, DWORD dwCallback,
HANDLE hSource, HANDLE hDest, LPVOID lpv)
{
COPY_STATE *pcs = (COPY_STATE *)lpv;
INT iPercent;
if (liTotSize.QuadPart != 0)
iPercent = (int)((liBytes.QuadPart * 100)/ liTotSize.QuadPart);
else
iPercent = 0;
pcs->dwBytesRead = (DWORD)liBytes.QuadPart;
pcs->dwBytesLeft = (DWORD)(liTotSize.QuadPart - liBytes.QuadPart);
DebugMsg(DM_TRACE, TEXT("CopyCallbackProc[%08lX], totsize=%08lX, bytes=%08lX => %d%%"),
dwCallback, liTotSize.LowPart, liBytes.LowPart, iPercent);
if (FOQueryAbort(pcs))
return PROGRESS_CANCEL;
SetProgressTime(pcs);
SendProgressMessage(pcs, PBM_SETPOS, iPercent, 0);
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->wFunc == FO_MOVE) ? &pcs->wfd.ftCreationTime : NULL,
NULL, &pcs->wfd.ftLastWriteTime);
#ifdef ENABLE_TRACK
//
// For Cairo, preserve the file oid
//
if (g_fNewTrack && pcs->fMove)
{
OBJECTID oid;
OBJECTID *poid;
Tracker_InitCode();
// Move the OFS object ID (oid) on a move operation
poid = RtlQueryObjectId((HANDLE)hSource, &oid) == STATUS_SUCCESS ? &oid : NULL;
if (poid != NULL)
RtlSetObjectId((HANDLE)hDest, poid);
}
#endif
pcs->fInitialize = FALSE;
}
switch(dwCallback)
{
case CALLBACK_STREAM_SWITCH:
break;
case CALLBACK_CHUNK_FINISHED:
break;
default:
break;
}
return PROGRESS_CONTINUE;
}
// This function queues copies. If the queue is full the queue is purged.
//
// in:
// hwnd Window to report things to.
// pszSource fully qualified source path (ANSI)
// pszDest fully qualified destination path (ANSI)
// pfd source file find data (size/date/time/attribs)
//
// returns:
// 0 success
// dos error code for failure
//
UINT FileCopy(COPY_STATE *pcs, LPCTSTR pszSource, LPCTSTR pszDest, const WIN32_FIND_DATA *pfd, BOOL fMove)
{
DWORD dwRead, dwWrite;
HFILE fh;
HFILE hSource = HFILE_ERROR;
HFILE hDest = HFILE_ERROR;
int iLastError;
UINT ret;
BOOL fRetryPath = FALSE;
BOOL fRetryAttr = FALSE;
// BUGBUG: better place to do this?
// initialize state variables for completion time estimate display
pcs->fShowTime = FALSE;
pcs->dwBytesLeft = pfd->nFileSizeLow;
pcs->dwPreviousTime = 0;
pcs->fMove = fMove;
pcs->fInitialize = TRUE;
pcs->wfd = *pfd;
SetProgressText(pcs, pszSource, pszDest);
// Make sure we can start
if (FOQueryAbort(pcs))
return DE_OPCANCELLED;
#ifdef COPY_USE_COPYFILEEX
SendProgressMessage(pcs, PBM_SETRANGE, 0, MAKELONG(0, 100));
//
// Now do the file copy
//
TryCopyAgain:
if (!CopyFileEx(pszSource, pszDest, CopyCallbackProc, pcs, &pcs->bAbort, 0))
{
iLastError = (int)GetLastError();
switch(iLastError)
{
case ERROR_DISK_FULL:
if (!IsRemovableDrive(DRIVEID(pszDest))
|| PathIsSameRoot(pszDest,pszSource)) // used to be !PathIsSameRoot
{
break;
}
iLastError = DE_NODISKSPACE;
// Fall through
case ERROR_PATH_NOT_FOUND:
if (!fRetryPath)
{
// ask the user to stick in another disk or empty wastebasket
iLastError = CopyMoveRetry(pcs, pszDest, iLastError, pfd->nFileSizeLow);
if (!iLastError) {
fRetryPath = TRUE;
goto TryCopyAgain;
}
CopyError(pcs, pszSource, pszDest, (UINT)iLastError | ERRORONDEST, FO_COPY, OPER_DOFILE);
return DE_OPCANCELLED;
}
break;
case ERROR_ACCESS_DENIED:
{
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;
}
}
}
}
break;
}
if (!pcs->bAbort)
CopyError(pcs, pszSource, pszDest, iLastError, FO_COPY, OPER_DOFILE);
return DE_OPCANCELLED; // error already reported
}
#else
// SizeFromLinkSpeed assumes there is a connection already established
if (!pcs->lpCopyBuffer) {
pcs->uSize = SizeFromLinkSpeed(pszSource, pszDest);
// BUGBUG: For wildcard or dir copies/moves, we calculate link speed and
// allocate the buffers for each file!
pcs->lpCopyBuffer = (void*)LocalAlloc(LPTR, pcs->uSize);
if (!pcs->lpCopyBuffer) {
DebugMsg(DM_TRACE, TEXT("insuf. mem for lpCopyBuffer"));
return DE_INSMEM; // memory failure
}
}
// Still ok to continue?
if (FOQueryAbort(pcs))
return DE_OPCANCELLED;
hSource = (HFILE)CreateFile(pszSource, GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, 0L, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (hSource == HFILE_ERROR) {
CopyError(pcs, pszSource, pszDest, (int)GetLastError(), FO_COPY, OPER_DOFILE);
return DE_OPCANCELLED; // error already reported
}
// open destination files
if (FOQueryAbort(pcs))
goto CloseSource;
SendProgressMessage(pcs, PBM_SETRANGE, 0, MAKELONG(0, (WORD)((pfd->nFileSizeLow + pcs->uSize - 1) / pcs->uSize)));
TryOpen:
if (!OpenDestFile(pszDest, &hDest, pfd->dwFileAttributes)) {
// error operning/creating destinaton file
fh = hDest;
if (fh == ERROR_PATH_NOT_FOUND) {
TryOpenDestAgain:
// ask the user to stick in another disk or empty wastebasket
fh = CopyMoveRetry(pcs, pszDest, fh, pfd->nFileSizeLow);
if (!fh) {
goto TryOpen;
}
}
// can't recover ... bail!
CopyError(pcs, pszSource, pszDest, (UINT)fh | ERRORONDEST, FO_COPY, OPER_DOFILE);
goto CloseSource;
}
dwRead = (DWORD)pcs->uSize;
// initialzie the file to the full size
// this takes 3 dos calls, so only do it if the file is big
if (pfd->nFileSizeLow > (COPYMAXBUFFERSIZE * 3)) {
// if there's a problem, bail
if ((_llseek(hDest, pfd->nFileSizeLow, 0L) == HFILE_ERROR) ||
(!SetEndOfFile((HANDLE)hDest))) {
iLastError = GetLastError();
goto ErrorOnWrite;
} else {
_llseek(hDest, 0, 0L);
}
}
/* Now copy between the open files */
do {
iLastError = 0;
if (FOQueryAbort(pcs))
goto OpCancelled;
SetProgressTime(pcs);
//dwRead = _lread(hSource, pcs->lpCopyBuffer, pcs->uSize);
if (! ReadFile((HANDLE)hSource, pcs->lpCopyBuffer, pcs->uSize, &dwRead, NULL)) {
// Error during file read
CopyError(pcs, pszSource, pszDest, (int)GetLastError(), FO_COPY, OPER_DOFILE);
goto OpCancelled;
}
SendProgressMessage(pcs, PBM_DELTAPOS, 1, 0);
//wWrite = _lwrite(hDest, pcs->lpCopyBuffer, wRead);
if (! WriteFile((HANDLE)hDest, pcs->lpCopyBuffer, dwRead, &dwWrite, NULL))
dwWrite = (DWORD)-1;
// write did not complete and removable drive?
if (dwRead != dwWrite) {
iLastError = GetLastError();
#ifndef WRITEFILE_SETSLASTERROR_ON_DISKFULL
// if no error set and we couldn't write, assume disk full
if (!iLastError)
iLastError = DE_NODISKSPACE;
#endif
}
ErrorOnWrite:
if ((iLastError == DE_NODISKSPACE) && IsRemovableDrive(DRIVEID(pszDest)) &&
!PathIsSameRoot(pszDest, pszSource)) {
// seek back to the start of the source.
_llseek(hSource, 0L, 0);
// destination disk must be full. close all
// destination files and delete those that
// have not been copied yet then
// give the user the option to insert a new disk.
_lclose(hDest);
hDest = (HFILE)-1;
Win32DeleteFile(pszDest);
fh = DE_NODISKSPACE;
goto TryOpenDestAgain; // and try to create the destiations
} else if (iLastError) {
// error writing file
CopyError(pcs, pszSource, pszDest, (int)iLastError | ERRORONDEST, FO_COPY, OPER_DOFILE);
goto OpCancelled;
}
// Reduce by ammount copied
pcs->dwBytesLeft -= dwRead;
// Add to so far read pile
pcs->dwBytesRead += dwRead;
} while (dwRead && pcs->dwBytesLeft);
// Close all destination files, set date time attribs
// 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->wFunc == FO_MOVE) ? &pfd->ftCreationTime : NULL,
NULL, &pfd->ftLastWriteTime);
_lclose(hDest);
// NB We may have opened the destination with different attributes than the source
// so reset them now.
if (pfd->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
SetFileAttributes(pszDest, pfd->dwFileAttributes);
_lclose(hSource);
#endif
SendProgressMessage(pcs, PBM_SETPOS, 0, 0);
// if it was a long copy, beep to alert the user it finished!
if (pcs->fShowTime)
{
// don't beep here because people think it's an error.
// MessageBeep(0);
if (!(pcs->fFlags & FOF_SILENT))
SetDlgItemText(pcs->hwndProgress, IDD_TIMEEST, szNULL);
}
SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, pszDest, NULL);
if (fMove)
{
// BUGBUG, will this fail if source attribs are readonly?
ret = Win32DeleteFile(pszSource) ? 0 : GetLastError();
if (ret == ERROR_ACCESS_DENIED)
{
// We may need to make the file not read-only
SetFileAttributes(pszSource, FILE_ATTRIBUTE_NORMAL);
ret = Win32DeleteFile(pszSource) ? 0 : GetLastError();
}
}
else
{
ret = 0;
}
return ret;
#ifndef COPY_USE_COPYFILEEX
OpCancelled:
if (hDest != HFILE_ERROR)
_lclose(hDest);
Win32DeleteFile(pszDest);
CloseSource:
if (hSource != HFILE_ERROR)
_lclose(hSource);
if (pcs->lpCopyBuffer) {
LocalFree((HLOCAL)pcs->lpCopyBuffer);
pcs->lpCopyBuffer = NULL;
}
return DE_OPCANCELLED;
#endif // COPY_USE_COPYFILEEX
}
// note: this is a very slow call
DWORD GetFreeClusters(LPCTSTR szPath)
{
DWORD dwFreeClus;
DWORD dwTemp;
if (GetDiskFreeSpace((LPTSTR)szPath,
&dwTemp, // Don't care
&dwTemp, // Don't care
&dwFreeClus,
&dwTemp)) // Don't care
return dwFreeClus;
else
return (DWORD)-1;
}
// note: this is a very slow call
DWORD TotalCapacity(LPCTSTR szPath)
{
DWORD dwSecPerClus, dwBytesPerSec, dwClusters;
int idDrive;
idDrive = PathGetDriveNumber(szPath);
if (idDrive != -1) {
DWORD dwTemp;
TCHAR szDrive[5];
PathBuildRoot(szDrive, idDrive);
if (GetDiskFreeSpace((LPTSTR)szDrive, &dwSecPerClus, &dwBytesPerSec, &dwTemp, &dwClusters))
return dwSecPerClus * dwBytesPerSec * dwClusters;
}
return 0;
}
//
// 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; // Extended error.
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 == DE_OPCANCELLED) // 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
PathCompactPath(NULL, szFile, GetSystemMetrics(SM_CXSCREEN) / 3);
}
// Get an extended error.
dwError = GetLastError();
// get the verb string
// since we now recycle folders as well as files, added OPER_ENTERDIR chekc 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) {
// BUGBUG:: 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 = DE_NODISKSPACE;
else if (dwError == DE_WRITEFAULT)
nError = DE_WRITEFAULT;
} 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 DE_CRCDATAERROR:
case DE_SEEKERROR:
case DE_SECTORNOTFOUND:
case DE_READFAULT:
case ERROR_GEN_FAILURE:
nError = ERROR_GEN_FAILURE;
break;
case DE_SHARINGVIOLATION:
nError = DE_ACCESSDENIEDSRC;
break;
}
}
}
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, (LPTSTR)PathFindFileName(szFile),
MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
} else {
ShellMessageBox(HINST_THISDLL, pcs->hwndDlgParent, MAKEINTRESOURCE(idVerb), MAKEINTRESOURCE(IDS_FILEERROR + wFunc), MB_OK | MB_ICONSTOP | MB_SETFOREGROUND, (LPTSTR) szReason, (LPTSTR)PathFindFileName(szFile));
}
}
//
// 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.
//
// NOTE: the destination drive must either be removable or have the recycle.bin
// on it or this function does not make a whole lot of sense.
//
// parameters:
// pszDest Fully qualified path to destination file (ANSI)
// nError type of error: DE_NODISKSPACE or ERROR_PATH_NOT_FOUND
// dwFileSize amount of space needed for this file if DE_NODISKSPACE
//
// returns:
// 0 success (destination path has been created)
// != 0 dos error code including DE_OPCANCELLED
//
int CopyMoveRetry(COPY_STATE *pcs, LPCTSTR pszDest, int nError, DWORD dwFileSize)
{
UINT wFlags;
int result;
LPCTSTR wID;
TCHAR szTemp[MAX_PATH];
BOOL fFirstRetry = TRUE;
if (pcs->fFlags & FOF_NOERRORUI) {
result = DE_OPCANCELLED;
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 // DE_NODISKSPACE
{
wFlags = MB_ICONEXCLAMATION | MB_RETRYCANCEL;
if (dwFileSize > TotalCapacity(pszDest))
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->wFunc), wFlags, (LPTSTR)szTemp);
} else {
result = IDYES;
}
pcs->dwStartTime = GetTickCount(); // reset the timer after wait for UI
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 (!DiskCheck(pcs, pcs->hwndDlgParent, pszDest, FO_COPY))
return DE_OPCANCELLED;
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 == DE_OPCANCELLED)
goto ErrorExit;
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 = DE_OPCANCELLED;
goto ErrorExit;
}
} while (result);
ErrorExit:
return result; // success
}
BOOL ValidFilenames(LPCTSTR pList)
{
for (; *pList; pList += lstrlen(pList) + 1) {
if (IsInvalidPath(pList))
return FALSE;
}
return TRUE;
}
void AddRenamePairToHDSA(LPCTSTR pszOldPath, LPCTSTR pszNewPath, HDSA* phdsaRenamePairs)
{
//
// Update our collision mapping table, for use by GetNextPair().
//
if (!*phdsaRenamePairs)
*phdsaRenamePairs = DSA_Create(SIZEOF(SHNAMEMAPPING), 4);
if (*phdsaRenamePairs)
{
SHNAMEMAPPING rp;
rp.cchOldPath = lstrlen(pszOldPath);
rp.cchNewPath = lstrlen(pszNewPath);
if (NULL != (rp.pszOldPath = Alloc((rp.cchOldPath + 1) * SIZEOF(TCHAR))))
{
if (NULL != (rp.pszNewPath = Alloc((rp.cchNewPath + 1) * SIZEOF(TCHAR))))
{
lstrcpy(rp.pszOldPath, pszOldPath);
lstrcpy(rp.pszNewPath, pszNewPath);
if (DSA_InsertItem(*phdsaRenamePairs,
DSA_GetItemCount(*phdsaRenamePairs),
&rp) == -1)
{
Free(rp.pszOldPath);
Free(rp.pszNewPath);
}
}
else
{
Free(rp.pszOldPath);
}
}
}
}
BOOL _HandleRename(LPCTSTR pszSource, LPTSTR pszDest, FILEOP_FLAGS fFlags, COPYROOT * pcr, HDSA * phdsaRenamePairs)
{
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)
{
lstrcpyn(lpszLongPlate + lstrlen(lpszLongPlate),
pszConflictingName,
MAX_PATH - ichFixed - lstrlen(pszExt));
lstrcat(lpszLongPlate, pszExt);
}
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, phdsaRenamePairs);
return(TRUE);
}
else
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 (IsWild(pInput))
return -1;
}
return count;
}
// set the attribs of a folder, but blow it off if there are no
// special attributes set
void SetDirAttributes(LPCTSTR szDest, DWORD dwFileAttributes)
{
if (dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN))
SetFileAttributes(szDest, dwFileAttributes & ~FILE_ATTRIBUTE_DIRECTORY);
}
TCHAR const c_szDblSpace[] = TEXT("DBLSPACE");
TCHAR const c_szDrvSpace[] = TEXT("DRVSPACE");
#define ISDIGIT(c) ((c) >= TEXT('0') && (c) <= TEXT('9'))
BOOL IsCompressedVolume(LPCTSTR szSource, DWORD dwAttributes)
{
int i;
LPTSTR lpszFileName;
LPTSTR lpszExtension;
TCHAR szPath[MAX_PATH];
// must be marked system and hidden
if ((dwAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN)) !=
(FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN)) {
return FALSE;
}
lstrcpy(szPath, szSource);
lpszFileName = PathFindFileName(szPath);
lpszExtension = PathFindExtension(lpszFileName);
// make sure the extension is a 3 digit number
if (!*lpszExtension || *lpszExtension != TEXT('.')) {
return FALSE;
}
for (i = 1; i < 4; i++) {
if (!lpszExtension[i] || !ISDIGIT(lpszExtension[i])) {
return FALSE;
}
}
// make sure it's null terminated here
if (lpszExtension[4]) {
return FALSE;
}
// now knock off the extension and make sure the stem matches
*lpszExtension = 0;
if (lstrcmpi(lpszFileName, c_szDrvSpace) &&
lstrcmpi(lpszFileName, c_szDblSpace)) {
return FALSE;
}
// make sure it's in the root
PathRemoveFileSpec(szPath);
if (!PathIsRoot(szPath)) {
return FALSE;
}
// passed all tests!
return TRUE;
}
int AllConfirmations(COPY_STATE *pcs, COPYROOT *pcr, WIN32_FIND_DATA *pfd, UINT oper, UINT wFunc,
LPTSTR szSource, LPTSTR szDest,
WIN32_FIND_DATA *pfinddata_dest, 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 = pfinddata_dest;
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 (!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, 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 {
fShowConfirm = TRUE;
pfdUse2 = pfinddata_dest;
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 = pfinddata_dest;
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 if (IsWindowsFile(szSource)) {
CopyError(pcs, szSource, szDest, DE_WINDOWSFILE, wFunc, oper);
result = IDNO;
} else {
fShowConfirm = TRUE;
szDest = NULL;
pfdUse2 = pfd;
fConfirm = CONFIRM_DELETE_FILE;
}
break;
}
if (fShowConfirm) {
result = ConfirmFileOp(pcs->hwndDlgParent, pcs, &pcs->cd, pcs->nSourceFiles, pcr->cDepth, fConfirm,
szSource, pfdUse1, szDest, pfdUse2);
}
// 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, pfinddata_dest->dwFileAttributes);
}
if ((result != IDCANCEL) && (result != IDNO) && fSetProgress)
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 lpszSource, LPTSTR lpszDest)
{
TCHAR szShortSrc[MAX_PATH];
TCHAR szShortDest[MAX_PATH];
GetShortPathName(lpszSource, szShortSrc, ARRAYSIZE(szShortSrc));
GetShortPathName(lpszDest, szShortDest, ARRAYSIZE(szShortSrc));
return !lstrcmpi(szShortSrc, szShortDest);
}
// 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(DM_TRACE, 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"
// BUGBUG, 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(DM_TRACE, 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, (LPTSTR)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
{
//
// We've now created an empty dir entry of this name type.
//
Win32DeleteFile(lpszDest);
}
DebugMsg(DM_TRACE, TEXT("ResolveShortNameCollision: %s = original, %s = destination,\n %s = temp file, %d = return"), szLongName, lpszDest, szTemp, fRet);
}
}
return fRet;
}
// actually this does move/copy/rename/delete
int MoveCopyDriver(COPY_STATE *pcs, LPSHFILEOPSTRUCT lpfo)
{
int result, ret;
LPTSTR p = NULL;
BOOL fMultiDest = FALSE;
UINT oper; // operation being performed
TCHAR szDestPath[MAX_PATH];
TCHAR szDest[MAX_PATH]; // Dest file (ANSI string)
TCHAR szSource[MAX_PATH]; // Source file (ANSI string)
WIN32_FIND_DATA *pfd, finddata_dest;
COPYROOT * pcr = NULL; // Structure for searching source tree
HDSA hdsaRenamePairs = NULL;
szDest[0] = szSource[0] = 0;
ret = 0;
oper = 0;
if (!ValidFilenames(lpfo->pFrom)) {
CopyError(pcs, szSource, szDest, DE_INVALIDFILES, lpfo->wFunc, 0);
return ERROR_ACCESS_DENIED;
}
/* Allocate buffer for searching the source tree */
pcr = (void*)LocalAlloc(LPTR, SIZEOF(COPYROOT));
if (!pcr)
{
CopyError(pcs, szSource, szDest, DE_INSMEM, lpfo->wFunc, 0);
DebugMsg(DM_TRACE, TEXT("MoveCopyDriver(), insuf. mem. for COPYROOT"));
return DE_INSMEM;
}
pcr->wFunc = lpfo->wFunc;
pcr->pSource = lpfo->pFrom;
pcr->pDest = lpfo->pTo;
pcr->fFlags = pcs->fFlags;
pcs->nSourceFiles = CountFiles(lpfo->pFrom); // multiple source files?
if ((pcr->wFunc != FO_COPY) && (pcs->nSourceFiles > 1))
{
pcs->fNonCopyProgress = TRUE;
// unhide the progress bar if we can
ShowWindow(GetDlgItem(pcs->hwndProgress, IDD_PROBAR), SW_SHOW);
SendProgressMessage(pcs, PBM_SETRANGE, 0, MAKELONG(0, pcs->nSourceFiles));
}
// skip destination processing if we are deleting files
if (pcr->wFunc != FO_DELETE) {
lstrcpyn(szDestPath, lpfo->pTo, ARRAYSIZE(szDestPath));
if (!szDestPath[0]) { // NULL dest is same as "."
szDestPath[0] = TEXT('.');
szDestPath[1] = 0;
}
if (IsInvalidPath(szDestPath)) {
CopyError(pcs, szSource, szDest, DE_INVALIDFILES | ERRORONDEST, pcr->wFunc, 0);
ret = ERROR_ACCESS_DENIED;
goto ExitLoop;
}
if (pcr->wFunc == FO_RENAME) {
// don't let them rename multiple files to one single file
if ((pcs->nSourceFiles != 1) && !IsWild(szDestPath)) {
CopyError(pcs, szSource, szDest, DE_MANYSRC1DEST, pcr->wFunc, 0);
ret = DE_MANYSRC1DEST;
goto ExitLoop;
}
} else { // FO_COPY or FO_MOVE at this point
fMultiDest = ((pcs->fFlags & FOF_MULTIDESTFILES) &&
(pcs->nSourceFiles == CountFiles(lpfo->pTo)));
if (!fMultiDest) {
// this is to make sure the PathIsDirectory() call below
// returns the right results in case the dest disk is
// not in place
if (!PCRDiskCheck(pcr, pcs, pcs->hwndDlgParent, szDestPath, pcr->wFunc))
goto ExitLoop;
// deal with case where directory is implicit in source
// move/copy: *.* -> c:\windows, c:\windows -> c:\temp
// or foo.bar -> c:\temp
if (!IsWild(szDestPath) && ((pcs->nSourceFiles != 1) || PathIsDirectory(szDestPath))) {
PathAppend(szDestPath, c_szStarDotStar);
}
}
}
/* FO_RENAME or FO_MOVE FO_COPY with a file name dest
(possibly including wildcards). Save the filespec and the path
part of the destination */
// if there are multiple destinations, we'll do this stuff at
// getnextpair time
if (!fMultiDest) {
// break szDestPath into path and spec parts
p = PathFindFileName(szDestPath);
*(p - 1) = 0;
}
}
pcr->fMultiDest = fMultiDest;
pcr->pDestPath = szDestPath;
pcr->pDestSpec = p;
for (;;) {
Assert(pcr->wFunc == lpfo->wFunc);
oper = GetNextPair(pcr, pcs, szSource, szDest, &hdsaRenamePairs);
if (!oper) { // all done?
LocalFree((HLOCAL)pcr);
pcr = NULL;
break;
}
// do this after the GetNextPair.
// this prevents us from flashing up the progress dialog real quick only
// to nuke it because we're done.
if (FOQueryAbort(pcs))
goto ExitLoop;
if (!pcr->cDepth)
pcs->fMerge = FALSE;
if ((oper & OPER_MASK) == OPER_ERROR) {
CopyError(pcs, szSource, szDest, LOBYTE(oper), pcr->wFunc, OPER_DOFILE);
goto ExitLoop;
}
pfd = &pcr->finddata;
DebugMsg(DM_TRACE, TEXT("MoveCopyDriver(): From(%s) To(%s)"), (LPCTSTR)szSource, (LPCTSTR)szDest);
// some operation that may effect the destination (have a collision)
if ((pcr->wFunc != FO_DELETE) && (oper != OPER_LEAVEDIR))
{
// this compare needs to be case sensitive
if (!lstrcmp(szSource, szDest) &&
!(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.
ret = DE_SAMEFILE;
goto ShowMessageBox;
}
/* Check to see if we are overwriting an existing file or
directory. If so, better confirm */
finddata_dest.dwFileAttributes = 0;
// we only have a potential for collision of we're at the top level or doing a merge
if ((pcs->fMerge || !pcr->cDepth) &&
(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(szDest, &finddata_dest)) != INVALID_HANDLE_VALUE)
{
FindClose(hfindT);
if (pcr->wFunc != FO_RENAME || !SameFile(szSource, szDest))
{
if (!ResolveShortNameCollisions(szDest, &finddata_dest)) {
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.
if (!_HandleRename(szSource, szDest, pcs->fFlags, pcr, &hdsaRenamePairs))
{
// Couldn't do it, so skip this file.
continue;
}
}
else
{
if (pcr->wFunc == FO_RENAME) {
ret = DE_RENAMREPLACE;
goto ShowMessageBox;
}
if (IsWindowsFile(szDest)) {
CopyError(pcs, szSource, szDest, DE_WINDOWSFILE | ERRORONDEST, pcr->wFunc, oper);
continue;
}
// 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.
result = ConfirmFileOp(pcs->hwndDlgParent, pcs, &pcs->cd, pcs->nSourceFiles, pcr->cDepth, CONFIRM_REPLACE_FILE, szSource, pfd, szDest, &finddata_dest);
switch (result) {
case IDYES:
if ((pcr->wFunc == FO_MOVE) && (PathIsSameRoot(szSource, szDest))) {
// For FO_MOVE we need to delete the
// destination first. Do that now.
// bugbug, this replace options should be undable
ret = Win32DeleteFile(szDest) ? 0 : GetLastError();
if (ret) {
ret |= ERRORONDEST;
goto ShowMessageBox;
}
}
if (pcs->lpua)
FOUndo_Release(pcs->lpua);
break;
case IDNO:
lpfo->fAnyOperationsAborted = TRUE;
continue;
case IDCANCEL:
lpfo->fAnyOperationsAborted = TRUE;
goto ExitLoop;
default:
ret = result;
goto ShowMessageBox;
}
}
}
}
}
}
} // pcr->wFunc != FO_DELETE
result = AllConfirmations(pcs, pcr, pfd, oper, pcr->wFunc, szSource, szDest, &finddata_dest, &ret);
switch (result) {
case IDNO:
if (oper == OPER_ENTERDIR)
PuntCurrentDirPair(pcr); // so we don't recurse down this folder
/* set attributes of dest to those of the source */
SetDirAttributes(szDest, pfd->dwFileAttributes);
lpfo->fAnyOperationsAborted = TRUE;
continue;
case IDCANCEL:
lpfo->fAnyOperationsAborted = TRUE;
pcs->bAbort = TRUE;
goto ExitLoop;
case IDYES:
break;
default:
ret = result;
goto ShowMessageBox;
}
/* Now determine which operation to perform */
switch (oper | pcr->wFunc) {
// Note that ENTERDIR is not done for a root, even though LEAVEDIR is
case OPER_ENTERDIR | FO_MOVE: // Create dest, verify source delete
// use new VFAT move folder code!
// if these are in the same drive, try using MoveFile on it.
// if that fails then fail through to the copy
if (PathIsSameRoot(szSource, szDest))
{
AvoidCurrentDirectory(szSource);
MoveTryAgain:
ret = Win32MoveFile(szSource, szDest, TRUE) ? 0 : GetLastError();
if (!ret) { // success!
DebugMsg(DM_TRACE, TEXT("Move Folder worked!"));
PuntCurrentDirPair(pcr); // so we don't recurse down this folder
/* set attributes of dest to those of the source */
SetDirAttributes(szDest, pfd->dwFileAttributes);
// add to the undo atom
if (pcs->lpua && !pcr->cDepth)
FOUndo_AddInfo(pcs->lpua, szSource, szDest, 0);
break; // all done here!
} else if (ret == ERROR_PATH_NOT_FOUND) {
ret = CopyMoveRetry(pcs, szDest, ret, 0);
if (!ret)
goto MoveTryAgain;
}
}
// fall through and do the normal (slow) move folder stuff...
case OPER_ENTERDIR | FO_COPY: // Create destination directory
Mkdir_TryAgain:
ret = SHCreateDirectory(pcs->hwndDlgParent, szDest);
switch (ret) {
case 0: // successful folder creation (or it already exists)
// propogate the attributes (if there are any)
SetDirAttributes(szDest, pfd->dwFileAttributes);
// add to the undo atom
if (pcs->lpua) {
DebugMsg(DM_TRACE, TEXT("Copy Undo stuff. %d"), pcr->cDepth);
if (!pcr->cDepth)
FOUndo_AddInfo(pcs->lpua, szSource, szDest, 0);
}
break;
case ERROR_DISK_FULL:
case ERROR_ALREADY_EXISTS:
case ERROR_ACCESS_DENIED:
{
DWORD dwFileAttributes = GetFileAttributes(szDest);
if (dwFileAttributes == (DWORD)-1)
{
// The dir does not exist, so it looks like a problem
// with a read-only drive or disk full
// BUGBUG: replace GetFreeClusters with a proper check of the error return value
if (IsRemovableDrive(DRIVEID(szDest)) && !PathIsSameRoot(szDest, szSource) &&
(ret == ERROR_DISK_FULL))
{
ret = CopyMoveRetry(pcs, szDest, DE_NODISKSPACE, 0);
if (!ret)
goto Mkdir_TryAgain;
else
goto ExitLoop;
} else {
CopyError(pcs, szSource, szDest, ERROR_ACCESS_DENIED | ERRORONDEST, FO_COPY, OPER_DOFILE);
goto ExitLoop;
}
}
if (!(dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
// A file with this name already exists
CopyError(pcs, szSource, szDest, DE_FLDDESTISFILE | ERRORONDEST, FO_COPY, OPER_DOFILE);
goto ExitLoop;
}
result = ConfirmFileOp(pcs->hwndDlgParent, pcs, &pcs->cd, pcs->nSourceFiles, pcr->cDepth, CONFIRM_REPLACE_FOLDER, szSource, pfd, szDest, &finddata_dest);
switch (result) {
case IDYES:
ret = 0; // convert to no error
pcs->fMerge = TRUE;
if (pcs->lpua)
FOUndo_Release(pcs->lpua);
break;
case IDNO:
PuntCurrentDirPair(pcr); // so we don't recurse down this folder
lpfo->fAnyOperationsAborted = TRUE;
ret=0; // Don't put up error message on this one...
continue;
case IDCANCEL:
lpfo->fAnyOperationsAborted = TRUE;
goto ExitLoop;
default:
ret = result;
goto ShowMessageBox;
}
break;
}
case DE_OPCANCELLED:
lpfo->fAnyOperationsAborted = TRUE;
goto ExitLoop;
default: // ret != 0 (dos error code)
ret |= ERRORONDEST;
break;
}
break;
case OPER_LEAVEDIR | FO_MOVE:
case OPER_LEAVEDIR | FO_DELETE:
// SetProgressText(pcs, szSource, szNULL);
if (PathIsRoot(szSource))
break;
AvoidCurrentDirectory(szSource);
// We already confirmed the delete at MKDIR time, so attempt
// to delete the directory
// bugbug, do we really want to support rmdir to bitbucket?
ret = Win32RemoveDirectory(szSource) ? 0 : GetLastError();
if (!ret) {
FOUndo_FileReallyDeleted(szSource);
}
break;
case OPER_ENTERDIR | FO_DELETE:
if (pcr->cDepth == 0 && pcs->lpua && BBDeleteFile(szSource, &ret, pcs->lpua, TRUE, pfd))
{
pcr->bGoIntoDir = FALSE; // Don't go into the thing.
}
break;
case OPER_LEAVEDIR | FO_COPY:
break;
case OPER_DOFILE | FO_COPY:
if (IsWindowsFile(szDest)) {
CopyError(pcs, szSource, szDest, DE_WINDOWSFILE | ERRORONDEST, pcr->wFunc, oper);
continue;
}
TRY_COPY_AGAIN:
/* 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. */
ret = FileCopy(pcs, szSource, szDest, pfd, FALSE);
if (ret == DE_OPCANCELLED)
goto ExitLoop;
if ((((ret & ~ERRORONDEST) == DE_NODISKSPACE) &&
IsRemovableDrive(DRIVEID(szDest))) ||
((ret & ~ERRORONDEST) == ERROR_PATH_NOT_FOUND))
{
ret = CopyMoveRetry(pcs, szDest, ret & ~ERRORONDEST, pfd->nFileSizeLow);
if (!ret)
goto TRY_COPY_AGAIN;
else
goto ExitLoop;
}
// 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) {
DebugMsg(DM_TRACE, TEXT("Copy Undo stuff. %d"), pcr->cDepth);
if (!pcr->cDepth)
FOUndo_AddInfo(pcs->lpua, szSource, szDest, 0);
}
// if we copied in a new desktop ini, send out an update event for the paretn
if (!lstrcmpi(PathFindFileName(szDest), c_szDesktopIni)) {
// warning.. this munges szDest.. but we don't need it anymore
// in this loop.
PathRemoveFileSpec(szDest);
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, szDest, NULL);
}
break;
case OPER_DOFILE | FO_RENAME:
/* Get raw source and dest paths. Check to make sure the
paths are the same */
p = PathFindFileName(szSource);
ret = !IntlStrEqNI(szSource, szDest, p - szSource);
if (ret) {
ret = DE_DIFFDIR;
break;
}
goto DoMoveRename;
case OPER_DOFILE | FO_MOVE:
DoMoveRename:
if (PathIsRoot(szSource)) {
ret = DE_ROOTDIR;
break;
}
if (PathIsRoot(szDest)) {
ret = DE_ROOTDIR | ERRORONDEST;
break;
}
AvoidCurrentDirectory(szSource);
if (IsWindowsFile(szSource))
{
CopyError(pcs, szSource, szDest, DE_WINDOWSFILE, pcr->wFunc, oper);
continue;
}
else
{
if (PathIsSameRoot(szSource, szDest))
{
TryAgain:
ret = Win32MoveFile(szSource, szDest, ISDIRFINDDATA(*pfd)) ? 0 : GetLastError();
// try to create the destination if it is not there
if (ret == ERROR_PATH_NOT_FOUND)
{
ret = CopyMoveRetry(pcs, szDest, ret, 0);
if (!ret)
goto TryAgain;
}
if (!ret) {
// SUCCESS!
/* set attributes of dest to those of the source */
//SetFileAttributes(szDest, pfd->dwFileAttributes);
// add to the undo atom
if (pcs->lpua && !pcr->cDepth)
FOUndo_AddInfo(pcs->lpua, szSource, szDest, 0);
// if we copied in a new desktop ini, send out an update event for the paretn
if (!lstrcmpi(PathFindFileName(szDest), c_szDesktopIni)) {
// warning.. this munges szDest.. but we don't need it anymore
// in this loop.
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
ret = FileCopy(pcs, szSource, szDest, pfd, TRUE);
if (!ret)
{
// add to the undo atom
if (pcs->lpua) {
DebugMsg(DM_TRACE, TEXT("Move Undo stuff. %d"), pcr->cDepth);
if (!pcr->cDepth)
FOUndo_AddInfo(pcs->lpua, szSource, szDest, 0);
}
// if we copied in a new desktop ini, send out an update event for the paretn
if (!lstrcmpi(PathFindFileName(szDest), c_szDesktopIni)) {
// warning.. this munges szDest.. but we don't need it anymore
// in this loop.
PathRemoveFileSpec(szDest);
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, szDest, NULL);
}
}
if (ret == DE_OPCANCELLED)
goto ExitLoop;
}
}
break;
case OPER_DOFILE | FO_DELETE:
if (pcr->cDepth != 0 || !pcs->lpua || !BBDeleteFile(szSource, &ret, pcs->lpua, ISDIRFINDDATA(*pfd), pfd))
{
ret = Win32DeleteFile(szSource) ? 0 : GetLastError();
if (!ret) {
FOUndo_FileReallyDeleted(szSource);
}
}
break;
default:
DebugMsg(DM_ERROR, TEXT("Invalid file operation"));
ret = 0; // internal error
break;
} // switch (oper | pcr->wFunc)
if (ret) { // any errors?
ShowMessageBox:
CopyError(pcs, szSource, szDest, ret, pcr->wFunc, oper);
goto ExitLoop;
}
if ((oper == OPER_DOFILE) && pcs->fNonCopyProgress)
{
Assert(pcr->wFunc != FO_COPY);
SendProgressMessage(pcs, PBM_DELTAPOS, 1, 0);
}
} // while(pcr)
ExitLoop:
// this happens in error cases where we broke out of the pcr loop
// without hitting the end
if (pcr)
{
GetNextCleanup(pcr);
LocalFree((HLOCAL)pcr);
pcr = NULL;
}
lpfo->hNameMappings = hdsaRenamePairs;
pcs->dwStartTime = GetTickCount(); // reset the timer after wait for UI
return ret;
}
void SetWindowTextFromRes(HWND hwnd, int id)
{
TCHAR szTemp[80];
LoadString(HINST_THISDLL, id, szTemp, ARRAYSIZE(szTemp));
SetWindowText(hwnd, szTemp);
}
BOOL CALLBACK FOFProgressDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
COPY_STATE *pcs = (COPY_STATE *)GetWindowLong(hDlg, DWL_USER);
switch (wMsg) {
case WM_INITDIALOG:
SetWindowLong(hDlg, DWL_USER, lParam);
pcs = (COPY_STATE *)lParam;
SetWindowTextFromRes(hDlg, IDS_ACTIONTITLE + pcs->wFunc);
if (pcs->wFunc != FO_COPY)
// note, other code unhides this if needed
ShowWindow(GetDlgItem(hDlg, IDD_PROBAR), SW_HIDE);
break;
case WM_SHOWWINDOW:
if (wParam)
{
int idAni;
Assert(pcs->wFunc >= FO_MOVE && pcs->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 (pcs->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
default:
idAni = (IDA_FILEMOVE + (int)pcs->wFunc - FO_MOVE);
}
Animate_Open(GetDlgItem(pcs->hwndProgress,IDD_ANIMATE), idAni);
}
break;
case WM_ENABLE:
PauseAnimation(pcs, wParam == 0);
break;
case WM_COMMAND:
switch (GET_WM_COMMAND_ID(wParam, lParam)) {
case IDCANCEL:
pcs->bAbort = TRUE;
break;
}
break;
case WM_APP:
// Make sure this window is shown before telling the user there
// is a problem
if (!pcs->bAbort)
ShowProgressWindow(pcs);
ShellMessageBox(HINST_THISDLL, hDlg, MAKEINTRESOURCE(IDS_CANTSHUTDOWN),
NULL, MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
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, WM_APP, 0, 0);
// Make sure the dialog box procedure returns FALSE
SetWindowLong(hDlg, DWL_MSGRESULT, FALSE);
return(TRUE);
default:
return FALSE;
}
return TRUE;
}
int CALLBACK FOUndo_FileReallyDeletedCallback(LPUNDOATOM lpua, LPARAM lParam)
{
LPTSTR * ppsz = (LPTSTR*)lParam;
int i, iMax;
// 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;
LPTSTR lpsz;
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, *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(LPUNDOATOM lpua, LPARAM lParam)
{
LPTSTR psz = (LPTSTR)lParam;
int i, iMax;
switch (lpua->uType) {
case IDS_DELETE: {
LPFOUNDODATA lpud = (LPFOUNDODATA)lpua->lpData;
HDPA hdpa = lpud->hdpa;
LPTSTR lpsz;
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(LPTSTR lpszFile)
{
EnumUndoAtoms(FOUndo_FileRestoredCallback, (LPARAM)lpszFile);
}
void FOUndo_AddInfo(LPUNDOATOM lpua, LPTSTR lpszSrc, LPTSTR lpszDest, DWORD dwAttributes)
{
HDPA hdpa;
LPTSTR lpsz = NULL;
int i;
LPFOUNDODATA lpud;
if (lpua->lpData == (LPVOID)-1)
return;
if (!lpua->lpData) {
lpua->lpData = Alloc(SIZEOF(FOUNDODATA));
if (!lpua->lpData)
return;
((LPFOUNDODATA)lpua->lpData)->hdpa = (LPVOID)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_InsertItem(lpud->hdsa, 0x7FFF, &dfi);
} else {
Str_SetPtr(&lpsz, lpszSrc);
if (!lpsz)
return;
if ((i = DPA_InsertPtr(hdpa, 0x7FFF, lpsz)) == -1)
{
return;
}
lpsz = NULL;
Str_SetPtr(&lpsz, lpszDest);
if (!lpsz ||
DPA_InsertPtr(hdpa, 0x7FFF, 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) {
lpsz = DPA_GetPtr(hdpa, i);
ichTemp = ichSize - 1;
ichSize += (lstrlen(lpsz) + 1);
lpszReturn = (LPTSTR)(void*)LocalReAlloc((HLOCAL)lpszReturn, ichSize * SIZEOF(TCHAR),
LMEM_MOVEABLE|LMEM_ZEROINIT);
if (!lpszReturn) {
Assert(0); // BUGBUG: out of memory do something.
break;
}
lstrcpy(lpszReturn + ichTemp, lpsz);
}
if ((i + iIncr) != iStart)
{
Assert(0);
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)
{
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;
lpszReturn = (LPTSTR)(void*)LocalReAlloc((HLOCAL)lpszReturn, ichSize * SIZEOF(TCHAR),
LMEM_MOVEABLE|LMEM_ZEROINIT);
if (!lpszReturn)
{
Assert(0); // BUGBUG: out of memory do something.
break;
}
// is it too long?
if (ichSize >= MAX_PATH)
{
lstrcat(lpszReturn, c_szEllipses);
return lpszReturn;
}
else
{
wsprintf(lpszReturn + ichTemp, TEXT("'%s'"), szFile);
}
Assert(ichSize == ichTemp + (lstrlen(lpszReturn + ichTemp) + 1));
ichTemp = ichSize - 1;
// check to see if we need the "and"
if ( (i + iIncr) < iEnd )
{
int id;
ichSize += 40;
if ((i + (iIncr*2)) >= iEnd)
{
id = IDS_SPACEANDSPACE;
}
else
{
id = IDS_COMMASPACE;
}
lpszReturn = (LPTSTR)LocalReAlloc((HLOCAL)lpszReturn, ichSize * SIZEOF(TCHAR),
LMEM_MOVEABLE|LMEM_ZEROINIT);
if (!lpszReturn)
{
Assert(0); // BUGBUG: out of memory do something.
break;
}
LoadString(HINST_THISDLL, id, lpszReturn + ichTemp, 40);
ichSize = ichTemp + (lstrlen(lpszReturn + ichTemp) + 1);
}
}
return lpszReturn;
}
void CALLBACK FOUndo_GetText(LPUNDOATOM lpua, TCHAR * buffer, int type)
{
LPFOUNDODATA lpud = (LPFOUNDODATA)lpua->lpData;
HDPA hdpa = lpud->hdpa;
// thank god for growable stacks..
TCHAR szTemplate[80];
TCHAR szFile1[MAX_PATH];
TCHAR szFile2[MAX_PATH];
TCHAR *lpszFile1;
TCHAR *lpszFile2;
SHELLSTATE ss;
if (type == UNDO_MENUTEXT) {
LoadString(HINST_THISDLL, lpua->uType, buffer, MAX_PATH);
} else {
// get the template
LoadString(HINST_THISDLL, lpua->uType + (IDS_UNDO_FILEOPHELP - IDS_UNDO_FILEOP), szTemplate, ARRAYSIZE(szTemplate));
if (lpua->uType == IDS_RENAME) {
// 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|SSF_SHOWALLOBJECTS, FALSE);
if (!ss.fShowExtensions) {
PathRemoveExtension(szFile1);
PathRemoveExtension(szFile2);
}
// length sanity check
if (lstrlen(szFile1) > 30) {
lstrcpy(szFile1 + 30, c_szEllipses);
}
if (lstrlen(szFile2) > 30) {
lstrcpy(szFile2 + 30, c_szEllipses);
}
wsprintf(buffer, szTemplate, szFile1, szFile2);
} else {
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_InsertPtr(hdpaFull, 0x7FFF, lpdfi->lpszName);
DPA_InsertPtr(hdpaFull, 0x7FFF, lpdfi->lpszName);
}
} else {
hdpaFull = hdpa;
}
}
lpszFile1 = DPA_ToQuotedFileList(hdpaFull, 0, DPA_GetPtrCount(hdpaFull), 2);
wsprintf(buffer, szTemplate, lpszFile1);
LocalFree((HLOCAL)lpszFile1);
if (hdpaFull != hdpa)
DPA_Destroy(hdpaFull);
}
}
}
void CALLBACK FOUndo_Release(LPUNDOATOM lpua)
{
LPFOUNDODATA lpud = (LPFOUNDODATA)lpua->lpData;
int i;
LPTSTR lpsz;
if (lpud && (lpud != (LPVOID)-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);
}
Free(lpud);
lpua->lpData = (LPVOID)-1;
}
}
DWORD WINAPI FOUndo_InvokeThreadInit(LPUNDOATOM 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;
}
}
}
break;
case IDS_COPY:
sFileOp.pFrom = DPA_ToFileList(hdpa, 1, iMax - 1, 2);
if (!sFileOp.pFrom)
goto Exit;
sFileOp.wFunc = FO_DELETE;
SHFileOperation(&sFileOp);
if (sFileOp.fAnyOperationsAborted) {
fNukeAtom = FALSE;
}
LocalFree((HLOCAL)sFileOp.pFrom);
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;
SHFileOperation(&sFileOp);
if (sFileOp.fAnyOperationsAborted) {
fNukeAtom = FALSE;
}
LocalFree((HLOCAL)sFileOp.pFrom);
LocalFree((HLOCAL)sFileOp.pTo);
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;
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;
BBUndeleteFiles(sFileOp.pTo, sFileOp.pFrom);
LocalFree((HLOCAL)sFileOp.pFrom);
LocalFree((HLOCAL)sFileOp.pTo);
}
break;
}
}
SHChangeNotify(0, SHCNF_FLUSH | SHCNF_FLUSHNOWAIT, NULL, NULL);
Exit:
SuspendUndo(FALSE);
if (fNukeAtom)
NukeUndoAtom(lpua);
return 1;
}
void CALLBACK FOUndo_Invoke(LPUNDOATOM lpua)
{
HANDLE hthread;
DWORD idThread;
hthread = CreateThread(NULL, 0, FOUndo_InvokeThreadInit, lpua, 0, &idThread);
if (hthread) {
CloseHandle(hthread);
}
}
LPUNDOATOM FOAllocUndoAtom(LPSHFILEOPSTRUCT lpfo)
{
LPUNDOATOM lpua = (LPUNDOATOM)Alloc(SIZEOF(UNDOATOM));
if (lpua) {
lpua->uType = IDS_UNDO_FILEOP + lpfo->wFunc;
lpua->GetText = FOUndo_GetText;
lpua->Invoke = FOUndo_Invoke;
lpua->Release = FOUndo_Release;
}
return lpua;
}
//============================================================================
//
// The following function is the mainline function for COPYing, RENAMEing,
// DELETEing, and MOVEing single or multiple files.
//
// in:
// hwnd either the dialog to use as the progress dialog or the parent
// to create the progress dialog from if FOF_CREATEPROGRESSDLG is set.
//
// this stuff is bull... (Gee, thanks)
//
//
// if FOF_CREATEPROGRESSDLG is not set the hwnd passed in must
// respond to the WM_QUERYABORT message. also, the dialog must contain
// controls for displaying the status information. this controls are:
// IDD_STATUS displays the operation in progress
// IDD_TOSTATUS field in front of destinaion name (IDD_TONAME)
// IDD_NAME displays the source file name
// IDD_TONAME displays the destination file name
// IDD_PROBAR the progress control for showing copy operations
//
// 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 (ANSI) 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 (ANSI). this must be a single file spec
// that indicates a directory or a wild card that all files will be
// copied to.
//
// 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;
COPY_STATE cs;
#ifdef CLOUDS
CloudHookFileOperation(&lpfo);
#endif
lpfo->fAnyOperationsAborted = FALSE;
lpfo->hNameMappings = NULL;
if (lpfo->wFunc < FO_MOVE || lpfo->wFunc > FO_RENAME) // validate
return 0;
//
// REVIEW: We want to allow copying of a file within a given directory
// by having default renaming on collisions within a directory.
//
cs.cd.fNoToAll = 0;
if (lpfo->fFlags & FOF_NOCONFIRMATION) {
cs.cd.fConfirm = 0;
} else {
cs.cd.fConfirm =
CONFIRM_DELETE_FILE |
CONFIRM_DELETE_FOLDER |
CONFIRM_REPLACE_FILE |
CONFIRM_REPLACE_FOLDER |
// CONFIRM_MOVE_FILE |
// CONFIRM_MOVE_FOLDER |
// CONFIRM_RENAME_FILE |
// CONFIRM_RENAME_FOLDER |
CONFIRM_SYSTEM_FILE |
CONFIRM_READONLY_FILE |
CONFIRM_MULTIPLE |
CONFIRM_PROGRAM_FILE |
CONFIRM_LFNTOFAT;
}
cs.bAbort = FALSE;
cs.fMerge = FALSE;
cs.fFlags = lpfo->fFlags; // duplicate some stuff here
cs.wFunc = lpfo->wFunc;
cs.dwStartTime = GetTickCount(); // we will show the progress dialog when this times out
cs.dwShowTime = 0;
cs.fNonCopyProgress = FALSE;
cs.lpszProgressTitle = lpfo->lpszProgressTitle;
cs.lpCopyBuffer = NULL;
cs.lpua = NULL;
cs.lpfo = lpfo;
// 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 (!(cs.fFlags & FOF_SILENT)) {
cs.hwndProgress = CreateDialogParam(HINST_THISDLL, MAKEINTRESOURCE(DLG_MOVECOPYPROGRESS),
lpfo->hwnd, FOFProgressDlgProc, (LPARAM)&cs);
if (!cs.hwndProgress)
return DE_INSMEM;
} else
cs.hwndProgress = NULL;
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);
cs.hwndCaller = lpfo->hwnd;
} else {
cs.hwndCaller = cs.hwndProgress;
}
cs.hwndDlgParent = cs.hwndCaller;
// 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) {
cs.lpua = FOAllocUndoAtom(lpfo);
if (lpfo->wFunc == FO_DELETE) {
SHELLSTATE ss;
// see if we should put up warnings
SHGetSetSettings(&ss, SSF_NOCONFIRMRECYCLE, FALSE);
cs.fNoConfirmRecycle = ss.fNoConfirmRecycle;
}
}
ret = MoveCopyDriver(&cs, lpfo);
if (cs.lpCopyBuffer) {
LocalFree((HLOCAL)cs.lpCopyBuffer);
cs.lpCopyBuffer = NULL;
}
if (cs.lpua) {
if (cs.lpua->lpData && (cs.lpua->lpData != (LPVOID)-1)) {
AddUndoAtom(cs.lpua);
} else {
FOUndo_Release(cs.lpua);
NukeUndoAtom(cs.lpua);
}
if (lpfo->wFunc == FO_DELETE && (cs.fFlags & FOF_ALLOWUNDO)) {
BBFlushCache();
}
}
// 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
idDriveSrc = PathGetDriveNumber(lpfo->pFrom);
if (lpfo->pTo)
idDriveDest = PathGetDriveNumber(lpfo->pTo);
if (lpfo->wFunc == FO_COPY) {
// nothing chagnes on the source
idDriveSrc = -1;
}
if (lpfo->wFunc == FO_MOVE &&
idDriveDest == idDriveSrc) {
// no freespace nothing changes
idDriveSrc = -1;
idDriveDest = -1;
}
if (idDriveSrc != -1) {
dwDrives |= (1 << idDriveSrc);
}
if (idDriveDest != -1) {
dwDrives |= (1 << idDriveDest);
}
if (dwDrives)
SHChangeNotify(SHCNE_FREESPACE, SHCNF_DWORD, (LPITEMIDLIST)dwDrives, 0);
}
SuspendUndo(FALSE);
if (!(lpfo->fFlags & FOF_WANTMAPPINGHANDLE))
{
SHFreeNameMappings(lpfo->hNameMappings);
lpfo->hNameMappings = NULL;
}
if (lpfo->hwnd)
EnableWindow(lpfo->hwnd, TRUE);
if (cs.dwShowTime) {
int iShowTimeLeft = MINSHOWTIME - (GetTickCount() - cs.dwShowTime);
if (iShowTimeLeft > 0)
Sleep(iShowTimeLeft);
}
if (cs.hwndProgress)
DestroyWindow(cs.hwndProgress);
return ret;
}
#ifdef UNICODE
int WINAPI SHFileOperationA(LPSHFILEOPSTRUCTA lpfo)
{
int iResult;
UINT uTotalSize;
UINT uSize;
UINT uSizeTitle;
SHFILEOPSTRUCTW shop;
LPCSTR lpAnsi;
LPWSTR lpBuffer;
LPWSTR lpTemp;
// BUGBUG Runtime size check? Move this to the shell intialization,
// since what you really want is compile-time checking, which
// can't be done (DavePl)
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;
MultiByteToWideChar(CP_ACP, 0,
lpAnsi, uSize,
lpTemp, uSize);
lpAnsi += uSize;
lpTemp += uSize;
} while (uSize != 1);
}
else
{
shop.pFrom = NULL;
}
if (lpfo->pTo)
{
shop.pTo = lpTemp;
lpAnsi = lpfo->pTo;
do {
uSize = lstrlenA(lpAnsi) + 1;
MultiByteToWideChar(CP_ACP, 0,
lpAnsi, uSize,
lpTemp, uSize);
lpAnsi += uSize;
lpTemp += uSize;
} 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);
if (lpBuffer)
LocalFree(lpBuffer);
return iResult;
}
#else
int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpfo)
{
return 0; // BUGBUG - BobDay - We should move this into SHUNIMP.C
}
#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_FIRSTAVG
void SetProgressTime(COPY_STATE *pcs)
{
DWORD dwNow = GetTickCount();
// first time we come in here?
if (pcs->dwPreviousTime == 0)
{
pcs->dwPreviousTime = dwNow;
pcs->dwBytesRead = 0;
pcs->dwBytesPerSec = 0;
}
else
{
// 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 (dwNow-pcs->dwPreviousTime > MS_RUNAVG
|| (dwNow-pcs->dwPreviousTime > MS_FIRSTAVG && !pcs->dwBytesPerSec))
{
DWORD dwBytesPerSec;
// We take 10 times the number of bytes and divide by the number of
// tenths of a second to minimize both overflow and roundoff
dwBytesPerSec = pcs->dwBytesRead * 10 / ((dwNow - pcs->dwPreviousTime) / 100);
if (!dwBytesPerSec)
{
// This could happen if the net went to sleep for a couple
// minutes while trying to copy a small (512 byte) buffer
dwBytesPerSec = 1;
}
if (pcs->dwBytesPerSec)
{
// Take the average of the current transfer rate and the
// previously computed one, just to try to smooth out
// some random fluctuations
dwBytesPerSec = (dwBytesPerSec + pcs->dwBytesPerSec) / 2;
}
pcs->dwBytesPerSec = dwBytesPerSec;
// Calculate time remaining (round up by adding 1)
// We only get here every 10 seconds, so always update
pcs->dwTimeLeft = (pcs->dwBytesLeft / dwBytesPerSec) + 1;
// It would be silly to show "1 second left" and then immediately
// clear it
if (pcs->fShowTime || pcs->dwTimeLeft > MIN_MINTIME4FEEDBACK)
{
// display new estimate of time left
SetProgressTimeEst(pcs);
}
// Reset previous time and # of bytes read
pcs->dwPreviousTime = dwNow;
pcs->dwBytesRead = 0;
}
}
}