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