#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;
            }
        }
}