diff options
38 files changed, 3332 insertions, 404 deletions
diff --git a/doc/installerfw.qdoc b/doc/installerfw.qdoc index 0d60a3fca..161e1a191 100644 --- a/doc/installerfw.qdoc +++ b/doc/installerfw.qdoc @@ -992,9 +992,16 @@ \section1 Data Directory The \c data directory contains the content that the installer extracts - during the installation. You must package the data as a 7zip archive (.7z). - You can use either the \l archivegen tool that is delivered with the Qt - Installer Framework or some other tool that generates 7zip archives. + during the installation. The data must be packaged into archive files. This is either + done automatically by \l binarycreator and \l repogen when creating an installer or + a repository, respectively, or you can do this beforehand for more control. + + For manual archive creation you can use either the \l archivegen tool that is + delivered with the Qt Installer Framework or some other tool that generates archives in + any of the file formats: \c{7z}, \c{zip}, \c{tar.gz}, \c{tar.bz2} and \c{tar.xz}. + + \note If the Installer Framework tools were built without libarchive support, + only \c{7z} format is supported. */ /*! @@ -1248,6 +1255,12 @@ \li Comma-separated list of packages to be updated based on the component sha checksum instead of the version number. This parameter adds a new \c <ContentSha1> node to the \c Updates.xml. + \row + \li --af or --archive-format 7z|zip|tar.gz|tar.bz2|tar.xz + \li Set the format used when packaging new component data archives. If + you omit this option, the 7z format will be used as a default. + \note If the Installer Framework tools were built without libarchive + support, only \c{7z} format is supported. \endtable \note We recommend that you use the \c {--update-new-packages} parameter to update an existing repository, especially if you have a content delivery @@ -1258,20 +1271,58 @@ \section1 archivegen - You can use \c archivegen to package files and directories into 7zip (.7z) - archives. + You can use \c archivegen to package files and directories into archives. The \c archivegen tool expects the following parameters in the following order: \code - archivegen <name.7z> <data> + archivegen <archive_name> <data> \endcode - Where \e <name.7z> is the path and file name of the archive to create and + Where \e <archive_name> is the path and file name of the archive to create and \e <data> contains the paths and names of the files or directories to package into the archive, separated by spaces. + \section2 Summary of archivegen Parameters + + \table + \header + \li Parameter + \li Use + \row + \li -h, --help + \li Displays this help. + \row + \li -v, --version + \li Displays version information. + \row + \li -f, --format <format> + \li Format for the archive. Defaults to 7z. + \note If the Installer Framework tools were built without libarchive + support, only \c{7z} format is supported. + \list + \li 7z (7z archive) + \li zip (ZIP archive) + \li tar.gz (gzip compressed tar archive) + \li tar.bz2 (bzip2 compressed tar archive) + \li tar.xz (xz compressed tar archive) + \endlist + \row + \li -c, --compression <5> + \li Defaults to 5 (Normal compression). \note Some formats do not support + all the possible values, for example bzip2 compression only supports + values from 1 to 9. + \list + \li 0 (No compression) + \li 1 (Fastest compressing) + \li 3 (Fast compressing) + \li 5 (Normal compressing) + \li 7 (Maximum compressing) + \li 9 (Ultra compressing) + \endlist + \endtable + \section1 devtool You can use \c devtool to update an existing installer or maintenance tool diff --git a/src/libs/ifwtools/binarycreator.cpp b/src/libs/ifwtools/binarycreator.cpp index a813c7f50..a1261d55e 100644 --- a/src/libs/ifwtools/binarycreator.cpp +++ b/src/libs/ifwtools/binarycreator.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -804,7 +804,7 @@ int QInstallerTools::createBinary(BinaryCreatorArgs args, QString &argumentError // 2.2; copy the packages data and setup the packages vector with the files we copied, // must happen before copying meta data because files will be compressed if // needed and meta data generation relies on this - copyComponentData(args.packagesDirectories, tmpRepoDir, &preparedPackages); + copyComponentData(args.packagesDirectories, tmpRepoDir, &preparedPackages, QLatin1String("7z")); // 2.3; add to common vector packages.append(preparedPackages); } diff --git a/src/libs/ifwtools/repositorygen.cpp b/src/libs/ifwtools/repositorygen.cpp index d8339b063..6d87caef4 100644 --- a/src/libs/ifwtools/repositorygen.cpp +++ b/src/libs/ifwtools/repositorygen.cpp @@ -33,10 +33,7 @@ #include "fileutils.h" #include "errors.h" #include "globals.h" -#include "lib7z_create.h" -#include "lib7z_extract.h" -#include "lib7z_facade.h" -#include "lib7z_list.h" +#include "archivefactory.h" #include "settings.h" #include "qinstallerglobal.h" #include "utils.h" @@ -256,6 +253,7 @@ void QInstallerTools::copyMetaData(const QString &_targetDir, const QString &met qDebug() << "calculate size of directory" << dataDir.absolutePath(); foreach (const QFileInfo &fi, entries) { try { + QScopedPointer<AbstractArchive> archive(ArchiveFactory::instance().create(fi.filePath())); if (fi.isDir()) { QDirIterator recursDirIt(fi.filePath(), QDirIterator::Subdirectories); while (recursDirIt.hasNext()) { @@ -264,14 +262,12 @@ void QInstallerTools::copyMetaData(const QString &_targetDir, const QString &met componentSize += size; compressedComponentSize += size; } - } else if (Lib7z::isSupportedArchive(fi.filePath())) { + } else if (archive && archive->open(QIODevice::ReadOnly) && archive->isSupported()) { // if it's an archive already, list its files and sum the uncompressed sizes - QFile archive(fi.filePath()); - compressedComponentSize += archive.size(); - QInstaller::openForRead(&archive); + compressedComponentSize += fi.size(); - QVector<Lib7z::File>::const_iterator fileIt; - const QVector<Lib7z::File> files = Lib7z::listArchive(&archive); + QVector<ArchiveEntry>::const_iterator fileIt; + const QVector<ArchiveEntry> files = archive->list(); for (fileIt = files.begin(); fileIt != files.end(); ++fileIt) componentSize += fileIt->uncompressedSize; } else { @@ -393,9 +389,15 @@ void QInstallerTools::copyMetaData(const QString &_targetDir, const QString &met } else { // Extract metadata from archive if (!info.metaFile.isEmpty()){ - QFile metaFile(info.metaFile); - QInstaller::openForRead(&metaFile); - Lib7z::extractArchive(&metaFile, targetDir); + QScopedPointer<AbstractArchive> metaFile(ArchiveFactory::instance().create(info.metaFile)); + if (!metaFile) { + throw QInstaller::Error(QString::fromLatin1("Could not create handler " + "object for archive \"%1\": \"%2\".").arg(info.metaFile, QLatin1String(Q_FUNC_INFO))); + } + if (!(metaFile->open(QIODevice::ReadOnly) && metaFile->extract(targetDir))) { + throw Error(QString::fromLatin1("Could not extract archive \"%1\": %2").arg( + QDir::toNativeSeparators(info.metaFile), metaFile->errorString())); + } } // Restore "PackageUpdate" node; @@ -411,9 +413,16 @@ void QInstallerTools::copyMetaData(const QString &_targetDir, const QString &met // Packages can be in repositories using different meta formats, // always extract unified meta if given as argument. foreach (const QString uniteMetadata, uniteMetadatas) { - QFile metaFile(QFileInfo(metaDataDir, uniteMetadata).absoluteFilePath()); - QInstaller::openForRead(&metaFile); - Lib7z::extractArchive(&metaFile, targetDir); + const QString metaFilePath = QFileInfo(metaDataDir, uniteMetadata).absoluteFilePath(); + QScopedPointer<AbstractArchive> metaFile(ArchiveFactory::instance().create(metaFilePath)); + if (!metaFile) { + throw QInstaller::Error(QString::fromLatin1("Could not create handler " + "object for archive \"%1\": \"%2\".").arg(metaFilePath, QLatin1String(Q_FUNC_INFO))); + } + if (!(metaFile->open(QIODevice::ReadOnly) && metaFile->extract(targetDir))) { + throw Error(QString::fromLatin1("Could not extract archive \"%1\": %2").arg( + QDir::toNativeSeparators(metaFilePath), metaFile->errorString())); + } } doc.appendChild(root); @@ -750,6 +759,19 @@ static void writeSHA1ToNodeWithName(QDomDocument &doc, QDomNodeList &list, const } } +void QInstallerTools::createArchive(const QString &filename, const QStringList &data) +{ + QScopedPointer<AbstractArchive> targetArchive(ArchiveFactory::instance().create(filename)); + if (!targetArchive) { + throw QInstaller::Error(QString::fromLatin1("Could not create handler " + "object for archive \"%1\": \"%2\".").arg(filename, QLatin1String(Q_FUNC_INFO))); + } + if (!(targetArchive->open(QIODevice::WriteOnly) && targetArchive->create(data))) { + throw Error(QString::fromLatin1("Could not create archive \"%1\": %2").arg( + QDir::toNativeSeparators(filename), targetArchive->errorString())); + } +} + void QInstallerTools::compressMetaDirectories(const QString &repoDir, const QString &existingUnite7zUrl, const QHash<QString, QString> &versionMapping, bool createSplitMetadata, bool createUnifiedMetadata) { @@ -799,9 +821,15 @@ QStringList QInstallerTools::unifyMetadata(const QString &repoDir, const QString QString existingRepoTemp = existingRepoTempDir.path(); if (!existingRepoDir.isEmpty()) { existingRepoTempDir.setAutoRemove(false); - QFile archiveFile(existingRepoDir); - QInstaller::openForRead(&archiveFile); - Lib7z::extractArchive(&archiveFile, existingRepoTemp); + QScopedPointer<AbstractArchive> archiveFile(ArchiveFactory::instance().create(existingRepoDir)); + if (!archiveFile) { + throw QInstaller::Error(QString::fromLatin1("Could not create handler " + "object for archive \"%1\": \"%2\".").arg(existingRepoDir, QLatin1String(Q_FUNC_INFO))); + } + if (!(archiveFile->open(QIODevice::ReadOnly) && archiveFile->extract(existingRepoTemp))) { + throw Error(QString::fromLatin1("Could not extract archive \"%1\": %2").arg( + QDir::toNativeSeparators(existingRepoDir), archiveFile->errorString())); + } QDir dir2(existingRepoTemp); QStringList existingRepoEntries = dir2.entryList(QDir::Dirs | QDir::NoDotAndDotDot); foreach (const QString existingRepoEntry, existingRepoEntries) { @@ -819,7 +847,7 @@ QStringList QInstallerTools::unifyMetadata(const QString &repoDir, const QString const QString metadataFilename = QDateTime::currentDateTime(). toString(QLatin1String("yyyy-MM-dd-hhmm")) + QLatin1String("_meta.7z"); const QString tmpTarget = repoDir + QDir::separator() + metadataFilename; - Lib7z::createArchive(tmpTarget, absPaths, Lib7z::TmpFile::No); + createArchive(tmpTarget, absPaths); QFile tmp(tmpTarget); tmp.open(QFile::ReadOnly); @@ -859,7 +887,9 @@ void QInstallerTools::splitMetadata(const QStringList &entryList, const QString const QString versionPrefix = versionMapping[path]; const QString fn = QLatin1String(versionPrefix.toLatin1() + "meta.7z"); const QString tmpTarget = repoDir + QLatin1String("/") + fn; - Lib7z::createArchive(tmpTarget, QStringList() << absPath, Lib7z::TmpFile::No); + + createArchive(tmpTarget, QStringList() << absPath); + // remove the files that got compressed QInstaller::removeFiles(absPath, true); QFile tmp(tmpTarget); @@ -876,7 +906,7 @@ void QInstallerTools::splitMetadata(const QStringList &entryList, const QString } void QInstallerTools::copyComponentData(const QStringList &packageDirs, const QString &repoDir, - PackageInfoVector *const infos) + PackageInfoVector *const infos, const QString &archiveSuffix) { for (int i = 0; i < infos->count(); ++i) { const PackageInfo info = infos->at(i); @@ -898,7 +928,9 @@ void QInstallerTools::copyComponentData(const QStringList &packageDirs, const QS QFileInfo fileInfo(dataDir.absoluteFilePath(entry)); if (fileInfo.isFile() && !fileInfo.isSymLink()) { const QString absoluteEntryFilePath = dataDir.absoluteFilePath(entry); - if (Lib7z::isSupportedArchive(absoluteEntryFilePath)) { + QScopedPointer<AbstractArchive> archive(ArchiveFactory::instance() + .create(absoluteEntryFilePath)); + if (archive && archive->open(QIODevice::ReadOnly) && archive->isSupported()) { QFile tmp(absoluteEntryFilePath); QString target = QString::fromLatin1("%1/%3%2").arg(namedRepoDir, entry, info.version); qDebug() << "Copying archive from" << tmp.fileName() << "to" << target; @@ -912,9 +944,8 @@ void QInstallerTools::copyComponentData(const QStringList &packageDirs, const QS } } else if (fileInfo.isDir()) { qDebug() << "Compressing data directory" << entry; - QString target = QString::fromLatin1("%1/%3%2.7z").arg(namedRepoDir, entry, info.version); - Lib7z::createArchive(target, QStringList() << dataDir.absoluteFilePath(entry), - Lib7z::TmpFile::No); + QString target = QString::fromLatin1("%1/%3%2.%4").arg(namedRepoDir, entry, info.version, archiveSuffix); + createArchive(target, QStringList() << dataDir.absoluteFilePath(entry)); compressedFiles.append(target); } else if (fileInfo.isSymLink()) { filesToCompress.append(dataDir.absoluteFilePath(entry)); @@ -924,9 +955,8 @@ void QInstallerTools::copyComponentData(const QStringList &packageDirs, const QS if (!filesToCompress.isEmpty()) { qDebug() << "Compressing files found in data directory:" << filesToCompress; - QString target = QString::fromLatin1("%1/%3%2").arg(namedRepoDir, QLatin1String("content.7z"), - info.version); - Lib7z::createArchive(target, filesToCompress, Lib7z::TmpFile::No); + QString target = QString::fromLatin1("%1/%2content.%3").arg(namedRepoDir, info.version, archiveSuffix); + createArchive(target, filesToCompress); compressedFiles.append(target); } @@ -1059,7 +1089,7 @@ PackageInfoVector QInstallerTools::collectPackages(RepositoryInfo info, QStringL } void QInstallerTools::createRepository(RepositoryInfo info, PackageInfoVector *packages, - const QString &tmpMetaDir, bool createComponentMetadata, bool createUnifiedMetadata) + const QString &tmpMetaDir, bool createComponentMetadata, bool createUnifiedMetadata, const QString &archiveSuffix) { QHash<QString, QString> pathToVersionMapping = QInstallerTools::buildPathToVersionMapping(*packages); @@ -1075,7 +1105,7 @@ void QInstallerTools::createRepository(RepositoryInfo info, PackageInfoVector *p unite7zFiles.append(it.fileInfo().absoluteFilePath()); } } - QInstallerTools::copyComponentData(directories, info.repositoryDir, packages); + QInstallerTools::copyComponentData(directories, info.repositoryDir, packages, archiveSuffix); QInstallerTools::copyMetaData(tmpMetaDir, info.repositoryDir, *packages, QLatin1String("{AnyApplication}"), QLatin1String(QUOTE(IFW_REPOSITORY_FORMAT_VERSION)), unite7zFiles); diff --git a/src/libs/ifwtools/repositorygen.h b/src/libs/ifwtools/repositorygen.h index 0da81db67..5f67b8220 100644 --- a/src/libs/ifwtools/repositorygen.h +++ b/src/libs/ifwtools/repositorygen.h @@ -77,6 +77,8 @@ PackageInfoVector IFWTOOLS_EXPORT createListOfRepositoryPackages(const QStringLi QHash<QString, QString> IFWTOOLS_EXPORT buildPathToVersionMapping(const PackageInfoVector &info); +void IFWTOOLS_EXPORT createArchive(const QString &filename, const QStringList &data); + void IFWTOOLS_EXPORT compressMetaDirectories(const QString &repoDir, const QString &existingUnite7zUrl, const QHash<QString, QString> &versionMapping, bool createSplitMetadata, bool createUnifiedMetadata); @@ -86,13 +88,15 @@ void splitMetadata(const QStringList &entryList, const QString &repoDir, QDomDoc void IFWTOOLS_EXPORT copyMetaData(const QString &outDir, const QString &dataDir, const PackageInfoVector &packages, const QString &appName, const QString& appVersion, const QStringList &uniteMetadatas); -void IFWTOOLS_EXPORT copyComponentData(const QStringList &packageDir, const QString &repoDir, PackageInfoVector *const infos); +void IFWTOOLS_EXPORT copyComponentData(const QStringList &packageDir, const QString &repoDir, + PackageInfoVector *const infos, const QString &archiveSuffix); void IFWTOOLS_EXPORT filterNewComponents(const QString &repositoryDir, QInstallerTools::PackageInfoVector &packages); QString IFWTOOLS_EXPORT existingUniteMeta7z(const QString &repositoryDir); PackageInfoVector IFWTOOLS_EXPORT collectPackages(RepositoryInfo info, QStringList *filteredPackages, FilterType filterType, bool updateNewComponents, QStringList packagesUpdatedWithSha); -void IFWTOOLS_EXPORT createRepository(RepositoryInfo info, PackageInfoVector *packages, const QString &tmpMetaDir, bool createComponentMetadata, bool createUnifiedMetadata); +void IFWTOOLS_EXPORT createRepository(RepositoryInfo info, PackageInfoVector *packages, const QString &tmpMetaDir, + bool createComponentMetadata, bool createUnifiedMetadata, const QString &archiveSuffix); } // namespace QInstallerTools #endif // REPOSITORYGEN_H diff --git a/src/libs/installer/abstractarchive.cpp b/src/libs/installer/abstractarchive.cpp new file mode 100644 index 000000000..dd9b8e625 --- /dev/null +++ b/src/libs/installer/abstractarchive.cpp @@ -0,0 +1,221 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#include "abstractarchive.h" + +namespace QInstaller { + +/*! + \inmodule QtInstallerFramework + \class QInstaller::ArchiveEntry + \brief The ArchiveEntry struct represents an entry in an archive file, + which can be for example a file or a directory. +*/ + +/*! + \inmodule QtInstallerFramework + \class QInstaller::AbstractArchive + \brief The AbstractArchive class is the base class for classes representing + different archive files. It cannot be instantiated on its own but + defines the API and provides common functionality when subclassed. +*/ + +/*! + \enum AbstractArchive::CompressionLevel + This enum holds the possible values for archive compression level. + + \value Non + \value Fastest + \value Fast + \value Normal + \value Maximum + \value Ultra +*/ + +/*! + \fn QInstaller::AbstractArchive::currentEntryChanged(const QString &filename) + + Current entry changed to \a filename. Subclasses should emit this signal whenever + the entry to process is changed. +*/ + +/*! + \fn QInstaller::AbstractArchive::completedChanged(quint64 completed, quint64 total) + + The ratio of \a completed entries from \a total changed. Subclasses should emit + this whenever the progress changes. +*/ + +/*! + \fn QInstaller::AbstractArchive::cancel() + + Cancels current operation. A subclass should implement this slot. +*/ + +/*! + \fn QInstaller::AbstractArchive::close() + + Closes the archive. A subclass should implement this method. +*/ + +/*! + \fn QInstaller::AbstractArchive::create(const QStringList &data) + + Creates an archive from \a data. Returns \c true on success; + \c false otherwise. A subclass should implement this method. +*/ + +/*! + \fn QInstaller::AbstractArchive::extract(const QString &dirPath) + + Extracts the archive to \a dirPath. Returns \c true on success; + \c false otherwise. A subclass should implement this method. +*/ + +/*! + \fn QInstaller::AbstractArchive::extract(const QString &dirPath, const quint64 totalFiles) + + Extracts the contents of an archive to \a dirPath with precalculated + count of \a totalFiles. Returns \c true on success; \c false otherwise. + A subclass should implement this method. +*/ + +/*! + \fn QInstaller::AbstractArchive::isSupported() + + Returns \c true if the archive is supported; \c false otherwise. + A subclass should implement this method. +*/ + +/*! + \fn QInstaller::AbstractArchive::list() + + Returns a list of entries in this archive. A subclass should implement this method. +*/ + +/*! + \fn QInstaller::AbstractArchive::open(QIODevice::OpenMode mode) + + Opens the file device for an archive in \a mode. Returns \c true on success; + \c false otherwise. A subclass should implement this method. +*/ + +/*! + \fn QInstaller::AbstractArchive::setFilename(const QString &filename) + + Sets the \a filename for the archive. A subclass should implement this method. +*/ + +/*! + Constructs a new archive object with \a parent as parent. Cannot be + called directly but instead from subclass constructors. +*/ +AbstractArchive::AbstractArchive(QObject *parent) + : QObject(parent) + , m_compressionLevel(CompressionLevel::Normal) +{ +} + +/*! + Virtual destructor for \c AbstractArchive. +*/ +AbstractArchive::~AbstractArchive() +{ +} + +/*! + Returns a human-readable description of the last error that occurred. +*/ +QString AbstractArchive::errorString() const +{ + return m_error; +} + +/*! + Sets the compression level for new archives to \a level. +*/ +void AbstractArchive::setCompressionLevel(const CompressionLevel level) +{ + m_compressionLevel = level; +} + +/*! + Sets a human-readable description of the current \a error. +*/ +void AbstractArchive::setErrorString(const QString &error) +{ + m_error = error; +} + +/*! + Returns the current compression level. +*/ +AbstractArchive::CompressionLevel AbstractArchive::compressionLevel() const +{ + return m_compressionLevel; +} + +/*! + Reads an \a entry from the specified \a istream. Returns a reference to \a istream. +*/ +QDataStream &operator>>(QDataStream &istream, ArchiveEntry &entry) +{ + istream >> entry.path >> entry.utcTime >> entry.isDirectory + >> entry.uncompressedSize >> entry.permissions_mode >> entry.permissions_enum; + + return istream; +} + +/*! + Writes an \a entry to the specified \a ostream. Returns a reference to \a ostream. +*/ +QDataStream &operator<<(QDataStream &ostream, const ArchiveEntry &entry) +{ + ostream << entry.path << entry.utcTime << entry.isDirectory + << entry.uncompressedSize << entry.permissions_mode << entry.permissions_enum; + + return ostream; +} + +/*! + Returns \c true if left-hand-side entry \a lhs is equal to right-hand-size entry \a rhs. +*/ +bool operator==(const ArchiveEntry &lhs, const ArchiveEntry &rhs) +{ + return lhs.path == rhs.path + && lhs.utcTime == rhs.utcTime + && lhs.isDirectory == rhs.isDirectory + && lhs.compressedSize == rhs.compressedSize + && lhs.uncompressedSize == rhs.uncompressedSize + && lhs.permissions_mode == rhs.permissions_mode + && (lhs.permissions_enum == rhs.permissions_enum // ignore invalid permissions + || lhs.permissions_enum == static_cast<QFile::Permissions>(-1) + || rhs.permissions_enum == static_cast<QFile::Permissions>(-1)); +} + +} // namespace QInstaller diff --git a/src/libs/installer/abstractarchive.h b/src/libs/installer/abstractarchive.h new file mode 100644 index 000000000..77cc35b61 --- /dev/null +++ b/src/libs/installer/abstractarchive.h @@ -0,0 +1,120 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#ifndef ABSTRACTARCHIVE_H +#define ABSTRACTARCHIVE_H + +#include "installer_global.h" + +#include <QFile> +#include <QDateTime> +#include <QDataStream> +#include <QPoint> + +#ifdef Q_OS_WIN +typedef int mode_t; +#endif + +namespace QInstaller { + +struct INSTALLER_EXPORT ArchiveEntry +{ + ArchiveEntry() + : isDirectory(false) + , compressedSize(0) + , uncompressedSize(0) + , permissions_mode(0) + , permissions_enum(0) + {} + + QString path; + QDateTime utcTime; + QPoint archiveIndex; + bool isDirectory; + quint64 compressedSize; + quint64 uncompressedSize; + mode_t permissions_mode; + QFile::Permissions permissions_enum; +}; + +class INSTALLER_EXPORT AbstractArchive : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(AbstractArchive) + +public: + enum CompressionLevel { + Non = 0, + Fastest = 1, + Fast = 3, + Normal = 5, + Maximum = 7, + Ultra = 9 + }; + Q_ENUM(CompressionLevel) + + explicit AbstractArchive(QObject *parent = nullptr); + virtual ~AbstractArchive() = 0; + + virtual bool open(QIODevice::OpenMode mode) = 0; + virtual void close() = 0; + virtual void setFilename(const QString &filename) = 0; + + virtual QString errorString() const; + + virtual bool extract(const QString &dirPath) = 0; + virtual bool extract(const QString &dirPath, const quint64 totalFiles) = 0; + virtual bool create(const QStringList &data) = 0; + virtual QVector<ArchiveEntry> list() = 0; + virtual bool isSupported() = 0; + + virtual void setCompressionLevel(const CompressionLevel level); + +Q_SIGNALS: + void currentEntryChanged(const QString &filename); + void completedChanged(const quint64 completed, const quint64 total); + +public Q_SLOTS: + virtual void cancel() = 0; + +protected: + void setErrorString(const QString &error); + CompressionLevel compressionLevel() const; + +private: + QString m_error; + CompressionLevel m_compressionLevel; +}; + +INSTALLER_EXPORT QDataStream &operator>>(QDataStream &istream, ArchiveEntry &entry); +INSTALLER_EXPORT QDataStream &operator<<(QDataStream &ostream, const ArchiveEntry &entry); +INSTALLER_EXPORT bool operator==(const ArchiveEntry &lhs, const ArchiveEntry &rhs); + +} // namespace QInstaller + +#endif // ABSTRACTARCHIVE_H diff --git a/src/libs/installer/archivefactory.cpp b/src/libs/installer/archivefactory.cpp new file mode 100644 index 000000000..a946f9416 --- /dev/null +++ b/src/libs/installer/archivefactory.cpp @@ -0,0 +1,147 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#include "archivefactory.h" +#include "lib7zarchive.h" +#ifdef IFW_LIBARCHIVE +#include "libarchivewrapper.h" +#endif + +#include <QFileInfo> + +using namespace QInstaller; + +/*! + \inmodule QtInstallerFramework + \class QInstaller::ArchiveFactory + \brief The ArchiveFactory class is used to create archive objects + based on the suffix of a given filename. + + This class acts as a factory for \c QInstaller::AbstractArchive. You can + register one or more archive handlers with this factory and create + registered objects based on the file suffix. + + This class follows the singleton design pattern. Only one instance + of this class can be created and its reference can be fetched from + the \c {instance()} method. + + The following archive handlers are registered by default: + \list + \li Lib7z + \li LibArchive + \endlist +*/ + +/*! + \fn void QInstaller::ArchiveFactory::registerArchive(const QString &name, const QStringList &types) + + Registers a new archive handler with the factory based on \a name and list + of supported file suffix \a types. +*/ + + +/*! + Returns the only instance of this class. +*/ +ArchiveFactory &ArchiveFactory::instance() +{ + static ArchiveFactory instance; + return instance; +} + +/*! + Constructs and returns a pointer to an archive object with \a filename and \a parent. + If the archive type referenced by \a filename is not registered, a null pointer is + returned instead. +*/ +AbstractArchive *ArchiveFactory::create(const QString &filename, QObject *parent) const +{ + const QString suffix = QFileInfo(filename).completeSuffix(); + QString name; + for (auto &types : m_supportedTypesHash) { + QStringList::const_iterator it; + for (it = types.constBegin(); it != types.constEnd(); ++it) { + if (suffix.endsWith(*it, Qt::CaseInsensitive)) { + name = m_supportedTypesHash.key(types); + break; + } + } + } + if (name.isEmpty()) + return nullptr; + + AbstractArchive *archive = GenericFactory<AbstractArchive, QString, QString, QObject *> + ::create(name, filename, parent); + + return archive; +} + +/*! + Returns a list of supported archive types. +*/ +QStringList ArchiveFactory::supportedTypes() +{ + QStringList types; + QHash<QString, QStringList> *const typesHash = &instance().m_supportedTypesHash; + for (auto &value : *typesHash) + types.append(value); + + return types; +} + +/*! + Returns \c true if the archive type from \a filename is registered with + an archive handler. +*/ +bool ArchiveFactory::isSupportedType(const QString &filename) +{ + const QString suffix = QFileInfo(filename).completeSuffix(); + QHash<QString, QStringList> *const typesHash = &instance().m_supportedTypesHash; + for (auto &types : *typesHash) { + QStringList::const_iterator it; + for (it = types.constBegin(); it != types.constEnd(); ++it) { + if (suffix.endsWith(*it, Qt::CaseInsensitive)) + return true; + } + } + return false; +} + +/*! + Private constructor for ArchiveFactory. Registers default archive handlers. +*/ +ArchiveFactory::ArchiveFactory() +{ + registerArchive<Lib7zArchive>(QLatin1String("Lib7z"), QStringList() + << QLatin1String("7z")); +#ifdef IFW_LIBARCHIVE + registerArchive<LibArchiveWrapper>(QLatin1String("LibArchive"), QStringList() + << QLatin1String("tar.gz") << QLatin1String("tar.bz2") + << QLatin1String("tar.xz") << QLatin1String("zip") ); +#endif +} diff --git a/src/libs/installer/archivefactory.h b/src/libs/installer/archivefactory.h new file mode 100644 index 000000000..6f545eefa --- /dev/null +++ b/src/libs/installer/archivefactory.h @@ -0,0 +1,69 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#ifndef ARCHIVEFACTORY_H +#define ARCHIVEFACTORY_H + +#include "installer_global.h" +#include "genericfactory.h" +#include "abstractarchive.h" + +namespace QInstaller { + +class INSTALLER_EXPORT ArchiveFactory + : public GenericFactory<AbstractArchive, QString, QString, QObject *> +{ + Q_DISABLE_COPY(ArchiveFactory) + +public: + static ArchiveFactory &instance(); + + template <typename T> + void registerArchive(const QString &name, const QStringList &types) + { + if (containsProduct(name)) + m_supportedTypesHash.remove(name); + + registerProduct<T>(name); + m_supportedTypesHash.insert(name, types); + } + AbstractArchive *create(const QString &filename, QObject *parent = nullptr) const; + + static QStringList supportedTypes(); + static bool isSupportedType(const QString &filename); + +private: + ArchiveFactory(); + +private: + QHash<QString, QStringList> m_supportedTypesHash; +}; + +} // namespace QInstaller + +#endif // ARCHIVEFACTORY_H diff --git a/src/libs/installer/component.cpp b/src/libs/installer/component.cpp index d50c36c61..22453833d 100644 --- a/src/libs/installer/component.cpp +++ b/src/libs/installer/component.cpp @@ -31,7 +31,7 @@ #include "errors.h" #include "fileutils.h" #include "globals.h" -#include "lib7z_facade.h" +#include "archivefactory.h" #include "messageboxhandler.h" #include "packagemanagercore.h" #include "remoteclient.h" @@ -864,7 +864,8 @@ void Component::createOperationsForArchive(const QString &archive) return; } - const bool isZip = Lib7z::isSupportedArchive(archive); + QScopedPointer<AbstractArchive> archiveFile(ArchiveFactory::instance().create(archive)); + const bool isZip = (archiveFile && archiveFile->open(QIODevice::ReadOnly) && archiveFile->isSupported()); if (isZip) { // component.xml can override this value diff --git a/src/libs/installer/createlocalrepositoryoperation.cpp b/src/libs/installer/createlocalrepositoryoperation.cpp index e05c34b91..a40838178 100644 --- a/src/libs/installer/createlocalrepositoryoperation.cpp +++ b/src/libs/installer/createlocalrepositoryoperation.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -34,8 +34,7 @@ #include "fileio.h" #include "fileutils.h" #include "copydirectoryoperation.h" -#include "lib7z_create.h" -#include "lib7z_facade.h" +#include "lib7zarchive.h" #include "packagemanagercore.h" #include "productkeycheck.h" #include "constants.h" @@ -124,8 +123,12 @@ static QString createArchive(const QString repoPath, const QString &sourceDir, c const QString fileName = QString::fromLatin1("/%1meta.7z").arg(version); QFile archive(repoPath + fileName); - QInstaller::openForWrite(&archive); - Lib7z::createArchive(&archive, QStringList() << sourceDir); + + Lib7zArchive archiveFile(archive.fileName()); + if (!(archiveFile.open(QIODevice::WriteOnly) && archiveFile.create(QStringList() << sourceDir))) { + throw Error(CreateLocalRepositoryOperation::tr("Cannot create archive \"%1\": %2") + .arg(QDir::toNativeSeparators(archive.fileName()), archiveFile.errorString())); + } removeFiles(sourceDir, helper); // cleanup the files we compressed if (!archive.rename(sourceDir + fileName)) { throw Error(CreateLocalRepositoryOperation::tr("Cannot move file \"%1\" to \"%2\": %3") @@ -356,10 +359,6 @@ bool CreateLocalRepositoryOperation::performOperation() } } catch (...) {} setValue(QLatin1String("local-repo"), repoPath); - } catch (const Lib7z::SevenZipException &e) { - setError(UserDefinedError); - setErrorString(e.message()); - return false; } catch (const QInstaller::Error &e) { setError(UserDefinedError); setErrorString(e.message()); diff --git a/src/libs/installer/directoryguard.cpp b/src/libs/installer/directoryguard.cpp new file mode 100644 index 000000000..9c97130a4 --- /dev/null +++ b/src/libs/installer/directoryguard.cpp @@ -0,0 +1,112 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#include "directoryguard.h" + +#include "globals.h" +#include "errors.h" + +#include <QCoreApplication> +#include <QDir> + +namespace QInstaller { + +/*! + \inmodule QtInstallerFramework + \class QInstaller::DirectoryGuard + \brief RAII class to create a directory and delete it on destruction unless released. +*/ + +/*! + Constructs a new guard object for \a path. +*/ +DirectoryGuard::DirectoryGuard(const QString &path) + : m_path(path) + , m_created(false) + , m_released(false) +{ + m_path.replace(QLatin1Char('\\'), QLatin1Char('/')); +} + +/*! + Destroys the directory guard instance and removes the + guarded directory unless released. +*/ +DirectoryGuard::~DirectoryGuard() +{ + if (!m_created || m_released) + return; + QDir dir(m_path); + if (!dir.rmdir(m_path)) + qCWarning(lcInstallerInstallLog) << "Cannot delete directory" << m_path; +} + +/*! + Tries to create the directory structure. + Returns a list of every directory created. +*/ +QStringList DirectoryGuard::tryCreate() +{ + if (m_path.isEmpty()) + return QStringList(); + + const QFileInfo fi(m_path); + if (fi.exists() && fi.isDir()) + return QStringList(); + if (fi.exists() && !fi.isDir()) { + throw Error(QCoreApplication::translate("DirectoryGuard", + "Path \"%1\" exists but is not a directory.").arg(QDir::toNativeSeparators(m_path))); + } + QStringList created; + + QDir toCreate(m_path); + while (!toCreate.exists()) { + QString p = toCreate.absolutePath(); + created.push_front(p); + p = p.section(QLatin1Char('/'), 0, -2); + toCreate = QDir(p); + } + + QDir dir(m_path); + m_created = dir.mkpath(m_path); + if (!m_created) { + throw Error(QCoreApplication::translate("DirectoryGuard", + "Cannot create directory \"%1\".").arg(QDir::toNativeSeparators(m_path))); + } + return created; +} + +/*! + Marks the directory as released. +*/ +void DirectoryGuard::release() +{ + m_released = true; +} + +} // namespace QInstaller diff --git a/src/libs/installer/directoryguard.h b/src/libs/installer/directoryguard.h new file mode 100644 index 000000000..c18402557 --- /dev/null +++ b/src/libs/installer/directoryguard.h @@ -0,0 +1,55 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#ifndef DIRECTORYGUARD_H +#define DIRECTORYGUARD_H + +#include "installer_global.h" + +#include <QString> + +namespace QInstaller { + +class INSTALLER_EXPORT DirectoryGuard +{ +public: + explicit DirectoryGuard(const QString &path); + ~DirectoryGuard(); + + QStringList tryCreate(); + void release(); + +private: + QString m_path; + bool m_created; + bool m_released; +}; + +} // namespace QInstaller + +#endif // DIRECTORYGUARD_H diff --git a/src/libs/installer/extractarchiveoperation.cpp b/src/libs/installer/extractarchiveoperation.cpp index c2d541929..b2bda24a2 100644 --- a/src/libs/installer/extractarchiveoperation.cpp +++ b/src/libs/installer/extractarchiveoperation.cpp @@ -88,27 +88,28 @@ bool ExtractArchiveOperation::performOperation() connect(&callback, &Callback::progressChanged, this, &ExtractArchiveOperation::progressChanged); - if (PackageManagerCore *core = packageManager()) { - connect(core, &PackageManagerCore::statusChanged, &callback, &Callback::statusChanged); - } - - Runnable *runnable = new Runnable(archivePath, targetDir, &callback); - connect(runnable, &Runnable::finished, &receiver, &Receiver::runnableFinished, + Worker *worker = new Worker(archivePath, targetDir, &callback); + connect(worker, &Worker::finished, &receiver, &Receiver::workerFinished, Qt::QueuedConnection); + if (PackageManagerCore *core = packageManager()) + connect(core, &PackageManagerCore::statusChanged, worker, &Worker::onStatusChanged); + QFileInfo fileInfo(archivePath); emit outputTextChanged(tr("Extracting \"%1\"").arg(fileInfo.fileName())); + { + QEventLoop loop; + QThread workerThread; + worker->moveToThread(&workerThread); - QEventLoop loop; - connect(&receiver, &Receiver::finished, &loop, &QEventLoop::quit); - if (QThreadPool::globalInstance()->tryStart(runnable)) { + connect(&workerThread, &QThread::started, worker, &Worker::run); + connect(&receiver, &Receiver::finished, &workerThread, &QThread::quit); + connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); + connect(&workerThread, &QThread::finished, &loop, &QEventLoop::quit); + + workerThread.start(); loop.exec(); - } else { - // HACK: In case there is no availabe thread we should call it directly. - runnable->run(); - receiver.runnableFinished(true, QString()); } - // Write all file names which belongs to a package to a separate file and only the separate // filename to a .dat file. There can be enormous amount of files in a package, which makes // the dat file very slow to read and write. The .dat file is read into memory in startup, diff --git a/src/libs/installer/extractarchiveoperation.h b/src/libs/installer/extractarchiveoperation.h index fa05d403a..7fc008887 100644 --- a/src/libs/installer/extractarchiveoperation.h +++ b/src/libs/installer/extractarchiveoperation.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -63,7 +63,7 @@ private: private: class Callback; - class Runnable; + class Worker; class Receiver; }; diff --git a/src/libs/installer/extractarchiveoperation_p.h b/src/libs/installer/extractarchiveoperation_p.h index 9cc07246f..706187eb7 100644 --- a/src/libs/installer/extractarchiveoperation_p.h +++ b/src/libs/installer/extractarchiveoperation_p.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -31,8 +31,7 @@ #include "extractarchiveoperation.h" #include "fileutils.h" -#include "lib7z_extract.h" -#include "lib7z_facade.h" +#include "archivefactory.h" #include "packagemanagercore.h" #include <QRunnable> @@ -85,7 +84,7 @@ private: typedef QPair<QString, QString> Backup; typedef QVector<Backup> BackupFiles; -class ExtractArchiveOperation::Callback : public QObject, public Lib7z::ExtractCallback +class ExtractArchiveOperation::Callback : public QObject { Q_OBJECT Q_DISABLE_COPY(Callback) @@ -93,36 +92,14 @@ class ExtractArchiveOperation::Callback : public QObject, public Lib7z::ExtractC public: Callback() = default; - BackupFiles backupFiles() const { + BackupFiles backupFiles() const + { return m_backupFiles; } - QStringList extractedFiles() const { - return m_extractedFiles; - } - -public slots: - void statusChanged(QInstaller::PackageManagerCore::Status status) + QStringList extractedFiles() const { - switch(status) { - case PackageManagerCore::Canceled: - m_state = E_ABORT; - break; - case PackageManagerCore::Failure: - m_state = E_FAIL; - break; - default: // ignore all other status values - break; - } - } - -signals: - void progressChanged(double progress); - -private: - void setCurrentFile(const QString &filename) Q_DECL_OVERRIDE - { - m_extractedFiles.prepend(QDir::toNativeSeparators(filename)); + return m_extractedFiles; } static QString generateBackupName(const QString &fn) @@ -135,7 +112,7 @@ private: return res; } - bool prepareForFile(const QString &filename) Q_DECL_OVERRIDE + bool prepareForFile(const QString &filename) { if (!QFile::exists(filename)) return true; @@ -151,59 +128,110 @@ private: return true; } - HRESULT setCompleted(quint64 completed, quint64 total) Q_DECL_OVERRIDE +Q_SIGNALS: + void progressChanged(double progress); + +public Q_SLOTS: + void onCurrentEntryChanged(const QString &filename) + { + m_extractedFiles.prepend(QDir::toNativeSeparators(filename)); + } + + void onCompletedChanged(quint64 completed, quint64 total) { emit progressChanged(double(completed) / total); - return m_state; } private: - HRESULT m_state = S_OK; BackupFiles m_backupFiles; QStringList m_extractedFiles; }; -class ExtractArchiveOperation::Runnable : public QObject, public QRunnable +class ExtractArchiveOperation::Worker : public QObject { Q_OBJECT - Q_DISABLE_COPY(Runnable) + Q_DISABLE_COPY(Worker) public: - Runnable(const QString &archivePath, const QString &targetDir, - ExtractArchiveOperation::Callback *callback) + Worker(const QString &archivePath, const QString &targetDir, Callback *callback) : m_archivePath(archivePath) , m_targetDir(targetDir) + , m_canceled(false) , m_callback(callback) {} +Q_SIGNALS: + void finished(bool success, const QString &errorString); + +public Q_SLOTS: void run() { - QFile archive(m_archivePath); - if (!archive.open(QIODevice::ReadOnly)) { - emit finished(false, tr("Cannot open archive \"%1\" for reading: %2").arg(m_archivePath, - archive.errorString())); + m_canceled = false; + m_archive.reset(ArchiveFactory::instance().create(m_archivePath)); + if (!m_archive) { + emit finished(false, tr("Could not create handler object for archive \"%1\": \"%2\".") + .arg(m_archivePath, QLatin1String(Q_FUNC_INFO))); return; } - try { - Lib7z::extractArchive(&archive, m_targetDir, m_callback); - emit finished(true, QString()); - } catch (const Lib7z::SevenZipException& e) { + connect(m_archive.get(), &AbstractArchive::currentEntryChanged, m_callback, &Callback::onCurrentEntryChanged); + connect(m_archive.get(), &AbstractArchive::completedChanged, m_callback, &Callback::onCompletedChanged); + + if (!(m_archive->open(QIODevice::ReadOnly) && m_archive->isSupported())) { + emit finished(false, tr("Cannot open archive \"%1\" for reading: %2").arg(m_archivePath, + m_archive->errorString())); + return; + } + const QVector<ArchiveEntry> entries = m_archive->list(); + if (entries.isEmpty()) { + emit finished(false, tr("Error while reading contents of archive \"%1\": %2").arg(m_archivePath, + m_archive->errorString())); + return; + } + for (auto &entry : entries) { + QString completeFilePath = m_targetDir + QDir::separator() + entry.path; + if (!entry.isDirectory && !m_callback->prepareForFile(completeFilePath)) { + emit finished(false, tr("Cannot prepare for file \"%1\"").arg(completeFilePath)); + return; + } + } + if (m_canceled) { + // For large archives the reading takes some time, and the user might have + // canceled before we start the actual extracting. + emit finished(false, tr("Extract for archive \"%1\" canceled.").arg(m_archivePath)); + } else if (!m_archive->extract(m_targetDir, entries.size())) { emit finished(false, tr("Error while extracting archive \"%1\": %2").arg(m_archivePath, - e.message())); - } catch (...) { - emit finished(false, tr("Unknown exception caught while extracting \"%1\".") - .arg(m_archivePath)); + m_archive->errorString())); + } else { + emit finished(true, QString()); } } -signals: - void finished(bool success, const QString &errorString); + void onStatusChanged(PackageManagerCore::Status status) + { + if (!m_archive) + return; + + switch (status) { + case PackageManagerCore::Canceled: + m_canceled = true; + m_archive->cancel(); + break; + case PackageManagerCore::Failure: + m_canceled = true; + m_archive->cancel(); + break; + default: // ignore all other status values + break; + } + } private: QString m_archivePath; QString m_targetDir; - ExtractArchiveOperation::Callback *m_callback; + QScopedPointer<AbstractArchive> m_archive; + bool m_canceled; + Callback *m_callback; }; class ExtractArchiveOperation::Receiver : public QObject @@ -223,7 +251,7 @@ public: } public slots: - void runnableFinished(bool ok, const QString &msg) + void workerFinished(bool ok, const QString &msg) { m_success = ok; m_errorString = msg; diff --git a/src/libs/installer/installer.pro b/src/libs/installer/installer.pro index c28a840bd..4721bb089 100644 --- a/src/libs/installer/installer.pro +++ b/src/libs/installer/installer.pro @@ -138,10 +138,18 @@ HEADERS += packagemanagercore.h \ repositorycategory.h \ componentselectionpage_p.h \ commandlineparser.h \ - commandlineparser_p.h + commandlineparser_p.h \ + abstractarchive.h \ + directoryguard.h \ + lib7zarchive.h \ + archivefactory.h SOURCES += packagemanagercore.cpp \ + abstractarchive.cpp \ + archivefactory.cpp \ aspectratiolabel.cpp \ + directoryguard.cpp \ + lib7zarchive.cpp \ loggingutils.cpp \ packagemanagercore_p.cpp \ packagemanagergui.cpp \ @@ -229,8 +237,19 @@ unix { else: SOURCES += adminauthorization_x11.cpp } +CONFIG(libarchive) { + HEADERS += libarchivearchive.h \ + libarchivewrapper.h \ + libarchivewrapper_p.h + + SOURCES += libarchivearchive.cpp \ + libarchivewrapper.cpp \ + libarchivewrapper_p.cpp + + LIBS += -llibarchive +} + LIBS += -l7z -CONFIG(libarchive): LIBS += -llibarchive win32 { SOURCES += adminauthorization_win.cpp sysinfo_win.cpp diff --git a/src/libs/installer/lib7z_create.h b/src/libs/installer/lib7z_create.h index bc61db7ab..72fc56c81 100644 --- a/src/libs/installer/lib7z_create.h +++ b/src/libs/installer/lib7z_create.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -30,6 +30,7 @@ #define LIB7Z_CREATE_H #include "installer_global.h" +#include "abstractarchive.h" #include <Common/MyCom.h> #include <7zip/UI/Common/Update.h> @@ -46,14 +47,7 @@ namespace Lib7z Yes }; - enum struct Compression { - Non = 0, - Fastest = 1, - Fast = 3, - Normal = 5, - Maximum = 7, - Ultra = 9 - }; + typedef QInstaller::AbstractArchive::CompressionLevel Compression; class INSTALLER_EXPORT UpdateCallback : public IUpdateCallbackUI2, public CMyUnknownImp { diff --git a/src/libs/installer/lib7z_facade.cpp b/src/libs/installer/lib7z_facade.cpp index 2f33c95e3..17176d7b0 100644 --- a/src/libs/installer/lib7z_facade.cpp +++ b/src/libs/installer/lib7z_facade.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -36,6 +36,7 @@ #include "lib7z_list.h" #include "lib7z_guid.h" #include "globals.h" +#include "directoryguard.h" #ifndef Q_OS_WIN # include "StdAfx.h" @@ -294,73 +295,6 @@ QString errorMessageFrom7zResult(const LONG &extractResult) return errorMessage; } -/* - RAII class to create a directory (tryCreate()) and delete it on destruction unless released. -*/ -struct DirectoryGuard -{ - explicit DirectoryGuard(const QString &path) - : m_path(path) - , m_created(false) - , m_released(false) - { - m_path.replace(QLatin1Char('\\'), QLatin1Char('/')); - } - - ~DirectoryGuard() - { - if (!m_created || m_released) - return; - QDir dir(m_path); - if (!dir.rmdir(m_path)) - qCWarning(QInstaller::lcInstallerInstallLog) << "Cannot delete directory " << m_path; - } - - /* - Tries to create the directory structure. - Returns a list of every directory created. - */ - QStringList tryCreate() - { - if (m_path.isEmpty()) - return QStringList(); - - const QFileInfo fi(m_path); - if (fi.exists() && fi.isDir()) - return QStringList(); - if (fi.exists() && !fi.isDir()) { - throw SevenZipException(QCoreApplication::translate("DirectoryGuard", - "Path \"%1\" exists but is not a directory.").arg(QDir::toNativeSeparators(m_path))); - } - QStringList created; - - QDir toCreate(m_path); - while (!toCreate.exists()) { - QString p = toCreate.absolutePath(); - created.push_front(p); - p = p.section(QLatin1Char('/'), 0, -2); - toCreate = QDir(p); - } - - QDir dir(m_path); - m_created = dir.mkpath(m_path); - if (!m_created) { - throw SevenZipException(QCoreApplication::translate("DirectoryGuard", - "Cannot create directory \"%1\".").arg(QDir::toNativeSeparators(m_path))); - } - return created; - } - - void release() - { - m_released = true; - } - - QString m_path; - bool m_created; - bool m_released; -}; - static UString QString2UString(const QString &str) { return str.toStdWString().c_str(); @@ -554,18 +488,6 @@ private: QPointer<QIODevice> m_device; }; -bool operator==(const File &lhs, const File &rhs) -{ - return lhs.path == rhs.path - && lhs.utcTime == rhs.utcTime - && lhs.isDirectory == rhs.isDirectory - && lhs.compressedSize == rhs.compressedSize - && lhs.uncompressedSize == rhs.uncompressedSize - && (lhs.permissions == rhs.permissions - || lhs.permissions == static_cast<QFile::Permissions>(-1) - || rhs.permissions == static_cast<QFile::Permissions>(-1)); -} - /*! Returns a list of files belonging to an \a archive. */ @@ -586,6 +508,8 @@ QVector<File> listArchive(QFileDevice *archive) op.types = &types; // Empty, because we use a stream. CIntVector excluded; + excluded.Add(codecs.FindFormatForExtension( + QString2UString(QLatin1String("xz")))); // handled by libarchive op.excludedFormats = &excluded; const CMyComPtr<IInStream> stream = new QIODeviceInStream(archive); @@ -620,7 +544,7 @@ QVector<File> listArchive(QFileDevice *archive) f.archiveIndex.setY(item); f.path = UString2QString(s).replace(QLatin1Char('\\'), QLatin1Char('/')); Archive_IsItem_Folder(arch, item, f.isDirectory); - f.permissions = getPermissions(arch, item, nullptr); + f.permissions_enum = getPermissions(arch, item, nullptr); getDateTimeProperty(arch, item, kpidMTime, &(f.utcTime)); f.uncompressedSize = getUInt64Property(arch, item, kpidSize, 0); f.compressedSize = getUInt64Property(arch, item, kpidPackSize, 0); @@ -692,7 +616,7 @@ STDMETHODIMP ExtractCallback::GetStream(UInt32 index, ISequentialOutStream **out const QFileInfo fi(QString::fromLatin1("%1/%2").arg(targetDir, UString2QString(s))); - DirectoryGuard guard(fi.absolutePath()); + QInstaller::DirectoryGuard guard(fi.absolutePath()); const QStringList directories = guard.tryCreate(); bool isDir = false; @@ -846,19 +770,6 @@ STDMETHODIMP ExtractCallback::SetOperationResult(Int32 /*resultEOperationResult* */ /*! - \enum Lib7z::Compression - - This enum specifies the compression ratio of an archive: - - \value Non - \value Fastest - \value Fast - \value Normal - \value Maximum - \value Ultra -*/ - -/*! \namespace Lib7z \inmodule QtInstallerFramework \brief The Lib7z namespace contains miscellaneous identifiers used throughout the Lib7z library. @@ -1176,7 +1087,7 @@ void extractArchive(QFileDevice *archive, const QString &directory, ExtractCallb localCallback = callback; } - DirectoryGuard outDir(QFileInfo(directory).absolutePath()); + QInstaller::DirectoryGuard outDir(QFileInfo(directory).absolutePath()); try { outDir.tryCreate(); @@ -1191,6 +1102,8 @@ void extractArchive(QFileDevice *archive, const QString &directory, ExtractCallb op.types = &types; // Empty, because we use a stream. CIntVector excluded; + excluded.Add(codecs.FindFormatForExtension( + QString2UString(QLatin1String("xz")))); // handled by libarchive op.excludedFormats = &excluded; const CMyComPtr<IInStream> stream = new QIODeviceInStream(archive); @@ -1248,6 +1161,8 @@ bool isSupportedArchive(QFileDevice *archive) op.types = &types; // Empty, because we use a stream. CIntVector excluded; + excluded.Add(codecs.FindFormatForExtension( + QString2UString(QLatin1String("xz")))); // handled by libarchive op.excludedFormats = &excluded; const CMyComPtr<IInStream> stream = new QIODeviceInStream(archive); diff --git a/src/libs/installer/lib7z_list.h b/src/libs/installer/lib7z_list.h index 965a8b5ea..e09c73746 100644 --- a/src/libs/installer/lib7z_list.h +++ b/src/libs/installer/lib7z_list.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -40,6 +40,7 @@ #define LIB7Z_LIST_H #include "installer_global.h" +#include "abstractarchive.h" #include <QDateTime> #include <QFile> @@ -47,19 +48,7 @@ namespace Lib7z { - struct INSTALLER_EXPORT File - { - public: - QString path; - QDateTime utcTime; - QPoint archiveIndex; - bool isDirectory = false; - quint64 compressedSize = 0; - quint64 uncompressedSize = 0; - QFile::Permissions permissions = 0; - }; - - INSTALLER_EXPORT bool operator==(const File &lhs, const File &rhs); + typedef QInstaller::ArchiveEntry File; QVector<File> INSTALLER_EXPORT listArchive(QFileDevice *archive); diff --git a/src/libs/installer/lib7zarchive.cpp b/src/libs/installer/lib7zarchive.cpp new file mode 100644 index 000000000..d7b0c0dc9 --- /dev/null +++ b/src/libs/installer/lib7zarchive.cpp @@ -0,0 +1,242 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#include "lib7zarchive.h" + +#include "errors.h" +#include "lib7z_facade.h" +#include "lib7z_create.h" +#include "lib7z_list.h" + +#include <QCoreApplication> + +namespace QInstaller { + +/*! + \inmodule QtInstallerFramework + \class QInstaller::Lib7zArchive + \brief The Lib7zArchive class represents an archive file + handled with the LZMA software development kit. +*/ + +/*! + \inmodule QtInstallerFramework + \class QInstaller::Lib7zArchive::ExtractCallbackWrapper + \internal +*/ + +/*! + Constructs an archive object representing an archive file + specified by \a filename with \a parent as parent object. +*/ +Lib7zArchive::Lib7zArchive(const QString &filename, QObject *parent) + : AbstractArchive(parent) + , m_extractCallback(new ExtractCallbackWrapper()) +{ + Lib7zArchive::setFilename(filename); + listenExtractCallback(); +} + +/*! + Constructs an archive object with the given \a parent. +*/ +Lib7zArchive::Lib7zArchive(QObject *parent) + : AbstractArchive(parent) + , m_extractCallback(new ExtractCallbackWrapper()) +{ + listenExtractCallback(); +} + +/*! + Destroys the instance and releases resources. +*/ +Lib7zArchive::~Lib7zArchive() +{ + delete m_extractCallback; +} + +/*! + \reimp + + Opens the underlying file device using \a mode. Returns \c true if + succesfull; otherwise \c false. +*/ +bool Lib7zArchive::open(QIODevice::OpenMode mode) +{ + if (!m_file.open(mode)) { + setErrorString(m_file.errorString()); + return false; + } + return true; +} + +/*! + \reimp + + Closes the underlying file device. +*/ +void Lib7zArchive::close() +{ + m_file.close(); +} + +/*! + \reimp + + Sets the \a filename of the underlying file device. +*/ +void Lib7zArchive::setFilename(const QString &filename) +{ + m_file.setFileName(filename); +} + +/*! + \reimp + + Extracts the contents of this archive to \a dirPath. + Returns \c true on success; \c false otherwise. +*/ +bool Lib7zArchive::extract(const QString &dirPath) +{ + m_extractCallback->setState(S_OK); + try { + Lib7z::extractArchive(&m_file, dirPath, m_extractCallback); + } catch (const Lib7z::SevenZipException &e) { + setErrorString(e.message()); + return false; + } + return true; +} + +/*! + \reimp + + Extracts the contents of this archive to \a dirPath. The \a totalFiles + parameter is unused. Returns \c true on success; \c false otherwise. +*/ +bool Lib7zArchive::extract(const QString &dirPath, const quint64 totalFiles) +{ + Q_UNUSED(totalFiles) + return extract(dirPath); +} + +/*! + \reimp + + Packages the given \a data into the archive and creates the file on disk. +*/ +bool Lib7zArchive::create(const QStringList &data) +{ + try { + // No support for callback yet. + Lib7z::createArchive(&m_file, data, compressionLevel()); + } catch (const Lib7z::SevenZipException &e) { + setErrorString(e.message()); + return false; + } + return true; +} + +/*! + \reimp + + Returns the contents of this archive as an array of \c ArchiveEntry objects. + On failure, returns an empty array. +*/ +QVector<ArchiveEntry> Lib7zArchive::list() +{ + try { + return Lib7z::listArchive(&m_file); + } catch (const Lib7z::SevenZipException &e) { + setErrorString(e.message()); + return QVector<ArchiveEntry>(); + } +} + +/*! + \reimp + + Returns \c true if the current archive is of supported format; + \c false otherwise. +*/ +bool Lib7zArchive::isSupported() +{ + try { + return Lib7z::isSupportedArchive(&m_file); + } catch (const Lib7z::SevenZipException &e) { + setErrorString(e.message()); + return false; + } +} + +/*! + \reimp + + Cancels the extract operation in progress. +*/ +void Lib7zArchive::cancel() +{ + m_extractCallback->setState(E_ABORT); +} + +/*! + \internal +*/ +void Lib7zArchive::listenExtractCallback() +{ + connect(m_extractCallback, &ExtractCallbackWrapper::currentEntryChanged, + this, &Lib7zArchive::currentEntryChanged); + connect(m_extractCallback, &ExtractCallbackWrapper::completedChanged, + this, &Lib7zArchive::completedChanged); +} + + +Lib7zArchive::ExtractCallbackWrapper::ExtractCallbackWrapper() + : m_state(S_OK) +{ +} + +void Lib7zArchive::ExtractCallbackWrapper::setState(HRESULT state) +{ + m_state = state; +} + +void Lib7zArchive::ExtractCallbackWrapper::setCurrentFile(const QString &filename) +{ + emit currentEntryChanged(filename); +} + +HRESULT Lib7zArchive::ExtractCallbackWrapper::setCompleted(quint64 completed, quint64 total) +{ + qApp->processEvents(); + + emit completedChanged(completed, total); + return m_state; +} + +} // namespace QInstaller diff --git a/src/libs/installer/lib7zarchive.h b/src/libs/installer/lib7zarchive.h new file mode 100644 index 000000000..45f352aeb --- /dev/null +++ b/src/libs/installer/lib7zarchive.h @@ -0,0 +1,95 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#ifndef LIB7ZARCHIVE_H +#define LIB7ZARCHIVE_H + +#include "installer_global.h" +#include "abstractarchive.h" +#include "lib7z_extract.h" + +namespace QInstaller { + +class INSTALLER_EXPORT Lib7zArchive : public AbstractArchive +{ + Q_OBJECT + Q_DISABLE_COPY(Lib7zArchive) + +public: + Lib7zArchive(const QString &filename, QObject *parent = nullptr); + explicit Lib7zArchive(QObject *parent = nullptr); + ~Lib7zArchive(); + + bool open(QIODevice::OpenMode mode) Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; + void setFilename(const QString &filename) Q_DECL_OVERRIDE; + + bool extract(const QString &dirPath) Q_DECL_OVERRIDE; + bool extract(const QString &dirPath, const quint64 totalFiles) Q_DECL_OVERRIDE; + bool create(const QStringList &data) Q_DECL_OVERRIDE; + QVector<ArchiveEntry> list() Q_DECL_OVERRIDE; + bool isSupported() Q_DECL_OVERRIDE; + +public Q_SLOTS: + void cancel() Q_DECL_OVERRIDE; + +private: + void listenExtractCallback(); + + class ExtractCallbackWrapper; + +private: + QFile m_file; + ExtractCallbackWrapper *const m_extractCallback; +}; + +class Lib7zArchive::ExtractCallbackWrapper : public QObject, public Lib7z::ExtractCallback +{ + Q_OBJECT + Q_DISABLE_COPY(ExtractCallbackWrapper) + +public: + ExtractCallbackWrapper(); + + void setState(HRESULT state); + +Q_SIGNALS: + void currentEntryChanged(const QString &filename); + void completedChanged(quint64 completed, quint64 total); + +private: + void setCurrentFile(const QString &filename) Q_DECL_OVERRIDE; + HRESULT setCompleted(quint64 completed, quint64 total) Q_DECL_OVERRIDE; + +private: + HRESULT m_state; +}; + +} // namespace QInstaller + +#endif // LIB7ZARCHIVE_H diff --git a/src/libs/installer/libarchivearchive.cpp b/src/libs/installer/libarchivearchive.cpp new file mode 100644 index 000000000..ad5609490 --- /dev/null +++ b/src/libs/installer/libarchivearchive.cpp @@ -0,0 +1,819 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#include "libarchivearchive.h" + +#include "directoryguard.h" +#include "errors.h" +#include "globals.h" + +#include <QApplication> +#include <QFileInfo> +#include <QDir> +#include <QTimer> + +namespace QInstaller { + +/*! + \inmodule QtInstallerFramework + \class QInstaller::ScopedPointerReaderDeleter + \internal +*/ + +/*! + \inmodule QtInstallerFramework + \class QInstaller::ScopedPointerWriterDeleter + \internal +*/ + +/*! + \inmodule QtInstallerFramework + \class QInstaller::ScopedPointerEntryDeleter + \internal +*/ + +/*! + \inmodule QtInstallerFramework + \class QInstaller::ExtractWorker + \internal +*/ + +ExtractWorker::Status ExtractWorker::status() const +{ + return m_status; +} + +void ExtractWorker::extract(const QString &dirPath, const quint64 totalFiles) +{ + m_status = Unfinished; + quint64 completed = 0; + + if (!totalFiles) { + m_status = Failure; + emit finished(QLatin1String("The file count for current archive is null!")); + return; + } + + QScopedPointer<archive, ScopedPointerReaderDeleter> reader(archive_read_new()); + QScopedPointer<archive, ScopedPointerWriterDeleter> writer(archive_write_disk_new()); + archive_entry *entry = nullptr; + + LibArchiveArchive::configureReader(reader.get()); + LibArchiveArchive::configureDiskWriter(writer.get()); + + DirectoryGuard targetDir(QFileInfo(dirPath).absolutePath()); + try { + const QStringList createdDirs = targetDir.tryCreate(); + // Make sure that all leading directories created get removed as well + foreach (const QString &directory, createdDirs) + emit currentEntryChanged(directory); + + int status = archive_read_open(reader.get(), this, nullptr, readCallback, nullptr); + if (status != ARCHIVE_OK) { + m_status = Failure; + emit finished(QLatin1String(archive_error_string(reader.get()))); + return; + } + + forever { + if (m_status == Canceled) { + emit finished(QLatin1String("Extract canceled.")); + return; + } + status = archive_read_next_header(reader.get(), &entry); + if (status == ARCHIVE_EOF) + break; + if (status != ARCHIVE_OK) { + m_status = Failure; + emit finished(QLatin1String(archive_error_string(reader.get()))); + return; + } + const char *current = archive_entry_pathname(entry); + const QString outputPath = dirPath + QDir::separator() + QString::fromLocal8Bit(current); + archive_entry_set_pathname(entry, outputPath.toLocal8Bit()); + + emit currentEntryChanged(outputPath); + if (!writeEntry(reader.get(), writer.get(), entry)) + return; + + ++completed; + emit completedChanged(completed, totalFiles); + + qApp->processEvents(); + } + } catch (const Error &e) { + m_status = Failure; + emit finished(e.message()); + return; + } + targetDir.release(); + m_status = Success; + emit finished(); +} + +void ExtractWorker::addDataBlock(const QByteArray buffer) +{ + m_buffer.append(buffer); + emit dataReadyForRead(); +} + +void ExtractWorker::cancel() +{ + m_status = Canceled; +} + +ssize_t ExtractWorker::readCallback(archive *reader, void *caller, const void **buff) +{ + Q_UNUSED(reader) + + ExtractWorker *obj; + if (!(obj = static_cast<ExtractWorker *>(caller))) + return ARCHIVE_FATAL; + + QByteArray *buffer = &obj->m_buffer; + if (!buffer->isEmpty()) + buffer->clear(); + + emit obj->dataBlockRequested(); + + // It's a bit bad that we have to wait here, but libarchive doesn't + // provide an event based reading method. + { + QEventLoop loop; + QTimer::singleShot(30000, &loop, &QEventLoop::quit); + connect(obj, &ExtractWorker::dataReadyForRead, &loop, &QEventLoop::quit); + connect(obj, &ExtractWorker::dataAtEnd, &loop, &QEventLoop::quit); + loop.exec(); + } + + if (!(*buff = static_cast<const void *>(buffer->constData()))) + return ARCHIVE_FATAL; + + return buffer->size(); +} + +bool ExtractWorker::writeEntry(archive *reader, archive *writer, archive_entry *entry) +{ + int status; + const void *buff; + size_t size; + int64_t offset; + + status = archive_write_header(writer, entry); + if (status != ARCHIVE_OK) { + emit finished(QLatin1String(archive_error_string(writer))); + return false; + } + + forever { + status = archive_read_data_block(reader, &buff, &size, &offset); + if (status == ARCHIVE_EOF) + return true; + if (status != ARCHIVE_OK) { + m_status = Failure; + emit finished(QLatin1String(archive_error_string(reader))); + return false; + } + status = archive_write_data_block(writer, buff, size, offset); + if (status != ARCHIVE_OK) { + m_status = Failure; + emit finished(QLatin1String(archive_error_string(writer))); + return false; + } + } +} + +/*! + \inmodule QtInstallerFramework + \class QInstaller::LibArchiveArchive + \brief The LibArchiveArchive class represents an archive file + handled with libarchive archive and compression library. + + In addition to extracting data from the underlying file device, + the class supports a non-blocking mode of extracting from an external + data source. When using this mode, the calling client must pass the data + to be read in chunks of arbitrary size, and inform the object when there + is no more data to read. +*/ + +/*! + \inmodule QtInstallerFramework + \class QInstaller::LibArchiveArchive::ArchiveData + \brief Bundles a file device and associated read buffer for access + as client data in libarchive callbacks. +*/ + +/*! + \fn QInstaller::LibArchiveArchive::dataBlockRequested() + + Emitted when the worker object requires more data to continue extracting. +*/ + +/*! + \fn QInstaller::LibArchiveArchive::workerFinished() + + Emitted when the worker object finished extracting an archive. +*/ + +/*! + \fn QInstaller::LibArchiveArchive::workerAboutToExtract(const QString &dirPath, const quint64 totalFiles) + + Emitted when the worker object is about to extract \a totalFiles + from an archive to \a dirPath. +*/ + +/*! + \fn QInstaller::LibArchiveArchive::workerAboutToAddDataBlock(const QByteArray buffer) + + Emitted when the worker object is about to add and read the data block in \a buffer. +*/ + +/*! + \fn QInstaller::LibArchiveArchive::workerAboutToSetDataAtEnd() + + Emitted when the worker object is about to set data-at-end, meaning there + will be no further read requests for the calling client. +*/ + +/*! + \fn QInstaller::LibArchiveArchive::workerAboutToCancel() + + Emitted when the worker object is about to cancel extracting. +*/ + +/*! + Constructs an archive object representing an archive file + specified by \a filename with \a parent as the parent object. +*/ +LibArchiveArchive::LibArchiveArchive(const QString &filename, QObject *parent) + : AbstractArchive(parent) + , m_data(new ArchiveData()) + , m_cancelScheduled(false) +{ + LibArchiveArchive::setFilename(filename); + initExtractWorker(); +} + +/*! + Constructs an archive object with the given \a parent. +*/ +LibArchiveArchive::LibArchiveArchive(QObject *parent) + : AbstractArchive(parent) + , m_data(new ArchiveData()) + , m_cancelScheduled(false) +{ + initExtractWorker(); +} + +/*! + Destroys the instance and releases resources. +*/ +LibArchiveArchive::~LibArchiveArchive() +{ + m_workerThread.quit(); + m_workerThread.wait(); + + delete m_data; +} + +/*! + \reimp + + Opens the underlying file device using \a mode. Returns \c true if + succesfull; otherwise \c false. +*/ +bool LibArchiveArchive::open(QIODevice::OpenMode mode) +{ + if (!m_data->file.open(mode)) { + setErrorString(m_data->file.errorString()); + return false; + } + return true; +} + +/*! + \reimp + + Closes the underlying file device. +*/ +void LibArchiveArchive::close() +{ + m_data->file.close(); +} + +/*! + \reimp + + Sets the \a filename of the underlying file device. +*/ +void LibArchiveArchive::setFilename(const QString &filename) +{ + m_data->file.setFileName(filename); +} + +/*! + \reimp + + Extracts the contents of this archive to \a dirPath. + Returns \c true on success; \c false otherwise. +*/ +bool LibArchiveArchive::extract(const QString &dirPath) +{ + return extract(dirPath, totalFiles()); +} + +/*! + \reimp + + Extracts the contents of this archive to \a dirPath with + precalculated count of \a totalFiles. Returns \c true on + success; \c false otherwise. +*/ +bool LibArchiveArchive::extract(const QString &dirPath, const quint64 totalFiles) +{ + m_cancelScheduled = false; + quint64 completed = 0; + if (!totalFiles) { + setErrorString(QLatin1String("The file count for current archive is null!")); + return false; + } + + QScopedPointer<archive, ScopedPointerReaderDeleter> reader(archive_read_new()); + QScopedPointer<archive, ScopedPointerWriterDeleter> writer(archive_write_disk_new()); + archive_entry *entry = nullptr; + + configureReader(reader.get()); + configureDiskWriter(writer.get()); + + DirectoryGuard targetDir(QFileInfo(dirPath).absolutePath()); + try { + const QStringList createdDirs = targetDir.tryCreate(); + // Make sure that all leading directories created get removed as well + foreach (const QString &directory, createdDirs) + emit currentEntryChanged(directory); + + int status = archive_read_open(reader.get(), m_data, nullptr, readCallback, nullptr); + if (status != ARCHIVE_OK) + throw Error(QLatin1String(archive_error_string(reader.get()))); + + forever { + if (m_cancelScheduled) + throw Error(QLatin1String("Extract canceled.")); + + status = archive_read_next_header(reader.get(), &entry); + if (status == ARCHIVE_EOF) + break; + if (status != ARCHIVE_OK) + throw Error(QLatin1String(archive_error_string(reader.get()))); + + const char *current = archive_entry_pathname(entry); + const QString outputPath = dirPath + QDir::separator() + QString::fromLocal8Bit(current); + archive_entry_set_pathname(entry, outputPath.toLocal8Bit()); + + emit currentEntryChanged(outputPath); + if (!writeEntry(reader.get(), writer.get(), entry)) + throw Error(errorString()); // appropriate error string set in writeEntry() + + ++completed; + emit completedChanged(completed, totalFiles); + + qApp->processEvents(); + } + } catch (const Error &e) { + setErrorString(e.message()); + m_data->file.seek(0); + return false; + } + targetDir.release(); + m_data->file.seek(0); + return true; +} + +/*! + \reimp + + Packages the given \a data into the archive and creates the file on disk. +*/ +bool LibArchiveArchive::create(const QStringList &data) +{ + QScopedPointer<archive, ScopedPointerWriterDeleter> writer(archive_write_new()); + configureWriter(writer.get()); + + try { + int status; + if ((status = archive_write_open_filename(writer.get(), m_data->file.fileName().toLocal8Bit()))) + throw Error(QLatin1String(archive_error_string(writer.get()))); + + for (auto &dataEntry : data) { + QScopedPointer<archive, ScopedPointerReaderDeleter> reader(archive_read_disk_new()); + configureDiskReader(reader.get()); + + if ((status = archive_read_disk_open(reader.get(), dataEntry.toLocal8Bit()))) + throw Error(QLatin1String(archive_error_string(reader.get()))); + + QDir basePath = QFileInfo(dataEntry).dir(); + forever { + QScopedPointer<archive_entry, ScopedPointerEntryDeleter> entry(archive_entry_new()); + status = archive_read_next_header2(reader.get(), entry.get()); + if (status == ARCHIVE_EOF) + break; + if (status != ARCHIVE_OK) + throw Error(QLatin1String(archive_error_string(reader.get()))); + + const QFileInfo fileOrDir(pathWithoutNamespace(QLatin1String(archive_entry_sourcepath(entry.get())))); + // Set new path name in archive, otherwise we add all directories from absolute path + const QString newPath = basePath.relativeFilePath(fileOrDir.filePath()); + archive_entry_set_pathname(entry.get(), newPath.toLocal8Bit()); + + archive_read_disk_descend(reader.get()); + status = archive_write_header(writer.get(), entry.get()); + if (status < ARCHIVE_OK) + throw Error(QLatin1String(archive_error_string(writer.get()))); + + if (fileOrDir.isDir()) + continue; // nothing to copy + + QFile file(pathWithoutNamespace(QLatin1String(archive_entry_sourcepath(entry.get())))); + if (!file.open(QIODevice::ReadOnly)) + throw Error(file.errorString()); + + QByteArray buffer; + constexpr qint64 blockSize = 4 * 1024; + buffer.resize(blockSize); + + ssize_t bytesRead = readData(&file, buffer.data(), blockSize); + while (bytesRead > 0) { + archive_write_data(writer.get(), buffer.constData(), blockSize); + bytesRead = readData(&file, buffer.data(), blockSize); + } + file.close(); + } + } + } catch (const Error &e) { + setErrorString(e.message()); + return false; + } + return true; +} + +/*! + \reimp + + Returns the contents of this archive as an array of \c ArchiveEntry objects. + On failure, returns an empty array. +*/ +QVector<ArchiveEntry> LibArchiveArchive::list() +{ + QScopedPointer<archive, ScopedPointerReaderDeleter> reader(archive_read_new()); + archive_entry *entry = nullptr; + + configureReader(reader.get()); + + QVector<ArchiveEntry> entries; + try { + int status = archive_read_open(reader.get(), m_data, nullptr, readCallback, nullptr); + if (status != ARCHIVE_OK) + throw Error(QLatin1String(archive_error_string(reader.get()))); + + forever { + status = archive_read_next_header(reader.get(), &entry); + if (status == ARCHIVE_EOF) + break; + if (status != ARCHIVE_OK) + throw Error(QLatin1String(archive_error_string(reader.get()))); + + ArchiveEntry archiveEntry; + archiveEntry.path = QLatin1String(archive_entry_pathname(entry)); + archiveEntry.utcTime = QDateTime::fromTime_t(archive_entry_mtime(entry)); + archiveEntry.isDirectory = (archive_entry_filetype(entry) == AE_IFDIR); + archiveEntry.uncompressedSize = archive_entry_size(entry); + archiveEntry.permissions_mode = archive_entry_perm(entry); + + entries.append(archiveEntry); + } + } catch (const Error &e) { + setErrorString(e.message()); + m_data->file.seek(0); + return QVector<ArchiveEntry>(); + } + m_data->file.seek(0); + return entries; +} + +/*! + \reimp + + Returns \c true if the current archive is of a supported format; + \c false otherwise. +*/ +bool LibArchiveArchive::isSupported() +{ + QScopedPointer<archive, ScopedPointerReaderDeleter> reader(archive_read_new()); + configureReader(reader.get()); + + try { + const int status = archive_read_open(reader.get(), m_data, nullptr, readCallback, nullptr); + if (status != ARCHIVE_OK) + throw Error(QLatin1String(archive_error_string(reader.get()))); + } catch (const Error &e) { + setErrorString(e.message()); + m_data->file.seek(0); + return false; + } + m_data->file.seek(0); + return true; +} + +/*! + Requests to extract the archive to \a dirPath with \a totalFiles + in a separate thread with a worker object. +*/ +void LibArchiveArchive::workerExtract(const QString &dirPath, const quint64 totalFiles) +{ + emit workerAboutToExtract(dirPath, totalFiles); +} + +/*! + Adds data to be read by the worker object in \a buffer. +*/ +void LibArchiveArchive::workerAddDataBlock(const QByteArray buffer) +{ + emit workerAboutToAddDataBlock(buffer); +} + +/*! + Signals the worker object that the client data is at end. +*/ +void LibArchiveArchive::workerSetDataAtEnd() +{ + emit workerAboutToSetDataAtEnd(); +} + +/*! + Cancels the extract in progress for the worker object. +*/ +void LibArchiveArchive::workerCancel() +{ + emit workerAboutToCancel(); +} + +/*! + Returns the status of the worker object. +*/ +ExtractWorker::Status LibArchiveArchive::workerStatus() const +{ + return m_worker.status(); +} + +/*! + \reimp + + Cancels the extract in progress. +*/ +void LibArchiveArchive::cancel() +{ + m_cancelScheduled = true; +} + +/*! + \internal +*/ +void LibArchiveArchive::onWorkerFinished(const QString &errorString) +{ + setErrorString(errorString); + emit workerFinished(); +} + +/*! + \internal +*/ +void LibArchiveArchive::configureReader(archive *archive) +{ + archive_read_support_filter_bzip2(archive); + archive_read_support_filter_gzip(archive); + archive_read_support_filter_xz(archive); + + archive_read_support_format_tar(archive); + archive_read_support_format_zip(archive); +} + +/*! + \internal +*/ +void LibArchiveArchive::configureWriter(archive *archive) +{ + if (QFileInfo(m_data->file.fileName()).suffix() == QLatin1String("zip")) { + archive_write_set_format_zip(archive); + } else { + archive_write_set_format_pax_restricted(archive); + archive_write_set_format_filter_by_ext(archive, m_data->file.fileName().toLatin1()); + } + const QByteArray options = "compression-level=" + QString::number(compressionLevel()).toLatin1(); + if (archive_write_set_options(archive, options.constData())) { // not fatal + qCWarning(QInstaller::lcInstallerInstallLog) << "Could not set options" << options + << "for archive" << m_data->file.fileName() << ":" << archive_error_string(archive); + } +} + +/*! + \internal +*/ +void LibArchiveArchive::configureDiskReader(archive *archive) +{ + archive_read_disk_set_standard_lookup(archive); +} + +/*! + \internal +*/ +void LibArchiveArchive::configureDiskWriter(archive *archive) +{ + constexpr int flags = ARCHIVE_EXTRACT_TIME + | ARCHIVE_EXTRACT_PERM + | ARCHIVE_EXTRACT_ACL + | ARCHIVE_EXTRACT_FFLAGS; + + archive_write_disk_set_options(archive, flags); + archive_write_disk_set_standard_lookup(archive); +} + +/*! + \internal +*/ +void LibArchiveArchive::initExtractWorker() +{ + m_worker.moveToThread(&m_workerThread); + + connect(this, &LibArchiveArchive::workerAboutToExtract, &m_worker, &ExtractWorker::extract); + connect(this, &LibArchiveArchive::workerAboutToAddDataBlock, &m_worker, &ExtractWorker::addDataBlock); + connect(this, &LibArchiveArchive::workerAboutToSetDataAtEnd, &m_worker, &ExtractWorker::dataAtEnd); + connect(this, &LibArchiveArchive::workerAboutToCancel, &m_worker, &ExtractWorker::cancel); + + connect(&m_worker, &ExtractWorker::dataBlockRequested, this, &LibArchiveArchive::dataBlockRequested); + connect(&m_worker, &ExtractWorker::finished, this, &LibArchiveArchive::onWorkerFinished); + + connect(&m_worker, &ExtractWorker::currentEntryChanged, this, &LibArchiveArchive::currentEntryChanged); + connect(&m_worker, &ExtractWorker::completedChanged, this, &LibArchiveArchive::completedChanged); + + m_workerThread.start(); +} + +/*! + Writes the current \a entry header, then pulls data from the archive \a reader + and writes it to the \a writer handle. +*/ +bool LibArchiveArchive::writeEntry(archive *reader, archive *writer, archive_entry *entry) +{ + int status; + const void *buff; + size_t size; + int64_t offset; + + status = archive_write_header(writer, entry); + if (status != ARCHIVE_OK) { + setErrorString(QLatin1String(archive_error_string(writer))); + return false; + } + + forever { + status = archive_read_data_block(reader, &buff, &size, &offset); + if (status == ARCHIVE_EOF) + return true; + if (status != ARCHIVE_OK) { + setErrorString(QLatin1String(archive_error_string(reader))); + return false; + } + status = archive_write_data_block(writer, buff, size, offset); + if (status != ARCHIVE_OK) { + setErrorString(QLatin1String(archive_error_string(writer))); + return false; + } + } +} + +/*! + \internal + + Reads \a data from the current position of \a file. The maximum bytes to + read are specified by \a maxSize. Returns the amount of bytes read. +*/ +qint64 LibArchiveArchive::readData(QFile *file, char *data, qint64 maxSize) +{ + if (!file->isOpen() || file->isSequential()) + return ARCHIVE_FATAL; + + if (file->atEnd() && file->seek(0)) + return ARCHIVE_OK; + + const qint64 bytesRead = file->read(data, maxSize); + if (bytesRead == -1) + return ARCHIVE_FATAL; + + return bytesRead; +} + +/*! + \internal + + Called by libarchive when new data is needed. Reads data from the file device + in \a archiveData into the buffer referenced by \a buff. Returns the number of bytes read. +*/ +ssize_t LibArchiveArchive::readCallback(archive *reader, void *archiveData, const void **buff) +{ + Q_UNUSED(reader) + constexpr qint64 blockSize = 1024 * 1024; // 1MB + + ArchiveData *data; + if (!(data = static_cast<ArchiveData *>(archiveData))) + return ARCHIVE_FATAL; + + if (!data->buffer.isEmpty()) + data->buffer.clear(); + + if (data->buffer.size() != blockSize) + data->buffer.resize(blockSize); + + if (!(*buff = static_cast<const void *>(data->buffer.constData()))) + return ARCHIVE_FATAL; + + // Doesn't matter if the buffer size exceeds the actual data read, + // the return value indicates the length of relevant bytes. + return readData(&data->file, data->buffer.data(), data->buffer.size()); +} + +/*! + Returns the \a path to a file or directory, without the Win32 namespace prefix. + On Unix platforms, the \a path is returned unaltered. +*/ +QString LibArchiveArchive::pathWithoutNamespace(const QString &path) +{ + QString aPath = path; +#ifdef Q_OS_WIN + if (aPath.size() > 4 && aPath.at(0) == QLatin1Char('\\') + && aPath.at(2) == QLatin1Char('?') && aPath.at(3) == QLatin1Char('\\')) { + aPath = aPath.mid(4); + } +#endif + return aPath; +} + +/*! + Returns the number of files in this archive. +*/ +quint64 LibArchiveArchive::totalFiles() +{ + QScopedPointer<archive, ScopedPointerReaderDeleter> reader(archive_read_new()); + archive_entry *entry = nullptr; + quint64 files = 0; + + configureReader(reader.get()); + + try { + int status = archive_read_open(reader.get(), m_data, nullptr, readCallback, nullptr); + if (status != ARCHIVE_OK) + throw Error(QLatin1String(archive_error_string(reader.get()))); + + forever { + status = archive_read_next_header(reader.get(), &entry); + if (status == ARCHIVE_EOF) + break; + if (status != ARCHIVE_OK) + throw Error(QLatin1String(archive_error_string(reader.get()))); + + ++files; + } + } catch (const Error &e) { + setErrorString(e.message()); + m_data->file.seek(0); + return 0; + } + m_data->file.seek(0); + return files; +} + +} // namespace QInstaller diff --git a/src/libs/installer/libarchivearchive.h b/src/libs/installer/libarchivearchive.h new file mode 100644 index 000000000..e0281e655 --- /dev/null +++ b/src/libs/installer/libarchivearchive.h @@ -0,0 +1,189 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#ifndef LIBARCHIVEARCHIVE_H +#define LIBARCHIVEARCHIVE_H + +#include "installer_global.h" +#include "abstractarchive.h" + +#include <archive.h> +#include <archive_entry.h> + +#include <QThread> + +#if defined(_MSC_VER) +#include <BaseTsd.h> +typedef SSIZE_T ssize_t; +#endif + +namespace QInstaller { + +class ExtractWorker : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(ExtractWorker) + +public: + enum Status { + Success = 0, + Failure = 1, + Canceled = 2, + Unfinished = 3 + }; + + ExtractWorker() = default; + + Status status() const; + +public Q_SLOTS: + void extract(const QString &dirPath, const quint64 totalFiles); + void addDataBlock(const QByteArray buffer); + void cancel(); + +Q_SIGNALS: + void dataBlockRequested(); + void dataAtEnd(); + void dataReadyForRead(); + void finished(const QString &errorString = QString()); + + void currentEntryChanged(const QString &filename); + void completedChanged(quint64 completed, quint64 total); + +private: + static ssize_t readCallback(archive *reader, void *caller, const void **buff); + bool writeEntry(archive *reader, archive *writer, archive_entry *entry); + +private: + QByteArray m_buffer; + Status m_status; +}; + +class INSTALLER_EXPORT LibArchiveArchive : public AbstractArchive +{ + Q_OBJECT + Q_DISABLE_COPY(LibArchiveArchive) + +public: + LibArchiveArchive(const QString &filename, QObject *parent = nullptr); + explicit LibArchiveArchive(QObject *parent = nullptr); + ~LibArchiveArchive(); + + bool open(QIODevice::OpenMode mode) Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; + void setFilename(const QString &filename) Q_DECL_OVERRIDE; + + bool extract(const QString &dirPath) Q_DECL_OVERRIDE; + bool extract(const QString &dirPath, const quint64 totalFiles) Q_DECL_OVERRIDE; + bool create(const QStringList &data) Q_DECL_OVERRIDE; + QVector<ArchiveEntry> list() Q_DECL_OVERRIDE; + bool isSupported() Q_DECL_OVERRIDE; + + void workerExtract(const QString &dirPath, const quint64 totalFiles); + void workerAddDataBlock(const QByteArray buffer); + void workerSetDataAtEnd(); + void workerCancel(); + ExtractWorker::Status workerStatus() const; + +Q_SIGNALS: + void dataBlockRequested(); + void workerFinished(); + + void workerAboutToExtract(const QString &dirPath, const quint64 totalFiles); + void workerAboutToAddDataBlock(const QByteArray buffer); + void workerAboutToSetDataAtEnd(); + void workerAboutToCancel(); + +public Q_SLOTS: + void cancel() Q_DECL_OVERRIDE; + +private Q_SLOTS: + void onWorkerFinished(const QString &errorString); + +private: + static void configureReader(archive *archive); + void configureWriter(archive *archive); + static void configureDiskReader(archive *archive); + static void configureDiskWriter(archive *archive); + + void initExtractWorker(); + + bool writeEntry(archive *reader, archive *writer, archive_entry *entry); + + static qint64 readData(QFile *file, char *data, qint64 maxSize); + static ssize_t readCallback(archive *reader, void *archiveData, const void **buff); + + static QString pathWithoutNamespace(const QString &path); + + quint64 totalFiles(); + +private: + friend class ExtractWorker; + friend class LibArchiveWrapperPrivate; + + struct ArchiveData + { + QFile file; + QByteArray buffer; + }; + +private: + ArchiveData *m_data; + ExtractWorker m_worker; + QThread m_workerThread; + + bool m_cancelScheduled; +}; + +struct ScopedPointerReaderDeleter +{ + static inline void cleanup(archive *p) + { + archive_read_free(p); + } +}; + +struct ScopedPointerWriterDeleter +{ + static inline void cleanup(archive *p) + { + archive_write_free(p); + } +}; + +struct ScopedPointerEntryDeleter +{ + static inline void cleanup(archive_entry *p) + { + archive_entry_free(p); + } +}; + +} // namespace QInstaller + +#endif // LIBARCHIVEARCHIVE_H diff --git a/src/libs/installer/libarchivewrapper.cpp b/src/libs/installer/libarchivewrapper.cpp new file mode 100644 index 000000000..c259678ca --- /dev/null +++ b/src/libs/installer/libarchivewrapper.cpp @@ -0,0 +1,204 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#include "libarchivewrapper.h" + +namespace QInstaller { + +/*! + \inmodule QtInstallerFramework + \class QInstaller::LibArchiveWrapper + \brief The LibArchiveWrapper class provides an interface for interacting + with archives handled using the libarchive archive and compression library. + + The invoked archive operations are performed in a normal user mode, or in an + on-demand elevated user mode through the remote client-server protocol of the framework. + + This class is not thread-safe; extra care should be taken especially when + elevated mode is active to not call its functions from any thread other than + where the object was created. +*/ + +/*! + \fn QInstaller::LibArchiveWrapper::currentEntryChanged(const QString &filename) + + Current entry changed to \a filename. Emitted when the entry to process is changed. +*/ + +/*! + \fn QInstaller::LibArchiveWrapper::completedChanged(quint64 completed, quint64 total) + + The ratio of \a completed entries from \a total changed. + Emitted when the progress changes. +*/ + +/*! + Constructs an archive object representing an archive file + specified by \a filename with \a parent as the parent object. +*/ +LibArchiveWrapper::LibArchiveWrapper(const QString &filename, QObject *parent) + : AbstractArchive(parent) + , d(new LibArchiveWrapperPrivate(filename)) +{ + connect(d, &LibArchiveWrapperPrivate::currentEntryChanged, + this, &LibArchiveWrapper::currentEntryChanged); + connect(d, &LibArchiveWrapperPrivate::completedChanged, + this, &LibArchiveWrapper::completedChanged); +} + +/*! + Constructs an archive object with the given \a parent. +*/ +LibArchiveWrapper::LibArchiveWrapper(QObject *parent) + : AbstractArchive(parent) + , d(new LibArchiveWrapperPrivate()) +{ + connect(d, &LibArchiveWrapperPrivate::currentEntryChanged, + this, &LibArchiveWrapper::currentEntryChanged); + connect(d, &LibArchiveWrapperPrivate::completedChanged, + this, &LibArchiveWrapper::completedChanged); +} + +/*! + Destroys the instance. +*/ +LibArchiveWrapper::~LibArchiveWrapper() +{ + delete d; +} + +/*! + Opens the file device using \a mode. + Returns \c true if succesfull; otherwise \c false. +*/ +bool LibArchiveWrapper::open(QIODevice::OpenMode mode) +{ + return d->open(mode); +} + +/*! + Closes the file device. +*/ +void LibArchiveWrapper::close() +{ + d->close(); +} + +/*! + Sets the \a filename for the archive. + + If the remote connection is active, the same method is called by the server. +*/ +void LibArchiveWrapper::setFilename(const QString &filename) +{ + d->setFilename(filename); +} + +/*! + Returns a human-readable description of the last error that occurred. + + If the remote connection is active, the method is called by the server instead. +*/ +QString LibArchiveWrapper::errorString() const +{ + return d->errorString(); +} + +/*! + Extracts the contents of this archive to \a dirPath. Returns \c true + on success; \c false otherwise. + + If the remote connection is active, the method is called by the server instead, + with the client starting a new event loop waiting for the extraction to finish. +*/ +bool LibArchiveWrapper::extract(const QString &dirPath) +{ + return d->extract(dirPath); +} + +/*! + Extracts the contents of this archive to \a dirPath with + precalculated count of \a totalFiles. Returns \c true + on success; \c false otherwise. + + If the remote connection is active, the method is called by the server instead, + with the client starting a new event loop waiting for the extraction to finish. +*/ +bool LibArchiveWrapper::extract(const QString &dirPath, const quint64 totalFiles) +{ + return d->extract(dirPath, totalFiles); +} + +/*! + Packages the given \a data into the archive and creates the file on disk. + Returns \c true on success; \c false otherwise. + + If the remote connection is active, the method is called by the server instead. +*/ +bool LibArchiveWrapper::create(const QStringList &data) +{ + return d->create(data); +} + +/*! + Returns the contents of this archive as an array of \c ArchiveEntry objects. + On failure, returns an empty array. +*/ +QVector<ArchiveEntry> LibArchiveWrapper::list() +{ + return d->list(); +} + +/*! + Returns \c true if the current archive is of a supported format; + \c false otherwise. +*/ +bool LibArchiveWrapper::isSupported() +{ + return d->isSupported(); +} + +/*! + Sets the compression level for new archives to \a level. +*/ +void LibArchiveWrapper::setCompressionLevel(const CompressionLevel level) +{ + d->setCompressionLevel(level); +} + +/*! + Cancels the extract operation in progress. + + If the remote connection is active, the method is called by the server instead. +*/ +void LibArchiveWrapper::cancel() +{ + d->cancel(); +} + +} // namespace QInstaller diff --git a/src/libs/installer/libarchivewrapper.h b/src/libs/installer/libarchivewrapper.h new file mode 100644 index 000000000..c638d10dc --- /dev/null +++ b/src/libs/installer/libarchivewrapper.h @@ -0,0 +1,71 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#ifndef LIBARCHIVEWRAPPER_H +#define LIBARCHIVEWRAPPER_H + +#include "installer_global.h" +#include "abstractarchive.h" +#include "libarchivewrapper_p.h" + +namespace QInstaller { + +class INSTALLER_EXPORT LibArchiveWrapper : public AbstractArchive +{ + Q_OBJECT + Q_DISABLE_COPY(LibArchiveWrapper) + +public: + LibArchiveWrapper(const QString &filename, QObject *parent = nullptr); + explicit LibArchiveWrapper(QObject *parent = nullptr); + ~LibArchiveWrapper(); + + bool open(QIODevice::OpenMode mode) Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; + void setFilename(const QString &filename) Q_DECL_OVERRIDE; + + QString errorString() const Q_DECL_OVERRIDE; + + bool extract(const QString &dirPath) Q_DECL_OVERRIDE; + bool extract(const QString &dirPath, const quint64 totalFiles) Q_DECL_OVERRIDE; + bool create(const QStringList &data) Q_DECL_OVERRIDE; + QVector<ArchiveEntry> list() Q_DECL_OVERRIDE; + bool isSupported() Q_DECL_OVERRIDE; + + void setCompressionLevel(const AbstractArchive::CompressionLevel level) Q_DECL_OVERRIDE; + +public Q_SLOTS: + void cancel() Q_DECL_OVERRIDE; + +private: + LibArchiveWrapperPrivate *const d; +}; + +} // namespace QInstaller + +#endif // LIBARCHIVEWRAPPER_H diff --git a/src/libs/installer/libarchivewrapper_p.cpp b/src/libs/installer/libarchivewrapper_p.cpp new file mode 100644 index 000000000..5509812cf --- /dev/null +++ b/src/libs/installer/libarchivewrapper_p.cpp @@ -0,0 +1,356 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#include "libarchivewrapper_p.h" + +#include "globals.h" + +#include <QFileInfo> + +namespace QInstaller { + +/*! + \inmodule QtInstallerFramework + \class QInstaller::LibArchiveWrapperPrivate + \internal +*/ + +/*! + \fn QInstaller::ArchiveWrapper::dataBlockRequested() + + Emitted when the server process has requested another data block. +*/ + +/*! + \fn QInstaller::ArchiveWrapper::remoteWorkerFinished() + + Emitted when the server process has finished extracting an archive. +*/ + +/*! + Constructs an archive object representing an archive file + specified by \a filename. +*/ +LibArchiveWrapperPrivate::LibArchiveWrapperPrivate(const QString &filename) + : RemoteObject(QLatin1String(Protocol::AbstractArchive)) +{ + init(); + LibArchiveWrapperPrivate::setFilename(filename); +} + +/*! + Constructs the object. +*/ +LibArchiveWrapperPrivate::LibArchiveWrapperPrivate() + : RemoteObject(QLatin1String(Protocol::AbstractArchive)) +{ + init(); +} + +/*! + Destroys the instance. +*/ +LibArchiveWrapperPrivate::~LibArchiveWrapperPrivate() +{ + m_timer.stop(); +} + +/*! + Opens the file device using \a mode. + Returns \c true if succesfull; otherwise \c false. +*/ +bool LibArchiveWrapperPrivate::open(QIODevice::OpenMode mode) +{ + return m_archive.open(mode); +} + +/*! + Closes the file device. +*/ +void LibArchiveWrapperPrivate::close() +{ + m_archive.close(); +} + +/*! + Sets the \a filename for the archive. + + If the remote connection is active, the same method is called by the server. +*/ +void LibArchiveWrapperPrivate::setFilename(const QString &filename) +{ + if (connectToServer()) { + m_lock.lockForWrite(); + callRemoteMethod(QLatin1String(Protocol::AbstractArchiveSetFilename), filename, dummy); + m_lock.unlock(); + } + m_archive.setFilename(filename); +} + +/*! + Returns a human-readable description of the last error that occurred. + + If the remote connection is active, the method is called by the server instead. +*/ +QString LibArchiveWrapperPrivate::errorString() const +{ + if ((const_cast<LibArchiveWrapperPrivate *>(this))->connectToServer()) { + m_lock.lockForWrite(); + const QString errorString + = callRemoteMethod<QString>(QLatin1String(Protocol::AbstractArchiveErrorString)); + m_lock.unlock(); + return errorString; + } + return m_archive.errorString(); +} + +/*! + Extracts the contents of this archive to \a dirPath with + an optional precalculated count of \a totalFiles. Returns \c true + on success; \c false otherwise. + + If the remote connection is active, the method is called by the server instead, + with the client starting a new event loop waiting for the extraction to finish. +*/ +bool LibArchiveWrapperPrivate::extract(const QString &dirPath, const quint64 totalFiles) +{ + if (connectToServer()) { + m_lock.lockForWrite(); + callRemoteMethod(QLatin1String(Protocol::AbstractArchiveExtract), dirPath, totalFiles); + m_lock.unlock(); + { + QEventLoop loop; + connect(this, &LibArchiveWrapperPrivate::remoteWorkerFinished, &loop, &QEventLoop::quit); + loop.exec(); + } + return (workerStatus() == ExtractWorker::Success); + } + return m_archive.extract(dirPath, totalFiles); +} + +/*! + Packages the given \a data into the archive and creates the file on disk. + Returns \c true on success; \c false otherwise. + + If the remote connection is active, the method is called by the server instead. +*/ +bool LibArchiveWrapperPrivate::create(const QStringList &data) +{ + if (connectToServer()) { + m_lock.lockForWrite(); + const bool success + = callRemoteMethod<bool>(QLatin1String(Protocol::AbstractArchiveCreate), data); + m_lock.unlock(); + return success; + } + return m_archive.create(data); +} + +/*! + Returns the contents of this archive as an array of \c ArchiveEntry objects. + On failure, returns an empty array. +*/ +QVector<ArchiveEntry> LibArchiveWrapperPrivate::list() +{ + return m_archive.list(); +} + +/*! + Returns \c true if the current archive is of a supported format; + \c false otherwise. +*/ +bool LibArchiveWrapperPrivate::isSupported() +{ + return m_archive.isSupported(); +} + +/*! + Sets the compression level for new archives to \a level. + + If the remote connection is active, the method is called by the server instead. +*/ +void LibArchiveWrapperPrivate::setCompressionLevel(const AbstractArchive::CompressionLevel level) +{ + if (connectToServer()) { + m_lock.lockForWrite(); + callRemoteMethod(QLatin1String(Protocol::AbstractArchiveSetCompressionLevel), level, dummy); + m_lock.unlock(); + return; + } + m_archive.setCompressionLevel(level); +} + +/*! + Cancels the extract operation in progress. + + If the remote connection is active, the method is called by the server instead. +*/ +void LibArchiveWrapperPrivate::cancel() +{ + if (connectToServer()) { + m_lock.lockForWrite(); + callRemoteMethod(QLatin1String(Protocol::AbstractArchiveCancel)); + m_lock.unlock(); + return; + } + m_archive.cancel(); +} + +/*! + Calls a remote method to get the associated queued signals from the server. + Signals are then processed and emitted client-side. +*/ +void LibArchiveWrapperPrivate::processSignals() +{ + if (!isConnectedToServer()) + return; + + if (!m_lock.tryLockForRead()) + return; + + QList<QVariant> receivedSignals = + callRemoteMethod<QList<QVariant>>(QString::fromLatin1(Protocol::GetAbstractArchiveSignals)); + + m_lock.unlock(); + while (!receivedSignals.isEmpty()) { + const QString name = receivedSignals.takeFirst().toString(); + if (name == QLatin1String(Protocol::AbstractArchiveSignalCurrentEntryChanged)) { + emit currentEntryChanged(receivedSignals.takeFirst().toString()); + } else if (name == QLatin1String(Protocol::AbstractArchiveSignalCompletedChanged)) { + const quint64 completed = receivedSignals.takeFirst().value<quint64>(); + const quint64 total = receivedSignals.takeFirst().value<quint64>(); + emit completedChanged(completed, total); + } else if (name == QLatin1String(Protocol::AbstractArchiveSignalDataBlockRequested)) { + emit dataBlockRequested(); + } else if (name == QLatin1String(Protocol::AbstractArchiveSignalWorkerFinished)) { + emit remoteWorkerFinished(); + } + } +} + +/*! + Reads a block of data from the current position of the underlying file device. +*/ +void LibArchiveWrapperPrivate::onDataBlockRequested() +{ + constexpr quint64 blockSize = 10 * 1024 * 1024; // 10MB + + QFile *const file = &m_archive.m_data->file; + if (!file->isOpen() || file->isSequential()) { + qCWarning(QInstaller::lcInstallerInstallLog) << file->errorString(); + setClientDataAtEnd(); + return; + } + if (file->atEnd() && file->seek(0)) { + setClientDataAtEnd(); + return; + } + + QByteArray *const buff = &m_archive.m_data->buffer; + if (!buff->isEmpty()) + buff->clear(); + + if (buff->size() != blockSize) + buff->resize(blockSize); + + const qint64 bytesRead = file->read(buff->data(), blockSize); + if (bytesRead == -1) { + qCWarning(QInstaller::lcInstallerInstallLog) << file->errorString(); + setClientDataAtEnd(); + return; + } + // The read callback in ExtractWorker class expects the buffer size to + // match the number of bytes read. Some formats will fail if the buffer + // is larger than the actual data. + if (buff->size() != bytesRead) + buff->resize(bytesRead); + + addDataBlock(*buff); +} + +/*! + Starts the timer to process server-side signals and connects handler + signals for the matching signals of the wrapper object. +*/ +void LibArchiveWrapperPrivate::init() +{ + m_timer.start(250); + QObject::connect(&m_timer, &QTimer::timeout, + this, &LibArchiveWrapperPrivate::processSignals); + + QObject::connect(&m_archive, &LibArchiveArchive::currentEntryChanged, + this, &LibArchiveWrapperPrivate::currentEntryChanged); + QObject::connect(&m_archive, &LibArchiveArchive::completedChanged, + this, &LibArchiveWrapperPrivate::completedChanged); + + QObject::connect(this, &LibArchiveWrapperPrivate::dataBlockRequested, + this, &LibArchiveWrapperPrivate::onDataBlockRequested); +} + +/*! + Calls a remote method to add a \a buffer for reading. +*/ +void LibArchiveWrapperPrivate::addDataBlock(const QByteArray &buffer) +{ + if (connectToServer()) { + m_lock.lockForWrite(); + callRemoteMethod(QLatin1String(Protocol::AbstractArchiveAddDataBlock), buffer, dummy); + m_lock.unlock(); + } +} + +/*! + Calls a remote method to inform that the client has finished + reading the current file. +*/ +void LibArchiveWrapperPrivate::setClientDataAtEnd() +{ + if (connectToServer()) { + m_lock.lockForWrite(); + callRemoteMethod(QLatin1String(Protocol::AbstractArchiveSetClientDataAtEnd)); + m_lock.unlock(); + } +} + +/*! + Calls a remote method to retrieve and return the status of + the extract worker on a server process. +*/ +ExtractWorker::Status LibArchiveWrapperPrivate::workerStatus() const +{ + ExtractWorker::Status status = ExtractWorker::Unfinished; + if ((const_cast<LibArchiveWrapperPrivate *>(this))->connectToServer()) { + m_lock.lockForWrite(); + status = static_cast<ExtractWorker::Status>( + callRemoteMethod<qint32>(QLatin1String(Protocol::AbstractArchiveWorkerStatus))); + m_lock.unlock(); + } + return status; +} + +} // namespace QInstaller diff --git a/src/libs/installer/libarchivewrapper_p.h b/src/libs/installer/libarchivewrapper_p.h new file mode 100644 index 000000000..ea8409da0 --- /dev/null +++ b/src/libs/installer/libarchivewrapper_p.h @@ -0,0 +1,94 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#ifndef LIBARCHIVEWRAPPER_P_H +#define LIBARCHIVEWRAPPER_P_H + +#include "installer_global.h" +#include "remoteobject.h" +#include "libarchivearchive.h" +#include "lib7zarchive.h" + +#include <QTimer> +#include <QReadWriteLock> + +namespace QInstaller { + +class INSTALLER_EXPORT LibArchiveWrapperPrivate : public RemoteObject +{ + Q_OBJECT + Q_DISABLE_COPY(LibArchiveWrapperPrivate) + +public: + explicit LibArchiveWrapperPrivate(const QString &filename); + LibArchiveWrapperPrivate(); + ~LibArchiveWrapperPrivate(); + + bool open(QIODevice::OpenMode mode); + void close(); + void setFilename(const QString &filename); + + QString errorString() const; + + bool extract(const QString &dirPath, const quint64 totalFiles = 0); + bool create(const QStringList &data); + QVector<ArchiveEntry> list(); + bool isSupported(); + + void setCompressionLevel(const AbstractArchive::CompressionLevel level); + +Q_SIGNALS: + void currentEntryChanged(const QString &filename); + void completedChanged(const quint64 completed, const quint64 total); + void dataBlockRequested(); + void remoteWorkerFinished(); + +public Q_SLOTS: + void cancel(); + +private Q_SLOTS: + void processSignals(); + void onDataBlockRequested(); + +private: + void init(); + + void addDataBlock(const QByteArray &buffer); + void setClientDataAtEnd(); + ExtractWorker::Status workerStatus() const; + +private: + QTimer m_timer; + mutable QReadWriteLock m_lock; + + LibArchiveArchive m_archive; +}; + +} // namespace QInstaller + +#endif // LIBARCHIVEWRAPPER_P_H diff --git a/src/libs/installer/metadatajob_p.h b/src/libs/installer/metadatajob_p.h index 9160f4cc9..99b48e626 100644 --- a/src/libs/installer/metadatajob_p.h +++ b/src/libs/installer/metadatajob_p.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -29,8 +29,7 @@ #ifndef METADATAJOB_P_H #define METADATAJOB_P_H -#include "lib7z_extract.h" -#include "lib7z_facade.h" +#include "lib7zarchive.h" #include "metadatajob.h" #include <QDir> @@ -76,21 +75,15 @@ public: return; // ignore already canceled } - QFile archive(m_archive); - if (archive.open(QIODevice::ReadOnly)) { - try { - Lib7z::extractArchive(&archive, m_targetDir); - } catch (const Lib7z::SevenZipException& e) { - fi.reportException(UnzipArchiveException(MetadataJob::tr("Error while extracting " - "archive \"%1\": %2").arg(QDir::toNativeSeparators(m_archive), e.message()))); - } catch (...) { - fi.reportException(UnzipArchiveException(MetadataJob::tr("Unknown exception " - "caught while extracting archive \"%1\".").arg(QDir::toNativeSeparators(m_archive)))); - } - } else { + Lib7zArchive archive(m_archive); + if (!archive.open(QIODevice::ReadOnly)) { fi.reportException(UnzipArchiveException(MetadataJob::tr("Cannot open file \"%1\" for " "reading: %2").arg(QDir::toNativeSeparators(m_archive), archive.errorString()))); } + if (!archive.extract(m_targetDir)) { + fi.reportException(UnzipArchiveException(MetadataJob::tr("Error while extracting " + "archive \"%1\": %2").arg(QDir::toNativeSeparators(m_archive), archive.errorString()))); + } fi.reportFinished(); } diff --git a/src/libs/installer/protocol.h b/src/libs/installer/protocol.h index 9ead7943c..c7eb9a308 100644 --- a/src/libs/installer/protocol.h +++ b/src/libs/installer/protocol.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -163,6 +163,29 @@ const char QAbstractFileEngineSyncToDisk[] = "QAbstractFileEngine::syncToDisk"; const char QAbstractFileEngineRenameOverwrite[] = "QAbstractFileEngine::renameOverwrite"; const char QAbstractFileEngineFileTime[] = "QAbstractFileEngine::fileTime"; + +// LibArchiveWrapper +const char AbstractArchive[] = "AbstractArchive"; +const char AbstractArchiveOpen[] = "AbstractArchive::open"; +const char AbstractArchiveClose[] = "AbstractArchive::close"; +const char AbstractArchiveSetFilename[] = "AbstractArchive::setFilename"; +const char AbstractArchiveErrorString[] = "AbstractArchive::errorString"; +const char AbstractArchiveExtract[] = "AbstractArchive::extract"; +const char AbstractArchiveCreate[] = "AbstractArchive::create"; +const char AbstractArchiveList[] = "AbstractArchive::list"; +const char AbstractArchiveIsSupported[] = "AbstractArchive::isSupported"; +const char AbstractArchiveSetCompressionLevel[] = "AbstractArchive::setCompressionLevel"; +const char AbstractArchiveAddDataBlock[] = "AbstractArchive::addDataBlock"; +const char AbstractArchiveSetClientDataAtEnd[] = "AbstractArchive::setClientDataAtEnd"; +const char AbstractArchiveWorkerStatus[] = "AbstractArchive::workerStatus"; +const char AbstractArchiveCancel[] = "AbstractArchive::cancel"; + +const char GetAbstractArchiveSignals[] = "GetAbstractArchiveSignals"; +const char AbstractArchiveSignalCurrentEntryChanged[] = "AbstractArchive::currentEntryChanged"; +const char AbstractArchiveSignalCompletedChanged[] = "AbstractArchive::completedChanged"; +const char AbstractArchiveSignalDataBlockRequested[] = "AbstractArchive::dataBlockRequested"; +const char AbstractArchiveSignalWorkerFinished[] = "AbstractArchive::workerFinished"; + } // namespace Protocol void INSTALLER_EXPORT sendPacket(QIODevice *device, const QByteArray &command, const QByteArray &data); diff --git a/src/libs/installer/remoteserverconnection.cpp b/src/libs/installer/remoteserverconnection.cpp index a6b66c081..5d72f834d 100644 --- a/src/libs/installer/remoteserverconnection.cpp +++ b/src/libs/installer/remoteserverconnection.cpp @@ -34,6 +34,9 @@ #include "utils.h" #include "permissionsettings.h" #include "globals.h" +#ifdef IFW_LIBARCHIVE +#include "libarchivearchive.h" +#endif #include <QCoreApplication> #include <QDataStream> @@ -59,8 +62,10 @@ RemoteServerConnection::RemoteServerConnection(qintptr socketDescriptor, const Q , m_socketDescriptor(socketDescriptor) , m_process(nullptr) , m_engine(nullptr) + , m_archive(nullptr) , m_authorizationKey(key) - , m_signalReceiver(nullptr) + , m_processSignalReceiver(nullptr) + , m_archiveSignalReceiver(nullptr) { setObjectName(QString::fromLatin1("RemoteServerConnection(%1)").arg(socketDescriptor)); } @@ -89,6 +94,7 @@ void RemoteServerConnection::run() if (!receivePacket(&socket, &cmd, &data)) { socket.waitForReadyRead(250); + qApp->processEvents(); continue; } @@ -142,11 +148,20 @@ void RemoteServerConnection::run() if (m_process) m_process->deleteLater(); m_process = new QProcess; - m_signalReceiver = new QProcessSignalReceiver(m_process); + m_processSignalReceiver = new QProcessSignalReceiver(m_process); } else if (type == QLatin1String(Protocol::QAbstractFileEngine)) { if (m_engine) delete m_engine; m_engine = new QFSFileEngine; + } else if (type == QLatin1String(Protocol::AbstractArchive)) { +#ifdef IFW_LIBARCHIVE + if (m_archive) + m_archive->deleteLater(); + m_archive = new LibArchiveArchive; + m_archiveSignalReceiver = new AbstractArchiveSignalReceiver(static_cast<LibArchiveArchive *>(m_archive)); +#else + Q_ASSERT_X(false, Q_FUNC_INFO, "No compatible archive handler exists for protocol."); +#endif } continue; } @@ -157,24 +172,44 @@ void RemoteServerConnection::run() if (type == QLatin1String(Protocol::QSettings)) { settings.reset(); } else if (type == QLatin1String(Protocol::QProcess)) { - m_signalReceiver->m_receivedSignals.clear(); + m_processSignalReceiver->m_receivedSignals.clear(); m_process->deleteLater(); m_process = nullptr; } else if (type == QLatin1String(Protocol::QAbstractFileEngine)) { delete m_engine; m_engine = nullptr; + } else if (type == QLatin1String(Protocol::AbstractArchive)) { +#ifdef IFW_LIBARCHIVE + m_archiveSignalReceiver->m_receivedSignals.clear(); + m_archive->deleteLater(); + m_archive = nullptr; +#else + Q_ASSERT_X(false, Q_FUNC_INFO, "No compatible archive handler exists for protocol."); +#endif } return; } if (command == QLatin1String(Protocol::GetQProcessSignals)) { - if (m_signalReceiver) { - QMutexLocker _(&m_signalReceiver->m_lock); - sendData(&socket, m_signalReceiver->m_receivedSignals); + if (m_processSignalReceiver) { + QMutexLocker _(&m_processSignalReceiver->m_lock); + sendData(&socket, m_processSignalReceiver->m_receivedSignals); socket.flush(); - m_signalReceiver->m_receivedSignals.clear(); + m_processSignalReceiver->m_receivedSignals.clear(); } continue; + } else if (command == QLatin1String(Protocol::GetAbstractArchiveSignals)) { +#ifdef IFW_LIBARCHIVE + if (m_archiveSignalReceiver) { + QMutexLocker _(&m_archiveSignalReceiver->m_lock); + sendData(&socket, m_archiveSignalReceiver->m_receivedSignals); + socket.flush(); + m_archiveSignalReceiver->m_receivedSignals.clear(); + } + continue; +#else + Q_ASSERT_X(false, Q_FUNC_INFO, "No compatible archive handler exists for protocol."); +#endif } if (command.startsWith(QLatin1String(Protocol::QProcess))) { @@ -183,6 +218,8 @@ void RemoteServerConnection::run() handleQSettings(&socket, command, stream, settings.data()); } else if (command.startsWith(QLatin1String(Protocol::QAbstractFileEngine))) { handleQFSFileEngine(&socket, command, stream); + } else if (command.startsWith(QLatin1String(Protocol::AbstractArchive))) { + handleArchive(&socket, command, stream); } else { qCDebug(QInstaller::lcServer) << "Unknown command:" << command; } @@ -519,4 +556,56 @@ void RemoteServerConnection::handleQFSFileEngine(QIODevice *socket, const QStrin } } +void RemoteServerConnection::handleArchive(QIODevice *socket, const QString &command, QDataStream &data) +{ +#ifdef IFW_LIBARCHIVE + LibArchiveArchive *archive = static_cast<LibArchiveArchive *>(m_archive); + if (command == QLatin1String(Protocol::AbstractArchiveOpen)) { + qint32 openMode; + data >> openMode; + sendData(socket, archive->open(static_cast<QIODevice::OpenMode>(openMode))); + } else if (command == QLatin1String(Protocol::AbstractArchiveClose)) { + archive->close(); + } else if (command == QLatin1String(Protocol::AbstractArchiveSetFilename)) { + QString fileName; + data >> fileName; + archive->setFilename(fileName); + } else if (command == QLatin1String(Protocol::AbstractArchiveErrorString)) { + sendData(socket, archive->errorString()); + } else if (command == QLatin1String(Protocol::AbstractArchiveExtract)) { + QString dirPath; + quint64 total; + data >> dirPath; + data >> total; + archive->workerExtract(dirPath, total); + } else if (command == QLatin1String(Protocol::AbstractArchiveCreate)) { + QStringList entries; + data >> entries; + sendData(socket, archive->create(entries)); + } else if (command == QLatin1String(Protocol::AbstractArchiveList)) { + sendData(socket, archive->list()); + } else if (command == QLatin1String(Protocol::AbstractArchiveIsSupported)) { + sendData(socket, archive->isSupported()); + } else if (command == QLatin1String(Protocol::AbstractArchiveSetCompressionLevel)) { + qint32 level; + data >> level; + archive->setCompressionLevel(static_cast<AbstractArchive::CompressionLevel>(level)); + } else if (command == QLatin1String(Protocol::AbstractArchiveAddDataBlock)) { + QByteArray buff; + data >> buff; + archive->workerAddDataBlock(buff); + } else if (command == QLatin1String(Protocol::AbstractArchiveSetClientDataAtEnd)) { + archive->workerSetDataAtEnd(); + } else if (command == QLatin1String(Protocol::AbstractArchiveWorkerStatus)) { + sendData(socket, static_cast<qint32>(archive->workerStatus())); + } else if (command == QLatin1String(Protocol::AbstractArchiveCancel)) { + archive->workerCancel(); + } else if (!command.isEmpty()) { + qCDebug(QInstaller::lcServer) << "Unknown AbstractArchive command:" << command; + } +#else + Q_ASSERT_X(false, Q_FUNC_INFO, "No compatible archive handler exists for protocol."); +#endif +} + } // namespace QInstaller diff --git a/src/libs/installer/remoteserverconnection.h b/src/libs/installer/remoteserverconnection.h index 07cfb98c0..ccb8e153d 100644 --- a/src/libs/installer/remoteserverconnection.h +++ b/src/libs/installer/remoteserverconnection.h @@ -29,6 +29,8 @@ #ifndef REMOTESERVERCONNECTION_H #define REMOTESERVERCONNECTION_H +#include "abstractarchive.h" + #include <QPointer> #include <QThread> @@ -44,6 +46,7 @@ namespace QInstaller { class PermissionSettings; class QProcessSignalReceiver; +class AbstractArchiveSignalReceiver; class RemoteServerConnection : public QThread { @@ -66,14 +69,17 @@ private: void handleQSettings(QIODevice *device, const QString &command, QDataStream &data, PermissionSettings *settings); void handleQFSFileEngine(QIODevice *device, const QString &command, QDataStream &data); + void handleArchive(QIODevice *device, const QString &command, QDataStream &data); private: qintptr m_socketDescriptor; QProcess *m_process; QFSFileEngine *m_engine; + AbstractArchive *m_archive; QString m_authorizationKey; - QProcessSignalReceiver *m_signalReceiver; + QProcessSignalReceiver *m_processSignalReceiver; + AbstractArchiveSignalReceiver *m_archiveSignalReceiver; }; } // namespace QInstaller diff --git a/src/libs/installer/remoteserverconnection_p.h b/src/libs/installer/remoteserverconnection_p.h index dad5f4133..dc6d794b6 100644 --- a/src/libs/installer/remoteserverconnection_p.h +++ b/src/libs/installer/remoteserverconnection_p.h @@ -30,6 +30,9 @@ #define REMOTESERVERCONNECTION_P_H #include "protocol.h" +#ifdef IFW_LIBARCHIVE +#include "libarchivearchive.h" +#endif #include <QMutex> #include <QProcess> @@ -124,6 +127,60 @@ private: QVariantList m_receivedSignals; }; +#ifdef IFW_LIBARCHIVE +class AbstractArchiveSignalReceiver : public QObject +{ + Q_OBJECT + friend class RemoteServerConnection; + +private: + explicit AbstractArchiveSignalReceiver(LibArchiveArchive *archive) + : QObject(archive) + { + connect(archive, &LibArchiveArchive::currentEntryChanged, + this, &AbstractArchiveSignalReceiver::onCurrentEntryChanged); + connect(archive, &LibArchiveArchive::completedChanged, + this, &AbstractArchiveSignalReceiver::onCompletedChanged); + connect(archive, &LibArchiveArchive::dataBlockRequested, + this, &AbstractArchiveSignalReceiver::onDataBlockRequested); + connect(archive, &LibArchiveArchive::workerFinished, + this, &AbstractArchiveSignalReceiver::onWorkerFinished); + } + +private Q_SLOTS: + void onCurrentEntryChanged(const QString &filename) + { + QMutexLocker _(&m_lock); + m_receivedSignals.append(QLatin1String(Protocol::AbstractArchiveSignalCurrentEntryChanged)); + m_receivedSignals.append(filename); + } + + void onCompletedChanged(quint64 completed, quint64 total) + { + QMutexLocker _(&m_lock); + m_receivedSignals.append(QLatin1String(Protocol::AbstractArchiveSignalCompletedChanged)); + m_receivedSignals.append(completed); + m_receivedSignals.append(total); + } + + void onDataBlockRequested() + { + QMutexLocker _(&m_lock); + m_receivedSignals.append(QLatin1String(Protocol::AbstractArchiveSignalDataBlockRequested)); + } + + void onWorkerFinished() + { + QMutexLocker _(&m_lock); + m_receivedSignals.append(QLatin1String(Protocol::AbstractArchiveSignalWorkerFinished)); + } + +private: + QMutex m_lock; + QVariantList m_receivedSignals; +}; +#endif + } // namespace QInstaller #endif // REMOTESERVERCONNECTION_P_H diff --git a/tests/auto/installer/extractarchiveoperationtest/tst_extractarchiveoperationtest.cpp b/tests/auto/installer/extractarchiveoperationtest/tst_extractarchiveoperationtest.cpp index baaf58da2..cbbc1a1c7 100644 --- a/tests/auto/installer/extractarchiveoperationtest/tst_extractarchiveoperationtest.cpp +++ b/tests/auto/installer/extractarchiveoperationtest/tst_extractarchiveoperationtest.cpp @@ -84,8 +84,7 @@ private slots: QVERIFY(op.undoOperation()); QCOMPARE(UpdateOperation::Error(op.error()), UpdateOperation::UserDefinedError); - QCOMPARE(op.errorString(), QString("Error while extracting archive \":///data/invalid.7z\": " - "Cannot open archive \":///data/invalid.7z\".")); + QCOMPARE(op.errorString(), QString("Cannot open archive \":///data/invalid.7z\" for reading: ")); } void testExtractArchiveFromXML() diff --git a/tests/auto/installer/lib7zfacade/tst_lib7zfacade.cpp b/tests/auto/installer/lib7zfacade/tst_lib7zfacade.cpp index ffc5b330a..4175bdf40 100644 --- a/tests/auto/installer/lib7zfacade/tst_lib7zfacade.cpp +++ b/tests/auto/installer/lib7zfacade/tst_lib7zfacade.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -46,7 +46,7 @@ private slots: Lib7z::initSevenZ(); m_file.path = "valid"; - m_file.permissions = 0; + m_file.permissions_enum = 0; m_file.compressedSize = 836; m_file.uncompressedSize = 5242880; m_file.isDirectory = false; diff --git a/tests/auto/tools/repotest/tst_repotest.cpp b/tests/auto/tools/repotest/tst_repotest.cpp index 02594f400..9b8df947a 100644 --- a/tests/auto/tools/repotest/tst_repotest.cpp +++ b/tests/auto/tools/repotest/tst_repotest.cpp @@ -30,6 +30,8 @@ #include <repositorygen.h> #include <repositorygen.cpp> #include <init.h> +#include <lib7z_facade.h> +#include <lib7zarchive.h> #include <QFile> #include <QTest> @@ -63,7 +65,7 @@ private: tmp.setAutoRemove(false); const QString tmpMetaDir = tmp.path(); QInstallerTools::createRepository(m_repoInfo, &m_packages, tmpMetaDir, createSplitMetadata, - createUnifiedMetadata); + createUnifiedMetadata, QLatin1String("7z")); QInstaller::removeDirectory(tmpMetaDir, true); } @@ -95,12 +97,12 @@ private: QString existingUniteMeta7z = QInstallerTools::existingUniteMeta7z(m_repoInfo.repositoryDir); QCOMPARE(2, matches.count()); QCOMPARE(existingUniteMeta7z, matches.at(1)); - QFile file(m_repoInfo.repositoryDir + QDir::separator() + matches.at(1)); + Lib7zArchive file(m_repoInfo.repositoryDir + QDir::separator() + matches.at(1)); QVERIFY(file.open(QIODevice::ReadOnly)); //We have script<version>.qs for package A in the unite metadata - QVector<Lib7z::File>::const_iterator fileIt; - const QVector<Lib7z::File> files = Lib7z::listArchive(&file); + QVector<ArchiveEntry>::const_iterator fileIt; + const QVector<ArchiveEntry> files = file.list(); for (fileIt = files.begin(); fileIt != files.end(); ++fileIt) { if (fileIt->isDirectory) continue; diff --git a/tools/archivegen/archive.cpp b/tools/archivegen/archive.cpp index e1a40a880..56aae1b21 100644 --- a/tools/archivegen/archive.cpp +++ b/tools/archivegen/archive.cpp @@ -27,102 +27,19 @@ **************************************************************************/ #include <errors.h> -#include <lib7z_create.h> +#include <archivefactory.h> #include <lib7z_facade.h> #include <utils.h> #include <QCoreApplication> #include <QCommandLineParser> #include <QDir> +#include <QMetaEnum> #include <iostream> using namespace QInstaller; -class FailOnErrorCallback : public Lib7z::UpdateCallback -{ - HRESULT OpenFileError(const wchar_t*, DWORD) Q_DECL_OVERRIDE { - return S_FALSE; - } - - HRESULT CanNotFindError(const wchar_t*, DWORD) Q_DECL_OVERRIDE { - return S_FALSE; - } - - HRESULT OpenResult(const wchar_t*, HRESULT result, const wchar_t*) Q_DECL_OVERRIDE { - return result; - } -}; - -class VerbosePrinterCallback : public Lib7z::UpdateCallback -{ -public: - ~VerbosePrinterCallback() { - m_PercentPrinter.ClosePrint(); - } - -private: - HRESULT SetTotal(UInt64 size) Q_DECL_OVERRIDE { - m_PercentPrinter.SetTotal(size); - return S_OK; - } - - HRESULT SetCompleted(const UInt64 *size) Q_DECL_OVERRIDE { - if (size) { - m_PercentPrinter.SetRatio(*size); - m_PercentPrinter.PrintRatio(); - } - return S_OK; - } - - HRESULT OpenResult(const wchar_t *file, HRESULT result, const wchar_t*) Q_DECL_OVERRIDE { - if (result != S_OK) { - printBlock(QCoreApplication::translate("archivegen", "Cannot update file \"%1\". " - "Unsupported archive.").arg(QDir::toNativeSeparators(QString::fromWCharArray(file))), Q_NULLPTR); - } - return result; - } - - HRESULT OpenFileError(const wchar_t *file, DWORD) Q_DECL_OVERRIDE { - printBlock(QCoreApplication::translate("archivegen", "Cannot open file "), file); - return S_FALSE; - } - - HRESULT CanNotFindError(const wchar_t *file, DWORD) Q_DECL_OVERRIDE { - printBlock(QCoreApplication::translate("archivegen", "Cannot find file "), file); - return S_FALSE; - } - - HRESULT StartArchive(const wchar_t *name, bool) Q_DECL_OVERRIDE { - printLine(QCoreApplication::translate("archivegen", "Create archive.")); - if (name) { - m_PercentPrinter.PrintNewLine(); - m_PercentPrinter.PrintString(name); - } - return S_OK; - } - - HRESULT FinishArchive() Q_DECL_OVERRIDE { - m_PercentPrinter.PrintNewLine(); - printLine(QCoreApplication::translate("archivegen", "Finished archive.")); - return S_OK; - } - - void printLine(const QString &message) { - m_PercentPrinter.PrintString(message.toStdWString().c_str()); - } - - void printBlock(const QString &message, const wchar_t *message2) { - m_PercentPrinter.PrintNewLine(); - m_PercentPrinter.PrintString(message.toStdWString().c_str()); - if (message2) - m_PercentPrinter.PrintString(message2); - m_PercentPrinter.PrintNewLine(); - } - - Lib7z::PercentPrinter m_PercentPrinter; -}; - int main(int argc, char *argv[]) { try { @@ -132,12 +49,16 @@ int main(int argc, char *argv[]) QCoreApplication::setApplicationVersion(QLatin1String(QUOTE(IFW_VERSION_STR))); #undef QUOTE #undef QUOTE_ - + const QString archiveFormats = ArchiveFactory::supportedTypes().join(QLatin1Char('|')); QCommandLineParser parser; const QCommandLineOption help = parser.addHelpOption(); const QCommandLineOption version = parser.addVersionOption(); - QCommandLineOption verbose(QLatin1String("verbose"), - QCoreApplication::translate("archivegen", "Verbose mode. Prints out more information.")); + const QCommandLineOption format = QCommandLineOption(QStringList() + << QLatin1String("f") << QLatin1String("format"), + QCoreApplication::translate("archivegen", + "%1\n" + "Format for the archive. Defaults to 7z." + ).arg(archiveFormats), QLatin1String("format"), QLatin1String("7z")); const QCommandLineOption compression = QCommandLineOption(QStringList() << QLatin1String("c") << QLatin1String("compression"), QCoreApplication::translate("archivegen", @@ -147,10 +68,12 @@ int main(int argc, char *argv[]) "5 (Normal compressing)\n" "7 (Maximum compressing)\n" "9 (Ultra compressing)\n" - "Defaults to 5 (Normal compression)." + "Defaults to 5 (Normal compression).\n" + "Note: some formats do not support all the possible values, " + "for example bzip2 compression only supports values from 1 to 9." ), QLatin1String("5"), QLatin1String("5")); - parser.addOption(verbose); + parser.addOption(format); parser.addOption(compression); parser.addPositionalArgument(QLatin1String("archive"), QCoreApplication::translate("archivegen", "Compressed archive to create.")); @@ -176,22 +99,29 @@ int main(int argc, char *argv[]) } bool ok = false; - const int values[6] = { 0, 1, 3, 5, 7, 9 }; + QMetaEnum levels = QMetaEnum::fromType<AbstractArchive::CompressionLevel>(); const int value = parser.value(compression).toInt(&ok); - if (!ok || (std::find(std::begin(values), std::end(values), value) == std::end(values))) { + if (!ok || !levels.valueToKey(value)) { throw QInstaller::Error(QCoreApplication::translate("archivegen", "Unknown compression level \"%1\". See 'archivgen --help'.").arg(value)); } Lib7z::initSevenZ(); - Lib7z::createArchive(args[0], args.mid(1), Lib7z::TmpFile::No, Lib7z::Compression(value), - [&] () -> Lib7z::UpdateCallback * { - if (parser.isSet(verbose)) - return new VerbosePrinterCallback; - return new FailOnErrorCallback; - } () - ); - return EXIT_SUCCESS; + QString archiveFilename = args[0]; + // Check if filename already has a supported suffix + if (!ArchiveFactory::isSupportedType(archiveFilename)) + archiveFilename += QLatin1Char('.') + parser.value(format); + + QScopedPointer<AbstractArchive> archive(ArchiveFactory::instance().create(archiveFilename)); + if (!archive) { + throw QInstaller::Error(QString::fromLatin1("Could not create handler " + "object for archive \"%1\": \"%2\".").arg(archiveFilename, QLatin1String(Q_FUNC_INFO))); + } + archive->setCompressionLevel(AbstractArchive::CompressionLevel(value)); + if (archive->open(QIODevice::WriteOnly) && archive->create(args.mid(1))) + return EXIT_SUCCESS; + + std::cerr << archive->errorString() << std::endl; } catch (const QInstaller::Error &e) { std::cerr << e.message() << std::endl; } catch (...) { diff --git a/tools/devtool/binaryreplace.cpp b/tools/devtool/binaryreplace.cpp index 97dac37d2..a959bd7dc 100644 --- a/tools/devtool/binaryreplace.cpp +++ b/tools/devtool/binaryreplace.cpp @@ -34,9 +34,7 @@ #include <errors.h> #include <fileio.h> #include <fileutils.h> -#include <lib7z_extract.h> -#include <lib7z_facade.h> -#include <lib7z_list.h> +#include <archivefactory.h> #include <QDir> #include <QFutureWatcher> @@ -68,29 +66,22 @@ int BinaryReplace::replace(const QString &source, const QString &target) return result; QString newInstallerBasePath = future.result().target(); - if (Lib7z::isSupportedArchive(newInstallerBasePath)) { - QFile archive(newInstallerBasePath); - if (archive.open(QIODevice::ReadOnly)) { - try { - Lib7z::extractArchive(&archive, QDir::tempPath()); - const QVector<Lib7z::File> files = Lib7z::listArchive(&archive); - newInstallerBasePath = QDir::tempPath() + QLatin1Char('/') + files.value(0) - .path; - result = EXIT_SUCCESS; - } catch (const Lib7z::SevenZipException& e) { - std::cerr << qPrintable(QString::fromLatin1("Error while extracting \"%1\": %2") - .arg(QDir::toNativeSeparators(newInstallerBasePath), e.message())) << std::endl; - } catch (...) { - std::cerr << qPrintable(QString::fromLatin1("Unknown exception caught while " - "extracting \"%1\".").arg(QDir::toNativeSeparators(newInstallerBasePath))) << std::endl; - } + QScopedPointer<QInstaller::AbstractArchive> archive( + QInstaller::ArchiveFactory::instance().create(newInstallerBasePath)); + + if (archive && archive->open(QIODevice::ReadOnly) && archive->isSupported()) { + if (archive->extract(QDir::tempPath())) { + const QVector<QInstaller::ArchiveEntry> files = archive->list(); + newInstallerBasePath = QDir::tempPath() + QLatin1Char('/') + files.value(0).path; + result = EXIT_SUCCESS; } else { - std::cerr << qPrintable(QString::fromLatin1("Cannot open \"%1\" for reading: %2") - .arg(QDir::toNativeSeparators(newInstallerBasePath), archive.errorString())) << std::endl; + std::cerr << qPrintable(QString::fromLatin1("Error while extracting \"%1\": %2") + .arg(QDir::toNativeSeparators(newInstallerBasePath), archive->errorString())) << std::endl; } - if (!archive.remove()) { - std::cerr << qPrintable(QString::fromLatin1("Cannot delete file \"%1\": %2") - .arg(QDir::toNativeSeparators(newInstallerBasePath), archive.errorString())) << std::endl; + + if (!QFile::remove(newInstallerBasePath)) { + std::cerr << qPrintable(QString::fromLatin1("Cannot delete file \"%1\"") + .arg(QDir::toNativeSeparators(newInstallerBasePath))) << std::endl; } if (result != EXIT_SUCCESS) return result; diff --git a/tools/repogen/repogen.cpp b/tools/repogen/repogen.cpp index b630f8cd3..a709899c6 100644 --- a/tools/repogen/repogen.cpp +++ b/tools/repogen/repogen.cpp @@ -34,7 +34,7 @@ #include <settings.h> #include <utils.h> #include <loggingutils.h> -#include <lib7z_facade.h> +#include <archivefactory.h> #include <QDomDocument> #include <QtCore/QDir> @@ -50,6 +50,8 @@ using namespace QInstaller; static void printUsage() { const QString appName = QFileInfo(QCoreApplication::applicationFilePath()).fileName(); + const QString archiveFormats = ArchiveFactory::supportedTypes().join(QLatin1Char('|')); + std::cout << "Usage: " << appName << " [options] repository-dir" << std::endl; std::cout << std::endl; std::cout << "Options:" << std::endl; @@ -71,6 +73,9 @@ static void printUsage() std::cout << " download phase." << std::endl; std::cout << " --component-metadata Creates one metadata 7z per component. " << std::endl; + std::cout << " --af|--archive-format " << archiveFormats << std::endl; + std::cout << " Set the format used when packaging new component data archives. If" << std::endl; + std::cout << " you omit this option the 7z format will be used as a default." << std::endl; std::cout << std::endl; std::cout << "Example:" << std::endl; @@ -105,6 +110,7 @@ int main(int argc, char** argv) bool updateExistingRepositoryWithNewComponents = false; bool createUnifiedMetadata = true; bool createComponentMetadata = true; + QString archiveSuffix = QLatin1String("7z"); //TODO: use a for loop without removing values from args like it is in binarycreator.cpp //for (QStringList::const_iterator it = args.begin(); it != args.end(); ++it) { @@ -197,6 +203,15 @@ int main(int argc, char** argv) args.removeFirst(); packagesUpdatedWithSha = args.first().split(QLatin1Char(',')); args.removeFirst(); + } else if (args.first() == QLatin1String("--af") || args.first() == QLatin1String("--archive-format")) { + args.removeFirst(); + if (args.isEmpty()) { + return printErrorAndUsageAndExit(QCoreApplication::translate("QInstaller", + "Error: Archive format parameter missing argument")); + } + // TODO: do we need early check for supported formats? + archiveSuffix = args.first(); + args.removeFirst(); } else { printUsage(); return 1; @@ -248,11 +263,9 @@ int main(int argc, char** argv) tmp.setAutoRemove(false); tmpMetaDir = tmp.path(); QInstallerTools::createRepository(repoInfo, &packages, tmpMetaDir, - createComponentMetadata, createUnifiedMetadata); + createComponentMetadata, createUnifiedMetadata, archiveSuffix); exitCode = EXIT_SUCCESS; - } catch (const Lib7z::SevenZipException &e) { - std::cerr << "Caught 7zip exception: " << e.message() << std::endl; } catch (const QInstaller::Error &e) { std::cerr << "Caught exception: " << e.message() << std::endl; } catch (...) { |