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