From: James McDonnell topgamer7@gmail.com
--- dlls/kernel32/path.c | 20 ++++-- dlls/kernel32/tests/file.c | 139 +++++++++++++++++++++++++++++++++++++ dlls/kernelbase/file.c | 58 ++++++++++++++-- 3 files changed, 206 insertions(+), 11 deletions(-)
diff --git a/dlls/kernel32/path.c b/dlls/kernel32/path.c index 2dd3eac3c26..180edf358fa 100644 --- a/dlls/kernel32/path.c +++ b/dlls/kernel32/path.c @@ -143,9 +143,13 @@ BOOL WINAPI CopyFileExA(LPCSTR sourceFilename, LPCSTR destFilename, */ BOOL WINAPI MoveFileTransactedA(const char *source, const char *dest, LPPROGRESS_ROUTINE progress, void *data, DWORD flags, HANDLE handle) { - FIXME("(%s, %s, %p, %p, %ld, %p)\n", debugstr_a(source), debugstr_a(dest), progress, data, flags, handle); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; + BOOL ret; + + TRACE("(%s, %s, %p, %p, %ld, %p)\n", debugstr_a(source), debugstr_a(dest), progress, data, flags, handle); + + ret = MoveFileWithProgressA(source, dest, progress, data, flags); + FIXME("Transaction handle not finalized."); + return ret; }
/************************************************************************** @@ -153,9 +157,13 @@ BOOL WINAPI MoveFileTransactedA(const char *source, const char *dest, LPPROGRESS */ BOOL WINAPI MoveFileTransactedW(const WCHAR *source, const WCHAR *dest, LPPROGRESS_ROUTINE progress, void *data, DWORD flags, HANDLE handle) { - FIXME("(%s, %s, %p, %p, %ld, %p)\n", debugstr_w(source), debugstr_w(dest), progress, data, flags, handle); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; + BOOL ret; + + TRACE("(%s, %s, %p, %p, %ld, %p)\n", debugstr_w(source), debugstr_w(dest), progress, data, flags, handle); + + ret = MoveFileWithProgressW(source, dest, progress, data, flags); + FIXME("Transaction handle not finalized."); + return ret; }
/************************************************************************** diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index bd8d8c91b2f..cf4f0fc126a 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -6139,6 +6139,144 @@ static void test_eof(void) ok(ret, "failed to delete %s, error %lu\n", debugstr_a(filename), GetLastError()); }
+const char * reasons[2] = { + "CALLBACK_CHUNK_FINISHED", + "CALLBACK_STREAM_SWITCH" +}; + +typedef struct { + LARGE_INTEGER TotalFileSize; + LARGE_INTEGER TotalBytesTransferred; + LARGE_INTEGER StreamSize; + LARGE_INTEGER StreamBytesTransferred; + DWORD dwStreamNumber; + DWORD dwCallbackReason; + HANDLE hSourceFile; + HANDLE hDestinationFile; +} ProgressCall; + +typedef struct +{ + ProgressCall *calls; + int call_count; + int calls_size; +} ProgressOutput; + +DWORD LpprogressRoutine(LARGE_INTEGER TotalFileSize, LARGE_INTEGER TotalBytesTransferred, LARGE_INTEGER StreamSize, LARGE_INTEGER StreamBytesTransferred, + DWORD dwStreamNumber, DWORD dwCallbackReason, HANDLE hSourceFile, HANDLE hDestinationFile, ProgressOutput *lpData) +{ + ProgressCall call; + if (dwStreamNumber > lpData->calls_size) return 0; + call = (ProgressCall){ + .TotalFileSize = TotalFileSize, + .TotalBytesTransferred = TotalBytesTransferred, + .StreamSize = StreamSize, + .StreamBytesTransferred = StreamBytesTransferred, + .dwStreamNumber = dwStreamNumber, + .dwCallbackReason = dwCallbackReason, + .hSourceFile = hSourceFile, + .hDestinationFile = hDestinationFile + }; + lpData->calls[lpData->call_count] = call; + lpData->call_count += 1; + + return PROGRESS_CONTINUE; +} + +static void check_progress_output(ProgressOutput progress_output, int expected_stream_count) +{ + int i, current_stream_number = 0, last_call = progress_output.call_count-1; + LARGE_INTEGER transfer_sum = {{0}}, stream_sum = {{0}}; + + ok(progress_output.call_count > 0,"Expected some progress calls, received %d\n", progress_output.call_count); + + for (i = 0; i < progress_output.call_count; i++) { + ProgressCall call = progress_output.calls[i]; + + trace("i=%d %s TotalFileSize=%lld TotalBytesTransferred=%lld dwStreamNumber=%lu StreamBytesTransferred=%lld StreamSize=%lld\n", + i, reasons[call.dwCallbackReason], call.TotalFileSize.QuadPart, call.TotalBytesTransferred.QuadPart, call.dwStreamNumber, + call.StreamBytesTransferred.QuadPart, progress_output.calls[0].StreamSize.QuadPart); + + if (call.dwCallbackReason == CALLBACK_STREAM_SWITCH) { + current_stream_number += 1; + + ok(call.dwStreamNumber == current_stream_number, + "Received unexpected stream number, expected %d, actual %lu\n", current_stream_number, call.dwStreamNumber); + + ok(call.StreamBytesTransferred.QuadPart == 0, + "Received unexpected stream bytes transferred, expected %lld, actual %lld\n", call.StreamBytesTransferred.QuadPart, transfer_sum.QuadPart); + + if (i > 0) { + ok(stream_sum.QuadPart == call.StreamSize.QuadPart, + "Expected full stream was transferred\n"); + } + + stream_sum.QuadPart = 0; + } else if (call.dwCallbackReason == CALLBACK_CHUNK_FINISHED) { + transfer_sum.QuadPart = call.StreamBytesTransferred.QuadPart; + stream_sum.QuadPart = call.StreamBytesTransferred.QuadPart; + } + ok(transfer_sum.QuadPart == call.TotalBytesTransferred.QuadPart, + "Wrong total bytes transferred expected %lld, actual %lld\n", transfer_sum.QuadPart, call.TotalBytesTransferred.QuadPart); + } + ok(0 == progress_output.calls[0].TotalBytesTransferred.QuadPart, + "Expected no bytes transferred expected %d, actual %lld\n", 0, progress_output.calls[0].TotalBytesTransferred.QuadPart); + + ok(transfer_sum.QuadPart == progress_output.calls[last_call].TotalFileSize.QuadPart, + "Wrong total bytes summation expected %lld, actual %lld\n", transfer_sum.QuadPart, + progress_output.calls[progress_output.call_count].TotalFileSize.QuadPart); + + ok(expected_stream_count == progress_output.calls[last_call].dwStreamNumber, + "Wrong expected stream count %d, actual %lu\n", expected_stream_count, progress_output.calls[last_call].dwStreamNumber); +} + +static void test_WithProgress(void) +{ + const char *ten = "1234567890"; + HANDLE handle; + char path_src[MAX_PATH], + path_dest[MAX_PATH], + tmp_path[MAX_PATH], + buffer[70000]; + ProgressCall calls[50]; + ProgressOutput progress_output; + int i; + + if (!GetTempPathA (sizeof (tmp_path), tmp_path)) + { + return; + } + + strcat (tmp_path, "filedir\"); + CreateDirectoryA(tmp_path, NULL); + + strcpy(path_src, tmp_path); + strcpy(path_dest, tmp_path); + strcat (path_src, "testfile.ext.ext2"); + strcat (path_dest, "testfile2.ext.ext2"); + + handle = CreateFileA(path_src, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0); + for (i = 0; i < 7000; i++) + { + snprintf(buffer, 70000, "%s%s", buffer, ten); + } + for (i = 0; i < 10; i++) + { + ok(WriteFile(handle, buffer, strlen(buffer), NULL, NULL), "Could not write to file"); + } + CloseHandle(handle); + + progress_output = (ProgressOutput){.calls_size = 10, .call_count = 0, .calls = (ProgressCall *)&calls}; + ok(CopyFileExA(path_src, path_dest, (LPPROGRESS_ROUTINE)LpprogressRoutine, &progress_output, NULL, 0), + "Failed to copy file %ld %s %s\n", GetLastError(), path_src, path_dest); + + check_progress_output(progress_output, 1); + + DeleteFileA(path_src); + DeleteFileA(path_dest); + RemoveDirectoryA(tmp_path); +} + START_TEST(file) { char temp_path[MAX_PATH]; @@ -6217,4 +6355,5 @@ START_TEST(file) test_hard_link(); test_move_file(); test_eof(); + test_WithProgress(); } diff --git a/dlls/kernelbase/file.c b/dlls/kernelbase/file.c index 8ae982294f6..62e395e03c8 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -491,6 +491,37 @@ BOOL WINAPI DECLSPEC_HOTPATCH AreFileApisANSI(void) }
+/*********************************************************************** + * handle_progress_callback + * Wrapper for progress callback, if the return value is false, the copy or move operation should be cancelled + */ +BOOL handle_progress_callback(LPPROGRESS_ROUTINE progress, unsigned stream_count, LARGE_INTEGER total, LARGE_INTEGER total_bytes_written, unsigned int buffer_size, LARGE_INTEGER stream_bytes_transferred, BOOL *invoke_progress, + DWORD callback_reason, HANDLE hSourceFile, HANDLE hDestinationFile, LPVOID param) +{ + DWORD ret_progress; + LARGE_INTEGER stream_size; + stream_size.QuadPart = buffer_size; + + if (!*invoke_progress || !progress) + { + return TRUE; + } + ret_progress = progress(total, total_bytes_written, stream_size, stream_bytes_transferred, stream_count, callback_reason, hSourceFile, hDestinationFile, param); + + switch(ret_progress) + { + case PROGRESS_STOP: + FIXME("Handle resumable copy/move operation"); + case PROGRESS_CANCEL: + return FALSE; + case PROGRESS_QUIET: + *invoke_progress = 0; + break; + } + return TRUE; +} + + /*********************************************************************** * CopyFileExW (kernelbase.@) */ @@ -501,9 +532,10 @@ BOOL WINAPI CopyFileExW( const WCHAR *source, const WCHAR *dest, LPPROGRESS_ROUT HANDLE h1, h2; FILE_BASIC_INFORMATION info; IO_STATUS_BLOCK io; - DWORD count; - BOOL ret = FALSE; - char *buffer; + DWORD count, res, stream_count = 0; + BOOL ret = FALSE, invoke_progress = 1; + char *buffer, *p; + LARGE_INTEGER total, total_written = {{0}}, stream_written = {{0}};
if (!source || !dest) { @@ -541,6 +573,10 @@ BOOL WINAPI CopyFileExW( const WCHAR *source, const WCHAR *dest, LPPROGRESS_ROUT return FALSE; }
+ if (progress) { + total.u.LowPart = GetFileSize(h1, (LPDWORD)&total.u.HighPart); + } + if (!(flags & COPY_FILE_FAIL_IF_EXISTS)) { BOOL same_file = FALSE; @@ -569,15 +605,27 @@ BOOL WINAPI CopyFileExW( const WCHAR *source, const WCHAR *dest, LPPROGRESS_ROUT return FALSE; }
+ stream_count++; while (ReadFile( h1, buffer, buffer_size, &count, NULL ) && count) { - char *p = buffer; + p = buffer; + if(total_written.QuadPart == 0 && !handle_progress_callback(progress, stream_count, total, total_written,buffer_size, + stream_written, &invoke_progress, CALLBACK_STREAM_SWITCH, h1, h2, param)) + { + break; + } while (count != 0) { - DWORD res; if (!WriteFile( h2, p, count, &res, NULL ) || !res) goto done; + total_written.QuadPart += res; + stream_written.QuadPart += res; p += res; count -= res; + if(!handle_progress_callback(progress, stream_count, total, total_written, buffer_size, + stream_written, &invoke_progress, CALLBACK_CHUNK_FINISHED, h1, h2, param)) + { + break; + } } } ret = TRUE;