File indexing completed on 2024-11-17 04:55:43

0001 /*
0002  *   SPDX-FileCopyrightText: 2013 Lukas Appelhans <l.appelhans@gmx.de>
0003  *
0004  *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 #include "PackageKitUpdater.h"
0007 #include "PackageKitMessages.h"
0008 #include <AppStreamQt/release.h>
0009 #include <appstream/AppStreamIntegration.h>
0010 
0011 #include <PackageKit/Daemon>
0012 #include <PackageKit/Offline>
0013 #include <QCryptographicHash>
0014 #include <QDBusConnection>
0015 #include <QDBusMessage>
0016 #include <QDBusReply>
0017 #include <QDebug>
0018 #include <QSet>
0019 
0020 #include <KConfigGroup>
0021 #include <KFormat>
0022 #include <KIO/FileSystemFreeSpaceJob>
0023 #include <KLocalizedString>
0024 #include <KSharedConfig>
0025 
0026 #include <optional>
0027 
0028 #include "libdiscover_backend_debug.h"
0029 #include "pk-offline-private.h"
0030 #include "utils.h"
0031 
0032 using namespace Qt::StringLiterals;
0033 
0034 int percentageWithStatus(PackageKit::Transaction::Status status, uint percentage)
0035 {
0036     const auto was = percentage;
0037     if (status != PackageKit::Transaction::StatusUnknown) {
0038         static const QMap<PackageKit::Transaction::Status, int> statuses = {
0039             {PackageKit::Transaction::Status::StatusDownload, 0},
0040             {PackageKit::Transaction::Status::StatusInstall, 1},
0041             {PackageKit::Transaction::Status::StatusRemove, 1},
0042             {PackageKit::Transaction::Status::StatusLoadingCache, 1},
0043             {PackageKit::Transaction::Status::StatusUpdate, 1},
0044         };
0045         const auto idx = statuses.value(status, -1);
0046         if (idx < 0) {
0047             qCDebug(LIBDISCOVER_BACKEND_LOG) << "Status not present" << status << "among" << statuses.keys() << percentage;
0048             return -1;
0049         }
0050         percentage = (idx * 100 + percentage) / 2 /*the maximum in statuses*/;
0051     }
0052     qCDebug(LIBDISCOVER_BACKEND_LOG) << "reporting progress with status:" << status << percentage << was;
0053     return percentage;
0054 }
0055 
0056 static void kRemoveDuplicates(QJsonArray &input, std::function<QString(const QJsonValueRef &)> fetchKey)
0057 {
0058     QSet<QString> ret;
0059     for (auto it = input.begin(); it != input.end();) {
0060         const auto key = fetchKey(*it);
0061         if (!ret.contains(key)) {
0062             ret << key;
0063             ++it;
0064         } else {
0065             it = input.erase(it);
0066         }
0067     }
0068 }
0069 
0070 class SystemUpgrade : public AbstractResource
0071 {
0072     Q_OBJECT
0073 public:
0074     SystemUpgrade(PackageKitBackend *backend)
0075         : AbstractResource(backend)
0076         , m_backend(backend)
0077         , m_updateSizeTimer(new QTimer(this))
0078     {
0079         connect(m_backend, &AbstractResourcesBackend::resourceRemoved, this, [this](AbstractResource *resource) {
0080             m_resources.remove(resource);
0081         });
0082 
0083         m_updateSizeTimer->setInterval(100);
0084         m_updateSizeTimer->setSingleShot(true);
0085         connect(m_updateSizeTimer, &QTimer::timeout, this, &SystemUpgrade::refreshResource);
0086     }
0087 
0088     QString packageName() const override
0089     {
0090         return QStringLiteral("discover-offline-upgrade");
0091     }
0092     QString name() const override
0093     {
0094         if (isDistroUpgrade()) {
0095             return i18nc("distro upgrade: name version", "%1 %2", AppStreamIntegration::global()->osRelease()->name(), m_distroUpgrade->version());
0096         }
0097         return i18n("System upgrade");
0098     }
0099     QString comment() override
0100     {
0101         return upgradeText();
0102     }
0103     QVariant icon() const override
0104     {
0105         return QStringLiteral("system-upgrade");
0106     }
0107     bool canExecute() const override
0108     {
0109         return false;
0110     }
0111     void invokeApplication() const override
0112     {
0113     }
0114     State state() override
0115     {
0116         return Upgradeable;
0117     }
0118     QStringList categories() override
0119     {
0120         return {};
0121     }
0122     AbstractResource::Type type() const override
0123     {
0124         return Technical;
0125     }
0126     bool isRemovable() const override
0127     {
0128         return false;
0129     }
0130 
0131     QVector<PackageKitResource *> withoutDuplicates() const
0132     {
0133         QVector<PackageKitResource *> ret;
0134         QSet<QString> donePkgs;
0135         for (auto resource : std::as_const(m_resources)) {
0136             const auto pkResource = qobject_cast<PackageKitResource *>(resource);
0137             const auto pkgName = pkResource->packageName();
0138             if (!donePkgs.contains(pkgName)) {
0139                 donePkgs.insert(pkgName);
0140                 ret += pkResource;
0141             }
0142         }
0143         return ret;
0144     }
0145 
0146     quint64 size() override
0147     {
0148         quint64 ret = 0;
0149         if (isDistroUpgrade()) {
0150             return ret;
0151         }
0152 
0153         const auto resources = withoutDuplicates();
0154         for (auto pkResource : resources) {
0155             ret += pkResource->size();
0156         }
0157         return ret;
0158     }
0159     QJsonArray licenses() override
0160     {
0161         QJsonArray ret;
0162         for (auto res : std::as_const(m_resources)) {
0163             ret += res->licenses();
0164         }
0165         kRemoveDuplicates(ret, [](const QJsonValueRef &val) -> QString {
0166             return val.toObject()[QLatin1String("name")].toString();
0167         });
0168         return ret;
0169     }
0170     QString section() override
0171     {
0172         return {};
0173     }
0174     QString origin() const override
0175     {
0176         return {};
0177     }
0178     QString author() const override
0179     {
0180         return {};
0181     }
0182     QList<PackageState> addonsInformation() override
0183     {
0184         return {};
0185     }
0186     QString upgradeText() const override
0187     {
0188         return i18np("1 package will be upgraded", "%1 packages will be upgraded", withoutDuplicates().count());
0189     }
0190     QString longDescription() override
0191     {
0192         QStringList changes;
0193         const auto resources = withoutDuplicates();
0194         for (auto resource : resources) {
0195             const auto changelog = resource->changelog();
0196             if (changelog.isEmpty()) {
0197                 changes += i18n("<h3>%1</h3>Upgrade to new version %2<br/>No release notes provided", resource->packageName(), resource->availableVersion());
0198             } else {
0199                 changes += i18n("<h3>%1</h3>Upgrade to new version %2<br/>Release notes:<blockquote>%3</blockquote>",
0200                                 resource->packageName(),
0201                                 resource->availableVersion(),
0202                                 changelog);
0203             }
0204         }
0205         changes.sort();
0206 
0207         if (isDistroUpgrade()) {
0208             changes.prepend(m_distroUpgrade->description());
0209         }
0210         return changes.join(QString());
0211     }
0212     void fetchChangelog() override
0213     {
0214         if (isDistroUpgrade()) {
0215             return;
0216         }
0217 
0218         for (auto resource : std::as_const(m_resources)) {
0219             resource->fetchUpdateDetails();
0220         }
0221         Q_EMIT changelogFetched({});
0222     }
0223 
0224     QString installedVersion() const override
0225     {
0226         return i18n("Present");
0227     }
0228     QString availableVersion() const override
0229     {
0230         return i18n("Future");
0231     }
0232     QString sourceIcon() const override
0233     {
0234         return QStringLiteral("package-x-generic");
0235     }
0236     QDate releaseDate() const override
0237     {
0238         return {};
0239     }
0240 
0241     QSet<AbstractResource *> resources() const
0242     {
0243         return m_resources;
0244     }
0245 
0246     QSet<QString> allPackageNames() const
0247     {
0248         QSet<QString> ret;
0249         for (auto resource : std::as_const(m_resources)) {
0250             ret += kToSet(qobject_cast<PackageKitResource *>(resource)->allPackageNames());
0251         }
0252         return ret;
0253     }
0254 
0255     void refreshResource()
0256     {
0257         Q_EMIT m_backend->resourcesChanged(this, {"size", "license"});
0258         Q_EMIT updateSizeChanged();
0259     }
0260 
0261     void setCandidates(const QSet<AbstractResource *> &candidates)
0262     {
0263         const auto toDisconnect = (m_resources - candidates);
0264         for (auto resource : toDisconnect) {
0265             disconnect(resource, &AbstractResource::sizeChanged, this, &SystemUpgrade::startIfStopped);
0266             disconnect(resource, &AbstractResource::changelogFetched, this, &SystemUpgrade::startIfStopped);
0267         }
0268 
0269         const auto newCandidates = (candidates - m_resources);
0270         m_resources = candidates;
0271 
0272         for (auto resource : newCandidates) {
0273             connect(resource, &AbstractResource::sizeChanged, this, &SystemUpgrade::startIfStopped);
0274             connect(resource, &AbstractResource::changelogFetched, this, &SystemUpgrade::startIfStopped);
0275         }
0276     }
0277 
0278     void startIfStopped()
0279     {
0280         if (!m_updateSizeTimer->isActive()) {
0281             m_updateSizeTimer->start();
0282         }
0283     }
0284 
0285     void setDistroUpgrade(const AppStream::Release &release)
0286     {
0287         m_distroUpgrade = release;
0288     }
0289 
0290     void clearDistroUpgrade()
0291     {
0292         m_distroUpgrade = std::nullopt;
0293         Q_EMIT m_backend->inlineMessageChanged({});
0294     }
0295 
0296     bool isDistroUpgrade() const
0297     {
0298         return m_distroUpgrade.has_value();
0299     }
0300 
0301     const AppStream::Release &getDistroUpgrade()
0302     {
0303         return m_distroUpgrade.value();
0304     }
0305 
0306 Q_SIGNALS:
0307     void updateSizeChanged();
0308 
0309 private:
0310     QSet<AbstractResource *> m_resources;
0311     PackageKitBackend *const m_backend;
0312     QTimer *m_updateSizeTimer;
0313     std::optional<AppStream::Release> m_distroUpgrade;
0314 };
0315 
0316 PackageKitUpdater::PackageKitUpdater(PackageKitBackend *parent)
0317     : AbstractBackendUpdater(parent)
0318     , m_transaction(nullptr)
0319     , m_backend(parent)
0320     , m_isCancelable(false)
0321     , m_isProgressing(false)
0322     , m_percentage(0)
0323     , m_lastUpdate()
0324     , m_upgrade(new SystemUpgrade(m_backend))
0325 {
0326     fetchLastUpdateTime();
0327 }
0328 
0329 PackageKitUpdater::~PackageKitUpdater()
0330 {
0331 }
0332 
0333 void PackageKitUpdater::prepare()
0334 {
0335     auto offline = PackageKit::Daemon::global()->offline();
0336     if (offline->updateTriggered() || offline->upgradeTriggered()) {
0337         m_toUpgrade.clear();
0338         m_allUpgradeable.clear();
0339         setNeedsReboot(true);
0340         return;
0341     }
0342 
0343     if (QFile::exists(QStringLiteral(PK_OFFLINE_RESULTS_FILENAME))) {
0344         qCDebug(LIBDISCOVER_BACKEND_LOG) << "Removed offline results file";
0345         offline->clearResults();
0346     }
0347 
0348     Q_ASSERT(!m_transaction);
0349     const auto candidates = m_backend->upgradeablePackages();
0350     if (useOfflineUpdates() && !candidates.isEmpty()) {
0351         m_upgrade->setCandidates(candidates);
0352 
0353         m_toUpgrade = {m_upgrade};
0354         connect(m_upgrade, &SystemUpgrade::updateSizeChanged, this, &PackageKitUpdater::checkFreeSpace);
0355     } else {
0356         m_toUpgrade = candidates;
0357     }
0358 
0359     checkFreeSpace();
0360     m_allUpgradeable = m_toUpgrade;
0361 }
0362 
0363 void PackageKitUpdater::checkFreeSpace()
0364 {
0365     auto job = KIO::fileSystemFreeSpace(QUrl::fromLocalFile(QStringLiteral("/usr")));
0366     connect(job, &KJob::result, this, [this, job]() {
0367         if (job->availableSize() < updateSize()) {
0368             setErrorMessage(i18nc("@info:status %1 is a formatted disk space string e.g. '240 MiB'",
0369                                   "Not enough space to perform the update; only %1 of space are available.",
0370                                   KFormat().formatByteSize(job->availableSize())));
0371         }
0372     });
0373 }
0374 
0375 void PackageKitUpdater::setupTransaction(PackageKit::Transaction::TransactionFlags flags)
0376 {
0377     m_packagesModified.clear();
0378     if (m_toUpgrade.contains(m_upgrade) && m_upgrade->isDistroUpgrade()) {
0379         const QString &upgradeVersion = m_upgrade->getDistroUpgrade().version();
0380         m_transaction = PackageKit::Daemon::upgradeSystem(upgradeVersion, PackageKit::Transaction::UpgradeKind::UpgradeKindComplete, flags);
0381         m_transaction->setHints(m_backend->globalHints() << QStringLiteral("cache-age=86400" /* 24*60*60 */));
0382     } else {
0383         auto pkgs = involvedPackages(m_toUpgrade).values();
0384         pkgs.sort();
0385         m_transaction = PackageKit::Daemon::updatePackages(pkgs, flags);
0386     }
0387     m_isCancelable = m_transaction->allowCancel();
0388     cancellableChanged();
0389 
0390     connect(m_transaction.data(), &PackageKit::Transaction::finished, this, &PackageKitUpdater::finished);
0391     connect(m_transaction.data(), &PackageKit::Transaction::package, this, &PackageKitUpdater::packageResolved);
0392     connect(m_transaction.data(), &PackageKit::Transaction::errorCode, this, &PackageKitUpdater::errorFound);
0393     connect(m_transaction.data(), &PackageKit::Transaction::mediaChangeRequired, this, &PackageKitUpdater::mediaChange);
0394     connect(m_transaction.data(), &PackageKit::Transaction::eulaRequired, this, &PackageKitUpdater::eulaRequired);
0395     connect(m_transaction.data(), &PackageKit::Transaction::repoSignatureRequired, this, &PackageKitUpdater::repoSignatureRequired);
0396     connect(m_transaction.data(), &PackageKit::Transaction::allowCancelChanged, this, &PackageKitUpdater::cancellableChanged);
0397     connect(m_transaction.data(), &PackageKit::Transaction::itemProgress, this, &PackageKitUpdater::itemProgress);
0398     connect(m_transaction.data(), &PackageKit::Transaction::speedChanged, this, [this] {
0399         Q_EMIT downloadSpeedChanged(downloadSpeed());
0400     });
0401     if (!(flags & PackageKit::Transaction::TransactionFlagSimulate)) {
0402         connect(m_transaction.data(), &PackageKit::Transaction::percentageChanged, this, &PackageKitUpdater::percentageChanged);
0403         if (m_toUpgrade.contains(m_upgrade)) {
0404             connect(m_transaction, &PackageKit::Transaction::percentageChanged, this, [this] {
0405                 if (m_transaction->status() == PackageKit::Transaction::StatusDownload) {
0406                     Q_EMIT resourceProgressed(m_upgrade, m_transaction->percentage(), Downloading);
0407                 }
0408             });
0409         }
0410     }
0411 }
0412 
0413 QSet<AbstractResource *> PackageKitUpdater::packagesForPackageId(const QSet<QString> &pkgids) const
0414 {
0415     const auto packages = kTransform<QSet<QString>>(pkgids, [](const QString &pkgid) {
0416         return PackageKit::Daemon::packageName(pkgid);
0417     });
0418 
0419     QSet<AbstractResource *> ret;
0420     for (auto resource : std::as_const(m_allUpgradeable)) {
0421         QSet<QString> allPackageNames;
0422 
0423         if (auto upgrade = qobject_cast<SystemUpgrade *>(resource)) {
0424             allPackageNames = upgrade->allPackageNames();
0425         } else if (auto pkResource = qobject_cast<PackageKitResource *>(resource)) {
0426             allPackageNames = kToSet(pkResource->allPackageNames());
0427         }
0428 
0429         if (!allPackageNames.isEmpty() && packages.contains(allPackageNames)) {
0430             ret.insert(resource);
0431         }
0432     }
0433 
0434     return ret;
0435 }
0436 
0437 QSet<QString> PackageKitUpdater::involvedPackages(const QSet<AbstractResource *> &packages) const
0438 {
0439     QSet<QString> packageIds;
0440     packageIds.reserve(packages.size());
0441     for (auto resource : packages) {
0442         if (auto upgrade = qobject_cast<SystemUpgrade *>(resource)) {
0443             packageIds = involvedPackages(upgrade->resources());
0444             continue;
0445         }
0446 
0447         auto pkResource = qobject_cast<PackageKitResource *>(resource);
0448         const QSet<QString> ids = m_backend->upgradeablePackageId(pkResource);
0449         if (ids.isEmpty()) {
0450             qWarning() << "no upgradeablePackageId for" << pkResource;
0451             continue;
0452         }
0453 
0454         packageIds.unite(ids);
0455     }
0456     return packageIds;
0457 }
0458 
0459 void PackageKitUpdater::processProceedFunction()
0460 {
0461     auto t = m_proceedFunctions.takeFirst()();
0462     connect(t, &PackageKit::Transaction::finished, this, [this](PackageKit::Transaction::Exit status) {
0463         if (status != PackageKit::Transaction::Exit::ExitSuccess) {
0464             qWarning() << "transaction failed" << sender() << status;
0465             cancel();
0466             return;
0467         }
0468 
0469         if (!m_proceedFunctions.isEmpty()) {
0470             processProceedFunction();
0471         } else {
0472             start();
0473         }
0474     });
0475 }
0476 
0477 void PackageKitUpdater::proceed()
0478 {
0479     if (!m_proceedFunctions.isEmpty()) {
0480         processProceedFunction();
0481     } else if (useOfflineUpdates()) {
0482         setupTransaction(PackageKit::Transaction::TransactionFlagOnlyTrusted | PackageKit::Transaction::TransactionFlagOnlyDownload);
0483     } else {
0484         setupTransaction(PackageKit::Transaction::TransactionFlagOnlyTrusted);
0485     }
0486 }
0487 
0488 bool PackageKitUpdater::useOfflineUpdates() const
0489 {
0490     return m_useOfflineUpdates || m_upgrade->isDistroUpgrade() || qEnvironmentVariableIntValue("PK_OFFLINE_UPDATE");
0491 }
0492 
0493 void PackageKitUpdater::setOfflineUpdates(bool use)
0494 {
0495     m_useOfflineUpdates = use;
0496 }
0497 
0498 void PackageKitUpdater::setDistroUpgrade(const AppStream::Release &release)
0499 {
0500     m_upgrade->setDistroUpgrade(release);
0501 }
0502 
0503 void PackageKitUpdater::clearDistroUpgrade()
0504 {
0505     m_upgrade->clearDistroUpgrade();
0506 }
0507 
0508 bool PackageKitUpdater::isDistroUpgrade() const
0509 {
0510     return m_upgrade->isDistroUpgrade();
0511 }
0512 
0513 void PackageKitUpdater::start()
0514 {
0515     Q_ASSERT(!isProgressing());
0516 
0517     setupTransaction(PackageKit::Transaction::TransactionFlagSimulate);
0518     setProgressing(true);
0519 
0520     if (useOfflineUpdates()) {
0521         setNeedsReboot(true);
0522     }
0523 }
0524 
0525 void PackageKitUpdater::finished(PackageKit::Transaction::Exit exit, uint /*time*/)
0526 {
0527     // qCDebug(LIBDISCOVER_BACKEND_LOG) << "update finished!" << exit << time;
0528     if (!m_proceedFunctions.isEmpty()) {
0529         return;
0530     }
0531     const bool cancel = exit == PackageKit::Transaction::ExitCancelled;
0532     const bool simulate = m_transaction->transactionFlags() & PackageKit::Transaction::TransactionFlagSimulate;
0533 
0534     disconnect(m_transaction, nullptr, this, nullptr);
0535     m_transaction = nullptr;
0536 
0537     if (!cancel && simulate) {
0538         auto toremoveOrig = m_packagesModified.value(PackageKit::Transaction::InfoRemoving);
0539         auto toremove = toremoveOrig;
0540         auto toinstall = QStringList() << m_packagesModified.value(PackageKit::Transaction::InfoInstalling)
0541                                        << m_packagesModified.value(PackageKit::Transaction::InfoUpdating);
0542 
0543         // some backends will treat upgrades as removal + install, which makes for terrible error messages.
0544         for (auto it = toremove.begin(), itEnd = toremove.end(); it != itEnd;) {
0545             const QString name = PackageKit::Transaction::packageName(*it);
0546             auto itInstall = std::find_if(toinstall.begin(), toinstall.end(), [&](const QString &pkgid) {
0547                 return name == PackageKit::Transaction::packageName(pkgid);
0548             });
0549             if (itInstall != toinstall.end()) {
0550                 toinstall.erase(itInstall);
0551                 it = toremove.erase(it);
0552             } else {
0553                 ++it;
0554             }
0555         };
0556 
0557         if (PackageKit::Daemon::backendName() == QLatin1String("dnf")) {
0558             // Fedora has some packages that it uninstalls then eventually creates on its own. No need to
0559             // notify about these.
0560             toremove = kFilter<QStringList>(toremove, [](const QString &pkgid) {
0561                 return !PackageKit::Transaction::packageName(pkgid).startsWith(QLatin1String("kmod"));
0562             });
0563         }
0564 
0565         if (!toremove.isEmpty()) {
0566             QStringList criticals;
0567             for (const auto &pkgid : std::as_const(toremove)) {
0568                 auto resources = kFilter<QVector<AbstractResource *>>(m_backend->resourcesByPackageName(pkgid), [](AbstractResource *resource) {
0569                     return static_cast<PackageKitResource *>(resource)->isCritical();
0570                 });
0571                 criticals << kTransform<QStringList>(resources, [](AbstractResource *resource) {
0572                     return resource->name();
0573                 });
0574                 if (!criticals.isEmpty()) {
0575                     break;
0576                 }
0577             }
0578 
0579             if (!criticals.isEmpty()) {
0580                 const QString msg = i18n(
0581                     "This update cannot be completed as it would remove the following software which is critical to the system's operation:<nl/>"
0582                     "<ul><li>%1</li></ul><nl/>"
0583                     "If you believe this is an error, please report it as a bug to the packagers of your distribution.",
0584                     criticals.constFirst());
0585                 Q_EMIT distroErrorMessage(msg);
0586             } else {
0587                 Q_EMIT proceedRequest(
0588                     i18n("Confirm Changes"),
0589                     i18n("The following packages will be removed by the update:<ul><li>%1</li></ul><br/>in order to install:<ul><li>%2</li></ul>",
0590                          PackageKitResource::joinPackages(toremove, QStringLiteral("</li><li>"), {}),
0591                          PackageKitResource::joinPackages(toinstall, QStringLiteral("</li><li>"), {})));
0592             }
0593         } else {
0594             proceed();
0595         }
0596         return;
0597     }
0598 
0599     if (!m_toUpgrade.contains(m_upgrade) || !m_upgrade->isDistroUpgrade()) {
0600         // If the distro-upgrade was prepared successfully, fetching updates now would invalidate it.
0601         // If the distro-upgrade failed, fetching updates now would find nothing and we would propose to reboot.
0602         setProgressing(false);
0603         m_backend->fetchUpdates();
0604         fetchLastUpdateTime();
0605     } else if (exit != PackageKit::Transaction::ExitSuccess) {
0606         Q_EMIT resourceProgressed(m_upgrade, 0, None);
0607         setNeedsReboot(false);
0608         setProgressing(false);
0609     }
0610 
0611     if (useOfflineUpdates() && exit == PackageKit::Transaction::ExitSuccess) {
0612         if (m_upgrade->isDistroUpgrade()) {
0613             QDBusPendingReply<void> reply = PackageKit::Daemon::global()->offline()->triggerUpgrade(PackageKit::Offline::ActionReboot);
0614             // Call may fail because of authorization
0615             reply.waitForFinished();
0616             if (reply.isError()) {
0617                 Q_EMIT resourceProgressed(m_upgrade, 0, None);
0618                 setNeedsReboot(false);
0619                 setProgressing(false);
0620                 Q_EMIT passiveMessage(reply.error().message());
0621                 return;
0622             }
0623             m_backend->clear();
0624             setProgressing(false);
0625         } else {
0626             PackageKit::Daemon::global()->offline()->trigger(PackageKit::Offline::ActionReboot);
0627         }
0628         enableReadyToReboot();
0629     }
0630 }
0631 
0632 void PackageKitUpdater::cancellableChanged()
0633 {
0634     if (m_isCancelable != m_transaction->allowCancel()) {
0635         m_isCancelable = m_transaction->allowCancel();
0636         Q_EMIT cancelableChanged(m_isCancelable);
0637     }
0638 }
0639 
0640 void PackageKitUpdater::percentageChanged()
0641 {
0642     const int percentage = m_transaction->percentage();
0643     if (percentage > 100) {
0644         return;
0645     }
0646     const auto actualPercentage = useOfflineUpdates() ? percentage : percentageWithStatus(m_transaction->status(), percentage);
0647     if (actualPercentage >= 0 && m_percentage != actualPercentage) {
0648         m_percentage = actualPercentage;
0649         Q_EMIT progressChanged(m_percentage);
0650     }
0651 }
0652 
0653 bool PackageKitUpdater::hasUpdates() const
0654 {
0655     return m_backend->updatesCount() > 0;
0656 }
0657 
0658 qreal PackageKitUpdater::progress() const
0659 {
0660     return m_percentage;
0661 }
0662 
0663 void PackageKitUpdater::removeResources(const QList<AbstractResource *> &resources)
0664 {
0665     const QSet<QString> pkgs = involvedPackages(kToSet(resources));
0666     m_toUpgrade.subtract(packagesForPackageId(pkgs));
0667 }
0668 
0669 void PackageKitUpdater::addResources(const QList<AbstractResource *> &resources)
0670 {
0671     const QSet<QString> pkgs = involvedPackages(kToSet(resources));
0672     m_toUpgrade.unite(packagesForPackageId(pkgs));
0673 }
0674 
0675 QList<AbstractResource *> PackageKitUpdater::toUpdate() const
0676 {
0677     return m_toUpgrade.values();
0678 }
0679 
0680 bool PackageKitUpdater::isMarked(AbstractResource *resource) const
0681 {
0682     return m_toUpgrade.contains(resource);
0683 }
0684 
0685 QDateTime PackageKitUpdater::lastUpdate() const
0686 {
0687     return m_lastUpdate;
0688 }
0689 
0690 bool PackageKitUpdater::isCancelable() const
0691 {
0692     return m_isCancelable;
0693 }
0694 
0695 bool PackageKitUpdater::isProgressing() const
0696 {
0697     return m_isProgressing;
0698 }
0699 
0700 void PackageKitUpdater::cancel()
0701 {
0702     if (m_transaction) {
0703         m_transaction->cancel();
0704     } else {
0705         setProgressing(false);
0706     }
0707 }
0708 
0709 void PackageKitUpdater::errorFound(PackageKit::Transaction::Error err, const QString &error)
0710 {
0711     if (err == PackageKit::Transaction::ErrorNoLicenseAgreement || err == PackageKit::Transaction::ErrorTransactionCancelled
0712         || err == PackageKit::Transaction::ErrorNotAuthorized) {
0713         return;
0714     }
0715     QString finalMessage = xi18nc("@info", "%1:<nl/><nl/>%2", PackageKitMessages::errorMessage(err, QString()), error);
0716     Q_EMIT passiveMessage(finalMessage);
0717     qWarning() << "Error happened" << err << error;
0718 }
0719 
0720 void PackageKitUpdater::mediaChange(PackageKit::Transaction::MediaType media, const QString &type, const QString &text)
0721 {
0722     Q_UNUSED(media)
0723     Q_EMIT passiveMessage(i18n("Media Change of type '%1' is requested.\n%2", type, text));
0724 }
0725 
0726 EulaHandling handleEula(const QString &eulaID, const QString &licenseAgreement)
0727 {
0728     KConfigGroup group(KSharedConfig::openConfig(), u"EULA"_s);
0729     auto licenseGroup = group.group(eulaID);
0730     QCryptographicHash hash(QCryptographicHash::Sha256);
0731     hash.addData(licenseAgreement.toUtf8());
0732     QByteArray hashHex = hash.result().toHex();
0733 
0734     EulaHandling ret;
0735     ret.request = licenseGroup.readEntry("Hash", QByteArray()) != hashHex;
0736     if (!ret.request) {
0737         ret.proceedFunction = [eulaID] {
0738             return PackageKit::Daemon::acceptEula(eulaID);
0739         };
0740     } else {
0741         ret.proceedFunction = [eulaID, hashHex] {
0742             KConfigGroup group(KSharedConfig::openConfig(), u"EULA"_s);
0743             KConfigGroup licenseGroup = group.group(eulaID);
0744             licenseGroup.writeEntry<QByteArray>("Hash", hashHex);
0745             return PackageKit::Daemon::acceptEula(eulaID);
0746         };
0747     }
0748     return ret;
0749 }
0750 
0751 void PackageKitUpdater::eulaRequired(const QString &eulaID, const QString &packageID, const QString &vendor, const QString &licenseAgreement)
0752 {
0753     const auto handle = handleEula(eulaID, licenseAgreement);
0754     m_proceedFunctions << handle.proceedFunction;
0755     if (handle.request) {
0756         Q_EMIT proceedRequest(i18n("Accept EULA"),
0757                               i18n("The package %1 and its vendor %2 require that you accept their license:\n %3",
0758                                    PackageKit::Daemon::packageName(packageID),
0759                                    vendor,
0760                                    licenseAgreement));
0761     } else {
0762         proceed();
0763     }
0764 }
0765 
0766 void PackageKitUpdater::setProgressing(bool progressing)
0767 {
0768     if (m_isProgressing != progressing) {
0769         m_isProgressing = progressing;
0770         Q_EMIT progressingChanged(m_isProgressing);
0771     }
0772 }
0773 
0774 void PackageKitUpdater::fetchLastUpdateTime()
0775 {
0776     QDBusPendingReply<uint> transaction = PackageKit::Daemon::global()->getTimeSinceAction(PackageKit::Transaction::RoleGetUpdates);
0777     QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(transaction, this);
0778     connect(watcher, &QDBusPendingCallWatcher::finished, this, &PackageKitUpdater::lastUpdateTimeReceived);
0779 }
0780 
0781 void PackageKitUpdater::lastUpdateTimeReceived(QDBusPendingCallWatcher *w)
0782 {
0783     QDBusPendingReply<uint> reply = w->reply();
0784     if (reply.isError()) {
0785         qWarning() << "Error when fetching the last update time" << reply.error();
0786     } else {
0787         m_lastUpdate = QDateTime::currentDateTime().addSecs(-int(reply.value()));
0788     }
0789     w->deleteLater();
0790 }
0791 
0792 AbstractBackendUpdater::State toUpdateState(PackageKit::Transaction::Status t)
0793 {
0794     switch (t) {
0795     case PackageKit::Transaction::StatusUnknown:
0796     case PackageKit::Transaction::StatusDownload:
0797         return AbstractBackendUpdater::Downloading;
0798     case PackageKit::Transaction::StatusDepResolve:
0799     case PackageKit::Transaction::StatusSigCheck:
0800     case PackageKit::Transaction::StatusTestCommit:
0801     case PackageKit::Transaction::StatusInstall:
0802     case PackageKit::Transaction::StatusCommit:
0803         return AbstractBackendUpdater::Installing;
0804     case PackageKit::Transaction::StatusFinished:
0805     case PackageKit::Transaction::StatusCancel:
0806         return AbstractBackendUpdater::Done;
0807     default:
0808         qCDebug(LIBDISCOVER_BACKEND_LOG) << "unknown packagekit status" << t;
0809         return AbstractBackendUpdater::None;
0810     }
0811     Q_UNREACHABLE();
0812 }
0813 
0814 void PackageKitUpdater::itemProgress(const QString &itemID, PackageKit::Transaction::Status status, uint percentage)
0815 {
0816     const auto resources = packagesForPackageId({itemID});
0817 
0818     for (auto resource : resources) {
0819         Q_EMIT resourceProgressed(resource, percentage, toUpdateState(status));
0820     }
0821 }
0822 
0823 void PackageKitUpdater::fetchChangelog() const
0824 {
0825     QStringList pkgids;
0826     for (auto resource : std::as_const(m_allUpgradeable)) {
0827         if (auto upgrade = qobject_cast<SystemUpgrade *>(resource)) {
0828             upgrade->fetchChangelog();
0829         } else {
0830             pkgids += static_cast<PackageKitResource *>(resource)->availablePackageId();
0831         }
0832     }
0833     Q_ASSERT(!pkgids.isEmpty());
0834 
0835     PackageKit::Transaction *transaction = PackageKit::Daemon::getUpdatesDetails(pkgids);
0836     connect(transaction, &PackageKit::Transaction::updateDetail, this, &PackageKitUpdater::updateDetail);
0837     connect(transaction, &PackageKit::Transaction::errorCode, this, &PackageKitUpdater::errorFound);
0838 }
0839 
0840 void PackageKitUpdater::updateDetail(const QString &packageID,
0841                                      const QStringList &updates,
0842                                      const QStringList &obsoletes,
0843                                      const QStringList &vendorUrls,
0844                                      const QStringList &bugzillaUrls,
0845                                      const QStringList &cveUrls,
0846                                      PackageKit::Transaction::Restart restart,
0847                                      const QString &updateText,
0848                                      const QString &changelog,
0849                                      PackageKit::Transaction::UpdateState state,
0850                                      const QDateTime &issued,
0851                                      const QDateTime &updated)
0852 {
0853     const auto resources = packagesForPackageId({packageID});
0854     for (auto resource : resources) {
0855         auto pkResource = static_cast<PackageKitResource *>(resource);
0856         pkResource->updateDetail(packageID, updates, obsoletes, vendorUrls, bugzillaUrls, cveUrls, restart, updateText, changelog, state, issued, updated);
0857     }
0858 }
0859 
0860 void PackageKitUpdater::packageResolved(PackageKit::Transaction::Info info, const QString &packageId)
0861 {
0862     m_packagesModified[info] << packageId;
0863 }
0864 
0865 void PackageKitUpdater::repoSignatureRequired(const QString &packageID,
0866                                               const QString &repoName,
0867                                               const QString &keyUrl,
0868                                               const QString &keyUserid,
0869                                               const QString &keyId,
0870                                               const QString &keyFingerprint,
0871                                               const QString &keyTimestamp,
0872                                               PackageKit::Transaction::SigType type)
0873 {
0874     Q_EMIT proceedRequest(i18n("Missing signature for %1 in %2", packageID, repoName),
0875                           i18n("Do you trust the following key?\n\nUrl: %1\nUser: %2\nKey: %3\nFingerprint: %4\nTimestamp: %4\n",
0876                                keyUrl,
0877                                keyUserid,
0878                                keyFingerprint,
0879                                keyTimestamp));
0880 
0881     m_proceedFunctions << [type, keyId, packageID]() {
0882         return PackageKit::Daemon::installSignature(type, keyId, packageID);
0883     };
0884 }
0885 
0886 double PackageKitUpdater::updateSize() const
0887 {
0888     double ret = 0.;
0889     QSet<QString> donePkgs;
0890     for (auto resource : std::as_const(m_toUpgrade)) {
0891         if (auto upgrade = qobject_cast<SystemUpgrade *>(resource)) {
0892             ret += upgrade->size();
0893         } else if (auto pkResource = qobject_cast<PackageKitResource *>(resource)) {
0894             QString pkgname = pkResource->packageName();
0895             if (!donePkgs.contains(pkgname)) {
0896                 donePkgs.insert(pkgname);
0897                 ret += pkResource->size();
0898             }
0899         }
0900     }
0901     return ret;
0902 }
0903 
0904 quint64 PackageKitUpdater::downloadSpeed() const
0905 {
0906     return m_transaction ? m_transaction->speed() : 0;
0907 }
0908 
0909 #include "PackageKitUpdater.moc"