From 686e46b43fb0cfd18bde1a6c0308eb067a7805a2 Mon Sep 17 00:00:00 2001 From: Andriy Palamarchuk Date: Wed, 4 Sep 2002 18:46:29 +0000 Subject: [PATCH] SHFileOperationA: improved, implemented FO_MOVE action, added more conformance tests. --- dlls/shell32/shlfileop.c | 140 +++++++++++++++++++--- dlls/shell32/tests/shlfileop.c | 206 ++++++++++++++++++++++++++++++++- 2 files changed, 324 insertions(+), 22 deletions(-) diff --git a/dlls/shell32/shlfileop.c b/dlls/shell32/shlfileop.c index de040135fc9..657e64f2a25 100644 --- a/dlls/shell32/shlfileop.c +++ b/dlls/shell32/shlfileop.c @@ -2,6 +2,7 @@ * SHFileOperation * * Copyright 2000 Juergen Schmied + * Copyright 2002 Andriy Palamarchuk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -163,6 +164,28 @@ BOOL WINAPI Win32DeleteFile(LPSTR fName) return TRUE; } +/************************************************************************** + * SHELL_FileNamesMatch() + * + * Accepts two \0 delimited lists of the file names. Checks whether number of + * files in the both lists is the same. + */ +BOOL SHELL_FileNamesMatch(LPCSTR pszFiles1, LPCSTR pszFiles2) +{ + while ((pszFiles1[strlen(pszFiles1) + 1] != '\0') && + (pszFiles2[strlen(pszFiles2) + 1] != '\0')) + { + pszFiles1 += strlen(pszFiles1) + 1; + pszFiles2 += strlen(pszFiles2) + 1; + } + + return + ((pszFiles1[strlen(pszFiles1) + 1] == '\0') && + (pszFiles2[strlen(pszFiles2) + 1] == '\0')) || + ((pszFiles1[strlen(pszFiles1) + 1] != '\0') && + (pszFiles2[strlen(pszFiles2) + 1] != '\0')); +} + /************************************************************************* * SHFileOperationA [SHELL32.@] * @@ -188,7 +211,9 @@ DWORD WINAPI SHFileOperationA (LPSHFILEOPSTRUCTA lpFileOp) lpFileOp->fFlags & FOF_NOERRORUI ? "FOF_NOERRORUI " : "", lpFileOp->fFlags & 0xf800 ? "MORE-UNKNOWN-Flags" : ""); switch(lpFileOp->wFunc) { - case FO_COPY: { + case FO_COPY: + case FO_MOVE: + { /* establish when pTo is interpreted as the name of the destination file * or the directory where the Fromfile should be copied to. * This depends on: @@ -207,16 +232,40 @@ DWORD WINAPI SHFileOperationA (LPSHFILEOPSTRUCTA lpFileOp) */ int multifrom = pFrom[strlen(pFrom) + 1] != '\0'; int destisdir = PathIsDirectoryA( pTo ); - int copytodir = 0; - TRACE("File Copy:\n"); + int todir = 0; + + if (lpFileOp->wFunc == FO_COPY) + TRACE("File Copy:\n"); + else + TRACE("File Move:\n"); + if( destisdir ) { if ( !((lpFileOp->fFlags & FOF_MULTIDESTFILES) && !multifrom)) - copytodir = 1; + todir = 1; } else { if ( !(lpFileOp->fFlags & FOF_MULTIDESTFILES) && multifrom) - copytodir = 1; + todir = 1; } - if ( copytodir ) { + + if ((pTo[strlen(pTo) + 1] != '\0') && + !(lpFileOp->fFlags & FOF_MULTIDESTFILES)) + { + WARN("Attempt to use multiple file names as a destination " + "without specifying FOF_MULTIDESTFILES\n"); + return 1; + } + + if ((lpFileOp->fFlags & FOF_MULTIDESTFILES) && + !SHELL_FileNamesMatch(pTo, pFrom)) + { + WARN("Attempt to use multiple file names as a destination " + "with mismatching number of files in the source and " + "destination lists\n"); + return 1; + } + + if ( todir ) { + char szTempFrom[MAX_PATH]; char *fromfile; int lenPTo; if ( ! destisdir) { @@ -225,17 +274,71 @@ DWORD WINAPI SHFileOperationA (LPSHFILEOPSTRUCTA lpFileOp) } lenPTo = strlen(pTo); while(1) { + HANDLE hFind; + WIN32_FIND_DATAA wfd; + if(!pFrom[0]) break; - fromfile = PathFindFileNameA( pFrom); - pTempTo = HeapAlloc(GetProcessHeap(), 0, lenPTo + strlen(fromfile) + 2); - if (pTempTo) { - strcpy(pTempTo,pTo); - if(lenPTo && pTo[lenPTo] != '\\') - strcat(pTempTo,"\\"); - strcat(pTempTo,fromfile); - TRACE(" From='%s' To='%s'\n", pFrom, pTempTo); - CopyFileA(pFrom, pTempTo, FALSE); - HeapFree(GetProcessHeap(), 0, pTempTo); + TRACE(" From Pattern='%s'\n", pFrom); + if(INVALID_HANDLE_VALUE != (hFind = FindFirstFileA(pFrom, &wfd))) + { + do + { + if(strcasecmp(wfd.cFileName, ".") && strcasecmp(wfd.cFileName, "..")) + { + strcpy(szTempFrom, pFrom); + + pTempTo = HeapAlloc(GetProcessHeap(), 0, + lenPTo + strlen(wfd.cFileName) + 5); + if (pTempTo) { + strcpy(pTempTo,pTo); + PathAddBackslashA(pTempTo); + strcat(pTempTo,wfd.cFileName); + + fromfile = PathFindFileNameA(szTempFrom); + fromfile[0] = '\0'; + PathAddBackslashA(szTempFrom); + strcat(szTempFrom, wfd.cFileName); + TRACE(" From='%s' To='%s'\n", szTempFrom, pTempTo); + if(lpFileOp->wFunc == FO_COPY) + { + if(FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes) + { + /* copy recursively */ + if(!(lpFileOp->fFlags & FOF_FILESONLY)) + { + SHFILEOPSTRUCTA shfo; + + SHCreateDirectory(NULL,pTempTo); + PathAddBackslashA(szTempFrom); + strcat(szTempFrom, "*.*"); + szTempFrom[strlen(szTempFrom) + 1] = '\0'; + pTempTo[strlen(pTempTo) + 1] = '\0'; + memcpy(&shfo, lpFileOp, sizeof(shfo)); + shfo.pFrom = szTempFrom; + shfo.pTo = pTempTo; + SHFileOperationA(&shfo); + + szTempFrom[strlen(szTempFrom) - 4] = '\0'; + } + } + else + CopyFileA(szTempFrom, pTempTo, FALSE); + } + else + { + /* move file/directory */ + MoveFileA(szTempFrom, pTempTo); + } + HeapFree(GetProcessHeap(), 0, pTempTo); + } + } + } while(FindNextFileA(hFind, &wfd)); + FindClose(hFind); + } + else + { + /* can't find file with specified name */ + break; } pFrom += strlen(pFrom) + 1; } @@ -254,7 +357,10 @@ DWORD WINAPI SHFileOperationA (LPSHFILEOPSTRUCTA lpFileOp) SHCreateDirectory(NULL,pTempTo); HeapFree(GetProcessHeap(), 0, pTempTo); } - CopyFileA(pFrom, pTo, FALSE); + if (lpFileOp->wFunc == FO_COPY) + CopyFileA(pFrom, pTo, FALSE); + else + MoveFileA(pFrom, pTo); pFrom += strlen(pFrom) + 1; pTo += strlen(pTo) + 1; diff --git a/dlls/shell32/tests/shlfileop.c b/dlls/shell32/tests/shlfileop.c index a4b8a005daf..4e7cffe0d14 100644 --- a/dlls/shell32/tests/shlfileop.c +++ b/dlls/shell32/tests/shlfileop.c @@ -30,9 +30,11 @@ void createTestFile(CHAR *name) { HANDLE file; DWORD written; + CHAR msg[MAX_PATH]; file = CreateFileA(name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, (HANDLE)NULL); - ok(file != INVALID_HANDLE_VALUE, "Failure to open file"); + sprintf(msg, "Failure to open file %s", name); + ok(file != INVALID_HANDLE_VALUE, msg); WriteFile(file, name, strlen(name), &written, NULL); WriteFile(file, "\n", strlen("\n"), &written, NULL); CloseHandle(file); @@ -51,6 +53,7 @@ void init_shfo_tests(void) createTestFile(".\\test2.txt"); createTestFile(".\\test3.txt"); CreateDirectoryA(".\\test4.txt", NULL); + CreateDirectoryA(".\\testdir2", NULL); } /* cleans after tests */ @@ -63,12 +66,19 @@ void clean_after_shfo_tests(void) DeleteFileA(".\\test4.txt\\test2.txt"); DeleteFileA(".\\test4.txt\\test3.txt"); RemoveDirectoryA(".\\test4.txt"); + DeleteFileA(".\\testdir2\\test1.txt"); + DeleteFileA(".\\testdir2\\test2.txt"); + DeleteFileA(".\\testdir2\\test3.txt"); + DeleteFileA(".\\testdir2\\test4.txt\\test1.txt"); + RemoveDirectoryA(".\\testdir2\\test4.txt"); + RemoveDirectoryA(".\\testdir2"); } -/** +/* puts into the specified buffer file names with current directory. files - string with file names, separated by null characters. Ends on a double - null characters */ + null characters +*/ void set_curr_dir_path(CHAR *buf, CHAR* files) { buf[0] = 0; @@ -110,7 +120,7 @@ void test_delete(void) ok(!SHFileOperationA(&shfo), "Directory exists, but is not removed"); ok(file_exists(".\\test4.txt"), "Directory should not be removed"); - shfo.fFlags = FOF_NOCONFIRMATION | FOF_SILENT; + shfo.fFlags = FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI; ok(!SHFileOperationA(&shfo), "Directory removed"); ok(!file_exists(".\\test4.txt"), "Directory should be removed"); @@ -135,7 +145,7 @@ void test_delete(void) /* tests the FO_RENAME action */ void test_rename() { - SHFILEOPSTRUCTA shfo; + SHFILEOPSTRUCTA shfo, shfo2; CHAR from[MAX_PATH]; CHAR to[MAX_PATH]; @@ -163,6 +173,14 @@ void test_rename() ok(SHFileOperationA(&shfo), "Can't rename many files"); ok(file_exists(".\\test1.txt"), "The file is not renamed - many files are specified "); + memcpy(&shfo2, &shfo, sizeof(SHFILEOPSTRUCTA)); + shfo2.fFlags |= FOF_MULTIDESTFILES; + + set_curr_dir_path(from, "test1.txt\0test2.txt\0test4.txt\0"); + set_curr_dir_path(to, "test6.txt\0test7.txt\0test8.txt\0"); + ok(SHFileOperationA(&shfo2), "Can't rename many files"); + ok(file_exists(".\\test1.txt"), "The file is not renamed - many files are specified "); + set_curr_dir_path(from, "test1.txt\0"); set_curr_dir_path(to, "test6.txt\0"); ok(!SHFileOperationA(&shfo), "Rename file"); @@ -182,6 +200,176 @@ void test_rename() ok(!SHFileOperationA(&shfo), "Rename dir back"); } +/* tests the FO_COPY action */ +void test_copy(void) +{ + SHFILEOPSTRUCTA shfo, shfo2; + CHAR from[MAX_PATH]; + CHAR to[MAX_PATH]; + FILEOP_FLAGS tmp_flags; + + shfo.hwnd = (HANDLE)NULL; + shfo.wFunc = FO_COPY; + shfo.pFrom = from; + shfo.pTo = to; + shfo.fFlags = FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI; + shfo.hNameMappings = NULL; + shfo.lpszProgressTitle = NULL; + + set_curr_dir_path(from, "test1.txt\0test2.txt\0test4.txt\0"); + set_curr_dir_path(to, "test6.txt\0test7.txt\0test8.txt\0"); + ok(SHFileOperationA(&shfo), "Can't copy many files"); + ok(!file_exists(".\\test6.txt"), "The file is not copied - many files are " + "specified as a target"); + + memcpy(&shfo2, &shfo, sizeof(SHFILEOPSTRUCTA)); + shfo2.fFlags |= FOF_MULTIDESTFILES; + + set_curr_dir_path(from, "test1.txt\0test2.txt\0test4.txt\0"); + set_curr_dir_path(to, "test6.txt\0test7.txt\0test8.txt\0"); + ok(!SHFileOperationA(&shfo2), "Can't copy many files"); + ok(file_exists(".\\test6.txt"), "The file is copied - many files are " + "specified as a target"); + DeleteFileA(".\\test6.txt"); + DeleteFileA(".\\test7.txt"); + RemoveDirectoryA(".\\test8.txt"); + + /* number of sources do not correspond to number of targets */ + set_curr_dir_path(from, "test1.txt\0test2.txt\0test4.txt\0"); + set_curr_dir_path(to, "test6.txt\0test7.txt\0"); + ok(SHFileOperationA(&shfo2), "Can't copy many files"); + ok(!file_exists(".\\test6.txt"), "The file is not copied - many files are " + "specified as a target"); + + set_curr_dir_path(from, "test1.txt\0"); + set_curr_dir_path(to, "test4.txt\0"); + ok(!SHFileOperationA(&shfo), "Prepare test to check how directories are copied recursively"); + ok(file_exists(".\\test4.txt\\test1.txt"), "The file is copied"); + + set_curr_dir_path(from, "test?.txt\0"); + set_curr_dir_path(to, "testdir2\0"); + ok(!file_exists(".\\testdir2\\test1.txt"), "The file is not copied yet"); + ok(!file_exists(".\\testdir2\\test4.txt"), "The directory is not copied yet"); + ok(!SHFileOperationA(&shfo), "Files and directories are copied to directory "); + ok(file_exists(".\\testdir2\\test1.txt"), "The file is copied"); + ok(file_exists(".\\testdir2\\test4.txt"), "The directory is copied"); + ok(file_exists(".\\testdir2\\test4.txt\\test1.txt"), "The file in subdirectory is copied"); + clean_after_shfo_tests(); + + init_shfo_tests(); + shfo.fFlags |= FOF_FILESONLY; + ok(!file_exists(".\\testdir2\\test1.txt"), "The file is not copied yet"); + ok(!file_exists(".\\testdir2\\test4.txt"), "The directory is not copied yet"); + ok(!SHFileOperationA(&shfo), "Files are copied to other directory "); + ok(file_exists(".\\testdir2\\test1.txt"), "The file is copied"); + ok(!file_exists(".\\testdir2\\test4.txt"), "The directory is copied"); + clean_after_shfo_tests(); + + init_shfo_tests(); + set_curr_dir_path(from, "test1.txt\0test2.txt\0"); + ok(!file_exists(".\\testdir2\\test1.txt"), "The file is not copied yet"); + ok(!file_exists(".\\testdir2\\test2.txt"), "The file is not copied yet"); + ok(!SHFileOperationA(&shfo), "Files are copied to other directory "); + ok(file_exists(".\\testdir2\\test1.txt"), "The file is copied"); + ok(file_exists(".\\testdir2\\test2.txt"), "The file is copied"); + clean_after_shfo_tests(); + + init_shfo_tests(); + tmp_flags = shfo.fFlags; + set_curr_dir_path(from, "test1.txt\0test10.txt\0test2.txt\0"); + ok(!file_exists(".\\testdir2\\test1.txt"), "The file is not copied yet"); + ok(!file_exists(".\\testdir2\\test2.txt"), "The file is not copied yet"); + ok(!SHFileOperationA(&shfo), "Files are copied to other directory "); + ok(file_exists(".\\testdir2\\test1.txt"), "The file is copied"); + ok(!file_exists(".\\testdir2\\test2.txt"), "The file is copied"); + shfo.fFlags = tmp_flags; +} + +/* tests the FO_MOVE action */ +void test_move(void) +{ + SHFILEOPSTRUCTA shfo, shfo2; + CHAR from[MAX_PATH]; + CHAR to[MAX_PATH]; + + shfo.hwnd = (HANDLE)NULL; + shfo.wFunc = FO_MOVE; + shfo.pFrom = from; + shfo.pTo = to; + shfo.fFlags = FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI; + shfo.hNameMappings = NULL; + shfo.lpszProgressTitle = NULL; + + set_curr_dir_path(from, "test1.txt\0"); + set_curr_dir_path(to, "test4.txt\0"); + ok(!SHFileOperationA(&shfo), "Prepare test to check how directories are moved recursively"); + ok(file_exists(".\\test4.txt\\test1.txt"), "The file is moved"); + + set_curr_dir_path(from, "test?.txt\0"); + set_curr_dir_path(to, "testdir2\0"); + ok(!file_exists(".\\testdir2\\test2.txt"), "The file is not moved yet"); + ok(!file_exists(".\\testdir2\\test4.txt"), "The directory is not moved yet"); + ok(!SHFileOperationA(&shfo), "Files and directories are moved to directory "); + ok(file_exists(".\\testdir2\\test2.txt"), "The file is moved"); + ok(file_exists(".\\testdir2\\test4.txt"), "The directory is moved"); + ok(file_exists(".\\testdir2\\test4.txt\\test1.txt"), "The file in subdirectory is moved"); + + clean_after_shfo_tests(); + init_shfo_tests(); + + memcpy(&shfo2, &shfo, sizeof(SHFILEOPSTRUCTA)); + shfo2.fFlags |= FOF_MULTIDESTFILES; + + set_curr_dir_path(from, "test1.txt\0test2.txt\0test4.txt\0"); + set_curr_dir_path(to, "test6.txt\0test7.txt\0test8.txt\0"); + ok(!SHFileOperationA(&shfo2), "Move many files"); + ok(file_exists(".\\test6.txt"), "The file is moved - many files are " + "specified as a target"); + DeleteFileA(".\\test6.txt"); + DeleteFileA(".\\test7.txt"); + RemoveDirectoryA(".\\test8.txt"); + + init_shfo_tests(); + + /* number of sources do not correspond to number of targets */ + set_curr_dir_path(from, "test1.txt\0test2.txt\0test4.txt\0"); + set_curr_dir_path(to, "test6.txt\0test7.txt\0"); + ok(SHFileOperationA(&shfo2), "Can't move many files"); + ok(!file_exists(".\\test6.txt"), "The file is not moved - many files are " + "specified as a target"); + + init_shfo_tests(); + + set_curr_dir_path(from, "test3.txt\0"); + set_curr_dir_path(to, "test4.txt\\test1.txt\0"); + ok(!SHFileOperationA(&shfo), "File is moved moving to other directory"); + ok(file_exists(".\\test4.txt\\test1.txt"), "The file is moved"); + + set_curr_dir_path(from, "test1.txt\0test2.txt\0test4.txt\0"); + set_curr_dir_path(to, "test6.txt\0test7.txt\0test8.txt\0"); + ok(SHFileOperationA(&shfo), "Can not move many files"); + ok(file_exists(".\\test1.txt"), "The file is not moved. Many files are specified "); + ok(file_exists(".\\test4.txt"), "The directory not is moved. Many files are specified "); + + set_curr_dir_path(from, "test1.txt\0"); + set_curr_dir_path(to, "test6.txt\0"); + ok(!SHFileOperationA(&shfo), "Move file"); + ok(!file_exists(".\\test1.txt"), "The file is moved"); + ok(file_exists(".\\test6.txt"), "The file is moved "); + set_curr_dir_path(from, "test6.txt\0"); + set_curr_dir_path(to, "test1.txt\0"); + ok(!SHFileOperationA(&shfo), "Move file back"); + + set_curr_dir_path(from, "test4.txt\0"); + set_curr_dir_path(to, "test6.txt\0"); + ok(!SHFileOperationA(&shfo), "Move dir"); + ok(!file_exists(".\\test4.txt"), "The dir is moved"); + ok(file_exists(".\\test6.txt"), "The dir is moved "); + set_curr_dir_path(from, "test6.txt\0"); + set_curr_dir_path(to, "test4.txt\0"); + ok(!SHFileOperationA(&shfo), "Move dir back"); +} + START_TEST(shlfileop) { clean_after_shfo_tests(); @@ -193,4 +381,12 @@ START_TEST(shlfileop) init_shfo_tests(); test_rename(); clean_after_shfo_tests(); + + init_shfo_tests(); + test_copy(); + clean_after_shfo_tests(); + + init_shfo_tests(); + test_move(); + clean_after_shfo_tests(); }