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