diff options
Diffstat (limited to 'src/libs/kdtools')
-rw-r--r-- | src/libs/kdtools/filedownloader.cpp | 96 | ||||
-rw-r--r-- | src/libs/kdtools/filedownloader.h | 6 | ||||
-rw-r--r-- | src/libs/kdtools/filedownloader_p.h | 69 | ||||
-rw-r--r-- | src/libs/kdtools/genericfactory.cpp | 26 | ||||
-rw-r--r-- | src/libs/kdtools/genericfactory.h | 21 | ||||
-rw-r--r-- | src/libs/kdtools/kdsysinfo_win.cpp | 14 | ||||
-rw-r--r-- | src/libs/kdtools/localpackagehub.cpp | 36 | ||||
-rw-r--r-- | src/libs/kdtools/localpackagehub.h | 12 | ||||
-rw-r--r-- | src/libs/kdtools/lockfile_win.cpp | 2 | ||||
-rw-r--r-- | src/libs/kdtools/sysinfo.cpp | 2 | ||||
-rw-r--r-- | src/libs/kdtools/sysinfo_x11.cpp | 12 | ||||
-rw-r--r-- | src/libs/kdtools/updatefinder.cpp | 414 | ||||
-rw-r--r-- | src/libs/kdtools/updatefinder.h | 94 | ||||
-rw-r--r-- | src/libs/kdtools/updateoperation.cpp | 179 | ||||
-rw-r--r-- | src/libs/kdtools/updateoperation.h | 19 | ||||
-rw-r--r-- | src/libs/kdtools/updateoperations.cpp | 65 | ||||
-rw-r--r-- | src/libs/kdtools/updateoperations.h | 63 | ||||
-rw-r--r-- | src/libs/kdtools/updatesinfo.cpp | 256 | ||||
-rw-r--r-- | src/libs/kdtools/updatesinfo_p.h | 6 | ||||
-rw-r--r-- | src/libs/kdtools/updatesinfodata_p.h | 14 |
20 files changed, 860 insertions, 546 deletions
diff --git a/src/libs/kdtools/filedownloader.cpp b/src/libs/kdtools/filedownloader.cpp index a9f5040f0..4d4002b7a 100644 --- a/src/libs/kdtools/filedownloader.cpp +++ b/src/libs/kdtools/filedownloader.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -657,14 +658,6 @@ void KDUpdater::FileDownloader::addCheckSumData(const QByteArray &data) } /*! - Adds the \a length of characters of \a data to the cryptographic hash of the downloaded file. -*/ -void KDUpdater::FileDownloader::addCheckSumData(const char *data, int length) -{ - d->m_hash.addData(data, length); -} - -/*! Resets SHA-1 checksum data of the downloaded file. */ void KDUpdater::FileDownloader::resetCheckSumData() @@ -732,6 +725,14 @@ void KDUpdater::FileDownloader::setIgnoreSslErrors(bool ignore) d->m_ignoreSslErrors = ignore; } +/*! + Returns the number of received bytes. +*/ +qint64 FileDownloader::getBytesReceived() const +{ + return d->m_bytesReceived; +} + // -- KDUpdater::LocalFileDownloader /*! @@ -912,7 +913,7 @@ void KDUpdater::LocalFileDownloader::timerEvent(QTimerEvent *event) toWrite -= numWritten; } addSample(numRead); - addCheckSumData(buffer.data(), numRead); + addCheckSumData(buffer.left(numRead)); if (numRead > 0) { setProgress(d->source->pos(), d->source->size()); emit downloadProgress(calcProgress(d->source->pos(), d->source->size())); @@ -1104,7 +1105,7 @@ void KDUpdater::ResourceFileDownloader::timerEvent(QTimerEvent *event) const qint64 numRead = d->destFile.read(buffer.data(), buffer.size()); addSample(numRead); - addCheckSumData(buffer.data(), numRead); + addCheckSumData(buffer.left(numRead)); if (numRead > 0) { setProgress(d->destFile.pos(), d->destFile.size()); @@ -1183,9 +1184,8 @@ struct KDUpdater::HttpDownloader::Private disconnect(http, &QNetworkReply::finished, q, &HttpDownloader::httpReqFinished); disconnect(http, &QNetworkReply::downloadProgress, q, &HttpDownloader::httpReadProgress); - void (QNetworkReply::*errorSignal)(QNetworkReply::NetworkError) = &QNetworkReply::error; - disconnect(http, errorSignal, q, &HttpDownloader::httpError); + disconnect(http, &QNetworkReply::errorOccurred, q, &HttpDownloader::httpError); http->deleteLater(); } http = 0; @@ -1211,8 +1211,14 @@ KDUpdater::HttpDownloader::HttpDownloader(QObject *parent) #endif connect(&d->manager, &QNetworkAccessManager::authenticationRequired, this, &HttpDownloader::onAuthenticationRequired); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) connect(&d->manager, &QNetworkAccessManager::networkAccessibleChanged, this, &HttpDownloader::onNetworkAccessibleChanged); +#else + auto netInfo = QNetworkInformation::instance(); + connect(netInfo, &QNetworkInformation::reachabilityChanged, + this, &HttpDownloader::onReachabilityChanged); +#endif } @@ -1305,7 +1311,7 @@ void KDUpdater::HttpDownloader::httpReadyRead() written += numWritten; } addSample(written); - addCheckSumData(buffer.data(), read); + addCheckSumData(buffer.left(read)); updateBytesDownloadedBeforeResume(written); } } @@ -1467,29 +1473,47 @@ void KDUpdater::HttpDownloader::startDownload(const QUrl &url) d->m_authenticationCount = 0; d->manager.setProxyFactory(proxyFactory()); clearBytesDownloadedBeforeResume(); - d->http = d->manager.get(QNetworkRequest(url)); + + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy); + request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false); + + d->http = d->manager.get(request); connect(d->http, &QIODevice::readyRead, this, &HttpDownloader::httpReadyRead); connect(d->http, &QNetworkReply::downloadProgress, this, &HttpDownloader::httpReadProgress); connect(d->http, &QNetworkReply::finished, this, &HttpDownloader::httpReqFinished); - void (QNetworkReply::*errorSignal)(QNetworkReply::NetworkError) = &QNetworkReply::error; - connect(d->http, errorSignal, this, &HttpDownloader::httpError); + connect(d->http, &QNetworkReply::errorOccurred, this, &HttpDownloader::httpError); + bool fileOpened = false; if (d->destFileName.isEmpty()) { QTemporaryFile *file = new QTemporaryFile(this); - file->open(); + fileOpened = file->open(); d->destination = file; } else { d->destination = new QFile(d->destFileName, this); - d->destination->open(QIODevice::ReadWrite | QIODevice::Truncate); + fileOpened = d->destination->open(QIODevice::ReadWrite | QIODevice::Truncate); } - - if (!d->destination->isOpen()) { - const QString error = d->destination->errorString(); - const QString fileName = d->destination->fileName(); - d->shutDown(); - setDownloadAborted(tr("Cannot download %1. Cannot create file \"%2\": %3").arg( - url.toString(), fileName, error)); + if (!fileOpened) { + qCWarning(QInstaller::lcInstallerInstallLog).nospace() << "Failed to open file " << d->destFileName + << ": "<<d->destination->errorString() << ". Trying again."; + QFileInfo fileInfo; + fileInfo.setFile(d->destination->fileName()); + if (!QDir().mkpath(fileInfo.absolutePath())) { + setDownloadAborted(tr("Cannot download %1. Cannot create directory for \"%2\"").arg( + url.toString(), fileInfo.filePath())); + } else { + fileOpened = d->destination->open(QIODevice::ReadWrite | QIODevice::Truncate); + if (fileOpened) + return; + if (d->destination->exists()) + qCWarning(QInstaller::lcInstallerInstallLog) << "File exists but installer is unable to open it."; + else + qCWarning(QInstaller::lcInstallerInstallLog) << "File does not exist."; + setDownloadAborted(tr("Cannot download %1. Cannot create file \"%2\": %3").arg( + url.toString(), d->destination->fileName(), d->destination->errorString())); + d->shutDown(); + } } } @@ -1509,8 +1533,7 @@ void KDUpdater::HttpDownloader::resumeDownload() connect(d->http, &QNetworkReply::downloadProgress, this, &HttpDownloader::httpReadProgress); connect(d->http, &QNetworkReply::finished, this, &HttpDownloader::httpReqFinished); - void (QNetworkReply::*errorSignal)(QNetworkReply::NetworkError) = &QNetworkReply::error; - connect(d->http, errorSignal, this, &HttpDownloader::httpError); + connect(d->http, &QNetworkReply::errorOccurred, this, &HttpDownloader::httpError); runDownloadSpeedTimer(); runDownloadDeadlineTimer(); } @@ -1552,6 +1575,7 @@ void KDUpdater::HttpDownloader::onAuthenticationRequired(QNetworkReply *reply, Q } } +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) void KDUpdater::HttpDownloader::onNetworkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility accessible) { if (accessible == QNetworkAccessManager::NotAccessible) { @@ -1566,6 +1590,22 @@ void KDUpdater::HttpDownloader::onNetworkAccessibleChanged(QNetworkAccessManager } } } +#else +void KDUpdater::HttpDownloader::onReachabilityChanged(QNetworkInformation::Reachability newReachability) +{ + if (newReachability == QNetworkInformation::Reachability::Online) { + if (isDownloadPaused()) { + setDownloadPaused(false); + resumeDownload(); + } + } else { + d->shutDown(false); + setDownloadPaused(true); + setDownloadResumed(false); + stopDownloadDeadlineTimer(); + } +} +#endif #ifndef QT_NO_SSL @@ -1603,7 +1643,7 @@ void KDUpdater::HttpDownloader::onSslErrors(QNetworkReply* reply, const QList<QS "the error may be temporary and you can try again."))); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); - msgBox.setButtonText(QMessageBox::Yes, tr("Try again")); + msgBox.addButton(tr("Try again"), QMessageBox::YesRole); msgBox.setDefaultButton(QMessageBox::Cancel); if (msgBox.exec() == QMessageBox::Cancel) { diff --git a/src/libs/kdtools/filedownloader.h b/src/libs/kdtools/filedownloader.h index ede20dcfa..e71f7d62f 100644 --- a/src/libs/kdtools/filedownloader.h +++ b/src/libs/kdtools/filedownloader.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -58,6 +59,8 @@ public: QByteArray assumedSha1Sum() const; void setAssumedSha1Sum(const QByteArray &sha1); + void setCheckSha1Sum(const bool checkSha1Sum); + bool checkSha1Sum() const; QString scheme() const; void setScheme(const QString &scheme); @@ -87,6 +90,8 @@ public: bool ignoreSslErrors(); void setIgnoreSslErrors(bool ignore); + qint64 getBytesReceived() const; + public Q_SLOTS: virtual void cancelDownload(); @@ -138,7 +143,6 @@ protected: void emitEstimatedDownloadTime(); void addCheckSumData(const QByteArray &data); - void addCheckSumData(const char *data, int length); void resetCheckSumData(); private Q_SLOTS: diff --git a/src/libs/kdtools/filedownloader_p.h b/src/libs/kdtools/filedownloader_p.h index 41a430554..23eff08d7 100644 --- a/src/libs/kdtools/filedownloader_p.h +++ b/src/libs/kdtools/filedownloader_p.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -34,6 +35,10 @@ #include <QtNetwork/QNetworkReply> #include <QNetworkAccessManager> +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include <QNetworkInformation> +#endif + // these classes are not a part of the public API namespace KDUpdater { @@ -46,22 +51,22 @@ public: explicit LocalFileDownloader(QObject *parent = 0); ~LocalFileDownloader(); - bool canDownload() const; - bool isDownloaded() const; - QString downloadedFileName() const; - void setDownloadedFileName(const QString &name); - LocalFileDownloader *clone(QObject *parent = 0) const; + bool canDownload() const override; + bool isDownloaded() const override; + QString downloadedFileName() const override; + void setDownloadedFileName(const QString &name) override; + LocalFileDownloader *clone(QObject *parent = 0) const override; public Q_SLOTS: - void cancelDownload(); + void cancelDownload() override; protected: - void timerEvent(QTimerEvent *te); - void onError(); - void onSuccess(); + void timerEvent(QTimerEvent *te) override; + void onError() override; + void onSuccess() override; private Q_SLOTS: - void doDownload(); + void doDownload() override; private: struct Private; @@ -76,22 +81,22 @@ public: explicit ResourceFileDownloader(QObject *parent = 0); ~ResourceFileDownloader(); - bool canDownload() const; - bool isDownloaded() const; - QString downloadedFileName() const; - void setDownloadedFileName(const QString &name); - ResourceFileDownloader *clone(QObject *parent = 0) const; + bool canDownload() const override; + bool isDownloaded() const override; + QString downloadedFileName() const override; + void setDownloadedFileName(const QString &name) override; + ResourceFileDownloader *clone(QObject *parent = 0) const override; public Q_SLOTS: - void cancelDownload(); + void cancelDownload() override; protected: - void timerEvent(QTimerEvent *te); - void onError(); - void onSuccess(); + void timerEvent(QTimerEvent *te) override; + void onError() override; + void onSuccess() override; private Q_SLOTS: - void doDownload(); + void doDownload() override; private: struct Private; @@ -106,22 +111,22 @@ public: explicit HttpDownloader(QObject *parent = 0); ~HttpDownloader(); - bool canDownload() const; - bool isDownloaded() const; - QString downloadedFileName() const; - void setDownloadedFileName(const QString &name); - HttpDownloader *clone(QObject *parent = 0) const; + bool canDownload() const override; + bool isDownloaded() const override; + QString downloadedFileName() const override; + void setDownloadedFileName(const QString &name) override; + HttpDownloader *clone(QObject *parent = 0) const override; public Q_SLOTS: - void cancelDownload(); + void cancelDownload() override; protected: - void onError(); - void onSuccess(); - void timerEvent(QTimerEvent *event); + void onError() override; + void onSuccess() override; + void timerEvent(QTimerEvent *event) override; private Q_SLOTS: - void doDownload(); + void doDownload() override; void httpReadyRead(); void httpReadProgress(qint64 done, qint64 total); @@ -129,7 +134,11 @@ private Q_SLOTS: void httpDone(bool error); void httpReqFinished(); void onAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) void onNetworkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility accessible); +#else + void onReachabilityChanged(QNetworkInformation::Reachability newReachability); +#endif #ifndef QT_NO_SSL void onSslErrors(QNetworkReply* reply, const QList<QSslError> &errors); #endif diff --git a/src/libs/kdtools/genericfactory.cpp b/src/libs/kdtools/genericfactory.cpp index e45fb89ff..4d4fcac98 100644 --- a/src/libs/kdtools/genericfactory.cpp +++ b/src/libs/kdtools/genericfactory.cpp @@ -1,11 +1,11 @@ /**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) -** Contact: http://www.qt.io/licensing/ +** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. ** -** $QT_BEGIN_LICENSE:LGPL$ +** $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 @@ -14,24 +14,13 @@ ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. +** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** @@ -128,6 +117,7 @@ /*! \fn template <typename BASE, typename IDENTIFIER, typename... ARGUMENTS> BASE *GenericFactory<BASE, IDENTIFIER, ARGUMENTS...>::create(const IDENTIFIER &id, ARGUMENTS... args) const - Creates and returns the type identified by \a id, but automatically upcasted to BASE. Ownership - of the type is transferred to the caller. + Creates and returns the type identified by \a id with variable number of + arguments \a args passed to the object's constructor, but automatically + upcasted to BASE. Ownership of the type is transferred to the caller. */ diff --git a/src/libs/kdtools/genericfactory.h b/src/libs/kdtools/genericfactory.h index 467f797d6..885271150 100644 --- a/src/libs/kdtools/genericfactory.h +++ b/src/libs/kdtools/genericfactory.h @@ -1,11 +1,11 @@ /**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) -** Contact: http://www.qt.io/licensing/ +** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. ** -** $QT_BEGIN_LICENSE:LGPL$ +** $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 @@ -14,24 +14,13 @@ ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. +** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** diff --git a/src/libs/kdtools/kdsysinfo_win.cpp b/src/libs/kdtools/kdsysinfo_win.cpp index 0aa7e7aa3..d423ef01e 100644 --- a/src/libs/kdtools/kdsysinfo_win.cpp +++ b/src/libs/kdtools/kdsysinfo_win.cpp @@ -66,9 +66,15 @@ QList<ProcessInfo> runningProcesses() QStringList deviceList; const DWORD bufferSize = 1024; char buffer[bufferSize + 1] = { 0 }; - if (QSysInfo::windowsVersion() <= QSysInfo::WV_5_2) { + + // Qt6 does not support Win before 10 + bool winVerLessEqual5_2 = false; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + winVerLessEqual5_2 = QSysInfo::windowsVersion() <= QSysInfo::WV_5_2; +#endif + if (winVerLessEqual5_2) { const DWORD size = GetLogicalDriveStringsA(bufferSize, buffer); - deviceList = QString::fromLatin1(buffer, size).split(QLatin1Char(char(0)), QString::SkipEmptyParts); + deviceList = QString::fromLatin1(buffer, size).split(QLatin1Char(char(0)), Qt::SkipEmptyParts); } QLibrary kernel32(QLatin1String("Kernel32.dll")); @@ -85,7 +91,7 @@ QList<ProcessInfo> runningProcesses() processStruct.dwSize = sizeof(PROCESSENTRY32); bool foundProcess = Process32First(snapshot, &processStruct); while (foundProcess) { - HANDLE procHandle = OpenProcess(QSysInfo::windowsVersion() > QSysInfo::WV_5_2 + HANDLE procHandle = OpenProcess(!winVerLessEqual5_2 ? KDSYSINFO_PROCESS_QUERY_LIMITED_INFORMATION : PROCESS_QUERY_INFORMATION, false, processStruct .th32ProcessID); @@ -93,7 +99,7 @@ QList<ProcessInfo> runningProcesses() QString executablePath; DWORD bufferSize = 1024; - if (QSysInfo::windowsVersion() > QSysInfo::WV_5_2) { + if (!winVerLessEqual5_2) { succ = pQueryFullProcessImageNamePtr(procHandle, 0, buffer, &bufferSize); executablePath = QString::fromLatin1(buffer); } else if (pGetProcessImageFileNamePtr) { diff --git a/src/libs/kdtools/localpackagehub.cpp b/src/libs/kdtools/localpackagehub.cpp index 2ee880e04..c7b627c51 100644 --- a/src/libs/kdtools/localpackagehub.cpp +++ b/src/libs/kdtools/localpackagehub.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: http://www.qt.io/licensing/ +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. ** @@ -124,6 +124,13 @@ bool LocalPackageHub::isValid() const } /*! + Returns a map of all local installed packages. Map key is the package name. +*/ +QMap<QString, LocalPackage> LocalPackageHub::localPackages() const +{ + return d->m_packageInfoMap; +} +/*! Returns a list of all local installed packages. */ QStringList LocalPackageHub::packageNames() const @@ -306,6 +313,7 @@ void LocalPackageHub::refresh() \a title, \a treeName, \a description, + \a sortingPriority, \a dependencies, \a autoDependencies, \a forcedInstallation, @@ -313,13 +321,15 @@ void LocalPackageHub::refresh() \a uncompressedSize, \a inheritVersionFrom, \a checkable, - and \a expandedByDefault for the package. + \a expandedByDefault, + and \a contentSha1 for the package. */ void LocalPackageHub::addPackage(const QString &name, const QString &version, const QString &title, - const QString &treeName, + const QPair<QString, bool> &treeName, const QString &description, + const int sortingPriority, const QStringList &dependencies, const QStringList &autoDependencies, bool forcedInstallation, @@ -344,6 +354,7 @@ void LocalPackageHub::addPackage(const QString &name, info.title = title; info.treeName = treeName; info.description = description; + info.sortingPriority = sortingPriority; info.dependencies = dependencies; info.autoDependencies = autoDependencies; info.forcedInstallation = forcedInstallation; @@ -403,7 +414,9 @@ void LocalPackageHub::writeToDisk() addTextChildHelper(&package, QLatin1String("Name"), info.name); addTextChildHelper(&package, QLatin1String("Title"), info.title); addTextChildHelper(&package, QLatin1String("Description"), info.description); - addTextChildHelper(&package, scTreeName, info.treeName); + addTextChildHelper(&package, QLatin1String("SortingPriority"), QString::number(info.sortingPriority)); + addTextChildHelper(&package, scTreeName, info.treeName.first, QLatin1String("moveChildren"), + QVariant(info.treeName.second).toString()); if (info.inheritVersionFrom.isEmpty()) addTextChildHelper(&package, QLatin1String("Version"), info.version); else @@ -476,9 +489,12 @@ void LocalPackageHub::PackagesInfoData::addPackageFrom(const QDomElement &packag info.title = childNodeE.text(); else if (childNodeE.tagName() == QLatin1String("Description")) info.description = childNodeE.text(); - else if (childNodeE.tagName() == scTreeName) - info.treeName = childNodeE.text(); - else if (childNodeE.tagName() == QLatin1String("Version")) { + else if (childNodeE.tagName() == QLatin1String("SortingPriority")) + info.sortingPriority = childNodeE.text().toInt(); + else if (childNodeE.tagName() == scTreeName) { + info.treeName.first = childNodeE.text(); + info.treeName.second = QVariant(childNodeE.attribute(QLatin1String("moveChildren"))).toBool(); + } else if (childNodeE.tagName() == QLatin1String("Version")) { info.version = childNodeE.text(); info.inheritVersionFrom = childNodeE.attribute(QLatin1String("inheritVersionFrom")); } @@ -488,10 +504,10 @@ void LocalPackageHub::PackagesInfoData::addPackageFrom(const QDomElement &packag info.uncompressedSize = childNodeE.text().toULongLong(); else if (childNodeE.tagName() == QLatin1String("Dependencies")) { info.dependencies = childNodeE.text().split(QInstaller::commaRegExp(), - QString::SkipEmptyParts); + Qt::SkipEmptyParts); } else if (childNodeE.tagName() == QLatin1String("AutoDependOn")) { info.autoDependencies = childNodeE.text().split(QInstaller::commaRegExp(), - QString::SkipEmptyParts); + Qt::SkipEmptyParts); } else if (childNodeE.tagName() == QLatin1String("ForcedInstallation")) info.forcedInstallation = childNodeE.text().toLower() == QLatin1String( "true" ) ? true : false; else if (childNodeE.tagName() == QLatin1String("LastUpdateDate")) diff --git a/src/libs/kdtools/localpackagehub.h b/src/libs/kdtools/localpackagehub.h index 648d6cf6e..5d69faf7d 100644 --- a/src/libs/kdtools/localpackagehub.h +++ b/src/libs/kdtools/localpackagehub.h @@ -1,8 +1,8 @@ /**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: http://www.qt.io/licensing/ +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. ** @@ -43,7 +43,8 @@ struct KDTOOLS_EXPORT LocalPackage QString name; QString title; QString description; - QString treeName; + int sortingPriority; + QPair<QString, bool> treeName; QString version; QString inheritVersionFrom; QStringList dependencies; @@ -77,6 +78,8 @@ public: }; bool isValid() const; + + QMap<QString, LocalPackage> localPackages() const; QStringList packageNames() const; Error error() const; @@ -100,8 +103,9 @@ public: void addPackage(const QString &pkgName, const QString &version, // mandatory const QString &title, - const QString &treeName, + const QPair<QString, bool> &treeName, const QString &description, + const int sortingPriority, const QStringList &dependencies, const QStringList &autoDependencies, bool forcedInstallation, diff --git a/src/libs/kdtools/lockfile_win.cpp b/src/libs/kdtools/lockfile_win.cpp index 20961cc4e..7fc808d39 100644 --- a/src/libs/kdtools/lockfile_win.cpp +++ b/src/libs/kdtools/lockfile_win.cpp @@ -45,7 +45,7 @@ bool LockFile::Private::lock() errorString.clear(); handle = CreateFile(filename.toStdWString().data(), GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ, NULL, QFileInfo(filename).exists() ? OPEN_EXISTING : CREATE_NEW, + FILE_SHARE_READ, NULL, QFileInfo::exists(filename) ? OPEN_EXISTING : CREATE_NEW, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); if (handle == INVALID_HANDLE_VALUE) { diff --git a/src/libs/kdtools/sysinfo.cpp b/src/libs/kdtools/sysinfo.cpp index df9a374f2..9512e59e3 100644 --- a/src/libs/kdtools/sysinfo.cpp +++ b/src/libs/kdtools/sysinfo.cpp @@ -71,7 +71,7 @@ VolumeInfo VolumeInfo::fromPath(const QString &path) } // the target directory does not exist yet, we need to cd up till we find the first existing dir - QStringList parts = targetPath.absolutePath().split(QDir::separator(),QString::SkipEmptyParts); + QStringList parts = targetPath.absolutePath().split(QDir::separator(), Qt::SkipEmptyParts); while (targetPath.absolutePath() != QDir::rootPath()) { if (targetPath.exists()) break; diff --git a/src/libs/kdtools/sysinfo_x11.cpp b/src/libs/kdtools/sysinfo_x11.cpp index bc55939a6..24ef099ca 100644 --- a/src/libs/kdtools/sysinfo_x11.cpp +++ b/src/libs/kdtools/sysinfo_x11.cpp @@ -40,7 +40,7 @@ #include <QtCore/QTextStream> #include <QtCore/QDir> #include <QtCore/QFileInfo> -#include <QtCore/QRegExp> +#include <QtCore/QRegularExpression> namespace KDUpdater { @@ -60,7 +60,7 @@ quint64 installedMemory() else if (s.isEmpty()) return quint64(); - const QStringList parts = s.split(QLatin1Char(' '), QString::SkipEmptyParts); + const QStringList parts = s.split(QLatin1Char(' '), Qt::SkipEmptyParts); return quint64(parts.at(1).toInt() * 1024LL); } #else @@ -99,7 +99,7 @@ QList<VolumeInfo> mountedVolumes() if (!s.startsWith(QLatin1Char('/')) && !s.startsWith(QLatin1String("tmpfs ") + QDir::tempPath())) continue; - const QStringList parts = s.split(QLatin1Char(' '), QString::SkipEmptyParts); + const QStringList parts = s.split(QLatin1Char(' '), Qt::SkipEmptyParts); VolumeInfo v; v.setMountPath(parts.at(1)); @@ -124,9 +124,9 @@ QList<ProcessInfo> runningProcesses() QList<ProcessInfo> processes; QDir procDir(QLatin1String("/proc")); const QFileInfoList procCont = procDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable); - QRegExp validator(QLatin1String("[0-9]+")); - Q_FOREACH (const QFileInfo &info, procCont) { - if (validator.exactMatch(info.fileName())) { + static const QRegularExpression validator(QLatin1String("^[0-9]+$")); + for (const QFileInfo &info : procCont) { + if (validator.match(info.fileName()).hasMatch()) { const QString linkPath = QDir(info.absoluteFilePath()).absoluteFilePath(QLatin1String("exe")); const QFileInfo linkInfo(linkPath); if (linkInfo.exists()) { diff --git a/src/libs/kdtools/updatefinder.cpp b/src/libs/kdtools/updatefinder.cpp index 034e162d3..dbe7825df 100644 --- a/src/libs/kdtools/updatefinder.cpp +++ b/src/libs/kdtools/updatefinder.cpp @@ -1,8 +1,8 @@ /**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: http://www.qt.io/licensing/ +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. ** @@ -39,7 +39,8 @@ #include <QCoreApplication> #include <QFileInfo> -#include <QRegExp> +#include <QRegularExpression> +#include <QFutureWatcher> using namespace KDUpdater; using namespace QInstaller; @@ -55,84 +56,107 @@ using namespace QInstaller; objects. */ + +static int computeProgressPercentage(int min, int max, int percent) +{ + return min + qint64(max-min) * percent / 100; +} + +static int computePercent(int done, int total) +{ + return total ? done * Q_INT64_C(100) / total : 0 ; +} + /*! - \fn void KDUpdater::UpdateFinder::addCompressedPackage(bool add) - \internal + Constructs an update finder. +*/ +UpdateFinder::UpdateFinder() + : Task(QLatin1String("UpdateFinder"), Stoppable) + , m_cancel(false) + , m_downloadCompleteCount(0) + , m_downloadsToComplete(0) + , m_updatesXmlTasks(0) + , m_updatesXmlTasksToComplete(0) +{ +} +/*! + Destructor */ +UpdateFinder::~UpdateFinder() +{ + clear(); +} /*! - \fn void KDUpdater::UpdateFinder::isCompressedPackage() - \internal + Returns a list of KDUpdater::Update objects. +*/ +QList<Update *> UpdateFinder::updates() const +{ + return m_updates.values(); +} +/*! + Sets the information about installed local packages \a hub. */ +void UpdateFinder::setLocalPackageHub(std::weak_ptr<LocalPackageHub> hub) +{ + m_localPackageHub = std::move(hub); +} -// -// Private -// -class UpdateFinder::Private +/*! + Sets the package \a sources information when searching for applicable packages. +*/ +void UpdateFinder::setPackageSources(const QSet<PackageSource> &sources) { -public: - enum struct Resolution { - AddPackage, - KeepExisting, - RemoveExisting - }; - - explicit Private(UpdateFinder *qq) - : q(qq) - , cancel(false) - , downloadCompleteCount(0) - , m_downloadsToComplete(0) - {} - - ~Private() - { - clear(); - } + m_packageSources = sources; +} - struct Data { - Data() - : downloader(0) {} - explicit Data(const PackageSource &i, FileDownloader *d = 0) - : info(i), downloader(d) {} +/*! + \internal - PackageSource info; - FileDownloader *downloader; - }; - UpdateFinder *q; - QHash<QString, Update *> updates; + Implemented from KDUpdater::Task::doRun(). +*/ +void UpdateFinder::doRun() +{ + computeUpdates(); +} - // Temporary structure that notes down information about updates. - bool cancel; - int downloadCompleteCount; - int m_downloadsToComplete; - QHash<UpdatesInfo *, Data> m_updatesInfoList; +/*! + \internal - void clear(); - void computeUpdates(); - void cancelComputeUpdates(); - bool downloadUpdateXMLFiles(); - bool computeApplicableUpdates(); + Implemented from KDUpdater::Task::doStop(). +*/ +bool UpdateFinder::doStop() +{ + cancelComputeUpdates(); - QList<UpdateInfo> applicableUpdates(UpdatesInfo *updatesInfo); - void createUpdateObjects(const PackageSource &source, const QList<UpdateInfo> &updateInfoList); - Resolution checkPriorityAndVersion(const PackageSource &source, const QVariantHash &data) const; - void slotDownloadDone(); + // Wait until the cancel has actually happened, and then return. + // Thinking of using QMutex for this. Frank/Till any suggestions? - QSet<PackageSource> packageSources; - std::weak_ptr<LocalPackageHub> m_localPackageHub; -}; + return true; +} +/*! + \internal -static int computeProgressPercentage(int min, int max, int percent) + Implemented from KDUpdater::Task::doStop(). +*/ +bool UpdateFinder::doPause() { - return min + qint64(max-min) * percent / 100; + // Not a pausable task + return false; } -static int computePercent(int done, int total) +/*! + \internal + + Implemented from KDUpdater::Task::doStop(). +*/ +bool UpdateFinder::doResume() { - return total ? done * Q_INT64_C(100) / total : 0 ; + // Not a pausable task, hence it is not resumable as well + return false; } /*! @@ -140,20 +164,22 @@ static int computePercent(int done, int total) Releases all internal resources consumed while downloading and computing updates. */ -void UpdateFinder::Private::clear() +void UpdateFinder::clear() { - qDeleteAll(updates); - updates.clear(); + qDeleteAll(m_updates); + m_updates.clear(); const QList<Data> values = m_updatesInfoList.values(); foreach (const Data &data, values) delete data.downloader; - qDeleteAll(m_updatesInfoList.keys()); + qDeleteAll(m_updatesInfoList.keyBegin(), m_updatesInfoList.keyEnd()); m_updatesInfoList.clear(); - downloadCompleteCount = 0; + m_downloadCompleteCount = 0; m_downloadsToComplete = 0; + qDeleteAll(m_xmlFileTasks); + m_xmlFileTasks.clear(); } /*! @@ -176,51 +202,59 @@ void UpdateFinder::Private::clear() \note Each time this function is called, all the previously computed updates are discarded and its resources are freed. */ -void UpdateFinder::Private::computeUpdates() +void UpdateFinder::computeUpdates() { // Computing updates is done in two stages // 1. Downloading Update XML files from all the update sources - // 2. Matching updates with Package XML and figuring out available updates + // 2. Parse attributes from Update XML documents to UpdateInfoList + // 3. Remove all updates with invalid content + // 4. Matching updates with Package XML and figuring out available updates - if (!q->isCompressedPackage()) - clear(); - cancel = false; + clear(); + m_cancel = false; // First do some quick sanity checks on the packages info std::shared_ptr<LocalPackageHub> packages = m_localPackageHub.lock(); if (!packages) { - q->reportError(tr("Cannot access the package information of this application.")); + reportError(tr("Cannot access the package information of this application.")); return; } if (!packages->isValid()) { - q->reportError(packages->errorString()); + reportError(packages->errorString()); return; } // Now do some quick sanity checks on the package sources. - if (packageSources.count() <= 0) { - q->reportError(tr("No package sources set for this application.")); + if (m_packageSources.count() <= 0) { + reportError(tr("No package sources set for this application.")); return; } // Now we can start... + if (!downloadUpdateXMLFiles() || m_cancel) { + clear(); + return; + } + + if (!parseUpdateXMLFiles() || m_cancel) { + clear(); + return; + } - // Step 1: 0 - 49 percent - if (!downloadUpdateXMLFiles() || cancel) { + if (!removeInvalidObjects() || m_cancel) { clear(); return; } - // Step 2: 50 - 100 percent - if (!computeApplicableUpdates() || cancel) { + if (!computeApplicableUpdates() || m_cancel) { clear(); return; } // All done - q->reportProgress(100, tr("%n update(s) found.", "", updates.count())); - q->reportDone(); + reportProgress(100, tr("%n update(s) found.", "", m_updates.count())); + reportDone(); } /*! @@ -230,9 +264,9 @@ void UpdateFinder::Private::computeUpdates() \sa computeUpdates() */ -void UpdateFinder::Private::cancelComputeUpdates() +void UpdateFinder::cancelComputeUpdates() { - cancel = true; + m_cancel = true; } /*! @@ -251,32 +285,32 @@ void UpdateFinder::Private::cancelComputeUpdates() The function gets into an event loop until all the downloads are complete. */ -bool UpdateFinder::Private::downloadUpdateXMLFiles() +bool UpdateFinder::downloadUpdateXMLFiles() { // create UpdatesInfo for each update source - foreach (const PackageSource &info, packageSources) { + foreach (const PackageSource &info, m_packageSources) { const QUrl url = QString::fromLatin1("%1/Updates.xml").arg(info.url.toString()); if (url.scheme() != QLatin1String("resource") && url.scheme() != QLatin1String("file")) { // create FileDownloader (except for local files and resources) - FileDownloader *downloader = FileDownloaderFactory::instance().create(url.scheme(), q); + FileDownloader *downloader = FileDownloaderFactory::instance().create(url.scheme(), this); if (!downloader) break; downloader->setUrl(url); downloader->setAutoRemoveDownloadedFile(true); - connect(downloader, SIGNAL(downloadCanceled()), q, SLOT(slotDownloadDone())); - connect(downloader, SIGNAL(downloadCompleted()), q, SLOT(slotDownloadDone())); - connect(downloader, SIGNAL(downloadAborted(QString)), q, SLOT(slotDownloadDone())); - m_updatesInfoList.insert(new UpdatesInfo, Data(info, downloader)); + connect(downloader, SIGNAL(downloadCanceled()), this, SLOT(slotDownloadDone())); + connect(downloader, SIGNAL(downloadCompleted()), this, SLOT(slotDownloadDone())); + connect(downloader, SIGNAL(downloadAborted(QString)), this, SLOT(slotDownloadDone())); + m_updatesInfoList.insert(new UpdatesInfo(info.postLoadComponentScript), Data(info, downloader)); } else { - UpdatesInfo *updatesInfo = new UpdatesInfo; + UpdatesInfo *updatesInfo = new UpdatesInfo(info.postLoadComponentScript); updatesInfo->setFileName(QInstaller::pathFromUrl(url)); m_updatesInfoList.insert(updatesInfo, Data(info)); } } // Trigger download of Updates.xml file - downloadCompleteCount = 0; + m_downloadCompleteCount = 0; m_downloadsToComplete = 0; foreach (const Data &data, m_updatesInfoList) { if (data.downloader) { @@ -286,39 +320,53 @@ bool UpdateFinder::Private::downloadUpdateXMLFiles() } // Wait until all downloaders have completed their downloads. - while (true) { - QCoreApplication::processEvents(); - if (cancel) - return false; - - if (downloadCompleteCount == m_downloadsToComplete) - break; - - q->reportProgress(computePercent(downloadCompleteCount, m_downloadsToComplete), - tr("Downloading Updates.xml from update sources.")); - } + return waitForJobToFinish(m_downloadCompleteCount, m_downloadsToComplete); +} +/*! + \internal +*/ +bool UpdateFinder::parseUpdateXMLFiles() +{ // Setup the update info objects with the files from download. - foreach (UpdatesInfo *updatesInfo, m_updatesInfoList.keys()) { + m_updatesXmlTasks = 0; + m_updatesXmlTasksToComplete = 0; + QList<UpdatesInfo *> keys = m_updatesInfoList.keys(); + for (UpdatesInfo *updatesInfo : qAsConst(keys)) { const Data data = m_updatesInfoList.value(updatesInfo); if (data.downloader) { if (!data.downloader->isDownloaded()) { - q->reportError(tr("Cannot download package source %1 from \"%2\".").arg(data - .downloader->url().fileName(), data.info.url.toString())); + reportError(tr("Cannot download package source %1 from \"%2\".").arg(data. + downloader->url().fileName(), data.info.url.toString())); } else { updatesInfo->setFileName(data.downloader->downloadedFileName()); } } + if (!updatesInfo->fileName().isEmpty()) { + ParseXmlFilesTask *const task = new ParseXmlFilesTask(updatesInfo); + m_xmlFileTasks.append(task); + QFutureWatcher<void> *watcher = new QFutureWatcher<void>(); + m_updatesXmlTasksToComplete++; + connect(watcher, &QFutureWatcherBase::finished, this, &UpdateFinder::parseUpdatesXmlTaskFinished); + watcher->setFuture(QtConcurrent::run(&ParseXmlFilesTask::doTask, task)); + } } - // Remove all invalid update info objects. + // Wait until all updates.xml files are parsed + return waitForJobToFinish(m_updatesXmlTasks, m_updatesXmlTasksToComplete); +} +/*! + \internal +*/ +bool UpdateFinder::removeInvalidObjects() +{ QMutableHashIterator<UpdatesInfo *, Data> it(m_updatesInfoList); while (it.hasNext()) { UpdatesInfo *info = it.next().key(); if (info->isValid()) continue; - q->reportError(info->errorString()); + reportError(info->errorString()); delete info; it.remove(); } @@ -326,7 +374,7 @@ bool UpdateFinder::Private::downloadUpdateXMLFiles() if (m_updatesInfoList.isEmpty()) return false; - q->reportProgress(49, tr("Updates.xml file(s) downloaded from update sources.")); + reportProgress(49, tr("Updates.xml file(s) downloaded from update sources.")); return true; } @@ -338,36 +386,40 @@ bool UpdateFinder::Private::downloadUpdateXMLFiles() KDUpdater::PackagesInfo. Thereby figures out whether an update is applicable for this application or not. */ -bool UpdateFinder::Private::computeApplicableUpdates() +bool UpdateFinder::computeApplicableUpdates() { int i = 0; - foreach (UpdatesInfo *updatesInfo, m_updatesInfoList.keys()) { + QList<UpdatesInfo *> keys = m_updatesInfoList.keys(); + for (UpdatesInfo *updatesInfo : qAsConst(keys)) { // Fetch updates applicable to this application. QList<UpdateInfo> updates = applicableUpdates(updatesInfo); if (!updates.count()) continue; - if (cancel) + if (m_cancel) return false; const PackageSource updateSource = m_updatesInfoList.value(updatesInfo).info; // Create Update objects for updates that have a valid // UpdateFile createUpdateObjects(updateSource, updates); - if (cancel) + if (m_cancel) return false; // Report progress - q->reportProgress(computeProgressPercentage(51, 100, computePercent(i, + reportProgress(computeProgressPercentage(51, 100, computePercent(i, m_updatesInfoList.count())), tr("Computing applicable updates.")); ++i; } - q->reportProgress(99, tr("Application updates computed.")); + reportProgress(99, tr("Application updates computed.")); return true; } -QList<UpdateInfo> UpdateFinder::Private::applicableUpdates(UpdatesInfo *updatesInfo) +/*! + \internal +*/ +QList<UpdateInfo> UpdateFinder::applicableUpdates(UpdatesInfo *updatesInfo) { const QList<UpdateInfo> dummy; if (!updatesInfo || updatesInfo->updateInfoCount() == 0) @@ -387,7 +439,7 @@ QList<UpdateInfo> UpdateFinder::Private::applicableUpdates(UpdatesInfo *updatesI // Catch hold of app names contained updatesInfo->applicationName() // If the application appName isn't one of the app names, then the updates are not applicable. - const QStringList apps = appName.split(QInstaller::commaRegExp(), QString::SkipEmptyParts); + const QStringList apps = appName.split(QInstaller::commaRegExp(), Qt::SkipEmptyParts); if (apps.indexOf([&packages] { return packages->isValid() ? packages->applicationName() : QCoreApplication::applicationName(); } ()) < 0) { return dummy; @@ -396,7 +448,10 @@ QList<UpdateInfo> UpdateFinder::Private::applicableUpdates(UpdatesInfo *updatesI return updatesInfo->updatesInfo(); } -void UpdateFinder::Private::createUpdateObjects(const PackageSource &source, +/*! + \internal +*/ +void UpdateFinder::createUpdateObjects(const PackageSource &source, const QList<UpdateInfo> &updateInfoList) { foreach (const UpdateInfo &info, updateInfoList) { @@ -406,24 +461,24 @@ void UpdateFinder::Private::createUpdateObjects(const PackageSource &source, const QString name = info.data.value(QLatin1String("Name")).toString(); if (value == Resolution::RemoveExisting) - delete updates.take(name); + delete m_updates.take(name); // Create and register the update - if (!q->isCompressedPackage() || value == Resolution::AddPackage) - updates.insert(name, new Update(source, info)); + m_updates.insert(name, new Update(source, info)); } } -/* +/*! + \internal If a package of the same name exists, always use the one with the higher version. If the new package has the same version but a higher priority, use the new new package, otherwise keep the already existing package. */ -UpdateFinder::Private::Resolution UpdateFinder::Private::checkPriorityAndVersion( +UpdateFinder::Resolution UpdateFinder::checkPriorityAndVersion( const PackageSource &source, const QVariantHash &newPackage) const { const QString name = newPackage.value(QLatin1String("Name")).toString(); - if (Update *existingPackage = updates.value(name)) { + if (Update *existingPackage = m_updates.value(name)) { // Bingo, package was previously found elsewhere. const int match = compareVersion(newPackage.value(QLatin1String("Version")).toString(), @@ -450,118 +505,56 @@ UpdateFinder::Private::Resolution UpdateFinder::Private::checkPriorityAndVersion << ", Source: " << QFileInfo(source.url.toLocalFile()).fileName() << "'"; return Resolution::RemoveExisting; } - if (q->isCompressedPackage() && match == 0 && source.priority == existingPackage->packageSource().priority) { - //Same package with the same priority and version already exists - return Resolution::RemoveExisting; - } return Resolution::KeepExisting; // otherwise keep existing } return Resolution::AddPackage; } -// -// UpdateFinder -// - -/*! - Constructs an update finder. -*/ -UpdateFinder::UpdateFinder() - : Task(QLatin1String("UpdateFinder"), Stoppable), - m_compressedPackage(false), - d(new Private(this)) -{ -} - -/*! - Destructor -*/ -UpdateFinder::~UpdateFinder() -{ - delete d; -} - -/*! - Returns a list of KDUpdater::Update objects. -*/ -QList<Update *> UpdateFinder::updates() const -{ - return d->updates.values(); -} - -/*! - Sets the information about installed local packages \a hub. -*/ -void UpdateFinder::setLocalPackageHub(std::weak_ptr<LocalPackageHub> hub) -{ - d->m_localPackageHub = std::move(hub); -} - -/*! - Sets the package \a sources information when searching for applicable packages. -*/ -void UpdateFinder::setPackageSources(const QSet<PackageSource> &sources) -{ - d->packageSources = sources; -} - /*! \internal - - Implemented from KDUpdater::Task::doRun(). */ -void UpdateFinder::doRun() +bool UpdateFinder::waitForJobToFinish(const int ¤tCount, const int &totalsCount) { - d->computeUpdates(); -} - -/*! - \internal - - Implemented from KDUpdater::Task::doStop(). -*/ -bool UpdateFinder::doStop() -{ - d->cancelComputeUpdates(); + while (true) { + QCoreApplication::processEvents(); + if (m_cancel) + return false; - // Wait until the cancel has actually happened, and then return. - // Thinking of using QMutex for this. Frank/Till any suggestions? + if (currentCount == totalsCount) + break; + reportProgress(computePercent(currentCount, totalsCount), + tr("Downloading Updates.xml from update sources.")); + } return true; } - /*! \internal - - Implemented from KDUpdater::Task::doStop(). */ -bool UpdateFinder::doPause() +void UpdateFinder::parseUpdatesXmlTaskFinished() { - // Not a pausable task - return false; -} + ++m_updatesXmlTasks; -/*! - \internal + int pc = computePercent(m_updatesXmlTasks, m_updatesXmlTasksToComplete); + pc = computeProgressPercentage(0, 45, pc); + reportProgress( pc, tr("Downloading Updates.xml from update sources.") ); - Implemented from KDUpdater::Task::doStop(). -*/ -bool UpdateFinder::doResume() -{ - // Not a pausable task, hence it is not resumable as well - return false; + QFutureWatcher<void> *watcher = static_cast<QFutureWatcher<void> *>(sender()); + watcher->waitForFinished(); + watcher->deleteLater(); } + /*! \internal */ -void UpdateFinder::Private::slotDownloadDone() +void UpdateFinder::slotDownloadDone() { - ++downloadCompleteCount; + ++m_downloadCompleteCount; - int pc = computePercent(downloadCompleteCount, m_downloadsToComplete); + int pc = computePercent(m_downloadCompleteCount, m_downloadsToComplete); pc = computeProgressPercentage(0, 45, pc); - q->reportProgress( pc, tr("Downloading Updates.xml from update sources.") ); + reportProgress( pc, tr("Downloading Updates.xml from update sources.") ); } @@ -607,8 +600,9 @@ int KDUpdater::compareVersion(const QString &v1, const QString &v2) return 0; // Split version components across ".", "-" or "_" - QStringList v1_comps = v1.split(QRegExp(QLatin1String( "\\.|-|_"))); - QStringList v2_comps = v2.split(QRegExp(QLatin1String( "\\.|-|_"))); + static const QRegularExpression regex(QLatin1String( "\\.|-|_")); + QStringList v1_comps = v1.split(regex); + QStringList v2_comps = v2.split(regex); // Check each component of the version int index = 0; diff --git a/src/libs/kdtools/updatefinder.h b/src/libs/kdtools/updatefinder.h index 47fa42c9a..626a700fd 100644 --- a/src/libs/kdtools/updatefinder.h +++ b/src/libs/kdtools/updatefinder.h @@ -1,8 +1,8 @@ /**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: http://www.qt.io/licensing/ +** Copyright (C) 2023 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. ** @@ -32,19 +32,68 @@ #include "task.h" #include "packagesource.h" +#include "filedownloader.h" +#include "updatesinfo_p.h" +#include "abstracttask.h" #include <memory> +using namespace QInstaller; namespace KDUpdater { class LocalPackageHub; class Update; +class ParseXmlFilesTask : public AbstractTask<void> +{ + Q_OBJECT + Q_DISABLE_COPY(ParseXmlFilesTask) + +public: + ParseXmlFilesTask(UpdatesInfo *info) + : m_info(info) + {} + + void doTask(QFutureInterface<void> &fi) override + { + fi.reportStarted(); + fi.setExpectedResultCount(1); + + if (fi.isCanceled()) { + fi.reportFinished(); + return; // ignore already canceled + } + m_info->parseFile(); + + fi.reportFinished(); + } + +private: + UpdatesInfo *const m_info; +}; + + class KDTOOLS_EXPORT UpdateFinder : public Task { Q_OBJECT class Private; + struct Data { + Data() + : downloader(0) {} + explicit Data(const QInstaller::PackageSource &i, KDUpdater::FileDownloader *d = 0) + : info(i), downloader(d) {} + + QInstaller::PackageSource info; + KDUpdater::FileDownloader *downloader; + }; + + enum struct Resolution { + AddPackage, + KeepExisting, + RemoveExisting + }; + public: UpdateFinder(); ~UpdateFinder(); @@ -53,18 +102,41 @@ public: void setLocalPackageHub(std::weak_ptr<LocalPackageHub> hub); void setPackageSources(const QSet<QInstaller::PackageSource> &sources); - void addCompressedPackage(bool add) { m_compressedPackage = add; } - bool isCompressedPackage() { return m_compressedPackage; } + private: - void doRun(); - bool doStop(); - bool doPause(); - bool doResume(); + void doRun() override; + bool doStop() override; + bool doPause() override; + bool doResume() override; + void clear(); + void computeUpdates(); + void cancelComputeUpdates(); + bool downloadUpdateXMLFiles(); + bool parseUpdateXMLFiles(); + bool removeInvalidObjects(); + bool computeApplicableUpdates(); + + QList<UpdateInfo> applicableUpdates(UpdatesInfo *updatesInfo); + void createUpdateObjects(const PackageSource &source, const QList<UpdateInfo> &updateInfoList); + Resolution checkPriorityAndVersion(const QInstaller::PackageSource &source, const QVariantHash &data) const; + bool waitForJobToFinish(const int ¤tCount, const int &totalsCount); + +private slots: + void parseUpdatesXmlTaskFinished(); + void slotDownloadDone(); private: - bool m_compressedPackage; - Private *d; - Q_PRIVATE_SLOT(d, void slotDownloadDone()) + QSet<PackageSource> m_packageSources; + std::weak_ptr<LocalPackageHub> m_localPackageHub; + QHash<QString, Update *> m_updates; + + bool m_cancel; + int m_downloadCompleteCount; + int m_downloadsToComplete; + QHash<UpdatesInfo *, Data> m_updatesInfoList; + int m_updatesXmlTasks; + int m_updatesXmlTasksToComplete; + QList<ParseXmlFilesTask*> m_xmlFileTasks; }; } // namespace KDUpdater diff --git a/src/libs/kdtools/updateoperation.cpp b/src/libs/kdtools/updateoperation.cpp index 897fecf1b..af89382a8 100644 --- a/src/libs/kdtools/updateoperation.cpp +++ b/src/libs/kdtools/updateoperation.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -80,6 +81,21 @@ using namespace KDUpdater; Undo operation. */ +/*! + \enum UpdateOperation::OperationGroup + This enum specifies the execution group of the operation. + + \value Unpack + Operation should be run in the unpacking phase. Operations in + this group are run concurrently between all selected components. + \value Install + Operation should be run in the installation phase. + \value All + All available operation groups. + \value Default + The default group for operations, synonym for Install. +*/ + /* \internal Returns a filename for a temporary file based on \a templateName. @@ -99,10 +115,13 @@ static QString backupFileName(const QString &templateName) \internal */ UpdateOperation::UpdateOperation(QInstaller::PackageManagerCore *core) - : m_error(0) + : m_group(OperationGroup::Default) + , m_error(0) , m_core(core) , m_requiresUnreplacedVariables(false) { + qRegisterMetaType<UpdateOperation *>(); + // Store the value for compatibility reasons. m_values[QLatin1String("installer")] = QVariant::fromValue(core); } @@ -139,6 +158,16 @@ QString UpdateOperation::operationCommand() const } /*! + Returns the execution group this operation belongs to. + + \sa setGroup() +*/ +UpdateOperation::OperationGroup UpdateOperation::group() const +{ + return m_group; +} + +/*! Returns \c true if a value called \a name exists, otherwise returns \c false. */ bool UpdateOperation::hasValue(const QString &name) const @@ -180,6 +209,17 @@ void UpdateOperation::setName(const QString &name) } /*! + Sets the execution group of the operation to \a group. Subclasses can change + the group to control which installation phase this operation should be run in. + + The default group is \c Install. +*/ +void UpdateOperation::setGroup(const OperationGroup &group) +{ + m_group = group; +} + +/*! Sets the arguments for the update operation to \a args. */ void UpdateOperation::setArguments(const QStringList &args) @@ -252,23 +292,18 @@ QStringList UpdateOperation::parsePerformOperationArguments() } /*! - Returns undo operation argument list. If the installation is - cancelled or failed, returns an empty list so that full undo - operation can be performed. + Returns \c true if operation undo should not be performed. + Returns \c false if the installation is cancelled or failed, or + \c UNDOOPERATION is not set in operation call. */ -QStringList UpdateOperation::parseUndoOperationArguments() +bool UpdateOperation::skipUndoOperation() { //Install has failed, allow a normal undo if (m_core && (m_core->status() == QInstaller::PackageManagerCore::Canceled || m_core->status() == QInstaller::PackageManagerCore::Failure)) { - return QStringList(); - } - int index = arguments().indexOf(QLatin1String("UNDOOPERATION")); - QStringList args; - if ((index != -1) && (arguments().length() > index + 1)) { - args = arguments().mid(index + 1); + return false; } - return args; + return arguments().contains(QLatin1String("UNDOOPERATION")); } /*! @@ -281,6 +316,41 @@ void UpdateOperation::setRequiresUnreplacedVariables(bool isRequired) m_requiresUnreplacedVariables = isRequired; } +/*! + Replaces installer \c value \a variableValue with predefined variable. + If \c key is found for the \a variableValue and the \c key ends with string _OLD, + the initial \a variableValue is replaced with the \c value having a key + without _OLD ending. This way we can replace the hard coded values defined for operations, + if the value has for some reason changed. For example if we set following variables + in install script: + \badcode + installer.setValue("MY_OWN_EXECUTABLE", "C:/Qt/NewLocation/Tools.exe") + installer.setValue("MY_OWN_EXECUTABLE_OLD", "C:/Qt/OldLocation/Tools.exe") + \endcode + and we have moved the Tools.exe from OldLocation to NewLocation, the operation + continues to work and use the Tools.exe from NewLocation although original + installation has been made with Tools.exe in OldLocation. + Returns \c true if \a variableValue is replaced. +*/ +bool UpdateOperation::variableReplacement(QString *variableValue) +{ + bool variableValueChanged = false; + const QString valueNormalized = QDir::cleanPath(*variableValue); + QString key = m_core->key(valueNormalized); + if (key.endsWith(QLatin1String("_OLD"))) { + key.chop(4); + if (m_core->containsValue(key)) { + key.prepend(QLatin1String("@")); + key.append(QLatin1String("@")); + *variableValue = m_core->replaceVariables(key); + qCDebug(QInstaller::lcInstallerInstallLog) << "Running above operation with replaced value: " << valueNormalized + << "has been replaced with" << *variableValue; + variableValueChanged = true; + } + } + return variableValueChanged; +} + struct StartsWith { explicit StartsWith(const QString &searchTerm) @@ -475,8 +545,15 @@ QDomDocument UpdateOperation::toXml() const const QString target = m_core ? m_core->value(QInstaller::scTargetDir) : QString(); Q_FOREACH (const QString &s, arguments()) { QDomElement arg = doc.createElement(QLatin1String("argument")); - arg.appendChild(doc.createTextNode(QInstaller::replacePath(s, target, - QLatin1String(QInstaller::scRelocatable)))); + // Do not call cleanPath to Execute operations paths. The operation might require the + // exact separators that are set in the operation call. + if (name() == QLatin1String("Execute")) { + arg.appendChild(doc.createTextNode(QInstaller::replacePath(s, target, + QLatin1String(QInstaller::scRelocatable), false))); + } else { + arg.appendChild(doc.createTextNode(QInstaller::replacePath(s, target, + QLatin1String(QInstaller::scRelocatable)))); + } args.appendChild(arg); } root.appendChild(args); @@ -495,14 +572,21 @@ QDomDocument UpdateOperation::toXml() const value.setAttribute(QLatin1String("name"), it.key()); value.setAttribute(QLatin1String("type"), QLatin1String(variant.typeName())); - if (variant.type() != QVariant::List && variant.type() != QVariant::StringList - && variant.canConvert(QVariant::String)) { - // it can convert to string? great! - value.appendChild(doc.createTextNode(QInstaller::replacePath(variant.toString(), - target, QLatin1String(QInstaller::scRelocatable)))); + int variantType; +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + variantType = variant.typeId(); +#else + variantType = variant.type(); +#endif + + if (variantType != QMetaType::QStringList + && variant.canConvert<QString>()) { + // it can convert to string? great! + value.appendChild(doc.createTextNode(QInstaller::replacePath(variant.toString(), + target, QLatin1String(QInstaller::scRelocatable)))); } else { // no? then we have to go the hard way... - if (variant.type() == QVariant::StringList) { + if (variantType == QMetaType::QStringList) { QStringList list = variant.toStringList(); for (int i = 0; i < list.count(); ++i) { list[i] = QInstaller::replacePath(list.at(i), target, @@ -528,6 +612,7 @@ QDomDocument UpdateOperation::toXml() const bool UpdateOperation::fromXml(const QDomDocument &doc) { QString target = QCoreApplication::applicationDirPath(); + static const QLatin1String relocatable = QLatin1String(QInstaller::scRelocatable); // Does not change target on non macOS platforms. if (QInstaller::isInBundle(target, &target)) target = QDir::cleanPath(target + QLatin1String("/..")); @@ -539,8 +624,20 @@ bool UpdateOperation::fromXml(const QDomDocument &doc) for (QDomNode n = argsElem.firstChild(); ! n.isNull(); n = n.nextSibling()) { const QDomElement e = n.toElement(); if (!e.isNull() && e.tagName() == QLatin1String("argument")) { - args << QInstaller::replacePath(e.text(), QLatin1String(QInstaller::scRelocatable), - target); + // Sniff the Execute -operations file path separator. The operation might be + // strict with the used path separator + bool useCleanPath = true; + if (name() == QLatin1String("Execute")) { + if (e.text().startsWith(relocatable) && e.text().size() > relocatable.size()) { + const QChar separator = e.text().at(relocatable.size()); + if (separator == QLatin1Char('\\')) { + target = QDir::toNativeSeparators(target); + useCleanPath = false; + } + } + } + args << QInstaller::replacePath(e.text(), relocatable, + target, useCleanPath); } } setArguments(args); @@ -556,25 +653,31 @@ bool UpdateOperation::fromXml(const QDomDocument &doc) const QString type = v.attribute(QLatin1String("type")); const QString value = v.text(); + int variantType; +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + const QMetaType t = QMetaType::fromName(type.toLatin1().data()); + variantType = t.id(); +#else const QVariant::Type t = QVariant::nameToType(type.toLatin1().data()); - QVariant var = qVariantFromValue(value); - if (t == QVariant::List || t == QVariant::StringList || !var.convert(t)) { - QDataStream stream(QByteArray::fromBase64( value.toLatin1())); + variantType = t; +#endif + QVariant var = QVariant::fromValue(value); + if (variantType == QMetaType::QStringList || !var.canConvert(t)) { + QDataStream stream(QByteArray::fromBase64(value.toLatin1())); stream >> var; - if (t == QVariant::StringList) { + if (variantType == QMetaType::QStringList) { QStringList list = var.toStringList(); for (int i = 0; i < list.count(); ++i) { list[i] = QInstaller::replacePath(list.at(i), - QLatin1String(QInstaller::scRelocatable), target); + relocatable, target); } var = QVariant::fromValue(list); } - } else if (t == QVariant::String) { - const QString str = QInstaller::replacePath(value, - QLatin1String(QInstaller::scRelocatable), target); - var = QVariant::fromValue(str); + } else if (variantType == QMetaType::QString) { + const QString str = QInstaller::replacePath(value, + relocatable, target); + var = QVariant::fromValue(str); } - m_values[name] = var; } @@ -582,6 +685,18 @@ bool UpdateOperation::fromXml(const QDomDocument &doc) } /*! + Returns a numerical representation of how this operation compares to + other operations in size, and in time it takes to perform the operation. + + The default returned value is \c 1. Subclasses may override this method to + implement custom size hints. +*/ +quint64 UpdateOperation::sizeHint() +{ + return 1; +} + +/*! \overload Restores operation arguments and values from the XML file at path \a xml. Returns \c true on diff --git a/src/libs/kdtools/updateoperation.h b/src/libs/kdtools/updateoperation.h index a8110791c..e25846cd3 100644 --- a/src/libs/kdtools/updateoperation.h +++ b/src/libs/kdtools/updateoperation.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -59,11 +60,20 @@ public: Undo }; + enum OperationGroup { + Unpack = 0x1, + Install = 0x2, + All = (Unpack | Install), + Default = Install + }; + Q_DECLARE_FLAGS(OperationGroups, OperationGroup) + explicit UpdateOperation(QInstaller::PackageManagerCore *core); virtual ~UpdateOperation(); QString name() const; QString operationCommand() const; + OperationGroup group() const; bool hasValue(const QString &name) const; void clearValue(const QString &name); @@ -90,8 +100,11 @@ public: virtual bool fromXml(const QString &xml); virtual bool fromXml(const QDomDocument &doc); + virtual quint64 sizeHint(); + protected: void setName(const QString &name); + void setGroup(const OperationGroup &group); void setErrorString(const QString &errorString); void setError(int error, const QString &errorString = QString()); void registerForDelayedDeletion(const QStringList &files); @@ -99,11 +112,13 @@ protected: bool checkArgumentCount(int minArgCount, int maxArgCount, const QString &argDescription = QString()); bool checkArgumentCount(int argCount); QStringList parsePerformOperationArguments(); - QStringList parseUndoOperationArguments(); + bool skipUndoOperation(); void setRequiresUnreplacedVariables(bool isRequired); + bool variableReplacement(QString *variableValue); private: QString m_name; + OperationGroup m_group; QStringList m_arguments; QString m_errorString; int m_error; @@ -115,4 +130,6 @@ private: } // namespace KDUpdater +Q_DECLARE_METATYPE(KDUpdater::UpdateOperation *) + #endif // UPDATEOPERATION_H diff --git a/src/libs/kdtools/updateoperations.cpp b/src/libs/kdtools/updateoperations.cpp index 9301d4f13..5f6135103 100644 --- a/src/libs/kdtools/updateoperations.cpp +++ b/src/libs/kdtools/updateoperations.cpp @@ -127,7 +127,7 @@ QString CopyOperation::sourcePath() QString CopyOperation::destinationPath() { - QString destination = arguments().last(); + QString destination = arguments().at(1); // if the target is a directory use the source filename to complete the destination path if (QFileInfo(destination).isDir()) @@ -135,7 +135,6 @@ QString CopyOperation::destinationPath() return destination; } - void CopyOperation::backup() { QString destination = destinationPath(); @@ -156,8 +155,8 @@ void CopyOperation::backup() bool CopyOperation::performOperation() { // We need two args to complete the copy operation. First arg provides the complete file name of source - // Second arg provides the complete file name of dest - if (!checkArgumentCount(2)) + // Second arg provides the complete file name of dest. Optionally UNDOOPERATION can be added as well + if (!checkArgumentCount(2, 4, QLatin1String("<source filename> <destination filename> [UNDOOPERATION, \"\"]"))) return false; QString source = sourcePath(); @@ -193,6 +192,8 @@ bool CopyOperation::performOperation() bool CopyOperation::undoOperation() { + if (skipUndoOperation()) + return true; QString source = sourcePath(); QString destination = destinationPath(); @@ -270,7 +271,7 @@ MoveOperation::~MoveOperation() void MoveOperation::backup() { - const QString dest = arguments().last(); + const QString dest = arguments().at(1); if (!QFile::exists(dest)) { clearValue(QLatin1String("backupOfExistingDestination")); return; @@ -286,9 +287,10 @@ void MoveOperation::backup() bool MoveOperation::performOperation() { - // We need two args to complete the copy operation. // First arg provides the complete file name of - // source, second arg provides the complete file name of dest - if (!checkArgumentCount(2)) + // We need two args to complete the copy operation. First arg provides the complete file name of + // source, second arg provides the complete file name of dest. Optionally UNDOOPERATION can be added as well + if (!checkArgumentCount(2, 4, QLatin1String("<complete source file name> <complete destination " + "file name> [UNDOOPERATION, \"\"]"))) return false; const QStringList args = arguments(); @@ -318,8 +320,10 @@ bool MoveOperation::performOperation() bool MoveOperation::undoOperation() { + if (skipUndoOperation()) + return true; const QStringList args = arguments(); - const QString dest = args.last(); + const QString dest = args.at(1); // first: copy back the destination to source QFile destF(dest); if (!destF.copy(args.first())) { @@ -391,7 +395,8 @@ void DeleteOperation::backup() bool DeleteOperation::performOperation() { // Requires only one parameter. That is the name of the file to remove. - if (!checkArgumentCount(1)) + // Optionally UNDOOPERATION can be added as well + if (!checkArgumentCount(1, 3, QLatin1String("<file to remove> [UNDOOPERATION, \"\"]"))) return false; return deleteFileNowOrLater(arguments().at(0)); @@ -399,7 +404,7 @@ bool DeleteOperation::performOperation() bool DeleteOperation::undoOperation() { - if (!hasValue(QLatin1String("backupOfExistingFile"))) + if (skipUndoOperation()) return true; const QString fileName = arguments().first(); @@ -478,7 +483,8 @@ void MkdirOperation::backup() bool MkdirOperation::performOperation() { // Requires only one parameter. That is the path which should be created - if (!checkArgumentCount(1)) + // Optionally UNDOOPERATION can be added as well + if (!checkArgumentCount(1, 3, QLatin1String("<file to remove> [UNDOOPERATION, \"\"]"))) return false; const QString dirName = arguments().at(0); @@ -493,7 +499,8 @@ bool MkdirOperation::performOperation() bool MkdirOperation::undoOperation() { - Q_ASSERT(arguments().count() == 1); + if (skipUndoOperation()) + return true; QString createdDirValue = value(QLatin1String("createddir")).toString(); if (packageManager()) { @@ -572,7 +579,8 @@ void RmdirOperation::backup() bool RmdirOperation::performOperation() { // Requires only one parameter. That is the name of the file to remove. - if (!checkArgumentCount(1)) + // Optionally UNDOOPERATION can be added as well + if (!checkArgumentCount(1, 3, QLatin1String("<file to remove> [UNDOOPERATION, \"\"]"))) return false; const QString firstArg = arguments().at(0); @@ -597,7 +605,7 @@ bool RmdirOperation::performOperation() bool RmdirOperation::undoOperation() { - if (!value(QLatin1String("removed")).toBool()) + if (!value(QLatin1String("removed")).toBool() || skipUndoOperation()) return true; errno = 0; @@ -633,6 +641,12 @@ AppendFileOperation::AppendFileOperation(QInstaller::PackageManagerCore *core) setName(QLatin1String("AppendFile")); } +AppendFileOperation::~AppendFileOperation() +{ + if (skipUndoOperation()) + deleteFileNowOrLater(value(QLatin1String("backupOfFile")).toString()); +} + void AppendFileOperation::backup() { const QString filename = arguments().first(); @@ -653,10 +667,10 @@ bool AppendFileOperation::performOperation() { // This operation takes two arguments. First argument is the name of the file into which a text has to be // appended. Second argument is the text to append. - if (!checkArgumentCount(2)) + if (!checkArgumentCount(2, 4, QLatin1String("<filename> <text to apply> [UNDOOPERATION, \"\"]"))) return false; - const QStringList args = this->arguments(); + const QStringList args = arguments(); const QString fName = args.at(0); QFile file(fName); if (!file.open(QFile::Append)) { @@ -695,6 +709,9 @@ bool AppendFileOperation::performOperation() bool AppendFileOperation::undoOperation() { + if (skipUndoOperation()) + return true; + // backupOfFile being empty -> file didn't exist before -> no error const QString filename = arguments().first(); const QString backupOfFile = value(QLatin1String("backupOfFile")).toString(); @@ -746,6 +763,12 @@ PrependFileOperation::PrependFileOperation(QInstaller::PackageManagerCore *core) setName(QLatin1String("PrependFile")); } +PrependFileOperation::~PrependFileOperation() +{ + if (skipUndoOperation()) + deleteFileNowOrLater(value(QLatin1String("backupOfFile")).toString()); +} + void PrependFileOperation::backup() { const QString filename = arguments().first(); @@ -767,10 +790,10 @@ bool PrependFileOperation::performOperation() // This operation takes two arguments. First argument is the name // of the file into which a text has to be appended. Second argument // is the text to append. - if (!checkArgumentCount(2)) + if (!checkArgumentCount(2, 4, QLatin1String("<filename> <text to prepend> [UNDOOPERATION, \"\"]"))) return false; - const QStringList args = this->arguments(); + const QStringList args = arguments(); const QString fName = args.at(0); // Load the file first. QFile file(fName); @@ -810,7 +833,9 @@ bool PrependFileOperation::performOperation() bool PrependFileOperation::undoOperation() { - // bockupOfFile being empty -> file didn't exist before -> no error + if (skipUndoOperation()) + return true; + const QString filename = arguments().first(); const QString backupOfFile = value(QLatin1String("backupOfFile")).toString(); if (!backupOfFile.isEmpty() && !QFile::exists(backupOfFile)) { diff --git a/src/libs/kdtools/updateoperations.h b/src/libs/kdtools/updateoperations.h index b13a42559..adbfc7de1 100644 --- a/src/libs/kdtools/updateoperations.h +++ b/src/libs/kdtools/updateoperations.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -40,12 +41,12 @@ public: explicit CopyOperation(QInstaller::PackageManagerCore *core = 0); ~CopyOperation(); - void backup(); - bool performOperation(); - bool undoOperation(); - bool testOperation(); + void backup() override; + bool performOperation() override; + bool undoOperation() override; + bool testOperation() override; - QDomDocument toXml() const; + QDomDocument toXml() const override; private: QString sourcePath(); QString destinationPath(); @@ -58,10 +59,10 @@ public: explicit MoveOperation(QInstaller::PackageManagerCore *core = 0); ~MoveOperation(); - void backup(); - bool performOperation(); - bool undoOperation(); - bool testOperation(); + void backup() override; + bool performOperation() override; + bool undoOperation() override; + bool testOperation() override; }; class KDTOOLS_EXPORT DeleteOperation : public UpdateOperation @@ -71,12 +72,12 @@ public: explicit DeleteOperation(QInstaller::PackageManagerCore *core = 0); ~DeleteOperation(); - void backup(); - bool performOperation(); - bool undoOperation(); - bool testOperation(); + void backup() override; + bool performOperation() override; + bool undoOperation() override; + bool testOperation() override; - QDomDocument toXml() const; + QDomDocument toXml() const override; }; class KDTOOLS_EXPORT MkdirOperation : public UpdateOperation @@ -85,10 +86,10 @@ class KDTOOLS_EXPORT MkdirOperation : public UpdateOperation public: explicit MkdirOperation(QInstaller::PackageManagerCore *core = 0); - void backup(); - bool performOperation(); - bool undoOperation(); - bool testOperation(); + void backup() override; + bool performOperation() override; + bool undoOperation() override; + bool testOperation() override; }; class KDTOOLS_EXPORT RmdirOperation : public UpdateOperation @@ -97,10 +98,10 @@ class KDTOOLS_EXPORT RmdirOperation : public UpdateOperation public: RmdirOperation(QInstaller::PackageManagerCore *core = 0); - void backup(); - bool performOperation(); - bool undoOperation(); - bool testOperation(); + void backup() override; + bool performOperation() override; + bool undoOperation() override; + bool testOperation() override; }; class KDTOOLS_EXPORT AppendFileOperation : public UpdateOperation @@ -108,11 +109,12 @@ class KDTOOLS_EXPORT AppendFileOperation : public UpdateOperation Q_DECLARE_TR_FUNCTIONS(KDUpdater::AppendFileOperation) public: explicit AppendFileOperation(QInstaller::PackageManagerCore *core = 0); + ~AppendFileOperation(); - void backup(); - bool performOperation(); - bool undoOperation(); - bool testOperation(); + void backup() override; + bool performOperation() override; + bool undoOperation() override; + bool testOperation() override; }; class KDTOOLS_EXPORT PrependFileOperation : public UpdateOperation @@ -120,11 +122,12 @@ class KDTOOLS_EXPORT PrependFileOperation : public UpdateOperation Q_DECLARE_TR_FUNCTIONS(KDUpdater::PrependFileOperation) public: explicit PrependFileOperation(QInstaller::PackageManagerCore *core = 0); + ~PrependFileOperation(); - void backup(); - bool performOperation(); - bool undoOperation(); - bool testOperation(); + void backup() override; + bool performOperation() override; + bool undoOperation() override; + bool testOperation() override; }; } // namespace KDUpdater diff --git a/src/libs/kdtools/updatesinfo.cpp b/src/libs/kdtools/updatesinfo.cpp index eaa9b039e..e82c1c1fb 100644 --- a/src/libs/kdtools/updatesinfo.cpp +++ b/src/libs/kdtools/updatesinfo.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -28,18 +29,20 @@ #include "updatesinfo_p.h" #include "utils.h" +#include "constants.h" -#include <QDomDocument> #include <QFile> #include <QLocale> #include <QPair> #include <QVector> #include <QUrl> +#include <QXmlStreamReader> using namespace KDUpdater; -UpdatesInfoData::UpdatesInfoData() +UpdatesInfoData::UpdatesInfoData(const bool postLoadComponentScript) : error(UpdatesInfo::NotYetReadError) + , m_postLoadComponentScript(postLoadComponentScript) { } @@ -62,35 +65,26 @@ void UpdatesInfoData::parseFile(const QString &updateXmlFile) return; } - QDomDocument doc; - QString parseErrorMessage; - int parseErrorLine, parseErrorColumn; - if (!doc.setContent(&file, &parseErrorMessage, &parseErrorLine, &parseErrorColumn)) { - error = UpdatesInfo::InvalidXmlError; - errorMessage = tr("Parse error in %1 at %2, %3: %4").arg(updateXmlFile, - QString::number(parseErrorLine), QString::number(parseErrorColumn), parseErrorMessage); - return; - } - - QDomElement rootE = doc.documentElement(); - if (rootE.tagName() != QLatin1String("Updates")) { - setInvalidContentError(tr("Root element %1 unexpected, should be \"Updates\".").arg(rootE.tagName())); - return; - } - - QDomNodeList childNodes = rootE.childNodes(); - for(int i = 0; i < childNodes.count(); i++) { - const QDomElement childE = childNodes.at(i).toElement(); - if (childE.isNull()) - continue; - - if (childE.tagName() == QLatin1String("ApplicationName")) - applicationName = childE.text(); - else if (childE.tagName() == QLatin1String("ApplicationVersion")) - applicationVersion = childE.text(); - else if (childE.tagName() == QLatin1String("PackageUpdate")) { - if (!parsePackageUpdateElement(childE)) - return; //error handled in subroutine + QXmlStreamReader reader(&file); + if (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("Updates")) { + while (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("ApplicationName")) { + applicationName = reader.readElementText(); + } else if (reader.name() == QLatin1String("ApplicationVersion")) { + applicationVersion = reader.readElementText(); + } else if (reader.name() == QLatin1String("Checksum")) { + checkSha1CheckSum = (reader.readElementText()); + } else if (reader.name() == QLatin1String("PackageUpdate")) { + if (!parsePackageUpdateElement(reader, checkSha1CheckSum)) + return; //error handled in subroutine + } else { + reader.skipCurrentElement(); + } + } + } else { + setInvalidContentError(tr("Root element %1 unexpected, should be \"Updates\".").arg(reader.name())); + return; } } @@ -108,70 +102,58 @@ void UpdatesInfoData::parseFile(const QString &updateXmlFile) error = UpdatesInfo::NoError; } -bool UpdatesInfoData::parsePackageUpdateElement(const QDomElement &updateE) +bool UpdatesInfoData::parsePackageUpdateElement(QXmlStreamReader &reader, const QString &checkSha1CheckSum) { - if (updateE.isNull()) - return false; - UpdateInfo info; - QMap<QString, QString> localizedDescriptions; - for (int i = 0; i < updateE.childNodes().count(); i++) { - QDomElement childE = updateE.childNodes().at(i).toElement(); - if (childE.isNull()) + QHash<QString, QVariant> scriptHash; + while (reader.readNext()) { + const QString elementName = reader.name().toString(); + if ((reader.name() == QLatin1String("PackageUpdate")) + && (reader.tokenType() == QXmlStreamReader::EndElement)) { + break; + } + if (elementName.isEmpty() || reader.tokenType() == QXmlStreamReader::EndElement) continue; - - if (childE.tagName() == QLatin1String("ReleaseNotes")) { - info.data[childE.tagName()] = QUrl(childE.text()); - } else if (childE.tagName() == QLatin1String("Licenses")) { - QHash<QString, QVariant> licenseHash; - const QDomNodeList licenseNodes = childE.childNodes(); - for (int index = 0; index < licenseNodes.count(); ++index) { - const QDomNode licenseNode = licenseNodes.at(index); - if (licenseNode.nodeName() == QLatin1String("License")) { - QDomElement element = licenseNode.toElement(); - QVariantMap attributes; - attributes.insert(QLatin1String("file"), element.attributeNode(QLatin1String("file")).value()); - if (!element.attributeNode(QLatin1String("priority")).isNull()) - attributes.insert(QLatin1String("priority"), element.attributeNode(QLatin1String("priority")).value()); - else - attributes.insert(QLatin1String("priority"), QLatin1String("0")); - licenseHash.insert(element.attributeNode(QLatin1String("name")).value(), attributes); - } - } - if (!licenseHash.isEmpty()) - info.data.insert(QLatin1String("Licenses"), licenseHash); - } else if (childE.tagName() == QLatin1String("Version")) { + if (elementName == QLatin1String("Licenses")) { + parseLicenses(reader, info.data); + } else if (elementName == QLatin1String("TreeName")) { + const QXmlStreamAttributes attr = reader.attributes(); + const bool moveChildren = attr.value(QLatin1String("moveChildren")).toString().toLower() == QInstaller::scTrue ? true : false; + const QPair<QString, bool> treeNamePair(reader.readElementText(), moveChildren); + info.data.insert(QLatin1String("TreeName"), QVariant::fromValue(treeNamePair)); + } else if (elementName == QLatin1String("Version")) { + const QXmlStreamAttributes attr = reader.attributes(); info.data.insert(QLatin1String("inheritVersionFrom"), - childE.attribute(QLatin1String("inheritVersionFrom"))); - info.data[childE.tagName()] = childE.text(); - } else if (childE.tagName() == QLatin1String("DisplayName")) { - processLocalizedTag(childE, info.data); - } else if (childE.tagName() == QLatin1String("Description")) { - if (!childE.hasAttribute(QLatin1String("xml:lang"))) - info.data[QLatin1String("Description")] = childE.text(); - QString languageAttribute = childE.attribute(QLatin1String("xml:lang"), QLatin1String("en")); - localizedDescriptions.insert(languageAttribute.toLower(), childE.text()); - } else if (childE.tagName() == QLatin1String("UpdateFile")) { - info.data[QLatin1String("CompressedSize")] = childE.attribute(QLatin1String("CompressedSize")); - info.data[QLatin1String("UncompressedSize")] = childE.attribute(QLatin1String("UncompressedSize")); - } else if (childE.tagName() == QLatin1String("Operations")) { - const QDomNodeList operationNodes = childE.childNodes(); - QVariant operationListVariant = parseOperations(childE.childNodes()); - info.data.insert(QLatin1String("Operations"), operationListVariant); + attr.value(QLatin1String("inheritVersionFrom")).toString()); + info.data[elementName] = reader.readElementText(); + } else if (elementName == QLatin1String("DisplayName") + || elementName == QLatin1String("Description")) { + processLocalizedTag(reader, info.data); + } else if (elementName == QLatin1String("UpdateFile")) { + info.data[QLatin1String("CompressedSize")] = reader.attributes().value(QLatin1String("CompressedSize")).toString(); + info.data[QLatin1String("UncompressedSize")] = reader.attributes().value(QLatin1String("UncompressedSize")).toString(); + } else if (elementName == QLatin1String("Operations")) { + parseOperations(reader, info.data); + } else if (elementName == QLatin1String("Script")) { + const QXmlStreamAttributes attr = reader.attributes(); + bool postLoad = false; + // postLoad can be set either to individual components or to whole repositories. + // If individual components has the postLoad attribute, it overwrites the repository value. + if (attr.hasAttribute(QLatin1String("postLoad"))) + postLoad = attr.value(QLatin1String("postLoad")).toString().toLower() == QInstaller::scTrue ? true : false; + else if (m_postLoadComponentScript) + postLoad = true; + + if (postLoad) + scriptHash.insert(QLatin1String("postLoadScript"), reader.readElementText()); + else + scriptHash.insert(QLatin1String("installScript"), reader.readElementText()); } else { - info.data[childE.tagName()] = childE.text(); - } - } - - QStringList candidates; - foreach (const QString &lang, QLocale().uiLanguages()) - candidates << QInstaller::localeCandidates(lang.toLower()); - foreach (const QString &candidate, candidates) { - if (localizedDescriptions.contains(candidate)) { - info.data[QLatin1String("Description")] = localizedDescriptions.value(candidate); - break; + info.data[elementName] = reader.readElementText(); } } + if (!scriptHash.isEmpty()) + info.data.insert(QLatin1String("Script"), scriptHash); if (!info.data.contains(QLatin1String("Name"))) { setInvalidContentError(tr("PackageUpdate element without Name")); @@ -185,54 +167,87 @@ bool UpdatesInfoData::parsePackageUpdateElement(const QDomElement &updateE) setInvalidContentError(tr("PackageUpdate element without ReleaseDate")); return false; } - + info.data[QLatin1String("CheckSha1CheckSum")] = checkSha1CheckSum; updateInfoList.append(info); return true; } -void UpdatesInfoData::processLocalizedTag(const QDomElement &childE, QHash<QString, QVariant> &info) const +void UpdatesInfoData::processLocalizedTag(QXmlStreamReader &reader, QHash<QString, QVariant> &info) const { - QString languageAttribute = childE.attribute(QLatin1String("xml:lang")).toLower(); - if (!info.contains(childE.tagName()) && (languageAttribute.isEmpty())) - info[childE.tagName()] = childE.text(); + const QString languageAttribute = reader.attributes().value(QLatin1String("xml:lang")).toString().toLower(); + const QString elementName = reader.name().toString(); + if (!info.contains(elementName) && (languageAttribute.isEmpty())) + info[elementName] = reader.readElementText(); + if (languageAttribute.isEmpty()) + return; // overwrite default if we have a language specific description if (QLocale().name().startsWith(languageAttribute, Qt::CaseInsensitive)) - info[childE.tagName()] = childE.text(); + info[elementName] = reader.readElementText(); } -QVariant UpdatesInfoData::parseOperations(const QDomNodeList &operationNodes) +void UpdatesInfoData::parseOperations(QXmlStreamReader &reader, QHash<QString, QVariant> &info) const { - QVariant operationListVariant; QList<QPair<QString, QVariant>> operationsList; - for (int i = 0; i < operationNodes.count(); ++i) { - const QDomNode operationNode = operationNodes.at(i); - if (operationNode.nodeName() == QLatin1String("Operation")) { - const QDomNodeList argumentNodes = operationNode.childNodes(); - QStringList attributes; - for (int index = 0; index < argumentNodes.count(); ++index) { - const QDomNode argumentNode = argumentNodes.at(index); - if (argumentNode.nodeName() == QLatin1String("Argument")) { - QDomElement argumentElement = argumentNode.toElement(); - attributes.append(argumentElement.text()); - } + while (reader.readNext()) { + const QString subElementName = reader.name().toString(); + // End of parsing operations + if ((subElementName == QLatin1String("Operations")) + && (reader.tokenType() == QXmlStreamReader::EndElement)) { + break; + } + if (subElementName != QLatin1String("Operation") || reader.tokenType() == QXmlStreamReader::EndElement) + continue; + QStringList attributes; + const QXmlStreamAttributes attr = reader.attributes(); + while (reader.readNext()) { + const QString subElementName2 = reader.name().toString(); + // End of parsing single operation + if ((subElementName2 == QLatin1String("Operation")) + && (reader.tokenType() == QXmlStreamReader::EndElement)) { + break; } - QPair<QString, QVariant> pair; - pair.first = operationNode.toElement().attributeNode(QLatin1String("name")).value(); - pair.second = attributes; - operationsList.append(pair); + if (subElementName2 != QLatin1String("Argument") || reader.tokenType() == QXmlStreamReader::EndElement) + continue; + attributes.append(reader.readElementText()); } + QPair<QString, QVariant> pair; + pair.first = attr.value(QLatin1String("name")).toString(); + pair.second = attributes; + operationsList.append(pair); } - operationListVariant.setValue(operationsList); - return operationListVariant; + info.insert(QLatin1String("Operations"), QVariant::fromValue(operationsList)); } - +void UpdatesInfoData::parseLicenses(QXmlStreamReader &reader, QHash<QString, QVariant> &info) const +{ + QHash<QString, QVariant> licenseHash; + while (reader.readNext()) { + const QString subElementName = reader.name().toString(); + // End of parsing Licenses + if ((subElementName == QLatin1String("Licenses")) + && (reader.tokenType() == QXmlStreamReader::EndElement)) { + break; + } + if (subElementName != QLatin1String("License") || reader.tokenType() == QXmlStreamReader::EndElement) + continue; + const QXmlStreamAttributes attr = reader.attributes(); + QVariantMap attributes; + attributes.insert(QLatin1String("file"), attr.value(QLatin1String("file")).toString()); + if (!attr.value(QLatin1String("priority")).isNull()) + attributes.insert(QLatin1String("priority"), attr.value(QLatin1String("priority")).toString()); + else + attributes.insert(QLatin1String("priority"), QLatin1String("0")); + licenseHash.insert(attr.value(QLatin1String("name")).toString(), attributes); + } + if (!licenseHash.isEmpty()) + info.insert(QLatin1String("Licenses"), licenseHash); +} // // UpdatesInfo // -UpdatesInfo::UpdatesInfo() - : d(new UpdatesInfoData) +UpdatesInfo::UpdatesInfo(const bool postLoadComponentScript) + : d(new UpdatesInfoData(postLoadComponentScript)) { } @@ -260,6 +275,10 @@ void UpdatesInfo::setFileName(const QString &updateXmlFile) d->updateInfoList.clear(); d->updateXmlFile = updateXmlFile; +} + +void UpdatesInfo::parseFile() +{ d->parseFile(d->updateXmlFile); } @@ -278,6 +297,11 @@ QString UpdatesInfo::applicationVersion() const return d->applicationVersion; } +QString UpdatesInfo::checkSha1CheckSum() const +{ + return d->checkSha1CheckSum; +} + int UpdatesInfo::updateInfoCount() const { return d->updateInfoList.count(); diff --git a/src/libs/kdtools/updatesinfo_p.h b/src/libs/kdtools/updatesinfo_p.h index 93e2fe8c6..a3768232a 100644 --- a/src/libs/kdtools/updatesinfo_p.h +++ b/src/libs/kdtools/updatesinfo_p.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -58,7 +59,7 @@ public: InvalidContentError }; - UpdatesInfo(); + UpdatesInfo(const bool postLoadComponentScript = false); ~UpdatesInfo(); bool isValid() const; @@ -68,10 +69,13 @@ public: QString fileName() const; void setFileName(const QString &updateXmlFile); + void parseFile(); QString applicationName() const; QString applicationVersion() const; + QString checkSha1CheckSum() const; + int updateInfoCount() const; UpdateInfo updateInfo(int index) const; QList<UpdateInfo> updatesInfo() const; diff --git a/src/libs/kdtools/updatesinfodata_p.h b/src/libs/kdtools/updatesinfodata_p.h index 07da6fcf0..c71a85193 100644 --- a/src/libs/kdtools/updatesinfodata_p.h +++ b/src/libs/kdtools/updatesinfodata_p.h @@ -32,8 +32,7 @@ #include <QCoreApplication> #include <QSharedData> -QT_FORWARD_DECLARE_CLASS(QDomElement) -QT_FORWARD_DECLARE_CLASS(QDomNodeList) +QT_FORWARD_DECLARE_CLASS(QXmlStreamReader) namespace KDUpdater { @@ -44,7 +43,7 @@ struct UpdatesInfoData : public QSharedData Q_DECLARE_TR_FUNCTIONS(KDUpdater::UpdatesInfoData) public: - UpdatesInfoData(); + UpdatesInfoData(const bool postLoadComponentScript); ~UpdatesInfoData(); int error; @@ -52,16 +51,19 @@ public: QString updateXmlFile; QString applicationName; QString applicationVersion; + QString checkSha1CheckSum; QList<UpdateInfo> updateInfoList; + bool m_postLoadComponentScript; void parseFile(const QString &updateXmlFile); - bool parsePackageUpdateElement(const QDomElement &updateE); + bool parsePackageUpdateElement(QXmlStreamReader &reader, const QString &checkSha1CheckSum); void setInvalidContentError(const QString &detail); private: - void processLocalizedTag(const QDomElement &childE, QHash<QString, QVariant> &info) const; - QVariant parseOperations(const QDomNodeList &operationNodes); + void processLocalizedTag(QXmlStreamReader &reader, QHash<QString, QVariant> &info) const; + void parseOperations(QXmlStreamReader &reader, QHash<QString, QVariant> &info) const; + void parseLicenses(QXmlStreamReader &reader, QHash<QString, QVariant> &info) const; }; } // namespace KDUpdater |