| /* | |
| * SHFileOperation | |
| * | |
| * Copyright 2000 Juergen Schmied | |
| * Copyright 2002 Andriy Palamarchuk | |
| * Copyright 2004 Dietrich Teickner (from Odin) | |
| * Copyright 2004 Rolf Kalbermatter | |
| * | |
| * 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 | |
| */ | |
| #include "config.h" | |
| #include "wine/port.h" | |
| #include <stdarg.h> | |
| #include <string.h> | |
| #include <ctype.h> | |
| #include <assert.h> | |
| #include "windef.h" | |
| #include "winbase.h" | |
| #include "winreg.h" | |
| #include "shellapi.h" | |
| #include "wingdi.h" | |
| #include "winuser.h" | |
| #include "shlobj.h" | |
| #include "shresdef.h" | |
| #define NO_SHLWAPI_STREAM | |
| #include "shlwapi.h" | |
| #include "shell32_main.h" | |
| #include "undocshell.h" | |
| #include "wine/debug.h" | |
| #include "xdg.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(shell); | |
| #define IsAttrib(x, y) ((INVALID_FILE_ATTRIBUTES != (x)) && ((x) & (y))) | |
| #define IsAttribFile(x) (!((x) & FILE_ATTRIBUTE_DIRECTORY)) | |
| #define IsAttribDir(x) IsAttrib(x, FILE_ATTRIBUTE_DIRECTORY) | |
| #define IsDotDir(x) ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0)))) | |
| #define FO_MASK 0xF | |
| static const WCHAR wWildcardFile[] = {'*',0}; | |
| static const WCHAR wWildcardChars[] = {'*','?',0}; | |
| static DWORD SHNotifyCreateDirectoryA(LPCSTR path, LPSECURITY_ATTRIBUTES sec); | |
| static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec); | |
| static DWORD SHNotifyRemoveDirectoryA(LPCSTR path); | |
| static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path); | |
| static DWORD SHNotifyDeleteFileA(LPCSTR path); | |
| static DWORD SHNotifyDeleteFileW(LPCWSTR path); | |
| static DWORD SHNotifyMoveFileW(LPCWSTR src, LPCWSTR dest); | |
| static DWORD SHNotifyCopyFileW(LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists); | |
| static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly); | |
| typedef struct | |
| { | |
| SHFILEOPSTRUCTW *req; | |
| DWORD dwYesToAllMask; | |
| BOOL bManyItems; | |
| BOOL bCancelled; | |
| } FILE_OPERATION; | |
| /* Confirm dialogs with an optional "Yes To All" as used in file operations confirmations | |
| */ | |
| static const WCHAR CONFIRM_MSG_PROP[] = {'W','I','N','E','_','C','O','N','F','I','R','M',0}; | |
| struct confirm_msg_info | |
| { | |
| LPWSTR lpszText; | |
| LPWSTR lpszCaption; | |
| HICON hIcon; | |
| BOOL bYesToAll; | |
| }; | |
| /* as some buttons may be hidden and the dialog height may change we may need | |
| * to move the controls */ | |
| static void confirm_msg_move_button(HWND hDlg, INT iId, INT *xPos, INT yOffset, BOOL bShow) | |
| { | |
| HWND hButton = GetDlgItem(hDlg, iId); | |
| RECT r; | |
| if (bShow) { | |
| POINT pt; | |
| int width; | |
| GetWindowRect(hButton, &r); | |
| width = r.right - r.left; | |
| pt.x = r.left; | |
| pt.y = r.top; | |
| ScreenToClient(hDlg, &pt); | |
| MoveWindow(hButton, *xPos - width, pt.y - yOffset, width, r.bottom - r.top, FALSE); | |
| *xPos -= width + 5; | |
| } | |
| else | |
| ShowWindow(hButton, SW_HIDE); | |
| } | |
| /* Note: we paint the text manually and don't use the static control to make | |
| * sure the text has the same height as the one computed in WM_INITDIALOG | |
| */ | |
| static INT_PTR ConfirmMsgBox_Paint(HWND hDlg) | |
| { | |
| PAINTSTRUCT ps; | |
| HFONT hOldFont; | |
| RECT r; | |
| HDC hdc; | |
| BeginPaint(hDlg, &ps); | |
| hdc = ps.hdc; | |
| GetClientRect(GetDlgItem(hDlg, IDD_MESSAGE), &r); | |
| /* this will remap the rect to dialog coords */ | |
| MapWindowPoints(GetDlgItem(hDlg, IDD_MESSAGE), hDlg, (LPPOINT)&r, 2); | |
| hOldFont = SelectObject(hdc, (HFONT)SendDlgItemMessageW(hDlg, IDD_MESSAGE, WM_GETFONT, 0, 0)); | |
| DrawTextW(hdc, GetPropW(hDlg, CONFIRM_MSG_PROP), -1, &r, DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_WORDBREAK); | |
| SelectObject(hdc, hOldFont); | |
| EndPaint(hDlg, &ps); | |
| return TRUE; | |
| } | |
| static INT_PTR ConfirmMsgBox_Init(HWND hDlg, LPARAM lParam) | |
| { | |
| struct confirm_msg_info *info = (struct confirm_msg_info *)lParam; | |
| INT xPos, yOffset; | |
| int width, height; | |
| HFONT hOldFont; | |
| HDC hdc; | |
| RECT r; | |
| SetWindowTextW(hDlg, info->lpszCaption); | |
| ShowWindow(GetDlgItem(hDlg, IDD_MESSAGE), SW_HIDE); | |
| SetPropW(hDlg, CONFIRM_MSG_PROP, info->lpszText); | |
| SendDlgItemMessageW(hDlg, IDD_ICON, STM_SETICON, (WPARAM)info->hIcon, 0); | |
| /* compute the text height and resize the dialog */ | |
| GetClientRect(GetDlgItem(hDlg, IDD_MESSAGE), &r); | |
| hdc = GetDC(hDlg); | |
| yOffset = r.bottom; | |
| hOldFont = SelectObject(hdc, (HFONT)SendDlgItemMessageW(hDlg, IDD_MESSAGE, WM_GETFONT, 0, 0)); | |
| DrawTextW(hdc, info->lpszText, -1, &r, DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_WORDBREAK | DT_CALCRECT); | |
| SelectObject(hdc, hOldFont); | |
| yOffset -= r.bottom; | |
| yOffset = min(yOffset, 35); /* don't make the dialog too small */ | |
| ReleaseDC(hDlg, hdc); | |
| GetClientRect(hDlg, &r); | |
| xPos = r.right - 7; | |
| GetWindowRect(hDlg, &r); | |
| width = r.right - r.left; | |
| height = r.bottom - r.top - yOffset; | |
| MoveWindow(hDlg, (GetSystemMetrics(SM_CXSCREEN) - width)/2, | |
| (GetSystemMetrics(SM_CYSCREEN) - height)/2, width, height, FALSE); | |
| confirm_msg_move_button(hDlg, IDCANCEL, &xPos, yOffset, info->bYesToAll); | |
| confirm_msg_move_button(hDlg, IDNO, &xPos, yOffset, TRUE); | |
| confirm_msg_move_button(hDlg, IDD_YESTOALL, &xPos, yOffset, info->bYesToAll); | |
| confirm_msg_move_button(hDlg, IDYES, &xPos, yOffset, TRUE); | |
| return TRUE; | |
| } | |
| static INT_PTR CALLBACK ConfirmMsgBoxProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) | |
| { | |
| switch (uMsg) | |
| { | |
| case WM_INITDIALOG: | |
| return ConfirmMsgBox_Init(hDlg, lParam); | |
| case WM_PAINT: | |
| return ConfirmMsgBox_Paint(hDlg); | |
| case WM_COMMAND: | |
| EndDialog(hDlg, wParam); | |
| break; | |
| case WM_CLOSE: | |
| EndDialog(hDlg, IDCANCEL); | |
| break; | |
| } | |
| return FALSE; | |
| } | |
| static int SHELL_ConfirmMsgBox(HWND hWnd, LPWSTR lpszText, LPWSTR lpszCaption, HICON hIcon, BOOL bYesToAll) | |
| { | |
| static const WCHAR wszTemplate[] = {'S','H','E','L','L','_','Y','E','S','T','O','A','L','L','_','M','S','G','B','O','X',0}; | |
| struct confirm_msg_info info; | |
| info.lpszText = lpszText; | |
| info.lpszCaption = lpszCaption; | |
| info.hIcon = hIcon; | |
| info.bYesToAll = bYesToAll; | |
| return DialogBoxParamW(shell32_hInstance, wszTemplate, hWnd, ConfirmMsgBoxProc, (LPARAM)&info); | |
| } | |
| /* confirmation dialogs content */ | |
| typedef struct | |
| { | |
| HINSTANCE hIconInstance; | |
| UINT icon_resource_id; | |
| UINT caption_resource_id, text_resource_id; | |
| } SHELL_ConfirmIDstruc; | |
| static BOOL SHELL_ConfirmIDs(int nKindOfDialog, SHELL_ConfirmIDstruc *ids) | |
| { | |
| ids->hIconInstance = shell32_hInstance; | |
| switch (nKindOfDialog) { | |
| case ASK_DELETE_FILE: | |
| ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE; | |
| ids->caption_resource_id = IDS_DELETEITEM_CAPTION; | |
| ids->text_resource_id = IDS_DELETEITEM_TEXT; | |
| return TRUE; | |
| case ASK_DELETE_FOLDER: | |
| ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE; | |
| ids->caption_resource_id = IDS_DELETEFOLDER_CAPTION; | |
| ids->text_resource_id = IDS_DELETEITEM_TEXT; | |
| return TRUE; | |
| case ASK_DELETE_MULTIPLE_ITEM: | |
| ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE; | |
| ids->caption_resource_id = IDS_DELETEITEM_CAPTION; | |
| ids->text_resource_id = IDS_DELETEMULTIPLE_TEXT; | |
| return TRUE; | |
| case ASK_TRASH_FILE: | |
| ids->icon_resource_id = IDI_SHELL_TRASH_FILE; | |
| ids->caption_resource_id = IDS_DELETEITEM_CAPTION; | |
| ids->text_resource_id = IDS_TRASHITEM_TEXT; | |
| return TRUE; | |
| case ASK_TRASH_FOLDER: | |
| ids->icon_resource_id = IDI_SHELL_TRASH_FILE; | |
| ids->caption_resource_id = IDS_DELETEFOLDER_CAPTION; | |
| ids->text_resource_id = IDS_TRASHFOLDER_TEXT; | |
| return TRUE; | |
| case ASK_TRASH_MULTIPLE_ITEM: | |
| ids->icon_resource_id = IDI_SHELL_TRASH_FILE; | |
| ids->caption_resource_id = IDS_DELETEITEM_CAPTION; | |
| ids->text_resource_id = IDS_TRASHMULTIPLE_TEXT; | |
| return TRUE; | |
| case ASK_CANT_TRASH_ITEM: | |
| ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE; | |
| ids->caption_resource_id = IDS_DELETEITEM_CAPTION; | |
| ids->text_resource_id = IDS_CANTTRASH_TEXT; | |
| return TRUE; | |
| case ASK_DELETE_SELECTED: | |
| ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE; | |
| ids->caption_resource_id = IDS_DELETEITEM_CAPTION; | |
| ids->text_resource_id = IDS_DELETESELECTED_TEXT; | |
| return TRUE; | |
| case ASK_OVERWRITE_FILE: | |
| ids->hIconInstance = NULL; | |
| ids->icon_resource_id = IDI_WARNING; | |
| ids->caption_resource_id = IDS_OVERWRITEFILE_CAPTION; | |
| ids->text_resource_id = IDS_OVERWRITEFILE_TEXT; | |
| return TRUE; | |
| case ASK_OVERWRITE_FOLDER: | |
| ids->hIconInstance = NULL; | |
| ids->icon_resource_id = IDI_WARNING; | |
| ids->caption_resource_id = IDS_OVERWRITEFILE_CAPTION; | |
| ids->text_resource_id = IDS_OVERWRITEFOLDER_TEXT; | |
| return TRUE; | |
| default: | |
| FIXME(" Unhandled nKindOfDialog %d stub\n", nKindOfDialog); | |
| } | |
| return FALSE; | |
| } | |
| static BOOL SHELL_ConfirmDialogW(HWND hWnd, int nKindOfDialog, LPCWSTR szDir, FILE_OPERATION *op) | |
| { | |
| WCHAR szCaption[255], szText[255], szBuffer[MAX_PATH + 256]; | |
| SHELL_ConfirmIDstruc ids; | |
| DWORD_PTR args[1]; | |
| HICON hIcon; | |
| int ret; | |
| assert(nKindOfDialog >= 0 && nKindOfDialog < 32); | |
| if (op && (op->dwYesToAllMask & (1 << nKindOfDialog))) | |
| return TRUE; | |
| if (!SHELL_ConfirmIDs(nKindOfDialog, &ids)) return FALSE; | |
| LoadStringW(shell32_hInstance, ids.caption_resource_id, szCaption, sizeof(szCaption)/sizeof(WCHAR)); | |
| LoadStringW(shell32_hInstance, ids.text_resource_id, szText, sizeof(szText)/sizeof(WCHAR)); | |
| args[0] = (DWORD_PTR)szDir; | |
| FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY, | |
| szText, 0, 0, szBuffer, sizeof(szBuffer), (__ms_va_list*)args); | |
| hIcon = LoadIconW(ids.hIconInstance, (LPWSTR)MAKEINTRESOURCE(ids.icon_resource_id)); | |
| ret = SHELL_ConfirmMsgBox(hWnd, szBuffer, szCaption, hIcon, op && op->bManyItems); | |
| if (op) { | |
| if (ret == IDD_YESTOALL) { | |
| op->dwYesToAllMask |= (1 << nKindOfDialog); | |
| ret = IDYES; | |
| } | |
| if (ret == IDCANCEL) | |
| op->bCancelled = TRUE; | |
| if (ret != IDYES) | |
| op->req->fAnyOperationsAborted = TRUE; | |
| } | |
| return ret == IDYES; | |
| } | |
| BOOL SHELL_ConfirmYesNoW(HWND hWnd, int nKindOfDialog, LPCWSTR szDir) | |
| { | |
| return SHELL_ConfirmDialogW(hWnd, nKindOfDialog, szDir, NULL); | |
| } | |
| static DWORD SHELL32_AnsiToUnicodeBuf(LPCSTR aPath, LPWSTR *wPath, DWORD minChars) | |
| { | |
| DWORD len = MultiByteToWideChar(CP_ACP, 0, aPath, -1, NULL, 0); | |
| if (len < minChars) | |
| len = minChars; | |
| *wPath = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); | |
| if (*wPath) | |
| { | |
| MultiByteToWideChar(CP_ACP, 0, aPath, -1, *wPath, len); | |
| return NO_ERROR; | |
| } | |
| return E_OUTOFMEMORY; | |
| } | |
| static void SHELL32_FreeUnicodeBuf(LPWSTR wPath) | |
| { | |
| HeapFree(GetProcessHeap(), 0, wPath); | |
| } | |
| HRESULT WINAPI SHIsFileAvailableOffline(LPCWSTR path, LPDWORD status) | |
| { | |
| FIXME("(%s, %p) stub\n", debugstr_w(path), status); | |
| return E_FAIL; | |
| } | |
| /************************************************************************** | |
| * SHELL_DeleteDirectory() [internal] | |
| * | |
| * Asks for confirmation when bShowUI is true and deletes the directory and | |
| * all its subdirectories and files if necessary. | |
| */ | |
| static BOOL SHELL_DeleteDirectoryW(HWND hwnd, LPCWSTR pszDir, BOOL bShowUI) | |
| { | |
| BOOL ret = TRUE; | |
| HANDLE hFind; | |
| WIN32_FIND_DATAW wfd; | |
| WCHAR szTemp[MAX_PATH]; | |
| /* Make sure the directory exists before eventually prompting the user */ | |
| PathCombineW(szTemp, pszDir, wWildcardFile); | |
| hFind = FindFirstFileW(szTemp, &wfd); | |
| if (hFind == INVALID_HANDLE_VALUE) | |
| return FALSE; | |
| if (!bShowUI || (ret = SHELL_ConfirmDialogW(hwnd, ASK_DELETE_FOLDER, pszDir, NULL))) | |
| { | |
| do | |
| { | |
| if (IsDotDir(wfd.cFileName)) | |
| continue; | |
| PathCombineW(szTemp, pszDir, wfd.cFileName); | |
| if (FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes) | |
| ret = SHELL_DeleteDirectoryW(hwnd, szTemp, FALSE); | |
| else | |
| ret = (SHNotifyDeleteFileW(szTemp) == ERROR_SUCCESS); | |
| } while (ret && FindNextFileW(hFind, &wfd)); | |
| } | |
| FindClose(hFind); | |
| if (ret) | |
| ret = (SHNotifyRemoveDirectoryW(pszDir) == ERROR_SUCCESS); | |
| return ret; | |
| } | |
| /************************************************************************** | |
| * Win32CreateDirectory [SHELL32.93] | |
| * | |
| * Creates a directory. Also triggers a change notify if one exists. | |
| * | |
| * PARAMS | |
| * path [I] path to directory to create | |
| * | |
| * RETURNS | |
| * TRUE if successful, FALSE otherwise | |
| * | |
| * NOTES | |
| * Verified on Win98 / IE 5 (SHELL32 4.72, March 1999 build) to be ANSI. | |
| * This is Unicode on NT/2000 | |
| */ | |
| static DWORD SHNotifyCreateDirectoryA(LPCSTR path, LPSECURITY_ATTRIBUTES sec) | |
| { | |
| LPWSTR wPath; | |
| DWORD retCode; | |
| TRACE("(%s, %p)\n", debugstr_a(path), sec); | |
| retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0); | |
| if (!retCode) | |
| { | |
| retCode = SHNotifyCreateDirectoryW(wPath, sec); | |
| SHELL32_FreeUnicodeBuf(wPath); | |
| } | |
| return retCode; | |
| } | |
| /**********************************************************************/ | |
| static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec) | |
| { | |
| TRACE("(%s, %p)\n", debugstr_w(path), sec); | |
| if (CreateDirectoryW(path, sec)) | |
| { | |
| SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW, path, NULL); | |
| return ERROR_SUCCESS; | |
| } | |
| return GetLastError(); | |
| } | |
| /**********************************************************************/ | |
| BOOL WINAPI Win32CreateDirectoryAW(LPCVOID path, LPSECURITY_ATTRIBUTES sec) | |
| { | |
| if (SHELL_OsIsUnicode()) | |
| return (SHNotifyCreateDirectoryW(path, sec) == ERROR_SUCCESS); | |
| return (SHNotifyCreateDirectoryA(path, sec) == ERROR_SUCCESS); | |
| } | |
| /************************************************************************ | |
| * Win32RemoveDirectory [SHELL32.94] | |
| * | |
| * Deletes a directory. Also triggers a change notify if one exists. | |
| * | |
| * PARAMS | |
| * path [I] path to directory to delete | |
| * | |
| * RETURNS | |
| * TRUE if successful, FALSE otherwise | |
| * | |
| * NOTES | |
| * Verified on Win98 / IE 5 (SHELL32 4.72, March 1999 build) to be ANSI. | |
| * This is Unicode on NT/2000 | |
| */ | |
| static DWORD SHNotifyRemoveDirectoryA(LPCSTR path) | |
| { | |
| LPWSTR wPath; | |
| DWORD retCode; | |
| TRACE("(%s)\n", debugstr_a(path)); | |
| retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0); | |
| if (!retCode) | |
| { | |
| retCode = SHNotifyRemoveDirectoryW(wPath); | |
| SHELL32_FreeUnicodeBuf(wPath); | |
| } | |
| return retCode; | |
| } | |
| /***********************************************************************/ | |
| static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path) | |
| { | |
| BOOL ret; | |
| TRACE("(%s)\n", debugstr_w(path)); | |
| ret = RemoveDirectoryW(path); | |
| if (!ret) | |
| { | |
| /* Directory may be write protected */ | |
| DWORD dwAttr = GetFileAttributesW(path); | |
| if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY)) | |
| if (SetFileAttributesW(path, dwAttr & ~FILE_ATTRIBUTE_READONLY)) | |
| ret = RemoveDirectoryW(path); | |
| } | |
| if (ret) | |
| { | |
| SHChangeNotify(SHCNE_RMDIR, SHCNF_PATHW, path, NULL); | |
| return ERROR_SUCCESS; | |
| } | |
| return GetLastError(); | |
| } | |
| /***********************************************************************/ | |
| BOOL WINAPI Win32RemoveDirectoryAW(LPCVOID path) | |
| { | |
| if (SHELL_OsIsUnicode()) | |
| return (SHNotifyRemoveDirectoryW(path) == ERROR_SUCCESS); | |
| return (SHNotifyRemoveDirectoryA(path) == ERROR_SUCCESS); | |
| } | |
| /************************************************************************ | |
| * Win32DeleteFile [SHELL32.164] | |
| * | |
| * Deletes a file. Also triggers a change notify if one exists. | |
| * | |
| * PARAMS | |
| * path [I] path to file to delete | |
| * | |
| * RETURNS | |
| * TRUE if successful, FALSE otherwise | |
| * | |
| * NOTES | |
| * Verified on Win98 / IE 5 (SHELL32 4.72, March 1999 build) to be ANSI. | |
| * This is Unicode on NT/2000 | |
| */ | |
| static DWORD SHNotifyDeleteFileA(LPCSTR path) | |
| { | |
| LPWSTR wPath; | |
| DWORD retCode; | |
| TRACE("(%s)\n", debugstr_a(path)); | |
| retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0); | |
| if (!retCode) | |
| { | |
| retCode = SHNotifyDeleteFileW(wPath); | |
| SHELL32_FreeUnicodeBuf(wPath); | |
| } | |
| return retCode; | |
| } | |
| /***********************************************************************/ | |
| static DWORD SHNotifyDeleteFileW(LPCWSTR path) | |
| { | |
| BOOL ret; | |
| TRACE("(%s)\n", debugstr_w(path)); | |
| ret = DeleteFileW(path); | |
| if (!ret) | |
| { | |
| /* File may be write protected or a system file */ | |
| DWORD dwAttr = GetFileAttributesW(path); | |
| if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)) | |
| if (SetFileAttributesW(path, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))) | |
| ret = DeleteFileW(path); | |
| } | |
| if (ret) | |
| { | |
| SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, path, NULL); | |
| return ERROR_SUCCESS; | |
| } | |
| return GetLastError(); | |
| } | |
| /***********************************************************************/ | |
| DWORD WINAPI Win32DeleteFileAW(LPCVOID path) | |
| { | |
| if (SHELL_OsIsUnicode()) | |
| return (SHNotifyDeleteFileW(path) == ERROR_SUCCESS); | |
| return (SHNotifyDeleteFileA(path) == ERROR_SUCCESS); | |
| } | |
| /************************************************************************ | |
| * SHNotifyMoveFile [internal] | |
| * | |
| * Moves a file. Also triggers a change notify if one exists. | |
| * | |
| * PARAMS | |
| * src [I] path to source file to move | |
| * dest [I] path to target file to move to | |
| * | |
| * RETURNS | |
| * ERORR_SUCCESS if successful | |
| */ | |
| static DWORD SHNotifyMoveFileW(LPCWSTR src, LPCWSTR dest) | |
| { | |
| BOOL ret; | |
| TRACE("(%s %s)\n", debugstr_w(src), debugstr_w(dest)); | |
| ret = MoveFileExW(src, dest, MOVEFILE_REPLACE_EXISTING); | |
| /* MOVEFILE_REPLACE_EXISTING fails with dirs, so try MoveFile */ | |
| if (!ret) | |
| ret = MoveFileW(src, dest); | |
| if (!ret) | |
| { | |
| DWORD dwAttr; | |
| dwAttr = SHFindAttrW(dest, FALSE); | |
| if (INVALID_FILE_ATTRIBUTES == dwAttr) | |
| { | |
| /* Source file may be write protected or a system file */ | |
| dwAttr = GetFileAttributesW(src); | |
| if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)) | |
| if (SetFileAttributesW(src, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))) | |
| ret = MoveFileW(src, dest); | |
| } | |
| } | |
| if (ret) | |
| { | |
| SHChangeNotify(SHCNE_RENAMEITEM, SHCNF_PATHW, src, dest); | |
| return ERROR_SUCCESS; | |
| } | |
| return GetLastError(); | |
| } | |
| /************************************************************************ | |
| * SHNotifyCopyFile [internal] | |
| * | |
| * Copies a file. Also triggers a change notify if one exists. | |
| * | |
| * PARAMS | |
| * src [I] path to source file to move | |
| * dest [I] path to target file to move to | |
| * bFailIfExists [I] if TRUE, the target file will not be overwritten if | |
| * a file with this name already exists | |
| * | |
| * RETURNS | |
| * ERROR_SUCCESS if successful | |
| */ | |
| static DWORD SHNotifyCopyFileW(LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists) | |
| { | |
| BOOL ret; | |
| TRACE("(%s %s %s)\n", debugstr_w(src), debugstr_w(dest), bFailIfExists ? "failIfExists" : ""); | |
| ret = CopyFileW(src, dest, bFailIfExists); | |
| if (ret) | |
| { | |
| SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, dest, NULL); | |
| return ERROR_SUCCESS; | |
| } | |
| return GetLastError(); | |
| } | |
| /************************************************************************* | |
| * SHCreateDirectory [SHELL32.165] | |
| * | |
| * This function creates a file system folder whose fully qualified path is | |
| * given by path. If one or more of the intermediate folders do not exist, | |
| * they will be created as well. | |
| * | |
| * PARAMS | |
| * hWnd [I] | |
| * path [I] path of directory to create | |
| * | |
| * RETURNS | |
| * ERROR_SUCCESS or one of the following values: | |
| * ERROR_BAD_PATHNAME if the path is relative | |
| * ERROR_FILE_EXISTS when a file with that name exists | |
| * ERROR_PATH_NOT_FOUND can't find the path, probably invalid | |
| * ERROR_INVALID_NAME if the path contains invalid chars | |
| * ERROR_ALREADY_EXISTS when the directory already exists | |
| * ERROR_FILENAME_EXCED_RANGE if the filename was to long to process | |
| * | |
| * NOTES | |
| * exported by ordinal | |
| * Win9x exports ANSI | |
| * WinNT/2000 exports Unicode | |
| */ | |
| DWORD WINAPI SHCreateDirectory(HWND hWnd, LPCVOID path) | |
| { | |
| if (SHELL_OsIsUnicode()) | |
| return SHCreateDirectoryExW(hWnd, path, NULL); | |
| return SHCreateDirectoryExA(hWnd, path, NULL); | |
| } | |
| /************************************************************************* | |
| * SHCreateDirectoryExA [SHELL32.@] | |
| * | |
| * This function creates a file system folder whose fully qualified path is | |
| * given by path. If one or more of the intermediate folders do not exist, | |
| * they will be created as well. | |
| * | |
| * PARAMS | |
| * hWnd [I] | |
| * path [I] path of directory to create | |
| * sec [I] security attributes to use or NULL | |
| * | |
| * RETURNS | |
| * ERROR_SUCCESS or one of the following values: | |
| * ERROR_BAD_PATHNAME or ERROR_PATH_NOT_FOUND if the path is relative | |
| * ERROR_INVALID_NAME if the path contains invalid chars | |
| * ERROR_FILE_EXISTS when a file with that name exists | |
| * ERROR_ALREADY_EXISTS when the directory already exists | |
| * ERROR_FILENAME_EXCED_RANGE if the filename was to long to process | |
| * | |
| * FIXME: Not implemented yet; | |
| * SHCreateDirectoryEx also verifies that the files in the directory will be visible | |
| * if the path is a network path to deal with network drivers which might have a limited | |
| * but unknown maximum path length. If not: | |
| * | |
| * If hWnd is set to a valid window handle, a message box is displayed warning | |
| * the user that the files may not be accessible. If the user chooses not to | |
| * proceed, the function returns ERROR_CANCELLED. | |
| * | |
| * If hWnd is set to NULL, no user interface is displayed and the function | |
| * returns ERROR_CANCELLED. | |
| */ | |
| int WINAPI SHCreateDirectoryExA(HWND hWnd, LPCSTR path, LPSECURITY_ATTRIBUTES sec) | |
| { | |
| LPWSTR wPath; | |
| DWORD retCode; | |
| TRACE("(%s, %p)\n", debugstr_a(path), sec); | |
| retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0); | |
| if (!retCode) | |
| { | |
| retCode = SHCreateDirectoryExW(hWnd, wPath, sec); | |
| SHELL32_FreeUnicodeBuf(wPath); | |
| } | |
| return retCode; | |
| } | |
| /************************************************************************* | |
| * SHCreateDirectoryExW [SHELL32.@] | |
| * | |
| * See SHCreateDirectoryExA. | |
| */ | |
| int WINAPI SHCreateDirectoryExW(HWND hWnd, LPCWSTR path, LPSECURITY_ATTRIBUTES sec) | |
| { | |
| int ret = ERROR_BAD_PATHNAME; | |
| TRACE("(%p, %s, %p)\n", hWnd, debugstr_w(path), sec); | |
| if (PathIsRelativeW(path)) | |
| { | |
| SetLastError(ret); | |
| } | |
| else | |
| { | |
| ret = SHNotifyCreateDirectoryW(path, sec); | |
| /* Refuse to work on certain error codes before trying to create directories recursively */ | |
| if (ret != ERROR_SUCCESS && | |
| ret != ERROR_FILE_EXISTS && | |
| ret != ERROR_ALREADY_EXISTS && | |
| ret != ERROR_FILENAME_EXCED_RANGE) | |
| { | |
| WCHAR *pEnd, *pSlash, szTemp[MAX_PATH + 1]; /* extra for PathAddBackslash() */ | |
| lstrcpynW(szTemp, path, MAX_PATH); | |
| pEnd = PathAddBackslashW(szTemp); | |
| pSlash = szTemp + 3; | |
| while (*pSlash) | |
| { | |
| while (*pSlash && *pSlash != '\\') pSlash++; | |
| if (*pSlash) | |
| { | |
| *pSlash = 0; /* terminate path at separator */ | |
| ret = SHNotifyCreateDirectoryW(szTemp, pSlash + 1 == pEnd ? sec : NULL); | |
| } | |
| *pSlash++ = '\\'; /* put the separator back */ | |
| } | |
| } | |
| if (ret && hWnd && (ERROR_CANCELLED != ret)) | |
| { | |
| /* We failed and should show a dialog box */ | |
| FIXME("Show system error message, creating path %s, failed with error %d\n", debugstr_w(path), ret); | |
| ret = ERROR_CANCELLED; /* Error has been already presented to user (not really yet!) */ | |
| } | |
| } | |
| return ret; | |
| } | |
| /************************************************************************* | |
| * SHFindAttrW [internal] | |
| * | |
| * Get the Attributes for a file or directory. The difference to GetAttributes() | |
| * is that this function will also work for paths containing wildcard characters | |
| * in its filename. | |
| * PARAMS | |
| * path [I] path of directory or file to check | |
| * fileOnly [I] TRUE if only files should be found | |
| * | |
| * RETURNS | |
| * INVALID_FILE_ATTRIBUTES if the path does not exist, the actual attributes of | |
| * the first file or directory found otherwise | |
| */ | |
| static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly) | |
| { | |
| WIN32_FIND_DATAW wfd; | |
| BOOL b_FileMask = fileOnly && (NULL != StrPBrkW(pName, wWildcardChars)); | |
| DWORD dwAttr = INVALID_FILE_ATTRIBUTES; | |
| HANDLE hFind = FindFirstFileW(pName, &wfd); | |
| TRACE("%s %d\n", debugstr_w(pName), fileOnly); | |
| if (INVALID_HANDLE_VALUE != hFind) | |
| { | |
| do | |
| { | |
| if (b_FileMask && IsAttribDir(wfd.dwFileAttributes)) | |
| continue; | |
| dwAttr = wfd.dwFileAttributes; | |
| break; | |
| } | |
| while (FindNextFileW(hFind, &wfd)); | |
| FindClose(hFind); | |
| } | |
| return dwAttr; | |
| } | |
| /************************************************************************* | |
| * | |
| * SHNameTranslate HelperFunction for SHFileOperationA | |
| * | |
| * Translates a list of 0 terminated ASCII 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. | |
| */ | |
| static 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, (LPCSTR)*pWToFrom, 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 | |
| * | |
| * RETURNS | |
| * Success: ERROR_SUCCESS. | |
| * Failure: ERROR_CANCELLED. | |
| * | |
| * NOTES | |
| * exported by name | |
| */ | |
| int WINAPI SHFileOperationA(LPSHFILEOPSTRUCTA lpFileOp) | |
| { | |
| SHFILEOPSTRUCTW nFileOp = *((LPSHFILEOPSTRUCTW)lpFileOp); | |
| int retCode = 0; | |
| DWORD size; | |
| LPWSTR ForFree = NULL, /* we change wString in SHNameTranslate and can't use it for freeing */ | |
| wString = NULL; /* we change this in SHNameTranslate */ | |
| TRACE("\n"); | |
| 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 */ | |
| while (1) /* every loop calculate size, second translate also, if we have storage for this */ | |
| { | |
| size = SHNameTranslate(&wString, &nFileOp.lpszProgressTitle, FALSE); /* no loop */ | |
| size += SHNameTranslate(&wString, &nFileOp.pFrom, TRUE); /* internal loop */ | |
| size += SHNameTranslate(&wString, &nFileOp.pTo, TRUE); /* internal loop */ | |
| if (ForFree) | |
| { | |
| retCode = SHFileOperationW(&nFileOp); | |
| HeapFree(GetProcessHeap(), 0, ForFree); /* we cannot use wString, it was changed */ | |
| break; | |
| } | |
| else | |
| { | |
| wString = ForFree = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR)); | |
| if (ForFree) continue; | |
| retCode = ERROR_OUTOFMEMORY; | |
| nFileOp.fAnyOperationsAborted = TRUE; | |
| SetLastError(retCode); | |
| return retCode; | |
| } | |
| } | |
| lpFileOp->hNameMappings = nFileOp.hNameMappings; | |
| lpFileOp->fAnyOperationsAborted = nFileOp.fAnyOperationsAborted; | |
| return retCode; | |
| } | |
| #define ERROR_SHELL_INTERNAL_FILE_NOT_FOUND 1026 | |
| typedef struct | |
| { | |
| DWORD attributes; | |
| LPWSTR szDirectory; | |
| LPWSTR szFilename; | |
| LPWSTR szFullPath; | |
| BOOL bFromWildcard; | |
| BOOL bFromRelative; | |
| BOOL bExists; | |
| } FILE_ENTRY; | |
| typedef struct | |
| { | |
| FILE_ENTRY *feFiles; | |
| DWORD num_alloc; | |
| DWORD dwNumFiles; | |
| BOOL bAnyFromWildcard; | |
| BOOL bAnyDirectories; | |
| BOOL bAnyDontExist; | |
| } FILE_LIST; | |
| static inline void grow_list(FILE_LIST *list) | |
| { | |
| FILE_ENTRY *new = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, list->feFiles, | |
| list->num_alloc * 2 * sizeof(*new) ); | |
| list->feFiles = new; | |
| list->num_alloc *= 2; | |
| } | |
| /* adds a file to the FILE_ENTRY struct | |
| */ | |
| static void add_file_to_entry(FILE_ENTRY *feFile, LPCWSTR szFile) | |
| { | |
| DWORD dwLen = lstrlenW(szFile) + 1; | |
| LPCWSTR ptr; | |
| feFile->szFullPath = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR)); | |
| lstrcpyW(feFile->szFullPath, szFile); | |
| ptr = StrRChrW(szFile, NULL, '\\'); | |
| if (ptr) | |
| { | |
| dwLen = ptr - szFile + 1; | |
| feFile->szDirectory = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR)); | |
| lstrcpynW(feFile->szDirectory, szFile, dwLen); | |
| dwLen = lstrlenW(feFile->szFullPath) - dwLen + 1; | |
| feFile->szFilename = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR)); | |
| lstrcpyW(feFile->szFilename, ptr + 1); /* skip over backslash */ | |
| } | |
| feFile->bFromWildcard = FALSE; | |
| } | |
| static LPWSTR wildcard_to_file(LPCWSTR szWildCard, LPCWSTR szFileName) | |
| { | |
| LPCWSTR ptr; | |
| LPWSTR szFullPath; | |
| DWORD dwDirLen, dwFullLen; | |
| ptr = StrRChrW(szWildCard, NULL, '\\'); | |
| dwDirLen = ptr - szWildCard + 1; | |
| dwFullLen = dwDirLen + lstrlenW(szFileName) + 1; | |
| szFullPath = HeapAlloc(GetProcessHeap(), 0, dwFullLen * sizeof(WCHAR)); | |
| lstrcpynW(szFullPath, szWildCard, dwDirLen + 1); | |
| lstrcatW(szFullPath, szFileName); | |
| return szFullPath; | |
| } | |
| static void parse_wildcard_files(FILE_LIST *flList, LPCWSTR szFile, LPDWORD pdwListIndex) | |
| { | |
| WIN32_FIND_DATAW wfd; | |
| HANDLE hFile = FindFirstFileW(szFile, &wfd); | |
| FILE_ENTRY *file; | |
| LPWSTR szFullPath; | |
| BOOL res; | |
| if (hFile == INVALID_HANDLE_VALUE) return; | |
| for (res = TRUE; res; res = FindNextFileW(hFile, &wfd)) | |
| { | |
| if (IsDotDir(wfd.cFileName)) continue; | |
| if (*pdwListIndex >= flList->num_alloc) grow_list( flList ); | |
| szFullPath = wildcard_to_file(szFile, wfd.cFileName); | |
| file = &flList->feFiles[(*pdwListIndex)++]; | |
| add_file_to_entry(file, szFullPath); | |
| file->bFromWildcard = TRUE; | |
| file->attributes = wfd.dwFileAttributes; | |
| if (IsAttribDir(file->attributes)) flList->bAnyDirectories = TRUE; | |
| HeapFree(GetProcessHeap(), 0, szFullPath); | |
| } | |
| FindClose(hFile); | |
| } | |
| /* takes the null-separated file list and fills out the FILE_LIST */ | |
| static HRESULT parse_file_list(FILE_LIST *flList, LPCWSTR szFiles) | |
| { | |
| LPCWSTR ptr = szFiles; | |
| WCHAR szCurFile[MAX_PATH]; | |
| DWORD i = 0; | |
| if (!szFiles) | |
| return ERROR_INVALID_PARAMETER; | |
| flList->bAnyFromWildcard = FALSE; | |
| flList->bAnyDirectories = FALSE; | |
| flList->bAnyDontExist = FALSE; | |
| flList->num_alloc = 32; | |
| flList->dwNumFiles = 0; | |
| /* empty list */ | |
| if (!szFiles[0]) | |
| return ERROR_ACCESS_DENIED; | |
| flList->feFiles = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, | |
| flList->num_alloc * sizeof(FILE_ENTRY)); | |
| while (*ptr) | |
| { | |
| if (i >= flList->num_alloc) grow_list( flList ); | |
| /* change relative to absolute path */ | |
| if (PathIsRelativeW(ptr)) | |
| { | |
| GetCurrentDirectoryW(MAX_PATH, szCurFile); | |
| PathCombineW(szCurFile, szCurFile, ptr); | |
| flList->feFiles[i].bFromRelative = TRUE; | |
| } | |
| else | |
| { | |
| lstrcpyW(szCurFile, ptr); | |
| flList->feFiles[i].bFromRelative = FALSE; | |
| } | |
| /* parse wildcard files if they are in the filename */ | |
| if (StrPBrkW(szCurFile, wWildcardChars)) | |
| { | |
| parse_wildcard_files(flList, szCurFile, &i); | |
| flList->bAnyFromWildcard = TRUE; | |
| i--; | |
| } | |
| else | |
| { | |
| FILE_ENTRY *file = &flList->feFiles[i]; | |
| add_file_to_entry(file, szCurFile); | |
| file->attributes = GetFileAttributesW( file->szFullPath ); | |
| file->bExists = (file->attributes != INVALID_FILE_ATTRIBUTES); | |
| if (!file->bExists) flList->bAnyDontExist = TRUE; | |
| if (IsAttribDir(file->attributes)) flList->bAnyDirectories = TRUE; | |
| } | |
| /* advance to the next string */ | |
| ptr += lstrlenW(ptr) + 1; | |
| i++; | |
| } | |
| flList->dwNumFiles = i; | |
| return S_OK; | |
| } | |
| /* free the FILE_LIST */ | |
| static void destroy_file_list(FILE_LIST *flList) | |
| { | |
| DWORD i; | |
| if (!flList || !flList->feFiles) | |
| return; | |
| for (i = 0; i < flList->dwNumFiles; i++) | |
| { | |
| HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szDirectory); | |
| HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFilename); | |
| HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFullPath); | |
| } | |
| HeapFree(GetProcessHeap(), 0, flList->feFiles); | |
| } | |
| static void copy_dir_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, LPCWSTR szDestPath) | |
| { | |
| WCHAR szFrom[MAX_PATH], szTo[MAX_PATH]; | |
| SHFILEOPSTRUCTW fileOp; | |
| static const WCHAR wildCardFiles[] = {'*','.','*',0}; | |
| if (IsDotDir(feFrom->szFilename)) | |
| return; | |
| if (PathFileExistsW(szDestPath)) | |
| PathCombineW(szTo, szDestPath, feFrom->szFilename); | |
| else | |
| lstrcpyW(szTo, szDestPath); | |
| if (!(op->req->fFlags & FOF_NOCONFIRMATION) && PathFileExistsW(szTo)) { | |
| if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FOLDER, feFrom->szFilename, op)) | |
| { | |
| /* Vista returns an ERROR_CANCELLED even if user pressed "No" */ | |
| if (!op->bManyItems) | |
| op->bCancelled = TRUE; | |
| return; | |
| } | |
| } | |
| szTo[lstrlenW(szTo) + 1] = '\0'; | |
| SHNotifyCreateDirectoryW(szTo, NULL); | |
| PathCombineW(szFrom, feFrom->szFullPath, wildCardFiles); | |
| szFrom[lstrlenW(szFrom) + 1] = '\0'; | |
| fileOp = *op->req; | |
| fileOp.pFrom = szFrom; | |
| fileOp.pTo = szTo; | |
| fileOp.fFlags &= ~FOF_MULTIDESTFILES; /* we know we're copying to one dir */ | |
| /* Don't ask the user about overwriting files when he accepted to overwrite the | |
| folder. FIXME: this is not exactly what Windows does - e.g. there would be | |
| an additional confirmation for a nested folder */ | |
| fileOp.fFlags |= FOF_NOCONFIRMATION; | |
| SHFileOperationW(&fileOp); | |
| } | |
| static BOOL copy_file_to_file(FILE_OPERATION *op, const WCHAR *szFrom, const WCHAR *szTo) | |
| { | |
| if (!(op->req->fFlags & FOF_NOCONFIRMATION) && PathFileExistsW(szTo)) | |
| { | |
| if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FILE, PathFindFileNameW(szTo), op)) | |
| return 0; | |
| } | |
| return SHNotifyCopyFileW(szFrom, szTo, FALSE) == 0; | |
| } | |
| /* copy a file or directory to another directory */ | |
| static void copy_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo) | |
| { | |
| if (!PathFileExistsW(feTo->szFullPath)) | |
| SHNotifyCreateDirectoryW(feTo->szFullPath, NULL); | |
| if (IsAttribFile(feFrom->attributes)) | |
| { | |
| WCHAR szDestPath[MAX_PATH]; | |
| PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename); | |
| copy_file_to_file(op, feFrom->szFullPath, szDestPath); | |
| } | |
| else if (!(op->req->fFlags & FOF_FILESONLY && feFrom->bFromWildcard)) | |
| copy_dir_to_dir(op, feFrom, feTo->szFullPath); | |
| } | |
| static void create_dest_dirs(LPCWSTR szDestDir) | |
| { | |
| WCHAR dir[MAX_PATH]; | |
| LPCWSTR ptr = StrChrW(szDestDir, '\\'); | |
| /* make sure all directories up to last one are created */ | |
| while (ptr && (ptr = StrChrW(ptr + 1, '\\'))) | |
| { | |
| lstrcpynW(dir, szDestDir, ptr - szDestDir + 1); | |
| if (!PathFileExistsW(dir)) | |
| SHNotifyCreateDirectoryW(dir, NULL); | |
| } | |
| /* create last directory */ | |
| if (!PathFileExistsW(szDestDir)) | |
| SHNotifyCreateDirectoryW(szDestDir, NULL); | |
| } | |
| /* the FO_COPY operation */ | |
| static HRESULT copy_files(FILE_OPERATION *op, const FILE_LIST *flFrom, FILE_LIST *flTo) | |
| { | |
| DWORD i; | |
| const FILE_ENTRY *entryToCopy; | |
| const FILE_ENTRY *fileDest = &flTo->feFiles[0]; | |
| if (flFrom->bAnyDontExist) | |
| return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND; | |
| if (flTo->dwNumFiles == 0) | |
| { | |
| /* If the destination is empty, SHFileOperation should use the current directory */ | |
| WCHAR curdir[MAX_PATH+1]; | |
| GetCurrentDirectoryW(MAX_PATH, curdir); | |
| curdir[lstrlenW(curdir)+1] = 0; | |
| destroy_file_list(flTo); | |
| ZeroMemory(flTo, sizeof(FILE_LIST)); | |
| parse_file_list(flTo, curdir); | |
| fileDest = &flTo->feFiles[0]; | |
| } | |
| if (op->req->fFlags & FOF_MULTIDESTFILES) | |
| { | |
| if (flFrom->bAnyFromWildcard) | |
| return ERROR_CANCELLED; | |
| if (flFrom->dwNumFiles != flTo->dwNumFiles) | |
| { | |
| if (flFrom->dwNumFiles != 1 && !IsAttribDir(fileDest->attributes)) | |
| return ERROR_CANCELLED; | |
| /* Free all but the first entry. */ | |
| for (i = 1; i < flTo->dwNumFiles; i++) | |
| { | |
| HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szDirectory); | |
| HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szFilename); | |
| HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szFullPath); | |
| } | |
| flTo->dwNumFiles = 1; | |
| } | |
| else if (IsAttribDir(fileDest->attributes)) | |
| { | |
| for (i = 1; i < flTo->dwNumFiles; i++) | |
| if (!IsAttribDir(flTo->feFiles[i].attributes) || | |
| !IsAttribDir(flFrom->feFiles[i].attributes)) | |
| { | |
| return ERROR_CANCELLED; | |
| } | |
| } | |
| } | |
| else if (flFrom->dwNumFiles != 1) | |
| { | |
| if (flTo->dwNumFiles != 1 && !IsAttribDir(fileDest->attributes)) | |
| return ERROR_CANCELLED; | |
| if (PathFileExistsW(fileDest->szFullPath) && | |
| IsAttribFile(fileDest->attributes)) | |
| { | |
| return ERROR_CANCELLED; | |
| } | |
| if (flTo->dwNumFiles == 1 && fileDest->bFromRelative && | |
| !PathFileExistsW(fileDest->szFullPath)) | |
| { | |
| return ERROR_CANCELLED; | |
| } | |
| } | |
| for (i = 0; i < flFrom->dwNumFiles; i++) | |
| { | |
| entryToCopy = &flFrom->feFiles[i]; | |
| if ((op->req->fFlags & FOF_MULTIDESTFILES) && | |
| flTo->dwNumFiles > 1) | |
| { | |
| fileDest = &flTo->feFiles[i]; | |
| } | |
| if (IsAttribDir(entryToCopy->attributes) && | |
| !lstrcmpiW(entryToCopy->szFullPath, fileDest->szDirectory)) | |
| { | |
| return ERROR_SUCCESS; | |
| } | |
| create_dest_dirs(fileDest->szDirectory); | |
| if (!lstrcmpiW(entryToCopy->szFullPath, fileDest->szFullPath)) | |
| { | |
| if (IsAttribFile(entryToCopy->attributes)) | |
| return ERROR_NO_MORE_SEARCH_HANDLES; | |
| else | |
| return ERROR_SUCCESS; | |
| } | |
| if ((flFrom->dwNumFiles > 1 && flTo->dwNumFiles == 1) || | |
| IsAttribDir(fileDest->attributes)) | |
| { | |
| copy_to_dir(op, entryToCopy, fileDest); | |
| } | |
| else if (IsAttribDir(entryToCopy->attributes)) | |
| { | |
| copy_dir_to_dir(op, entryToCopy, fileDest->szFullPath); | |
| } | |
| else | |
| { | |
| if (!copy_file_to_file(op, entryToCopy->szFullPath, fileDest->szFullPath)) | |
| { | |
| op->req->fAnyOperationsAborted = TRUE; | |
| return ERROR_CANCELLED; | |
| } | |
| } | |
| /* Vista return code. XP would return e.g. ERROR_FILE_NOT_FOUND, ERROR_ALREADY_EXISTS */ | |
| if (op->bCancelled) | |
| return ERROR_CANCELLED; | |
| } | |
| /* Vista return code. On XP if the used pressed "No" for the last item, | |
| * ERROR_ARENA_TRASHED would be returned */ | |
| return ERROR_SUCCESS; | |
| } | |
| static BOOL confirm_delete_list(HWND hWnd, DWORD fFlags, BOOL fTrash, const FILE_LIST *flFrom) | |
| { | |
| if (flFrom->dwNumFiles > 1) | |
| { | |
| WCHAR tmp[8]; | |
| const WCHAR format[] = {'%','d',0}; | |
| wnsprintfW(tmp, sizeof(tmp)/sizeof(tmp[0]), format, flFrom->dwNumFiles); | |
| return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_MULTIPLE_ITEM:ASK_DELETE_MULTIPLE_ITEM), tmp, NULL); | |
| } | |
| else | |
| { | |
| const FILE_ENTRY *fileEntry = &flFrom->feFiles[0]; | |
| if (IsAttribFile(fileEntry->attributes)) | |
| return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FILE:ASK_DELETE_FILE), fileEntry->szFullPath, NULL); | |
| else if (!(fFlags & FOF_FILESONLY && fileEntry->bFromWildcard)) | |
| return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FOLDER:ASK_DELETE_FOLDER), fileEntry->szFullPath, NULL); | |
| } | |
| return TRUE; | |
| } | |
| /* the FO_DELETE operation */ | |
| static HRESULT delete_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom) | |
| { | |
| const FILE_ENTRY *fileEntry; | |
| DWORD i; | |
| BOOL bPathExists; | |
| BOOL bTrash; | |
| if (!flFrom->dwNumFiles) | |
| return ERROR_SUCCESS; | |
| /* Windows also checks only the first item */ | |
| bTrash = (lpFileOp->fFlags & FOF_ALLOWUNDO) | |
| && TRASH_CanTrashFile(flFrom->feFiles[0].szFullPath); | |
| if (!(lpFileOp->fFlags & FOF_NOCONFIRMATION) || (!bTrash && lpFileOp->fFlags & FOF_WANTNUKEWARNING)) | |
| if (!confirm_delete_list(lpFileOp->hwnd, lpFileOp->fFlags, bTrash, flFrom)) | |
| { | |
| lpFileOp->fAnyOperationsAborted = TRUE; | |
| return 0; | |
| } | |
| for (i = 0; i < flFrom->dwNumFiles; i++) | |
| { | |
| bPathExists = TRUE; | |
| fileEntry = &flFrom->feFiles[i]; | |
| if (!IsAttribFile(fileEntry->attributes) && | |
| (lpFileOp->fFlags & FOF_FILESONLY && fileEntry->bFromWildcard)) | |
| continue; | |
| if (bTrash) | |
| { | |
| BOOL bDelete; | |
| if (TRASH_TrashFile(fileEntry->szFullPath)) | |
| continue; | |
| /* Note: Windows silently deletes the file in such a situation, we show a dialog */ | |
| if (!(lpFileOp->fFlags & FOF_NOCONFIRMATION) || (lpFileOp->fFlags & FOF_WANTNUKEWARNING)) | |
| bDelete = SHELL_ConfirmDialogW(lpFileOp->hwnd, ASK_CANT_TRASH_ITEM, fileEntry->szFullPath, NULL); | |
| else | |
| bDelete = TRUE; | |
| if (!bDelete) | |
| { | |
| lpFileOp->fAnyOperationsAborted = TRUE; | |
| break; | |
| } | |
| } | |
| /* delete the file or directory */ | |
| if (IsAttribFile(fileEntry->attributes)) | |
| bPathExists = DeleteFileW(fileEntry->szFullPath); | |
| else | |
| bPathExists = SHELL_DeleteDirectoryW(lpFileOp->hwnd, fileEntry->szFullPath, FALSE); | |
| if (!bPathExists) | |
| return ERROR_PATH_NOT_FOUND; | |
| } | |
| return ERROR_SUCCESS; | |
| } | |
| static void move_dir_to_dir(LPSHFILEOPSTRUCTW lpFileOp, const FILE_ENTRY *feFrom, LPCWSTR szDestPath) | |
| { | |
| WCHAR szFrom[MAX_PATH], szTo[MAX_PATH]; | |
| SHFILEOPSTRUCTW fileOp; | |
| static const WCHAR wildCardFiles[] = {'*','.','*',0}; | |
| if (IsDotDir(feFrom->szFilename)) | |
| return; | |
| SHNotifyCreateDirectoryW(szDestPath, NULL); | |
| PathCombineW(szFrom, feFrom->szFullPath, wildCardFiles); | |
| szFrom[lstrlenW(szFrom) + 1] = '\0'; | |
| lstrcpyW(szTo, szDestPath); | |
| szTo[lstrlenW(szDestPath) + 1] = '\0'; | |
| fileOp = *lpFileOp; | |
| fileOp.pFrom = szFrom; | |
| fileOp.pTo = szTo; | |
| SHFileOperationW(&fileOp); | |
| } | |
| /* moves a file or directory to another directory */ | |
| static void move_to_dir(LPSHFILEOPSTRUCTW lpFileOp, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo) | |
| { | |
| WCHAR szDestPath[MAX_PATH]; | |
| PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename); | |
| if (IsAttribFile(feFrom->attributes)) | |
| SHNotifyMoveFileW(feFrom->szFullPath, szDestPath); | |
| else if (!(lpFileOp->fFlags & FOF_FILESONLY && feFrom->bFromWildcard)) | |
| move_dir_to_dir(lpFileOp, feFrom, szDestPath); | |
| } | |
| /* the FO_MOVE operation */ | |
| static HRESULT move_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom, const FILE_LIST *flTo) | |
| { | |
| DWORD i; | |
| const FILE_ENTRY *entryToMove; | |
| const FILE_ENTRY *fileDest; | |
| if (!flFrom->dwNumFiles || !flTo->dwNumFiles) | |
| return ERROR_CANCELLED; | |
| if (!(lpFileOp->fFlags & FOF_MULTIDESTFILES) && | |
| flTo->dwNumFiles > 1 && flFrom->dwNumFiles > 1) | |
| { | |
| return ERROR_CANCELLED; | |
| } | |
| if (!(lpFileOp->fFlags & FOF_MULTIDESTFILES) && | |
| !flFrom->bAnyDirectories && | |
| flFrom->dwNumFiles > flTo->dwNumFiles) | |
| { | |
| return ERROR_CANCELLED; | |
| } | |
| if (!PathFileExistsW(flTo->feFiles[0].szDirectory)) | |
| return ERROR_CANCELLED; | |
| if ((lpFileOp->fFlags & FOF_MULTIDESTFILES) && | |
| flFrom->dwNumFiles != flTo->dwNumFiles) | |
| { | |
| return ERROR_CANCELLED; | |
| } | |
| fileDest = &flTo->feFiles[0]; | |
| for (i = 0; i < flFrom->dwNumFiles; i++) | |
| { | |
| entryToMove = &flFrom->feFiles[i]; | |
| if (lpFileOp->fFlags & FOF_MULTIDESTFILES) | |
| fileDest = &flTo->feFiles[i]; | |
| if (!PathFileExistsW(fileDest->szDirectory)) | |
| return ERROR_CANCELLED; | |
| if (fileDest->bExists && IsAttribDir(fileDest->attributes)) | |
| move_to_dir(lpFileOp, entryToMove, fileDest); | |
| else | |
| SHNotifyMoveFileW(entryToMove->szFullPath, fileDest->szFullPath); | |
| } | |
| return ERROR_SUCCESS; | |
| } | |
| /* the FO_RENAME files */ | |
| static HRESULT rename_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom, const FILE_LIST *flTo) | |
| { | |
| const FILE_ENTRY *feFrom; | |
| const FILE_ENTRY *feTo; | |
| if (flFrom->dwNumFiles != 1) | |
| return ERROR_GEN_FAILURE; | |
| if (flTo->dwNumFiles != 1) | |
| return ERROR_CANCELLED; | |
| feFrom = &flFrom->feFiles[0]; | |
| feTo= &flTo->feFiles[0]; | |
| /* fail if destination doesn't exist */ | |
| if (!feFrom->bExists) | |
| return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND; | |
| /* fail if destination already exists */ | |
| if (feTo->bExists) | |
| return ERROR_ALREADY_EXISTS; | |
| return SHNotifyMoveFileW(feFrom->szFullPath, feTo->szFullPath); | |
| } | |
| /* alert the user if an unsupported flag is used */ | |
| static void check_flags(FILEOP_FLAGS fFlags) | |
| { | |
| WORD wUnsupportedFlags = FOF_NO_CONNECTED_ELEMENTS | | |
| FOF_NOCOPYSECURITYATTRIBS | FOF_NORECURSEREPARSE | | |
| FOF_RENAMEONCOLLISION | FOF_WANTMAPPINGHANDLE; | |
| if (fFlags & wUnsupportedFlags) | |
| FIXME("Unsupported flags: %04x\n", fFlags); | |
| } | |
| /************************************************************************* | |
| * SHFileOperationW [SHELL32.@] | |
| * | |
| * See SHFileOperationA | |
| */ | |
| int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp) | |
| { | |
| FILE_OPERATION op; | |
| FILE_LIST flFrom, flTo; | |
| int ret = 0; | |
| if (!lpFileOp) | |
| return ERROR_INVALID_PARAMETER; | |
| check_flags(lpFileOp->fFlags); | |
| ZeroMemory(&flFrom, sizeof(FILE_LIST)); | |
| ZeroMemory(&flTo, sizeof(FILE_LIST)); | |
| if ((ret = parse_file_list(&flFrom, lpFileOp->pFrom))) | |
| return ret; | |
| if (lpFileOp->wFunc != FO_DELETE) | |
| parse_file_list(&flTo, lpFileOp->pTo); | |
| ZeroMemory(&op, sizeof(op)); | |
| op.req = lpFileOp; | |
| op.bManyItems = (flFrom.dwNumFiles > 1); | |
| switch (lpFileOp->wFunc) | |
| { | |
| case FO_COPY: | |
| ret = copy_files(&op, &flFrom, &flTo); | |
| break; | |
| case FO_DELETE: | |
| ret = delete_files(lpFileOp, &flFrom); | |
| break; | |
| case FO_MOVE: | |
| ret = move_files(lpFileOp, &flFrom, &flTo); | |
| break; | |
| case FO_RENAME: | |
| ret = rename_files(lpFileOp, &flFrom, &flTo); | |
| break; | |
| default: | |
| ret = ERROR_INVALID_PARAMETER; | |
| break; | |
| } | |
| destroy_file_list(&flFrom); | |
| if (lpFileOp->wFunc != FO_DELETE) | |
| destroy_file_list(&flTo); | |
| if (ret == ERROR_CANCELLED) | |
| lpFileOp->fAnyOperationsAborted = TRUE; | |
| return ret; | |
| } | |
| #define SHDSA_GetItemCount(hdsa) (*(int*)(hdsa)) | |
| /************************************************************************* | |
| * SHFreeNameMappings [shell32.246] | |
| * | |
| * Free the mapping handle returned by SHFileOperation if FOF_WANTSMAPPINGHANDLE | |
| * was specified. | |
| * | |
| * PARAMS | |
| * hNameMapping [I] handle to the name mappings used during renaming of files | |
| * | |
| * RETURNS | |
| * Nothing | |
| */ | |
| void WINAPI SHFreeNameMappings(HANDLE hNameMapping) | |
| { | |
| if (hNameMapping) | |
| { | |
| int i = SHDSA_GetItemCount((HDSA)hNameMapping) - 1; | |
| for (; i>= 0; i--) | |
| { | |
| LPSHNAMEMAPPINGW lp = DSA_GetItemPtr(hNameMapping, i); | |
| SHFree(lp->pszOldPath); | |
| SHFree(lp->pszNewPath); | |
| } | |
| DSA_Destroy(hNameMapping); | |
| } | |
| } | |
| /************************************************************************* | |
| * SheGetDirA [SHELL32.@] | |
| * | |
| * drive = 0: returns the current directory path | |
| * drive > 0: returns the current directory path of the specified drive | |
| * drive=1 -> A: drive=2 -> B: ... | |
| * returns 0 if successful | |
| */ | |
| DWORD WINAPI SheGetDirA(DWORD drive, LPSTR buffer) | |
| { | |
| WCHAR org_path[MAX_PATH]; | |
| DWORD ret; | |
| char drv_path[3]; | |
| /* change current directory to the specified drive */ | |
| if (drive) { | |
| strcpy(drv_path, "A:"); | |
| drv_path[0] += (char)drive-1; | |
| GetCurrentDirectoryW(MAX_PATH, org_path); | |
| SetCurrentDirectoryA(drv_path); | |
| } | |
| /* query current directory path of the specified drive */ | |
| ret = GetCurrentDirectoryA(MAX_PATH, buffer); | |
| /* back to the original drive */ | |
| if (drive) | |
| SetCurrentDirectoryW(org_path); | |
| if (!ret) | |
| return GetLastError(); | |
| return 0; | |
| } | |
| /************************************************************************* | |
| * SheGetDirW [SHELL32.@] | |
| * | |
| * drive = 0: returns the current directory path | |
| * drive > 0: returns the current directory path of the specified drive | |
| * drive=1 -> A: drive=2 -> B: ... | |
| * returns 0 if successful | |
| */ | |
| DWORD WINAPI SheGetDirW(DWORD drive, LPWSTR buffer) | |
| { | |
| WCHAR org_path[MAX_PATH]; | |
| DWORD ret; | |
| char drv_path[3]; | |
| /* change current directory to the specified drive */ | |
| if (drive) { | |
| strcpy(drv_path, "A:"); | |
| drv_path[0] += (char)drive-1; | |
| GetCurrentDirectoryW(MAX_PATH, org_path); | |
| SetCurrentDirectoryA(drv_path); | |
| } | |
| /* query current directory path of the specified drive */ | |
| ret = GetCurrentDirectoryW(MAX_PATH, buffer); | |
| /* back to the original drive */ | |
| if (drive) | |
| SetCurrentDirectoryW(org_path); | |
| if (!ret) | |
| return GetLastError(); | |
| return 0; | |
| } | |
| /************************************************************************* | |
| * SheChangeDirA [SHELL32.@] | |
| * | |
| * changes the current directory to the specified path | |
| * and returns 0 if successful | |
| */ | |
| DWORD WINAPI SheChangeDirA(LPSTR path) | |
| { | |
| if (SetCurrentDirectoryA(path)) | |
| return 0; | |
| else | |
| return GetLastError(); | |
| } | |
| /************************************************************************* | |
| * SheChangeDirW [SHELL32.@] | |
| * | |
| * changes the current directory to the specified path | |
| * and returns 0 if successful | |
| */ | |
| DWORD WINAPI SheChangeDirW(LPWSTR path) | |
| { | |
| if (SetCurrentDirectoryW(path)) | |
| return 0; | |
| else | |
| return GetLastError(); | |
| } | |
| /************************************************************************* | |
| * IsNetDrive [SHELL32.66] | |
| */ | |
| BOOL WINAPI IsNetDrive(DWORD drive) | |
| { | |
| char root[4]; | |
| strcpy(root, "A:\\"); | |
| root[0] += (char)drive; | |
| return (GetDriveTypeA(root) == DRIVE_REMOTE); | |
| } | |
| /************************************************************************* | |
| * RealDriveType [SHELL32.524] | |
| */ | |
| INT WINAPI RealDriveType(INT drive, BOOL bQueryNet) | |
| { | |
| char root[] = "A:\\"; | |
| root[0] += (char)drive; | |
| return GetDriveTypeA(root); | |
| } | |
| /*********************************************************************** | |
| * SHPathPrepareForWriteA (SHELL32.@) | |
| */ | |
| HRESULT WINAPI SHPathPrepareForWriteA(HWND hwnd, IUnknown *modless, LPCSTR path, DWORD flags) | |
| { | |
| WCHAR wpath[MAX_PATH]; | |
| MultiByteToWideChar( CP_ACP, 0, path, -1, wpath, MAX_PATH); | |
| return SHPathPrepareForWriteW(hwnd, modless, wpath, flags); | |
| } | |
| /*********************************************************************** | |
| * SHPathPrepareForWriteW (SHELL32.@) | |
| */ | |
| HRESULT WINAPI SHPathPrepareForWriteW(HWND hwnd, IUnknown *modless, LPCWSTR path, DWORD flags) | |
| { | |
| DWORD res; | |
| DWORD err; | |
| LPCWSTR realpath; | |
| int len; | |
| WCHAR* last_slash; | |
| WCHAR* temppath=NULL; | |
| TRACE("%p %p %s 0x%80x\n", hwnd, modless, debugstr_w(path), flags); | |
| if (flags & ~(SHPPFW_DIRCREATE|SHPPFW_ASKDIRCREATE|SHPPFW_IGNOREFILENAME)) | |
| FIXME("unimplemented flags 0x%08x\n", flags); | |
| /* cut off filename if necessary */ | |
| if (flags & SHPPFW_IGNOREFILENAME) | |
| { | |
| last_slash = StrRChrW(path, NULL, '\\'); | |
| if (last_slash == NULL) | |
| len = 1; | |
| else | |
| len = last_slash - path + 1; | |
| temppath = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); | |
| if (!temppath) | |
| return E_OUTOFMEMORY; | |
| StrCpyNW(temppath, path, len); | |
| realpath = temppath; | |
| } | |
| else | |
| { | |
| realpath = path; | |
| } | |
| /* try to create the directory if asked to */ | |
| if (flags & (SHPPFW_DIRCREATE|SHPPFW_ASKDIRCREATE)) | |
| { | |
| if (flags & SHPPFW_ASKDIRCREATE) | |
| FIXME("treating SHPPFW_ASKDIRCREATE as SHPPFW_DIRCREATE\n"); | |
| SHCreateDirectoryExW(0, realpath, NULL); | |
| } | |
| /* check if we can access the directory */ | |
| res = GetFileAttributesW(realpath); | |
| HeapFree(GetProcessHeap(), 0, temppath); | |
| if (res == INVALID_FILE_ATTRIBUTES) | |
| { | |
| err = GetLastError(); | |
| if (err == ERROR_FILE_NOT_FOUND) | |
| return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); | |
| return HRESULT_FROM_WIN32(err); | |
| } | |
| else if (res & FILE_ATTRIBUTE_DIRECTORY) | |
| return S_OK; | |
| else | |
| return HRESULT_FROM_WIN32(ERROR_DIRECTORY); | |
| } |