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