diff options
Diffstat (limited to 'src/libs/kdtools/updatefinder.cpp')
-rw-r--r-- | src/libs/kdtools/updatefinder.cpp | 414 |
1 files changed, 204 insertions, 210 deletions
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; |