diff --git a/autotests/kerfuffle/archivetest.cpp b/autotests/kerfuffle/archivetest.cpp
--- a/autotests/kerfuffle/archivetest.cpp
+++ b/autotests/kerfuffle/archivetest.cpp
@@ -149,6 +149,17 @@
qDebug() << "lrzip executable not found in path. Skipping lrzip test.";
}
+ // Only run test for lz4-compressed tar if lz4 executable is found in path.
+ if (!QStandardPaths::findExecutable(QStringLiteral("lz4")).isEmpty()) {
+ QTest::newRow("lz4-compressed tarball")
+ << QFINDTESTDATA("data/simplearchive.tar.lz4")
+ << QStringLiteral("simplearchive")
+ << false << false << false << Archive::Unencrypted
+ << QStringLiteral("simplearchive");
+ } else {
+ qDebug() << "lz4 executable not found in path. Skipping lz4 test.";
+ }
+
QTest::newRow("xar archive")
<< QFINDTESTDATA("data/simplearchive.xar")
<< QStringLiteral("simplearchive")
@@ -461,6 +472,28 @@
qDebug() << "lrzip executable not found in path. Skipping lrzip test.";
}
+ // Only run test for lz4-compressed tar if lz4 executable is found in path.
+ if (!QStandardPaths::findExecutable(QStringLiteral("lz4")).isEmpty()) {
+ archivePath = QFINDTESTDATA("data/simplearchive.tar.lz4");
+ QTest::newRow("extract selected entries from a lz4-compressed tarball without path")
+ << archivePath
+ << QVariantList {
+ QVariant::fromValue(fileRootNodePair(QStringLiteral("file3.txt"), QString())),
+ QVariant::fromValue(fileRootNodePair(QStringLiteral("dir2/file22.txt"), QString()))
+ }
+ << ExtractionOptions()
+ << 2;
+
+ archivePath = QFINDTESTDATA("data/simplearchive.tar.lz4");
+ QTest::newRow("extract all entries from a lz4-compressed tarball with path")
+ << archivePath
+ << QVariantList()
+ << optionsPreservePaths
+ << 7;
+ } else {
+ qDebug() << "lz4 executable not found in path. Skipping lz4 test.";
+ }
+
archivePath = QFINDTESTDATA("data/simplearchive.xar");
QTest::newRow("extract selected entries from a xar archive without path")
<< archivePath
diff --git a/autotests/kerfuffle/mimetypetest.cpp b/autotests/kerfuffle/mimetypetest.cpp
--- a/autotests/kerfuffle/mimetypetest.cpp
+++ b/autotests/kerfuffle/mimetypetest.cpp
@@ -56,6 +56,7 @@
const QString compressedLzipTarMime = QStringLiteral("application/x-lzip-compressed-tar");
const QString compressedLzopTarMime = QStringLiteral("application/x-tzo");
const QString compressedLrzipTarMime = QStringLiteral("application/x-lrzip-compressed-tar");
+ const QString compressedLz4TarMime = QStringLiteral("application/x-lz4-compressed-tar");
const QString isoMimeType = QStringLiteral("application/x-cd-image");
const QString debMimeType = QMimeDatabase().mimeTypeForFile(QStringLiteral("dummy.deb"), QMimeDatabase::MatchExtension).name();
const QString xarMimeType = QStringLiteral("application/x-xar");
@@ -69,6 +70,7 @@
QTest::newRow("tar.lz") << QFINDTESTDATA("data/simplearchive.tar.lz") << compressedLzipTarMime;
QTest::newRow("tar.lzo") << QFINDTESTDATA("data/simplearchive.tar.lzo") << compressedLzopTarMime;
QTest::newRow("tar.lrz") << QFINDTESTDATA("data/simplearchive.tar.lrz") << compressedLrzipTarMime;
+ QTest::newRow("tar.lz4") << QFINDTESTDATA("data/simplearchive.tar.lz4") << compressedLz4TarMime;
QTest::newRow("deb") << QFINDTESTDATA("data/smallarchive.deb") << debMimeType;
QTest::newRow("xar") << QFINDTESTDATA("data/simplearchive.xar") << xarMimeType;
diff --git a/kerfuffle/mime/kerfuffle.xml b/kerfuffle/mime/kerfuffle.xml
--- a/kerfuffle/mime/kerfuffle.xml
+++ b/kerfuffle/mime/kerfuffle.xml
@@ -13,4 +13,9 @@
+
+ Tar archive (LZ4-compressed)
+ Tar archive (LZ4-compressed)
+
+
diff --git a/kerfuffle/mimetypes.cpp b/kerfuffle/mimetypes.cpp
--- a/kerfuffle/mimetypes.cpp
+++ b/kerfuffle/mimetypes.cpp
@@ -47,13 +47,36 @@
// we cannot rely on it when the archive extension is wrong; we need to validate by hand.
if (fileinfo.completeSuffix().toLower().remove(QRegularExpression(QStringLiteral("[^a-z\\.]"))).contains(QStringLiteral("tar."))) {
inputFile.chop(fileinfo.completeSuffix().length());
+ QString cleanExtension(fileinfo.completeSuffix().toLower());
+
+ // tar.bz2 and tar.lz4 need special treatment since they contain numbers.
+ bool isBZ2 = false;
+ bool isLZ4 = false;
+ if (fileinfo.completeSuffix().toLower().contains(QStringLiteral("bz2"))) {
+ cleanExtension.remove(QStringLiteral("bz2"));
+ isBZ2 = true;
+ }
+ if (fileinfo.completeSuffix().toLower().contains(QStringLiteral("lz4"))) {
+ cleanExtension.remove(QStringLiteral("lz4"));
+ isLZ4 = true;
+ }
+
// We remove non-alpha chars from the filename extension, but not periods.
// If the filename is e.g. "foo.tar.gz.1", we get the "foo.tar.gz." string,
// so we need to manually drop the last period character from it.
- QString cleanExtension = fileinfo.completeSuffix().toLower().remove(QRegularExpression(QStringLiteral("[^a-z\\.]")));
+ cleanExtension.remove(QRegularExpression(QStringLiteral("[^a-z\\.]")));
if (cleanExtension.endsWith(QLatin1Char('.'))) {
cleanExtension.chop(1);
}
+
+ // Re-add extension for tar.bz2 and tar.lz4.
+ if (isBZ2) {
+ cleanExtension.append(QStringLiteral(".bz2"));
+ }
+ if (isLZ4) {
+ cleanExtension.append(QStringLiteral(".lz4"));
+ }
+
inputFile += cleanExtension;
qCDebug(ARK) << "Validated filename of compressed tar" << filename << "into filename" << inputFile;
}
@@ -83,7 +106,9 @@
(mimeFromExtension == db.mimeTypeForName(QStringLiteral("application/x-lzip-compressed-tar")) &&
mimeFromContent == db.mimeTypeForName(QStringLiteral("application/x-lzip"))) ||
(mimeFromExtension == db.mimeTypeForName(QStringLiteral("application/x-lrzip-compressed-tar")) &&
- mimeFromContent == db.mimeTypeForName(QStringLiteral("application/x-lrzip")))) {
+ mimeFromContent == db.mimeTypeForName(QStringLiteral("application/x-lrzip"))) ||
+ (mimeFromExtension == db.mimeTypeForName(QStringLiteral("application/x-lz4-compressed-tar")) &&
+ mimeFromContent == db.mimeTypeForName(QStringLiteral("application/x-lz4")))) {
return mimeFromExtension;
}
diff --git a/kerfuffle/pluginmanager.cpp b/kerfuffle/pluginmanager.cpp
--- a/kerfuffle/pluginmanager.cpp
+++ b/kerfuffle/pluginmanager.cpp
@@ -119,6 +119,11 @@
supported.remove(QStringLiteral("application/x-lrzip-compressed-tar"));
}
+ // Remove entry for lz4-compressed tar if lz4 executable not found in path.
+ if (QStandardPaths::findExecutable(QStringLiteral("lz4")).isEmpty()) {
+ supported.remove(QStringLiteral("application/x-lz4-compressed-tar"));
+ }
+
return sortByComment(supported);
}
@@ -134,6 +139,11 @@
supported.remove(QStringLiteral("application/x-lrzip-compressed-tar"));
}
+ // Remove entry for lz4-compressed tar if lz4 executable not found in path.
+ if (QStandardPaths::findExecutable(QStringLiteral("lz4")).isEmpty()) {
+ supported.remove(QStringLiteral("application/x-lz4-compressed-tar"));
+ }
+
return sortByComment(supported);
}
diff --git a/plugins/libarchive/CMakeLists.txt b/plugins/libarchive/CMakeLists.txt
--- a/plugins/libarchive/CMakeLists.txt
+++ b/plugins/libarchive/CMakeLists.txt
@@ -6,6 +6,12 @@
set(SUPPORTED_LIBARCHIVE_READONLY_MIMETYPES "application/vnd.debian.binary-package;application/x-deb;application/x-cd-image;application/x-bcpio;application/x-cpio;application/x-cpio-compressed;application/x-sv4cpio;application/x-sv4crc;")
set(SUPPORTED_LIBARCHIVE_READONLY_MIMETYPES "${SUPPORTED_LIBARCHIVE_READONLY_MIMETYPES}application/x-rpm;application/x-source-rpm;application/vnd.ms-cab-compressed;application/x-xar;")
+if(LibArchive_FOUND)
+ if(LibArchive_VERSION VERSION_GREATER "3.1.2")
+ set(SUPPORTED_LIBARCHIVE_READWRITE_MIMETYPES "${SUPPORTED_LIBARCHIVE_READWRITE_MIMETYPES}application/x-lz4-compressed-tar;")
+ endif()
+endif()
+
set(INSTALLED_LIBARCHIVE_PLUGINS "")
set(kerfuffle_libarchive_readonly_SRCS libarchiveplugin.cpp readonlylibarchiveplugin.cpp ark_debug.cpp)
@@ -46,6 +52,14 @@
\"application/x-tzo\",
\"application/x-lrzip-compressed-tar")
+if(LibArchive_FOUND)
+ if(LibArchive_VERSION VERSION_GREATER "3.1.2")
+ set(SUPPORTED_READWRITE_MIMETYPES
+ "${SUPPORTED_READWRITE_MIMETYPES}\",
+ \"application/x-lz4-compressed-tar")
+ endif()
+endif()
+
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/kerfuffle_libarchive_readonly.json.cmake
${CMAKE_CURRENT_BINARY_DIR}/kerfuffle_libarchive_readonly.json)
diff --git a/plugins/libarchive/kerfuffle_libarchive.json b/plugins/libarchive/kerfuffle_libarchive.json
--- a/plugins/libarchive/kerfuffle_libarchive.json
+++ b/plugins/libarchive/kerfuffle_libarchive.json
@@ -44,7 +44,8 @@
"application/x-lzma-compressed-tar",
"application/x-lzip-compressed-tar",
"application/x-tzo",
- "application/x-lrzip-compressed-tar"
+ "application/x-lrzip-compressed-tar",
+ "application/x-lz4-compressed-tar"
],
"Name": "kerfuffle_libarchive",
"Name[sv]": "Kerfuffle LibArchive",
@@ -92,5 +93,10 @@
"CompressionLevelDefault": 6,
"CompressionLevelMax": 9,
"CompressionLevelMin": 0
+ },
+ "application/x-lz4-compressed-tar": {
+ "CompressionLevelDefault": 1,
+ "CompressionLevelMax": 9,
+ "CompressionLevelMin": 1
}
-}
\ No newline at end of file
+}
diff --git a/plugins/libarchive/readwritelibarchiveplugin.cpp b/plugins/libarchive/readwritelibarchiveplugin.cpp
--- a/plugins/libarchive/readwritelibarchiveplugin.cpp
+++ b/plugins/libarchive/readwritelibarchiveplugin.cpp
@@ -98,7 +98,7 @@
archive_write_set_format_pax_restricted(arch_writer.data());
int ret;
- bool isLrzip = false;
+ bool requiresExecutable = false;
if (creatingNewFile) {
if (filename().right(2).toUpper() == QLatin1String("GZ")) {
qCDebug(ARK) << "Detected gzip compression for new file";
@@ -124,7 +124,11 @@
} else if (filename().right(3).toUpper() == QLatin1String("LRZ")) {
qCDebug(ARK) << "Detected lrzip compression for new file";
ret = archive_write_add_filter_lrzip(arch_writer.data());
- isLrzip = true;
+ requiresExecutable = true;
+ } else if (filename().right(3).toUpper() == QLatin1String("LZ4")) {
+ qCDebug(ARK) << "Detected lz4 compression for new file";
+ ret = archive_write_add_filter_lz4(arch_writer.data());
+ requiresExecutable = true;
} else if (filename().right(3).toUpper() == QLatin1String("TAR")) {
qCDebug(ARK) << "Detected no compression for new file (pure tar)";
ret = archive_write_add_filter_none(arch_writer.data());
@@ -134,8 +138,8 @@
}
// Libarchive emits a warning for lrzip due to using external executable.
- if ((isLrzip && ret != ARCHIVE_WARN) ||
- (!isLrzip && ret != ARCHIVE_OK)) {
+ if ((requiresExecutable && ret != ARCHIVE_WARN) ||
+ (!requiresExecutable && ret != ARCHIVE_OK)) {
emit error(xi18nc("@info", "Setting the compression method failed with the following error:%1",
QLatin1String(archive_error_string(arch_writer.data()))));
return false;
@@ -178,7 +182,11 @@
break;
case ARCHIVE_FILTER_LRZIP:
ret = archive_write_add_filter_lrzip(arch_writer.data());
- isLrzip = true;
+ requiresExecutable = true;
+ break;
+ case ARCHIVE_FILTER_LZ4:
+ ret = archive_write_add_filter_lz4(arch_writer.data());
+ requiresExecutable = true;
break;
case ARCHIVE_FILTER_NONE:
ret = archive_write_add_filter_none(arch_writer.data());
@@ -190,8 +198,8 @@
}
// Libarchive emits a warning for lrzip due to using external executable.
- if ((isLrzip && ret != ARCHIVE_WARN) ||
- (!isLrzip && ret != ARCHIVE_OK)) {
+ if ((requiresExecutable && ret != ARCHIVE_WARN) ||
+ (!requiresExecutable && ret != ARCHIVE_OK)) {
emit error(xi18nc("@info", "Setting the compression method failed with the following error:%1",
QLatin1String(archive_error_string(arch_writer.data()))));
return false;