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