summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/installerfw.qdoc65
-rw-r--r--src/libs/ifwtools/binarycreator.cpp4
-rw-r--r--src/libs/ifwtools/repositorygen.cpp92
-rw-r--r--src/libs/ifwtools/repositorygen.h8
-rw-r--r--src/libs/installer/abstractarchive.cpp221
-rw-r--r--src/libs/installer/abstractarchive.h120
-rw-r--r--src/libs/installer/archivefactory.cpp147
-rw-r--r--src/libs/installer/archivefactory.h69
-rw-r--r--src/libs/installer/component.cpp5
-rw-r--r--src/libs/installer/createlocalrepositoryoperation.cpp17
-rw-r--r--src/libs/installer/directoryguard.cpp112
-rw-r--r--src/libs/installer/directoryguard.h55
-rw-r--r--src/libs/installer/extractarchiveoperation.cpp29
-rw-r--r--src/libs/installer/extractarchiveoperation.h4
-rw-r--r--src/libs/installer/extractarchiveoperation_p.h136
-rw-r--r--src/libs/installer/installer.pro23
-rw-r--r--src/libs/installer/lib7z_create.h12
-rw-r--r--src/libs/installer/lib7z_facade.cpp107
-rw-r--r--src/libs/installer/lib7z_list.h17
-rw-r--r--src/libs/installer/lib7zarchive.cpp242
-rw-r--r--src/libs/installer/lib7zarchive.h95
-rw-r--r--src/libs/installer/libarchivearchive.cpp819
-rw-r--r--src/libs/installer/libarchivearchive.h189
-rw-r--r--src/libs/installer/libarchivewrapper.cpp204
-rw-r--r--src/libs/installer/libarchivewrapper.h71
-rw-r--r--src/libs/installer/libarchivewrapper_p.cpp356
-rw-r--r--src/libs/installer/libarchivewrapper_p.h94
-rw-r--r--src/libs/installer/metadatajob_p.h23
-rw-r--r--src/libs/installer/protocol.h25
-rw-r--r--src/libs/installer/remoteserverconnection.cpp103
-rw-r--r--src/libs/installer/remoteserverconnection.h8
-rw-r--r--src/libs/installer/remoteserverconnection_p.h57
-rw-r--r--tests/auto/installer/extractarchiveoperationtest/tst_extractarchiveoperationtest.cpp3
-rw-r--r--tests/auto/installer/lib7zfacade/tst_lib7zfacade.cpp4
-rw-r--r--tests/auto/tools/repotest/tst_repotest.cpp10
-rw-r--r--tools/archivegen/archive.cpp130
-rw-r--r--tools/devtool/binaryreplace.cpp39
-rw-r--r--tools/repogen/repogen.cpp21
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 (...) {