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