[SHELL32] Copy-paste files/folders into current place.
[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 *
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 ((HANDLE)fileEntry->attributes == INVALID_HANDLE_VALUE)
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 }