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"