diff options
Diffstat (limited to 'src')
26 files changed, 856 insertions, 185 deletions
diff --git a/src/libs/installer/abstractarchive.h b/src/libs/installer/abstractarchive.h index a41d7e795..86b4b4ffa 100644 --- a/src/libs/installer/abstractarchive.h +++ b/src/libs/installer/abstractarchive.h @@ -52,6 +52,8 @@ struct INSTALLER_EXPORT ArchiveEntry , permissions_mode(0) {} + ArchiveEntry(const ArchiveEntry &) = default; + QString path; QDateTime utcTime; QPoint archiveIndex; diff --git a/src/libs/installer/component.cpp b/src/libs/installer/component.cpp index d4750cee9..87ec3e4bf 100644 --- a/src/libs/installer/component.cpp +++ b/src/libs/installer/component.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -48,6 +48,7 @@ #include <QtCore/QRegularExpression> #include <QApplication> +#include <QtConcurrentFilter> #include <QtUiTools/QUiLoader> @@ -1053,8 +1054,10 @@ QStringList Component::stopProcessForUpdateRequests() const /*! Returns the operations needed to install this component. If autoCreateOperations() is \c true, createOperations() is called if no operations have been automatically created yet. + + The \a mask parameter filters the returned operations by their group. */ -OperationList Component::operations() const +OperationList Component::operations(const Operation::OperationGroups &mask) const { if (d->m_autoCreateOperations && !d->m_operationsCreated) { const_cast<Component*>(this)->createOperations(); @@ -1081,7 +1084,11 @@ OperationList Component::operations() const d->m_operations.append(d->m_licenseOperation); } } - return d->m_operations; + OperationList operations = d->m_operations; + QtConcurrent::blockingFilter(operations, [&](const Operation *op) { + return mask.testFlag(op->group()); + }); + return operations; } /*! diff --git a/src/libs/installer/component.h b/src/libs/installer/component.h index 6d2784616..203856c0e 100644 --- a/src/libs/installer/component.h +++ b/src/libs/installer/component.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -139,7 +139,7 @@ public: QList<QPair<QString, bool> > pathsForUninstallation() const; Q_INVOKABLE void registerPathForUninstallation(const QString &path, bool wipe = false); - OperationList operations() const; + OperationList operations(const Operation::OperationGroups &mask = Operation::All) const; void addOperation(Operation *operation); Q_INVOKABLE bool addOperation(QQmlV4Function *args); diff --git a/src/libs/installer/concurrentoperationrunner.cpp b/src/libs/installer/concurrentoperationrunner.cpp new file mode 100644 index 000000000..c10f72784 --- /dev/null +++ b/src/libs/installer/concurrentoperationrunner.cpp @@ -0,0 +1,206 @@ +/************************************************************************** +** +** Copyright (C) 2022 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 "concurrentoperationrunner.h" + +#include "errors.h" + +#include <QtConcurrent> + +using namespace QInstaller; + +/*! + \inmodule QtInstallerFramework + \class QInstaller::ConcurrentOperationRunner + \brief The ConcurrentOperationRunner class can be used to perform installer + operations concurrently. + + The class accepts an operation list of any registered operation type. It can be + used to execute the \c Backup, \c Perform, or \c Undo steps of the operations. The + operations are run in the global thread pool, which by default limits the maximum + number of threads to the ideal number of logical processor cores in the system. +*/ + +/*! + \fn QInstaller::ConcurrentOperationRunner::finished() + + Emitted when the execution of all pooled operations is finished. +*/ + +/*! + Constructs an operation runner with \a parent as the parent object. +*/ +ConcurrentOperationRunner::ConcurrentOperationRunner(QObject *parent) + : QObject(parent) + , m_operations(nullptr) + , m_type(Operation::OperationType::Perform) +{ +} + +/*! + Constructs an operation runner with \a operations of type \a type to be performed, + and \a parent as the parent object. +*/ +ConcurrentOperationRunner::ConcurrentOperationRunner(OperationList *operations, + const Operation::OperationType type, QObject *parent) + : QObject(parent) + , m_operations(operations) + , m_type(type) +{ +} + +/*! + Destroys the instance and releases resources. +*/ +ConcurrentOperationRunner::~ConcurrentOperationRunner() +{ + qDeleteAll(m_operationWatchers); +} + +/*! + Sets the list of operations to be performed to \a operations. +*/ +void ConcurrentOperationRunner::setOperations(OperationList *operations) +{ + m_operations = operations; +} + +/*! + Sets \a type of operations to be performed. This can be either + \c Backup, \c Perform, or \c Undo. +*/ +void ConcurrentOperationRunner::setType(const Operation::OperationType type) +{ + m_type = type; +} + +/*! + \internal + + Runs \a operation in mode of \a type. Returns \c true on success, \c false otherwise. +*/ +static bool runOperation(Operation *const operation, const Operation::OperationType type) +{ + switch (type) { + case Operation::Backup: + operation->backup(); + return true; + case Operation::Perform: + return operation->performOperation(); + case Operation::Undo: + return operation->undoOperation(); + default: + Q_ASSERT(!"Unexpected operation type"); + } + return false; +} + +/*! + Performs the current operations. Returns a hash of pointers to the performed operation + objects and their results. The result is a boolean value. +*/ +QHash<Operation *, bool> ConcurrentOperationRunner::run() +{ + reset(); + + QEventLoop loop; + for (auto &operation : qAsConst(*m_operations)) { + auto futureWatcher = new QFutureWatcher<bool>(); + m_operationWatchers.insert(operation, futureWatcher); + + connect(futureWatcher, &QFutureWatcher<bool>::finished, + this, &ConcurrentOperationRunner::onOperationfinished); + + futureWatcher->setFuture(QtConcurrent::run(&runOperation, operation, m_type)); + } + + if (!m_operationWatchers.isEmpty()) { + connect(this, &ConcurrentOperationRunner::finished, &loop, &QEventLoop::quit); + loop.exec(); + } + + return m_results; +} + +/*! + Cancels operations pending for an asynchronous run. + + \note This does not stop already running operations, which + should provide a separate mechanism for canceling. +*/ +void ConcurrentOperationRunner::cancel() +{ + for (auto &watcher : m_operationWatchers) + watcher->cancel(); +} + +/*! + \internal + + Invoked when the execution of a single operation finishes. Adds the result + of the operation to the return hash of \c ConcurrentOperationRunner::run(). +*/ +void ConcurrentOperationRunner::onOperationfinished() +{ + auto watcher = static_cast<QFutureWatcher<bool> *>(sender()); + + Operation *op = m_operationWatchers.key(watcher); + if (!watcher->isCanceled()) { + try { + // Catch transferred exceptions + m_results.insert(op, watcher->future().result()); + } catch (const Error &e) { + qCritical() << "Caught exception:" << e.message(); + m_results.insert(op, false); + } catch (const QUnhandledException &) { + qCritical() << "Caught unhandled exception in:" << Q_FUNC_INFO; + m_results.insert(op, false); + } + } else { + // Remember also operations canceled before execution + m_results.insert(op, false); + } + + delete m_operationWatchers.take(op); + + // All finished + if (m_operationWatchers.isEmpty()) + emit finished(); +} + +/*! + \internal + + Clears previous results and deletes remaining operation watchers. +*/ +void ConcurrentOperationRunner::reset() +{ + qDeleteAll(m_operationWatchers); + m_operationWatchers.clear(); + m_results.clear(); +} diff --git a/src/libs/installer/concurrentoperationrunner.h b/src/libs/installer/concurrentoperationrunner.h new file mode 100644 index 000000000..29cd2adfb --- /dev/null +++ b/src/libs/installer/concurrentoperationrunner.h @@ -0,0 +1,78 @@ +/************************************************************************** +** +** Copyright (C) 2022 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 CONCURRENTOPERATIONRUNNER_H +#define CONCURRENTOPERATIONRUNNER_H + +#include "qinstallerglobal.h" + +#include <QObject> +#include <QHash> +#include <QFutureWatcher> + +namespace QInstaller { + +class INSTALLER_EXPORT ConcurrentOperationRunner : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(ConcurrentOperationRunner) + +public: + explicit ConcurrentOperationRunner(QObject *parent = nullptr); + explicit ConcurrentOperationRunner(OperationList *operations, + const Operation::OperationType type, QObject *parent = nullptr); + ~ConcurrentOperationRunner(); + + void setOperations(OperationList *operations); + void setType(const Operation::OperationType type); + + QHash<Operation *, bool> run(); + +signals: + void finished(); + +public slots: + void cancel(); + +private slots: + void onOperationfinished(); + +private: + void reset(); + +private: + QHash<Operation *, QFutureWatcher<bool> *> m_operationWatchers; + QHash<Operation *, bool> m_results; + + OperationList *m_operations; + Operation::OperationType m_type; +}; + +} // namespace QInstaller + +#endif // CONCURRENTOPERATIONRUNNER_H diff --git a/src/libs/installer/errors.h b/src/libs/installer/errors.h index 117eb5119..e8f2ee914 100644 --- a/src/libs/installer/errors.h +++ b/src/libs/installer/errors.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -31,23 +31,21 @@ #include <QtCore/QDebug> #include <QtCore/QString> - -#include <stdexcept> +#include <QtCore/QException> namespace QInstaller { -class Error : public std::exception +class Error : public QException { public: - Error() - {} + Error() = default; explicit Error(const QString &message) : m_message(message) {} - virtual ~Error() throw() - {} + void raise() const override { throw *this; } + Error *clone() const override { return new Error(*this); } QString message() const { return m_message; } diff --git a/src/libs/installer/extractarchiveoperation.cpp b/src/libs/installer/extractarchiveoperation.cpp index 19db53472..17dfc7dfb 100644 --- a/src/libs/installer/extractarchiveoperation.cpp +++ b/src/libs/installer/extractarchiveoperation.cpp @@ -30,6 +30,7 @@ #include "constants.h" #include "globals.h" +#include "fileguard.h" #include <QEventLoop> #include <QThreadPool> @@ -45,14 +46,14 @@ namespace QInstaller { */ /*! - \typedef QInstaller::Backup + \typedef QInstaller::ExtractArchiveOperation::Backup Synonym for QPair<QString, QString>. Contains a pair of an original and a generated backup filename for a file. */ /*! - \typedef QInstaller::BackupFiles + \typedef QInstaller::ExtractArchiveOperation::BackupFiles Synonym for QVector<Backup>. */ @@ -65,13 +66,64 @@ namespace QInstaller { ExtractArchiveOperation::ExtractArchiveOperation(PackageManagerCore *core) : UpdateOperation(core) + , m_totalEntries(0) { setName(QLatin1String("Extract")); + setGroup(OperationGroup::Unpack); } void ExtractArchiveOperation::backup() { - // we need to backup on the fly... + if (!checkArgumentCount(2)) + return; + + const QStringList args = arguments(); + const QString archivePath = args.at(0); + const QString targetDir = args.at(1); + + QScopedPointer<AbstractArchive> archive(ArchiveFactory::instance().create(archivePath)); + if (!archive) { + setError(UserDefinedError); + setErrorString(tr("Unsupported archive \"%1\": no handler registered for file suffix \"%2\".") + .arg(archivePath, QFileInfo(archivePath).suffix())); + return; + } + + if (!(archive->open(QIODevice::ReadOnly) && archive->isSupported())) { + setError(UserDefinedError); + setErrorString(tr("Cannot open archive \"%1\" for reading: %2") + .arg(archivePath, archive->errorString())); + return; + } + const QVector<ArchiveEntry> entries = archive->list(); + if (entries.isEmpty()) { + setError(UserDefinedError); + setErrorString(tr("Error while reading contents of archive \"%1\": %2") + .arg(archivePath, archive->errorString())); + return; + } + + const bool hasAdminRights = (AdminAuthorization::hasAdminRights() || RemoteClient::instance().isActive()); + const bool canCreateSymLinks = QInstaller::canCreateSymbolicLinks(); + bool needsAdminRights = false; + + for (auto &entry : entries) { + const QString completeFilePath = targetDir + QDir::separator() + entry.path; + if (!entry.isDirectory) { + // Ignore failed backups, existing files are overwritten when extracting. + // Should the backups be used on rollback too, this may not be the + // desired behavior anymore. + prepareForFile(completeFilePath); + } + if (!hasAdminRights && !canCreateSymLinks && entry.isSymbolicLink) + needsAdminRights = true; + } + m_totalEntries = entries.size(); + if (needsAdminRights) + setValue(QLatin1String("admin"), true); + + // Show something was done + emit progressChanged(scBackupProgressPart); } bool ExtractArchiveOperation::performOperation() @@ -88,16 +140,15 @@ bool ExtractArchiveOperation::performOperation() connect(&callback, &Callback::progressChanged, this, &ExtractArchiveOperation::progressChanged); - Worker *worker = new Worker(archivePath, targetDir, &callback); + Worker *worker = new Worker(archivePath, targetDir, m_totalEntries, &callback); connect(worker, &Worker::finished, &receiver, &Receiver::workerFinished, Qt::QueuedConnection); - if (PackageManagerCore *core = packageManager()) { + if (PackageManagerCore *core = packageManager()) connect(core, &PackageManagerCore::statusChanged, worker, &Worker::onStatusChanged); - worker->setPackageManagerCore(core); - } QFileInfo fileInfo(archivePath); + qCDebug(lcInstallerInstallLog) << "Extracting" << fileInfo.fileName(); emit outputTextChanged(tr("Extracting \"%1\"").arg(fileInfo.fileName())); { QEventLoop loop; @@ -132,6 +183,7 @@ bool ExtractArchiveOperation::performOperation() if (packageManager()) installDir = packageManager()->value(scTargetDir); const QString resourcesPath = installDir + QLatin1Char('/') + QLatin1String("installerResources"); + QString fileDirectory = resourcesPath + QLatin1Char('/') + archivePath.section(QLatin1Char('/'), 1, 1, QString::SectionSkipEmpty) + QLatin1Char('/'); QString archiveFileName = archivePath.section(QLatin1Char('/'), 2, 2, QString::SectionSkipEmpty); @@ -142,10 +194,20 @@ bool ExtractArchiveOperation::performOperation() QFileInfo targetDirectoryInfo(fileDirectory); - QDir dir(targetDirectoryInfo.absolutePath()); - if (!dir.exists()) { - dir.mkpath(targetDirectoryInfo.absolutePath()); - } + // We need to create the directory structure step-by-step to avoid a rare race condition + // on Windows. QDir::mkpath() doesn't check if the leading directories were created elsewhere + // (like from within another thread) after the initial check that the given path requires + // creating also parent directories. + // + // On Unix patforms this case is handled by QFileSystemEngine though. + QDir resourcesDir(resourcesPath); + if (!resourcesDir.exists()) + resourcesDir.mkdir(resourcesPath); + + QDir resourceFileDir(targetDirectoryInfo.absolutePath()); + if (!resourceFileDir.exists()) + resourceFileDir.mkdir(targetDirectoryInfo.absolutePath()); + setDefaultFilePermissions(resourcesPath, DefaultFilePermissions::Executable); setDefaultFilePermissions(targetDirectoryInfo.absolutePath(), DefaultFilePermissions::Executable); @@ -166,7 +228,7 @@ bool ExtractArchiveOperation::performOperation() // TODO: Use backups for rollback, too? Doesn't work for uninstallation though. // delete all backups we can delete right now, remember the rest - foreach (const QInstaller::Backup &i, callback.backupFiles()) + foreach (const Backup &i, m_backupFiles) deleteFileNowOrLater(i.second); if (!receiver.success()) { @@ -233,6 +295,35 @@ void ExtractArchiveOperation::deleteDataFile(const QString &fileName) } } +QString ExtractArchiveOperation::generateBackupName(const QString &fn) +{ + const QString bfn = fn + QLatin1String(".tmpUpdate"); + QString res = bfn; + int i = 0; + while (QFile::exists(res)) + res = bfn + QString::fromLatin1(".%1").arg(i++); + return res; +} + +bool ExtractArchiveOperation::prepareForFile(const QString &filename) +{ + if (!QFile::exists(filename)) + return true; + + FileGuardLocker locker(filename, FileGuard::globalObject()); + + const QString backup = generateBackupName(filename); + QFile f(filename); + const bool renamed = f.rename(backup); + if (f.exists() && !renamed) { + qCritical("Cannot rename %s to %s: %s", qPrintable(filename), qPrintable(backup), + qPrintable(f.errorString())); + return false; + } + m_backupFiles.append(qMakePair(filename, backup)); + return true; +} + bool ExtractArchiveOperation::testOperation() { return true; diff --git a/src/libs/installer/extractarchiveoperation.h b/src/libs/installer/extractarchiveoperation.h index 5b13fd229..7df472348 100644 --- a/src/libs/installer/extractarchiveoperation.h +++ b/src/libs/installer/extractarchiveoperation.h @@ -58,13 +58,21 @@ private: void startUndoProcess(const QStringList &files); void deleteDataFile(const QString &fileName); -private: - QString m_relocatedDataFileName; + QString generateBackupName(const QString &fn); + bool prepareForFile(const QString &filename); private: + typedef QPair<QString, QString> Backup; + typedef QVector<Backup> BackupFiles; + class Callback; class Worker; class Receiver; + +private: + QString m_relocatedDataFileName; + BackupFiles m_backupFiles; + quint64 m_totalEntries; }; } diff --git a/src/libs/installer/extractarchiveoperation_p.h b/src/libs/installer/extractarchiveoperation_p.h index 87c126a81..f440af23b 100644 --- a/src/libs/installer/extractarchiveoperation_p.h +++ b/src/libs/installer/extractarchiveoperation_p.h @@ -43,6 +43,9 @@ namespace QInstaller { +constexpr double scBackupProgressPart = 0.1; +constexpr double scPerformProgressPart = (1 - scBackupProgressPart); + class WorkerThread : public QThread { Q_OBJECT @@ -85,53 +88,21 @@ private: ExtractArchiveOperation *m_op; }; -typedef QPair<QString, QString> Backup; -typedef QVector<Backup> BackupFiles; - class ExtractArchiveOperation::Callback : public QObject { Q_OBJECT Q_DISABLE_COPY(Callback) public: - Callback() = default; - - BackupFiles backupFiles() const - { - return m_backupFiles; - } + Callback() + : m_lastProgressPercentage(0) + {} QStringList extractedFiles() const { return m_extractedFiles; } - static QString generateBackupName(const QString &fn) - { - const QString bfn = fn + QLatin1String(".tmpUpdate"); - QString res = bfn; - int i = 0; - while (QFile::exists(res)) - res = bfn + QString::fromLatin1(".%1").arg(i++); - return res; - } - - bool prepareForFile(const QString &filename) - { - if (!QFile::exists(filename)) - return true; - const QString backup = generateBackupName(filename); - QFile f(filename); - const bool renamed = f.rename(backup); - if (f.exists() && !renamed) { - qCritical("Cannot rename %s to %s: %s", qPrintable(filename), qPrintable(backup), - qPrintable(f.errorString())); - return false; - } - m_backupFiles.append(qMakePair(filename, backup)); - return true; - } - Q_SIGNALS: void progressChanged(double progress); @@ -143,12 +114,20 @@ public Q_SLOTS: void onCompletedChanged(quint64 completed, quint64 total) { - emit progressChanged(double(completed) / total); + const double currentProgress = double(completed) / total + * scPerformProgressPart + scBackupProgressPart; + + const int currentProgressPercentage = qRound(currentProgress * 100); + if (currentProgressPercentage > m_lastProgressPercentage) { + // Emit only full percentage changes + m_lastProgressPercentage = currentProgressPercentage; + emit progressChanged(currentProgress); + } } private: - BackupFiles m_backupFiles; QStringList m_extractedFiles; + int m_lastProgressPercentage; }; class ExtractArchiveOperation::Worker : public QObject @@ -157,26 +136,19 @@ class ExtractArchiveOperation::Worker : public QObject Q_DISABLE_COPY(Worker) public: - Worker(const QString &archivePath, const QString &targetDir, Callback *callback) + Worker(const QString &archivePath, const QString &targetDir, quint64 totalEntries, Callback *callback) : m_archivePath(archivePath) , m_targetDir(targetDir) - , m_canceled(false) + , m_totalEntries(totalEntries) , m_callback(callback) - , m_core(nullptr) {} - void setPackageManagerCore(PackageManagerCore *core) - { - m_core = core; - } - Q_SIGNALS: void finished(bool success, const QString &errorString); public Q_SLOTS: void run() { - 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\".") @@ -187,60 +159,18 @@ public Q_SLOTS: 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())) { + if (!m_archive->open(QIODevice::ReadOnly)) { 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; - } - const bool hasAdminRights = (AdminAuthorization::hasAdminRights() || RemoteClient::instance().isActive()); - const bool canCreateSymLinks = QInstaller::canCreateSymbolicLinks(); - bool needsAdminRights = false; - - 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 (!hasAdminRights && !canCreateSymLinks && entry.isSymbolicLink) - needsAdminRights = true; - } - 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)); - return; - } - bool gainedAdminRights = false; - if (needsAdminRights && m_core) { - // This must be invoked in the main thread. - QMetaObject::invokeMethod(m_core, [&] { - try { - return m_core->gainAdminRights(); - } catch (const QInstaller::Error &) { - return false; - } - }, Qt::BlockingQueuedConnection, &gainedAdminRights); - } - if (needsAdminRights && !gainedAdminRights) { - emit finished(false, tr("Could not request administrator privileges required to extract " - "archive \"%1\".").arg(m_archivePath)); - } else if (!m_archive->extract(m_targetDir, entries.size())) { + if (!m_archive->extract(m_targetDir, m_totalEntries)) { emit finished(false, tr("Error while extracting archive \"%1\": %2").arg(m_archivePath, m_archive->errorString())); } else { emit finished(true, QString()); } - - if (gainedAdminRights) - m_core->dropAdminRights(); } void onStatusChanged(PackageManagerCore::Status status) @@ -250,11 +180,9 @@ public Q_SLOTS: 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 @@ -265,10 +193,9 @@ public Q_SLOTS: private: QString m_archivePath; QString m_targetDir; + quint64 m_totalEntries; QScopedPointer<AbstractArchive> m_archive; - bool m_canceled; Callback *m_callback; - PackageManagerCore *m_core; }; class ExtractArchiveOperation::Receiver : public QObject diff --git a/src/libs/installer/fileguard.cpp b/src/libs/installer/fileguard.cpp new file mode 100644 index 000000000..1f5fb46b7 --- /dev/null +++ b/src/libs/installer/fileguard.cpp @@ -0,0 +1,120 @@ +/************************************************************************** +** +** Copyright (C) 2022 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 "fileguard.h" + +#include <QTimer> + +using namespace QInstaller; + +/*! + \inmodule QtInstallerFramework + \class QInstaller::FileGuard + \brief The \c FileGuard class provides basic access serialization for file paths. + + This class keeps a list of file paths that are locked from mutual + access. Attempting to lock them from another thread will fail until the + the locked path name is released. +*/ + +Q_GLOBAL_STATIC(FileGuard, globalFileGuard) + +/*! + Attempts to lock \a path. Returns \c true if the lock could be + acquired, \c false if another thread has already locked the path. +*/ +bool FileGuard::tryLock(const QString &path) +{ + QMutexLocker _(&m_mutex); + if (path.isEmpty()) + return false; + + if (m_paths.contains(path)) + return false; + + m_paths.append(path); + return true; +} + +/*! + Unlocks \a path. +*/ +void FileGuard::release(const QString &path) +{ + QMutexLocker _(&m_mutex); + m_paths.removeOne(path); +} + +/*! + Returns the application global instance. +*/ +FileGuard *FileGuard::globalObject() +{ + return globalFileGuard; +} + +/*! + \inmodule QtInstallerFramework + \class QInstaller::FileGuardLocker + \brief The \c FileGuardLocker class locks a file path and releases it on destruction. + + A convenience class for locking a file path using the resource acquisition + is initialization (RAII) programming idiom. +*/ + +/*! + Constructs the object and attempts to lock \a path with \a guard. If the lock is already + held by another thread, this method will wait for it to become available. +*/ +FileGuardLocker::FileGuardLocker(const QString &path, FileGuard *guard) + : m_path(path) + , m_guard(guard) +{ + if (!m_guard->tryLock(m_path)) { + QTimer timer; + QEventLoop loop; + + QObject::connect(&timer, &QTimer::timeout, [&]() { + if (m_guard->tryLock(m_path)) + loop.quit(); + }); + + timer.start(100); + loop.exec(); + } +} + +/*! + Destructs the object and unlocks the locked file path. +*/ +FileGuardLocker::~FileGuardLocker() +{ + m_guard->release(m_path); +} + + diff --git a/src/libs/installer/fileguard.h b/src/libs/installer/fileguard.h new file mode 100644 index 000000000..a2f96a16e --- /dev/null +++ b/src/libs/installer/fileguard.h @@ -0,0 +1,66 @@ +/************************************************************************** +** +** Copyright (C) 2022 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 FILEGUARD_H +#define FILEGUARD_H + +#include "qinstallerglobal.h" + +#include <QMutex> + +namespace QInstaller { + +class INSTALLER_EXPORT FileGuard +{ +public: + FileGuard() = default; + + bool tryLock(const QString &path); + void release(const QString &path); + + static FileGuard *globalObject(); + +private: + QMutex m_mutex; + QStringList m_paths; +}; + +class INSTALLER_EXPORT FileGuardLocker +{ +public: + explicit FileGuardLocker(const QString &path, FileGuard *guard); + ~FileGuardLocker(); + +private: + QString m_path; + FileGuard *m_guard; +}; + +} // namespace QInstaller + +#endif // FILEGUARD_H diff --git a/src/libs/installer/installer.pro b/src/libs/installer/installer.pro index 19e80d9b5..60c695fd2 100644 --- a/src/libs/installer/installer.pro +++ b/src/libs/installer/installer.pro @@ -44,12 +44,14 @@ win32:QT += winextras HEADERS += packagemanagercore.h \ aspectratiolabel.h \ componentsortfilterproxymodel.h \ + concurrentoperationrunner.h \ loggingutils.h \ packagemanagercore_p.h \ packagemanagergui.h \ binaryformat.h \ binaryformatengine.h \ binaryformatenginehandler.h \ + fileguard.h \ repository.h \ utils.h \ errors.h \ @@ -141,7 +143,9 @@ SOURCES += packagemanagercore.cpp \ abstractarchive.cpp \ archivefactory.cpp \ aspectratiolabel.cpp \ + concurrentoperationrunner.cpp \ directoryguard.cpp \ + fileguard.cpp \ componentsortfilterproxymodel.cpp \ loggingutils.cpp \ packagemanagercore_p.cpp \ diff --git a/src/libs/installer/lib7z_facade.cpp b/src/libs/installer/lib7z_facade.cpp index fc8c6c334..bd08c68c5 100644 --- a/src/libs/installer/lib7z_facade.cpp +++ b/src/libs/installer/lib7z_facade.cpp @@ -37,6 +37,7 @@ #include "lib7z_guid.h" #include "globals.h" #include "directoryguard.h" +#include "fileguard.h" #ifndef Q_OS_WIN # include "StdAfx.h" @@ -623,6 +624,11 @@ STDMETHODIMP ExtractCallback::GetStream(UInt32 index, ISequentialOutStream **out foreach (const QString &directory, directories) setCurrentFile(directory); + QScopedPointer<QInstaller::FileGuardLocker> locker(nullptr); + if (!isDir) { + locker.reset(new QInstaller::FileGuardLocker( + fi.absoluteFilePath(), QInstaller::FileGuard::globalObject())); + } if (!isDir && !prepareForFile(fi.absoluteFilePath())) return E_FAIL; diff --git a/src/libs/installer/libarchivearchive.cpp b/src/libs/installer/libarchivearchive.cpp index 0990706bf..9f88adb27 100644 --- a/src/libs/installer/libarchivearchive.cpp +++ b/src/libs/installer/libarchivearchive.cpp @@ -29,6 +29,7 @@ #include "libarchivearchive.h" #include "directoryguard.h" +#include "fileguard.h" #include "errors.h" #include "globals.h" @@ -409,6 +410,11 @@ bool ExtractWorker::writeEntry(archive *reader, archive *writer, archive_entry * size_t size; int64_t offset; + const QString entryPath = ArchiveEntryPaths::callWithSystemLocale + <QString>(ArchiveEntryPaths::pathname, entry); + + FileGuardLocker locker(entryPath, FileGuard::globalObject()); + status = archive_write_header(writer, entry); if (status != ARCHIVE_OK) { emit finished(LibArchiveArchive::errorStringWithCode(writer)); @@ -1015,6 +1021,11 @@ bool LibArchiveArchive::writeEntry(archive *reader, archive *writer, archive_ent size_t size; int64_t offset; + const QString entryPath = ArchiveEntryPaths::callWithSystemLocale + <QString>(ArchiveEntryPaths::pathname, entry); + + FileGuardLocker locker(entryPath, FileGuard::globalObject()); + status = archive_write_header(writer, entry); if (status != ARCHIVE_OK) { setErrorString(errorStringWithCode(writer)); diff --git a/src/libs/installer/loggingutils.cpp b/src/libs/installer/loggingutils.cpp index 9420830f4..c2e707928 100644 --- a/src/libs/installer/loggingutils.cpp +++ b/src/libs/installer/loggingutils.cpp @@ -132,6 +132,8 @@ LoggingHandler::~LoggingHandler() */ void LoggingHandler::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + QMutexLocker _(&m_mutex); + // suppress warning from QPA minimal plugin if (msg.contains(QLatin1String("This plugin does not support propagateSizeHints"))) return; diff --git a/src/libs/installer/loggingutils.h b/src/libs/installer/loggingutils.h index 246871fb6..f0e76037c 100644 --- a/src/libs/installer/loggingutils.h +++ b/src/libs/installer/loggingutils.h @@ -36,6 +36,7 @@ #include <QIODevice> #include <QTextStream> #include <QBuffer> +#include <QMutex> namespace QInstaller { @@ -79,6 +80,8 @@ private: private: VerbosityLevel m_verbLevel; bool m_outputRedirected; + + QMutex m_mutex; }; class INSTALLER_EXPORT VerboseWriterOutput diff --git a/src/libs/installer/packagemanagercore_p.cpp b/src/libs/installer/packagemanagercore_p.cpp index 464a68109..3a786b082 100644 --- a/src/libs/installer/packagemanagercore_p.cpp +++ b/src/libs/installer/packagemanagercore_p.cpp @@ -49,6 +49,7 @@ #include "globals.h" #include "binarycreator.h" #include "loggingutils.h" +#include "concurrentoperationrunner.h" #include "selfrestarter.h" #include "filedownloaderfactory.h" @@ -89,8 +90,10 @@ namespace QInstaller { class OperationTracer { public: - OperationTracer(Operation *operation) : m_operation(nullptr) + OperationTracer(Operation *operation = nullptr) : m_operation(nullptr) { + if (!operation) + return; // don't create output for that hacky pseudo operation if (operation->name() != QLatin1String("MinimumProgress")) m_operation = operation; @@ -116,9 +119,9 @@ private: Operation *m_operation; }; -static bool runOperation(Operation *operation, Operation::OperationType type) +static bool runOperation(Operation *operation, Operation::OperationType type, const bool trace) { - OperationTracer tracer(operation); + OperationTracer tracer = trace ? OperationTracer(operation) : OperationTracer(); switch (type) { case Operation::Backup: tracer.trace(QLatin1String("backup")); @@ -365,10 +368,11 @@ bool PackageManagerCorePrivate::isProcessRunning(const QString &name, } /* static */ -bool PackageManagerCorePrivate::performOperationThreaded(Operation *operation, Operation::OperationType type) +bool PackageManagerCorePrivate::performOperationThreaded(Operation *operation, + Operation::OperationType type, bool trace) { QFutureWatcher<bool> futureWatcher; - const QFuture<bool> future = QtConcurrent::run(runOperation, operation, type); + const QFuture<bool> future = QtConcurrent::run(runOperation, operation, type, trace); QEventLoop loop; QObject::connect(&futureWatcher, &decltype(futureWatcher)::finished, &loop, &QEventLoop::quit, @@ -1690,6 +1694,10 @@ bool PackageManagerCorePrivate::runInstaller() + (PackageManagerCore::createLocalRepositoryFromBinary() ? 1 : 0); double progressOperationSize = componentsInstallPartProgressSize / progressOperationCount; + // Perform extract operations + unpackComponents(componentsToInstall, progressOperationSize, adminRightsGained); + + // Perform rest of the operations and mark component as installed foreach (Component *component, componentsToInstall) installComponent(component, progressOperationSize, adminRightsGained); @@ -1914,6 +1922,10 @@ bool PackageManagerCorePrivate::runPackageUpdater() const double progressOperationCount = countProgressOperations(componentsToInstall); const double progressOperationSize = componentsInstallPartProgressSize / progressOperationCount; + // Perform extract operations + unpackComponents(componentsToInstall, progressOperationSize, adminRightsGained); + + // Perform rest of the operations and mark component as installed foreach (Component *component, componentsToInstall) installComponent(component, progressOperationSize, adminRightsGained); @@ -2158,10 +2170,116 @@ bool PackageManagerCorePrivate::runOfflineGenerator() return success; } +void PackageManagerCorePrivate::unpackComponents(const QList<Component *> &components, + double progressOperationSize, bool adminRightsGained) +{ + OperationList unpackOperations; + bool becameAdmin = false; + + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("\nUnpacking components...")); + + for (auto *component : components) { + const OperationList operations = component->operations(Operation::Unpack); + if (!component->operationsCreatedSuccessfully()) + m_core->setCanceled(); + + for (auto &op : operations) { + if (statusCanceledOrFailed()) + throw Error(tr("Installation canceled by user")); + + unpackOperations.append(op); + + // There's currently no way to control this on a per-operation basis, so + // any op requesting execution as admin means all extracts are done as admin. + if (!adminRightsGained && !becameAdmin && op->value(QLatin1String("admin")).toBool()) + becameAdmin = m_core->gainAdminRights(); + + connectOperationToInstaller(op, progressOperationSize); + connectOperationCallMethodRequest(op); + } + } + + ConcurrentOperationRunner runner(&unpackOperations, Operation::Backup); + connect(m_core, &PackageManagerCore::installationInterrupted, + &runner, &ConcurrentOperationRunner::cancel); + + const QHash<Operation *, bool> backupResults = runner.run(); + const OperationList backupOperations = backupResults.keys(); + + for (auto &operation : backupOperations) { + if (!backupResults.value(operation) || operation->error() != Operation::NoError) { + // For Extract, backup stops only on read errors. That means the perform step will + // also fail later on, which handles the user selection on what to do with the error. + qCWarning(QInstaller::lcInstallerInstallLog) << QString::fromLatin1("Backup of operation " + "\"%1\" with arguments \"%2\" failed: %3").arg(operation->name(), operation->arguments() + .join(QLatin1String("; ")), operation->errorString()); + continue; + } + // Backup may request performing operation as admin + if (!adminRightsGained && !becameAdmin && operation->value(QLatin1String("admin")).toBool()) + becameAdmin = m_core->gainAdminRights(); + } + + // TODO: Do we need to rollback backups too? For Extract op this works without, + // as the backup files are not used in Undo step. + if (statusCanceledOrFailed()) + throw Error(tr("Installation canceled by user")); + + runner.setType(Operation::Perform); + const QHash<Operation *, bool> results = runner.run(); + const OperationList performedOperations = backupResults.keys(); + + QString error; + for (auto &operation : performedOperations) { + const QString component = operation->value(QLatin1String("component")).toString(); + + bool ignoreError = false; + bool ok = results.value(operation); + + while (!ok && !ignoreError && m_core->status() != PackageManagerCore::Canceled) { + qCDebug(QInstaller::lcInstallerInstallLog) << QString::fromLatin1("Operation \"%1\" with arguments " + "\"%2\" failed: %3").arg(operation->name(), operation->arguments() + .join(QLatin1String("; ")), operation->errorString()); + + const QMessageBox::StandardButton button = + MessageBoxHandler::warning(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("installationErrorWithCancel"), tr("Installer Error"), + tr("Error during installation process (%1):\n%2").arg(component, operation->errorString()), + QMessageBox::Retry | QMessageBox::Ignore | QMessageBox::Cancel, QMessageBox::Cancel); + + if (button == QMessageBox::Retry) + ok = performOperationThreaded(operation); + else if (button == QMessageBox::Ignore) + ignoreError = true; + else if (button == QMessageBox::Cancel) + m_core->interrupt(); + } + + if (ok || operation->error() > Operation::InvalidArguments) { + // Remember that the operation was performed, that allows us to undo it + // if this operation failed but still needs an undo call to cleanup. + addPerformed(operation); + } + + // Catch the error message from first failure, but throw only after all + // operations requiring undo step are marked as performed. + if (!ok && !ignoreError && error.isEmpty()) + error = operation->errorString(); + } + + if (becameAdmin) + m_core->dropAdminRights(); + + if (!error.isEmpty()) + throw Error(error); + + ProgressCoordinator::instance()->emitDetailTextChanged(tr("Done")); +} + void PackageManagerCorePrivate::installComponent(Component *component, double progressOperationSize, bool adminRightsGained) { - const OperationList operations = component->operations(); + OperationList operations = component->operations(Operation::Install); if (!component->operationsCreatedSuccessfully()) m_core->setCanceled(); @@ -2223,13 +2341,13 @@ void PackageManagerCorePrivate::installComponent(Component *component, double pr if (!ok && !ignoreError) throw Error(operation->errorString()); + } - if (!m_core->isCommandLineInstance()) { - if ((component->value(scEssential, scFalse) == scTrue) && !isInstaller()) - m_needsHardRestart = true; - else if ((component->value(scForcedUpdate, scFalse) == scTrue) && isUpdater()) - m_needsHardRestart = true; - } + if (!m_core->isCommandLineInstance()) { + if ((component->value(scEssential, scFalse) == scTrue) && !isInstaller()) + m_needsHardRestart = true; + else if ((component->value(scForcedUpdate, scFalse) == scTrue) && isUpdater()) + m_needsHardRestart = true; } registerPathsForUninstallation(component->pathsForUninstallation(), component->name()); diff --git a/src/libs/installer/packagemanagercore_p.h b/src/libs/installer/packagemanagercore_p.h index d17a752f9..fef8a0431 100644 --- a/src/libs/installer/packagemanagercore_p.h +++ b/src/libs/installer/packagemanagercore_p.h @@ -75,7 +75,7 @@ public: static bool isProcessRunning(const QString &name, const QList<ProcessInfo> &processes); static bool performOperationThreaded(Operation *op, UpdateOperation::OperationType type - = UpdateOperation::Perform); + = UpdateOperation::Perform, bool trace = true); void initialize(const QHash<QString, QString> ¶ms); bool isOfflineOnly() const; @@ -161,6 +161,9 @@ public: m_performedOperationsCurrentSession.clear(); } + void unpackComponents(const QList<Component *> &components, double progressOperationSize, + bool adminRightsGained = false); + void installComponent(Component *component, double progressOperationSize, bool adminRightsGained = false); diff --git a/src/libs/installer/protocol.h b/src/libs/installer/protocol.h index 65241e00b..841b1a2a3 100644 --- a/src/libs/installer/protocol.h +++ b/src/libs/installer/protocol.h @@ -52,7 +52,6 @@ const char DefaultSocket[] = "ifw_srv"; const char DefaultAuthorizationKey[] = "DefaultAuthorizationKey"; const char Create[] = "Create"; -const char Destroy[] = "Destroy"; const char Shutdown[] = "Shutdown"; const char Authorize[] = "Authorize"; const char Reply[] = "Reply"; diff --git a/src/libs/installer/remotefileengine.cpp b/src/libs/installer/remotefileengine.cpp index 3c9ee217f..b600da618 100644 --- a/src/libs/installer/remotefileengine.cpp +++ b/src/libs/installer/remotefileengine.cpp @@ -31,7 +31,7 @@ #include "protocol.h" #include "remoteclient.h" -#include <QRegExp> +#include <QRegularExpression> namespace QInstaller { @@ -48,8 +48,8 @@ QAbstractFileEngine* RemoteFileEngineHandler::create(const QString &fileName) co if (!RemoteClient::instance().isActive()) return 0; - static QRegExp re(QLatin1String("^[a-z0-9]*://.*$")); - if (re.exactMatch(fileName)) // stuff like installer:// + static const QRegularExpression re(QLatin1String("^[a-z0-9]*://.*$")); + if (re.match(fileName).hasMatch()) // stuff like installer:// return 0; if (fileName.isEmpty() || fileName.startsWith(QLatin1String(":"))) diff --git a/src/libs/installer/remoteobject.cpp b/src/libs/installer/remoteobject.cpp index 7c875b183..54034eb65 100644 --- a/src/libs/installer/remoteobject.cpp +++ b/src/libs/installer/remoteobject.cpp @@ -60,7 +60,6 @@ RemoteObject::~RemoteObject() if (QThread::currentThread() == m_socket->thread()) { if ((m_type != QLatin1String("RemoteClientPrivate")) && (m_socket->state() == QLocalSocket::ConnectedState)) { - writeData(QLatin1String(Protocol::Destroy), m_type, dummy, dummy); while (m_socket->bytesToWrite()) { // QAbstractSocket::waitForBytesWritten() may fail randomly on Windows, use // an event loop and the bytesWritten() signal instead as the docs suggest. diff --git a/src/libs/installer/remoteserverconnection.cpp b/src/libs/installer/remoteserverconnection.cpp index 9e141ea48..2464c8469 100644 --- a/src/libs/installer/remoteserverconnection.cpp +++ b/src/libs/installer/remoteserverconnection.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -60,10 +60,10 @@ RemoteServerConnection::RemoteServerConnection(qintptr socketDescriptor, const Q QObject *parent) : QThread(parent) , m_socketDescriptor(socketDescriptor) + , m_authorizationKey(key) , m_process(nullptr) , m_engine(nullptr) , m_archive(nullptr) - , m_authorizationKey(key) , m_processSignalReceiver(nullptr) , m_archiveSignalReceiver(nullptr) { @@ -145,20 +145,15 @@ void RemoteServerConnection::run() settings.reset(new PermissionSettings(fileName.toString(), QSettings::Format(format.toInt()))); } } else if (type == QLatin1String(Protocol::QProcess)) { - if (m_process) - m_process->deleteLater(); - m_process = new QProcess; - m_processSignalReceiver = new QProcessSignalReceiver(m_process); + m_process.reset(new QProcess); + m_processSignalReceiver = new QProcessSignalReceiver(m_process.get()); } else if (type == QLatin1String(Protocol::QAbstractFileEngine)) { - if (m_engine) - delete m_engine; - m_engine = new QFSFileEngine; + m_engine.reset(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)); + m_archive.reset(new LibArchiveArchive); + m_archiveSignalReceiver = new AbstractArchiveSignalReceiver( + static_cast<LibArchiveArchive *>(m_archive.get())); #else Q_ASSERT_X(false, Q_FUNC_INFO, "No compatible archive handler exists for protocol."); #endif @@ -166,30 +161,6 @@ void RemoteServerConnection::run() continue; } - if (command == QLatin1String(Protocol::Destroy)) { - QString type; - stream >> type; - if (type == QLatin1String(Protocol::QSettings)) { - settings.reset(); - } else if (type == QLatin1String(Protocol::QProcess)) { - 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_processSignalReceiver) { QMutexLocker _(&m_processSignalReceiver->m_lock); @@ -559,7 +530,7 @@ 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); + LibArchiveArchive *archive = static_cast<LibArchiveArchive *>(m_archive.get()); if (command == QLatin1String(Protocol::AbstractArchiveOpen)) { qint32 openMode; data >> openMode; diff --git a/src/libs/installer/remoteserverconnection.h b/src/libs/installer/remoteserverconnection.h index f7661e70e..1fd179430 100644 --- a/src/libs/installer/remoteserverconnection.h +++ b/src/libs/installer/remoteserverconnection.h @@ -29,20 +29,21 @@ #ifndef REMOTESERVERCONNECTION_H #define REMOTESERVERCONNECTION_H +#include "abstractarchive.h" + #include <QPointer> #include <QThread> +#include <QProcess> #include <QtCore/private/qfsfileengine_p.h> QT_BEGIN_NAMESPACE -class QProcess; class QIODevice; QT_END_NAMESPACE namespace QInstaller { class PermissionSettings; -class AbstractArchive; class QProcessSignalReceiver; class AbstractArchiveSignalReceiver; @@ -72,11 +73,12 @@ private: private: qintptr m_socketDescriptor; - - QProcess *m_process; - QFSFileEngine *m_engine; - AbstractArchive *m_archive; QString m_authorizationKey; + + QScopedPointer<QProcess> m_process; + QScopedPointer<QFSFileEngine> m_engine; + QScopedPointer<AbstractArchive> m_archive; + QProcessSignalReceiver *m_processSignalReceiver; AbstractArchiveSignalReceiver *m_archiveSignalReceiver; }; diff --git a/src/libs/kdtools/updateoperation.cpp b/src/libs/kdtools/updateoperation.cpp index 998d33094..e56c72228 100644 --- a/src/libs/kdtools/updateoperation.cpp +++ b/src/libs/kdtools/updateoperation.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -80,6 +81,21 @@ using namespace KDUpdater; Undo operation. */ +/*! + \enum UpdateOperation::OperationGroup + This enum specifies the execution group of the operation. + + \value Unpack + Operation should be run in the unpacking phase. Operations in + this group are run concurrently between all selected components. + \value Install + Operation should be run in the installation phase. + \value All + All available operation groups. + \value Default + The default group for operations, synonym for Install. +*/ + /* \internal Returns a filename for a temporary file based on \a templateName. @@ -99,7 +115,8 @@ static QString backupFileName(const QString &templateName) \internal */ UpdateOperation::UpdateOperation(QInstaller::PackageManagerCore *core) - : m_error(0) + : m_group(OperationGroup::Default) + , m_error(0) , m_core(core) , m_requiresUnreplacedVariables(false) { @@ -139,6 +156,16 @@ QString UpdateOperation::operationCommand() const } /*! + Returns the execution group this operation belongs to. + + \sa setGroup() +*/ +UpdateOperation::OperationGroup UpdateOperation::group() const +{ + return m_group; +} + +/*! Returns \c true if a value called \a name exists, otherwise returns \c false. */ bool UpdateOperation::hasValue(const QString &name) const @@ -180,6 +207,17 @@ void UpdateOperation::setName(const QString &name) } /*! + Sets the execution group of the operation to \a group. Subclasses can change + the group to control which installation phase this operation should be run in. + + The default group is \c Install. +*/ +void UpdateOperation::setGroup(const OperationGroup &group) +{ + m_group = group; +} + +/*! Sets the arguments for the update operation to \a args. */ void UpdateOperation::setArguments(const QStringList &args) diff --git a/src/libs/kdtools/updateoperation.h b/src/libs/kdtools/updateoperation.h index a8110791c..3bde3e945 100644 --- a/src/libs/kdtools/updateoperation.h +++ b/src/libs/kdtools/updateoperation.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -59,11 +60,20 @@ public: Undo }; + enum OperationGroup { + Unpack = 0x1, + Install = 0x2, + All = (Unpack | Install), + Default = Install + }; + Q_DECLARE_FLAGS(OperationGroups, OperationGroup) + explicit UpdateOperation(QInstaller::PackageManagerCore *core); virtual ~UpdateOperation(); QString name() const; QString operationCommand() const; + OperationGroup group() const; bool hasValue(const QString &name) const; void clearValue(const QString &name); @@ -92,6 +102,7 @@ public: protected: void setName(const QString &name); + void setGroup(const OperationGroup &group); void setErrorString(const QString &errorString); void setError(int error, const QString &errorString = QString()); void registerForDelayedDeletion(const QStringList &files); @@ -104,6 +115,7 @@ protected: private: QString m_name; + OperationGroup m_group; QStringList m_arguments; QString m_errorString; int m_error; diff --git a/src/sdk/sdk.pro b/src/sdk/sdk.pro index cc20244fd..4a47fb825 100644 --- a/src/sdk/sdk.pro +++ b/src/sdk/sdk.pro @@ -9,7 +9,7 @@ include(../../installerfw.pri) include($$SQUISH_PATH/qtbuiltinhook.pri) } -QT += network qml xml widgets +QT += network qml xml widgets concurrent # add the minimal plugin in static build to be able to start the installer headless with: # installer-binary --platform minimal # using QT += qpa_minimal_plugin would result in a minimal only compiled version |