summaryrefslogtreecommitdiffstats
path: root/src/libs
diff options
context:
space:
mode:
authorArttu Tarkiainen <arttu.tarkiainen@qt.io>2022-03-01 17:54:22 +0200
committerArttu Tarkiainen <arttu.tarkiainen@qt.io>2022-04-21 17:24:31 +0300
commitd14b1b34bafc7cc82383e8032f3adb626747849b (patch)
tree9e6568c3bc3f2eab072055c711264677dbe869cd /src/libs
parentbda347d641ba6fdbec5f201bb89e1a8ac88b4c5b (diff)
Add support for parallel extraction of component archives
Introduce ConcurrentOperationRunner class used for running installer operations concurrently in the global thread pool. Add execution groups for operations; Unpack operations are run concurrently for all components requesting installation, operations belonging to Install group are run sequentially for sorted components one at a time as before. From the default registered operations the Extract op is moved to Unpack group. Move the previously on-the-fly backup steps of Extract operation to the ExtractArchiveOperation::backup(), so that backups are done before any archives are extracted, and that we know if any of the archives requires administrator privileges to unpack. Reparent QInstaller::Error to QException to support throwing and catching exceptions across thread boundaries. Use RAII for the server-side objects of the classes supporting the remote client-server protocol of installer framework. The concurrent extraction revealed that it was still possible that the local socket was disconnected and thus the RemoteServer- Connection thread finished before receiving and processing the final "Destroy" command packet, leaking the dynamically allocated objects. Task-number: QTIFW-2566 Change-Id: Ib8c2928b9405b7b3465c731018df73acb51e949f Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io> Reviewed-by: Katja Marttila <katja.marttila@qt.io>
Diffstat (limited to 'src/libs')
-rw-r--r--src/libs/installer/abstractarchive.h2
-rw-r--r--src/libs/installer/component.cpp13
-rw-r--r--src/libs/installer/component.h4
-rw-r--r--src/libs/installer/concurrentoperationrunner.cpp206
-rw-r--r--src/libs/installer/concurrentoperationrunner.h78
-rw-r--r--src/libs/installer/errors.h14
-rw-r--r--src/libs/installer/extractarchiveoperation.cpp115
-rw-r--r--src/libs/installer/extractarchiveoperation.h12
-rw-r--r--src/libs/installer/extractarchiveoperation_p.h115
-rw-r--r--src/libs/installer/fileguard.cpp120
-rw-r--r--src/libs/installer/fileguard.h66
-rw-r--r--src/libs/installer/installer.pro4
-rw-r--r--src/libs/installer/lib7z_facade.cpp6
-rw-r--r--src/libs/installer/libarchivearchive.cpp11
-rw-r--r--src/libs/installer/loggingutils.cpp2
-rw-r--r--src/libs/installer/loggingutils.h3
-rw-r--r--src/libs/installer/packagemanagercore_p.cpp142
-rw-r--r--src/libs/installer/packagemanagercore_p.h5
-rw-r--r--src/libs/installer/protocol.h1
-rw-r--r--src/libs/installer/remotefileengine.cpp6
-rw-r--r--src/libs/installer/remoteobject.cpp1
-rw-r--r--src/libs/installer/remoteserverconnection.cpp47
-rw-r--r--src/libs/installer/remoteserverconnection.h14
-rw-r--r--src/libs/kdtools/updateoperation.cpp40
-rw-r--r--src/libs/kdtools/updateoperation.h12
25 files changed, 855 insertions, 184 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> &params);
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;