From aab2f0be9d50167192c482eb022d1c2f0dbf5b95 Mon Sep 17 00:00:00 2001 From: cen1 Date: Sat, 5 Oct 2024 19:17:10 +0200 Subject: [PATCH] Feature/strategy options (#206) * Adds compression strategy (level, method) to JlCompress --------- Co-authored-by: ran <1353599815@qq.com> --- quazip/JlCompress.cpp | 10 +- quazip/JlCompress.h | 74 +++++++-- qztest/qztest.cpp | 20 +++ qztest/testjlcompress.cpp | 307 ++++++++++++++++++++++++++------------ 4 files changed, 296 insertions(+), 115 deletions(-) diff --git a/quazip/JlCompress.cpp b/quazip/JlCompress.cpp index ca48492e..d5ba418b 100644 --- a/quazip/JlCompress.cpp +++ b/quazip/JlCompress.cpp @@ -55,10 +55,10 @@ bool JlCompress::compressFile(QuaZip* zip, QString fileName, QString fileDest, c QuaZipFile outFile(zip); if (options.getDateTime().isNull()) { - if(!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, fileName))) return false; + if(!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, fileName), nullptr, 0, options.getCompressionMethod(), options.getCompressionLevel())) return false; } else { - if(!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, fileName, options.getDateTime()))) return false; + if(!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, fileName, options.getDateTime()), nullptr, 0, options.getCompressionMethod(), options.getCompressionLevel())) return false; } QFileInfo input(fileName); @@ -106,13 +106,13 @@ bool JlCompress::compressSubDir(QuaZip* zip, QString dir, QString origDir, bool QuaZipFile dirZipFile(zip); std::unique_ptr qzni; if (options.getDateTime().isNull()) { - qzni = std::make_unique(origDirectory.relativeFilePath(dir) + QLatin1String("/"), dir); + qzni = std::make_unique(origDirectory.relativeFilePath(dir) + QLatin1String("/"), dir); } else { - qzni = std::make_unique(origDirectory.relativeFilePath(dir) + QLatin1String("/"), dir, options.getDateTime()); + qzni = std::make_unique(origDirectory.relativeFilePath(dir) + QLatin1String("/"), dir, options.getDateTime()); } if (!dirZipFile.open(QIODevice::WriteOnly, *qzni, nullptr, 0, 0)) { - return false; + return false; } dirZipFile.close(); } diff --git a/quazip/JlCompress.h b/quazip/JlCompress.h index 1c1432e3..a852549b 100644 --- a/quazip/JlCompress.h +++ b/quazip/JlCompress.h @@ -44,21 +44,71 @@ class QUAZIP_EXPORT JlCompress { public: class Options { public: - explicit Options(const QDateTime& dateTime = QDateTime()) - : m_dateTime(dateTime) {} + /** + * The enum values refer to the comments in the open function of the quazipfile.h file. + * + * The value is represented by two hexadecimal characters, + * the left character indicating the compression method, + * and the right character indicating the compression level. + * + * method == 0 indicates that the file is not compressed but rather stored as is. + * method == 8(Z_DEFLATED) indicates that zlib compression is used. + * + * A higher value of level indicates a smaller size of the compressed file, + * although it also implies more time consumed during the compression process. + */ + enum CompressionStrategy + { + /// Storage without compression + Storage = 0x00, // Z_NO_COMPRESSION 0 + /// The fastest compression speed + Fastest = 0x81, // Z_BEST_SPEED 1 + /// Relatively fast compression speed + Faster = 0x83, + /// Standard compression speed and ratio + Standard = 0x86, + /// Better compression ratio + Better = 0x87, + /// The best compression ratio + Best = 0x89, // Z_BEST_COMPRESSION 9 + /// The default compression strategy, according to the open function of quazipfile.h, + /// the value of method is Z_DEFLATED, and the value of level is Z_DEFAULT_COMPRESSION -1 (equals lvl 6) + Default = 0xff + }; - QDateTime getDateTime() const { - return m_dateTime; - } + public: + explicit Options(const QDateTime& dateTime = QDateTime(), const CompressionStrategy& strategy = Default) + : m_dateTime(dateTime), m_compressionStrategy(strategy) {} + + QDateTime getDateTime() const { + return m_dateTime; + } + + void setDateTime(const QDateTime &dateTime) { + m_dateTime = dateTime; + } + + CompressionStrategy getCompressionStrategy() const { + return m_compressionStrategy; + } + + int getCompressionMethod() const { + return m_compressionStrategy != Default ? m_compressionStrategy >> 4 : Z_DEFLATED; + } + + int getCompressionLevel() const { + return m_compressionStrategy != Default ? m_compressionStrategy & 0x0f : Z_DEFAULT_COMPRESSION; + } - void setDateTime(const QDateTime &dateTime) { - m_dateTime = dateTime; - } + void setCompressionStrategy(const CompressionStrategy &strategy) { + m_compressionStrategy = strategy; + } private: - // If set, used as last modified on file inside the archive. - // If compressing a directory, used for all files. - QDateTime m_dateTime; + // If set, used as last modified on file inside the archive. + // If compressing a directory, used for all files. + QDateTime m_dateTime; + CompressionStrategy m_compressionStrategy; }; static bool copyData(QIODevice &inFile, QIODevice &outFile); @@ -287,7 +337,7 @@ class QUAZIP_EXPORT JlCompress { list of the entries, including both files and directories if they are present separately. */ - static QStringList getFileList(QIODevice *ioDevice); + static QStringList getFileList(QIODevice *ioDevice); }; #endif /* JLCOMPRESSFOLDER_H_ */ diff --git a/qztest/qztest.cpp b/qztest/qztest.cpp index a3611da0..f3d55f52 100644 --- a/qztest/qztest.cpp +++ b/qztest/qztest.cpp @@ -56,6 +56,15 @@ bool createTestFiles(const QStringList &fileNames, int size, const QString &dir) testDir.path().toUtf8().constData()); return false; } + //qDebug() << "Created path " << testDir.path(); + QFile dirFile(testDir.path()); + if (!dirFile.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner | + QFileDevice::ReadGroup | QFileDevice::ExeGroup | + QFileDevice::ReadOther | QFileDevice::ExeOther)) { + qWarning("Couldn't set permissions for %s", + testDir.path().toUtf8().constData()); + return false; + } } if (fileName.endsWith('/')) { if (!curDir.mkpath(filePath)) { @@ -63,6 +72,15 @@ bool createTestFiles(const QStringList &fileNames, int size, const QString &dir) fileName.toUtf8().constData()); return false; } + //qDebug() << "Created path " << filePath; + QFile dirFile(filePath); + if (!dirFile.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner | + QFileDevice::ReadGroup | QFileDevice::ExeGroup | + QFileDevice::ReadOther | QFileDevice::ExeOther)) { + qWarning("Couldn't set permissions for %s", + filePath.toUtf8().constData()); + return false; + } } else { QFile testFile(filePath); if (!testFile.open(QIODevice::WriteOnly | QIODevice::Text)) { @@ -70,6 +88,8 @@ bool createTestFiles(const QStringList &fileNames, int size, const QString &dir) fileName.toUtf8().constData()); return false; } + testFile.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | + QFileDevice::ReadGroup | QFileDevice::ReadOther); if (size == -1) { QTextStream testStream(&testFile); testStream << "This is a test file named " << fileName << quazip_endl; diff --git a/qztest/testjlcompress.cpp b/qztest/testjlcompress.cpp index c81fface..c768e8d6 100644 --- a/qztest/testjlcompress.cpp +++ b/qztest/testjlcompress.cpp @@ -29,6 +29,7 @@ see quazip/(un)zip.h files for details. Basically it's the zlib license. #include #include #include +#include #include #include @@ -39,6 +40,8 @@ see quazip/(un)zip.h files for details. Basically it's the zlib license. #include #endif +Q_DECLARE_METATYPE(JlCompress::Options::CompressionStrategy) + void TestJlCompress::compressFile_data() { QTest::addColumn("zipName"); @@ -76,57 +79,96 @@ void TestJlCompress::compressFile() void TestJlCompress::compressFileOptions_data() { - QTest::addColumn("zipName"); - QTest::addColumn("fileName"); - QTest::addColumn("dateTime"); - QTest::addColumn("sha256sum_unix"); // Due to extra data archives are not identical - QTest::addColumn("sha256sum_win"); - QTest::newRow("simple") << "jlsimplefile.zip" - << "test0.txt" - << QDateTime(QDate(2024, 9, 19), QTime(21, 0, 0), QTimeZone::utc()) - << "5eedd83aee92cf3381155d167fee54a4ef6e43b8bc7a979c903611d9aa28610a" - << "cb1847dff1a5c33a805efde2558fc74024ad4c64c8607f8b12903e4d92385955"; + QTest::addColumn("zipName"); + QTest::addColumn("fileName"); + QTest::addColumn("dateTime"); + QTest::addColumn("strategy"); + QTest::addColumn("sha256sum_unix"); // Due to extra data archives are not identical + QTest::addColumn("sha256sum_win"); + QTest::newRow("simple") << "jlsimplefile.zip" + << "test0.txt" + << QDateTime(QDate(2024, 9, 19), QTime(21, 0, 0), QTimeZone::utc()) + << JlCompress::Options::Default + << "5eedd83aee92cf3381155d167fee54a4ef6e43b8bc7a979c903611d9aa28610a" + << "cb1847dff1a5c33a805efde2558fc74024ad4c64c8607f8b12903e4d92385955"; + QTest::newRow("simple-storage") << "jlsimplefile-storage.zip" + << "test0.txt" + << QDateTime(QDate(2024, 9, 19), QTime(21, 0, 0), QTimeZone::utc()) + << JlCompress::Options::Storage + << "" + << ""; + QTest::newRow("simple-fastest") << "jlsimplefile-fastest.zip" + << "test0.txt" + << QDateTime(QDate(2024, 9, 19), QTime(21, 0, 0), QTimeZone::utc()) + << JlCompress::Options::Fastest + << "" + << ""; + QTest::newRow("simple-faster") << "jlsimplefile-faster.zip" + << "test0.txt" + << QDateTime(QDate(2024, 9, 19), QTime(21, 0, 0), QTimeZone::utc()) + << JlCompress::Options::Faster + << "" + << ""; + QTest::newRow("simple-standard") << "jlsimplefile-standard.zip" + << "test0.txt" + << QDateTime(QDate(2024, 9, 19), QTime(21, 0, 0), QTimeZone::utc()) + << JlCompress::Options::Standard + << "5eedd83aee92cf3381155d167fee54a4ef6e43b8bc7a979c903611d9aa28610a" + << "cb1847dff1a5c33a805efde2558fc74024ad4c64c8607f8b12903e4d92385955"; + QTest::newRow("simple-better") << "jlsimplefile-better.zip" + << "test0.txt" + << QDateTime(QDate(2024, 9, 19), QTime(21, 0, 0), QTimeZone::utc()) + << JlCompress::Options::Better + << "" + << ""; + QTest::newRow("simple-best") << "jlsimplefile-best.zip" + << "test0.txt" + << QDateTime(QDate(2024, 9, 19), QTime(21, 0, 0), QTimeZone::utc()) + << JlCompress::Options::Best + << "" + << ""; } void TestJlCompress::compressFileOptions() { - QFETCH(QString, zipName); - QFETCH(QString, fileName); - QFETCH(QDateTime, dateTime); - QFETCH(QString, sha256sum_unix); - QFETCH(QString, sha256sum_win); - QDir curDir; - if (curDir.exists(zipName)) { + QFETCH(QString, zipName); + QFETCH(QString, fileName); + QFETCH(QDateTime, dateTime); + QFETCH(JlCompress::Options::CompressionStrategy, strategy); + QFETCH(QString, sha256sum_unix); + QFETCH(QString, sha256sum_win); + QDir curDir; + if (curDir.exists(zipName)) { if (!curDir.remove(zipName)) QFAIL("Can't remove zip file"); - } - if (!createTestFiles(QStringList() << fileName)) { + } + if (!createTestFiles(QStringList() << fileName)) { QFAIL("Can't create test file"); - } - - const JlCompress::Options options(dateTime); - QVERIFY(JlCompress::compressFile(zipName, "tmp/" + fileName, options)); - // get the file list and check it - QStringList fileList = JlCompress::getFileList(zipName); - QCOMPARE(fileList.count(), 1); - QVERIFY(fileList[0] == fileName); - // now test the QIODevice* overload of getFileList() - QFile zipFile(zipName); - QVERIFY(zipFile.open(QIODevice::ReadOnly)); - fileList = JlCompress::getFileList(zipName); - QCOMPARE(fileList.count(), 1); - QVERIFY(fileList[0] == fileName); - // Hash is computed on the resulting file externally, then hardcoded in the test data - // This should help detecting any library breakage since we compare against a well-known stable result - QString hash = QCryptographicHash::hash(zipFile.readAll(), QCryptographicHash::Sha256).toHex(); -#ifdef _WIN32 - QCOMPARE(hash, sha256sum_win); -#else - QCOMPARE(hash, sha256sum_unix); -#endif - zipFile.close(); - removeTestFiles(QStringList() << fileName); - curDir.remove(zipName); + } + + const JlCompress::Options options(dateTime, strategy); + QVERIFY(JlCompress::compressFile(zipName, "tmp/" + fileName, options)); + // get the file list and check it + QStringList fileList = JlCompress::getFileList(zipName); + QCOMPARE(fileList.count(), 1); + QVERIFY(fileList[0] == fileName); + // now test the QIODevice* overload of getFileList() + QFile zipFile(zipName); + QVERIFY(zipFile.open(QIODevice::ReadOnly)); + fileList = JlCompress::getFileList(zipName); + QCOMPARE(fileList.count(), 1); + QVERIFY(fileList[0] == fileName); + // Hash is computed on the resulting file externally, then hardcoded in the test data + // This should help detecting any library breakage since we compare against a well-known stable result + QString hash = QCryptographicHash::hash(zipFile.readAll(), QCryptographicHash::Sha256).toHex(); + #ifdef Q_OS_WIN + if (!sha256sum_win.isEmpty()) QCOMPARE(hash, sha256sum_win); + #else + if (!sha256sum_unix.isEmpty()) QCOMPARE(hash, sha256sum_unix); + #endif + zipFile.close(); + removeTestFiles(QStringList() << fileName); + curDir.remove(zipName); } void TestJlCompress::compressFiles_data() @@ -220,69 +262,138 @@ void TestJlCompress::compressDir() void TestJlCompress::compressDirOptions_data() { - QTest::addColumn("zipName"); - QTest::addColumn("fileNames"); - QTest::addColumn("expected"); - QTest::addColumn("dateTime"); - QTest::addColumn("sha256sum_unix"); - QTest::addColumn("sha256sum_win"); - QTest::newRow("simple") << "jldir.zip" - << (QStringList() << "test0.txt" << "testdir1/test1.txt" - << "testdir2/test2.txt" << "testdir2/subdir/test2sub.txt") - << (QStringList() << "test0.txt" - << "testdir1/" << "testdir1/test1.txt" - << "testdir2/" << "testdir2/test2.txt" - << "testdir2/subdir/" << "testdir2/subdir/test2sub.txt") - << QDateTime(QDate(2024, 9, 19), QTime(21, 0, 0), QTimeZone::utc()) - << "ed0d5921b2fc11b6b4cb214b3e43ea3ea28987d6ff8080faab54c4756de30af6" - << "1eba110a33718c07a4ddf3fa515d1b4c6e3f4fc912b2e29e5e32783e2cddf852"; + QTest::addColumn("zipName"); + QTest::addColumn("fileNames"); + QTest::addColumn("expected"); + QTest::addColumn("dateTime"); + QTest::addColumn("strategy"); + QTest::addColumn("sha256sum_unix"); + QTest::addColumn("sha256sum_win"); + QTest::newRow("simple") << "jldir.zip" + << (QStringList() << "test0.txt" << "testdir1/test1.txt" + << "testdir2/test2.txt" << "testdir2/subdir/test2sub.txt") + << (QStringList() << "test0.txt" + << "testdir1/" << "testdir1/test1.txt" + << "testdir2/" << "testdir2/test2.txt" + << "testdir2/subdir/" << "testdir2/subdir/test2sub.txt") + << QDateTime(QDate(2024, 9, 19), QTime(21, 0, 0), QTimeZone::utc()) + << JlCompress::Options::Default + << "ed0d5921b2fc11b6b4cb214b3e43ea3ea28987d6ff8080faab54c4756de30af6" + << "1eba110a33718c07a4ddf3fa515d1b4c6e3f4fc912b2e29e5e32783e2cddf852"; + QTest::newRow("simple-storage") << "jldir-storage.zip" + << (QStringList() << "test0.txt" << "testdir1/test1.txt" + << "testdir2/test2.txt" << "testdir2/subdir/test2sub.txt") + << (QStringList() << "test0.txt" + << "testdir1/" << "testdir1/test1.txt" + << "testdir2/" << "testdir2/test2.txt" + << "testdir2/subdir/" << "testdir2/subdir/test2sub.txt") + << QDateTime(QDate(2024, 9, 19), QTime(21, 0, 0), QTimeZone::utc()) + << JlCompress::Options::Storage + << "" + << ""; + QTest::newRow("simple-fastest") << "jldir-fastest.zip" + << (QStringList() << "test0.txt" << "testdir1/test1.txt" + << "testdir2/test2.txt" << "testdir2/subdir/test2sub.txt") + << (QStringList() << "test0.txt" + << "testdir1/" << "testdir1/test1.txt" + << "testdir2/" << "testdir2/test2.txt" + << "testdir2/subdir/" << "testdir2/subdir/test2sub.txt") + << QDateTime(QDate(2024, 9, 19), QTime(21, 0, 0), QTimeZone::utc()) + << JlCompress::Options::Fastest + << "" + << ""; + QTest::newRow("simple-faster") << "jldir-faster.zip" + << (QStringList() << "test0.txt" << "testdir1/test1.txt" + << "testdir2/test2.txt" << "testdir2/subdir/test2sub.txt") + << (QStringList() << "test0.txt" + << "testdir1/" << "testdir1/test1.txt" + << "testdir2/" << "testdir2/test2.txt" + << "testdir2/subdir/" << "testdir2/subdir/test2sub.txt") + << QDateTime(QDate(2024, 9, 19), QTime(21, 0, 0), QTimeZone::utc()) + << JlCompress::Options::Faster + << "" + << ""; + QTest::newRow("simple-standard") << "jldir-standard.zip" + << (QStringList() << "test0.txt" << "testdir1/test1.txt" + << "testdir2/test2.txt" << "testdir2/subdir/test2sub.txt") + << (QStringList() << "test0.txt" + << "testdir1/" << "testdir1/test1.txt" + << "testdir2/" << "testdir2/test2.txt" + << "testdir2/subdir/" << "testdir2/subdir/test2sub.txt") + << QDateTime(QDate(2024, 9, 19), QTime(21, 0, 0), QTimeZone::utc()) + << JlCompress::Options::Standard + << "ed0d5921b2fc11b6b4cb214b3e43ea3ea28987d6ff8080faab54c4756de30af6" + << "1eba110a33718c07a4ddf3fa515d1b4c6e3f4fc912b2e29e5e32783e2cddf852"; + QTest::newRow("simple-better") << "jldir-better.zip" + << (QStringList() << "test0.txt" << "testdir1/test1.txt" + << "testdir2/test2.txt" << "testdir2/subdir/test2sub.txt") + << (QStringList() << "test0.txt" + << "testdir1/" << "testdir1/test1.txt" + << "testdir2/" << "testdir2/test2.txt" + << "testdir2/subdir/" << "testdir2/subdir/test2sub.txt") + << QDateTime(QDate(2024, 9, 19), QTime(21, 0, 0), QTimeZone::utc()) + << JlCompress::Options::Better + << "" + << ""; + QTest::newRow("simple-best") << "jldir-best.zip" + << (QStringList() << "test0.txt" << "testdir1/test1.txt" + << "testdir2/test2.txt" << "testdir2/subdir/test2sub.txt") + << (QStringList() << "test0.txt" + << "testdir1/" << "testdir1/test1.txt" + << "testdir2/" << "testdir2/test2.txt" + << "testdir2/subdir/" << "testdir2/subdir/test2sub.txt") + << QDateTime(QDate(2024, 9, 19), QTime(21, 0, 0), QTimeZone::utc()) + << JlCompress::Options::Best + << "" + << ""; } void TestJlCompress::compressDirOptions() { - QFETCH(QString, zipName); - QFETCH(QStringList, fileNames); - QFETCH(QStringList, expected); - QFETCH(QDateTime, dateTime); - QFETCH(QString, sha256sum_unix); - QFETCH(QString, sha256sum_win); - QDir curDir; - if (curDir.exists(zipName)) { - if (!curDir.remove(zipName)) - QFAIL("Can't remove zip file"); - } - if (!createTestFiles(fileNames, -1, "compressDir_tmp")) { - QFAIL("Can't create test files"); - } + QFETCH(QString, zipName); + QFETCH(QStringList, fileNames); + QFETCH(QStringList, expected); + QFETCH(QDateTime, dateTime); + QFETCH(JlCompress::Options::CompressionStrategy, strategy); + QFETCH(QString, sha256sum_unix); + QFETCH(QString, sha256sum_win); + QDir curDir; + if (curDir.exists(zipName)) { + if (!curDir.remove(zipName)) + QFAIL("Can't remove zip file"); + } + if (!createTestFiles(fileNames, -1, "compressDir_tmp")) { + QFAIL("Can't create test files"); + } #ifdef Q_OS_WIN - for (int i = 0; i < fileNames.size(); ++i) { - if (fileNames.at(i).startsWith(".")) { - QString fn = "compressDir_tmp\\" + fileNames.at(i); - SetFileAttributesW(reinterpret_cast(fn.utf16()), - FILE_ATTRIBUTE_HIDDEN); + for (int i = 0; i < fileNames.size(); ++i) { + if (fileNames.at(i).startsWith(".")) { + QString fn = "compressDir_tmp\\" + fileNames.at(i); + SetFileAttributesW(reinterpret_cast(fn.utf16()), + FILE_ATTRIBUTE_HIDDEN); + } } - } #endif - const JlCompress::Options options(dateTime); - QVERIFY(JlCompress::compressDir(zipName, "compressDir_tmp", true, QDir::Hidden, options)); - // get the file list and check it - QStringList fileList = JlCompress::getFileList(zipName); - fileList.sort(); - expected.sort(); - QCOMPARE(fileList, expected); - QFile zipFile(curDir.absoluteFilePath(zipName)); - if (!zipFile.open(QIODevice::ReadOnly)) { - QFAIL("Can't read output zip file"); - } - QString hash = QCryptographicHash::hash(zipFile.readAll(), QCryptographicHash::Sha256).toHex(); -#ifdef _WIN32 - QCOMPARE(hash, sha256sum_win); + const JlCompress::Options options(dateTime, strategy); + QVERIFY(JlCompress::compressDir(zipName, "compressDir_tmp", true, QDir::Hidden, options)); + // get the file list and check it + QStringList fileList = JlCompress::getFileList(zipName); + fileList.sort(); + expected.sort(); + QCOMPARE(fileList, expected); + QFile zipFile(curDir.absoluteFilePath(zipName)); + if (!zipFile.open(QIODevice::ReadOnly)) { + QFAIL("Can't read output zip file"); + } + QString hash = QCryptographicHash::hash(zipFile.readAll(), QCryptographicHash::Sha256).toHex(); +#ifdef Q_OS_WIN + if (!sha256sum_win.isEmpty()) QCOMPARE(hash, sha256sum_win); #else - QCOMPARE(hash, sha256sum_unix); + if (!sha256sum_unix.isEmpty()) QCOMPARE(hash, sha256sum_unix); #endif - zipFile.close(); - removeTestFiles(fileNames, "compressDir_tmp"); - curDir.remove(zipName); + zipFile.close(); + removeTestFiles(fileNames, "compressDir_tmp"); + curDir.remove(zipName); } void TestJlCompress::extractFile_data()