1 #include <QCoreApplication>
2 #include <QTest>
3
4 #include "sevenzipfile.h"
5
SevenZipFile(QString fileName,QObject * parent)6 SevenZipFile::SevenZipFile(QString fileName, QObject *parent) :
7 QObject(parent),
8 m_blockIndex(0xFFFFFFFF),
9 m_bufferSize(0),
10 m_sizeProcessed(0),
11 m_buffer(0),
12 m_fileName(fileName),
13 m_extractor(0),
14 m_isOpen(false),
15 m_firstExtraction(true),
16 m_fillingDictionary(false)
17 {
18 m_allocImp.Alloc = SzAlloc;
19 m_allocImp.Free = SzFree;
20 m_allocTempImp.Alloc = SzAllocTemp;
21 m_allocTempImp.Free = SzFreeTemp;
22 }
23
~SevenZipFile()24 SevenZipFile::~SevenZipFile()
25 {
26 IAlloc_Free(&m_allocImp, m_buffer);
27 }
28
read(QString name,QByteArray * buffer)29 quint64 SevenZipFile::read(QString name, QByteArray *buffer)
30 {
31 m_lastError.clear();
32 int index = indexOfName(name);
33 if ( index >= 0 )
34 return read(index, buffer);
35 else {
36 m_lastError = tr("file name '%1' not found").arg(name);
37 emit error(lastError());
38 return 0;
39 }
40 }
41
read(uint index,QByteArray * buffer,bool * async)42 quint64 SevenZipFile::read(uint index, QByteArray *buffer, bool *async)
43 {
44 m_lastError.clear();
45 if ( !isOpen() ) {
46 m_lastError = tr("archive not open");
47 emit error(lastError());
48 return 0;
49 }
50 if ( !buffer ) {
51 m_lastError = tr("null-buffer not allowed");
52 emit error(lastError());
53 return 0;
54 }
55 if ( m_firstExtraction ) {
56 // do the first extraction in a separate thread because 7z decompresses the *complete* LZMA stream at once (and returns pointers to already decompressed data on subsequent extractions)
57 if ( !m_extractor ) {
58 m_extractor = new SevenZipExtractorThread(this);
59 connect(m_extractor, SIGNAL(extracted(uint)), this, SLOT(asyncExtractionFinished(uint)));
60 connect(m_extractor, SIGNAL(failed(uint)), this, SLOT(asyncExtractionFinished(uint)));
61 m_extractor->setParams(db(), &m_lookStream.s, index, &m_blockIndex, &m_buffer, &m_bufferSize, &m_offsetMap[index], &m_sizeProcessed, &m_allocImp, &m_allocTempImp);
62 }
63 if ( async && *async ) {
64 if ( m_extractor->isActive() )
65 return 0;
66 if ( m_extractor->fileCount() == 0 ) {
67 buffer->clear();
68 m_sizeProcessed = 0;
69 m_fillingDictionary = true;
70 m_extractor->waitCondition().wakeAll();
71 }
72 if ( isFillingDictionary() )
73 return 0;
74 if ( m_extractor->result() == SZ_OK )
75 buffer->setRawData((const char *)(m_buffer + m_offsetMap[index]), m_sizeProcessed);
76 else {
77 m_lastError = tr("extraction of file '%1' (index %2) failed").arg(entryList()[index].name()).arg(index) + " - " + errorCodeToString(m_extractor->result());
78 emit error(lastError());
79 m_sizeProcessed = 0;
80 }
81 delete m_extractor;
82 m_extractor = 0;
83 m_firstExtraction = false;
84 *async = false;
85 return m_sizeProcessed;
86 } else {
87 m_fillingDictionary = true;
88 buffer->clear();
89 m_sizeProcessed = 0;
90 while ( isFillingDictionary() ) {
91 m_extractor->waitCondition().wakeAll();
92 QTest::qWait(25);
93 qApp->processEvents();
94 }
95 if ( m_extractor->result() == SZ_OK )
96 buffer->setRawData((const char *)(m_buffer + m_offsetMap[index]), m_sizeProcessed);
97 else {
98 m_lastError = tr("extraction of file '%1' (index %2) failed").arg(entryList()[index].name()).arg(index) + " - " + errorCodeToString(m_extractor->result());
99 emit error(lastError());
100 m_sizeProcessed = 0;
101 }
102 delete m_extractor;
103 m_extractor = 0;
104 m_firstExtraction = false;
105 if ( async )
106 *async = false;
107 return m_sizeProcessed;
108 }
109 } else {
110 buffer->clear();
111 m_sizeProcessed = 0;
112 SRes result = SzArEx_Extract(db(), &m_lookStream.s, index, &m_blockIndex, &m_buffer, &m_bufferSize, &m_offsetMap[index], &m_sizeProcessed, &m_allocImp, &m_allocTempImp);
113 if ( result == SZ_OK )
114 buffer->setRawData((const char *)(m_buffer + m_offsetMap[index]), m_sizeProcessed);
115 else {
116 m_lastError = tr("extraction of file '%1' (index %2) failed").arg(entryList()[index].name()).arg(index) + " - " + errorCodeToString(result);
117 emit error(lastError());
118 m_sizeProcessed = 0;
119 }
120 if ( async )
121 *async = false;
122 return m_sizeProcessed;
123 }
124 }
125
readBig(uint index,BigByteArray * buffer,bool * async)126 quint64 SevenZipFile::readBig(uint index, BigByteArray *buffer, bool *async)
127 {
128 m_lastError.clear();
129 if ( !isOpen() ) {
130 m_lastError = tr("archive not open");
131 emit error(lastError());
132 return 0;
133 }
134 if ( !buffer ) {
135 m_lastError = tr("null-buffer not allowed");
136 emit error(lastError());
137 return 0;
138 }
139 if ( m_firstExtraction ) {
140 // do the first extraction in a separate thread because 7z decompresses the *complete* LZMA stream at once (and returns pointers to already decompressed data on subsequent extractions)
141 if ( !m_extractor ) {
142 m_extractor = new SevenZipExtractorThread(this);
143 connect(m_extractor, SIGNAL(extracted(uint)), this, SLOT(asyncExtractionFinished(uint)));
144 connect(m_extractor, SIGNAL(failed(uint)), this, SLOT(asyncExtractionFinished(uint)));
145 m_extractor->setParams(db(), &m_lookStream.s, index, &m_blockIndex, &m_buffer, &m_bufferSize, &m_offsetMap[index], &m_sizeProcessed, &m_allocImp, &m_allocTempImp);
146 }
147 if ( async && *async ) {
148 if ( m_extractor->isActive() )
149 return 0;
150 if ( m_extractor->fileCount() == 0 ) {
151 buffer->clear();
152 m_sizeProcessed = 0;
153 m_fillingDictionary = true;
154 m_extractor->waitCondition().wakeAll();
155 }
156 if ( isFillingDictionary() )
157 return 0;
158 if ( m_extractor->result() == SZ_OK )
159 buffer->setRawData((const char *)(m_buffer + m_offsetMap[index]), m_sizeProcessed);
160 else {
161 m_lastError = tr("extraction of file '%1' (index %2) failed").arg(entryList()[index].name()).arg(index) + " - " + errorCodeToString(m_extractor->result());
162 emit error(lastError());
163 m_sizeProcessed = 0;
164 }
165 delete m_extractor;
166 m_extractor = 0;
167 m_firstExtraction = false;
168 *async = false;
169 return m_sizeProcessed;
170 } else {
171 m_fillingDictionary = true;
172 buffer->clear();
173 m_sizeProcessed = 0;
174 while ( isFillingDictionary() ) {
175 m_extractor->waitCondition().wakeAll();
176 QTest::qWait(25);
177 qApp->processEvents();
178 }
179 if ( m_extractor->result() == SZ_OK )
180 buffer->setRawData((const char *)(m_buffer + m_offsetMap[index]), m_sizeProcessed);
181 else {
182 m_lastError = tr("extraction of file '%1' (index %2) failed").arg(entryList()[index].name()).arg(index) + " - " + errorCodeToString(m_extractor->result());
183 emit error(lastError());
184 m_sizeProcessed = 0;
185 }
186 delete m_extractor;
187 m_extractor = 0;
188 m_firstExtraction = false;
189 if ( async )
190 *async = false;
191 return m_sizeProcessed;
192 }
193 } else {
194 buffer->clear();
195 m_sizeProcessed = 0;
196 SRes result = SzArEx_Extract(db(), &m_lookStream.s, index, &m_blockIndex, &m_buffer, &m_bufferSize, &m_offsetMap[index], &m_sizeProcessed, &m_allocImp, &m_allocTempImp);
197 if ( result == SZ_OK )
198 buffer->setRawData((const char *)(m_buffer + m_offsetMap[index]), m_sizeProcessed);
199 else {
200 m_lastError = tr("extraction of file '%1' (index %2) failed").arg(entryList()[index].name()).arg(index) + " - " + errorCodeToString(result);
201 emit error(lastError());
202 m_sizeProcessed = 0;
203 }
204 if ( async )
205 *async = false;
206 return m_sizeProcessed;
207 }
208 }
209
errorCodeToString(SRes errorCode)210 QString SevenZipFile::errorCodeToString(SRes errorCode)
211 {
212 switch ( errorCode ) {
213 case SZ_OK:
214 return tr("no error");
215 case SZ_ERROR_DATA:
216 return tr("incorrect data");
217 case SZ_ERROR_MEM:
218 return tr("out of memory");
219 case SZ_ERROR_CRC:
220 return tr("incorrect CRC");
221 case SZ_ERROR_UNSUPPORTED:
222 return tr("unsupported compression");
223 case SZ_ERROR_PARAM:
224 return tr("incorrect properties");
225 case SZ_ERROR_INPUT_EOF:
226 return tr("premature end-of-file (input)");
227 case SZ_ERROR_OUTPUT_EOF:
228 return tr("premature end-of-file (output)");
229 case SZ_ERROR_READ:
230 return tr("failed reading");
231 case SZ_ERROR_WRITE:
232 return tr("failed writing");
233 case SZ_ERROR_PROGRESS:
234 return tr("failed signalling progress");
235 case SZ_ERROR_FAIL:
236 return tr("fatal error");
237 case SZ_ERROR_THREAD:
238 return tr("thread error");
239 case SZ_ERROR_ARCHIVE:
240 return tr("invalid archive structure");
241 case SZ_ERROR_NO_ARCHIVE:
242 return tr("invalid header structure");
243 default:
244 return tr("unknown error");
245 }
246 }
247
open(QString fileName)248 bool SevenZipFile::open(QString fileName)
249 {
250 m_lastError.clear();
251
252 if ( isOpen() )
253 close();
254
255 if ( !fileName.isEmpty() )
256 m_fileName = fileName;
257
258 if ( m_fileName.isEmpty() ) {
259 m_lastError = tr("no file name specified");
260 emit error(lastError());
261 return false;
262 }
263
264 if ( InFile_Open(&m_archiveStream.file, m_fileName.toUtf8().constData()) ) {
265 m_lastError = tr("can't open archive '%1'").arg(m_fileName);
266 emit error(lastError());
267 return false;
268 }
269
270 FileInStream_CreateVTable(&m_archiveStream);
271 LookToRead_CreateVTable(&m_lookStream, false);
272 m_lookStream.realStream = &m_archiveStream.s;
273 LookToRead_Init(&m_lookStream);
274 CrcGenerateTable();
275 SzArEx_Init(db());
276
277 SRes result = SzArEx_Open(db(), &m_lookStream.s, &m_allocImp, &m_allocTempImp);
278
279 if ( result == SZ_OK ) {
280 m_isOpen = true;
281 createEntryList();
282 emit opened();
283 return true;
284 } else {
285 m_lastError = tr("can't open archive '%1'").arg(m_fileName) + ", " + errorCodeToString(result);
286 emit error(lastError());
287 return false;
288 }
289 }
290
close()291 void SevenZipFile::close()
292 {
293 m_lastError.clear();
294
295 if ( isOpen() ) {
296 File_Close(&m_archiveStream.file);
297 SzArEx_Free(db(), &m_allocImp);
298 emit closed();
299 }
300
301 entryList().clear();
302 m_nameToIndexCache.clear();
303 m_crcToIndexCache.clear();
304 m_isOpen = false;
305 m_firstExtraction = true;
306 }
307
asyncExtractionFinished(uint)308 void SevenZipFile::asyncExtractionFinished(uint /*index*/)
309 {
310 m_fillingDictionary = false;
311 emit dataReady();
312 }
313
314 #define QMC2_SEVENZIP_PERIOD_4 (4 * 365 + 1)
315 #define QMC2_SEVENZIP_PERIOD_100 (QMC2_SEVENZIP_PERIOD_4 * 25 - 1)
316 #define QMC2_SEVENZIP_PERIOD_400 (QMC2_SEVENZIP_PERIOD_100 * 4 + 1)
317
convertFileTime(const CNtfsFileTime * ft)318 QDateTime SevenZipFile::convertFileTime(const CNtfsFileTime *ft)
319 {
320 unsigned year, mon, day, hour, min, sec;
321 UInt64 v64 = (ft->Low | ((UInt64)ft->High << 32)) / 10000000;
322 Byte ms[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
323 unsigned t;
324 UInt32_7z v;
325 sec = (unsigned)(v64 % 60);
326 v64 /= 60;
327 min = (unsigned)(v64 % 60);
328 v64 /= 60;
329 hour = (unsigned)(v64 % 24);
330 v64 /= 24;
331 v = (UInt32_7z)v64;
332 year = (unsigned)(1601 + v / QMC2_SEVENZIP_PERIOD_400 * 400);
333 v %= QMC2_SEVENZIP_PERIOD_400;
334 t = v / QMC2_SEVENZIP_PERIOD_100;
335 if ( t == 4 )
336 t = 3;
337 year += t * 100;
338 v -= t * QMC2_SEVENZIP_PERIOD_100;
339 t = v / QMC2_SEVENZIP_PERIOD_4;
340 if ( t == 25 )
341 t = 24;
342 year += t * 4;
343 v -= t * QMC2_SEVENZIP_PERIOD_4;
344 t = v / 365;
345 if ( t == 4 )
346 t = 3;
347 year += t;
348 v -= t * 365;
349 if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))
350 ms[1] = 29;
351 for (mon = 1; mon <= 12; mon++) {
352 unsigned s = ms[mon - 1];
353 if ( v < s )
354 break;
355 v -= s;
356 }
357 day = (unsigned)v + 1;
358 QDateTime dateTime;
359 dateTime.setDate(QDate(year, mon, day));
360 dateTime.setTime(QTime(hour, min, sec));
361 return dateTime;
362 }
363
createEntryList()364 void SevenZipFile::createEntryList()
365 {
366 entryList().clear();
367 m_nameToIndexCache.clear();
368 m_crcToIndexCache.clear();
369 m_crcDuplicates.clear();
370 if ( !isOpen() )
371 return;
372 for (uint i = 0; i < db()->NumFiles; i++)
373 {
374 if ( SzArEx_IsDir(db(), i) )
375 continue;
376 int fileItemLength = SzArEx_GetFileNameUtf16(db(), i, 0);
377 UInt16 *tempFileName = (UInt16 *)SzAlloc(0, fileItemLength * sizeof(UInt16));
378 SzArEx_GetFileNameUtf16(db(), i, tempFileName);
379 QString fileItemName(QString::fromUtf16(tempFileName, fileItemLength - 1));
380 m_nameToIndexCache.insert(fileItemName, i);
381 SzFree(0, tempFileName);
382 QDateTime dateTime;
383 if ( SzBitWithVals_Check(&db()->MTime, i) )
384 dateTime = convertFileTime(&db()->MTime.Vals[i]);
385 QString crc("00000000");
386 if ( SzBitWithVals_Check(&db()->CRCs, i) ) {
387 crc = QString::number(db()->CRCs.Vals[i], 16).rightJustified(8, '0');
388 m_crcToIndexCache.insert(crc, i);
389 m_crcDuplicates.insert(crc, m_crcDuplicates.contains(crc));
390 }
391 entryList() << SevenZipMetaData(fileItemName, SzArEx_GetFileSize(db(), i), dateTime, crc);
392 }
393 }
394
SevenZipExtractorThread(QObject * parent)395 SevenZipExtractorThread::SevenZipExtractorThread(QObject *parent) :
396 QThread(parent),
397 m_quitFlag(false),
398 m_active(false),
399 m_fileCount(0),
400 m_result(SZ_OK)
401 {
402 start();
403 }
404
~SevenZipExtractorThread()405 SevenZipExtractorThread::~SevenZipExtractorThread()
406 {
407 setQuitFlag(true);
408 waitCondition().wakeAll();
409 wait();
410 quit();
411 }
412
setParams(CSzArEx * db,ILookInStream * lookInStream,uint fileIndex,UInt32_7z * blockIndex,Byte ** buffer,size_t * bufferSize,size_t * offset,size_t * sizeProcessed,ISzAlloc * allocImp,ISzAlloc * allocTempImp)413 void SevenZipExtractorThread::setParams(CSzArEx *db, ILookInStream *lookInStream, uint fileIndex, UInt32_7z *blockIndex, Byte **buffer, size_t *bufferSize, size_t *offset, size_t *sizeProcessed, ISzAlloc *allocImp, ISzAlloc *allocTempImp)
414 {
415 m_db = db;
416 m_lookInStream = lookInStream;
417 m_fileIndex = fileIndex;
418 m_blockIndex = blockIndex;
419 m_buffer = buffer;
420 m_bufferSize = bufferSize;
421 m_offset = offset;
422 m_sizeProcessed = sizeProcessed;
423 m_allocImp = allocImp;
424 m_allocTempImp = allocTempImp;
425 }
426
run()427 void SevenZipExtractorThread::run()
428 {
429 while ( !quitFlag() ) {
430 m_active = false;
431 waitMutex().lock();
432 waitCondition().wait(&m_waitMutex);
433 waitMutex().unlock();
434 m_active = true;
435 m_fileCount++;
436 if ( !quitFlag() ) {
437 // we assume that all 7z params are set when we are triggered to go on!
438 m_result = SzArEx_Extract(m_db, m_lookInStream, m_fileIndex, m_blockIndex, m_buffer, m_bufferSize, m_offset, m_sizeProcessed, m_allocImp, m_allocTempImp);
439 if ( result() == SZ_OK )
440 emit extracted(m_fileIndex);
441 else
442 emit failed(m_fileIndex);
443 }
444 }
445 }
446