summaryrefslogtreecommitdiffstats
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
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>
-rw-r--r--doc/includes/IFWDoc2
-rw-r--r--doc/operations.qdoc11
-rw-r--r--doc/scripting.qdoc1
-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
-rw-r--r--src/sdk/sdk.pro2
-rw-r--r--tools/archivegen/archivegen.pro2
-rw-r--r--tools/binarycreator/binarycreator.pro2
-rw-r--r--tools/devtool/devtool.pro2
-rw-r--r--tools/repogen/repogen.pro2
33 files changed, 874 insertions, 189 deletions
diff --git a/doc/includes/IFWDoc b/doc/includes/IFWDoc
index 918d6df52..936a6812e 100644
--- a/doc/includes/IFWDoc
+++ b/doc/includes/IFWDoc
@@ -15,6 +15,7 @@
#include "componentchecker.h"
#include "component.h"
#include "componentmodel.h"
+#include "concurrentoperationrunner.h"
#include "constants.h"
#include "consumeoutputoperation.h"
#include "copydirectoryoperation.h"
@@ -30,6 +31,7 @@
#include "errors.h"
#include "extractarchiveoperation.h"
#include "fakestopprocessforupdateoperation.h"
+#include "fileguard.h"
#include "fileio.h"
#include "fileutils.h"
#include "globalsettingsoperation.h"
diff --git a/doc/operations.qdoc b/doc/operations.qdoc
index 68662d289..c61e2af0c 100644
--- a/doc/operations.qdoc
+++ b/doc/operations.qdoc
@@ -300,4 +300,15 @@
devtool operation <binary> DO,Copy,<source>,<target>
\endcode
+ \section1 Execution Groups
+
+ The operations owned by a component belong to either of two groups: \e Unpack or \e Install.
+ The \c Extract operations are performed as part of the \e Unpack group before all other
+ operations and executed concurrently between all components that are going
+ to be installed. The rest of the operations are performed in the \e Install group and
+ executed sequentially for each component at a time.
+
+ Custom operations can define their execution group by calling the \c setGroup() method
+ in their constructor. For more information about custom operations, see
+ \l{Registering Custom Operations}.
*/
diff --git a/doc/scripting.qdoc b/doc/scripting.qdoc
index d196bf8fb..bb547a0d6 100644
--- a/doc/scripting.qdoc
+++ b/doc/scripting.qdoc
@@ -147,6 +147,7 @@
CustomOperation()
{
setName( "CustomOperation" );
+ setGroup( Install );
}
void backup()
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;
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
diff --git a/tools/archivegen/archivegen.pro b/tools/archivegen/archivegen.pro
index fef39ae6f..7440f0843 100644
--- a/tools/archivegen/archivegen.pro
+++ b/tools/archivegen/archivegen.pro
@@ -5,7 +5,7 @@ INCLUDEPATH += . ..
include(../../installerfw.pri)
QT -= gui
-QT += qml xml
+QT += qml xml concurrent
CONFIG -= import_plugins
CONFIG(lzmasdk) {
diff --git a/tools/binarycreator/binarycreator.pro b/tools/binarycreator/binarycreator.pro
index df83cc0d5..1266b91c6 100644
--- a/tools/binarycreator/binarycreator.pro
+++ b/tools/binarycreator/binarycreator.pro
@@ -5,7 +5,7 @@ INCLUDEPATH += . ..
include(../../installerfw.pri)
QT -= gui
-QT += qml xml
+QT += qml xml concurrent
CONFIG -= import_plugins
diff --git a/tools/devtool/devtool.pro b/tools/devtool/devtool.pro
index 71d2d2b87..def7066af 100644
--- a/tools/devtool/devtool.pro
+++ b/tools/devtool/devtool.pro
@@ -1,7 +1,7 @@
TEMPLATE = app
TARGET = devtool
-QT = core network qml xml
+QT = core network qml xml concurrent
include(../../installerfw.pri)
diff --git a/tools/repogen/repogen.pro b/tools/repogen/repogen.pro
index c8ef74e8b..73f1eafe5 100644
--- a/tools/repogen/repogen.pro
+++ b/tools/repogen/repogen.pro
@@ -5,7 +5,7 @@ INCLUDEPATH += . ..
include(../../installerfw.pri)
QT -= gui
-QT += qml xml
+QT += qml xml concurrent
CONFIG -= import_plugins