summaryrefslogtreecommitdiffstats
path: root/src/libs/installer
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/installer')
-rw-r--r--src/libs/installer/abstractarchive.cpp221
-rw-r--r--src/libs/installer/abstractarchive.h119
-rw-r--r--src/libs/installer/adminauthorization_x11.cpp234
-rw-r--r--src/libs/installer/archivefactory.cpp151
-rw-r--r--src/libs/installer/archivefactory.h69
-rw-r--r--src/libs/installer/aspectratiolabel.cpp8
-rw-r--r--src/libs/installer/aspectratiolabel.h6
-rw-r--r--src/libs/installer/binarycontent.h9
-rw-r--r--src/libs/installer/commandlineparser.cpp120
-rw-r--r--src/libs/installer/commandlineparser.h14
-rw-r--r--src/libs/installer/commandlineparser_p.cpp4
-rw-r--r--src/libs/installer/commandlineparser_p.h17
-rw-r--r--src/libs/installer/component.cpp231
-rw-r--r--src/libs/installer/component.h17
-rw-r--r--src/libs/installer/component_p.cpp11
-rw-r--r--src/libs/installer/component_p.h7
-rw-r--r--src/libs/installer/componentmodel.cpp59
-rw-r--r--src/libs/installer/componentmodel.h6
-rw-r--r--src/libs/installer/componentselectionpage_p.cpp184
-rw-r--r--src/libs/installer/componentselectionpage_p.h13
-rw-r--r--src/libs/installer/componentsortfilterproxymodel.cpp152
-rw-r--r--src/libs/installer/componentsortfilterproxymodel.h63
-rw-r--r--src/libs/installer/constants.h27
-rw-r--r--src/libs/installer/copydirectoryoperation.cpp8
-rw-r--r--src/libs/installer/createdesktopentryoperation.cpp9
-rw-r--r--src/libs/installer/createlocalrepositoryoperation.cpp28
-rw-r--r--src/libs/installer/directoryguard.cpp112
-rw-r--r--src/libs/installer/directoryguard.h (renamed from src/libs/installer/lazyplaintextedit.h)43
-rw-r--r--src/libs/installer/downloadarchivesjob.cpp81
-rw-r--r--src/libs/installer/downloadarchivesjob.h11
-rw-r--r--src/libs/installer/downloadfiletask.cpp6
-rw-r--r--src/libs/installer/elevatedexecuteoperation.cpp104
-rw-r--r--src/libs/installer/elevatedexecuteoperation.h8
-rw-r--r--src/libs/installer/extractarchiveoperation.cpp49
-rw-r--r--src/libs/installer/extractarchiveoperation.h4
-rw-r--r--src/libs/installer/extractarchiveoperation_p.h136
-rw-r--r--src/libs/installer/fakestopprocessforupdateoperation.cpp2
-rw-r--r--src/libs/installer/fileutils.cpp174
-rw-r--r--src/libs/installer/fileutils.h13
-rw-r--r--src/libs/installer/globals.cpp187
-rw-r--r--src/libs/installer/globals.h23
-rw-r--r--src/libs/installer/globalsettingsoperation.cpp9
-rw-r--r--src/libs/installer/graph.h2
-rw-r--r--src/libs/installer/init.cpp97
-rw-r--r--src/libs/installer/installer.pro29
-rw-r--r--src/libs/installer/installercalculator.cpp3
-rw-r--r--src/libs/installer/installiconsoperation.cpp33
-rw-r--r--src/libs/installer/keepaliveobject.cpp7
-rw-r--r--src/libs/installer/lazyplaintextedit.cpp101
-rw-r--r--src/libs/installer/lib7z_create.h12
-rw-r--r--src/libs/installer/lib7z_facade.cpp207
-rw-r--r--src/libs/installer/lib7z_list.h17
-rw-r--r--src/libs/installer/lib7zarchive.cpp242
-rw-r--r--src/libs/installer/lib7zarchive.h95
-rw-r--r--src/libs/installer/libarchivearchive.cpp963
-rw-r--r--src/libs/installer/libarchivearchive.h200
-rw-r--r--src/libs/installer/libarchivewrapper.cpp191
-rw-r--r--src/libs/installer/libarchivewrapper.h71
-rw-r--r--src/libs/installer/libarchivewrapper_p.cpp403
-rw-r--r--src/libs/installer/libarchivewrapper_p.h95
-rw-r--r--src/libs/installer/loggingutils.cpp503
-rw-r--r--src/libs/installer/loggingutils.h129
-rw-r--r--src/libs/installer/messageboxhandler.cpp4
-rw-r--r--src/libs/installer/metadatajob.cpp34
-rw-r--r--src/libs/installer/metadatajob.h4
-rw-r--r--src/libs/installer/metadatajob_p.h27
-rw-r--r--src/libs/installer/packagemanagercore.cpp930
-rw-r--r--src/libs/installer/packagemanagercore.h48
-rw-r--r--src/libs/installer/packagemanagercore_p.cpp474
-rw-r--r--src/libs/installer/packagemanagercore_p.h40
-rw-r--r--src/libs/installer/packagemanagercoredata.cpp120
-rw-r--r--src/libs/installer/packagemanagercoredata.h14
-rw-r--r--src/libs/installer/packagemanagergui.cpp207
-rw-r--r--src/libs/installer/packagemanagergui.h11
-rw-r--r--src/libs/installer/performinstallationform.cpp19
-rw-r--r--src/libs/installer/performinstallationform.h5
-rw-r--r--src/libs/installer/progresscoordinator.cpp5
-rw-r--r--src/libs/installer/protocol.cpp8
-rw-r--r--src/libs/installer/protocol.h27
-rw-r--r--src/libs/installer/proxycredentialsdialog.cpp4
-rw-r--r--src/libs/installer/qsettingswrapper.cpp4
-rw-r--r--src/libs/installer/qtpatch.cpp14
-rw-r--r--src/libs/installer/registerfiletypeoperation.cpp6
-rw-r--r--src/libs/installer/remoteclient_p.h23
-rw-r--r--src/libs/installer/remoteobject.cpp15
-rw-r--r--src/libs/installer/remoteserverconnection.cpp107
-rw-r--r--src/libs/installer/remoteserverconnection.h8
-rw-r--r--src/libs/installer/remoteserverconnection_p.h67
-rw-r--r--src/libs/installer/repository.cpp19
-rw-r--r--src/libs/installer/repository.h3
-rw-r--r--src/libs/installer/repositorycategory.cpp12
-rw-r--r--src/libs/installer/repositorycategory.h4
-rw-r--r--src/libs/installer/scriptengine.cpp22
-rw-r--r--src/libs/installer/scriptengine_p.h25
-rw-r--r--src/libs/installer/selfrestartoperation.cpp2
-rw-r--r--src/libs/installer/settings.cpp109
-rw-r--r--src/libs/installer/settings.h2
-rw-r--r--src/libs/installer/settingsoperation.cpp2
-rw-r--r--src/libs/installer/simplemovefileoperation.cpp2
-rw-r--r--src/libs/installer/sysinfo_win.cpp2
-rw-r--r--src/libs/installer/testrepository.cpp3
-rw-r--r--src/libs/installer/uninstallercalculator.cpp38
-rw-r--r--src/libs/installer/uninstallercalculator.h1
-rw-r--r--src/libs/installer/utils.cpp137
-rw-r--r--src/libs/installer/utils.h43
105 files changed, 6729 insertions, 2071 deletions
diff --git a/src/libs/installer/abstractarchive.cpp b/src/libs/installer/abstractarchive.cpp
new file mode 100644
index 000000000..f2b4300c4
--- /dev/null
+++ b/src/libs/installer/abstractarchive.cpp
@@ -0,0 +1,221 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#include "abstractarchive.h"
+
+namespace QInstaller {
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::ArchiveEntry
+ \brief The ArchiveEntry struct represents an entry in an archive file,
+ which can be for example a file or a directory.
+*/
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::AbstractArchive
+ \brief The AbstractArchive class is the base class for classes representing
+ different archive files. It cannot be instantiated on its own but
+ defines the API and provides common functionality when subclassed.
+*/
+
+/*!
+ \enum AbstractArchive::CompressionLevel
+ This enum holds the possible values for archive compression level.
+
+ \value Non
+ \value Fastest
+ \value Fast
+ \value Normal
+ \value Maximum
+ \value Ultra
+*/
+
+/*!
+ \fn QInstaller::AbstractArchive::currentEntryChanged(const QString &filename)
+
+ Current entry changed to \a filename. Subclasses should emit this signal whenever
+ the entry to process is changed.
+*/
+
+/*!
+ \fn QInstaller::AbstractArchive::completedChanged(const quint64 completed, const quint64 total)
+
+ The ratio of \a completed entries from \a total changed. Subclasses should emit
+ this whenever the progress changes.
+*/
+
+/*!
+ \fn QInstaller::AbstractArchive::cancel()
+
+ Cancels current operation. A subclass should implement this slot.
+*/
+
+/*!
+ \fn QInstaller::AbstractArchive::close()
+
+ Closes the archive. A subclass should implement this method.
+*/
+
+/*!
+ \fn QInstaller::AbstractArchive::create(const QStringList &data)
+
+ Creates an archive from \a data. Returns \c true on success;
+ \c false otherwise. A subclass should implement this method.
+*/
+
+/*!
+ \fn QInstaller::AbstractArchive::extract(const QString &dirPath)
+
+ Extracts the archive to \a dirPath. Returns \c true on success;
+ \c false otherwise. A subclass should implement this method.
+*/
+
+/*!
+ \fn QInstaller::AbstractArchive::extract(const QString &dirPath, const quint64 totalFiles)
+
+ Extracts the contents of an archive to \a dirPath with precalculated
+ count of \a totalFiles. Returns \c true on success; \c false otherwise.
+ A subclass should implement this method.
+*/
+
+/*!
+ \fn QInstaller::AbstractArchive::isSupported()
+
+ Returns \c true if the archive is supported; \c false otherwise.
+ A subclass should implement this method.
+*/
+
+/*!
+ \fn QInstaller::AbstractArchive::list()
+
+ Returns a list of entries in this archive. A subclass should implement this method.
+*/
+
+/*!
+ \fn QInstaller::AbstractArchive::open(QIODevice::OpenMode mode)
+
+ Opens the file device for an archive in \a mode. Returns \c true on success;
+ \c false otherwise. A subclass should implement this method.
+*/
+
+/*!
+ \fn QInstaller::AbstractArchive::setFilename(const QString &filename)
+
+ Sets the \a filename for the archive. A subclass should implement this method.
+*/
+
+/*!
+ Constructs a new archive object with \a parent as parent. Cannot be
+ called directly but instead from subclass constructors.
+*/
+AbstractArchive::AbstractArchive(QObject *parent)
+ : QObject(parent)
+ , m_compressionLevel(CompressionLevel::Normal)
+{
+}
+
+/*!
+ Virtual destructor for \c AbstractArchive.
+*/
+AbstractArchive::~AbstractArchive()
+{
+}
+
+/*!
+ Returns a human-readable description of the last error that occurred.
+*/
+QString AbstractArchive::errorString() const
+{
+ return m_error;
+}
+
+/*!
+ Sets the compression level for new archives to \a level.
+*/
+void AbstractArchive::setCompressionLevel(const CompressionLevel level)
+{
+ m_compressionLevel = level;
+}
+
+/*!
+ Sets a human-readable description of the current \a error.
+*/
+void AbstractArchive::setErrorString(const QString &error)
+{
+ m_error = error;
+}
+
+/*!
+ Returns the current compression level.
+*/
+AbstractArchive::CompressionLevel AbstractArchive::compressionLevel() const
+{
+ return m_compressionLevel;
+}
+
+/*!
+ Reads an \a entry from the specified \a istream. Returns a reference to \a istream.
+*/
+QDataStream &operator>>(QDataStream &istream, ArchiveEntry &entry)
+{
+ istream >> entry.path >> entry.utcTime >> entry.isDirectory
+ >> entry.uncompressedSize >> entry.permissions_mode >> entry.permissions_enum;
+
+ return istream;
+}
+
+/*!
+ Writes an \a entry to the specified \a ostream. Returns a reference to \a ostream.
+*/
+QDataStream &operator<<(QDataStream &ostream, const ArchiveEntry &entry)
+{
+ ostream << entry.path << entry.utcTime << entry.isDirectory
+ << entry.uncompressedSize << entry.permissions_mode << entry.permissions_enum;
+
+ return ostream;
+}
+
+/*!
+ Returns \c true if left-hand-side entry \a lhs is equal to right-hand-size entry \a rhs.
+*/
+bool operator==(const ArchiveEntry &lhs, const ArchiveEntry &rhs)
+{
+ return lhs.path == rhs.path
+ && lhs.utcTime == rhs.utcTime
+ && lhs.isDirectory == rhs.isDirectory
+ && lhs.compressedSize == rhs.compressedSize
+ && lhs.uncompressedSize == rhs.uncompressedSize
+ && lhs.permissions_mode == rhs.permissions_mode
+ && (lhs.permissions_enum == rhs.permissions_enum // ignore invalid permissions
+ || lhs.permissions_enum == static_cast<QFile::Permissions>(-1)
+ || rhs.permissions_enum == static_cast<QFile::Permissions>(-1));
+}
+
+} // namespace QInstaller
diff --git a/src/libs/installer/abstractarchive.h b/src/libs/installer/abstractarchive.h
new file mode 100644
index 000000000..55489eb14
--- /dev/null
+++ b/src/libs/installer/abstractarchive.h
@@ -0,0 +1,119 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#ifndef ABSTRACTARCHIVE_H
+#define ABSTRACTARCHIVE_H
+
+#include "installer_global.h"
+
+#include <QFile>
+#include <QDateTime>
+#include <QDataStream>
+#include <QPoint>
+
+#ifdef Q_OS_WIN
+typedef int mode_t;
+#endif
+
+namespace QInstaller {
+
+struct INSTALLER_EXPORT ArchiveEntry
+{
+ ArchiveEntry()
+ : isDirectory(false)
+ , compressedSize(0)
+ , uncompressedSize(0)
+ , permissions_mode(0)
+ {}
+
+ QString path;
+ QDateTime utcTime;
+ QPoint archiveIndex;
+ bool isDirectory;
+ quint64 compressedSize;
+ quint64 uncompressedSize;
+ mode_t permissions_mode;
+ QFile::Permissions permissions_enum;
+};
+
+class INSTALLER_EXPORT AbstractArchive : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(AbstractArchive)
+
+public:
+ enum CompressionLevel {
+ Non = 0,
+ Fastest = 1,
+ Fast = 3,
+ Normal = 5,
+ Maximum = 7,
+ Ultra = 9
+ };
+ Q_ENUM(CompressionLevel)
+
+ explicit AbstractArchive(QObject *parent = nullptr);
+ virtual ~AbstractArchive() = 0;
+
+ virtual bool open(QIODevice::OpenMode mode) = 0;
+ virtual void close() = 0;
+ virtual void setFilename(const QString &filename) = 0;
+
+ virtual QString errorString() const;
+
+ virtual bool extract(const QString &dirPath) = 0;
+ virtual bool extract(const QString &dirPath, const quint64 totalFiles) = 0;
+ virtual bool create(const QStringList &data) = 0;
+ virtual QVector<ArchiveEntry> list() = 0;
+ virtual bool isSupported() = 0;
+
+ virtual void setCompressionLevel(const CompressionLevel level);
+
+Q_SIGNALS:
+ void currentEntryChanged(const QString &filename);
+ void completedChanged(const quint64 completed, const quint64 total);
+
+public Q_SLOTS:
+ virtual void cancel() = 0;
+
+protected:
+ void setErrorString(const QString &error);
+ CompressionLevel compressionLevel() const;
+
+private:
+ QString m_error;
+ CompressionLevel m_compressionLevel;
+};
+
+INSTALLER_EXPORT QDataStream &operator>>(QDataStream &istream, ArchiveEntry &entry);
+INSTALLER_EXPORT QDataStream &operator<<(QDataStream &ostream, const ArchiveEntry &entry);
+INSTALLER_EXPORT bool operator==(const ArchiveEntry &lhs, const ArchiveEntry &rhs);
+
+} // namespace QInstaller
+
+#endif // ABSTRACTARCHIVE_H
diff --git a/src/libs/installer/adminauthorization_x11.cpp b/src/libs/installer/adminauthorization_x11.cpp
index 14691c9de..f4951d523 100644
--- a/src/libs/installer/adminauthorization_x11.cpp
+++ b/src/libs/installer/adminauthorization_x11.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -28,41 +28,18 @@
#include "adminauthorization.h"
-#include <QtCore/QFile>
-#include <QtCore/QRegExp>
-#include <QDebug>
+#include "globals.h"
+#include <QDebug>
#include <QApplication>
-#include <QInputDialog>
#include <QMessageBox>
+#include <QProcess>
-#include <cstdlib>
-#include <sys/resource.h>
#include <unistd.h>
-#include <fcntl.h>
-
-#ifdef Q_OS_LINUX
-#include <linux/limits.h>
-#include <pty.h>
-#else
-#ifdef Q_OS_FREEBSD
-#include <libutil.h>
-#include <signal.h>
-#else
-#include <util.h>
-#endif
-#endif
-#include <sys/ioctl.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <errno.h>
#include <iostream>
-#include "globals.h"
-
-#define SU_COMMAND "/usr/bin/sudo"
-//#define SU_COMMAND "/bin/echo"
+#define PKEXEC_COMMAND "/usr/bin/pkexec"
namespace QInstaller {
@@ -72,24 +49,6 @@ namespace QInstaller {
\internal
*/
-static QString getPassword(QWidget *parent)
-{
- if (qobject_cast<QApplication*> (qApp) != 0) {
- bool ok = false;
- const QString result = QInputDialog::getText(parent, QObject::tr("Authorization required"),
- QObject::tr("Enter your password to authorize for sudo:"),
- QLineEdit::Password, QString(), &ok);
- return ok ? result : QString();
- } else {
- std::cout << QObject::tr("Authorization required").toStdString() << std::endl;
- std::cout << QObject::tr("Enter your password to authorize for sudo:").toStdString()
- << std::endl;
- std::string password;
- std::cin >> password;
- return QString::fromStdString(password);
- }
-}
-
static void printError(QWidget *parent, const QString &value)
{
if (qobject_cast<QApplication*> (qApp) != 0) {
@@ -105,184 +64,21 @@ bool AdminAuthorization::execute(QWidget *parent, const QString &program, const
const QString fallback = program + QLatin1String(" ") + arguments.join(QLatin1String(" "));
qCDebug(QInstaller::lcServer) << "Fallback:" << fallback;
- // as we cannot pipe the password to su in QProcess, we need to setup a pseudo-terminal for it
- int masterFD = -1;
- int slaveFD = -1;
- char ptsn[ PATH_MAX ];
+ QProcess process;
+ process.setProcessChannelMode(QProcess::MergedChannels);
+ process.start(QLatin1String(PKEXEC_COMMAND), QStringList() << program << arguments, QIODevice::ReadOnly);
- if (::openpty(&masterFD, &slaveFD, ptsn, 0, 0))
- return false;
-
- masterFD = ::posix_openpt(O_RDWR | O_NOCTTY);
- if (masterFD < 0)
- return false;
-
- const QByteArray ttyName = ::ptsname(masterFD);
-
- if (::grantpt(masterFD)) {
- ::close(masterFD);
- return false;
- }
-
- ::revoke(ttyName);
- ::unlockpt(masterFD);
-
- slaveFD = ::open(ttyName, O_RDWR | O_NOCTTY);
- if (slaveFD < 0) {
- ::close(masterFD);
+ if (!process.waitForStarted() || !process.waitForFinished(-1)) {
+ printError(parent, process.errorString());
+ if (process.state() > QProcess::NotRunning)
+ process.kill();
return false;
}
-
- ::fcntl(masterFD, F_SETFD, FD_CLOEXEC);
- ::fcntl(slaveFD, F_SETFD, FD_CLOEXEC);
- int pipedData[2];
- if (pipe(pipedData) != 0)
- return false;
-
- int flags = ::fcntl(pipedData[0], F_GETFL);
- if (flags != -1)
- ::fcntl(pipedData[0], F_SETFL, flags | O_NONBLOCK);
-
- flags = ::fcntl(masterFD, F_GETFL);
- if (flags != -1)
- ::fcntl(masterFD, F_SETFL, flags | O_NONBLOCK);
-
- pid_t child = fork();
-
- if (child < -1) {
- ::close(masterFD);
- ::close(slaveFD);
- ::close(pipedData[0]);
- ::close(pipedData[1]);
- return false;
- }
-
- // parent process
- else if (child > 0) {
- ::close(slaveFD);
- //close writing end of pipe
- ::close(pipedData[1]);
-
- QRegExp re(QLatin1String("[Pp]assword.*:"));
- QByteArray data;
- QByteArray errData;
- int bytes = 0;
- int errBytes = 0;
- char buf[1024];
- char errBuf[1024];
- int status;
- bool statusValid = false;
- while (bytes >= 0) {
- const pid_t waitResult = ::waitpid(child, &status, WNOHANG);
- if (waitResult == -1) {
- break;
- }
- if (waitResult == child) {
- statusValid = true;
- break;
- }
- bytes = ::read(masterFD, buf, 1023);
- if (bytes == -1 && errno == EAGAIN)
- bytes = 0;
- else if (bytes > 0)
- data.append(buf, bytes);
- errBytes = ::read(pipedData[0], errBuf, 1023);
- if (errBytes > 0)
- {
- errData.append(errBuf, errBytes);
- errBytes=0;
- }
- if (bytes > 0) {
- const QString line = QString::fromLatin1(data);
- if (re.indexIn(line) != -1) {
- const QString password = getPassword(parent);
- if (password.isEmpty()) {
- QByteArray pwd = password.toLatin1();
- for (int i = 0; i < 3; ++i) {
- ::write(masterFD, pwd.data(), pwd.length());
- ::write(masterFD, "\n", 1);
- }
- return false;
- }
- QByteArray pwd = password.toLatin1();
- ::write(masterFD, pwd.data(), pwd.length());
- ::write(masterFD, "\n", 1);
- ::read(masterFD, buf, pwd.length() + 1);
- }
- }
- if (bytes == 0)
- ::usleep(100000);
- }
-
- while (true) {
- errBytes = ::read(pipedData[0], errBuf, 1023);
- if (errBytes == -1 && errno == EAGAIN) {
- ::usleep(100000);
- continue;
- }
-
- if (errBytes <= 0)
- break;
-
- errData.append(errBuf, errBytes);
- }
-
- const bool success = statusValid && WIFEXITED(status) && WEXITSTATUS(status) == 0;
-
- if (!success && !errData.isEmpty()) {
- printError(parent, QString::fromLocal8Bit(errData.constData()));
- }
-
- ::close(pipedData[0]);
- return success;
- }
-
- // child process
- else {
- ::close(pipedData[0]);
- // Reset signal handlers
- for (int sig = 1; sig < NSIG; ++sig)
- signal(sig, SIG_DFL);
- signal(SIGHUP, SIG_IGN);
-
- ::setsid();
-
- ::ioctl(slaveFD, TIOCSCTTY, 1);
- int pgrp = ::getpid();
- ::tcsetpgrp(slaveFD, pgrp);
-
- ::dup2(slaveFD, 0);
- ::dup2(slaveFD, 1);
- ::dup2(pipedData[1], 2);
-
- // close all file descriptors
- struct rlimit rlp;
- getrlimit(RLIMIT_NOFILE, &rlp);
- for (int i = 3; i < static_cast<int>(rlp.rlim_cur); ++i)
- ::close(i);
-
- char **argp = (char **) ::malloc((arguments.count() + 4) * sizeof(char *));
- QList<QByteArray> args;
- args.push_back(SU_COMMAND);
- args.push_back("-b");
- args.push_back(program.toLocal8Bit());
- for (QStringList::const_iterator it = arguments.begin(); it != arguments.end(); ++it)
- args.push_back(it->toLocal8Bit());
-
- int i = 0;
- for (QList<QByteArray>::iterator it = args.begin(); it != args.end(); ++it, ++i)
- argp[i] = it->data();
- argp[i] = 0;
-
- ::unsetenv("LANG");
- ::unsetenv("LC_ALL");
-
- int exitStatus = 0;
- if (::execv(SU_COMMAND, argp) == -1)
- exitStatus = -errno;
- _exit(exitStatus);
+ if (process.exitCode() != EXIT_SUCCESS) {
+ printError(parent, QLatin1String(process.readAll()));
return false;
}
+ return true;
}
// has no guarantee to work
diff --git a/src/libs/installer/archivefactory.cpp b/src/libs/installer/archivefactory.cpp
new file mode 100644
index 000000000..5bfabe943
--- /dev/null
+++ b/src/libs/installer/archivefactory.cpp
@@ -0,0 +1,151 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#include "archivefactory.h"
+#ifdef IFW_LIBARCHIVE
+#include "libarchivewrapper.h"
+#else
+#include "lib7zarchive.h"
+#endif
+
+#include <QFileInfo>
+
+using namespace QInstaller;
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::ArchiveFactory
+ \brief The ArchiveFactory class is used to create archive objects
+ based on the suffix of a given filename.
+
+ This class acts as a factory for \c QInstaller::AbstractArchive. You can
+ register one or more archive handlers with this factory and create
+ registered objects based on the file suffix.
+
+ This class follows the singleton design pattern. Only one instance
+ of this class can be created and its reference can be fetched from
+ the \c {instance()} method.
+
+ Depending of the configuration features set at build time, one of the
+ following archive handlers is registered by default:
+ \list
+ \li Lib7z
+ \li LibArchive
+ \endlist
+*/
+
+/*!
+ \fn void QInstaller::ArchiveFactory::registerArchive(const QString &name, const QStringList &types)
+
+ Registers a new archive handler with the factory based on \a name and list
+ of supported file suffix \a types.
+*/
+
+
+/*!
+ Returns the only instance of this class.
+*/
+ArchiveFactory &ArchiveFactory::instance()
+{
+ static ArchiveFactory instance;
+ return instance;
+}
+
+/*!
+ Constructs and returns a pointer to an archive object with \a filename and \a parent.
+ If the archive type referenced by \a filename is not registered, a null pointer is
+ returned instead.
+*/
+AbstractArchive *ArchiveFactory::create(const QString &filename, QObject *parent) const
+{
+ const QString suffix = QFileInfo(filename).completeSuffix();
+ QString name;
+ for (auto &types : m_supportedTypesHash) {
+ QStringList::const_iterator it;
+ for (it = types.constBegin(); it != types.constEnd(); ++it) {
+ if (suffix.endsWith(*it, Qt::CaseInsensitive)) {
+ name = m_supportedTypesHash.key(types);
+ break;
+ }
+ }
+ }
+ if (name.isEmpty())
+ return nullptr;
+
+ AbstractArchive *archive = GenericFactory<AbstractArchive, QString, QString, QObject *>
+ ::create(name, filename, parent);
+
+ return archive;
+}
+
+/*!
+ Returns a list of supported archive types.
+*/
+QStringList ArchiveFactory::supportedTypes()
+{
+ QStringList types;
+ QHash<QString, QStringList> *const typesHash = &instance().m_supportedTypesHash;
+ for (auto &value : *typesHash)
+ types.append(value);
+
+ return types;
+}
+
+/*!
+ Returns \c true if the archive type from \a filename is registered with
+ an archive handler.
+*/
+bool ArchiveFactory::isSupportedType(const QString &filename)
+{
+ const QString suffix = QFileInfo(filename).completeSuffix();
+ QHash<QString, QStringList> *const typesHash = &instance().m_supportedTypesHash;
+ for (auto &types : *typesHash) {
+ QStringList::const_iterator it;
+ for (it = types.constBegin(); it != types.constEnd(); ++it) {
+ if (suffix.endsWith(*it, Qt::CaseInsensitive))
+ return true;
+ }
+ }
+ return false;
+}
+
+/*!
+ Private constructor for ArchiveFactory. Registers default archive handlers.
+*/
+ArchiveFactory::ArchiveFactory()
+{
+#ifdef IFW_LIBARCHIVE
+ registerArchive<LibArchiveWrapper>(QLatin1String("LibArchive"), QStringList()
+ << QLatin1String("tar") << QLatin1String("tar.gz") << QLatin1String("tar.bz2")
+ << QLatin1String("tar.xz") << QLatin1String("zip") << QLatin1String("7z")
+ << QLatin1String("qbsp"));
+#else
+ registerArchive<Lib7zArchive>(QLatin1String("Lib7z"), QStringList()
+ << QLatin1String("7z") << QLatin1String("qbsp"));
+#endif
+}
diff --git a/src/libs/installer/archivefactory.h b/src/libs/installer/archivefactory.h
new file mode 100644
index 000000000..6f545eefa
--- /dev/null
+++ b/src/libs/installer/archivefactory.h
@@ -0,0 +1,69 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#ifndef ARCHIVEFACTORY_H
+#define ARCHIVEFACTORY_H
+
+#include "installer_global.h"
+#include "genericfactory.h"
+#include "abstractarchive.h"
+
+namespace QInstaller {
+
+class INSTALLER_EXPORT ArchiveFactory
+ : public GenericFactory<AbstractArchive, QString, QString, QObject *>
+{
+ Q_DISABLE_COPY(ArchiveFactory)
+
+public:
+ static ArchiveFactory &instance();
+
+ template <typename T>
+ void registerArchive(const QString &name, const QStringList &types)
+ {
+ if (containsProduct(name))
+ m_supportedTypesHash.remove(name);
+
+ registerProduct<T>(name);
+ m_supportedTypesHash.insert(name, types);
+ }
+ AbstractArchive *create(const QString &filename, QObject *parent = nullptr) const;
+
+ static QStringList supportedTypes();
+ static bool isSupportedType(const QString &filename);
+
+private:
+ ArchiveFactory();
+
+private:
+ QHash<QString, QStringList> m_supportedTypesHash;
+};
+
+} // namespace QInstaller
+
+#endif // ARCHIVEFACTORY_H
diff --git a/src/libs/installer/aspectratiolabel.cpp b/src/libs/installer/aspectratiolabel.cpp
index a9af93a55..c8c3c1693 100644
--- a/src/libs/installer/aspectratiolabel.cpp
+++ b/src/libs/installer/aspectratiolabel.cpp
@@ -81,9 +81,11 @@ QSize AspectRatioLabel::sizeHint() const
*/
QPixmap AspectRatioLabel::scaledPixmap() const
{
- return m_pixmap.isNull()
- ? QPixmap()
- : m_pixmap.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ if (m_pixmap.isNull())
+ return QPixmap();
+
+ return m_pixmap.scaled(size() * m_pixmap.devicePixelRatio(),
+ Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
/*!
diff --git a/src/libs/installer/aspectratiolabel.h b/src/libs/installer/aspectratiolabel.h
index 28fc5aea4..6fbc774a2 100644
--- a/src/libs/installer/aspectratiolabel.h
+++ b/src/libs/installer/aspectratiolabel.h
@@ -44,12 +44,12 @@ class INSTALLER_EXPORT AspectRatioLabel : public QLabel
public:
explicit AspectRatioLabel(QWidget *parent = nullptr);
- int heightForWidth(int w) const;
- QSize sizeHint() const;
+ int heightForWidth(int w) const Q_DECL_OVERRIDE;
+ QSize sizeHint() const Q_DECL_OVERRIDE;
public slots:
void setPixmap (const QPixmap &pixmap);
- void resizeEvent(QResizeEvent *event);
+ void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
private:
QPixmap scaledPixmap() const;
diff --git a/src/libs/installer/binarycontent.h b/src/libs/installer/binarycontent.h
index a21cd69e7..7d50c0e28 100644
--- a/src/libs/installer/binarycontent.h
+++ b/src/libs/installer/binarycontent.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -48,6 +48,13 @@ public:
static const qint64 MagicUpdaterMarker = 0x12023235UL;
static const qint64 MagicPackageManagerMarker = 0x12023236UL;
+ // additional distinguishers only used at runtime, not written to the binary itself
+ enum MagicMarkerSupplement {
+ Default = 0x0,
+ OfflineGenerator = 0x1,
+ PackageViewer = 0x2
+ };
+
// the cookie put at the end of the file
static const quint64 MagicCookie = 0xc2630a1c99d668f8LL; // binary
static const quint64 MagicCookieDat = 0xc2630a1c99d668f9LL; // data
diff --git a/src/libs/installer/commandlineparser.cpp b/src/libs/installer/commandlineparser.cpp
index 74f0b5b3e..4bfc4ad3a 100644
--- a/src/libs/installer/commandlineparser.cpp
+++ b/src/libs/installer/commandlineparser.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -40,7 +40,7 @@ static const QLatin1String scInstallerValue("InstallerValue");
} // namespace CommandLineOptions
CommandLineParser::CommandLineParser()
- : d(new CommandLineParserPrivate())
+ : d(new CommandLineParserPrivate(this))
{
static const QLatin1String indent(" ");
static const QString preformatted = QLatin1String("\nQt Installer Framework supports both GUI and "
@@ -55,10 +55,15 @@ CommandLineParser::CommandLineParser()
.arg(CommandLineOptions::scUpdateShort, CommandLineOptions::scUpdateLong)
+ indent + QString::fromLatin1("%1, %2 - uninstall packages and their child components - <pkg ...>\n")
.arg(CommandLineOptions::scRemoveShort, CommandLineOptions::scRemoveLong)
- + indent + QString::fromLatin1("%1, %2 - list currently installed packages\n")
+ + indent + QString::fromLatin1("%1, %2 - list currently installed packages - <regexp>\n")
.arg(CommandLineOptions::scListShort, CommandLineOptions::scListLong)
+ indent + QString::fromLatin1("%1, %2 - search available packages - <regexp>\n")
.arg(CommandLineOptions::scSearchShort, CommandLineOptions::scSearchLong)
+ + indent + indent + QString::fromLatin1("Note: The --%1 option can be used to specify\n")
+ .arg(CommandLineOptions::scFilterPackagesLong)
+ + indent + indent + QLatin1String("additional filters for the search operation\n")
+ + indent + QString::fromLatin1("%1, %2 - create offline installer from selected packages - <pkg ...>\n")
+ .arg(CommandLineOptions::scCreateOfflineShort, CommandLineOptions::scCreateOfflineLong)
+ indent + QString::fromLatin1("%1, %2 - uninstall all packages and remove entire program directory")
.arg(CommandLineOptions::scPurgeShort, CommandLineOptions::scPurgeLong);
@@ -66,17 +71,17 @@ CommandLineParser::CommandLineParser()
// Help & version information
m_parser.addHelpOption();
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scVersionShort << CommandLineOptions::scVersionLong,
QLatin1String("Displays version information.")));
// Output related options
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scVerboseShort << CommandLineOptions::scVerboseLong,
QString::fromLatin1("Verbose mode. Prints out more information. Adding -%1 or --%2 more "
"than once increases verbosity.").arg(CommandLineOptions::scVerboseShort,
CommandLineOptions::scVerboseLong)));
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scLoggingRulesShort << CommandLineOptions::scLoggingRulesLong,
QLatin1String("Enables logging according to passed rules. Comma separated logging rules "
"have the following syntax: loggingCategory=true/false. Passing empty logging "
@@ -86,15 +91,15 @@ CommandLineParser::CommandLineParser()
QLatin1String("rules")));
// Repository management options
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scAddRepositoryShort << CommandLineOptions::scAddRepositoryLong,
QLatin1String("Add a local or remote repository to the list of user defined repositories."),
QLatin1String("URI,...")));
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scAddTmpRepositoryShort << CommandLineOptions::scAddTmpRepositoryLong,
QLatin1String("Add a local or remote repository to the list of temporary available repositories."),
QLatin1String("URI,...")));
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scSetTmpRepositoryShort << CommandLineOptions::scSetTmpRepositoryLong,
QLatin1String("Set a local or remote repository as temporary repository, it is the only "
"one used during fetch.\nNote: URI must be prefixed with the protocol, i.e. "
@@ -102,106 +107,117 @@ CommandLineParser::CommandLineParser()
QLatin1String("URI,...")));
// Proxy options
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scSystemProxyShort << CommandLineOptions::scSystemProxyLong,
QLatin1String("Use system proxy on Windows and Linux. This option has no effect on macOS. (Default)")));
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scNoProxyShort << CommandLineOptions::scNoProxyLong,
QLatin1String("Do not use system proxy.")));
// Starting mode options
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scStartUpdaterShort << CommandLineOptions::scStartUpdaterLong,
QLatin1String("Start application in updater mode. This will override the internal "
"marker that is used to distinguish which kind of binary is currently running.")));
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scStartPackageManagerShort << CommandLineOptions::scStartPackageManagerLong,
QLatin1String("Start application in package manager mode. This will override the internal "
"marker that is used to distinguish which kind of binary is currently running.")));
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scStartUninstallerShort << CommandLineOptions::scStartUninstallerLong,
QLatin1String("Start application in uninstaller mode. This will override the internal "
"marker that is used to distinguish which kind of binary is currently running.")));
// Misc installation options
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOptionWithContext(QCommandLineOption(QStringList()
<< CommandLineOptions::scRootShort << CommandLineOptions::scRootLong,
QLatin1String("[CLI] Set installation root directory."),
- QLatin1String("directory")));
- m_parser.addOption(QCommandLineOption(QStringList()
+ QLatin1String("directory")), CommandLineOnly);
+ addOptionWithContext(QCommandLineOption(QStringList()
+ << CommandLineOptions::scOfflineInstallerNameShort << CommandLineOptions::scOfflineInstallerNameLong,
+ QLatin1String("[CLI] Set custom filename for the generated offline installer. Without this "
+ "the original filename is used with an added \"_offline-yyyy-MM-dd\" suffix."),
+ QLatin1String("filename")), CommandLineOnly);
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scPlatformShort << CommandLineOptions::scPlatformLong,
QLatin1String("Use the specified platform plugin."),
QLatin1String("plugin")));
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scNoForceInstallationShort << CommandLineOptions::scNoForceInstallationLong,
QLatin1String("Allow deselecting components that are marked as forced.")));
- m_parser.addOption(QCommandLineOption(QStringList() << CommandLineOptions::scNoDefaultInstallationShort
+ addOption(QCommandLineOption(QStringList() << CommandLineOptions::scNoDefaultInstallationShort
<< CommandLineOptions::scNoDefaultInstallationLong,
QLatin1String("Deselects components that are marked as default.")));
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scNoSizeCheckingShort << CommandLineOptions::scNoSizeCheckingLong,
QLatin1String("Disable checking of free space for installation target.")));
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scShowVirtualComponentsShort << CommandLineOptions::scShowVirtualComponentsLong,
QLatin1String("Show virtual components in installer and package manager.")));
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scInstallCompressedRepositoryShort
<< CommandLineOptions::scInstallCompressedRepositoryLong,
QLatin1String("Installs QBSP or 7z file. The QBSP (Board Support Package) file must be a .7z "
"file which contains a valid repository."),
QLatin1String("URI,...")));
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scCreateLocalRepositoryShort << CommandLineOptions::scCreateLocalRepositoryLong,
QLatin1String("Create a local repository inside the installation directory. This option "
"has no effect on online installers.")));
+ addOptionWithContext(QCommandLineOption(QStringList()
+ << CommandLineOptions::scFilterPackagesShort << CommandLineOptions::scFilterPackagesLong,
+ QLatin1String("[CLI] Comma separated list of additional key-value pair filters used to query packages with the "
+ "search command. The keys can be any of the possible package information elements, like "
+ "\"DisplayName\" and \"Description\"."),
+ QLatin1String("element=regex,...")), CommandLineOnly);
// Message query options
- m_parser.addOption(QCommandLineOption(QStringList() << CommandLineOptions::scAcceptMessageQueryShort
+ addOptionWithContext(QCommandLineOption(QStringList() << CommandLineOptions::scAcceptMessageQueryShort
<< CommandLineOptions::scAcceptMessageQueryLong,
- QLatin1String("[CLI] Accepts all message queries without user input.")));
- m_parser.addOption(QCommandLineOption(QStringList() << CommandLineOptions::scRejectMessageQueryShort
+ QLatin1String("[CLI] Accepts all message queries without user input.")), CommandLineOnly);
+ addOptionWithContext(QCommandLineOption(QStringList() << CommandLineOptions::scRejectMessageQueryShort
<< CommandLineOptions::scRejectMessageQueryLong,
- QLatin1String("[CLI] Rejects all message queries without user input.")));
- m_parser.addOption(QCommandLineOption(QStringList() << CommandLineOptions::scMessageAutomaticAnswerShort
+ QLatin1String("[CLI] Rejects all message queries without user input.")), CommandLineOnly);
+ addOptionWithContext(QCommandLineOption(QStringList() << CommandLineOptions::scMessageAutomaticAnswerShort
<< CommandLineOptions::scMessageAutomaticAnswerLong,
QLatin1String("[CLI] Automatically answers the message queries with the message identifier and button value. "
"Several identifier=value pairs can be given separated with comma, "
"for example --auto-answer message.id=Ok,message.id2=Cancel."),
- QLatin1String("identifier=value")));
- m_parser.addOption(QCommandLineOption(QStringList() << CommandLineOptions::scMessageDefaultAnswerShort
+ QLatin1String("identifier=value")), CommandLineOnly);
+ addOptionWithContext(QCommandLineOption(QStringList() << CommandLineOptions::scMessageDefaultAnswerShort
<< CommandLineOptions::scMessageDefaultAnswerLong,
- QLatin1String("[CLI] Automatically answers to message queries with their default values.")));
- m_parser.addOption(QCommandLineOption(QStringList() << CommandLineOptions::scAcceptLicensesShort
+ QLatin1String("[CLI] Automatically answers to message queries with their default values.")), CommandLineOnly);
+ addOptionWithContext(QCommandLineOption(QStringList() << CommandLineOptions::scAcceptLicensesShort
<< CommandLineOptions::scAcceptLicensesLong,
- QLatin1String("[CLI] Accepts all licenses without user input.")));
- m_parser.addOption(QCommandLineOption(QStringList() << CommandLineOptions::scFileDialogAutomaticAnswer,
+ QLatin1String("[CLI] Accepts all licenses without user input.")), CommandLineOnly);
+ addOptionWithContext(QCommandLineOption(QStringList() << CommandLineOptions::scFileDialogAutomaticAnswer,
QLatin1String("[CLI] Automatically sets the QFileDialog values getExistingDirectory() or getOpenFileName() "
"requested by install script. "
"Several identifier=value pairs can be given separated with comma, "
"for example --file-query filedialog.id=C:\Temp,filedialog.id2=C:\Temp2"),
- QLatin1String("identifier=value")));
- m_parser.addOption(QCommandLineOption(QStringList() << CommandLineOptions::scConfirmCommandShort
+ QLatin1String("identifier=value")), CommandLineOnly);
+ addOptionWithContext(QCommandLineOption(QStringList() << CommandLineOptions::scConfirmCommandShort
<< CommandLineOptions::scConfirmCommandLong, QLatin1String("[CLI] Confirms starting of "
- "installation, update or removal of components without user input.")));
+ "installation, update or removal of components without user input.")), CommandLineOnly);
// Developer options
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scScriptShort << CommandLineOptions::scScriptLong,
QLatin1String("Execute the script given as argument."),
QLatin1String("file")));
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scStartServerShort << CommandLineOptions::scStartServerLong,
QLatin1String("Starts the application as headless process waiting for commands to execute. Mode "
"can be DEBUG or PRODUCTION. In DEBUG mode, the option values can be omitted. Note: "
"The server will not shutdown on his own, you need to quit the process by hand."),
QLatin1String("mode, socketname, key")));
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scStartClientShort << CommandLineOptions::scStartClientLong,
QLatin1String("Starts the application to debug the client-server communication. If a value is "
"omitted, the client will use a default instead. Note: The server process is not "
"started by the client application in that case, you need to start it on your own."),
QLatin1String("socketname, key")));
- m_parser.addOption(QCommandLineOption(QStringList()
+ addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scSquishPortShort << CommandLineOptions::scSquishPortLong,
QLatin1String("Give a port where Squish can connect to. If no port is given, default port 11233 is "
"used. Note: To enable Squish support you first need to build IFW with SQUISH_PATH "
@@ -212,14 +228,14 @@ CommandLineParser::CommandLineParser()
// Deprecated options
QCommandLineOption deprecatedUpdater(CommandLineOptions::scDeprecatedUpdater);
deprecatedUpdater.setHidden(true);
- m_parser.addOption(deprecatedUpdater);
+ addOption(deprecatedUpdater);
QCommandLineOption deprecatedCheckUpdates(CommandLineOptions::scDeprecatedCheckUpdates);
deprecatedCheckUpdates.setHidden(true);
- m_parser.addOption(deprecatedCheckUpdates); // Behaves like check-updates but does not default to verbose output
+ addOption(deprecatedCheckUpdates); // Behaves like check-updates but does not default to verbose output
// Custom extension options
- m_parser.addOptions(d->extensionsOptions());
+ d->addExtensionsOptions();
// Positional arguments
m_parser.addPositionalArgument(CommandLineOptions::scCommand,
@@ -237,3 +253,19 @@ CommandLineParser::~CommandLineParser()
{
delete d;
}
+
+bool CommandLineParser::addOptionWithContext(const QCommandLineOption &option, CommandLineParser::OptionContextFlags flags)
+{
+ if (!m_parser.addOption(option))
+ return false;
+
+ for (auto &name : option.names())
+ m_optionContextFlagsNameHash.insert(name, flags);
+
+ return true;
+}
+
+CommandLineParser::OptionContextFlags CommandLineParser::optionContextFlags(const QString &option) const
+{
+ return m_optionContextFlagsNameHash.value(option);
+}
diff --git a/src/libs/installer/commandlineparser.h b/src/libs/installer/commandlineparser.h
index d1ad31eb5..8f112fcb9 100644
--- a/src/libs/installer/commandlineparser.h
+++ b/src/libs/installer/commandlineparser.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -36,9 +36,17 @@
class CommandLineParser
{
public:
+ enum OptionContextFlag {
+ CommandLineOnly = 0x1
+ };
+ Q_DECLARE_FLAGS(OptionContextFlags, OptionContextFlag)
+
CommandLineParser();
~CommandLineParser();
+ bool addOption(const QCommandLineOption &option) { return m_parser.addOption(option); }
+ bool addOptionWithContext(const QCommandLineOption &option, OptionContextFlags flags);
+
QString helpText() const { return m_parser.helpText(); }
bool isSet(const QString &option) { return m_parser.isSet(option); }
QStringList unknownOptionNames() const { return m_parser.unknownOptionNames(); }
@@ -47,9 +55,13 @@ public:
QString value(const QString &option) const { return m_parser.value(option); }
QStringList optionNames() const { return m_parser.optionNames(); }
+ OptionContextFlags optionContextFlags(const QString &option) const;
+
private:
QCommandLineParser m_parser;
class CommandLineParserPrivate *const d;
+
+ QHash<QString, OptionContextFlags> m_optionContextFlagsNameHash;
};
#endif // COMMANDLINEPARSER_H
diff --git a/src/libs/installer/commandlineparser_p.cpp b/src/libs/installer/commandlineparser_p.cpp
index de9ca45dd..a6b2a4a50 100644
--- a/src/libs/installer/commandlineparser_p.cpp
+++ b/src/libs/installer/commandlineparser_p.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -28,6 +28,6 @@
#include "commandlineparser_p.h"
-CommandLineParserPrivate::CommandLineParserPrivate()
+void CommandLineParserPrivate::addExtensionsOptions() const
{
}
diff --git a/src/libs/installer/commandlineparser_p.h b/src/libs/installer/commandlineparser_p.h
index 1c050a389..3c0016b96 100644
--- a/src/libs/installer/commandlineparser_p.h
+++ b/src/libs/installer/commandlineparser_p.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -29,20 +29,23 @@
#ifndef COMMANDLINEPARSER_P_H
#define COMMANDLINEPARSER_P_H
+#include "commandlineparser.h"
+
#include <QCommandLineParser>
+class CommandLineParser;
+
class CommandLineParserPrivate
{
+ Q_DISABLE_COPY(CommandLineParserPrivate)
+
public:
- CommandLineParserPrivate();
+ explicit CommandLineParserPrivate(CommandLineParser *parser) : q(parser) {};
- QList<QCommandLineOption> &extensionsOptions()
- {
- return m_extensionOptions;
- }
+ void addExtensionsOptions() const;
private:
- QList<QCommandLineOption> m_extensionOptions;
+ CommandLineParser *const q;
};
#endif // COMMANDLINEPARSER_P_H
diff --git a/src/libs/installer/component.cpp b/src/libs/installer/component.cpp
index 5c74a2139..d4750cee9 100644
--- a/src/libs/installer/component.cpp
+++ b/src/libs/installer/component.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -31,7 +31,7 @@
#include "errors.h"
#include "fileutils.h"
#include "globals.h"
-#include "lib7z_facade.h"
+#include "archivefactory.h"
#include "messageboxhandler.h"
#include "packagemanagercore.h"
#include "remoteclient.h"
@@ -45,6 +45,7 @@
#include <QtCore/QDirIterator>
#include <QtCore/QRegExp>
#include <QtCore/QTranslator>
+#include <QtCore/QRegularExpression>
#include <QApplication>
@@ -86,6 +87,8 @@ static const QLatin1String scUnstable("Unstable");
Component script has errors or loading fails.
\value MissingDependency
Component has dependencies to missing components.
+ \value InvalidTreeName
+ Component has an invalid tree name.
*/
/*!
@@ -212,6 +215,16 @@ static const QLatin1String scUnstable("Unstable");
*/
/*!
+ \property QInstaller::Component::treeName
+
+ \brief The tree name of the component. Specifies the location of the component in the install tree view.
+
+ \note If the tree name is not set, the tree view is organized based on the component name.
+
+ \note Must be unique, the tree name must not conflict with other component's names or tree names.
+*/
+
+/*!
\fn QInstaller::Component::loaded()
\sa {component::loaded}{component.loaded}
@@ -237,6 +250,7 @@ static const QLatin1String scUnstable("Unstable");
*/
Component::Component(PackageManagerCore *core)
: d(new ComponentPrivate(core, this))
+ , m_defaultArchivePath(QLatin1String("@TargetDir@"))
{
setPrivate(d);
@@ -284,19 +298,21 @@ void Component::loadDataFromPackage(const KDUpdater::LocalPackage &package)
setValue(scAutoDependOn, package.autoDependencies.join(QLatin1String(",")));
setValue(scForcedInstallation, package.forcedInstallation ? scTrue : scFalse);
- if (package.forcedInstallation & !PackageManagerCore::noForceInstallation()) {
- setCheckable(false);
- setCheckState(Qt::Checked);
- }
setValue(scVirtual, package.virtualComp ? scTrue : scFalse);
setValue(scCurrentState, scInstalled);
setValue(scCheckable, package.checkable ? scTrue : scFalse);
setValue(scExpandedByDefault, package.expandedByDefault ? scTrue : scFalse);
+ setValue(scContentSha1, package.contentSha1);
+
+ setValue(scTreeName, package.treeName.first);
+ d->m_treeNameMoveChildren = package.treeName.second;
}
/*!
Sets variables according to the values set in the package.xml file of \a package.
Also loads UI files, licenses and translations if they are referenced in the package.xml.
+ If the \c PackageManagerCore object of this component is run as package viewer, then
+ only sets the variables without loading referenced files.
*/
void Component::loadDataFromPackage(const Package &package)
{
@@ -317,6 +333,7 @@ void Component::loadDataFromPackage(const Package &package)
setValue(scSortingPriority, package.data(scSortingPriority).toString());
setValue(scEssential, package.data(scEssential).toString());
+ setValue(scForcedUpdate, package.data(scForcedUpdate).toString());
setValue(scUpdateText, package.data(scUpdateText).toString());
setValue(scNewComponent, package.data(scNewComponent).toString());
setValue(scRequiresAdminRights, package.data(scRequiresAdminRights).toString());
@@ -331,25 +348,32 @@ void Component::loadDataFromPackage(const Package &package)
if (PackageManagerCore::noForceInstallation())
forced = scFalse;
setValue(scForcedInstallation, forced);
- if (forced == scTrue) {
- setCheckable(false);
- setCheckState(Qt::Checked);
- }
+ setValue(scContentSha1, package.data(scContentSha1).toString());
+
+ const auto treeNamePair = package.data(QLatin1String(scTreeName)).value<QPair<QString, bool>>();
+ setValue(scTreeName, treeNamePair.first);
+ d->m_treeNameMoveChildren = treeNamePair.second;
+
+ if (d->m_core->isPackageViewer())
+ return;
setLocalTempPath(QInstaller::pathFromUrl(package.packageSource().url));
const QStringList uis = package.data(QLatin1String("UserInterfaces")).toString()
- .split(QInstaller::commaRegExp(), QString::SkipEmptyParts);
+ .split(QInstaller::commaRegExp(), Qt::SkipEmptyParts);
if (!uis.isEmpty())
loadUserInterfaces(QDir(QString::fromLatin1("%1/%2").arg(localTempPath(), name())), uis);
#ifndef IFW_DISABLE_TRANSLATIONS
const QStringList qms = package.data(QLatin1String("Translations")).toString()
- .split(QInstaller::commaRegExp(), QString::SkipEmptyParts);
+ .split(QInstaller::commaRegExp(), Qt::SkipEmptyParts);
if (!qms.isEmpty())
loadTranslations(QDir(QString::fromLatin1("%1/%2").arg(localTempPath(), name())), qms);
#endif
QHash<QString, QVariant> licenseHash = package.data(QLatin1String("Licenses")).toHash();
if (!licenseHash.isEmpty())
loadLicenses(QString::fromLatin1("%1/%2/").arg(localTempPath(), name()), licenseHash);
+ QVariant operationsVariant = package.data(QLatin1String("Operations"));
+ if (operationsVariant.canConvert<QList<QPair<QString, QVariant>>>())
+ m_operationsList = operationsVariant.value<QList<QPair<QString, QVariant>>>();
}
/*!
@@ -359,16 +383,20 @@ quint64 Component::updateUncompressedSize()
{
quint64 size = 0;
- if (installAction() == ComponentModelHelper::Install
- || installAction() == ComponentModelHelper::KeepInstalled) {
+ const bool installOrKeepInstalled = (installAction() == ComponentModelHelper::Install
+ || installAction() == ComponentModelHelper::KeepInstalled);
+
+ if (installOrKeepInstalled)
size = d->m_vars.value(scUncompressedSize).toLongLong();
- }
foreach (Component* comp, d->m_allChildComponents)
size += comp->updateUncompressedSize();
setValue(scUncompressedSizeSum, QString::number(size));
- setData(humanReadableSize(size), UncompressedSize);
+ if (size == 0 && !installOrKeepInstalled)
+ setData(QVariant(), UncompressedSize);
+ else
+ setData(humanReadableSize(size), UncompressedSize);
return size;
}
@@ -404,6 +432,16 @@ QString Component::value(const QString &key, const QString &defaultValue) const
}
/*!
+ Removes all the values that have the \a key from the variables set for this component.
+ Returns the number of values removed which is 1 if the key exists in the variables,
+ and 0 otherwise.
+*/
+int Component::removeValue(const QString &key)
+{
+ return d->m_vars.remove(key);
+}
+
+/*!
Sets the value of the variable with \a key to \a value.
\sa {component::setValue}{component.setValue}
@@ -423,6 +461,12 @@ void Component::setValue(const QString &key, const QString &value)
this->setCheckable(normalizedValue.toLower() == scTrue);
if (key == scExpandedByDefault)
this->setExpandedByDefault(normalizedValue.toLower() == scTrue);
+ if (key == scForcedInstallation) {
+ if (value == scTrue && !PackageManagerCore::noForceInstallation()) {
+ setCheckable(false);
+ setCheckState(Qt::Checked);
+ }
+ }
d->m_vars[key] = normalizedValue;
emit valueChanged(key, normalizedValue);
@@ -520,6 +564,27 @@ QString Component::displayName() const
}
/*!
+ Returns this component's location in the tree view. If the tree name is not
+ set, returns the component name. The tree name must be unique, it must not
+ conflict with other tree names or component names.
+*/
+QString Component::treeName() const
+{
+ const QString defaultValue = d->m_vars.value(scAutoTreeName, name());
+ return d->m_vars.value(scTreeName, defaultValue);
+}
+
+/*!
+ Returns \c true if descendants of this component should have automatically
+ created tree names in relation to the parent component's modified location,
+ \c false otherwise.
+*/
+bool Component::treeNameMoveChildren() const
+{
+ return d->m_treeNameMoveChildren;
+}
+
+/*!
Loads the component script into the script engine.
*/
void Component::loadComponentScript()
@@ -592,15 +657,15 @@ void Component::loadTranslations(const QDir &directory, const QStringList &qms)
while (it.hasNext()) {
const QString filename = it.next();
const QString basename = QFileInfo(filename).baseName();
- if (!uiLanguage.startsWith(QFileInfo(filename).baseName(), Qt::CaseInsensitive))
- continue; // do not load the file if it does not match the UI language
if (!translations.isEmpty()) {
bool found = false;
foreach (const QString &translation, translations)
- found |= translation.startsWith(basename, Qt::CaseInsensitive);
+ found |= translation.startsWith(QLatin1String("ifw_") + basename, Qt::CaseInsensitive);
if (!found) // don't load the file if it does match the UI language but is not allowed to be used
continue;
+ } else if (!uiLanguage.startsWith(QFileInfo(filename).baseName(), Qt::CaseInsensitive)) {
+ continue; // do not load the file if it does not match the UI language
}
QScopedPointer<QTranslator> translator(new QTranslator(this));
@@ -644,13 +709,14 @@ void Component::loadUserInterfaces(const QDir &directory, const QStringList &uis
/*!
Loads the text of the licenses contained in \a licenseHash from \a directory.
- This is saved into a new hash containing the filename and the text of that file.
+ This is saved into a new hash containing the filename, the text and the priority of that file.
*/
void Component::loadLicenses(const QString &directory, const QHash<QString, QVariant> &licenseHash)
{
QHash<QString, QVariant>::const_iterator it;
for (it = licenseHash.begin(); it != licenseHash.end(); ++it) {
- const QString &fileName = it.value().toString();
+ QVariantMap license = it.value().toMap();
+ const QString &fileName = license.value(QLatin1String("file")).toString();
if (!ProductKeyCheck::instance()->isValidLicenseTextFile(fileName))
continue;
@@ -684,10 +750,44 @@ void Component::loadLicenses(const QString &directory, const QHash<QString, QVar
}
QTextStream stream(&file);
stream.setCodec("UTF-8");
- d->m_licenses.insert(it.key(), qMakePair(fileName, stream.readAll()));
+ license.insert(QLatin1String("content"), stream.readAll());
+ d->m_licenses.insert(it.key(), license);
+ }
+}
+
+/*!
+ Loads all operations defined in the component.xml except Extract operation.
+ Operations are added to the list of operations needed to install this component.
+*/
+void Component::loadXMLOperations()
+{
+ for (auto operation: m_operationsList) {
+ if (operation.first != QLatin1String("Extract"))
+ addOperation(operation.first, operation.second.toStringList());
}
}
+/*!
+ Loads all Extract operations defined in the component.xml.
+ Operations are overwriting the default implementation of Extract operation.
+*/
+void Component::loadXMLExtractOperations()
+{
+ for (auto operation: m_operationsList) {
+ if (operation.first == QLatin1String("Extract")) {
+ // Create hash for Extract operations. Operation has a mandatory extract folder as
+ // first argument and optional archive name as second argument.
+ const QStringList &operationArgs = operation.second.toStringList();
+ if (operationArgs.count() == 2) {
+ const QString archiveName = value(scVersion) + operationArgs.at(1);
+ const QString archivePath = QString::fromLatin1("installer://%1/%2").arg(name()).arg(archiveName);
+ m_archivesHash.insert(archivePath, operationArgs.at(0));
+ } else if (operationArgs.count() == 1) {
+ m_defaultArchivePath = operationArgs.at(0);
+ }
+ }
+ }
+}
/*!
\property QInstaller::Component::userInterfaces
@@ -700,9 +800,9 @@ QStringList Component::userInterfaces() const
}
/*!
- Returns a hash that contains the file names and text of license files for the component.
+ Returns a hash that contains the file names, text and priorities of license files for the component.
*/
-QHash<QString, QPair<QString, QString> > Component::licenses() const
+QHash<QString, QVariantMap> Component::licenses() const
{
return d->m_licenses;
}
@@ -793,11 +893,15 @@ void Component::createOperationsForArchive(const QString &archive)
return;
}
- const bool isZip = Lib7z::isSupportedArchive(archive);
+ QScopedPointer<AbstractArchive> archiveFile(ArchiveFactory::instance().create(archive));
+ const bool isZip = (archiveFile && archiveFile->open(QIODevice::ReadOnly) && archiveFile->isSupported());
if (isZip) {
- // archives get completely extracted per default (if the script isn't doing other stuff)
- addOperation(QLatin1String("Extract"), QStringList() << archive << QLatin1String("@TargetDir@"));
+ // component.xml can override this value
+ if (m_archivesHash.contains(archive))
+ addOperation(QLatin1String("Extract"), QStringList() << archive << m_archivesHash.value(archive));
+ else
+ addOperation(QLatin1String("Extract"), QStringList() << archive << m_defaultArchivePath);
} else {
createOperationsForPath(archive);
}
@@ -824,10 +928,11 @@ void Component::createOperations()
d->m_operationsCreated = true;
return;
}
-
+ loadXMLExtractOperations();
foreach (const QString &archive, archives())
createOperationsForArchive(archive);
+ loadXMLOperations();
d->m_operationsCreated = true;
}
@@ -967,9 +1072,11 @@ OperationList Component::operations() const
d->m_licenseOperation->setValue(QLatin1String("component"), name());
QVariantMap licenses;
- const QList<QPair<QString, QString> > values = d->m_licenses.values();
- for (int i = 0; i < values.count(); ++i)
- licenses.insert(values.at(i).first, values.at(i).second);
+ const QList<QVariantMap> values = d->m_licenses.values();
+ for (int i = 0; i < values.count(); ++i) {
+ licenses.insert(values.at(i).value(QLatin1String("file")).toString(),
+ values.at(i).value(QLatin1String("content")));
+ }
d->m_licenseOperation->setValue(QLatin1String("licenses"), licenses);
d->m_operations.append(d->m_licenseOperation);
}
@@ -1052,7 +1159,13 @@ Operation *Component::createOperation(const QString &operationName, const QStrin
if (operation->name() == QLatin1String("Delete"))
operation->setValue(QLatin1String("performUndo"), false);
- operation->setArguments(d->m_core->replaceVariables(parameters));
+ // Operation can contain variables which are resolved when performing the operation
+ if (operation->requiresUnreplacedVariables())
+ operation->setArguments(parameters);
+ else
+ operation->setArguments(d->m_core->replaceVariables(parameters));
+
+
operation->setValue(QLatin1String("component"), name());
return operation;
}
@@ -1226,6 +1339,8 @@ bool Component::validatePage()
/*!
Adds the component specified by \a newDependency to the list of dependencies.
+ Alternatively, multiple components can be specified by separating each with
+ a comma.
\sa {component::addDependency}{component.addDependency}
\sa dependencies
@@ -1242,11 +1357,13 @@ void Component::addDependency(const QString &newDependency)
QStringList Component::dependencies() const
{
- return d->m_vars.value(scDependencies).split(QInstaller::commaRegExp(), QString::SkipEmptyParts);
+ return d->m_vars.value(scDependencies).split(QInstaller::commaRegExp(), Qt::SkipEmptyParts);
}
/*!
Adds the component specified by \a newDependOn to the automatic depend-on list.
+ Alternatively, multiple components can be specified by separating each with
+ a comma.
\sa {component::addAutoDependOn}{component.addAutoDependOn}
\sa autoDependencies
@@ -1263,7 +1380,7 @@ void Component::addAutoDependOn(const QString &newDependOn)
QStringList Component::autoDependencies() const
{
- return d->m_vars.value(scAutoDependOn).split(QInstaller::commaRegExp(), QString::SkipEmptyParts);
+ return d->m_vars.value(scAutoDependOn).split(QInstaller::commaRegExp(), Qt::SkipEmptyParts);
}
/*!
@@ -1294,12 +1411,14 @@ bool Component::isAutoDependOn(const QSet<QString> &componentsToInstall) const
// essential updates needs to be installed first, otherwise non-essential components
// will be installed
if (packageManagerCore()->foundEssentialUpdate()) {
- const QSet<QString> autoDependOnSet = autoDependOnList.toSet();
+ const QSet<QString> autoDependOnSet(autoDependOnList.begin(), autoDependOnList.end());
if (componentsToInstall.contains(autoDependOnSet)) {
foreach (const QString &autoDep, autoDependOnSet) {
Component *component = packageManagerCore()->componentByName(autoDep);
- if (component->value(scEssential, scFalse).toLower() == scTrue)
+ if ((component->value(scEssential, scFalse).toLower() == scTrue)
+ || component->isForcedUpdate()) {
return true;
+ }
}
}
return false;
@@ -1406,6 +1525,17 @@ bool Component::componentChangeRequested()
return updateRequested() || isSelectedForInstallation() || uninstallationRequested();
}
+/*!
+ Returns \c true if the component is installed and has a \c ForcedUpdate flag set.
+ ForcedUpdate components will be updated together with essential components before
+ any other component can be updated or installed.
+
+ \sa {component::isForcedUpdate}{component.isForcedUpdate}
+*/
+bool Component::isForcedUpdate()
+{
+ return isInstalled() && (value(scForcedUpdate, scFalse).toLower() == scTrue);
+}
/*!
\sa {component::setUninstalled}{component.setUninstalled}
@@ -1543,26 +1673,23 @@ void Component::updateModelData(const QString &key, const QString &data)
setData(humanReadableSize(size), UncompressedSize);
}
+ QString tooltipText;
const QString &updateInfo = d->m_vars.value(scUpdateText);
if (!d->m_core->isUpdater() || updateInfo.isEmpty()) {
- QString tooltipText
- = QString::fromLatin1("<html><body>%1</body></html>").arg(d->m_vars.value(scDescription));
- if (isUnstable()) {
- tooltipText += QLatin1String("<br>") + tr("There was an error loading the selected component. "
- "This component can not be installed.");
- }
- setData(tooltipText, Qt::ToolTipRole);
+ tooltipText = QString::fromLatin1("<html><body>%1</body></html>").arg(d->m_vars.value(scDescription));
} else {
- QString tooltipText
- = d->m_vars.value(scDescription) + QLatin1String("<br><br>")
- + tr("Update Info: ") + updateInfo;
- if (isUnstable()) {
- tooltipText += QLatin1String("<br>") + tr("There was an error loading the selected component. "
- "This component can not be updated.");
- }
-
- setData(tooltipText, Qt::ToolTipRole);
+ tooltipText = d->m_vars.value(scDescription) + QLatin1String("<br><br>")
+ + tr("Update Info: ") + updateInfo;
+ }
+ if (isUnstable()) {
+ tooltipText += QLatin1String("<br>") + tr("There was an error loading the selected component. "
+ "This component cannot be installed.");
}
+ // replace {external-link}='' fields in component description with proper link tags
+ tooltipText.replace(QRegularExpression(QLatin1String("{external-link}='(.*?)'")),
+ QLatin1String("<a href=\"\\1\">\\1</a>"));
+
+ setData(tooltipText, Qt::ToolTipRole);
}
/*!
diff --git a/src/libs/installer/component.h b/src/libs/installer/component.h
index fd12a53f8..6d2784616 100644
--- a/src/libs/installer/component.h
+++ b/src/libs/installer/component.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -62,6 +62,7 @@ class INSTALLER_EXPORT Component : public QObject, public ComponentModelHelper
Q_PROPERTY(bool installed READ isInstalled)
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled)
Q_PROPERTY(bool unstable READ isUnstable)
+ Q_PROPERTY(QString treeName READ treeName)
public:
explicit Component(PackageManagerCore *core);
@@ -71,7 +72,8 @@ public:
DepencyToUnstable = 0,
ShaMismatch,
ScriptLoadingFailed,
- MissingDependency
+ MissingDependency,
+ InvalidTreeName
};
Q_ENUM(UnstableError)
@@ -105,6 +107,7 @@ public:
QHash<QString, QString> variables() const;
Q_INVOKABLE void setValue(const QString &key, const QString &value);
Q_INVOKABLE QString value(const QString &key, const QString &defaultValue = QString()) const;
+ int removeValue(const QString &key);
QStringList archives() const;
PackageManagerCore *packageManagerCore() const;
@@ -121,10 +124,12 @@ public:
void loadTranslations(const QDir &directory, const QStringList &qms);
void loadUserInterfaces(const QDir &directory, const QStringList &uis);
void loadLicenses(const QString &directory, const QHash<QString, QVariant> &hash);
+ void loadXMLOperations();
+ void loadXMLExtractOperations();
void markAsPerformedInstallation();
QStringList userInterfaces() const;
- QHash<QString, QPair<QString, QString> > licenses() const;
+ QHash<QString, QVariantMap> licenses() const;
Q_INVOKABLE QWidget *userInterface(const QString &name) const;
Q_INVOKABLE virtual void beginInstallation();
Q_INVOKABLE virtual void createOperations();
@@ -155,6 +160,8 @@ public:
QString name() const;
QString displayName() const;
+ QString treeName() const;
+ bool treeNameMoveChildren() const;
quint64 updateUncompressedSize();
QUrl repositoryUrl() const;
@@ -189,6 +196,7 @@ public:
Q_INVOKABLE bool updateRequested();
Q_INVOKABLE bool componentChangeRequested();
+ Q_INVOKABLE bool isForcedUpdate();
bool isUnstable() const;
void setUnstable(Component::UnstableError error, const QString &errorMessage = QString());
@@ -227,6 +235,9 @@ private:
private:
QString validatorCallbackName;
ComponentPrivate *d;
+ QList<QPair<QString, QVariant>> m_operationsList;
+ QHash<QString, QString> m_archivesHash;
+ QString m_defaultArchivePath;
};
QDebug operator<<(QDebug dbg, Component *component);
diff --git a/src/libs/installer/component_p.cpp b/src/libs/installer/component_p.cpp
index 7c107cac2..4030d266e 100644
--- a/src/libs/installer/component_p.cpp
+++ b/src/libs/installer/component_p.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -54,6 +54,7 @@ ComponentPrivate::ComponentPrivate(PackageManagerCore *core, Component *qq)
, m_autoCreateOperations(true)
, m_operationsCreatedSuccessfully(true)
, m_updateIsAvailable(false)
+ , m_treeNameMoveChildren(false)
{
}
@@ -187,14 +188,6 @@ void ComponentModelHelper::setCheckable(bool checkable)
}
/*!
- Returns whether the component is selectable by the user. The default value is \c true.
-*/
-bool ComponentModelHelper::isSelectable() const
-{
- return (flags() & Qt::ItemIsSelectable) != 0;
-}
-
-/*!
Sets whether the component is selectable. If \a selectable is \c true, the component can be selected by the
user; otherwise, the user cannot select the component.
*/
diff --git a/src/libs/installer/component_p.h b/src/libs/installer/component_p.h
index 05b169d4b..bdc67898d 100644
--- a/src/libs/installer/component_p.h
+++ b/src/libs/installer/component_p.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -63,7 +63,7 @@ public:
bool m_autoCreateOperations;
bool m_operationsCreatedSuccessfully;
bool m_updateIsAvailable;
- bool m_unstable;
+ bool m_treeNameMoveChildren;
QString m_componentName;
QUrl m_repositoryUrl;
@@ -77,7 +77,7 @@ public:
QHash<QString, QPointer<QWidget> > m_userInterfaces;
// < display name, < file name, file content > >
- QHash<QString, QPair<QString, QString> > m_licenses;
+ QHash<QString, QVariantMap> m_licenses;
QList<QPair<QString, bool> > m_pathsForUninstallation;
};
@@ -129,7 +129,6 @@ public:
bool isCheckable() const;
void setCheckable(bool checkable);
- bool isSelectable() const;
void setSelectable(bool selectable);
bool isExpandedByDefault() const;
diff --git a/src/libs/installer/componentmodel.cpp b/src/libs/installer/componentmodel.cpp
index 45e27ba4a..88e502164 100644
--- a/src/libs/installer/componentmodel.cpp
+++ b/src/libs/installer/componentmodel.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.
@@ -101,7 +101,6 @@ ComponentModel::ComponentModel(int columns, PackageManagerCore *core)
, m_modelState(DefaultChecked)
{
m_headerData.insert(0, columns, QVariant());
- connect(this, &QAbstractItemModel::modelReset, this, &ComponentModel::slotModelReset);
}
/*!
@@ -159,7 +158,7 @@ QModelIndex ComponentModel::parent(const QModelIndex &child) const
if (Component *childComponent = componentFromIndex(child)) {
if (Component *parent = childComponent->parentComponent())
- return indexFromComponentName(parent->name());
+ return indexFromComponentName(parent->treeName());
}
return QModelIndex();
}
@@ -253,16 +252,18 @@ bool ComponentModel::setData(const QModelIndex &index, const QVariant &value, in
if (role == Qt::CheckStateRole) {
if (index.column() != 0)
return false;
- ComponentSet nodes = component->childItems().toSet();
+
+ const QList<Component*> childItems = component->childItems();
+ ComponentSet nodes(childItems.begin(), childItems.end());
Qt::CheckState newValue = Qt::CheckState(value.toInt());
if (newValue == Qt::PartiallyChecked) {
const Qt::CheckState oldValue = component->checkState();
newValue = (oldValue == Qt::Checked) ? Qt::Unchecked : Qt::Checked;
}
QSet<QModelIndex> changed = updateCheckedState(nodes << component, newValue);
- foreach (const QModelIndex &index, changed) {
- emit dataChanged(index, index);
- emit checkStateChanged(index);
+ foreach (const QModelIndex &changedIndex, changed) {
+ emit dataChanged(changedIndex, changedIndex);
+ emit checkStateChanged(changedIndex);
}
updateAndEmitModelState(); // update the internal state
} else {
@@ -385,13 +386,13 @@ Component *ComponentModel::componentFromIndex(const QModelIndex &index) const
// -- public slots
/*!
- Sets \a rootComponents to be the list of currently shown components.
+ Resets model and sets \a rootComponents to be the list of currently shown components.
The model is repopulated and the individual component's checked state is used to show the check
mark in front of the visual component representation. The modelAboutToBeReset() and
modelReset() signals are emitted.
*/
-void ComponentModel::setRootComponents(QList<QInstaller::Component*> rootComponents)
+void ComponentModel::reset(QList<Component *> rootComponents)
{
beginResetModel();
@@ -417,6 +418,7 @@ void ComponentModel::setRootComponents(QList<QInstaller::Component*> rootCompone
m_rootComponentList.append(component);
}
endResetModel();
+ postModelReset();
}
/*!
@@ -459,7 +461,16 @@ void ComponentModel::setCheckedState(QInstaller::ComponentModel::ModelStateFlag
// -- private slots
-void ComponentModel::slotModelReset()
+void ComponentModel::onVirtualStateChanged()
+{
+ // If the virtual state of a component changes, force a reset of the component model.
+ reset(m_core->components(PackageManagerCore::ComponentType::Root));
+}
+
+
+// -- private
+
+void ComponentModel::postModelReset()
{
ComponentList components = m_rootComponentList;
if (!m_core->isUpdater()) {
@@ -485,15 +496,6 @@ void ComponentModel::slotModelReset()
updateAndEmitModelState(); // update the internal state
}
-void ComponentModel::onVirtualStateChanged()
-{
- // If the virtual state of a component changes, force a reset of the component model.
- setRootComponents(m_core->components(PackageManagerCore::ComponentType::Root));
-}
-
-
-// -- private
-
void ComponentModel::updateAndEmitModelState()
{
m_modelState = ComponentModel::DefaultChecked;
@@ -511,20 +513,11 @@ void ComponentModel::updateAndEmitModelState()
}
emit checkStateChanged(m_modelState);
-
- foreach (const Component *component, m_rootComponentList) {
- emit dataChanged(indexFromComponentName(component->name()),
- indexFromComponentName(component->name()));
- QList<Component *> children = component->childItems();
- foreach (const Component *child, children)
- emit dataChanged(indexFromComponentName(child->name()),
- indexFromComponentName(child->name()));
- }
}
void ComponentModel::collectComponents(Component *const component, const QModelIndex &parent) const
{
- m_indexByNameCache.insert(component->name(), parent);
+ m_indexByNameCache.insert(component->treeName(), parent);
for (int i = 0; i < component->childCount(); ++i)
collectComponents(component->childAt(i), index(i, 0, parent));
}
@@ -566,10 +559,10 @@ static Qt::CheckState verifyPartiallyChecked(Component *component)
QSet<QModelIndex> ComponentModel::updateCheckedState(const ComponentSet &components, Qt::CheckState state)
{
// get all parent nodes for the components we're going to update
- QMap<QString, Component *> sortedNodesMap;
+ QMultiMap<QString, Component *> sortedNodesMap;
foreach (Component *component, components) {
- while (component && !sortedNodesMap.values(component->name()).contains(component)) {
- sortedNodesMap.insertMulti(component->name(), component);
+ while (component && !sortedNodesMap.values(component->treeName()).contains(component)) {
+ sortedNodesMap.insert(component->treeName(), component);
component = component->parentComponent();
}
}
@@ -596,7 +589,7 @@ QSet<QModelIndex> ComponentModel::updateCheckedState(const ComponentSet &compone
continue;
node->setCheckState(newState);
- changed.insert(indexFromComponentName(node->name()));
+ changed.insert(indexFromComponentName(node->treeName()));
m_currentCheckedState[Qt::Checked].remove(node);
m_currentCheckedState[Qt::Unchecked].remove(node);
diff --git a/src/libs/installer/componentmodel.h b/src/libs/installer/componentmodel.h
index 8a9fbf884..829c95111 100644
--- a/src/libs/installer/componentmodel.h
+++ b/src/libs/installer/componentmodel.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -86,7 +86,7 @@ public:
Component* componentFromIndex(const QModelIndex &index) const;
public Q_SLOTS:
- void setRootComponents(QList<QInstaller::Component*> rootComponents);
+ void reset(QList<Component *> rootComponents = QList<Component *>());
void setCheckedState(QInstaller::ComponentModel::ModelStateFlag state);
Q_SIGNALS:
@@ -94,10 +94,10 @@ Q_SIGNALS:
void checkStateChanged(QInstaller::ComponentModel::ModelState state);
private Q_SLOTS:
- void slotModelReset();
void onVirtualStateChanged();
private:
+ void postModelReset();
void updateAndEmitModelState();
void collectComponents(Component *const component, const QModelIndex &parent) const;
QSet<QModelIndex> updateCheckedState(const ComponentSet &components, Qt::CheckState state);
diff --git a/src/libs/installer/componentselectionpage_p.cpp b/src/libs/installer/componentselectionpage_p.cpp
index 6d3300081..7717f4cf3 100644
--- a/src/libs/installer/componentselectionpage_p.cpp
+++ b/src/libs/installer/componentselectionpage_p.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2018 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -49,6 +49,8 @@
#include <QFileDialog>
#include <QStackedLayout>
#include <QStackedWidget>
+#include <QToolBox>
+#include <QLineEdit>
namespace QInstaller {
@@ -66,19 +68,32 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP
, m_updaterModel(m_core->updaterComponentModel())
, m_currentModel(m_allModel)
, m_allowCompressedRepositoryInstall(false)
+ , m_toolBox(nullptr)
+ , m_descriptionBaseWidget(nullptr)
, m_categoryWidget(Q_NULLPTR)
+ , m_categoryLayoutVisible(false)
+ , m_proxyModel(new ComponentSortFilterProxyModel(q))
{
m_treeView->setObjectName(QLatin1String("ComponentsTreeView"));
+ m_proxyModel->setRecursiveFilteringEnabled(true);
+ m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
- QVBoxLayout *descriptionVLayout = new QVBoxLayout;
+ m_descriptionBaseWidget = new QWidget(q);
+ m_descriptionBaseWidget->setObjectName(QLatin1String("DescriptionBaseWidget"));
+
+ QVBoxLayout *descriptionVLayout = new QVBoxLayout(m_descriptionBaseWidget);
descriptionVLayout->setObjectName(QLatin1String("DescriptionLayout"));
+ descriptionVLayout->setContentsMargins(0, 0, 0, 0);
+
+ m_toolBox = new QToolBox(q);
+ m_toolBox->setObjectName(QLatin1String("ToolBox"));
QScrollArea *descriptionScrollArea = new QScrollArea(q);
descriptionScrollArea->setWidgetResizable(true);
descriptionScrollArea->setFrameShape(QFrame::NoFrame);
descriptionScrollArea->setObjectName(QLatin1String("DescriptionScrollArea"));
- m_descriptionLabel = new QLabel(q);
+ m_descriptionLabel = new QLabel(m_descriptionBaseWidget);
m_descriptionLabel->setWordWrap(true);
m_descriptionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
m_descriptionLabel->setOpenExternalLinks(true);
@@ -87,8 +102,7 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP
descriptionScrollArea->setWidget(m_descriptionLabel);
descriptionVLayout->addWidget(descriptionScrollArea);
- m_sizeLabel = new QLabel(q);
- m_sizeLabel->setMargin(5);
+ m_sizeLabel = new QLabel(m_descriptionBaseWidget);
m_sizeLabel->setWordWrap(true);
m_sizeLabel->setObjectName(QLatin1String("ComponentSizeLabel"));
descriptionVLayout->addWidget(m_sizeLabel);
@@ -100,14 +114,17 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP
if (m_core->isInstaller()) {
m_checkDefault->setObjectName(QLatin1String("SelectDefaultComponentsButton"));
m_checkDefault->setShortcut(QKeySequence(ComponentSelectionPage::tr("Alt+A",
- "select default components")));
+ "Select default components")));
m_checkDefault->setText(ComponentSelectionPage::tr("Def&ault"));
+ m_checkDefault->setToolTip(ComponentSelectionPage::tr("Select default components in the tree view."));
} else {
m_checkDefault->setEnabled(false);
m_checkDefault->setObjectName(QLatin1String("ResetComponentsButton"));
m_checkDefault->setShortcut(QKeySequence(ComponentSelectionPage::tr("Alt+R",
- "reset to already installed components")));
+ "Reset to already installed components")));
m_checkDefault->setText(ComponentSelectionPage::tr("&Reset"));
+ m_checkDefault->setToolTip(
+ ComponentSelectionPage::tr("Reset all components to their original selection state in the tree view."));
}
buttonHLayout->addWidget(m_checkDefault);
@@ -116,8 +133,9 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP
this, &ComponentSelectionPagePrivate::selectAll);
m_checkAll->setObjectName(QLatin1String("SelectAllComponentsButton"));
m_checkAll->setShortcut(QKeySequence(ComponentSelectionPage::tr("Alt+S",
- "select all components")));
+ "Select all components")));
m_checkAll->setText(ComponentSelectionPage::tr("&Select All"));
+ m_checkAll->setToolTip(ComponentSelectionPage::tr("Select all components in the tree view."));
buttonHLayout->addWidget(m_checkAll);
m_uncheckAll = new QPushButton;
@@ -125,8 +143,9 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP
this, &ComponentSelectionPagePrivate::deselectAll);
m_uncheckAll->setObjectName(QLatin1String("DeselectAllComponentsButton"));
m_uncheckAll->setShortcut(QKeySequence(ComponentSelectionPage::tr("Alt+D",
- "deselect all components")));
+ "Deselect all components")));
m_uncheckAll->setText(ComponentSelectionPage::tr("&Deselect All"));
+ m_uncheckAll->setToolTip(ComponentSelectionPage::tr("Deselect all components in the tree view."));
buttonHLayout->addWidget(m_uncheckAll);
QWidget *progressStackedWidget = new QWidget();
@@ -140,17 +159,26 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP
metaLayout->addWidget(m_progressBar);
metaLayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding));
+ m_searchLineEdit = new QLineEdit(q);
+ m_searchLineEdit->setObjectName(QLatin1String("SearchLineEdit"));
+ m_searchLineEdit->setPlaceholderText(ComponentSelectionPage::tr("Search"));
+ m_searchLineEdit->setClearButtonEnabled(true);
+ connect(m_searchLineEdit, &QLineEdit::textChanged,
+ this, &ComponentSelectionPagePrivate::setSearchPattern);
+ connect(q, &ComponentSelectionPage::entered, m_searchLineEdit, &QLineEdit::clear);
+
QVBoxLayout *treeViewVLayout = new QVBoxLayout;
treeViewVLayout->setObjectName(QLatin1String("TreeviewLayout"));
treeViewVLayout->addWidget(m_treeView, 3);
+ treeViewVLayout->addWidget(m_searchLineEdit);
QWidget *mainStackedWidget = new QWidget();
m_mainGLayout = new QGridLayout(mainStackedWidget);
- m_mainGLayout->addLayout(buttonHLayout, 0, 1);
- m_mainGLayout->addLayout(treeViewVLayout, 1, 1);
- m_mainGLayout->addLayout(descriptionVLayout, 1, 2);
- m_mainGLayout->setColumnStretch(1, 3);
- m_mainGLayout->setColumnStretch(2, 2);
+ m_mainGLayout->addLayout(buttonHLayout, 0, 0);
+ m_mainGLayout->addLayout(treeViewVLayout, 1, 0);
+ m_mainGLayout->addWidget(m_descriptionBaseWidget, 1, 1);
+ m_mainGLayout->setColumnStretch(0, 3);
+ m_mainGLayout->setColumnStretch(1, 2);
m_stackedLayout = new QStackedLayout(q);
m_stackedLayout->addWidget(mainStackedWidget);
@@ -193,6 +221,9 @@ void ComponentSelectionPagePrivate::showCompressedRepositoryButton()
wizard->setOption(QWizard::HaveCustomButton2, true);
wizard->setButtonText(QWizard::CustomButton2,
ComponentSelectionPage::tr("&Browse QBSP files"));
+ wizard->button(QWizard::CustomButton2)->setToolTip(
+ ComponentSelectionPage::tr("Select a Qt Board Support Package file to install "
+ "additional content that is not directly available from the online repositories."));
connect(wizard, &QWizard::customButtonClicked,
this, &ComponentSelectionPagePrivate::customButtonClicked);
q->gui()->updateButtonLayout();
@@ -219,11 +250,13 @@ void ComponentSelectionPagePrivate::setupCategoryLayout()
vLayout->setContentsMargins(0, 0, 0, 0);
m_categoryWidget->setLayout(vLayout);
m_categoryGroupBox = new QGroupBox(q);
- m_categoryGroupBox->setTitle(m_core->settings().repositoryCategoryDisplayName());
m_categoryGroupBox->setObjectName(QLatin1String("CategoryGroupBox"));
QVBoxLayout *categoryLayout = new QVBoxLayout(m_categoryGroupBox);
QPushButton *fetchCategoryButton = new QPushButton(tr("Filter"));
fetchCategoryButton->setObjectName(QLatin1String("FetchCategoryButton"));
+ fetchCategoryButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ fetchCategoryButton->setToolTip(
+ ComponentSelectionPage::tr("Filter the enabled repository categories"));
connect(fetchCategoryButton, &QPushButton::clicked, this,
&ComponentSelectionPagePrivate::fetchRepositoryCategories);
@@ -231,8 +264,6 @@ void ComponentSelectionPagePrivate::setupCategoryLayout()
QCheckBox *checkBox = new QCheckBox;
checkBox->setObjectName(repository.displayname());
checkBox->setChecked(repository.isEnabled());
- connect(checkBox, &QCheckBox::stateChanged, this,
- &ComponentSelectionPagePrivate::checkboxStateChanged);
checkBox->setText(repository.displayname());
checkBox->setToolTip(repository.tooltip());
categoryLayout->addWidget(checkBox);
@@ -241,16 +272,29 @@ void ComponentSelectionPagePrivate::setupCategoryLayout()
vLayout->addWidget(m_categoryGroupBox);
vLayout->addStretch();
- m_mainGLayout->addWidget(m_categoryWidget, 1, 0);
+ m_toolBox->insertItem(1, m_categoryWidget, m_core->settings().repositoryCategoryDisplayName());
}
void ComponentSelectionPagePrivate::showCategoryLayout(bool show)
{
+ if (!show && !m_categoryWidget)
+ return;
+
+ if (show == m_categoryLayoutVisible)
+ return;
+
+ setupCategoryLayout();
if (show) {
- setupCategoryLayout();
+ m_mainGLayout->removeWidget(m_descriptionBaseWidget);
+ m_toolBox->insertItem(0, m_descriptionBaseWidget, tr("Component Information"));
+ m_mainGLayout->addWidget(m_toolBox, 1, 1);
+ } else {
+ m_toolBox->removeItem(0);
+ m_mainGLayout->removeWidget(m_toolBox);
+ m_mainGLayout->addWidget(m_descriptionBaseWidget, 1, 1);
}
- if (m_categoryWidget)
- m_categoryWidget->setVisible(show);
+ m_toolBox->setVisible(show);
+ m_categoryLayoutVisible = show;
}
void ComponentSelectionPagePrivate::updateTreeView()
@@ -261,15 +305,11 @@ void ComponentSelectionPagePrivate::updateTreeView()
this, &ComponentSelectionPagePrivate::currentSelectedChanged);
}
+ m_searchLineEdit->setVisible(!m_core->isUpdater());
m_currentModel = m_core->isUpdater() ? m_updaterModel : m_allModel;
- m_treeView->setModel(m_currentModel);
- m_treeView->setExpanded(m_currentModel->index(0, 0), true);
- foreach (Component *component, m_core->components(PackageManagerCore::ComponentType::All)) {
- if (component->isExpandedByDefault()) {
- const QModelIndex index = m_currentModel->indexFromComponentName(component->name());
- m_treeView->setExpanded(index, true);
- }
- }
+ m_proxyModel->setSourceModel(m_currentModel);
+ m_treeView->setModel(m_proxyModel);
+ expandDefault();
const bool installActionColumnVisible = m_core->settings().installActionColumnVisible();
if (!installActionColumnVisible)
@@ -310,7 +350,44 @@ void ComponentSelectionPagePrivate::updateTreeView()
connect(m_treeView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &ComponentSelectionPagePrivate::currentSelectedChanged);
- m_treeView->setCurrentIndex(m_currentModel->index(0, 0));
+ m_treeView->setCurrentIndex(m_proxyModel->index(0, 0));
+}
+
+/*!
+ Expands components that should be expanded by default.
+*/
+void ComponentSelectionPagePrivate::expandDefault()
+{
+ m_treeView->setExpanded(m_proxyModel->index(0, 0), true);
+ foreach (auto *component, m_core->components(PackageManagerCore::ComponentType::All)) {
+ if (component->isExpandedByDefault()) {
+ const QModelIndex index = m_proxyModel->mapFromSource(
+ m_currentModel->indexFromComponentName(component->treeName()));
+ m_treeView->setExpanded(index, true);
+ }
+ }
+}
+
+/*!
+ Expands components that were accepted by proxy models filter.
+*/
+void ComponentSelectionPagePrivate::expandSearchResults()
+{
+ // Expand parents of root indexes accepted by filter
+ const QVector<QModelIndex> acceptedIndexes = m_proxyModel->directlyAcceptedIndexes();
+ for (auto proxyModelIndex : acceptedIndexes) {
+ if (!proxyModelIndex.isValid())
+ continue;
+
+ QModelIndex index = proxyModelIndex.parent();
+ while (index.isValid()) {
+ if (m_treeView->isExpanded(index))
+ break; // Multiple direct matches in a branch, can be skipped
+
+ m_treeView->expand(index);
+ index = index.parent();
+ }
+ }
}
void ComponentSelectionPagePrivate::currentSelectedChanged(const QModelIndex &current)
@@ -320,16 +397,12 @@ void ComponentSelectionPagePrivate::currentSelectedChanged(const QModelIndex &cu
m_sizeLabel->setText(QString());
- QString description = m_currentModel->data(m_currentModel->index(current.row(),
+ QString description = m_proxyModel->data(m_proxyModel->index(current.row(),
ComponentModelHelper::NameColumn, current.parent()), Qt::ToolTipRole).toString();
- // replace {external-link}='' fields in component description with proper link tags
- description.replace(QRegularExpression(QLatin1String("{external-link}='(.*?)'")),
- QLatin1String("<a href=\"\\1\"><span style=\"color:#17a81a;\">\\1</span></a>"));
-
m_descriptionLabel->setText(description);
- Component *component = m_currentModel->componentFromIndex(current);
+ Component *component = m_currentModel->componentFromIndex(m_proxyModel->mapToSource(current));
if ((m_core->isUninstaller()) || (!component))
return;
@@ -350,18 +423,6 @@ void ComponentSelectionPagePrivate::deselectAll()
m_currentModel->setCheckedState(ComponentModel::AllUnchecked);
}
-void ComponentSelectionPagePrivate::checkboxStateChanged()
-{
- QList<QCheckBox*> checkboxes = m_categoryGroupBox->findChildren<QCheckBox *>();
- bool enableFetchButton = false;
- foreach (QCheckBox *checkbox, checkboxes) {
- if (checkbox->isChecked()) {
- enableFetchButton = true;
- break;
- }
- }
-}
-
void ComponentSelectionPagePrivate::enableRepositoryCategory(const QString &repositoryName, bool enable)
{
QMap<QString, RepositoryCategory> organizedRepositoryCategories = m_core->settings().organizedRepositoryCategories();
@@ -403,10 +464,9 @@ void ComponentSelectionPagePrivate::fetchRepositoryCategories()
{
updateWidgetVisibility(true);
- QCheckBox *checkbox;
QList<QCheckBox*> checkboxes = m_categoryGroupBox->findChildren<QCheckBox *>();
for (int i = 0; i < checkboxes.count(); i++) {
- checkbox = checkboxes.at(i);
+ QCheckBox *checkbox = checkboxes.at(i);
enableRepositoryCategory(checkbox->objectName(), checkbox->isChecked());
}
@@ -415,7 +475,7 @@ void ComponentSelectionPagePrivate::fetchRepositoryCategories()
QLatin1String("FailToFetchPackages"), tr("Error"), m_core->error());
}
updateWidgetVisibility(false);
- updateTreeView();
+ m_searchLineEdit->text().isEmpty() ? expandDefault() : expandSearchResults();
}
void ComponentSelectionPagePrivate::customButtonClicked(int which)
@@ -494,4 +554,26 @@ void ComponentSelectionPagePrivate::onModelStateChanged(QInstaller::ComponentMod
currentSelectedChanged(m_treeView->selectionModel()->currentIndex());
}
+/*!
+ Sets the new filter pattern to \a text and expands the tree nodes.
+*/
+void ComponentSelectionPagePrivate::setSearchPattern(const QString &text)
+{
+ m_proxyModel->setFilterWildcard(text);
+
+ m_treeView->collapseAll();
+ if (text.isEmpty()) {
+ // Expand user selection and default expanded, ensure selected is visible
+ QModelIndex index = m_treeView->selectionModel()->currentIndex();
+ while (index.isValid()) {
+ m_treeView->expand(index);
+ index = index.parent();
+ }
+ expandDefault();
+ m_treeView->scrollTo(m_treeView->selectionModel()->currentIndex());
+ } else {
+ expandSearchResults();
+ }
+}
+
} // namespace QInstaller
diff --git a/src/libs/installer/componentselectionpage_p.h b/src/libs/installer/componentselectionpage_p.h
index bce13246b..fc37ebdaa 100644
--- a/src/libs/installer/componentselectionpage_p.h
+++ b/src/libs/installer/componentselectionpage_p.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2018 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -34,6 +34,7 @@
#include "componentmodel.h"
#include "packagemanagergui.h"
+#include "componentsortfilterproxymodel.h"
class QTreeView;
class QLabel;
@@ -46,6 +47,7 @@ class QVBoxLayout;
class QHBoxLayout;
class QGridLayout;
class QStackedLayout;
+class QToolBox;
namespace QInstaller {
@@ -69,12 +71,13 @@ public:
void setupCategoryLayout();
void showCategoryLayout(bool show);
void updateTreeView();
+ void expandDefault();
+ void expandSearchResults();
public slots:
void currentSelectedChanged(const QModelIndex &current);
void selectAll();
void deselectAll();
- void checkboxStateChanged();
void enableRepositoryCategory(const QString &repositoryName, bool enable);
void updateWidgetVisibility(bool show);
void fetchRepositoryCategories();
@@ -84,11 +87,14 @@ public slots:
void setTotalProgress(int totalProgress);
void selectDefault();
void onModelStateChanged(QInstaller::ComponentModel::ModelState state);
+ void setSearchPattern(const QString &text);
private:
ComponentSelectionPage *q;
PackageManagerCore *m_core;
QTreeView *m_treeView;
+ QToolBox *m_toolBox;
+ QWidget *m_descriptionBaseWidget;
QLabel *m_sizeLabel;
QLabel *m_descriptionLabel;
QPushButton *m_checkAll;
@@ -100,10 +106,13 @@ private:
QProgressBar *m_progressBar;
QGridLayout *m_mainGLayout;
bool m_allowCompressedRepositoryInstall;
+ bool m_categoryLayoutVisible;
ComponentModel *m_allModel;
ComponentModel *m_updaterModel;
ComponentModel *m_currentModel;
QStackedLayout *m_stackedLayout;
+ ComponentSortFilterProxyModel *m_proxyModel;
+ QLineEdit *m_searchLineEdit;
};
} // namespace QInstaller
diff --git a/src/libs/installer/componentsortfilterproxymodel.cpp b/src/libs/installer/componentsortfilterproxymodel.cpp
new file mode 100644
index 000000000..9308d2356
--- /dev/null
+++ b/src/libs/installer/componentsortfilterproxymodel.cpp
@@ -0,0 +1,152 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#include "componentsortfilterproxymodel.h"
+
+namespace QInstaller {
+
+/*!
+ \class QInstaller::ComponentSortFilterProxyModel
+ \inmodule QtInstallerFramework
+ \brief The ComponentSortFilterProxyModel provides support for sorting and
+ filtering data passed between another model and a view.
+
+ The class subclasses QSortFilterProxyModel. Compared to the base class,
+ filters affect also child indexes in the base model, meaning if a
+ certain row has a parent that is accepted by filter, it is also accepted.
+ A distinction is made betweed directly and indirectly accepted indexes.
+*/
+
+/*!
+ \enum ComponentSortFilterProxyModel::AcceptType
+
+ This enum holds the possible values for filter acception type for model indexes.
+
+ \value Direct
+ Index was accepted directly by filter.
+ \value Descendant
+ Index is a descendant of an accepted index.
+ \value Rejected
+ Index was not accepted by filter.
+*/
+
+/*!
+ Constructs object with \a parent.
+*/
+ComponentSortFilterProxyModel::ComponentSortFilterProxyModel(QObject *parent)
+ : QSortFilterProxyModel(parent)
+{
+}
+
+/*!
+ Returns a list of source model indexes that were accepted directly by the filter.
+*/
+QVector<QModelIndex> ComponentSortFilterProxyModel::directlyAcceptedIndexes() const
+{
+ QVector<QModelIndex> indexes;
+ for (int i = 0; i < rowCount(); i++) {
+ QModelIndex childIndex = index(i, 0, QModelIndex());
+ findDirectlyAcceptedIndexes(childIndex, indexes);
+ }
+ return indexes;
+}
+
+/*!
+ Returns \c true if the item in the row indicated by the given \a sourceRow and
+ \a sourceParent should be included in the model; otherwise returns \c false.
+*/
+bool ComponentSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ return acceptsRow(sourceRow, sourceParent);
+}
+
+/*!
+ Returns \c true if the item in the row indicated by the given \a sourceRow and
+ \a sourceParent should be included in the model; otherwise returns \c false. The
+ acception type can be retrieved with \a type.
+*/
+bool ComponentSortFilterProxyModel::acceptsRow(int sourceRow, const QModelIndex &sourceParent,
+ AcceptType *type) const
+{
+ if (type)
+ *type = AcceptType::Rejected;
+
+ if (QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent)) {
+ if (type)
+ *type = AcceptType::Direct;
+ return true;
+ }
+
+ if (!isRecursiveFilteringEnabled()) // filter not applied for children
+ return false;
+
+ if (!sourceParent.isValid()) // already root
+ return false;
+
+ int currentRow = sourceParent.row();
+ QModelIndex currentParent = sourceParent.parent();
+ // Ascend and check if any parent is accepted
+ forever {
+ if (QSortFilterProxyModel::filterAcceptsRow(currentRow, currentParent)) {
+ if (type)
+ *type = AcceptType::Descendant;
+ return true;
+ }
+ if (!currentParent.isValid()) // we hit a root node
+ break;
+
+ currentRow = currentParent.row();
+ currentParent = currentParent.parent();
+ }
+ return false;
+}
+
+/*!
+ Finds directly accepted child \a indexes for parent index \a in. Returns \c true
+ if at least one accepted index was found, \c false otherwise.
+*/
+bool ComponentSortFilterProxyModel::findDirectlyAcceptedIndexes(const QModelIndex &in, QVector<QModelIndex> &indexes) const
+{
+ bool found = false;
+ for (int i = 0; i < rowCount(in); i++) {
+ if (findDirectlyAcceptedIndexes(index(i, 0, in), indexes))
+ found = true;
+ }
+ if (!hasChildren(in) || !found) { // No need to check current if any child matched
+ AcceptType acceptType;
+ const QModelIndex sourceIndex = mapToSource(in);
+ acceptsRow(sourceIndex.row(), sourceIndex.parent(), &acceptType);
+ if (acceptType == AcceptType::Direct) {
+ indexes.append(in);
+ found = true;
+ }
+ }
+ return found;
+}
+
+} // namespace QInstaller
diff --git a/src/libs/installer/componentsortfilterproxymodel.h b/src/libs/installer/componentsortfilterproxymodel.h
new file mode 100644
index 000000000..a6167d17b
--- /dev/null
+++ b/src/libs/installer/componentsortfilterproxymodel.h
@@ -0,0 +1,63 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#ifndef COMPONENTSORTFILTERPROXYMODEL_H
+#define COMPONENTSORTFILTERPROXYMODEL_H
+
+#include "installer_global.h"
+
+#include <QSortFilterProxyModel>
+
+namespace QInstaller {
+
+class INSTALLER_EXPORT ComponentSortFilterProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ enum AcceptType {
+ Direct,
+ Descendant,
+ Rejected
+ };
+
+ explicit ComponentSortFilterProxyModel(QObject *parent = nullptr);
+
+ QVector<QModelIndex> directlyAcceptedIndexes() const;
+
+protected:
+ bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const Q_DECL_OVERRIDE;
+
+private:
+ bool acceptsRow(int sourceRow, const QModelIndex &sourceParent, AcceptType *type = nullptr) const;
+ bool findDirectlyAcceptedIndexes(const QModelIndex &in, QVector<QModelIndex> &indexes) const;
+};
+
+} // namespace QInstaller
+
+#endif // COMPONENTSORTFILTERPROXYMODEL_H
diff --git a/src/libs/installer/constants.h b/src/libs/installer/constants.h
index 35fcb9c89..1b7807e22 100644
--- a/src/libs/installer/constants.h
+++ b/src/libs/installer/constants.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -40,6 +40,7 @@ static const QLatin1String scFalse("false");
static const QLatin1String scScript("script");
static const QLatin1String scAllUsersStartMenuProgramsPath("AllUsersStartMenuProgramsPath");
static const QLatin1String scUserStartMenuProgramsPath("UserStartMenuProgramsPath");
+static const QLatin1String scUILanguage("UILanguage");
static const QLatin1String scName("Name");
static const QLatin1String scVersion("Version");
@@ -50,10 +51,13 @@ static const QLatin1String scInheritVersion("inheritVersionFrom");
static const QLatin1String scReplaces("Replaces");
static const QLatin1String scDownloadableArchives("DownloadableArchives");
static const QLatin1String scEssential("Essential");
+static const QLatin1String scForcedUpdate("ForcedUpdate");
static const QLatin1String scTargetDir("TargetDir");
static const QLatin1String scReleaseDate("ReleaseDate");
static const QLatin1String scDescription("Description");
static const QLatin1String scDisplayName("DisplayName");
+static const QLatin1String scTreeName("TreeName");
+static const QLatin1String scAutoTreeName("AutoTreeName");
static const QLatin1String scDependencies("Dependencies");
static const QLatin1String scAutoDependOn("AutoDependOn");
static const QLatin1String scNewComponent("NewComponent");
@@ -63,7 +67,9 @@ static const QLatin1String scInstalledVersion("InstalledVersion");
static const QLatin1String scUncompressedSize("UncompressedSize");
static const QLatin1String scUncompressedSizeSum("UncompressedSizeSum");
static const QLatin1String scRequiresAdminRights("RequiresAdminRights");
+static const QLatin1String scOfflineBinaryName("OfflineBinaryName");
static const QLatin1String scSHA1("SHA1");
+static const QLatin1String scContentSha1("ContentSha1");
// constants used throughout the components class
static const QLatin1String scVirtual("Virtual");
@@ -82,6 +88,8 @@ static const QLatin1String scTargetConfigurationFile("TargetConfigurationFile");
static const QLatin1String scAllowNonAsciiCharacters("AllowNonAsciiCharacters");
static const QLatin1String scDisableAuthorizationFallback("DisableAuthorizationFallback");
static const QLatin1String scDisableCommandLineInterface("DisableCommandLineInterface");
+static const QLatin1String scRemoteRepositories("RemoteRepositories");
+static const QLatin1String scRepositoryCategories("RepositoryCategories");
static const QLatin1String scRepositorySettingsPageVisible("RepositorySettingsPageVisible");
static const QLatin1String scAllowSpaceInPath("AllowSpaceInPath");
static const QLatin1String scWizardStyle("WizardStyle");
@@ -89,6 +97,8 @@ static const QLatin1String scStyleSheet("StyleSheet");
static const QLatin1String scTitleColor("TitleColor");
static const QLatin1String scWizardDefaultWidth("WizardDefaultWidth");
static const QLatin1String scWizardDefaultHeight("WizardDefaultHeight");
+static const QLatin1String scWizardMinimumWidth("WizardMinimumWidth");
+static const QLatin1String scWizardMinimumHeight("WizardMinimumHeight");
static const QLatin1String scWizardShowPageList("WizardShowPageList");
static const QLatin1String scProductImages("ProductImages");
static const QLatin1String scUrlQueryString("UrlQueryString");
@@ -98,7 +108,12 @@ static const QLatin1String scSupportsModify("SupportsModify");
static const QLatin1String scAllowUnstableComponents("AllowUnstableComponents");
static const QLatin1String scSaveDefaultRepositories("SaveDefaultRepositories");
static const QLatin1String scRepositoryCategoryDisplayName("RepositoryCategoryDisplayName");
-
+static const QLatin1String scHighDpi("@2x.");
+static const QLatin1String scWatermark("Watermark");
+static const QLatin1String scBanner("Banner");
+static const QLatin1String scLogo("Logo");
+static const QLatin1String scBackground("Background");
+static const QLatin1String scPageListPixmap("PageListPixmap");
const char scRelocatable[] = "@RELOCATABLE_PATH@";
}
@@ -129,6 +144,8 @@ static const QLatin1String scListShort("li");
static const QLatin1String scListLong("list");
static const QLatin1String scSearchShort("se");
static const QLatin1String scSearchLong("search");
+static const QLatin1String scCreateOfflineShort("co");
+static const QLatin1String scCreateOfflineLong("create-offline");
static const QLatin1String scPurgeShort("pr");
static const QLatin1String scPurgeLong("purge");
@@ -172,6 +189,8 @@ static const QLatin1String scConfirmCommandLong("confirm-command");
// Misc installation options
static const QLatin1String scRootShort("t");
static const QLatin1String scRootLong("root");
+static const QLatin1String scOfflineInstallerNameShort("oi");
+static const QLatin1String scOfflineInstallerNameLong("offline-installer-name");
static const QLatin1String scPlatformShort("p");
static const QLatin1String scPlatformLong("platform");
static const QLatin1String scNoForceInstallationShort("nf");
@@ -186,6 +205,8 @@ static const QLatin1String scCreateLocalRepositoryShort("cl");
static const QLatin1String scCreateLocalRepositoryLong("create-local-repository");
static const QLatin1String scNoDefaultInstallationShort("nd");
static const QLatin1String scNoDefaultInstallationLong("no-default-installations");
+static const QLatin1String scFilterPackagesShort("fp");
+static const QLatin1String scFilterPackagesLong("filter-packages");
// Developer options
static const QLatin1String scScriptShort("s");
@@ -215,6 +236,8 @@ static const QStringList scCommandLineInterfaceOptions = {
scListLong,
scSearchShort,
scSearchLong,
+ scCreateOfflineShort,
+ scCreateOfflineLong,
scPurgeShort,
scPurgeLong
};
diff --git a/src/libs/installer/copydirectoryoperation.cpp b/src/libs/installer/copydirectoryoperation.cpp
index 4cef3facf..a2ef2cf5a 100644
--- a/src/libs/installer/copydirectoryoperation.cpp
+++ b/src/libs/installer/copydirectoryoperation.cpp
@@ -67,7 +67,7 @@ bool CopyDirectoryOperation::performOperation()
if (!checkArgumentCount(2, 3, tr("<source> <target> [\"forceOverwrite\"]")))
return false;
- const QStringList args = arguments();
+ const QStringList args = parsePerformOperationArguments();
const QString sourcePath = args.at(0);
const QString targetPath = args.at(1);
bool overwrite = false;
@@ -153,7 +153,11 @@ bool CopyDirectoryOperation::performOperation()
bool CopyDirectoryOperation::undoOperation()
{
- Q_ASSERT(arguments().count() == 2);
+ if (parseUndoOperationArguments().count() > 0)
+ return true;
+
+ if (!checkArgumentCount(2))
+ return false;
QDir dir;
const QStringList files = value(QLatin1String("files")).toStringList();
diff --git a/src/libs/installer/createdesktopentryoperation.cpp b/src/libs/installer/createdesktopentryoperation.cpp
index 5907e2f21..6c92cc178 100644
--- a/src/libs/installer/createdesktopentryoperation.cpp
+++ b/src/libs/installer/createdesktopentryoperation.cpp
@@ -30,6 +30,8 @@
#include "errors.h"
#include "fileutils.h"
#include "globals.h"
+#include "adminauthorization.h"
+#include "remoteclient.h"
#include <QDir>
#include <QFile>
@@ -58,9 +60,12 @@ QString CreateDesktopEntryOperation::absoluteFileName()
QStringList XDG_DATA_HOME = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME"))
.split(QLatin1Char(':'),
- QString::SkipEmptyParts);
+ Qt::SkipEmptyParts);
- XDG_DATA_HOME.push_back(QDir::home().absoluteFilePath(QLatin1String(".local/share"))); // default path
+ XDG_DATA_HOME.push_back(QDir::home().absoluteFilePath(QLatin1String(".local/share"))); // default user-specific path
+
+ if (AdminAuthorization::hasAdminRights() || RemoteClient::instance().isActive())
+ XDG_DATA_HOME.push_front(QLatin1String("/usr/local/share")); // default system-wide path
const QStringList directories = XDG_DATA_HOME;
QString directory;
diff --git a/src/libs/installer/createlocalrepositoryoperation.cpp b/src/libs/installer/createlocalrepositoryoperation.cpp
index 1d194e5b3..7090f9a8b 100644
--- a/src/libs/installer/createlocalrepositoryoperation.cpp
+++ b/src/libs/installer/createlocalrepositoryoperation.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -34,8 +34,7 @@
#include "fileio.h"
#include "fileutils.h"
#include "copydirectoryoperation.h"
-#include "lib7z_create.h"
-#include "lib7z_facade.h"
+#include "archivefactory.h"
#include "packagemanagercore.h"
#include "productkeycheck.h"
#include "constants.h"
@@ -124,8 +123,17 @@ static QString createArchive(const QString repoPath, const QString &sourceDir, c
const QString fileName = QString::fromLatin1("/%1meta.7z").arg(version);
QFile archive(repoPath + fileName);
- QInstaller::openForWrite(&archive);
- Lib7z::createArchive(&archive, QStringList() << sourceDir);
+
+ QScopedPointer<AbstractArchive> archiveFile(ArchiveFactory::instance().create(archive.fileName()));
+ if (!archiveFile) {
+ throw Error(CreateLocalRepositoryOperation::tr("Unsupported archive \"%1\": no handler "
+ "registered for file suffix \"%2\".").arg(archive.fileName(), QFileInfo(archive.fileName()).suffix()));
+ }
+ if (!(archiveFile->open(QIODevice::WriteOnly) && archiveFile->create(QStringList() << sourceDir))) {
+ throw Error(CreateLocalRepositoryOperation::tr("Cannot create archive \"%1\": %2")
+ .arg(QDir::toNativeSeparators(archive.fileName()), archiveFile->errorString()));
+ }
+ archiveFile->close();
removeFiles(sourceDir, helper); // cleanup the files we compressed
if (!archive.rename(sourceDir + fileName)) {
throw Error(CreateLocalRepositoryOperation::tr("Cannot move file \"%1\" to \"%2\": %3")
@@ -356,10 +364,6 @@ bool CreateLocalRepositoryOperation::performOperation()
}
} catch (...) {}
setValue(QLatin1String("local-repo"), repoPath);
- } catch (const Lib7z::SevenZipException &e) {
- setError(UserDefinedError);
- setErrorString(e.message());
- return false;
} catch (const QInstaller::Error &e) {
setError(UserDefinedError);
setErrorString(e.message());
@@ -374,7 +378,11 @@ bool CreateLocalRepositoryOperation::performOperation()
bool CreateLocalRepositoryOperation::undoOperation()
{
- Q_ASSERT(arguments().count() == 2);
+ if (parseUndoOperationArguments().count() > 0)
+ return true;
+
+ if (!checkArgumentCount(2))
+ return false;
AutoHelper _(this);
emit progressChanged(0.0);
diff --git a/src/libs/installer/directoryguard.cpp b/src/libs/installer/directoryguard.cpp
new file mode 100644
index 000000000..9c97130a4
--- /dev/null
+++ b/src/libs/installer/directoryguard.cpp
@@ -0,0 +1,112 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#include "directoryguard.h"
+
+#include "globals.h"
+#include "errors.h"
+
+#include <QCoreApplication>
+#include <QDir>
+
+namespace QInstaller {
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::DirectoryGuard
+ \brief RAII class to create a directory and delete it on destruction unless released.
+*/
+
+/*!
+ Constructs a new guard object for \a path.
+*/
+DirectoryGuard::DirectoryGuard(const QString &path)
+ : m_path(path)
+ , m_created(false)
+ , m_released(false)
+{
+ m_path.replace(QLatin1Char('\\'), QLatin1Char('/'));
+}
+
+/*!
+ Destroys the directory guard instance and removes the
+ guarded directory unless released.
+*/
+DirectoryGuard::~DirectoryGuard()
+{
+ if (!m_created || m_released)
+ return;
+ QDir dir(m_path);
+ if (!dir.rmdir(m_path))
+ qCWarning(lcInstallerInstallLog) << "Cannot delete directory" << m_path;
+}
+
+/*!
+ Tries to create the directory structure.
+ Returns a list of every directory created.
+*/
+QStringList DirectoryGuard::tryCreate()
+{
+ if (m_path.isEmpty())
+ return QStringList();
+
+ const QFileInfo fi(m_path);
+ if (fi.exists() && fi.isDir())
+ return QStringList();
+ if (fi.exists() && !fi.isDir()) {
+ throw Error(QCoreApplication::translate("DirectoryGuard",
+ "Path \"%1\" exists but is not a directory.").arg(QDir::toNativeSeparators(m_path)));
+ }
+ QStringList created;
+
+ QDir toCreate(m_path);
+ while (!toCreate.exists()) {
+ QString p = toCreate.absolutePath();
+ created.push_front(p);
+ p = p.section(QLatin1Char('/'), 0, -2);
+ toCreate = QDir(p);
+ }
+
+ QDir dir(m_path);
+ m_created = dir.mkpath(m_path);
+ if (!m_created) {
+ throw Error(QCoreApplication::translate("DirectoryGuard",
+ "Cannot create directory \"%1\".").arg(QDir::toNativeSeparators(m_path)));
+ }
+ return created;
+}
+
+/*!
+ Marks the directory as released.
+*/
+void DirectoryGuard::release()
+{
+ m_released = true;
+}
+
+} // namespace QInstaller
diff --git a/src/libs/installer/lazyplaintextedit.h b/src/libs/installer/directoryguard.h
index 445fa752d..c18402557 100644
--- a/src/libs/installer/lazyplaintextedit.h
+++ b/src/libs/installer/directoryguard.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -26,31 +26,30 @@
**
**************************************************************************/
-#ifndef LAZYPLAINTEXTEDIT_H
-#define LAZYPLAINTEXTEDIT_H
+#ifndef DIRECTORYGUARD_H
+#define DIRECTORYGUARD_H
-#include <QPlainTextEdit>
+#include "installer_global.h"
-class LazyPlainTextEdit : public QPlainTextEdit
+#include <QString>
+
+namespace QInstaller {
+
+class INSTALLER_EXPORT DirectoryGuard
{
- Q_OBJECT
public:
- enum struct TextCursorPosition {
- Keep,
- ForceEnd
- };
- explicit LazyPlainTextEdit(QWidget *parent = 0);
- void updateCursor(TextCursorPosition position);
-
-public slots:
- void append(const QString &text);
- virtual void clear();
- virtual void setVisible ( bool visible );
-protected:
- void timerEvent(QTimerEvent *event);
+ explicit DirectoryGuard(const QString &path);
+ ~DirectoryGuard();
+
+ QStringList tryCreate();
+ void release();
+
private:
- int m_timerId;
- QString m_cachedOutput;
+ QString m_path;
+ bool m_created;
+ bool m_released;
};
-#endif // LAZYPLAINTEXTEDIT_H
+} // namespace QInstaller
+
+#endif // DIRECTORYGUARD_H
diff --git a/src/libs/installer/downloadarchivesjob.cpp b/src/libs/installer/downloadarchivesjob.cpp
index 5dbccc0b6..4d17ef4a2 100644
--- a/src/libs/installer/downloadarchivesjob.cpp
+++ b/src/libs/installer/downloadarchivesjob.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -32,6 +32,7 @@
#include "messageboxhandler.h"
#include "packagemanagercore.h"
#include "utils.h"
+#include "fileutils.h"
#include "filedownloader.h"
#include "filedownloaderfactory.h"
@@ -55,6 +56,8 @@ DownloadArchivesJob::DownloadArchivesJob(PackageManagerCore *core)
, m_canceled(false)
, m_lastFileProgress(0)
, m_progressChangedTimerId(0)
+ , m_totalSizeToDownload(0)
+ , m_totalSizeDownloaded(0)
{
setCapabilities(Cancelable);
}
@@ -79,10 +82,19 @@ void DownloadArchivesJob::setArchivesToDownload(const QList<QPair<QString, QStri
}
/*!
+ Sets the expected total size of archives to download to \a total.
+*/
+void DownloadArchivesJob::setExpectedTotalSize(quint64 total)
+{
+ m_totalSizeToDownload = total;
+}
+
+/*!
\reimp
*/
void DownloadArchivesJob::doStart()
{
+ m_totalDownloadSpeedTimer.start();
m_archivesDownloaded = 0;
fetchNextArchiveHash();
}
@@ -198,6 +210,70 @@ void DownloadArchivesJob::timerEvent(QTimerEvent *event)
}
/*!
+ Builds a textual representation of the total download \a status and
+ emits the \c {downloadStatusChanged()} signal.
+*/
+void DownloadArchivesJob::onDownloadStatusChanged(const QString &status)
+{
+ if (!m_downloader || m_canceled) {
+ emit downloadStatusChanged(status);
+ return;
+ }
+
+ QString extendedStatus;
+ quint64 currentDownloaded = m_totalSizeDownloaded + m_downloader->getBytesReceived();
+ if (m_totalSizeToDownload > 0) {
+ QString bytesReceived = humanReadableSize(currentDownloaded);
+ const QString bytesToReceive = humanReadableSize(m_totalSizeToDownload);
+
+ // remove the unit from the bytesReceived value if bytesToReceive has the same
+ const QString tmp = bytesToReceive.mid(bytesToReceive.indexOf(QLatin1Char(' ')));
+ if (bytesReceived.endsWith(tmp))
+ bytesReceived.chop(tmp.length());
+
+ extendedStatus = tr("%1 of %2").arg(bytesReceived, bytesToReceive);
+ } else if (currentDownloaded > 0) {
+ extendedStatus = tr("%1 downloaded.").arg(humanReadableSize(currentDownloaded));
+ }
+
+ const quint64 totalDownloadSpeed = currentDownloaded
+ / double(m_totalDownloadSpeedTimer.elapsed() / 1000);
+
+ if (m_totalSizeToDownload > 0 && totalDownloadSpeed > 0) {
+ const qint64 time = (m_totalSizeToDownload - currentDownloaded) / totalDownloadSpeed;
+
+ int s = time % 60;
+ const int d = time / 86400;
+ const int h = (time / 3600) - (d * 24);
+ const int m = (time / 60) - (d * 1440) - (h * 60);
+
+ QString days;
+ if (d > 0)
+ days = tr("%n day(s), ", "", d);
+
+ QString hours;
+ if (h > 0)
+ hours = tr("%n hour(s), ", "", h);
+
+ QString minutes;
+ if (m > 0)
+ minutes = tr("%n minute(s)", "", m);
+
+ QString seconds;
+ if (s >= 0 && minutes.isEmpty()) {
+ s = (s <= 0 ? 1 : s);
+ seconds = tr("%n second(s)", "", s);
+ }
+ extendedStatus += tr(" - %1%2%3%4 remaining.").arg(days, hours, minutes, seconds);
+ } else {
+ extendedStatus += tr(" - unknown time remaining.");
+ }
+
+ emit downloadStatusChanged(tr("Archive: ") + status
+ + QLatin1String("<br>") + tr("Total: ")+ extendedStatus);
+}
+
+/*!
Registers the just downloaded file in the installer's file system.
*/
void DownloadArchivesJob::registerFile()
@@ -224,6 +300,7 @@ void DownloadArchivesJob::registerFile()
}
} else {
++m_archivesDownloaded;
+ m_totalSizeDownloaded += QFile(m_downloader->downloadedFileName()).size();
if (m_progressChangedTimerId) {
killTimer(m_progressChangedTimerId);
m_progressChangedTimerId = 0;
@@ -295,7 +372,7 @@ KDUpdater::FileDownloader *DownloadArchivesJob::setupDownloader(const QString &s
connect(downloader, &FileDownloader::downloadCanceled, this, &DownloadArchivesJob::downloadCanceled);
connect(downloader, &FileDownloader::downloadAborted, this, &DownloadArchivesJob::downloadFailed,
Qt::QueuedConnection);
- connect(downloader, &FileDownloader::downloadStatus, this, &DownloadArchivesJob::downloadStatusChanged);
+ connect(downloader, &FileDownloader::downloadStatus, this, &DownloadArchivesJob::onDownloadStatusChanged);
if (FileDownloaderFactory::isSupportedScheme(scheme)) {
downloader->setDownloadedFileName(component->localTempPath() + QLatin1Char('/')
diff --git a/src/libs/installer/downloadarchivesjob.h b/src/libs/installer/downloadarchivesjob.h
index 8f2392064..bd764e01c 100644
--- a/src/libs/installer/downloadarchivesjob.h
+++ b/src/libs/installer/downloadarchivesjob.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -32,6 +32,7 @@
#include "job.h"
#include <QtCore/QPair>
+#include <QtCore/QElapsedTimer>
QT_BEGIN_NAMESPACE
class QTimerEvent;
@@ -56,6 +57,7 @@ public:
int numberOfDownloads() const { return m_archivesDownloaded; }
void setArchivesToDownload(const QList<QPair<QString, QString> > &archives);
+ void setExpectedTotalSize(quint64 total);
Q_SIGNALS:
void progressChanged(double progress);
@@ -67,6 +69,9 @@ protected:
void doCancel();
void timerEvent(QTimerEvent *event);
+public Q_SLOTS:
+ void onDownloadStatusChanged(const QString &status);
+
protected Q_SLOTS:
void registerFile();
void downloadCanceled();
@@ -92,6 +97,10 @@ private:
QByteArray m_currentHash;
double m_lastFileProgress;
int m_progressChangedTimerId;
+
+ quint64 m_totalSizeToDownload;
+ quint64 m_totalSizeDownloaded;
+ QElapsedTimer m_totalDownloadSpeedTimer;
};
} // namespace QInstaller
diff --git a/src/libs/installer/downloadfiletask.cpp b/src/libs/installer/downloadfiletask.cpp
index d9f1e3432..1b9f81ecc 100644
--- a/src/libs/installer/downloadfiletask.cpp
+++ b/src/libs/installer/downloadfiletask.cpp
@@ -1,7 +1,7 @@
/**************************************************************************
**
-** 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.
@@ -227,8 +227,8 @@ void Downloader::onFinished(QNetworkReply *reply)
QNetworkReply *const redirectReply = startDownload(taskItem);
foreach (const QUrl &redirect, redirects)
- m_redirects.insertMulti(redirectReply, redirect);
- m_redirects.insertMulti(redirectReply, url);
+ m_redirects.insert(redirectReply, redirect);
+ m_redirects.insert(redirectReply, url);
m_downloads.erase(reply);
m_redirects.remove(reply);
diff --git a/src/libs/installer/elevatedexecuteoperation.cpp b/src/libs/installer/elevatedexecuteoperation.cpp
index f5640ef8d..fb1778fe0 100644
--- a/src/libs/installer/elevatedexecuteoperation.cpp
+++ b/src/libs/installer/elevatedexecuteoperation.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -31,6 +31,7 @@
#include "environment.h"
#include "qprocesswrapper.h"
#include "globals.h"
+#include "packagemanagercore.h"
#include <QtCore/QDebug>
#include <QtCore/QProcessEnvironment>
@@ -60,7 +61,11 @@ private:
public:
void readProcessOutput();
- bool run(const QStringList &arguments);
+ int run(QStringList &arguments, const OperationType type);
+
+private:
+ bool needsRerunWithReplacedVariables(QStringList &arguments, const OperationType type);
+
QProcessWrapper *process;
bool showStandardError;
@@ -72,6 +77,7 @@ ElevatedExecuteOperation::ElevatedExecuteOperation(PackageManagerCore *core)
{
// this operation has to "overwrite" the Execute operation from KDUpdater
setName(QLatin1String("Execute"));
+ setRequiresUnreplacedVariables(true);
}
ElevatedExecuteOperation::~ElevatedExecuteOperation()
@@ -94,10 +100,14 @@ bool ElevatedExecuteOperation::performOperation()
break; //we don't need the UNDOEXECUTE args here
}
- return d->run(args);
+ if (requiresUnreplacedVariables()) {
+ PackageManagerCore *const core = packageManager();
+ args = core->replaceVariables(args);
+ }
+ return d->run(args, Operation::Perform) ? false : true;
}
-bool ElevatedExecuteOperation::Private::run(const QStringList &arguments)
+int ElevatedExecuteOperation::Private::run(QStringList &arguments, const OperationType type)
{
QStringList args = arguments;
QString workingDirectory;
@@ -141,13 +151,15 @@ bool ElevatedExecuteOperation::Private::run(const QStringList &arguments)
// unix style: when there's an ampersand after the command, it's started detached
if (args.count() >= 2 && args.last() == QLatin1String("&")) {
+ int returnValue = NoError;
args.pop_back();
const bool success = QProcessWrapper::startDetached(args.front(), args.mid(1));
if (!success) {
q->setError(UserDefinedError);
q->setErrorString(tr("Cannot start detached: \"%1\"").arg(callstr));
+ returnValue = Error;
}
- return success;
+ return returnValue;
}
process = new QProcessWrapper();
@@ -157,7 +169,7 @@ bool ElevatedExecuteOperation::Private::run(const QStringList &arguments)
<< workingDirectory;
}
- QProcessEnvironment penv;
+ QProcessEnvironment penv = QProcessEnvironment::systemEnvironment();
// there is no way to serialize a QProcessEnvironment properly other than per mangled QStringList:
// (i.e. no other way to list all keys)
process->setEnvironment(KDUpdater::Environment::instance().applyTo(penv).toStringList());
@@ -186,13 +198,17 @@ bool ElevatedExecuteOperation::Private::run(const QStringList &arguments)
success = process->waitForFinished(-1);
}
- bool returnValue = true;
+ int returnValue = NoError;
if (!success) {
q->setError(UserDefinedError);
//TODO: pass errorString() through the wrapper */
q->setErrorString(tr("Cannot start: \"%1\": %2").arg(callstr,
process->errorString()));
- returnValue = false;
+ if (!needsRerunWithReplacedVariables(arguments, type)) {
+ returnValue = Error;
+ } else {
+ returnValue = NeedsRerun;
+ }
}
if (QThread::currentThread() == qApp->thread()) {
@@ -207,26 +223,29 @@ bool ElevatedExecuteOperation::Private::run(const QStringList &arguments)
if (process->exitStatus() == QProcessWrapper::CrashExit) {
q->setError(UserDefinedError);
q->setErrorString(tr("Program crashed: \"%1\"").arg(callstr));
- returnValue = false;
+ returnValue = Error;
}
- if (!allowedExitCodes.contains(process->exitCode())) {
- q->setError(UserDefinedError);
- if (customErrorMessage.isEmpty()) {
- q->setErrorString(tr("Execution failed (Unexpected exit code: %1): \"%2\"")
- .arg(QString::number(process->exitCode()), callstr));
+ if (!allowedExitCodes.contains(process->exitCode()) && returnValue != NeedsRerun) {
+ if (!needsRerunWithReplacedVariables(arguments, type)) {
+ q->setError(UserDefinedError);
+ if (customErrorMessage.isEmpty()) {
+ q->setErrorString(tr("Execution failed (Unexpected exit code: %1): \"%2\"")
+ .arg(QString::number(process->exitCode()), callstr));
+ } else {
+ q->setErrorString(customErrorMessage);
+ }
+
+ QByteArray standardErrorOutput = process->readAllStandardError();
+ // in error case it would be useful to see something in verbose output
+ if (!standardErrorOutput.isEmpty())
+ qCWarning(QInstaller::lcInstallerInstallLog).noquote() << standardErrorOutput;
+
+ returnValue = Error;
} else {
- q->setErrorString(customErrorMessage);
+ returnValue = NeedsRerun;
}
-
- QByteArray standardErrorOutput = process->readAllStandardError();
- // in error case it would be useful to see something in verbose output
- if (!standardErrorOutput.isEmpty())
- qCWarning(QInstaller::lcInstallerInstallLog).noquote() << standardErrorOutput;
-
- returnValue = false;
}
-
Q_ASSERT(process);
Q_ASSERT(process->state() == QProcessWrapper::NotRunning);
delete process;
@@ -235,6 +254,28 @@ bool ElevatedExecuteOperation::Private::run(const QStringList &arguments)
return returnValue;
}
+bool ElevatedExecuteOperation::Private::needsRerunWithReplacedVariables(QStringList &arguments, const OperationType type)
+{
+ if (type != Operation::Undo)
+ return false;
+ bool rerun = false;
+ PackageManagerCore *const core = q->packageManager();
+ for (int i = 0; i < arguments.count(); i++) {
+ QString key = core->key(arguments.at(i));
+ if (!key.isEmpty() && key.endsWith(QLatin1String("_OLD"))) {
+ key.remove(key.length() - 4, 4);
+ if (core->containsValue(key)) {
+ key.prepend(QLatin1String("@"));
+ key.append(QLatin1String("@"));
+ QString value = core->replaceVariables(key);
+ arguments.replace(i, value);
+ rerun = true;
+ }
+ }
+ }
+ return rerun;
+}
+
/*!
Cancels the ElevatedExecuteOperation. This methods tries to terminate the process
gracefully by calling QProcessWrapper::terminate. After 10 seconds, the process gets killed.
@@ -262,7 +303,6 @@ void ElevatedExecuteOperation::Private::readProcessOutput()
}
}
-
bool ElevatedExecuteOperation::undoOperation()
{
QStringList args;
@@ -276,7 +316,21 @@ bool ElevatedExecuteOperation::undoOperation()
if (args.isEmpty())
return true;
- return d->run(args);
+ if (requiresUnreplacedVariables()) {
+ PackageManagerCore *const core = packageManager();
+ args = core->replaceVariables(args);
+ }
+
+ int returnValue = d->run(args, Operation::Undo);
+ if (returnValue == NeedsRerun) {
+ qCDebug(QInstaller::lcInstallerInstallLog).noquote() << QString::fromLatin1("Failed to run "
+ "undo operation \"%1\" for component %2. Trying again with arguments %3").arg(name(),
+ value(QLatin1String("component")).toString(), args.join(QLatin1String(", ")));
+ setError(NoError);
+ setErrorString(QString());
+ returnValue = d->run(args, Operation::Undo);
+ }
+ return returnValue ? false : true;
}
bool ElevatedExecuteOperation::testOperation()
diff --git a/src/libs/installer/elevatedexecuteoperation.h b/src/libs/installer/elevatedexecuteoperation.h
index 5585e77a8..470d3e506 100644
--- a/src/libs/installer/elevatedexecuteoperation.h
+++ b/src/libs/installer/elevatedexecuteoperation.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -38,6 +38,12 @@ class INSTALLER_EXPORT ElevatedExecuteOperation : public QObject, public Operati
Q_OBJECT
public:
+ enum Error {
+ NoError = 0,
+ Error,
+ NeedsRerun
+ };
+
explicit ElevatedExecuteOperation(PackageManagerCore *core);
~ElevatedExecuteOperation();
diff --git a/src/libs/installer/extractarchiveoperation.cpp b/src/libs/installer/extractarchiveoperation.cpp
index 99dd88a7e..7660dadb0 100644
--- a/src/libs/installer/extractarchiveoperation.cpp
+++ b/src/libs/installer/extractarchiveoperation.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -88,27 +88,28 @@ bool ExtractArchiveOperation::performOperation()
connect(&callback, &Callback::progressChanged, this, &ExtractArchiveOperation::progressChanged);
- if (PackageManagerCore *core = packageManager()) {
- connect(core, &PackageManagerCore::statusChanged, &callback, &Callback::statusChanged);
- }
-
- Runnable *runnable = new Runnable(archivePath, targetDir, &callback);
- connect(runnable, &Runnable::finished, &receiver, &Receiver::runnableFinished,
+ Worker *worker = new Worker(archivePath, targetDir, &callback);
+ connect(worker, &Worker::finished, &receiver, &Receiver::workerFinished,
Qt::QueuedConnection);
+ if (PackageManagerCore *core = packageManager())
+ connect(core, &PackageManagerCore::statusChanged, worker, &Worker::onStatusChanged);
+
QFileInfo fileInfo(archivePath);
emit outputTextChanged(tr("Extracting \"%1\"").arg(fileInfo.fileName()));
+ {
+ QEventLoop loop;
+ QThread workerThread;
+ worker->moveToThread(&workerThread);
- QEventLoop loop;
- connect(&receiver, &Receiver::finished, &loop, &QEventLoop::quit);
- if (QThreadPool::globalInstance()->tryStart(runnable)) {
+ connect(&workerThread, &QThread::started, worker, &Worker::run);
+ connect(&receiver, &Receiver::finished, &workerThread, &QThread::quit);
+ connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
+ connect(&workerThread, &QThread::finished, &loop, &QEventLoop::quit);
+
+ workerThread.start();
loop.exec();
- } else {
- // HACK: In case there is no availabe thread we should call it directly.
- runnable->run();
- receiver.runnableFinished(true, QString());
}
-
// Write all file names which belongs to a package to a separate file and only the separate
// filename to a .dat file. There can be enormous amount of files in a package, which makes
// the dat file very slow to read and write. The .dat file is read into memory in startup,
@@ -122,9 +123,15 @@ bool ExtractArchiveOperation::performOperation()
QStringList files = callback.extractedFiles();
- const QString resourcesPath = targetDir + QLatin1Char('/') + QLatin1String("installerResources");
- QString fileDirectory = resourcesPath + QLatin1Char('/') +
- archivePath.section(QLatin1Char('/'), 1, 1, QString::SectionSkipEmpty) + QLatin1Char('/');
+ QString installDir = targetDir;
+ // If we have package manager in use (normal installer run) then use
+ // TargetDir for saving filenames, otherwise those would be saved to
+ // extracted folder.
+ 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);
QFileInfo fileInfo2(archiveFileName);
QString suffix = fileInfo2.suffix();
@@ -145,7 +152,7 @@ bool ExtractArchiveOperation::performOperation()
setDefaultFilePermissions(file.fileName(), DefaultFilePermissions::NonExecutable);
QDataStream out (&file);
for (int i = 0; i < files.count(); ++i) {
- files[i] = replacePath(files.at(i), targetDir, QLatin1String(scRelocatable));
+ files[i] = replacePath(files.at(i), installDir, QLatin1String(scRelocatable));
}
out << files;
setValue(QLatin1String("files"), file.fileName());
@@ -157,7 +164,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 Backup &i, callback.backupFiles())
+ foreach (const QInstaller::Backup &i, callback.backupFiles())
deleteFileNowOrLater(i.second);
if (!receiver.success()) {
@@ -176,6 +183,8 @@ bool ExtractArchiveOperation::undoOperation()
// If yes, files are listed in .dat instead of in a separate file.
bool useStringListType(value(QLatin1String("files")).type() == QVariant::StringList);
QString targetDir = arguments().at(1);
+ if (packageManager())
+ targetDir = packageManager()->value(scTargetDir);
QStringList files;
if (useStringListType) {
files = value(QLatin1String("files")).toStringList();
diff --git a/src/libs/installer/extractarchiveoperation.h b/src/libs/installer/extractarchiveoperation.h
index fa05d403a..7fc008887 100644
--- a/src/libs/installer/extractarchiveoperation.h
+++ b/src/libs/installer/extractarchiveoperation.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -63,7 +63,7 @@ private:
private:
class Callback;
- class Runnable;
+ class Worker;
class Receiver;
};
diff --git a/src/libs/installer/extractarchiveoperation_p.h b/src/libs/installer/extractarchiveoperation_p.h
index 9cc07246f..706187eb7 100644
--- a/src/libs/installer/extractarchiveoperation_p.h
+++ b/src/libs/installer/extractarchiveoperation_p.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -31,8 +31,7 @@
#include "extractarchiveoperation.h"
#include "fileutils.h"
-#include "lib7z_extract.h"
-#include "lib7z_facade.h"
+#include "archivefactory.h"
#include "packagemanagercore.h"
#include <QRunnable>
@@ -85,7 +84,7 @@ private:
typedef QPair<QString, QString> Backup;
typedef QVector<Backup> BackupFiles;
-class ExtractArchiveOperation::Callback : public QObject, public Lib7z::ExtractCallback
+class ExtractArchiveOperation::Callback : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(Callback)
@@ -93,36 +92,14 @@ class ExtractArchiveOperation::Callback : public QObject, public Lib7z::ExtractC
public:
Callback() = default;
- BackupFiles backupFiles() const {
+ BackupFiles backupFiles() const
+ {
return m_backupFiles;
}
- QStringList extractedFiles() const {
- return m_extractedFiles;
- }
-
-public slots:
- void statusChanged(QInstaller::PackageManagerCore::Status status)
+ QStringList extractedFiles() const
{
- switch(status) {
- case PackageManagerCore::Canceled:
- m_state = E_ABORT;
- break;
- case PackageManagerCore::Failure:
- m_state = E_FAIL;
- break;
- default: // ignore all other status values
- break;
- }
- }
-
-signals:
- void progressChanged(double progress);
-
-private:
- void setCurrentFile(const QString &filename) Q_DECL_OVERRIDE
- {
- m_extractedFiles.prepend(QDir::toNativeSeparators(filename));
+ return m_extractedFiles;
}
static QString generateBackupName(const QString &fn)
@@ -135,7 +112,7 @@ private:
return res;
}
- bool prepareForFile(const QString &filename) Q_DECL_OVERRIDE
+ bool prepareForFile(const QString &filename)
{
if (!QFile::exists(filename))
return true;
@@ -151,59 +128,110 @@ private:
return true;
}
- HRESULT setCompleted(quint64 completed, quint64 total) Q_DECL_OVERRIDE
+Q_SIGNALS:
+ void progressChanged(double progress);
+
+public Q_SLOTS:
+ void onCurrentEntryChanged(const QString &filename)
+ {
+ m_extractedFiles.prepend(QDir::toNativeSeparators(filename));
+ }
+
+ void onCompletedChanged(quint64 completed, quint64 total)
{
emit progressChanged(double(completed) / total);
- return m_state;
}
private:
- HRESULT m_state = S_OK;
BackupFiles m_backupFiles;
QStringList m_extractedFiles;
};
-class ExtractArchiveOperation::Runnable : public QObject, public QRunnable
+class ExtractArchiveOperation::Worker : public QObject
{
Q_OBJECT
- Q_DISABLE_COPY(Runnable)
+ Q_DISABLE_COPY(Worker)
public:
- Runnable(const QString &archivePath, const QString &targetDir,
- ExtractArchiveOperation::Callback *callback)
+ Worker(const QString &archivePath, const QString &targetDir, Callback *callback)
: m_archivePath(archivePath)
, m_targetDir(targetDir)
+ , m_canceled(false)
, m_callback(callback)
{}
+Q_SIGNALS:
+ void finished(bool success, const QString &errorString);
+
+public Q_SLOTS:
void run()
{
- QFile archive(m_archivePath);
- if (!archive.open(QIODevice::ReadOnly)) {
- emit finished(false, tr("Cannot open archive \"%1\" for reading: %2").arg(m_archivePath,
- archive.errorString()));
+ m_canceled = false;
+ m_archive.reset(ArchiveFactory::instance().create(m_archivePath));
+ if (!m_archive) {
+ emit finished(false, tr("Could not create handler object for archive \"%1\": \"%2\".")
+ .arg(m_archivePath, QLatin1String(Q_FUNC_INFO)));
return;
}
- try {
- Lib7z::extractArchive(&archive, m_targetDir, m_callback);
- emit finished(true, QString());
- } catch (const Lib7z::SevenZipException& e) {
+ connect(m_archive.get(), &AbstractArchive::currentEntryChanged, m_callback, &Callback::onCurrentEntryChanged);
+ connect(m_archive.get(), &AbstractArchive::completedChanged, m_callback, &Callback::onCompletedChanged);
+
+ if (!(m_archive->open(QIODevice::ReadOnly) && m_archive->isSupported())) {
+ emit finished(false, tr("Cannot open archive \"%1\" for reading: %2").arg(m_archivePath,
+ m_archive->errorString()));
+ return;
+ }
+ const QVector<ArchiveEntry> entries = m_archive->list();
+ if (entries.isEmpty()) {
+ emit finished(false, tr("Error while reading contents of archive \"%1\": %2").arg(m_archivePath,
+ m_archive->errorString()));
+ return;
+ }
+ for (auto &entry : entries) {
+ QString completeFilePath = m_targetDir + QDir::separator() + entry.path;
+ if (!entry.isDirectory && !m_callback->prepareForFile(completeFilePath)) {
+ emit finished(false, tr("Cannot prepare for file \"%1\"").arg(completeFilePath));
+ return;
+ }
+ }
+ if (m_canceled) {
+ // For large archives the reading takes some time, and the user might have
+ // canceled before we start the actual extracting.
+ emit finished(false, tr("Extract for archive \"%1\" canceled.").arg(m_archivePath));
+ } else if (!m_archive->extract(m_targetDir, entries.size())) {
emit finished(false, tr("Error while extracting archive \"%1\": %2").arg(m_archivePath,
- e.message()));
- } catch (...) {
- emit finished(false, tr("Unknown exception caught while extracting \"%1\".")
- .arg(m_archivePath));
+ m_archive->errorString()));
+ } else {
+ emit finished(true, QString());
}
}
-signals:
- void finished(bool success, const QString &errorString);
+ void onStatusChanged(PackageManagerCore::Status status)
+ {
+ if (!m_archive)
+ return;
+
+ switch (status) {
+ case PackageManagerCore::Canceled:
+ m_canceled = true;
+ m_archive->cancel();
+ break;
+ case PackageManagerCore::Failure:
+ m_canceled = true;
+ m_archive->cancel();
+ break;
+ default: // ignore all other status values
+ break;
+ }
+ }
private:
QString m_archivePath;
QString m_targetDir;
- ExtractArchiveOperation::Callback *m_callback;
+ QScopedPointer<AbstractArchive> m_archive;
+ bool m_canceled;
+ Callback *m_callback;
};
class ExtractArchiveOperation::Receiver : public QObject
@@ -223,7 +251,7 @@ public:
}
public slots:
- void runnableFinished(bool ok, const QString &msg)
+ void workerFinished(bool ok, const QString &msg)
{
m_success = ok;
m_errorString = msg;
diff --git a/src/libs/installer/fakestopprocessforupdateoperation.cpp b/src/libs/installer/fakestopprocessforupdateoperation.cpp
index 36892eac7..67d60a92f 100644
--- a/src/libs/installer/fakestopprocessforupdateoperation.cpp
+++ b/src/libs/installer/fakestopprocessforupdateoperation.cpp
@@ -68,7 +68,7 @@ bool FakeStopProcessForUpdateOperation::undoOperation()
return false;
}
- QStringList processes = arguments().at(0).split(QLatin1Char(','), QString::SkipEmptyParts);
+ QStringList processes = arguments().at(0).split(QLatin1Char(','), Qt::SkipEmptyParts);
for (int i = processes.count() - 1; i >= 0; --i) {
if (!core->isProcessRunning(processes.at(i)))
processes.removeAt(i);
diff --git a/src/libs/installer/fileutils.cpp b/src/libs/installer/fileutils.cpp
index d0dd342a8..d534b3651 100644
--- a/src/libs/installer/fileutils.cpp
+++ b/src/libs/installer/fileutils.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -28,6 +28,8 @@
#include "fileutils.h"
#include "globals.h"
+#include "constants.h"
+#include "fileio.h"
#include <errors.h>
#include <QtCore/QDateTime>
@@ -39,6 +41,9 @@
#include <QtCore/QUrl>
#include <QtCore/QCoreApplication>
#include <QImageReader>
+#include <QRandomGenerator>
+#include <QGuiApplication>
+#include <QScreen>
#include <errno.h>
@@ -73,7 +78,7 @@ TempDirDeleter::TempDirDeleter(const QString &path)
}
TempDirDeleter::TempDirDeleter(const QStringList &paths)
- : m_paths(paths.toSet())
+ : m_paths(QSet<QString>(paths.begin(), paths.end()))
{
}
@@ -94,29 +99,7 @@ void TempDirDeleter::add(const QString &path)
void TempDirDeleter::add(const QStringList &paths)
{
- m_paths += paths.toSet();
-}
-
-void TempDirDeleter::releaseAll()
-{
- m_paths.clear();
-}
-
-void TempDirDeleter::release(const QString &path)
-{
- m_paths.remove(path);
-}
-
-void TempDirDeleter::passAndReleaseAll(TempDirDeleter &tdd)
-{
- tdd.m_paths = m_paths;
- releaseAll();
-}
-
-void TempDirDeleter::passAndRelease(TempDirDeleter &tdd, const QString &path)
-{
- tdd.add(path);
- release(path);
+ m_paths += QSet<QString>(paths.begin(), paths.end());
}
void TempDirDeleter::releaseAndDeleteAll()
@@ -471,32 +454,21 @@ void QInstaller::mkpath(const QString &path)
*/
QString QInstaller::generateTemporaryFileName(const QString &templ)
{
- if (templ.isEmpty()) {
- QTemporaryFile f;
- if (!f.open()) {
+ static const QLatin1String staticPart("%1.tmp.XXXXXX");
+
+ QTemporaryFile f;
+ if (!templ.isEmpty())
+ f.setFileTemplate(QString(staticPart).arg(templ));
+
+ if (!f.open()) {
+ if (!templ.isEmpty()) {
+ throw Error(QCoreApplication::translate("QInstaller",
+ "Cannot open temporary file for template %1: %2").arg(templ, f.errorString()));
+ } else {
throw Error(QCoreApplication::translate("QInstaller",
"Cannot open temporary file: %1").arg(f.errorString()));
}
- return f.fileName();
}
-
- static const QString characters = QLatin1String("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
- QString suffix;
- qsrand(qrand() * QDateTime::currentDateTime().toTime_t());
- for (int i = 0; i < 5; ++i)
- suffix += characters[qrand() % characters.length()];
-
- const QString tmp = QLatin1String("%1.tmp.%2.%3");
- int count = 1;
- while (QFile::exists(tmp.arg(templ, suffix).arg(count)))
- ++count;
-
- QFile f(tmp.arg(templ, suffix).arg(count));
- if (!f.open(QIODevice::WriteOnly)) {
- throw Error(QCoreApplication::translate("QInstaller",
- "Cannot open temporary file for template %1: %2").arg(templ, f.errorString()));
- }
- f.remove();
return f.fileName();
}
@@ -729,3 +701,111 @@ QString QInstaller::replacePath(const QString &path, const QString &before, cons
return QDir::cleanPath(after) + pathToPatch.mid(pathToReplace.size());
return path;
}
+
+/*!
+ Replaces \a imagePath with high dpi image. If high dpi image is not provided or
+ high dpi screen is not in use, the original value is returned.
+*/
+void QInstaller::replaceHighDpiImage(QString &imagePath)
+{
+ if (QGuiApplication::primaryScreen()->devicePixelRatio() >= 2 ) {
+ QFileInfo fi(imagePath);
+ QString highdpiPixmap = fi.absolutePath() + QLatin1Char('/') + fi.baseName() + scHighDpi + fi.suffix();
+ if (QFileInfo::exists(highdpiPixmap))
+ imagePath = highdpiPixmap;
+ }
+}
+
+/*!
+ Copies an internal configuration file from \a source to \a target. The XML elements,
+ and their children, specified by \a elementsToRemoveTags will be removed from the \a target
+ file. All relative filenames referenced in the \a source configuration file will be
+ also copied to the location of the \a target file.
+
+ Throws \c QInstaller::Error in case of failure.
+*/
+void QInstaller::trimmedCopyConfigData(const QString &source, const QString &target, const QStringList &elementsToRemoveTags)
+{
+ qCDebug(QInstaller::lcDeveloperBuild) << "Copying configuration file and associated data.";
+
+ const QString targetPath = QFileInfo(target).absolutePath();
+ if (!QDir(targetPath).exists() && !QDir().mkpath(targetPath)) {
+ throw Error(QCoreApplication::translate("QInstaller",
+ "Cannot create directory \"%1\".").arg(targetPath));
+ }
+
+ QFile xmlFile(source);
+ if (!xmlFile.copy(target)) {
+ throw Error(QCoreApplication::translate("QInstaller",
+ "Cannot copy file \"%1\" to \"%2\": %3").arg(source, target, xmlFile.errorString()));
+ }
+ setDefaultFilePermissions(target, DefaultFilePermissions::NonExecutable);
+
+ xmlFile.setFileName(target);
+ QInstaller::openForRead(&xmlFile); // throws in case of error
+
+ QDomDocument dom;
+ dom.setContent(&xmlFile);
+ xmlFile.close();
+
+ foreach (auto elementTag, elementsToRemoveTags) {
+ QDomNodeList elementsToRemove = dom.elementsByTagName(elementTag);
+ for (int i = 0; i < elementsToRemove.length(); i++) {
+ QDomNode elementToRemove = elementsToRemove.item(i);
+ elementToRemove.parentNode().removeChild(elementToRemove);
+
+ qCDebug(QInstaller::lcDeveloperBuild) << "Removed dom element from target file:"
+ << elementToRemove.toElement().text();
+ }
+ }
+
+ const QDomNodeList children = dom.documentElement().childNodes();
+ copyConfigChildElements(dom, children, QFileInfo(source).absolutePath(), QFileInfo(target).absolutePath());
+
+ QInstaller::openForWrite(&xmlFile); // throws in case of error
+ QTextStream stream(&xmlFile);
+ dom.save(stream, 4); // use 4 as the amount of space for indentation
+
+ qCDebug(QInstaller::lcDeveloperBuild) << "Finished copying configuration data.";
+}
+
+/*!
+ \internal
+
+ Recursively iterates over a list of QDomNode \a objects belonging to \a dom and their
+ children accordingly, searching for relative file names. Found files are copied from
+ \a sourceDir to \a targetDir.
+
+ Throws \c QInstaller::Error in case of failure.
+*/
+void QInstaller::copyConfigChildElements(QDomDocument &dom, const QDomNodeList &objects,
+ const QString &sourceDir, const QString &targetDir)
+{
+ for (int i = 0; i < objects.length(); i++) {
+ QDomElement domElement = objects.at(i).toElement();
+ if (domElement.isNull())
+ continue;
+
+ // Iterate recursively over all child nodes
+ const QDomNodeList elementChildren = domElement.childNodes();
+ QInstaller::copyConfigChildElements(dom, elementChildren, sourceDir, targetDir);
+
+ // Filename may also contain a path relative to source directory but we
+ // copy it strictly into target directory without extra paths
+ const QString newName = domElement.text()
+ .replace(QRegExp(QLatin1String("\\\\|/|\\.|:")), QLatin1String("_"));
+
+ const QString targetFile = targetDir + QDir::separator() + newName;
+ const QFileInfo elementFileInfo = QFileInfo(sourceDir, domElement.text());
+
+ if (!elementFileInfo.exists() || elementFileInfo.isDir())
+ continue;
+
+ domElement.replaceChild(dom.createTextNode(newName), domElement.firstChild());
+
+ if (!QFile::copy(elementFileInfo.absoluteFilePath(), targetFile)) {
+ throw Error(QCoreApplication::translate("QInstaller",
+ "Cannot copy file \"%1\" to \"%2\".").arg(elementFileInfo.absoluteFilePath(), targetFile));
+ }
+ }
+}
diff --git a/src/libs/installer/fileutils.h b/src/libs/installer/fileutils.h
index 24b033e98..ac3f95098 100644
--- a/src/libs/installer/fileutils.h
+++ b/src/libs/installer/fileutils.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -33,6 +33,8 @@
#include <QtCore/QSet>
#include <QtCore/QString>
#include <QtCore/QStringList>
+#include <QtXml/QDomDocument>
+#include <QtXml/QDomNodeList>
QT_BEGIN_NAMESPACE
class QFileInfo;
@@ -59,11 +61,6 @@ public:
void add(const QString &path);
void add(const QStringList &paths);
- void releaseAll();
- void release(const QString &path);
- void passAndReleaseAll(TempDirDeleter &tdd);
- void passAndRelease(TempDirDeleter &tdd, const QString &path);
-
void releaseAndDeleteAll();
void releaseAndDelete(const QString &path);
@@ -97,6 +94,10 @@ private:
bool INSTALLER_EXPORT isInBundle(const QString &path, QString *bundlePath = 0);
QString replacePath(const QString &path, const QString &pathBefore, const QString &pathAfter);
+ void replaceHighDpiImage(QString &imagePath);
+
+ void INSTALLER_EXPORT trimmedCopyConfigData(const QString &source, const QString &target, const QStringList &elementsToRemoveTags);
+ void copyConfigChildElements(QDomDocument &dom, const QDomNodeList &objects, const QString &sourceDir, const QString &targetDir);
#ifdef Q_OS_WIN
QString INSTALLER_EXPORT getLongPathName(const QString &name);
diff --git a/src/libs/installer/globals.cpp b/src/libs/installer/globals.cpp
index 66de2fec6..adf1d2f6e 100644
--- a/src/libs/installer/globals.cpp
+++ b/src/libs/installer/globals.cpp
@@ -31,30 +31,6 @@
#include "globals.h"
const char IFW_SERVER[] = "ifw.server";
-
-const char IFW_PACKAGE_DISPLAYNAME[] = "ifw.package.displayname";
-const char IFW_PACKAGE_DESCRIPTION[] = "ifw.package.description";
-const char IFW_PACKAGE_VERSION[] = "ifw.package.version";
-const char IFW_PACKAGE_INSTALLEDVERSION[] = "ifw.package.installedversion";
-const char IFW_PACKAGE_RELEASEDATE[] = "ifw.package.releasedate";
-const char IFW_PACKAGE_INSTALLDATE[] = "ifw.package.installdate";
-const char IFW_PACKAGE_UPDATEDATE[] = "ifw.package.updatedate";
-const char IFW_PACKAGE_NAME[] = "ifw.package.name";
-const char IFW_PACKAGE_DEPENDENCIES[] = "ifw.package.dependencies";
-const char IFW_PACKAGE_AUTODEPENDON[] = "ifw.package.autodependon";
-const char IFW_PACKAGE_VIRTUAL[] = "ifw.package.virtual";
-const char IFW_PACKAGE_SORTINGPRIORITY[] = "ifw.package.sortingpriority";
-const char IFW_PACKAGE_SCRIPT[] = "ifw.package.script";
-const char IFW_PACKAGE_DEFAULT[] = "ifw.package.default";
-const char IFW_PACKAGE_ESSETIAL[] = "ifw.package.essential";
-const char IFW_PACKAGE_FORCEDINSTALLATION[] = "ifw.package.forcedinstallation";
-const char IFW_PACKAGE_REPLACES[] = "ifw.package.replaces";
-const char IFW_PACKAGE_DOWNLOADABLEARCHIVES[] = "ifw.package.downloadablearchives";
-const char IFW_PACKAGE_REQUIRESADMINRIGHTS[] = "ifw.package.requiresadminrights";
-const char IFW_PACKAGE_CHECKABLE[] = "ifw.package.checkable";
-const char IFW_PACKAGE_LICENSES[] = "ifw.package.licenses";
-const char IFW_PACKAGE_COMPRESSEDSIZE[] = "ifw.package.compressedsize";
-const char IFW_PACKAGE_UNCOMPRESSEDSIZE[] = "ifw.package.uncompressedsize";
const char IFW_INSTALLER_INSTALLLOG[] = "ifw.installer.installlog";
const char IFW_DEVELOPER_BUILD[] = "ifw.developer.build";
@@ -70,121 +46,6 @@ namespace QInstaller
*/
/*!
- \fn QInstaller::lcPackageDisplayname()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageDescription()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageVersion()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageInstalledVersion()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageReleasedate()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageInstallDate()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageUpdateDate()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageName()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageDependencies()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageAutodependon()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageVirtual()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageSortingpriority()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageScript()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageDefault()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageEssential()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageForcedinstallation()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageReplaces()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageDownloadableArchives()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageRequiresAdminRights()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageCheckable()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageLicenses()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageUncompressedSize()
- \internal
-*/
-
-/*!
- \fn QInstaller::lcPackageCompressedSize()
- \internal
-*/
-
-/*!
\fn QInstaller::lcInstallerInstallLog()
\internal
*/
@@ -200,33 +61,8 @@ namespace QInstaller
*/
Q_LOGGING_CATEGORY(lcServer, IFW_SERVER)
-
-Q_LOGGING_CATEGORY(lcPackageDisplayname, IFW_PACKAGE_DISPLAYNAME);
-Q_LOGGING_CATEGORY(lcPackageDescription, IFW_PACKAGE_DESCRIPTION)
-Q_LOGGING_CATEGORY(lcPackageVersion, IFW_PACKAGE_VERSION)
-Q_LOGGING_CATEGORY(lcPackageInstalledVersion, IFW_PACKAGE_INSTALLEDVERSION)
-Q_LOGGING_CATEGORY(lcPackageReleasedate, IFW_PACKAGE_RELEASEDATE)
-Q_LOGGING_CATEGORY(lcPackageInstallDate, IFW_PACKAGE_INSTALLDATE)
-Q_LOGGING_CATEGORY(lcPackageUpdateDate, IFW_PACKAGE_UPDATEDATE)
-Q_LOGGING_CATEGORY(lcPackageName, IFW_PACKAGE_NAME)
-Q_LOGGING_CATEGORY(lcPackageDependencies, IFW_PACKAGE_DEPENDENCIES)
-Q_LOGGING_CATEGORY(lcPackageAutodependon, IFW_PACKAGE_AUTODEPENDON)
-Q_LOGGING_CATEGORY(lcPackageVirtual, IFW_PACKAGE_VIRTUAL)
-Q_LOGGING_CATEGORY(lcPackageSortingpriority, IFW_PACKAGE_SORTINGPRIORITY)
-Q_LOGGING_CATEGORY(lcPackageScript, IFW_PACKAGE_SCRIPT)
-Q_LOGGING_CATEGORY(lcPackageDefault, IFW_PACKAGE_DEFAULT)
-Q_LOGGING_CATEGORY(lcPackageEssential, IFW_PACKAGE_ESSETIAL)
-Q_LOGGING_CATEGORY(lcPackageForcedinstallation, IFW_PACKAGE_FORCEDINSTALLATION)
-Q_LOGGING_CATEGORY(lcPackageReplaces, IFW_PACKAGE_REPLACES)
-Q_LOGGING_CATEGORY(lcPackageDownloadableArchives, IFW_PACKAGE_DOWNLOADABLEARCHIVES)
-Q_LOGGING_CATEGORY(lcPackageRequiresAdminRights, IFW_PACKAGE_REQUIRESADMINRIGHTS)
-Q_LOGGING_CATEGORY(lcPackageCheckable, IFW_PACKAGE_CHECKABLE)
-Q_LOGGING_CATEGORY(lcPackageLicenses, IFW_PACKAGE_LICENSES)
-Q_LOGGING_CATEGORY(lcPackageUncompressedSize, IFW_PACKAGE_UNCOMPRESSEDSIZE)
-Q_LOGGING_CATEGORY(lcPackageCompressedSize, IFW_PACKAGE_COMPRESSEDSIZE)
Q_LOGGING_CATEGORY(lcInstallerInstallLog, IFW_INSTALLER_INSTALLLOG)
Q_LOGGING_CATEGORY(lcProgressIndicator, IFW_PROGRESS_INDICATOR)
-
Q_LOGGING_CATEGORY(lcDeveloperBuild, IFW_DEVELOPER_BUILD)
/*!
@@ -235,29 +71,6 @@ Q_LOGGING_CATEGORY(lcDeveloperBuild, IFW_DEVELOPER_BUILD)
QStringList loggingCategories()
{
static QStringList categories = QStringList()
- << QLatin1String(IFW_PACKAGE_DISPLAYNAME)
- << QLatin1String(IFW_PACKAGE_DESCRIPTION)
- << QLatin1String(IFW_PACKAGE_VERSION)
- << QLatin1String(IFW_PACKAGE_INSTALLEDVERSION)
- << QLatin1String(IFW_PACKAGE_RELEASEDATE)
- << QLatin1String(IFW_PACKAGE_INSTALLDATE)
- << QLatin1String(IFW_PACKAGE_UPDATEDATE)
- << QLatin1String(IFW_PACKAGE_NAME)
- << QLatin1String(IFW_PACKAGE_DEPENDENCIES)
- << QLatin1String(IFW_PACKAGE_AUTODEPENDON)
- << QLatin1String(IFW_PACKAGE_VIRTUAL)
- << QLatin1String(IFW_PACKAGE_SORTINGPRIORITY)
- << QLatin1String(IFW_PACKAGE_SCRIPT)
- << QLatin1String(IFW_PACKAGE_DEFAULT)
- << QLatin1String(IFW_PACKAGE_ESSETIAL)
- << QLatin1String(IFW_PACKAGE_FORCEDINSTALLATION)
- << QLatin1String(IFW_PACKAGE_REPLACES)
- << QLatin1String(IFW_PACKAGE_DOWNLOADABLEARCHIVES)
- << QLatin1String(IFW_PACKAGE_REQUIRESADMINRIGHTS)
- << QLatin1String(IFW_PACKAGE_CHECKABLE)
- << QLatin1String(IFW_PACKAGE_LICENSES)
- << QLatin1String(IFW_PACKAGE_UNCOMPRESSEDSIZE)
- << QLatin1String(IFW_PACKAGE_COMPRESSEDSIZE)
<< QLatin1String(IFW_INSTALLER_INSTALLLOG)
<< QLatin1String(IFW_SERVER);
return categories;
diff --git a/src/libs/installer/globals.h b/src/libs/installer/globals.h
index c7608ca15..b22331e2c 100644
--- a/src/libs/installer/globals.h
+++ b/src/libs/installer/globals.h
@@ -38,29 +38,6 @@ namespace QInstaller {
INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcServer)
INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcInstallerInstallLog)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageDisplayname)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageDescription)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageVersion)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageInstalledVersion)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageReleasedate)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageInstallDate)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageUpdateDate)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageName)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageDependencies)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageAutodependon)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageVirtual)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageSortingpriority)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageScript)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageDefault)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageEssential)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageForcedinstallation)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageReplaces)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageDownloadableArchives)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageRequiresAdminRights)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageCheckable)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageLicenses)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageUncompressedSize)
-INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcPackageCompressedSize)
INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcProgressIndicator)
INSTALLER_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcDeveloperBuild)
diff --git a/src/libs/installer/globalsettingsoperation.cpp b/src/libs/installer/globalsettingsoperation.cpp
index f1f184ceb..dc085f4b6 100644
--- a/src/libs/installer/globalsettingsoperation.cpp
+++ b/src/libs/installer/globalsettingsoperation.cpp
@@ -49,8 +49,9 @@ void GlobalSettingsOperation::backup()
bool GlobalSettingsOperation::performOperation()
{
+ const QStringList args = parsePerformOperationArguments();
QString key, value;
- QScopedPointer<QSettingsWrapper> settings(setup(&key, &value, arguments()));
+ QScopedPointer<QSettingsWrapper> settings(setup(&key, &value, args));
if (settings.isNull())
return false;
@@ -76,8 +77,12 @@ bool GlobalSettingsOperation::performOperation()
bool GlobalSettingsOperation::undoOperation()
{
+ if (parseUndoOperationArguments().count() > 0)
+ return true;
+
+ const QStringList args = parsePerformOperationArguments();
QString key, val;
- QScopedPointer<QSettingsWrapper> settings(setup(&key, &val, arguments()));
+ QScopedPointer<QSettingsWrapper> settings(setup(&key, &val, args));
if (settings.isNull())
return false;
diff --git a/src/libs/installer/graph.h b/src/libs/installer/graph.h
index 5f330e69b..49da9c2ed 100644
--- a/src/libs/installer/graph.h
+++ b/src/libs/installer/graph.h
@@ -63,7 +63,7 @@ public:
QList<T> edges(const T &node) const
{
- return m_graph.value(node).toList();
+ return m_graph.value(node).values();
}
void addEdge(const T &node, const T &edge)
diff --git a/src/libs/installer/init.cpp b/src/libs/installer/init.cpp
index ea6f75e1b..4aa65296f 100644
--- a/src/libs/installer/init.cpp
+++ b/src/libs/installer/init.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -47,24 +47,14 @@
#include "licenseoperation.h"
#include "settingsoperation.h"
#include "consumeoutputoperation.h"
-#include "globals.h"
+#include "loggingutils.h"
#include "lib7z_facade.h"
-#include "utils.h"
#include "updateoperationfactory.h"
#include "filedownloaderfactory.h"
#include <QtPlugin>
-#include <QElapsedTimer>
-
-#include <iostream>
-
-#if defined(Q_OS_UNIX)
-#include <unistd.h>
-#elif defined(Q_OS_WIN)
-#include <fileapi.h>
-#endif
using namespace KDUpdater;
using namespace QInstaller;
@@ -76,81 +66,6 @@ static void initResources()
}
#endif
-static bool s_outputRedirected = false;
-
-static QString trimAndPrepend(QtMsgType type, const QString &msg)
-{
- QString ba(msg);
- // last character is a space from qDebug
- if (ba.endsWith(QLatin1Char(' ')))
- ba.chop(1);
-
- // remove quotes if the whole message is surrounded with them
- if (ba.startsWith(QLatin1Char('"')) && ba.endsWith(QLatin1Char('"')))
- ba = ba.mid(1, ba.length() - 2);
-
- // prepend the message type, skip QtDebugMsg
- switch (type) {
- case QtWarningMsg:
- ba.prepend(QStringLiteral("Warning: "));
- break;
-
- case QtCriticalMsg:
- ba.prepend(QStringLiteral("Critical: "));
- break;
-
- case QtFatalMsg:
- ba.prepend(QStringLiteral("Fatal: "));
- break;
-
- default:
- break;
- }
- return ba;
-}
-
-// start timer on construction (so we can use it as static member)
-class Uptime : public QElapsedTimer {
-public:
- Uptime() { start(); }
-};
-
-void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
-{
- // suppress warning from QPA minimal plugin
- if (msg.contains(QLatin1String("This plugin does not support propagateSizeHints")))
- return;
-
- if (context.category == lcProgressIndicator().categoryName()) {
- if (!s_outputRedirected)
- std::cout << msg.toStdString() << "\r" << std::flush;
- return;
- }
-
- static Uptime uptime;
-
- QString ba = QLatin1Char('[') + QString::number(uptime.elapsed()) + QStringLiteral("] ")
- + trimAndPrepend(type, msg);
-
- if (type != QtDebugMsg && context.file) {
- ba += QString(QStringLiteral(" (%1:%2, %3)")).arg(
- QString::fromLatin1(context.file)).arg(context.line).arg(
- QString::fromLatin1(context.function));
- }
-
- if (VerboseWriter *log = VerboseWriter::instance())
- log->appendLine(ba);
-
- if (type != QtDebugMsg || isVerbose())
- std::cout << qPrintable(ba) << std::endl;
-
- if (type == QtFatalMsg) {
- QtMessageHandler oldMsgHandler = qInstallMessageHandler(nullptr);
- qt_message_output(type, context, msg);
- qInstallMessageHandler(oldMsgHandler);
- }
-}
-
/*!
Initializes the 7z library and installer resources. Registers
custom operations and installs a custom message handler.
@@ -187,10 +102,8 @@ void QInstaller::init()
FileDownloaderFactory::setFollowRedirects(true);
-#if defined(Q_OS_UNIX)
- s_outputRedirected = !isatty(fileno(stdout));
-#elif defined(Q_OS_WIN)
- s_outputRedirected = (GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)) == FILE_TYPE_DISK);
-#endif
+ auto messageHandler = [](QtMsgType type, const QMessageLogContext &context, const QString &msg) {
+ LoggingHandler::instance().messageHandler(type, context, msg);
+ };
qInstallMessageHandler(messageHandler);
}
diff --git a/src/libs/installer/installer.pro b/src/libs/installer/installer.pro
index 225b1c8d1..ed7d739ed 100644
--- a/src/libs/installer/installer.pro
+++ b/src/libs/installer/installer.pro
@@ -6,6 +6,7 @@ CONFIG += staticlib
include(../7zip/7zip.pri)
include(../kdtools/kdtools.pri)
+include(../ifwtools/ifwtools.pri)
include(../../../installerfw.pri)
# productkeycheck API
@@ -43,6 +44,8 @@ win32:QT += winextras
HEADERS += packagemanagercore.h \
aspectratiolabel.h \
+ componentsortfilterproxymodel.h \
+ loggingutils.h \
packagemanagercore_p.h \
packagemanagergui.h \
binaryformat.h \
@@ -77,7 +80,6 @@ HEADERS += packagemanagercore.h \
adminauthorization.h \
elevatedexecuteoperation.h \
fakestopprocessforupdateoperation.h \
- lazyplaintextedit.h \
progresscoordinator.h \
minimumprogressoperation.h \
performinstallationform.h \
@@ -137,10 +139,20 @@ HEADERS += packagemanagercore.h \
repositorycategory.h \
componentselectionpage_p.h \
commandlineparser.h \
- commandlineparser_p.h
+ commandlineparser_p.h \
+ abstractarchive.h \
+ directoryguard.h \
+ lib7zarchive.h \
+ archivefactory.h
SOURCES += packagemanagercore.cpp \
+ abstractarchive.cpp \
+ archivefactory.cpp \
aspectratiolabel.cpp \
+ directoryguard.cpp \
+ lib7zarchive.cpp \
+ componentsortfilterproxymodel.cpp \
+ loggingutils.cpp \
packagemanagercore_p.cpp \
packagemanagergui.cpp \
binaryformat.cpp \
@@ -170,7 +182,6 @@ SOURCES += packagemanagercore.cpp \
init.cpp \
elevatedexecuteoperation.cpp \
fakestopprocessforupdateoperation.cpp \
- lazyplaintextedit.cpp \
progresscoordinator.cpp \
minimumprogressoperation.cpp \
performinstallationform.cpp \
@@ -228,6 +239,18 @@ unix {
else: SOURCES += adminauthorization_x11.cpp
}
+CONFIG(libarchive) {
+ HEADERS += libarchivearchive.h \
+ libarchivewrapper.h \
+ libarchivewrapper_p.h
+
+ SOURCES += libarchivearchive.cpp \
+ libarchivewrapper.cpp \
+ libarchivewrapper_p.cpp
+
+ LIBS += -llibarchive
+}
+
LIBS += -l7z
win32 {
SOURCES += adminauthorization_win.cpp sysinfo_win.cpp
diff --git a/src/libs/installer/installercalculator.cpp b/src/libs/installer/installercalculator.cpp
index 363837dd1..ba8bf9db3 100644
--- a/src/libs/installer/installercalculator.cpp
+++ b/src/libs/installer/installercalculator.cpp
@@ -164,7 +164,8 @@ bool InstallerCalculator::appendComponentsToInstall(const QList<Component *> &co
bool InstallerCalculator::appendComponentToInstall(Component *component, const QString &version)
{
- QSet<QString> allDependencies = component->dependencies().toSet();
+ const QStringList dependenciesList = component->dependencies();
+ QSet<QString> allDependencies(dependenciesList.begin(), dependenciesList.end());
QString requiredDependencyVersion = version;
foreach (const QString &dependencyComponentName, allDependencies) {
// PackageManagerCore::componentByName returns 0 if dependencyComponentName contains a
diff --git a/src/libs/installer/installiconsoperation.cpp b/src/libs/installer/installiconsoperation.cpp
index eea83a462..5927ebf74 100644
--- a/src/libs/installer/installiconsoperation.cpp
+++ b/src/libs/installer/installiconsoperation.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -30,6 +30,9 @@
#include "fileutils.h"
#include "packagemanagercore.h"
#include "globals.h"
+#include "adminauthorization.h"
+#include "remoteclient.h"
+#include "errors.h"
#include <QDebug>
#include <QDir>
@@ -51,8 +54,11 @@ QString InstallIconsOperation::targetDirectory()
QStringList XDG_DATA_HOME = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME"))
.split(QLatin1Char(':'),
- QString::SkipEmptyParts);
- XDG_DATA_HOME.push_back(QDir::home().absoluteFilePath(QLatin1String(".local/share"))); // default path
+ Qt::SkipEmptyParts);
+ XDG_DATA_HOME.push_back(QDir::home().absoluteFilePath(QLatin1String(".local/share"))); // default user-specific path
+
+ if (AdminAuthorization::hasAdminRights() || RemoteClient::instance().isActive())
+ XDG_DATA_HOME.push_front(QLatin1String("/usr/local/share")); // default system-wide path
QString directory;
const QStringList& directories = XDG_DATA_HOME;
@@ -129,8 +135,8 @@ bool InstallIconsOperation::performOperation()
if (status == PackageManagerCore::Canceled || status == PackageManagerCore::Failure)
return true;
- const QString &source = it.next();
- QString target = targetDir.absoluteFilePath(sourceDir.relativeFilePath(source));
+ const QString &source2 = it.next();
+ QString target = targetDir.absoluteFilePath(sourceDir.relativeFilePath(source2));
emit outputTextChanged(target);
@@ -153,7 +159,16 @@ bool InstallIconsOperation::performOperation()
if (QFile(target).exists()) {
// first backup...
- const QString backup = generateTemporaryFileName(target);
+ QString backup;
+ try {
+ backup = generateTemporaryFileName(target);
+ } catch (const QInstaller::Error &e) {
+ setError(UserDefinedError);
+ setErrorString(tr("Cannot prepare to backup file \"%1\": %2")
+ .arg(QDir::toNativeSeparators(target), e.message()));
+ undoOperation();
+ return false;
+ }
QFile bf(target);
if (!bf.copy(backup)) {
setError(UserDefinedError);
@@ -180,7 +195,7 @@ bool InstallIconsOperation::performOperation()
}
// copy the file to its new location
- QFile cf(source);
+ QFile cf(source2);
if (!cf.copy(target)) {
setError(UserDefinedError);
setErrorString(tr("Failed to copy file \"%1\": %2").arg(
@@ -188,8 +203,8 @@ bool InstallIconsOperation::performOperation()
undoOperation();
return false;
}
- deleteFileNowOrLater(source);
- files.push_back(source);
+ deleteFileNowOrLater(source2);
+ files.push_back(source2);
files.push_back(target);
setValue(QLatin1String("files"), files);
} else if (fi.isDir() && !QDir(target).exists()) {
diff --git a/src/libs/installer/keepaliveobject.cpp b/src/libs/installer/keepaliveobject.cpp
index 94e91efd8..18ec9743a 100644
--- a/src/libs/installer/keepaliveobject.cpp
+++ b/src/libs/installer/keepaliveobject.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -48,7 +48,12 @@ KeepAliveObject::KeepAliveObject()
void KeepAliveObject::start()
{
+ if (m_timer)
+ delete m_timer;
m_timer = new QTimer(this);
+
+ if (m_socket)
+ delete m_socket;
m_socket = new QLocalSocket(this);
connect(m_timer, &QTimer::timeout, [this]() {
diff --git a/src/libs/installer/lazyplaintextedit.cpp b/src/libs/installer/lazyplaintextedit.cpp
deleted file mode 100644
index a4a699a78..000000000
--- a/src/libs/installer/lazyplaintextedit.cpp
+++ /dev/null
@@ -1,101 +0,0 @@
-/**************************************************************************
-**
-** Copyright (C) 2017 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 "lazyplaintextedit.h"
-
-#include <QScrollBar>
-
-const int INTERVAL = 20;
-
-LazyPlainTextEdit::LazyPlainTextEdit(QWidget *parent)
- : QPlainTextEdit(parent)
- , m_timerId(0)
-{
-}
-
-void LazyPlainTextEdit::timerEvent(QTimerEvent *event)
-{
- if (event->timerId() == m_timerId) {
- killTimer(m_timerId);
- m_timerId = 0;
- m_cachedOutput.chop(1); //removes the last \n
- if (!m_cachedOutput.isEmpty()) {
- appendPlainText(m_cachedOutput);
- updateCursor(TextCursorPosition::Keep);
- horizontalScrollBar()->setValue(0);
- m_cachedOutput.clear();
- }
- }
-}
-
-void LazyPlainTextEdit::append(const QString &text)
-{
- m_cachedOutput.append(text + QLatin1String("\n"));
- if (isVisible() && m_timerId == 0)
- m_timerId = startTimer(INTERVAL);
-}
-
-void LazyPlainTextEdit::clear()
-{
- if (m_timerId) {
- killTimer(m_timerId);
- m_timerId = 0;
- m_cachedOutput.clear();
- }
- QPlainTextEdit::clear();
-}
-
-void LazyPlainTextEdit::setVisible(bool visible)
-{
- if (m_timerId) {
- killTimer(m_timerId);
- m_timerId = 0;
- }
-
- if (visible)
- m_timerId = startTimer(INTERVAL);
-
- QPlainTextEdit::setVisible(visible);
- updateCursor(TextCursorPosition::Keep);
-}
-
-void LazyPlainTextEdit::updateCursor(TextCursorPosition position)
-{
- QTextCursor cursor = textCursor();
- if ((position == TextCursorPosition::ForceEnd) || cursor.atEnd()) {
- // Workaround for height calculation issue if scrollbar is set to Qt::ScrollBarAsNeeded.
- Qt::ScrollBarPolicy policy = horizontalScrollBarPolicy();
- setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); // enforce always on
-
- cursor.movePosition(QTextCursor::End);
- setTextCursor(cursor);
- ensureCursorVisible();
-
- setHorizontalScrollBarPolicy(policy); // but reset once we updated the cursor position
- }
-}
diff --git a/src/libs/installer/lib7z_create.h b/src/libs/installer/lib7z_create.h
index bc61db7ab..72fc56c81 100644
--- a/src/libs/installer/lib7z_create.h
+++ b/src/libs/installer/lib7z_create.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -30,6 +30,7 @@
#define LIB7Z_CREATE_H
#include "installer_global.h"
+#include "abstractarchive.h"
#include <Common/MyCom.h>
#include <7zip/UI/Common/Update.h>
@@ -46,14 +47,7 @@ namespace Lib7z
Yes
};
- enum struct Compression {
- Non = 0,
- Fastest = 1,
- Fast = 3,
- Normal = 5,
- Maximum = 7,
- Ultra = 9
- };
+ typedef QInstaller::AbstractArchive::CompressionLevel Compression;
class INSTALLER_EXPORT UpdateCallback : public IUpdateCallbackUI2, public CMyUnknownImp
{
diff --git a/src/libs/installer/lib7z_facade.cpp b/src/libs/installer/lib7z_facade.cpp
index 3e6b10259..6d1b6a57d 100644
--- a/src/libs/installer/lib7z_facade.cpp
+++ b/src/libs/installer/lib7z_facade.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -36,6 +36,7 @@
#include "lib7z_list.h"
#include "lib7z_guid.h"
#include "globals.h"
+#include "directoryguard.h"
#ifndef Q_OS_WIN
# include "StdAfx.h"
@@ -116,6 +117,42 @@ void registerCodecByteSwap();
namespace Lib7z {
/*!
+ \inmodule Lib7z
+ \class Lib7z::File
+ \internal
+*/
+
+/*!
+ \inmodule Lib7z
+ \class Lib7z::UpdateCallback
+ \internal
+*/
+
+/*!
+ \inmodule Lib7z
+ \class Lib7z::SevenZipException
+ \brief The SevenZipException provides a class for lib7z exceptions.
+*/
+
+/*!
+ \fn explicit Lib7z::SevenZipException::SevenZipException(const QString &msg)
+
+ Constructs an instance of SevenZipException with error message \a msg.
+*/
+
+/*!
+ \fn explicit Lib7z::SevenZipException::SevenZipException(const char *msg)
+
+ Constructs an instance of SevenZipException with error message \a msg.
+*/
+
+/*!
+ \inmodule Lib7z
+ \class Lib7z::PercentPrinter
+ \brief The PercentPrinter class displays the archiving process.
+*/
+
+/*!
\fn void Lib7z::PercentPrinter::PrintRatio()
Prints ratio.
@@ -151,16 +188,13 @@ namespace Lib7z {
Prints string \a s.
*/
-/*!
- \fn bool Lib7z::operator==(const File &lhs, const File &rhs);
-
- Returns \c true if \a lhs and \a rhs are equal; otherwise returns \c false.
-*/
-
// -- 7z init codecs, archives
std::once_flag gOnceFlag;
+/*!
+ Initializes 7z and registers codecs and compression methods.
+*/
void initSevenZ()
{
std::call_once(gOnceFlag, [] {
@@ -222,7 +256,7 @@ QString errorMessageFrom7zResult(const LONG &extractResult)
if (!lastError().isEmpty())
return lastError();
- QString errorMessage = QCoreApplication::translate("Lib7z", "internal code: %1");
+ QString errorMessage = QCoreApplication::translate("Lib7z", "Internal code: %1");
switch (extractResult) {
case S_OK:
qFatal("S_OK value is not a valid error code.");
@@ -243,7 +277,7 @@ QString errorMessageFrom7zResult(const LONG &extractResult)
errorMessage = errorMessage.arg(QLatin1String("STG_E_INVALIDFUNCTION"));
break;
case E_OUTOFMEMORY:
- errorMessage = QCoreApplication::translate("Lib7z", "not enough memory");
+ errorMessage = QCoreApplication::translate("Lib7z", "Not enough memory");
break;
case E_INVALIDARG:
errorMessage = errorMessage.arg(QLatin1String("E_INVALIDARG"));
@@ -255,73 +289,6 @@ QString errorMessageFrom7zResult(const LONG &extractResult)
return errorMessage;
}
-/*
- RAII class to create a directory (tryCreate()) and delete it on destruction unless released.
-*/
-struct DirectoryGuard
-{
- explicit DirectoryGuard(const QString &path)
- : m_path(path)
- , m_created(false)
- , m_released(false)
- {
- m_path.replace(QLatin1Char('\\'), QLatin1Char('/'));
- }
-
- ~DirectoryGuard()
- {
- if (!m_created || m_released)
- return;
- QDir dir(m_path);
- if (!dir.rmdir(m_path))
- qCWarning(QInstaller::lcInstallerInstallLog) << "Cannot delete directory " << m_path;
- }
-
- /*
- Tries to create the directory structure.
- Returns a list of every directory created.
- */
- QStringList tryCreate()
- {
- if (m_path.isEmpty())
- return QStringList();
-
- const QFileInfo fi(m_path);
- if (fi.exists() && fi.isDir())
- return QStringList();
- if (fi.exists() && !fi.isDir()) {
- throw SevenZipException(QCoreApplication::translate("DirectoryGuard",
- "Path \"%1\" exists but is not a directory.").arg(QDir::toNativeSeparators(m_path)));
- }
- QStringList created;
-
- QDir toCreate(m_path);
- while (!toCreate.exists()) {
- QString p = toCreate.absolutePath();
- created.push_front(p);
- p = p.section(QLatin1Char('/'), 0, -2);
- toCreate = QDir(p);
- }
-
- QDir dir(m_path);
- m_created = dir.mkpath(m_path);
- if (!m_created) {
- throw SevenZipException(QCoreApplication::translate("DirectoryGuard",
- "Cannot create directory \"%1\".").arg(QDir::toNativeSeparators(m_path)));
- }
- return created;
- }
-
- void release()
- {
- m_released = true;
- }
-
- QString m_path;
- bool m_created;
- bool m_released;
-};
-
static UString QString2UString(const QString &str)
{
return str.toStdWString().c_str();
@@ -515,18 +482,9 @@ private:
QPointer<QIODevice> m_device;
};
-bool operator==(const File &lhs, const File &rhs)
-{
- return lhs.path == rhs.path
- && lhs.utcTime == rhs.utcTime
- && lhs.isDirectory == rhs.isDirectory
- && lhs.compressedSize == rhs.compressedSize
- && lhs.uncompressedSize == rhs.uncompressedSize
- && (lhs.permissions == rhs.permissions
- || lhs.permissions == static_cast<QFile::Permissions>(-1)
- || rhs.permissions == static_cast<QFile::Permissions>(-1));
-}
-
+/*!
+ Returns a list of files belonging to an \a archive.
+*/
QVector<File> listArchive(QFileDevice *archive)
{
LIB7Z_ASSERTS(archive, Readable)
@@ -544,6 +502,8 @@ QVector<File> listArchive(QFileDevice *archive)
op.types = &types; // Empty, because we use a stream.
CIntVector excluded;
+ excluded.Add(codecs.FindFormatForExtension(
+ QString2UString(QLatin1String("xz")))); // handled by libarchive
op.excludedFormats = &excluded;
const CMyComPtr<IInStream> stream = new QIODeviceInStream(archive);
@@ -578,7 +538,7 @@ QVector<File> listArchive(QFileDevice *archive)
f.archiveIndex.setY(item);
f.path = UString2QString(s).replace(QLatin1Char('\\'), QLatin1Char('/'));
Archive_IsItem_Folder(arch, item, f.isDirectory);
- f.permissions = getPermissions(arch, item, nullptr);
+ f.permissions_enum = getPermissions(arch, item, nullptr);
getDateTimeProperty(arch, item, kpidMTime, &(f.utcTime));
f.uncompressedSize = getUInt64Property(arch, item, kpidSize, 0);
f.compressedSize = getUInt64Property(arch, item, kpidPackSize, 0);
@@ -600,15 +560,24 @@ QVector<File> listArchive(QFileDevice *archive)
return QVector<File>(); // never reached
}
+/*!
+ \inmodule Lib7z
+ \class Lib7z::ExtractCallback
+ \brief Provides a callback for archive extraction.
+*/
-// -- ExtractCallback
-
+/*!
+ \internal
+*/
STDMETHODIMP ExtractCallback::SetTotal(UInt64 t)
{
total = t;
return S_OK;
}
+/*!
+ \internal
+*/
STDMETHODIMP ExtractCallback::SetCompleted(const UInt64 *c)
{
completed = *c;
@@ -617,8 +586,12 @@ STDMETHODIMP ExtractCallback::SetCompleted(const UInt64 *c)
return S_OK;
}
-// this method will be called by CFolderOutStream::OpenFile to stream via
-// CDecoder::CodeSpec extracted content to an output stream.
+/*!
+ \internal
+
+ This method will be called by CFolderOutStream::OpenFile to stream via
+ CDecoder::CodeSpec extracted content to an output stream.
+*/
STDMETHODIMP ExtractCallback::GetStream(UInt32 index, ISequentialOutStream **outStream, Int32 /*askExtractMode*/)
{
*outStream = nullptr;
@@ -637,7 +610,7 @@ STDMETHODIMP ExtractCallback::GetStream(UInt32 index, ISequentialOutStream **out
const QFileInfo fi(QString::fromLatin1("%1/%2").arg(targetDir, UString2QString(s)));
- DirectoryGuard guard(fi.absolutePath());
+ QInstaller::DirectoryGuard guard(fi.absolutePath());
const QStringList directories = guard.tryCreate();
bool isDir = false;
@@ -679,11 +652,17 @@ STDMETHODIMP ExtractCallback::GetStream(UInt32 index, ISequentialOutStream **out
return S_OK;
}
+/*!
+ \internal
+*/
STDMETHODIMP ExtractCallback::PrepareOperation(Int32 /*askExtractMode*/)
{
return S_OK;
}
+/*!
+ \internal
+*/
STDMETHODIMP ExtractCallback::SetOperationResult(Int32 /*resultEOperationResult*/)
{
if (targetDir.isEmpty())
@@ -785,16 +764,15 @@ STDMETHODIMP ExtractCallback::SetOperationResult(Int32 /*resultEOperationResult*
*/
/*!
- \enum Lib7z::Compression
+ \typedef Lib7z::Compression
- This enum specifies the compression ratio of an archive:
+ Synonym for QInstaller::CompressionLevel
+*/
- \value Non
- \value Fastest
- \value Fast
- \value Normal
- \value Maximum
- \value Ultra
+/*!
+ \typedef Lib7z::File
+
+ Synonym for QInstaller::ArchiveEntry
*/
/*!
@@ -832,9 +810,26 @@ STDMETHODIMP ExtractCallback::SetOperationResult(Int32 /*resultEOperationResult*
/*!
\fn virtual Lib7z::ExtractCallback::setCompleted(quint64 completed, quint64 total)
- Always returns \c true. \c completed and \c total are unused.
+ Always returns \c true.
+*/
+
+/*!
+ \fn ULONG Lib7z::ExtractCallback::AddRef()
+
+ \internal
+*/
+
+/*!
+ \fn LONG Lib7z::ExtractCallback::QueryInterface(const GUID &iid, void **outObject)
+
+ \internal
*/
+/*!
+ \fn ULONG Lib7z::ExtractCallback::Release()
+
+ \internal
+*/
// -- UpdateCallback
@@ -1098,7 +1093,7 @@ void extractArchive(QFileDevice *archive, const QString &directory, ExtractCallb
localCallback = callback;
}
- DirectoryGuard outDir(QFileInfo(directory).absolutePath());
+ QInstaller::DirectoryGuard outDir(QFileInfo(directory).absolutePath());
try {
outDir.tryCreate();
@@ -1113,6 +1108,8 @@ void extractArchive(QFileDevice *archive, const QString &directory, ExtractCallb
op.types = &types; // Empty, because we use a stream.
CIntVector excluded;
+ excluded.Add(codecs.FindFormatForExtension(
+ QString2UString(QLatin1String("xz")))); // handled by libarchive
op.excludedFormats = &excluded;
const CMyComPtr<IInStream> stream = new QIODeviceInStream(archive);
@@ -1170,6 +1167,8 @@ bool isSupportedArchive(QFileDevice *archive)
op.types = &types; // Empty, because we use a stream.
CIntVector excluded;
+ excluded.Add(codecs.FindFormatForExtension(
+ QString2UString(QLatin1String("xz")))); // handled by libarchive
op.excludedFormats = &excluded;
const CMyComPtr<IInStream> stream = new QIODeviceInStream(archive);
diff --git a/src/libs/installer/lib7z_list.h b/src/libs/installer/lib7z_list.h
index 965a8b5ea..e09c73746 100644
--- a/src/libs/installer/lib7z_list.h
+++ b/src/libs/installer/lib7z_list.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2015 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -40,6 +40,7 @@
#define LIB7Z_LIST_H
#include "installer_global.h"
+#include "abstractarchive.h"
#include <QDateTime>
#include <QFile>
@@ -47,19 +48,7 @@
namespace Lib7z
{
- struct INSTALLER_EXPORT File
- {
- public:
- QString path;
- QDateTime utcTime;
- QPoint archiveIndex;
- bool isDirectory = false;
- quint64 compressedSize = 0;
- quint64 uncompressedSize = 0;
- QFile::Permissions permissions = 0;
- };
-
- INSTALLER_EXPORT bool operator==(const File &lhs, const File &rhs);
+ typedef QInstaller::ArchiveEntry File;
QVector<File> INSTALLER_EXPORT listArchive(QFileDevice *archive);
diff --git a/src/libs/installer/lib7zarchive.cpp b/src/libs/installer/lib7zarchive.cpp
new file mode 100644
index 000000000..d7b0c0dc9
--- /dev/null
+++ b/src/libs/installer/lib7zarchive.cpp
@@ -0,0 +1,242 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#include "lib7zarchive.h"
+
+#include "errors.h"
+#include "lib7z_facade.h"
+#include "lib7z_create.h"
+#include "lib7z_list.h"
+
+#include <QCoreApplication>
+
+namespace QInstaller {
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::Lib7zArchive
+ \brief The Lib7zArchive class represents an archive file
+ handled with the LZMA software development kit.
+*/
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::Lib7zArchive::ExtractCallbackWrapper
+ \internal
+*/
+
+/*!
+ Constructs an archive object representing an archive file
+ specified by \a filename with \a parent as parent object.
+*/
+Lib7zArchive::Lib7zArchive(const QString &filename, QObject *parent)
+ : AbstractArchive(parent)
+ , m_extractCallback(new ExtractCallbackWrapper())
+{
+ Lib7zArchive::setFilename(filename);
+ listenExtractCallback();
+}
+
+/*!
+ Constructs an archive object with the given \a parent.
+*/
+Lib7zArchive::Lib7zArchive(QObject *parent)
+ : AbstractArchive(parent)
+ , m_extractCallback(new ExtractCallbackWrapper())
+{
+ listenExtractCallback();
+}
+
+/*!
+ Destroys the instance and releases resources.
+*/
+Lib7zArchive::~Lib7zArchive()
+{
+ delete m_extractCallback;
+}
+
+/*!
+ \reimp
+
+ Opens the underlying file device using \a mode. Returns \c true if
+ succesfull; otherwise \c false.
+*/
+bool Lib7zArchive::open(QIODevice::OpenMode mode)
+{
+ if (!m_file.open(mode)) {
+ setErrorString(m_file.errorString());
+ return false;
+ }
+ return true;
+}
+
+/*!
+ \reimp
+
+ Closes the underlying file device.
+*/
+void Lib7zArchive::close()
+{
+ m_file.close();
+}
+
+/*!
+ \reimp
+
+ Sets the \a filename of the underlying file device.
+*/
+void Lib7zArchive::setFilename(const QString &filename)
+{
+ m_file.setFileName(filename);
+}
+
+/*!
+ \reimp
+
+ Extracts the contents of this archive to \a dirPath.
+ Returns \c true on success; \c false otherwise.
+*/
+bool Lib7zArchive::extract(const QString &dirPath)
+{
+ m_extractCallback->setState(S_OK);
+ try {
+ Lib7z::extractArchive(&m_file, dirPath, m_extractCallback);
+ } catch (const Lib7z::SevenZipException &e) {
+ setErrorString(e.message());
+ return false;
+ }
+ return true;
+}
+
+/*!
+ \reimp
+
+ Extracts the contents of this archive to \a dirPath. The \a totalFiles
+ parameter is unused. Returns \c true on success; \c false otherwise.
+*/
+bool Lib7zArchive::extract(const QString &dirPath, const quint64 totalFiles)
+{
+ Q_UNUSED(totalFiles)
+ return extract(dirPath);
+}
+
+/*!
+ \reimp
+
+ Packages the given \a data into the archive and creates the file on disk.
+*/
+bool Lib7zArchive::create(const QStringList &data)
+{
+ try {
+ // No support for callback yet.
+ Lib7z::createArchive(&m_file, data, compressionLevel());
+ } catch (const Lib7z::SevenZipException &e) {
+ setErrorString(e.message());
+ return false;
+ }
+ return true;
+}
+
+/*!
+ \reimp
+
+ Returns the contents of this archive as an array of \c ArchiveEntry objects.
+ On failure, returns an empty array.
+*/
+QVector<ArchiveEntry> Lib7zArchive::list()
+{
+ try {
+ return Lib7z::listArchive(&m_file);
+ } catch (const Lib7z::SevenZipException &e) {
+ setErrorString(e.message());
+ return QVector<ArchiveEntry>();
+ }
+}
+
+/*!
+ \reimp
+
+ Returns \c true if the current archive is of supported format;
+ \c false otherwise.
+*/
+bool Lib7zArchive::isSupported()
+{
+ try {
+ return Lib7z::isSupportedArchive(&m_file);
+ } catch (const Lib7z::SevenZipException &e) {
+ setErrorString(e.message());
+ return false;
+ }
+}
+
+/*!
+ \reimp
+
+ Cancels the extract operation in progress.
+*/
+void Lib7zArchive::cancel()
+{
+ m_extractCallback->setState(E_ABORT);
+}
+
+/*!
+ \internal
+*/
+void Lib7zArchive::listenExtractCallback()
+{
+ connect(m_extractCallback, &ExtractCallbackWrapper::currentEntryChanged,
+ this, &Lib7zArchive::currentEntryChanged);
+ connect(m_extractCallback, &ExtractCallbackWrapper::completedChanged,
+ this, &Lib7zArchive::completedChanged);
+}
+
+
+Lib7zArchive::ExtractCallbackWrapper::ExtractCallbackWrapper()
+ : m_state(S_OK)
+{
+}
+
+void Lib7zArchive::ExtractCallbackWrapper::setState(HRESULT state)
+{
+ m_state = state;
+}
+
+void Lib7zArchive::ExtractCallbackWrapper::setCurrentFile(const QString &filename)
+{
+ emit currentEntryChanged(filename);
+}
+
+HRESULT Lib7zArchive::ExtractCallbackWrapper::setCompleted(quint64 completed, quint64 total)
+{
+ qApp->processEvents();
+
+ emit completedChanged(completed, total);
+ return m_state;
+}
+
+} // namespace QInstaller
diff --git a/src/libs/installer/lib7zarchive.h b/src/libs/installer/lib7zarchive.h
new file mode 100644
index 000000000..45f352aeb
--- /dev/null
+++ b/src/libs/installer/lib7zarchive.h
@@ -0,0 +1,95 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#ifndef LIB7ZARCHIVE_H
+#define LIB7ZARCHIVE_H
+
+#include "installer_global.h"
+#include "abstractarchive.h"
+#include "lib7z_extract.h"
+
+namespace QInstaller {
+
+class INSTALLER_EXPORT Lib7zArchive : public AbstractArchive
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(Lib7zArchive)
+
+public:
+ Lib7zArchive(const QString &filename, QObject *parent = nullptr);
+ explicit Lib7zArchive(QObject *parent = nullptr);
+ ~Lib7zArchive();
+
+ bool open(QIODevice::OpenMode mode) Q_DECL_OVERRIDE;
+ void close() Q_DECL_OVERRIDE;
+ void setFilename(const QString &filename) Q_DECL_OVERRIDE;
+
+ bool extract(const QString &dirPath) Q_DECL_OVERRIDE;
+ bool extract(const QString &dirPath, const quint64 totalFiles) Q_DECL_OVERRIDE;
+ bool create(const QStringList &data) Q_DECL_OVERRIDE;
+ QVector<ArchiveEntry> list() Q_DECL_OVERRIDE;
+ bool isSupported() Q_DECL_OVERRIDE;
+
+public Q_SLOTS:
+ void cancel() Q_DECL_OVERRIDE;
+
+private:
+ void listenExtractCallback();
+
+ class ExtractCallbackWrapper;
+
+private:
+ QFile m_file;
+ ExtractCallbackWrapper *const m_extractCallback;
+};
+
+class Lib7zArchive::ExtractCallbackWrapper : public QObject, public Lib7z::ExtractCallback
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(ExtractCallbackWrapper)
+
+public:
+ ExtractCallbackWrapper();
+
+ void setState(HRESULT state);
+
+Q_SIGNALS:
+ void currentEntryChanged(const QString &filename);
+ void completedChanged(quint64 completed, quint64 total);
+
+private:
+ void setCurrentFile(const QString &filename) Q_DECL_OVERRIDE;
+ HRESULT setCompleted(quint64 completed, quint64 total) Q_DECL_OVERRIDE;
+
+private:
+ HRESULT m_state;
+};
+
+} // namespace QInstaller
+
+#endif // LIB7ZARCHIVE_H
diff --git a/src/libs/installer/libarchivearchive.cpp b/src/libs/installer/libarchivearchive.cpp
new file mode 100644
index 000000000..3ada73dd2
--- /dev/null
+++ b/src/libs/installer/libarchivearchive.cpp
@@ -0,0 +1,963 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#include "libarchivearchive.h"
+
+#include "directoryguard.h"
+#include "errors.h"
+#include "globals.h"
+
+#include <stdio.h>
+
+#include <QApplication>
+#include <QFileInfo>
+#include <QDir>
+#include <QTimer>
+
+namespace QInstaller {
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::ScopedPointerReaderDeleter
+ \internal
+*/
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::ScopedPointerWriterDeleter
+ \internal
+*/
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::ScopedPointerEntryDeleter
+ \internal
+*/
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::ExtractWorker
+ \internal
+*/
+
+ExtractWorker::Status ExtractWorker::status() const
+{
+ return m_status;
+}
+
+void ExtractWorker::extract(const QString &dirPath, const quint64 totalFiles)
+{
+ m_status = Unfinished;
+ quint64 completed = 0;
+
+ if (!totalFiles) {
+ m_status = Failure;
+ emit finished(QLatin1String("The file count for current archive is null!"));
+ return;
+ }
+
+ QScopedPointer<archive, ScopedPointerReaderDeleter> reader(archive_read_new());
+ QScopedPointer<archive, ScopedPointerWriterDeleter> writer(archive_write_disk_new());
+ archive_entry *entry = nullptr;
+
+ LibArchiveArchive::configureReader(reader.get());
+ LibArchiveArchive::configureDiskWriter(writer.get());
+
+ DirectoryGuard targetDir(QFileInfo(dirPath).absolutePath());
+ try {
+ const QStringList createdDirs = targetDir.tryCreate();
+ // Make sure that all leading directories created get removed as well
+ foreach (const QString &directory, createdDirs)
+ emit currentEntryChanged(directory);
+
+ archive_read_set_read_callback(reader.get(), readCallback);
+ archive_read_set_callback_data(reader.get(), this);
+ archive_read_set_seek_callback(reader.get(), seekCallback);
+
+ int status = archive_read_open1(reader.get());
+ if (status != ARCHIVE_OK) {
+ m_status = Failure;
+ emit finished(QLatin1String(archive_error_string(reader.get())));
+ return;
+ }
+
+ forever {
+ if (m_status == Canceled) {
+ emit finished(QLatin1String("Extract canceled."));
+ return;
+ }
+ status = archive_read_next_header(reader.get(), &entry);
+ if (status == ARCHIVE_EOF)
+ break;
+ if (status != ARCHIVE_OK) {
+ m_status = Failure;
+ emit finished(QLatin1String(archive_error_string(reader.get())));
+ return;
+ }
+ const char *current = archive_entry_pathname(entry);
+ const QString outputPath = dirPath + QDir::separator() + QString::fromLocal8Bit(current);
+ archive_entry_set_pathname(entry, outputPath.toLocal8Bit());
+
+ const char *hardlink = archive_entry_hardlink(entry);
+ if (hardlink) {
+ const QString hardLinkPath = dirPath + QDir::separator() + QString::fromLocal8Bit(hardlink);
+ archive_entry_set_hardlink(entry, hardLinkPath.toLocal8Bit());
+ }
+
+ emit currentEntryChanged(outputPath);
+ if (!writeEntry(reader.get(), writer.get(), entry))
+ return;
+
+ ++completed;
+ emit completedChanged(completed, totalFiles);
+
+ qApp->processEvents();
+ }
+ } catch (const Error &e) {
+ m_status = Failure;
+ emit finished(e.message());
+ return;
+ }
+ targetDir.release();
+ m_status = Success;
+ emit finished();
+}
+
+void ExtractWorker::addDataBlock(const QByteArray buffer)
+{
+ m_buffer.append(buffer);
+ emit dataReadyForRead();
+}
+
+void ExtractWorker::onFilePositionChanged(qint64 pos)
+{
+ m_lastPos = pos;
+ emit seekReady();
+}
+
+void ExtractWorker::cancel()
+{
+ m_status = Canceled;
+}
+
+ssize_t ExtractWorker::readCallback(archive *reader, void *caller, const void **buff)
+{
+ Q_UNUSED(reader)
+
+ ExtractWorker *obj;
+ if (!(obj = static_cast<ExtractWorker *>(caller)))
+ return ARCHIVE_FATAL;
+
+ QByteArray *buffer = &obj->m_buffer;
+ if (!buffer->isEmpty())
+ buffer->clear();
+
+ emit obj->dataBlockRequested();
+
+ // It's a bit bad that we have to wait here, but libarchive doesn't
+ // provide an event based reading method.
+ {
+ QEventLoop loop;
+ QTimer::singleShot(30000, &loop, &QEventLoop::quit);
+ connect(obj, &ExtractWorker::dataReadyForRead, &loop, &QEventLoop::quit);
+ connect(obj, &ExtractWorker::dataAtEnd, &loop, &QEventLoop::quit);
+ loop.exec();
+ }
+
+ if (!(*buff = static_cast<const void *>(buffer->constData())))
+ return ARCHIVE_FATAL;
+
+ return buffer->size();
+}
+
+la_int64_t ExtractWorker::seekCallback(archive *reader, void *caller, la_int64_t offset, int whence)
+{
+ Q_UNUSED(reader)
+
+ ExtractWorker *obj;
+ if (!(obj = static_cast<ExtractWorker *>(caller)))
+ return ARCHIVE_FATAL;
+
+ emit obj->seekRequested(static_cast<qint64>(offset), whence);
+
+ {
+ QEventLoop loop;
+ QTimer::singleShot(30000, &loop, &QEventLoop::quit);
+ connect(obj, &ExtractWorker::seekReady, &loop, &QEventLoop::quit);
+ loop.exec();
+ }
+
+ return static_cast<la_int64_t>(obj->m_lastPos);
+}
+
+bool ExtractWorker::writeEntry(archive *reader, archive *writer, archive_entry *entry)
+{
+ int status;
+ const void *buff;
+ size_t size;
+ int64_t offset;
+
+ status = archive_write_header(writer, entry);
+ if (status != ARCHIVE_OK) {
+ emit finished(QLatin1String(archive_error_string(writer)));
+ return false;
+ }
+
+ forever {
+ status = archive_read_data_block(reader, &buff, &size, &offset);
+ if (status == ARCHIVE_EOF)
+ return true;
+ if (status != ARCHIVE_OK) {
+ m_status = Failure;
+ emit finished(QLatin1String(archive_error_string(reader)));
+ return false;
+ }
+ status = archive_write_data_block(writer, buff, size, offset);
+ if (status != ARCHIVE_OK) {
+ m_status = Failure;
+ emit finished(QLatin1String(archive_error_string(writer)));
+ return false;
+ }
+ }
+}
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::LibArchiveArchive
+ \brief The LibArchiveArchive class represents an archive file
+ handled with libarchive archive and compression library.
+
+ In addition to extracting data from the underlying file device,
+ the class supports a non-blocking mode of extracting from an external
+ data source. When using this mode, the calling client must pass the data
+ to be read in chunks of arbitrary size, and inform the object when there
+ is no more data to read.
+*/
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::LibArchiveArchive::ArchiveData
+ \brief Bundles a file device and associated read buffer for access
+ as client data in libarchive callbacks.
+*/
+
+/*!
+ \fn QInstaller::LibArchiveArchive::dataBlockRequested()
+
+ Emitted when the worker object requires more data to continue extracting.
+*/
+
+/*!
+ \fn QInstaller::LibArchiveArchive::seekRequested(qint64 offset, int whence)
+
+ Emitted when the worker object requires a seek to \a offset to continue extracting.
+ The \a whence value defines the starting position for \a offset.
+*/
+
+/*!
+ \fn QInstaller::LibArchiveArchive::workerFinished()
+
+ Emitted when the worker object finished extracting an archive.
+*/
+
+/*!
+ \fn QInstaller::LibArchiveArchive::workerAboutToExtract(const QString &dirPath, const quint64 totalFiles)
+
+ Emitted when the worker object is about to extract \a totalFiles
+ from an archive to \a dirPath.
+*/
+
+/*!
+ \fn QInstaller::LibArchiveArchive::workerAboutToAddDataBlock(const QByteArray buffer)
+
+ Emitted when the worker object is about to add and read the data block in \a buffer.
+*/
+
+/*!
+ \fn QInstaller::LibArchiveArchive::workerAboutToSetDataAtEnd()
+
+ Emitted when the worker object is about to set data-at-end, meaning there
+ will be no further read requests for the calling client.
+*/
+
+/*!
+ \fn QInstaller::LibArchiveArchive::workerAboutToCancel()
+
+ Emitted when the worker object is about to cancel extracting.
+*/
+
+/*!
+ \fn QInstaller::LibArchiveArchive::workerAboutToSetFilePosition(qint64 pos)
+
+ Emitted when the worker object is about to set new file position \a pos.
+*/
+
+/*!
+ Constructs an archive object representing an archive file
+ specified by \a filename with \a parent as the parent object.
+*/
+LibArchiveArchive::LibArchiveArchive(const QString &filename, QObject *parent)
+ : AbstractArchive(parent)
+ , m_data(new ArchiveData())
+ , m_cancelScheduled(false)
+{
+ LibArchiveArchive::setFilename(filename);
+ initExtractWorker();
+}
+
+/*!
+ Constructs an archive object with the given \a parent.
+*/
+LibArchiveArchive::LibArchiveArchive(QObject *parent)
+ : AbstractArchive(parent)
+ , m_data(new ArchiveData())
+ , m_cancelScheduled(false)
+{
+ initExtractWorker();
+}
+
+/*!
+ Destroys the instance and releases resources.
+*/
+LibArchiveArchive::~LibArchiveArchive()
+{
+ m_workerThread.quit();
+ m_workerThread.wait();
+
+ delete m_data;
+}
+
+/*!
+ \reimp
+
+ Opens the underlying file device using \a mode. Returns \c true if
+ succesfull; otherwise \c false.
+*/
+bool LibArchiveArchive::open(QIODevice::OpenMode mode)
+{
+ if (!m_data->file.open(mode)) {
+ setErrorString(m_data->file.errorString());
+ return false;
+ }
+ return true;
+}
+
+/*!
+ \reimp
+
+ Closes the underlying file device.
+*/
+void LibArchiveArchive::close()
+{
+ m_data->file.close();
+}
+
+/*!
+ \reimp
+
+ Sets the \a filename of the underlying file device.
+*/
+void LibArchiveArchive::setFilename(const QString &filename)
+{
+ m_data->file.setFileName(filename);
+}
+
+/*!
+ \reimp
+
+ Extracts the contents of this archive to \a dirPath.
+ Returns \c true on success; \c false otherwise.
+*/
+bool LibArchiveArchive::extract(const QString &dirPath)
+{
+ return extract(dirPath, totalFiles());
+}
+
+/*!
+ \reimp
+
+ Extracts the contents of this archive to \a dirPath with
+ precalculated count of \a totalFiles. Returns \c true on
+ success; \c false otherwise.
+*/
+bool LibArchiveArchive::extract(const QString &dirPath, const quint64 totalFiles)
+{
+ m_cancelScheduled = false;
+ quint64 completed = 0;
+ if (!totalFiles) {
+ setErrorString(QLatin1String("The file count for current archive is null!"));
+ return false;
+ }
+
+ QScopedPointer<archive, ScopedPointerReaderDeleter> reader(archive_read_new());
+ QScopedPointer<archive, ScopedPointerWriterDeleter> writer(archive_write_disk_new());
+ archive_entry *entry = nullptr;
+
+ configureReader(reader.get());
+ configureDiskWriter(writer.get());
+
+ DirectoryGuard targetDir(QFileInfo(dirPath).absolutePath());
+ try {
+ const QStringList createdDirs = targetDir.tryCreate();
+ // Make sure that all leading directories created get removed as well
+ foreach (const QString &directory, createdDirs)
+ emit currentEntryChanged(directory);
+
+ int status = archiveReadOpenWithCallbacks(reader.get());
+ if (status != ARCHIVE_OK)
+ throw Error(QLatin1String(archive_error_string(reader.get())));
+
+ forever {
+ if (m_cancelScheduled)
+ throw Error(QLatin1String("Extract canceled."));
+
+ status = archive_read_next_header(reader.get(), &entry);
+ if (status == ARCHIVE_EOF)
+ break;
+ if (status != ARCHIVE_OK)
+ throw Error(QLatin1String(archive_error_string(reader.get())));
+
+ const char *current = archive_entry_pathname(entry);
+ const QString outputPath = dirPath + QDir::separator() + QString::fromLocal8Bit(current);
+ archive_entry_set_pathname(entry, outputPath.toLocal8Bit());
+
+ const char *hardlink = archive_entry_hardlink(entry);
+ if (hardlink) {
+ const QString hardLinkPath = dirPath + QDir::separator() + QString::fromLocal8Bit(hardlink);
+ archive_entry_set_hardlink(entry, hardLinkPath.toLocal8Bit());
+ }
+
+ emit currentEntryChanged(outputPath);
+ if (!writeEntry(reader.get(), writer.get(), entry))
+ throw Error(errorString()); // appropriate error string set in writeEntry()
+
+ ++completed;
+ emit completedChanged(completed, totalFiles);
+
+ qApp->processEvents();
+ }
+ } catch (const Error &e) {
+ setErrorString(e.message());
+ m_data->file.seek(0);
+ return false;
+ }
+ targetDir.release();
+ m_data->file.seek(0);
+ return true;
+}
+
+/*!
+ \reimp
+
+ Packages the given \a data into the archive and creates the file on disk.
+*/
+bool LibArchiveArchive::create(const QStringList &data)
+{
+ QScopedPointer<archive, ScopedPointerWriterDeleter> writer(archive_write_new());
+ configureWriter(writer.get());
+
+ QStringList globbedData;
+ for (auto &dataEntry : data) { // Expand glob pattern entries with proper filenames
+ if (!dataEntry.contains(QLatin1Char('*'))) {
+ globbedData.append(dataEntry);
+ continue;
+ }
+ const QFileInfo entryInfo(dataEntry);
+ if (entryInfo.path().contains(QLatin1Char('*'))) {
+ setErrorString(QString::fromLatin1("Invalid argument \"%1\": glob patterns "
+ "are not supported between directory paths.").arg(dataEntry));
+ return false;
+ }
+ const QDir parentDir = entryInfo.dir();
+ const QList<QFileInfo> infoList = parentDir.entryInfoList(QStringList()
+ << entryInfo.fileName(), QDir::AllEntries | QDir::Hidden | QDir::NoDotAndDotDot);
+
+ for (auto &info : infoList)
+ globbedData.append(info.absoluteFilePath());
+ }
+
+ try {
+ int status;
+ if ((status = archive_write_open_filename(writer.get(), m_data->file.fileName().toLocal8Bit())))
+ throw Error(QLatin1String(archive_error_string(writer.get())));
+
+ for (auto &dataEntry : globbedData) {
+ QScopedPointer<archive, ScopedPointerReaderDeleter> reader(archive_read_disk_new());
+ configureDiskReader(reader.get());
+
+ if ((status = archive_read_disk_open(reader.get(), dataEntry.toLocal8Bit())))
+ throw Error(QLatin1String(archive_error_string(reader.get())));
+
+ QDir basePath = QFileInfo(dataEntry).dir();
+ forever {
+ QScopedPointer<archive_entry, ScopedPointerEntryDeleter> entry(archive_entry_new());
+ status = archive_read_next_header2(reader.get(), entry.get());
+ if (status == ARCHIVE_EOF)
+ break;
+ if (status != ARCHIVE_OK)
+ throw Error(QLatin1String(archive_error_string(reader.get())));
+
+ const QFileInfo fileOrDir(pathWithoutNamespace(QLatin1String(archive_entry_sourcepath(entry.get()))));
+ // Set new path name in archive, otherwise we add all directories from absolute path
+ const QString newPath = basePath.relativeFilePath(fileOrDir.filePath());
+ archive_entry_set_pathname(entry.get(), newPath.toLocal8Bit());
+
+ archive_read_disk_descend(reader.get());
+ status = archive_write_header(writer.get(), entry.get());
+ if (status < ARCHIVE_OK)
+ throw Error(QLatin1String(archive_error_string(writer.get())));
+
+ if (fileOrDir.isDir() || archive_entry_size(entry.get()) == 0)
+ continue; // nothing to copy
+
+ QFile file(pathWithoutNamespace(QLatin1String(archive_entry_sourcepath(entry.get()))));
+ if (!file.open(QIODevice::ReadOnly))
+ throw Error(file.errorString());
+
+ QByteArray buffer;
+ constexpr qint64 blockSize = 4 * 1024;
+ buffer.resize(blockSize);
+
+ ssize_t bytesRead = readData(&file, buffer.data(), blockSize);
+ while (bytesRead > 0) {
+ archive_write_data(writer.get(), buffer.constData(), blockSize);
+ bytesRead = readData(&file, buffer.data(), blockSize);
+ }
+ file.close();
+ }
+ }
+ } catch (const Error &e) {
+ setErrorString(e.message());
+ return false;
+ }
+ return true;
+}
+
+/*!
+ \reimp
+
+ Returns the contents of this archive as an array of \c ArchiveEntry objects.
+ On failure, returns an empty array.
+*/
+QVector<ArchiveEntry> LibArchiveArchive::list()
+{
+ QScopedPointer<archive, ScopedPointerReaderDeleter> reader(archive_read_new());
+ archive_entry *entry = nullptr;
+
+ configureReader(reader.get());
+
+ QVector<ArchiveEntry> entries;
+ try {
+ int status = archiveReadOpenWithCallbacks(reader.get());
+ if (status != ARCHIVE_OK)
+ throw Error(QLatin1String(archive_error_string(reader.get())));
+
+ forever {
+ status = archive_read_next_header(reader.get(), &entry);
+ if (status == ARCHIVE_EOF)
+ break;
+ if (status != ARCHIVE_OK)
+ throw Error(QLatin1String(archive_error_string(reader.get())));
+
+ ArchiveEntry archiveEntry;
+ archiveEntry.path = QLatin1String(archive_entry_pathname(entry));
+ archiveEntry.utcTime = QDateTime::fromTime_t(archive_entry_mtime(entry));
+ archiveEntry.isDirectory = (archive_entry_filetype(entry) == AE_IFDIR);
+ archiveEntry.uncompressedSize = archive_entry_size(entry);
+ archiveEntry.permissions_mode = archive_entry_perm(entry);
+
+ entries.append(archiveEntry);
+ }
+ } catch (const Error &e) {
+ setErrorString(e.message());
+ m_data->file.seek(0);
+ return QVector<ArchiveEntry>();
+ }
+ m_data->file.seek(0);
+ return entries;
+}
+
+/*!
+ \reimp
+
+ Returns \c true if the current archive is of a supported format;
+ \c false otherwise.
+*/
+bool LibArchiveArchive::isSupported()
+{
+ QScopedPointer<archive, ScopedPointerReaderDeleter> reader(archive_read_new());
+ configureReader(reader.get());
+
+ try {
+ const int status = archiveReadOpenWithCallbacks(reader.get());
+ if (status != ARCHIVE_OK)
+ throw Error(QLatin1String(archive_error_string(reader.get())));
+ } catch (const Error &e) {
+ setErrorString(e.message());
+ m_data->file.seek(0);
+ return false;
+ }
+ m_data->file.seek(0);
+ return true;
+}
+
+/*!
+ Requests to extract the archive to \a dirPath with \a totalFiles
+ in a separate thread with a worker object.
+*/
+void LibArchiveArchive::workerExtract(const QString &dirPath, const quint64 totalFiles)
+{
+ emit workerAboutToExtract(dirPath, totalFiles);
+}
+
+/*!
+ Adds data to be read by the worker object in \a buffer.
+*/
+void LibArchiveArchive::workerAddDataBlock(const QByteArray buffer)
+{
+ emit workerAboutToAddDataBlock(buffer);
+}
+
+/*!
+ Signals the worker object that the client data is at end.
+*/
+void LibArchiveArchive::workerSetDataAtEnd()
+{
+ emit workerAboutToSetDataAtEnd();
+}
+
+/*!
+ Signals the worker object that the client file position changed to \a pos.
+*/
+void LibArchiveArchive::workerSetFilePosition(qint64 pos)
+{
+ emit workerAboutToSetFilePosition(pos);
+}
+
+/*!
+ Cancels the extract in progress for the worker object.
+*/
+void LibArchiveArchive::workerCancel()
+{
+ emit workerAboutToCancel();
+}
+
+/*!
+ Returns the status of the worker object.
+*/
+ExtractWorker::Status LibArchiveArchive::workerStatus() const
+{
+ return m_worker.status();
+}
+
+/*!
+ \reimp
+
+ Cancels the extract in progress.
+*/
+void LibArchiveArchive::cancel()
+{
+ m_cancelScheduled = true;
+}
+
+/*!
+ \internal
+*/
+void LibArchiveArchive::onWorkerFinished(const QString &errorString)
+{
+ setErrorString(errorString);
+ emit workerFinished();
+}
+
+/*!
+ \internal
+*/
+void LibArchiveArchive::configureReader(archive *archive)
+{
+ archive_read_support_filter_bzip2(archive);
+ archive_read_support_filter_gzip(archive);
+ archive_read_support_filter_xz(archive);
+
+ archive_read_support_format_tar(archive);
+ archive_read_support_format_zip(archive);
+ archive_read_support_format_7zip(archive);
+}
+
+/*!
+ \internal
+*/
+void LibArchiveArchive::configureWriter(archive *archive)
+{
+ const QString fileName = m_data->file.fileName();
+ if (fileName.endsWith(QLatin1String(".qbsp"), Qt::CaseInsensitive)) {
+ // The Qt board support package file extension is really a 7z.
+ archive_write_set_format_7zip(archive);
+ } else {
+ archive_write_set_format_filter_by_ext(archive, fileName.toLatin1());
+ }
+
+ if (compressionLevel() == CompressionLevel::Normal)
+ return;
+
+ const QByteArray options = "compression-level=" + QString::number(compressionLevel()).toLatin1();
+ if (archive_write_set_options(archive, options.constData())) { // not fatal
+ qCWarning(QInstaller::lcInstallerInstallLog) << "Could not set options" << options
+ << "for archive" << m_data->file.fileName() << ":" << archive_error_string(archive);
+ }
+}
+
+/*!
+ \internal
+*/
+void LibArchiveArchive::configureDiskReader(archive *archive)
+{
+ archive_read_disk_set_standard_lookup(archive);
+}
+
+/*!
+ \internal
+*/
+void LibArchiveArchive::configureDiskWriter(archive *archive)
+{
+ constexpr int flags = ARCHIVE_EXTRACT_TIME
+ | ARCHIVE_EXTRACT_PERM
+ | ARCHIVE_EXTRACT_ACL
+ | ARCHIVE_EXTRACT_FFLAGS;
+
+ archive_write_disk_set_options(archive, flags);
+ archive_write_disk_set_standard_lookup(archive);
+}
+
+/*!
+ \internal
+*/
+void LibArchiveArchive::initExtractWorker()
+{
+ m_worker.moveToThread(&m_workerThread);
+
+ connect(this, &LibArchiveArchive::workerAboutToExtract, &m_worker, &ExtractWorker::extract);
+ connect(this, &LibArchiveArchive::workerAboutToAddDataBlock, &m_worker, &ExtractWorker::addDataBlock);
+ connect(this, &LibArchiveArchive::workerAboutToSetDataAtEnd, &m_worker, &ExtractWorker::dataAtEnd);
+ connect(this, &LibArchiveArchive::workerAboutToSetFilePosition, &m_worker, &ExtractWorker::onFilePositionChanged);
+ connect(this, &LibArchiveArchive::workerAboutToCancel, &m_worker, &ExtractWorker::cancel);
+
+ connect(&m_worker, &ExtractWorker::dataBlockRequested, this, &LibArchiveArchive::dataBlockRequested);
+ connect(&m_worker, &ExtractWorker::seekRequested, this, &LibArchiveArchive::seekRequested);
+ connect(&m_worker, &ExtractWorker::finished, this, &LibArchiveArchive::onWorkerFinished);
+
+ connect(&m_worker, &ExtractWorker::currentEntryChanged, this, &LibArchiveArchive::currentEntryChanged);
+ connect(&m_worker, &ExtractWorker::completedChanged, this, &LibArchiveArchive::completedChanged);
+
+ m_workerThread.start();
+}
+
+/*!
+ \internal
+
+ Sets default callbacks for archive \a reader and opens for reading.
+*/
+int LibArchiveArchive::archiveReadOpenWithCallbacks(archive *reader)
+{
+ archive_read_set_read_callback(reader, readCallback);
+ archive_read_set_callback_data(reader, m_data);
+ archive_read_set_seek_callback(reader, seekCallback);
+
+ return archive_read_open1(reader);
+}
+
+/*!
+ Writes the current \a entry header, then pulls data from the archive \a reader
+ and writes it to the \a writer handle.
+*/
+bool LibArchiveArchive::writeEntry(archive *reader, archive *writer, archive_entry *entry)
+{
+ int status;
+ const void *buff;
+ size_t size;
+ int64_t offset;
+
+ status = archive_write_header(writer, entry);
+ if (status != ARCHIVE_OK) {
+ setErrorString(QLatin1String(archive_error_string(writer)));
+ return false;
+ }
+
+ forever {
+ status = archive_read_data_block(reader, &buff, &size, &offset);
+ if (status == ARCHIVE_EOF)
+ return true;
+ if (status != ARCHIVE_OK) {
+ setErrorString(QLatin1String(archive_error_string(reader)));
+ return false;
+ }
+ status = archive_write_data_block(writer, buff, size, offset);
+ if (status != ARCHIVE_OK) {
+ setErrorString(QLatin1String(archive_error_string(writer)));
+ return false;
+ }
+ }
+}
+
+/*!
+ \internal
+
+ Reads \a data from the current position of \a file. The maximum bytes to
+ read are specified by \a maxSize. Returns the amount of bytes read.
+*/
+qint64 LibArchiveArchive::readData(QFile *file, char *data, qint64 maxSize)
+{
+ if (!file->isOpen() || file->isSequential())
+ return ARCHIVE_FATAL;
+
+ if (file->atEnd() && file->seek(0))
+ return ARCHIVE_OK;
+
+ const qint64 bytesRead = file->read(data, maxSize);
+ if (bytesRead == -1)
+ return ARCHIVE_FATAL;
+
+ return bytesRead;
+}
+
+/*!
+ \internal
+
+ Called by libarchive when new data is needed. Reads data from the file device
+ in \a archiveData into the buffer referenced by \a buff. Returns the number of bytes read.
+*/
+ssize_t LibArchiveArchive::readCallback(archive *reader, void *archiveData, const void **buff)
+{
+ Q_UNUSED(reader)
+ constexpr qint64 blockSize = 1024 * 1024; // 1MB
+
+ ArchiveData *data;
+ if (!(data = static_cast<ArchiveData *>(archiveData)))
+ return ARCHIVE_FATAL;
+
+ if (!data->buffer.isEmpty())
+ data->buffer.clear();
+
+ if (data->buffer.size() != blockSize)
+ data->buffer.resize(blockSize);
+
+ if (!(*buff = static_cast<const void *>(data->buffer.constData())))
+ return ARCHIVE_FATAL;
+
+ // Doesn't matter if the buffer size exceeds the actual data read,
+ // the return value indicates the length of relevant bytes.
+ return readData(&data->file, data->buffer.data(), data->buffer.size());
+}
+
+/*!
+ \internal
+
+ Seeks to specified \a offset in the file device in \a archiveData and returns the position.
+ Possible \a whence values are \c SEEK_SET, \c SEEK_CUR, and \c SEEK_END. Returns
+ \c ARCHIVE_FATAL if the seek fails.
+*/
+la_int64_t LibArchiveArchive::seekCallback(archive *reader, void *archiveData, la_int64_t offset, int whence)
+{
+ Q_UNUSED(reader)
+
+ ArchiveData *data;
+ if (!(data = static_cast<ArchiveData *>(archiveData)))
+ return ARCHIVE_FATAL;
+
+ if (!data->file.isOpen() || data->file.isSequential())
+ return ARCHIVE_FATAL;
+
+ switch (whence) {
+ case SEEK_SET: // moves file pointer position to the beginning of the file
+ if (!data->file.seek(offset))
+ return ARCHIVE_FATAL;
+ break;
+ case SEEK_CUR: // moves file pointer position to given location
+ if (!data->file.seek(data->file.pos() + offset))
+ return ARCHIVE_FATAL;
+ break;
+ case SEEK_END: // moves file pointer position to the end of file
+ if (!data->file.seek(data->file.size() + offset))
+ return ARCHIVE_FATAL;
+ break;
+ default:
+ return ARCHIVE_FATAL;
+ }
+ return data->file.pos();
+}
+
+/*!
+ Returns the \a path to a file or directory, without the Win32 namespace prefix.
+ On Unix platforms, the \a path is returned unaltered.
+*/
+QString LibArchiveArchive::pathWithoutNamespace(const QString &path)
+{
+ QString aPath = path;
+#ifdef Q_OS_WIN
+ if (aPath.size() > 4 && aPath.at(0) == QLatin1Char('\\')
+ && aPath.at(2) == QLatin1Char('?') && aPath.at(3) == QLatin1Char('\\')) {
+ aPath = aPath.mid(4);
+ }
+#endif
+ return aPath;
+}
+
+/*!
+ Returns the number of files in this archive.
+*/
+quint64 LibArchiveArchive::totalFiles()
+{
+ QScopedPointer<archive, ScopedPointerReaderDeleter> reader(archive_read_new());
+ archive_entry *entry = nullptr;
+ quint64 files = 0;
+
+ configureReader(reader.get());
+
+ try {
+ int status = archiveReadOpenWithCallbacks(reader.get());
+ if (status != ARCHIVE_OK)
+ throw Error(QLatin1String(archive_error_string(reader.get())));
+
+ forever {
+ status = archive_read_next_header(reader.get(), &entry);
+ if (status == ARCHIVE_EOF)
+ break;
+ if (status != ARCHIVE_OK)
+ throw Error(QLatin1String(archive_error_string(reader.get())));
+
+ ++files;
+ }
+ } catch (const Error &e) {
+ setErrorString(e.message());
+ m_data->file.seek(0);
+ return 0;
+ }
+ m_data->file.seek(0);
+ return files;
+}
+
+} // namespace QInstaller
diff --git a/src/libs/installer/libarchivearchive.h b/src/libs/installer/libarchivearchive.h
new file mode 100644
index 000000000..796069ed1
--- /dev/null
+++ b/src/libs/installer/libarchivearchive.h
@@ -0,0 +1,200 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#ifndef LIBARCHIVEARCHIVE_H
+#define LIBARCHIVEARCHIVE_H
+
+#include "installer_global.h"
+#include "abstractarchive.h"
+
+#include <archive.h>
+#include <archive_entry.h>
+
+#include <QThread>
+
+#if defined(_MSC_VER)
+#include <BaseTsd.h>
+typedef SSIZE_T ssize_t;
+#endif
+
+namespace QInstaller {
+
+class ExtractWorker : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(ExtractWorker)
+
+public:
+ enum Status {
+ Success = 0,
+ Failure = 1,
+ Canceled = 2,
+ Unfinished = 3
+ };
+
+ ExtractWorker() = default;
+
+ Status status() const;
+
+public Q_SLOTS:
+ void extract(const QString &dirPath, const quint64 totalFiles);
+ void addDataBlock(const QByteArray buffer);
+ void onFilePositionChanged(qint64 pos);
+ void cancel();
+
+Q_SIGNALS:
+ void dataBlockRequested();
+ void dataAtEnd();
+ void dataReadyForRead();
+ void seekRequested(qint64 offset, int whence);
+ void seekReady();
+ void finished(const QString &errorString = QString());
+
+ void currentEntryChanged(const QString &filename);
+ void completedChanged(quint64 completed, quint64 total);
+
+private:
+ static ssize_t readCallback(archive *reader, void *caller, const void **buff);
+ static la_int64_t seekCallback(archive *reader, void *caller, la_int64_t offset, int whence);
+ bool writeEntry(archive *reader, archive *writer, archive_entry *entry);
+
+private:
+ QByteArray m_buffer;
+ qint64 m_lastPos = 0;
+ Status m_status;
+};
+
+class INSTALLER_EXPORT LibArchiveArchive : public AbstractArchive
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(LibArchiveArchive)
+
+public:
+ LibArchiveArchive(const QString &filename, QObject *parent = nullptr);
+ explicit LibArchiveArchive(QObject *parent = nullptr);
+ ~LibArchiveArchive();
+
+ bool open(QIODevice::OpenMode mode) Q_DECL_OVERRIDE;
+ void close() Q_DECL_OVERRIDE;
+ void setFilename(const QString &filename) Q_DECL_OVERRIDE;
+
+ bool extract(const QString &dirPath) Q_DECL_OVERRIDE;
+ bool extract(const QString &dirPath, const quint64 totalFiles) Q_DECL_OVERRIDE;
+ bool create(const QStringList &data) Q_DECL_OVERRIDE;
+ QVector<ArchiveEntry> list() Q_DECL_OVERRIDE;
+ bool isSupported() Q_DECL_OVERRIDE;
+
+ void workerExtract(const QString &dirPath, const quint64 totalFiles);
+ void workerAddDataBlock(const QByteArray buffer);
+ void workerSetDataAtEnd();
+ void workerSetFilePosition(qint64 pos);
+ void workerCancel();
+ ExtractWorker::Status workerStatus() const;
+
+Q_SIGNALS:
+ void dataBlockRequested();
+ void seekRequested(qint64 offset, int whence);
+ void workerFinished();
+
+ void workerAboutToExtract(const QString &dirPath, const quint64 totalFiles);
+ void workerAboutToAddDataBlock(const QByteArray buffer);
+ void workerAboutToSetDataAtEnd();
+ void workerAboutToSetFilePosition(qint64 pos);
+ void workerAboutToCancel();
+
+public Q_SLOTS:
+ void cancel() Q_DECL_OVERRIDE;
+
+private Q_SLOTS:
+ void onWorkerFinished(const QString &errorString);
+
+private:
+ static void configureReader(archive *archive);
+ void configureWriter(archive *archive);
+ static void configureDiskReader(archive *archive);
+ static void configureDiskWriter(archive *archive);
+
+ void initExtractWorker();
+
+ int archiveReadOpenWithCallbacks(archive *reader);
+ bool writeEntry(archive *reader, archive *writer, archive_entry *entry);
+
+ static qint64 readData(QFile *file, char *data, qint64 maxSize);
+ static ssize_t readCallback(archive *reader, void *archiveData, const void **buff);
+
+ static la_int64_t seekCallback(archive *reader, void *archiveData, la_int64_t offset, int whence);
+
+ static QString pathWithoutNamespace(const QString &path);
+
+ quint64 totalFiles();
+
+private:
+ friend class ExtractWorker;
+ friend class LibArchiveWrapperPrivate;
+
+ struct ArchiveData
+ {
+ QFile file;
+ QByteArray buffer;
+ };
+
+private:
+ ArchiveData *m_data;
+ ExtractWorker m_worker;
+ QThread m_workerThread;
+
+ bool m_cancelScheduled;
+};
+
+struct ScopedPointerReaderDeleter
+{
+ static inline void cleanup(archive *p)
+ {
+ archive_read_free(p);
+ }
+};
+
+struct ScopedPointerWriterDeleter
+{
+ static inline void cleanup(archive *p)
+ {
+ archive_write_free(p);
+ }
+};
+
+struct ScopedPointerEntryDeleter
+{
+ static inline void cleanup(archive_entry *p)
+ {
+ archive_entry_free(p);
+ }
+};
+
+} // namespace QInstaller
+
+#endif // LIBARCHIVEARCHIVE_H
diff --git a/src/libs/installer/libarchivewrapper.cpp b/src/libs/installer/libarchivewrapper.cpp
new file mode 100644
index 000000000..9fbbeb889
--- /dev/null
+++ b/src/libs/installer/libarchivewrapper.cpp
@@ -0,0 +1,191 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#include "libarchivewrapper.h"
+
+namespace QInstaller {
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::LibArchiveWrapper
+ \brief The LibArchiveWrapper class provides an interface for interacting
+ with archives handled using the libarchive archive and compression library.
+
+ The invoked archive operations are performed in a normal user mode, or in an
+ on-demand elevated user mode through the remote client-server protocol of the framework.
+
+ This class is not thread-safe; extra care should be taken especially when
+ elevated mode is active to not call its functions from any thread other than
+ where the object was created.
+*/
+
+/*!
+ Constructs an archive object representing an archive file
+ specified by \a filename with \a parent as the parent object.
+*/
+LibArchiveWrapper::LibArchiveWrapper(const QString &filename, QObject *parent)
+ : AbstractArchive(parent)
+ , d(new LibArchiveWrapperPrivate(filename))
+{
+ connect(d, &LibArchiveWrapperPrivate::currentEntryChanged,
+ this, &LibArchiveWrapper::currentEntryChanged);
+ connect(d, &LibArchiveWrapperPrivate::completedChanged,
+ this, &LibArchiveWrapper::completedChanged);
+}
+
+/*!
+ Constructs an archive object with the given \a parent.
+*/
+LibArchiveWrapper::LibArchiveWrapper(QObject *parent)
+ : AbstractArchive(parent)
+ , d(new LibArchiveWrapperPrivate())
+{
+ connect(d, &LibArchiveWrapperPrivate::currentEntryChanged,
+ this, &LibArchiveWrapper::currentEntryChanged);
+ connect(d, &LibArchiveWrapperPrivate::completedChanged,
+ this, &LibArchiveWrapper::completedChanged);
+}
+
+/*!
+ Destroys the instance.
+*/
+LibArchiveWrapper::~LibArchiveWrapper()
+{
+ delete d;
+}
+
+/*!
+ Opens the file device using \a mode.
+ Returns \c true if succesfull; otherwise \c false.
+*/
+bool LibArchiveWrapper::open(QIODevice::OpenMode mode)
+{
+ return d->open(mode);
+}
+
+/*!
+ Closes the file device.
+*/
+void LibArchiveWrapper::close()
+{
+ d->close();
+}
+
+/*!
+ Sets the \a filename for the archive.
+
+ If the remote connection is active, the same method is called by the server.
+*/
+void LibArchiveWrapper::setFilename(const QString &filename)
+{
+ d->setFilename(filename);
+}
+
+/*!
+ Returns a human-readable description of the last error that occurred.
+
+ If the remote connection is active, the method is called by the server instead.
+*/
+QString LibArchiveWrapper::errorString() const
+{
+ return d->errorString();
+}
+
+/*!
+ Extracts the contents of this archive to \a dirPath. Returns \c true
+ on success; \c false otherwise.
+
+ If the remote connection is active, the method is called by the server instead,
+ with the client starting a new event loop waiting for the extraction to finish.
+*/
+bool LibArchiveWrapper::extract(const QString &dirPath)
+{
+ return d->extract(dirPath);
+}
+
+/*!
+ Extracts the contents of this archive to \a dirPath with
+ precalculated count of \a totalFiles. Returns \c true
+ on success; \c false otherwise.
+
+ If the remote connection is active, the method is called by the server instead,
+ with the client starting a new event loop waiting for the extraction to finish.
+*/
+bool LibArchiveWrapper::extract(const QString &dirPath, const quint64 totalFiles)
+{
+ return d->extract(dirPath, totalFiles);
+}
+
+/*!
+ Packages the given \a data into the archive and creates the file on disk.
+ Returns \c true on success; \c false otherwise.
+
+ If the remote connection is active, the method is called by the server instead.
+*/
+bool LibArchiveWrapper::create(const QStringList &data)
+{
+ return d->create(data);
+}
+
+/*!
+ Returns the contents of this archive as an array of \c ArchiveEntry objects.
+ On failure, returns an empty array.
+*/
+QVector<ArchiveEntry> LibArchiveWrapper::list()
+{
+ return d->list();
+}
+
+/*!
+ Returns \c true if the current archive is of a supported format;
+ \c false otherwise.
+*/
+bool LibArchiveWrapper::isSupported()
+{
+ return d->isSupported();
+}
+
+/*!
+ Sets the compression level for new archives to \a level.
+*/
+void LibArchiveWrapper::setCompressionLevel(const CompressionLevel level)
+{
+ d->setCompressionLevel(level);
+}
+
+/*!
+ Cancels the extract operation in progress.
+
+ If the remote connection is active, the method is called by the server instead.
+*/
+void LibArchiveWrapper::cancel()
+{
+ d->cancel();
+}
+
+} // namespace QInstaller
diff --git a/src/libs/installer/libarchivewrapper.h b/src/libs/installer/libarchivewrapper.h
new file mode 100644
index 000000000..c638d10dc
--- /dev/null
+++ b/src/libs/installer/libarchivewrapper.h
@@ -0,0 +1,71 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#ifndef LIBARCHIVEWRAPPER_H
+#define LIBARCHIVEWRAPPER_H
+
+#include "installer_global.h"
+#include "abstractarchive.h"
+#include "libarchivewrapper_p.h"
+
+namespace QInstaller {
+
+class INSTALLER_EXPORT LibArchiveWrapper : public AbstractArchive
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(LibArchiveWrapper)
+
+public:
+ LibArchiveWrapper(const QString &filename, QObject *parent = nullptr);
+ explicit LibArchiveWrapper(QObject *parent = nullptr);
+ ~LibArchiveWrapper();
+
+ bool open(QIODevice::OpenMode mode) Q_DECL_OVERRIDE;
+ void close() Q_DECL_OVERRIDE;
+ void setFilename(const QString &filename) Q_DECL_OVERRIDE;
+
+ QString errorString() const Q_DECL_OVERRIDE;
+
+ bool extract(const QString &dirPath) Q_DECL_OVERRIDE;
+ bool extract(const QString &dirPath, const quint64 totalFiles) Q_DECL_OVERRIDE;
+ bool create(const QStringList &data) Q_DECL_OVERRIDE;
+ QVector<ArchiveEntry> list() Q_DECL_OVERRIDE;
+ bool isSupported() Q_DECL_OVERRIDE;
+
+ void setCompressionLevel(const AbstractArchive::CompressionLevel level) Q_DECL_OVERRIDE;
+
+public Q_SLOTS:
+ void cancel() Q_DECL_OVERRIDE;
+
+private:
+ LibArchiveWrapperPrivate *const d;
+};
+
+} // namespace QInstaller
+
+#endif // LIBARCHIVEWRAPPER_H
diff --git a/src/libs/installer/libarchivewrapper_p.cpp b/src/libs/installer/libarchivewrapper_p.cpp
new file mode 100644
index 000000000..e5c1e8598
--- /dev/null
+++ b/src/libs/installer/libarchivewrapper_p.cpp
@@ -0,0 +1,403 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#include "libarchivewrapper_p.h"
+
+#include "globals.h"
+
+#include <QFileInfo>
+
+namespace QInstaller {
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::LibArchiveWrapperPrivate
+ \internal
+*/
+
+/*!
+ \fn QInstaller::LibArchiveWrapperPrivate::dataBlockRequested()
+
+ Emitted when the server process has requested another data block.
+*/
+
+/*!
+ \fn QInstaller::LibArchiveWrapperPrivate::remoteWorkerFinished()
+
+ Emitted when the server process has finished extracting an archive.
+*/
+
+/*!
+ Constructs an archive object representing an archive file
+ specified by \a filename.
+*/
+LibArchiveWrapperPrivate::LibArchiveWrapperPrivate(const QString &filename)
+ : RemoteObject(QLatin1String(Protocol::AbstractArchive))
+{
+ init();
+ LibArchiveWrapperPrivate::setFilename(filename);
+}
+
+/*!
+ Constructs the object.
+*/
+LibArchiveWrapperPrivate::LibArchiveWrapperPrivate()
+ : RemoteObject(QLatin1String(Protocol::AbstractArchive))
+{
+ init();
+}
+
+/*!
+ Destroys the instance.
+*/
+LibArchiveWrapperPrivate::~LibArchiveWrapperPrivate()
+{
+}
+
+/*!
+ Opens the file device using \a mode.
+ Returns \c true if succesfull; otherwise \c false.
+*/
+bool LibArchiveWrapperPrivate::open(QIODevice::OpenMode mode)
+{
+ return m_archive.open(mode);
+}
+
+/*!
+ Closes the file device.
+*/
+void LibArchiveWrapperPrivate::close()
+{
+ m_archive.close();
+}
+
+/*!
+ Sets the \a filename for the archive.
+
+ If the remote connection is active, the same method is called by the server.
+*/
+void LibArchiveWrapperPrivate::setFilename(const QString &filename)
+{
+ if (connectToServer()) {
+ m_lock.lockForWrite();
+ callRemoteMethod(QLatin1String(Protocol::AbstractArchiveSetFilename), filename, dummy);
+ m_lock.unlock();
+ }
+ m_archive.setFilename(filename);
+}
+
+/*!
+ Returns a human-readable description of the last error that occurred.
+
+ If the remote connection is active, the method is called by the server instead.
+*/
+QString LibArchiveWrapperPrivate::errorString() const
+{
+ if ((const_cast<LibArchiveWrapperPrivate *>(this))->connectToServer()) {
+ m_lock.lockForWrite();
+ const QString errorString
+ = callRemoteMethod<QString>(QLatin1String(Protocol::AbstractArchiveErrorString));
+ m_lock.unlock();
+ return errorString;
+ }
+ return m_archive.errorString();
+}
+
+/*!
+ Extracts the contents of this archive to \a dirPath with
+ an optional precalculated count of \a totalFiles. Returns \c true
+ on success; \c false otherwise.
+
+ If the remote connection is active, the method is called by the server instead,
+ with the client starting a new event loop waiting for the extraction to finish.
+*/
+bool LibArchiveWrapperPrivate::extract(const QString &dirPath, const quint64 totalFiles)
+{
+ const quint64 total = totalFiles ? totalFiles : m_archive.totalFiles();
+ if (connectToServer()) {
+ QTimer timer;
+ connect(&timer, &QTimer::timeout, this, &LibArchiveWrapperPrivate::processSignals);
+ timer.start();
+
+ m_lock.lockForWrite();
+ callRemoteMethod(QLatin1String(Protocol::AbstractArchiveExtract), dirPath, total);
+ m_lock.unlock();
+ {
+ QEventLoop loop;
+ connect(this, &LibArchiveWrapperPrivate::remoteWorkerFinished, &loop, &QEventLoop::quit);
+ loop.exec();
+ }
+ timer.stop();
+ return (workerStatus() == ExtractWorker::Success);
+ }
+ return m_archive.extract(dirPath, total);
+}
+
+/*!
+ Packages the given \a data into the archive and creates the file on disk.
+ Returns \c true on success; \c false otherwise.
+
+ If the remote connection is active, the method is called by the server instead.
+*/
+bool LibArchiveWrapperPrivate::create(const QStringList &data)
+{
+ if (connectToServer()) {
+ m_lock.lockForWrite();
+ const bool success
+ = callRemoteMethod<bool>(QLatin1String(Protocol::AbstractArchiveCreate), data);
+ m_lock.unlock();
+ return success;
+ }
+ return m_archive.create(data);
+}
+
+/*!
+ Returns the contents of this archive as an array of \c ArchiveEntry objects.
+ On failure, returns an empty array.
+*/
+QVector<ArchiveEntry> LibArchiveWrapperPrivate::list()
+{
+ return m_archive.list();
+}
+
+/*!
+ Returns \c true if the current archive is of a supported format;
+ \c false otherwise.
+*/
+bool LibArchiveWrapperPrivate::isSupported()
+{
+ return m_archive.isSupported();
+}
+
+/*!
+ Sets the compression level for new archives to \a level.
+
+ If the remote connection is active, the method is called by the server instead.
+*/
+void LibArchiveWrapperPrivate::setCompressionLevel(const AbstractArchive::CompressionLevel level)
+{
+ if (connectToServer()) {
+ m_lock.lockForWrite();
+ callRemoteMethod(QLatin1String(Protocol::AbstractArchiveSetCompressionLevel), level, dummy);
+ m_lock.unlock();
+ return;
+ }
+ m_archive.setCompressionLevel(level);
+}
+
+/*!
+ Cancels the extract operation in progress.
+
+ If the remote connection is active, the method is called by the server instead.
+*/
+void LibArchiveWrapperPrivate::cancel()
+{
+ if (connectToServer()) {
+ m_lock.lockForWrite();
+ callRemoteMethod(QLatin1String(Protocol::AbstractArchiveCancel));
+ m_lock.unlock();
+ return;
+ }
+ m_archive.cancel();
+}
+
+/*!
+ Calls a remote method to get the associated queued signals from the server.
+ Signals are then processed and emitted client-side.
+*/
+void LibArchiveWrapperPrivate::processSignals()
+{
+ if (!isConnectedToServer())
+ return;
+
+ if (!m_lock.tryLockForRead())
+ return;
+
+ QList<QVariant> receivedSignals =
+ callRemoteMethod<QList<QVariant>>(QString::fromLatin1(Protocol::GetAbstractArchiveSignals));
+
+ m_lock.unlock();
+ while (!receivedSignals.isEmpty()) {
+ const QString name = receivedSignals.takeFirst().toString();
+ if (name == QLatin1String(Protocol::AbstractArchiveSignalCurrentEntryChanged)) {
+ emit currentEntryChanged(receivedSignals.takeFirst().toString());
+ } else if (name == QLatin1String(Protocol::AbstractArchiveSignalCompletedChanged)) {
+ const quint64 completed = receivedSignals.takeFirst().value<quint64>();
+ const quint64 total = receivedSignals.takeFirst().value<quint64>();
+ emit completedChanged(completed, total);
+ } else if (name == QLatin1String(Protocol::AbstractArchiveSignalDataBlockRequested)) {
+ emit dataBlockRequested();
+ } else if (name == QLatin1String(Protocol::AbstractArchiveSignalSeekRequested)) {
+ const qint64 offset = receivedSignals.takeFirst().value<qint64>();
+ const int whence = receivedSignals.takeFirst().value<int>();
+ emit seekRequested(offset, whence);
+ } else if (name == QLatin1String(Protocol::AbstractArchiveSignalWorkerFinished)) {
+ emit remoteWorkerFinished();
+ }
+ }
+}
+
+/*!
+ Reads a block of data from the current position of the underlying file device.
+*/
+void LibArchiveWrapperPrivate::onDataBlockRequested()
+{
+ constexpr quint64 blockSize = 1024 * 1024; // 1MB
+
+ QFile *const file = &m_archive.m_data->file;
+ if (!file->isOpen() || file->isSequential()) {
+ qCWarning(QInstaller::lcInstallerInstallLog) << file->errorString();
+ setClientDataAtEnd();
+ return;
+ }
+ if (file->atEnd() && file->seek(0)) {
+ setClientDataAtEnd();
+ return;
+ }
+
+ QByteArray *const buff = &m_archive.m_data->buffer;
+ if (!buff->isEmpty())
+ buff->clear();
+
+ if (buff->size() != blockSize)
+ buff->resize(blockSize);
+
+ const qint64 bytesRead = file->read(buff->data(), blockSize);
+ if (bytesRead == -1) {
+ qCWarning(QInstaller::lcInstallerInstallLog) << file->errorString();
+ setClientDataAtEnd();
+ return;
+ }
+ // The read callback in ExtractWorker class expects the buffer size to
+ // match the number of bytes read. Some formats will fail if the buffer
+ // is larger than the actual data.
+ if (buff->size() != bytesRead)
+ buff->resize(bytesRead);
+
+ addDataBlock(*buff);
+}
+
+/*!
+ Seeks to specified \a offset in the underlying file device. Possible \a whence
+ values are \c SEEK_SET, \c SEEK_CUR, and \c SEEK_END.
+*/
+void LibArchiveWrapperPrivate::onSeekRequested(qint64 offset, int whence)
+{
+ QFile *const file = &m_archive.m_data->file;
+ if (!file->isOpen() || file->isSequential()) {
+ qCWarning(QInstaller::lcInstallerInstallLog) << file->errorString();
+ setClientFilePosition(ARCHIVE_FATAL);
+ return;
+ }
+ bool success = false;
+ switch (whence) {
+ case SEEK_SET: // moves file pointer position to the beginning of the file
+ success = file->seek(offset);
+ break;
+ case SEEK_CUR: // moves file pointer position to given location
+ success = file->seek(file->pos() + offset);
+ break;
+ case SEEK_END: // moves file pointer position to the end of file
+ success = file->seek(file->size() + offset);
+ break;
+ default:
+ break;
+ }
+ setClientFilePosition(success ? file->pos() : ARCHIVE_FATAL);
+}
+
+/*!
+ Connects handler signals for the matching signals of the wrapper object.
+*/
+void LibArchiveWrapperPrivate::init()
+{
+ QObject::connect(&m_archive, &LibArchiveArchive::currentEntryChanged,
+ this, &LibArchiveWrapperPrivate::currentEntryChanged);
+ QObject::connect(&m_archive, &LibArchiveArchive::completedChanged,
+ this, &LibArchiveWrapperPrivate::completedChanged);
+
+ QObject::connect(this, &LibArchiveWrapperPrivate::dataBlockRequested,
+ this, &LibArchiveWrapperPrivate::onDataBlockRequested);
+ QObject::connect(this, &LibArchiveWrapperPrivate::seekRequested,
+ this, &LibArchiveWrapperPrivate::onSeekRequested);
+}
+
+/*!
+ Calls a remote method to add a \a buffer for reading.
+*/
+void LibArchiveWrapperPrivate::addDataBlock(const QByteArray &buffer)
+{
+ if (connectToServer()) {
+ m_lock.lockForWrite();
+ callRemoteMethod(QLatin1String(Protocol::AbstractArchiveAddDataBlock), buffer, dummy);
+ m_lock.unlock();
+ }
+}
+
+/*!
+ Calls a remote method to inform that the client has finished
+ reading the current file.
+*/
+void LibArchiveWrapperPrivate::setClientDataAtEnd()
+{
+ if (connectToServer()) {
+ m_lock.lockForWrite();
+ callRemoteMethod(QLatin1String(Protocol::AbstractArchiveSetClientDataAtEnd));
+ m_lock.unlock();
+ }
+}
+
+/*!
+ Calls a remote method to set new file position \a pos.
+*/
+void LibArchiveWrapperPrivate::setClientFilePosition(qint64 pos)
+{
+ if (connectToServer()) {
+ m_lock.lockForWrite();
+ callRemoteMethod(QLatin1String(Protocol::AbstractArchiveSetFilePosition), pos, dummy);
+ m_lock.unlock();
+ }
+}
+
+/*!
+ Calls a remote method to retrieve and return the status of
+ the extract worker on a server process.
+*/
+ExtractWorker::Status LibArchiveWrapperPrivate::workerStatus() const
+{
+ ExtractWorker::Status status = ExtractWorker::Unfinished;
+ if ((const_cast<LibArchiveWrapperPrivate *>(this))->connectToServer()) {
+ m_lock.lockForWrite();
+ status = static_cast<ExtractWorker::Status>(
+ callRemoteMethod<qint32>(QLatin1String(Protocol::AbstractArchiveWorkerStatus)));
+ m_lock.unlock();
+ }
+ return status;
+}
+
+} // namespace QInstaller
diff --git a/src/libs/installer/libarchivewrapper_p.h b/src/libs/installer/libarchivewrapper_p.h
new file mode 100644
index 000000000..9e612fab3
--- /dev/null
+++ b/src/libs/installer/libarchivewrapper_p.h
@@ -0,0 +1,95 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#ifndef LIBARCHIVEWRAPPER_P_H
+#define LIBARCHIVEWRAPPER_P_H
+
+#include "installer_global.h"
+#include "remoteobject.h"
+#include "libarchivearchive.h"
+
+#include <QTimer>
+#include <QReadWriteLock>
+
+namespace QInstaller {
+
+class INSTALLER_EXPORT LibArchiveWrapperPrivate : public RemoteObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(LibArchiveWrapperPrivate)
+
+public:
+ explicit LibArchiveWrapperPrivate(const QString &filename);
+ LibArchiveWrapperPrivate();
+ ~LibArchiveWrapperPrivate();
+
+ bool open(QIODevice::OpenMode mode);
+ void close();
+ void setFilename(const QString &filename);
+
+ QString errorString() const;
+
+ bool extract(const QString &dirPath, const quint64 totalFiles = 0);
+ bool create(const QStringList &data);
+ QVector<ArchiveEntry> list();
+ bool isSupported();
+
+ void setCompressionLevel(const AbstractArchive::CompressionLevel level);
+
+Q_SIGNALS:
+ void currentEntryChanged(const QString &filename);
+ void completedChanged(const quint64 completed, const quint64 total);
+ void dataBlockRequested();
+ void seekRequested(qint64 offset, int whence);
+ void remoteWorkerFinished();
+
+public Q_SLOTS:
+ void cancel();
+
+private Q_SLOTS:
+ void processSignals();
+ void onDataBlockRequested();
+ void onSeekRequested(qint64 offset, int whence);
+
+private:
+ void init();
+
+ void addDataBlock(const QByteArray &buffer);
+ void setClientDataAtEnd();
+ void setClientFilePosition(qint64 pos);
+ ExtractWorker::Status workerStatus() const;
+
+private:
+ mutable QReadWriteLock m_lock;
+
+ LibArchiveArchive m_archive;
+};
+
+} // namespace QInstaller
+
+#endif // LIBARCHIVEWRAPPER_P_H
diff --git a/src/libs/installer/loggingutils.cpp b/src/libs/installer/loggingutils.cpp
new file mode 100644
index 000000000..a2b561c53
--- /dev/null
+++ b/src/libs/installer/loggingutils.cpp
@@ -0,0 +1,503 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#include "loggingutils.h"
+
+#include "component.h"
+#include "localpackagehub.h"
+#include "globals.h"
+#include "fileutils.h"
+#include "remoteclient.h"
+#include "remotefileengine.h"
+
+#include <QDomDocument>
+#include <QElapsedTimer>
+
+#include <iostream>
+#if defined(Q_OS_UNIX)
+#include <unistd.h>
+#elif defined(Q_OS_WIN)
+#include <stdio.h>
+#include <io.h>
+#endif
+
+namespace QInstaller {
+
+/*!
+ \class QInstaller::LoggingHandler
+ \inmodule QtInstallerFramework
+ \brief The LoggingHandler class provides methods for manipulating the
+ application-wide verbosiveness and format of printed debug messages.
+
+ The class also contains utility methods for printing common preformatted messages.
+*/
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::Uptime
+ \internal
+*/
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::VerboseWriter
+ \internal
+*/
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::VerboseWriterOutput
+ \internal
+*/
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::PlainVerboseWriterOutput
+ \internal
+*/
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::VerboseWriterAdminOutput
+ \internal
+*/
+
+/*!
+ \enum LoggingHandler::VerbosityLevel
+ \brief This enum holds the possible levels of output verbosity.
+
+ \value Silent
+ \value Normal
+ \value Detailed
+ \value Minimum
+ Minimum possible verbosity level. Synonym for \c VerbosityLevel::Silent.
+ \value Maximum
+ Maximum possible verbosity level. Synonym for \c VerbosityLevel::Detailed.
+*/
+
+// start timer on construction (so we can use it as static member)
+class Uptime : public QElapsedTimer {
+public:
+ Uptime() { start(); }
+};
+
+/*!
+ \internal
+*/
+LoggingHandler::LoggingHandler()
+ : m_verbLevel(VerbosityLevel::Silent)
+{
+#if defined(Q_OS_UNIX)
+ m_outputRedirected = !isatty(fileno(stdout));
+#elif defined(Q_OS_WIN)
+ m_outputRedirected = !_isatty(_fileno(stdout));
+#endif
+}
+
+/*!
+ \internal
+*/
+LoggingHandler::~LoggingHandler()
+{
+}
+
+/*!
+ Prints out preformatted debug messages, warnings, critical and fatal error messages
+ specified by \a msg and \a type. The message \a context provides information about
+ the source code location the message was generated.
+*/
+void LoggingHandler::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
+{
+ // suppress warning from QPA minimal plugin
+ if (msg.contains(QLatin1String("This plugin does not support propagateSizeHints")))
+ return;
+
+ if (context.category == lcProgressIndicator().categoryName()) {
+ if (!outputRedirected())
+ std::cout << msg.toStdString() << "\r" << std::flush;
+ return;
+ }
+
+ static Uptime uptime;
+
+ QString ba = QLatin1Char('[') + QString::number(uptime.elapsed()) + QStringLiteral("] ");
+ ba += trimAndPrepend(type, msg);
+
+ if (type != QtDebugMsg && context.file) {
+ ba += QString(QStringLiteral(" (%1:%2, %3)")).arg(
+ QString::fromLatin1(context.file)).arg(context.line).arg(
+ QString::fromLatin1(context.function));
+ }
+
+ if (VerboseWriter *log = VerboseWriter::instance())
+ log->appendLine(ba);
+
+ if (type != QtDebugMsg || isVerbose())
+ std::cout << qPrintable(ba) << std::endl;
+
+ if (type == QtFatalMsg) {
+ QtMessageHandler oldMsgHandler = qInstallMessageHandler(nullptr);
+ qt_message_output(type, context, msg);
+ qInstallMessageHandler(oldMsgHandler);
+ }
+}
+
+/*!
+ Trims the trailing space character and surrounding quotes from \a msg.
+ Also prepends the message \a type to the message.
+*/
+QString LoggingHandler::trimAndPrepend(QtMsgType type, const QString &msg) const
+{
+ QString ba(msg);
+ // last character is a space from qDebug
+ if (ba.endsWith(QLatin1Char(' ')))
+ ba.chop(1);
+
+ // remove quotes if the whole message is surrounded with them
+ if (ba.startsWith(QLatin1Char('"')) && ba.endsWith(QLatin1Char('"')))
+ ba = ba.mid(1, ba.length() - 2);
+
+ // prepend the message type, skip QtDebugMsg
+ switch (type) {
+ case QtWarningMsg:
+ ba.prepend(QStringLiteral("Warning: "));
+ break;
+
+ case QtCriticalMsg:
+ ba.prepend(QStringLiteral("Critical: "));
+ break;
+
+ case QtFatalMsg:
+ ba.prepend(QStringLiteral("Fatal: "));
+ break;
+
+ default:
+ break;
+ }
+ return ba;
+}
+
+/*!
+ Returns the only instance of this class.
+*/
+LoggingHandler &LoggingHandler::instance()
+{
+ static LoggingHandler instance;
+ return instance;
+}
+
+/*!
+ Sets to verbose output if \a v is set to \c true. Calling this multiple
+ times increases or decreases the verbosity level accordingly.
+*/
+void LoggingHandler::setVerbose(bool v)
+{
+ if (v)
+ m_verbLevel++;
+ else
+ m_verbLevel--;
+}
+
+/*!
+ Returns \c true if the installer is set to verbose output.
+*/
+bool LoggingHandler::isVerbose() const
+{
+ return m_verbLevel != VerbosityLevel::Silent;
+}
+
+/*!
+ Returns the current verbosity level.
+*/
+LoggingHandler::VerbosityLevel LoggingHandler::verboseLevel() const
+{
+ return m_verbLevel;
+}
+
+/*!
+ Returns \c true if output is redirected, i.e. to a file.
+*/
+bool LoggingHandler::outputRedirected() const
+{
+ return m_outputRedirected;
+}
+
+/*!
+ Prints update information from \a components.
+*/
+void LoggingHandler::printUpdateInformation(const QList<Component *> components) const
+{
+ QDomDocument doc;
+ QDomElement root = doc.createElement(QLatin1String("updates"));
+ doc.appendChild(root);
+
+ foreach (const Component *component, components) {
+ QDomElement update = doc.createElement(QLatin1String("update"));
+ update.setAttribute(QLatin1String("name"), component->value(scDisplayName));
+ update.setAttribute(QLatin1String("version"), component->value(scVersion));
+ update.setAttribute(QLatin1String("size"), component->value(scUncompressedSize));
+ update.setAttribute(QLatin1String("id"), component->value(scName));
+ root.appendChild(update);
+ }
+ std::cout << qPrintable(doc.toString(4));
+}
+
+/*!
+ Prints basic or more detailed information about local \a packages,
+ depending on the current verbosity level.
+*/
+void LoggingHandler::printLocalPackageInformation(const QList<KDUpdater::LocalPackage> &packages) const
+{
+ QDomDocument doc;
+ QDomElement root = doc.createElement(QLatin1String("localpackages"));
+ doc.appendChild(root);
+ foreach (KDUpdater::LocalPackage package, packages) {
+ QDomElement update = doc.createElement(QLatin1String("package"));
+ update.setAttribute(QLatin1String("name"), package.name);
+ update.setAttribute(QLatin1String("displayname"), package.title);
+ update.setAttribute(QLatin1String("version"), package.version);
+ if (verboseLevel() == VerbosityLevel::Detailed) {
+ update.setAttribute(QLatin1String("description"), package.description);
+ update.setAttribute(QLatin1String("dependencies"), package.dependencies.join(QLatin1Char(',')));
+ update.setAttribute(QLatin1String("autoDependencies"), package.autoDependencies.join(QLatin1Char(',')));
+ update.setAttribute(QLatin1String("virtual"), package.virtualComp);
+ update.setAttribute(QLatin1String("forcedInstallation"), package.forcedInstallation);
+ update.setAttribute(QLatin1String("checkable"), package.checkable);
+ update.setAttribute(QLatin1String("uncompressedSize"), package.uncompressedSize);
+ update.setAttribute(QLatin1String("installDate"), package.installDate.toString());
+ update.setAttribute(QLatin1String("lastUpdateDate"), package.lastUpdateDate.toString());
+ }
+ root.appendChild(update);
+ }
+ std::cout << qPrintable(doc.toString(4));
+}
+
+/*!
+ Prints basic or more detailed information about available \a matchedPackages,
+ depending on the current verbosity level. If a package is also present in \a installedPackages,
+ the installed version will be included in printed information.
+*/
+void LoggingHandler::printPackageInformation(const PackagesList &matchedPackages, const LocalPackagesHash &installedPackages) const
+{
+ QDomDocument doc;
+ QDomElement root = doc.createElement(QLatin1String("availablepackages"));
+ doc.appendChild(root);
+ foreach (Package *package, matchedPackages) {
+ const QString name = package->data(scName).toString();
+ QDomElement update = doc.createElement(QLatin1String("package"));
+ update.setAttribute(QLatin1String("name"), name);
+ update.setAttribute(QLatin1String("displayname"), package->data(scDisplayName).toString());
+ update.setAttribute(QLatin1String("version"), package->data(scVersion).toString());
+ //Check if package already installed
+ if (installedPackages.contains(name))
+ update.setAttribute(QLatin1String("installedVersion"), installedPackages.value(name).version);
+ if (verboseLevel() == VerbosityLevel::Detailed) {
+ update.setAttribute(QLatin1String("description"), package->data(scDescription).toString());
+ update.setAttribute(QLatin1String("dependencies"), package->data(scDependencies).toString());
+ update.setAttribute(QLatin1String("autoDependencies"), package->data(scAutoDependOn).toString());
+ update.setAttribute(QLatin1String("virtual"), package->data(scVirtual).toString());
+ update.setAttribute(QLatin1String("forcedInstallation"), package->data(QLatin1String("ForcedInstallation")).toString());
+ update.setAttribute(QLatin1String("checkable"), package->data(scCheckable).toString());
+ update.setAttribute(QLatin1String("default"), package->data(scDefault).toString());
+ update.setAttribute(QLatin1String("essential"), package->data(scEssential).toString());
+ update.setAttribute(QLatin1String("forcedUpdate"), package->data(scForcedUpdate).toString());
+ update.setAttribute(QLatin1String("compressedsize"), package->data(QLatin1String("CompressedSize")).toString());
+ update.setAttribute(QLatin1String("uncompressedsize"), package->data(QLatin1String("UncompressedSize")).toString());
+ update.setAttribute(QLatin1String("releaseDate"), package->data(scReleaseDate).toString());
+ update.setAttribute(QLatin1String("downloadableArchives"), package->data(scDownloadableArchives).toString());
+ update.setAttribute(QLatin1String("licenses"), package->data(QLatin1String("Licenses")).toString());
+ update.setAttribute(QLatin1String("script"), package->data(scScript).toString());
+ update.setAttribute(QLatin1String("sortingPriority"), package->data(scSortingPriority).toString());
+ update.setAttribute(QLatin1String("replaces"), package->data(scReplaces).toString());
+ update.setAttribute(QLatin1String("requiresAdminRights"), package->data(scRequiresAdminRights).toString());
+ }
+ root.appendChild(update);
+ }
+ std::cout << qPrintable(doc.toString(4));
+}
+
+/*!
+ \internal
+*/
+VerboseWriter::VerboseWriter()
+{
+ m_preFileBuffer.open(QIODevice::ReadWrite);
+ m_stream.setDevice(&m_preFileBuffer);
+ m_currentDateTimeAsString = QDateTime::currentDateTime().toString();
+}
+
+/*!
+ \internal
+*/
+VerboseWriter::~VerboseWriter()
+{
+ if (m_preFileBuffer.isOpen()) {
+ PlainVerboseWriterOutput output;
+ (void)flush(&output);
+ }
+}
+
+/*!
+ \internal
+*/
+bool VerboseWriter::flush(VerboseWriterOutput *output)
+{
+ m_stream.flush();
+ if (m_logFileName.isEmpty()) // binarycreator
+ return true;
+ if (!m_preFileBuffer.isOpen())
+ return true;
+ //if the installer installed nothing - there is no target directory - where the logfile can be saved
+ if (!QFileInfo(m_logFileName).absoluteDir().exists())
+ return true;
+
+ QString logInfo;
+ logInfo += QLatin1String("************************************* Invoked: ");
+ logInfo += m_currentDateTimeAsString;
+ logInfo += QLatin1String("\n");
+
+ QBuffer buffer;
+ buffer.open(QIODevice::WriteOnly);
+ buffer.write(logInfo.toLocal8Bit());
+ buffer.write(m_preFileBuffer.data());
+ buffer.close();
+
+ if (output->write(m_logFileName, QIODevice::ReadWrite | QIODevice::Append | QIODevice::Text, buffer.data())) {
+ m_preFileBuffer.close();
+ m_stream.setDevice(nullptr);
+ return true;
+ }
+ return false;
+}
+
+/*!
+ \internal
+*/
+void VerboseWriter::setFileName(const QString &fileName)
+{
+ m_logFileName = fileName;
+}
+
+Q_GLOBAL_STATIC(VerboseWriter, verboseWriter)
+
+/*!
+ \internal
+*/
+VerboseWriter *VerboseWriter::instance()
+{
+ return verboseWriter();
+}
+
+/*!
+ \internal
+*/
+void VerboseWriter::appendLine(const QString &msg)
+{
+ m_stream << msg << endl;
+}
+
+/*!
+ \internal
+*/
+VerboseWriterOutput::~VerboseWriterOutput()
+{
+}
+
+/*!
+ \internal
+*/
+bool PlainVerboseWriterOutput::write(const QString &fileName, QIODevice::OpenMode openMode, const QByteArray &data)
+{
+ QFile output(fileName);
+ if (output.open(openMode)) {
+ output.write(data);
+ setDefaultFilePermissions(&output, DefaultFilePermissions::NonExecutable);
+ return true;
+ }
+ return false;
+}
+
+/*!
+ \internal
+*/
+bool VerboseWriterAdminOutput::write(const QString &fileName, QIODevice::OpenMode openMode, const QByteArray &data)
+{
+ bool gainedAdminRights = false;
+
+ if (!RemoteClient::instance().isActive()) {
+ m_core->gainAdminRights();
+ gainedAdminRights = true;
+ }
+
+ RemoteFileEngine file;
+ file.setFileName(fileName);
+ if (file.open(openMode)) {
+ file.write(data.constData(), data.size());
+ file.close();
+ if (gainedAdminRights)
+ m_core->dropAdminRights();
+ return true;
+ }
+
+ if (gainedAdminRights)
+ m_core->dropAdminRights();
+
+ return false;
+}
+
+/*!
+ \internal
+
+ Increments verbosity \a level.
+*/
+LoggingHandler::VerbosityLevel &operator++(LoggingHandler::VerbosityLevel &level, int)
+{
+ const int i = static_cast<int>(level) + 1;
+ level = (i > LoggingHandler::VerbosityLevel::Maximum)
+ ? LoggingHandler::VerbosityLevel::Maximum
+ : static_cast<LoggingHandler::VerbosityLevel>(i);
+
+ return level;
+}
+
+/*!
+ \internal
+
+ Decrements verbosity \a level.
+*/
+LoggingHandler::VerbosityLevel &operator--(LoggingHandler::VerbosityLevel &level, int)
+{
+ const int i = static_cast<int>(level) - 1;
+ level = (i < LoggingHandler::VerbosityLevel::Minimum)
+ ? LoggingHandler::VerbosityLevel::Minimum
+ : static_cast<LoggingHandler::VerbosityLevel>(i);
+
+ return level;
+}
+
+} // namespace QInstaller
diff --git a/src/libs/installer/loggingutils.h b/src/libs/installer/loggingutils.h
new file mode 100644
index 000000000..06f0400f8
--- /dev/null
+++ b/src/libs/installer/loggingutils.h
@@ -0,0 +1,129 @@
+/**************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#ifndef LOGGINGUTILS_H
+#define LOGGINGUTILS_H
+
+#include "component.h"
+
+#include <QObject>
+
+namespace QInstaller {
+
+class INSTALLER_EXPORT LoggingHandler
+{
+ Q_DISABLE_COPY(LoggingHandler)
+ Q_ENUMS(VerbosityLevel)
+
+public:
+ enum VerbosityLevel {
+ Silent = 0,
+ Normal = 1,
+ Detailed = 2,
+ Minimum = Silent,
+ Maximum = Detailed
+ };
+
+ static LoggingHandler &instance();
+ void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
+
+ void setVerbose(bool v);
+ bool isVerbose() const;
+ VerbosityLevel verboseLevel() const;
+ bool outputRedirected() const;
+
+ void printUpdateInformation(const QList<Component *> components) const;
+ void printLocalPackageInformation(const QList<KDUpdater::LocalPackage> &packages) const;
+ void printPackageInformation(const PackagesList &matchedPackages, const LocalPackagesHash &installedPackages) const;
+
+ friend VerbosityLevel &operator++(VerbosityLevel &level, int);
+ friend VerbosityLevel &operator--(VerbosityLevel &level, int);
+
+private:
+ LoggingHandler();
+ ~LoggingHandler();
+
+ QString trimAndPrepend(QtMsgType type, const QString &msg) const;
+
+private:
+ VerbosityLevel m_verbLevel;
+ bool m_outputRedirected;
+};
+
+class INSTALLER_EXPORT VerboseWriterOutput
+{
+public:
+ virtual bool write(const QString &fileName, QIODevice::OpenMode openMode, const QByteArray &data) = 0;
+
+protected:
+ ~VerboseWriterOutput();
+};
+
+class INSTALLER_EXPORT PlainVerboseWriterOutput : public VerboseWriterOutput
+{
+public:
+ virtual bool write(const QString &fileName, QIODevice::OpenMode openMode, const QByteArray &data) override;
+};
+
+class INSTALLER_EXPORT VerboseWriterAdminOutput : public VerboseWriterOutput
+{
+public:
+ VerboseWriterAdminOutput(PackageManagerCore *core) : m_core(core) {}
+
+ virtual bool write(const QString &fileName, QIODevice::OpenMode openMode, const QByteArray &data) override;
+
+private:
+ PackageManagerCore *m_core;
+};
+
+class INSTALLER_EXPORT VerboseWriter
+{
+public:
+ VerboseWriter();
+ ~VerboseWriter();
+
+ static VerboseWriter *instance();
+
+ bool flush(VerboseWriterOutput *output);
+
+ void appendLine(const QString &msg);
+ void setFileName(const QString &fileName);
+
+private:
+ QTextStream m_stream;
+ QBuffer m_preFileBuffer;
+ QString m_logFileName;
+ QString m_currentDateTimeAsString;
+};
+
+LoggingHandler::VerbosityLevel &operator++(LoggingHandler::VerbosityLevel &level, int);
+LoggingHandler::VerbosityLevel &operator--(LoggingHandler::VerbosityLevel &level, int);
+
+} // namespace QInstaller
+
+#endif // LOGGINGUTILS_H
diff --git a/src/libs/installer/messageboxhandler.cpp b/src/libs/installer/messageboxhandler.cpp
index 79630a54a..e2d461ba8 100644
--- a/src/libs/installer/messageboxhandler.cpp
+++ b/src/libs/installer/messageboxhandler.cpp
@@ -29,6 +29,7 @@
#include "messageboxhandler.h"
#include "globals.h"
+#include "loggingutils.h"
#include <QtCore/QDebug>
@@ -420,6 +421,9 @@ QMessageBox::StandardButton MessageBoxHandler::showMessageBox(MessageType messag
if (qobject_cast<QApplication*> (qApp) == nullptr) {
QMessageBox::StandardButton button = defaultButton;
bool showAnswerInLog = true;
+ if (LoggingHandler::instance().outputRedirected() && (m_defaultAction == AskUser))
+ setDefaultAction(Reject);
+
if (m_defaultAction == AskUser) {
if (!availableAnswers.isEmpty()) {
while (!askAnswerFromUser(button, buttons)) {
diff --git a/src/libs/installer/metadatajob.cpp b/src/libs/installer/metadatajob.cpp
index a15dd8848..50fd723ab 100644
--- a/src/libs/installer/metadatajob.cpp
+++ b/src/libs/installer/metadatajob.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 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.
@@ -39,6 +39,7 @@
#include <QTemporaryDir>
#include <QtMath>
+#include <QRandomGenerator>
const QStringList metaElements = {QLatin1String("Script"), QLatin1String("Licenses"), QLatin1String("UserInterfaces"), QLatin1String("Translations")};
@@ -174,15 +175,11 @@ void MetadataJob::doStart()
url += m_core->value(scUrlQueryString) + QLatin1Char('&');
// also append a random string to avoid proxy caches
- FileTaskItem item(url.append(QString::number(qrand() * qrand())));
+ FileTaskItem item(url.append(QString::number(QRandomGenerator::global()->generate())));
item.insert(TaskRole::UserRole, QVariant::fromValue(repo));
item.insert(TaskRole::Authenticator, QVariant::fromValue(authenticator));
items.append(item);
}
- else {
- qCWarning(QInstaller::lcInstallerInstallLog) << "Trying to parse compressed repo as "
- "normal repository. Check repository syntax.";
- }
}
}
if (items.count() > 0) {
@@ -237,7 +234,7 @@ void MetadataJob::startXMLTask(const QList<FileTaskItem> &items)
void MetadataJob::doCancel()
{
reset();
- emitFinishedWithError(Job::Canceled, tr("Meta data download canceled."));
+ emitFinishedWithError(Job::Canceled, tr("Metadata download canceled."));
}
void MetadataJob::startUnzipRepositoryTask(const Repository &repo)
@@ -291,8 +288,6 @@ void MetadataJob::unzipRepositoryTaskFinished()
m_unzipRepositoryitems.append(item);
} else {
//Repository is not valid, remove it
- Repository repository;
- repository.setUrl(QUrl(task->archive()));
Settings &s = m_core->settings();
QSet<Repository> temporaries = s.temporaryRepositories();
foreach (Repository repository, temporaries) {
@@ -546,7 +541,9 @@ void MetadataJob::reset()
try {
m_xmlTask.cancel();
+ m_xmlTask.waitForFinished();
m_metadataTask.cancel();
+ m_metadataTask.waitForFinished();
} catch (...) {}
m_tempDirDeleter.releaseAndDeleteAll();
m_metadataResult.clear();
@@ -655,8 +652,11 @@ MetadataJob::Status MetadataJob::parseUpdatesXml(const QList<FileTaskResult> &re
metaFound = parsePackageUpdate(c2, packageName, packageVersion, packageHash,
online, testCheckSum);
- //If meta element (script, licenses, etc.) is not found, no need to fetch metadata
- if (metaFound) {
+ // If meta element (script, licenses, etc.) is not found, no need to fetch metadata.
+ // The offline-generator instance is an exception to this - if the Updates.xml contains
+ // checksum element for the meta-archive, we will fetch it, so that the temporary
+ // location contents match the remote repository.
+ if (metaFound || (m_core->isOfflineGenerator() && !packageHash.isEmpty())) {
const QString repoUrl = metadata.repository.url().toString();
addFileTaskItem(QString::fromLatin1("%1/%2/%3meta.7z").arg(repoUrl, packageName, packageVersion),
metadata.directory + QString::fromLatin1("/%1-%2-meta.7z").arg(packageName, packageVersion),
@@ -678,7 +678,7 @@ MetadataJob::Status MetadataJob::parseUpdatesXml(const QList<FileTaskResult> &re
//Hash metadata to help checking if meta for repository is already fetched
ArchiveMetadata archiveMetadata;
archiveMetadata.metaData = metadata;
- m_fetchedArchive.insertMulti(metadata.repository.categoryname(), archiveMetadata);
+ m_fetchedArchive.insert(metadata.repository.categoryname(), archiveMetadata);
//Check if other categories have the same url (contains same metadata)
//so we can speed up other category fetches
@@ -686,7 +686,7 @@ MetadataJob::Status MetadataJob::parseUpdatesXml(const QList<FileTaskResult> &re
if (category.displayname() != metadata.repository.categoryname()) {
foreach (Repository repository, category.repositories()) {
if (repository.url() == metadata.repository.url()) {
- m_fetchedArchive.insertMulti(category.displayname(), archiveMetadata);
+ m_fetchedArchive.insert(category.displayname(), archiveMetadata);
}
}
}
@@ -789,7 +789,7 @@ bool MetadataJob::parsePackageUpdate(const QDomNodeList &c2, QString &packageNam
QHash<QString, QPair<Repository, Repository> > MetadataJob::searchAdditionalRepositories
(const QDomNode &repositoryUpdate, const FileTaskResult &result, const Metadata &metadata)
{
- QHash<QString, QPair<Repository, Repository> > repositoryUpdates;
+ QMultiHash<QString, QPair<Repository, Repository> > repositoryUpdates;
const QDomNodeList children = repositoryUpdate.toElement().childNodes();
for (int i = 0; i < children.count(); ++i) {
const QDomElement el = children.at(i).toElement();
@@ -802,14 +802,14 @@ QHash<QString, QPair<Repository, Repository> > MetadataJob::searchAdditionalRepo
repository.setPassword(el.attribute(QLatin1String("password")));
repository.setDisplayName(el.attribute(QLatin1String("displayname")));
if (ProductKeyCheck::instance()->isValidRepository(repository)) {
- repositoryUpdates.insertMulti(action, qMakePair(repository, Repository()));
+ repositoryUpdates.insert(action, qMakePair(repository, Repository()));
qDebug() << "Repository to add:" << repository.displayname();
}
} else if (action == QLatin1String("remove")) {
// remove possible default repositories using the given server url
Repository repository(resolveUrl(result, el.attribute(QLatin1String("url"))), true);
repository.setDisplayName(el.attribute(QLatin1String("displayname")));
- repositoryUpdates.insertMulti(action, qMakePair(repository, Repository()));
+ repositoryUpdates.insert(action, qMakePair(repository, Repository()));
qDebug() << "Repository to remove:" << repository.displayname();
} else if (action == QLatin1String("replace")) {
@@ -822,7 +822,7 @@ QHash<QString, QPair<Repository, Repository> > MetadataJob::searchAdditionalRepo
if (ProductKeyCheck::instance()->isValidRepository(newRepository)) {
// store the new repository and the one old it replaces
- repositoryUpdates.insertMulti(action, qMakePair(newRepository, oldRepository));
+ repositoryUpdates.insert(action, qMakePair(newRepository, oldRepository));
qDebug() << "Replace repository" << oldRepository.displayname() << "with"
<< newRepository.displayname();
}
diff --git a/src/libs/installer/metadatajob.h b/src/libs/installer/metadatajob.h
index eb0e91a4f..3ee6ccf46 100644
--- a/src/libs/installer/metadatajob.h
+++ b/src/libs/installer/metadatajob.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2018 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.
@@ -127,7 +127,7 @@ private:
int m_taskNumber;
int m_totalTaskCount;
QStringList m_shaMissmatchPackages;
- QHash<QString, ArchiveMetadata> m_fetchedArchive;
+ QMultiHash<QString, ArchiveMetadata> m_fetchedArchive;
QHash<QString, Metadata> m_metaFromDefaultRepositories;
QHash<QString, Metadata> m_metaFromArchive; //for faster lookups.
};
diff --git a/src/libs/installer/metadatajob_p.h b/src/libs/installer/metadatajob_p.h
index 9160f4cc9..2511f4b00 100644
--- a/src/libs/installer/metadatajob_p.h
+++ b/src/libs/installer/metadatajob_p.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -29,8 +29,7 @@
#ifndef METADATAJOB_P_H
#define METADATAJOB_P_H
-#include "lib7z_extract.h"
-#include "lib7z_facade.h"
+#include "archivefactory.h"
#include "metadatajob.h"
#include <QDir>
@@ -76,20 +75,16 @@ public:
return; // ignore already canceled
}
- QFile archive(m_archive);
- if (archive.open(QIODevice::ReadOnly)) {
- try {
- Lib7z::extractArchive(&archive, m_targetDir);
- } catch (const Lib7z::SevenZipException& e) {
- fi.reportException(UnzipArchiveException(MetadataJob::tr("Error while extracting "
- "archive \"%1\": %2").arg(QDir::toNativeSeparators(m_archive), e.message())));
- } catch (...) {
- fi.reportException(UnzipArchiveException(MetadataJob::tr("Unknown exception "
- "caught while extracting archive \"%1\".").arg(QDir::toNativeSeparators(m_archive))));
- }
- } else {
+ QScopedPointer<AbstractArchive> archive(ArchiveFactory::instance().create(m_archive));
+ if (!archive) {
+ fi.reportException(UnzipArchiveException(MetadataJob::tr("Unsupported archive \"%1\": no handler "
+ "registered for file suffix \"%2\".").arg(m_archive, QFileInfo(m_archive).suffix())));
+ } else if (!archive->open(QIODevice::ReadOnly)) {
fi.reportException(UnzipArchiveException(MetadataJob::tr("Cannot open file \"%1\" for "
- "reading: %2").arg(QDir::toNativeSeparators(m_archive), archive.errorString())));
+ "reading: %2").arg(QDir::toNativeSeparators(m_archive), archive->errorString())));
+ } else if (!archive->extract(m_targetDir)) {
+ fi.reportException(UnzipArchiveException(MetadataJob::tr("Error while extracting "
+ "archive \"%1\": %2").arg(QDir::toNativeSeparators(m_archive), archive->errorString())));
}
fi.reportFinished();
diff --git a/src/libs/installer/packagemanagercore.cpp b/src/libs/installer/packagemanagercore.cpp
index ed658a2ed..010a68d21 100644
--- a/src/libs/installer/packagemanagercore.cpp
+++ b/src/libs/installer/packagemanagercore.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -43,9 +43,9 @@
#include "remoteclient.h"
#include "remotefileengine.h"
#include "settings.h"
-#include "utils.h"
#include "installercalculator.h"
#include "uninstallercalculator.h"
+#include "loggingutils.h"
#include <productkeycheck.h>
@@ -70,7 +70,8 @@
#include "updateoperationfactory.h"
#ifdef Q_OS_WIN
-# include "qt_windows.h"
+#include "qt_windows.h"
+#include <limits>
#endif
#include <QStandardPaths>
@@ -213,6 +214,13 @@ using namespace QInstaller;
*/
/*!
+ \fn QInstaller::PackageManagerCore::defaultTranslationsLoadedForLanguage(QLocale::Language lang)
+
+ Emitted when the language \a lang has changed.
+
+*/
+
+/*!
\fn QInstaller::PackageManagerCore::finishButtonClicked()
\sa {installer::finishButtonClicked}{installer.finishButtonClicked}
@@ -317,6 +325,18 @@ using namespace QInstaller;
*/
/*!
+ \fn QInstaller::PackageManagerCore::offlineGenerationStarted()
+
+ \sa {installer::offlineGenerationStarted()}{installer.offlineGenerationStarted()}
+*/
+
+/*!
+ \fn QInstaller::PackageManagerCore::offlineGenerationFinished()
+
+ \sa {installer::offlineGenerationFinished()}{installer.offlineGenerationFinished()}
+*/
+
+/*!
\fn QInstaller::PackageManagerCore::titleMessageChanged(const QString &title)
Emitted when the text of the installer status (on the PerformInstallation page) changes to
@@ -416,6 +436,13 @@ using namespace QInstaller;
Emitted when installer binary marker \a magicMarker has changed.
*/
+/*!
+ \fn QInstaller::PackageManagerCore::componentsRecalculated()
+
+ Emitted when the component tree is recalculated. In a graphical interface,
+ this signal is emitted also after the categories are fetched.
+*/
+
Q_GLOBAL_STATIC(QMutex, globalModelMutex);
static QFont *sVirtualComponentsFont = nullptr;
Q_GLOBAL_STATIC(QMutex, globalVirtualComponentsFontMutex);
@@ -714,7 +741,7 @@ quint64 PackageManagerCore::requiredDiskSpace() const
quint64 result = 0;
foreach (QInstaller::Component *component, orderedComponentsToInstall())
- result += size(component, scUncompressedSize);
+ result += size(component, isOfflineGenerator() ? scCompressedSize : scUncompressedSize);
return result;
}
@@ -744,6 +771,7 @@ int PackageManagerCore::downloadNeededArchives(double partProgressSize)
Q_ASSERT(partProgressSize >= 0 && partProgressSize <= 1);
QList<QPair<QString, QString> > archivesToDownload;
+ quint64 archivesToDownloadTotalSize = 0;
QList<Component*> neededComponents = orderedComponentsToInstall();
foreach (Component *component, neededComponents) {
// collect all archives to be downloaded
@@ -753,6 +781,7 @@ int PackageManagerCore::downloadNeededArchives(double partProgressSize)
.arg(component->name(), versionFreeString), QString::fromLatin1("%1/%2/%3")
.arg(component->repositoryUrl().toString(), component->name(), versionFreeString)));
}
+ archivesToDownloadTotalSize += component->value(scCompressedSize).toULongLong();
}
if (archivesToDownload.isEmpty())
@@ -763,6 +792,7 @@ int PackageManagerCore::downloadNeededArchives(double partProgressSize)
DownloadArchivesJob archivesJob(this);
archivesJob.setAutoDelete(false);
archivesJob.setArchivesToDownload(archivesToDownload);
+ archivesJob.setExpectedTotalSize(archivesToDownloadTotalSize);
connect(this, &PackageManagerCore::installationInterrupted, &archivesJob, &Job::cancel);
connect(&archivesJob, &DownloadArchivesJob::outputTextChanged,
ProgressCoordinator::instance(), &ProgressCoordinator::emitLabelAndDetailTextChanged);
@@ -827,7 +857,7 @@ void PackageManagerCore::setNeedsHardRestart(bool needsHardRestart)
*/
void PackageManagerCore::rollBackInstallation()
{
- emit titleMessageChanged(tr("Cancelling the Installer"));
+ emit titleMessageChanged(tr("Canceling the Installer"));
// this unregisters all operation progressChanged connected
ProgressCoordinator::instance()->setUndoMode();
@@ -863,7 +893,7 @@ void PackageManagerCore::rollBackInstallation()
operation->setValue(QLatin1String("forceremoval"), false);
}
- PackageManagerCorePrivate::performOperationThreaded(operation, PackageManagerCorePrivate::Undo);
+ PackageManagerCorePrivate::performOperationThreaded(operation, Operation::Undo);
const QString componentName = operation->value(QLatin1String("component")).toString();
if (!componentName.isEmpty()) {
@@ -877,11 +907,10 @@ void PackageManagerCore::rollBackInstallation()
}
d->m_localPackageHub->writeToDisk();
- if (isInstaller()) {
- if (d->m_localPackageHub->packageInfoCount() == 0) {
- QFile file(d->m_localPackageHub->fileName());
+ if (isInstaller() && d->m_localPackageHub->packageInfoCount() == 0) {
+ QFile file(d->m_localPackageHub->fileName());
+ if (!file.fileName().isEmpty() && file.exists())
file.remove();
- }
}
if (becameAdmin)
@@ -959,6 +988,8 @@ QString PackageManagerCore::readFile(const QString &filePath, const QString &cod
* characters. If \a maxlen is 0, the line can be of any length.
*
* \note Can be only called when installing from command line instance without GUI.
+ * If the output device is not a TTY, i.e. when forwarding to a file, the function
+ * will throw an error.
*
* \sa {installer::readConsoleLine}{installer.readConsoleLine}
*/
@@ -966,6 +997,10 @@ QString PackageManagerCore::readConsoleLine(const QString &title, qint64 maxlen)
{
if (!isCommandLineInstance())
return QString();
+ if (LoggingHandler::instance().outputRedirected()) {
+ throw Error(tr("User input is required but the output "
+ "device is not associated with a terminal."));
+ }
if (!title.isEmpty())
qDebug() << title;
QTextStream stream(stdin);
@@ -975,8 +1010,36 @@ QString PackageManagerCore::readConsoleLine(const QString &title, qint64 maxlen)
}
/*!
- Checks whether the target directory \a targetDirectory exists and has contents:
+ Returns \a path with the '/' separators converted to separators that are
+ appropriate for the underlying operating system.
+
+ On Unix platforms the returned string is the same as the argument.
+
+ \sa {installer::toNativeSeparators}{installer.toNativeSeparators}
+ \sa fromNativeSeparators()
+*/
+QString PackageManagerCore::toNativeSeparators(const QString &path)
+{
+ return QDir::toNativeSeparators(path);
+}
+
+/*!
+ Returns \a path using '/' as file separator.
+
+ On Unix platforms the returned string is the same as the argument.
+
+ \sa {installer::fromNativeSeparators}{installer.fromNativeSeparators}
+ \sa toNativeSeparators()
+*/
+QString PackageManagerCore::fromNativeSeparators(const QString &path)
+{
+ return QDir::fromNativeSeparators(path);
+}
+
+/*!
+ Checks whether installation is allowed to \a targetDirectory:
\list
+ \li Returns \c true if the directory does not exist.
\li Returns \c true if the directory exists and is empty.
\li Returns \c false if the directory already exists and contains an installation.
\li Returns \c false if the target is a file or a symbolic link.
@@ -984,14 +1047,17 @@ QString PackageManagerCore::readConsoleLine(const QString &title, qint64 maxlen)
choice that the end users make in the displayed message box.
\endlist
*/
-bool PackageManagerCore::checkTargetDir(const QString &targetDirectory)
+bool PackageManagerCore::installationAllowedToDirectory(const QString &targetDirectory)
{
+ const QFileInfo fi(targetDirectory);
+ if (!fi.exists())
+ return true;
+
const QDir dir(targetDirectory);
// the directory exists and is empty...
if (dir.exists() && dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot).isEmpty())
return true;
- const QFileInfo fi(targetDirectory);
if (fi.isDir()) {
QString fileName = settings().maintenanceToolName();
#if defined(Q_OS_MACOS)
@@ -1048,8 +1114,8 @@ QString PackageManagerCore::targetDirWarning(const QString &targetDirectory) con
}
}
- target = target.canonicalPath();
- if (!target.isEmpty() && (target == QDir::root() || target == QDir::home())) {
+ target.setPath(target.canonicalPath());
+ if (!target.path().isEmpty() && (target == QDir::root() || target == QDir::home())) {
return tr("As the install directory is completely deleted, installing in %1 is forbidden.")
.arg(QDir::toNativeSeparators(target.path()));
}
@@ -1166,7 +1232,8 @@ PackageManagerCore::PackageManagerCore(qint64 magicmaker, const QList<OperationB
// Sanity check to detect a broken installations with missing operations.
// Every installed package should have at least one MinimalProgress operation.
//
- QSet<QString> installedPackages = d->m_core->localInstalledPackages().keys().toSet();
+ const QStringList localPackageList = d->m_core->localInstalledPackages().keys();
+ QSet<QString> installedPackages(localPackageList.begin(), localPackageList.end());
QSet<QString> operationPackages;
foreach (QInstaller::Operation *operation, d->m_performedOperationsOld) {
if (operation->hasValue(QLatin1String("component")))
@@ -1190,40 +1257,6 @@ PackageManagerCore::PackageManagerCore(qint64 magicmaker, const QList<OperationB
ProgressCoordinator::instance(), &ProgressCoordinator::printProgressMessage);
}
-class VerboseWriterAdminOutput : public VerboseWriterOutput
-{
-public:
- VerboseWriterAdminOutput(PackageManagerCore *core) : m_core(core) {}
-
- virtual bool write(const QString &fileName, QIODevice::OpenMode openMode, const QByteArray &data)
- {
- bool gainedAdminRights = false;
-
- if (!RemoteClient::instance().isActive()) {
- m_core->gainAdminRights();
- gainedAdminRights = true;
- }
-
- RemoteFileEngine file;
- file.setFileName(fileName);
- if (file.open(openMode)) {
- file.write(data.constData(), data.size());
- file.close();
- if (gainedAdminRights)
- m_core->dropAdminRights();
- return true;
- }
-
- if (gainedAdminRights)
- m_core->dropAdminRights();
-
- return false;
- }
-
-private:
- PackageManagerCore *m_core;
-};
-
/*!
Destroys the instance.
*/
@@ -1383,23 +1416,64 @@ bool PackageManagerCore::fetchLocalPackagesTree()
d->clearAllComponentLists();
QHash<QString, QInstaller::Component*> components;
+ QMap<QString, QString> treeNameComponents;
+
+ std::function<void(QList<LocalPackage> *, bool)> loadLocalPackages;
+ loadLocalPackages = [&](QList<LocalPackage> *treeNamePackages, bool firstRun) {
+ foreach (auto &package, (firstRun ? installedPackages.values() : *treeNamePackages)) {
+ if (firstRun && !package.treeName.first.isEmpty()) {
+ // Package has a tree name, leave for later
+ treeNamePackages->append(package);
+ continue;
+ }
- const QStringList &keys = installedPackages.keys();
- foreach (const QString &key, keys) {
- QScopedPointer<QInstaller::Component> component(new QInstaller::Component(this));
- component->loadDataFromPackage(installedPackages.value(key));
- const QString &name = component->name();
- if (components.contains(name)) {
- qCritical("Cannot register component! Component with identifier %s already registered.",
- qPrintable(name));
- continue;
+ QScopedPointer<QInstaller::Component> component(new QInstaller::Component(this));
+ component->loadDataFromPackage(package);
+ QString name = component->treeName();
+ if (components.contains(name)) {
+ qCritical() << "Cannot register component" << component->name() << "with name" << name
+ << "! Component with identifier" << name << "already exists.";
+ // Conflicting original name, skip.
+ if (component->value(scTreeName).isEmpty())
+ continue;
+
+ // Conflicting tree name, check if we can add with original name.
+ name = component->name();
+ if (!settings().allowUnstableComponents() || components.contains(name))
+ continue;
+
+ qCDebug(lcInstallerInstallLog)
+ << "Registering component with the original indetifier:" << name;
+ component->removeValue(scTreeName);
+ const QString errorString = QLatin1String("Tree name conflicts with an existing indentifier");
+ d->m_pendingUnstableComponents.insert(component->name(),
+ QPair<Component::UnstableError, QString>(Component::InvalidTreeName, errorString));
+ }
+ const QString treeName = component->value(scTreeName);
+ if (!treeName.isEmpty())
+ treeNameComponents.insert(component->name(), treeName);
+
+ components.insert(name, component.take());
}
- components.insert(name, component.take());
+ // Second pass with leftover packages
+ if (firstRun)
+ loadLocalPackages(treeNamePackages, false);
+ };
+
+ {
+ // Loading package data is performed in two steps: first, components without
+ // - and then components with tree names. This is to ensure components with tree
+ // names do not replace other components when registering fails to a name conflict.
+ QList<LocalPackage> treeNamePackagesTmp;
+ loadLocalPackages(&treeNamePackagesTmp, true);
}
+ createAutoTreeNames(components, treeNameComponents);
+
if (!d->buildComponentTree(components, false))
return false;
+ d->commitPendingUnstableComponents();
updateDisplayVersions(scDisplayVersion);
emit finishAllComponentsReset(d->m_rootComponents);
@@ -1486,20 +1560,16 @@ bool PackageManagerCore::fetchCompressedPackagesTree()
if (!isInstaller() && status() == Failure)
return false;
- if (!d->fetchMetaInformationFromCompressedRepositories())
+ if (!d->fetchMetaInformationFromRepositories(DownloadType::CompressedPackage))
return false;
if (!d->addUpdateResourcesFromRepositories(true, true)) {
return false;
}
- PackagesList packages;
- const PackagesList &compPackages = d->compressedPackages();
- if (compPackages.isEmpty())
+ const PackagesList &packages = d->remotePackages();
+ if (packages.isEmpty())
return false;
- packages.append(compPackages);
- const PackagesList &rPackages = d->remotePackages();
- packages.append(rPackages);
return fetchPackagesTree(packages, installedPackages);
}
@@ -1529,7 +1599,7 @@ bool PackageManagerCore::fetchRemotePackagesTree()
if (!d->fetchMetaInformationFromRepositories())
return false;
- if (!d->fetchMetaInformationFromCompressedRepositories())
+ if (!d->fetchMetaInformationFromRepositories(DownloadType::CompressedPackage))
return false;
if (!d->addUpdateResourcesFromRepositories(true))
@@ -1551,24 +1621,29 @@ bool PackageManagerCore::fetchPackagesTree(const PackagesList &packages, const L
return false;
if (success && isPackageManager()) {
foreach (Package *const update, packages) {
- if (update->data(scEssential, scFalse).toString().toLower() == scTrue) {
+ bool essentialUpdate = (update->data(scEssential, scFalse).toString().toLower() == scTrue);
+ bool forcedUpdate = (update->data(scForcedUpdate, scFalse).toString().toLower() == scTrue);
+ if (essentialUpdate || forcedUpdate) {
const QString name = update->data(scName).toString();
- if (!installedPackages.contains(name)) {
+ // 'Essential' package not installed, install.
+ if (essentialUpdate && !installedPackages.contains(name)) {
success = false;
- continue; // unusual, the maintenance tool should always be available
+ continue;
}
+ // 'Forced update' package not installed, no update needed
+ if (forcedUpdate && !installedPackages.contains(name))
+ continue;
const LocalPackage localPackage = installedPackages.value(name);
- const QString updateVersion = update->data(scVersion).toString();
- if (KDUpdater::compareVersion(updateVersion, localPackage.version) <= 0)
- continue; // remote version equals or is less than the installed maintenance tool
+ if (!d->packageNeedsUpdate(localPackage, update))
+ continue;
const QDate updateDate = update->data(scReleaseDate).toDate();
if (localPackage.lastUpdateDate >= updateDate)
continue; // remote release date equals or is less than the installed maintenance tool
success = false;
- break; // we found a newer version of the maintenance tool
+ break; // we found a newer version of the forced/essential update package
}
}
@@ -1587,6 +1662,7 @@ bool PackageManagerCore::fetchPackagesTree(const PackagesList &packages, const L
if (success && !d->statusCanceledOrFailed())
d->setStatus(Success);
+ emit componentsRecalculated();
return success;
}
@@ -1865,8 +1941,9 @@ void PackageManagerCore::appendRootComponent(Component *component)
/*!
Returns a list of components depending on the component types passed in \a mask.
+ Optionally, a \a regexp expression can be used to further filter the listed packages.
*/
-QList<Component *> PackageManagerCore::components(ComponentTypes mask) const
+QList<Component *> PackageManagerCore::components(ComponentTypes mask, const QString &regexp) const
{
QList<Component *> components;
@@ -1887,6 +1964,17 @@ QList<Component *> PackageManagerCore::components(ComponentTypes mask) const
// No descendants here, updates are always a flat list and cannot have children!
}
+ if (!regexp.isEmpty()) {
+ QRegularExpression re(regexp);
+ QList<Component*>::iterator iter = components.begin();
+ while (iter != components.end()) {
+ if (!re.match(iter.i->t()->name()).hasMatch())
+ iter = components.erase(iter);
+ else
+ iter++;
+ }
+ }
+
return components;
}
@@ -2071,7 +2159,9 @@ bool PackageManagerCore::calculateComponentsToUninstall() const
emit aboutCalculateComponentsToUninstall();
if (!isUpdater()) {
// hack to avoid removing needed dependencies
- QSet<Component*> componentsToInstall = d->installerCalculator()->orderedComponentsToInstall().toSet();
+ const QList<Component *> componentsToInstallList
+ = d->installerCalculator()->orderedComponentsToInstall();
+ QSet<Component*> componentsToInstall(componentsToInstallList.begin(), componentsToInstallList.end());
QList<Component*> componentsToUninstall;
foreach (Component *component, components(ComponentType::All)) {
@@ -2160,8 +2250,11 @@ ComponentModel *PackageManagerCore::defaultComponentModel() const
d->m_defaultModel = componentModel(const_cast<PackageManagerCore*> (this),
QLatin1String("AllComponentsModel"));
}
+ connect(this, &PackageManagerCore::startAllComponentsReset, [&] {
+ d->m_defaultModel->reset();
+ });
connect(this, &PackageManagerCore::finishAllComponentsReset, d->m_defaultModel,
- &ComponentModel::setRootComponents);
+ &ComponentModel::reset);
return d->m_defaultModel;
}
@@ -2175,38 +2268,68 @@ ComponentModel *PackageManagerCore::updaterComponentModel() const
d->m_updaterModel = componentModel(const_cast<PackageManagerCore*> (this),
QLatin1String("UpdaterComponentsModel"));
}
+ connect(this, &PackageManagerCore::startUpdaterComponentsReset, [&] {
+ d->m_updaterModel->reset();
+ });
connect(this, &PackageManagerCore::finishUpdaterComponentsReset, d->m_updaterModel,
- &ComponentModel::setRootComponents);
+ &ComponentModel::reset);
return d->m_updaterModel;
}
/*!
Lists available packages filtered with \a regexp without GUI. Virtual
- components are not listed unless set visible.
+ components are not listed unless set visible. Optionally, a \a filters
+ hash containing package information elements and regular expressions
+ can be used to further filter listed packages.
\sa setVirtualComponentsVisible()
*/
-void PackageManagerCore::listAvailablePackages(const QString &regexp)
+void PackageManagerCore::listAvailablePackages(const QString &regexp, const QHash<QString, QString> &filters)
{
+ setPackageViewer();
qCDebug(QInstaller::lcInstallerInstallLog)
<< "Searching packages with regular expression:" << regexp;
+
+ ComponentModel *model = defaultComponentModel();
d->fetchMetaInformationFromRepositories(DownloadType::UpdatesXML);
d->addUpdateResourcesFromRepositories(true);
QRegularExpression re(regexp);
+ re.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
const PackagesList &packages = d->remotePackages();
+ if (!fetchAllPackages(packages, LocalPackagesHash())) {
+ qCWarning(QInstaller::lcInstallerInstallLog)
+ << "There was a problem with loading the package data.";
+ return;
+ }
+
+ PackagesList matchedPackages;
+ foreach (Package *package, packages) {
+ const QString name = package->data(scName).toString();
+ Component *component = componentByName(name);
+ if (!component)
+ continue;
- bool foundMatch = false;
- foreach (const Package *update, packages) {
- const QString name = update->data(scName).toString();
- if (re.match(name).hasMatch() &&
- (virtualComponentsVisible() ? true : !update->data(scVirtual, false).toBool())) {
- d->printPackageInformation(name, update);
- foundMatch = true;
+ const QModelIndex &idx = model->indexFromComponentName(component->treeName());
+ if (idx.isValid() && re.match(name).hasMatch()) {
+ bool ignoreComponent = false;
+ for (auto &key : filters.keys()) {
+ const QString elementValue = component->value(key);
+ QRegularExpression elementRegexp(filters.value(key));
+ elementRegexp.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
+ if (elementValue.isEmpty() || !elementRegexp.match(elementValue).hasMatch()) {
+ ignoreComponent = true;
+ break;
+ }
+ }
+ if (!ignoreComponent)
+ matchedPackages.append(package);
}
}
- if (!foundMatch)
+ if (matchedPackages.count() == 0)
qCDebug(QInstaller::lcInstallerInstallLog) << "No matching packages found.";
+ else
+ LoggingHandler::instance().printPackageInformation(matchedPackages, localInstalledPackages());
}
bool PackageManagerCore::componentUninstallableFromCommandLine(const QString &componentName)
@@ -2219,7 +2342,7 @@ bool PackageManagerCore::componentUninstallableFromCommandLine(const QString &co
return false;
}
ComponentModel *model = defaultComponentModel();
- const QModelIndex &idx = model->indexFromComponentName(componentName);
+ const QModelIndex &idx = model->indexFromComponentName(component->treeName());
if (model->data(idx, Qt::CheckStateRole) == QVariant::Invalid) {
// Component cannot be unselected, check why
if (component->forcedInstallation()) {
@@ -2242,17 +2365,91 @@ bool PackageManagerCore::componentUninstallableFromCommandLine(const QString &co
}
/*!
- Lists installed packages without GUI.
+ \internal
+
+ Tries to set \c Qt::CheckStateRole to \c Qt::Checked for given \a components in the
+ default component model. Returns \c true if \a components contains at least one component
+ eligible for installation, otherwise returns \c false. An error message can be retrieved
+ with \a errorMessage.
+*/
+bool PackageManagerCore::checkComponentsForInstallation(const QStringList &components, QString &errorMessage)
+{
+ bool installComponentsFound = false;
+
+ ComponentModel *model = defaultComponentModel();
+ foreach (const QString &name, components) {
+ Component *component = componentByName(name);
+ if (!component) {
+ errorMessage.append(tr("Cannot install %1. Component not found.\n").arg(name));
+ continue;
+ }
+ const QModelIndex &idx = model->indexFromComponentName(component->treeName());
+ if (idx.isValid()) {
+ if ((model->data(idx, Qt::CheckStateRole) == QVariant::Invalid) && !component->forcedInstallation()) {
+ // User cannot select the component, check why
+ if (component->autoDependencies().count() > 0) {
+ errorMessage.append(tr("Cannot install component %1. Component is installed only as automatic "
+ "dependency to %2.\n").arg(name, component->autoDependencies().join(QLatin1Char(','))));
+ } else if (!component->isCheckable()) {
+ errorMessage.append(tr("Cannot install component %1. Component is not checkable, meaning you "
+ "have to select one of the subcomponents.\n").arg(name));
+ }
+ } else if (component->isInstalled()) {
+ errorMessage.append(tr("Component %1 already installed\n").arg(name));
+ } else {
+ model->setData(idx, Qt::Checked, Qt::CheckStateRole);
+ installComponentsFound = true;
+ }
+ } else {
+ auto isDescendantOfVirtual = [&]() {
+ Component *trace = component;
+ forever {
+ trace = trace->parentComponent();
+ if (!trace) {
+ // We already checked the root component if there is no parent
+ return false;
+ } else if (trace->isVirtual()) {
+ errorMessage.append(tr("Cannot install %1. Component is a descendant "
+ "of a virtual component %2.\n").arg(name, trace->name()));
+ return true;
+ }
+ }
+ };
+ // idx is invalid and component valid when we have invisible virtual component
+ if (component->isVirtual())
+ errorMessage.append(tr("Cannot install %1. Component is virtual.\n").arg(name));
+ else if (!isDescendantOfVirtual())
+ errorMessage.append(tr("Cannot install %1. Component not found.\n").arg(name));
+ }
+ }
+ if (!installComponentsFound)
+ setCanceled();
+
+ return installComponentsFound;
+}
+
+/*!
+ Lists installed packages without GUI. List of packages can be filtered with \a regexp.
*/
-void PackageManagerCore::listInstalledPackages()
+void PackageManagerCore::listInstalledPackages(const QString &regexp)
{
+ setPackageViewer();
LocalPackagesHash installedPackages = this->localInstalledPackages();
+ if (!regexp.isEmpty()) {
+ qCDebug(QInstaller::lcInstallerInstallLog)
+ << "Searching packages with regular expression:" << regexp;
+ }
+ QRegularExpression re(regexp);
+ re.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
const QStringList &keys = installedPackages.keys();
+ QList<LocalPackage> packages;
foreach (const QString &key, keys) {
KDUpdater::LocalPackage package = installedPackages.value(key);
- d->printLocalPackageInformation(package);
+ if (re.match(package.name).hasMatch())
+ packages.append(package);
}
+ LoggingHandler::instance().printLocalPackageInformation(packages);
}
/*!
@@ -2274,13 +2471,15 @@ PackageManagerCore::Status PackageManagerCore::updateComponentsSilently(const QS
if (componentList.count() == 0) {
qCDebug(QInstaller::lcInstallerInstallLog) << "No updates available.";
+ setCanceled();
} else {
// Check if essential components are available (essential components are disabled).
// If essential components are found, update first essential updates,
// restart installer and install rest of the updates.
bool essentialUpdatesFound = false;
foreach (Component *component, componentList) {
- if (component->value(scEssential, scFalse).toLower() == scTrue)
+ if ((component->value(scEssential, scFalse).toLower() == scTrue)
+ || component->isForcedUpdate())
essentialUpdatesFound = true;
}
if (!essentialUpdatesFound) {
@@ -2288,7 +2487,7 @@ PackageManagerCore::Status PackageManagerCore::updateComponentsSilently(const QS
QList<Component*> componentsToBeUpdated;
//Mark components to be updated
foreach (Component *comp, componentList) {
- const QModelIndex &idx = model->indexFromComponentName(comp->name());
+ const QModelIndex &idx = model->indexFromComponentName(comp->treeName());
if (!userSelectedComponents) { // No components given, update all
model->setData(idx, Qt::Checked, Qt::CheckStateRole);
} else {
@@ -2305,10 +2504,10 @@ PackageManagerCore::Status PackageManagerCore::updateComponentsSilently(const QS
if (userSelectedComponents && componentsToBeUpdated.isEmpty()) {
qCDebug(QInstaller::lcInstallerInstallLog)
<< "No updates available for selected components.";
- return PackageManagerCore::Success;
+ return PackageManagerCore::Canceled;
}
foreach (Component *componentToUpdate, componentsToBeUpdated) {
- const QModelIndex &idx = model->indexFromComponentName(componentToUpdate->name());
+ const QModelIndex &idx = model->indexFromComponentName(componentToUpdate->treeName());
model->setData(idx, Qt::Checked, Qt::CheckStateRole);
}
}
@@ -2337,6 +2536,42 @@ void PackageManagerCore::commitSessionOperations()
}
/*!
+ * Clears all previously added licenses.
+ */
+void PackageManagerCore::clearLicenses()
+{
+ d->m_licenseItems.clear();
+}
+
+/*!
+ * Returns licenses hash which can be sorted by priority.
+ */
+QHash<QString, QMap<QString, QString>> PackageManagerCore::sortedLicenses()
+{
+ QHash<QString, QMap<QString, QString>> priorityHash;
+ for (QString licenseName : d->m_licenseItems.keys()) {
+ QMap<QString, QString> licenses;
+ QString priority = d->m_licenseItems.value(licenseName).value(QLatin1String("priority")).toString();
+ licenses = priorityHash.value(priority);
+ licenses.insert(licenseName, d->m_licenseItems.value(licenseName).value(QLatin1String("content")).toString());
+ priorityHash.insert(priority, licenses);
+ }
+ return priorityHash;
+}
+
+/*!
+ * Adds new set of \a licenses. If a license with the key already exists, it is not added again.
+ */
+void PackageManagerCore::addLicenseItem(const QHash<QString, QVariantMap> &licenses)
+{
+ for (QHash<QString, QVariantMap>::const_iterator it = licenses.begin();
+ it != licenses.end(); ++it) {
+ if (!d->m_licenseItems.contains(it.key()))
+ d->m_licenseItems.insert(it.key(), it.value());
+ }
+}
+
+/*!
Uninstalls the selected components \a components without GUI.
Returns PackageManagerCore installation status.
*/
@@ -2347,7 +2582,7 @@ PackageManagerCore::Status PackageManagerCore::uninstallComponentsSilently(const
if (components.isEmpty()) {
qCDebug(QInstaller::lcInstallerInstallLog) << "No components selected for uninstallation.";
- return PackageManagerCore::Success;
+ return PackageManagerCore::Canceled;
}
ComponentModel *model = defaultComponentModel();
@@ -2356,11 +2591,11 @@ PackageManagerCore::Status PackageManagerCore::uninstallComponentsSilently(const
bool uninstallComponentFound = false;
foreach (const QString &componentName, components){
- const QModelIndex &idx = model->indexFromComponentName(componentName);
Component *component = componentByName(componentName);
if (component) {
- if (componentUninstallableFromCommandLine(componentName)) {
+ const QModelIndex &idx = model->indexFromComponentName(component->treeName());
+ if (componentUninstallableFromCommandLine(component->name())) {
model->setData(idx, Qt::Unchecked, Qt::CheckStateRole);
uninstallComponentFound = true;
}
@@ -2398,6 +2633,33 @@ PackageManagerCore::Status PackageManagerCore::removeInstallationSilently()
}
/*!
+ Creates an offline installer from selected \a componentsToAdd without displaying
+ a user interface. Virtual components cannot be selected unless made visible with
+ --show-virtual-components as in installation. AutoDependOn nor non-checkable components
+ cannot be selected directly. Returns \c PackageManagerCore::Status.
+*/
+PackageManagerCore::Status PackageManagerCore::createOfflineInstaller(const QStringList &componentsToAdd)
+{
+ setOfflineGenerator();
+ // init default model before fetching remote packages tree
+ ComponentModel *model = defaultComponentModel();
+ Q_UNUSED(model);
+ if (!fetchRemotePackagesTree())
+ return status();
+
+ QString errorMessage;
+ if (checkComponentsForInstallation(componentsToAdd, errorMessage)) {
+ if (d->calculateComponentsAndRun()) {
+ qCDebug(QInstaller::lcInstallerInstallLog)
+ << "Created installer to:" << offlineBinaryName();
+ }
+ } else {
+ qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << errorMessage;
+ }
+ return status();
+}
+
+/*!
Installs the selected components \a components without displaying a user
interface. Virtual components cannot be installed unless made visible with
--show-virtual-components. AutoDependOn nor non-checkable components cannot
@@ -2418,43 +2680,22 @@ PackageManagerCore::Status PackageManagerCore::installSelectedComponentsSilently
helperStrList.removeDuplicates();
if (helperStrList.count() == installedPackages.count()) {
qCDebug(QInstaller::lcInstallerInstallLog) << "Components already installed.";
- return PackageManagerCore::Success;
+ return PackageManagerCore::Canceled;
}
}
+ // init default model before fetching remote packages tree
ComponentModel *model = defaultComponentModel();
+ Q_UNUSED(model);
if (!fetchRemotePackagesTree())
return status();
- bool installComponentsFound = false;
- foreach (const QString &name, components){
- const QModelIndex &idx = model->indexFromComponentName(name);
- Component *component = componentByName(name);
- if (idx.isValid()) {
- if ((model->data(idx, Qt::CheckStateRole) == QVariant::Invalid) &&
- !component->forcedInstallation()) { // User cannot select the component, check why
- if (component && component->autoDependencies().count() > 0)
- qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << "Cannot install component "<< name
- << ". Component is installed only as automatic dependency to "<< component->autoDependencies().join(QLatin1Char(',')) << ".";
- if (component && !component->isCheckable())
- qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << "Cannot install component "<< name
- <<". Component is not checkable meaning you have to select one of the subcomponents.";
- } else if (component->isInstalled()) {
- qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << "Component " << name <<" already installed";
- } else {
- model->setData(idx, Qt::Checked, Qt::CheckStateRole);
- installComponentsFound = true;
- }
- } else { // idx is invalid and component valid when we have invisible virtual component
- if (component && component->isVirtual())
- qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << "Cannot install " << name <<". Component is virtual.";
- else
- qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << "Cannot install " << name <<". Component not found.";
- }
- }
- if (installComponentsFound) {
+ QString errorMessage;
+ if (checkComponentsForInstallation(components, errorMessage)) {
if (d->calculateComponentsAndRun())
qCDebug(QInstaller::lcInstallerInstallLog) << "Components installed successfully";
+ } else {
+ qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << errorMessage;
}
return status();
}
@@ -2478,6 +2719,7 @@ PackageManagerCore::Status PackageManagerCore::installDefaultComponentsSilently(
}
} else {
qCDebug(QInstaller::lcInstallerInstallLog) << "No components available for default installation.";
+ setCanceled();
}
return status();
}
@@ -2553,6 +2795,9 @@ bool PackageManagerCore::checkAvailableSpace(QString &message) const
// if we create a local repository, take that space into account as well
required += repositorySize;
}
+ // if we create offline installer, take current executable size into account
+ if (isOfflineGenerator())
+ required += QFile(QCoreApplication::applicationFilePath()).size();
qDebug() << "Installation space required:" << humanReadableSize(required) << "Temporary space "
"required:" << humanReadableSize(tempRequired) << "Local repository size:"
@@ -2588,21 +2833,21 @@ bool PackageManagerCore::checkAvailableSpace(QString &message) const
if (tempOnSameVolume && (installVolumeAvailableSize <= (required + tempRequired))) {
message = tr("Not enough disk space to store temporary files and the "
- "installation. %1 are available, while %2 are at least required.").arg(
+ "installation. %1 are available, while the minimum required is %2.").arg(
humanReadableSize(installVolumeAvailableSize), humanReadableSize(required + tempRequired));
return false;
}
if (installVolumeAvailableSize < required) {
message = tr("Not enough disk space to store all selected components! %1 are "
- "available while %2 are at least required.").arg(humanReadableSize(installVolumeAvailableSize),
+ "available, while the minimum required is %2.").arg(humanReadableSize(installVolumeAvailableSize),
humanReadableSize(required));
return false;
}
if (tempVolumeAvailableSize < tempRequired) {
- message = tr("Not enough disk space to store temporary files! %1 are available "
- "while %2 are at least required.").arg(humanReadableSize(tempVolumeAvailableSize),
+ message = tr("Not enough disk space to store temporary files! %1 are available, "
+ "while the minimum required is %2.").arg(humanReadableSize(tempVolumeAvailableSize),
humanReadableSize(tempRequired));
return false;
}
@@ -2616,6 +2861,13 @@ bool PackageManagerCore::checkAvailableSpace(QString &message) const
message = tr("The volume you selected for installation seems to have sufficient "
"space for installation, but there will be less than 100 MB available afterwards.");
}
+#ifdef Q_OS_WIN
+ if (isOfflineGenerator() && (required > UINT_MAX)) {
+ message = tr("The estimated installer size %1 would exceed the supported executable "
+ "size limit of %2. The application may not be able to run.")
+ .arg(humanReadableSize(required), humanReadableSize(UINT_MAX));
+ }
+#endif
}
message = QString::fromLatin1("%1 %2").arg(message, tr("Installation will use %1 of disk space.")
.arg(humanReadableSize(requiredDiskSpace()))).simplified();
@@ -2879,7 +3131,7 @@ bool PackageManagerCore::performOperation(const QString &name, const QStringList
op->setArguments(replaceVariables(arguments));
op->backup();
if (!PackageManagerCorePrivate::performOperationThreaded(op.data())) {
- PackageManagerCorePrivate::performOperationThreaded(op.data(), PackageManagerCorePrivate::Undo);
+ PackageManagerCorePrivate::performOperationThreaded(op.data(), Operation::Undo);
return false;
}
return true;
@@ -2992,6 +3244,27 @@ void PackageManagerCore::setInstallerBaseBinary(const QString &path)
}
/*!
+ Sets the \c installerbase binary located at \a path to use when writing the
+ offline installer. Setting this makes it possible to run the offline generator
+ in cases where we are not running a real installer, i.e. when executing autotests.
+
+ For normal runs, the executable segment of the running installer will be used.
+*/
+void PackageManagerCore::setOfflineBaseBinary(const QString &path)
+{
+ d->m_offlineBaseBinaryUnreplaced = path;
+}
+
+/*!
+ Adds the resource collection in \a rcPath to the list of resource files
+ to be included into the generated offline installer binary.
+*/
+void PackageManagerCore::addResourcesForOfflineGeneration(const QString &rcPath)
+{
+ d->m_offlineGeneratorResourceCollections.append(rcPath);
+}
+
+/*!
Returns the installer value for \a key. If \a key is not known to the system, \a defaultValue is
returned. Additionally, on Windows, \a key can be a registry key.
@@ -3016,6 +3289,17 @@ QStringList PackageManagerCore::values(const QString &key, const QStringList &de
}
/*!
+ Returns the installer key for \a value. If \a value is not known, empty string is
+ returned.
+
+ \sa {installer::key}{installer.key}
+*/
+QString PackageManagerCore::key(const QString &value) const
+{
+ return d->m_data.key(value);
+}
+
+/*!
Sets the installer value for \a key to \a value.
\sa {installer::setValue}{installer.setValue}
@@ -3044,15 +3328,7 @@ bool PackageManagerCore::containsValue(const QString &key) const
*/
bool PackageManagerCore::isVerbose() const
{
- return QInstaller::isVerbose();
-}
-
-/*!
- Returns verbose level.
-*/
-uint PackageManagerCore::verboseLevel() const
-{
- return QInstaller::verboseLevel();
+ return LoggingHandler::instance().isVerbose();
}
/*!
@@ -3061,7 +3337,7 @@ uint PackageManagerCore::verboseLevel() const
*/
void PackageManagerCore::setVerbose(bool on)
{
- QInstaller::setVerbose(on);
+ LoggingHandler::instance().setVerbose(on);
}
PackageManagerCore::Status PackageManagerCore::status() const
@@ -3146,6 +3422,22 @@ QString PackageManagerCore::installerBinaryPath() const
}
/*!
+ Sets the \a name for the generated offline binary.
+*/
+void PackageManagerCore::setOfflineBinaryName(const QString &name)
+{
+ setValue(scOfflineBinaryName, name);
+}
+
+/*!
+ Returns the path set for the generated offline binary.
+*/
+QString PackageManagerCore::offlineBinaryName() const
+{
+ return d->offlineBinaryName();
+}
+
+/*!
\sa {installer::setInstaller}{installer.setInstaller}
\sa isInstaller(), setUpdater(), setPackageManager()
*/
@@ -3240,6 +3532,42 @@ bool PackageManagerCore::isPackageManager() const
}
/*!
+ Sets current installer to be offline generator.
+*/
+void PackageManagerCore::setOfflineGenerator()
+{
+ d->m_magicMarkerSupplement = BinaryContent::OfflineGenerator;
+}
+
+/*!
+ Returns \c true if current installer is executed as offline generator.
+
+ \sa {installer::isOfflineGenerator}{installer.isOfflineGenerator}
+*/
+bool PackageManagerCore::isOfflineGenerator() const
+{
+ return d->isOfflineGenerator();
+}
+
+/*!
+ Sets the current installer as the package viewer.
+*/
+void PackageManagerCore::setPackageViewer()
+{
+ d->m_magicMarkerSupplement = BinaryContent::PackageViewer;
+}
+
+/*!
+ Returns \c true if the current installer is executed as package viewer.
+
+ \sa {installer::isPackageViewer}{installer.isPackageViewer}
+*/
+bool PackageManagerCore::isPackageViewer() const
+{
+ return d->isPackageViewer();
+}
+
+/*!
Sets the installer magic binary marker based on \a magicMarker and
userSetBinaryMarker to \c true.
*/
@@ -3327,6 +3655,16 @@ bool PackageManagerCore::runPackageUpdater()
}
/*!
+ Runs the offline generator. Returns \c true on success, \c false otherwise.
+
+ \sa {installer::runOfflineGenerator}{installer.runOfflineGenerator}
+*/
+bool PackageManagerCore::runOfflineGenerator()
+{
+ return d->runOfflineGenerator();
+}
+
+/*!
\sa {installer::languageChanged}{installer.languageChanged}
*/
void PackageManagerCore::languageChanged()
@@ -3336,17 +3674,20 @@ void PackageManagerCore::languageChanged()
}
/*!
- Runs the installer, uninstaller, updater, or package manager, depending on
+ Runs the installer, uninstaller, updater, package manager, or offline generator depending on
the type of this binary. Returns \c true on success, otherwise \c false.
*/
bool PackageManagerCore::run()
{
- if (isInstaller())
+ if (isOfflineGenerator())
+ return d->runOfflineGenerator();
+ else if (isInstaller())
return d->runInstaller();
else if (isUninstaller())
return d->runUninstaller();
else if (isMaintainer())
return d->runPackageUpdater();
+
return false;
}
@@ -3361,28 +3702,47 @@ QString PackageManagerCore::maintenanceToolName() const
bool PackageManagerCore::updateComponentData(struct Data &data, Component *component)
{
try {
- // check if we already added the component to the available components list
- const QString name = data.package->data(scName).toString();
+ // Check if we already added the component to the available components list.
+ // Component treenames and names must be unique.
+ const QString packageName = data.package->data(scName).toString();
+ const QString packageTreeName = data.package->data(scTreeName).value<QPair<QString, bool>>().first;
+
+ QString name = packageTreeName.isEmpty() ? packageName : packageTreeName;
if (data.components->contains(name)) {
- qCritical("Cannot register component! Component with identifier %s already registered.",
- qPrintable(name));
- return false;
- }
+ qCritical() << "Cannot register component" << packageName << "with name" << name
+ << "! Component with identifier" << name << "already exists.";
+ // Conflicting original name, skip.
+ if (packageTreeName.isEmpty())
+ return false;
+
+ // Conflicting tree name, check if we can add with original name.
+ if (!settings().allowUnstableComponents() || data.components->contains(packageName))
+ return false;
+
+ qCDebug(lcInstallerInstallLog)
+ << "Registering component with the original indetifier:" << packageName;
+ component->removeValue(scTreeName);
+ const QString errorString = QLatin1String("Tree name conflicts with an existing indentifier");
+ d->m_pendingUnstableComponents.insert(component->name(),
+ QPair<Component::UnstableError, QString>(Component::InvalidTreeName, errorString));
+ }
+ name = packageName;
if (settings().allowUnstableComponents()) {
// Check if there are sha checksum mismatch. Component will still show in install tree
// but is unselectable.
foreach (const QString packageName, d->m_metadataJob.shaMismatchPackages()) {
if (packageName == component->name()) {
- QString errorString = QLatin1String("SHA mismatch detected for component ") + packageName;
- component->setUnstable(Component::UnstableError::ShaMismatch, errorString);
+ const QString errorString = QLatin1String("SHA mismatch detected for component ") + packageName;
+ d->m_pendingUnstableComponents.insert(component->name(),
+ QPair<Component::UnstableError, QString>(Component::ShaMismatch, errorString));
}
}
}
component->setUninstalled();
const QString localPath = component->localTempPath();
- if (verboseLevel() > 1) {
+ if (LoggingHandler::instance().verboseLevel() == LoggingHandler::Detailed) {
static QString lastLocalPath;
if (lastLocalPath != localPath)
qCDebug(QInstaller::lcDeveloperBuild()) << "Url is:" << localPath;
@@ -3399,7 +3759,7 @@ bool PackageManagerCore::updateComponentData(struct Data &data, Component *compo
// add downloadable archive from xml
const QStringList downloadableArchives = data.package->data(scDownloadableArchives).toString()
- .split(QInstaller::commaRegExp(), QString::SkipEmptyParts);
+ .split(QInstaller::commaRegExp(), Qt::SkipEmptyParts);
if (component->isFromOnlineRepository()) {
foreach (const QString downloadableArchive, downloadableArchives)
@@ -3407,7 +3767,7 @@ bool PackageManagerCore::updateComponentData(struct Data &data, Component *compo
}
const QStringList componentsToReplace = data.package->data(scReplaces).toString()
- .split(QInstaller::commaRegExp(), QString::SkipEmptyParts);
+ .split(QInstaller::commaRegExp(), Qt::SkipEmptyParts);
if (!componentsToReplace.isEmpty()) {
// Store the component (this is a component that replaces others) and all components that
@@ -3433,8 +3793,8 @@ bool PackageManagerCore::updateComponentData(struct Data &data, Component *compo
foreach (const QString &componentName, componentsToReplace) {
if (data.installedPackages->contains(componentName)) {
// We found a replacement that is installed.
- if (isPackageManager()) {
- // Mark the replacement component as installed as well. Only do this in package manager
+ if (isUpdater()) {
+ // Mark the replacement component as installed as well. Only do this in updater
// mode, otherwise it would not show up in the updaters component list.
component->setInstalled();
component->setValue(scInstalledVersion, data.installedPackages->value(componentName).version);
@@ -3449,13 +3809,21 @@ bool PackageManagerCore::updateComponentData(struct Data &data, Component *compo
return true;
}
-void PackageManagerCore::storeReplacedComponents(QHash<QString, Component *> &components, const struct Data &data)
+void PackageManagerCore::storeReplacedComponents(QHash<QString, Component *> &components,
+ const struct Data &data, QMap<QString, QString> *const treeNameComponents)
{
QHash<Component*, QStringList>::const_iterator it = data.replacementToExchangeables.constBegin();
// remember all components that got a replacement, required for uninstall
for (; it != data.replacementToExchangeables.constEnd(); ++it) {
foreach (const QString &componentName, it.value()) {
- Component *componentToReplace = components.take(componentName);
+ QString key = componentName;
+ if (treeNameComponents && treeNameComponents->contains(componentName)) {
+ // The exchangeable component is stored with a tree name key,
+ // remove from the list of components with tree name.
+ key = treeNameComponents->value(componentName);
+ treeNameComponents->remove(componentName);
+ }
+ Component *componentToReplace = components.take(key);
if (!componentToReplace) {
// If a component replaces another component which is not existing in the
// installer binary or the installed component list, just ignore it. This
@@ -3464,14 +3832,7 @@ void PackageManagerCore::storeReplacedComponents(QHash<QString, Component *> &co
qCWarning(QInstaller::lcDeveloperBuild) << componentName << "- Does not exist in the repositories anymore.";
continue;
}
- if (!componentToReplace && !d->componentsToReplace().contains(componentName)) {
- componentToReplace = new Component(this);
- componentToReplace->setValue(scName, componentName);
- } else {
- // This case can happen when in installer mode as well, a component
- // is in the installer binary and its replacement component as well.
- d->replacementDependencyComponents().append(componentToReplace);
- }
+ d->replacementDependencyComponents().append(componentToReplace);
d->componentsToReplace().insert(componentName, qMakePair(it.key(), componentToReplace));
}
}
@@ -3482,42 +3843,129 @@ bool PackageManagerCore::fetchAllPackages(const PackagesList &remotes, const Loc
emit startAllComponentsReset();
d->clearAllComponentLists();
- QHash<QString, QInstaller::Component*> components;
+ QHash<QString, QInstaller::Component*> allComponents;
Data data;
- data.components = &components;
+ data.components = &allComponents;
data.installedPackages = &locals;
- foreach (Package *const package, remotes) {
- if (d->statusCanceledOrFailed())
- return false;
+ QMap<QString, QString> remoteTreeNameComponents;
+ QMap<QString, QString> allTreeNameComponents;
- if (!ProductKeyCheck::instance()->isValidPackage(package->data(scName).toString()))
- continue;
+ std::function<bool(PackagesList *, bool)> loadRemotePackages;
+ loadRemotePackages = [&](PackagesList *treeNamePackages, bool firstRun) -> bool {
+ foreach (Package *const package, (firstRun ? remotes : *treeNamePackages)) {
+ if (d->statusCanceledOrFailed())
+ return false;
- QScopedPointer<QInstaller::Component> component(new QInstaller::Component(this));
- data.package = package;
- component->loadDataFromPackage(*package);
- if (updateComponentData(data, component.data())) {
- const QString name = component->name();
- components.insert(name, component.take());
+ if (!ProductKeyCheck::instance()->isValidPackage(package->data(scName).toString()))
+ continue;
+
+ if (firstRun && !package->data(scTreeName)
+ .value<QPair<QString, bool>>().first.isEmpty()) {
+ // Package has a tree name, leave for later
+ treeNamePackages->append(package);
+ continue;
+ }
+
+ QScopedPointer<QInstaller::Component> remoteComponent(new QInstaller::Component(this));
+ data.package = package;
+ remoteComponent->loadDataFromPackage(*package);
+ if (updateComponentData(data, remoteComponent.data())) {
+ // Create a list where is name and treename. Repo can contain a package with
+ // a different treename of component which is already installed. We don't want
+ // to move already installed local packages.
+ const QString treeName = remoteComponent->value(scTreeName);
+ if (!treeName.isEmpty())
+ remoteTreeNameComponents.insert(remoteComponent->name(), treeName);
+ const QString name = remoteComponent->treeName();
+ allComponents.insert(name, remoteComponent.take());
+ }
}
+ // Second pass with leftover packages
+ return firstRun ? loadRemotePackages(treeNamePackages, false) : true;
+ };
+
+ {
+ // Loading remote package data is performed in two steps: first, components without
+ // - and then components with tree names. This is to ensure components with tree
+ // names do not replace other components when registering fails to a name conflict.
+ PackagesList treeNamePackagesTmp;
+ if (!loadRemotePackages(&treeNamePackagesTmp, true))
+ return false;
}
+ allTreeNameComponents = remoteTreeNameComponents;
+
+ foreach (auto &package, locals) {
+ QScopedPointer<QInstaller::Component> localComponent(new QInstaller::Component(this));
+ localComponent->loadDataFromPackage(package);
+ const QString name = localComponent->treeName();
+
+ // 1. Component has a treename in local but not in remote, add with local treename
+ if (!remoteTreeNameComponents.contains(localComponent->name()) && !localComponent->value(scTreeName).isEmpty()) {
+ delete allComponents.take(localComponent->name());
+ // 2. Component has different treename in local and remote, add with local treename
+ } else if (remoteTreeNameComponents.contains(localComponent->name())) {
+ const QString remoteTreeName = remoteTreeNameComponents.value(localComponent->name());
+ const QString localTreeName = localComponent->value(scTreeName);
+ if (remoteTreeName != localTreeName) {
+ delete allComponents.take(remoteTreeNameComponents.value(localComponent->name()));
+ } else {
+ // 3. Component has same treename in local and remote, don't add the component again.
+ continue;
+ }
+ // 4. Component does not have treename in local or remote, don't add the component again.
+ } else if (allComponents.contains(localComponent->name())) {
+ Component *const component = allComponents.value(localComponent->name());
+ if (component->value(scTreeName).isEmpty() && localComponent->value(scTreeName).isEmpty())
+ continue;
+ }
+ // 5. Remote has treename for a different component that is already reserved
+ // by this local component, Or, remote adds component without treename
+ // but it conflicts with a local treename.
+ if (allComponents.contains(name)) {
+ const QString key = remoteTreeNameComponents.key(name);
+ qCritical() << "Cannot register component" << (key.isEmpty() ? name : key)
+ << "with name" << name << "! Component with identifier" << name
+ << "already exists.";
+
+ if (!key.isEmpty())
+ allTreeNameComponents.remove(key);
+
+ // Try to re-add the remote component as unstable
+ if (!key.isEmpty() && !allComponents.contains(key) && settings().allowUnstableComponents()) {
+ qCDebug(lcInstallerInstallLog)
+ << "Registering component with the original indetifier:" << key;
+
+ Component *component = allComponents.take(name);
+ component->removeValue(scTreeName);
+ const QString errorString = QLatin1String("Tree name conflicts with an existing indentifier");
+ d->m_pendingUnstableComponents.insert(component->name(),
+ QPair<Component::UnstableError, QString>(Component::InvalidTreeName, errorString));
+
+ allComponents.insert(key, component);
+ } else {
+ delete allComponents.take(name);
+ }
+ }
- foreach (const QString &key, locals.keys()) {
- QScopedPointer<QInstaller::Component> component(new QInstaller::Component(this));
- component->loadDataFromPackage(locals.value(key));
- const QString &name = component->name();
- if (!components.contains(name))
- components.insert(name, component.take());
+ const QString treeName = localComponent->value(scTreeName);
+ if (!treeName.isEmpty())
+ allTreeNameComponents.insert(localComponent->name(), treeName);
+ allComponents.insert(name, localComponent.take());
}
// store all components that got a replacement
- storeReplacedComponents(components, data);
+ storeReplacedComponents(allComponents, data, &allTreeNameComponents);
+
+ // Move children of treename components
+ createAutoTreeNames(allComponents, allTreeNameComponents);
- if (!d->buildComponentTree(components, true))
+ if (!d->buildComponentTree(allComponents, true))
return false;
+ d->commitPendingUnstableComponents();
+
emit finishAllComponentsReset(d->m_rootComponents);
return true;
}
@@ -3560,9 +4008,9 @@ bool PackageManagerCore::fetchUpdaterPackages(const PackagesList &remotes, const
installedPackages.take(name); // remove from local installed packages
bool isValidUpdate = locals.contains(name);
- if (!isValidUpdate && !replaces.isEmpty()) {
+ if (!replaces.isEmpty()) {
const QStringList possibleNames = replaces.split(QInstaller::commaRegExp(),
- QString::SkipEmptyParts);
+ Qt::SkipEmptyParts);
foreach (const QString &possibleName, possibleNames) {
if (locals.contains(possibleName)) {
isValidUpdate = true;
@@ -3577,10 +4025,8 @@ bool PackageManagerCore::fetchUpdaterPackages(const PackagesList &remotes, const
continue; // Update for not installed package found, skip it.
const LocalPackage &localPackage = locals.value(name);
- const QString updateVersion = update->data(scVersion).toString();
- if (KDUpdater::compareVersion(updateVersion, localPackage.version) <= 0)
+ if (!d->packageNeedsUpdate(localPackage, update))
continue;
-
// It is quite possible that we may have already installed the update. Lets check the last
// update date of the package and the release date of the update. This way we can compare and
// figure out if the update has been installed or not.
@@ -3588,8 +4034,10 @@ bool PackageManagerCore::fetchUpdaterPackages(const PackagesList &remotes, const
if (localPackage.lastUpdateDate > updateDate)
continue;
- if (update->data(scEssential, scFalse).toString().toLower() == scTrue)
+ if (update->data(scEssential, scFalse).toString().toLower() == scTrue ||
+ update->data(scForcedUpdate, scFalse).toString().toLower() == scTrue) {
setFoundEssentialUpdate(true);
+ }
// this is not a dependency, it is a real update
components.insert(name, d->m_updaterComponentsDeps.takeLast());
@@ -3601,9 +4049,18 @@ bool PackageManagerCore::fetchUpdaterPackages(const PackagesList &remotes, const
QInstaller::Component *component = new QInstaller::Component(this);
component->loadDataFromPackage(installedPackages.value(key));
d->m_updaterComponentsDeps.append(component);
+ }
+
+ foreach (const QString &key, locals.keys()) {
// Keep a list of local components that should be replaced
- if (replaceMes.contains(component->name()))
+ // Remove from components list - we don't want to update the component
+ // as it is replaced by other component
+ if (replaceMes.contains(key)) {
+ QInstaller::Component *component = new QInstaller::Component(this);
+ component->loadDataFromPackage(locals.value(key));
localReplaceMes.insert(component->name(), component);
+ delete components.take(component->name());
+ }
}
// store all components that got a replacement, but do not modify the components list
@@ -3647,13 +4104,14 @@ bool PackageManagerCore::fetchUpdaterPackages(const PackagesList &remotes, const
component->setCheckable(false);
component->setSelectable(false);
- if (component->value(scEssential, scFalse).toLower() == scFalse) {
+ if ((component->value(scEssential, scFalse).toLower() == scTrue)
+ || (component->value(scForcedUpdate, scFalse).toLower() == scTrue)) {
+ // essential updates are enabled, still not checkable but checked
+ component->setEnabled(true);
+ } else {
// non essential updates are disabled, not checkable and unchecked
component->setEnabled(false);
component->setCheckState(Qt::Unchecked);
- } else {
- // essential updates are enabled, still not checkable but checked
- component->setEnabled(true);
}
}
}
@@ -3666,7 +4124,6 @@ bool PackageManagerCore::fetchUpdaterPackages(const PackagesList &remotes, const
}
} catch (const Error &error) {
d->clearUpdaterComponentLists();
- emit finishUpdaterComponentsReset(QList<QInstaller::Component*>());
d->setStatus(Failure, error.message());
// TODO: make sure we remove all message boxes inside the library at some point.
@@ -3679,6 +4136,75 @@ bool PackageManagerCore::fetchUpdaterPackages(const PackagesList &remotes, const
return true;
}
+/*!
+ \internal
+
+ Creates automatic tree names for \a components that have a parent declaring
+ an explicit tree name. The child components keep the relative location
+ to their parent component.
+
+ The \a treeNameComponents is a map of original component names and new tree names.
+*/
+void PackageManagerCore::createAutoTreeNames(QHash<QString, Component *> &components
+ , const QMap<QString, QString> &treeNameComponents)
+{
+ if (treeNameComponents.isEmpty())
+ return;
+
+ QHash<QString, Component *> componentsTemp = components;
+ for (auto *component : qAsConst(components)) {
+ if (component->treeName() != component->name()) // already handled
+ continue;
+
+ QString newName;
+ // Check treename candidates, keep the name closest to a leaf component
+ for (auto &name : treeNameComponents.keys()) {
+ if (!component->name().startsWith(name))
+ continue;
+
+ const Component *parent = components.value(treeNameComponents.value(name));
+ if (!(parent && parent->treeNameMoveChildren()))
+ continue; // TreeName only applied to parent
+
+ if (newName.split(QLatin1Char('.'), QString::SkipEmptyParts).count()
+ > name.split(QLatin1Char('.'), QString::SkipEmptyParts).count()) {
+ continue;
+ }
+ newName = name;
+ }
+ if (newName.isEmpty()) // Nothing to do
+ continue;
+
+ const QString treeName = component->name()
+ .replace(newName, treeNameComponents.value(newName));
+
+ if (components.contains(treeName) || treeNameComponents.contains(treeName)) {
+ // Can happen if the parent was moved to an existing identifier (which did not
+ // have a component) and contains child that has a conflicting name with a component
+ // in the existing branch.
+ qCritical() << "Cannot register component" << component->name() << "with automatic "
+ "tree name" << treeName << "! Component with identifier" << treeName << "already exists.";
+
+ if (settings().allowUnstableComponents()) {
+ qCDebug(lcInstallerInstallLog)
+ << "Falling back to using the original indetifier:" << component->name();
+
+ const QString errorString = QLatin1String("Tree name conflicts with an existing indentifier");
+ d->m_pendingUnstableComponents.insert(component->name(),
+ QPair<Component::UnstableError, QString>(Component::InvalidTreeName, errorString));
+ } else {
+ componentsTemp.remove(componentsTemp.key(component));
+ }
+ continue;
+ }
+ component->setValue(scAutoTreeName, treeName);
+
+ componentsTemp.remove(componentsTemp.key(component));
+ componentsTemp.insert(treeName, component);
+ }
+ components = componentsTemp;
+}
+
void PackageManagerCore::restoreCheckState()
{
d->restoreCheckState();
@@ -3702,7 +4228,7 @@ void PackageManagerCore::updateDisplayVersions(const QString &displayKey)
const QString displayVersionRemote = findDisplayVersion(key, componentsHash,
scVersion, visited);
if (displayVersionRemote.isEmpty())
- componentsHash.value(key)->setValue(displayKey, tr("invalid"));
+ componentsHash.value(key)->setValue(displayKey, tr("Invalid"));
else
componentsHash.value(key)->setValue(displayKey, displayVersionRemote);
}
diff --git a/src/libs/installer/packagemanagercore.h b/src/libs/installer/packagemanagercore.h
index b0feb1a57..cf1916c48 100644
--- a/src/libs/installer/packagemanagercore.h
+++ b/src/libs/installer/packagemanagercore.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -32,6 +32,8 @@
#include "protocol.h"
#include "repository.h"
#include "qinstallerglobal.h"
+#include "utils.h"
+#include "commandlineparser.h"
#include <QtCore/QHash>
#include <QtCore/QObject>
@@ -159,12 +161,16 @@ public:
Q_INVOKABLE static QString findPath(const QString &name, const QStringList &paths = QStringList());
Q_INVOKABLE void setInstallerBaseBinary(const QString &path);
+ void setOfflineBaseBinary(const QString &path);
+
+ void addResourcesForOfflineGeneration(const QString &rcPath);
// parameter handling
Q_INVOKABLE bool containsValue(const QString &key) const;
Q_INVOKABLE void setValue(const QString &key, const QString &value);
Q_INVOKABLE QString value(const QString &key, const QString &defaultValue = QString()) const;
Q_INVOKABLE QStringList values(const QString &key, const QStringList &defaultValue = QStringList()) const;
+ Q_INVOKABLE QString key(const QString &value) const;
QString replaceVariables(const QString &str) const;
QByteArray replaceVariables(const QByteArray &str) const;
@@ -178,6 +184,9 @@ public:
QString maintenanceToolName() const;
QString installerBinaryPath() const;
+ void setOfflineBinaryName(const QString &name);
+ QString offlineBinaryName() const;
+
bool testChecksum() const;
void setTestChecksum(bool test);
@@ -204,7 +213,10 @@ public:
Q_INVOKABLE QString readFile(const QString &filePath, const QString &codecName) const;
Q_INVOKABLE QString readConsoleLine(const QString &title = QString(), qint64 maxlen = 0) const;
- bool checkTargetDir(const QString &targetDirectory);
+ Q_INVOKABLE QString toNativeSeparators(const QString &path);
+ Q_INVOKABLE QString fromNativeSeparators(const QString &path);
+
+ bool installationAllowedToDirectory(const QString &targetDirectory);
QString targetDirWarning(const QString &targetDirectory) const;
public:
@@ -216,7 +228,7 @@ public:
void appendRootComponent(Component *components);
void appendUpdaterComponent(Component *components);
- QList<Component *> components(ComponentTypes mask) const;
+ QList<Component *> components(ComponentTypes mask, const QString &regexp = QString()) const;
Component *componentByName(const QString &identifier) const;
Q_INVOKABLE bool calculateComponentsToInstall() const;
@@ -233,13 +245,15 @@ public:
ComponentModel *defaultComponentModel() const;
ComponentModel *updaterComponentModel() const;
- void listInstalledPackages();
- void listAvailablePackages(const QString &regexp);
+ void listInstalledPackages(const QString &regexp = QString());
+ void listAvailablePackages(const QString &regexp = QString(),
+ const QHash<QString, QString> &filters = QHash<QString, QString>());
PackageManagerCore::Status updateComponentsSilently(const QStringList &componentsToUpdate);
PackageManagerCore::Status installSelectedComponentsSilently(const QStringList& components);
PackageManagerCore::Status installDefaultComponentsSilently();
PackageManagerCore::Status uninstallComponentsSilently(const QStringList& components);
PackageManagerCore::Status removeInstallationSilently();
+ PackageManagerCore::Status createOfflineInstaller(const QStringList &componentsToAdd);
// convenience
Q_INVOKABLE void setInstaller();
@@ -255,6 +269,12 @@ public:
Q_INVOKABLE void setPackageManager();
Q_INVOKABLE bool isPackageManager() const;
+ void setOfflineGenerator();
+ Q_INVOKABLE bool isOfflineGenerator() const;
+
+ void setPackageViewer();
+ Q_INVOKABLE bool isPackageViewer() const;
+
void setUserSetBinaryMarker(qint64 magicMarker);
Q_INVOKABLE bool isUserSetBinaryMarker() const;
@@ -267,8 +287,6 @@ public:
bool isVerbose() const;
void setVerbose(bool on);
- uint verboseLevel() const;
-
Q_INVOKABLE bool gainAdminRights();
Q_INVOKABLE void dropAdminRights();
@@ -314,11 +332,15 @@ public:
static void parseNameAndVersion(const QString &requirement, QString *name, QString *version);
static QStringList parseNames(const QStringList &requirements);
void commitSessionOperations();
+ void clearLicenses();
+ QHash<QString, QMap<QString, QString>> sortedLicenses();
+ void addLicenseItem(const QHash<QString, QVariantMap> &licenses);
public Q_SLOTS:
bool runInstaller();
bool runUninstaller();
bool runPackageUpdater();
+ bool runOfflineGenerator();
void interrupt();
void setCanceled();
void languageChanged();
@@ -337,6 +359,7 @@ Q_SIGNALS:
void updaterComponentsAdded(QList<QInstaller::Component*> components);
void valueChanged(const QString &key, const QString &value);
void statusChanged(QInstaller::PackageManagerCore::Status);
+ void defaultTranslationsLoadedForLanguage(QLocale::Language lang);
void currentPageChanged(int page);
void finishButtonClicked();
@@ -356,6 +379,8 @@ Q_SIGNALS:
void updateFinished();
void uninstallationStarted();
void uninstallationFinished();
+ void offlineGenerationStarted();
+ void offlineGenerationFinished();
void titleMessageChanged(const QString &title);
void wizardPageInsertionRequested(QWidget *widget, QInstaller::PackageManagerCore::WizardPage page);
@@ -373,6 +398,7 @@ Q_SIGNALS:
void guiObjectChanged(QObject *gui);
void unstableComponentFound(const QString &type, const QString &errorMessage, const QString &component);
void installerBinaryMarkerChanged(qint64 magicMarker);
+ void componentsRecalculated();
private:
struct Data {
@@ -383,10 +409,14 @@ private:
};
bool updateComponentData(struct Data &data, QInstaller::Component *component);
- void storeReplacedComponents(QHash<QString, Component*> &components, const struct Data &data);
+ void storeReplacedComponents(QHash<QString, Component*> &components, const struct Data &data,
+ QMap<QString, QString> *const treeNameComponents = nullptr);
bool fetchAllPackages(const PackagesList &remotePackages, const LocalPackagesHash &localPackages);
bool fetchUpdaterPackages(const PackagesList &remotePackages, const LocalPackagesHash &localPackages);
+ void createAutoTreeNames(QHash<QString, Component *> &components,
+ const QMap<QString, QString> &treeNameComponents);
+
void updateDisplayVersions(const QString &displayKey);
QString findDisplayVersion(const QString &componentName, const QHash<QString, QInstaller::Component*> &components,
const QString& versionKey, QHash<QString, bool> &visited);
@@ -395,6 +425,7 @@ private:
bool fetchPackagesTree(const PackagesList &packages, const LocalPackagesHash installedPackages);
bool componentUninstallableFromCommandLine(const QString &componentName);
+ bool checkComponentsForInstallation(const QStringList &components, QString &errorMessage);
private:
PackageManagerCorePrivate *const d;
@@ -411,5 +442,6 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(PackageManagerCore::ComponentTypes)
}
Q_DECLARE_METATYPE(QInstaller::PackageManagerCore*)
+Q_DECLARE_METATYPE(QInstaller::PackageManagerCore::Status)
#endif // PACKAGEMANAGERCORE_H
diff --git a/src/libs/installer/packagemanagercore_p.cpp b/src/libs/installer/packagemanagercore_p.cpp
index edbf6459d..38909b4ea 100644
--- a/src/libs/installer/packagemanagercore_p.cpp
+++ b/src/libs/installer/packagemanagercore_p.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -31,7 +31,6 @@
#include "binarycontent.h"
#include "binaryformatenginehandler.h"
#include "binarylayout.h"
-#include "component.h"
#include "scriptengine.h"
#include "componentmodel.h"
#include "errors.h"
@@ -48,6 +47,8 @@
#include "uninstallercalculator.h"
#include "componentchecker.h"
#include "globals.h"
+#include "binarycreator.h"
+#include "loggingutils.h"
#include "selfrestarter.h"
#include "filedownloaderfactory.h"
@@ -100,8 +101,11 @@ public:
return;
qCDebug(QInstaller::lcInstallerInstallLog).noquote() << QString::fromLatin1("%1 %2 operation: %3")
.arg(state, m_operation->value(QLatin1String("component")).toString(), m_operation->name());
+ QStringList args = m_operation->arguments();
+ if (m_operation->requiresUnreplacedVariables())
+ args = m_operation->packageManager()->replaceVariables(m_operation->arguments());
qCDebug(QInstaller::lcInstallerInstallLog).noquote() << QString::fromLatin1("\t- arguments: %1")
- .arg(m_operation->arguments().join(QLatin1String(", ")));
+ .arg(args.join(QLatin1String(", ")));
}
~OperationTracer() {
if (!m_operation)
@@ -112,18 +116,18 @@ private:
Operation *m_operation;
};
-static bool runOperation(Operation *operation, PackageManagerCorePrivate::OperationType type)
+static bool runOperation(Operation *operation, Operation::OperationType type)
{
OperationTracer tracer(operation);
switch (type) {
- case PackageManagerCorePrivate::Backup:
+ case Operation::Backup:
tracer.trace(QLatin1String("backup"));
operation->backup();
return true;
- case PackageManagerCorePrivate::Perform:
+ case Operation::Perform:
tracer.trace(QLatin1String("perform"));
return operation->performOperation();
- case PackageManagerCorePrivate::Undo:
+ case Operation::Undo:
tracer.trace(QLatin1String("undo"));
return operation->undoOperation();
default:
@@ -206,7 +210,6 @@ static void deferredRename(const QString &oldName, const QString &newName, bool
PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core)
: m_updateFinder(nullptr)
- , m_compressedFinder(nullptr)
, m_localPackageHub(std::make_shared<LocalPackageHub>())
, m_status(PackageManagerCore::Unfinished)
, m_needsHardRestart(false)
@@ -220,6 +223,7 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core)
, m_repoFetched(false)
, m_updateSourcesAdded(false)
, m_magicBinaryMarker(0) // initialize with pseudo marker
+ , m_magicMarkerSupplement(BinaryContent::Default)
, m_componentsToInstallCalculated(false)
, m_componentScriptEngine(nullptr)
, m_controlScriptEngine(nullptr)
@@ -244,7 +248,6 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core)
PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core, qint64 magicInstallerMaker,
const QList<OperationBlob> &performedOperations)
: m_updateFinder(nullptr)
- , m_compressedFinder(nullptr)
, m_localPackageHub(std::make_shared<LocalPackageHub>())
, m_status(PackageManagerCore::Unfinished)
, m_needsHardRestart(false)
@@ -258,6 +261,7 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core, q
, m_repoFetched(false)
, m_updateSourcesAdded(false)
, m_magicBinaryMarker(magicInstallerMaker)
+ , m_magicMarkerSupplement(BinaryContent::Default)
, m_componentsToInstallCalculated(false)
, m_componentScriptEngine(nullptr)
, m_controlScriptEngine(nullptr)
@@ -302,6 +306,10 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core, q
m_core, &PackageManagerCore::uninstallationStarted);
connect(this, &PackageManagerCorePrivate::uninstallationFinished,
m_core, &PackageManagerCore::uninstallationFinished);
+ connect(this, &PackageManagerCorePrivate::offlineGenerationStarted,
+ m_core, &PackageManagerCore::offlineGenerationStarted);
+ connect(this, &PackageManagerCorePrivate::offlineGenerationFinished,
+ m_core, &PackageManagerCore::offlineGenerationFinished);
}
PackageManagerCorePrivate::~PackageManagerCorePrivate()
@@ -357,7 +365,7 @@ bool PackageManagerCorePrivate::isProcessRunning(const QString &name,
}
/* static */
-bool PackageManagerCorePrivate::performOperationThreaded(Operation *operation, OperationType type)
+bool PackageManagerCorePrivate::performOperationThreaded(Operation *operation, Operation::OperationType type)
{
QFutureWatcher<bool> futureWatcher;
const QFuture<bool> future = QtConcurrent::run(runOperation, operation, type);
@@ -448,6 +456,7 @@ bool PackageManagerCorePrivate::buildComponentTree(QHash<QString, Component*> &c
clearInstallerCalculator();
if (installerCalculator()->appendComponentsToInstall(components.values()) == false) {
+ setStatus(PackageManagerCore::Failure, installerCalculator()->componentsToInstallError());
MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), QLatin1String("Error"),
tr("Unresolved dependencies"), installerCalculator()->componentsToInstallError());
return false;
@@ -455,7 +464,7 @@ bool PackageManagerCorePrivate::buildComponentTree(QHash<QString, Component*> &c
restoreCheckState();
- if (m_core->verboseLevel() > 1) {
+ if (LoggingHandler::instance().verboseLevel() == LoggingHandler::Detailed) {
foreach (QInstaller::Component *component, components) {
const QStringList warnings = ComponentChecker::checkComponent(component);
foreach (const QString &warning, warnings)
@@ -465,7 +474,6 @@ bool PackageManagerCorePrivate::buildComponentTree(QHash<QString, Component*> &c
} catch (const Error &error) {
clearAllComponentLists();
- emit m_core->finishAllComponentsReset(QList<QInstaller::Component*>());
setStatus(PackageManagerCore::Failure, error.message());
// TODO: make sure we remove all message boxes inside the library at some point.
@@ -599,7 +607,7 @@ UninstallerCalculator *PackageManagerCorePrivate::uninstallerCalculator() const
void PackageManagerCorePrivate::initialize(const QHash<QString, QString> &params)
{
m_coreCheckedHash.clear();
- m_data = PackageManagerCoreData(params);
+ m_data = PackageManagerCoreData(params, isInstaller());
m_componentsToInstallCalculated = false;
#ifdef Q_OS_LINUX
@@ -613,6 +621,9 @@ void PackageManagerCorePrivate::initialize(const QHash<QString, QString> &params
#else
readMaintenanceConfigFiles(QCoreApplication::applicationDirPath());
#endif
+ // Maintenancetool might have overwritten the variables
+ // user has given from command line. Reset those variables.
+ m_data.setUserDefinedVariables(params);
}
processFilesForDelayedDeletion();
@@ -638,6 +649,10 @@ void PackageManagerCorePrivate::initialize(const QHash<QString, QString> &params
ProgressCoordinator::instance(), &ProgressCoordinator::reset);
connect(this, &PackageManagerCorePrivate::uninstallationStarted,
ProgressCoordinator::instance(), &ProgressCoordinator::reset);
+ disconnect(this, &PackageManagerCorePrivate::offlineGenerationStarted,
+ ProgressCoordinator::instance(), &ProgressCoordinator::reset);
+ connect(this, &PackageManagerCorePrivate::offlineGenerationStarted,
+ ProgressCoordinator::instance(), &ProgressCoordinator::reset);
if (!isInstaller())
m_localPackageHub->setFileName(componentsXmlPath());
@@ -696,6 +711,16 @@ bool PackageManagerCorePrivate::isPackageManager() const
return m_magicBinaryMarker == BinaryContent::MagicPackageManagerMarker;
}
+bool PackageManagerCorePrivate::isOfflineGenerator() const
+{
+ return m_magicMarkerSupplement == BinaryContent::OfflineGenerator;
+}
+
+bool PackageManagerCorePrivate::isPackageViewer() const
+{
+ return m_magicMarkerSupplement == BinaryContent::PackageViewer;
+}
+
bool PackageManagerCorePrivate::statusCanceledOrFailed() const
{
return m_status == PackageManagerCore::Canceled || m_status == PackageManagerCore::Failure;
@@ -758,6 +783,18 @@ QString PackageManagerCorePrivate::maintenanceToolName() const
return QString::fromLatin1("%1/%2").arg(targetDir()).arg(filename);
}
+QString PackageManagerCorePrivate::offlineBinaryName() const
+{
+ QString filename = m_core->value(scOfflineBinaryName, qApp->applicationName()
+ + QLatin1String("_offline-") + QDate::currentDate().toString(Qt::ISODate));
+#if defined(Q_OS_WIN)
+ const QString suffix = QLatin1String(".exe");
+ if (!filename.endsWith(suffix))
+ filename += suffix;
+#endif
+ return QString::fromLatin1("%1/%2").arg(targetDir()).arg(filename);
+}
+
static QNetworkProxy readProxy(QXmlStreamReader &reader)
{
QNetworkProxy proxy(QNetworkProxy::HttpProxy);
@@ -884,8 +921,13 @@ void PackageManagerCorePrivate::readMaintenanceConfigFiles(const QString &target
const QVariantHash v = cfg.value(QLatin1String("Variables")).toHash(); // Do not change to
// QVariantMap! Breaks reading from existing .ini files, cause the variant types do not match.
for (QVariantHash::const_iterator it = v.constBegin(); it != v.constEnd(); ++it) {
- m_data.setValue(it.key(), replacePath(it.value().toString(), QLatin1String(scRelocatable),
- targetDir));
+ if (m_data.contains(it.key()) && !m_data.value(it.key()).isNull()) {
+ // Exception: StartMenuDir should be permanent after initial installation
+ // and must be read from maintenancetool.ini
+ if (it.key() != scStartMenuDir)
+ continue;
+ }
+ m_data.setValue(it.key(), replacePath(it.value().toString(), QLatin1String(scRelocatable), targetDir));
}
QSet<Repository> repos;
const QVariantList variants = cfg.value(QLatin1String("DefaultRepositories"))
@@ -1222,7 +1264,7 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper
// create the directory containing the maintenance tool (like a bundle structure on macOS...)
Operation *op = createOwnedOperation(QLatin1String("Mkdir"));
op->setArguments(QStringList() << targetAppDirPath);
- performOperationThreaded(op, Backup);
+ performOperationThreaded(op, Operation::Backup);
performOperationThreaded(op);
performedOperations.append(takeOwnedOperation(op));
}
@@ -1234,14 +1276,14 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper
Operation *op = createOwnedOperation(QLatin1String("Copy"));
op->setArguments(QStringList() << (sourceAppDirPath + QLatin1String("/../PkgInfo"))
<< (targetAppDirPath + QLatin1String("/../PkgInfo")));
- performOperationThreaded(op, Backup);
+ performOperationThreaded(op, Operation::Backup);
performOperationThreaded(op);
// copy Info.plist to target directory
op = createOwnedOperation(QLatin1String("Copy"));
op->setArguments(QStringList() << (sourceAppDirPath + QLatin1String("/../Info.plist"))
<< (targetAppDirPath + QLatin1String("/../Info.plist")));
- performOperationThreaded(op, Backup);
+ performOperationThreaded(op, Operation::Backup);
performOperationThreaded(op);
// patch the Info.plist after copying it
@@ -1274,7 +1316,7 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper
op = createOwnedOperation(QLatin1String("Mkdir"));
op->setArguments(QStringList() << (QFileInfo(targetAppDirPath).path() + QLatin1String("/Resources")));
- performOperationThreaded(op, Backup);
+ performOperationThreaded(op, Operation::Backup);
performOperationThreaded(op);
// copy application icons if it exists.
@@ -1283,7 +1325,7 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper
op = createOwnedOperation(QLatin1String("Copy"));
op->setArguments(QStringList() << (sourceAppDirPath + QLatin1String("/../Resources/") + icon)
<< (targetAppDirPath + QLatin1String("/../Resources/") + icon));
- performOperationThreaded(op, Backup);
+ performOperationThreaded(op, Operation::Backup);
performOperationThreaded(op);
// finally, copy everything within Frameworks and plugins
@@ -1477,6 +1519,58 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper
m_needToWriteMaintenanceTool = false;
}
+void PackageManagerCorePrivate::writeOfflineBaseBinary()
+{
+ qint64 size;
+ QFile input(installerBinaryPath());
+
+ QInstaller::openForRead(&input);
+#ifndef Q_OS_MACOS
+ BinaryLayout layout = BinaryContent::binaryLayout(&input, BinaryContent::MagicCookie);
+ size = layout.endOfExectuable;
+#else
+ // On macOS the data is on a separate file so we can just get the size
+ size = input.size();
+#endif
+
+ const QString offlineBinaryTempName = offlineBinaryName() + QLatin1String(".new");
+ qCDebug(QInstaller::lcInstallerInstallLog) << "Writing offline base binary:" << offlineBinaryTempName;
+ ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("Writing offline base binary."));
+
+ QFile out(generateTemporaryFileName());
+ QInstaller::openForWrite(&out); // throws an exception in case of error
+
+ if (!input.seek(0))
+ throw Error(tr("Failed to seek in file %1: %2").arg(input.fileName(), input.errorString()));
+
+ QInstaller::appendData(&out, &input, size);
+
+ {
+ // Check if we have an existing binary, for any reason
+ QFile dummy(offlineBinaryTempName);
+ if (dummy.exists() && !dummy.remove()) {
+ throw Error(tr("Cannot remove file \"%1\": %2").arg(dummy.fileName(),
+ dummy.errorString()));
+ }
+ // Offline binary name might contain non-existing leading directories
+ const QString offlineBinaryAbsolutePath = QFileInfo(offlineBinaryTempName).absolutePath();
+ QDir dummyDir(offlineBinaryAbsolutePath);
+ if (!dummyDir.exists() && !dummyDir.mkpath(offlineBinaryAbsolutePath)) {
+ throw Error(tr("Cannot create directory \"%1\".").arg(dummyDir.absolutePath()));
+ }
+ }
+
+ if (!out.copy(offlineBinaryTempName)) {
+ throw Error(tr("Cannot write offline binary to \"%1\": %2").arg(offlineBinaryTempName,
+ out.errorString()));
+ }
+
+ if (out.exists() && !out.remove()) {
+ qCWarning(QInstaller::lcInstallerInstallLog) << tr("Cannot remove temporary file \"%1\": %2")
+ .arg(out.fileName(), out.errorString());
+ }
+}
+
QString PackageManagerCorePrivate::registerPath()
{
#ifdef Q_OS_WIN
@@ -1533,7 +1627,7 @@ bool PackageManagerCorePrivate::runInstaller()
mkdirOp->setValue(QLatin1String("forceremoval"), true);
mkdirOp->setValue(QLatin1String("uninstall-only"), true);
- performOperationThreaded(mkdirOp, Backup);
+ performOperationThreaded(mkdirOp, Operation::Backup);
if (!performOperationThreaded(mkdirOp)) {
// if we cannot create the target dir, we try to activate the admin rights
adminRightsGained = m_core->gainAdminRights();
@@ -1924,12 +2018,144 @@ bool PackageManagerCorePrivate::runUninstaller()
m_core->dropAdminRights();
ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(QString::fromLatin1("\n%1").arg(
- success ? tr("Uninstallation completed successfully.") : tr("Uninstallation aborted.")));
+ success ? tr("Removal completed successfully.") : tr("Removal aborted.")));
emit uninstallationFinished();
return success;
}
+bool PackageManagerCorePrivate::runOfflineGenerator()
+{
+ const QString offlineBinaryTempName = offlineBinaryName() + QLatin1String(".new");
+ const QString tempSettingsFilePath = generateTemporaryFileName()
+ + QDir::separator() + QLatin1String("config.xml");
+
+ bool adminRightsGained = false;
+ try {
+ setStatus(PackageManagerCore::Running);
+ emit offlineGenerationStarted(); // Resets also the ProgressCoordninator
+
+ // Never write the maintenance tool when generating offline installer
+ m_needToWriteMaintenanceTool = false;
+
+ // Reserve some progress for the final writing, it should take
+ // only a fraction of time spent in the download part
+ ProgressCoordinator::instance()->addReservePercentagePoints(1);
+
+ const QString target = QDir::cleanPath(targetDir().replace(QLatin1Char('\\'), QLatin1Char('/')));
+ if (target.isEmpty())
+ throw Error(tr("Variable 'TargetDir' not set."));
+
+ // Create target directory for installer to be generated
+ if (!QDir(target).exists()) {
+ if (!QDir().mkpath(target)) {
+ adminRightsGained = m_core->gainAdminRights();
+ // Try again with admin privileges
+ if (!QDir().mkpath(target))
+ throw Error(tr("Cannot create target directory for installer."));
+ }
+ } else if (QDir(target).exists()) {
+ if (!directoryWritable(targetDir()))
+ adminRightsGained = m_core->gainAdminRights();
+ }
+ setDefaultFilePermissions(target, DefaultFilePermissions::Executable);
+
+ // Show that there was some work
+ ProgressCoordinator::instance()->addManualPercentagePoints(1);
+ ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("Preparing offline generation..."));
+
+ const QList<Component*> componentsToInclude = m_core->orderedComponentsToInstall();
+ qCDebug(QInstaller::lcInstallerInstallLog) << "Included components:" << componentsToInclude.size();
+
+ // Give full part progress size as this is the most time consuming step
+ m_core->downloadNeededArchives(double(1));
+
+ const QString installerBaseReplacement = replaceVariables(m_offlineBaseBinaryUnreplaced);
+ if (!installerBaseReplacement.isEmpty() && QFileInfo(installerBaseReplacement).exists()) {
+ qCDebug(QInstaller::lcInstallerInstallLog) << "Got a replacement installer base binary:"
+ << offlineBinaryTempName;
+
+ if (!QFile::copy(installerBaseReplacement, offlineBinaryTempName)) {
+ qCWarning(QInstaller::lcInstallerInstallLog) << QString::fromLatin1("Cannot copy "
+ "replacemement binary to temporary location \"%1\" from \"%2\".")
+ .arg(offlineBinaryTempName, installerBaseReplacement);
+ }
+ } else {
+ writeOfflineBaseBinary();
+ }
+
+ ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("Preparing installer configuration..."));
+
+ // Create copy of internal config file and data, remove repository related elements
+ QInstaller::trimmedCopyConfigData(m_data.settingsFilePath(), tempSettingsFilePath,
+ QStringList() << scRemoteRepositories << scRepositoryCategories);
+
+ // Assemble final installer binary
+ QInstallerTools::BinaryCreatorArgs args;
+ args.target = offlineBinaryName();
+#ifdef Q_OS_MACOS
+ // Target is a disk image on macOS
+ args.target.append(QLatin1String(".dmg"));
+#endif
+ args.templateBinary = offlineBinaryTempName;
+ args.offlineOnly = true;
+ args.configFile = tempSettingsFilePath;
+ args.ftype = QInstallerTools::Include;
+ // Add possible custom resources
+ if (!m_offlineGeneratorResourceCollections.isEmpty())
+ args.resources = m_offlineGeneratorResourceCollections;
+
+ foreach (auto component, componentsToInclude) {
+ args.filteredPackages.append(component->name());
+ args.repositoryDirectories.append(component->localTempPath());
+ }
+ args.repositoryDirectories.removeDuplicates();
+
+ ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("Creating the installer..."));
+
+ QString errorMessage;
+ if (QInstallerTools::createBinary(args, errorMessage) == EXIT_FAILURE) {
+ throw Error(tr("Failed to create offline installer. %1").arg(errorMessage));
+ } else {
+ setStatus(PackageManagerCore::Success);
+ }
+
+ // Fake a possible wrong value to show a full progress
+ const int progress = ProgressCoordinator::instance()->progressInPercentage();
+ // This should be only the reserved one (1) from the beginning
+ if (progress < 100)
+ ProgressCoordinator::instance()->addManualPercentagePoints(100 - progress);
+
+ } catch (const Error &err) {
+ if (m_core->status() != PackageManagerCore::Canceled) {
+ setStatus(PackageManagerCore::Failure);
+ MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(),
+ QLatin1String("installationError"), tr("Error"), err.message());
+ }
+ }
+ QFile tempBinary(offlineBinaryTempName);
+ if (tempBinary.exists() && !tempBinary.remove()) {
+ qCWarning(QInstaller::lcInstallerInstallLog) << tr("Cannot remove temporary file \"%1\": %2")
+ .arg(tempBinary.fileName(), tempBinary.errorString());
+ }
+
+ QDir tempSettingsDir(QFileInfo(tempSettingsFilePath).absolutePath());
+ if (tempSettingsDir.exists() && !tempSettingsDir.removeRecursively()) {
+ qCWarning(QInstaller::lcInstallerInstallLog) << tr("Cannot remove temporary directory \"%1\".")
+ .arg(tempSettingsDir.path());
+ }
+
+ const bool success = m_core->status() == PackageManagerCore::Success;
+ if (adminRightsGained)
+ m_core->dropAdminRights();
+
+ ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(QString::fromLatin1("\n%1").arg(
+ success ? tr("Offline generation completed successfully.") : tr("Offline generation aborted!")));
+
+ emit offlineGenerationFinished();
+ return success;
+}
+
void PackageManagerCorePrivate::installComponent(Component *component, double progressOperationSize,
bool adminRightsGained)
{
@@ -1961,7 +2187,7 @@ void PackageManagerCorePrivate::installComponent(Component *component, double pr
connectOperationCallMethodRequest(operation);
// allow the operation to backup stuff before performing the operation
- performOperationThreaded(operation, PackageManagerCorePrivate::Backup);
+ performOperationThreaded(operation, Operation::Backup);
bool ignoreError = false;
bool ok = performOperationThreaded(operation);
@@ -1996,8 +2222,10 @@ void PackageManagerCorePrivate::installComponent(Component *component, double pr
if (!ok && !ignoreError)
throw Error(operation->errorString());
- if ((component->value(scEssential, scFalse) == scTrue) && !m_core->isCommandLineInstance())
+ if (((component->value(scEssential, scFalse) == scTrue) || (component->value(scForcedUpdate, scFalse) == scTrue))
+ && !m_core->isCommandLineInstance()) {
m_needsHardRestart = true;
+ }
}
registerPathsForUninstallation(component->pathsForUninstallation(), component->name());
@@ -2015,6 +2243,8 @@ void PackageManagerCorePrivate::installComponent(Component *component, double pr
m_localPackageHub->addPackage(component->name(),
component->value(scVersion),
component->value(scDisplayName),
+ QPair<QString, bool>(component->value(scTreeName),
+ component->treeNameMoveChildren()),
component->value(scDescription),
component->dependencies(),
component->autoDependencies(),
@@ -2023,7 +2253,8 @@ void PackageManagerCorePrivate::installComponent(Component *component, double pr
component->value(scUncompressedSize).toULongLong(),
component->value(scInheritVersion),
component->isCheckable(),
- component->isExpandedByDefault());
+ component->isExpandedByDefault(),
+ component->value(scContentSha1));
m_localPackageHub->writeToDisk();
component->setInstalled();
@@ -2055,7 +2286,13 @@ bool PackageManagerCorePrivate::runningProcessesFound()
void PackageManagerCorePrivate::setComponentSelection(const QString &id, Qt::CheckState state)
{
ComponentModel *model = m_core->isUpdater() ? m_core->updaterComponentModel() : m_core->defaultComponentModel();
- const QModelIndex &idx = model->indexFromComponentName(id);
+ Component *component = m_core->componentByName(id);
+ if (!component) {
+ qCWarning(QInstaller::lcInstallerInstallLog).nospace()
+ << "Unable to set selection for: " << id << ". Component not found.";
+ return;
+ }
+ const QModelIndex &idx = model->indexFromComponentName(component->treeName());
if (idx.isValid())
model->setData(idx, state, Qt::CheckStateRole);
}
@@ -2074,7 +2311,7 @@ void PackageManagerCorePrivate::deleteMaintenanceTool()
QLatin1String("uninstall.vbs")).absoluteFilePath());
QFile f(batchfile);
if (!f.open(QIODevice::WriteOnly | QIODevice::Text))
- throw Error(tr("Cannot prepare uninstall"));
+ throw Error(tr("Cannot prepare removal"));
QTextStream batch(&f);
batch << "Set fso = WScript.CreateObject(\"Scripting.FileSystemObject\")\n";
@@ -2105,7 +2342,7 @@ void PackageManagerCorePrivate::deleteMaintenanceTool()
}
if (!QProcessWrapper::startDetached(QLatin1String("cscript"), arguments, QDir::rootPath()))
- throw Error(tr("Cannot start uninstall"));
+ throw Error(tr("Cannot start removal"));
#else
// every other platform has no problem if we just delete ourselves now
QFile maintenanceTool(QFileInfo(installerBinaryPath()).absoluteFilePath());
@@ -2194,7 +2431,7 @@ void PackageManagerCorePrivate::runUndoOperations(const OperationList &undoOpera
qCDebug(QInstaller::lcInstallerInstallLog) << "undo operation=" << undoOperation->name();
bool ignoreError = false;
- bool ok = performOperationThreaded(undoOperation, PackageManagerCorePrivate::Undo);
+ bool ok = performOperationThreaded(undoOperation, Operation::Undo);
const QString componentName = undoOperation->value(QLatin1String("component")).toString();
@@ -2203,11 +2440,11 @@ void PackageManagerCorePrivate::runUndoOperations(const OperationList &undoOpera
const QMessageBox::StandardButton button =
MessageBoxHandler::warning(MessageBoxHandler::currentBestSuitParent(),
QLatin1String("installationErrorWithIgnore"), tr("Installer Error"),
- tr("Error during uninstallation process:\n%1").arg(undoOperation->errorString()),
+ tr("Error during removal process:\n%1").arg(undoOperation->errorString()),
QMessageBox::Retry | QMessageBox::Ignore, QMessageBox::Ignore);
if (button == QMessageBox::Retry)
- ok = performOperationThreaded(undoOperation, Undo);
+ ok = performOperationThreaded(undoOperation, Operation::Undo);
else if (button == QMessageBox::Ignore)
ignoreError = true;
}
@@ -2246,7 +2483,7 @@ PackagesList PackageManagerCorePrivate::remotePackages()
m_updateFinder = new KDUpdater::UpdateFinder;
m_updateFinder->setAutoDelete(false);
- m_updateFinder->setPackageSources(m_packageSources);
+ m_updateFinder->setPackageSources(m_packageSources + m_compressedPackageSources);
m_updateFinder->setLocalPackageHub(m_localPackageHub);
m_updateFinder->run();
@@ -2260,29 +2497,6 @@ PackagesList PackageManagerCorePrivate::remotePackages()
return m_updateFinder->updates();
}
-PackagesList PackageManagerCorePrivate::compressedPackages()
-{
- if (m_compressedUpdates && m_compressedFinder)
- return m_compressedFinder->updates();
- m_compressedUpdates = false;
- delete m_compressedFinder;
-
- m_compressedFinder = new KDUpdater::UpdateFinder;
- m_compressedFinder->setAutoDelete(false);
- m_compressedFinder->addCompressedPackage(true);
- m_compressedFinder->setPackageSources(m_compressedPackageSources);
-
- m_compressedFinder->setLocalPackageHub(m_localPackageHub);
- m_compressedFinder->run();
- if (m_compressedFinder->updates().isEmpty()) {
- setStatus(PackageManagerCore::Failure, tr("Cannot retrieve remote tree %1.")
- .arg(m_compressedFinder->errorString()));
- return PackagesList();
- }
- m_compressedUpdates = true;
- return m_compressedFinder->updates();
-}
-
/*!
Returns a hash containing the installed package name and it's associated package information. If
the application is running in installer mode or the local components file could not be parsed, the
@@ -2350,42 +2564,6 @@ bool PackageManagerCorePrivate::fetchMetaInformationFromRepositories(DownloadTyp
return m_repoFetched;
}
-bool PackageManagerCorePrivate::fetchMetaInformationFromCompressedRepositories()
-{
- bool compressedRepoFetched = false;
-
- m_compressedUpdates = false;
- m_updateSourcesAdded = false;
-
- try {
- //Tell MetadataJob that only compressed packages needed to be fetched and not all.
- //We cannot do this in general fetch meta method as the compressed packages might be
- //installed after components tree is generated
- m_metadataJob.addDownloadType(DownloadType::CompressedPackage);
- m_metadataJob.start();
- m_metadataJob.waitForFinished();
- } catch (Error &error) {
- setStatus(PackageManagerCore::Failure, tr("Cannot retrieve meta information: %1")
- .arg(error.message()));
- return compressedRepoFetched;
- }
-
- if (m_metadataJob.error() != Job::NoError) {
- switch (m_metadataJob.error()) {
- case QInstaller::UserIgnoreError:
- break; // we can simply ignore this error, the user knows about it
- default:
- //Do not change core status here, we can recover if there is invalid
- //compressed repository
- setStatus(m_core->status(), m_metadataJob.errorString());
- return compressedRepoFetched;
- }
- }
-
- compressedRepoFetched = true;
- return compressedRepoFetched;
-}
-
bool PackageManagerCorePrivate::addUpdateResourcesFromRepositories(bool parseChecksum, bool compressedRepository)
{
if (!compressedRepository && m_updateSourcesAdded)
@@ -2445,8 +2623,8 @@ bool PackageManagerCorePrivate::addUpdateResourcesFromRepositories(bool parseChe
if (!checksum.isNull())
m_core->setTestChecksum(checksum.toElement().text().toLower() == scTrue);
}
- if (compressedRepository)
- m_compressedPackageSources.insert(PackageSource(QUrl::fromLocalFile(data.directory), 1));
+ if (data.repository.isCompressed())
+ m_compressedPackageSources.insert(PackageSource(QUrl::fromLocalFile(data.directory), 2));
else
m_packageSources.insert(PackageSource(QUrl::fromLocalFile(data.directory), 1));
@@ -2582,7 +2760,12 @@ bool PackageManagerCorePrivate::calculateComponentsAndRun()
qCDebug(QInstaller::lcInstallerInstallLog) << "Installation canceled.";
} else if (componentsOk && acceptLicenseAgreements()) {
qCDebug(QInstaller::lcInstallerInstallLog).noquote() << htmlToString(htmlOutput);
- if (!(m_autoConfirmCommand || askUserConfirmCommand())) {
+
+ QString spaceInfo;
+ const bool spaceOk = m_core->checkAvailableSpace(spaceInfo);
+ qCDebug(QInstaller::lcInstallerInstallLog) << spaceInfo;
+
+ if (!spaceOk || !(m_autoConfirmCommand || askUserConfirmCommand())) {
qCDebug(QInstaller::lcInstallerInstallLog) << "Installation aborted.";
} else if (m_core->run()) {
// Write maintenance tool if required
@@ -2599,21 +2782,31 @@ bool PackageManagerCorePrivate::acceptLicenseAgreements() const
if (isUninstaller())
return true;
- typedef QHash<QString, QPair<QString, QString> > LicensesHash;
foreach (Component *component, m_core->orderedComponentsToInstall()) {
// Package manager or updater, no need to accept again as long as
// the component is installed.
if (m_core->isMaintainer() && component->isInstalled())
continue;
+ m_core->addLicenseItem(component->licenses());
+ }
- LicensesHash hash = component->licenses();
- for (LicensesHash::iterator it = hash.begin(); it != hash.end(); ++it) {
- if (m_autoAcceptLicenses || askUserAcceptLicense(it.key(), it.value().second)) {
+ QHash<QString, QMap<QString, QString>> priorityHash = m_core->sortedLicenses();
+ QStringList priorities = priorityHash.keys();
+ priorities.sort();
+ for (int i = priorities.length() - 1; i >= 0; --i) {
+ QString priority = priorities.at(i);
+ QMap<QString, QString> licenses = priorityHash.value(priority);
+
+ QStringList licenseNames = licenses.keys();
+ licenseNames.sort(Qt::CaseInsensitive);
+ for (QString licenseName : licenseNames) {
+ if (m_autoAcceptLicenses
+ || askUserAcceptLicense(licenseName, licenses.value(licenseName))) {
qCDebug(QInstaller::lcInstallerInstallLog) << "License"
- << it.key() << "accepted by user.";
+ << licenseName << "accepted by user.";
} else {
qCDebug(QInstaller::lcInstallerInstallLog) << "License"
- << it.key() << "not accepted by user. Aborting.";
+ << licenseName<< "not accepted by user. Aborting.";
return false;
}
}
@@ -2645,51 +2838,6 @@ bool PackageManagerCorePrivate::askUserAcceptLicense(const QString &name, const
}
}
-void PackageManagerCorePrivate::printPackageInformation(const QString &name, const Package *update)
-{
- qCDebug(QInstaller::lcPackageName).noquote() << "Id:" << name;
- qCDebug(QInstaller::lcPackageDisplayname).noquote() << "\tDisplay name:" << update->data(scDisplayName).toString();
- qCDebug(QInstaller::lcPackageVersion).noquote() << "\tVersion:" << update->data(scVersion).toString();
- qCDebug(QInstaller::lcPackageDescription).noquote() << "\tDescription:" << update->data(scDescription).toString();
- qCDebug(QInstaller::lcPackageReleasedate).noquote() << "\tRelease date:" << update->data(scReleaseDate).toString();
- qCDebug(QInstaller::lcPackageDependencies).noquote() << "\tDependencies:" << update->data(scDependencies).toString();
- qCDebug(QInstaller::lcPackageAutodependon).noquote() << "\tAutodependon:" << update->data(scAutoDependOn).toString();
- qCDebug(QInstaller::lcPackageVirtual).noquote() << "\tVirtual:" << update->data(scVirtual, false).toString();
- qCDebug(QInstaller::lcPackageSortingpriority).noquote() << "\tSorting priority:" << update->data(scSortingPriority).toString();
- qCDebug(QInstaller::lcPackageScript).noquote() << "\tScript:" << update->data(scScript).toString();
- qCDebug(QInstaller::lcPackageDefault).noquote() << "\tDefault:"<< update->data(scDefault, false).toString();
- qCDebug(QInstaller::lcPackageEssential).noquote() << "\tEssential:" << update->data(scEssential, false).toString();
- qCDebug(QInstaller::lcPackageForcedinstallation).noquote() << "\tForced installation:" << update->data(QLatin1String("ForcedInstallation"), false).toString();
- qCDebug(QInstaller::lcPackageReplaces).noquote() << "\tReplaces:" << update->data(scReplaces).toString();
- qCDebug(QInstaller::lcPackageDownloadableArchives).noquote() << "\tDownloadable archives:" << update->data(scDownloadableArchives).toString();
- qCDebug(QInstaller::lcPackageRequiresAdminRights).noquote() << "\tRequires admin rights:" << update->data(scRequiresAdminRights).toString();
- qCDebug(QInstaller::lcPackageCheckable).noquote() << "\tCheckable:" << update->data(scCheckable).toString();
- qCDebug(QInstaller::lcPackageLicenses).noquote() << "\tLicenses:" << update->data(QLatin1String("Licenses")).toString();
- qCDebug(QInstaller::lcPackageCompressedSize).noquote() << "\tCompressed size:" << update->data(QLatin1String("CompressedSize")).toString();
- qCDebug(QInstaller::lcPackageUncompressedSize).noquote() << "\tUncompressed size:" << update->data(QLatin1String("UncompressedSize")).toString();
-
- //Check if package already installed
- LocalPackagesHash installedPackages = this->localInstalledPackages();
- if (installedPackages.contains(name))
- qCDebug(QInstaller::lcPackageInstalledVersion).noquote() << "\tInstalled version:" << installedPackages.value(name).version;
-}
-
-void PackageManagerCorePrivate::printLocalPackageInformation(const KDUpdater::LocalPackage package) const
-{
- qCDebug(QInstaller::lcPackageName).noquote() << "Id:" << package.name;
- qCDebug(QInstaller::lcPackageDisplayname).noquote() << "\tDisplay name:" << package.title;
- qCDebug(QInstaller::lcPackageVersion).noquote() << "\tVersion:" << package.version;
- qCDebug(QInstaller::lcPackageDescription).noquote() << "\tDescription:" << package.description;
- qCDebug(QInstaller::lcPackageDependencies).noquote() << "\tDependencies:" << package.dependencies;
- qCDebug(QInstaller::lcPackageAutodependon).noquote() << "\tAutodependon:" << package.autoDependencies;
- qCDebug(QInstaller::lcPackageVirtual).noquote() << "\tVirtual:" << package.virtualComp;
- qCDebug(QInstaller::lcPackageForcedinstallation).noquote() << "\tForced installation:" << package.forcedInstallation;
- qCDebug(QInstaller::lcPackageCheckable).noquote() << "\tCheckable:" << package.checkable;
- qCDebug(QInstaller::lcPackageUncompressedSize).noquote() << "\tUncompressed size:" << package.uncompressedSize;
- qCDebug(QInstaller::lcPackageInstallDate).noquote() << "\tInstalled:" << package.installDate;
- qCDebug(QInstaller::lcPackageUpdateDate).noquote() << "\tLast updated:" << package.lastUpdateDate;
-}
-
bool PackageManagerCorePrivate::askUserConfirmCommand() const
{
qCDebug(QInstaller::lcInstallerInstallLog) << "Do you want to continue?";
@@ -2709,4 +2857,40 @@ bool PackageManagerCorePrivate::askUserConfirmCommand() const
}
}
+bool PackageManagerCorePrivate::packageNeedsUpdate(const LocalPackage &localPackage, const Package *update) const
+{
+ bool updateNeeded = true;
+ const QString contentSha1 = update->data(scContentSha1).toString();
+ if (!contentSha1.isEmpty()) {
+ if (contentSha1 == localPackage.contentSha1)
+ updateNeeded = false;
+ } else {
+ const QString updateVersion = update->data(scVersion).toString();
+ if (KDUpdater::compareVersion(updateVersion, localPackage.version) <= 0)
+ updateNeeded = false;
+ }
+ return updateNeeded;
+}
+
+void PackageManagerCorePrivate::commitPendingUnstableComponents()
+{
+ if (m_pendingUnstableComponents.isEmpty())
+ return;
+
+ for (auto &componentName : m_pendingUnstableComponents.keys()) {
+ Component *const component = m_core->componentByName(componentName);
+ if (!component) {
+ qCWarning(lcInstallerInstallLog) << "Failure while marking component "
+ "unstable. No such component exists:" << componentName;
+ continue;
+ }
+
+ const QPair<Component::UnstableError, QString> unstableError
+ = m_pendingUnstableComponents.value(componentName);
+
+ component->setUnstable(unstableError.first, unstableError.second);
+ }
+ m_pendingUnstableComponents.clear();
+}
+
} // namespace QInstaller
diff --git a/src/libs/installer/packagemanagercore_p.h b/src/libs/installer/packagemanagercore_p.h
index df7a887f2..73c378f43 100644
--- a/src/libs/installer/packagemanagercore_p.h
+++ b/src/libs/installer/packagemanagercore_p.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -35,6 +35,7 @@
#include "packagemanagerproxyfactory.h"
#include "packagesource.h"
#include "qinstallerglobal.h"
+#include "component.h"
#include "sysinfo.h"
#include "updatefinder.h"
@@ -52,7 +53,6 @@ using namespace KDUpdater;
namespace QInstaller {
struct BinaryLayout;
-class Component;
class ScriptEngine;
class ComponentModel;
class TempDirDeleter;
@@ -67,12 +67,6 @@ class PackageManagerCorePrivate : public QObject
Q_DISABLE_COPY(PackageManagerCorePrivate)
public:
- enum OperationType {
- Backup,
- Perform,
- Undo
- };
-
explicit PackageManagerCorePrivate(PackageManagerCore *core);
explicit PackageManagerCorePrivate(PackageManagerCore *core, qint64 magicInstallerMaker,
const QList<OperationBlob> &performedOperations);
@@ -80,8 +74,8 @@ public:
static bool isProcessRunning(const QString &name, const QList<ProcessInfo> &processes);
- static bool performOperationThreaded(Operation *op, PackageManagerCorePrivate::OperationType type
- = PackageManagerCorePrivate::Perform);
+ static bool performOperationThreaded(Operation *op, UpdateOperation::OperationType type
+ = UpdateOperation::Perform);
void initialize(const QHash<QString, QString> &params);
bool isOfflineOnly() const;
@@ -96,11 +90,13 @@ public:
QString maintenanceToolName() const;
QString installerBinaryPath() const;
+ QString offlineBinaryName() const;
void writeMaintenanceConfigFiles();
void readMaintenanceConfigFiles(const QString &targetDir);
void writeMaintenanceTool(OperationList performedOperations);
+ void writeOfflineBaseBinary();
QString componentsXmlPath() const;
QString configurationFileName() const;
@@ -133,6 +129,11 @@ public:
bool runPackageUpdater();
bool isPackageManager() const;
+ bool runOfflineGenerator();
+ bool isOfflineGenerator() const;
+
+ bool isPackageViewer() const;
+
QString replaceVariables(const QString &str) const;
QByteArray replaceVariables(const QByteArray &str) const;
@@ -171,10 +172,11 @@ signals:
void installationFinished();
void uninstallationStarted();
void uninstallationFinished();
+ void offlineGenerationStarted();
+ void offlineGenerationFinished();
public:
UpdateFinder *m_updateFinder;
- UpdateFinder *m_compressedFinder;
QSet<PackageSource> m_packageSources;
QSet<PackageSource> m_compressedPackageSources;
std::shared_ptr<LocalPackageHub> m_localPackageHub;
@@ -194,6 +196,8 @@ public:
bool m_needToWriteMaintenanceTool;
PackageManagerCoreData m_data;
QString m_installerBaseBinaryUnreplaced;
+ QString m_offlineBaseBinaryUnreplaced;
+ QStringList m_offlineGeneratorResourceCollections;
QList<QInstaller::Component*> m_rootComponents;
QList<QInstaller::Component*> m_rootDependencyReplacements;
@@ -239,10 +243,8 @@ private:
bool adminRightsGained, bool deleteOperation);
PackagesList remotePackages();
- PackagesList compressedPackages();
LocalPackagesHash localInstalledPackages();
bool fetchMetaInformationFromRepositories(DownloadType type = DownloadType::All);
- bool fetchMetaInformationFromCompressedRepositories();
bool addUpdateResourcesFromRepositories(bool parseChecksum, bool compressedRepository = false);
void processFilesForDelayedDeletion();
void findExecutablesRecursive(const QString &path, const QStringList &excludeFiles, QStringList *result);
@@ -251,20 +253,21 @@ private:
bool acceptLicenseAgreements() const;
bool askUserAcceptLicense(const QString &name, const QString &content) const;
bool askUserConfirmCommand() const;
- void printPackageInformation(const QString &name, const Package *update);
- void printLocalPackageInformation(const KDUpdater::LocalPackage package) const;
+ bool packageNeedsUpdate(const LocalPackage &localPackage, const Package *update) const;
+ void commitPendingUnstableComponents();
private:
PackageManagerCore *m_core;
MetadataJob m_metadataJob;
bool m_updates;
- bool m_compressedUpdates;
bool m_repoFetched;
bool m_updateSourcesAdded;
qint64 m_magicBinaryMarker;
+ int m_magicMarkerSupplement;
+
bool m_componentsToInstallCalculated;
- bool m_foundEssentialUpdate;
+ bool m_foundEssentialUpdate;;
mutable ScriptEngine *m_componentScriptEngine;
mutable ScriptEngine *m_controlScriptEngine;
@@ -272,6 +275,8 @@ private:
QHash<QString, QPair<Component*, Component*> > m_componentsToReplaceAllMode;
QHash<QString, QPair<Component*, Component*> > m_componentsToReplaceUpdaterMode;
+ QHash<QString, QPair<Component::UnstableError, QString>> m_pendingUnstableComponents;
+
InstallerCalculator *m_installerCalculator;
UninstallerCalculator *m_uninstallerCalculator;
@@ -282,6 +287,7 @@ private:
QObject *m_guiObject;
QScopedPointer<RemoteFileEngineHandler> m_remoteFileEngineHandler;
+ QHash<QString, QVariantMap> m_licenseItems;
private:
// remove once we deprecate isSelected, setSelected etc...
diff --git a/src/libs/installer/packagemanagercoredata.cpp b/src/libs/installer/packagemanagercoredata.cpp
index 14f9cff47..58782c03f 100644
--- a/src/libs/installer/packagemanagercoredata.cpp
+++ b/src/libs/installer/packagemanagercoredata.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -50,54 +50,60 @@ namespace QInstaller
\internal
*/
-PackageManagerCoreData::PackageManagerCoreData(const QHash<QString, QString> &variables)
+PackageManagerCoreData::PackageManagerCoreData(const QHash<QString, QString> &variables, const bool isInstaller)
{
- setDynamicPredefinedVariables();
-
+ // Add user defined variables before dynamic as user settings can affect dynamic variables.
+ setUserDefinedVariables(variables);
+ addDynamicPredefinedVariables();
// Set some common variables that may used e.g. as placeholder in some of the settings variables or
// in a script or...
- m_variables.insert(scTargetConfigurationFile, QLatin1String("components.xml"));
- m_variables.insert(QLatin1String("InstallerDirPath"), QCoreApplication::applicationDirPath());
- m_variables.insert(QLatin1String("InstallerFilePath"), QCoreApplication::applicationFilePath());
+ addNewVariable(QLatin1String("InstallerDirPath"), QCoreApplication::applicationDirPath());
+ addNewVariable(QLatin1String("InstallerFilePath"), QCoreApplication::applicationFilePath());
#ifdef Q_OS_WIN
- m_variables.insert(QLatin1String("os"), QLatin1String("win"));
+ addNewVariable(QLatin1String("os"), QLatin1String("win"));
#elif defined(Q_OS_MACOS)
- m_variables.insert(QLatin1String("os"), QLatin1String("mac"));
+ addNewVariable(QLatin1String("os"), QLatin1String("mac"));
#elif defined(Q_OS_LINUX)
- m_variables.insert(QLatin1String("os"), QLatin1String("x11"));
+ addNewVariable(QLatin1String("os"), QLatin1String("x11"));
#else
// TODO: add more platforms as needed...
#endif
- m_settings = Settings::fromFileAndPrefix(QLatin1String(":/metadata/installer-config/config.xml"),
- QLatin1String(":/metadata/installer-config/"), Settings::RelaxedParseMode);
+ m_settingsFilePath = QLatin1String(":/metadata/installer-config/config.xml");
+ m_settings = Settings::fromFileAndPrefix(m_settingsFilePath,
+ QFileInfo(m_settingsFilePath).absolutePath(), Settings::RelaxedParseMode);
// fill the variables defined in the settings
- m_variables.insert(QLatin1String("ProductName"), m_settings.applicationName());
- m_variables.insert(QLatin1String("ProductVersion"), m_settings.version());
- m_variables.insert(scTitle, replaceVariables(m_settings.title()));
- m_variables.insert(scPublisher, m_settings.publisher());
- m_variables.insert(QLatin1String("Url"), m_settings.url());
- m_variables.insert(scStartMenuDir, m_settings.startMenuDir());
- m_variables.insert(scTargetConfigurationFile, m_settings.configurationFileName());
- m_variables.insert(QLatin1String("LogoPixmap"), m_settings.logo());
- m_variables.insert(QLatin1String("WatermarkPixmap"), m_settings.watermark());
- m_variables.insert(QLatin1String("BannerPixmap"), m_settings.banner());
- m_variables.insert(QLatin1String("PageListPixmap"), m_settings.pageListPixmap());
+ addNewVariable(QLatin1String("ProductName"), m_settings.applicationName());
+ addNewVariable(QLatin1String("ProductVersion"), m_settings.version());
+ addNewVariable(scTitle, replaceVariables(m_settings.title()));
+ addNewVariable(scPublisher, m_settings.publisher());
+ addNewVariable(QLatin1String("Url"), m_settings.url());
+ addNewVariable(scLogo, m_settings.logo());
+ addNewVariable(scWatermark, m_settings.watermark());
+ addNewVariable(scBanner, m_settings.banner());
+ addNewVariable(scPageListPixmap, m_settings.pageListPixmap());
const QString description = m_settings.runProgramDescription();
if (!description.isEmpty())
- m_variables.insert(scRunProgramDescription, description);
-
- m_variables.insert(scTargetDir, replaceVariables(m_settings.targetDir()));
- m_variables.insert(scRemoveTargetDir, replaceVariables(m_settings.removeTargetDir()));
+ addNewVariable(scRunProgramDescription, description);
+
+ // Some settings might change during install, read those settings later from
+ // maintenancetool if maintenancetool is used.
+ if (isInstaller) {
+ addNewVariable(scTargetDir, replaceVariables(m_settings.targetDir()));
+ addNewVariable(scTargetConfigurationFile, m_settings.configurationFileName());
+ addNewVariable(scStartMenuDir, m_settings.startMenuDir());
+ } else {
+#ifdef Q_OS_MACOS
+ addNewVariable(scTargetDir, QFileInfo(QCoreApplication::applicationDirPath() + QLatin1String("/../../..")).absoluteFilePath());
+#else
+ addNewVariable(scTargetDir, QCoreApplication::applicationDirPath());
+#endif
- // Iterate over user defined parameters. If those are found, add to table or
- // replace existing values.
- QHash<QString, QString>::const_iterator it;
- for (it = variables.begin(); it != variables.end(); ++it)
- m_variables.insert(it.key(), it.value());
+ }
+ addNewVariable(scRemoveTargetDir, replaceVariables(m_settings.removeTargetDir()));
}
void PackageManagerCoreData::clear()
@@ -110,12 +116,12 @@ void PackageManagerCoreData::clear()
Set some common variables that may be used e.g. as placeholder in some of the settings
variables or in a script or...
*/
-void PackageManagerCoreData::setDynamicPredefinedVariables()
+void PackageManagerCoreData::addDynamicPredefinedVariables()
{
- m_variables.insert(QLatin1String("rootDir"), QDir::rootPath());
- m_variables.insert(QLatin1String("homeDir"), QDir::homePath());
- m_variables.insert(QLatin1String("RootDir"), QDir::rootPath());
- m_variables.insert(QLatin1String("HomeDir"), QDir::homePath());
+ addNewVariable(QLatin1String("rootDir"), QDir::rootPath());
+ addNewVariable(QLatin1String("homeDir"), QDir::homePath());
+ addNewVariable(QLatin1String("RootDir"), QDir::rootPath());
+ addNewVariable(QLatin1String("HomeDir"), QDir::homePath());
QString dir = QLatin1String("/opt");
#ifdef Q_OS_WIN
@@ -125,13 +131,13 @@ void PackageManagerCoreData::setDynamicPredefinedVariables()
#elif defined (Q_OS_MACOS)
dir = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation).value(0);
#endif
- m_variables.insert(QLatin1String("ApplicationsDir"), dir);
+ addNewVariable(QLatin1String("ApplicationsDir"), dir);
QString dirUser = dir;
#ifdef Q_OS_MACOS
dirUser = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation).value(0);
#endif
- m_variables.insert(QLatin1String("ApplicationsDirUser"), dirUser);
+ addNewVariable(QLatin1String("ApplicationsDirUser"), dirUser);
QString dirX86 = dir;
QString dirX64 = dir;
@@ -154,8 +160,8 @@ void PackageManagerCoreData::setDynamicPredefinedVariables()
dirX86 = replaceWindowsEnvironmentVariables(programfilesX86);
dirX64 = replaceWindowsEnvironmentVariables(programfilesX64);
#endif
- m_variables.insert(QLatin1String("ApplicationsDirX86"), dirX86);
- m_variables.insert(QLatin1String("ApplicationsDirX64"), dirX64);
+ addNewVariable(QLatin1String("ApplicationsDirX86"), dirX86);
+ addNewVariable(QLatin1String("ApplicationsDirX64"), dirX64);
#ifdef Q_OS_WIN
QSettingsWrapper user(QLatin1String("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\"
@@ -169,23 +175,38 @@ void PackageManagerCoreData::setDynamicPredefinedVariables()
QString desktop;
if (m_variables.value(QLatin1String("AllUsers")) == scTrue) {
- desktop = system.value(QLatin1String("Desktop")).toString();
+ desktop = system.value(QLatin1String("Common Desktop")).toString();
} else {
desktop = user.value(QLatin1String("Desktop")).toString();
}
- m_variables.insert(QLatin1String("DesktopDir"), replaceWindowsEnvironmentVariables(desktop));
- m_variables.insert(scUserStartMenuProgramsPath,
+ addNewVariable(QLatin1String("DesktopDir"), replaceWindowsEnvironmentVariables(desktop));
+ addNewVariable(scUserStartMenuProgramsPath,
replaceWindowsEnvironmentVariables(programs));
- m_variables.insert(scAllUsersStartMenuProgramsPath,
+ addNewVariable(scAllUsersStartMenuProgramsPath,
replaceWindowsEnvironmentVariables(allPrograms));
#endif
#define QUOTE_(x) #x
#define QUOTE(x) QUOTE_(x)
- m_variables.insert(QLatin1String("IFW_VERSION_STR"), QLatin1String(QUOTE(IFW_VERSION_STR)));
+ addNewVariable(QLatin1String("FrameworkVersion"), QLatin1String(QUOTE(IFW_VERSION_STR)));
+ // Undocumented, left for compatibility with scripts using the old key
+ addNewVariable(QLatin1String("IFW_VERSION_STR"), QLatin1String(QUOTE(IFW_VERSION_STR)));
#undef QUOTE
#undef QUOTE_
}
+void PackageManagerCoreData::setUserDefinedVariables(const QHash<QString, QString> &variables)
+{
+ QHash<QString, QString>::const_iterator it;
+ for (it = variables.begin(); it != variables.end(); ++it)
+ m_variables.insert(it.key(), it.value());
+}
+
+void PackageManagerCoreData::addNewVariable(const QString &key, const QString &value)
+{
+ if (!m_variables.contains(key))
+ m_variables.insert(key, value);
+}
+
Settings &PackageManagerCoreData::settings() const
{
return m_settings;
@@ -216,7 +237,7 @@ QVariant PackageManagerCoreData::value(const QString &key, const QVariant &_defa
if (dir.isEmpty())
dir = replaceVariables(m_settings.value(key, _default).toString());
#ifdef Q_OS_WIN
- return QInstaller::normalizePathName(dir);
+ return QDir::fromNativeSeparators(QInstaller::normalizePathName(dir));
#else
if (dir.startsWith(QLatin1String("~/")))
return QDir::home().absoluteFilePath(dir.mid(2));
@@ -241,6 +262,11 @@ QVariant PackageManagerCoreData::value(const QString &key, const QVariant &_defa
return m_settings.value(key, _default);
}
+QString PackageManagerCoreData::key(const QString &value) const
+{
+ return m_variables.key(value, QString());
+}
+
QString PackageManagerCoreData::replaceVariables(const QString &str) const
{
static const QChar at = QLatin1Char('@');
diff --git a/src/libs/installer/packagemanagercoredata.h b/src/libs/installer/packagemanagercoredata.h
index ec0d4c302..3cfb77c95 100644
--- a/src/libs/installer/packagemanagercoredata.h
+++ b/src/libs/installer/packagemanagercoredata.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -37,23 +37,31 @@ class PackageManagerCoreData
{
public:
PackageManagerCoreData() {}
- explicit PackageManagerCoreData(const QHash<QString, QString> &variables);
+ explicit PackageManagerCoreData(const QHash<QString, QString> &variables, const bool isInstaller);
void clear();
- void setDynamicPredefinedVariables();
+ void addDynamicPredefinedVariables();
+ void setUserDefinedVariables(const QHash<QString, QString> &variables);
+ void addNewVariable(const QString &key, const QString &value);
Settings &settings() const;
QStringList keys() const;
+ inline QString settingsFilePath() {
+ return m_settingsFilePath;
+ }
+
bool contains(const QString &key) const;
bool setValue(const QString &key, const QString &normalizedValue);
QVariant value(const QString &key, const QVariant &_default = QVariant()) const;
+ QString key(const QString &value) const;
QString replaceVariables(const QString &str) const;
QByteArray replaceVariables(const QByteArray &ba) const;
private:
mutable Settings m_settings;
+ QString m_settingsFilePath;
QHash<QString, QString> m_variables;
};
diff --git a/src/libs/installer/packagemanagergui.cpp b/src/libs/installer/packagemanagergui.cpp
index c6f0e9439..5d847c061 100644
--- a/src/libs/installer/packagemanagergui.cpp
+++ b/src/libs/installer/packagemanagergui.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -41,6 +41,7 @@
#include "productkeycheck.h"
#include "repositorycategory.h"
#include "componentselectionpage_p.h"
+#include "loggingutils.h"
#include "sysinfo.h"
#include "globals.h"
@@ -67,6 +68,7 @@
#include <QProgressBar>
#include <QPushButton>
#include <QRadioButton>
+#include <QSplitter>
#include <QStringListModel>
#include <QTextBrowser>
@@ -123,7 +125,7 @@ public:
return m_widget;
}
- bool isComplete() const
+ bool isComplete() const Q_DECL_OVERRIDE
{
return m_widget->property("complete").toBool();
}
@@ -300,8 +302,11 @@ PackageManagerGui::PackageManagerGui(PackageManagerCore *core, QWidget *parent)
#ifndef Q_OS_MACOS
setWindowIcon(QIcon(m_core->settings().installerWindowIcon()));
#endif
- if (!m_core->settings().wizardShowPageList())
- setPixmap(QWizard::BackgroundPixmap, m_core->settings().background());
+ if (!m_core->settings().wizardShowPageList()) {
+ QString pixmapStr = m_core->settings().background();
+ QInstaller::replaceHighDpiImage(pixmapStr);
+ setPixmap(QWizard::BackgroundPixmap, pixmapStr);
+ }
#ifdef Q_OS_LINUX
setWizardStyle(QWizard::ModernStyle);
setSizeGripEnabled(true);
@@ -316,7 +321,7 @@ PackageManagerGui::PackageManagerGui(PackageManagerCore *core, QWidget *parent)
QFile sheet(styleSheetFile);
if (sheet.exists()) {
if (sheet.open(QIODevice::ReadOnly)) {
- setStyleSheet(QString::fromLatin1(sheet.readAll()));
+ qApp->setStyleSheet(QString::fromLatin1(sheet.readAll()));
} else {
qCWarning(QInstaller::lcDeveloperBuild) << "The specified style sheet file "
"can not be opened.";
@@ -344,11 +349,13 @@ PackageManagerGui::PackageManagerGui(PackageManagerCore *core, QWidget *parent)
m_pageListWidget->setFocusPolicy(Qt::NoFocus);
m_pageListWidget->setSelectionMode(QAbstractItemView::NoSelection);
m_pageListWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
+ m_pageListWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
QVBoxLayout *sideWidgetLayout = new QVBoxLayout(sideWidget);
- const QString pageListPixmap = m_core->settings().pageListPixmap();
+ QString pageListPixmap = m_core->settings().pageListPixmap();
if (!pageListPixmap.isEmpty()) {
+ QInstaller::replaceHighDpiImage(pageListPixmap);
QLabel *pageListPixmapLabel = new QLabel(sideWidget);
pageListPixmapLabel->setObjectName(QLatin1String("PageListPixmapLabel"));
pageListPixmapLabel->setPixmap(pageListPixmap);
@@ -420,7 +427,12 @@ PackageManagerGui::PackageManagerGui(PackageManagerCore *core, QWidget *parent)
*/
void PackageManagerGui::setMaxSize()
{
- setMaximumSize(qApp->desktop()->availableGeometry(this).size());
+ QSize size = qApp->desktop()->availableGeometry(this).size();
+ int windowFrameHeight = frameGeometry().height() - geometry().height();
+ int availableHeight = size.height() - windowFrameHeight;
+
+ size.setHeight(availableHeight);
+ setMaximumSize(size);
}
/*!
@@ -458,7 +470,7 @@ void PackageManagerGui::updatePageListWidget()
itemText.replace(regExp2, QLatin1String("\\1 \\2"));
}
QListWidgetItem *item = new QListWidgetItem(itemText, m_pageListWidget);
- item->setSizeHint(QSize(item->sizeHint().width(), 30));
+ item->setSizeHint(QSize(m_pageListWidget->width(), 30));
// Give visual indication about current & non-visited pages
if (id == d->m_currentId) {
@@ -738,7 +750,16 @@ void PackageManagerGui::showEvent(QShowEvent *event)
}
}
}
- setMinimumSize(size());
+ QSize minimumSize;
+ minimumSize.setWidth(m_core->settings().wizardMinimumWidth()
+ ? m_core->settings().wizardMinimumWidth()
+ : width());
+
+ minimumSize.setHeight(m_core->settings().wizardMinimumHeight()
+ ? m_core->settings().wizardMinimumHeight()
+ : height());
+
+ setMinimumSize(minimumSize);
if (minimumWidth() < m_core->settings().wizardDefaultWidth())
resize(m_core->settings().wizardDefaultWidth(), height());
if (minimumHeight() < m_core->settings().wizardDefaultHeight())
@@ -925,7 +946,7 @@ void PackageManagerGui::cancelButtonClicked()
interrupt = true;
question = tr("Do you want to cancel the installation process?");
if (m_core->isUninstaller())
- question = tr("Do you want to cancel the uninstallation process?");
+ question = tr("Do you want to cancel the removal process?");
} else {
question = tr("Do you want to quit the installer application?");
if (m_core->isUninstaller())
@@ -995,6 +1016,8 @@ void PackageManagerGui::showSettingsButton(bool show)
d->m_showSettingsButton = show;
setOption(QWizard::HaveCustomButton1, show);
setButtonText(QWizard::CustomButton1, tr("Settings"));
+ button(QWizard::CustomButton1)->setToolTip(
+ PackageManagerGui::tr("Specify proxy settings and configure repositories for add-on components."));
updateButtonLayout();
}
@@ -1188,10 +1211,10 @@ PackageManagerPage::PackageManagerPage(PackageManagerCore *core)
m_titleColor = m_core->settings().titleColor();
if (!m_core->settings().wizardShowPageList())
- setPixmap(QWizard::WatermarkPixmap, watermarkPixmap());
+ setPixmap(QWizard::WatermarkPixmap, wizardPixmap(scWatermark));
- setPixmap(QWizard::BannerPixmap, bannerPixmap());
- setPixmap(QWizard::LogoPixmap, logoPixmap());
+ setPixmap(QWizard::BannerPixmap, wizardPixmap(scBanner));
+ setPixmap(QWizard::LogoPixmap, wizardPixmap(scLogo));
// Can't use PackageManagerPage::gui() here as the page is not set yet
if (PackageManagerGui *gui = qobject_cast<PackageManagerGui *>(core->guiObject())) {
@@ -1209,39 +1232,26 @@ PackageManagerCore *PackageManagerPage::packageManagerCore() const
}
/*!
- Returns the watermark pixmap specified in the \c <Watermark> element of the package information
- file.
+ Returns the pixmap specified by \a pixmapType. \a pixmapType can be \c <Banner>,
+ \c <Logo> or \c <Watermark> element of the package information file. If @2x image
+ is provided, returns that instead for high DPI displays.
*/
-QPixmap PackageManagerPage::watermarkPixmap() const
+QPixmap PackageManagerPage::wizardPixmap(const QString &pixmapType) const
{
- return QPixmap(m_core->value(QLatin1String("WatermarkPixmap")));
-}
-
-/*!
- Returns the banner pixmap specified in the \c <Banner> element of the package information file.
- Only used by the modern UI style.
-*/
-QPixmap PackageManagerPage::bannerPixmap() const
-{
- QPixmap banner(m_core->value(QLatin1String("BannerPixmap")));
-
- if (!banner.isNull()) {
- int width;
- if (m_core->settings().containsValue(QLatin1String("WizardDefaultWidth")) )
- width = m_core->settings().wizardDefaultWidth();
- else
- width = size().width();
- banner = banner.scaledToWidth(width, Qt::SmoothTransformation);
+ QString pixmapStr = m_core->value(pixmapType);
+ QInstaller::replaceHighDpiImage(pixmapStr);
+ QPixmap pixmap(pixmapStr);
+ if (pixmapType == scBanner) {
+ if (!pixmap.isNull()) {
+ int width;
+ if (m_core->settings().containsValue(QLatin1String("WizardDefaultWidth")) )
+ width = m_core->settings().wizardDefaultWidth();
+ else
+ width = size().width();
+ pixmap = pixmap.scaledToWidth(width, Qt::SmoothTransformation);
+ }
}
- return banner;
-}
-
-/*!
- Returns the logo pixmap specified in the \c <Logo> element of the package information file.
-*/
-QPixmap PackageManagerPage::logoPixmap() const
-{
- return QPixmap(m_core->value(QLatin1String("LogoPixmap")));
+ return pixmap;
}
/*!
@@ -1536,9 +1546,6 @@ int IntroductionPage::nextId() const
if (packageManagerCore()->isUninstaller())
return PackageManagerCore::ReadyForInstallation;
- if (packageManagerCore()->isMaintainer())
- return PackageManagerCore::ComponentSelection;
-
return PackageManagerPage::nextId();
}
@@ -1615,6 +1622,10 @@ bool IntroductionPage::validatePage()
error = QLatin1String("<font color=\"red\">") + error + tr(" Only local package "
"management available.") + QLatin1String("</font>");
}
+ } else if (core->status() == PackageManagerCore::ForceUpdate) {
+ // replaces the error string from packagemanagercore
+ error = tr("There is an important update available. Please select '%1' first")
+ .arg(m_updateComponents->text().remove(QLatin1Char('&')));
}
setErrorMessage(error);
}
@@ -1763,6 +1774,8 @@ void IntroductionPage::setUpdater(bool value)
gui()->showSettingsButton(true);
packageManagerCore()->setUpdater();
emit packageManagerCoreTypeChanged();
+
+ gui()->updatePageListWidget();
}
}
@@ -1773,6 +1786,8 @@ void IntroductionPage::setUninstaller(bool value)
gui()->showSettingsButton(false);
packageManagerCore()->setUninstaller();
emit packageManagerCoreTypeChanged();
+
+ gui()->updatePageListWidget();
}
}
@@ -1783,6 +1798,8 @@ void IntroductionPage::setPackageManager(bool value)
gui()->showSettingsButton(true);
packageManagerCore()->setPackageManager();
emit packageManagerCoreTypeChanged();
+
+ gui()->updatePageListWidget();
}
}
/*!
@@ -1925,7 +1942,6 @@ LicenseAgreementPage::LicenseAgreementPage(PackageManagerCore *core)
m_licenseListWidget = new QListWidget(this);
m_licenseListWidget->setObjectName(QLatin1String("LicenseListWidget"));
- m_licenseListWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
connect(m_licenseListWidget, &QListWidget::currentItemChanged,
this, &LicenseAgreementPage::currentItemChanged);
@@ -1934,20 +1950,23 @@ LicenseAgreementPage::LicenseAgreementPage(PackageManagerCore *core)
m_textBrowser->setOpenLinks(false);
m_textBrowser->setOpenExternalLinks(true);
m_textBrowser->setObjectName(QLatin1String("LicenseTextBrowser"));
- m_textBrowser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
connect(m_textBrowser, &QTextBrowser::anchorClicked, this, &LicenseAgreementPage::openLicenseUrl);
- QVBoxLayout *licenseBoxLayout = new QVBoxLayout();
- licenseBoxLayout->addWidget(m_licenseListWidget);
- licenseBoxLayout->addWidget(m_textBrowser);
+ QSplitter *licenseSplitter = new QSplitter(this);
+ licenseSplitter->setOrientation(Qt::Vertical);
+ licenseSplitter->setChildrenCollapsible(false);
+ licenseSplitter->addWidget(m_licenseListWidget);
+ licenseSplitter->addWidget(m_textBrowser);
+ licenseSplitter->setStretchFactor(0, 1);
+ licenseSplitter->setStretchFactor(1, 3);
QVBoxLayout *layout = new QVBoxLayout(this);
- layout->addLayout(licenseBoxLayout);
+ layout->addWidget(licenseSplitter);
- m_acceptRadioButton = new QRadioButton(this);
- m_acceptRadioButton->setShortcut(QKeySequence(tr("Alt+A", "agree license")));
- m_acceptRadioButton->setObjectName(QLatin1String("AcceptLicenseRadioButton"));
- ClickForwarder *acceptClickForwarder = new ClickForwarder(m_acceptRadioButton);
+ m_acceptCheckBox = new QCheckBox(this);
+ m_acceptCheckBox->setShortcut(QKeySequence(tr("Alt+A", "Agree license")));
+ m_acceptCheckBox->setObjectName(QLatin1String("AcceptLicenseCheckBox"));
+ ClickForwarder *acceptClickForwarder = new ClickForwarder(m_acceptCheckBox);
m_acceptLabel = new QLabel;
m_acceptLabel->setWordWrap(true);
@@ -1955,29 +1974,13 @@ LicenseAgreementPage::LicenseAgreementPage(PackageManagerCore *core)
m_acceptLabel->setObjectName(QLatin1String("AcceptLicenseLabel"));
m_acceptLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
- m_rejectRadioButton = new QRadioButton(this);
- ClickForwarder *rejectClickForwarder = new ClickForwarder(m_rejectRadioButton);
- m_rejectRadioButton->setObjectName(QString::fromUtf8("RejectLicenseRadioButton"));
- m_rejectRadioButton->setShortcut(QKeySequence(tr("Alt+D", "do not agree license")));
-
- m_rejectLabel = new QLabel;
- m_rejectLabel->setWordWrap(true);
- m_rejectLabel->installEventFilter(rejectClickForwarder);
- m_rejectLabel->setObjectName(QLatin1String("RejectLicenseLabel"));
- m_rejectLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
-
QGridLayout *gridLayout = new QGridLayout;
gridLayout->setColumnStretch(1, 1);
- gridLayout->addWidget(m_acceptRadioButton, 0, 0);
+ gridLayout->addWidget(m_acceptCheckBox, 0, 0);
gridLayout->addWidget(m_acceptLabel, 0, 1);
- gridLayout->addWidget(m_rejectRadioButton, 1, 0);
- gridLayout->addWidget(m_rejectLabel, 1, 1);
layout->addLayout(gridLayout);
- connect(m_acceptRadioButton, &QAbstractButton::toggled, this, &QWizardPage::completeChanged);
- connect(m_rejectRadioButton, &QAbstractButton::toggled, this, &QWizardPage::completeChanged);
-
- m_rejectRadioButton->setChecked(true);
+ connect(m_acceptCheckBox, &QAbstractButton::toggled, this, &QWizardPage::completeChanged);
}
/*!
@@ -1992,7 +1995,9 @@ void LicenseAgreementPage::entering()
packageManagerCore()->calculateComponentsToInstall();
foreach (QInstaller::Component *component, packageManagerCore()->orderedComponentsToInstall())
- addLicenseItem(component->licenses());
+ packageManagerCore()->addLicenseItem(component->licenses());
+
+ createLicenseWidgets();
const int licenseCount = m_licenseListWidget->count();
if (licenseCount > 0) {
@@ -2000,6 +2005,8 @@ void LicenseAgreementPage::entering()
m_licenseListWidget->setCurrentItem(m_licenseListWidget->item(0));
}
+ packageManagerCore()->clearLicenses();
+
updateUi();
}
@@ -2009,7 +2016,7 @@ void LicenseAgreementPage::entering()
*/
bool LicenseAgreementPage::isComplete() const
{
- return m_acceptRadioButton->isChecked();
+ return m_acceptCheckBox->isChecked();
}
void LicenseAgreementPage::openLicenseUrl(const QUrl &url)
@@ -2023,12 +2030,22 @@ void LicenseAgreementPage::currentItemChanged(QListWidgetItem *current)
m_textBrowser->setText(current->data(Qt::UserRole).toString());
}
-void LicenseAgreementPage::addLicenseItem(const QHash<QString, QPair<QString, QString> > &hash)
+void LicenseAgreementPage::createLicenseWidgets()
{
- for (QHash<QString, QPair<QString, QString> >::const_iterator it = hash.begin();
- it != hash.end(); ++it) {
- QListWidgetItem *item = new QListWidgetItem(it.key(), m_licenseListWidget);
- item->setData(Qt::UserRole, it.value().second);
+ QHash<QString, QMap<QString, QString>> priorityHash = packageManagerCore()->sortedLicenses();
+
+ QStringList priorities = priorityHash.keys();
+ priorities.sort();
+
+ for (int i = priorities.length() - 1; i >= 0; --i) {
+ QString priority = priorities.at(i);
+ QMap<QString, QString> licenses = priorityHash.value(priority);
+ QStringList licenseNames = licenses.keys();
+ licenseNames.sort(Qt::CaseInsensitive);
+ for (QString licenseName : licenseNames) {
+ QListWidgetItem *item = new QListWidgetItem(licenseName, m_licenseListWidget);
+ item->setData(Qt::UserRole, licenses.value(licenseName));
+ }
}
}
@@ -2036,23 +2053,19 @@ void LicenseAgreementPage::updateUi()
{
QString subTitleText;
QString acceptButtonText;
- QString rejectButtonText;
if (m_licenseListWidget->count() == 1) {
subTitleText = tr("Please read the following license agreement. You must accept the terms "
"contained in this agreement before continuing with the installation.");
acceptButtonText = tr("I accept the license.");
- rejectButtonText = tr("I do not accept the license.");
} else {
subTitleText = tr("Please read the following license agreements. You must accept the terms "
"contained in these agreements before continuing with the installation.");
acceptButtonText = tr("I accept the licenses.");
- rejectButtonText = tr("I do not accept the licenses.");
}
m_licenseListWidget->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
setColoredSubTitle(subTitleText);
m_acceptLabel->setText(acceptButtonText);
- m_rejectLabel->setText(rejectButtonText);
}
@@ -2233,7 +2246,16 @@ bool ComponentSelectionPage::isComplete() const
{
if (packageManagerCore()->isInstaller() || packageManagerCore()->isUpdater())
return d->m_currentModel->checked().count();
- return d->m_currentModel->checkedState().testFlag(ComponentModel::DefaultChecked) == false;
+
+ if (d->m_currentModel->checkedState().testFlag(ComponentModel::DefaultChecked) == false)
+ return true;
+
+ const QSet<Component *> uncheckable = d->m_currentModel->uncheckable();
+ for (auto &component : uncheckable) {
+ if (component->forcedInstallation() && !component->isInstalled())
+ return true; // allow installation for new forced components
+ }
+ return false;
}
@@ -2295,8 +2317,9 @@ TargetDirectoryPage::TargetDirectoryPage(PackageManagerCore *core)
QPushButton *browseButton = new QPushButton(this);
browseButton->setObjectName(QLatin1String("BrowseDirectoryButton"));
connect(browseButton, &QAbstractButton::clicked, this, &TargetDirectoryPage::dirRequested);
- browseButton->setShortcut(QKeySequence(tr("Alt+R", "browse file system to choose a file")));
+ browseButton->setShortcut(QKeySequence(tr("Alt+R", "Browse file system to choose a file")));
browseButton->setText(tr("B&rowse..."));
+ browseButton->setToolTip(TargetDirectoryPage::tr("Browse file system to choose the installation directory."));
hlayout->addWidget(browseButton);
layout->addLayout(hlayout);
@@ -2369,7 +2392,7 @@ bool TargetDirectoryPage::validatePage()
if (!QVariant(remove).toBool())
return true;
- return this->packageManagerCore()->checkTargetDir(targetDir());
+ return this->packageManagerCore()->installationAllowedToDirectory(targetDir());
}
/*!
@@ -2581,7 +2604,7 @@ void ReadyForInstallationPage::entering()
QString htmlOutput;
bool componentsOk = packageManagerCore()->calculateComponents(&htmlOutput);
m_taskDetailsBrowser->setHtml(htmlOutput);
- m_taskDetailsBrowser->setVisible(!componentsOk || isVerbose());
+ m_taskDetailsBrowser->setVisible(!componentsOk || LoggingHandler::instance().isVerbose());
setComplete(componentsOk);
QString spaceInfo;
@@ -2722,7 +2745,7 @@ void PerformInstallationPage::entering()
if (packageManagerCore()->settings().productImages().count() > 1)
m_imageChangeTimer.start();
- if (isVerbose()) {
+ if (LoggingHandler::instance().isVerbose()) {
m_performInstallationForm->toggleDetails();
}
if (packageManagerCore()->isUninstaller()) {
@@ -2809,7 +2832,6 @@ void PerformInstallationPage::installationFinished()
{
m_performInstallationForm->stopUpdateProgress();
if (!isAutoSwitching()) {
- m_performInstallationForm->scrollDetailsToTheEnd();
m_performInstallationForm->setDetailsButtonEnabled(false);
setComplete(true);
@@ -2927,7 +2949,8 @@ void FinishedPage::entering()
connect(m_commitButton, &QAbstractButton::clicked, this, &FinishedPage::handleFinishClicked);
}
- if (packageManagerCore()->status() == PackageManagerCore::Success) {
+ if (packageManagerCore()->status() == PackageManagerCore::Success
+ || packageManagerCore()->status() == PackageManagerCore::EssentialUpdated) {
const QString finishedText = packageManagerCore()->value(QLatin1String("FinishedText"));
if (!finishedText.isEmpty())
m_msgLabel->setText(finishedText);
diff --git a/src/libs/installer/packagemanagergui.h b/src/libs/installer/packagemanagergui.h
index fc0718be4..ce8bcfd66 100644
--- a/src/libs/installer/packagemanagergui.h
+++ b/src/libs/installer/packagemanagergui.h
@@ -152,10 +152,8 @@ public:
explicit PackageManagerPage(PackageManagerCore *core);
virtual ~PackageManagerPage() {}
- virtual QPixmap logoPixmap() const;
virtual QString productName() const;
- virtual QPixmap watermarkPixmap() const;
- virtual QPixmap bannerPixmap() const;
+ virtual QPixmap wizardPixmap(const QString &pixmapType) const;
void setColoredTitle(const QString &title);
void setColoredSubTitle(const QString &subTitle);
@@ -296,18 +294,15 @@ private Q_SLOTS:
void currentItemChanged(QListWidgetItem *current);
private:
- void addLicenseItem(const QHash<QString, QPair<QString, QString> > &hash);
+ void createLicenseWidgets();
void updateUi();
private:
QTextBrowser *m_textBrowser;
QListWidget *m_licenseListWidget;
- QRadioButton *m_acceptRadioButton;
- QRadioButton *m_rejectRadioButton;
-
+ QCheckBox *m_acceptCheckBox;
QLabel *m_acceptLabel;
- QLabel *m_rejectLabel;
};
diff --git a/src/libs/installer/performinstallationform.cpp b/src/libs/installer/performinstallationform.cpp
index f60ef0ab1..a61c0d88b 100644
--- a/src/libs/installer/performinstallationform.cpp
+++ b/src/libs/installer/performinstallationform.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -28,7 +28,6 @@
#include "performinstallationform.h"
-#include "lazyplaintextedit.h"
#include "progresscoordinator.h"
#include "globals.h"
@@ -81,6 +80,7 @@ PerformInstallationForm::PerformInstallationForm(QObject *parent)
: QObject(parent)
, m_progressBar(nullptr)
, m_progressLabel(nullptr)
+ , m_downloadStatus(nullptr)
, m_productImagesScrollArea(nullptr)
, m_productImagesLabel(nullptr)
, m_detailsButton(nullptr)
@@ -121,6 +121,8 @@ void PerformInstallationForm::setupUi(QWidget *widget)
m_downloadStatus = new QLabel(widget);
m_downloadStatus->setObjectName(QLatin1String("DownloadStatus"));
m_downloadStatus->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
+ m_downloadStatus->setWordWrap(true);
+ m_downloadStatus->setTextFormat(Qt::TextFormat::RichText);
topLayout->addWidget(m_downloadStatus);
connect(ProgressCoordinator::instance(), &ProgressCoordinator::downloadStatusChanged, this,
&PerformInstallationForm::onDownloadStatusChanged);
@@ -147,7 +149,7 @@ void PerformInstallationForm::setupUi(QWidget *widget)
m_productImagesScrollArea->setWidget(m_productImagesLabel);
bottomLayout->addWidget(m_productImagesScrollArea);
- m_detailsBrowser = new LazyPlainTextEdit(widget);
+ m_detailsBrowser = new QTextEdit(widget);
m_detailsBrowser->setReadOnly(true);
m_detailsBrowser->setWordWrapMode(QTextOption::NoWrap);
m_detailsBrowser->setObjectName(QLatin1String("DetailsBrowser"));
@@ -269,14 +271,6 @@ void PerformInstallationForm::setDetailsButtonEnabled(bool enable)
}
/*!
- Scrolls to the bottom of the details browser.
-*/
-void PerformInstallationForm::scrollDetailsToTheEnd()
-{
- m_detailsBrowser->updateCursor(LazyPlainTextEdit::TextCursorPosition::ForceEnd);
-}
-
-/*!
Returns \c true if the details browser is visible.
*/
bool PerformInstallationForm::isShowingDetails() const
@@ -290,8 +284,7 @@ bool PerformInstallationForm::isShowingDetails() const
*/
void PerformInstallationForm::onDownloadStatusChanged(const QString &status)
{
- m_downloadStatus->setText(m_downloadStatus->fontMetrics().elidedText(status, Qt::ElideRight,
- m_downloadStatus->width()));
+ m_downloadStatus->setText(status);
}
/*!
diff --git a/src/libs/installer/performinstallationform.h b/src/libs/installer/performinstallationform.h
index 20b193857..d67f6ac4b 100644
--- a/src/libs/installer/performinstallationform.h
+++ b/src/libs/installer/performinstallationform.h
@@ -32,6 +32,7 @@
#include "aspectratiolabel.h"
#include <QObject>
+#include <QTextEdit>
QT_BEGIN_NAMESPACE
class QLabel;
@@ -43,7 +44,6 @@ class QWinTaskbarButton;
class QScrollArea;
QT_END_NAMESPACE
-class LazyPlainTextEdit;
namespace QInstaller {
@@ -60,7 +60,6 @@ public:
void startUpdateProgress();
void stopUpdateProgress();
void setDetailsButtonEnabled(bool enable);
- void scrollDetailsToTheEnd();
bool isShowingDetails() const;
signals:
@@ -81,7 +80,7 @@ private:
QScrollArea *m_productImagesScrollArea;
AspectRatioLabel *m_productImagesLabel;
QPushButton *m_detailsButton;
- LazyPlainTextEdit *m_detailsBrowser;
+ QTextEdit *m_detailsBrowser;
QTimer *m_updateTimer;
#ifdef Q_OS_WIN
diff --git a/src/libs/installer/progresscoordinator.cpp b/src/libs/installer/progresscoordinator.cpp
index 7af24cb77..01b92bff8 100644
--- a/src/libs/installer/progresscoordinator.cpp
+++ b/src/libs/installer/progresscoordinator.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -35,6 +35,7 @@
#include "globals.h"
#include "utils.h"
+#include "loggingutils.h"
using namespace QInstaller;
@@ -312,7 +313,7 @@ void ProgressCoordinator::emitDownloadStatus(const QString &status)
void ProgressCoordinator::printProgressPercentage(int progress)
{
- if (!isVerbose())
+ if (!LoggingHandler::instance().isVerbose())
return;
Q_ASSERT(m_progressSpinner->currentIndex < m_progressSpinner->spinnerChars.size());
diff --git a/src/libs/installer/protocol.cpp b/src/libs/installer/protocol.cpp
index 3bafc481e..fb16086e5 100644
--- a/src/libs/installer/protocol.cpp
+++ b/src/libs/installer/protocol.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -35,19 +35,19 @@ typedef qint32 PackageSize;
/*!
\inmodule QtInstallerFramework
- \namespace Protocol
+ \namespace QInstaller::Protocol
\brief Contains values related to the internal client-server connection protocol.
*/
/*!
- \enum Protocol::Mode
+ \enum QInstaller::Protocol::Mode
\value Debug
\value Production
*/
/*!
- \enum Protocol::StartAs
+ \enum QInstaller::Protocol::StartAs
\value User
\value SuperUser
diff --git a/src/libs/installer/protocol.h b/src/libs/installer/protocol.h
index 9ead7943c..65241e00b 100644
--- a/src/libs/installer/protocol.h
+++ b/src/libs/installer/protocol.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -163,6 +163,31 @@ const char QAbstractFileEngineSyncToDisk[] = "QAbstractFileEngine::syncToDisk";
const char QAbstractFileEngineRenameOverwrite[] = "QAbstractFileEngine::renameOverwrite";
const char QAbstractFileEngineFileTime[] = "QAbstractFileEngine::fileTime";
+
+// LibArchiveWrapper
+const char AbstractArchive[] = "AbstractArchive";
+const char AbstractArchiveOpen[] = "AbstractArchive::open";
+const char AbstractArchiveClose[] = "AbstractArchive::close";
+const char AbstractArchiveSetFilename[] = "AbstractArchive::setFilename";
+const char AbstractArchiveErrorString[] = "AbstractArchive::errorString";
+const char AbstractArchiveExtract[] = "AbstractArchive::extract";
+const char AbstractArchiveCreate[] = "AbstractArchive::create";
+const char AbstractArchiveList[] = "AbstractArchive::list";
+const char AbstractArchiveIsSupported[] = "AbstractArchive::isSupported";
+const char AbstractArchiveSetCompressionLevel[] = "AbstractArchive::setCompressionLevel";
+const char AbstractArchiveAddDataBlock[] = "AbstractArchive::addDataBlock";
+const char AbstractArchiveSetClientDataAtEnd[] = "AbstractArchive::setClientDataAtEnd";
+const char AbstractArchiveSetFilePosition[] = "AbstractArchive::setFilePosition";
+const char AbstractArchiveWorkerStatus[] = "AbstractArchive::workerStatus";
+const char AbstractArchiveCancel[] = "AbstractArchive::cancel";
+
+const char GetAbstractArchiveSignals[] = "GetAbstractArchiveSignals";
+const char AbstractArchiveSignalCurrentEntryChanged[] = "AbstractArchive::currentEntryChanged";
+const char AbstractArchiveSignalCompletedChanged[] = "AbstractArchive::completedChanged";
+const char AbstractArchiveSignalDataBlockRequested[] = "AbstractArchive::dataBlockRequested";
+const char AbstractArchiveSignalSeekRequested[] = "AbstractArchive::seekRequested";
+const char AbstractArchiveSignalWorkerFinished[] = "AbstractArchive::workerFinished";
+
} // namespace Protocol
void INSTALLER_EXPORT sendPacket(QIODevice *device, const QByteArray &command, const QByteArray &data);
diff --git a/src/libs/installer/proxycredentialsdialog.cpp b/src/libs/installer/proxycredentialsdialog.cpp
index f536a05cd..2ac07b855 100644
--- a/src/libs/installer/proxycredentialsdialog.cpp
+++ b/src/libs/installer/proxycredentialsdialog.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -34,7 +34,7 @@ namespace QInstaller {
/*!
\inmodule QtInstallerFramework
- \namespace Ui
+ \namespace QInstaller::Ui
\brief Groups user interface forms generated with Qt Designer.
*/
diff --git a/src/libs/installer/qsettingswrapper.cpp b/src/libs/installer/qsettingswrapper.cpp
index 56db28d78..f57750bc1 100644
--- a/src/libs/installer/qsettingswrapper.cpp
+++ b/src/libs/installer/qsettingswrapper.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -74,9 +74,9 @@ public:
Private(const QString &fileName, QSettings::Format format)
: m_filename(fileName)
+ , m_format(format)
, settings(fileName, format)
{
- m_format = format;
m_scope = settings.scope();
m_application = settings.applicationName();
m_organization = settings.organizationName();
diff --git a/src/libs/installer/qtpatch.cpp b/src/libs/installer/qtpatch.cpp
index 89b5dddf6..f72e67867 100644
--- a/src/libs/installer/qtpatch.cpp
+++ b/src/libs/installer/qtpatch.cpp
@@ -82,14 +82,16 @@ QHash<QString, QByteArray> QtPatch::qmakeValues(const QString &qmakePath, QByteA
process.start(qmake.absoluteFilePath(), args, QIODevice::ReadOnly);
if (process.waitForFinished(10000)) {
QByteArray output = process.readAllStandardOutput();
- qmakeOutput->append(output);
- if (process.exitStatus() == QProcess::CrashExit) {
- qCWarning(QInstaller::lcInstallerInstallLog) << qmake.absoluteFilePath() << args
- << "crashed with exit code" << process.exitCode()
- << "standard output:" << output
- << "error output:" << process.readAllStandardError();
+ if ((process.exitStatus() == QProcess::CrashExit) || (process.exitCode() != EXIT_SUCCESS)) {
+ QStringList detailedOutput = { qmake.absoluteFilePath() + QLatin1Char(' ') + args.join(QLatin1Char(' '))
+ + QString::fromLatin1(" returned with exit code: \"%1\".").arg(QString::number(process.exitCode()))
+ , QString::fromLatin1("Standard output: \"%1\".").arg(QLatin1String(output))
+ , QString::fromLatin1("Error output: \"%1\".").arg(QLatin1String(process.readAllStandardError()))
+ };
+ qmakeOutput->append(detailedOutput.join(QLatin1Char('\n')));
return qmakeValueHash;
}
+ qmakeOutput->append(output);
qmakeValueHash = readQmakeOutput(output);
}
if (qmakeValueHash.isEmpty()) {
diff --git a/src/libs/installer/registerfiletypeoperation.cpp b/src/libs/installer/registerfiletypeoperation.cpp
index 0a7c2eaa5..c1ee7a038 100644
--- a/src/libs/installer/registerfiletypeoperation.cpp
+++ b/src/libs/installer/registerfiletypeoperation.cpp
@@ -97,7 +97,7 @@ bool RegisterFileTypeOperation::performOperation()
ensureOptionalArgumentsRead();
if (!checkArgumentCount(2, 5, QString::fromLatin1("<extension> <command> [description [contentType [icon]]]")))
return false;
- QStringList args = arguments();
+ QStringList args = parsePerformOperationArguments();
bool allUsers = false;
PackageManagerCore *const core = packageManager();
@@ -153,8 +153,10 @@ bool RegisterFileTypeOperation::undoOperation()
{
#ifdef Q_OS_WIN
ensureOptionalArgumentsRead();
- QStringList args = arguments();
+ if (parseUndoOperationArguments().count() > 0)
+ return true;
+ QStringList args = arguments();
if (!checkArgumentCount(2, 5, tr("Register File Type: Invalid arguments")))
return false;
diff --git a/src/libs/installer/remoteclient_p.h b/src/libs/installer/remoteclient_p.h
index ba60bc7ac..e1809e0af 100644
--- a/src/libs/installer/remoteclient_p.h
+++ b/src/libs/installer/remoteclient_p.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -39,7 +39,8 @@
#include "constants.h"
#include <QCoreApplication>
-#include <QElapsedTimer>
+#include <QDeadlineTimer>
+#include <QTimer>
#include <QMutex>
#include <QThread>
@@ -133,8 +134,8 @@ public:
QCoreApplication::translate("RemoteClient", "Cannot get authorization."),
QCoreApplication::translate("RemoteClient",
"Cannot get authorization that is needed for continuing the installation.\n\n"
- "Please start the setup program as a user with the appropriate rights.\n"
- "Or accept the elevation of access rights if being asked."),
+ "Please start the setup program as a user with the appropriate rights,\n"
+ "or accept the elevation of access rights if being asked."),
QMessageBox::Abort | QMessageBox::Retry, QMessageBox::Abort);
if (res == QMessageBox::Retry)
started = AdminAuthorization::execute(0, m_serverCommand, m_serverArguments);
@@ -165,11 +166,19 @@ public:
}
if (started) {
- QElapsedTimer t;
- t.start();
+ QTimer timer;
+ QEventLoop loop;
// 30 seconds waiting ought to be enough for the app to start
- while ((!m_serverStarted) && (t.elapsed() < 30000))
+ QDeadlineTimer deadline(30000);
+
+ connect(&timer, &QTimer::timeout, [&]() {
m_serverStarted = authorize();
+ if (m_serverStarted || deadline.hasExpired())
+ loop.quit();
+ });
+
+ timer.start(100);
+ loop.exec();
}
}
diff --git a/src/libs/installer/remoteobject.cpp b/src/libs/installer/remoteobject.cpp
index 1c521cdf3..f6f0f2bfe 100644
--- a/src/libs/installer/remoteobject.cpp
+++ b/src/libs/installer/remoteobject.cpp
@@ -30,6 +30,7 @@
#include "protocol.h"
#include "remoteclient.h"
+#include "globals.h"
#include <QCoreApplication>
#include <QElapsedTimer>
@@ -57,8 +58,20 @@ RemoteObject::~RemoteObject()
{
if (m_socket) {
if (QThread::currentThread() == m_socket->thread()) {
- if (m_type != QLatin1String("RemoteClientPrivate"))
+ if (m_type != QLatin1String("RemoteClientPrivate")) {
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.
+ QEventLoop loop;
+ connect(m_socket, &QLocalSocket::bytesWritten, &loop, &QEventLoop::quit);
+ loop.exec();
+ }
+ if (!m_socket->waitForDisconnected()) {
+ qCWarning(lcServer) << "Error while disconnecting from remote server:"
+ << m_socket->error();
+ }
+ }
} else {
Q_ASSERT_X(false, Q_FUNC_INFO, "Socket running in a different Thread than this object.");
}
diff --git a/src/libs/installer/remoteserverconnection.cpp b/src/libs/installer/remoteserverconnection.cpp
index a6b66c081..9e141ea48 100644
--- a/src/libs/installer/remoteserverconnection.cpp
+++ b/src/libs/installer/remoteserverconnection.cpp
@@ -34,6 +34,9 @@
#include "utils.h"
#include "permissionsettings.h"
#include "globals.h"
+#ifdef IFW_LIBARCHIVE
+#include "libarchivearchive.h"
+#endif
#include <QCoreApplication>
#include <QDataStream>
@@ -59,8 +62,10 @@ RemoteServerConnection::RemoteServerConnection(qintptr socketDescriptor, const Q
, m_socketDescriptor(socketDescriptor)
, m_process(nullptr)
, m_engine(nullptr)
+ , m_archive(nullptr)
, m_authorizationKey(key)
- , m_signalReceiver(nullptr)
+ , m_processSignalReceiver(nullptr)
+ , m_archiveSignalReceiver(nullptr)
{
setObjectName(QString::fromLatin1("RemoteServerConnection(%1)").arg(socketDescriptor));
}
@@ -89,6 +94,7 @@ void RemoteServerConnection::run()
if (!receivePacket(&socket, &cmd, &data)) {
socket.waitForReadyRead(250);
+ qApp->processEvents();
continue;
}
@@ -142,11 +148,20 @@ void RemoteServerConnection::run()
if (m_process)
m_process->deleteLater();
m_process = new QProcess;
- m_signalReceiver = new QProcessSignalReceiver(m_process);
+ m_processSignalReceiver = new QProcessSignalReceiver(m_process);
} else if (type == QLatin1String(Protocol::QAbstractFileEngine)) {
if (m_engine)
delete m_engine;
m_engine = new QFSFileEngine;
+ } else if (type == QLatin1String(Protocol::AbstractArchive)) {
+#ifdef IFW_LIBARCHIVE
+ if (m_archive)
+ m_archive->deleteLater();
+ m_archive = new LibArchiveArchive;
+ m_archiveSignalReceiver = new AbstractArchiveSignalReceiver(static_cast<LibArchiveArchive *>(m_archive));
+#else
+ Q_ASSERT_X(false, Q_FUNC_INFO, "No compatible archive handler exists for protocol.");
+#endif
}
continue;
}
@@ -157,24 +172,44 @@ void RemoteServerConnection::run()
if (type == QLatin1String(Protocol::QSettings)) {
settings.reset();
} else if (type == QLatin1String(Protocol::QProcess)) {
- m_signalReceiver->m_receivedSignals.clear();
+ m_processSignalReceiver->m_receivedSignals.clear();
m_process->deleteLater();
m_process = nullptr;
} else if (type == QLatin1String(Protocol::QAbstractFileEngine)) {
delete m_engine;
m_engine = nullptr;
+ } else if (type == QLatin1String(Protocol::AbstractArchive)) {
+#ifdef IFW_LIBARCHIVE
+ m_archiveSignalReceiver->m_receivedSignals.clear();
+ m_archive->deleteLater();
+ m_archive = nullptr;
+#else
+ Q_ASSERT_X(false, Q_FUNC_INFO, "No compatible archive handler exists for protocol.");
+#endif
}
return;
}
if (command == QLatin1String(Protocol::GetQProcessSignals)) {
- if (m_signalReceiver) {
- QMutexLocker _(&m_signalReceiver->m_lock);
- sendData(&socket, m_signalReceiver->m_receivedSignals);
+ if (m_processSignalReceiver) {
+ QMutexLocker _(&m_processSignalReceiver->m_lock);
+ sendData(&socket, m_processSignalReceiver->m_receivedSignals);
socket.flush();
- m_signalReceiver->m_receivedSignals.clear();
+ m_processSignalReceiver->m_receivedSignals.clear();
}
continue;
+ } else if (command == QLatin1String(Protocol::GetAbstractArchiveSignals)) {
+#ifdef IFW_LIBARCHIVE
+ if (m_archiveSignalReceiver) {
+ QMutexLocker _(&m_archiveSignalReceiver->m_lock);
+ sendData(&socket, m_archiveSignalReceiver->m_receivedSignals);
+ socket.flush();
+ m_archiveSignalReceiver->m_receivedSignals.clear();
+ }
+ continue;
+#else
+ Q_ASSERT_X(false, Q_FUNC_INFO, "No compatible archive handler exists for protocol.");
+#endif
}
if (command.startsWith(QLatin1String(Protocol::QProcess))) {
@@ -183,6 +218,8 @@ void RemoteServerConnection::run()
handleQSettings(&socket, command, stream, settings.data());
} else if (command.startsWith(QLatin1String(Protocol::QAbstractFileEngine))) {
handleQFSFileEngine(&socket, command, stream);
+ } else if (command.startsWith(QLatin1String(Protocol::AbstractArchive))) {
+ handleArchive(&socket, command, stream);
} else {
qCDebug(QInstaller::lcServer) << "Unknown command:" << command;
}
@@ -519,4 +556,60 @@ void RemoteServerConnection::handleQFSFileEngine(QIODevice *socket, const QStrin
}
}
+void RemoteServerConnection::handleArchive(QIODevice *socket, const QString &command, QDataStream &data)
+{
+#ifdef IFW_LIBARCHIVE
+ LibArchiveArchive *archive = static_cast<LibArchiveArchive *>(m_archive);
+ if (command == QLatin1String(Protocol::AbstractArchiveOpen)) {
+ qint32 openMode;
+ data >> openMode;
+ sendData(socket, archive->open(static_cast<QIODevice::OpenMode>(openMode)));
+ } else if (command == QLatin1String(Protocol::AbstractArchiveClose)) {
+ archive->close();
+ } else if (command == QLatin1String(Protocol::AbstractArchiveSetFilename)) {
+ QString fileName;
+ data >> fileName;
+ archive->setFilename(fileName);
+ } else if (command == QLatin1String(Protocol::AbstractArchiveErrorString)) {
+ sendData(socket, archive->errorString());
+ } else if (command == QLatin1String(Protocol::AbstractArchiveExtract)) {
+ QString dirPath;
+ quint64 total;
+ data >> dirPath;
+ data >> total;
+ archive->workerExtract(dirPath, total);
+ } else if (command == QLatin1String(Protocol::AbstractArchiveCreate)) {
+ QStringList entries;
+ data >> entries;
+ sendData(socket, archive->create(entries));
+ } else if (command == QLatin1String(Protocol::AbstractArchiveList)) {
+ sendData(socket, archive->list());
+ } else if (command == QLatin1String(Protocol::AbstractArchiveIsSupported)) {
+ sendData(socket, archive->isSupported());
+ } else if (command == QLatin1String(Protocol::AbstractArchiveSetCompressionLevel)) {
+ qint32 level;
+ data >> level;
+ archive->setCompressionLevel(static_cast<AbstractArchive::CompressionLevel>(level));
+ } else if (command == QLatin1String(Protocol::AbstractArchiveAddDataBlock)) {
+ QByteArray buff;
+ data >> buff;
+ archive->workerAddDataBlock(buff);
+ } else if (command == QLatin1String(Protocol::AbstractArchiveSetClientDataAtEnd)) {
+ archive->workerSetDataAtEnd();
+ } else if (command == QLatin1String(Protocol::AbstractArchiveSetFilePosition)) {
+ qint64 pos;
+ data >> pos;
+ archive->workerSetFilePosition(pos);
+ } else if (command == QLatin1String(Protocol::AbstractArchiveWorkerStatus)) {
+ sendData(socket, static_cast<qint32>(archive->workerStatus()));
+ } else if (command == QLatin1String(Protocol::AbstractArchiveCancel)) {
+ archive->workerCancel();
+ } else if (!command.isEmpty()) {
+ qCDebug(QInstaller::lcServer) << "Unknown AbstractArchive command:" << command;
+ }
+#else
+ Q_ASSERT_X(false, Q_FUNC_INFO, "No compatible archive handler exists for protocol.");
+#endif
+}
+
} // namespace QInstaller
diff --git a/src/libs/installer/remoteserverconnection.h b/src/libs/installer/remoteserverconnection.h
index 07cfb98c0..ccb8e153d 100644
--- a/src/libs/installer/remoteserverconnection.h
+++ b/src/libs/installer/remoteserverconnection.h
@@ -29,6 +29,8 @@
#ifndef REMOTESERVERCONNECTION_H
#define REMOTESERVERCONNECTION_H
+#include "abstractarchive.h"
+
#include <QPointer>
#include <QThread>
@@ -44,6 +46,7 @@ namespace QInstaller {
class PermissionSettings;
class QProcessSignalReceiver;
+class AbstractArchiveSignalReceiver;
class RemoteServerConnection : public QThread
{
@@ -66,14 +69,17 @@ private:
void handleQSettings(QIODevice *device, const QString &command, QDataStream &data,
PermissionSettings *settings);
void handleQFSFileEngine(QIODevice *device, const QString &command, QDataStream &data);
+ void handleArchive(QIODevice *device, const QString &command, QDataStream &data);
private:
qintptr m_socketDescriptor;
QProcess *m_process;
QFSFileEngine *m_engine;
+ AbstractArchive *m_archive;
QString m_authorizationKey;
- QProcessSignalReceiver *m_signalReceiver;
+ QProcessSignalReceiver *m_processSignalReceiver;
+ AbstractArchiveSignalReceiver *m_archiveSignalReceiver;
};
} // namespace QInstaller
diff --git a/src/libs/installer/remoteserverconnection_p.h b/src/libs/installer/remoteserverconnection_p.h
index dad5f4133..977a64711 100644
--- a/src/libs/installer/remoteserverconnection_p.h
+++ b/src/libs/installer/remoteserverconnection_p.h
@@ -30,6 +30,9 @@
#define REMOTESERVERCONNECTION_P_H
#include "protocol.h"
+#ifdef IFW_LIBARCHIVE
+#include "libarchivearchive.h"
+#endif
#include <QMutex>
#include <QProcess>
@@ -124,6 +127,70 @@ private:
QVariantList m_receivedSignals;
};
+#ifdef IFW_LIBARCHIVE
+class AbstractArchiveSignalReceiver : public QObject
+{
+ Q_OBJECT
+ friend class RemoteServerConnection;
+
+private:
+ explicit AbstractArchiveSignalReceiver(LibArchiveArchive *archive)
+ : QObject(archive)
+ {
+ connect(archive, &LibArchiveArchive::currentEntryChanged,
+ this, &AbstractArchiveSignalReceiver::onCurrentEntryChanged);
+ connect(archive, &LibArchiveArchive::completedChanged,
+ this, &AbstractArchiveSignalReceiver::onCompletedChanged);
+ connect(archive, &LibArchiveArchive::dataBlockRequested,
+ this, &AbstractArchiveSignalReceiver::onDataBlockRequested);
+ connect(archive, &LibArchiveArchive::seekRequested,
+ this, &AbstractArchiveSignalReceiver::onSeekRequested);
+ connect(archive, &LibArchiveArchive::workerFinished,
+ this, &AbstractArchiveSignalReceiver::onWorkerFinished);
+ }
+
+private Q_SLOTS:
+ void onCurrentEntryChanged(const QString &filename)
+ {
+ QMutexLocker _(&m_lock);
+ m_receivedSignals.append(QLatin1String(Protocol::AbstractArchiveSignalCurrentEntryChanged));
+ m_receivedSignals.append(filename);
+ }
+
+ void onCompletedChanged(quint64 completed, quint64 total)
+ {
+ QMutexLocker _(&m_lock);
+ m_receivedSignals.append(QLatin1String(Protocol::AbstractArchiveSignalCompletedChanged));
+ m_receivedSignals.append(completed);
+ m_receivedSignals.append(total);
+ }
+
+ void onDataBlockRequested()
+ {
+ QMutexLocker _(&m_lock);
+ m_receivedSignals.append(QLatin1String(Protocol::AbstractArchiveSignalDataBlockRequested));
+ }
+
+ void onSeekRequested(qint64 offset, int whence)
+ {
+ QMutexLocker _(&m_lock);
+ m_receivedSignals.append(QLatin1String(Protocol::AbstractArchiveSignalSeekRequested));
+ m_receivedSignals.append(offset);
+ m_receivedSignals.append(whence);
+ }
+
+ void onWorkerFinished()
+ {
+ QMutexLocker _(&m_lock);
+ m_receivedSignals.append(QLatin1String(Protocol::AbstractArchiveSignalWorkerFinished));
+ }
+
+private:
+ QMutex m_lock;
+ QVariantList m_receivedSignals;
+};
+#endif
+
} // namespace QInstaller
#endif // REMOTESERVERCONNECTION_P_H
diff --git a/src/libs/installer/repository.cpp b/src/libs/installer/repository.cpp
index 3957ce5aa..50f3eceb6 100644
--- a/src/libs/installer/repository.cpp
+++ b/src/libs/installer/repository.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -32,6 +32,13 @@
#include <QDataStream>
#include <QFileInfo>
#include <QStringList>
+#include <QDir>
+
+/*!
+ \fn inline uint QInstaller::qHash(const Repository &repository)
+
+ Returns a hash of the \a repository.
+*/
namespace QInstaller {
@@ -83,7 +90,7 @@ Repository::Repository(const QUrl &url, bool isDefault, bool compressed)
*/
Repository Repository::fromUserInput(const QString &repositoryUrl, bool compressed)
{
- QUrl url = QUrl::fromUserInput(repositoryUrl);
+ QUrl url = QUrl::fromUserInput(repositoryUrl, QDir::currentPath());
const QStringList supportedSchemes = KDUpdater::FileDownloaderFactory::supportedSchemes();
if (!supportedSchemes.contains(url.scheme()) && QFileInfo(url.toString()).exists())
url = QLatin1String("file:///") + url.toString();
@@ -225,14 +232,6 @@ bool Repository::isCompressed() const
}
/*!
- Sets this repository to \a compressed state to know weather the repository
- needs to be uncompressed before use.
-*/
-void Repository::setCompressed(bool compressed)
-{
- m_compressed = compressed;
-}
-/*!
Compares the values of this repository to \a other and returns true if they are equal (same server,
default state, enabled state as well as username and password). \sa operator!=()
*/
diff --git a/src/libs/installer/repository.h b/src/libs/installer/repository.h
index 546cddc97..3f28e4d99 100644
--- a/src/libs/installer/repository.h
+++ b/src/libs/installer/repository.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -68,7 +68,6 @@ public:
void setCategoryName(const QString &categoryname);
bool isCompressed() const;
- void setCompressed(bool compressed);
bool operator==(const Repository &other) const;
bool operator!=(const Repository &other) const;
diff --git a/src/libs/installer/repositorycategory.cpp b/src/libs/installer/repositorycategory.cpp
index 89f88c1d7..e651b7f0c 100644
--- a/src/libs/installer/repositorycategory.cpp
+++ b/src/libs/installer/repositorycategory.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2018 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.
@@ -34,6 +34,12 @@
#include <QFileInfo>
#include <QStringList>
+/*!
+ \fn inline uint QInstaller::qHash(const RepositoryCategory &repository)
+
+ Returns a hash of the repository category \a repository.
+*/
+
namespace QInstaller {
@@ -122,7 +128,7 @@ void RepositoryCategory::setRepositories(const QSet<Repository> repositories, co
m_data.remove(scRepositories);
foreach (const Repository &repository, repositories)
- m_data.insertMulti(scRepositories, QVariant().fromValue(repository));
+ m_data.insert(scRepositories, QVariant().fromValue(repository));
}
/*!
@@ -130,7 +136,7 @@ void RepositoryCategory::setRepositories(const QSet<Repository> repositories, co
*/
void RepositoryCategory::addRepository(const Repository &repository)
{
- m_data.insertMulti(scRepositories, QVariant().fromValue(repository));
+ m_data.insert(scRepositories, QVariant().fromValue(repository));
}
/*!
diff --git a/src/libs/installer/repositorycategory.h b/src/libs/installer/repositorycategory.h
index 51dc3ddfb..dc45527eb 100644
--- a/src/libs/installer/repositorycategory.h
+++ b/src/libs/installer/repositorycategory.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2018 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.
@@ -69,7 +69,7 @@ public:
friend INSTALLER_EXPORT QDataStream &operator<<(QDataStream &ostream, const RepositoryCategory &repository);
private:
- QVariantHash m_data;
+ QMultiHash<QString, QVariant> m_data;
QString m_displayname;
QString m_tooltip;
bool m_enabled;
diff --git a/src/libs/installer/scriptengine.cpp b/src/libs/installer/scriptengine.cpp
index 4d062c9cb..c8fc65d34 100644
--- a/src/libs/installer/scriptengine.cpp
+++ b/src/libs/installer/scriptengine.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -31,6 +31,7 @@
#include "errors.h"
#include "scriptengine_p.h"
#include "systeminfo.h"
+#include "loggingutils.h"
#include <QMetaEnum>
#include <QQmlEngine>
@@ -74,10 +75,10 @@ namespace QInstaller {
\internal
*/
-QJSValue InstallerProxy::components() const
+QJSValue InstallerProxy::components(const QString &regexp) const
{
if (m_core) {
- const QList<Component*> all = m_core->components(PackageManagerCore::ComponentType::All);
+ const QList<Component*> all = m_core->components(PackageManagerCore::ComponentType::All, regexp);
QJSValue scriptComponentsObject = m_engine->newArray(all.count());
for (int i = 0; i < all.count(); ++i) {
Component *const component = all.at(i);
@@ -344,7 +345,7 @@ QString QFileDialogProxy::getExistingFileOrDirectory(const QString &caption,
.arg(identifier, selectedDirectoryOrFile);
selectedDirectoryOrFile = QString();
}
- } else {
+ } else if (!LoggingHandler::instance().outputRedirected()) {
qDebug().nospace().noquote() << identifier << ": " << caption << ": ";
QTextStream stream(stdin);
stream.readLineInto(&selectedDirectoryOrFile);
@@ -358,6 +359,12 @@ QString QFileDialogProxy::getExistingFileOrDirectory(const QString &caption,
.arg(selectedDirectoryOrFile);
selectedDirectoryOrFile = QString();
}
+ } else {
+ qCDebug(QInstaller::lcInstallerInstallLog).nospace()
+ << "No answer available for "<< identifier << ": " << caption;
+
+ throw Error(tr("User input is required but the output "
+ "device is not associated with a terminal."));
}
if (!errorString.isEmpty())
qCWarning(QInstaller::lcInstallerInstallLog).nospace() << errorString;
@@ -372,6 +379,7 @@ ScriptEngine::ScriptEngine(PackageManagerCore *core) :
QObject(core),
m_guiProxy(new GuiProxy(this, this))
{
+ m_engine.installExtensions(QJSEngine::TranslationExtension);
QJSValue global = m_engine.globalObject();
global.setProperty(QLatin1String("console"), m_engine.newQObject(new ConsoleProxy));
global.setProperty(QLatin1String("QFileDialog"), m_engine.newQObject(new QFileDialogProxy(core)));
@@ -379,12 +387,6 @@ ScriptEngine::ScriptEngine(PackageManagerCore *core) :
global.setProperty(QLatin1String("InstallerProxy"), proxy);
global.setProperty(QLatin1String("print"), m_engine.newQObject(new ConsoleProxy)
.property(QLatin1String("log")));
-#if QT_VERSION < 0x050400
- global.setProperty(QLatin1String("qsTr"), m_engine.newQObject(new QCoreApplicationProxy)
- .property(QStringLiteral("qsTr")));
-#else
- m_engine.installTranslatorFunctions();
-#endif
global.setProperty(QLatin1String("systemInfo"), m_engine.newQObject(new SystemInfo));
global.setProperty(QLatin1String("QInstaller"), generateQInstallerObject());
diff --git a/src/libs/installer/scriptengine_p.h b/src/libs/installer/scriptengine_p.h
index bf8240d98..e5c39663c 100644
--- a/src/libs/installer/scriptengine_p.h
+++ b/src/libs/installer/scriptengine_p.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -63,7 +63,7 @@ public:
: m_engine(engine), m_core(core) {}
public slots:
- QJSValue components() const;
+ QJSValue components(const QString &regexp = QString()) const;
QJSValue componentByName(const QString &componentName);
private:
@@ -125,24 +125,6 @@ private:
ScriptEngine *m_engine;
};
-#if QT_VERSION < 0x050400
-class QCoreApplicationProxy : public QObject
-{
- Q_OBJECT
- Q_DISABLE_COPY(QCoreApplicationProxy)
-
-public:
- QCoreApplicationProxy() {}
-
-public slots:
- QString qsTr(const QString &text = QString(), const QString &disambiguation = QString(), int n = -1) const
- {
- return QCoreApplication::translate(QCoreApplication::applicationName().toUtf8().constData(),
- text.toUtf8().constData(), disambiguation.toUtf8().constData(), n);
- }
-};
-#endif
-
class GuiProxy : public QObject
{
Q_OBJECT
@@ -198,8 +180,5 @@ Q_DECLARE_METATYPE(QInstaller::ConsoleProxy*)
Q_DECLARE_METATYPE(QInstaller::InstallerProxy*)
Q_DECLARE_METATYPE(QInstaller::QFileDialogProxy*)
Q_DECLARE_METATYPE(QInstaller::QDesktopServicesProxy*)
-#if QT_VERSION < 0x050400
-Q_DECLARE_METATYPE(QInstaller::QCoreApplicationProxy*)
-#endif
#endif // SCRIPTENGINE_H
diff --git a/src/libs/installer/selfrestartoperation.cpp b/src/libs/installer/selfrestartoperation.cpp
index 8e53b8201..360dc60b2 100644
--- a/src/libs/installer/selfrestartoperation.cpp
+++ b/src/libs/installer/selfrestartoperation.cpp
@@ -61,7 +61,7 @@ bool SelfRestartOperation::performOperation()
if (!core->isMaintainer()) {
setError(UserDefinedError);
- setErrorString(tr("Self Restart: Only valid within updater or packagemanager mode."));
+ setErrorString(tr("Self Restart: Only valid within updater or package manager mode."));
return false;
}
diff --git a/src/libs/installer/settings.cpp b/src/libs/installer/settings.cpp
index aeacf7a8e..0b94e57a6 100644
--- a/src/libs/installer/settings.cpp
+++ b/src/libs/installer/settings.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 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.
@@ -32,6 +32,7 @@
#include "repository.h"
#include "repositorycategory.h"
#include "globals.h"
+#include "fileutils.h"
#include <QtCore/QFileInfo>
#include <QtCore/QStringList>
@@ -58,20 +59,13 @@ using namespace QInstaller;
static const QLatin1String scInstallerApplicationIcon("InstallerApplicationIcon");
static const QLatin1String scInstallerWindowIcon("InstallerWindowIcon");
-static const QLatin1String scLogo("Logo");
static const QLatin1String scPrefix("Prefix");
-static const QLatin1String scWatermark("Watermark");
-static const QLatin1String scBanner("Banner");
static const QLatin1String scProductUrl("ProductUrl");
-static const QLatin1String scBackground("Background");
-static const QLatin1String scPageListPixmap("PageListPixmap");
static const QLatin1String scAdminTargetDir("AdminTargetDir");
static const QLatin1String scMaintenanceToolName("MaintenanceToolName");
static const QLatin1String scUserRepositories("UserRepositories");
static const QLatin1String scTmpRepositories("TemporaryRepositories");
static const QLatin1String scMaintenanceToolIniFile("MaintenanceToolIniFile");
-static const QLatin1String scRemoteRepositories("RemoteRepositories");
-static const QLatin1String scRepositoryCategories("RepositoryCategories");
static const QLatin1String scDependsOnLocalInstallerBinary("DependsOnLocalInstallerBinary");
static const QLatin1String scTranslations("Translations");
static const QLatin1String scCreateLocalRepository("CreateLocalRepository");
@@ -138,7 +132,7 @@ static QStringList readArgumentAttributes(QXmlStreamReader &reader, Settings::Pa
if (reader.isWhitespace())
continue;
arguments.append(reader.text().toString().split(QRegularExpression(QLatin1String("\\s+")),
- QString::SkipEmptyParts));
+ Qt::SkipEmptyParts));
}
break;
case QXmlStreamReader::EndElement: {
@@ -235,7 +229,7 @@ public:
: m_replacementRepos(false)
{}
- QVariantHash m_data;
+ QMultiHash<QString, QVariant> m_data;
bool m_replacementRepos;
QString absolutePathFromKey(const QString &key, const QString &suffix = QString()) const
@@ -305,14 +299,15 @@ Settings Settings::fromFileAndPrefix(const QString &path, const QString &prefix,
<< scAllowSpaceInPath << scAllowNonAsciiCharacters << scDisableAuthorizationFallback
<< scDisableCommandLineInterface
<< scWizardStyle << scStyleSheet << scTitleColor
- << scWizardDefaultWidth << scWizardDefaultHeight << scWizardShowPageList << scProductImages
+ << scWizardDefaultWidth << scWizardDefaultHeight << scWizardMinimumWidth << scWizardMinimumHeight
+ << scWizardShowPageList << scProductImages
<< scRepositorySettingsPageVisible << scTargetConfigurationFile
<< scRemoteRepositories << scTranslations << scUrlQueryString << QLatin1String(scControlScript)
<< scCreateLocalRepository << scInstallActionColumnVisible << scSupportsModify << scAllowUnstableComponents
<< scSaveDefaultRepositories << scRepositoryCategories;
Settings s;
- s.d->m_data.insert(scPrefix, prefix);
+ s.d->m_data.replace(scPrefix, prefix);
while (reader.readNextStartElement()) {
const QString name = reader.name().toString();
if (!elementList.contains(name))
@@ -327,7 +322,7 @@ Settings Settings::fromFileAndPrefix(const QString &path, const QString &prefix,
reader.raiseError(QString::fromLatin1("Element \"%1\" has been defined before.").arg(name));
if (name == scTranslations) {
- s.setTranslations(readArgumentAttributes(reader, parseMode, QLatin1String("Translation"), true));
+ s.setTranslations(readArgumentAttributes(reader, parseMode, QLatin1String("Translation"), false));
} else if (name == scRunProgramArguments) {
s.setRunProgramArguments(readArgumentAttributes(reader, parseMode, QLatin1String("Argument")));
} else if (name == scProductImages) {
@@ -341,7 +336,7 @@ Settings Settings::fromFileAndPrefix(const QString &path, const QString &prefix,
s.setRepositoryCategoryDisplayName(repositoryCategoryName);
}
} else {
- s.d->m_data.insert(name, reader.readElementText(QXmlStreamReader::SkipChildElements));
+ s.d->m_data.replace(name, reader.readElementText(QXmlStreamReader::SkipChildElements));
}
}
if (reader.error() != QXmlStreamReader::NoError) {
@@ -356,39 +351,39 @@ Settings Settings::fromFileAndPrefix(const QString &path, const QString &prefix,
// Add some possible missing values
if (!s.d->m_data.contains(scInstallerApplicationIcon))
- s.d->m_data.insert(scInstallerApplicationIcon, QLatin1String(":/installer"));
+ s.d->m_data.replace(scInstallerApplicationIcon, QLatin1String(":/installer"));
if (!s.d->m_data.contains(scInstallerWindowIcon)) {
- s.d->m_data.insert(scInstallerWindowIcon,
+ s.d->m_data.replace(scInstallerWindowIcon,
QString(QLatin1String(":/installer") + s.systemIconSuffix()));
}
if (!s.d->m_data.contains(scRemoveTargetDir))
- s.d->m_data.insert(scRemoveTargetDir, scTrue);
+ s.d->m_data.replace(scRemoveTargetDir, scTrue);
if (s.d->m_data.value(scMaintenanceToolName).toString().isEmpty()) {
- s.d->m_data.insert(scMaintenanceToolName,
+ s.d->m_data.replace(scMaintenanceToolName,
// TODO: Remove deprecated 'UninstallerName'.
s.d->m_data.value(QLatin1String("UninstallerName"), QLatin1String("maintenancetool"))
.toString());
}
if (s.d->m_data.value(scTargetConfigurationFile).toString().isEmpty())
- s.d->m_data.insert(scTargetConfigurationFile, QLatin1String("components.xml"));
+ s.d->m_data.replace(scTargetConfigurationFile, QLatin1String("components.xml"));
if (s.d->m_data.value(scMaintenanceToolIniFile).toString().isEmpty()) {
- s.d->m_data.insert(scMaintenanceToolIniFile,
+ s.d->m_data.replace(scMaintenanceToolIniFile,
// TODO: Remove deprecated 'UninstallerIniFile'.
s.d->m_data.value(QLatin1String("UninstallerIniFile"), QString(s.maintenanceToolName()
+ QLatin1String(".ini"))).toString());
}
if (!s.d->m_data.contains(scDependsOnLocalInstallerBinary))
- s.d->m_data.insert(scDependsOnLocalInstallerBinary, false);
+ s.d->m_data.replace(scDependsOnLocalInstallerBinary, false);
if (!s.d->m_data.contains(scRepositorySettingsPageVisible))
- s.d->m_data.insert(scRepositorySettingsPageVisible, true);
+ s.d->m_data.replace(scRepositorySettingsPageVisible, true);
if (!s.d->m_data.contains(scCreateLocalRepository))
- s.d->m_data.insert(scCreateLocalRepository, false);
+ s.d->m_data.replace(scCreateLocalRepository, false);
if (!s.d->m_data.contains(scInstallActionColumnVisible))
- s.d->m_data.insert(scInstallActionColumnVisible, false);
+ s.d->m_data.replace(scInstallActionColumnVisible, false);
if (!s.d->m_data.contains(scAllowUnstableComponents))
- s.d->m_data.insert(scAllowUnstableComponents, false);
+ s.d->m_data.replace(scAllowUnstableComponents, false);
if (!s.d->m_data.contains(scSaveDefaultRepositories))
- s.d->m_data.insert(scSaveDefaultRepositories, true);
+ s.d->m_data.replace(scSaveDefaultRepositories, true);
return s;
}
@@ -487,6 +482,16 @@ int Settings::wizardDefaultHeight() const
return lengthToInt(d->m_data.value(scWizardDefaultHeight));
}
+int Settings::wizardMinimumWidth() const
+{
+ return lengthToInt(d->m_data.value(scWizardMinimumWidth));
+}
+
+int Settings::wizardMinimumHeight() const
+{
+ return lengthToInt(d->m_data.value(scWizardMinimumHeight));
+}
+
bool Settings::wizardShowPageList() const
{
return d->m_data.value(scWizardShowPageList, true).toBool();
@@ -497,10 +502,12 @@ QStringList Settings::productImages() const
const QVariant variant = d->m_data.value(scProductImages);
QStringList imagePaths;
if (variant.canConvert<QStringList>()) {
- foreach (const QString &imagePath, variant.value<QStringList>()) {
- QFileInfo(imagePath).isAbsolute()
- ? imagePaths.append(imagePath)
- : imagePaths.append(d->m_data.value(scPrefix).toString() + QLatin1Char('/') + imagePath);
+ foreach (auto image, variant.value<QStringList>()) {
+ QString imagePath = QFileInfo(image).isAbsolute()
+ ? image
+ : d->m_data.value(scPrefix).toString() + QLatin1Char('/') + image;
+ QInstaller::replaceHighDpiImage(imagePath);
+ imagePaths.append(imagePath);
}
}
return imagePaths;
@@ -508,7 +515,7 @@ QStringList Settings::productImages() const
void Settings::setProductImages(const QStringList &images)
{
- d->m_data.insert(scProductImages, images);
+ d->m_data.replace(scProductImages, images);
}
QString Settings::installerApplicationIcon() const
@@ -562,7 +569,7 @@ QStringList Settings::runProgramArguments() const
void Settings::setRunProgramArguments(const QStringList &arguments)
{
- d->m_data.insert(scRunProgramArguments, arguments);
+ d->m_data.replace(scRunProgramArguments, arguments);
}
@@ -669,7 +676,7 @@ void Settings::setDefaultRepositories(const QSet<Repository> &repositories)
void Settings::addDefaultRepositories(const QSet<Repository> &repositories)
{
foreach (const Repository &repository, repositories)
- d->m_data.insertMulti(scRepositories, QVariant().fromValue(repository));
+ d->m_data.insert(scRepositories, QVariant().fromValue(repository));
}
void Settings::setRepositoryCategories(const QSet<RepositoryCategory> &repositories)
@@ -681,7 +688,7 @@ void Settings::setRepositoryCategories(const QSet<RepositoryCategory> &repositor
void Settings::addRepositoryCategories(const QSet<RepositoryCategory> &repositories)
{
foreach (const RepositoryCategory &repository, repositories)
- d->m_data.insertMulti(scRepositoryCategories, QVariant().fromValue(repository));
+ d->m_data.insert(scRepositoryCategories, QVariant().fromValue(repository));
}
Settings::Update Settings::updateRepositoryCategories(const RepoHash &updates)
@@ -706,7 +713,7 @@ Settings::Update Settings::updateRepositoryCategories(const RepoHash &updates)
}
}
if (update) {
- categories = categoriesList.toSet();
+ categories = QSet<RepositoryCategory>(categoriesList.begin(), categoriesList.end());
setRepositoryCategories(categories);
}
return update ? Settings::UpdatesApplied : Settings::NoUpdatesApplied;
@@ -757,8 +764,10 @@ Settings::Update Settings::updateDefaultRepositories(const RepoHash &updates)
}
const bool updated = apply(updates, &defaultRepos);
- if (updated)
- setDefaultRepositories(defaultRepos.values().toSet());
+ if (updated) {
+ const QList<Repository> repositoriesList = defaultRepos.values();
+ setDefaultRepositories(QSet<Repository>(repositoriesList.begin(), repositoriesList.end()));
+ }
return updated ? Settings::UpdatesApplied : Settings::NoUpdatesApplied;
}
@@ -777,7 +786,7 @@ void Settings::addTemporaryRepositories(const QSet<Repository> &repositories, bo
{
d->m_replacementRepos = replace;
foreach (const Repository &repository, repositories)
- d->m_data.insertMulti(scTmpRepositories, QVariant().fromValue(repository));
+ d->m_data.insert(scTmpRepositories, QVariant().fromValue(repository));
}
QSet<Repository> Settings::userRepositories() const
@@ -794,7 +803,7 @@ void Settings::setUserRepositories(const QSet<Repository> &repositories)
void Settings::addUserRepositories(const QSet<Repository> &repositories)
{
foreach (const Repository &repository, repositories)
- d->m_data.insertMulti(scUserRepositories, QVariant().fromValue(repository));
+ d->m_data.insert(scUserRepositories, QVariant().fromValue(repository));
}
Settings::Update Settings::updateUserRepositories(const RepoHash &updates)
@@ -809,8 +818,10 @@ Settings::Update Settings::updateUserRepositories(const RepoHash &updates)
}
const bool updated = apply(updates, &reposToUpdate);
- if (updated)
- setUserRepositories(reposToUpdate.values().toSet());
+ if (updated) {
+ const QList<Repository> repositoriesList = reposToUpdate.values();
+ setUserRepositories(QSet<Repository>(repositoriesList.begin(), repositoriesList.end()));
+ }
return updated ? Settings::UpdatesApplied : Settings::NoUpdatesApplied;
}
@@ -837,7 +848,7 @@ bool Settings::repositorySettingsPageVisible() const
void Settings::setRepositorySettingsPageVisible(bool visible)
{
- d->m_data.insert(scRepositorySettingsPageVisible, visible);
+ d->m_data.replace(scRepositorySettingsPageVisible, visible);
}
Settings::ProxyType Settings::proxyType() const
@@ -847,7 +858,7 @@ Settings::ProxyType Settings::proxyType() const
void Settings::setProxyType(Settings::ProxyType type)
{
- d->m_data.insert(scProxyType, type);
+ d->m_data.replace(scProxyType, type);
}
QNetworkProxy Settings::ftpProxy() const
@@ -860,7 +871,7 @@ QNetworkProxy Settings::ftpProxy() const
void Settings::setFtpProxy(const QNetworkProxy &proxy)
{
- d->m_data.insert(scFtpProxy, QVariant::fromValue(proxy));
+ d->m_data.replace(scFtpProxy, QVariant::fromValue(proxy));
}
QNetworkProxy Settings::httpProxy() const
@@ -873,7 +884,7 @@ QNetworkProxy Settings::httpProxy() const
void Settings::setHttpProxy(const QNetworkProxy &proxy)
{
- d->m_data.insert(scHttpProxy, QVariant::fromValue(proxy));
+ d->m_data.replace(scHttpProxy, QVariant::fromValue(proxy));
}
QStringList Settings::translations() const
@@ -886,7 +897,7 @@ QStringList Settings::translations() const
void Settings::setTranslations(const QStringList &translations)
{
- d->m_data.insert(scTranslations, translations);
+ d->m_data.replace(scTranslations, translations);
}
QString Settings::controlScript() const
@@ -906,7 +917,7 @@ bool Settings::allowUnstableComponents() const
void Settings::setAllowUnstableComponents(bool allow)
{
- d->m_data.insert(scAllowUnstableComponents, allow);
+ d->m_data.replace(scAllowUnstableComponents, allow);
}
bool Settings::saveDefaultRepositories() const
@@ -916,7 +927,7 @@ bool Settings::saveDefaultRepositories() const
void Settings::setSaveDefaultRepositories(bool save)
{
- d->m_data.insert(scSaveDefaultRepositories, save);
+ d->m_data.replace(scSaveDefaultRepositories, save);
}
QString Settings::repositoryCategoryDisplayName() const
@@ -927,5 +938,5 @@ QString Settings::repositoryCategoryDisplayName() const
void Settings::setRepositoryCategoryDisplayName(const QString& name)
{
- d->m_data.insert(scRepositoryCategoryDisplayName, name);
+ d->m_data.replace(scRepositoryCategoryDisplayName, name);
}
diff --git a/src/libs/installer/settings.h b/src/libs/installer/settings.h
index 4227ae957..68a1592ff 100644
--- a/src/libs/installer/settings.h
+++ b/src/libs/installer/settings.h
@@ -89,6 +89,8 @@ public:
QString titleColor() const;
int wizardDefaultWidth() const;
int wizardDefaultHeight() const;
+ int wizardMinimumWidth() const;
+ int wizardMinimumHeight() const;
bool wizardShowPageList() const;
QStringList productImages() const;
void setProductImages(const QStringList &images);
diff --git a/src/libs/installer/settingsoperation.cpp b/src/libs/installer/settingsoperation.cpp
index 95ba5266d..c5fe8384c 100644
--- a/src/libs/installer/settingsoperation.cpp
+++ b/src/libs/installer/settingsoperation.cpp
@@ -82,7 +82,7 @@ bool SettingsOperation::checkArguments()
if (!possibleMethodValues.contains(method)) {
setError(InvalidArguments);
setErrorString(tr("Current method argument calling \"%1\" with arguments \"%2\" is not "
- "supported. Please use set, remove, add_array_value or remove_array_value.").arg(name(),
+ "supported. Please use set, remove, add_array_value, or remove_array_value.").arg(name(),
arguments().join(QLatin1String("; "))));
return false;
}
diff --git a/src/libs/installer/simplemovefileoperation.cpp b/src/libs/installer/simplemovefileoperation.cpp
index 80654b367..5f3000be0 100644
--- a/src/libs/installer/simplemovefileoperation.cpp
+++ b/src/libs/installer/simplemovefileoperation.cpp
@@ -93,6 +93,8 @@ bool SimpleMoveFileOperation::performOperation()
bool SimpleMoveFileOperation::undoOperation()
{
+ if (parseUndoOperationArguments().count() > 0)
+ return true;
const QString source = arguments().at(0);
const QString target = arguments().at(1);
diff --git a/src/libs/installer/sysinfo_win.cpp b/src/libs/installer/sysinfo_win.cpp
index 508ce7a6e..12ffbcdd7 100644
--- a/src/libs/installer/sysinfo_win.cpp
+++ b/src/libs/installer/sysinfo_win.cpp
@@ -109,7 +109,7 @@ QList<VolumeInfo> localVolumeInfosFromMountPoints(PTCHAR volumeGUID)
TCHAR volumeNames[MAX_PATH + 1] = { 0 };
if (GetVolumePathNamesForVolumeName(volumeGUID, volumeNames, MAX_PATH, &bufferSize)) {
QStringList mountedPaths = QString::fromWCharArray(volumeNames, bufferSize).split(QLatin1Char(char(0)),
- QString::SkipEmptyParts);
+ Qt::SkipEmptyParts);
foreach (const QString &mountedPath, mountedPaths) {
VolumeInfo info;
info.setMountPath(mountedPath);
diff --git a/src/libs/installer/testrepository.cpp b/src/libs/installer/testrepository.cpp
index 3bbf304e7..10c4360e6 100644
--- a/src/libs/installer/testrepository.cpp
+++ b/src/libs/installer/testrepository.cpp
@@ -33,6 +33,7 @@
#include "serverauthenticationdialog.h"
#include <QFile>
+#include <QRandomGenerator>
namespace QInstaller {
@@ -88,7 +89,7 @@ void TestRepository::doStart()
auth.setPassword(m_repository.password());
FileTaskItem item(m_repository.url().toString() + QLatin1String("/Updates.xml?") +
- QString::number(qrand() * qrand()));
+ QString::number(QRandomGenerator::global()->generate()));
item.insert(TaskRole::Authenticator, QVariant::fromValue(auth));
m_timer.start(10000);
diff --git a/src/libs/installer/uninstallercalculator.cpp b/src/libs/installer/uninstallercalculator.cpp
index 6fd63d499..10b44d80a 100644
--- a/src/libs/installer/uninstallercalculator.cpp
+++ b/src/libs/installer/uninstallercalculator.cpp
@@ -62,8 +62,9 @@ void UninstallerCalculator::appendComponentToUninstall(Component *component)
PackageManagerCore *core = component->packageManagerCore();
// remove all already resolved dependees
- QSet<Component *> dependees = core->dependees(component).toSet()
- .subtract(m_componentsToUninstall);
+ const QList<Component *> dependeesList = core->dependees(component);
+ QSet<Component *> dependees = QSet<Component *>(dependeesList.begin(),
+ dependeesList.end()).subtract(m_componentsToUninstall);
foreach (Component *dependee, dependees)
appendComponentToUninstall(dependee);
@@ -104,7 +105,7 @@ void UninstallerCalculator::appendComponentsToUninstall(const QList<Component*>
foreach (Component *c, m_installedComponents) {
const QString replaces = c->value(scReplaces);
const QStringList possibleNames = replaces.split(QInstaller::commaRegExp(),
- QString::SkipEmptyParts) << c->name();
+ Qt::SkipEmptyParts) << c->name();
foreach (const QString &possibleName, possibleNames) {
Component *cc = PackageManagerCore::componentByName(possibleName, m_installedComponents);
@@ -125,6 +126,37 @@ void UninstallerCalculator::appendComponentsToUninstall(const QList<Component*>
if (!autoDependOnList.isEmpty())
appendComponentsToUninstall(autoDependOnList);
+ else
+ continueAppendComponentsToUninstall();
+}
+
+void UninstallerCalculator::continueAppendComponentsToUninstall()
+{
+ QList<Component*> unneededVirtualList;
+ // Check for virtual components without dependees
+ foreach (Component *component, m_installedComponents) {
+ if (component->isInstalled() && component->isVirtual() && !m_componentsToUninstall.contains(component)) {
+ // Components with auto dependencies were handled in the previous step
+ if (!component->autoDependencies().isEmpty())
+ continue;
+ if (component->forcedInstallation())
+ continue;
+
+ bool required = false;
+ PackageManagerCore *core = component->packageManagerCore();
+ foreach (Component *dependee, core->dependees(component)) {
+ if (dependee->isInstalled() && !m_componentsToUninstall.contains(dependee)) {
+ required = true;
+ break;
+ }
+ }
+ if (!required)
+ unneededVirtualList.append(component);
+ }
+ }
+
+ if (!unneededVirtualList.isEmpty())
+ appendComponentsToUninstall(unneededVirtualList);
}
} // namespace QInstaller
diff --git a/src/libs/installer/uninstallercalculator.h b/src/libs/installer/uninstallercalculator.h
index cb4fe1ff6..a684c92cc 100644
--- a/src/libs/installer/uninstallercalculator.h
+++ b/src/libs/installer/uninstallercalculator.h
@@ -51,6 +51,7 @@ public:
private:
void appendComponentToUninstall(Component *component);
+ void continueAppendComponentsToUninstall();
QList<Component *> m_installedComponents;
QSet<Component *> m_componentsToUninstall;
diff --git a/src/libs/installer/utils.cpp b/src/libs/installer/utils.cpp
index e3a18100b..7506a13fe 100644
--- a/src/libs/installer/utils.cpp
+++ b/src/libs/installer/utils.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -52,24 +52,6 @@
#endif
/*!
- \inmodule QtInstallerFramework
- \class QInstaller::PlainVerboseWriterOutput
- \internal
-*/
-
-/*!
- \inmodule QtInstallerFramework
- \class QInstaller::VerboseWriterOutput
- \internal
-*/
-
-/*!
- \inmodule QtInstallerFramework
- \class QInstaller::VerboseWriter
- \internal
-*/
-
-/*!
\internal
*/
void QInstaller::uiDetachedWait(int ms)
@@ -151,37 +133,6 @@ QStringList QInstaller::localeCandidates(const QString &locale_)
return candidates;
}
-
-static uint verbLevel = 0;
-
-/*!
- Sets to verbose output if \a v is set to \c true. Calling this multiple
- times increases or decreases the verbosity level accordingly.
-*/
-void QInstaller::setVerbose(bool v)
-{
- if (v)
- verbLevel++;
- else if (verbLevel > 0)
- verbLevel--;
-}
-
-/*!
- Returns \c true if the installer is set to verbose output.
-*/
-bool QInstaller::isVerbose()
-{
- return verbLevel > 0 ? true : false;
-}
-
-/*!
- Returns the current verbosity level.
-*/
-uint QInstaller::verboseLevel()
-{
- return verbLevel;
-}
-
/*!
Returns a list of mutually exclusive options passed to the \a parser, if there is
at least one mutually exclusive pair of options set. Otherwise returns an empty
@@ -282,84 +233,6 @@ QString QInstaller::replaceWindowsEnvironmentVariables(const QString &str)
return res;
}
-QInstaller::VerboseWriter::VerboseWriter()
-{
- preFileBuffer.open(QIODevice::ReadWrite);
- stream.setDevice(&preFileBuffer);
- currentDateTimeAsString = QDateTime::currentDateTime().toString();
-}
-
-QInstaller::VerboseWriter::~VerboseWriter()
-{
- if (preFileBuffer.isOpen()) {
- PlainVerboseWriterOutput output;
- (void)flush(&output);
- }
-}
-
-bool QInstaller::VerboseWriter::flush(VerboseWriterOutput *output)
-{
- stream.flush();
- if (logFileName.isEmpty()) // binarycreator
- return true;
- if (!preFileBuffer.isOpen())
- return true;
- //if the installer installed nothing - there is no target directory - where the logfile can be saved
- if (!QFileInfo(logFileName).absoluteDir().exists())
- return true;
-
- QString logInfo;
- logInfo += QLatin1String("************************************* Invoked: ");
- logInfo += currentDateTimeAsString;
- logInfo += QLatin1String("\n");
-
- QBuffer buffer;
- buffer.open(QIODevice::WriteOnly);
- buffer.write(logInfo.toLocal8Bit());
- buffer.write(preFileBuffer.data());
- buffer.close();
-
- if (output->write(logFileName, QIODevice::ReadWrite | QIODevice::Append | QIODevice::Text, buffer.data())) {
- preFileBuffer.close();
- stream.setDevice(nullptr);
- return true;
- }
- return false;
-}
-
-void QInstaller::VerboseWriter::setFileName(const QString &fileName)
-{
- logFileName = fileName;
-}
-
-
-Q_GLOBAL_STATIC(QInstaller::VerboseWriter, verboseWriter)
-
-QInstaller::VerboseWriter *QInstaller::VerboseWriter::instance()
-{
- return verboseWriter();
-}
-
-void QInstaller::VerboseWriter::appendLine(const QString &msg)
-{
- stream << msg << endl;
-}
-
-QInstaller::VerboseWriterOutput::~VerboseWriterOutput()
-{
-}
-
-bool QInstaller::PlainVerboseWriterOutput::write(const QString &fileName, QIODevice::OpenMode openMode, const QByteArray &data)
-{
- QFile output(fileName);
- if (output.open(openMode)) {
- output.write(data);
- setDefaultFilePermissions(&output, DefaultFilePermissions::NonExecutable);
- return true;
- }
- return false;
-}
-
#ifdef Q_OS_WIN
// taken from qcoreapplication_p.h
template<typename Char>
@@ -478,12 +351,12 @@ static QString qt_create_commandline(const QString &program, const QStringList &
// as escaping the quote -- rather put the \ behind the quote: e.g.
// rather use "foo"\ than "foo\"
QString endQuote(QLatin1Char('\"'));
- int i = tmp.length();
- while (i > 0 && tmp.at(i - 1) == QLatin1Char('\\')) {
- --i;
+ int j = tmp.length();
+ while (j > 0 && tmp.at(j - 1) == QLatin1Char('\\')) {
+ --j;
endQuote += QLatin1Char('\\');
}
- args += QLatin1String(" \"") + tmp.left(i) + endQuote;
+ args += QLatin1String(" \"") + tmp.left(j) + endQuote;
} else {
args += QLatin1Char(' ') + tmp;
}
diff --git a/src/libs/installer/utils.h b/src/libs/installer/utils.h
index 055f5cc0f..a22acd879 100644
--- a/src/libs/installer/utils.h
+++ b/src/libs/installer/utils.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -45,6 +45,7 @@ class QIODevice;
QT_END_NAMESPACE
namespace QInstaller {
+
void INSTALLER_EXPORT uiDetachedWait(int ms);
bool INSTALLER_EXPORT startDetached(const QString &program, const QStringList &arguments,
const QString &workingDirectory, qint64 *pid = 0);
@@ -63,49 +64,9 @@ namespace QInstaller {
QStringList INSTALLER_EXPORT localeCandidates(const QString &locale);
- void INSTALLER_EXPORT setVerbose(bool v);
- bool INSTALLER_EXPORT isVerbose();
- uint INSTALLER_EXPORT verboseLevel();
-
QStringList INSTALLER_EXPORT checkMutualOptions(CommandLineParser &parser, const QStringList &options);
INSTALLER_EXPORT std::ostream& operator<<(std::ostream &os, const QString &string);
-
- class INSTALLER_EXPORT VerboseWriterOutput
- {
- public:
- virtual bool write(const QString &fileName, QIODevice::OpenMode openMode, const QByteArray &data) = 0;
-
- protected:
- ~VerboseWriterOutput();
- };
-
- class INSTALLER_EXPORT PlainVerboseWriterOutput : public VerboseWriterOutput
- {
- public:
- virtual bool write(const QString &fileName, QIODevice::OpenMode openMode, const QByteArray &data);
- };
-
- class INSTALLER_EXPORT VerboseWriter
- {
- public:
- VerboseWriter();
- ~VerboseWriter();
-
- static VerboseWriter *instance();
-
- bool flush(VerboseWriterOutput *output);
-
- void appendLine(const QString &msg);
- void setFileName(const QString &fileName);
-
- private:
- QTextStream stream;
- QBuffer preFileBuffer;
- QString logFileName;
- QString currentDateTimeAsString;
- };
-
}
#endif // QINSTALLER_UTILS_H