Upload 2.0.2
[physicsfs] / lzma / CPP / 7zip / UI / Common / ArchiveExtractCallback.cpp
1 // ArchiveExtractCallback.cpp
2
3 #include "StdAfx.h"
4
5 #include "ArchiveExtractCallback.h"
6
7 #include "Common/Wildcard.h"
8 #include "Common/StringConvert.h"
9 #include "Common/ComTry.h"
10
11 #include "Windows/FileDir.h"
12 #include "Windows/FileFind.h"
13 #include "Windows/Time.h"
14 #include "Windows/Defs.h"
15 #include "Windows/PropVariant.h"
16
17 #include "Windows/PropVariantConversions.h"
18
19 #include "../../Common/FilePathAutoRename.h"
20
21 #include "../Common/ExtractingFilePath.h"
22 #include "OpenArchive.h"
23
24 using namespace NWindows;
25
26 static const wchar_t *kCantAutoRename = L"ERROR: Can not create file with auto name";
27 static const wchar_t *kCantRenameFile = L"ERROR: Can not rename existing file ";
28 static const wchar_t *kCantDeleteOutputFile = L"ERROR: Can not delete output file ";
29
30
31 void CArchiveExtractCallback::Init(
32     IInArchive *archiveHandler,
33     IFolderArchiveExtractCallback *extractCallback2,
34     bool stdOutMode,
35     const UString &directoryPath, 
36     const UStringVector &removePathParts,
37     const UString &itemDefaultName,
38     const FILETIME &utcLastWriteTimeDefault,
39     UInt32 attributesDefault,
40     UInt64 packSize)
41 {
42   _stdOutMode = stdOutMode;
43   _numErrors = 0;
44   _unpTotal = 1;
45   _packTotal = packSize;
46
47   _extractCallback2 = extractCallback2;
48   _compressProgress.Release();
49   _extractCallback2.QueryInterface(IID_ICompressProgressInfo, &_compressProgress);
50
51   LocalProgressSpec->Init(extractCallback2, true);
52   LocalProgressSpec->SendProgress = false;
53
54   _itemDefaultName = itemDefaultName;
55   _utcLastWriteTimeDefault = utcLastWriteTimeDefault;
56   _attributesDefault = attributesDefault;
57   _removePathParts = removePathParts;
58   _archiveHandler = archiveHandler;
59   _directoryPath = directoryPath;
60   NFile::NName::NormalizeDirPathPrefix(_directoryPath);
61 }
62
63 STDMETHODIMP CArchiveExtractCallback::SetTotal(UInt64 size)
64 {
65   COM_TRY_BEGIN
66   _unpTotal = size;
67   if (!_multiArchives && _extractCallback2)
68     return _extractCallback2->SetTotal(size);
69   return S_OK;
70   COM_TRY_END
71 }
72
73 static void NormalizeVals(UInt64 &v1, UInt64 &v2)
74 {
75   const UInt64 kMax = (UInt64)1 << 31;
76   while (v1 > kMax)
77   {
78     v1 >>= 1;
79     v2 >>= 1;
80   }
81 }
82
83 static UInt64 MyMultDiv64(UInt64 unpCur, UInt64 unpTotal, UInt64 packTotal)
84 {
85   NormalizeVals(packTotal, unpTotal);
86   NormalizeVals(unpCur, unpTotal);
87   if (unpTotal == 0)
88     unpTotal = 1;
89   return unpCur * packTotal / unpTotal;
90 }
91
92 STDMETHODIMP CArchiveExtractCallback::SetCompleted(const UInt64 *completeValue)
93 {
94   COM_TRY_BEGIN
95   if (!_extractCallback2)
96     return S_OK;
97
98   if (_multiArchives)
99   {
100     if (completeValue != NULL)
101     {
102       UInt64 packCur = LocalProgressSpec->InSize + MyMultDiv64(*completeValue, _unpTotal, _packTotal);
103       return _extractCallback2->SetCompleted(&packCur);
104     }
105   }
106   return _extractCallback2->SetCompleted(completeValue);
107   COM_TRY_END
108 }
109
110 STDMETHODIMP CArchiveExtractCallback::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)
111 {
112   COM_TRY_BEGIN
113   return _localProgress->SetRatioInfo(inSize, outSize);
114   COM_TRY_END
115 }
116
117 void CArchiveExtractCallback::CreateComplexDirectory(const UStringVector &dirPathParts, UString &fullPath)
118 {
119   fullPath = _directoryPath;
120   for(int i = 0; i < dirPathParts.Size(); i++)
121   {
122     if (i > 0)
123       fullPath += wchar_t(NFile::NName::kDirDelimiter);
124     fullPath += dirPathParts[i];
125     NFile::NDirectory::MyCreateDirectory(fullPath);
126   }
127 }
128
129 static UString MakePathNameFromParts(const UStringVector &parts)
130 {
131   UString result;
132   for(int i = 0; i < parts.Size(); i++)
133   {
134     if(i != 0)
135       result += wchar_t(NFile::NName::kDirDelimiter);
136     result += parts[i];
137   }
138   return result;
139 }
140
141
142 HRESULT CArchiveExtractCallback::GetTime(int index, PROPID propID, FILETIME &filetime, bool &filetimeIsDefined)
143 {
144   filetimeIsDefined = false;
145   NCOM::CPropVariant prop;
146   RINOK(_archiveHandler->GetProperty(index, propID, &prop));
147   if (prop.vt == VT_FILETIME)
148   {
149     filetime = prop.filetime;
150     filetimeIsDefined = (filetime.dwHighDateTime != 0 || filetime.dwLowDateTime != 0);
151   }
152   else if (prop.vt != VT_EMPTY)
153     return E_FAIL;
154   return S_OK;
155 }
156
157 STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode)
158 {
159   COM_TRY_BEGIN
160   *outStream = 0;
161   _outFileStream.Release();
162
163   _encrypted = false;
164   _isSplit = false;
165   _curSize = 0;
166
167   UString fullPath;
168
169   RINOK(GetArchiveItemPath(_archiveHandler, index, _itemDefaultName, fullPath));
170   RINOK(IsArchiveItemFolder(_archiveHandler, index, _processedFileInfo.IsDirectory));
171
172   _filePath = fullPath;
173
174   {
175     NCOM::CPropVariant prop;
176     RINOK(_archiveHandler->GetProperty(index, kpidPosition, &prop));
177     if (prop.vt != VT_EMPTY)
178     {
179       if (prop.vt != VT_UI8)
180         return E_FAIL;
181       _position = prop.uhVal.QuadPart;
182       _isSplit = true;
183     }
184   }
185     
186   RINOK(IsArchiveItemProp(_archiveHandler, index, kpidEncrypted, _encrypted));
187
188   bool newFileSizeDefined;
189   UInt64 newFileSize;
190   {
191     NCOM::CPropVariant prop;
192     RINOK(_archiveHandler->GetProperty(index, kpidSize, &prop));
193     newFileSizeDefined = (prop.vt != VT_EMPTY);
194     if (newFileSizeDefined)
195     {
196       newFileSize = ConvertPropVariantToUInt64(prop);
197       _curSize = newFileSize;
198     }
199   }
200
201   if(askExtractMode == NArchive::NExtract::NAskMode::kExtract)
202   {
203     if (_stdOutMode)
204     {
205       CMyComPtr<ISequentialOutStream> outStreamLoc = new CStdOutFileStream;
206       *outStream = outStreamLoc.Detach();
207       return S_OK;
208     }
209
210     {
211       NCOM::CPropVariant prop;
212       RINOK(_archiveHandler->GetProperty(index, kpidAttributes, &prop));
213       if (prop.vt == VT_EMPTY)
214       {
215         _processedFileInfo.Attributes = _attributesDefault;
216         _processedFileInfo.AttributesAreDefined = false;
217       }
218       else
219       {
220         if (prop.vt != VT_UI4)
221           return E_FAIL;
222         _processedFileInfo.Attributes = prop.ulVal;
223         _processedFileInfo.AttributesAreDefined = true;
224       }
225     }
226
227     RINOK(GetTime(index, kpidCreationTime, _processedFileInfo.CreationTime,
228         _processedFileInfo.IsCreationTimeDefined));
229     RINOK(GetTime(index, kpidLastWriteTime, _processedFileInfo.LastWriteTime, 
230         _processedFileInfo.IsLastWriteTimeDefined));
231     RINOK(GetTime(index, kpidLastAccessTime, _processedFileInfo.LastAccessTime,
232         _processedFileInfo.IsLastAccessTimeDefined));
233
234     bool isAnti = false;
235     RINOK(IsArchiveItemProp(_archiveHandler, index, kpidIsAnti, isAnti));
236
237     UStringVector pathParts; 
238     SplitPathToParts(fullPath, pathParts);
239     
240     if(pathParts.IsEmpty())
241       return E_FAIL;
242     int numRemovePathParts = 0;
243     switch(_pathMode)
244     {
245       case NExtract::NPathMode::kFullPathnames:
246         break;
247       case NExtract::NPathMode::kCurrentPathnames:
248       {
249         numRemovePathParts = _removePathParts.Size();
250         if (pathParts.Size() <= numRemovePathParts)
251           return E_FAIL;
252         for (int i = 0; i < numRemovePathParts; i++)
253           if (_removePathParts[i].CompareNoCase(pathParts[i]) != 0)
254             return E_FAIL;
255         break;
256       }
257       case NExtract::NPathMode::kNoPathnames:
258       {
259         numRemovePathParts = pathParts.Size() - 1;
260         break;
261       }
262     }
263     pathParts.Delete(0, numRemovePathParts);
264     MakeCorrectPath(pathParts);
265     UString processedPath = MakePathNameFromParts(pathParts);
266     if (!isAnti)
267     {
268       if (!_processedFileInfo.IsDirectory)
269       {
270         if (!pathParts.IsEmpty())
271           pathParts.DeleteBack();
272       }
273     
274       if (!pathParts.IsEmpty())
275       {
276         UString fullPathNew;
277         CreateComplexDirectory(pathParts, fullPathNew);
278         if (_processedFileInfo.IsDirectory)
279           NFile::NDirectory::SetDirTime(fullPathNew, 
280             (WriteCreated && _processedFileInfo.IsCreationTimeDefined) ? &_processedFileInfo.CreationTime : NULL, 
281             (WriteAccessed && _processedFileInfo.IsLastAccessTimeDefined) ? &_processedFileInfo.LastAccessTime : NULL, 
282             (WriteModified && _processedFileInfo.IsLastWriteTimeDefined) ? &_processedFileInfo.LastWriteTime : &_utcLastWriteTimeDefault);
283       }
284     }
285
286
287     UString fullProcessedPath = _directoryPath + processedPath;
288
289     if(_processedFileInfo.IsDirectory)
290     {
291       _diskFilePath = fullProcessedPath;
292       if (isAnti)
293         NFile::NDirectory::MyRemoveDirectory(_diskFilePath);
294       return S_OK;
295     }
296
297     if (!_isSplit)
298     {
299     NFile::NFind::CFileInfoW fileInfo;
300     if(NFile::NFind::FindFile(fullProcessedPath, fileInfo))
301     {
302       switch(_overwriteMode)
303       {
304         case NExtract::NOverwriteMode::kSkipExisting:
305           return S_OK;
306         case NExtract::NOverwriteMode::kAskBefore:
307         {
308           Int32 overwiteResult;
309           RINOK(_extractCallback2->AskOverwrite(
310               fullProcessedPath, &fileInfo.LastWriteTime, &fileInfo.Size, fullPath, 
311               _processedFileInfo.IsLastWriteTimeDefined ? &_processedFileInfo.LastWriteTime : NULL, 
312               newFileSizeDefined ? &newFileSize : NULL, 
313               &overwiteResult))
314
315           switch(overwiteResult)
316           {
317             case NOverwriteAnswer::kCancel:
318               return E_ABORT;
319             case NOverwriteAnswer::kNo:
320               return S_OK;
321             case NOverwriteAnswer::kNoToAll:
322               _overwriteMode = NExtract::NOverwriteMode::kSkipExisting;
323               return S_OK;
324             case NOverwriteAnswer::kYesToAll:
325               _overwriteMode = NExtract::NOverwriteMode::kWithoutPrompt;
326               break;
327             case NOverwriteAnswer::kYes:
328               break;
329             case NOverwriteAnswer::kAutoRename:
330               _overwriteMode = NExtract::NOverwriteMode::kAutoRename;
331               break;
332             default:
333               return E_FAIL;
334           }
335         }
336       }
337       if (_overwriteMode == NExtract::NOverwriteMode::kAutoRename)
338       {
339         if (!AutoRenamePath(fullProcessedPath))
340         {
341           UString message = UString(kCantAutoRename) + fullProcessedPath;
342           RINOK(_extractCallback2->MessageError(message));
343           return E_FAIL;
344         }
345       }
346       else if (_overwriteMode == NExtract::NOverwriteMode::kAutoRenameExisting)
347       {
348         UString existPath = fullProcessedPath;
349         if (!AutoRenamePath(existPath))
350         {
351           UString message = kCantAutoRename + fullProcessedPath;
352           RINOK(_extractCallback2->MessageError(message));
353           return E_FAIL;
354         }
355         if(!NFile::NDirectory::MyMoveFile(fullProcessedPath, existPath))
356         {
357           UString message = UString(kCantRenameFile) + fullProcessedPath;
358           RINOK(_extractCallback2->MessageError(message));
359           return E_FAIL;
360         }
361       }
362       else
363         if (!NFile::NDirectory::DeleteFileAlways(fullProcessedPath))
364         {
365           UString message = UString(kCantDeleteOutputFile) +  fullProcessedPath;
366           RINOK(_extractCallback2->MessageError(message));
367           return S_OK;
368           // return E_FAIL;
369         }
370     }
371     }
372     if (!isAnti)
373     {
374       _outFileStreamSpec = new COutFileStream;
375       CMyComPtr<ISequentialOutStream> outStreamLoc(_outFileStreamSpec);
376       if (!_outFileStreamSpec->Open(fullProcessedPath, _isSplit ? OPEN_ALWAYS: CREATE_ALWAYS))
377       {
378         // if (::GetLastError() != ERROR_FILE_EXISTS || !isSplit)
379         {
380           UString message = L"can not open output file " + fullProcessedPath;
381           RINOK(_extractCallback2->MessageError(message));
382           return S_OK;
383         }
384       }
385       if (_isSplit)
386       {
387         RINOK(_outFileStreamSpec->Seek(_position, STREAM_SEEK_SET, NULL));
388       }
389       _outFileStream = outStreamLoc;
390       *outStream = outStreamLoc.Detach();
391     }
392     _diskFilePath = fullProcessedPath;
393   }
394   else
395   {
396     *outStream = NULL;
397   }
398   return S_OK;
399   COM_TRY_END
400 }
401
402 STDMETHODIMP CArchiveExtractCallback::PrepareOperation(Int32 askExtractMode)
403 {
404   COM_TRY_BEGIN
405   _extractMode = false;
406   switch (askExtractMode)
407   {
408     case NArchive::NExtract::NAskMode::kExtract:
409       _extractMode = true;
410   };
411   return _extractCallback2->PrepareOperation(_filePath, _processedFileInfo.IsDirectory, 
412       askExtractMode, _isSplit ? &_position: 0);
413   COM_TRY_END
414 }
415
416 STDMETHODIMP CArchiveExtractCallback::SetOperationResult(Int32 operationResult)
417 {
418   COM_TRY_BEGIN
419   switch(operationResult)
420   {
421     case NArchive::NExtract::NOperationResult::kOK:
422     case NArchive::NExtract::NOperationResult::kUnSupportedMethod:
423     case NArchive::NExtract::NOperationResult::kCRCError:
424     case NArchive::NExtract::NOperationResult::kDataError:
425       break;
426     default:
427       _outFileStream.Release();
428       return E_FAIL;
429   }
430   if (_outFileStream != NULL)
431   {
432     _outFileStreamSpec->SetTime(
433         (WriteCreated && _processedFileInfo.IsCreationTimeDefined) ? &_processedFileInfo.CreationTime : NULL, 
434         (WriteAccessed && _processedFileInfo.IsLastAccessTimeDefined) ? &_processedFileInfo.LastAccessTime : NULL, 
435         (WriteModified && _processedFileInfo.IsLastWriteTimeDefined) ? &_processedFileInfo.LastWriteTime : &_utcLastWriteTimeDefault);
436     _curSize = _outFileStreamSpec->ProcessedSize;
437     RINOK(_outFileStreamSpec->Close());
438     _outFileStream.Release();
439   }
440   UnpackSize += _curSize;
441   if (_processedFileInfo.IsDirectory)
442     NumFolders++;
443   else
444     NumFiles++;
445
446   if (_extractMode && _processedFileInfo.AttributesAreDefined)
447     NFile::NDirectory::MySetFileAttributes(_diskFilePath, _processedFileInfo.Attributes);
448   RINOK(_extractCallback2->SetOperationResult(operationResult, _encrypted));
449   return S_OK;
450   COM_TRY_END
451 }
452
453 /*
454 STDMETHODIMP CArchiveExtractCallback::GetInStream(
455     const wchar_t *name, ISequentialInStream **inStream)
456 {
457   COM_TRY_BEGIN
458   CInFileStream *inFile = new CInFileStream;
459   CMyComPtr<ISequentialInStream> inStreamTemp = inFile;
460   if (!inFile->Open(_srcDirectoryPrefix + name))
461     return ::GetLastError();
462   *inStream = inStreamTemp.Detach();
463   return S_OK;
464   COM_TRY_END
465 }
466 */
467
468 STDMETHODIMP CArchiveExtractCallback::CryptoGetTextPassword(BSTR *password)
469 {
470   COM_TRY_BEGIN
471   if (!_cryptoGetTextPassword)
472   {
473     RINOK(_extractCallback2.QueryInterface(IID_ICryptoGetTextPassword, 
474         &_cryptoGetTextPassword));
475   }
476   return _cryptoGetTextPassword->CryptoGetTextPassword(password);
477   COM_TRY_END
478 }
479