From 9f62e18e05a30c2f04b46462afc71528dac657fd Mon Sep 17 00:00:00 2001 From: Holden Ramsey Date: Wed, 9 Jul 2025 21:06:06 -0400 Subject: [PATCH] Utilities: Add karchive library --- src/Utilities/Compression/CMakeLists.txt | 33 +++++ src/Utilities/Compression/karchive.cc | 164 +++++++++++++++++++++++ src/Utilities/Compression/karchive.h | 14 ++ 3 files changed, 211 insertions(+) create mode 100644 src/Utilities/Compression/karchive.cc create mode 100644 src/Utilities/Compression/karchive.h diff --git a/src/Utilities/Compression/CMakeLists.txt b/src/Utilities/Compression/CMakeLists.txt index 900010494534..dd4f83dcaaaa 100644 --- a/src/Utilities/Compression/CMakeLists.txt +++ b/src/Utilities/Compression/CMakeLists.txt @@ -1,5 +1,7 @@ target_sources(${CMAKE_PROJECT_NAME} PRIVATE + karchive.cc + karchive.h QGCLZMA.cc QGCLZMA.h QGCZip.cc @@ -75,3 +77,34 @@ target_compile_definitions(xz ) target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE xz) + +#===========================================================================# + +CPMAddPackage( + NAME ECM + GITHUB_REPOSITORY KDE/extra-cmake-modules + VERSION 6.18.0 + DOWNLOAD_ONLY + OPTIONS + "BUILD_DOC OFF" + "BUILD_TESTING OFF" +) + +list(APPEND CMAKE_MODULE_PATH + "${ECM_SOURCE_DIR}/modules" +) + +#===========================================================================# + +CPMAddPackage( + NAME karchive + GITHUB_REPOSITORY KDE/karchive + VERSION 6.18.0 + OPTIONS + "WITH_BZIP2 ON" + "WITH_LIBLZMA ON" + "WITH_OPENSSL OFF" + "WITH_LIBZSTD OFF" +) + +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE KF6::Archive) diff --git a/src/Utilities/Compression/karchive.cc b/src/Utilities/Compression/karchive.cc new file mode 100644 index 000000000000..4f09be50fc6e --- /dev/null +++ b/src/Utilities/Compression/karchive.cc @@ -0,0 +1,164 @@ +#include "karchive.h" +#include "QGCLoggingCategory.h" + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#if __has_include() +#include +#define KARCHIVE_HAS_K7ZIP 1 +#else +#define KARCHIVE_HAS_K7ZIP 0 +#endif + +QGC_LOGGING_CATEGORY(karchiveLog, "qgc.utilities.compression.karchive") + +namespace +{ + +/// Returns only the file/folder name component of a path ("/a/b/c" -> "c") +QString _archiveRootName(const QString &path) +{ + return QFileInfo(path).fileName(); +} + +/// Factory: choose the correct *writer* class based on the file extension. +std::unique_ptr _makeWriter(const QString &archivePath) +{ + const QString lower = archivePath.toLower(); + + if (lower.endsWith(".zip")) { + return std::make_unique(archivePath); + } + + // Tar family (plain, gzip, bzip2, xz) + if (lower.endsWith(".tar") || lower.endsWith(".tar.gz") || lower.endsWith(".tgz") || + lower.endsWith(".tar.bz2")|| lower.endsWith(".tbz2") || lower.endsWith(".tbz") || + lower.endsWith(".tar.xz") || lower.endsWith(".txz")) { + return std::make_unique(archivePath); + } + +#if KARCHIVE_HAS_K7ZIP + // NOTE: K7Zip (at least in KF5/KF6) is **read‑only**. We therefore return nullptr + // to tell the caller this archive type cannot be *created*. + if (lower.endsWith(".7z")) { + return nullptr; + } +#endif + + return nullptr; // Unknown format +} + +/// Factory: choose the correct *reader* class based on the file extension. +std::unique_ptr _makeReader(const QString &archivePath) +{ + const QString lower = archivePath.toLower(); + + if (lower.endsWith(".zip")) { + return std::make_unique(archivePath); + } + + if (lower.endsWith(".tar") || lower.endsWith(".tar.gz") || lower.endsWith(".tgz") || + lower.endsWith(".tar.bz2")|| lower.endsWith(".tbz2") || lower.endsWith(".tbz") || + lower.endsWith(".tar.xz") || lower.endsWith(".txz")) { + return std::make_unique(archivePath); + } + +#if KARCHIVE_HAS_K7ZIP + if (lower.endsWith(".7z")) { + return std::make_unique(archivePath); + } +#endif + + return nullptr; // Unknown format +} + +} + +namespace karchive +{ + +bool createArchive(const QString &directoryPath, const QString &archivePath) +{ + // Sanity checks -------------------------------------------------- + if (!QDir(directoryPath).exists()) { + qCWarning(karchiveLog) << "Directory does not exist:" << directoryPath; + return false; + } + + const QFileInfo info(archivePath); + if (!QDir().mkpath(info.absolutePath())) { + qCWarning(karchiveLog) << "Unable to create directory for archive:" << info.absolutePath(); + return false; + } + + // Select writer --------------------------------------------------- + auto archive = _makeWriter(archivePath); + if (!archive) { + qCWarning(karchiveLog) << "Unsupported or read‑only archive type for writing:" << archivePath; + return false; + } + + if (!archive->open(QIODevice::WriteOnly)) { + qCWarning(karchiveLog) << "Cannot create archive:" << archivePath << "error:" << archive->errorString(); + return false; + } + + // Add contents ---------------------------------------------------- + if (!archive->addLocalDirectory(directoryPath, _archiveRootName(directoryPath))) { + qCWarning(karchiveLog) << "Failed to add directory" << directoryPath << "to archive"; + archive->close(); + return false; + } + + archive->close(); + return true; +} + +bool extractArchive(const QString &archivePath, const QString &outputDirectoryPath) +{ + // Sanity checks -------------------------------------------------- + if (!QFile::exists(archivePath)) { + qCWarning(karchiveLog) << "Archive does not exist:" << archivePath; + return false; + } + + if (!QDir().mkpath(outputDirectoryPath)) { + qCWarning(karchiveLog) << "Cannot create output directory:" << outputDirectoryPath; + return false; + } + + // Select reader --------------------------------------------------- + auto archive = _makeReader(archivePath); + if (!archive) { + qCWarning(karchiveLog) << "Unsupported archive type:" << archivePath; + return false; + } + + if (!archive->open(QIODevice::ReadOnly)) { + qCWarning(karchiveLog) << "Cannot open" << archivePath << "error:" << archive->errorString(); + return false; + } + + // Extract --------------------------------------------------------- + const KArchiveDirectory *root = archive->directory(); + if (!root) { + qCWarning(karchiveLog) << "Failed to obtain root directory from archive"; + archive->close(); + return false; + } + + root->copyTo(outputDirectoryPath, true); + archive->close(); + return true; +} + +} // namespace karchive diff --git a/src/Utilities/Compression/karchive.h b/src/Utilities/Compression/karchive.h new file mode 100644 index 000000000000..31bdc5c52620 --- /dev/null +++ b/src/Utilities/Compression/karchive.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +Q_DECLARE_LOGGING_CATEGORY(karchiveLog) + +namespace karchive +{ + /// Method to zip files in a given directory + bool createArchive(const QString &directoryPath, const QString &archivePath); + + /// Method to unzip files to a given directory + bool extractArchive(const QString &archivePath, const QString &outputDirectoryPath); +}