mirror of
https://github.com/reactos/reactos.git
synced 2025-04-06 05:34:22 +00:00
[SHELL32]
- Implement progress dialogs for SHFileOperation - Patch by Hwu Davies CORE-4476 svn path=/trunk/; revision=66383
This commit is contained in:
parent
af1e5ea7ad
commit
194015ecae
3 changed files with 359 additions and 83 deletions
|
@ -693,6 +693,13 @@ BEGIN
|
|||
IDS_OVERWRITEFILE_CAPTION "Confirm file overwrite"
|
||||
IDS_OVERWRITEFOLDER_TEXT "This folder already contains a folder named '%1'.\n\nIf the files in the destination folder have the same names as files in the\nselected folder they will be replaced. Do you still want to move or copy\nthe folder?"
|
||||
|
||||
IDS_FILEOOP_COPYING "Copying"
|
||||
IDS_FILEOOP_MOVING "Moving"
|
||||
IDS_FILEOOP_DELETING "Deleting"
|
||||
IDS_FILEOOP_FROM_TO "From %1 to %2"
|
||||
IDS_FILEOOP_FROM "From %1"
|
||||
IDS_FILEOOP_PREFLIGHT "Preflight"
|
||||
|
||||
/* message box strings */
|
||||
IDS_RESTART_TITLE "Restart"
|
||||
IDS_RESTART_PROMPT "Do you want to restart the system?"
|
||||
|
|
|
@ -37,10 +37,15 @@ static const WCHAR wWildcardChars[] = {'*','?',0};
|
|||
|
||||
static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec);
|
||||
static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path);
|
||||
static DWORD SHNotifyDeleteFileW(LPCWSTR path);
|
||||
static DWORD SHNotifyMoveFileW(LPCWSTR src, LPCWSTR dest, BOOL isdir);
|
||||
static DWORD SHNotifyCopyFileW(LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists);
|
||||
static DWORD SHNotifyDeleteFileW(FILE_OPERATION *op, LPCWSTR path);
|
||||
static DWORD SHNotifyMoveFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL isdir);
|
||||
static DWORD SHNotifyCopyFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists);
|
||||
static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly);
|
||||
static HRESULT copy_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, FILE_LIST *flTo);
|
||||
static DWORD move_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, const FILE_LIST *flTo);
|
||||
|
||||
DWORD WINAPI _FileOpCountManager(FILE_OPERATION *op, const FILE_LIST *flFrom);
|
||||
static BOOL _FileOpCount(FILE_OPERATION *op, LPWSTR pwszBuf, BOOL bFolder, DWORD *ticks);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
|
@ -48,6 +53,10 @@ typedef struct
|
|||
DWORD dwYesToAllMask;
|
||||
BOOL bManyItems;
|
||||
BOOL bCancelled;
|
||||
IProgressDialog *progress;
|
||||
ULARGE_INTEGER completedSize;
|
||||
ULARGE_INTEGER totalSize;
|
||||
WCHAR szBuilderString[50];
|
||||
} FILE_OPERATION;
|
||||
|
||||
#define ERROR_SHELL_INTERNAL_FILE_NOT_FOUND 1026
|
||||
|
@ -73,17 +82,6 @@ typedef struct
|
|||
BOOL bAnyDontExist;
|
||||
} FILE_LIST;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
FILE_LIST * from;
|
||||
FILE_LIST * to;
|
||||
FILE_OPERATION * op;
|
||||
DWORD Index;
|
||||
HWND hDlgCtrl;
|
||||
HWND hwndDlg;
|
||||
}FILE_OPERATION_CONTEXT;
|
||||
|
||||
|
||||
/* Confirm dialogs with an optional "Yes To All" as used in file operations confirmations
|
||||
*/
|
||||
static const WCHAR CONFIRM_MSG_PROP[] = {'W','I','N','E','_','C','O','N','F','I','R','M',0};
|
||||
|
@ -371,7 +369,7 @@ EXTERN_C HRESULT WINAPI SHIsFileAvailableOffline(LPCWSTR path, LPDWORD status)
|
|||
* Asks for confirmation when bShowUI is true and deletes the directory and
|
||||
* all its subdirectories and files if necessary.
|
||||
*/
|
||||
BOOL SHELL_DeleteDirectoryW(HWND hwnd, LPCWSTR pszDir, BOOL bShowUI)
|
||||
BOOL SHELL_DeleteDirectoryW(FILE_OPERATION *op, LPCWSTR pszDir, BOOL bShowUI)
|
||||
{
|
||||
BOOL ret = TRUE;
|
||||
HANDLE hFind;
|
||||
|
@ -384,7 +382,7 @@ BOOL SHELL_DeleteDirectoryW(HWND hwnd, LPCWSTR pszDir, BOOL bShowUI)
|
|||
if (hFind == INVALID_HANDLE_VALUE)
|
||||
return FALSE;
|
||||
|
||||
if (!bShowUI || (ret = SHELL_ConfirmDialogW(hwnd, ASK_DELETE_FOLDER, pszDir, NULL)))
|
||||
if (!bShowUI || (ret = SHELL_ConfirmDialogW(op->req->hwnd, ASK_DELETE_FOLDER, pszDir, NULL)))
|
||||
{
|
||||
do
|
||||
{
|
||||
|
@ -392,10 +390,13 @@ BOOL SHELL_DeleteDirectoryW(HWND hwnd, LPCWSTR pszDir, BOOL bShowUI)
|
|||
continue;
|
||||
PathCombineW(szTemp, pszDir, wfd.cFileName);
|
||||
if (FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes)
|
||||
ret = SHELL_DeleteDirectoryW(hwnd, szTemp, FALSE);
|
||||
ret = SHELL_DeleteDirectoryW(op, szTemp, FALSE);
|
||||
else
|
||||
ret = (SHNotifyDeleteFileW(szTemp) == ERROR_SUCCESS);
|
||||
} while (ret && FindNextFileW(hFind, &wfd));
|
||||
ret = (SHNotifyDeleteFileW(op, szTemp) == ERROR_SUCCESS);
|
||||
|
||||
if (op->progress != NULL)
|
||||
op->bCancelled |= op->progress->HasUserCancelled();
|
||||
} while (ret && FindNextFileW(hFind, &wfd) && !op->bCancelled);
|
||||
}
|
||||
FindClose(hFind);
|
||||
if (ret)
|
||||
|
@ -474,23 +475,152 @@ EXTERN_C BOOL WINAPI Win32RemoveDirectoryW(LPCWSTR path)
|
|||
return (SHNotifyRemoveDirectoryW(path) == ERROR_SUCCESS);
|
||||
}
|
||||
|
||||
static void _SetOperationTitle(FILE_OPERATION *op) {
|
||||
if (op->progress == NULL)
|
||||
return;
|
||||
WCHAR szTitle[50], szPreflight[50];
|
||||
|
||||
switch (op->req->wFunc)
|
||||
{
|
||||
case FO_COPY:
|
||||
LoadStringW(shell32_hInstance, IDS_FILEOOP_COPYING, szTitle, sizeof(szTitle)/sizeof(WCHAR));
|
||||
LoadStringW(shell32_hInstance, IDS_FILEOOP_FROM_TO, op->szBuilderString, sizeof( op->szBuilderString)/sizeof(WCHAR));
|
||||
break;
|
||||
case FO_DELETE:
|
||||
LoadStringW(shell32_hInstance, IDS_FILEOOP_DELETING, szTitle, sizeof(szTitle)/sizeof(WCHAR));
|
||||
LoadStringW(shell32_hInstance, IDS_FILEOOP_FROM, op->szBuilderString, sizeof( op->szBuilderString)/sizeof(WCHAR));
|
||||
break;
|
||||
case FO_MOVE:
|
||||
LoadStringW(shell32_hInstance, IDS_FILEOOP_MOVING, szTitle, sizeof(szTitle)/sizeof(WCHAR));
|
||||
LoadStringW(shell32_hInstance, IDS_FILEOOP_FROM_TO, op->szBuilderString, sizeof( op->szBuilderString)/sizeof(WCHAR));
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
LoadStringW(shell32_hInstance, IDS_FILEOOP_PREFLIGHT, szPreflight, sizeof(szPreflight)/sizeof(WCHAR));
|
||||
|
||||
op->progress->SetTitle(szTitle);
|
||||
op->progress->SetLine(1, szPreflight, false, NULL);
|
||||
}
|
||||
|
||||
static void _SetOperationTexts(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest) {
|
||||
if (op->progress == NULL || src == NULL)
|
||||
return;
|
||||
LPWSTR fileSpecS, pathSpecS, fileSpecD, pathSpecD;
|
||||
WCHAR szFolderS[50], szFolderD[50], szFinalString[260];
|
||||
|
||||
DWORD_PTR args[2];
|
||||
|
||||
fileSpecS = (pathSpecS = (LPWSTR) src);
|
||||
fileSpecD = (pathSpecD = (LPWSTR) dest);
|
||||
|
||||
// March across the string to get the file path and it's parent dir.
|
||||
for (LPWSTR ptr = (LPWSTR) src; *ptr; ptr++) {
|
||||
if (*ptr == '\\') {
|
||||
pathSpecS = fileSpecS;
|
||||
fileSpecS = ptr+1;
|
||||
}
|
||||
}
|
||||
lstrcpynW(szFolderS, pathSpecS, min(50, fileSpecS - pathSpecS));
|
||||
args[0] = (DWORD_PTR) szFolderS;
|
||||
|
||||
switch (op->req->wFunc)
|
||||
{
|
||||
case FO_COPY:
|
||||
case FO_MOVE:
|
||||
if (dest == NULL)
|
||||
return;
|
||||
for (LPWSTR ptr = (LPWSTR) dest; *ptr; ptr++) {
|
||||
if (*ptr == '\\') {
|
||||
pathSpecD = fileSpecD;
|
||||
fileSpecD = ptr + 1;
|
||||
}
|
||||
}
|
||||
lstrcpynW(szFolderD, pathSpecD, min(50, fileSpecD - pathSpecD));
|
||||
args[1] = (DWORD_PTR) szFolderD;
|
||||
break;
|
||||
case FO_DELETE:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
||||
op->szBuilderString, 0, 0, szFinalString, sizeof(szFinalString), (va_list*)args);
|
||||
|
||||
op->progress->SetLine(1, fileSpecS, false, NULL);
|
||||
op->progress->SetLine(2, szFinalString, false, NULL);
|
||||
}
|
||||
|
||||
|
||||
DWORD CALLBACK SHCopyProgressRoutine(
|
||||
LARGE_INTEGER TotalFileSize,
|
||||
LARGE_INTEGER TotalBytesTransferred,
|
||||
LARGE_INTEGER StreamSize,
|
||||
LARGE_INTEGER StreamBytesTransferred,
|
||||
DWORD dwStreamNumber,
|
||||
DWORD dwCallbackReason,
|
||||
HANDLE hSourceFile,
|
||||
HANDLE hDestinationFile,
|
||||
LPVOID lpData
|
||||
) {
|
||||
FILE_OPERATION *op = (FILE_OPERATION *) lpData;
|
||||
|
||||
if (op->progress) {
|
||||
/*
|
||||
* This is called at the start of each file. To keop less state,
|
||||
* I'm adding the file to the completed size here, and the re-subtracting
|
||||
* it when drawing the progress bar.
|
||||
*/
|
||||
if (dwCallbackReason & CALLBACK_STREAM_SWITCH)
|
||||
op->completedSize.QuadPart += TotalFileSize.QuadPart;
|
||||
|
||||
op->progress->SetProgress64(op->completedSize.QuadPart -
|
||||
TotalFileSize.QuadPart +
|
||||
TotalBytesTransferred.QuadPart
|
||||
, op->totalSize.QuadPart);
|
||||
|
||||
|
||||
op->bCancelled = op->progress->HasUserCancelled();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************
|
||||
* Win32DeleteFile [SHELL32.164]
|
||||
* SHNotifyDeleteFileW [internal]
|
||||
*
|
||||
* Deletes a file. Also triggers a change notify if one exists.
|
||||
*
|
||||
* PARAMS
|
||||
* path [I] path to file to delete
|
||||
* op [I] File Operation context
|
||||
* path [I] path to source file to move
|
||||
*
|
||||
* RETURNS
|
||||
* TRUE if successful, FALSE otherwise
|
||||
* ERORR_SUCCESS if successful
|
||||
*/
|
||||
static DWORD SHNotifyDeleteFileW(LPCWSTR path)
|
||||
static DWORD SHNotifyDeleteFileW(FILE_OPERATION *op, LPCWSTR path)
|
||||
{
|
||||
BOOL ret;
|
||||
|
||||
TRACE("(%s)\n", debugstr_w(path));
|
||||
|
||||
_SetOperationTexts(op, path, NULL);
|
||||
|
||||
LARGE_INTEGER FileSize;
|
||||
FileSize.QuadPart = 0;
|
||||
|
||||
WIN32_FIND_DATAW wfd;
|
||||
HANDLE hFile = FindFirstFileW(path, &wfd);
|
||||
if (hFile != INVALID_HANDLE_VALUE && IsAttribFile(wfd.dwFileAttributes)) {
|
||||
ULARGE_INTEGER tmp;
|
||||
tmp.u.LowPart = wfd.nFileSizeLow;
|
||||
tmp.u.HighPart = wfd.nFileSizeHigh;
|
||||
FileSize.QuadPart = tmp.QuadPart;
|
||||
}
|
||||
FindClose(hFile);
|
||||
|
||||
ret = DeleteFileW(path);
|
||||
if (!ret)
|
||||
{
|
||||
|
@ -502,17 +632,28 @@ static DWORD SHNotifyDeleteFileW(LPCWSTR path)
|
|||
}
|
||||
if (ret)
|
||||
{
|
||||
// Bit of a hack to make the progress bar move. We don't have progress inside the file, so inform when done.
|
||||
SHCopyProgressRoutine(FileSize, FileSize, FileSize, FileSize, 0, CALLBACK_STREAM_SWITCH, NULL, NULL, op);
|
||||
SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, path, NULL);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
return GetLastError();
|
||||
}
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
/************************************************************************
|
||||
* Win32DeleteFile [SHELL32.164]
|
||||
*
|
||||
* Deletes a file. Also triggers a change notify if one exists.
|
||||
*
|
||||
* PARAMS
|
||||
* path [I] path to file to delete
|
||||
*
|
||||
* RETURNS
|
||||
* TRUE if successful, FALSE otherwise
|
||||
*/
|
||||
EXTERN_C DWORD WINAPI Win32DeleteFileW(LPCWSTR path)
|
||||
{
|
||||
return (SHNotifyDeleteFileW(path) == ERROR_SUCCESS);
|
||||
return (SHNotifyDeleteFileW(NULL, path) == ERROR_SUCCESS);
|
||||
}
|
||||
|
||||
/************************************************************************
|
||||
|
@ -521,23 +662,26 @@ EXTERN_C DWORD WINAPI Win32DeleteFileW(LPCWSTR path)
|
|||
* Moves a file. Also triggers a change notify if one exists.
|
||||
*
|
||||
* PARAMS
|
||||
* op [I] File Operation context
|
||||
* src [I] path to source file to move
|
||||
* dest [I] path to target file to move to
|
||||
*
|
||||
* RETURNS
|
||||
* ERORR_SUCCESS if successful
|
||||
*/
|
||||
static DWORD SHNotifyMoveFileW(LPCWSTR src, LPCWSTR dest, BOOL isdir)
|
||||
static DWORD SHNotifyMoveFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL isdir)
|
||||
{
|
||||
BOOL ret;
|
||||
|
||||
TRACE("(%s %s)\n", debugstr_w(src), debugstr_w(dest));
|
||||
|
||||
ret = MoveFileExW(src, dest, MOVEFILE_REPLACE_EXISTING);
|
||||
_SetOperationTexts(op, src, dest);
|
||||
|
||||
/* MOVEFILE_REPLACE_EXISTING fails with dirs, so try MoveFile */
|
||||
if (!ret)
|
||||
ret = MoveFileW(src, dest);
|
||||
ret = MoveFileWithProgressW(src, dest, SHCopyProgressRoutine, op, MOVEFILE_REPLACE_EXISTING);
|
||||
|
||||
/* MOVEFILE_REPLACE_EXISTING fails with dirs, so try MoveFile */
|
||||
if (!ret)
|
||||
ret = MoveFileW(src, dest);
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
|
@ -576,13 +720,15 @@ static DWORD SHNotifyMoveFileW(LPCWSTR src, LPCWSTR dest, BOOL isdir)
|
|||
* RETURNS
|
||||
* ERROR_SUCCESS if successful
|
||||
*/
|
||||
static DWORD SHNotifyCopyFileW(LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists)
|
||||
static DWORD SHNotifyCopyFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists)
|
||||
{
|
||||
BOOL ret;
|
||||
DWORD attribs;
|
||||
|
||||
TRACE("(%s %s %s)\n", debugstr_w(src), debugstr_w(dest), bFailIfExists ? "failIfExists" : "");
|
||||
|
||||
_SetOperationTexts(op, src, dest);
|
||||
|
||||
/* Destination file may already exist with read only attribute */
|
||||
attribs = GetFileAttributesW(dest);
|
||||
if (IsAttrib(attribs, FILE_ATTRIBUTE_READONLY))
|
||||
|
@ -597,7 +743,7 @@ static DWORD SHNotifyCopyFileW(LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists)
|
|||
}
|
||||
}
|
||||
|
||||
ret = CopyFileW(src, dest, bFailIfExists);
|
||||
ret = CopyFileExW(src, dest, SHCopyProgressRoutine, op, &op->bCancelled, bFailIfExists);
|
||||
if (ret)
|
||||
{
|
||||
SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, dest, NULL);
|
||||
|
@ -1072,7 +1218,7 @@ static void destroy_file_list(FILE_LIST *flList)
|
|||
static void copy_dir_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, LPCWSTR szDestPath)
|
||||
{
|
||||
WCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
|
||||
SHFILEOPSTRUCTW fileOp;
|
||||
FILE_LIST flFromNew, flToNew;
|
||||
|
||||
static const WCHAR wildCardFiles[] = {'*','.','*',0};
|
||||
|
||||
|
@ -1101,17 +1247,15 @@ static void copy_dir_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, LPCWST
|
|||
PathCombineW(szFrom, feFrom->szFullPath, wildCardFiles);
|
||||
szFrom[lstrlenW(szFrom) + 1] = '\0';
|
||||
|
||||
fileOp = *op->req;
|
||||
fileOp.pFrom = szFrom;
|
||||
fileOp.pTo = szTo;
|
||||
fileOp.fFlags &= ~FOF_MULTIDESTFILES; /* we know we're copying to one dir */
|
||||
ZeroMemory(&flFromNew, sizeof(FILE_LIST));
|
||||
ZeroMemory(&flToNew, sizeof(FILE_LIST));
|
||||
parse_file_list(&flFromNew, szFrom);
|
||||
parse_file_list(&flToNew, szTo);
|
||||
|
||||
/* Don't ask the user about overwriting files when he accepted to overwrite the
|
||||
folder. FIXME: this is not exactly what Windows does - e.g. there would be
|
||||
an additional confirmation for a nested folder */
|
||||
fileOp.fFlags |= FOF_NOCONFIRMATION;
|
||||
copy_files(op, FALSE, &flFromNew, &flToNew);
|
||||
|
||||
SHFileOperationW(&fileOp);
|
||||
destroy_file_list(&flFromNew);
|
||||
destroy_file_list(&flToNew);
|
||||
}
|
||||
|
||||
static BOOL copy_file_to_file(FILE_OPERATION *op, const WCHAR *szFrom, const WCHAR *szTo)
|
||||
|
@ -1122,7 +1266,7 @@ static BOOL copy_file_to_file(FILE_OPERATION *op, const WCHAR *szFrom, const WCH
|
|||
return 0;
|
||||
}
|
||||
|
||||
return SHNotifyCopyFileW(szFrom, szTo, FALSE) == 0;
|
||||
return SHNotifyCopyFileW(op, szFrom, szTo, FALSE) == 0;
|
||||
}
|
||||
|
||||
/* copy a file or directory to another directory */
|
||||
|
@ -1162,7 +1306,7 @@ static void create_dest_dirs(LPCWSTR szDestDir)
|
|||
}
|
||||
|
||||
/* the FO_COPY operation */
|
||||
static HRESULT copy_files(FILE_OPERATION *op, const FILE_LIST *flFrom, FILE_LIST *flTo)
|
||||
static HRESULT copy_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, FILE_LIST *flTo)
|
||||
{
|
||||
DWORD i;
|
||||
const FILE_ENTRY *entryToCopy;
|
||||
|
@ -1185,7 +1329,7 @@ static HRESULT copy_files(FILE_OPERATION *op, const FILE_LIST *flFrom, FILE_LIST
|
|||
fileDest = &flTo->feFiles[0];
|
||||
}
|
||||
|
||||
if (op->req->fFlags & FOF_MULTIDESTFILES)
|
||||
if (multiDest)
|
||||
{
|
||||
if (flFrom->bAnyFromWildcard)
|
||||
return ERROR_CANCELLED;
|
||||
|
@ -1237,7 +1381,7 @@ static HRESULT copy_files(FILE_OPERATION *op, const FILE_LIST *flFrom, FILE_LIST
|
|||
{
|
||||
entryToCopy = &flFrom->feFiles[i];
|
||||
|
||||
if ((op->req->fFlags & FOF_MULTIDESTFILES) &&
|
||||
if ((multiDest) &&
|
||||
flTo->dwNumFiles > 1)
|
||||
{
|
||||
fileDest = &flTo->feFiles[i];
|
||||
|
@ -1276,7 +1420,9 @@ static HRESULT copy_files(FILE_OPERATION *op, const FILE_LIST *flFrom, FILE_LIST
|
|||
return ERROR_CANCELLED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (op->progress != NULL)
|
||||
op->bCancelled |= op->progress->HasUserCancelled();
|
||||
/* Vista return code. XP would return e.g. ERROR_FILE_NOT_FOUND, ERROR_ALREADY_EXISTS */
|
||||
if (op->bCancelled)
|
||||
return ERROR_CANCELLED;
|
||||
|
@ -1310,7 +1456,7 @@ static BOOL confirm_delete_list(HWND hWnd, DWORD fFlags, BOOL fTrash, const FILE
|
|||
}
|
||||
|
||||
/* the FO_DELETE operation */
|
||||
static HRESULT delete_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom)
|
||||
static HRESULT delete_files(FILE_OPERATION *op, const FILE_LIST *flFrom)
|
||||
{
|
||||
const FILE_ENTRY *fileEntry;
|
||||
DWORD i;
|
||||
|
@ -1321,13 +1467,13 @@ static HRESULT delete_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom)
|
|||
return ERROR_SUCCESS;
|
||||
|
||||
/* Windows also checks only the first item */
|
||||
bTrash = (lpFileOp->fFlags & FOF_ALLOWUNDO)
|
||||
bTrash = (op->req->fFlags & FOF_ALLOWUNDO)
|
||||
&& TRASH_CanTrashFile(flFrom->feFiles[0].szFullPath);
|
||||
|
||||
if (!(lpFileOp->fFlags & FOF_NOCONFIRMATION) || (!bTrash && lpFileOp->fFlags & FOF_WANTNUKEWARNING))
|
||||
if (!confirm_delete_list(lpFileOp->hwnd, lpFileOp->fFlags, bTrash, flFrom))
|
||||
if (!(op->req->fFlags & FOF_NOCONFIRMATION) || (!bTrash && op->req->fFlags & FOF_WANTNUKEWARNING))
|
||||
if (!confirm_delete_list(op->req->hwnd, op->req->fFlags, bTrash, flFrom))
|
||||
{
|
||||
lpFileOp->fAnyOperationsAborted = TRUE;
|
||||
op->req->fAnyOperationsAborted = TRUE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1337,7 +1483,7 @@ static HRESULT delete_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom)
|
|||
fileEntry = &flFrom->feFiles[i];
|
||||
|
||||
if (!IsAttribFile(fileEntry->attributes) &&
|
||||
(lpFileOp->fFlags & FOF_FILESONLY && fileEntry->bFromWildcard))
|
||||
(op->req->fFlags & FOF_FILESONLY && fileEntry->bFromWildcard))
|
||||
continue;
|
||||
|
||||
if (bTrash)
|
||||
|
@ -1350,14 +1496,14 @@ static HRESULT delete_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom)
|
|||
}
|
||||
|
||||
/* Note: Windows silently deletes the file in such a situation, we show a dialog */
|
||||
if (!(lpFileOp->fFlags & FOF_NOCONFIRMATION) || (lpFileOp->fFlags & FOF_WANTNUKEWARNING))
|
||||
bDelete = SHELL_ConfirmDialogW(lpFileOp->hwnd, ASK_CANT_TRASH_ITEM, fileEntry->szFullPath, NULL);
|
||||
if (!(op->req->fFlags & FOF_NOCONFIRMATION) || (op->req->fFlags & FOF_WANTNUKEWARNING))
|
||||
bDelete = SHELL_ConfirmDialogW(op->req->hwnd, ASK_CANT_TRASH_ITEM, fileEntry->szFullPath, NULL);
|
||||
else
|
||||
bDelete = TRUE;
|
||||
|
||||
if (!bDelete)
|
||||
{
|
||||
lpFileOp->fAnyOperationsAborted = TRUE;
|
||||
op->req->fAnyOperationsAborted = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1365,11 +1511,10 @@ static HRESULT delete_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom)
|
|||
/* delete the file or directory */
|
||||
if (IsAttribFile(fileEntry->attributes))
|
||||
{
|
||||
bPathExists = DeleteFileW(fileEntry->szFullPath);
|
||||
SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, fileEntry->szFullPath, NULL);
|
||||
bPathExists = (ERROR_SUCCESS == SHNotifyDeleteFileW(op, fileEntry->szFullPath));
|
||||
}
|
||||
else
|
||||
bPathExists = SHELL_DeleteDirectoryW(lpFileOp->hwnd, fileEntry->szFullPath, FALSE);
|
||||
bPathExists = SHELL_DeleteDirectoryW(op, fileEntry->szFullPath, FALSE);
|
||||
|
||||
if (!bPathExists)
|
||||
{
|
||||
|
@ -1386,15 +1531,21 @@ static HRESULT delete_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom)
|
|||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (op->progress != NULL)
|
||||
op->bCancelled |= op->progress->HasUserCancelled();
|
||||
/* Should fire on progress dialog only */
|
||||
if (op->bCancelled)
|
||||
return ERROR_CANCELLED;
|
||||
}
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static void move_dir_to_dir(LPSHFILEOPSTRUCTW lpFileOp, const FILE_ENTRY *feFrom, LPCWSTR szDestPath)
|
||||
static void move_dir_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, LPCWSTR szDestPath)
|
||||
{
|
||||
WCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
|
||||
SHFILEOPSTRUCTW fileOp;
|
||||
FILE_LIST flFromNew, flToNew;
|
||||
|
||||
static const WCHAR wildCardFiles[] = {'*','.','*',0};
|
||||
|
||||
|
@ -1409,31 +1560,36 @@ static void move_dir_to_dir(LPSHFILEOPSTRUCTW lpFileOp, const FILE_ENTRY *feFrom
|
|||
lstrcpyW(szTo, szDestPath);
|
||||
szTo[lstrlenW(szDestPath) + 1] = '\0';
|
||||
|
||||
fileOp = *lpFileOp;
|
||||
fileOp.pFrom = szFrom;
|
||||
fileOp.pTo = szTo;
|
||||
ZeroMemory(&flFromNew, sizeof(FILE_LIST));
|
||||
ZeroMemory(&flToNew, sizeof(FILE_LIST));
|
||||
parse_file_list(&flFromNew, szFrom);
|
||||
parse_file_list(&flToNew, szTo);
|
||||
|
||||
SHFileOperationW(&fileOp);
|
||||
move_files(op, FALSE, &flFromNew, &flToNew);
|
||||
|
||||
destroy_file_list(&flFromNew);
|
||||
destroy_file_list(&flToNew);
|
||||
}
|
||||
|
||||
/* moves a file or directory to another directory */
|
||||
static void move_to_dir(LPSHFILEOPSTRUCTW lpFileOp, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo)
|
||||
static void move_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo)
|
||||
{
|
||||
WCHAR szDestPath[MAX_PATH];
|
||||
|
||||
PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename);
|
||||
|
||||
if (IsAttribFile(feFrom->attributes))
|
||||
SHNotifyMoveFileW(feFrom->szFullPath, szDestPath, FALSE);
|
||||
else if (!(lpFileOp->fFlags & FOF_FILESONLY && feFrom->bFromWildcard))
|
||||
move_dir_to_dir(lpFileOp, feFrom, szDestPath);
|
||||
SHNotifyMoveFileW(op, feFrom->szFullPath, szDestPath, FALSE);
|
||||
else if (!(op->req->fFlags & FOF_FILESONLY && feFrom->bFromWildcard))
|
||||
move_dir_to_dir(op, feFrom, szDestPath);
|
||||
}
|
||||
|
||||
/* the FO_MOVE operation */
|
||||
static DWORD move_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom, const FILE_LIST *flTo)
|
||||
static DWORD move_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, const FILE_LIST *flTo)
|
||||
{
|
||||
DWORD i;
|
||||
INT mismatched = 0;
|
||||
|
||||
const FILE_ENTRY *entryToMove;
|
||||
const FILE_ENTRY *fileDest;
|
||||
|
||||
|
@ -1443,13 +1599,13 @@ static DWORD move_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom, con
|
|||
if (!flTo->dwNumFiles)
|
||||
return ERROR_FILE_NOT_FOUND;
|
||||
|
||||
if (!(lpFileOp->fFlags & FOF_MULTIDESTFILES) &&
|
||||
if (!(multiDest) &&
|
||||
flTo->dwNumFiles > 1 && flFrom->dwNumFiles > 1)
|
||||
{
|
||||
return ERROR_CANCELLED;
|
||||
}
|
||||
|
||||
if (!(lpFileOp->fFlags & FOF_MULTIDESTFILES) &&
|
||||
if (!(multiDest) &&
|
||||
!flFrom->bAnyDirectories &&
|
||||
flFrom->dwNumFiles > flTo->dwNumFiles)
|
||||
{
|
||||
|
@ -1459,7 +1615,7 @@ static DWORD move_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom, con
|
|||
if (!PathFileExistsW(flTo->feFiles[0].szDirectory))
|
||||
return ERROR_CANCELLED;
|
||||
|
||||
if (lpFileOp->fFlags & FOF_MULTIDESTFILES)
|
||||
if (multiDest)
|
||||
mismatched = flFrom->dwNumFiles - flTo->dwNumFiles;
|
||||
|
||||
fileDest = &flTo->feFiles[0];
|
||||
|
@ -1470,7 +1626,7 @@ static DWORD move_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom, con
|
|||
if (!PathFileExistsW(fileDest->szDirectory))
|
||||
return ERROR_CANCELLED;
|
||||
|
||||
if (lpFileOp->fFlags & FOF_MULTIDESTFILES)
|
||||
if (multiDest)
|
||||
{
|
||||
if (i >= flTo->dwNumFiles)
|
||||
break;
|
||||
|
@ -1484,9 +1640,16 @@ static DWORD move_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom, con
|
|||
}
|
||||
|
||||
if (fileDest->bExists && IsAttribDir(fileDest->attributes))
|
||||
move_to_dir(lpFileOp, entryToMove, fileDest);
|
||||
move_to_dir(op, entryToMove, fileDest);
|
||||
else
|
||||
SHNotifyMoveFileW(entryToMove->szFullPath, fileDest->szFullPath, IsAttribDir(entryToMove->attributes));
|
||||
SHNotifyMoveFileW(op, entryToMove->szFullPath, fileDest->szFullPath, IsAttribDir(entryToMove->attributes));
|
||||
|
||||
if (op->progress != NULL)
|
||||
op->bCancelled |= op->progress->HasUserCancelled();
|
||||
/* Should fire on progress dialog only */
|
||||
if (op->bCancelled)
|
||||
return ERROR_CANCELLED;
|
||||
|
||||
}
|
||||
|
||||
if (mismatched > 0)
|
||||
|
@ -1501,7 +1664,7 @@ static DWORD move_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom, con
|
|||
}
|
||||
|
||||
/* the FO_RENAME files */
|
||||
static HRESULT rename_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom, const FILE_LIST *flTo)
|
||||
static HRESULT rename_files(FILE_OPERATION *op, const FILE_LIST *flFrom, const FILE_LIST *flTo)
|
||||
{
|
||||
const FILE_ENTRY *feFrom;
|
||||
const FILE_ENTRY *feTo;
|
||||
|
@ -1523,7 +1686,7 @@ static HRESULT rename_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom,
|
|||
if (feTo->bExists)
|
||||
return ERROR_ALREADY_EXISTS;
|
||||
|
||||
return SHNotifyMoveFileW(feFrom->szFullPath, feTo->szFullPath, IsAttribDir(feFrom->attributes));
|
||||
return SHNotifyMoveFileW(op, feFrom->szFullPath, feTo->szFullPath, IsAttribDir(feFrom->attributes));
|
||||
}
|
||||
|
||||
/* alert the user if an unsupported flag is used */
|
||||
|
@ -1564,27 +1727,41 @@ int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp)
|
|||
|
||||
ZeroMemory(&op, sizeof(op));
|
||||
op.req = lpFileOp;
|
||||
op.totalSize.QuadPart = 0ull;
|
||||
op.completedSize.QuadPart = 0ull;
|
||||
op.bManyItems = (flFrom.dwNumFiles > 1);
|
||||
|
||||
if (lpFileOp->wFunc != FO_RENAME && !(lpFileOp->fFlags & FOF_SILENT)) {
|
||||
CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC_SERVER, IID_IProgressDialog, (void**) &op.progress);
|
||||
op.progress->StartProgressDialog(op.req->hwnd, NULL, PROGDLG_NORMAL & PROGDLG_AUTOTIME, NULL);
|
||||
_SetOperationTitle(&op);
|
||||
_FileOpCountManager(&op, &flFrom);
|
||||
}
|
||||
|
||||
switch (lpFileOp->wFunc)
|
||||
{
|
||||
case FO_COPY:
|
||||
ret = copy_files(&op, &flFrom, &flTo);
|
||||
ret = copy_files(&op, op.req->fFlags & FOF_MULTIDESTFILES, &flFrom, &flTo);
|
||||
break;
|
||||
case FO_DELETE:
|
||||
ret = delete_files(lpFileOp, &flFrom);
|
||||
ret = delete_files(&op, &flFrom);
|
||||
break;
|
||||
case FO_MOVE:
|
||||
ret = move_files(lpFileOp, &flFrom, &flTo);
|
||||
ret = move_files(&op, op.req->fFlags & FOF_MULTIDESTFILES, &flFrom, &flTo);
|
||||
break;
|
||||
case FO_RENAME:
|
||||
ret = rename_files(lpFileOp, &flFrom, &flTo);
|
||||
ret = rename_files(&op, &flFrom, &flTo);
|
||||
break;
|
||||
default:
|
||||
ret = ERROR_INVALID_PARAMETER;
|
||||
break;
|
||||
}
|
||||
|
||||
if (op.progress) {
|
||||
op.progress->StopProgressDialog();
|
||||
op.progress->Release();
|
||||
}
|
||||
|
||||
destroy_file_list(&flFrom);
|
||||
|
||||
if (lpFileOp->wFunc != FO_DELETE)
|
||||
|
@ -1824,3 +2001,87 @@ EXTERN_C HRESULT WINAPI SHPathPrepareForWriteA(HWND hwnd, IUnknown *modless, LPC
|
|||
MultiByteToWideChar( CP_ACP, 0, path, -1, wpath, MAX_PATH);
|
||||
return SHPathPrepareForWriteW(hwnd, modless, wpath, flags);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* The two following background operations were modified from filedefext.cpp
|
||||
* They use an inordinate amount of mutable state across the string functions,
|
||||
* so are not easy to follow and care is required when modifying.
|
||||
*/
|
||||
|
||||
DWORD WINAPI
|
||||
_FileOpCountManager(FILE_OPERATION *op, const FILE_LIST *from)
|
||||
{
|
||||
DWORD ticks = GetTickCount();
|
||||
FILE_ENTRY *entryToCount;
|
||||
|
||||
for (UINT i = 0; i < from->dwNumFiles; i++)
|
||||
{
|
||||
entryToCount = &from->feFiles[i];
|
||||
|
||||
WCHAR theFileName[MAX_PATH];
|
||||
StringCchCopyW(theFileName, MAX_PATH, entryToCount->szFullPath);
|
||||
_FileOpCount(op, theFileName, IsAttribDir(entryToCount->attributes), &ticks);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// All path manipulations, even when this function is nested, occur on the one buffer.
|
||||
static BOOL
|
||||
_FileOpCount(FILE_OPERATION *op, LPWSTR pwszBuf, BOOL bFolder, DWORD *ticks)
|
||||
{
|
||||
/* Find filename position */
|
||||
UINT cchBuf = wcslen(pwszBuf);
|
||||
WCHAR *pwszFilename = pwszBuf + cchBuf;
|
||||
size_t cchFilenameMax = MAX_PATH - cchBuf;
|
||||
if (!cchFilenameMax)
|
||||
return FALSE;
|
||||
|
||||
if (bFolder) {
|
||||
*(pwszFilename++) = '\\';
|
||||
--cchFilenameMax;
|
||||
/* Find all files, FIXME: shouldn't be "*"? */
|
||||
StringCchCopyW(pwszFilename, cchFilenameMax, L"*");
|
||||
}
|
||||
|
||||
WIN32_FIND_DATAW wfd;
|
||||
HANDLE hFind = FindFirstFileW(pwszBuf, &wfd);
|
||||
if (hFind == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
ERR("FindFirstFileW %ls failed\n", pwszBuf);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
{
|
||||
/* Don't process "." and ".." items */
|
||||
if (!wcscmp(wfd.cFileName, L".") || !wcscmp(wfd.cFileName, L".."))
|
||||
continue;
|
||||
|
||||
StringCchCopyW(pwszFilename, cchFilenameMax, wfd.cFileName);
|
||||
_FileOpCount(op, pwszBuf, TRUE, ticks);
|
||||
}
|
||||
else
|
||||
{
|
||||
ULARGE_INTEGER FileSize;
|
||||
FileSize.u.LowPart = wfd.nFileSizeLow;
|
||||
FileSize.u.HighPart = wfd.nFileSizeHigh;
|
||||
op->totalSize.QuadPart += FileSize.QuadPart;
|
||||
}
|
||||
if (GetTickCount() - *ticks > (DWORD) 500)
|
||||
{
|
||||
// Check if the dialog has ended. If it has, we'll spin down.
|
||||
if (op->progress != NULL)
|
||||
op->bCancelled = op->progress->HasUserCancelled();
|
||||
|
||||
if (op->bCancelled)
|
||||
break;
|
||||
*ticks = GetTickCount();
|
||||
}
|
||||
} while(FindNextFileW(hFind, &wfd));
|
||||
|
||||
FindClose(hFind);
|
||||
return TRUE;
|
||||
}
|
||||
|
|
|
@ -203,6 +203,14 @@
|
|||
#define IDS_DESCRIPTION 331
|
||||
#define IDS_COPY_OF 332
|
||||
|
||||
/* Strings for file operations*/
|
||||
#define IDS_FILEOOP_COPYING 333
|
||||
#define IDS_FILEOOP_MOVING 334
|
||||
#define IDS_FILEOOP_DELETING 335
|
||||
#define IDS_FILEOOP_FROM_TO 336
|
||||
#define IDS_FILEOOP_FROM 337
|
||||
#define IDS_FILEOOP_PREFLIGHT 338
|
||||
|
||||
#define IDS_MENU_EMPTY 34561
|
||||
|
||||
/* Note: those strings are referenced from the registry */
|
||||
|
|
Loading…
Reference in a new issue