This is yet another trial to get the SHFileOperation function changed. This implementation is done in Unicode and the ASCI function calls it. The function is basically mostly rewritten by Dietrich Teickner from Odin to support the program Beyond32 from Scootersoftware. The function is tested rather extensively to behave in as many cases as possible like Win98 and to some less degree NT/2K. After the last patch to the shell32 test it also passes all Wine tests fully. There is still a problem that Shell Change Notifications for delete and move operations do not work properly, but that is a shortcoming of the current SHChangeNotify implementation trying to create fully qualified ItemIDLists which will fail on non-existing files/directories. Another patch to SHSimpleIDListFromPath and making SHChangeNotify to use this will fix that problem as well, but it will take some time for this. Changelog: * include/shellapi.h Add a newly documented flag to the FOF_ flags * dlls/shell32/shlfileop.c Reimplement SHFileOperation in Unicode License: LGPL Rolf Kalbermatter Index: include/shellapi.h =================================================================== RCS file: /home/wine/wine/include/shellapi.h,v retrieving revision 1.19 diff -u -r1.19 shellapi.h --- include/shellapi.h 10 Dec 2002 19:10:11 -0000 1.19 +++ include/shellapi.h 5 Apr 2003 22:54:32 -0000 @@ -176,6 +176,7 @@ #define FOF_SIMPLEPROGRESS 0x0100 #define FOF_NOCONFIRMMKDIR 0x0200 #define FOF_NOERRORUI 0x0400 +#define FOF_NOCOPYSECURITYATTRIBS 0x0800 // don't copy NT file Security Attributes typedef WORD FILEOP_FLAGS; Index: dlls/shell32/shlfileop.c =================================================================== RCS file: /home/wine/wine/dlls/shell32/shlfileop.c,v retrieving revision 1.26 diff -u -r1.26 shlfileop.c --- dlls/shell32/shlfileop.c 21 Mar 2003 21:23:33 -0000 1.26 +++ dlls/shell32/shlfileop.c 5 Apr 2003 22:41:41 -0000 @@ -3,6 +3,8 @@ * * Copyright 2000 Juergen Schmied * Copyright 2002 Andriy Palamarchuk + * Copyright 2002 Dietrich Teickner (from Odin) + * Copyright 2002 Rolf Kalbermatter * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,7 +20,6 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - #include "config.h" #include "wine/port.h" @@ -29,9 +30,11 @@ #include "shellapi.h" #include "shlobj.h" #include "shresdef.h" +#define NO_SHLWAPI_STREAM +#include "shlwapi.h" #include "shell32_main.h" #include "undocshell.h" -#include "shlwapi.h" +#include "wine/unicode.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(shell); @@ -41,6 +44,8 @@ #define IsDotDir(x) ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0)))) +#define FO_MASK 0xF + CHAR aWildcardFile[] = {'*','.','*',0}; WCHAR wWildcardFile[] = {'*','.','*',0}; WCHAR wWildcardChars[] = {'*','?',0}; @@ -537,288 +542,711 @@ return ret; } -/************************************************************************** - * SHELL_FileNamesMatch() +/************************************************************************* + * + * SHFileStrICmp HelperFunction for SHFileOperationW * - * 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) +BOOL SHFileStrICmpW(LPWSTR p1, LPWSTR p2, LPWSTR p1End, LPWSTR p2End) { - while ((pszFiles1[strlen(pszFiles1) + 1] != '\0') && - (pszFiles2[strlen(pszFiles2) + 1] != '\0')) - { - pszFiles1 += strlen(pszFiles1) + 1; - pszFiles2 += strlen(pszFiles2) + 1; - } + WCHAR C1 = '\0'; + WCHAR C2 = '\0'; + int i_Temp = -1; + int i_len1 = lstrlenW(p1); + int i_len2 = lstrlenW(p2); - return - ((pszFiles1[strlen(pszFiles1) + 1] == '\0') && - (pszFiles2[strlen(pszFiles2) + 1] == '\0')) || - ((pszFiles1[strlen(pszFiles1) + 1] != '\0') && - (pszFiles2[strlen(pszFiles2) + 1] != '\0')); + if (p1End && (&p1[i_len1] >= p1End) && ('\\' == p1End[0])) + { + C1 = p1End[0]; + p1End[0] = '\0'; + i_len1 = lstrlenW(p1); + } + if (p2End) + { + if ((&p2[i_len2] >= p2End) && ('\\' == p2End[0])) + { + C2 = p2End[0]; + if (C2) + p2End[0] = '\0'; + } + } + else + { + if ((i_len1 <= i_len2) && ('\\' == p2[i_len1])) + { + C2 = p2[i_len1]; + if (C2) + p2[i_len1] = '\0'; + } + } + i_len2 = lstrlenW(p2); + if (i_len1 == i_len2) + i_Temp = lstrcmpiW(p1,p2); + if (C1) + p1[i_len1] = C1; + if (C2) + p2[i_len2] = C2; + return !(i_Temp); } /************************************************************************* - * SHFileOperationA [SHELL32.@] * - * NOTES - * exported by name + * SHFileStrCpyCat HelperFunction for SHFileOperationW + * */ -DWORD WINAPI SHFileOperationA (LPSHFILEOPSTRUCTA lpFileOp) +LPWSTR SHFileStrCpyCatW(LPWSTR pTo, LPCWSTR pFrom, LPCWSTR pCatStr) { - LPSTR pFrom = (LPSTR)lpFileOp->pFrom; - LPSTR pTo = (LPSTR)lpFileOp->pTo; - LPSTR pTempTo; - TRACE("flags (0x%04x) : %s%s%s%s%s%s%s%s%s%s%s%s \n", lpFileOp->fFlags, - lpFileOp->fFlags & FOF_MULTIDESTFILES ? "FOF_MULTIDESTFILES " : "", - lpFileOp->fFlags & FOF_CONFIRMMOUSE ? "FOF_CONFIRMMOUSE " : "", - lpFileOp->fFlags & FOF_SILENT ? "FOF_SILENT " : "", - lpFileOp->fFlags & FOF_RENAMEONCOLLISION ? "FOF_RENAMEONCOLLISION " : "", - lpFileOp->fFlags & FOF_NOCONFIRMATION ? "FOF_NOCONFIRMATION " : "", - lpFileOp->fFlags & FOF_WANTMAPPINGHANDLE ? "FOF_WANTMAPPINGHANDLE " : "", - lpFileOp->fFlags & FOF_ALLOWUNDO ? "FOF_ALLOWUNDO " : "", - lpFileOp->fFlags & FOF_FILESONLY ? "FOF_FILESONLY " : "", - lpFileOp->fFlags & FOF_SIMPLEPROGRESS ? "FOF_SIMPLEPROGRESS " : "", - lpFileOp->fFlags & FOF_NOCONFIRMMKDIR ? "FOF_NOCONFIRMMKDIR " : "", - lpFileOp->fFlags & FOF_NOERRORUI ? "FOF_NOERRORUI " : "", - lpFileOp->fFlags & 0xf800 ? "MORE-UNKNOWN-Flags" : ""); - switch(lpFileOp->wFunc) { - 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: - * (1) pTo points to the name of an existing directory; - * (2) the flag FOF_MULTIDESTFILES is present; - * (3) whether pFrom point to multiple filenames. - * - * Some experiments: - * - * destisdir 1 1 1 1 0 0 0 0 - * FOF_MULTIDESTFILES 1 1 0 0 1 1 0 0 - * multiple from filenames 1 0 1 0 1 0 1 0 - * --------------- - * copy files to dir 1 0 1 1 0 0 1 0 - * create dir 0 0 0 0 0 0 1 0 - */ - int multifrom = pFrom[strlen(pFrom) + 1] != '\0'; - int destisdir = PathIsDirectoryA( pTo ); - 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)) - todir = 1; - } else { - if ( !(lpFileOp->fFlags & FOF_MULTIDESTFILES) && multifrom) - todir = 1; - } - - 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) { - TRACE(" creating directory %s\n",pTo); - SHCreateDirectoryExA(NULL, pTo, NULL); - } - lenPTo = strlen(pTo); - while(1) { - HANDLE hFind; - WIN32_FIND_DATAA wfd; - - if(!pFrom[0]) break; - 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; - - SHCreateDirectoryExA(NULL, pTempTo, NULL); - 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; - } - } else { - while (1) { - if(!pFrom[0]) break; - if(!pTo[0]) break; - TRACE(" From='%s' To='%s'\n", pFrom, pTo); - - pTempTo = HeapAlloc(GetProcessHeap(), 0, strlen(pTo)+1); - if (pTempTo) - { - strcpy( pTempTo, pTo ); - PathRemoveFileSpecA(pTempTo); - TRACE(" Creating Directory '%s'\n", pTempTo); - SHCreateDirectoryExA(NULL, pTempTo, NULL); - HeapFree(GetProcessHeap(), 0, pTempTo); - } - if (lpFileOp->wFunc == FO_COPY) - CopyFileA(pFrom, pTo, FALSE); - else - MoveFileA(pFrom, pTo); - - pFrom += strlen(pFrom) + 1; - pTo += strlen(pTo) + 1; - } - } - TRACE("Setting AnyOpsAborted=FALSE\n"); - lpFileOp->fAnyOperationsAborted=FALSE; - return 0; - } - - case FO_DELETE: - { - HANDLE hFind; - WIN32_FIND_DATAA wfd; - char szTemp[MAX_PATH]; - char *file_name; - - TRACE("File Delete:\n"); - while(1) { - if(!pFrom[0]) break; - TRACE(" Pattern='%s'\n", pFrom); - if(INVALID_HANDLE_VALUE != (hFind = FindFirstFileA(pFrom, &wfd))) - { - do - { - if(strcasecmp(wfd.cFileName, ".") && strcasecmp(wfd.cFileName, "..")) - { - strcpy(szTemp, pFrom); - file_name = PathFindFileNameA(szTemp); - file_name[0] = '\0'; - PathAddBackslashA(szTemp); - strcat(szTemp, wfd.cFileName); - - TRACE(" File='%s'\n", szTemp); - if(FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes) - { - if(!(lpFileOp->fFlags & FOF_FILESONLY)) - SHELL_DeleteDirectoryA(szTemp, FALSE); - } - else - DeleteFileA(szTemp); - } - } while(FindNextFileA(hFind, &wfd)); - - FindClose(hFind); - } - pFrom += strlen(pFrom) + 1; - } - TRACE("Setting AnyOpsAborted=FALSE\n"); - lpFileOp->fAnyOperationsAborted=FALSE; - return 0; - } - - case FO_RENAME: - TRACE("File Rename:\n"); - if (pFrom[strlen(pFrom) + 1] != '\0') - { - WARN("Attempt to rename more than one file\n"); - return 1; - } - lpFileOp->fAnyOperationsAborted = FALSE; - TRACE("From %s, To %s\n", pFrom, pTo); - return !MoveFileA(pFrom, pTo); - - default: - FIXME("Unhandled shell file operation %d\n", lpFileOp->wFunc); + LPWSTR pToFile = NULL; + int i_len; + if (pTo) + { + if (pFrom) + lstrcpyW(pTo, pFrom); + if (pCatStr) + { + i_len = lstrlenW(pTo); + if ((i_len) && (pTo[--i_len] != '\\')) + i_len++; + pTo[i_len] = '\\'; + if (pCatStr[0] == '\\') + pCatStr++; \ + lstrcpyW(&pTo[i_len+1], pCatStr); + } + pToFile = StrRChrW(pTo,NULL,'\\'); +/* !! termination of the new string-group */ + pTo[(lstrlenW(pTo)) + 1] = '\0'; } + return pToFile; +} - return 1; +/************************************************************************** + * SHELL_FileNamesMatch() + * + * Accepts two \0 delimited lists of the file names. Checks whether number of + * files in both lists is the same, and checks also if source-name exists. + */ +BOOL SHELL_FileNamesMatch(LPCWSTR pszFiles1, LPCWSTR pszFiles2, BOOL bOnlySrc) +{ + while ((pszFiles1[0] != '\0') && + (bOnlySrc || (pszFiles2[0] != '\0'))) + { + if (NULL == StrPBrkW(pszFiles1, wWildcardChars)) + { + if (-1 == GetFileAttributesW(pszFiles1)) + return FALSE; + } + pszFiles1 += lstrlenW(pszFiles1) + 1; + if (!bOnlySrc) + pszFiles2 += lstrlenW(pszFiles2) + 1; + } + return ((pszFiles1[0] == '\0') && (bOnlySrc || (pszFiles2[0] == '\0'))); } /************************************************************************* - * SHFileOperationW [SHELL32.@] + * + * SHName(s)Translate HelperFunction for SHFileOperationA + * + * Translates a list of 0 terminated ASCI strings into Unicode. If *wString + * is NULL, only the necessary size of the string is determined and returned, + * otherwise the ASCII strings are copied into it and the buffer is increased + * to point to the location after the final 0 termination char. + */ +DWORD SHNameTranslate(LPWSTR* wString, LPCWSTR* pWToFrom, BOOL more) +{ + DWORD size = 0, aSize = 0; + LPCSTR aString = (LPCSTR)*pWToFrom; + + if (aString) + { + do + { + size = lstrlenA(aString) + 1; + aSize += size; + aString += size; + } while ((size != 1) && more); + /* The two sizes might be different in the case of multibyte chars */ + size = MultiByteToWideChar(CP_ACP, 0, aString, aSize, *wString, 0); + if (*wString) /* only in the second loop */ + { + MultiByteToWideChar(CP_ACP, 0, (LPCSTR)*pWToFrom, aSize, *wString, size); + *pWToFrom = *wString; + *wString += size; + } + } + return size; +} +/************************************************************************* + * SHFileOperationA [SHELL32.@] + * + * Function to copy, move, delete and create one or more files with optional + * user prompts. + * + * PARAMS + * lpFileOp [I/O] pointer to a structure containing all the necessary information * * NOTES - * exported by name + * exported by name */ -DWORD WINAPI SHFileOperationW (LPSHFILEOPSTRUCTW lpFileOp) +DWORD WINAPI SHFileOperationA(LPSHFILEOPSTRUCTA lpFileOp) { - FIXME("(%p):stub.\n", lpFileOp); - return 1; + SHFILEOPSTRUCTW nFileOp = *((LPSHFILEOPSTRUCTW)lpFileOp); + DWORD retCode = 0, size = 0; + LPWSTR wString = NULL; /* we change this in SHNameTranslate */ + + TRACE("SHFileOperationA"); + if (FO_DELETE == (nFileOp.wFunc & FO_MASK)) + nFileOp.pTo = NULL; /* we need a NULL or a valid pointer for translation */ + if (!(nFileOp.fFlags & FOF_SIMPLEPROGRESS)) + nFileOp.lpszProgressTitle = NULL; /* we need a NULL or a valid pointer for translation */ + do + { + if (size) + { + wString = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR)); + if (!wString) + { + retCode = ERROR_OUTOFMEMORY; + nFileOp.fAnyOperationsAborted = TRUE; + SetLastError(retCode); + return retCode; + } + } + size = SHNameTranslate(&wString, &nFileOp.lpszProgressTitle, FALSE); /* no loop */ + size += SHNameTranslate(&wString, &nFileOp.pFrom, TRUE); /* internal loop */ + size += SHNameTranslate(&wString, &nFileOp.pTo, TRUE); /* internal loop */ + /* first loop only for calculate size, no translation, we hav a NULL-pointer */ + } while (!wString); /* second loop calculate size, also translation. We have a valid pointer */ + + retCode = SHFileOperationW(&nFileOp); + + if (wString) + HeapFree(GetProcessHeap(), 0, wString); + + if (retCode) + { + nFileOp.fAnyOperationsAborted = TRUE; + } + lpFileOp->hNameMappings = nFileOp.hNameMappings; + lpFileOp->fAnyOperationsAborted = nFileOp.fAnyOperationsAborted; + return retCode; +} + +/************************************************************************* + * SHFileOperationW [SHELL32.@] + * + * See SHFileOperationA + */ + +/* in w98 brings shell32_test.exe now this 2 failures + shlfileop.c:177: Test failed: Can't rename many files + * W98 return 0 without action + shlfileop.c:185: Test failed: Can't rename many files + * W98 return 0 without action + shlfileop: 120 tests executed, 0 marked as todo, 2 failures. +*/ +DWORD WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp) +{ + SHFILEOPSTRUCTW nFileOp = *(lpFileOp); + + LPCWSTR pNextFrom = nFileOp.pFrom; + LPCWSTR pNextTo = nFileOp.pTo; + LPCWSTR pFrom = pNextFrom; + LPCWSTR pTo = NULL; +#if 0 + LPCSTR Debug_pFrom; + LPCSTR Debug_pTo; +#endif + HANDLE hFind = INVALID_HANDLE_VALUE; + WIN32_FIND_DATAW wfd; + LPWSTR pTempFrom = NULL; + LPWSTR pTempTo = NULL; + LPWSTR pFromFile; + LPWSTR pToFile; + LPWSTR lpFileName; + long retCode = 0; + DWORD ToAttr; + DWORD ToPathAttr; + DWORD FromPathAttr; + FILEOP_FLAGS OFl = ((FILEOP_FLAGS)lpFileOp->fFlags & 0xfff); + + BOOL b_Multi = (nFileOp.fFlags & FOF_MULTIDESTFILES); + + BOOL b_MultiTo = (FO_DELETE != (lpFileOp->wFunc & FO_MASK)); + BOOL b_MultiPaired = (!b_MultiTo); + BOOL b_MultiFrom = FALSE; + BOOL not_overwrite; + BOOL ask_overwrite; + BOOL b_SameRoot; + BOOL b_SameTailName; + BOOL b_ToInvalidTail; + BOOL b_ToValid; /* for W98-Bug for FO_MOVE with source and taget in same rootdrive */ + BOOL b_Mask; /* wird als Schalter benutzt, vieleicht finde ich die richtige bitposition */ + BOOL b_ToTailSlash; + LPCSTR cFO_Name [] = {"FO_????","FO_MOVE","FO_COPY","FO_DELETE","FO_RENAME"}; + + long FuncSwitch = (nFileOp.wFunc & FO_MASK); + long level= nFileOp.wFunc>>4; + + /* default no error */ + nFileOp.fAnyOperationsAborted = FALSE; + + if ((FuncSwitch < FO_MOVE) || (FuncSwitch > FO_RENAME)) + goto shfileop_normal; /* no valid FunctionCode */ + + cFO_Name[0] = cFO_Name [FuncSwitch]; + if (level == 0) + TRACE("%s: flags (0x%04x) : %s%s%s%s%s%s%s%s%s%s%s%s%s \n",cFO_Name[0], nFileOp.fFlags, + nFileOp.fFlags & FOF_MULTIDESTFILES ? "FOF_MULTIDESTFILES " : "", + nFileOp.fFlags & FOF_CONFIRMMOUSE ? "FOF_CONFIRMMOUSE " : "", + nFileOp.fFlags & FOF_SILENT ? "FOF_SILENT " : "", + nFileOp.fFlags & FOF_RENAMEONCOLLISION ? "FOF_RENAMEONCOLLISION " : "", + nFileOp.fFlags & FOF_NOCONFIRMATION ? "FOF_NOCONFIRMATION " : "", + nFileOp.fFlags & FOF_WANTMAPPINGHANDLE ? "FOF_WANTMAPPINGHANDLE " : "", + nFileOp.fFlags & FOF_ALLOWUNDO ? "FOF_ALLOWUNDO " : "", + nFileOp.fFlags & FOF_FILESONLY ? "FOF_FILESONLY " : "", + nFileOp.fFlags & FOF_SIMPLEPROGRESS ? "FOF_SIMPLEPROGRESS " : "", + nFileOp.fFlags & FOF_NOCONFIRMMKDIR ? "FOF_NOCONFIRMMKDIR " : "", + nFileOp.fFlags & FOF_NOERRORUI ? "FOF_NOERRORUI " : "", + nFileOp.fFlags & FOF_NOCOPYSECURITYATTRIBS ? "FOF_NOCOPYSECURITYATTRIBS" : "", + nFileOp.fFlags & 0xf000 ? "MORE-UNKNOWN-Flags" : ""); + ; + { + /* 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: + * (1) pTo points to the name of an existing directory; + * (2) the flag FOF_MULTIDESTFILES is present; + * (3) whether pFrom point to multiple filenames. + * + * Some experiments: + * + * destisdir 1 1 1 1 0 0 0 0 + * FOF_MULTIDESTFILES 1 1 0 0 1 1 0 0 + * multiple from filenames 1 0 1 0 1 0 1 0 + * --------------- + * copy files to dir 1 0 1 1 0 0 1 0 + * create dir 0 0 0 0 0 0 1 0 + */ +/* + * FOF_MULTIDESTFILES, FOF_NOCONFIRMATION, FOF_FILESONLY are implemented + * FOF_CONFIRMMOUSE, FOF_SILENT, FOF_NOCONFIRMMKDIR, + * FOF_SIMPLEPROGRESS, FOF_NOCOPYSECURITYATTRIBS are not implemented and ignored + * FOF_RENAMEONCOLLISION are implemented partially and breaks if file exist + * FOF_ALLOWUNDO, FOF_WANTMAPPINGHANDLE are not implemented and breaks + * if any other flag set, an error occurs + */ + TRACE(" %s level=%ld nFileOp.fFlags=0x%x\n", cFO_Name[0], level, lpFileOp->fFlags); + +/* OFl &= (-1 - (FOF_MULTIDESTFILES | FOF_FILESONLY)); */ +/* OFl ^= (FOF_SILENT | FOF_NOCONFIRMATION | FOF_SIMPLEPROGRESS | FOF_NOCONFIRMMKDIR); */ + OFl &= (~(FOF_MULTIDESTFILES | FOF_NOCONFIRMATION | FOF_FILESONLY)); /* implemented */ + OFl ^= (FOF_SILENT | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_NOCOPYSECURITYATTRIBS); /* ignored, if one */ + OFl &= (~FOF_SIMPLEPROGRESS); /* ignored, only with FOF_SILENT */ + if (OFl) + { + if (OFl & (~(FOF_CONFIRMMOUSE | FOF_SILENT | FOF_RENAMEONCOLLISION | + FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_NOCOPYSECURITYATTRIBS))) + { + TRACE("%s level=%ld lpFileOp->fFlags=0x%x not implemented, Aborted=TRUE, stub\n", cFO_Name[0], level, OFl); + retCode = 0x403; /* 1027, we need an extension to shlfileop */ + goto shfileop_error; + } + else + { + TRACE("%s level=%ld lpFileOp->fFlags=0x%x not fully implemented, stub\n", cFO_Name[0], level, OFl); + } /* endif */ + } /* endif */ + + if ((pNextFrom) && (!(b_MultiTo) || (pNextTo))) + { + nFileOp.pFrom = pTempFrom = HeapAlloc(GetProcessHeap(), 0, ((1 + 2 * (b_MultiTo)) * MAX_PATH + 6) * sizeof(WCHAR)); + if (!pTempFrom) + { + retCode = ERROR_OUTOFMEMORY; + SetLastError(retCode); + goto shfileop_error; + } + if (b_MultiTo) + pTempTo = &pTempFrom[MAX_PATH + 4]; + nFileOp.pTo = pTempTo; + ask_overwrite = (!(nFileOp.fFlags & FOF_NOCONFIRMATION) && !(nFileOp.fFlags & FOF_RENAMEONCOLLISION)); + not_overwrite = (!(nFileOp.fFlags & FOF_NOCONFIRMATION) || (nFileOp.fFlags & FOF_RENAMEONCOLLISION)); + } + else + { + retCode = 0x402; /* 1026 */ + goto shfileop_error; + } + /* need break at error before change sourcepointer */ + while(!nFileOp.fAnyOperationsAborted && (pNextFrom[0])) + { + nFileOp.wFunc = ((level + 1) << 4) + FuncSwitch; + nFileOp.fFlags = lpFileOp->fFlags; + + if (b_MultiTo) + { + pTo = pNextTo; + pNextTo = &pNextTo[lstrlenW(pTo)+1]; + b_MultiTo = (b_Multi && pNextTo[0]); + } + + pFrom = pNextFrom; + pNextFrom = &pNextFrom[lstrlenW(pNextFrom)+1]; + if (!b_MultiFrom && !b_MultiTo) + b_MultiFrom = (pNextFrom[0]); + + pFromFile = SHFileStrCpyCatW(pTempFrom, pFrom, NULL); + +#if 0 + /* for seeing in debugger outside from TRACE */ + Debug_pFrom = debugstr_w(pFrom); + Debug_pTo = debugstr_w(pTo); + TRACE("%s level=%ld with %s %s%s\n", + cFO_Name[0], level, Debug_pFrom, pTo ? "-> ":"", Debug_pTo); +#endif + if (pTo) + { + pToFile = SHFileStrCpyCatW(pTempTo, pTo, NULL); + } + if (!b_MultiPaired) + { + b_MultiPaired = + SHELL_FileNamesMatch(lpFileOp->pFrom, lpFileOp->pTo, (!b_Multi || b_MultiFrom)); + } /* endif */ + if (!(b_MultiPaired) || !(pFromFile) || !(pFromFile[1]) || ((pTo) && !(pToFile))) + { + retCode = 0x402; /* 1026 */ + goto shfileop_error; + } + if (pTo) + { + b_ToTailSlash = (!pToFile[1]); + if (b_ToTailSlash) + { + pToFile[0] = '\0'; + if (StrChrW(pTempTo,'\\')) + { + pToFile = SHFileStrCpyCatW(pTempTo, NULL, NULL); + } + } + b_ToInvalidTail = (NULL != StrPBrkW(&pToFile[1], wWildcardChars)); + } + + /* for all */ + b_Mask = (NULL != StrPBrkW(&pFromFile[1], wWildcardChars)); + if (FO_RENAME == FuncSwitch) + { + /* temporary only for FO_RENAME */ +/* ??? b_Mask = (NULL != strrbrk(pFrom,"*?")); */ + if (b_MultiTo || b_MultiFrom || (b_Mask && !b_ToInvalidTail)) + { + /* no work, only RC=0 */ +/* ??? nFileOp.fAnyOperationsAborted = TRUE; */ +//#define W98_FO_RENEME +#ifdef W98_FO_RENEME + goto shfileop_normal; +#endif + retCode = 0x1; /* 1 value unknown, W98 returns no error */ + goto shfileop_error; + } + } + + hFind = FindFirstFileW(pFrom, &wfd); + if (INVALID_HANDLE_VALUE == hFind) + { + if ((FO_DELETE == FuncSwitch) && (b_Mask)) + { + pFromFile[0] = '\0'; + FromPathAttr = GetFileAttributesW(pTempFrom); + pFromFile[0] = '\\'; + if (IsAttribDir(FromPathAttr)) + { + /* FO_DELETE with mask and without found is valid */ + goto shfileop_normal; + } /* endif */ + } /* endif */ + /* root (without mask) is also not allowed as source, tested in W98 */ + retCode = 0x402; /* 1026 */ + goto shfileop_error; + } /* endif */ + +/* for all */ +#define HIGH_ADR (LPWSTR)0xffffffff + +/* ??? b_Mask = (!SHFileStrICmpA(&pFromFile[1], &wfd.cFileName[0], HIGH_ADR, HIGH_ADR)); */ + if (!pTo) /* FO_DELETE */ + { + do + { + lpFileName = wfd.cAlternateFileName; + if (!lpFileName[0]) + lpFileName = wfd.cFileName; + if (IsDotDir(lpFileName) || + ((b_Mask) && IsAttribDir(wfd.dwFileAttributes) && (nFileOp.fFlags & FOF_FILESONLY))) + continue; + SHFileStrCpyCatW(&pFromFile[1], lpFileName, NULL); + /* TODO: Check the SHELL_DeleteFileOrDirectoryW() function in shell32.dll */ + if (IsAttribFile(wfd.dwFileAttributes)) + { + nFileOp.fAnyOperationsAborted = (!SHNotifyDeleteFileW(pTempFrom)); + retCode = 0x78; /* value unknown */ + } + else + { + nFileOp.fAnyOperationsAborted = (!SHELL_DeleteDirectoryW(pTempFrom, (!(nFileOp.fFlags & FOF_NOCONFIRMATION)))); + retCode = 0x79; /* value unknown */ + } + } while (!nFileOp.fAnyOperationsAborted && FindNextFileW(hFind, &wfd)); + FindClose(hFind); + hFind = INVALID_HANDLE_VALUE; + if (nFileOp.fAnyOperationsAborted) + { + goto shfileop_error; + } + continue; + } /* FO_DELETE ends, pTo must be always valid from here */ + + b_SameRoot = (toupperW(pTempFrom[0]) == toupperW(pTempTo[0])); + b_SameTailName = SHFileStrICmpW(pToFile, pFromFile, NULL, NULL); + + ToPathAttr = ToAttr = GetFileAttributesW(pTempTo); + if (!b_Mask && (ToAttr -1) && (pToFile)) + { + pToFile[0] = '\0'; + ToPathAttr = GetFileAttributesW(pTempTo); + pToFile[0] = '\\'; + } + + if (FO_RENAME == FuncSwitch) + { + if (!b_SameRoot || b_Mask /* FO_RENAME works not with Mask */ + || !SHFileStrICmpW(pTempFrom, pTempTo, pFromFile, NULL) + || (SHFileStrICmpW(pTempFrom, pTempTo, pFromFile, HIGH_ADR) && !b_ToTailSlash)) + { + retCode = 0x73; + goto shfileop_error; + } + if (b_ToInvalidTail) + { + retCode=0x2; + goto shfileop_error; + } + if (-1 == ToPathAttr) + { + retCode = 0x75; + goto shfileop_error; + } + if (IsAttribDir(wfd.dwFileAttributes) && IsAttribDir(ToAttr)) + { + retCode = (b_ToTailSlash) ? 0xb7 : 0x7b; + goto shfileop_error; + } /* endif */ + /* we use SHNotifyMoveFile() instead MoveFileW */ + if (!SHNotifyMoveFileW(pTempFrom, pTempTo)) + { + /* we need still the value for the returncode, we use the mostly assumed */ + retCode = 0xb7; + goto shfileop_error; + } + goto shfileop_normal; + } + + /* W98 Bug with FO_MOVE different to FO_COPY, better the same as FO_COPY */ + b_ToValid = ((b_SameTailName && b_SameRoot && (FO_COPY == FuncSwitch)) || + (b_SameTailName && !b_SameRoot) || (b_ToInvalidTail)); + + /* handle mask in source */ + if (b_Mask) + { + if (!IsAttribDir(ToAttr)) + { + retCode = (b_ToInvalidTail &&/* b_SameTailName &&*/ (FO_MOVE == FuncSwitch)) \ + ? 0x2 : 0x75; + goto shfileop_error; + } + pToFile = SHFileStrCpyCatW(pTempTo, NULL, wBackslash); + nFileOp.fFlags = (nFileOp.fFlags | FOF_MULTIDESTFILES); + do + { + lpFileName = wfd.cAlternateFileName; + if (!lpFileName[0]) + lpFileName = wfd.cFileName; + if (IsDotDir(lpFileName) || + (IsAttribDir(wfd.dwFileAttributes) && (nFileOp.fFlags & FOF_FILESONLY))) + continue; /* next name in pTempFrom(dir) */ + SHFileStrCpyCatW(&pToFile[1], lpFileName, NULL); + SHFileStrCpyCatW(&pFromFile[1], lpFileName, NULL); + retCode = SHFileOperationW (&nFileOp); + } while(!nFileOp.fAnyOperationsAborted && FindNextFileW(hFind, &wfd)); + } + FindClose(hFind); + hFind = INVALID_HANDLE_VALUE; + /* FO_COPY/FO_MOVE with mask, FO_DELETE and FO_RENAME are solved */ + if (b_Mask) + continue; + + /* only FO_COPY/FO_MOVE without mask, all others are (must be) solved */ + if (IsAttribDir(wfd.dwFileAttributes) && (ToAttr -1)) + { + if (pToFile) + { + pToFile[0] = '\0'; + ToPathAttr = GetFileAttributesW(pTempTo); + if ((ToPathAttr == -1) && b_ToValid) + { + /* create dir must be here, sample target D:\y\ *.* create with RC=10003 */ + if (SHCreateDirectoryExW(NULL, pTempTo, NULL)) + { + retCode = 0x73;/* value unknown */ + goto shfileop_error; + } + ToPathAttr = GetFileAttributesW(pTempTo); + } + pToFile[0] = '\\'; + if (b_ToInvalidTail) + { + retCode = 0x10003; + goto shfileop_error; + } + } + } + + /* trailing BackSlash is ever removed and pToFile points to BackSlash before */ + if (!b_MultiTo && (b_MultiFrom || (!(b_Multi) && IsAttribDir(ToAttr)))) + { + if ((FO_MOVE == FuncSwitch) && IsAttribDir(ToAttr) && IsAttribDir(wfd.dwFileAttributes)) + { + if (b_Multi) + { + retCode = 0x73; /* !b_Multi = 0x8 ?? */ + goto shfileop_error; + } + } + pToFile = SHFileStrCpyCatW(pTempTo, NULL, wfd.cFileName); + ToAttr = GetFileAttributesW(pTempTo); + } + + if (IsAttribDir(ToAttr)) + { + if (IsAttribFile(wfd.dwFileAttributes)) + { + retCode = (FO_COPY == FuncSwitch) ? 0x75 : 0xb7; + goto shfileop_error; + } + } + else + { + pToFile[0] = '\0'; + ToPathAttr = GetFileAttributesW(pTempTo); + pToFile[0] = '\\'; + if (IsAttribFile(ToPathAttr)) + { + /* error, is this tested ? */ + retCode = 0x777402; + goto shfileop_error; + } /* endif */ + } + + /* singlesource + no mask */ + if (-1 == (ToAttr & ToPathAttr)) + { + /* Target-dir does not exist, and cannot be created */ + retCode=0x75; + goto shfileop_error; + } + + switch(FuncSwitch) + { + case FO_MOVE: + pToFile = NULL; + if ((ToAttr == -1) && SHFileStrICmpW(pTempFrom, pTempTo, pFromFile, NULL)) + { + nFileOp.wFunc = ((level+1)<<4) + FO_RENAME; + } + else + { + if (b_SameRoot && IsAttribDir(ToAttr) && IsAttribDir(wfd.dwFileAttributes)) + { + /* we need pToFile for FO_DELETE after FO_MOVE contence */ + pToFile = SHFileStrCpyCatW(pTempFrom, NULL, wWildcardFile); + } + else + { + nFileOp.wFunc = ((level+1)<<4) + FO_COPY; + } + } + retCode = SHFileOperationW(&nFileOp); + if (pToFile) + ((DWORD*)pToFile)[0] = '\0'; + if (!nFileOp.fAnyOperationsAborted && (FO_RENAME != (nFileOp.wFunc & 0xf))) + { + nFileOp.wFunc = ((level+1)<<4) + FO_DELETE; + retCode = SHFileOperationW(&nFileOp); + } + continue; + case FO_COPY: + if (SHFileStrICmpW(pTempFrom, pTempTo, NULL, NULL)) + { /* target is the same as source ? */ + /* we still need the value for the returncode, we assume 0x71 */ + retCode = 0x71; + goto shfileop_error; + } /* endif */ + if (IsAttribDir((ToAttr & wfd.dwFileAttributes))) + { + if (IsAttribDir(ToAttr) || !SHCreateDirectoryExW(NULL,pTempTo, NULL)) + { +/* ??? nFileOp.fFlags = (nFileOp.fFlags | FOF_MULTIDESTFILES); */ + SHFileStrCpyCatW(pTempFrom, NULL, wWildcardFile); + retCode = SHFileOperationW(&nFileOp); + } + else + { + retCode = 0x750;/* value unknown */ + goto shfileop_error; + } + } + else + { + if (!(ask_overwrite && SHELL_ConfirmDialogW(ASK_OVERWRITE_FILE, pTempTo)) + && (not_overwrite)) + { + /* we still need the value for the returncode, we use the mostly assumed */ + retCode = 0x73; + goto shfileop_error; + } + /* use SHNotifyCopyFile() instead CopyFileW, we must solve overwriting yet */ + if (!(SHNotifyCopyFileW(pTempFrom, pTempTo, nFileOp.fFlags & FOF_RENAMEONCOLLISION))) + { + retCode = 0x77; /* value unknown */ + goto shfileop_error; + } + } + } /* end-switch */ + } /* end-while */ + } +shfileop_normal: + if (!(nFileOp.fAnyOperationsAborted)) + retCode = 0; +shfileop_error: + if (hFind != INVALID_HANDLE_VALUE) + FindClose(hFind); + hFind = INVALID_HANDLE_VALUE; + if (pTempFrom) + HeapFree(GetProcessHeap(), 0, pTempFrom); + if (retCode) + { + nFileOp.fAnyOperationsAborted = TRUE; + } + TRACE("%s level=%ld AnyOpsAborted=%s ret=0x%lx, with %s %s%s\n", + cFO_Name[0], level, nFileOp.fAnyOperationsAborted ? "TRUE":"FALSE", + retCode, debugstr_w(pFrom), pTo ? "-> ":"", debugstr_w(pTo)); + + lpFileOp->fAnyOperationsAborted = nFileOp.fAnyOperationsAborted; + return retCode; } /************************************************************************* - * SHFileOperation [SHELL32.@] + * SHFileOperation [SHELL32.@] * */ DWORD WINAPI SHFileOperationAW(LPVOID lpFileOp)