1 |
/* |
2 |
Copyright (C) 2005-2014 Sergey A. Tachenov |
3 |
|
4 |
This file is part of QuaZIP. |
5 |
|
6 |
QuaZIP is free software: you can redistribute it and/or modify |
7 |
it under the terms of the GNU Lesser General Public License as published by |
8 |
the Free Software Foundation, either version 2.1 of the License, or |
9 |
(at your option) any later version. |
10 |
|
11 |
QuaZIP is distributed in the hope that it will be useful, |
12 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 |
GNU Lesser General Public License for more details. |
15 |
|
16 |
You should have received a copy of the GNU Lesser General Public License |
17 |
along with QuaZIP. If not, see <http://www.gnu.org/licenses/>. |
18 |
|
19 |
See COPYING file for the full LGPL text. |
20 |
|
21 |
Original ZIP package is copyrighted by Gilles Vollant and contributors, |
22 |
see quazip/(un)zip.h files for details. Basically it's the zlib license. |
23 |
*/ |
24 |
|
25 |
#include "quazipdir.h" |
26 |
|
27 |
#include <QSet> |
28 |
#include <QSharedData> |
29 |
|
30 |
/// \cond internal |
31 |
class QuaZipDirPrivate: public QSharedData { |
32 |
friend class QuaZipDir; |
33 |
private: |
34 |
QuaZipDirPrivate(QuaZip *zip, const QString &dir = QString()): |
35 |
zip(zip), dir(dir), caseSensitivity(QuaZip::csDefault), |
36 |
filter(QDir::NoFilter), sorting(QDir::NoSort) {} |
37 |
QuaZip *zip; |
38 |
QString dir; |
39 |
QuaZip::CaseSensitivity caseSensitivity; |
40 |
QDir::Filters filter; |
41 |
QStringList nameFilters; |
42 |
QDir::SortFlags sorting; |
43 |
template<typename TFileInfoList> |
44 |
bool entryInfoList(QStringList nameFilters, QDir::Filters filter, |
45 |
QDir::SortFlags sort, TFileInfoList &result) const; |
46 |
inline QString simplePath() const {return QDir::cleanPath(dir);} |
47 |
}; |
48 |
/// \endcond |
49 |
|
50 |
QuaZipDir::QuaZipDir(const QuaZipDir &that): |
51 |
d(that.d) |
52 |
{ |
53 |
} |
54 |
|
55 |
QuaZipDir::QuaZipDir(QuaZip *zip, const QString &dir): |
56 |
d(new QuaZipDirPrivate(zip, dir)) |
57 |
{ |
58 |
if (d->dir.startsWith('/')) |
59 |
d->dir = d->dir.mid(1); |
60 |
} |
61 |
|
62 |
QuaZipDir::~QuaZipDir() |
63 |
{ |
64 |
} |
65 |
|
66 |
bool QuaZipDir::operator==(const QuaZipDir &that) |
67 |
{ |
68 |
return d->zip == that.d->zip && d->dir == that.d->dir; |
69 |
} |
70 |
|
71 |
QuaZipDir& QuaZipDir::operator=(const QuaZipDir &that) |
72 |
{ |
73 |
this->d = that.d; |
74 |
return *this; |
75 |
} |
76 |
|
77 |
QString QuaZipDir::operator[](int pos) const |
78 |
{ |
79 |
return entryList().at(pos); |
80 |
} |
81 |
|
82 |
QuaZip::CaseSensitivity QuaZipDir::caseSensitivity() const |
83 |
{ |
84 |
return d->caseSensitivity; |
85 |
} |
86 |
|
87 |
bool QuaZipDir::cd(const QString &directoryName) |
88 |
{ |
89 |
if (directoryName == "/") { |
90 |
d->dir = ""; |
91 |
return true; |
92 |
} |
93 |
QString dirName = directoryName; |
94 |
if (dirName.endsWith('/')) |
95 |
dirName.chop(1); |
96 |
if (dirName.contains('/')) { |
97 |
QuaZipDir dir(*this); |
98 |
if (dirName.startsWith('/')) { |
99 |
#ifdef QUAZIP_QUAZIPDIR_DEBUG |
100 |
qDebug("QuaZipDir::cd(%s): going to /", |
101 |
dirName.toUtf8().constData()); |
102 |
#endif |
103 |
if (!dir.cd("/")) |
104 |
return false; |
105 |
} |
106 |
QStringList path = dirName.split('/', QString::SkipEmptyParts); |
107 |
for (QStringList::const_iterator i = path.constBegin(); |
108 |
i != path.end(); |
109 |
++i) { |
110 |
const QString &step = *i; |
111 |
#ifdef QUAZIP_QUAZIPDIR_DEBUG |
112 |
qDebug("QuaZipDir::cd(%s): going to %s", |
113 |
dirName.toUtf8().constData(), |
114 |
step.toUtf8().constData()); |
115 |
#endif |
116 |
if (!dir.cd(step)) |
117 |
return false; |
118 |
} |
119 |
d->dir = dir.path(); |
120 |
return true; |
121 |
} else { // no '/' |
122 |
if (dirName == ".") { |
123 |
return true; |
124 |
} else if (dirName == "..") { |
125 |
if (isRoot()) { |
126 |
return false; |
127 |
} else { |
128 |
int slashPos = d->dir.lastIndexOf('/'); |
129 |
if (slashPos == -1) { |
130 |
d->dir = ""; |
131 |
} else { |
132 |
d->dir = d->dir.left(slashPos); |
133 |
} |
134 |
return true; |
135 |
} |
136 |
} else { // a simple subdirectory |
137 |
if (exists(dirName)) { |
138 |
if (isRoot()) |
139 |
d->dir = dirName; |
140 |
else |
141 |
d->dir += "/" + dirName; |
142 |
return true; |
143 |
} else { |
144 |
return false; |
145 |
} |
146 |
} |
147 |
} |
148 |
} |
149 |
|
150 |
bool QuaZipDir::cdUp() |
151 |
{ |
152 |
return cd(".."); |
153 |
} |
154 |
|
155 |
uint QuaZipDir::count() const |
156 |
{ |
157 |
return entryList().count(); |
158 |
} |
159 |
|
160 |
QString QuaZipDir::dirName() const |
161 |
{ |
162 |
return QDir(d->dir).dirName(); |
163 |
} |
164 |
|
165 |
QuaZipFileInfo64 QuaZipDir_getFileInfo(QuaZip *zip, bool *ok, |
166 |
const QString &relativeName, |
167 |
bool isReal) |
168 |
{ |
169 |
QuaZipFileInfo64 info; |
170 |
if (isReal) { |
171 |
*ok = zip->getCurrentFileInfo(&info); |
172 |
} else { |
173 |
*ok = true; |
174 |
info.compressedSize = 0; |
175 |
info.crc = 0; |
176 |
info.diskNumberStart = 0; |
177 |
info.externalAttr = 0; |
178 |
info.flags = 0; |
179 |
info.internalAttr = 0; |
180 |
info.method = 0; |
181 |
info.uncompressedSize = 0; |
182 |
info.versionCreated = info.versionNeeded = 0; |
183 |
} |
184 |
info.name = relativeName; |
185 |
return info; |
186 |
} |
187 |
|
188 |
static void QuaZipDir_convertInfoList(const QList<QuaZipFileInfo64> &from, |
189 |
QList<QuaZipFileInfo64> &to) |
190 |
{ |
191 |
to = from; |
192 |
} |
193 |
|
194 |
static void QuaZipDir_convertInfoList(const QList<QuaZipFileInfo64> &from, |
195 |
QStringList &to) |
196 |
{ |
197 |
to.clear(); |
198 |
for (QList<QuaZipFileInfo64>::const_iterator i = from.constBegin(); |
199 |
i != from.constEnd(); |
200 |
++i) { |
201 |
to.append(i->name); |
202 |
} |
203 |
} |
204 |
|
205 |
static void QuaZipDir_convertInfoList(const QList<QuaZipFileInfo64> &from, |
206 |
QList<QuaZipFileInfo> &to) |
207 |
{ |
208 |
to.clear(); |
209 |
for (QList<QuaZipFileInfo64>::const_iterator i = from.constBegin(); |
210 |
i != from.constEnd(); |
211 |
++i) { |
212 |
QuaZipFileInfo info32; |
213 |
i->toQuaZipFileInfo(info32); |
214 |
to.append(info32); |
215 |
} |
216 |
} |
217 |
|
218 |
/// \cond internal |
219 |
/** |
220 |
An utility class to restore the current file. |
221 |
*/ |
222 |
class QuaZipDirRestoreCurrent { |
223 |
public: |
224 |
inline QuaZipDirRestoreCurrent(QuaZip *zip): |
225 |
zip(zip), currentFile(zip->getCurrentFileName()) {} |
226 |
inline ~QuaZipDirRestoreCurrent() |
227 |
{ |
228 |
zip->setCurrentFile(currentFile); |
229 |
} |
230 |
private: |
231 |
QuaZip *zip; |
232 |
QString currentFile; |
233 |
}; |
234 |
/// \endcond |
235 |
|
236 |
/// \cond internal |
237 |
class QuaZipDirComparator |
238 |
{ |
239 |
private: |
240 |
QDir::SortFlags sort; |
241 |
static QString getExtension(const QString &name); |
242 |
int compareStrings(const QString &string1, const QString &string2); |
243 |
public: |
244 |
inline QuaZipDirComparator(QDir::SortFlags sort): sort(sort) {} |
245 |
bool operator()(const QuaZipFileInfo64 &info1, const QuaZipFileInfo64 &info2); |
246 |
}; |
247 |
|
248 |
QString QuaZipDirComparator::getExtension(const QString &name) |
249 |
{ |
250 |
if (name.endsWith('.') || name.indexOf('.', 1) == -1) { |
251 |
return ""; |
252 |
} else { |
253 |
return name.mid(name.lastIndexOf('.') + 1); |
254 |
} |
255 |
|
256 |
} |
257 |
|
258 |
int QuaZipDirComparator::compareStrings(const QString &string1, |
259 |
const QString &string2) |
260 |
{ |
261 |
if (sort & QDir::LocaleAware) { |
262 |
if (sort & QDir::IgnoreCase) { |
263 |
return string1.toLower().localeAwareCompare(string2.toLower()); |
264 |
} else { |
265 |
return string1.localeAwareCompare(string2); |
266 |
} |
267 |
} else { |
268 |
return string1.compare(string2, (sort & QDir::IgnoreCase) |
269 |
? Qt::CaseInsensitive : Qt::CaseSensitive); |
270 |
} |
271 |
} |
272 |
|
273 |
bool QuaZipDirComparator::operator()(const QuaZipFileInfo64 &info1, |
274 |
const QuaZipFileInfo64 &info2) |
275 |
{ |
276 |
QDir::SortFlags order = sort |
277 |
& (QDir::Name | QDir::Time | QDir::Size | QDir::Type); |
278 |
if ((sort & QDir::DirsFirst) == QDir::DirsFirst |
279 |
|| (sort & QDir::DirsLast) == QDir::DirsLast) { |
280 |
if (info1.name.endsWith('/') && !info2.name.endsWith('/')) |
281 |
return (sort & QDir::DirsFirst) == QDir::DirsFirst; |
282 |
else if (!info1.name.endsWith('/') && info2.name.endsWith('/')) |
283 |
return (sort & QDir::DirsLast) == QDir::DirsLast; |
284 |
} |
285 |
bool result; |
286 |
int extDiff; |
287 |
switch (order) { |
288 |
case QDir::Name: |
289 |
result = compareStrings(info1.name, info2.name) < 0; |
290 |
break; |
291 |
case QDir::Type: |
292 |
extDiff = compareStrings(getExtension(info1.name), |
293 |
getExtension(info2.name)); |
294 |
if (extDiff == 0) { |
295 |
result = compareStrings(info1.name, info2.name) < 0; |
296 |
} else { |
297 |
result = extDiff < 0; |
298 |
} |
299 |
break; |
300 |
case QDir::Size: |
301 |
if (info1.uncompressedSize == info2.uncompressedSize) { |
302 |
result = compareStrings(info1.name, info2.name) < 0; |
303 |
} else { |
304 |
result = info1.uncompressedSize < info2.uncompressedSize; |
305 |
} |
306 |
break; |
307 |
case QDir::Time: |
308 |
if (info1.dateTime == info2.dateTime) { |
309 |
result = compareStrings(info1.name, info2.name) < 0; |
310 |
} else { |
311 |
result = info1.dateTime < info2.dateTime; |
312 |
} |
313 |
break; |
314 |
default: |
315 |
qWarning("QuaZipDirComparator(): Invalid sort mode 0x%2X", |
316 |
static_cast<unsigned>(sort)); |
317 |
return false; |
318 |
} |
319 |
return (sort & QDir::Reversed) ? !result : result; |
320 |
} |
321 |
|
322 |
template<typename TFileInfoList> |
323 |
bool QuaZipDirPrivate::entryInfoList(QStringList nameFilters, |
324 |
QDir::Filters filter, QDir::SortFlags sort, TFileInfoList &result) const |
325 |
{ |
326 |
QString basePath = simplePath(); |
327 |
if (!basePath.isEmpty()) |
328 |
basePath += "/"; |
329 |
int baseLength = basePath.length(); |
330 |
result.clear(); |
331 |
QuaZipDirRestoreCurrent saveCurrent(zip); |
332 |
if (!zip->goToFirstFile()) { |
333 |
return zip->getZipError() == UNZ_OK; |
334 |
} |
335 |
QDir::Filters fltr = filter; |
336 |
if (fltr == QDir::NoFilter) |
337 |
fltr = this->filter; |
338 |
if (fltr == QDir::NoFilter) |
339 |
fltr = QDir::AllEntries; |
340 |
QStringList nmfltr = nameFilters; |
341 |
if (nmfltr.isEmpty()) |
342 |
nmfltr = this->nameFilters; |
343 |
QSet<QString> dirsFound; |
344 |
QList<QuaZipFileInfo64> list; |
345 |
do { |
346 |
QString name = zip->getCurrentFileName(); |
347 |
if (!name.startsWith(basePath)) |
348 |
continue; |
349 |
QString relativeName = name.mid(baseLength); |
350 |
if (relativeName.isEmpty()) |
351 |
continue; |
352 |
bool isDir = false; |
353 |
bool isReal = true; |
354 |
if (relativeName.contains('/')) { |
355 |
int indexOfSlash = relativeName.indexOf('/'); |
356 |
// something like "subdir/" |
357 |
isReal = indexOfSlash == relativeName.length() - 1; |
358 |
relativeName = relativeName.left(indexOfSlash + 1); |
359 |
if (dirsFound.contains(relativeName)) |
360 |
continue; |
361 |
isDir = true; |
362 |
} |
363 |
dirsFound.insert(relativeName); |
364 |
if ((fltr & QDir::Dirs) == 0 && isDir) |
365 |
continue; |
366 |
if ((fltr & QDir::Files) == 0 && !isDir) |
367 |
continue; |
368 |
if (!nmfltr.isEmpty() && !QDir::match(nmfltr, relativeName)) |
369 |
continue; |
370 |
bool ok; |
371 |
QuaZipFileInfo64 info = QuaZipDir_getFileInfo(zip, &ok, relativeName, |
372 |
isReal); |
373 |
if (!ok) { |
374 |
return false; |
375 |
} |
376 |
list.append(info); |
377 |
} while (zip->goToNextFile()); |
378 |
QDir::SortFlags srt = sort; |
379 |
if (srt == QDir::NoSort) |
380 |
srt = sorting; |
381 |
#ifdef QUAZIP_QUAZIPDIR_DEBUG |
382 |
qDebug("QuaZipDirPrivate::entryInfoList(): before sort:"); |
383 |
foreach (QuaZipFileInfo64 info, list) { |
384 |
qDebug("%s\t%s", info.name.toUtf8().constData(), |
385 |
info.dateTime.toString(Qt::ISODate).toUtf8().constData()); |
386 |
} |
387 |
#endif |
388 |
if (srt != QDir::NoSort && (srt & QDir::Unsorted) != QDir::Unsorted) { |
389 |
if (QuaZip::convertCaseSensitivity(caseSensitivity) |
390 |
== Qt::CaseInsensitive) |
391 |
srt |= QDir::IgnoreCase; |
392 |
QuaZipDirComparator lessThan(srt); |
393 |
qSort(list.begin(), list.end(), lessThan); |
394 |
} |
395 |
QuaZipDir_convertInfoList(list, result); |
396 |
return true; |
397 |
} |
398 |
|
399 |
/// \endcond |
400 |
|
401 |
QList<QuaZipFileInfo> QuaZipDir::entryInfoList(const QStringList &nameFilters, |
402 |
QDir::Filters filters, QDir::SortFlags sort) const |
403 |
{ |
404 |
QList<QuaZipFileInfo> result; |
405 |
if (d->entryInfoList(nameFilters, filters, sort, result)) |
406 |
return result; |
407 |
else |
408 |
return QList<QuaZipFileInfo>(); |
409 |
} |
410 |
|
411 |
QList<QuaZipFileInfo> QuaZipDir::entryInfoList(QDir::Filters filters, |
412 |
QDir::SortFlags sort) const |
413 |
{ |
414 |
return entryInfoList(QStringList(), filters, sort); |
415 |
} |
416 |
|
417 |
QList<QuaZipFileInfo64> QuaZipDir::entryInfoList64(const QStringList &nameFilters, |
418 |
QDir::Filters filters, QDir::SortFlags sort) const |
419 |
{ |
420 |
QList<QuaZipFileInfo64> result; |
421 |
if (d->entryInfoList(nameFilters, filters, sort, result)) |
422 |
return result; |
423 |
else |
424 |
return QList<QuaZipFileInfo64>(); |
425 |
} |
426 |
|
427 |
QList<QuaZipFileInfo64> QuaZipDir::entryInfoList64(QDir::Filters filters, |
428 |
QDir::SortFlags sort) const |
429 |
{ |
430 |
return entryInfoList64(QStringList(), filters, sort); |
431 |
} |
432 |
|
433 |
QStringList QuaZipDir::entryList(const QStringList &nameFilters, |
434 |
QDir::Filters filters, QDir::SortFlags sort) const |
435 |
{ |
436 |
QStringList result; |
437 |
if (d->entryInfoList(nameFilters, filters, sort, result)) |
438 |
return result; |
439 |
else |
440 |
return QStringList(); |
441 |
} |
442 |
|
443 |
QStringList QuaZipDir::entryList(QDir::Filters filters, |
444 |
QDir::SortFlags sort) const |
445 |
{ |
446 |
return entryList(QStringList(), filters, sort); |
447 |
} |
448 |
|
449 |
bool QuaZipDir::exists(const QString &filePath) const |
450 |
{ |
451 |
if (filePath == "/" || filePath.isEmpty()) |
452 |
return true; |
453 |
QString fileName = filePath; |
454 |
if (fileName.endsWith('/')) |
455 |
fileName.chop(1); |
456 |
if (fileName.contains('/')) { |
457 |
QFileInfo fileInfo(fileName); |
458 |
#ifdef QUAZIP_QUAZIPDIR_DEBUG |
459 |
qDebug("QuaZipDir::exists(): fileName=%s, fileInfo.fileName()=%s, " |
460 |
"fileInfo.path()=%s", fileName.toUtf8().constData(), |
461 |
fileInfo.fileName().toUtf8().constData(), |
462 |
fileInfo.path().toUtf8().constData()); |
463 |
#endif |
464 |
QuaZipDir dir(*this); |
465 |
return dir.cd(fileInfo.path()) && dir.exists(fileInfo.fileName()); |
466 |
} else { |
467 |
if (fileName == "..") { |
468 |
return !isRoot(); |
469 |
} else if (fileName == ".") { |
470 |
return true; |
471 |
} else { |
472 |
QStringList entries = entryList(QDir::AllEntries, QDir::NoSort); |
473 |
#ifdef QUAZIP_QUAZIPDIR_DEBUG |
474 |
qDebug("QuaZipDir::exists(): looking for %s", |
475 |
fileName.toUtf8().constData()); |
476 |
for (QStringList::const_iterator i = entries.constBegin(); |
477 |
i != entries.constEnd(); |
478 |
++i) { |
479 |
qDebug("QuaZipDir::exists(): entry: %s", |
480 |
i->toUtf8().constData()); |
481 |
} |
482 |
#endif |
483 |
Qt::CaseSensitivity cs = QuaZip::convertCaseSensitivity( |
484 |
d->caseSensitivity); |
485 |
if (filePath.endsWith('/')) { |
486 |
return entries.contains(filePath, cs); |
487 |
} else { |
488 |
return entries.contains(fileName, cs) |
489 |
|| entries.contains(fileName + "/", cs); |
490 |
} |
491 |
} |
492 |
} |
493 |
} |
494 |
|
495 |
bool QuaZipDir::exists() const |
496 |
{ |
497 |
return QuaZipDir(d->zip).exists(d->dir); |
498 |
} |
499 |
|
500 |
QString QuaZipDir::filePath(const QString &fileName) const |
501 |
{ |
502 |
return QDir(d->dir).filePath(fileName); |
503 |
} |
504 |
|
505 |
QDir::Filters QuaZipDir::filter() |
506 |
{ |
507 |
return d->filter; |
508 |
} |
509 |
|
510 |
bool QuaZipDir::isRoot() const |
511 |
{ |
512 |
return d->simplePath().isEmpty(); |
513 |
} |
514 |
|
515 |
QStringList QuaZipDir::nameFilters() const |
516 |
{ |
517 |
return d->nameFilters; |
518 |
} |
519 |
|
520 |
QString QuaZipDir::path() const |
521 |
{ |
522 |
return d->dir; |
523 |
} |
524 |
|
525 |
QString QuaZipDir::relativeFilePath(const QString &fileName) const |
526 |
{ |
527 |
return QDir("/" + d->dir).relativeFilePath(fileName); |
528 |
} |
529 |
|
530 |
void QuaZipDir::setCaseSensitivity(QuaZip::CaseSensitivity caseSensitivity) |
531 |
{ |
532 |
d->caseSensitivity = caseSensitivity; |
533 |
} |
534 |
|
535 |
void QuaZipDir::setFilter(QDir::Filters filters) |
536 |
{ |
537 |
d->filter = filters; |
538 |
} |
539 |
|
540 |
void QuaZipDir::setNameFilters(const QStringList &nameFilters) |
541 |
{ |
542 |
d->nameFilters = nameFilters; |
543 |
} |
544 |
|
545 |
void QuaZipDir::setPath(const QString &path) |
546 |
{ |
547 |
QString newDir = path; |
548 |
if (newDir == "/") { |
549 |
d->dir = ""; |
550 |
} else { |
551 |
if (newDir.endsWith('/')) |
552 |
newDir.chop(1); |
553 |
if (newDir.startsWith('/')) |
554 |
newDir = newDir.mid(1); |
555 |
d->dir = newDir; |
556 |
} |
557 |
} |
558 |
|
559 |
void QuaZipDir::setSorting(QDir::SortFlags sort) |
560 |
{ |
561 |
d->sorting = sort; |
562 |
} |
563 |
|
564 |
QDir::SortFlags QuaZipDir::sorting() const |
565 |
{ |
566 |
return d->sorting; |
567 |
} |