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