1 // FSFolderCopy.cpp
2
3 #include "StdAfx.h"
4
5 #include "../../../Common/MyWindows.h"
6
7 #include <WinBase.h>
8
9 #include "../../../Common/Defs.h"
10 #include "../../../Common/StringConvert.h"
11 #include "../../../Common/Wildcard.h"
12
13 #include "../../../Windows/DLL.h"
14 #include "../../../Windows/ErrorMsg.h"
15 #include "../../../Windows/FileDir.h"
16 #include "../../../Windows/FileName.h"
17
18 #include "../../Common/FilePathAutoRename.h"
19
20 #include "FSFolder.h"
21
22 using namespace NWindows;
23 using namespace NFile;
24 using namespace NDir;
25 using namespace NName;
26 using namespace NFind;
27
28 #ifndef _UNICODE
29 extern bool g_IsNT;
30 #endif
31
32 namespace NFsFolder {
33
MyCopyFile(CFSTR inPath,CFSTR outPath,DWORD attrib)34 HRESULT CCopyStateIO::MyCopyFile(CFSTR inPath, CFSTR outPath, DWORD attrib)
35 {
36 ErrorFileIndex = -1;
37 ErrorMessage.Empty();
38 CurrentSize = 0;
39
40 {
41 const size_t kBufSize = 1 << 16;
42 CByteArr buf(kBufSize);
43
44 NIO::CInFile inFile;
45 NIO::COutFile outFile;
46
47 if (!inFile.Open(inPath))
48 {
49 ErrorFileIndex = 0;
50 return S_OK;
51 }
52
53 if (!outFile.Create(outPath, true))
54 {
55 ErrorFileIndex = 1;
56 return S_OK;
57 }
58
59 for (;;)
60 {
61 UInt32 num;
62 if (!inFile.Read(buf, kBufSize, num))
63 {
64 ErrorFileIndex = 0;
65 return S_OK;
66 }
67 if (num == 0)
68 break;
69
70 UInt32 written = 0;
71 if (!outFile.Write(buf, num, written))
72 {
73 ErrorFileIndex = 1;
74 return S_OK;
75 }
76 if (written != num)
77 {
78 ErrorMessage = "Write error";
79 return S_OK;
80 }
81 CurrentSize += num;
82 if (Progress)
83 {
84 UInt64 completed = StartPos + CurrentSize;
85 RINOK(Progress->SetCompleted(&completed));
86 }
87 }
88 }
89
90 if (attrib != INVALID_FILE_ATTRIBUTES)
91 SetFileAttrib(outPath, attrib);
92
93 if (DeleteSrcFile)
94 {
95 if (!DeleteFileAlways(inPath))
96 {
97 ErrorFileIndex = 0;
98 return S_OK;
99 }
100 }
101
102 return S_OK;
103 }
104
105
106 /*
107 static bool IsItWindows2000orHigher()
108 {
109 OSVERSIONINFO versionInfo;
110 versionInfo.dwOSVersionInfoSize = sizeof(versionInfo);
111 if (!::GetVersionEx(&versionInfo))
112 return false;
113 return (versionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) &&
114 (versionInfo.dwMajorVersion >= 5);
115 }
116 */
117
118 struct CProgressInfo
119 {
120 UInt64 TotalSize;
121 UInt64 StartPos;
122 UInt64 FileSize;
123 IProgress *Progress;
124 HRESULT ProgressResult;
125
InitNFsFolder::CProgressInfo126 void Init() { ProgressResult = S_OK; }
127 };
128
129 #ifndef PROGRESS_CONTINUE
130
131 #define PROGRESS_CONTINUE 0
132 #define PROGRESS_CANCEL 1
133
134 #define COPY_FILE_FAIL_IF_EXISTS 0x00000001
135
136 typedef
137 DWORD
138 (WINAPI* LPPROGRESS_ROUTINE)(
139 LARGE_INTEGER TotalFileSize,
140 LARGE_INTEGER TotalBytesTransferred,
141 LARGE_INTEGER StreamSize,
142 LARGE_INTEGER StreamBytesTransferred,
143 DWORD dwStreamNumber,
144 DWORD dwCallbackReason,
145 HANDLE hSourceFile,
146 HANDLE hDestinationFile,
147 LPVOID lpData
148 );
149
150 #endif
151
CopyProgressRoutine(LARGE_INTEGER TotalFileSize,LARGE_INTEGER TotalBytesTransferred,LARGE_INTEGER,LARGE_INTEGER,DWORD,DWORD,HANDLE,HANDLE,LPVOID lpData)152 static DWORD CALLBACK CopyProgressRoutine(
153 LARGE_INTEGER TotalFileSize, // file size
154 LARGE_INTEGER TotalBytesTransferred, // bytes transferred
155 LARGE_INTEGER /* StreamSize */, // bytes in stream
156 LARGE_INTEGER /* StreamBytesTransferred */, // bytes transferred for stream
157 DWORD /* dwStreamNumber */, // current stream
158 DWORD /* dwCallbackReason */, // callback reason
159 HANDLE /* hSourceFile */, // handle to source file
160 HANDLE /* hDestinationFile */, // handle to destination file
161 LPVOID lpData // from CopyFileEx
162 )
163 {
164 // StreamSize = StreamSize;
165 // StreamBytesTransferred = StreamBytesTransferred;
166 // dwStreamNumber = dwStreamNumber;
167 // dwCallbackReason = dwCallbackReason;
168
169 CProgressInfo &pi = *(CProgressInfo *)lpData;
170
171 if ((UInt64)TotalFileSize.QuadPart > pi.FileSize)
172 {
173 pi.TotalSize += (UInt64)TotalFileSize.QuadPart - pi.FileSize;
174 pi.FileSize = (UInt64)TotalFileSize.QuadPart;
175 pi.ProgressResult = pi.Progress->SetTotal(pi.TotalSize);
176 }
177 UInt64 completed = pi.StartPos + TotalBytesTransferred.QuadPart;
178 pi.ProgressResult = pi.Progress->SetCompleted(&completed);
179 return (pi.ProgressResult == S_OK ? PROGRESS_CONTINUE : PROGRESS_CANCEL);
180 }
181
182 typedef BOOL (WINAPI * Func_CopyFileExA)(
183 IN LPCSTR lpExistingFileName,
184 IN LPCSTR lpNewFileName,
185 IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
186 IN LPVOID lpData OPTIONAL,
187 IN LPBOOL pbCancel OPTIONAL,
188 IN DWORD dwCopyFlags
189 );
190
191 typedef BOOL (WINAPI * Func_CopyFileExW)(
192 IN LPCWSTR lpExistingFileName,
193 IN LPCWSTR lpNewFileName,
194 IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
195 IN LPVOID lpData OPTIONAL,
196 IN LPBOOL pbCancel OPTIONAL,
197 IN DWORD dwCopyFlags
198 );
199
200 typedef BOOL (WINAPI * Func_MoveFileWithProgressW)(
201 IN LPCWSTR lpExistingFileName,
202 IN LPCWSTR lpNewFileName,
203 IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
204 IN LPVOID lpData OPTIONAL,
205 IN DWORD dwFlags
206 );
207
208 struct CCopyState
209 {
210 CProgressInfo ProgressInfo;
211 IFolderOperationsExtractCallback *Callback;
212 bool MoveMode;
213 bool UseReadWriteMode;
214
215 Func_CopyFileExW my_CopyFileExW;
216 #ifndef UNDER_CE
217 Func_MoveFileWithProgressW my_MoveFileWithProgressW;
218 #endif
219 #ifndef _UNICODE
220 Func_CopyFileExA my_CopyFileExA;
221 #endif
222
223 void Prepare();
224 bool CopyFile_NT(const wchar_t *oldFile, const wchar_t *newFile);
225 bool CopyFile_Sys(CFSTR oldFile, CFSTR newFile);
226 bool MoveFile_Sys(CFSTR oldFile, CFSTR newFile);
227
228 HRESULT CallProgress();
229
IsCallbackProgressErrorNFsFolder::CCopyState230 bool IsCallbackProgressError() { return ProgressInfo.ProgressResult != S_OK; }
231 };
232
CallProgress()233 HRESULT CCopyState::CallProgress()
234 {
235 return ProgressInfo.Progress->SetCompleted(&ProgressInfo.StartPos);
236 }
237
Prepare()238 void CCopyState::Prepare()
239 {
240 my_CopyFileExW = NULL;
241 #ifndef UNDER_CE
242 my_MoveFileWithProgressW = NULL;
243 #endif
244 #ifndef _UNICODE
245 my_CopyFileExA = NULL;
246 if (!g_IsNT)
247 {
248 my_CopyFileExA = (Func_CopyFileExA)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "CopyFileExA");
249 }
250 else
251 #endif
252 {
253 HMODULE module = ::GetModuleHandleW(
254 #ifdef UNDER_CE
255 L"coredll.dll"
256 #else
257 L"kernel32.dll"
258 #endif
259 );
260 my_CopyFileExW = (Func_CopyFileExW)My_GetProcAddress(module, "CopyFileExW");
261 #ifndef UNDER_CE
262 my_MoveFileWithProgressW = (Func_MoveFileWithProgressW)My_GetProcAddress(module, "MoveFileWithProgressW");
263 #endif
264 }
265 }
266
267 /* WinXP-64:
268 CopyFileW(fromFile, toFile:altStream)
269 OK - there are NO alt streams in fromFile
270 ERROR_INVALID_PARAMETER - there are alt streams in fromFile
271 */
272
CopyFile_NT(const wchar_t * oldFile,const wchar_t * newFile)273 bool CCopyState::CopyFile_NT(const wchar_t *oldFile, const wchar_t *newFile)
274 {
275 BOOL cancelFlag = FALSE;
276 if (my_CopyFileExW)
277 return BOOLToBool(my_CopyFileExW(oldFile, newFile, CopyProgressRoutine,
278 &ProgressInfo, &cancelFlag, COPY_FILE_FAIL_IF_EXISTS));
279 return BOOLToBool(::CopyFileW(oldFile, newFile, TRUE));
280 }
281
CopyFile_Sys(CFSTR oldFile,CFSTR newFile)282 bool CCopyState::CopyFile_Sys(CFSTR oldFile, CFSTR newFile)
283 {
284 #ifndef _UNICODE
285 if (!g_IsNT)
286 {
287 if (my_CopyFileExA)
288 {
289 BOOL cancelFlag = FALSE;
290 if (my_CopyFileExA(fs2fas(oldFile), fs2fas(newFile),
291 CopyProgressRoutine, &ProgressInfo, &cancelFlag, COPY_FILE_FAIL_IF_EXISTS))
292 return true;
293 if (::GetLastError() != ERROR_CALL_NOT_IMPLEMENTED)
294 return false;
295 }
296 return BOOLToBool(::CopyFile(fs2fas(oldFile), fs2fas(newFile), TRUE));
297 }
298 else
299 #endif
300 {
301 IF_USE_MAIN_PATH_2(oldFile, newFile)
302 {
303 if (CopyFile_NT(fs2us(oldFile), fs2us(newFile)))
304 return true;
305 }
306 #ifdef WIN_LONG_PATH
307 if (USE_SUPER_PATH_2)
308 {
309 if (IsCallbackProgressError())
310 return false;
311 UString superPathOld, superPathNew;
312 if (!GetSuperPaths(oldFile, newFile, superPathOld, superPathNew, USE_MAIN_PATH_2))
313 return false;
314 if (CopyFile_NT(superPathOld, superPathNew))
315 return true;
316 }
317 #endif
318 return false;
319 }
320 }
321
MoveFile_Sys(CFSTR oldFile,CFSTR newFile)322 bool CCopyState::MoveFile_Sys(CFSTR oldFile, CFSTR newFile)
323 {
324 #ifndef UNDER_CE
325 // if (IsItWindows2000orHigher())
326 // {
327 if (my_MoveFileWithProgressW)
328 {
329 IF_USE_MAIN_PATH_2(oldFile, newFile)
330 {
331 if (my_MoveFileWithProgressW(fs2us(oldFile), fs2us(newFile), CopyProgressRoutine,
332 &ProgressInfo, MOVEFILE_COPY_ALLOWED))
333 return true;
334 }
335 #ifdef WIN_LONG_PATH
336 if ((!(USE_MAIN_PATH_2) || ::GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) && USE_SUPER_PATH_2)
337 {
338 if (IsCallbackProgressError())
339 return false;
340 UString superPathOld, superPathNew;
341 if (!GetSuperPaths(oldFile, newFile, superPathOld, superPathNew, USE_MAIN_PATH_2))
342 return false;
343 if (my_MoveFileWithProgressW(superPathOld, superPathNew, CopyProgressRoutine,
344 &ProgressInfo, MOVEFILE_COPY_ALLOWED))
345 return true;
346 }
347 #endif
348 if (::GetLastError() != ERROR_CALL_NOT_IMPLEMENTED)
349 return false;
350 }
351 // }
352 // else
353 #endif
354 return MyMoveFile(oldFile, newFile);
355 }
356
SendMessageError(IFolderOperationsExtractCallback * callback,const wchar_t * message,const FString & fileName)357 static HRESULT SendMessageError(IFolderOperationsExtractCallback *callback,
358 const wchar_t *message, const FString &fileName)
359 {
360 UString s = message;
361 s += " : ";
362 s += fs2us(fileName);
363 return callback->ShowMessage(s);
364 }
365
SendMessageError(IFolderOperationsExtractCallback * callback,const char * message,const FString & fileName)366 static HRESULT SendMessageError(IFolderOperationsExtractCallback *callback,
367 const char *message, const FString &fileName)
368 {
369 return SendMessageError(callback, MultiByteToUnicodeString(message), fileName);
370 }
371
Return_LastError_or_FAIL()372 static DWORD Return_LastError_or_FAIL()
373 {
374 DWORD errorCode = GetLastError();
375 if (errorCode == 0)
376 errorCode = (DWORD)E_FAIL;
377 return errorCode;
378 }
379
GetLastErrorMessage()380 static UString GetLastErrorMessage()
381 {
382 return NError::MyFormatMessage(Return_LastError_or_FAIL());
383 }
384
SendLastErrorMessage(IFolderOperationsExtractCallback * callback,const FString & fileName)385 HRESULT SendLastErrorMessage(IFolderOperationsExtractCallback *callback, const FString &fileName)
386 {
387 return SendMessageError(callback, GetLastErrorMessage(), fileName);
388 }
389
CopyFile_Ask(CCopyState & state,const FString & srcPath,const CFileInfo & srcFileInfo,const FString & destPath)390 static HRESULT CopyFile_Ask(
391 CCopyState &state,
392 const FString &srcPath,
393 const CFileInfo &srcFileInfo,
394 const FString &destPath)
395 {
396 if (CompareFileNames(destPath, srcPath) == 0)
397 {
398 RINOK(SendMessageError(state.Callback,
399 state.MoveMode ?
400 "Cannot move file onto itself" :
401 "Cannot copy file onto itself"
402 , destPath));
403 return E_ABORT;
404 }
405
406 Int32 writeAskResult;
407 CMyComBSTR destPathResult;
408 RINOK(state.Callback->AskWrite(
409 fs2us(srcPath),
410 BoolToInt(false),
411 &srcFileInfo.MTime, &srcFileInfo.Size,
412 fs2us(destPath),
413 &destPathResult,
414 &writeAskResult));
415
416 if (IntToBool(writeAskResult))
417 {
418 FString destPathNew = us2fs((LPCOLESTR)destPathResult);
419 RINOK(state.Callback->SetCurrentFilePath(fs2us(srcPath)));
420
421 if (state.UseReadWriteMode)
422 {
423 NFsFolder::CCopyStateIO state2;
424 state2.Progress = state.Callback;
425 state2.DeleteSrcFile = state.MoveMode;
426 state2.TotalSize = state.ProgressInfo.TotalSize;
427 state2.StartPos = state.ProgressInfo.StartPos;
428
429 RINOK(state2.MyCopyFile(srcPath, destPathNew, srcFileInfo.Attrib));
430
431 if (state2.ErrorFileIndex >= 0)
432 {
433 if (state2.ErrorMessage.IsEmpty())
434 state2.ErrorMessage = GetLastErrorMessage();
435 FString errorName;
436 if (state2.ErrorFileIndex == 0)
437 errorName = srcPath;
438 else
439 errorName = destPathNew;
440 RINOK(SendMessageError(state.Callback, state2.ErrorMessage, errorName));
441 return E_ABORT;
442 }
443 state.ProgressInfo.StartPos += state2.CurrentSize;
444 }
445 else
446 {
447 state.ProgressInfo.FileSize = srcFileInfo.Size;
448 bool res;
449 if (state.MoveMode)
450 res = state.MoveFile_Sys(srcPath, destPathNew);
451 else
452 res = state.CopyFile_Sys(srcPath, destPathNew);
453 RINOK(state.ProgressInfo.ProgressResult);
454 if (!res)
455 {
456 // GetLastError() is ERROR_REQUEST_ABORTED in case of PROGRESS_CANCEL.
457 RINOK(SendMessageError(state.Callback, GetLastErrorMessage(), destPathNew));
458 return E_ABORT;
459 }
460 state.ProgressInfo.StartPos += state.ProgressInfo.FileSize;
461 }
462 }
463 else
464 {
465 if (state.ProgressInfo.TotalSize >= srcFileInfo.Size)
466 {
467 state.ProgressInfo.TotalSize -= srcFileInfo.Size;
468 RINOK(state.ProgressInfo.Progress->SetTotal(state.ProgressInfo.TotalSize));
469 }
470 }
471 return state.CallProgress();
472 }
473
CombinePath(const FString & folderPath,const FString & fileName)474 static FString CombinePath(const FString &folderPath, const FString &fileName)
475 {
476 FString s (folderPath);
477 s.Add_PathSepar(); // FCHAR_PATH_SEPARATOR
478 s += fileName;
479 return s;
480 }
481
IsDestChild(const FString & src,const FString & dest)482 static bool IsDestChild(const FString &src, const FString &dest)
483 {
484 unsigned len = src.Len();
485 if (dest.Len() < len)
486 return false;
487 if (dest.Len() != len && dest[len] != FCHAR_PATH_SEPARATOR)
488 return false;
489 return CompareFileNames(dest.Left(len), src) == 0;
490 }
491
CopyFolder(CCopyState & state,const FString & srcPath,const FString & destPath)492 static HRESULT CopyFolder(
493 CCopyState &state,
494 const FString &srcPath, // without TAIL separator
495 const FString &destPath) // without TAIL separator
496 {
497 RINOK(state.CallProgress());
498
499 if (IsDestChild(srcPath, destPath))
500 {
501 RINOK(SendMessageError(state.Callback,
502 state.MoveMode ?
503 "Cannot copy folder onto itself" :
504 "Cannot move folder onto itself"
505 , destPath));
506 return E_ABORT;
507 }
508
509 if (state.MoveMode)
510 {
511 if (state.MoveFile_Sys(srcPath, destPath))
512 return S_OK;
513
514 // MSDN: MoveFile() fails for dirs on different volumes.
515 }
516
517 if (!CreateComplexDir(destPath))
518 {
519 RINOK(SendMessageError(state.Callback, "Cannot create folder", destPath));
520 return E_ABORT;
521 }
522
523 CEnumerator enumerator;
524 enumerator.SetDirPrefix(CombinePath(srcPath, FString()));
525
526 for (;;)
527 {
528 NFind::CFileInfo fi;
529 bool found;
530 if (!enumerator.Next(fi, found))
531 {
532 SendLastErrorMessage(state.Callback, srcPath);
533 return S_OK;
534 }
535 if (!found)
536 break;
537 const FString srcPath2 = CombinePath(srcPath, fi.Name);
538 const FString destPath2 = CombinePath(destPath, fi.Name);
539 if (fi.IsDir())
540 {
541 RINOK(CopyFolder(state, srcPath2, destPath2))
542 }
543 else
544 {
545 RINOK(CopyFile_Ask(state, srcPath2, fi, destPath2));
546 }
547 }
548
549 if (state.MoveMode)
550 {
551 if (!RemoveDir(srcPath))
552 {
553 RINOK(SendMessageError(state.Callback, "Cannot remove folder", srcPath));
554 return E_ABORT;
555 }
556 }
557
558 return S_OK;
559 }
560
CopyTo(Int32 moveMode,const UInt32 * indices,UInt32 numItems,Int32,Int32,const wchar_t * path,IFolderOperationsExtractCallback * callback)561 STDMETHODIMP CFSFolder::CopyTo(Int32 moveMode, const UInt32 *indices, UInt32 numItems,
562 Int32 /* includeAltStreams */, Int32 /* replaceAltStreamColon */,
563 const wchar_t *path, IFolderOperationsExtractCallback *callback)
564 {
565 if (numItems == 0)
566 return S_OK;
567
568 FString destPath = us2fs(path);
569 if (destPath.IsEmpty())
570 return E_INVALIDARG;
571
572 bool isAltDest = NName::IsAltPathPrefix(destPath);
573 bool isDirectPath = (!isAltDest && !IsPathSepar(destPath.Back()));
574
575 if (isDirectPath)
576 {
577 if (numItems > 1)
578 return E_INVALIDARG;
579 }
580
581 CFsFolderStat stat;
582 stat.Progress = callback;
583 RINOK(GetItemsFullSize(indices, numItems, stat));
584
585 if (stat.NumFolders != 0 && isAltDest)
586 return E_NOTIMPL;
587
588 RINOK(callback->SetTotal(stat.Size));
589 RINOK(callback->SetNumFiles(stat.NumFiles));
590
591 UInt64 completedSize = 0;
592 RINOK(callback->SetCompleted(&completedSize));
593
594 CCopyState state;
595 state.ProgressInfo.TotalSize = stat.Size;
596 state.ProgressInfo.StartPos = 0;
597 state.ProgressInfo.Progress = callback;
598 state.ProgressInfo.Init();
599 state.Callback = callback;
600 state.MoveMode = IntToBool(moveMode);
601 state.UseReadWriteMode = isAltDest;
602 state.Prepare();
603
604 for (UInt32 i = 0; i < numItems; i++)
605 {
606 UInt32 index = indices[i];
607 if (index >= (UInt32)Files.Size())
608 continue;
609 const CDirItem &fi = Files[index];
610 FString destPath2 = destPath;
611 if (!isDirectPath)
612 destPath2 += fi.Name;
613 FString srcPath;
614 GetFullPath(fi, srcPath);
615
616 if (fi.IsDir())
617 {
618 RINOK(CopyFolder(state, srcPath, destPath2));
619 }
620 else
621 {
622 RINOK(CopyFile_Ask(state, srcPath, fi, destPath2));
623 }
624 }
625 return S_OK;
626 }
627
CopyFrom(Int32,const wchar_t *,const wchar_t * const *,UInt32,IProgress *)628 STDMETHODIMP CFSFolder::CopyFrom(Int32 /* moveMode */, const wchar_t * /* fromFolderPath */,
629 const wchar_t * const * /* itemsPaths */, UInt32 /* numItems */, IProgress * /* progress */)
630 {
631 /*
632 UInt64 numFolders, numFiles, totalSize;
633 numFiles = numFolders = totalSize = 0;
634 UInt32 i;
635 for (i = 0; i < numItems; i++)
636 {
637 UString path = (UString)fromFolderPath + itemsPaths[i];
638
639 CFileInfo fi;
640 if (!FindFile(path, fi))
641 return ::GetLastError();
642 if (fi.IsDir())
643 {
644 UInt64 subFolders, subFiles, subSize;
645 RINOK(GetFolderSize(CombinePath(path, fi.Name), subFolders, subFiles, subSize, progress));
646 numFolders += subFolders;
647 numFolders++;
648 numFiles += subFiles;
649 totalSize += subSize;
650 }
651 else
652 {
653 numFiles++;
654 totalSize += fi.Size;
655 }
656 }
657 RINOK(progress->SetTotal(totalSize));
658 RINOK(callback->SetNumFiles(numFiles));
659 for (i = 0; i < numItems; i++)
660 {
661 UString path = (UString)fromFolderPath + itemsPaths[i];
662 }
663 return S_OK;
664 */
665 return E_NOTIMPL;
666 }
667
CopyFromFile(UInt32,const wchar_t *,IProgress *)668 STDMETHODIMP CFSFolder::CopyFromFile(UInt32 /* index */, const wchar_t * /* fullFilePath */, IProgress * /* progress */)
669 {
670 return E_NOTIMPL;
671 }
672
673 }
674