Added patch for support of shell32 file operation progress dialog.

This commit is contained in:
Sebastian Lackner
2015-02-27 02:12:30 +01:00
parent cc1368ba9e
commit e01d563654
9 changed files with 2030 additions and 7 deletions

View File

@@ -0,0 +1,453 @@
From a6f9101ab518d9cf5a9ab34533ba1d684ee61d8b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michael=20M=C3=BCller?= <michael@fds-team.de>
Date: Fri, 27 Feb 2015 01:04:33 +0100
Subject: shell32: Implement file operation progress dialog.
Based on a patch by Huw Campbell.
---
dlls/shell32/shell32.rc | 7 ++
dlls/shell32/shlfileop.c | 277 +++++++++++++++++++++++++++++++++++++++++++++--
dlls/shell32/shresdef.h | 8 ++
3 files changed, 285 insertions(+), 7 deletions(-)
diff --git a/dlls/shell32/shell32.rc b/dlls/shell32/shell32.rc
index 37acbe7..dd054fb 100644
--- a/dlls/shell32/shell32.rc
+++ b/dlls/shell32/shell32.rc
@@ -184,6 +184,13 @@ If the files in the destination folder have the same names as files in the\n\
selected folder they will be replaced. Do you still want to move or copy\n\
the folder?"
+ IDS_FILEOP_COPYING "Copying"
+ IDS_FILEOP_MOVING "Moving"
+ IDS_FILEOP_DELETING "Deleting"
+ IDS_FILEOP_FROM_TO "From %1 to %2"
+ IDS_FILEOP_FROM "From %1"
+ IDS_FILEOP_PREFLIGHT "Preflight"
+
/* message box strings */
IDS_RESTART_TITLE "Restart"
IDS_RESTART_PROMPT "Do you want to simulate a Windows reboot?"
diff --git a/dlls/shell32/shlfileop.c b/dlls/shell32/shlfileop.c
index ed8ff38..cef2246 100644
--- a/dlls/shell32/shlfileop.c
+++ b/dlls/shell32/shlfileop.c
@@ -65,6 +65,10 @@ typedef struct
DWORD dwYesToAllMask;
BOOL bManyItems;
BOOL bCancelled;
+ IProgressDialog *progress;
+ ULARGE_INTEGER completedSize;
+ ULARGE_INTEGER totalSize;
+ WCHAR szBuilderString[64];
} FILE_OPERATION;
typedef struct
@@ -103,6 +107,12 @@ static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly);
static DWORD 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);
+static void progressbar_calc_totalsize(FILE_OPERATION *op, const FILE_LIST *from);
+static void progressbar_update_title(FILE_OPERATION *op);
+static void progressbar_update_files(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dst);
+static DWORD CALLBACK progressbar_copy_routine(LARGE_INTEGER total_size, LARGE_INTEGER total_transferred, LARGE_INTEGER stream_size,
+ LARGE_INTEGER stream_transferred, DWORD stream_number, DWORD reason, HANDLE src_file, HANDLE dst_file, LPVOID user);
+
/* 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};
@@ -395,6 +405,13 @@ static DWORD SHELL_DeleteDirectoryW(FILE_OPERATION *op, LPCWSTR pszDir, BOOL bSh
ret = SHELL_DeleteDirectoryW(op, szTemp, FALSE);
else
ret = SHNotifyDeleteFileW(op, szTemp);
+
+ /* Check if dialog was cancelled in the meantime */
+ if (op->progress != NULL)
+ op->bCancelled |= IProgressDialog_HasUserCancelled(op->progress);
+ if (op->bCancelled)
+ break;
+
} while (!ret && FindNextFileW(hFind, &wfd));
}
FindClose(hFind);
@@ -548,10 +565,22 @@ static DWORD SHNotifyDeleteFileA(FILE_OPERATION *op, LPCSTR path)
static DWORD SHNotifyDeleteFileW(FILE_OPERATION *op, LPCWSTR path)
{
BOOL ret;
+ LARGE_INTEGER filesize;
+ filesize.QuadPart = 0;
TRACE("(%s)\n", debugstr_w(path));
- /* FIXME: Implement progress dialog - op can also be zero! */
+ /* Warning: can also be called with empty op */
+ if (op)
+ {
+ WIN32_FILE_ATTRIBUTE_DATA info;
+ progressbar_update_files(op, path, NULL);
+ if (GetFileAttributesExW(path, GetFileExInfoStandard, &info))
+ {
+ filesize.u.HighPart = info.nFileSizeHigh;
+ filesize.u.LowPart = info.nFileSizeLow;
+ }
+ }
ret = DeleteFileW(path);
if (!ret)
@@ -564,6 +593,14 @@ static DWORD SHNotifyDeleteFileW(FILE_OPERATION *op, LPCWSTR path)
}
if (ret)
{
+ if (op)
+ {
+ /* There is no progress while deleting a file,
+ * simply report full file size when we are done. */
+ progressbar_copy_routine(filesize, filesize, filesize, filesize, 0,
+ CALLBACK_STREAM_SWITCH, NULL, NULL, op);
+ }
+
SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, path, NULL);
return ERROR_SUCCESS;
}
@@ -598,9 +635,10 @@ static DWORD SHNotifyMoveFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest)
TRACE("(%s %s)\n", debugstr_w(src), debugstr_w(dest));
- /* FIXME: Implement progress dialog */
+ progressbar_update_files(op, src, dest);
- ret = MoveFileExW(src, dest, MOVEFILE_REPLACE_EXISTING);
+ ret = MoveFileWithProgressW(src, dest, progressbar_copy_routine,
+ op, MOVEFILE_REPLACE_EXISTING);
/* MOVEFILE_REPLACE_EXISTING fails with dirs, so try MoveFile */
if (!ret)
@@ -650,14 +688,15 @@ static DWORD SHNotifyCopyFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BO
TRACE("(%s %s %s)\n", debugstr_w(src), debugstr_w(dest), bFailIfExists ? "failIfExists" : "");
- /* FIXME: Update progress dialog */
+ progressbar_update_files(op, src, dest);
/* Destination file may already exist with read only attribute */
attribs = GetFileAttributesW(dest);
if (IsAttrib(attribs, FILE_ATTRIBUTE_READONLY))
SetFileAttributesW(dest, attribs & ~FILE_ATTRIBUTE_READONLY);
- ret = CopyFileW(src, dest, bFailIfExists);
+ ret = CopyFileExW(src, dest, progressbar_copy_routine, op,
+ &op->bCancelled, bFailIfExists);
if (ret)
{
SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, dest, NULL);
@@ -1293,6 +1332,8 @@ static DWORD copy_files(FILE_OPERATION *op, BOOL multidest, const FILE_LIST *flF
}
/* Vista return code. XP would return e.g. ERROR_FILE_NOT_FOUND, ERROR_ALREADY_EXISTS */
+ if (op->progress != NULL)
+ op->bCancelled |= IProgressDialog_HasUserCancelled(op->progress);
if (op->bCancelled)
return ERROR_CANCELLED;
}
@@ -1374,13 +1415,17 @@ static DWORD delete_files(FILE_OPERATION *op, const FILE_LIST *flFrom)
/* delete the file or directory */
if (IsAttribFile(fileEntry->attributes))
- ret = DeleteFileW(fileEntry->szFullPath) ?
- ERROR_SUCCESS : GetLastError();
+ ret = SHNotifyDeleteFileW(op, fileEntry->szFullPath);
else
ret = SHELL_DeleteDirectoryW(op, fileEntry->szFullPath, FALSE);
if (ret)
return ret;
+
+ if (op->progress != NULL)
+ op->bCancelled |= IProgressDialog_HasUserCancelled(op->progress);
+ if (op->bCancelled)
+ return ERROR_CANCELLED;
}
return ERROR_SUCCESS;
@@ -1485,6 +1530,11 @@ static DWORD move_files(FILE_OPERATION *op, BOOL multidest, const FILE_LIST *flF
move_to_dir(op, entryToMove, fileDest);
else
SHNotifyMoveFileW(op, entryToMove->szFullPath, fileDest->szFullPath);
+
+ if (op->progress != NULL)
+ op->bCancelled |= IProgressDialog_HasUserCancelled(op->progress);
+ if (op->bCancelled)
+ return ERROR_CANCELLED;
}
if (mismatched > 0)
@@ -1544,6 +1594,7 @@ int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp)
{
FILE_OPERATION op;
FILE_LIST flFrom, flTo;
+ HRESULT co_ret = E_FAIL;
int ret = 0;
if (!lpFileOp)
@@ -1562,9 +1613,31 @@ int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp)
ZeroMemory(&op, sizeof(op));
op.req = lpFileOp;
+ op.totalSize.QuadPart = 0;
+ op.completedSize.QuadPart = 0;
op.bManyItems = (flFrom.dwNumFiles > 1);
lpFileOp->fAnyOperationsAborted = FALSE;
+ if (lpFileOp->wFunc != FO_RENAME && !(lpFileOp->fFlags & FOF_SILENT))
+ {
+ co_ret = CoInitialize(NULL);
+ ret = CoCreateInstance(&CLSID_ProgressDialog, NULL, CLSCTX_INPROC_SERVER,
+ &IID_IProgressDialog, (void**)&op.progress);
+ if (SUCCEEDED(ret))
+ {
+ IProgressDialog_StartProgressDialog(op.progress, op.req->hwnd, NULL,
+ PROGDLG_NORMAL | PROGDLG_AUTOTIME, NULL);
+
+ progressbar_update_title(&op);
+ progressbar_calc_totalsize(&op, &flFrom);
+ }
+ else
+ {
+ FIXME("Failed to create progress dialog\n");
+ op.progress = NULL;
+ }
+ }
+
switch (lpFileOp->wFunc)
{
case FO_COPY:
@@ -1584,6 +1657,12 @@ int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp)
break;
}
+ if (op.progress)
+ {
+ IProgressDialog_StopProgressDialog(op.progress);
+ IProgressDialog_Release(op.progress);
+ }
+
destroy_file_list(&flFrom);
if (lpFileOp->wFunc != FO_DELETE)
@@ -1592,6 +1671,9 @@ int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp)
if (ret == ERROR_CANCELLED)
lpFileOp->fAnyOperationsAborted = TRUE;
+ if (SUCCEEDED(co_ret))
+ CoUninitialize();
+
return ret;
}
@@ -1822,3 +1904,184 @@ HRESULT WINAPI SHPathPrepareForWriteW(HWND hwnd, IUnknown *modless, LPCWSTR path
else
return HRESULT_FROM_WIN32(ERROR_DIRECTORY);
}
+
+static BOOL progressbar_calc_size(FILE_OPERATION *op, LPWSTR buf, BOOL is_folder, DWORD *ticks)
+{
+ WIN32_FIND_DATAW wfd;
+ HANDLE find;
+ UINT i = strlenW(buf);
+ WCHAR *file = buf + i;
+ size_t size = MAX_PATH - i;
+
+ if (size < 3)
+ return FALSE;
+
+ if (is_folder)
+ {
+ *file++ = '\\';
+ size--;
+
+ file[0] = '*';
+ file[1] = 0;
+ }
+ else
+ {
+ file[0] = 0;
+ }
+
+ find = FindFirstFileW(buf, &wfd);
+ if (find == INVALID_HANDLE_VALUE)
+ {
+ WARN("FindFirstFileW %s failed\n", debugstr_w(buf));
+ return FALSE;
+ }
+
+ do
+ {
+ if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ if (wfd.cFileName[0] == '.')
+ {
+ if (wfd.cFileName[1] == 0) continue;
+ if (wfd.cFileName[1] == '.' && wfd.cFileName[2] == 0) continue;
+ }
+
+ if (!lstrcpynW(file, wfd.cFileName, size)) continue;
+ progressbar_calc_size(op, buf, TRUE, ticks);
+ }
+ else
+ {
+ LARGE_INTEGER filesize;
+ filesize.u.LowPart = wfd.nFileSizeLow;
+ filesize.u.HighPart = wfd.nFileSizeHigh;
+ op->totalSize.QuadPart += filesize.QuadPart;
+ }
+
+ if (GetTickCount() - *ticks > (DWORD) 500)
+ {
+ if (op->progress != NULL)
+ op->bCancelled |= IProgressDialog_HasUserCancelled(op->progress);
+ if (op->bCancelled)
+ break;
+ *ticks = GetTickCount();
+ }
+
+ }
+ while (FindNextFileW(find, &wfd));
+
+ FindClose(find);
+ return TRUE;
+}
+
+static void progressbar_calc_totalsize(FILE_OPERATION *op, const FILE_LIST *from)
+{
+ WCHAR filename[MAX_PATH];
+ DWORD ticks = GetTickCount();
+ UINT i;
+
+ op->totalSize.QuadPart = 0;
+
+ for (i = 0; i < from->dwNumFiles && !op->bCancelled; i++)
+ {
+ if (!lstrcpynW(filename, from->feFiles[i].szFullPath, sizeof(filename)/sizeof(filename[0])))
+ continue;
+ progressbar_calc_size(op, filename, IsAttribDir(from->feFiles[i].attributes), &ticks);
+ }
+}
+
+static void progressbar_update_title(FILE_OPERATION *op)
+{
+ WCHAR buf[64];
+ UINT title_id, builder_id;
+
+ if (op->progress == NULL)
+ return;
+
+ switch (op->req->wFunc)
+ {
+ case FO_COPY:
+ title_id = IDS_FILEOP_COPYING;
+ builder_id = IDS_FILEOP_FROM_TO;
+ break;
+
+ case FO_DELETE:
+ title_id = IDS_FILEOP_DELETING;
+ builder_id = IDS_FILEOP_FROM;
+ break;
+
+ case FO_MOVE:
+ title_id = IDS_FILEOP_MOVING;
+ builder_id = IDS_FILEOP_FROM_TO;
+ break;
+
+ default:
+ return;
+ }
+
+ LoadStringW(shell32_hInstance, title_id, buf, sizeof(buf)/sizeof(WCHAR));
+ IProgressDialog_SetTitle(op->progress, buf);
+
+ LoadStringW(shell32_hInstance, builder_id, op->szBuilderString,
+ sizeof(op->szBuilderString)/sizeof(WCHAR));
+
+ LoadStringW(shell32_hInstance, IDS_FILEOP_PREFLIGHT, buf, sizeof(buf)/sizeof(WCHAR));
+ IProgressDialog_SetLine(op->progress, 1, buf, FALSE, NULL);
+}
+
+static void progressbar_update_files(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dst)
+{
+ LPWSTR src_file, dst_file;
+ WCHAR src_dir[64], dst_dir[64], final[260];
+ DWORD_PTR args[2] = {0, 0};
+
+ if (!op->progress || !src || (op->req->wFunc == FO_MOVE && !dst))
+ return;
+
+ if (op->req->wFunc != FO_COPY &&
+ op->req->wFunc != FO_MOVE &&
+ op->req->wFunc != FO_DELETE)
+ {
+ return;
+ }
+
+ src_file = PathFindFileNameW(src);
+ lstrcpynW(src_dir, src, min(sizeof(src_dir) / sizeof(WCHAR) - 1, src_file - src));
+ args[0] = (DWORD_PTR)&src_dir;
+
+ if (op->req->wFunc == FO_MOVE ||
+ op->req->wFunc == FO_COPY)
+ {
+ if (PathIsDirectoryW(dst))
+ args[1] = (DWORD_PTR)&dst;
+ else
+ {
+ dst_file = PathFindFileNameW(dst);
+ lstrcpynW(dst_dir, dst, min(sizeof(dst_dir) / sizeof(WCHAR) - 1, dst_file - dst));
+ args[1] = (DWORD_PTR)&dst_dir;
+ }
+ }
+
+ FormatMessageW(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, op->szBuilderString,
+ 0, 0, final, sizeof(final)/sizeof(final[0]), (__ms_va_list *)&args);
+
+ IProgressDialog_SetLine(op->progress, 1, src_file, FALSE, NULL);
+ IProgressDialog_SetLine(op->progress, 2, final, FALSE, NULL);
+}
+
+static DWORD CALLBACK progressbar_copy_routine(LARGE_INTEGER total_size, LARGE_INTEGER total_transferred, LARGE_INTEGER stream_size,
+ LARGE_INTEGER stream_transferred, DWORD stream_number, DWORD reason, HANDLE src_file, HANDLE dst_file, LPVOID user)
+{
+ FILE_OPERATION *op = (FILE_OPERATION *)user;
+
+ if (!op->progress)
+ return PROGRESS_CONTINUE;
+
+ if (reason == CALLBACK_STREAM_SWITCH)
+ op->completedSize.QuadPart += total_size.QuadPart;
+
+ IProgressDialog_SetProgress64(op->progress, op->completedSize.QuadPart - total_size.QuadPart +
+ total_transferred.QuadPart, op->totalSize.QuadPart);
+
+ op->bCancelled |= IProgressDialog_HasUserCancelled(op->progress);
+ return op->bCancelled ? PROGRESS_CANCEL : PROGRESS_CONTINUE;
+}
diff --git a/dlls/shell32/shresdef.h b/dlls/shell32/shresdef.h
index 183a75e..8ee525a 100644
--- a/dlls/shell32/shresdef.h
+++ b/dlls/shell32/shresdef.h
@@ -149,6 +149,14 @@
#define IDM_RECYCLEBIN_RESTORE 301
#define IDM_RECYCLEBIN_ERASE 302
+/* Strings for file operations */
+#define IDS_FILEOP_COPYING 333
+#define IDS_FILEOP_MOVING 334
+#define IDS_FILEOP_DELETING 335
+#define IDS_FILEOP_FROM_TO 336
+#define IDS_FILEOP_FROM 337
+#define IDS_FILEOP_PREFLIGHT 338
+
/* Note: this string is referenced from the registry*/
#define IDS_RECYCLEBIN_FOLDER_NAME 8964
--
2.3.0

View File

@@ -0,0 +1,2 @@
Fixes: Support for shell32 file operation progress dialog
Depends: kernel32-CopyFileEx