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 }