/* * SPDX-FileCopyrightText: 2013 Aleix Pol Gonzalez * * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL */ #include "PKTransaction.h" #include "LocalFilePKResource.h" #include "PackageKitBackend.h" #include "PackageKitMessages.h" #include "PackageKitResource.h" #include "PackageKitUpdater.h" #include "libdiscover_backend_packagekit_debug.h" #include "utils.h" #include #include #include #include #include #include PKTransaction::PKTransaction(const QVector &resources, Transaction::Role role) : Transaction(resources.first(), resources.first(), role) , m_apps(resources) { Q_ASSERT(!resources.contains(nullptr)); for (auto resource : resources) { auto pkResource = qobject_cast(resource); m_pkgnames.unite(kToSet(pkResource->allPackageNames())); } QTimer::singleShot(0, this, &PKTransaction::start); } static QStringList packageIds(const QVector &resources, std::function func) { QStringList ret; for (auto resource : resources) { ret += func(qobject_cast(resource)); } ret.removeDuplicates(); return ret; } bool PKTransaction::isLocal() const { return m_apps.size() == 1 && qobject_cast(m_apps.at(0)); } void PKTransaction::start() { trigger(PackageKit::Transaction::TransactionFlagSimulate); } void PKTransaction::trigger(PackageKit::Transaction::TransactionFlags flags) { if (m_trans) { m_trans->deleteLater(); } m_newPackageStates.clear(); if (isLocal() && role() == Transaction::InstallRole) { auto resource = qobject_cast(m_apps.at(0)); m_trans = PackageKit::Daemon::installFile(QUrl(resource->packageName()).toLocalFile(), flags); } else switch (role()) { case Transaction::ChangeAddonsRole: case Transaction::InstallRole: { const auto ids = packageIds(m_apps, [](PackageKitResource *resource) { return resource->availablePackageId(); }); if (ids.isEmpty()) { // FIXME this state shouldn't exist qWarning() << "Installing no packages found!"; for (auto resource : std::as_const(m_apps)) { qCDebug(LIBDISCOVER_BACKEND_PACKAGEKIT_LOG) << "app" << resource << resource->state(); } setStatus(Transaction::DoneWithErrorStatus); return; } m_trans = PackageKit::Daemon::installPackages(ids, flags); break; } case Transaction::RemoveRole: // see bug #315063 #ifdef PACKAGEKIT_AUTOREMOVE constexpr bool autoRemove = true; #else constexpr bool autoRemove = false; #endif m_trans = PackageKit::Daemon::removePackages(packageIds(m_apps, [](PackageKitResource *resource) { return resource->installedPackageId(); }), true /*allowDeps*/, autoRemove, flags); break; }; Q_ASSERT(m_trans); if (false) { connect(m_trans.data(), &PackageKit::Transaction::statusChanged, this, [this]() { qCDebug(LIBDISCOVER_BACKEND_PACKAGEKIT_LOG) << "state..." << m_trans->status(); }); } connect(m_trans.data(), &PackageKit::Transaction::package, this, &PKTransaction::packageResolved); connect(m_trans.data(), &PackageKit::Transaction::finished, this, &PKTransaction::cleanup); connect(m_trans.data(), &PackageKit::Transaction::errorCode, this, &PKTransaction::errorFound); connect(m_trans.data(), &PackageKit::Transaction::mediaChangeRequired, this, &PKTransaction::mediaChange); connect(m_trans.data(), &PackageKit::Transaction::requireRestart, this, &PKTransaction::requireRestart); connect(m_trans.data(), &PackageKit::Transaction::repoSignatureRequired, this, &PKTransaction::repoSignatureRequired); connect(m_trans.data(), &PackageKit::Transaction::percentageChanged, this, &PKTransaction::progressChanged); connect(m_trans.data(), &PackageKit::Transaction::statusChanged, this, &PKTransaction::statusChanged); connect(m_trans.data(), &PackageKit::Transaction::eulaRequired, this, &PKTransaction::eulaRequired); connect(m_trans.data(), &PackageKit::Transaction::allowCancelChanged, this, &PKTransaction::cancellableChanged); connect(m_trans.data(), &PackageKit::Transaction::remainingTimeChanged, this, [this]() { setRemainingTime(m_trans->remainingTime()); }); connect(m_trans.data(), &PackageKit::Transaction::speedChanged, this, [this]() { setDownloadSpeed(m_trans->speed()); }); setCancellable(m_trans->allowCancel()); } void PKTransaction::statusChanged() { setStatus(m_trans->status() == PackageKit::Transaction::StatusDownload ? Transaction::DownloadingStatus : Transaction::CommittingStatus); progressChanged(); } void PKTransaction::progressChanged() { auto percent = m_trans->percentage(); if (percent == 101) { qCWarning(LIBDISCOVER_BACKEND_PACKAGEKIT_LOG) << "percentage cannot be calculated"; percent = 50; } const auto processedPercentage = percentageWithStatus(m_trans->status(), qBound(0, percent, 100)); if (processedPercentage >= 0) { setProgress(processedPercentage); } } void PKTransaction::cancellableChanged() { setCancellable(m_trans->allowCancel()); } void PKTransaction::cancel() { if (!m_trans) { setStatus(CancelledStatus); } else if (m_trans->allowCancel()) { m_trans->cancel(); } else { qWarning() << "trying to cancel a non-cancellable transaction: " << resource()->name(); } } void PKTransaction::cleanup(PackageKit::Transaction::Exit exit, uint runtime) { Q_UNUSED(runtime) const bool cancel = !m_proceedFunctions.isEmpty() || exit == PackageKit::Transaction::ExitCancelled; const bool failed = exit == PackageKit::Transaction::ExitFailed || exit == PackageKit::Transaction::ExitUnknown; const bool simulate = m_trans->transactionFlags() & PackageKit::Transaction::TransactionFlagSimulate; disconnect(m_trans, nullptr, this, nullptr); m_trans = nullptr; const auto backend = qobject_cast(resource()->backend()); if (!cancel && !failed && simulate) { auto packagesToRemove = m_newPackageStates.value(PackageKit::Transaction::InfoRemoving); QMutableListIterator i(packagesToRemove); QSet removedResources; while (i.hasNext()) { const auto pkgname = PackageKit::Daemon::packageName(i.next()); removedResources.unite(backend->resourcesByPackageName(pkgname)); if (m_pkgnames.contains(pkgname)) { i.remove(); } } removedResources.subtract(kToSet(m_apps)); auto isCritical = [](AbstractResource *resource) { return static_cast(resource)->isCritical(); }; auto criticals = kFilter>(removedResources, isCritical); criticals.unite(kFilter>(m_apps, isCritical)); auto resourceName = [](AbstractResource *a) { return a->name(); }; if (!criticals.isEmpty()) { const QString msg = i18n( "This action cannot be completed as it would remove the following software which is critical to the system's operation:" "
  • %1
