1 /* 2 * SHFileOperation 3 * 4 * Copyright 2000 Juergen Schmied 5 * Copyright 2002 Andriy Palamarchuk 6 * Copyright 2004 Dietrich Teickner (from Odin) 7 * Copyright 2004 Rolf Kalbermatter 8 * 9 * This library is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU Lesser General Public 11 * License as published by the Free Software Foundation; either 12 * version 2.1 of the License, or (at your option) any later version. 13 * 14 * This library is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * Lesser General Public License for more details. 18 * 19 * You should have received a copy of the GNU Lesser General Public 20 * License along with this library; if not, write to the Free Software 21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 22 */ 23 24 #include "precomp.h" 25 26 WINE_DEFAULT_DEBUG_CHANNEL(shell); 27 28 #define IsAttrib(x, y) ((INVALID_FILE_ATTRIBUTES != (x)) && ((x) & (y))) 29 #define IsAttribFile(x) (!((x) & FILE_ATTRIBUTE_DIRECTORY)) 30 #define IsAttribDir(x) IsAttrib(x, FILE_ATTRIBUTE_DIRECTORY) 31 #define IsDotDir(x) ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0)))) 32 33 #define FO_MASK 0xF 34 35 static const WCHAR wWildcardFile[] = {'*',0}; 36 static const WCHAR wWildcardChars[] = {'*','?',0}; 37 38 typedef struct 39 { 40 SHFILEOPSTRUCTW *req; 41 DWORD dwYesToAllMask; 42 BOOL bManyItems; 43 BOOL bCancelled; 44 IProgressDialog *progress; 45 ULARGE_INTEGER completedSize; 46 ULARGE_INTEGER totalSize; 47 WCHAR szBuilderString[50]; 48 } FILE_OPERATION; 49 50 #define ERROR_SHELL_INTERNAL_FILE_NOT_FOUND 1026 51 52 typedef struct 53 { 54 DWORD attributes; 55 LPWSTR szDirectory; 56 LPWSTR szFilename; 57 LPWSTR szFullPath; 58 BOOL bFromWildcard; 59 BOOL bFromRelative; 60 BOOL bExists; 61 } FILE_ENTRY; 62 63 typedef struct 64 { 65 FILE_ENTRY *feFiles; 66 DWORD num_alloc; 67 DWORD dwNumFiles; 68 BOOL bAnyFromWildcard; 69 BOOL bAnyDirectories; 70 BOOL bAnyDontExist; 71 } FILE_LIST; 72 73 static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec); 74 static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path); 75 static DWORD SHNotifyDeleteFileW(FILE_OPERATION *op, LPCWSTR path); 76 static DWORD SHNotifyMoveFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL isdir); 77 static DWORD SHNotifyCopyFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists); 78 static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly); 79 static HRESULT copy_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, FILE_LIST *flTo); 80 static DWORD move_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, const FILE_LIST *flTo); 81 82 DWORD WINAPI _FileOpCountManager(FILE_OPERATION *op, const FILE_LIST *flFrom); 83 static BOOL _FileOpCount(FILE_OPERATION *op, LPWSTR pwszBuf, BOOL bFolder, DWORD *ticks); 84 85 /* Confirm dialogs with an optional "Yes To All" as used in file operations confirmations 86 */ 87 static const WCHAR CONFIRM_MSG_PROP[] = {'W','I','N','E','_','C','O','N','F','I','R','M',0}; 88 89 struct confirm_msg_info 90 { 91 LPWSTR lpszText; 92 LPWSTR lpszCaption; 93 HICON hIcon; 94 BOOL bYesToAll; 95 }; 96 97 /* as some buttons may be hidden and the dialog height may change we may need 98 * to move the controls */ 99 static void confirm_msg_move_button(HWND hDlg, INT iId, INT *xPos, INT yOffset, BOOL bShow) 100 { 101 HWND hButton = GetDlgItem(hDlg, iId); 102 RECT r; 103 104 if (bShow) 105 { 106 POINT pt; 107 int width; 108 109 GetWindowRect(hButton, &r); 110 width = r.right - r.left; 111 pt.x = r.left; 112 pt.y = r.top; 113 ScreenToClient(hDlg, &pt); 114 MoveWindow(hButton, *xPos - width, pt.y - yOffset, width, r.bottom - r.top, FALSE); 115 *xPos -= width + 5; 116 } 117 else 118 ShowWindow(hButton, SW_HIDE); 119 } 120 121 /* Note: we paint the text manually and don't use the static control to make 122 * sure the text has the same height as the one computed in WM_INITDIALOG 123 */ 124 static INT_PTR ConfirmMsgBox_Paint(HWND hDlg) 125 { 126 PAINTSTRUCT ps; 127 HFONT hOldFont; 128 RECT r; 129 HDC hdc; 130 131 BeginPaint(hDlg, &ps); 132 hdc = ps.hdc; 133 SetBkMode(hdc, TRANSPARENT); 134 135 GetClientRect(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), &r); 136 /* this will remap the rect to dialog coords */ 137 MapWindowPoints(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), hDlg, (LPPOINT)&r, 2); 138 hOldFont = (HFONT)SelectObject(hdc, (HFONT)SendDlgItemMessageW(hDlg, IDC_YESTOALL_MESSAGE, WM_GETFONT, 0, 0)); 139 DrawTextW(hdc, (LPWSTR)GetPropW(hDlg, CONFIRM_MSG_PROP), -1, &r, DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_WORDBREAK); 140 SelectObject(hdc, hOldFont); 141 EndPaint(hDlg, &ps); 142 143 return TRUE; 144 } 145 146 static INT_PTR ConfirmMsgBox_Init(HWND hDlg, LPARAM lParam) 147 { 148 struct confirm_msg_info *info = (struct confirm_msg_info *)lParam; 149 INT xPos, yOffset; 150 int width, height; 151 HFONT hOldFont; 152 HDC hdc; 153 RECT r; 154 155 SetWindowTextW(hDlg, info->lpszCaption); 156 ShowWindow(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), SW_HIDE); 157 SetPropW(hDlg, CONFIRM_MSG_PROP, info->lpszText); 158 SendDlgItemMessageW(hDlg, IDC_YESTOALL_ICON, STM_SETICON, (WPARAM)info->hIcon, 0); 159 160 /* compute the text height and resize the dialog */ 161 GetClientRect(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), &r); 162 hdc = GetDC(hDlg); 163 yOffset = r.bottom; 164 hOldFont = (HFONT)SelectObject(hdc, (HFONT)SendDlgItemMessageW(hDlg, IDC_YESTOALL_MESSAGE, WM_GETFONT, 0, 0)); 165 DrawTextW(hdc, info->lpszText, -1, &r, DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_WORDBREAK | DT_CALCRECT); 166 SelectObject(hdc, hOldFont); 167 yOffset -= r.bottom; 168 yOffset = min(yOffset, 35); /* don't make the dialog too small */ 169 ReleaseDC(hDlg, hdc); 170 171 GetClientRect(hDlg, &r); 172 xPos = r.right - 7; 173 GetWindowRect(hDlg, &r); 174 width = r.right - r.left; 175 height = r.bottom - r.top - yOffset; 176 MoveWindow(hDlg, (GetSystemMetrics(SM_CXSCREEN) - width)/2, 177 (GetSystemMetrics(SM_CYSCREEN) - height)/2, width, height, FALSE); 178 179 confirm_msg_move_button(hDlg, IDCANCEL, &xPos, yOffset, info->bYesToAll); 180 confirm_msg_move_button(hDlg, IDNO, &xPos, yOffset, TRUE); 181 confirm_msg_move_button(hDlg, IDC_YESTOALL, &xPos, yOffset, info->bYesToAll); 182 confirm_msg_move_button(hDlg, IDYES, &xPos, yOffset, TRUE); 183 184 return TRUE; 185 } 186 187 static INT_PTR CALLBACK ConfirmMsgBoxProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) 188 { 189 switch (uMsg) 190 { 191 case WM_INITDIALOG: 192 return ConfirmMsgBox_Init(hDlg, lParam); 193 case WM_PAINT: 194 return ConfirmMsgBox_Paint(hDlg); 195 case WM_COMMAND: 196 EndDialog(hDlg, wParam); 197 break; 198 case WM_CLOSE: 199 EndDialog(hDlg, IDCANCEL); 200 break; 201 } 202 return FALSE; 203 } 204 205 int SHELL_ConfirmMsgBox(HWND hWnd, LPWSTR lpszText, LPWSTR lpszCaption, HICON hIcon, BOOL bYesToAll) 206 { 207 struct confirm_msg_info info; 208 209 info.lpszText = lpszText; 210 info.lpszCaption = lpszCaption; 211 info.hIcon = hIcon; 212 info.bYesToAll = bYesToAll; 213 return DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_YESTOALL_MSGBOX), hWnd, ConfirmMsgBoxProc, (LPARAM)&info); 214 } 215 216 /* confirmation dialogs content */ 217 typedef struct 218 { 219 HINSTANCE hIconInstance; 220 UINT icon_resource_id; 221 UINT caption_resource_id, text_resource_id; 222 } SHELL_ConfirmIDstruc; 223 224 static BOOL SHELL_ConfirmIDs(int nKindOfDialog, SHELL_ConfirmIDstruc *ids) 225 { 226 ids->hIconInstance = shell32_hInstance; 227 switch (nKindOfDialog) 228 { 229 case ASK_DELETE_FILE: 230 ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE; 231 ids->caption_resource_id = IDS_DELETEITEM_CAPTION; 232 ids->text_resource_id = IDS_DELETEITEM_TEXT; 233 return TRUE; 234 235 case ASK_DELETE_FOLDER: 236 ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE; 237 ids->caption_resource_id = IDS_DELETEFOLDER_CAPTION; 238 ids->text_resource_id = IDS_DELETEITEM_TEXT; 239 return TRUE; 240 241 case ASK_DELETE_MULTIPLE_ITEM: 242 ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE; 243 ids->caption_resource_id = IDS_DELETEITEM_CAPTION; 244 ids->text_resource_id = IDS_DELETEMULTIPLE_TEXT; 245 return TRUE; 246 247 case ASK_TRASH_FILE: 248 ids->icon_resource_id = IDI_SHELL_TRASH_FILE; 249 ids->caption_resource_id = IDS_DELETEITEM_CAPTION; 250 ids->text_resource_id = IDS_TRASHITEM_TEXT; 251 return TRUE; 252 253 case ASK_TRASH_FOLDER: 254 ids->icon_resource_id = IDI_SHELL_TRASH_FILE; 255 ids->caption_resource_id = IDS_DELETEFOLDER_CAPTION; 256 ids->text_resource_id = IDS_TRASHFOLDER_TEXT; 257 return TRUE; 258 259 case ASK_TRASH_MULTIPLE_ITEM: 260 ids->icon_resource_id = IDI_SHELL_TRASH_FILE; 261 ids->caption_resource_id = IDS_DELETEITEM_CAPTION; 262 ids->text_resource_id = IDS_TRASHMULTIPLE_TEXT; 263 return TRUE; 264 265 case ASK_CANT_TRASH_ITEM: 266 ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE; 267 ids->caption_resource_id = IDS_DELETEITEM_CAPTION; 268 ids->text_resource_id = IDS_CANTTRASH_TEXT; 269 return TRUE; 270 271 case ASK_DELETE_SELECTED: 272 ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE; 273 ids->caption_resource_id = IDS_DELETEITEM_CAPTION; 274 ids->text_resource_id = IDS_DELETESELECTED_TEXT; 275 return TRUE; 276 277 case ASK_OVERWRITE_FILE: 278 ids->icon_resource_id = IDI_SHELL_FOLDER_MOVE2; 279 ids->caption_resource_id = IDS_OVERWRITEFILE_CAPTION; 280 ids->text_resource_id = IDS_OVERWRITEFILE_TEXT; 281 return TRUE; 282 283 case ASK_OVERWRITE_FOLDER: 284 ids->icon_resource_id = IDI_SHELL_FOLDER_MOVE2; 285 ids->caption_resource_id = IDS_OVERWRITEFILE_CAPTION; 286 ids->text_resource_id = IDS_OVERWRITEFOLDER_TEXT; 287 return TRUE; 288 289 default: 290 FIXME(" Unhandled nKindOfDialog %d stub\n", nKindOfDialog); 291 } 292 return FALSE; 293 } 294 295 static BOOL SHELL_ConfirmDialogW(HWND hWnd, int nKindOfDialog, LPCWSTR szDir, FILE_OPERATION *op) 296 { 297 WCHAR szCaption[255], szText[255], szBuffer[MAX_PATH + 256]; 298 SHELL_ConfirmIDstruc ids; 299 DWORD_PTR args[1]; 300 HICON hIcon; 301 int ret; 302 303 assert(nKindOfDialog >= 0 && nKindOfDialog < 32); 304 if (op && (op->dwYesToAllMask & (1 << nKindOfDialog))) 305 return TRUE; 306 307 if (!SHELL_ConfirmIDs(nKindOfDialog, &ids)) return FALSE; 308 309 LoadStringW(shell32_hInstance, ids.caption_resource_id, szCaption, sizeof(szCaption)/sizeof(WCHAR)); 310 LoadStringW(shell32_hInstance, ids.text_resource_id, szText, sizeof(szText)/sizeof(WCHAR)); 311 312 args[0] = (DWORD_PTR)szDir; 313 FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY, 314 szText, 0, 0, szBuffer, sizeof(szBuffer), (va_list*)args); 315 hIcon = LoadIconW(ids.hIconInstance, (LPWSTR)MAKEINTRESOURCE(ids.icon_resource_id)); 316 317 ret = SHELL_ConfirmMsgBox(hWnd, szBuffer, szCaption, hIcon, op && op->bManyItems); 318 if (op) 319 { 320 if (ret == IDC_YESTOALL) 321 { 322 op->dwYesToAllMask |= (1 << nKindOfDialog); 323 ret = IDYES; 324 } 325 if (ret == IDCANCEL) 326 op->bCancelled = TRUE; 327 if (ret != IDYES) 328 op->req->fAnyOperationsAborted = TRUE; 329 } 330 return ret == IDYES; 331 } 332 333 BOOL SHELL_ConfirmYesNoW(HWND hWnd, int nKindOfDialog, LPCWSTR szDir) 334 { 335 return SHELL_ConfirmDialogW(hWnd, nKindOfDialog, szDir, NULL); 336 } 337 338 static DWORD SHELL32_AnsiToUnicodeBuf(LPCSTR aPath, LPWSTR *wPath, DWORD minChars) 339 { 340 DWORD len = MultiByteToWideChar(CP_ACP, 0, aPath, -1, NULL, 0); 341 342 if (len < minChars) 343 len = minChars; 344 345 *wPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); 346 if (*wPath) 347 { 348 MultiByteToWideChar(CP_ACP, 0, aPath, -1, *wPath, len); 349 return NO_ERROR; 350 } 351 return E_OUTOFMEMORY; 352 } 353 354 static void SHELL32_FreeUnicodeBuf(LPWSTR wPath) 355 { 356 HeapFree(GetProcessHeap(), 0, wPath); 357 } 358 359 EXTERN_C HRESULT WINAPI SHIsFileAvailableOffline(LPCWSTR path, LPDWORD status) 360 { 361 FIXME("(%s, %p) stub\n", debugstr_w(path), status); 362 return E_FAIL; 363 } 364 365 /************************************************************************** 366 * SHELL_DeleteDirectory() [internal] 367 * 368 * Asks for confirmation when bShowUI is true and deletes the directory and 369 * all its subdirectories and files if necessary. 370 */ 371 BOOL SHELL_DeleteDirectoryW(FILE_OPERATION *op, LPCWSTR pszDir, BOOL bShowUI) 372 { 373 BOOL ret = TRUE; 374 HANDLE hFind; 375 WIN32_FIND_DATAW wfd; 376 WCHAR szTemp[MAX_PATH]; 377 378 /* Make sure the directory exists before eventually prompting the user */ 379 PathCombineW(szTemp, pszDir, wWildcardFile); 380 hFind = FindFirstFileW(szTemp, &wfd); 381 if (hFind == INVALID_HANDLE_VALUE) 382 return FALSE; 383 384 if (!bShowUI || (ret = SHELL_ConfirmDialogW(op->req->hwnd, ASK_DELETE_FOLDER, pszDir, NULL))) 385 { 386 do 387 { 388 if (IsDotDir(wfd.cFileName)) 389 continue; 390 PathCombineW(szTemp, pszDir, wfd.cFileName); 391 if (FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes) 392 ret = SHELL_DeleteDirectoryW(op, szTemp, FALSE); 393 else 394 ret = (SHNotifyDeleteFileW(op, szTemp) == ERROR_SUCCESS); 395 396 if (op->progress != NULL) 397 op->bCancelled |= op->progress->HasUserCancelled(); 398 } while (ret && FindNextFileW(hFind, &wfd) && !op->bCancelled); 399 } 400 FindClose(hFind); 401 if (ret) 402 ret = (SHNotifyRemoveDirectoryW(pszDir) == ERROR_SUCCESS); 403 return ret; 404 } 405 406 /************************************************************************** 407 * Win32CreateDirectory [SHELL32.93] 408 * 409 * Creates a directory. Also triggers a change notify if one exists. 410 * 411 * PARAMS 412 * path [I] path to directory to create 413 * 414 * RETURNS 415 * TRUE if successful, FALSE otherwise 416 */ 417 418 static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec) 419 { 420 TRACE("(%s, %p)\n", debugstr_w(path), sec); 421 422 if (CreateDirectoryW(path, sec)) 423 { 424 SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW, path, NULL); 425 return ERROR_SUCCESS; 426 } 427 return GetLastError(); 428 } 429 430 /**********************************************************************/ 431 432 EXTERN_C BOOL WINAPI Win32CreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec) 433 { 434 return (SHNotifyCreateDirectoryW(path, sec) == ERROR_SUCCESS); 435 } 436 437 /************************************************************************ 438 * Win32RemoveDirectory [SHELL32.94] 439 * 440 * Deletes a directory. Also triggers a change notify if one exists. 441 * 442 * PARAMS 443 * path [I] path to directory to delete 444 * 445 * RETURNS 446 * TRUE if successful, FALSE otherwise 447 */ 448 static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path) 449 { 450 BOOL ret; 451 TRACE("(%s)\n", debugstr_w(path)); 452 453 ret = RemoveDirectoryW(path); 454 if (!ret) 455 { 456 /* Directory may be write protected */ 457 DWORD dwAttr = GetFileAttributesW(path); 458 if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY)) 459 if (SetFileAttributesW(path, dwAttr & ~FILE_ATTRIBUTE_READONLY)) 460 ret = RemoveDirectoryW(path); 461 } 462 if (ret) 463 { 464 SHChangeNotify(SHCNE_RMDIR, SHCNF_PATHW, path, NULL); 465 return ERROR_SUCCESS; 466 } 467 return GetLastError(); 468 } 469 470 /***********************************************************************/ 471 472 EXTERN_C BOOL WINAPI Win32RemoveDirectoryW(LPCWSTR path) 473 { 474 return (SHNotifyRemoveDirectoryW(path) == ERROR_SUCCESS); 475 } 476 477 static void _SetOperationTitle(FILE_OPERATION *op) { 478 if (op->progress == NULL) 479 return; 480 WCHAR szTitle[50], szPreflight[50]; 481 UINT animation_id = NULL; 482 483 switch (op->req->wFunc) 484 { 485 case FO_COPY: 486 LoadStringW(shell32_hInstance, IDS_FILEOOP_COPYING, szTitle, sizeof(szTitle)/sizeof(WCHAR)); 487 LoadStringW(shell32_hInstance, IDS_FILEOOP_FROM_TO, op->szBuilderString, sizeof( op->szBuilderString)/sizeof(WCHAR)); 488 animation_id = IDA_SHELL_COPY; 489 break; 490 case FO_DELETE: 491 LoadStringW(shell32_hInstance, IDS_FILEOOP_DELETING, szTitle, sizeof(szTitle)/sizeof(WCHAR)); 492 LoadStringW(shell32_hInstance, IDS_FILEOOP_FROM, op->szBuilderString, sizeof( op->szBuilderString)/sizeof(WCHAR)); 493 animation_id = IDA_SHELL_DELETE; 494 break; 495 case FO_MOVE: 496 LoadStringW(shell32_hInstance, IDS_FILEOOP_MOVING, szTitle, sizeof(szTitle)/sizeof(WCHAR)); 497 LoadStringW(shell32_hInstance, IDS_FILEOOP_FROM_TO, op->szBuilderString, sizeof( op->szBuilderString)/sizeof(WCHAR)); 498 animation_id = IDA_SHELL_COPY; 499 break; 500 default: 501 return; 502 } 503 LoadStringW(shell32_hInstance, IDS_FILEOOP_PREFLIGHT, szPreflight, sizeof(szPreflight)/sizeof(WCHAR)); 504 505 op->progress->SetTitle(szTitle); 506 op->progress->SetLine(1, szPreflight, false, NULL); 507 op->progress->SetAnimation(shell32_hInstance, animation_id); 508 } 509 510 static void _SetOperationTexts(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest) { 511 if (op->progress == NULL || src == NULL) 512 return; 513 LPWSTR fileSpecS, pathSpecS, fileSpecD, pathSpecD; 514 WCHAR szFolderS[50], szFolderD[50], szFinalString[260]; 515 516 DWORD_PTR args[2]; 517 518 fileSpecS = (pathSpecS = (LPWSTR) src); 519 fileSpecD = (pathSpecD = (LPWSTR) dest); 520 521 // March across the string to get the file path and it's parent dir. 522 for (LPWSTR ptr = (LPWSTR) src; *ptr; ptr++) { 523 if (*ptr == '\\') { 524 pathSpecS = fileSpecS; 525 fileSpecS = ptr+1; 526 } 527 } 528 lstrcpynW(szFolderS, pathSpecS, min(50, fileSpecS - pathSpecS)); 529 args[0] = (DWORD_PTR) szFolderS; 530 531 switch (op->req->wFunc) 532 { 533 case FO_COPY: 534 case FO_MOVE: 535 if (dest == NULL) 536 return; 537 for (LPWSTR ptr = (LPWSTR) dest; *ptr; ptr++) { 538 if (*ptr == '\\') { 539 pathSpecD = fileSpecD; 540 fileSpecD = ptr + 1; 541 } 542 } 543 lstrcpynW(szFolderD, pathSpecD, min(50, fileSpecD - pathSpecD)); 544 args[1] = (DWORD_PTR) szFolderD; 545 break; 546 case FO_DELETE: 547 break; 548 default: 549 return; 550 } 551 552 FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY, 553 op->szBuilderString, 0, 0, szFinalString, sizeof(szFinalString), (va_list*)args); 554 555 op->progress->SetLine(1, fileSpecS, false, NULL); 556 op->progress->SetLine(2, szFinalString, false, NULL); 557 } 558 559 560 DWORD CALLBACK SHCopyProgressRoutine( 561 LARGE_INTEGER TotalFileSize, 562 LARGE_INTEGER TotalBytesTransferred, 563 LARGE_INTEGER StreamSize, 564 LARGE_INTEGER StreamBytesTransferred, 565 DWORD dwStreamNumber, 566 DWORD dwCallbackReason, 567 HANDLE hSourceFile, 568 HANDLE hDestinationFile, 569 LPVOID lpData 570 ) { 571 FILE_OPERATION *op = (FILE_OPERATION *) lpData; 572 573 if (op->progress) { 574 /* 575 * This is called at the start of each file. To keop less state, 576 * I'm adding the file to the completed size here, and the re-subtracting 577 * it when drawing the progress bar. 578 */ 579 if (dwCallbackReason & CALLBACK_STREAM_SWITCH) 580 op->completedSize.QuadPart += TotalFileSize.QuadPart; 581 582 op->progress->SetProgress64(op->completedSize.QuadPart - 583 TotalFileSize.QuadPart + 584 TotalBytesTransferred.QuadPart 585 , op->totalSize.QuadPart); 586 587 588 op->bCancelled = op->progress->HasUserCancelled(); 589 } 590 591 return 0; 592 } 593 594 595 /************************************************************************ 596 * SHNotifyDeleteFileW [internal] 597 * 598 * Deletes a file. Also triggers a change notify if one exists. 599 * 600 * PARAMS 601 * op [I] File Operation context 602 * path [I] path to source file to move 603 * 604 * RETURNS 605 * ERORR_SUCCESS if successful 606 */ 607 static DWORD SHNotifyDeleteFileW(FILE_OPERATION *op, LPCWSTR path) 608 { 609 BOOL ret; 610 611 TRACE("(%s)\n", debugstr_w(path)); 612 613 _SetOperationTexts(op, path, NULL); 614 615 LARGE_INTEGER FileSize; 616 FileSize.QuadPart = 0; 617 618 WIN32_FIND_DATAW wfd; 619 HANDLE hFile = FindFirstFileW(path, &wfd); 620 if (hFile != INVALID_HANDLE_VALUE && IsAttribFile(wfd.dwFileAttributes)) { 621 ULARGE_INTEGER tmp; 622 tmp.u.LowPart = wfd.nFileSizeLow; 623 tmp.u.HighPart = wfd.nFileSizeHigh; 624 FileSize.QuadPart = tmp.QuadPart; 625 } 626 FindClose(hFile); 627 628 ret = DeleteFileW(path); 629 if (!ret) 630 { 631 /* File may be write protected or a system file */ 632 DWORD dwAttr = GetFileAttributesW(path); 633 if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)) 634 if (SetFileAttributesW(path, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))) 635 ret = DeleteFileW(path); 636 } 637 if (ret) 638 { 639 // Bit of a hack to make the progress bar move. We don't have progress inside the file, so inform when done. 640 SHCopyProgressRoutine(FileSize, FileSize, FileSize, FileSize, 0, CALLBACK_STREAM_SWITCH, NULL, NULL, op); 641 SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, path, NULL); 642 return ERROR_SUCCESS; 643 } 644 return GetLastError(); 645 } 646 647 /************************************************************************ 648 * Win32DeleteFile [SHELL32.164] 649 * 650 * Deletes a file. Also triggers a change notify if one exists. 651 * 652 * PARAMS 653 * path [I] path to file to delete 654 * 655 * RETURNS 656 * TRUE if successful, FALSE otherwise 657 */ 658 EXTERN_C DWORD WINAPI Win32DeleteFileW(LPCWSTR path) 659 { 660 return (SHNotifyDeleteFileW(NULL, path) == ERROR_SUCCESS); 661 } 662 663 /************************************************************************ 664 * SHNotifyMoveFile [internal] 665 * 666 * Moves a file. Also triggers a change notify if one exists. 667 * 668 * PARAMS 669 * op [I] File Operation context 670 * src [I] path to source file to move 671 * dest [I] path to target file to move to 672 * 673 * RETURNS 674 * ERROR_SUCCESS if successful 675 */ 676 static DWORD SHNotifyMoveFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL isdir) 677 { 678 BOOL ret; 679 680 TRACE("(%s %s)\n", debugstr_w(src), debugstr_w(dest)); 681 682 _SetOperationTexts(op, src, dest); 683 684 ret = MoveFileWithProgressW(src, dest, SHCopyProgressRoutine, op, MOVEFILE_REPLACE_EXISTING); 685 686 /* MOVEFILE_REPLACE_EXISTING fails with dirs, so try MoveFile */ 687 if (!ret) 688 ret = MoveFileW(src, dest); 689 690 if (!ret) 691 { 692 DWORD dwAttr; 693 694 dwAttr = SHFindAttrW(dest, FALSE); 695 if (INVALID_FILE_ATTRIBUTES == dwAttr) 696 { 697 /* Source file may be write protected or a system file */ 698 dwAttr = GetFileAttributesW(src); 699 if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)) 700 if (SetFileAttributesW(src, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))) 701 ret = MoveFileW(src, dest); 702 } 703 } 704 if (ret) 705 { 706 SHChangeNotify(isdir ? SHCNE_MKDIR : SHCNE_CREATE, SHCNF_PATHW, dest, NULL); 707 SHChangeNotify(isdir ? SHCNE_RMDIR : SHCNE_DELETE, SHCNF_PATHW, src, NULL); 708 return ERROR_SUCCESS; 709 } 710 return GetLastError(); 711 } 712 713 /************************************************************************ 714 * SHNotifyCopyFile [internal] 715 * 716 * Copies a file. Also triggers a change notify if one exists. 717 * 718 * PARAMS 719 * src [I] path to source file to move 720 * dest [I] path to target file to move to 721 * bFailIfExists [I] if TRUE, the target file will not be overwritten if 722 * a file with this name already exists 723 * 724 * RETURNS 725 * ERROR_SUCCESS if successful 726 */ 727 static DWORD SHNotifyCopyFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists) 728 { 729 BOOL ret; 730 DWORD attribs; 731 732 TRACE("(%s %s %s)\n", debugstr_w(src), debugstr_w(dest), bFailIfExists ? "failIfExists" : ""); 733 734 _SetOperationTexts(op, src, dest); 735 736 /* Destination file may already exist with read only attribute */ 737 attribs = GetFileAttributesW(dest); 738 if (IsAttrib(attribs, FILE_ATTRIBUTE_READONLY)) 739 SetFileAttributesW(dest, attribs & ~FILE_ATTRIBUTE_READONLY); 740 741 if (GetFileAttributesW(dest) & FILE_ATTRIBUTE_READONLY) 742 { 743 SetFileAttributesW(dest, attribs & ~FILE_ATTRIBUTE_READONLY); 744 if (GetFileAttributesW(dest) & FILE_ATTRIBUTE_READONLY) 745 { 746 TRACE("[shell32, SHNotifyCopyFileW] STILL SHIT\n"); 747 } 748 } 749 750 ret = CopyFileExW(src, dest, SHCopyProgressRoutine, op, &op->bCancelled, bFailIfExists); 751 if (ret) 752 { 753 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, dest, NULL); 754 return ERROR_SUCCESS; 755 } 756 757 return GetLastError(); 758 } 759 760 /************************************************************************* 761 * SHCreateDirectory [SHELL32.165] 762 * 763 * This function creates a file system folder whose fully qualified path is 764 * given by path. If one or more of the intermediate folders do not exist, 765 * they will be created as well. 766 * 767 * PARAMS 768 * hWnd [I] 769 * path [I] path of directory to create 770 * 771 * RETURNS 772 * ERROR_SUCCESS or one of the following values: 773 * ERROR_BAD_PATHNAME if the path is relative 774 * ERROR_FILE_EXISTS when a file with that name exists 775 * ERROR_PATH_NOT_FOUND can't find the path, probably invalid 776 * ERROR_INVALID_NAME if the path contains invalid chars 777 * ERROR_ALREADY_EXISTS when the directory already exists 778 * ERROR_FILENAME_EXCED_RANGE if the filename was to long to process 779 * 780 * NOTES 781 * exported by ordinal 782 * Win9x exports ANSI 783 * WinNT/2000 exports Unicode 784 */ 785 int WINAPI SHCreateDirectory(HWND hWnd, LPCWSTR path) 786 { 787 return SHCreateDirectoryExW(hWnd, path, NULL); 788 } 789 790 /************************************************************************* 791 * SHCreateDirectoryExA [SHELL32.@] 792 * 793 * This function creates a file system folder whose fully qualified path is 794 * given by path. If one or more of the intermediate folders do not exist, 795 * they will be created as well. 796 * 797 * PARAMS 798 * hWnd [I] 799 * path [I] path of directory to create 800 * sec [I] security attributes to use or NULL 801 * 802 * RETURNS 803 * ERROR_SUCCESS or one of the following values: 804 * ERROR_BAD_PATHNAME or ERROR_PATH_NOT_FOUND if the path is relative 805 * ERROR_INVALID_NAME if the path contains invalid chars 806 * ERROR_FILE_EXISTS when a file with that name exists 807 * ERROR_ALREADY_EXISTS when the directory already exists 808 * ERROR_FILENAME_EXCED_RANGE if the filename was too long to process 809 * 810 * FIXME: Not implemented yet; 811 * SHCreateDirectoryEx also verifies that the files in the directory will be visible 812 * if the path is a network path to deal with network drivers which might have a limited 813 * but unknown maximum path length. If not: 814 * 815 * If hWnd is set to a valid window handle, a message box is displayed warning 816 * the user that the files may not be accessible. If the user chooses not to 817 * proceed, the function returns ERROR_CANCELLED. 818 * 819 * If hWnd is set to NULL, no user interface is displayed and the function 820 * returns ERROR_CANCELLED. 821 */ 822 int WINAPI SHCreateDirectoryExA(HWND hWnd, LPCSTR path, LPSECURITY_ATTRIBUTES sec) 823 { 824 LPWSTR wPath; 825 DWORD retCode; 826 827 TRACE("(%s, %p)\n", debugstr_a(path), sec); 828 829 retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0); 830 if (!retCode) 831 { 832 retCode = SHCreateDirectoryExW(hWnd, wPath, sec); 833 SHELL32_FreeUnicodeBuf(wPath); 834 } 835 return retCode; 836 } 837 838 /************************************************************************* 839 * SHCreateDirectoryExW [SHELL32.@] 840 * 841 * See SHCreateDirectoryExA. 842 */ 843 int WINAPI SHCreateDirectoryExW(HWND hWnd, LPCWSTR path, LPSECURITY_ATTRIBUTES sec) 844 { 845 int ret = ERROR_BAD_PATHNAME; 846 TRACE("(%p, %s, %p)\n", hWnd, debugstr_w(path), sec); 847 848 if (PathIsRelativeW(path)) 849 { 850 SetLastError(ret); 851 } 852 else 853 { 854 ret = SHNotifyCreateDirectoryW(path, sec); 855 /* Refuse to work on certain error codes before trying to create directories recursively */ 856 if (ret != ERROR_SUCCESS && 857 ret != ERROR_FILE_EXISTS && 858 ret != ERROR_ALREADY_EXISTS && 859 ret != ERROR_FILENAME_EXCED_RANGE) 860 { 861 WCHAR *pEnd, *pSlash, szTemp[MAX_PATH + 1]; /* extra for PathAddBackslash() */ 862 863 lstrcpynW(szTemp, path, MAX_PATH); 864 pEnd = PathAddBackslashW(szTemp); 865 pSlash = szTemp + 3; 866 867 while (*pSlash) 868 { 869 while (*pSlash && *pSlash != '\\') pSlash++; 870 if (*pSlash) 871 { 872 *pSlash = 0; /* terminate path at separator */ 873 874 ret = SHNotifyCreateDirectoryW(szTemp, pSlash + 1 == pEnd ? sec : NULL); 875 } 876 *pSlash++ = '\\'; /* put the separator back */ 877 } 878 } 879 880 if (ret && hWnd && (ERROR_CANCELLED != ret)) 881 { 882 ShellMessageBoxW(shell32_hInstance, hWnd, MAKEINTRESOURCEW(IDS_CREATEFOLDER_DENIED), MAKEINTRESOURCEW(IDS_CREATEFOLDER_CAPTION), 883 MB_ICONEXCLAMATION | MB_OK, path); 884 ret = ERROR_CANCELLED; 885 } 886 } 887 888 return ret; 889 } 890 891 /************************************************************************* 892 * SHFindAttrW [internal] 893 * 894 * Get the Attributes for a file or directory. The difference to GetAttributes() 895 * is that this function will also work for paths containing wildcard characters 896 * in its filename. 897 898 * PARAMS 899 * path [I] path of directory or file to check 900 * fileOnly [I] TRUE if only files should be found 901 * 902 * RETURNS 903 * INVALID_FILE_ATTRIBUTES if the path does not exist, the actual attributes of 904 * the first file or directory found otherwise 905 */ 906 static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly) 907 { 908 WIN32_FIND_DATAW wfd; 909 BOOL b_FileMask = fileOnly && (NULL != StrPBrkW(pName, wWildcardChars)); 910 DWORD dwAttr = INVALID_FILE_ATTRIBUTES; 911 HANDLE hFind = FindFirstFileW(pName, &wfd); 912 913 TRACE("%s %d\n", debugstr_w(pName), fileOnly); 914 if (INVALID_HANDLE_VALUE != hFind) 915 { 916 do 917 { 918 if (b_FileMask && IsAttribDir(wfd.dwFileAttributes)) 919 continue; 920 dwAttr = wfd.dwFileAttributes; 921 break; 922 } while (FindNextFileW(hFind, &wfd)); 923 924 FindClose(hFind); 925 } 926 return dwAttr; 927 } 928 929 /************************************************************************* 930 * 931 * _ConvertAtoW helper function for SHFileOperationA 932 * 933 * Converts a string or string-list to unicode. 934 */ 935 static DWORD _ConvertAtoW(PCSTR strSrc, PCWSTR* pStrDest, BOOL isList) 936 { 937 *pStrDest = NULL; 938 939 // If the input is null, nothing to convert. 940 if (!strSrc) 941 return 0; 942 943 // Measure the total size, depending on if it's a zero-terminated list. 944 int sizeA = 0; 945 if (isList) 946 { 947 PCSTR tmpSrc = strSrc; 948 int size; 949 do 950 { 951 size = lstrlenA(tmpSrc) + 1; 952 sizeA += size; 953 tmpSrc += size; 954 } while (size != 1); 955 } 956 else 957 { 958 sizeA = lstrlenA(strSrc) + 1; 959 } 960 961 // Measure the needed allocation size. 962 int sizeW = MultiByteToWideChar(CP_ACP, 0, strSrc, sizeA, NULL, 0); 963 if (!sizeW) 964 return GetLastError(); 965 966 PWSTR strDest = (PWSTR) HeapAlloc(GetProcessHeap(), 0, sizeW * sizeof(WCHAR)); 967 if (!strDest) 968 return ERROR_OUTOFMEMORY; 969 970 int err = MultiByteToWideChar(CP_ACP, 0, strSrc, sizeA, strDest, sizeW); 971 if (!err) 972 { 973 HeapFree(GetProcessHeap(), 0, strDest); 974 return GetLastError(); 975 } 976 977 *pStrDest = strDest; 978 return 0; 979 } 980 981 /************************************************************************* 982 * SHFileOperationA [SHELL32.@] 983 * 984 * Function to copy, move, delete and create one or more files with optional 985 * user prompts. 986 * 987 * PARAMS 988 * lpFileOp [I/O] pointer to a structure containing all the necessary information 989 * 990 * RETURNS 991 * Success: ERROR_SUCCESS. 992 * Failure: ERROR_CANCELLED. 993 * 994 * NOTES 995 * exported by name 996 */ 997 int WINAPI SHFileOperationA(LPSHFILEOPSTRUCTA lpFileOp) 998 { 999 int errCode, retCode; 1000 SHFILEOPSTRUCTW nFileOp = { 0 }; 1001 1002 // Convert A information to W 1003 nFileOp.hwnd = lpFileOp->hwnd; 1004 nFileOp.wFunc = lpFileOp->wFunc; 1005 nFileOp.fFlags = lpFileOp->fFlags; 1006 1007 errCode = _ConvertAtoW(lpFileOp->pFrom, &nFileOp.pFrom, TRUE); 1008 if (errCode != 0) 1009 goto cleanup; 1010 1011 if (FO_DELETE != (nFileOp.wFunc & FO_MASK)) 1012 { 1013 errCode = _ConvertAtoW(lpFileOp->pTo, &nFileOp.pTo, TRUE); 1014 if (errCode != 0) 1015 goto cleanup; 1016 } 1017 1018 if (nFileOp.fFlags & FOF_SIMPLEPROGRESS) 1019 { 1020 errCode = _ConvertAtoW(lpFileOp->lpszProgressTitle, &nFileOp.lpszProgressTitle, FALSE); 1021 if (errCode != 0) 1022 goto cleanup; 1023 } 1024 1025 // Call the actual function 1026 retCode = SHFileOperationW(&nFileOp); 1027 1028 // Cleanup 1029 cleanup: 1030 if (nFileOp.pFrom) 1031 HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.pFrom); 1032 if (nFileOp.pTo) 1033 HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.pTo); 1034 if (nFileOp.lpszProgressTitle) 1035 HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.lpszProgressTitle); 1036 1037 if (errCode != 0) 1038 { 1039 lpFileOp->fAnyOperationsAborted = TRUE; 1040 SetLastError(errCode); 1041 1042 return errCode; 1043 } 1044 1045 // Thankfully, starting with NT4 the name mappings are always unicode, so no need to convert. 1046 lpFileOp->hNameMappings = nFileOp.hNameMappings; 1047 lpFileOp->fAnyOperationsAborted = nFileOp.fAnyOperationsAborted; 1048 return retCode; 1049 } 1050 1051 static void __inline grow_list(FILE_LIST *list) 1052 { 1053 FILE_ENTRY *newx = (FILE_ENTRY *)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, list->feFiles, 1054 list->num_alloc * 2 * sizeof(*newx) ); 1055 list->feFiles = newx; 1056 list->num_alloc *= 2; 1057 } 1058 1059 /* adds a file to the FILE_ENTRY struct 1060 */ 1061 static void add_file_to_entry(FILE_ENTRY *feFile, LPCWSTR szFile) 1062 { 1063 DWORD dwLen = lstrlenW(szFile) + 1; 1064 LPCWSTR ptr; 1065 1066 feFile->szFullPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR)); 1067 lstrcpyW(feFile->szFullPath, szFile); 1068 1069 ptr = StrRChrW(szFile, NULL, '\\'); 1070 if (ptr) 1071 { 1072 dwLen = ptr - szFile + 1; 1073 feFile->szDirectory = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR)); 1074 lstrcpynW(feFile->szDirectory, szFile, dwLen); 1075 1076 dwLen = lstrlenW(feFile->szFullPath) - dwLen + 1; 1077 feFile->szFilename = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR)); 1078 lstrcpyW(feFile->szFilename, ptr + 1); /* skip over backslash */ 1079 } 1080 feFile->bFromWildcard = FALSE; 1081 } 1082 1083 static LPWSTR wildcard_to_file(LPCWSTR szWildCard, LPCWSTR szFileName) 1084 { 1085 LPCWSTR ptr; 1086 LPWSTR szFullPath; 1087 DWORD dwDirLen, dwFullLen; 1088 1089 ptr = StrRChrW(szWildCard, NULL, '\\'); 1090 dwDirLen = ptr - szWildCard + 1; 1091 1092 dwFullLen = dwDirLen + lstrlenW(szFileName) + 1; 1093 szFullPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwFullLen * sizeof(WCHAR)); 1094 1095 lstrcpynW(szFullPath, szWildCard, dwDirLen + 1); 1096 lstrcatW(szFullPath, szFileName); 1097 1098 return szFullPath; 1099 } 1100 1101 static void parse_wildcard_files(FILE_LIST *flList, LPCWSTR szFile, LPDWORD pdwListIndex) 1102 { 1103 WIN32_FIND_DATAW wfd; 1104 HANDLE hFile = FindFirstFileW(szFile, &wfd); 1105 FILE_ENTRY *file; 1106 LPWSTR szFullPath; 1107 BOOL res; 1108 1109 if (hFile == INVALID_HANDLE_VALUE) return; 1110 1111 for (res = TRUE; res; res = FindNextFileW(hFile, &wfd)) 1112 { 1113 if (IsDotDir(wfd.cFileName)) 1114 continue; 1115 1116 if (*pdwListIndex >= flList->num_alloc) 1117 grow_list( flList ); 1118 1119 szFullPath = wildcard_to_file(szFile, wfd.cFileName); 1120 file = &flList->feFiles[(*pdwListIndex)++]; 1121 add_file_to_entry(file, szFullPath); 1122 file->bFromWildcard = TRUE; 1123 file->attributes = wfd.dwFileAttributes; 1124 1125 if (IsAttribDir(file->attributes)) 1126 flList->bAnyDirectories = TRUE; 1127 1128 HeapFree(GetProcessHeap(), 0, szFullPath); 1129 } 1130 1131 FindClose(hFile); 1132 } 1133 1134 /* takes the null-separated file list and fills out the FILE_LIST */ 1135 static HRESULT parse_file_list(FILE_LIST *flList, LPCWSTR szFiles) 1136 { 1137 LPCWSTR ptr = szFiles; 1138 WCHAR szCurFile[MAX_PATH]; 1139 DWORD i = 0; 1140 1141 if (!szFiles) 1142 return ERROR_INVALID_PARAMETER; 1143 1144 flList->bAnyFromWildcard = FALSE; 1145 flList->bAnyDirectories = FALSE; 1146 flList->bAnyDontExist = FALSE; 1147 flList->num_alloc = 32; 1148 flList->dwNumFiles = 0; 1149 1150 /* empty list */ 1151 if (!szFiles[0]) 1152 return ERROR_ACCESS_DENIED; 1153 1154 flList->feFiles = (FILE_ENTRY *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 1155 flList->num_alloc * sizeof(FILE_ENTRY)); 1156 1157 while (*ptr) 1158 { 1159 if (i >= flList->num_alloc) grow_list( flList ); 1160 1161 /* change relative to absolute path */ 1162 if (PathIsRelativeW(ptr)) 1163 { 1164 GetCurrentDirectoryW(MAX_PATH, szCurFile); 1165 PathCombineW(szCurFile, szCurFile, ptr); 1166 flList->feFiles[i].bFromRelative = TRUE; 1167 } 1168 else 1169 { 1170 lstrcpyW(szCurFile, ptr); 1171 flList->feFiles[i].bFromRelative = FALSE; 1172 } 1173 1174 /* parse wildcard files if they are in the filename */ 1175 if (StrPBrkW(szCurFile, wWildcardChars)) 1176 { 1177 parse_wildcard_files(flList, szCurFile, &i); 1178 flList->bAnyFromWildcard = TRUE; 1179 i--; 1180 } 1181 else 1182 { 1183 FILE_ENTRY *file = &flList->feFiles[i]; 1184 add_file_to_entry(file, szCurFile); 1185 file->attributes = GetFileAttributesW( file->szFullPath ); 1186 file->bExists = (file->attributes != INVALID_FILE_ATTRIBUTES); 1187 1188 if (!file->bExists) 1189 flList->bAnyDontExist = TRUE; 1190 1191 if (IsAttribDir(file->attributes)) 1192 flList->bAnyDirectories = TRUE; 1193 } 1194 1195 /* advance to the next string */ 1196 ptr += lstrlenW(ptr) + 1; 1197 i++; 1198 } 1199 flList->dwNumFiles = i; 1200 1201 return S_OK; 1202 } 1203 1204 /* free the FILE_LIST */ 1205 static void destroy_file_list(FILE_LIST *flList) 1206 { 1207 DWORD i; 1208 1209 if (!flList || !flList->feFiles) 1210 return; 1211 1212 for (i = 0; i < flList->dwNumFiles; i++) 1213 { 1214 HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szDirectory); 1215 HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFilename); 1216 HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFullPath); 1217 } 1218 1219 HeapFree(GetProcessHeap(), 0, flList->feFiles); 1220 } 1221 1222 static void copy_dir_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, LPCWSTR szDestPath) 1223 { 1224 WCHAR szFrom[MAX_PATH], szTo[MAX_PATH]; 1225 FILE_LIST flFromNew, flToNew; 1226 1227 static const WCHAR wildCardFiles[] = {'*','.','*',0}; 1228 1229 if (IsDotDir(feFrom->szFilename)) 1230 return; 1231 1232 if (PathFileExistsW(szDestPath)) 1233 PathCombineW(szTo, szDestPath, feFrom->szFilename); 1234 else 1235 lstrcpyW(szTo, szDestPath); 1236 1237 if (!(op->req->fFlags & FOF_NOCONFIRMATION) && PathFileExistsW(szTo)) 1238 { 1239 if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FOLDER, feFrom->szFilename, op)) 1240 { 1241 /* Vista returns an ERROR_CANCELLED even if user pressed "No" */ 1242 if (!op->bManyItems) 1243 op->bCancelled = TRUE; 1244 return; 1245 } 1246 } 1247 1248 szTo[lstrlenW(szTo) + 1] = '\0'; 1249 SHNotifyCreateDirectoryW(szTo, NULL); 1250 1251 PathCombineW(szFrom, feFrom->szFullPath, wildCardFiles); 1252 szFrom[lstrlenW(szFrom) + 1] = '\0'; 1253 1254 ZeroMemory(&flFromNew, sizeof(FILE_LIST)); 1255 ZeroMemory(&flToNew, sizeof(FILE_LIST)); 1256 parse_file_list(&flFromNew, szFrom); 1257 parse_file_list(&flToNew, szTo); 1258 1259 copy_files(op, FALSE, &flFromNew, &flToNew); 1260 1261 destroy_file_list(&flFromNew); 1262 destroy_file_list(&flToNew); 1263 } 1264 1265 static BOOL copy_file_to_file(FILE_OPERATION *op, const WCHAR *szFrom, const WCHAR *szTo) 1266 { 1267 if (!(op->req->fFlags & FOF_NOCONFIRMATION) && PathFileExistsW(szTo)) 1268 { 1269 if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FILE, PathFindFileNameW(szTo), op)) 1270 return FALSE; 1271 } 1272 1273 return SHNotifyCopyFileW(op, szFrom, szTo, FALSE) == 0; 1274 } 1275 1276 /* copy a file or directory to another directory */ 1277 static void copy_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo) 1278 { 1279 if (!PathFileExistsW(feTo->szFullPath)) 1280 SHNotifyCreateDirectoryW(feTo->szFullPath, NULL); 1281 1282 if (IsAttribFile(feFrom->attributes)) 1283 { 1284 WCHAR szDestPath[MAX_PATH]; 1285 1286 PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename); 1287 copy_file_to_file(op, feFrom->szFullPath, szDestPath); 1288 } 1289 else if (!(op->req->fFlags & FOF_FILESONLY && feFrom->bFromWildcard)) 1290 copy_dir_to_dir(op, feFrom, feTo->szFullPath); 1291 } 1292 1293 static void create_dest_dirs(LPCWSTR szDestDir) 1294 { 1295 WCHAR dir[MAX_PATH]; 1296 LPCWSTR ptr = StrChrW(szDestDir, '\\'); 1297 1298 /* make sure all directories up to last one are created */ 1299 while (ptr && (ptr = StrChrW(ptr + 1, '\\'))) 1300 { 1301 lstrcpynW(dir, szDestDir, ptr - szDestDir + 1); 1302 1303 if (!PathFileExistsW(dir)) 1304 SHNotifyCreateDirectoryW(dir, NULL); 1305 } 1306 1307 /* create last directory */ 1308 if (!PathFileExistsW(szDestDir)) 1309 SHNotifyCreateDirectoryW(szDestDir, NULL); 1310 } 1311 1312 /* the FO_COPY operation */ 1313 static HRESULT copy_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, FILE_LIST *flTo) 1314 { 1315 DWORD i; 1316 const FILE_ENTRY *entryToCopy; 1317 const FILE_ENTRY *fileDest = &flTo->feFiles[0]; 1318 1319 if (flFrom->bAnyDontExist) 1320 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND; 1321 1322 if (flTo->dwNumFiles == 0) 1323 { 1324 /* If the destination is empty, SHFileOperation should use the current directory */ 1325 WCHAR curdir[MAX_PATH+1]; 1326 1327 GetCurrentDirectoryW(MAX_PATH, curdir); 1328 curdir[lstrlenW(curdir)+1] = 0; 1329 1330 destroy_file_list(flTo); 1331 ZeroMemory(flTo, sizeof(FILE_LIST)); 1332 parse_file_list(flTo, curdir); 1333 fileDest = &flTo->feFiles[0]; 1334 } 1335 1336 if (multiDest) 1337 { 1338 if (flFrom->bAnyFromWildcard) 1339 return ERROR_CANCELLED; 1340 1341 if (flFrom->dwNumFiles != flTo->dwNumFiles) 1342 { 1343 if (flFrom->dwNumFiles != 1 && !IsAttribDir(fileDest->attributes)) 1344 return ERROR_CANCELLED; 1345 1346 /* Free all but the first entry. */ 1347 for (i = 1; i < flTo->dwNumFiles; i++) 1348 { 1349 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szDirectory); 1350 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szFilename); 1351 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szFullPath); 1352 } 1353 1354 flTo->dwNumFiles = 1; 1355 } 1356 else if (IsAttribDir(fileDest->attributes)) 1357 { 1358 for (i = 1; i < flTo->dwNumFiles; i++) 1359 if (!IsAttribDir(flTo->feFiles[i].attributes) || 1360 !IsAttribDir(flFrom->feFiles[i].attributes)) 1361 { 1362 return ERROR_CANCELLED; 1363 } 1364 } 1365 } 1366 else if (flFrom->dwNumFiles != 1) 1367 { 1368 if (flTo->dwNumFiles != 1 && !IsAttribDir(fileDest->attributes)) 1369 return ERROR_CANCELLED; 1370 1371 if (PathFileExistsW(fileDest->szFullPath) && 1372 IsAttribFile(fileDest->attributes)) 1373 { 1374 return ERROR_CANCELLED; 1375 } 1376 1377 if (flTo->dwNumFiles == 1 && fileDest->bFromRelative && 1378 !PathFileExistsW(fileDest->szFullPath)) 1379 { 1380 return ERROR_CANCELLED; 1381 } 1382 } 1383 1384 for (i = 0; i < flFrom->dwNumFiles; i++) 1385 { 1386 entryToCopy = &flFrom->feFiles[i]; 1387 1388 if ((multiDest) && 1389 flTo->dwNumFiles > 1) 1390 { 1391 fileDest = &flTo->feFiles[i]; 1392 } 1393 1394 if (IsAttribDir(entryToCopy->attributes) && 1395 !lstrcmpiW(entryToCopy->szFullPath, fileDest->szDirectory)) 1396 { 1397 return ERROR_SUCCESS; 1398 } 1399 1400 create_dest_dirs(fileDest->szDirectory); 1401 1402 if (!lstrcmpiW(entryToCopy->szFullPath, fileDest->szFullPath)) 1403 { 1404 if (IsAttribFile(entryToCopy->attributes)) 1405 return ERROR_NO_MORE_SEARCH_HANDLES; 1406 else 1407 return ERROR_SUCCESS; 1408 } 1409 1410 if ((flFrom->dwNumFiles > 1 && flTo->dwNumFiles == 1) || 1411 IsAttribDir(fileDest->attributes)) 1412 { 1413 copy_to_dir(op, entryToCopy, fileDest); 1414 } 1415 else if (IsAttribDir(entryToCopy->attributes)) 1416 { 1417 copy_dir_to_dir(op, entryToCopy, fileDest->szFullPath); 1418 } 1419 else 1420 { 1421 if (!copy_file_to_file(op, entryToCopy->szFullPath, fileDest->szFullPath)) 1422 { 1423 op->req->fAnyOperationsAborted = TRUE; 1424 return ERROR_CANCELLED; 1425 } 1426 } 1427 1428 if (op->progress != NULL) 1429 op->bCancelled |= op->progress->HasUserCancelled(); 1430 /* Vista return code. XP would return e.g. ERROR_FILE_NOT_FOUND, ERROR_ALREADY_EXISTS */ 1431 if (op->bCancelled) 1432 return ERROR_CANCELLED; 1433 } 1434 1435 /* Vista return code. On XP if the used pressed "No" for the last item, 1436 * ERROR_ARENA_TRASHED would be returned */ 1437 return ERROR_SUCCESS; 1438 } 1439 1440 static BOOL confirm_delete_list(HWND hWnd, DWORD fFlags, BOOL fTrash, const FILE_LIST *flFrom) 1441 { 1442 if (flFrom->dwNumFiles > 1) 1443 { 1444 WCHAR tmp[8]; 1445 const WCHAR format[] = {'%','d',0}; 1446 1447 wnsprintfW(tmp, sizeof(tmp)/sizeof(tmp[0]), format, flFrom->dwNumFiles); 1448 return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_MULTIPLE_ITEM:ASK_DELETE_MULTIPLE_ITEM), tmp, NULL); 1449 } 1450 else 1451 { 1452 const FILE_ENTRY *fileEntry = &flFrom->feFiles[0]; 1453 1454 if (IsAttribFile(fileEntry->attributes)) 1455 return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FILE:ASK_DELETE_FILE), fileEntry->szFullPath, NULL); 1456 else if (!(fFlags & FOF_FILESONLY && fileEntry->bFromWildcard)) 1457 return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FOLDER:ASK_DELETE_FOLDER), fileEntry->szFullPath, NULL); 1458 } 1459 return TRUE; 1460 } 1461 1462 /* the FO_DELETE operation */ 1463 static HRESULT delete_files(FILE_OPERATION *op, const FILE_LIST *flFrom) 1464 { 1465 const FILE_ENTRY *fileEntry; 1466 DWORD i; 1467 BOOL bPathExists; 1468 BOOL bTrash; 1469 1470 if (!flFrom->dwNumFiles) 1471 return ERROR_SUCCESS; 1472 1473 /* Windows also checks only the first item */ 1474 bTrash = (op->req->fFlags & FOF_ALLOWUNDO) 1475 && TRASH_CanTrashFile(flFrom->feFiles[0].szFullPath); 1476 1477 if (!(op->req->fFlags & FOF_NOCONFIRMATION) || (!bTrash && op->req->fFlags & FOF_WANTNUKEWARNING)) 1478 if (!confirm_delete_list(op->req->hwnd, op->req->fFlags, bTrash, flFrom)) 1479 { 1480 op->req->fAnyOperationsAborted = TRUE; 1481 return 0; 1482 } 1483 1484 /* Check files. Do not delete one if one file does not exists */ 1485 for (i = 0; i < flFrom->dwNumFiles; i++) 1486 { 1487 fileEntry = &flFrom->feFiles[i]; 1488 1489 if ((HANDLE)fileEntry->attributes == INVALID_HANDLE_VALUE) 1490 { 1491 // This is a windows 2003 server specific value which has been removed. 1492 // Later versions of windows return ERROR_FILE_NOT_FOUND. 1493 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND; 1494 } 1495 } 1496 1497 for (i = 0; i < flFrom->dwNumFiles; i++) 1498 { 1499 bPathExists = TRUE; 1500 fileEntry = &flFrom->feFiles[i]; 1501 1502 if (!IsAttribFile(fileEntry->attributes) && 1503 (op->req->fFlags & FOF_FILESONLY && fileEntry->bFromWildcard)) 1504 continue; 1505 1506 if (bTrash) 1507 { 1508 BOOL bDelete; 1509 if (TRASH_TrashFile(fileEntry->szFullPath)) 1510 { 1511 SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, fileEntry->szFullPath, NULL); 1512 continue; 1513 } 1514 1515 /* Note: Windows silently deletes the file in such a situation, we show a dialog */ 1516 if (!(op->req->fFlags & FOF_NOCONFIRMATION) || (op->req->fFlags & FOF_WANTNUKEWARNING)) 1517 bDelete = SHELL_ConfirmDialogW(op->req->hwnd, ASK_CANT_TRASH_ITEM, fileEntry->szFullPath, NULL); 1518 else 1519 bDelete = TRUE; 1520 1521 if (!bDelete) 1522 { 1523 op->req->fAnyOperationsAborted = TRUE; 1524 break; 1525 } 1526 } 1527 1528 /* delete the file or directory */ 1529 if (IsAttribFile(fileEntry->attributes)) 1530 { 1531 bPathExists = (ERROR_SUCCESS == SHNotifyDeleteFileW(op, fileEntry->szFullPath)); 1532 } 1533 else 1534 bPathExists = SHELL_DeleteDirectoryW(op, fileEntry->szFullPath, FALSE); 1535 1536 if (!bPathExists) 1537 { 1538 DWORD err = GetLastError(); 1539 1540 if (ERROR_FILE_NOT_FOUND == err) 1541 { 1542 // This is a windows 2003 server specific value which ahs been removed. 1543 // Later versions of windows return ERROR_FILE_NOT_FOUND. 1544 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND; 1545 } 1546 else 1547 { 1548 return err; 1549 } 1550 } 1551 1552 if (op->progress != NULL) 1553 op->bCancelled |= op->progress->HasUserCancelled(); 1554 /* Should fire on progress dialog only */ 1555 if (op->bCancelled) 1556 return ERROR_CANCELLED; 1557 } 1558 1559 return ERROR_SUCCESS; 1560 } 1561 1562 static void move_dir_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, LPCWSTR szDestPath) 1563 { 1564 WCHAR szFrom[MAX_PATH], szTo[MAX_PATH]; 1565 FILE_LIST flFromNew, flToNew; 1566 1567 static const WCHAR wildCardFiles[] = {'*','.','*',0}; 1568 1569 if (IsDotDir(feFrom->szFilename)) 1570 return; 1571 1572 SHNotifyCreateDirectoryW(szDestPath, NULL); 1573 1574 PathCombineW(szFrom, feFrom->szFullPath, wildCardFiles); 1575 szFrom[lstrlenW(szFrom) + 1] = '\0'; 1576 1577 lstrcpyW(szTo, szDestPath); 1578 szTo[lstrlenW(szDestPath) + 1] = '\0'; 1579 1580 ZeroMemory(&flFromNew, sizeof(FILE_LIST)); 1581 ZeroMemory(&flToNew, sizeof(FILE_LIST)); 1582 parse_file_list(&flFromNew, szFrom); 1583 parse_file_list(&flToNew, szTo); 1584 1585 move_files(op, FALSE, &flFromNew, &flToNew); 1586 1587 destroy_file_list(&flFromNew); 1588 destroy_file_list(&flToNew); 1589 } 1590 1591 /* moves a file or directory to another directory */ 1592 static void move_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo) 1593 { 1594 WCHAR szDestPath[MAX_PATH]; 1595 1596 PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename); 1597 1598 if (IsAttribFile(feFrom->attributes)) 1599 SHNotifyMoveFileW(op, feFrom->szFullPath, szDestPath, FALSE); 1600 else if (!(op->req->fFlags & FOF_FILESONLY && feFrom->bFromWildcard)) 1601 move_dir_to_dir(op, feFrom, szDestPath); 1602 } 1603 1604 /* the FO_MOVE operation */ 1605 static DWORD move_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, const FILE_LIST *flTo) 1606 { 1607 DWORD i; 1608 INT mismatched = 0; 1609 1610 const FILE_ENTRY *entryToMove; 1611 const FILE_ENTRY *fileDest; 1612 1613 if (!flFrom->dwNumFiles) 1614 return ERROR_SUCCESS; 1615 1616 if (!flTo->dwNumFiles) 1617 return ERROR_FILE_NOT_FOUND; 1618 1619 if (!(multiDest) && 1620 flTo->dwNumFiles > 1 && flFrom->dwNumFiles > 1) 1621 { 1622 return ERROR_CANCELLED; 1623 } 1624 1625 if (!(multiDest) && 1626 !flFrom->bAnyDirectories && 1627 flFrom->dwNumFiles > flTo->dwNumFiles) 1628 { 1629 return ERROR_CANCELLED; 1630 } 1631 1632 if (!PathFileExistsW(flTo->feFiles[0].szDirectory)) 1633 return ERROR_CANCELLED; 1634 1635 if (multiDest) 1636 mismatched = flFrom->dwNumFiles - flTo->dwNumFiles; 1637 1638 fileDest = &flTo->feFiles[0]; 1639 for (i = 0; i < flFrom->dwNumFiles; i++) 1640 { 1641 entryToMove = &flFrom->feFiles[i]; 1642 1643 if (!PathFileExistsW(fileDest->szDirectory)) 1644 return ERROR_CANCELLED; 1645 1646 if (multiDest) 1647 { 1648 if (i >= flTo->dwNumFiles) 1649 break; 1650 fileDest = &flTo->feFiles[i]; 1651 if (mismatched && !fileDest->bExists) 1652 { 1653 create_dest_dirs(flTo->feFiles[i].szFullPath); 1654 flTo->feFiles[i].bExists = TRUE; 1655 flTo->feFiles[i].attributes = FILE_ATTRIBUTE_DIRECTORY; 1656 } 1657 } 1658 1659 if (fileDest->bExists && IsAttribDir(fileDest->attributes)) 1660 move_to_dir(op, entryToMove, fileDest); 1661 else 1662 SHNotifyMoveFileW(op, entryToMove->szFullPath, fileDest->szFullPath, IsAttribDir(entryToMove->attributes)); 1663 1664 if (op->progress != NULL) 1665 op->bCancelled |= op->progress->HasUserCancelled(); 1666 /* Should fire on progress dialog only */ 1667 if (op->bCancelled) 1668 return ERROR_CANCELLED; 1669 1670 } 1671 1672 if (mismatched > 0) 1673 { 1674 if (flFrom->bAnyDirectories) 1675 return DE_DESTSAMETREE; 1676 else 1677 return DE_SAMEFILE; 1678 } 1679 1680 return ERROR_SUCCESS; 1681 } 1682 1683 /* the FO_RENAME files */ 1684 static HRESULT rename_files(FILE_OPERATION *op, const FILE_LIST *flFrom, const FILE_LIST *flTo) 1685 { 1686 const FILE_ENTRY *feFrom; 1687 const FILE_ENTRY *feTo; 1688 1689 if (flFrom->dwNumFiles != 1) 1690 return ERROR_GEN_FAILURE; 1691 1692 if (flTo->dwNumFiles != 1) 1693 return ERROR_CANCELLED; 1694 1695 feFrom = &flFrom->feFiles[0]; 1696 feTo= &flTo->feFiles[0]; 1697 1698 /* fail if destination doesn't exist */ 1699 if (!feFrom->bExists) 1700 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND; 1701 1702 /* fail if destination already exists */ 1703 if (feTo->bExists) 1704 return ERROR_ALREADY_EXISTS; 1705 1706 return SHNotifyMoveFileW(op, feFrom->szFullPath, feTo->szFullPath, IsAttribDir(feFrom->attributes)); 1707 } 1708 1709 /* alert the user if an unsupported flag is used */ 1710 static void check_flags(FILEOP_FLAGS fFlags) 1711 { 1712 WORD wUnsupportedFlags = FOF_NO_CONNECTED_ELEMENTS | 1713 FOF_NOCOPYSECURITYATTRIBS | FOF_NORECURSEREPARSE | 1714 FOF_RENAMEONCOLLISION | FOF_WANTMAPPINGHANDLE; 1715 1716 if (fFlags & wUnsupportedFlags) 1717 FIXME("Unsupported flags: %04x\n", fFlags); 1718 } 1719 1720 /************************************************************************* 1721 * SHFileOperationW [SHELL32.@] 1722 * 1723 * See SHFileOperationA 1724 */ 1725 int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp) 1726 { 1727 FILE_OPERATION op; 1728 FILE_LIST flFrom, flTo; 1729 int ret = 0; 1730 1731 if (!lpFileOp) 1732 return ERROR_INVALID_PARAMETER; 1733 1734 ret = CoInitialize(NULL); 1735 if (FAILED(ret)) 1736 return ret; 1737 1738 check_flags(lpFileOp->fFlags); 1739 1740 ZeroMemory(&flFrom, sizeof(FILE_LIST)); 1741 ZeroMemory(&flTo, sizeof(FILE_LIST)); 1742 1743 if ((ret = parse_file_list(&flFrom, lpFileOp->pFrom))) 1744 return ret; 1745 1746 if (lpFileOp->wFunc != FO_DELETE) 1747 parse_file_list(&flTo, lpFileOp->pTo); 1748 1749 ZeroMemory(&op, sizeof(op)); 1750 op.req = lpFileOp; 1751 op.totalSize.QuadPart = 0ull; 1752 op.completedSize.QuadPart = 0ull; 1753 op.bManyItems = (flFrom.dwNumFiles > 1); 1754 1755 if (lpFileOp->wFunc != FO_RENAME && !(lpFileOp->fFlags & FOF_SILENT)) { 1756 ret = CoCreateInstance(CLSID_ProgressDialog, 1757 NULL, 1758 CLSCTX_INPROC_SERVER, 1759 IID_PPV_ARG(IProgressDialog, &op.progress)); 1760 if (FAILED(ret)) 1761 goto cleanup; 1762 1763 op.progress->StartProgressDialog(op.req->hwnd, NULL, PROGDLG_NORMAL & PROGDLG_AUTOTIME, NULL); 1764 _SetOperationTitle(&op); 1765 _FileOpCountManager(&op, &flFrom); 1766 } 1767 1768 switch (lpFileOp->wFunc) 1769 { 1770 case FO_COPY: 1771 ret = copy_files(&op, op.req->fFlags & FOF_MULTIDESTFILES, &flFrom, &flTo); 1772 break; 1773 case FO_DELETE: 1774 ret = delete_files(&op, &flFrom); 1775 break; 1776 case FO_MOVE: 1777 ret = move_files(&op, op.req->fFlags & FOF_MULTIDESTFILES, &flFrom, &flTo); 1778 break; 1779 case FO_RENAME: 1780 ret = rename_files(&op, &flFrom, &flTo); 1781 break; 1782 default: 1783 ret = ERROR_INVALID_PARAMETER; 1784 break; 1785 } 1786 1787 if (op.progress) { 1788 op.progress->StopProgressDialog(); 1789 op.progress->Release(); 1790 } 1791 1792 cleanup: 1793 destroy_file_list(&flFrom); 1794 1795 if (lpFileOp->wFunc != FO_DELETE) 1796 destroy_file_list(&flTo); 1797 1798 if (ret == ERROR_CANCELLED) 1799 lpFileOp->fAnyOperationsAborted = TRUE; 1800 1801 CoUninitialize(); 1802 1803 return ret; 1804 } 1805 1806 // Used by SHFreeNameMappings 1807 static int CALLBACK _DestroyCallback(void *p, void *pData) 1808 { 1809 LPSHNAMEMAPPINGW lp = (SHNAMEMAPPINGW *)p; 1810 1811 SHFree(lp->pszOldPath); 1812 SHFree(lp->pszNewPath); 1813 1814 return TRUE; 1815 } 1816 1817 /************************************************************************* 1818 * SHFreeNameMappings [shell32.246] 1819 * 1820 * Free the mapping handle returned by SHFileOperation if FOF_WANTSMAPPINGHANDLE 1821 * was specified. 1822 * 1823 * PARAMS 1824 * hNameMapping [I] handle to the name mappings used during renaming of files 1825 * 1826 * RETURNS 1827 * Nothing 1828 */ 1829 void WINAPI SHFreeNameMappings(HANDLE hNameMapping) 1830 { 1831 if (hNameMapping) 1832 { 1833 DSA_DestroyCallback((HDSA) hNameMapping, _DestroyCallback, NULL); 1834 } 1835 } 1836 1837 /************************************************************************* 1838 * SheGetDirA [SHELL32.@] 1839 * 1840 * drive = 0: returns the current directory path 1841 * drive > 0: returns the current directory path of the specified drive 1842 * drive=1 -> A: drive=2 -> B: ... 1843 * returns 0 if successful 1844 */ 1845 EXTERN_C DWORD WINAPI SheGetDirA(DWORD drive, LPSTR buffer) 1846 { 1847 WCHAR org_path[MAX_PATH]; 1848 DWORD ret; 1849 char drv_path[3]; 1850 1851 /* change current directory to the specified drive */ 1852 if (drive) { 1853 strcpy(drv_path, "A:"); 1854 drv_path[0] += (char)drive-1; 1855 1856 GetCurrentDirectoryW(MAX_PATH, org_path); 1857 1858 SetCurrentDirectoryA(drv_path); 1859 } 1860 1861 /* query current directory path of the specified drive */ 1862 ret = GetCurrentDirectoryA(MAX_PATH, buffer); 1863 1864 /* back to the original drive */ 1865 if (drive) 1866 SetCurrentDirectoryW(org_path); 1867 1868 if (!ret) 1869 return GetLastError(); 1870 1871 return 0; 1872 } 1873 1874 /************************************************************************* 1875 * SheGetDirW [SHELL32.@] 1876 * 1877 * drive = 0: returns the current directory path 1878 * drive > 0: returns the current directory path of the specified drive 1879 * drive=1 -> A: drive=2 -> B: ... 1880 * returns 0 if successful 1881 */ 1882 EXTERN_C DWORD WINAPI SheGetDirW(DWORD drive, LPWSTR buffer) 1883 { 1884 WCHAR org_path[MAX_PATH]; 1885 DWORD ret; 1886 char drv_path[3]; 1887 1888 /* change current directory to the specified drive */ 1889 if (drive) 1890 { 1891 strcpy(drv_path, "A:"); 1892 drv_path[0] += (char)drive-1; 1893 1894 GetCurrentDirectoryW(MAX_PATH, org_path); 1895 1896 SetCurrentDirectoryA(drv_path); 1897 } 1898 1899 /* query current directory path of the specified drive */ 1900 ret = GetCurrentDirectoryW(MAX_PATH, buffer); 1901 1902 /* back to the original drive */ 1903 if (drive) 1904 SetCurrentDirectoryW(org_path); 1905 1906 if (!ret) 1907 return GetLastError(); 1908 1909 return 0; 1910 } 1911 1912 /************************************************************************* 1913 * SheChangeDirA [SHELL32.@] 1914 * 1915 * changes the current directory to the specified path 1916 * and returns 0 if successful 1917 */ 1918 EXTERN_C DWORD WINAPI SheChangeDirA(LPSTR path) 1919 { 1920 if (SetCurrentDirectoryA(path)) 1921 return 0; 1922 else 1923 return GetLastError(); 1924 } 1925 1926 /************************************************************************* 1927 * SheChangeDirW [SHELL32.@] 1928 * 1929 * changes the current directory to the specified path 1930 * and returns 0 if successful 1931 */ 1932 EXTERN_C DWORD WINAPI SheChangeDirW(LPWSTR path) 1933 { 1934 if (SetCurrentDirectoryW(path)) 1935 return 0; 1936 else 1937 return GetLastError(); 1938 } 1939 1940 /************************************************************************* 1941 * IsNetDrive [SHELL32.66] 1942 */ 1943 EXTERN_C int WINAPI IsNetDrive(int drive) 1944 { 1945 char root[4]; 1946 strcpy(root, "A:\\"); 1947 root[0] += (char)drive; 1948 return (GetDriveTypeA(root) == DRIVE_REMOTE); 1949 } 1950 1951 1952 /************************************************************************* 1953 * RealDriveType [SHELL32.524] 1954 */ 1955 EXTERN_C INT WINAPI RealDriveType(INT drive, BOOL bQueryNet) 1956 { 1957 char root[] = "A:\\"; 1958 root[0] += (char)drive; 1959 return GetDriveTypeA(root); 1960 } 1961 1962 /*********************************************************************** 1963 * SHPathPrepareForWriteW (SHELL32.@) 1964 */ 1965 EXTERN_C HRESULT WINAPI SHPathPrepareForWriteW(HWND hwnd, IUnknown *modless, LPCWSTR path, DWORD flags) 1966 { 1967 DWORD res; 1968 DWORD err; 1969 LPCWSTR realpath; 1970 int len; 1971 WCHAR* last_slash; 1972 WCHAR* temppath=NULL; 1973 1974 TRACE("%p %p %s 0x%08x\n", hwnd, modless, debugstr_w(path), flags); 1975 1976 if (flags & ~(SHPPFW_DIRCREATE|SHPPFW_ASKDIRCREATE|SHPPFW_IGNOREFILENAME)) 1977 FIXME("unimplemented flags 0x%08x\n", flags); 1978 1979 /* cut off filename if necessary */ 1980 if (flags & SHPPFW_IGNOREFILENAME) 1981 { 1982 last_slash = StrRChrW(path, NULL, '\\'); 1983 if (last_slash == NULL) 1984 len = 1; 1985 else 1986 len = last_slash - path + 1; 1987 temppath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); 1988 if (!temppath) 1989 return E_OUTOFMEMORY; 1990 StrCpyNW(temppath, path, len); 1991 realpath = temppath; 1992 } 1993 else 1994 { 1995 realpath = path; 1996 } 1997 1998 /* try to create the directory if asked to */ 1999 if (flags & (SHPPFW_DIRCREATE|SHPPFW_ASKDIRCREATE)) 2000 { 2001 if (flags & SHPPFW_ASKDIRCREATE) 2002 FIXME("treating SHPPFW_ASKDIRCREATE as SHPPFW_DIRCREATE\n"); 2003 2004 SHCreateDirectoryExW(0, realpath, NULL); 2005 } 2006 2007 /* check if we can access the directory */ 2008 res = GetFileAttributesW(realpath); 2009 2010 HeapFree(GetProcessHeap(), 0, temppath); 2011 2012 if (res == INVALID_FILE_ATTRIBUTES) 2013 { 2014 err = GetLastError(); 2015 if (err == ERROR_FILE_NOT_FOUND) 2016 return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); 2017 return HRESULT_FROM_WIN32(err); 2018 } 2019 else if (res & FILE_ATTRIBUTE_DIRECTORY) 2020 return S_OK; 2021 else 2022 return HRESULT_FROM_WIN32(ERROR_DIRECTORY); 2023 } 2024 2025 /*********************************************************************** 2026 * SHPathPrepareForWriteA (SHELL32.@) 2027 */ 2028 EXTERN_C HRESULT WINAPI SHPathPrepareForWriteA(HWND hwnd, IUnknown *modless, LPCSTR path, DWORD flags) 2029 { 2030 WCHAR wpath[MAX_PATH]; 2031 MultiByteToWideChar( CP_ACP, 0, path, -1, wpath, MAX_PATH); 2032 return SHPathPrepareForWriteW(hwnd, modless, wpath, flags); 2033 } 2034 2035 2036 /* 2037 * The two following background operations were modified from filedefext.cpp 2038 * They use an inordinate amount of mutable state across the string functions, 2039 * so are not easy to follow and care is required when modifying. 2040 */ 2041 2042 DWORD WINAPI 2043 _FileOpCountManager(FILE_OPERATION *op, const FILE_LIST *from) 2044 { 2045 DWORD ticks = GetTickCount(); 2046 FILE_ENTRY *entryToCount; 2047 2048 for (UINT i = 0; i < from->dwNumFiles; i++) 2049 { 2050 entryToCount = &from->feFiles[i]; 2051 2052 WCHAR theFileName[MAX_PATH]; 2053 StringCchCopyW(theFileName, MAX_PATH, entryToCount->szFullPath); 2054 _FileOpCount(op, theFileName, IsAttribDir(entryToCount->attributes), &ticks); 2055 } 2056 return 0; 2057 } 2058 2059 // All path manipulations, even when this function is nested, occur on the one buffer. 2060 static BOOL 2061 _FileOpCount(FILE_OPERATION *op, LPWSTR pwszBuf, BOOL bFolder, DWORD *ticks) 2062 { 2063 /* Find filename position */ 2064 UINT cchBuf = wcslen(pwszBuf); 2065 WCHAR *pwszFilename = pwszBuf + cchBuf; 2066 size_t cchFilenameMax = MAX_PATH - cchBuf; 2067 if (!cchFilenameMax) 2068 return FALSE; 2069 2070 if (bFolder) { 2071 *(pwszFilename++) = '\\'; 2072 --cchFilenameMax; 2073 /* Find all files, FIXME: shouldn't be "*"? */ 2074 StringCchCopyW(pwszFilename, cchFilenameMax, L"*"); 2075 } 2076 2077 WIN32_FIND_DATAW wfd; 2078 HANDLE hFind = FindFirstFileW(pwszBuf, &wfd); 2079 if (hFind == INVALID_HANDLE_VALUE) 2080 { 2081 ERR("FindFirstFileW %ls failed\n", pwszBuf); 2082 return FALSE; 2083 } 2084 2085 do 2086 { 2087 if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 2088 { 2089 /* Don't process "." and ".." items */ 2090 if (!wcscmp(wfd.cFileName, L".") || !wcscmp(wfd.cFileName, L"..")) 2091 continue; 2092 2093 StringCchCopyW(pwszFilename, cchFilenameMax, wfd.cFileName); 2094 _FileOpCount(op, pwszBuf, TRUE, ticks); 2095 } 2096 else 2097 { 2098 ULARGE_INTEGER FileSize; 2099 FileSize.u.LowPart = wfd.nFileSizeLow; 2100 FileSize.u.HighPart = wfd.nFileSizeHigh; 2101 op->totalSize.QuadPart += FileSize.QuadPart; 2102 } 2103 if (GetTickCount() - *ticks > (DWORD) 500) 2104 { 2105 // Check if the dialog has ended. If it has, we'll spin down. 2106 if (op->progress != NULL) 2107 op->bCancelled = op->progress->HasUserCancelled(); 2108 2109 if (op->bCancelled) 2110 break; 2111 *ticks = GetTickCount(); 2112 } 2113 } while(FindNextFileW(hFind, &wfd)); 2114 2115 FindClose(hFind); 2116 return TRUE; 2117 } 2118