From: Paul Gofman pgofman@codeweavers.com
--- dlls/shell32/shell32_classes.idl | 5 +++++ 1 file changed, 5 insertions(+)
diff --git a/dlls/shell32/shell32_classes.idl b/dlls/shell32/shell32_classes.idl index 135ef52f7b0..932b6f395b2 100644 --- a/dlls/shell32/shell32_classes.idl +++ b/dlls/shell32/shell32_classes.idl @@ -189,3 +189,8 @@ coclass KnownFolderManager { interface IKnownFolderManager; } uuid(d969a300-e7ff-11d0-a93b-00a0c90f2719) ] coclass NewMenu {} + +[ + threading(apartment), + uuid(9ac9fbe1-e0a2-4ad6-b4ee-e212013ea917) +] coclass ShellItem { interface IShellItem2; }
From: Paul Gofman pgofman@codeweavers.com
--- include/Makefile.in | 1 + include/sherrors.h | 115 ++++++++++++++++++++++++++++++++++++++ include/shobjidl_core.idl | 17 ++++++ 3 files changed, 133 insertions(+) create mode 100644 include/sherrors.h
diff --git a/include/Makefile.in b/include/Makefile.in index bbf28cfc87e..000214484ed 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -709,6 +709,7 @@ SOURCES = \ shdispid.h \ shellapi.h \ shellscalingapi.h \ + sherrors.h \ shimgdata.idl \ shldisp.idl \ shlguid.h \ diff --git a/include/sherrors.h b/include/sherrors.h new file mode 100644 index 00000000000..ff0a63f606c --- /dev/null +++ b/include/sherrors.h @@ -0,0 +1,115 @@ +/* + * Copyright 2024 Paul Gofman for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef __WINE_SHERRORS_H +#define __WINE_SHERRORS_H + +#include <winerror.h> + +#define COPYENGINE_S_YES _HRESULT_TYPEDEF_(0x270001) +#define COPYENGINE_S_NOT_HANDLED _HRESULT_TYPEDEF_(0x270003) +#define COPYENGINE_S_USER_RETRY _HRESULT_TYPEDEF_(0x270004) +#define COPYENGINE_S_USER_IGNORED _HRESULT_TYPEDEF_(0x270005) +#define COPYENGINE_S_MERGE _HRESULT_TYPEDEF_(0x270006) +#define COPYENGINE_S_DONT_PROCESS_CHILDREN _HRESULT_TYPEDEF_(0x270008) +#define COPYENGINE_S_ALREADY_DONE _HRESULT_TYPEDEF_(0x27000a) +#define COPYENGINE_S_PENDING _HRESULT_TYPEDEF_(0x27000b) +#define COPYENGINE_S_KEEP_BOTH _HRESULT_TYPEDEF_(0x27000c) +#define COPYENGINE_S_CLOSE_PROGRAM _HRESULT_TYPEDEF_(0x27000d) +#define COPYENGINE_S_COLLISIONRESOLVED _HRESULT_TYPEDEF_(0x27000e) +#define COPYENGINE_S_PROGRESS_PAUSE _HRESULT_TYPEDEF_(0x27000f) +#define COPYENGINE_S_PENDING_DELETE _HRESULT_TYPEDEF_(0x270010) + +#define COPYENGINE_E_USER_CANCELLED _HRESULT_TYPEDEF_(0x80270000) +#define COPYENGINE_E_CANCELLED _HRESULT_TYPEDEF_(0x80270001) +#define COPYENGINE_E_REQUIRES_ELEVATION _HRESULT_TYPEDEF_(0x80270002) +#define COPYENGINE_E_SAME_FILE _HRESULT_TYPEDEF_(0x80270003) +#define COPYENGINE_E_DIFF_DIR _HRESULT_TYPEDEF_(0x80270004) +#define COPYENGINE_E_MANY_SRC_1_DEST _HRESULT_TYPEDEF_(0x80270005) +#define COPYENGINE_E_DEST_SUBTREE _HRESULT_TYPEDEF_(0x80270009) +#define COPYENGINE_E_DEST_SAME_TREE _HRESULT_TYPEDEF_(0x8027000a) +#define COPYENGINE_E_FLD_IS_FILE_DEST _HRESULT_TYPEDEF_(0x8027000b) +#define COPYENGINE_E_FILE_IS_FLD_DEST _HRESULT_TYPEDEF_(0x8027000c) +#define COPYENGINE_E_FILE_TOO_LARGE _HRESULT_TYPEDEF_(0x8027000d) +#define COPYENGINE_E_REMOVABLE_FULL _HRESULT_TYPEDEF_(0x8027000e) +#define COPYENGINE_E_DEST_IS_RO_CD _HRESULT_TYPEDEF_(0x8027000f) +#define COPYENGINE_E_DEST_IS_RW_CD _HRESULT_TYPEDEF_(0x80270010) +#define COPYENGINE_E_DEST_IS_R_CD _HRESULT_TYPEDEF_(0x80270011) +#define COPYENGINE_E_DEST_IS_RO_DVD _HRESULT_TYPEDEF_(0x80270012) +#define COPYENGINE_E_DEST_IS_RW_DVD _HRESULT_TYPEDEF_(0x80270013) +#define COPYENGINE_E_DEST_IS_R_DVD _HRESULT_TYPEDEF_(0x80270014) +#define COPYENGINE_E_SRC_IS_RO_CD _HRESULT_TYPEDEF_(0x80270015) +#define COPYENGINE_E_SRC_IS_RW_CD _HRESULT_TYPEDEF_(0x80270016) +#define COPYENGINE_E_SRC_IS_R_CD _HRESULT_TYPEDEF_(0x80270017) +#define COPYENGINE_E_SRC_IS_RO_DVD _HRESULT_TYPEDEF_(0x80270018) +#define COPYENGINE_E_SRC_IS_RW_DVD _HRESULT_TYPEDEF_(0x80270019) +#define COPYENGINE_E_SRC_IS_R_DVD _HRESULT_TYPEDEF_(0x8027001a) +#define COPYENGINE_E_INVALID_FILES_SRC _HRESULT_TYPEDEF_(0x8027001b) +#define COPYENGINE_E_INVALID_FILES_DEST _HRESULT_TYPEDEF_(0x8027001c) +#define COPYENGINE_E_PATH_TOO_DEEP_SRC _HRESULT_TYPEDEF_(0x8027001d) +#define COPYENGINE_E_PATH_TOO_DEEP_DEST _HRESULT_TYPEDEF_(0x8027001e) +#define COPYENGINE_E_ROOT_DIR_SRC _HRESULT_TYPEDEF_(0x8027001f) +#define COPYENGINE_E_ROOT_DIR_DEST _HRESULT_TYPEDEF_(0x80270020) +#define COPYENGINE_E_ACCESS_DENIED_SRC _HRESULT_TYPEDEF_(0x80270021) +#define COPYENGINE_E_ACCESS_DENIED_DEST _HRESULT_TYPEDEF_(0x80270022) +#define COPYENGINE_E_PATH_NOT_FOUND_SRC _HRESULT_TYPEDEF_(0x80270023) +#define COPYENGINE_E_PATH_NOT_FOUND_DEST _HRESULT_TYPEDEF_(0x80270024) +#define COPYENGINE_E_NET_DISCONNECT_SRC _HRESULT_TYPEDEF_(0x80270025) +#define COPYENGINE_E_NET_DISCONNECT_DEST _HRESULT_TYPEDEF_(0x80270026) +#define COPYENGINE_E_SHARING_VIOLATION_SRC _HRESULT_TYPEDEF_(0x80270027) +#define COPYENGINE_E_SHARING_VIOLATION_DEST _HRESULT_TYPEDEF_(0x80270028) +#define COPYENGINE_E_ALREADY_EXISTS_NORMAL _HRESULT_TYPEDEF_(0x80270029) +#define COPYENGINE_E_ALREADY_EXISTS_READONLY _HRESULT_TYPEDEF_(0x8027002a) +#define COPYENGINE_E_ALREADY_EXISTS_SYSTEM _HRESULT_TYPEDEF_(0x8027002b) +#define COPYENGINE_E_ALREADY_EXISTS_FOLDER _HRESULT_TYPEDEF_(0x8027002c) +#define COPYENGINE_E_STREAM_LOSS _HRESULT_TYPEDEF_(0x8027002d) +#define COPYENGINE_E_EA_LOSS _HRESULT_TYPEDEF_(0x8027002e) +#define COPYENGINE_E_PROPERTY_LOSS _HRESULT_TYPEDEF_(0x8027002f) +#define COPYENGINE_E_PROPERTIES_LOSS _HRESULT_TYPEDEF_(0x80270030) +#define COPYENGINE_E_ENCRYPTION_LOSS _HRESULT_TYPEDEF_(0x80270031) +#define COPYENGINE_E_DISK_FULL _HRESULT_TYPEDEF_(0x80270032) +#define COPYENGINE_E_DISK_FULL_CLEAN _HRESULT_TYPEDEF_(0x80270033) +#define COPYENGINE_E_EA_NOT_SUPPORTED _HRESULT_TYPEDEF_(0x80270034) +#define COPYENGINE_E_RECYCLE_UNKNOWN_ERROR _HRESULT_TYPEDEF_(0x80270035) +#define COPYENGINE_E_RECYCLE_FORCE_NUKE _HRESULT_TYPEDEF_(0x80270036) +#define COPYENGINE_E_RECYCLE_SIZE_TOO_BIG _HRESULT_TYPEDEF_(0x80270037) +#define COPYENGINE_E_RECYCLE_PATH_TOO_LONG _HRESULT_TYPEDEF_(0x80270038) +#define COPYENGINE_E_RECYCLE_BIN_NOT_FOUND _HRESULT_TYPEDEF_(0x8027003a) +#define COPYENGINE_E_NEWFILE_NAME_TOO_LONG _HRESULT_TYPEDEF_(0x8027003b) +#define COPYENGINE_E_NEWFOLDER_NAME_TOO_LONG _HRESULT_TYPEDEF_(0x8027003c) +#define COPYENGINE_E_DIR_NOT_EMPTY _HRESULT_TYPEDEF_(0x8027003d) +#define COPYENGINE_E_FAT_MAX_IN_ROOT _HRESULT_TYPEDEF_(0x8027003e) +#define COPYENGINE_E_ACCESSDENIED_READONLY _HRESULT_TYPEDEF_(0x8027003f) +#define COPYENGINE_E_REDIRECTED_TO_WEBPAGE _HRESULT_TYPEDEF_(0x80270040) +#define COPYENGINE_E_SERVER_BAD_FILE_TYPE _HRESULT_TYPEDEF_(0x80270041) +#define COPYENGINE_E_INTERNET_ITEM_UNAVAILABLE _HRESULT_TYPEDEF_(0x80270042) +#define COPYENGINE_E_CANNOT_MOVE_FROM_RECYCLE_BIN _HRESULT_TYPEDEF_(0x80270043) +#define COPYENGINE_E_CANNOT_MOVE_SHARED_FOLDER _HRESULT_TYPEDEF_(0x80270044) +#define COPYENGINE_E_INTERNET_ITEM_STORAGE_PROVIDER_ERROR _HRESULT_TYPEDEF_(0x80270045) +#define COPYENGINE_E_INTERNET_ITEM_STORAGE_PROVIDER_PAUSED _HRESULT_TYPEDEF_(0x80270046) +#define COPYENGINE_E_REQUIRES_EDP_CONSENT _HRESULT_TYPEDEF_(0x80270047) +#define COPYENGINE_E_BLOCKED_BY_EDP_POLICY _HRESULT_TYPEDEF_(0x80270048) +#define COPYENGINE_E_REQUIRES_EDP_CONSENT_FOR_REMOVABLE_DRIVE _HRESULT_TYPEDEF_(0x80270049) +#define COPYENGINE_E_BLOCKED_BY_EDP_FOR_REMOVABLE_DRIVE _HRESULT_TYPEDEF_(0x8027004a) +#define COPYENGINE_E_RMS_REQUIRES_EDP_CONSENT_FOR_REMOVABLE_DRIVE _HRESULT_TYPEDEF_(0x8027004b) +#define COPYENGINE_E_RMS_BLOCKED_BY_EDP_FOR_REMOVABLE_DRIVE _HRESULT_TYPEDEF_(0x8027004c) +#define COPYENGINE_E_WARNED_BY_DLP_POLICY _HRESULT_TYPEDEF_(0x8027004d) +#define COPYENGINE_E_BLOCKED_BY_DLP_POLICY _HRESULT_TYPEDEF_(0x8027004e) +#define COPYENGINE_E_SILENT_FAIL_BY_DLP_POLICY _HRESULT_TYPEDEF_(0x8027004f) + +#endif diff --git a/include/shobjidl_core.idl b/include/shobjidl_core.idl index dd64204682e..89a06bed47c 100644 --- a/include/shobjidl_core.idl +++ b/include/shobjidl_core.idl @@ -16,6 +16,23 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+cpp_quote("#define FOFX_NOSKIPJUNCTIONS 0x00010000") +cpp_quote("#define FOFX_PREFERHARDLINK 0x00020000") +cpp_quote("#define FOFX_SHOWELEVATIONPROMPT 0x00040000") +cpp_quote("#define FOFX_RECYCLEONDELETE 0x00080000") +cpp_quote("#define FOFX_EARLYFAILURE 0x00100000") +cpp_quote("#define FOFX_PRESERVEFILEEXTENSIONS 0x00200000") +cpp_quote("#define FOFX_KEEPNEWERFILE 0x00400000") +cpp_quote("#define FOFX_NOCOPYHOOKS 0x00800000") +cpp_quote("#define FOFX_NOMINIMIZEBOX 0x01000000") +cpp_quote("#define FOFX_MOVEACLSACROSSVOLUMES 0x02000000") +cpp_quote("#define FOFX_DONTDISPLAYSOURCEPATH 0x04000000") +cpp_quote("#define FOFX_DONTDISPLAYDESTPATH 0x08000000") +cpp_quote("#define FOFX_REQUIREELEVATION 0x10000000") +cpp_quote("#define FOFX_ADDUNDORECORD 0x20000000") +cpp_quote("#define FOFX_COPYASDOWNLOAD 0x40000000") +cpp_quote("#define FOFX_DONTDISPLAYLOCATIONS 0x80000000") + [ uuid(56f9f44f-f74c-4e38-99bc-9f3ebd3d696a) ]
From: Paul Gofman pgofman@codeweavers.com
--- dlls/shell32/tests/shlfileop.c | 234 +++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+)
diff --git a/dlls/shell32/tests/shlfileop.c b/dlls/shell32/tests/shlfileop.c index f191a3016c0..feba3627a00 100644 --- a/dlls/shell32/tests/shlfileop.c +++ b/dlls/shell32/tests/shlfileop.c @@ -24,6 +24,7 @@ #define COBJMACROS #include <windows.h> #include "shellapi.h" +#include "shlwapi.h" #include "shlobj.h" #include "commoncontrols.h"
@@ -109,6 +110,20 @@ static BOOL file_has_content(const CHAR *name, const CHAR *content) return strcmp(buf, content)==0; }
+static void remove_directory(const WCHAR *name) +{ + SHFILEOPSTRUCTW shfo; + WCHAR path[MAX_PATH]; + + memset(&shfo, 0, sizeof(shfo)); + shfo.wFunc = FO_DELETE; + wcscpy(path, name); + path[wcslen(name) + 1] = 0; + shfo.pFrom = path; + shfo.fFlags = FOF_NOCONFIRMATION | FOF_SILENT; + SHFileOperationW(&shfo); +} + /* initializes the tests */ static void init_shfo_tests(void) { @@ -2753,11 +2768,37 @@ static BOOL is_old_shell32(void) return FALSE; }
+static void set_shell_item_path(IShellItem *item, const WCHAR *path, BOOL todo) +{ + IPersistIDList *idlist; + ITEMIDLIST *pidl; + HRESULT hr; + + hr = SHParseDisplayName(path, NULL, &pidl, 0, NULL); + todo_wine_if(todo) ok(hr == S_OK, "got %#lx.\n", hr); + if (FAILED(hr)) + return; + hr = IShellItem_QueryInterface(item, &IID_IPersistIDList, (void **)&idlist); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IPersistIDList_SetIDList(idlist, pidl); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + IPersistIDList_Release(idlist); + ILFree(pidl); +} + static void test_file_operation(void) { + WCHAR dirpath[MAX_PATH], tmpfile[MAX_PATH], path[MAX_PATH]; IFileOperation *operation; + IShellItem *item, *item2; + IShellItem *folder; + LONG refcount; IUnknown *unk; + DWORD cookie; + BOOL aborted; HRESULT hr; + BOOL bret; + DWORD ret;
hr = CoCreateInstance(&CLSID_FileOperation, NULL, CLSCTX_INPROC_SERVER, &IID_IFileOperation, (void **)&operation); @@ -2773,7 +2814,200 @@ static void test_file_operation(void) ok(hr == S_OK, "Got hr %#lx.\n", hr); IUnknown_Release(unk);
+ hr = IFileOperation_Advise(operation, NULL, &cookie); + todo_wine ok(hr == E_INVALIDARG, "got %#lx.\n", hr); + + hr = IFileOperation_PerformOperations(operation); + todo_wine ok(hr == E_UNEXPECTED, "got %#lx.\n", hr); + + hr = CoCreateInstance(&CLSID_ShellItem, NULL, CLSCTX_INPROC_SERVER, &IID_IShellItem, (void **)&item); + ok(hr == S_OK, "got %#lx.\n", hr); + hr = CoCreateInstance(&CLSID_ShellItem, NULL, CLSCTX_INPROC_SERVER, &IID_IShellItem, (void **)&folder); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = IFileOperation_MoveItem(operation, item, folder, L"test", NULL); + todo_wine ok(hr == E_INVALIDARG, "got %#lx.\n", hr); + + GetTempPathW(ARRAY_SIZE(dirpath), dirpath); + PathCombineW(tmpfile, dirpath, L"testfile1"); + createTestFileW(tmpfile); + + set_shell_item_path(folder, dirpath, FALSE); + set_shell_item_path(item, tmpfile, FALSE); + + hr = IFileOperation_SetOperationFlags(operation, 0); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + + hr = IFileOperation_MoveItem(operation, item, folder, L"test", NULL); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + hr = IFileOperation_SetOperationFlags(operation, FOF_NO_UI); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + hr = IFileOperation_PerformOperations(operation); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + aborted = 0xdeadbeef; + hr = IFileOperation_GetAnyOperationsAborted(operation, &aborted); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + todo_wine ok(!aborted, "got %d.\n", aborted); + + hr = IFileOperation_PerformOperations(operation); + todo_wine ok(hr == E_UNEXPECTED, "got %#lx.\n", hr); + + /* Input file does not exist: PerformOperations succeeds, 'aborted' is set. */ + hr = IFileOperation_MoveItem(operation, item, folder, L"test2", NULL); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + hr = IFileOperation_PerformOperations(operation); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + aborted = 0; + hr = IFileOperation_GetAnyOperationsAborted(operation, &aborted); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + todo_wine ok(aborted == TRUE, "got %d.\n", aborted); + aborted = 0; + hr = IFileOperation_GetAnyOperationsAborted(operation, &aborted); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + todo_wine ok(aborted == TRUE, "got %d.\n", aborted); + + /* Input file exists: PerformOperations succeeds, the item data at the moment of MoveItem is used. */ + PathCombineW(path, dirpath, L"test"); + set_shell_item_path(item, path, TRUE); + hr = IFileOperation_MoveItem(operation, item, folder, L"test2", NULL); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + PathCombineW(tmpfile, dirpath, L"testfile2"); + /* Actual paths are fetched at _MoveItem and not at _Perform operation: changing item after doesn't matter. */ + createTestFileW(tmpfile); + set_shell_item_path(item, tmpfile, FALSE); + bret = DeleteFileW(tmpfile); + ok(bret, "got error %ld.\n", GetLastError()); + hr = IFileOperation_PerformOperations(operation); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + aborted = 0; + hr = IFileOperation_GetAnyOperationsAborted(operation, &aborted); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + todo_wine ok(aborted == TRUE, "got %d.\n", aborted); + ret = GetFileAttributesW(tmpfile); + ok(ret == INVALID_FILE_ATTRIBUTES, "got %#lx.\n", ret); + PathCombineW(path, dirpath, L"test"); + ret = GetFileAttributesW(path); + ok(ret == INVALID_FILE_ATTRIBUTES, "got %#lx.\n", ret); + PathCombineW(path, dirpath, L"test2"); + ret = GetFileAttributesW(path); + todo_wine ok(ret != INVALID_FILE_ATTRIBUTES, "got %#lx.\n", ret); + bret = DeleteFileW(path); + todo_wine ok(bret, "got error %ld.\n", GetLastError()); + + refcount = IShellItem_Release(item); + ok(!refcount, "got %ld.\n", refcount); + IShellItem_AddRef(folder); + refcount = IShellItem_Release(folder); + todo_wine ok(refcount > 1, "got %ld.\n", refcount); + + hr = IFileOperation_Unadvise(operation, 0xdeadbeef); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + IFileOperation_Release(operation); + refcount = IShellItem_Release(folder); + ok(!refcount, "got %ld.\n", refcount); + + /* Move directory to directory. */ + hr = CoCreateInstance(&CLSID_FileOperation, NULL, CLSCTX_INPROC_SERVER, &IID_IFileOperation, (void **)&operation); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = IFileOperation_SetOperationFlags(operation, FOF_NO_UI); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + + PathCombineW(path, dirpath, L"test_dir1"); + bret = CreateDirectoryW(path, NULL); + PathCombineW(tmpfile, path, L"testfile3"); + createTestFileW(tmpfile); + PathCombineW(tmpfile, path, L"testfile4"); + createTestFileW(tmpfile); + hr = SHCreateItemFromParsingName(path, NULL, &IID_IShellItem, (void**)&item); + ok(hr == S_OK, "got %#lx.\n", hr); + PathCombineW(path, dirpath, L"test_dir2"); + bret = CreateDirectoryW(path, NULL); + ok(bret, "got error %ld.\n", GetLastError()); + + hr = SHCreateItemFromParsingName(path, NULL, &IID_IShellItem, (void**)&folder); + ok(hr == S_OK, "got %#lx.\n", hr); + + PathCombineW(path, path, L"test2"); + createTestFileW(path); + + /* Source is directory, destination test2 is file. */ + hr = IFileOperation_MoveItem(operation, item, folder, L"test2", NULL); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + hr = IFileOperation_PerformOperations(operation); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + aborted = 0; + hr = IFileOperation_GetAnyOperationsAborted(operation, &aborted); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + todo_wine ok(aborted, "got %d.\n", aborted); + + bret = DeleteFileW(path); + ok(bret, "got error %ld.\n", GetLastError()); + IFileOperation_Release(operation); + + /* Source is directory, destination is absent (simple move). */ + hr = CoCreateInstance(&CLSID_FileOperation, NULL, CLSCTX_INPROC_SERVER, &IID_IFileOperation, (void **)&operation); + ok(hr == S_OK, "got %#lx.\n", hr); + hr = IFileOperation_SetOperationFlags(operation, FOF_NO_UI); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + + hr = IFileOperation_MoveItem(operation, item, folder, L"test2", NULL); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + hr = IFileOperation_PerformOperations(operation); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + IFileOperation_Release(operation); + + /* Source and dest are directories, merge is performed. */ + hr = CoCreateInstance(&CLSID_FileOperation, NULL, CLSCTX_INPROC_SERVER, &IID_IFileOperation, (void **)&operation); + ok(hr == S_OK, "got %#lx.\n", hr); + hr = IFileOperation_SetOperationFlags(operation, FOF_NO_UI); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + + PathCombineW(path, dirpath, L"test_dir3"); + bret = CreateDirectoryW(path, NULL); + set_shell_item_path(item, path, FALSE); + ok(bret, "got error %ld.\n", GetLastError()); + PathCombineW(tmpfile, path, L"testfile5"); + createTestFileW(tmpfile); + PathCombineW(tmpfile, path, L"testfile6"); + createTestFileW(tmpfile); + PathCombineW(path, path, L"inner_dir"); + bret = CreateDirectoryW(path, NULL); + ok(bret, "got error %ld.\n", GetLastError()); + PathCombineW(tmpfile, path, L"testfile7"); + createTestFileW(tmpfile); + PathCombineW(tmpfile, dirpath, L"testfile8"); + createTestFileW(tmpfile); + + hr = SHCreateItemFromParsingName(tmpfile, NULL, &IID_IShellItem, (void**)&item2); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = IFileOperation_MoveItem(operation, item, folder, L"test2", NULL); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + hr = IFileOperation_MoveItem(operation, item2, folder, NULL, NULL); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + refcount = IShellItem_Release(item2); + ok(!refcount, "got %ld.\n", refcount); + hr = IFileOperation_PerformOperations(operation); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + + PathCombineW(path, dirpath, L"test_dir2"); + PathCombineW(tmpfile, dirpath, L"test_dir2\test2\testfile6"); + ret = GetFileAttributesW(tmpfile); + todo_wine ok(ret != INVALID_FILE_ATTRIBUTES, "got %#lx.\n", ret); + remove_directory(path); + PathCombineW(path, dirpath, L"test_dir3"); + ret = GetFileAttributesW(path); + todo_wine ok(ret == INVALID_FILE_ATTRIBUTES, "got %#lx.\n", ret); + remove_directory(path); + + IFileOperation_Release(operation); + + refcount = IShellItem_Release(item); + ok(!refcount, "got %ld.\n", refcount); + refcount = IShellItem_Release(folder); + ok(!refcount, "got %ld.\n", refcount); }
START_TEST(shlfileop)
From: Paul Gofman pgofman@codeweavers.com
--- dlls/shell32/tests/shlfileop.c | 434 ++++++++++++++++++++++++++++++++- 1 file changed, 432 insertions(+), 2 deletions(-)
diff --git a/dlls/shell32/tests/shlfileop.c b/dlls/shell32/tests/shlfileop.c index feba3627a00..d6a0a1b252d 100644 --- a/dlls/shell32/tests/shlfileop.c +++ b/dlls/shell32/tests/shlfileop.c @@ -26,6 +26,7 @@ #include "shellapi.h" #include "shlwapi.h" #include "shlobj.h" +#include "sherrors.h" #include "commoncontrols.h"
#include "wine/test.h" @@ -2768,6 +2769,300 @@ static BOOL is_old_shell32(void) return FALSE; }
+struct progress_sink +{ + IFileOperationProgressSink IFileOperationProgressSink_iface; + unsigned int instance_id; + LONG ref; + struct progress_expected_notifications *expected; +}; + +struct progress_expected_notification +{ + const char *text; + DWORD tsf, tsf_broken; + HRESULT hres, hres_broken; +}; +struct progress_expected_notifications +{ + unsigned int index, count, line; + const WCHAR *dir_prefix; + const struct progress_expected_notification *expected; +}; + +static inline struct progress_sink *impl_from_IFileOperationProgressSink(IFileOperationProgressSink *iface) +{ + return CONTAINING_RECORD(iface, struct progress_sink, IFileOperationProgressSink_iface); +} + +#define progress_init_check_notifications(a, b, c, d, e) progress_init_check_notifications_(__LINE__, a, b, c, d, e) +static void progress_init_check_notifications_(unsigned int line, IFileOperationProgressSink *iface, + unsigned int count, const struct progress_expected_notification *expected, const WCHAR *dir_prefix, + struct progress_expected_notifications *n) +{ + struct progress_sink *progress = impl_from_IFileOperationProgressSink(iface); + n->line = line; + n->index = 0; + n->count = count; + n->expected = expected; + n->dir_prefix = dir_prefix; + progress->expected = n; +} + +static void progress_check_notification(struct progress_sink *progress, const char *text, DWORD tsf, HRESULT hres) +{ + struct progress_expected_notifications *e = progress->expected; + char str[4096]; + + ok(!!e, "expected notifications are not set up.\n"); + sprintf(str, "[%u] %s", progress->instance_id, text); + ok_(__FILE__, e->line)(e->index < e->count, "extra notification %s.\n", debugstr_a(str)); + if (e->index < e->count) + { + ok_(__FILE__, e->line)(!strcmp(str, e->expected[e->index].text), "got notification %s, expected %s, index %u.\n", + debugstr_a(str), debugstr_a(e->expected[e->index].text), e->index); + ok_(__FILE__, e->line)(tsf == e->expected[e->index].tsf || broken(tsf == e->expected[e->index].tsf_broken), + "got tsf %#lx, expected %#lx, index %u (%s).\n", tsf, e->expected[e->index].tsf, e->index, debugstr_a(text)); + ok_(__FILE__, e->line)(hres == e->expected[e->index].hres || broken(hres == e->expected[e->index].hres_broken), + "got hres %#lx, expected %#lx, index %u (%s).\n", hres, e->expected[e->index].hres, e->index, debugstr_a(text)); + } + ++e->index; +} + +static void progress_end_check_notifications(IFileOperationProgressSink *iface) +{ + struct progress_sink *progress = impl_from_IFileOperationProgressSink(iface); + struct progress_expected_notifications *e = progress->expected; + + ok(!!e, "expected notifications are not set up.\n"); + todo_wine ok_(__FILE__, e->line)(e->index == e->count, "got notification count %u, expected %u.\n", e->index, e->count); + progress->expected = NULL; +} + +static const char *shellitem_str(struct progress_sink *progress, IShellItem *item) +{ + char str[MAX_PATH]; + unsigned int len; + const char *ret; + WCHAR *path; + HRESULT hr; + + if (!item) + return "<null>"; + + hr = IShellItem_GetDisplayName(item, SIGDN_FILESYSPATH, &path); + if (FAILED(hr)) + return "<invalid>"; + + len = wcslen(progress->expected->dir_prefix); + if (progress->expected->dir_prefix[len - 1] == '\') + --len; + if (wcsncmp(path, progress->expected->dir_prefix, len)) + { + ret = debugstr_w(path); + } + else + { + sprintf(str, "<prefix>%s", debugstr_w(path + len) + 1); + ret = __wine_dbg_strdup(str); + } + CoTaskMemFree(path); + return ret; +} + +static HRESULT WINAPI progress_QueryInterface(IFileOperationProgressSink *iface, REFIID riid, void **out) +{ + struct progress_sink *operation = impl_from_IFileOperationProgressSink(iface); + + if (IsEqualIID(&IID_IFileOperationProgressSink, riid) || IsEqualIID(&IID_IUnknown, riid)) + *out = &operation->IFileOperationProgressSink_iface; + else + { + trace("not implemented for %s.\n", debugstr_guid(riid)); + *out = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown *)*out); + return S_OK; +} + +static ULONG WINAPI progress_AddRef(IFileOperationProgressSink *iface) +{ + struct progress_sink *progress = impl_from_IFileOperationProgressSink(iface); + + return InterlockedIncrement(&progress->ref);; +} + +static ULONG WINAPI progress_Release(IFileOperationProgressSink *iface) +{ + struct progress_sink *progress = impl_from_IFileOperationProgressSink(iface); + LONG ref = InterlockedDecrement(&progress->ref); + + if (!ref) + free(progress); + + return ref; +} + +static HRESULT WINAPI progress_StartOperations(IFileOperationProgressSink *iface) +{ + progress_check_notification(impl_from_IFileOperationProgressSink(iface), "StartOperations", 0, 0); + return S_OK; +} + +static HRESULT WINAPI progress_FinishOperations(IFileOperationProgressSink *iface, HRESULT result) +{ + progress_check_notification(impl_from_IFileOperationProgressSink(iface), "FinishOperations", 0, result); + return S_OK; +} + +static HRESULT WINAPI progress_PreRenameItem(IFileOperationProgressSink *iface, DWORD flags, IShellItem *item, + const WCHAR *new_name) +{ + ok(0, ".\n"); + + return E_NOTIMPL; +} + +static HRESULT WINAPI progress_PostRenameItem(IFileOperationProgressSink *iface, DWORD flags, IShellItem *item, + const WCHAR *new_name, HRESULT hrRename, IShellItem *newly_created) +{ + ok(0, ".\n"); + + return E_NOTIMPL; +} + +static HRESULT WINAPI progress_PreMoveItem(IFileOperationProgressSink *iface, DWORD flags, IShellItem *item, + IShellItem *dest_folder, const WCHAR *new_name) +{ + struct progress_sink *progress = impl_from_IFileOperationProgressSink(iface); + char str[1024]; + + sprintf(str, "PreMoveItem %s, %s, %s", shellitem_str(progress, item), + shellitem_str(progress, dest_folder), debugstr_w(new_name) + 1); + progress_check_notification(progress, str, flags, 0); + return S_OK; +} + +static HRESULT WINAPI progress_PostMoveItem(IFileOperationProgressSink *iface, DWORD flags, IShellItem *item, + IShellItem *dest_folder, const WCHAR *new_name, HRESULT result, IShellItem *newly_created) +{ + struct progress_sink *progress = impl_from_IFileOperationProgressSink(iface); + char str[1024]; + + sprintf(str, "PostMoveItem %s, %s, %s -> %s", shellitem_str(progress, item), + shellitem_str(progress, dest_folder), debugstr_w(new_name) + 1, shellitem_str(progress, newly_created)); + progress_check_notification(progress, str, flags, result); + return S_OK; +} + +static HRESULT WINAPI progress_PreCopyItem(IFileOperationProgressSink *iface, DWORD flags, IShellItem *item, + IShellItem *dest_folder,LPCWSTR pszNewName) +{ + ok(0, ".\n"); + + return E_NOTIMPL; +} + +static HRESULT WINAPI progress_PostCopyItem(IFileOperationProgressSink *iface, DWORD flags, IShellItem *item, + IShellItem *dest_folder, const WCHAR *new_name, HRESULT result, IShellItem *newly_created) +{ + ok(0, ".\n"); + + return E_NOTIMPL; +} + +static HRESULT WINAPI progress_PreDeleteItem(IFileOperationProgressSink *iface, DWORD flags, IShellItem *item) +{ + ok(0, ".\n"); + + return E_NOTIMPL; +} + +static HRESULT WINAPI progress_PostDeleteItem(IFileOperationProgressSink *iface, DWORD flags, IShellItem *item, + HRESULT result, IShellItem *newly_created) +{ + ok(0, ".\n"); + + return E_NOTIMPL; +} + +static HRESULT WINAPI progress_PreNewItem(IFileOperationProgressSink *iface, DWORD flags,IShellItem *dest_folder, + const WCHAR *new_name) +{ + ok(0, ".\n"); + + return E_NOTIMPL; +} + +static HRESULT WINAPI progress_PostNewItem(IFileOperationProgressSink *iface, DWORD flags, IShellItem *dest_folder, + const WCHAR *new_name, const WCHAR *template_name, DWORD file_attrs, HRESULT result, IShellItem *newly_created) +{ + ok(0, ".\n"); + + return E_NOTIMPL; +} + +static HRESULT WINAPI progress_UpdateProgress(IFileOperationProgressSink *iface, UINT total, UINT sofar) +{ + return S_OK; +} + +static HRESULT WINAPI progress_ResetTimer(IFileOperationProgressSink *iface) +{ + progress_check_notification(impl_from_IFileOperationProgressSink(iface), "ResetTimer", 0, 0); + return S_OK; +} + +static HRESULT WINAPI progress_PauseTimer(IFileOperationProgressSink *iface) +{ + ok(0, ".\n"); + + return E_NOTIMPL; +} + +static HRESULT WINAPI progress_ResumeTimer(IFileOperationProgressSink *iface) +{ + ok(0, ".\n"); + + return E_NOTIMPL; +} + +static const IFileOperationProgressSinkVtbl progress_vtbl = +{ + progress_QueryInterface, + progress_AddRef, + progress_Release, + progress_StartOperations, + progress_FinishOperations, + progress_PreRenameItem, + progress_PostRenameItem, + progress_PreMoveItem, + progress_PostMoveItem, + progress_PreCopyItem, + progress_PostCopyItem, + progress_PreDeleteItem, + progress_PostDeleteItem, + progress_PreNewItem, + progress_PostNewItem, + progress_UpdateProgress, + progress_ResetTimer, + progress_PauseTimer, + progress_ResumeTimer, +}; + +static IFileOperationProgressSink *create_progress_sink(unsigned int instance_id) +{ + struct progress_sink *obj; + + obj = calloc(1, sizeof(*obj)); + obj->IFileOperationProgressSink_iface.lpVtbl = &progress_vtbl; + obj->instance_id = instance_id; + obj->ref = 1; + return &obj->IFileOperationProgressSink_iface; +} + static void set_shell_item_path(IShellItem *item, const WCHAR *path, BOOL todo) { IPersistIDList *idlist; @@ -2788,7 +3083,109 @@ static void set_shell_item_path(IShellItem *item, const WCHAR *path, BOOL todo)
static void test_file_operation(void) { +#define DEFAULT_TSF_FLAGS (TSF_COPY_LOCALIZED_NAME | TSF_COPY_WRITE_TIME | TSF_COPY_CREATION_TIME | TSF_OVERWRITE_EXIST) +#define MEGRE_TSF_FLAGS (TSF_COPY_WRITE_TIME | TSF_COPY_CREATION_TIME | TSF_OVERWRITE_EXIST) +#define UNKNOWN_POST_MERGE_TSF_FLAG 0x1000 + static const struct progress_expected_notification notifications1[] = + { + {"[0] StartOperations"}, + {"[0] ResetTimer"}, + {"[0] PreMoveItem <prefix>"\\testfile1", <prefix>"", "test"", DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS}, + {"[1] PreMoveItem <prefix>"\\testfile1", <prefix>"", "test"", DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS}, + {"[1] PostMoveItem <prefix>"\\testfile1", <prefix>"", "test" -> <prefix>"\\test"", + DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS, + COPYENGINE_S_DONT_PROCESS_CHILDREN, COPYENGINE_S_DONT_PROCESS_CHILDREN}, + {"[0] PostMoveItem <prefix>"\\testfile1", <prefix>"", "test" -> <prefix>"\\test"", + DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS, + COPYENGINE_S_DONT_PROCESS_CHILDREN, COPYENGINE_S_DONT_PROCESS_CHILDREN}, + {"[0] FinishOperations"}, + }; + static const struct progress_expected_notification notifications2[] = + { + {"[0] StartOperations"}, + {"[0] ResetTimer"}, + {"[0] PreMoveItem <prefix>"\\testfile1", <prefix>"", "test2"", DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS}, + {"[0] PostMoveItem <prefix>"\\testfile1", <prefix>"", "test2" -> <null>", + DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS, + 0x80070002, COPYENGINE_S_USER_IGNORED}, + {"[0] FinishOperations"}, + }; + static const struct progress_expected_notification notifications3[] = + { + {"[0] StartOperations"}, + {"[0] ResetTimer"}, + {"[0] PreMoveItem <prefix>"\\test", <prefix>"", "test2"", DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS}, + {"[0] PostMoveItem <prefix>"\\test", <prefix>"", "test2" -> <prefix>"\\test2"", + DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS, + COPYENGINE_S_DONT_PROCESS_CHILDREN, COPYENGINE_S_DONT_PROCESS_CHILDREN}, + {"[0] FinishOperations"}, + }; + static const struct progress_expected_notification notifications4[] = + { + {"[0] StartOperations"}, + {"[0] ResetTimer"}, + {"[0] PreMoveItem <prefix>"\\test_dir1", <prefix>"\\test_dir2", "test2"", DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS}, + {"[0] PostMoveItem <prefix>"\\test_dir1", <prefix>"\\test_dir2", "test2" -> <null>", + DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS, + COPYENGINE_E_FLD_IS_FILE_DEST, COPYENGINE_S_USER_IGNORED}, + {"[0] FinishOperations"}, + }; + static const struct progress_expected_notification notifications5[] = + { + {"[0] StartOperations"}, + {"[0] ResetTimer"}, + {"[0] PreMoveItem <prefix>"\\test_dir1", <prefix>"\\test_dir2", "test2"", DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS}, + {"[0] PostMoveItem <prefix>"\\test_dir1", <prefix>"\\test_dir2", "test2" -> <prefix>"\\test_dir2\\test2"", + DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS, + COPYENGINE_S_DONT_PROCESS_CHILDREN, COPYENGINE_S_DONT_PROCESS_CHILDREN}, + {"[0] FinishOperations"}, + }; + static const struct progress_expected_notification notifications6[] = + { + {"[0] StartOperations"}, + {"[0] ResetTimer"}, + {"[0] PreMoveItem <prefix>"\\test_dir3", <prefix>"\\test_dir2", "test2"", DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS}, + {"[1] PreMoveItem <prefix>"\\test_dir3", <prefix>"\\test_dir2", "test2"", DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS}, + {"[1] PostMoveItem <prefix>"\\test_dir3", <prefix>"\\test_dir2", "test2" -> <prefix>"\\test_dir2\\test2"", + DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS, + COPYENGINE_S_NOT_HANDLED, COPYENGINE_S_MERGE}, + {"[0] PostMoveItem <prefix>"\\test_dir3", <prefix>"\\test_dir2", "test2" -> <prefix>"\\test_dir2\\test2"", + DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS, + COPYENGINE_S_NOT_HANDLED, COPYENGINE_S_MERGE}, + {"[0] PreMoveItem <prefix>"\\test_dir3\\testfile5", <prefix>"\\test_dir2\\test2", """, MEGRE_TSF_FLAGS, MEGRE_TSF_FLAGS}, + {"[1] PreMoveItem <prefix>"\\test_dir3\\testfile5", <prefix>"\\test_dir2\\test2", """, MEGRE_TSF_FLAGS, MEGRE_TSF_FLAGS}, + {"[1] PostMoveItem <prefix>"\\test_dir3\\testfile5", <prefix>"\\test_dir2\\test2", "testfile5" -> <prefix>"\\test_dir2\\test2\\testfile5"", + MEGRE_TSF_FLAGS | UNKNOWN_POST_MERGE_TSF_FLAG, MEGRE_TSF_FLAGS, + COPYENGINE_S_DONT_PROCESS_CHILDREN, COPYENGINE_S_DONT_PROCESS_CHILDREN}, + {"[0] PostMoveItem <prefix>"\\test_dir3\\testfile5", <prefix>"\\test_dir2\\test2", "testfile5" -> <prefix>"\\test_dir2\\test2\\testfile5"", + MEGRE_TSF_FLAGS | UNKNOWN_POST_MERGE_TSF_FLAG, MEGRE_TSF_FLAGS, + COPYENGINE_S_DONT_PROCESS_CHILDREN, COPYENGINE_S_DONT_PROCESS_CHILDREN}, + {"[0] PreMoveItem <prefix>"\\test_dir3\\testfile6", <prefix>"\\test_dir2\\test2", """, MEGRE_TSF_FLAGS, MEGRE_TSF_FLAGS}, + {"[1] PreMoveItem <prefix>"\\test_dir3\\testfile6", <prefix>"\\test_dir2\\test2", """, MEGRE_TSF_FLAGS, MEGRE_TSF_FLAGS}, + {"[1] PostMoveItem <prefix>"\\test_dir3\\testfile6", <prefix>"\\test_dir2\\test2", "testfile6" -> <prefix>"\\test_dir2\\test2\\testfile6"", + MEGRE_TSF_FLAGS | UNKNOWN_POST_MERGE_TSF_FLAG, MEGRE_TSF_FLAGS, + COPYENGINE_S_DONT_PROCESS_CHILDREN, COPYENGINE_S_DONT_PROCESS_CHILDREN}, + {"[0] PostMoveItem <prefix>"\\test_dir3\\testfile6", <prefix>"\\test_dir2\\test2", "testfile6" -> <prefix>"\\test_dir2\\test2\\testfile6"", + MEGRE_TSF_FLAGS | UNKNOWN_POST_MERGE_TSF_FLAG, MEGRE_TSF_FLAGS, + COPYENGINE_S_DONT_PROCESS_CHILDREN, COPYENGINE_S_DONT_PROCESS_CHILDREN}, + {"[0] PreMoveItem <prefix>"\\test_dir3\\inner_dir", <prefix>"\\test_dir2\\test2", """, MEGRE_TSF_FLAGS, MEGRE_TSF_FLAGS}, + {"[1] PreMoveItem <prefix>"\\test_dir3\\inner_dir", <prefix>"\\test_dir2\\test2", """, MEGRE_TSF_FLAGS, MEGRE_TSF_FLAGS}, + {"[1] PostMoveItem <prefix>"\\test_dir3\\inner_dir", <prefix>"\\test_dir2\\test2", "inner_dir" -> <prefix>"\\test_dir2\\test2\\inner_dir"", + MEGRE_TSF_FLAGS | UNKNOWN_POST_MERGE_TSF_FLAG, MEGRE_TSF_FLAGS, + COPYENGINE_S_DONT_PROCESS_CHILDREN, COPYENGINE_S_DONT_PROCESS_CHILDREN}, + {"[0] PostMoveItem <prefix>"\\test_dir3\\inner_dir", <prefix>"\\test_dir2\\test2", "inner_dir" -> <prefix>"\\test_dir2\\test2\\inner_dir"", + MEGRE_TSF_FLAGS | UNKNOWN_POST_MERGE_TSF_FLAG, MEGRE_TSF_FLAGS, + COPYENGINE_S_DONT_PROCESS_CHILDREN, COPYENGINE_S_DONT_PROCESS_CHILDREN}, + {"[0] PreMoveItem <prefix>"\\testfile8", <prefix>"\\test_dir2", """, DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS}, + {"[0] PostMoveItem <prefix>"\\testfile8", <prefix>"\\test_dir2", "testfile8" -> <prefix>"\\test_dir2\\testfile8"", + DEFAULT_TSF_FLAGS, DEFAULT_TSF_FLAGS, + COPYENGINE_S_DONT_PROCESS_CHILDREN, COPYENGINE_S_DONT_PROCESS_CHILDREN}, + {"[0] FinishOperations"}, + }; + WCHAR dirpath[MAX_PATH], tmpfile[MAX_PATH], path[MAX_PATH]; + struct progress_expected_notifications expected_notif; + IFileOperationProgressSink *progress, *progress2; IFileOperation *operation; IShellItem *item, *item2; IShellItem *folder; @@ -2817,6 +3214,10 @@ static void test_file_operation(void) hr = IFileOperation_Advise(operation, NULL, &cookie); todo_wine ok(hr == E_INVALIDARG, "got %#lx.\n", hr);
+ progress = create_progress_sink(0); + hr = IFileOperation_Advise(operation, progress, &cookie); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + hr = IFileOperation_PerformOperations(operation); todo_wine ok(hr == E_UNEXPECTED, "got %#lx.\n", hr);
@@ -2838,7 +3239,10 @@ static void test_file_operation(void) hr = IFileOperation_SetOperationFlags(operation, 0); todo_wine ok(hr == S_OK, "got %#lx.\n", hr);
- hr = IFileOperation_MoveItem(operation, item, folder, L"test", NULL); + progress2 = create_progress_sink(1); + progress_init_check_notifications(progress, ARRAY_SIZE(notifications1), notifications1, dirpath, &expected_notif); + progress_init_check_notifications(progress2, ARRAY_SIZE(notifications1), notifications1, dirpath, &expected_notif); + hr = IFileOperation_MoveItem(operation, item, folder, L"test", progress2); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); hr = IFileOperation_SetOperationFlags(operation, FOF_NO_UI); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); @@ -2848,6 +3252,7 @@ static void test_file_operation(void) hr = IFileOperation_GetAnyOperationsAborted(operation, &aborted); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); todo_wine ok(!aborted, "got %d.\n", aborted); + progress_end_check_notifications(progress);
hr = IFileOperation_PerformOperations(operation); todo_wine ok(hr == E_UNEXPECTED, "got %#lx.\n", hr); @@ -2855,8 +3260,10 @@ static void test_file_operation(void) /* Input file does not exist: PerformOperations succeeds, 'aborted' is set. */ hr = IFileOperation_MoveItem(operation, item, folder, L"test2", NULL); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + progress_init_check_notifications(progress, ARRAY_SIZE(notifications2), notifications2, dirpath, &expected_notif); hr = IFileOperation_PerformOperations(operation); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + progress_end_check_notifications(progress); aborted = 0; hr = IFileOperation_GetAnyOperationsAborted(operation, &aborted); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); @@ -2877,8 +3284,10 @@ static void test_file_operation(void) set_shell_item_path(item, tmpfile, FALSE); bret = DeleteFileW(tmpfile); ok(bret, "got error %ld.\n", GetLastError()); + progress_init_check_notifications(progress, ARRAY_SIZE(notifications3), notifications3, dirpath, &expected_notif); hr = IFileOperation_PerformOperations(operation); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + progress_end_check_notifications(progress); aborted = 0; hr = IFileOperation_GetAnyOperationsAborted(operation, &aborted); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); @@ -2902,6 +3311,8 @@ static void test_file_operation(void)
hr = IFileOperation_Unadvise(operation, 0xdeadbeef); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + hr = IFileOperation_Unadvise(operation, cookie); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr);
IFileOperation_Release(operation); refcount = IShellItem_Release(folder); @@ -2914,6 +3325,9 @@ static void test_file_operation(void) hr = IFileOperation_SetOperationFlags(operation, FOF_NO_UI); todo_wine ok(hr == S_OK, "got %#lx.\n", hr);
+ hr = IFileOperation_Advise(operation, progress, &cookie); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + PathCombineW(path, dirpath, L"test_dir1"); bret = CreateDirectoryW(path, NULL); PathCombineW(tmpfile, path, L"testfile3"); @@ -2935,8 +3349,10 @@ static void test_file_operation(void) /* Source is directory, destination test2 is file. */ hr = IFileOperation_MoveItem(operation, item, folder, L"test2", NULL); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + progress_init_check_notifications(progress, ARRAY_SIZE(notifications4), notifications4, dirpath, &expected_notif); hr = IFileOperation_PerformOperations(operation); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + progress_end_check_notifications(progress); aborted = 0; hr = IFileOperation_GetAnyOperationsAborted(operation, &aborted); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); @@ -2949,18 +3365,24 @@ static void test_file_operation(void) /* Source is directory, destination is absent (simple move). */ hr = CoCreateInstance(&CLSID_FileOperation, NULL, CLSCTX_INPROC_SERVER, &IID_IFileOperation, (void **)&operation); ok(hr == S_OK, "got %#lx.\n", hr); + hr = IFileOperation_Advise(operation, progress, &cookie); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); hr = IFileOperation_SetOperationFlags(operation, FOF_NO_UI); todo_wine ok(hr == S_OK, "got %#lx.\n", hr);
hr = IFileOperation_MoveItem(operation, item, folder, L"test2", NULL); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + progress_init_check_notifications(progress, ARRAY_SIZE(notifications5), notifications5, dirpath, &expected_notif); hr = IFileOperation_PerformOperations(operation); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + progress_end_check_notifications(progress); IFileOperation_Release(operation);
/* Source and dest are directories, merge is performed. */ hr = CoCreateInstance(&CLSID_FileOperation, NULL, CLSCTX_INPROC_SERVER, &IID_IFileOperation, (void **)&operation); ok(hr == S_OK, "got %#lx.\n", hr); + hr = IFileOperation_Advise(operation, progress, &cookie); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); hr = IFileOperation_SetOperationFlags(operation, FOF_NO_UI); todo_wine ok(hr == S_OK, "got %#lx.\n", hr);
@@ -2983,14 +3405,16 @@ static void test_file_operation(void) hr = SHCreateItemFromParsingName(tmpfile, NULL, &IID_IShellItem, (void**)&item2); ok(hr == S_OK, "got %#lx.\n", hr);
- hr = IFileOperation_MoveItem(operation, item, folder, L"test2", NULL); + hr = IFileOperation_MoveItem(operation, item, folder, L"test2", progress2); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); hr = IFileOperation_MoveItem(operation, item2, folder, NULL, NULL); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); refcount = IShellItem_Release(item2); ok(!refcount, "got %ld.\n", refcount); + progress_init_check_notifications(progress, ARRAY_SIZE(notifications6), notifications6, dirpath, &expected_notif); hr = IFileOperation_PerformOperations(operation); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + progress_end_check_notifications(progress);
PathCombineW(path, dirpath, L"test_dir2"); PathCombineW(tmpfile, dirpath, L"test_dir2\test2\testfile6"); @@ -3008,6 +3432,11 @@ static void test_file_operation(void) ok(!refcount, "got %ld.\n", refcount); refcount = IShellItem_Release(folder); ok(!refcount, "got %ld.\n", refcount); + + refcount = IFileOperationProgressSink_Release(progress); + ok(!refcount, "got %ld.\n", refcount); + refcount = IFileOperationProgressSink_Release(progress2); + ok(!refcount, "got %ld.\n", refcount); }
START_TEST(shlfileop) @@ -3018,6 +3447,7 @@ START_TEST(shlfileop) old_shell32 = is_old_shell32(); if (old_shell32) win_skip("Need to cater for old shell32 (4.0.x) on Win95\n"); + clean_after_shfo_tests();
init_shfo_tests();
From: Paul Gofman pgofman@codeweavers.com
--- dlls/shell32/shlfileop.c | 51 +++++++++++++++++++++++++++++++--- dlls/shell32/tests/shlfileop.c | 14 +++++----- 2 files changed, 54 insertions(+), 11 deletions(-)
diff --git a/dlls/shell32/shlfileop.c b/dlls/shell32/shlfileop.c index 305cbe26e9a..3cb9b28de81 100644 --- a/dlls/shell32/shlfileop.c +++ b/dlls/shell32/shlfileop.c @@ -42,6 +42,7 @@ #include "shell32_main.h" #include "shfldr.h" #include "wine/debug.h" +#include "wine/list.h"
WINE_DEFAULT_DEBUG_CHANNEL(shell);
@@ -1848,10 +1849,19 @@ HRESULT WINAPI SHMultiFileProperties(IDataObject *pdtobj, DWORD flags) return E_NOTIMPL; }
+struct file_operation_sink +{ + struct list entry; + IFileOperationProgressSink *sink; + DWORD cookie; +}; + struct file_operation { IFileOperation IFileOperation_iface; LONG ref; + struct list sinks; + DWORD next_cookie; };
static inline struct file_operation *impl_from_IFileOperation(IFileOperation *iface) @@ -1898,6 +1908,14 @@ static ULONG WINAPI file_operation_Release(IFileOperation *iface)
if (!ref) { + struct file_operation_sink *sink, *next_sink; + + LIST_FOR_EACH_ENTRY_SAFE(sink, next_sink, &operation->sinks, struct file_operation_sink, entry) + { + IFileOperationProgressSink_Release(sink->sink); + list_remove(&sink->entry); + free(sink); + } free(operation); }
@@ -1906,16 +1924,40 @@ static ULONG WINAPI file_operation_Release(IFileOperation *iface)
static HRESULT WINAPI file_operation_Advise(IFileOperation *iface, IFileOperationProgressSink *sink, DWORD *cookie) { - FIXME("(%p, %p, %p): stub.\n", iface, sink, cookie); + struct file_operation *operation = impl_from_IFileOperation(iface); + struct file_operation_sink *op_sink;
- return E_NOTIMPL; + TRACE("(%p, %p, %p).\n", iface, sink, cookie); + + if (!sink) + return E_INVALIDARG; + if (!(op_sink = calloc(1, sizeof(*op_sink)))) + return E_OUTOFMEMORY; + + op_sink->cookie = ++operation->next_cookie; + IFileOperationProgressSink_AddRef(sink); + op_sink->sink = sink; + list_add_tail(&operation->sinks, &op_sink->entry); + return S_OK; }
static HRESULT WINAPI file_operation_Unadvise(IFileOperation *iface, DWORD cookie) { - FIXME("(%p, %lx): stub.\n", iface, cookie); + struct file_operation *operation = impl_from_IFileOperation(iface); + struct file_operation_sink *sink;
- return E_NOTIMPL; + TRACE("(%p, %lx).\n", iface, cookie); + LIST_FOR_EACH_ENTRY(sink, &operation->sinks, struct file_operation_sink, entry) + { + if (sink->cookie == cookie) + { + IFileOperationProgressSink_Release(sink->sink); + list_remove(&sink->entry); + free(sink); + return S_OK; + } + } + return S_OK; }
static HRESULT WINAPI file_operation_SetOperationFlags(IFileOperation *iface, DWORD flags) @@ -2087,6 +2129,7 @@ HRESULT WINAPI IFileOperation_Constructor(IUnknown *outer, REFIID riid, void **o return E_OUTOFMEMORY;
object->IFileOperation_iface.lpVtbl = &file_operation_vtbl; + list_init(&object->sinks); object->ref = 1;
hr = IFileOperation_QueryInterface(&object->IFileOperation_iface, riid, out); diff --git a/dlls/shell32/tests/shlfileop.c b/dlls/shell32/tests/shlfileop.c index d6a0a1b252d..ec8e8e5869f 100644 --- a/dlls/shell32/tests/shlfileop.c +++ b/dlls/shell32/tests/shlfileop.c @@ -3212,11 +3212,11 @@ static void test_file_operation(void) IUnknown_Release(unk);
hr = IFileOperation_Advise(operation, NULL, &cookie); - todo_wine ok(hr == E_INVALIDARG, "got %#lx.\n", hr); + ok(hr == E_INVALIDARG, "got %#lx.\n", hr);
progress = create_progress_sink(0); hr = IFileOperation_Advise(operation, progress, &cookie); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr);
hr = IFileOperation_PerformOperations(operation); todo_wine ok(hr == E_UNEXPECTED, "got %#lx.\n", hr); @@ -3310,9 +3310,9 @@ static void test_file_operation(void) todo_wine ok(refcount > 1, "got %ld.\n", refcount);
hr = IFileOperation_Unadvise(operation, 0xdeadbeef); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); hr = IFileOperation_Unadvise(operation, cookie); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr);
IFileOperation_Release(operation); refcount = IShellItem_Release(folder); @@ -3326,7 +3326,7 @@ static void test_file_operation(void) todo_wine ok(hr == S_OK, "got %#lx.\n", hr);
hr = IFileOperation_Advise(operation, progress, &cookie); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr);
PathCombineW(path, dirpath, L"test_dir1"); bret = CreateDirectoryW(path, NULL); @@ -3366,7 +3366,7 @@ static void test_file_operation(void) hr = CoCreateInstance(&CLSID_FileOperation, NULL, CLSCTX_INPROC_SERVER, &IID_IFileOperation, (void **)&operation); ok(hr == S_OK, "got %#lx.\n", hr); hr = IFileOperation_Advise(operation, progress, &cookie); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); hr = IFileOperation_SetOperationFlags(operation, FOF_NO_UI); todo_wine ok(hr == S_OK, "got %#lx.\n", hr);
@@ -3382,7 +3382,7 @@ static void test_file_operation(void) hr = CoCreateInstance(&CLSID_FileOperation, NULL, CLSCTX_INPROC_SERVER, &IID_IFileOperation, (void **)&operation); ok(hr == S_OK, "got %#lx.\n", hr); hr = IFileOperation_Advise(operation, progress, &cookie); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); hr = IFileOperation_SetOperationFlags(operation, FOF_NO_UI); todo_wine ok(hr == S_OK, "got %#lx.\n", hr);
From: Paul Gofman pgofman@codeweavers.com
--- dlls/shell32/shlfileop.c | 96 +++++++++++++++++++++++++++++++++- dlls/shell32/tests/shlfileop.c | 20 +++---- 2 files changed, 104 insertions(+), 12 deletions(-)
diff --git a/dlls/shell32/shlfileop.c b/dlls/shell32/shlfileop.c index 3cb9b28de81..9cf834d37fe 100644 --- a/dlls/shell32/shlfileop.c +++ b/dlls/shell32/shlfileop.c @@ -1849,6 +1849,21 @@ HRESULT WINAPI SHMultiFileProperties(IDataObject *pdtobj, DWORD flags) return E_NOTIMPL; }
+enum copy_engine_opcode +{ + COPY_ENGINE_MOVE, +}; + +struct copy_engine_operation +{ + struct list entry; + IFileOperationProgressSink *sink; + enum copy_engine_opcode opcode; + IShellItem *folder; + PIDLIST_ABSOLUTE item_pidl; + WCHAR *name; +}; + struct file_operation_sink { struct list entry; @@ -1862,8 +1877,74 @@ struct file_operation LONG ref; struct list sinks; DWORD next_cookie; + struct list ops; };
+static void free_file_operation_ops(struct file_operation *operation) +{ + struct copy_engine_operation *op, *next; + + LIST_FOR_EACH_ENTRY_SAFE(op, next, &operation->ops, struct copy_engine_operation, entry) + { + if (op->sink) + IFileOperationProgressSink_Release(op->sink); + ILFree(op->item_pidl); + if (op->folder) + IShellItem_Release(op->folder); + CoTaskMemFree(op->name); + list_remove(&op->entry); + free(op); + } +} + +static HRESULT add_operation(struct file_operation *operation, enum copy_engine_opcode opcode, IShellItem *item, + IShellItem *folder, const WCHAR *name, IFileOperationProgressSink *sink) +{ + struct copy_engine_operation *op; + HRESULT hr; + + if (!name) + name = L""; + + if (!(op = calloc(1, sizeof(*op)))) + return E_OUTOFMEMORY; + + op->opcode = opcode; + if (item && FAILED((hr = SHGetIDListFromObject((IUnknown *)item, &op->item_pidl)))) + { + hr = E_INVALIDARG; + goto error; + } + + if (folder) + { + IShellItem_AddRef(folder); + op->folder = folder; + } + if (!(op->name = wcsdup(name))) + { + hr = E_OUTOFMEMORY; + goto error; + } + if (sink) + { + IFileOperationProgressSink_AddRef(sink); + op->sink = sink; + } + list_add_tail(&operation->ops, &op->entry); + return S_OK; + +error: + ILFree(op->item_pidl); + if (op->folder) + IShellItem_Release(op->folder); + if (op->sink) + IFileOperationProgressSink_Release(sink); + CoTaskMemFree(op->name); + free(op); + return hr; +} + static inline struct file_operation *impl_from_IFileOperation(IFileOperation *iface) { return CONTAINING_RECORD(iface, struct file_operation, IFileOperation_iface); @@ -1916,6 +1997,7 @@ static ULONG WINAPI file_operation_Release(IFileOperation *iface) list_remove(&sink->entry); free(sink); } + free_file_operation_ops(operation); free(operation); }
@@ -2027,9 +2109,12 @@ static HRESULT WINAPI file_operation_RenameItems(IFileOperation *iface, IUnknown static HRESULT WINAPI file_operation_MoveItem(IFileOperation *iface, IShellItem *item, IShellItem *folder, LPCWSTR name, IFileOperationProgressSink *sink) { - FIXME("(%p, %p, %p, %s, %p): stub.\n", iface, item, folder, debugstr_w(name), sink); + TRACE("(%p, %p, %p, %s, %p).\n", iface, item, folder, debugstr_w(name), sink);
- return E_NOTIMPL; + if (!folder || !item) + return E_INVALIDARG; + + return add_operation(impl_from_IFileOperation(iface), COPY_ENGINE_MOVE, item, folder, name, sink); }
static HRESULT WINAPI file_operation_MoveItems(IFileOperation *iface, IUnknown *items, IShellItem *folder) @@ -2080,8 +2165,14 @@ static HRESULT WINAPI file_operation_NewItem(IFileOperation *iface, IShellItem *
static HRESULT WINAPI file_operation_PerformOperations(IFileOperation *iface) { + struct file_operation *operation = impl_from_IFileOperation(iface); + FIXME("(%p): stub.\n", iface);
+ if (list_empty(&operation->ops)) + return E_UNEXPECTED; + + free_file_operation_ops(operation); return E_NOTIMPL; }
@@ -2130,6 +2221,7 @@ HRESULT WINAPI IFileOperation_Constructor(IUnknown *outer, REFIID riid, void **o
object->IFileOperation_iface.lpVtbl = &file_operation_vtbl; list_init(&object->sinks); + list_init(&object->ops); object->ref = 1;
hr = IFileOperation_QueryInterface(&object->IFileOperation_iface, riid, out); diff --git a/dlls/shell32/tests/shlfileop.c b/dlls/shell32/tests/shlfileop.c index ec8e8e5869f..ae978b0e32e 100644 --- a/dlls/shell32/tests/shlfileop.c +++ b/dlls/shell32/tests/shlfileop.c @@ -3219,7 +3219,7 @@ static void test_file_operation(void) ok(hr == S_OK, "got %#lx.\n", hr);
hr = IFileOperation_PerformOperations(operation); - todo_wine ok(hr == E_UNEXPECTED, "got %#lx.\n", hr); + ok(hr == E_UNEXPECTED, "got %#lx.\n", hr);
hr = CoCreateInstance(&CLSID_ShellItem, NULL, CLSCTX_INPROC_SERVER, &IID_IShellItem, (void **)&item); ok(hr == S_OK, "got %#lx.\n", hr); @@ -3227,7 +3227,7 @@ static void test_file_operation(void) ok(hr == S_OK, "got %#lx.\n", hr);
hr = IFileOperation_MoveItem(operation, item, folder, L"test", NULL); - todo_wine ok(hr == E_INVALIDARG, "got %#lx.\n", hr); + ok(hr == E_INVALIDARG, "got %#lx.\n", hr);
GetTempPathW(ARRAY_SIZE(dirpath), dirpath); PathCombineW(tmpfile, dirpath, L"testfile1"); @@ -3243,7 +3243,7 @@ static void test_file_operation(void) progress_init_check_notifications(progress, ARRAY_SIZE(notifications1), notifications1, dirpath, &expected_notif); progress_init_check_notifications(progress2, ARRAY_SIZE(notifications1), notifications1, dirpath, &expected_notif); hr = IFileOperation_MoveItem(operation, item, folder, L"test", progress2); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); hr = IFileOperation_SetOperationFlags(operation, FOF_NO_UI); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); hr = IFileOperation_PerformOperations(operation); @@ -3255,11 +3255,11 @@ static void test_file_operation(void) progress_end_check_notifications(progress);
hr = IFileOperation_PerformOperations(operation); - todo_wine ok(hr == E_UNEXPECTED, "got %#lx.\n", hr); + ok(hr == E_UNEXPECTED, "got %#lx.\n", hr);
/* Input file does not exist: PerformOperations succeeds, 'aborted' is set. */ hr = IFileOperation_MoveItem(operation, item, folder, L"test2", NULL); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); progress_init_check_notifications(progress, ARRAY_SIZE(notifications2), notifications2, dirpath, &expected_notif); hr = IFileOperation_PerformOperations(operation); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); @@ -3277,7 +3277,7 @@ static void test_file_operation(void) PathCombineW(path, dirpath, L"test"); set_shell_item_path(item, path, TRUE); hr = IFileOperation_MoveItem(operation, item, folder, L"test2", NULL); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); PathCombineW(tmpfile, dirpath, L"testfile2"); /* Actual paths are fetched at _MoveItem and not at _Perform operation: changing item after doesn't matter. */ createTestFileW(tmpfile); @@ -3348,7 +3348,7 @@ static void test_file_operation(void)
/* Source is directory, destination test2 is file. */ hr = IFileOperation_MoveItem(operation, item, folder, L"test2", NULL); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); progress_init_check_notifications(progress, ARRAY_SIZE(notifications4), notifications4, dirpath, &expected_notif); hr = IFileOperation_PerformOperations(operation); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); @@ -3371,7 +3371,7 @@ static void test_file_operation(void) todo_wine ok(hr == S_OK, "got %#lx.\n", hr);
hr = IFileOperation_MoveItem(operation, item, folder, L"test2", NULL); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); progress_init_check_notifications(progress, ARRAY_SIZE(notifications5), notifications5, dirpath, &expected_notif); hr = IFileOperation_PerformOperations(operation); todo_wine ok(hr == S_OK, "got %#lx.\n", hr); @@ -3406,9 +3406,9 @@ static void test_file_operation(void) ok(hr == S_OK, "got %#lx.\n", hr);
hr = IFileOperation_MoveItem(operation, item, folder, L"test2", progress2); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); hr = IFileOperation_MoveItem(operation, item2, folder, NULL, NULL); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); refcount = IShellItem_Release(item2); ok(!refcount, "got %ld.\n", refcount); progress_init_check_notifications(progress, ARRAY_SIZE(notifications6), notifications6, dirpath, &expected_notif);
From: Paul Gofman pgofman@codeweavers.com
--- dlls/shell32/shlfileop.c | 104 +++++++++++++++++++++++++++++++-- dlls/shell32/tests/shlfileop.c | 60 +++++++++---------- 2 files changed, 128 insertions(+), 36 deletions(-)
diff --git a/dlls/shell32/shlfileop.c b/dlls/shell32/shlfileop.c index 9cf834d37fe..6c310186f7a 100644 --- a/dlls/shell32/shlfileop.c +++ b/dlls/shell32/shlfileop.c @@ -41,6 +41,7 @@ #include "shlwapi.h" #include "shell32_main.h" #include "shfldr.h" +#include "sherrors.h" #include "wine/debug.h" #include "wine/list.h"
@@ -1878,6 +1879,8 @@ struct file_operation struct list sinks; DWORD next_cookie; struct list ops; + DWORD flags; + BOOL aborted; };
static void free_file_operation_ops(struct file_operation *operation) @@ -1945,6 +1948,82 @@ error: return hr; }
+static HRESULT copy_engine_move(struct file_operation *operation, struct copy_engine_operation *op, IShellItem *src_item, + IShellItem **dest_folder) +{ + WCHAR path[MAX_PATH], item_path[MAX_PATH]; + DWORD src_attrs, dst_attrs; + WCHAR *str, *ptr; + HRESULT hr; + + *dest_folder = NULL; + if (FAILED(hr = IShellItem_GetDisplayName(src_item, SIGDN_FILESYSPATH, &str))) + return hr; + wcscpy_s(item_path, ARRAY_SIZE(item_path), str); + if (!*op->name && (ptr = StrRChrW(str, NULL, '\'))) + { + free(op->name); + op->name = wcsdup(ptr + 1); + } + CoTaskMemFree(str); + + if (FAILED(hr = IShellItem_GetDisplayName(op->folder, SIGDN_FILESYSPATH, &str))) + return hr; + hr = PathCombineW(path, str, op->name) ? S_OK : HRESULT_FROM_WIN32(GetLastError()); + CoTaskMemFree(str); + if (FAILED(hr)) + return hr; + + if ((src_attrs = GetFileAttributesW(item_path)) == INVALID_FILE_ATTRIBUTES) + return HRESULT_FROM_WIN32(GetLastError()); + dst_attrs = GetFileAttributesW(path); + if (IsAttribFile(src_attrs) && IsAttribDir(dst_attrs)) + return COPYENGINE_E_FILE_IS_FLD_DEST; + if (IsAttribDir(src_attrs) && IsAttribFile(dst_attrs)) + return COPYENGINE_E_FLD_IS_FILE_DEST; + if (dst_attrs == INVALID_FILE_ATTRIBUTES || (IsAttribFile(src_attrs) && IsAttribFile(dst_attrs))) + { + if (MoveFileExW(item_path, path, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING)) + { + if (FAILED((hr = SHCreateItemFromParsingName(path, NULL, &IID_IShellItem, (void **)dest_folder)))) + return hr; + return COPYENGINE_S_DONT_PROCESS_CHILDREN; + } + IShellItem_Release(*dest_folder); + return HRESULT_FROM_WIN32(GetLastError()); + } + + /* Merge directory to existing directory. */ + FIXME("Megre directories.\n"); + return E_FAIL; +} + +static HRESULT perform_file_operations(struct file_operation *operation) +{ + struct copy_engine_operation *op; + IShellItem *item, *created; + HRESULT hr; + + LIST_FOR_EACH_ENTRY(op, &operation->ops, struct copy_engine_operation, entry) + { + hr = E_FAIL; + switch (op->opcode) + { + case COPY_ENGINE_MOVE: + if (FAILED(hr = SHCreateItemFromIDList(op->item_pidl, &IID_IShellItem, (void**)&item))) + break; + hr = copy_engine_move(operation, op, item, &created); + IShellItem_Release(item); + if (SUCCEEDED(hr)) + IShellItem_Release(created); + break; + } + TRACE("op %d, hr %#lx.\n", op->opcode, hr); + operation->aborted = FAILED(hr) || operation->aborted; + } + return S_OK; +} + static inline struct file_operation *impl_from_IFileOperation(IFileOperation *iface) { return CONTAINING_RECORD(iface, struct file_operation, IFileOperation_iface); @@ -2044,9 +2123,12 @@ static HRESULT WINAPI file_operation_Unadvise(IFileOperation *iface, DWORD cooki
static HRESULT WINAPI file_operation_SetOperationFlags(IFileOperation *iface, DWORD flags) { - FIXME("(%p, %lx): stub.\n", iface, flags); + struct file_operation *operation = impl_from_IFileOperation(iface);
- return E_NOTIMPL; + TRACE("(%p, %lx).\n", iface, flags); + + operation->flags = flags; + return S_OK; }
static HRESULT WINAPI file_operation_SetProgressMessage(IFileOperation *iface, LPCWSTR message) @@ -2166,21 +2248,31 @@ static HRESULT WINAPI file_operation_NewItem(IFileOperation *iface, IShellItem * static HRESULT WINAPI file_operation_PerformOperations(IFileOperation *iface) { struct file_operation *operation = impl_from_IFileOperation(iface); + HRESULT hr;
- FIXME("(%p): stub.\n", iface); + TRACE("(%p).\n", iface);
if (list_empty(&operation->ops)) return E_UNEXPECTED;
+ if (operation->flags != FOF_NO_UI) + FIXME("Unhandled flags %#lx.\n", operation->flags); + hr = perform_file_operations(operation); free_file_operation_ops(operation); - return E_NOTIMPL; + return hr; }
static HRESULT WINAPI file_operation_GetAnyOperationsAborted(IFileOperation *iface, BOOL *aborted) { - FIXME("(%p, %p): stub.\n", iface, aborted); + struct file_operation *operation = impl_from_IFileOperation(iface);
- return E_NOTIMPL; + TRACE("(%p, %p).\n", iface, aborted); + + if (!aborted) + return E_POINTER; + *aborted = operation->aborted; + TRACE("-> aborted %d.\n", *aborted); + return S_OK; }
static const IFileOperationVtbl file_operation_vtbl = diff --git a/dlls/shell32/tests/shlfileop.c b/dlls/shell32/tests/shlfileop.c index ae978b0e32e..018c503d59d 100644 --- a/dlls/shell32/tests/shlfileop.c +++ b/dlls/shell32/tests/shlfileop.c @@ -3063,14 +3063,14 @@ static IFileOperationProgressSink *create_progress_sink(unsigned int instance_id return &obj->IFileOperationProgressSink_iface; }
-static void set_shell_item_path(IShellItem *item, const WCHAR *path, BOOL todo) +static void set_shell_item_path(IShellItem *item, const WCHAR *path) { IPersistIDList *idlist; ITEMIDLIST *pidl; HRESULT hr;
hr = SHParseDisplayName(path, NULL, &pidl, 0, NULL); - todo_wine_if(todo) ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); if (FAILED(hr)) return; hr = IShellItem_QueryInterface(item, &IID_IPersistIDList, (void **)&idlist); @@ -3233,11 +3233,11 @@ static void test_file_operation(void) PathCombineW(tmpfile, dirpath, L"testfile1"); createTestFileW(tmpfile);
- set_shell_item_path(folder, dirpath, FALSE); - set_shell_item_path(item, tmpfile, FALSE); + set_shell_item_path(folder, dirpath); + set_shell_item_path(item, tmpfile);
hr = IFileOperation_SetOperationFlags(operation, 0); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr);
progress2 = create_progress_sink(1); progress_init_check_notifications(progress, ARRAY_SIZE(notifications1), notifications1, dirpath, &expected_notif); @@ -3245,13 +3245,13 @@ static void test_file_operation(void) hr = IFileOperation_MoveItem(operation, item, folder, L"test", progress2); ok(hr == S_OK, "got %#lx.\n", hr); hr = IFileOperation_SetOperationFlags(operation, FOF_NO_UI); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); hr = IFileOperation_PerformOperations(operation); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); aborted = 0xdeadbeef; hr = IFileOperation_GetAnyOperationsAborted(operation, &aborted); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); - todo_wine ok(!aborted, "got %d.\n", aborted); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(!aborted, "got %d.\n", aborted); progress_end_check_notifications(progress);
hr = IFileOperation_PerformOperations(operation); @@ -3262,36 +3262,36 @@ static void test_file_operation(void) ok(hr == S_OK, "got %#lx.\n", hr); progress_init_check_notifications(progress, ARRAY_SIZE(notifications2), notifications2, dirpath, &expected_notif); hr = IFileOperation_PerformOperations(operation); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); progress_end_check_notifications(progress); aborted = 0; hr = IFileOperation_GetAnyOperationsAborted(operation, &aborted); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); - todo_wine ok(aborted == TRUE, "got %d.\n", aborted); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(aborted == TRUE, "got %d.\n", aborted); aborted = 0; hr = IFileOperation_GetAnyOperationsAborted(operation, &aborted); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); - todo_wine ok(aborted == TRUE, "got %d.\n", aborted); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(aborted == TRUE, "got %d.\n", aborted);
/* Input file exists: PerformOperations succeeds, the item data at the moment of MoveItem is used. */ PathCombineW(path, dirpath, L"test"); - set_shell_item_path(item, path, TRUE); + set_shell_item_path(item, path); hr = IFileOperation_MoveItem(operation, item, folder, L"test2", NULL); ok(hr == S_OK, "got %#lx.\n", hr); PathCombineW(tmpfile, dirpath, L"testfile2"); /* Actual paths are fetched at _MoveItem and not at _Perform operation: changing item after doesn't matter. */ createTestFileW(tmpfile); - set_shell_item_path(item, tmpfile, FALSE); + set_shell_item_path(item, tmpfile); bret = DeleteFileW(tmpfile); ok(bret, "got error %ld.\n", GetLastError()); progress_init_check_notifications(progress, ARRAY_SIZE(notifications3), notifications3, dirpath, &expected_notif); hr = IFileOperation_PerformOperations(operation); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); progress_end_check_notifications(progress); aborted = 0; hr = IFileOperation_GetAnyOperationsAborted(operation, &aborted); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); - todo_wine ok(aborted == TRUE, "got %d.\n", aborted); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(aborted == TRUE, "got %d.\n", aborted); ret = GetFileAttributesW(tmpfile); ok(ret == INVALID_FILE_ATTRIBUTES, "got %#lx.\n", ret); PathCombineW(path, dirpath, L"test"); @@ -3299,9 +3299,9 @@ static void test_file_operation(void) ok(ret == INVALID_FILE_ATTRIBUTES, "got %#lx.\n", ret); PathCombineW(path, dirpath, L"test2"); ret = GetFileAttributesW(path); - todo_wine ok(ret != INVALID_FILE_ATTRIBUTES, "got %#lx.\n", ret); + ok(ret != INVALID_FILE_ATTRIBUTES, "got %#lx.\n", ret); bret = DeleteFileW(path); - todo_wine ok(bret, "got error %ld.\n", GetLastError()); + ok(bret, "got error %ld.\n", GetLastError());
refcount = IShellItem_Release(item); ok(!refcount, "got %ld.\n", refcount); @@ -3323,7 +3323,7 @@ static void test_file_operation(void) ok(hr == S_OK, "got %#lx.\n", hr);
hr = IFileOperation_SetOperationFlags(operation, FOF_NO_UI); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr);
hr = IFileOperation_Advise(operation, progress, &cookie); ok(hr == S_OK, "got %#lx.\n", hr); @@ -3351,12 +3351,12 @@ static void test_file_operation(void) ok(hr == S_OK, "got %#lx.\n", hr); progress_init_check_notifications(progress, ARRAY_SIZE(notifications4), notifications4, dirpath, &expected_notif); hr = IFileOperation_PerformOperations(operation); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); progress_end_check_notifications(progress); aborted = 0; hr = IFileOperation_GetAnyOperationsAborted(operation, &aborted); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); - todo_wine ok(aborted, "got %d.\n", aborted); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(aborted, "got %d.\n", aborted);
bret = DeleteFileW(path); ok(bret, "got error %ld.\n", GetLastError()); @@ -3368,13 +3368,13 @@ static void test_file_operation(void) hr = IFileOperation_Advise(operation, progress, &cookie); ok(hr == S_OK, "got %#lx.\n", hr); hr = IFileOperation_SetOperationFlags(operation, FOF_NO_UI); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr);
hr = IFileOperation_MoveItem(operation, item, folder, L"test2", NULL); ok(hr == S_OK, "got %#lx.\n", hr); progress_init_check_notifications(progress, ARRAY_SIZE(notifications5), notifications5, dirpath, &expected_notif); hr = IFileOperation_PerformOperations(operation); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); progress_end_check_notifications(progress); IFileOperation_Release(operation);
@@ -3384,11 +3384,11 @@ static void test_file_operation(void) hr = IFileOperation_Advise(operation, progress, &cookie); ok(hr == S_OK, "got %#lx.\n", hr); hr = IFileOperation_SetOperationFlags(operation, FOF_NO_UI); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr);
PathCombineW(path, dirpath, L"test_dir3"); bret = CreateDirectoryW(path, NULL); - set_shell_item_path(item, path, FALSE); + set_shell_item_path(item, path); ok(bret, "got error %ld.\n", GetLastError()); PathCombineW(tmpfile, path, L"testfile5"); createTestFileW(tmpfile); @@ -3413,7 +3413,7 @@ static void test_file_operation(void) ok(!refcount, "got %ld.\n", refcount); progress_init_check_notifications(progress, ARRAY_SIZE(notifications6), notifications6, dirpath, &expected_notif); hr = IFileOperation_PerformOperations(operation); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + ok(hr == S_OK, "got %#lx.\n", hr); progress_end_check_notifications(progress);
PathCombineW(path, dirpath, L"test_dir2");
From: Paul Gofman pgofman@codeweavers.com
--- dlls/shell32/shlfileop.c | 83 ++++++++++++++++++++++++++++++++-- dlls/shell32/tests/shlfileop.c | 4 +- 2 files changed, 80 insertions(+), 7 deletions(-)
diff --git a/dlls/shell32/shlfileop.c b/dlls/shell32/shlfileop.c index 6c310186f7a..c540994658e 100644 --- a/dlls/shell32/shlfileop.c +++ b/dlls/shell32/shlfileop.c @@ -1853,6 +1853,7 @@ HRESULT WINAPI SHMultiFileProperties(IDataObject *pdtobj, DWORD flags) enum copy_engine_opcode { COPY_ENGINE_MOVE, + COPY_ENGINE_REMOVE_DIRECTORY_SILENT, };
struct copy_engine_operation @@ -1901,7 +1902,7 @@ static void free_file_operation_ops(struct file_operation *operation) }
static HRESULT add_operation(struct file_operation *operation, enum copy_engine_opcode opcode, IShellItem *item, - IShellItem *folder, const WCHAR *name, IFileOperationProgressSink *sink) + IShellItem *folder, const WCHAR *name, IFileOperationProgressSink *sink, struct list *add_after) { struct copy_engine_operation *op; HRESULT hr; @@ -1934,7 +1935,10 @@ static HRESULT add_operation(struct file_operation *operation, enum copy_engine_ IFileOperationProgressSink_AddRef(sink); op->sink = sink; } - list_add_tail(&operation->ops, &op->entry); + if (add_after) + list_add_after(add_after, &op->entry); + else + list_add_tail(&operation->ops, &op->entry); return S_OK;
error: @@ -1948,11 +1952,55 @@ error: return hr; }
+static HRESULT copy_engine_merge_dir(struct file_operation *operation, struct copy_engine_operation *op, + const WCHAR *src_dir_path, IShellItem *dest_folder, struct list **add_after) +{ + struct list *add_files_after, *curr_add_after; + WIN32_FIND_DATAW wfd; + WCHAR path[MAX_PATH]; + IShellItem *item; + HRESULT hr = S_OK; + HANDLE fh; + + if (!PathCombineW(path, src_dir_path, L"*.*")) + return HRESULT_FROM_WIN32(GetLastError()); + + fh = FindFirstFileW(path, &wfd); + if (fh == INVALID_HANDLE_VALUE) return S_OK; + add_files_after = *add_after; + do + { + if (!wcscmp(wfd.cFileName, L".") || !wcscmp(wfd.cFileName, L"..")) + continue; + if (!PathCombineW(path, src_dir_path, wfd.cFileName)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + break; + } + if (FAILED(hr = SHCreateItemFromParsingName(path, NULL, &IID_IShellItem, (void **)&item))) + break; + + /* Queue files before directories. */ + curr_add_after = IsAttribFile(wfd.dwFileAttributes) ? add_files_after : *add_after; + hr = add_operation(operation, COPY_ENGINE_MOVE, item, dest_folder, NULL, op->sink, curr_add_after); + IShellItem_Release(item); + if (FAILED(hr)) + break; + if (curr_add_after == *add_after) + *add_after = (*add_after)->next; + if (IsAttribFile(wfd.dwFileAttributes)) + add_files_after = add_files_after->next; + } while (FindNextFileW(fh, &wfd)); + FindClose(fh); + return hr; +} + static HRESULT copy_engine_move(struct file_operation *operation, struct copy_engine_operation *op, IShellItem *src_item, IShellItem **dest_folder) { WCHAR path[MAX_PATH], item_path[MAX_PATH]; DWORD src_attrs, dst_attrs; + struct list *add_after; WCHAR *str, *ptr; HRESULT hr;
@@ -1994,8 +2042,19 @@ static HRESULT copy_engine_move(struct file_operation *operation, struct copy_en }
/* Merge directory to existing directory. */ - FIXME("Megre directories.\n"); - return E_FAIL; + if (FAILED((hr = SHCreateItemFromParsingName(path, NULL, &IID_IShellItem, (void **)dest_folder)))) + return hr; + + add_after = &op->entry; + if (FAILED((hr = copy_engine_merge_dir(operation, op, item_path, *dest_folder, &add_after)))) + { + IShellItem_Release(*dest_folder); + *dest_folder = NULL; + return hr; + } + add_operation(operation, COPY_ENGINE_REMOVE_DIRECTORY_SILENT, src_item, NULL, NULL, NULL, add_after); + + return COPYENGINE_S_NOT_HANDLED; }
static HRESULT perform_file_operations(struct file_operation *operation) @@ -2009,6 +2068,20 @@ static HRESULT perform_file_operations(struct file_operation *operation) hr = E_FAIL; switch (op->opcode) { + case COPY_ENGINE_REMOVE_DIRECTORY_SILENT: + { + WCHAR *path; + if (FAILED(SHGetNameFromIDList(op->item_pidl, SIGDN_FILESYSPATH, &path))) + { + ERR("SHGetNameFromIDList failed.\n"); + break; + } + if (!RemoveDirectoryW(path)) + WARN("Remove directory failed, err %lu.\n", GetLastError()); + CoTaskMemFree(path); + break; + } + case COPY_ENGINE_MOVE: if (FAILED(hr = SHCreateItemFromIDList(op->item_pidl, &IID_IShellItem, (void**)&item))) break; @@ -2196,7 +2269,7 @@ static HRESULT WINAPI file_operation_MoveItem(IFileOperation *iface, IShellItem if (!folder || !item) return E_INVALIDARG;
- return add_operation(impl_from_IFileOperation(iface), COPY_ENGINE_MOVE, item, folder, name, sink); + return add_operation(impl_from_IFileOperation(iface), COPY_ENGINE_MOVE, item, folder, name, sink, NULL); }
static HRESULT WINAPI file_operation_MoveItems(IFileOperation *iface, IUnknown *items, IShellItem *folder) diff --git a/dlls/shell32/tests/shlfileop.c b/dlls/shell32/tests/shlfileop.c index 018c503d59d..5aa6cecb0ec 100644 --- a/dlls/shell32/tests/shlfileop.c +++ b/dlls/shell32/tests/shlfileop.c @@ -3419,11 +3419,11 @@ static void test_file_operation(void) PathCombineW(path, dirpath, L"test_dir2"); PathCombineW(tmpfile, dirpath, L"test_dir2\test2\testfile6"); ret = GetFileAttributesW(tmpfile); - todo_wine ok(ret != INVALID_FILE_ATTRIBUTES, "got %#lx.\n", ret); + ok(ret != INVALID_FILE_ATTRIBUTES, "got %#lx.\n", ret); remove_directory(path); PathCombineW(path, dirpath, L"test_dir3"); ret = GetFileAttributesW(path); - todo_wine ok(ret == INVALID_FILE_ATTRIBUTES, "got %#lx.\n", ret); + ok(ret == INVALID_FILE_ATTRIBUTES, "got %#lx.\n", ret); remove_directory(path);
IFileOperation_Release(operation);
From: Paul Gofman pgofman@codeweavers.com
--- dlls/shell32/shlfileop.c | 131 ++++++++++++++++++++++++++++++--- dlls/shell32/tests/shlfileop.c | 2 +- 2 files changed, 122 insertions(+), 11 deletions(-)
diff --git a/dlls/shell32/shlfileop.c b/dlls/shell32/shlfileop.c index c540994658e..4fb0642fc5d 100644 --- a/dlls/shell32/shlfileop.c +++ b/dlls/shell32/shlfileop.c @@ -1856,6 +1856,8 @@ enum copy_engine_opcode COPY_ENGINE_REMOVE_DIRECTORY_SILENT, };
+#define TSF_UNKNOWN_MEGRE_FLAG 0x1000 + struct copy_engine_operation { struct list entry; @@ -1864,6 +1866,7 @@ struct copy_engine_operation IShellItem *folder; PIDLIST_ABSOLUTE item_pidl; WCHAR *name; + DWORD tsf; };
struct file_operation_sink @@ -1882,6 +1885,7 @@ struct file_operation struct list ops; DWORD flags; BOOL aborted; + unsigned int progress_total, progress_sofar; };
static void free_file_operation_ops(struct file_operation *operation) @@ -1902,7 +1906,7 @@ static void free_file_operation_ops(struct file_operation *operation) }
static HRESULT add_operation(struct file_operation *operation, enum copy_engine_opcode opcode, IShellItem *item, - IShellItem *folder, const WCHAR *name, IFileOperationProgressSink *sink, struct list *add_after) + IShellItem *folder, const WCHAR *name, IFileOperationProgressSink *sink, DWORD tsf, struct list *add_after) { struct copy_engine_operation *op; HRESULT hr; @@ -1920,6 +1924,7 @@ static HRESULT add_operation(struct file_operation *operation, enum copy_engine_ goto error; }
+ op->tsf = tsf; if (folder) { IShellItem_AddRef(folder); @@ -1952,6 +1957,86 @@ error: return hr; }
+static HRESULT file_operation_notify(struct file_operation *operation, struct copy_engine_operation *op, BOOL post_notif, + void *context, + HRESULT (*callback)(IFileOperationProgressSink *, struct file_operation *, struct copy_engine_operation *op, void *)) +{ + struct file_operation_sink *op_sink; + HRESULT hr = S_OK; + + if (op && op->sink && post_notif) + { + if (FAILED(hr = callback(op->sink, operation, op, context))) + goto done; + } + LIST_FOR_EACH_ENTRY(op_sink, &operation->sinks, struct file_operation_sink, entry) + { + if (FAILED(hr = callback(op_sink->sink, operation, op, context))) + goto done; + } + if (op && op->sink && !post_notif) + hr = callback(op->sink, operation, op, context); + +done: + if (FAILED(hr)) + WARN("sink returned %#lx.\n", hr); + return hr; +} + +static HRESULT notify_start_operations(IFileOperationProgressSink *sink, struct file_operation *operations, + struct copy_engine_operation *op, void *context) +{ + return IFileOperationProgressSink_StartOperations(sink); +} + +static HRESULT notify_reset_timer(IFileOperationProgressSink *sink, struct file_operation *operations, + struct copy_engine_operation *op, void *context) +{ + return IFileOperationProgressSink_ResetTimer(sink); +} + +static HRESULT notify_finish_operations(IFileOperationProgressSink *sink, struct file_operation *operations, + struct copy_engine_operation *op, void *context) +{ + return IFileOperationProgressSink_FinishOperations(sink, S_OK); +} + +static HRESULT notify_update_progress(IFileOperationProgressSink *sink, struct file_operation *operations, + struct copy_engine_operation *op, void *context) +{ + return IFileOperationProgressSink_UpdateProgress(sink, operations->progress_total, operations->progress_sofar); +} + +struct notify_move_item_param +{ + IShellItem *item; + IShellItem *new_item; + HRESULT result; +}; + +static HRESULT notify_pre_move_item(IFileOperationProgressSink *sink, struct file_operation *operations, + struct copy_engine_operation *op, void *context) +{ + struct notify_move_item_param *p = context; + + return IFileOperationProgressSink_PreMoveItem(sink, op->tsf & ~TSF_UNKNOWN_MEGRE_FLAG, p->item, op->folder, op->name); +} + +static HRESULT notify_post_move_item(IFileOperationProgressSink *sink, struct file_operation *operations, + struct copy_engine_operation *op, void *context) +{ + struct notify_move_item_param *p = context; + + return IFileOperationProgressSink_PostMoveItem(sink, op->tsf, p->item, op->folder, op->name, p->result, p->new_item); +} + +static void set_file_operation_progress(struct file_operation *operation, unsigned int total, unsigned int sofar) +{ + operation->progress_total = total; + operation->progress_sofar = sofar; + file_operation_notify(operation, NULL, FALSE, NULL, notify_update_progress); +} + static HRESULT copy_engine_merge_dir(struct file_operation *operation, struct copy_engine_operation *op, const WCHAR *src_dir_path, IShellItem *dest_folder, struct list **add_after) { @@ -1982,7 +2067,8 @@ static HRESULT copy_engine_merge_dir(struct file_operation *operation, struct co
/* Queue files before directories. */ curr_add_after = IsAttribFile(wfd.dwFileAttributes) ? add_files_after : *add_after; - hr = add_operation(operation, COPY_ENGINE_MOVE, item, dest_folder, NULL, op->sink, curr_add_after); + hr = add_operation(operation, COPY_ENGINE_MOVE, item, dest_folder, NULL, op->sink, + (op->tsf & ~TSF_COPY_LOCALIZED_NAME) | TSF_UNKNOWN_MEGRE_FLAG, curr_add_after); IShellItem_Release(item); if (FAILED(hr)) break; @@ -2052,7 +2138,7 @@ static HRESULT copy_engine_move(struct file_operation *operation, struct copy_en *dest_folder = NULL; return hr; } - add_operation(operation, COPY_ENGINE_REMOVE_DIRECTORY_SILENT, src_item, NULL, NULL, NULL, add_after); + add_operation(operation, COPY_ENGINE_REMOVE_DIRECTORY_SILENT, src_item, NULL, NULL, NULL, 0, add_after);
return COPYENGINE_S_NOT_HANDLED; } @@ -2060,9 +2146,13 @@ static HRESULT copy_engine_move(struct file_operation *operation, struct copy_en static HRESULT perform_file_operations(struct file_operation *operation) { struct copy_engine_operation *op; - IShellItem *item, *created; HRESULT hr;
+ file_operation_notify(operation, NULL, FALSE, NULL, notify_start_operations); + set_file_operation_progress(operation, 0, 0); + set_file_operation_progress(operation, list_count(&operation->ops), 0); + file_operation_notify(operation, NULL, FALSE, NULL, notify_reset_timer); + LIST_FOR_EACH_ENTRY(op, &operation->ops, struct copy_engine_operation, entry) { hr = E_FAIL; @@ -2083,17 +2173,37 @@ static HRESULT perform_file_operations(struct file_operation *operation) }
case COPY_ENGINE_MOVE: - if (FAILED(hr = SHCreateItemFromIDList(op->item_pidl, &IID_IShellItem, (void**)&item))) + { + struct notify_move_item_param p; + + p.new_item = NULL; + if (FAILED(hr = SHCreateItemFromIDList(op->item_pidl, &IID_IShellItem, (void**)&p.item))) break; - hr = copy_engine_move(operation, op, item, &created); - IShellItem_Release(item); - if (SUCCEEDED(hr)) - IShellItem_Release(created); + if (FAILED((hr = file_operation_notify(operation, op, FALSE, &p, notify_pre_move_item)))) + { + IShellItem_Release(p.item); + return hr; + } + hr = copy_engine_move(operation, op, p.item, &p.new_item); + p.result = hr; + if (FAILED(hr = file_operation_notify(operation, op, TRUE, &p, notify_post_move_item))) + { + if (p.new_item) + IShellItem_Release(p.new_item); + return hr; + } + hr = p.result; + IShellItem_Release(p.item); + if (p.new_item) + IShellItem_Release(p.new_item); break; + } } + set_file_operation_progress(operation, list_count(&operation->ops), operation->progress_sofar + 1); TRACE("op %d, hr %#lx.\n", op->opcode, hr); operation->aborted = FAILED(hr) || operation->aborted; } + file_operation_notify(operation, NULL, FALSE, NULL, notify_finish_operations); return S_OK; }
@@ -2269,7 +2379,8 @@ static HRESULT WINAPI file_operation_MoveItem(IFileOperation *iface, IShellItem if (!folder || !item) return E_INVALIDARG;
- return add_operation(impl_from_IFileOperation(iface), COPY_ENGINE_MOVE, item, folder, name, sink, NULL); + return add_operation(impl_from_IFileOperation(iface), COPY_ENGINE_MOVE, item, folder, name, sink, + TSF_COPY_LOCALIZED_NAME | TSF_COPY_WRITE_TIME | TSF_COPY_CREATION_TIME | TSF_OVERWRITE_EXIST, NULL); }
static HRESULT WINAPI file_operation_MoveItems(IFileOperation *iface, IUnknown *items, IShellItem *folder) diff --git a/dlls/shell32/tests/shlfileop.c b/dlls/shell32/tests/shlfileop.c index 5aa6cecb0ec..192fa1dd9bb 100644 --- a/dlls/shell32/tests/shlfileop.c +++ b/dlls/shell32/tests/shlfileop.c @@ -2835,7 +2835,7 @@ static void progress_end_check_notifications(IFileOperationProgressSink *iface) struct progress_expected_notifications *e = progress->expected;
ok(!!e, "expected notifications are not set up.\n"); - todo_wine ok_(__FILE__, e->line)(e->index == e->count, "got notification count %u, expected %u.\n", e->index, e->count); + ok_(__FILE__, e->line)(e->index == e->count, "got notification count %u, expected %u.\n", e->index, e->count); progress->expected = NULL; }
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=147753
Your paranoid android.
=== debian11b (64 bit WoW report) ===
user32: input.c:4305: Test succeeded inside todo block: button_down_hwnd_todo 1: got MSG_TEST_WIN hwnd 0000000001B70106, msg WM_LBUTTONDOWN, wparam 0x1, lparam 0x320032
Fixes Doom Eternal (modded version) being unable to download mods.
I am aware of MR https://gitlab.winehq.org/wine/wine/-/merge_requests/4817 from 6 months ago. I don't know why exactly it wasn't merge but for me it looks wrong to implement the newer and more feature rich IFileOperations interface on top of old SHFileOperationW(). In particular, sink notifications are not possible to implement this way, there might be much more functional changes revealing when adding tests. So I decided to implement that in an entirely different way, also adding some tests and implementation for notification sink at once. Besides that being used in the mentioned game, it also makes it easier to test how the operations are actually performed and make sure that behaviour does not diverge from Windows too much.
Making some common helpers shared between old and new engines also seems to be adding more complexity than saving an effort. There is
There are also (currently unimplemented in Wine) ITransferSource / ITransferDestination interfaces which are sort of related and apparently using the same "copy engine". I looked at those a tiny bit and I didn't see how implementing those first would help here, e. g., they have different notification system (the one from here is not implementable on top of that). So I concluded that implementing those first and using something from that in IFileOperations is not beneficial, rather those can be implemented on top of IFileOperations or both can use shared internal "copy engine" helpers.
Turns out there is yet another, newer but smaller, implementation attempt: https://gitlab.winehq.org/wine/wine/-/merge_requests/5671 . This functionality is probably very important :)