" "If you believe this is an error, please report it as a bug to the packagers of your distribution.", resourceName(*criticals.begin())); Q_EMIT distroErrorMessage(msg); setStatus(Transaction::DoneWithErrorStatus); } else if (!packagesToRemove.isEmpty() || !removedResources.isEmpty()) { QString msg; const QStringList removedResourcesStr = kTransform(removedResources, resourceName); msg += QLatin1String("
  • ") + PackageKitResource::joinPackages(packagesToRemove, QLatin1String("
  • "), {}) + QLatin1Char('\n'); msg += removedResourcesStr.join(QLatin1String("
  • ")); msg += QStringLiteral("
"); Q_EMIT proceedRequest(i18n("Confirm package removal"), i18np("This action will also remove the following package:\n%2", "This action will also remove the following packages:\n%2", packagesToRemove.count(), msg)); } else { proceed(); } return; } this->submitResolve(); if (isLocal()) { qobject_cast(m_apps.at(0))->resolve({}); } if (failed) { setStatus(Transaction::DoneWithErrorStatus); } else if (cancel) { setStatus(Transaction::CancelledStatus); } else { setStatus(Transaction::DoneStatus); } } void PKTransaction::processProceedFunction() { auto t = m_proceedFunctions.takeFirst()(); connect(t, &PackageKit::Transaction::finished, this, [this](PackageKit::Transaction::Exit status) { if (status != PackageKit::Transaction::Exit::ExitSuccess) { qWarning() << "transaction failed" << sender() << status; cancel(); return; } if (!m_proceedFunctions.isEmpty()) { processProceedFunction(); } else { start(); } }); } void PKTransaction::proceed() { if (!m_proceedFunctions.isEmpty()) { processProceedFunction(); } else { if (isLocal()) { trigger(PackageKit::Transaction::TransactionFlagNone); } else { trigger(PackageKit::Transaction::TransactionFlagOnlyTrusted); } } } void PKTransaction::packageResolved(PackageKit::Transaction::Info info, const QString &packageId) { m_newPackageStates[info].append(packageId); } void PKTransaction::submitResolve() { const auto backend = qobject_cast(resource()->backend()); QStringList needResolving; for (auto it = m_newPackageStates.constBegin(), itEnd = m_newPackageStates.constEnd(); it != itEnd; ++it) { const auto &itValue = it.value(); for (const auto &pkgid : itValue) { const auto resources = backend->resourcesByPackageName(PackageKit::Daemon::packageName(pkgid)); for (auto resource : resources) { auto pkResource = qobject_cast(resource); pkResource->clearPackageIds(); Q_EMIT pkResource->stateChanged(); needResolving << pkResource->allPackageNames(); } } } needResolving.removeDuplicates(); backend->resolvePackages(needResolving); } PackageKit::Transaction *PKTransaction::transaction() { return m_trans; } void PKTransaction::eulaRequired(const QString &eulaID, const QString &packageID, const QString &vendor, const QString &licenseAgreement) { const auto handle = handleEula(eulaID, licenseAgreement); m_proceedFunctions << handle.proceedFunction; if (handle.request) { Q_EMIT proceedRequest(i18n("Accept EULA"), i18n("The package %1 and its vendor %2 require that you accept their license:\n %3", PackageKit::Daemon::packageName(packageID), vendor, licenseAgreement)); } else { proceed(); } } void PKTransaction::errorFound(PackageKit::Transaction::Error err, const QString &error) { if (err == PackageKit::Transaction::ErrorNoLicenseAgreement || err == PackageKit::Transaction::ErrorTransactionCancelled || err == PackageKit::Transaction::ErrorNotAuthorized) { return; } qWarning() << "PackageKit error:" << err << PackageKitMessages::errorMessage(err, error) << error; Q_EMIT passiveMessage(PackageKitMessages::errorMessage(err, error)); } void PKTransaction::mediaChange(PackageKit::Transaction::MediaType media, const QString &type, const QString &text) { Q_UNUSED(media) Q_EMIT passiveMessage(i18n("Media Change of type '%1' is requested.\n%2", type, text)); } void PKTransaction::requireRestart(PackageKit::Transaction::Restart restart, const QString &pkgid) { Q_EMIT passiveMessage(PackageKitMessages::restartMessage(restart, pkgid)); } void PKTransaction::repoSignatureRequired(const QString &packageID, const QString &repoName, const QString &keyUrl, const QString &keyUserid, const QString &keyId, const QString &keyFingerprint, const QString &keyTimestamp, PackageKit::Transaction::SigType type) { Q_EMIT proceedRequest(i18n("Missing signature for %1 in %2", packageID, repoName), i18n("Do you trust the following key?\n\nUrl: %1\nUser: %2\nKey: %3\nFingerprint: %4\nTimestamp: %4\n", keyUrl, keyUserid, keyFingerprint, keyTimestamp)); m_proceedFunctions << [type, keyId, packageID]() { return PackageKit::Daemon::installSignature(type, keyId, packageID); }; } #include "moc_PKTransaction.cpp"