diff --git a/arguments.cpp b/arguments.cpp index db5ea5c..3f8656e 100644 --- a/arguments.cpp +++ b/arguments.cpp @@ -20,13 +20,7 @@ #include Arguments::Arguments() : - _extract( -#ifndef UNZZZ - false -#else - true -#endif - ), _help(false), _quiet(false) + _extract(false), _help(false), _quiet(false) { parse(); } @@ -36,7 +30,7 @@ const QStringList &Arguments::paths() const return _paths; } -const QDir &Arguments::destination() const +const QDir &Arguments::directory() const { return _directory; } @@ -61,30 +55,47 @@ void Arguments::parse() QStringList args = qApp->arguments(); args.removeFirst(); // Application path - while (!args.isEmpty()) { + if (!args.isEmpty()) { const QString &arg = args.takeFirst(); - if (arg == "-e" || arg == "--extract") { + if (arg == "extract") { _extract = true; - } else if (arg == "-h" || arg == "--help") { - _help = true; - } else if (arg == "-q" || arg == "--quiet") { - _quiet = true; + } else if (arg == "pack") { + _extract = false; } else { - _paths << arg; + _help = true; + } + + bool stopOptions = false; + + while (!args.isEmpty()) { + const QString &arg = args.takeFirst(); + + if (!stopOptions) { + if (arg == "-h" || arg == "--help") { + _help = true; + } else if (arg == "-q" || arg == "--quiet") { + _quiet = true; + } else if (arg == "--") { + stopOptions = true; + } else { + _paths << arg; + } + } else { + _paths << arg; + } } - } - wilcardParse(); + wilcardParse(); + } else { + _help = true; + } } QMap Arguments::commands() const { QMap options; -#ifndef UNZZZ - options["-e --extract"] = "Extract."; -#endif options["-h --help"] = "Show this help and quit."; options["-q --quiet"] = "Suppress all outputs"; @@ -94,11 +105,9 @@ QMap Arguments::commands() const void Arguments::showHelp(int exitCode) { QTextStream out(stdout, QIODevice::WriteOnly); -#ifdef UNZZZ - out << "unzzz [files...] [output directory]\n"; -#else - out << "zzz [-d] [files...] [output directory]\n"; -#endif + out << "zzz extract ZZZ_FILE [ZZZ_FILE2...] OUTPUT_DIR\n"; + out << "zzz pack ZZZ_FILE SOURCE_DIR\n"; + out << "Options\n"; QMapIterator it(commands()); diff --git a/arguments.h b/arguments.h index 891cd24..a93dd9d 100644 --- a/arguments.h +++ b/arguments.h @@ -27,7 +27,7 @@ class Arguments Arguments(); [[ noreturn ]] void showHelp(int exitCode = 0); const QStringList &paths() const; - const QDir &destination() const; + const QDir &directory() const; bool extract() const; bool help() const; bool quiet() const; diff --git a/main.cpp b/main.cpp index 88e90e1..04fe9c2 100644 --- a/main.cpp +++ b/main.cpp @@ -54,11 +54,13 @@ int main(int argc, char *argv[]) Zzz zzz(path); if (args.extract()) { - if (!zzz.extractTo(args.destination(), observer)) { + if (!zzz.extractTo(args.directory(), observer)) { std::cout << "Error " << qPrintable(zzz.lastInErrorString()) << qPrintable(zzz.lastOutErrorString()); } } else { - std::cout << "Not implemented"; + if (!zzz.packFrom(args.directory(), observer)) { + std::cout << "Error " << qPrintable(zzz.lastInErrorString()) << qPrintable(zzz.lastOutErrorString()); + } } } } diff --git a/observer.cpp b/observer.cpp index 48157c8..ecf00d1 100644 --- a/observer.cpp +++ b/observer.cpp @@ -16,12 +16,14 @@ ****************************************************************************/ #include "observer.h" #include +#include Observer::Observer() { } -ObserverPercent::ObserverPercent() +ObserverPercent::ObserverPercent() : + _lastPercent(255) { } @@ -42,4 +44,5 @@ ObserverStdOut::ObserverStdOut() void ObserverStdOut::setPercent(quint8 percent) { printf("[%d%%] %s\r", percent, qPrintable(_filename)); + fflush(stdout); } diff --git a/zzz.cpp b/zzz.cpp index 96fa5e9..a57626f 100644 --- a/zzz.cpp +++ b/zzz.cpp @@ -1,5 +1,8 @@ #include "zzz.h" #include "zzzfile.h" +#include +#include +#include Zzz::Zzz(const QString &fileName) : f(fileName) { @@ -23,17 +26,17 @@ bool Zzz::extractTo(const QDir &dir, Observer *observer) } for (quint32 i = 0; i < fileCount; ++i) { - ZzzToc toc; + ZzzTocEntry tocEntry; if (observer) { observer->setValue(i); } - if (!zzzFile.readTocEntry(toc)) { + if (!zzzFile.readTocEntry(tocEntry)) { return false; } - QFileInfo fileInfo = toQtSeparators(toc.name()); + QFileInfo fileInfo = toQtSeparators(tocEntry.fileName()); if (!dir.mkpath(fileInfo.dir().path())) { return false; @@ -48,7 +51,7 @@ bool Zzz::extractTo(const QDir &dir, Observer *observer) return false; } - if (!zzzFile.copyFile(toc, &out)) { + if (!zzzFile.copyFile(tocEntry, &out)) { _lastOutError = out.error(); _lastOutErrorString = out.errorString(); @@ -59,6 +62,119 @@ bool Zzz::extractTo(const QDir &dir, Observer *observer) return true; } +bool Zzz::packFrom(const QDir &dir, Observer *observer) +{ + QString oldFilename = f.fileName(); + f.setFileName(f.fileName().append(".tmp")); + + if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + return false; + } + + bool ok = _packFrom(&f, dir, observer); + + if (!ok) { + f.remove(); + } else { + if (QFile::exists(oldFilename)) { + QFile::remove(oldFilename); + } + + if (!f.rename(oldFilename)) { + return false; + } + } + + return ok; +} + +bool Zzz::_packFrom(QFile *temp, const QDir &dir, Observer *observer) +{ + ZzzFile zzzFile(temp); + QStringList files; + QMap entries; + quint64 dataPos = 4; + QDirIterator it(dir.path(), QDir::Files, QDirIterator::Subdirectories); + + if (observer) { + observer->setValue(0); + } + + while (it.hasNext()) { + const QString &path = it.next(); + QString fileName = path.mid(dir.path().size() + 1); + QFileInfo fileInfo(path); + + if (fileInfo.size() > qint64(quint32(-1))) { + // TODO: error + return false; + } + + ZzzTocEntry entry; + entry.setFilePos(0); + entry.setFileSize(quint32(fileInfo.size())); + entry.setFileName(toFF8Separators(fileName).toLatin1()); + + entries.insert(path, entry); + dataPos += quint64(entry.fileName().size() + 16); + } + + int fileCount = entries.size(); + + if (observer) { + observer->setMaximum(fileCount - 1); + } + + if (!zzzFile.writeHeader(quint32(fileCount))) { + return false; + } + + quint32 i = 0; + QMutableMapIterator it2(entries); + + while (it2.hasNext()) { + it2.next(); + ZzzTocEntry &entry = it2.value(); + + entry.setFilePos(dataPos); + + if (!zzzFile.writeTocEntry(entry)) { + return false; + } + + dataPos += entry.fileSize(); + } + + QMapIterator it3(entries); + + while (it3.hasNext()) { + it3.next(); + const QString &path = it3.key(); + + if (observer) { + observer->setValue(i++); + } + + QFile in(path); + + if (!in.open(QIODevice::ReadOnly)) { + _lastOutError = in.error(); + _lastOutErrorString = in.errorString(); + + return false; + } + + if (!zzzFile.writeFile(&in)) { + _lastOutError = in.error(); + _lastOutErrorString = in.errorString(); + + return false; + } + } + + return true; +} + QString Zzz::toQtSeparators(const QString &path) { QString n(path); @@ -72,3 +188,17 @@ QString Zzz::toQtSeparators(const QString &path) return n; } + +QString Zzz::toFF8Separators(const QString &path) +{ + QString n(path); + QChar * const data = n.data(); + + for (int i = 0; i < n.length(); ++i) { + if (data[i] == QLatin1Char('/')) { + data[i] = QLatin1Char('\\'); + } + } + + return n; +} diff --git a/zzz.h b/zzz.h index a07302e..853fa1f 100644 --- a/zzz.h +++ b/zzz.h @@ -11,6 +11,7 @@ class Zzz public: explicit Zzz(const QString &fileName); bool extractTo(const QDir &dir, Observer *observer); + bool packFrom(const QDir &dir, Observer *observer); inline const QString &lastOutErrorString() const { return _lastOutErrorString; @@ -20,7 +21,9 @@ class Zzz return f.errorString(); } private: + bool _packFrom(QFile *temp, const QDir &dir, Observer *observer); static QString toQtSeparators(const QString &path); + static QString toFF8Separators(const QString &path); QFile f; QFile::FileError _lastOutError; QString _lastOutErrorString; diff --git a/zzz.pro b/zzz.pro index 047c7c2..079ff9f 100644 --- a/zzz.pro +++ b/zzz.pro @@ -3,11 +3,6 @@ QT -= gui TARGET = zzz -configUNZZZ { - TARGET = unzzz - DEFINES += UNZZZ -} - CONFIG += console CONFIG -= app_bundle @@ -18,14 +13,14 @@ SOURCES += main.cpp \ observer.cpp \ zzz.cpp \ zzzfile.cpp \ - zzztoc.cpp + zzztocentry.cpp HEADERS += \ arguments.h \ observer.h \ zzz.h \ zzzfile.h \ - zzztoc.h + zzztocentry.h OTHER_FILES += README.md \ deploy.bat diff --git a/zzzfile.cpp b/zzzfile.cpp index 268fc8d..ff4781b 100644 --- a/zzzfile.cpp +++ b/zzzfile.cpp @@ -9,7 +9,12 @@ bool ZzzFile::readHeader(quint32 &fileCount) return readUInt(fileCount); } -bool ZzzFile::readTocEntry(ZzzToc &toc) +bool ZzzFile::writeHeader(const quint32 &fileCount) +{ + return writeUInt(fileCount); +} + +bool ZzzFile::readTocEntry(ZzzTocEntry &tocEntry) { quint32 size; @@ -27,7 +32,7 @@ bool ZzzFile::readTocEntry(ZzzToc &toc) return false; } - toc.setName(name); + tocEntry.setFileName(name); quint64 filePos; quint32 fileSize; @@ -36,26 +41,43 @@ bool ZzzFile::readTocEntry(ZzzToc &toc) return false; } - toc.setPos(filePos); + tocEntry.setFilePos(filePos); if (!readUInt(fileSize)) { return false; } - toc.setSize(fileSize); + tocEntry.setFileSize(fileSize); return true; } -bool ZzzFile::copyFile(const ZzzToc &toc, QIODevice *out) +bool ZzzFile::writeTocEntry(const ZzzTocEntry &tocEntry) +{ + quint32 size = quint32(tocEntry.fileName().size()); + + if (!writeUInt(size)) { + return false; + } + + _io->write(tocEntry.fileName()); + + if (!writeULong(tocEntry.filePos())) { + return false; + } + + return writeUInt(tocEntry.fileSize()); +} + +bool ZzzFile::copyFile(const ZzzTocEntry &tocEntry, QIODevice *out) { qint64 curPos = _io->pos(); - if (!_io->seek(qint64(toc.pos()))) { + if (!_io->seek(qint64(tocEntry.filePos()))) { return false; } - bool ret = _copyFile(toc, out); + bool ret = _copyFile(tocEntry, out); if (!_io->seek(curPos)) { return false; @@ -64,10 +86,10 @@ bool ZzzFile::copyFile(const ZzzToc &toc, QIODevice *out) return ret; } -bool ZzzFile::_copyFile(const ZzzToc &toc, QIODevice *out) +bool ZzzFile::_copyFile(const ZzzTocEntry &tocEntry, QIODevice *out) { - quint32 chunkCount = toc.size() / CHUNK_SIZE, - remaining = toc.size() % CHUNK_SIZE; + quint32 chunkCount = tocEntry.fileSize() / CHUNK_SIZE, + remaining = tocEntry.fileSize() % CHUNK_SIZE; QByteArray data; for (quint32 i = 0; i < chunkCount; ++i) { @@ -97,12 +119,55 @@ bool ZzzFile::_copyFile(const ZzzToc &toc, QIODevice *out) return true; } +bool ZzzFile::writeFile(QIODevice *in) +{ + qint64 chunkCount = in->size() / CHUNK_SIZE, + remaining = in->size() % CHUNK_SIZE; + QByteArray data; + + for (quint32 i = 0; i < chunkCount; ++i) { + data = in->read(CHUNK_SIZE); + + if (data.size() != CHUNK_SIZE) { + return false; + } + + if (_io->write(data) != data.size()) { + return false; + } + } + + if (remaining > 0) { + data = in->read(remaining); + + if (data.size() != int(remaining)) { + return false; + } + + if (_io->write(data) != data.size()) { + return false; + } + } + + return true; +} + bool ZzzFile::readUInt(quint32 &val) { return _io->read((char *)&val, 4) == 4; } +bool ZzzFile::writeUInt(const quint32 &val) +{ + return _io->write((const char *)&val, 4) == 4; +} + bool ZzzFile::readULong(quint64 &val) { return _io->read((char *)&val, 8) == 8; } + +bool ZzzFile::writeULong(const quint64 &val) +{ + return _io->write((const char *)&val, 8) == 8; +} diff --git a/zzzfile.h b/zzzfile.h index f29b57e..55397f9 100644 --- a/zzzfile.h +++ b/zzzfile.h @@ -2,7 +2,7 @@ #define ZZZFILE_H #include -#include "zzztoc.h" +#include "zzztocentry.h" #define CHUNK_SIZE 10485760 // 10 Mio @@ -11,12 +11,17 @@ class ZzzFile public: explicit ZzzFile(QIODevice *io); bool readHeader(quint32 &fileCount); - bool readTocEntry(ZzzToc &toc); - bool copyFile(const ZzzToc &toc, QIODevice *out); + bool writeHeader(const quint32 &fileCount); + bool readTocEntry(ZzzTocEntry &tocEntry); + bool writeTocEntry(const ZzzTocEntry &tocEntry); + bool copyFile(const ZzzTocEntry &tocEntry, QIODevice *out); + bool writeFile(QIODevice *in); private: - bool _copyFile(const ZzzToc &toc, QIODevice *out); + bool _copyFile(const ZzzTocEntry &tocEntry, QIODevice *out); bool readUInt(quint32 &val); + bool writeUInt(const quint32 &val); bool readULong(quint64 &val); + bool writeULong(const quint64 &val); QIODevice *_io; }; diff --git a/zzztoc.cpp b/zzztoc.cpp deleted file mode 100644 index 7edb91c..0000000 --- a/zzztoc.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "zzztoc.h" - -ZzzToc::ZzzToc() : - _name(), _pos(0), _size(0) -{ -} diff --git a/zzztoc.h b/zzztoc.h deleted file mode 100644 index 052a9fb..0000000 --- a/zzztoc.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef ZZZTOC_H -#define ZZZTOC_H - -#include -#include - -class ZzzToc -{ -public: - ZzzToc(); - - inline void setName(const QByteArray &name) { - _name = name; - } - - inline const QByteArray &name() const { - return _name; - } - - inline quint64 pos() const { - return _pos; - } - - inline void setPos(quint64 pos) { - _pos = pos; - } - - inline quint32 size() const { - return _size; - } - - inline void setSize(quint32 size) { - _size = size; - } -private: - QByteArray _name; - quint64 _pos; - quint32 _size; -}; - -#endif // ZZZTOC_H diff --git a/zzztocentry.cpp b/zzztocentry.cpp new file mode 100644 index 0000000..07a3e28 --- /dev/null +++ b/zzztocentry.cpp @@ -0,0 +1,6 @@ +#include "zzztocentry.h" + +ZzzTocEntry::ZzzTocEntry() : + _name(), _pos(0), _size(0) +{ +} diff --git a/zzztocentry.h b/zzztocentry.h new file mode 100644 index 0000000..07af366 --- /dev/null +++ b/zzztocentry.h @@ -0,0 +1,41 @@ +#ifndef ZZZTOCENTRY_H +#define ZZZTOCENTRY_H + +#include +#include + +class ZzzTocEntry +{ +public: + ZzzTocEntry(); + + inline void setFileName(const QByteArray &name) { + _name = name; + } + + inline const QByteArray &fileName() const { + return _name; + } + + inline quint64 filePos() const { + return _pos; + } + + inline void setFilePos(quint64 pos) { + _pos = pos; + } + + inline quint32 fileSize() const { + return _size; + } + + inline void setFileSize(quint32 size) { + _size = size; + } +private: + QByteArray _name; + quint64 _pos; + quint32 _size; +}; + +#endif // ZZZTOCENTRY_H