Imported Upstream version 9.20
[platform/upstream/7zip.git] / CPP / 7zip / Archive / Cab / CabHandler.cpp
1 // CabHandler.cpp\r
2 \r
3 #include "StdAfx.h"\r
4 \r
5 #include "../../../../C/Alloc.h"\r
6 \r
7 #include "Common/Buffer.h"\r
8 #include "Common/ComTry.h"\r
9 #include "Common/Defs.h"\r
10 #include "Common/IntToString.h"\r
11 #include "Common/StringConvert.h"\r
12 #include "Common/UTFConvert.h"\r
13 \r
14 #include "Windows/PropVariant.h"\r
15 #include "Windows/Time.h"\r
16 \r
17 #include "../../Common/ProgressUtils.h"\r
18 #include "../../Common/StreamUtils.h"\r
19 \r
20 #include "../../Compress/CopyCoder.h"\r
21 #include "../../Compress/DeflateDecoder.h"\r
22 #include "../../Compress/LzxDecoder.h"\r
23 #include "../../Compress/QuantumDecoder.h"\r
24 \r
25 #include "../Common/ItemNameUtils.h"\r
26 \r
27 #include "CabBlockInStream.h"\r
28 #include "CabHandler.h"\r
29 \r
30 using namespace NWindows;\r
31 \r
32 namespace NArchive {\r
33 namespace NCab {\r
34 \r
35 // #define _CAB_DETAILS\r
36 \r
37 #ifdef _CAB_DETAILS\r
38 enum\r
39 {\r
40   kpidBlockReal = kpidUserDefined\r
41 };\r
42 #endif\r
43 \r
44 static STATPROPSTG kProps[] =\r
45 {\r
46   { NULL, kpidPath, VT_BSTR},\r
47   { NULL, kpidSize, VT_UI8},\r
48   { NULL, kpidMTime, VT_FILETIME},\r
49   { NULL, kpidAttrib, VT_UI4},\r
50   { NULL, kpidMethod, VT_BSTR},\r
51   { NULL, kpidBlock, VT_I4}\r
52   #ifdef _CAB_DETAILS\r
53   ,\r
54   { L"BlockReal", kpidBlockReal, VT_UI4},\r
55   { NULL, kpidOffset, VT_UI4},\r
56   { NULL, kpidVolume, VT_UI4}\r
57   #endif\r
58 };\r
59 \r
60 static const char *kMethods[] =\r
61 {\r
62   "None",\r
63   "MSZip",\r
64   "Quantum",\r
65   "LZX"\r
66 };\r
67 \r
68 static const int kNumMethods = sizeof(kMethods) / sizeof(kMethods[0]);\r
69 static const char *kUnknownMethod = "Unknown";\r
70 \r
71 static STATPROPSTG kArcProps[] =\r
72 {\r
73   { NULL, kpidMethod, VT_BSTR},\r
74   // { NULL, kpidSolid, VT_BOOL},\r
75   { NULL, kpidNumBlocks, VT_UI4},\r
76   { NULL, kpidNumVolumes, VT_UI4}\r
77 };\r
78 \r
79 IMP_IInArchive_Props\r
80 IMP_IInArchive_ArcProps\r
81 \r
82 STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)\r
83 {\r
84   COM_TRY_BEGIN\r
85   NWindows::NCOM::CPropVariant prop;\r
86   switch(propID)\r
87   {\r
88     case kpidMethod:\r
89     {\r
90       AString resString;\r
91       CRecordVector<Byte> ids;\r
92       int i;\r
93       for (int v = 0; v < m_Database.Volumes.Size(); v++)\r
94       {\r
95         const CDatabaseEx &de = m_Database.Volumes[v];\r
96         for (i = 0; i < de.Folders.Size(); i++)\r
97           ids.AddToUniqueSorted(de.Folders[i].GetCompressionMethod());\r
98       }\r
99       for (i = 0; i < ids.Size(); i++)\r
100       {\r
101         Byte id = ids[i];\r
102         AString method = (id < kNumMethods) ? kMethods[id] : kUnknownMethod;\r
103         if (!resString.IsEmpty())\r
104           resString += ' ';\r
105         resString += method;\r
106       }\r
107       prop = resString;\r
108       break;\r
109     }\r
110     // case kpidSolid: prop = _database.IsSolid(); break;\r
111     case kpidNumBlocks:\r
112     {\r
113       UInt32 numFolders = 0;\r
114       for (int v = 0; v < m_Database.Volumes.Size(); v++)\r
115         numFolders += m_Database.Volumes[v].Folders.Size();\r
116       prop = numFolders;\r
117       break;\r
118     }\r
119     case kpidNumVolumes:\r
120     {\r
121       prop = (UInt32)m_Database.Volumes.Size();\r
122       break;\r
123     }\r
124   }\r
125   prop.Detach(value);\r
126   return S_OK;\r
127   COM_TRY_END\r
128 }\r
129 \r
130 STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID,  PROPVARIANT *value)\r
131 {\r
132   COM_TRY_BEGIN\r
133   NWindows::NCOM::CPropVariant prop;\r
134   \r
135   const CMvItem &mvItem = m_Database.Items[index];\r
136   const CDatabaseEx &db = m_Database.Volumes[mvItem.VolumeIndex];\r
137   int itemIndex = mvItem.ItemIndex;\r
138   const CItem &item = db.Items[itemIndex];\r
139   switch(propID)\r
140   {\r
141     case kpidPath:\r
142     {\r
143       UString unicodeName;\r
144       if (item.IsNameUTF())\r
145         ConvertUTF8ToUnicode(item.Name, unicodeName);\r
146       else\r
147         unicodeName = MultiByteToUnicodeString(item.Name, CP_ACP);\r
148       prop = (const wchar_t *)NItemName::WinNameToOSName(unicodeName);\r
149       break;\r
150     }\r
151     case kpidIsDir:  prop = item.IsDir(); break;\r
152     case kpidSize:  prop = item.Size; break;\r
153     case kpidAttrib:  prop = item.GetWinAttributes(); break;\r
154 \r
155     case kpidMTime:\r
156     {\r
157       FILETIME localFileTime, utcFileTime;\r
158       if (NTime::DosTimeToFileTime(item.Time, localFileTime))\r
159       {\r
160         if (!LocalFileTimeToFileTime(&localFileTime, &utcFileTime))\r
161           utcFileTime.dwHighDateTime = utcFileTime.dwLowDateTime = 0;\r
162       }\r
163       else\r
164         utcFileTime.dwHighDateTime = utcFileTime.dwLowDateTime = 0;\r
165       prop = utcFileTime;\r
166       break;\r
167     }\r
168 \r
169     case kpidMethod:\r
170     {\r
171       UInt32 realFolderIndex = item.GetFolderIndex(db.Folders.Size());\r
172       const CFolder &folder = db.Folders[realFolderIndex];\r
173       int methodIndex = folder.GetCompressionMethod();\r
174       AString method = (methodIndex < kNumMethods) ? kMethods[methodIndex] : kUnknownMethod;\r
175       if (methodIndex == NHeader::NCompressionMethodMajor::kLZX ||\r
176         methodIndex == NHeader::NCompressionMethodMajor::kQuantum)\r
177       {\r
178         method += ':';\r
179         char temp[32];\r
180         ConvertUInt64ToString(folder.CompressionTypeMinor, temp);\r
181         method += temp;\r
182       }\r
183       prop = method;\r
184       break;\r
185     }\r
186     case kpidBlock:  prop = (Int32)m_Database.GetFolderIndex(&mvItem); break;\r
187     \r
188     #ifdef _CAB_DETAILS\r
189     \r
190     case kpidBlockReal:  prop = (UInt32)item.FolderIndex; break;\r
191     case kpidOffset:  prop = (UInt32)item.Offset; break;\r
192     case kpidVolume:  prop = (UInt32)mvItem.VolumeIndex; break;\r
193 \r
194     #endif\r
195   }\r
196   prop.Detach(value);\r
197   return S_OK;\r
198   COM_TRY_END\r
199 }\r
200 \r
201 /*\r
202 class CProgressImp: public CProgressVirt\r
203 {\r
204   CMyComPtr<IArchiveOpenCallback> m_OpenArchiveCallback;\r
205 public:\r
206   STDMETHOD(SetTotal)(const UInt64 *numFiles);\r
207   STDMETHOD(SetCompleted)(const UInt64 *numFiles);\r
208   void Init(IArchiveOpenCallback *openArchiveCallback)\r
209     { m_OpenArchiveCallback = openArchiveCallback; }\r
210 };\r
211 \r
212 STDMETHODIMP CProgressImp::SetTotal(const UInt64 *numFiles)\r
213 {\r
214   if (m_OpenArchiveCallback)\r
215     return m_OpenArchiveCallback->SetCompleted(numFiles, NULL);\r
216   return S_OK;\r
217 }\r
218 \r
219 STDMETHODIMP CProgressImp::SetCompleted(const UInt64 *numFiles)\r
220 {\r
221   if (m_OpenArchiveCallback)\r
222     return m_OpenArchiveCallback->SetCompleted(numFiles, NULL);\r
223   return S_OK;\r
224 }\r
225 */\r
226 \r
227 STDMETHODIMP CHandler::Open(IInStream *inStream,\r
228     const UInt64 *maxCheckStartPosition,\r
229     IArchiveOpenCallback *callback)\r
230 {\r
231   COM_TRY_BEGIN\r
232   Close();\r
233   HRESULT res = S_FALSE;\r
234   CInArchive archive;\r
235   CMyComPtr<IArchiveOpenVolumeCallback> openVolumeCallback;\r
236   callback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback);\r
237   \r
238   CMyComPtr<IInStream> nextStream = inStream;\r
239   bool prevChecked = false;\r
240   UInt64 numItems = 0;\r
241   try\r
242   {\r
243     while (nextStream != 0)\r
244     {\r
245       CDatabaseEx db;\r
246       db.Stream = nextStream;\r
247       res = archive.Open(maxCheckStartPosition, db);\r
248       if (res == S_OK)\r
249       {\r
250         if (!m_Database.Volumes.IsEmpty())\r
251         {\r
252           const CDatabaseEx &dbPrev = m_Database.Volumes[prevChecked ? m_Database.Volumes.Size() - 1 : 0];\r
253           if (dbPrev.ArchiveInfo.SetID != db.ArchiveInfo.SetID ||\r
254               dbPrev.ArchiveInfo.CabinetNumber + (prevChecked ? 1: - 1) !=\r
255               db.ArchiveInfo.CabinetNumber)\r
256             res = S_FALSE;\r
257         }\r
258       }\r
259       if (res == S_OK)\r
260         m_Database.Volumes.Insert(prevChecked ? m_Database.Volumes.Size() : 0, db);\r
261       else if (res != S_FALSE)\r
262         return res;\r
263       else\r
264       {\r
265         if (m_Database.Volumes.IsEmpty())\r
266           return S_FALSE;\r
267         if (prevChecked)\r
268           break;\r
269         prevChecked = true;\r
270       }\r
271 \r
272       numItems += db.Items.Size();\r
273       RINOK(callback->SetCompleted(&numItems, NULL));\r
274         \r
275       nextStream = 0;\r
276       for (;;)\r
277       {\r
278         const COtherArchive *otherArchive = 0;\r
279         if (!prevChecked)\r
280         {\r
281           const CInArchiveInfo &ai = m_Database.Volumes.Front().ArchiveInfo;\r
282           if (ai.IsTherePrev())\r
283             otherArchive = &ai.PrevArc;\r
284           else\r
285             prevChecked = true;\r
286         }\r
287         if (otherArchive == 0)\r
288         {\r
289           const CInArchiveInfo &ai = m_Database.Volumes.Back().ArchiveInfo;\r
290           if (ai.IsThereNext())\r
291             otherArchive = &ai.NextArc;\r
292         }\r
293         if (!otherArchive)\r
294           break;\r
295         const UString fullName = MultiByteToUnicodeString(otherArchive->FileName, CP_ACP);\r
296         if (!openVolumeCallback)\r
297           break;\r
298 \r
299         HRESULT result = openVolumeCallback->GetStream(fullName, &nextStream);\r
300         if (result == S_OK)\r
301           break;\r
302         if (result != S_FALSE)\r
303           return result;\r
304         if (prevChecked)\r
305           break;\r
306         prevChecked = true;\r
307       }\r
308     }\r
309     if (res == S_OK)\r
310     {\r
311       m_Database.FillSortAndShrink();\r
312       if (!m_Database.Check())\r
313         res = S_FALSE;\r
314     }\r
315   }\r
316   catch(...)\r
317   {\r
318     res = S_FALSE;\r
319   }\r
320   if (res != S_OK)\r
321   {\r
322     Close();\r
323     return res;\r
324   }\r
325   COM_TRY_END\r
326   return S_OK;\r
327 }\r
328 \r
329 STDMETHODIMP CHandler::Close()\r
330 {\r
331   m_Database.Clear();\r
332   return S_OK;\r
333 }\r
334 \r
335 class CFolderOutStream:\r
336   public ISequentialOutStream,\r
337   public CMyUnknownImp\r
338 {\r
339 public:\r
340   MY_UNKNOWN_IMP\r
341 \r
342   STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize);\r
343 private:\r
344   const CMvDatabaseEx *m_Database;\r
345   const CRecordVector<bool> *m_ExtractStatuses;\r
346   \r
347   Byte *TempBuf;\r
348   UInt32 TempBufSize;\r
349   int NumIdenticalFiles;\r
350   bool TempBufMode;\r
351   UInt32 m_BufStartFolderOffset;\r
352 \r
353   int m_StartIndex;\r
354   int m_CurrentIndex;\r
355   CMyComPtr<IArchiveExtractCallback> m_ExtractCallback;\r
356   bool m_TestMode;\r
357 \r
358   CMyComPtr<ISequentialOutStream> m_RealOutStream;\r
359 \r
360   bool m_IsOk;\r
361   bool m_FileIsOpen;\r
362   UInt32 m_RemainFileSize;\r
363   UInt64 m_FolderSize;\r
364   UInt64 m_PosInFolder;\r
365 \r
366   void FreeTempBuf()\r
367   {\r
368     ::MyFree(TempBuf);\r
369     TempBuf = NULL;\r
370   }\r
371 \r
372   HRESULT OpenFile();\r
373   HRESULT CloseFileWithResOp(Int32 resOp);\r
374   HRESULT CloseFile();\r
375   HRESULT Write2(const void *data, UInt32 size, UInt32 *processedSize, bool isOK);\r
376 public:\r
377   HRESULT WriteEmptyFiles();\r
378 \r
379   CFolderOutStream(): TempBuf(NULL) {}\r
380   ~CFolderOutStream() { FreeTempBuf(); }\r
381   void Init(\r
382       const CMvDatabaseEx *database,\r
383       const CRecordVector<bool> *extractStatuses,\r
384       int startIndex,\r
385       UInt64 folderSize,\r
386       IArchiveExtractCallback *extractCallback,\r
387       bool testMode);\r
388   HRESULT FlushCorrupted();\r
389   HRESULT Unsupported();\r
390 \r
391   UInt64 GetRemain() const { return m_FolderSize - m_PosInFolder; }\r
392   UInt64 GetPosInFolder() const { return m_PosInFolder; }\r
393 };\r
394 \r
395 void CFolderOutStream::Init(\r
396     const CMvDatabaseEx *database,\r
397     const CRecordVector<bool> *extractStatuses,\r
398     int startIndex,\r
399     UInt64 folderSize,\r
400     IArchiveExtractCallback *extractCallback,\r
401     bool testMode)\r
402 {\r
403   m_Database = database;\r
404   m_ExtractStatuses = extractStatuses;\r
405   m_StartIndex = startIndex;\r
406   m_FolderSize = folderSize;\r
407 \r
408   m_ExtractCallback = extractCallback;\r
409   m_TestMode = testMode;\r
410 \r
411   m_CurrentIndex = 0;\r
412   m_PosInFolder = 0;\r
413   m_FileIsOpen = false;\r
414   m_IsOk = true;\r
415   TempBufMode = false;\r
416   NumIdenticalFiles = 0;\r
417 }\r
418 \r
419 HRESULT CFolderOutStream::CloseFileWithResOp(Int32 resOp)\r
420 {\r
421   m_RealOutStream.Release();\r
422   m_FileIsOpen = false;\r
423   NumIdenticalFiles--;\r
424   return m_ExtractCallback->SetOperationResult(resOp);\r
425 }\r
426 \r
427 HRESULT CFolderOutStream::CloseFile()\r
428 {\r
429   return CloseFileWithResOp(m_IsOk ?\r
430       NExtract::NOperationResult::kOK:\r
431       NExtract::NOperationResult::kDataError);\r
432 }\r
433 \r
434 HRESULT CFolderOutStream::OpenFile()\r
435 {\r
436   if (NumIdenticalFiles == 0)\r
437   {\r
438     const CMvItem &mvItem = m_Database->Items[m_StartIndex + m_CurrentIndex];\r
439     const CItem &item = m_Database->Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex];\r
440     int numExtractItems = 0;\r
441     int curIndex;\r
442     for (curIndex = m_CurrentIndex; curIndex < m_ExtractStatuses->Size(); curIndex++)\r
443     {\r
444       const CMvItem &mvItem2 = m_Database->Items[m_StartIndex + curIndex];\r
445       const CItem &item2 = m_Database->Volumes[mvItem2.VolumeIndex].Items[mvItem2.ItemIndex];\r
446       if (item.Offset != item2.Offset ||\r
447           item.Size != item2.Size ||\r
448           item.Size == 0)\r
449         break;\r
450       if (!m_TestMode && (*m_ExtractStatuses)[curIndex])\r
451         numExtractItems++;\r
452     }\r
453     NumIdenticalFiles = (curIndex - m_CurrentIndex);\r
454     if (NumIdenticalFiles == 0)\r
455       NumIdenticalFiles = 1;\r
456     TempBufMode = false;\r
457     if (numExtractItems > 1)\r
458     {\r
459       if (!TempBuf || item.Size > TempBufSize)\r
460       {\r
461         FreeTempBuf();\r
462         TempBuf = (Byte *)MyAlloc(item.Size);\r
463         TempBufSize = item.Size;\r
464         if (TempBuf == NULL)\r
465           return E_OUTOFMEMORY;\r
466       }\r
467       TempBufMode = true;\r
468       m_BufStartFolderOffset = item.Offset;\r
469     }\r
470     else if (numExtractItems == 1)\r
471     {\r
472       while (NumIdenticalFiles && !(*m_ExtractStatuses)[m_CurrentIndex])\r
473       {\r
474         CMyComPtr<ISequentialOutStream> stream;\r
475         RINOK(m_ExtractCallback->GetStream(m_StartIndex + m_CurrentIndex, &stream, NExtract::NAskMode::kSkip));\r
476         if (stream)\r
477           return E_FAIL;\r
478         RINOK(m_ExtractCallback->PrepareOperation(NExtract::NAskMode::kSkip));\r
479         m_CurrentIndex++;\r
480         m_FileIsOpen = true;\r
481         CloseFile();\r
482       }\r
483     }\r
484   }\r
485 \r
486   Int32 askMode = (*m_ExtractStatuses)[m_CurrentIndex] ? (m_TestMode ?\r
487       NExtract::NAskMode::kTest :\r
488       NExtract::NAskMode::kExtract) :\r
489       NExtract::NAskMode::kSkip;\r
490   RINOK(m_ExtractCallback->GetStream(m_StartIndex + m_CurrentIndex, &m_RealOutStream, askMode));\r
491   if (!m_RealOutStream && !m_TestMode)\r
492     askMode = NExtract::NAskMode::kSkip;\r
493   return m_ExtractCallback->PrepareOperation(askMode);\r
494 }\r
495 \r
496 HRESULT CFolderOutStream::WriteEmptyFiles()\r
497 {\r
498   if (m_FileIsOpen)\r
499     return S_OK;\r
500   for (; m_CurrentIndex < m_ExtractStatuses->Size(); m_CurrentIndex++)\r
501   {\r
502     const CMvItem &mvItem = m_Database->Items[m_StartIndex + m_CurrentIndex];\r
503     const CItem &item = m_Database->Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex];\r
504     UInt64 fileSize = item.Size;\r
505     if (fileSize != 0)\r
506       return S_OK;\r
507     HRESULT result = OpenFile();\r
508     m_RealOutStream.Release();\r
509     RINOK(result);\r
510     RINOK(m_ExtractCallback->SetOperationResult(NExtract::NOperationResult::kOK));\r
511   }\r
512   return S_OK;\r
513 }\r
514 \r
515 // This is Write function\r
516 HRESULT CFolderOutStream::Write2(const void *data, UInt32 size, UInt32 *processedSize, bool isOK)\r
517 {\r
518   COM_TRY_BEGIN\r
519   UInt32 realProcessed = 0;\r
520   if (processedSize != NULL)\r
521    *processedSize = 0;\r
522   while (size != 0)\r
523   {\r
524     if (m_FileIsOpen)\r
525     {\r
526       UInt32 numBytesToWrite = MyMin(m_RemainFileSize, size);\r
527       HRESULT res = S_OK;\r
528       if (numBytesToWrite > 0)\r
529       {\r
530         if (!isOK)\r
531           m_IsOk = false;\r
532         if (m_RealOutStream)\r
533         {\r
534           UInt32 processedSizeLocal = 0;\r
535           res = m_RealOutStream->Write((const Byte *)data, numBytesToWrite, &processedSizeLocal);\r
536           numBytesToWrite = processedSizeLocal;\r
537         }\r
538         if (TempBufMode && TempBuf)\r
539           memcpy(TempBuf + (m_PosInFolder - m_BufStartFolderOffset), data, numBytesToWrite);\r
540       }\r
541       realProcessed += numBytesToWrite;\r
542       if (processedSize != NULL)\r
543         *processedSize = realProcessed;\r
544       data = (const void *)((const Byte *)data + numBytesToWrite);\r
545       size -= numBytesToWrite;\r
546       m_RemainFileSize -= numBytesToWrite;\r
547       m_PosInFolder += numBytesToWrite;\r
548       if (res != S_OK)\r
549         return res;\r
550       if (m_RemainFileSize == 0)\r
551       {\r
552         RINOK(CloseFile());\r
553 \r
554         while (NumIdenticalFiles)\r
555         {\r
556           HRESULT result = OpenFile();\r
557           m_FileIsOpen = true;\r
558           m_CurrentIndex++;\r
559           if (result == S_OK && m_RealOutStream && TempBuf)\r
560             result = WriteStream(m_RealOutStream, TempBuf, (size_t)(m_PosInFolder - m_BufStartFolderOffset));\r
561           \r
562           if (!TempBuf && TempBufMode && m_RealOutStream)\r
563           {\r
564             RINOK(CloseFileWithResOp(NExtract::NOperationResult::kUnSupportedMethod));\r
565           }\r
566           else\r
567           {\r
568             RINOK(CloseFile());\r
569           }\r
570           RINOK(result);\r
571         }\r
572         TempBufMode = false;\r
573       }\r
574       if (realProcessed > 0)\r
575         break; // with this break this function works as Write-Part\r
576     }\r
577     else\r
578     {\r
579       if (m_CurrentIndex >= m_ExtractStatuses->Size())\r
580         return E_FAIL;\r
581 \r
582       const CMvItem &mvItem = m_Database->Items[m_StartIndex + m_CurrentIndex];\r
583       const CItem &item = m_Database->Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex];\r
584 \r
585       m_RemainFileSize = item.Size;\r
586 \r
587       UInt32 fileOffset = item.Offset;\r
588       if (fileOffset < m_PosInFolder)\r
589         return E_FAIL;\r
590       if (fileOffset > m_PosInFolder)\r
591       {\r
592         UInt32 numBytesToWrite = MyMin(fileOffset - (UInt32)m_PosInFolder, size);\r
593         realProcessed += numBytesToWrite;\r
594         if (processedSize != NULL)\r
595           *processedSize = realProcessed;\r
596         data = (const void *)((const Byte *)data + numBytesToWrite);\r
597         size -= numBytesToWrite;\r
598         m_PosInFolder += numBytesToWrite;\r
599       }\r
600       if (fileOffset == m_PosInFolder)\r
601       {\r
602         RINOK(OpenFile());\r
603         m_FileIsOpen = true;\r
604         m_CurrentIndex++;\r
605         m_IsOk = true;\r
606       }\r
607     }\r
608   }\r
609   return WriteEmptyFiles();\r
610   COM_TRY_END\r
611 }\r
612 \r
613 STDMETHODIMP CFolderOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)\r
614 {\r
615   return Write2(data, size, processedSize, true);\r
616 }\r
617 \r
618 HRESULT CFolderOutStream::FlushCorrupted()\r
619 {\r
620   const UInt32 kBufferSize = (1 << 10);\r
621   Byte buffer[kBufferSize];\r
622   for (int i = 0; i < kBufferSize; i++)\r
623     buffer[i] = 0;\r
624   for (;;)\r
625   {\r
626     UInt64 remain = GetRemain();\r
627     if (remain == 0)\r
628       return S_OK;\r
629     UInt32 size = (UInt32)MyMin(remain, (UInt64)kBufferSize);\r
630     UInt32 processedSizeLocal = 0;\r
631     RINOK(Write2(buffer, size, &processedSizeLocal, false));\r
632   }\r
633 }\r
634 \r
635 HRESULT CFolderOutStream::Unsupported()\r
636 {\r
637   while(m_CurrentIndex < m_ExtractStatuses->Size())\r
638   {\r
639     HRESULT result = OpenFile();\r
640     if (result != S_FALSE && result != S_OK)\r
641       return result;\r
642     m_RealOutStream.Release();\r
643     RINOK(m_ExtractCallback->SetOperationResult(NExtract::NOperationResult::kUnSupportedMethod));\r
644     m_CurrentIndex++;\r
645   }\r
646   return S_OK;\r
647 }\r
648 \r
649 \r
650 STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,\r
651     Int32 testModeSpec, IArchiveExtractCallback *extractCallback)\r
652 {\r
653   COM_TRY_BEGIN\r
654   bool allFilesMode = (numItems == (UInt32)-1);\r
655   if (allFilesMode)\r
656     numItems = m_Database.Items.Size();\r
657   if(numItems == 0)\r
658     return S_OK;\r
659   bool testMode = (testModeSpec != 0);\r
660   UInt64 totalUnPacked = 0;\r
661 \r
662   UInt32 i;\r
663   int lastFolder = -2;\r
664   UInt64 lastFolderSize = 0;\r
665   for(i = 0; i < numItems; i++)\r
666   {\r
667     int index = allFilesMode ? i : indices[i];\r
668     const CMvItem &mvItem = m_Database.Items[index];\r
669     const CItem &item = m_Database.Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex];\r
670     if (item.IsDir())\r
671       continue;\r
672     int folderIndex = m_Database.GetFolderIndex(&mvItem);\r
673     if (folderIndex != lastFolder)\r
674       totalUnPacked += lastFolderSize;\r
675     lastFolder = folderIndex;\r
676     lastFolderSize = item.GetEndOffset();\r
677   }\r
678   totalUnPacked += lastFolderSize;\r
679 \r
680   extractCallback->SetTotal(totalUnPacked);\r
681 \r
682   totalUnPacked = 0;\r
683 \r
684   UInt64 totalPacked = 0;\r
685 \r
686   CLocalProgress *lps = new CLocalProgress;\r
687   CMyComPtr<ICompressProgressInfo> progress = lps;\r
688   lps->Init(extractCallback, false);\r
689 \r
690   NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder;\r
691   CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;\r
692 \r
693   NCompress::NDeflate::NDecoder::CCOMCoder *deflateDecoderSpec = NULL;\r
694   CMyComPtr<ICompressCoder> deflateDecoder;\r
695 \r
696   NCompress::NLzx::CDecoder *lzxDecoderSpec = NULL;\r
697   CMyComPtr<ICompressCoder> lzxDecoder;\r
698 \r
699   NCompress::NQuantum::CDecoder *quantumDecoderSpec = NULL;\r
700   CMyComPtr<ICompressCoder> quantumDecoder;\r
701 \r
702   CCabBlockInStream *cabBlockInStreamSpec = new CCabBlockInStream();\r
703   CMyComPtr<ISequentialInStream> cabBlockInStream = cabBlockInStreamSpec;\r
704   if (!cabBlockInStreamSpec->Create())\r
705     return E_OUTOFMEMORY;\r
706 \r
707   CRecordVector<bool> extractStatuses;\r
708   for(i = 0; i < numItems;)\r
709   {\r
710     int index = allFilesMode ? i : indices[i];\r
711 \r
712     const CMvItem &mvItem = m_Database.Items[index];\r
713     const CDatabaseEx &db = m_Database.Volumes[mvItem.VolumeIndex];\r
714     int itemIndex = mvItem.ItemIndex;\r
715     const CItem &item = db.Items[itemIndex];\r
716 \r
717     i++;\r
718     if (item.IsDir())\r
719     {\r
720       Int32 askMode = testMode ?\r
721           NExtract::NAskMode::kTest :\r
722           NExtract::NAskMode::kExtract;\r
723       CMyComPtr<ISequentialOutStream> realOutStream;\r
724       RINOK(extractCallback->GetStream(index, &realOutStream, askMode));\r
725       RINOK(extractCallback->PrepareOperation(askMode));\r
726       realOutStream.Release();\r
727       RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));\r
728       continue;\r
729     }\r
730     int folderIndex = m_Database.GetFolderIndex(&mvItem);\r
731     if (folderIndex < 0)\r
732     {\r
733       // If we need previous archive\r
734       Int32 askMode= testMode ?\r
735           NExtract::NAskMode::kTest :\r
736           NExtract::NAskMode::kExtract;\r
737       CMyComPtr<ISequentialOutStream> realOutStream;\r
738       RINOK(extractCallback->GetStream(index, &realOutStream, askMode));\r
739       RINOK(extractCallback->PrepareOperation(askMode));\r
740       realOutStream.Release();\r
741       RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kDataError));\r
742       continue;\r
743     }\r
744     int startIndex2 = m_Database.FolderStartFileIndex[folderIndex];\r
745     int startIndex = startIndex2;\r
746     extractStatuses.Clear();\r
747     for (; startIndex < index; startIndex++)\r
748       extractStatuses.Add(false);\r
749     extractStatuses.Add(true);\r
750     startIndex++;\r
751     UInt64 curUnpack = item.GetEndOffset();\r
752     for(;i < numItems; i++)\r
753     {\r
754       int indexNext = allFilesMode ? i : indices[i];\r
755       const CMvItem &mvItem = m_Database.Items[indexNext];\r
756       const CItem &item = m_Database.Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex];\r
757       if (item.IsDir())\r
758         continue;\r
759       int newFolderIndex = m_Database.GetFolderIndex(&mvItem);\r
760 \r
761       if (newFolderIndex != folderIndex)\r
762         break;\r
763       for (; startIndex < indexNext; startIndex++)\r
764         extractStatuses.Add(false);\r
765       extractStatuses.Add(true);\r
766       startIndex++;\r
767       curUnpack = item.GetEndOffset();\r
768     }\r
769 \r
770     lps->OutSize = totalUnPacked;\r
771     lps->InSize = totalPacked;\r
772     RINOK(lps->SetCur());\r
773 \r
774     CFolderOutStream *cabFolderOutStream = new CFolderOutStream;\r
775     CMyComPtr<ISequentialOutStream> outStream(cabFolderOutStream);\r
776 \r
777     const CFolder &folder = db.Folders[item.GetFolderIndex(db.Folders.Size())];\r
778 \r
779     cabFolderOutStream->Init(&m_Database, &extractStatuses, startIndex2,\r
780         curUnpack, extractCallback, testMode);\r
781 \r
782     cabBlockInStreamSpec->MsZip = false;\r
783     switch(folder.GetCompressionMethod())\r
784     {\r
785       case NHeader::NCompressionMethodMajor::kNone:\r
786         break;\r
787       case NHeader::NCompressionMethodMajor::kMSZip:\r
788         if(deflateDecoderSpec == NULL)\r
789         {\r
790           deflateDecoderSpec = new NCompress::NDeflate::NDecoder::CCOMCoder;\r
791           deflateDecoder = deflateDecoderSpec;\r
792         }\r
793         cabBlockInStreamSpec->MsZip = true;\r
794         break;\r
795       case NHeader::NCompressionMethodMajor::kLZX:\r
796         if(lzxDecoderSpec == NULL)\r
797         {\r
798           lzxDecoderSpec = new NCompress::NLzx::CDecoder;\r
799           lzxDecoder = lzxDecoderSpec;\r
800         }\r
801         RINOK(lzxDecoderSpec->SetParams(folder.CompressionTypeMinor));\r
802         break;\r
803       case NHeader::NCompressionMethodMajor::kQuantum:\r
804         if(quantumDecoderSpec == NULL)\r
805         {\r
806           quantumDecoderSpec = new NCompress::NQuantum::CDecoder;\r
807           quantumDecoder = quantumDecoderSpec;\r
808         }\r
809         quantumDecoderSpec->SetParams(folder.CompressionTypeMinor);\r
810         break;\r
811       default:\r
812       {\r
813         RINOK(cabFolderOutStream->Unsupported());\r
814         totalUnPacked += curUnpack;\r
815         continue;\r
816       }\r
817     }\r
818 \r
819     cabBlockInStreamSpec->InitForNewFolder();\r
820 \r
821     HRESULT res = S_OK;\r
822 \r
823     {\r
824       int volIndex = mvItem.VolumeIndex;\r
825       int locFolderIndex = item.GetFolderIndex(db.Folders.Size());\r
826       bool keepHistory = false;\r
827       bool keepInputBuffer = false;\r
828       for (UInt32 f = 0; cabFolderOutStream->GetRemain() != 0;)\r
829       {\r
830         if (volIndex >= m_Database.Volumes.Size())\r
831         {\r
832           res = S_FALSE;\r
833           break;\r
834         }\r
835 \r
836         const CDatabaseEx &db = m_Database.Volumes[volIndex];\r
837         const CFolder &folder = db.Folders[locFolderIndex];\r
838         if (f == 0)\r
839         {\r
840           cabBlockInStreamSpec->SetStream(db.Stream);\r
841           cabBlockInStreamSpec->ReservedSize = db.ArchiveInfo.GetDataBlockReserveSize();\r
842           RINOK(db.Stream->Seek(db.StartPosition + folder.DataStart, STREAM_SEEK_SET, NULL));\r
843         }\r
844         if (f == folder.NumDataBlocks)\r
845         {\r
846           volIndex++;\r
847           locFolderIndex = 0;\r
848           f = 0;\r
849           continue;\r
850         }\r
851         f++;\r
852 \r
853         cabBlockInStreamSpec->DataError = false;\r
854         \r
855         if (!keepInputBuffer)\r
856           cabBlockInStreamSpec->InitForNewBlock();\r
857 \r
858         UInt32 packSize, unpackSize;\r
859         res = cabBlockInStreamSpec->PreRead(packSize, unpackSize);\r
860         if (res == S_FALSE)\r
861           break;\r
862         RINOK(res);\r
863         keepInputBuffer = (unpackSize == 0);\r
864         if (keepInputBuffer)\r
865           continue;\r
866 \r
867         UInt64 totalUnPacked2 = totalUnPacked + cabFolderOutStream->GetPosInFolder();\r
868         totalPacked += packSize;\r
869 \r
870         lps->OutSize = totalUnPacked2;\r
871         lps->InSize = totalPacked;\r
872         RINOK(lps->SetCur());\r
873 \r
874         UInt64 unpackRemain = cabFolderOutStream->GetRemain();\r
875 \r
876         const UInt32 kBlockSizeMax = (1 << 15);\r
877         if (unpackRemain > kBlockSizeMax)\r
878           unpackRemain = kBlockSizeMax;\r
879         if (unpackRemain > unpackSize)\r
880           unpackRemain  = unpackSize;\r
881    \r
882         switch(folder.GetCompressionMethod())\r
883         {\r
884           case NHeader::NCompressionMethodMajor::kNone:\r
885             res = copyCoder->Code(cabBlockInStream, outStream, NULL, &unpackRemain, NULL);\r
886             break;\r
887           case NHeader::NCompressionMethodMajor::kMSZip:\r
888             deflateDecoderSpec->SetKeepHistory(keepHistory);\r
889             res = deflateDecoder->Code(cabBlockInStream, outStream, NULL, &unpackRemain, NULL);\r
890             break;\r
891           case NHeader::NCompressionMethodMajor::kLZX:\r
892             lzxDecoderSpec->SetKeepHistory(keepHistory);\r
893             res = lzxDecoder->Code(cabBlockInStream, outStream, NULL, &unpackRemain, NULL);\r
894             break;\r
895           case NHeader::NCompressionMethodMajor::kQuantum:\r
896             quantumDecoderSpec->SetKeepHistory(keepHistory);\r
897             res = quantumDecoder->Code(cabBlockInStream, outStream, NULL, &unpackRemain, NULL);\r
898           break;\r
899         }\r
900         if (res != S_OK)\r
901         {\r
902           if (res != S_FALSE)\r
903             RINOK(res);\r
904           break;\r
905         }\r
906         keepHistory = true;\r
907       }\r
908       if (res == S_OK)\r
909       {\r
910         RINOK(cabFolderOutStream->WriteEmptyFiles());\r
911       }\r
912     }\r
913     if (res != S_OK || cabFolderOutStream->GetRemain() != 0)\r
914     {\r
915       RINOK(cabFolderOutStream->FlushCorrupted());\r
916     }\r
917     totalUnPacked += curUnpack;\r
918   }\r
919   return S_OK;\r
920   COM_TRY_END\r
921 }\r
922 \r
923 STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)\r
924 {\r
925   *numItems = m_Database.Items.Size();\r
926   return S_OK;\r
927 }\r
928 \r
929 }}\r