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

0001 /*
0002  *   SPDX-FileCopyrightText: 2013 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
0003  *
0004  *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "PKTransaction.h"
0008 #include "LocalFilePKResource.h"
0009 #include "PackageKitBackend.h"
0010 #include "PackageKitMessages.h"
0011 #include "PackageKitResource.h"
0012 #include "PackageKitUpdater.h"
0013 #include "libdiscover_backend_debug.h"
0014 #include "utils.h"
0015 #include <KLocalizedString>
0016 #include <PackageKit/Daemon>
0017 #include <QDebug>
0018 #include <QTimer>
0019 #include <functional>
0020 #include <resources/AbstractResource.h>
0021 
0022 PKTransaction::PKTransaction(const QVector<AbstractResource *> &resources, Transaction::Role role)
0023     : Transaction(resources.first(), resources.first(), role)
0024     , m_apps(resources)
0025 {
0026     Q_ASSERT(!resources.contains(nullptr));
0027     for (auto resource : resources) {
0028         auto pkResource = qobject_cast<PackageKitResource *>(resource);
0029         m_pkgnames.unite(kToSet(pkResource->allPackageNames()));
0030     }
0031 
0032     QTimer::singleShot(0, this, &PKTransaction::start);
0033 }
0034 
0035 static QStringList packageIds(const QVector<AbstractResource *> &resources, std::function<QString(PackageKitResource *)> func)
0036 {
0037     QStringList ret;
0038     for (auto resource : resources) {
0039         ret += func(qobject_cast<PackageKitResource *>(resource));
0040     }
0041     ret.removeDuplicates();
0042     return ret;
0043 }
0044 
0045 bool PKTransaction::isLocal() const
0046 {
0047     return m_apps.size() == 1 && qobject_cast<LocalFilePKResource *>(m_apps.at(0));
0048 }
0049 
0050 void PKTransaction::start()
0051 {
0052     trigger(PackageKit::Transaction::TransactionFlagSimulate);
0053 }
0054 
0055 void PKTransaction::trigger(PackageKit::Transaction::TransactionFlags flags)
0056 {
0057     if (m_trans) {
0058         m_trans->deleteLater();
0059     }
0060     m_newPackageStates.clear();
0061 
0062     if (isLocal() && role() == Transaction::InstallRole) {
0063         auto resource = qobject_cast<LocalFilePKResource *>(m_apps.at(0));
0064         m_trans = PackageKit::Daemon::installFile(QUrl(resource->packageName()).toLocalFile(), flags);
0065     } else
0066         switch (role()) {
0067         case Transaction::ChangeAddonsRole:
0068         case Transaction::InstallRole: {
0069             const auto ids = packageIds(m_apps, [](PackageKitResource *resource) {
0070                 return resource->availablePackageId();
0071             });
0072             if (ids.isEmpty()) {
0073                 // FIXME this state shouldn't exist
0074                 qWarning() << "Installing no packages found!";
0075                 for (auto resource : std::as_const(m_apps)) {
0076                     qCDebug(LIBDISCOVER_BACKEND_LOG) << "app" << resource << resource->state();
0077                 }
0078 
0079                 setStatus(Transaction::DoneWithErrorStatus);
0080                 return;
0081             }
0082             m_trans = PackageKit::Daemon::installPackages(ids, flags);
0083             break;
0084         }
0085         case Transaction::RemoveRole:
0086             // see bug #315063
0087 #ifdef PACKAGEKIT_AUTOREMOVE
0088             constexpr bool autoRemove = true;
0089 #else
0090             constexpr bool autoRemove = false;
0091 #endif
0092             m_trans = PackageKit::Daemon::removePackages(packageIds(m_apps,
0093                                                                     [](PackageKitResource *resource) {
0094                                                                         return resource->installedPackageId();
0095                                                                     }),
0096                                                          true /*allowDeps*/,
0097                                                          autoRemove,
0098                                                          flags);
0099             break;
0100         };
0101     Q_ASSERT(m_trans);
0102 
0103     if (false) {
0104         connect(m_trans.data(), &PackageKit::Transaction::statusChanged, this, [this]() {
0105             qCDebug(LIBDISCOVER_BACKEND_LOG) << "state..." << m_trans->status();
0106         });
0107     }
0108 
0109     connect(m_trans.data(), &PackageKit::Transaction::package, this, &PKTransaction::packageResolved);
0110     connect(m_trans.data(), &PackageKit::Transaction::finished, this, &PKTransaction::cleanup);
0111     connect(m_trans.data(), &PackageKit::Transaction::errorCode, this, &PKTransaction::errorFound);
0112     connect(m_trans.data(), &PackageKit::Transaction::mediaChangeRequired, this, &PKTransaction::mediaChange);
0113     connect(m_trans.data(), &PackageKit::Transaction::requireRestart, this, &PKTransaction::requireRestart);
0114     connect(m_trans.data(), &PackageKit::Transaction::repoSignatureRequired, this, &PKTransaction::repoSignatureRequired);
0115     connect(m_trans.data(), &PackageKit::Transaction::percentageChanged, this, &PKTransaction::progressChanged);
0116     connect(m_trans.data(), &PackageKit::Transaction::statusChanged, this, &PKTransaction::statusChanged);
0117     connect(m_trans.data(), &PackageKit::Transaction::eulaRequired, this, &PKTransaction::eulaRequired);
0118     connect(m_trans.data(), &PackageKit::Transaction::allowCancelChanged, this, &PKTransaction::cancellableChanged);
0119     connect(m_trans.data(), &PackageKit::Transaction::remainingTimeChanged, this, [this]() {
0120         setRemainingTime(m_trans->remainingTime());
0121     });
0122     connect(m_trans.data(), &PackageKit::Transaction::speedChanged, this, [this]() {
0123         setDownloadSpeed(m_trans->speed());
0124     });
0125 
0126     setCancellable(m_trans->allowCancel());
0127 }
0128 
0129 void PKTransaction::statusChanged()
0130 {
0131     setStatus(m_trans->status() == PackageKit::Transaction::StatusDownload ? Transaction::DownloadingStatus : Transaction::CommittingStatus);
0132     progressChanged();
0133 }
0134 
0135 void PKTransaction::progressChanged()
0136 {
0137     auto percent = m_trans->percentage();
0138     if (percent == 101) {
0139         qCWarning(LIBDISCOVER_BACKEND_LOG) << "percentage cannot be calculated";
0140         percent = 50;
0141     }
0142 
0143     const auto processedPercentage = percentageWithStatus(m_trans->status(), qBound<int>(0, percent, 100));
0144     if (processedPercentage >= 0) {
0145         setProgress(processedPercentage);
0146     }
0147 }
0148 
0149 void PKTransaction::cancellableChanged()
0150 {
0151     setCancellable(m_trans->allowCancel());
0152 }
0153 
0154 void PKTransaction::cancel()
0155 {
0156     if (!m_trans) {
0157         setStatus(CancelledStatus);
0158     } else if (m_trans->allowCancel()) {
0159         m_trans->cancel();
0160     } else {
0161         qWarning() << "trying to cancel a non-cancellable transaction: " << resource()->name();
0162     }
0163 }
0164 
0165 void PKTransaction::cleanup(PackageKit::Transaction::Exit exit, uint runtime)
0166 {
0167     Q_UNUSED(runtime)
0168     const bool cancel = !m_proceedFunctions.isEmpty() || exit == PackageKit::Transaction::ExitCancelled;
0169     const bool failed = exit == PackageKit::Transaction::ExitFailed || exit == PackageKit::Transaction::ExitUnknown;
0170     const bool simulate = m_trans->transactionFlags() & PackageKit::Transaction::TransactionFlagSimulate;
0171 
0172     disconnect(m_trans, nullptr, this, nullptr);
0173     m_trans = nullptr;
0174 
0175     const auto backend = qobject_cast<PackageKitBackend *>(resource()->backend());
0176 
0177     if (!cancel && !failed && simulate) {
0178         auto packagesToRemove = m_newPackageStates.value(PackageKit::Transaction::InfoRemoving);
0179         QMutableListIterator<QString> i(packagesToRemove);
0180         QSet<AbstractResource *> removedResources;
0181         while (i.hasNext()) {
0182             const auto pkgname = PackageKit::Daemon::packageName(i.next());
0183             removedResources.unite(backend->resourcesByPackageName(pkgname));
0184 
0185             if (m_pkgnames.contains(pkgname)) {
0186                 i.remove();
0187             }
0188         }
0189         removedResources.subtract(kToSet(m_apps));
0190 
0191         auto isCritical = [](AbstractResource *resource) {
0192             return static_cast<PackageKitResource *>(resource)->isCritical();
0193         };
0194         auto criticals = kFilter<QSet<AbstractResource *>>(removedResources, isCritical);
0195         criticals.unite(kFilter<QSet<AbstractResource *>>(m_apps, isCritical));
0196         auto resourceName = [](AbstractResource *a) {
0197             return a->name();
0198         };
0199         if (!criticals.isEmpty()) {
0200             const QString msg = i18n(
0201                 "This action cannot be completed as it would remove the following software which is critical to the system's operation:<nl/>"
0202                 "<ul><li>%1</li></ul><nl/>"
0203                 "If you believe this is an error, please report it as a bug to the packagers of your distribution.",
0204                 resourceName(*criticals.begin()));
0205             Q_EMIT distroErrorMessage(msg);
0206             setStatus(Transaction::DoneWithErrorStatus);
0207         } else if (!packagesToRemove.isEmpty() || !removedResources.isEmpty()) {
0208             QString msg;
0209             const QStringList removedResourcesStr = kTransform<QStringList>(removedResources, resourceName);
0210             msg += QLatin1String("<ul><li>") + PackageKitResource::joinPackages(packagesToRemove, QLatin1String("</li><li>"), {}) + QLatin1Char('\n');
0211             msg += removedResourcesStr.join(QLatin1String("</li><li>"));
0212             msg += QStringLiteral("</li></ul>");
0213 
0214             Q_EMIT proceedRequest(i18n("Confirm package removal"),
0215                                   i18np("This action will also remove the following package:\n%2",
0216                                         "This action will also remove the following packages:\n%2",
0217                                         packagesToRemove.count(),
0218                                         msg));
0219         } else {
0220             proceed();
0221         }
0222         return;
0223     }
0224 
0225     this->submitResolve();
0226     if (isLocal()) {
0227         qobject_cast<LocalFilePKResource *>(m_apps.at(0))->resolve({});
0228     }
0229     if (failed) {
0230         setStatus(Transaction::DoneWithErrorStatus);
0231     } else if (cancel) {
0232         setStatus(Transaction::CancelledStatus);
0233     } else {
0234         setStatus(Transaction::DoneStatus);
0235     }
0236 }
0237 
0238 void PKTransaction::processProceedFunction()
0239 {
0240     auto t = m_proceedFunctions.takeFirst()();
0241     connect(t, &PackageKit::Transaction::finished, this, [this](PackageKit::Transaction::Exit status) {
0242         if (status != PackageKit::Transaction::Exit::ExitSuccess) {
0243             qWarning() << "transaction failed" << sender() << status;
0244             cancel();
0245             return;
0246         }
0247 
0248         if (!m_proceedFunctions.isEmpty()) {
0249             processProceedFunction();
0250         } else {
0251             start();
0252         }
0253     });
0254 }
0255 
0256 void PKTransaction::proceed()
0257 {
0258     if (!m_proceedFunctions.isEmpty()) {
0259         processProceedFunction();
0260     } else {
0261         if (isLocal()) {
0262             trigger(PackageKit::Transaction::TransactionFlagNone);
0263         } else {
0264             trigger(PackageKit::Transaction::TransactionFlagOnlyTrusted);
0265         }
0266     }
0267 }
0268 
0269 void PKTransaction::packageResolved(PackageKit::Transaction::Info info, const QString &packageId)
0270 {
0271     m_newPackageStates[info].append(packageId);
0272 }
0273 
0274 void PKTransaction::submitResolve()
0275 {
0276     const auto backend = qobject_cast<PackageKitBackend *>(resource()->backend());
0277     QStringList needResolving;
0278     for (auto it = m_newPackageStates.constBegin(), itEnd = m_newPackageStates.constEnd(); it != itEnd; ++it) {
0279         const auto &itValue = it.value();
0280         for (const auto &pkgid : itValue) {
0281             const auto resources = backend->resourcesByPackageName(PackageKit::Daemon::packageName(pkgid));
0282             for (auto resource : resources) {
0283                 auto pkResource = qobject_cast<PackageKitResource *>(resource);
0284                 pkResource->clearPackageIds();
0285                 Q_EMIT pkResource->stateChanged();
0286                 needResolving << pkResource->allPackageNames();
0287             }
0288         }
0289     }
0290     needResolving.removeDuplicates();
0291     backend->resolvePackages(needResolving);
0292 }
0293 
0294 PackageKit::Transaction *PKTransaction::transaction()
0295 {
0296     return m_trans;
0297 }
0298 
0299 void PKTransaction::eulaRequired(const QString &eulaID, const QString &packageID, const QString &vendor, const QString &licenseAgreement)
0300 {
0301     const auto handle = handleEula(eulaID, licenseAgreement);
0302     m_proceedFunctions << handle.proceedFunction;
0303     if (handle.request) {
0304         Q_EMIT proceedRequest(i18n("Accept EULA"),
0305                               i18n("The package %1 and its vendor %2 require that you accept their license:\n %3",
0306                                    PackageKit::Daemon::packageName(packageID),
0307                                    vendor,
0308                                    licenseAgreement));
0309     } else {
0310         proceed();
0311     }
0312 }
0313 
0314 void PKTransaction::errorFound(PackageKit::Transaction::Error err, const QString &error)
0315 {
0316     if (err == PackageKit::Transaction::ErrorNoLicenseAgreement || err == PackageKit::Transaction::ErrorTransactionCancelled
0317         || err == PackageKit::Transaction::ErrorNotAuthorized) {
0318         return;
0319     }
0320     qWarning() << "PackageKit error:" << err << PackageKitMessages::errorMessage(err, error) << error;
0321     Q_EMIT passiveMessage(PackageKitMessages::errorMessage(err, error));
0322 }
0323 
0324 void PKTransaction::mediaChange(PackageKit::Transaction::MediaType media, const QString &type, const QString &text)
0325 {
0326     Q_UNUSED(media)
0327     Q_EMIT passiveMessage(i18n("Media Change of type '%1' is requested.\n%2", type, text));
0328 }
0329 
0330 void PKTransaction::requireRestart(PackageKit::Transaction::Restart restart, const QString &pkgid)
0331 {
0332     Q_EMIT passiveMessage(PackageKitMessages::restartMessage(restart, pkgid));
0333 }
0334 
0335 void PKTransaction::repoSignatureRequired(const QString &packageID,
0336                                           const QString &repoName,
0337                                           const QString &keyUrl,
0338                                           const QString &keyUserid,
0339                                           const QString &keyId,
0340                                           const QString &keyFingerprint,
0341                                           const QString &keyTimestamp,
0342                                           PackageKit::Transaction::SigType type)
0343 {
0344     Q_EMIT proceedRequest(i18n("Missing signature for %1 in %2", packageID, repoName),
0345                           i18n("Do you trust the following key?\n\nUrl: %1\nUser: %2\nKey: %3\nFingerprint: %4\nTimestamp: %4\n",
0346                                keyUrl,
0347                                keyUserid,
0348                                keyFingerprint,
0349                                keyTimestamp));
0350 
0351     m_proceedFunctions << [type, keyId, packageID]() {
0352         return PackageKit::Daemon::installSignature(type, keyId, packageID);
0353     };
0354 }