File indexing completed on 2024-05-05 17:33:21

0001 /*
0002  *   SPDX-FileCopyrightText: 2012 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 "ResourcesUpdatesModel.h"
0008 #include "AbstractBackendUpdater.h"
0009 #include "AbstractResource.h"
0010 #include "ResourcesModel.h"
0011 #include "libdiscover_debug.h"
0012 #include "utils.h"
0013 #include <Transaction/Transaction.h>
0014 #include <Transaction/TransactionModel.h>
0015 
0016 #include <KConfigGroup>
0017 #include <KConfigWatcher>
0018 #include <KFormat>
0019 #include <KLocalizedString>
0020 #include <KSharedConfig>
0021 
0022 class UpdateTransaction : public Transaction
0023 {
0024     Q_OBJECT
0025 public:
0026     UpdateTransaction(ResourcesUpdatesModel * /*parent*/, const QVector<AbstractBackendUpdater *> &updaters)
0027         : Transaction(nullptr, nullptr, Transaction::InstallRole)
0028         , m_allUpdaters(updaters)
0029     {
0030         bool cancelable = false;
0031         for (auto updater : qAsConst(m_allUpdaters)) {
0032             connect(updater, &AbstractBackendUpdater::progressingChanged, this, &UpdateTransaction::slotProgressingChanged);
0033             connect(updater, &AbstractBackendUpdater::downloadSpeedChanged, this, &UpdateTransaction::slotDownloadSpeedChanged);
0034             connect(updater, &AbstractBackendUpdater::progressChanged, this, &UpdateTransaction::slotUpdateProgress);
0035             connect(updater, &AbstractBackendUpdater::proceedRequest, this, &UpdateTransaction::processProceedRequest);
0036             connect(updater, &AbstractBackendUpdater::distroErrorMessage, this, &UpdateTransaction::distroErrorMessage);
0037             connect(updater, &AbstractBackendUpdater::cancelableChanged, this, [this](bool) {
0038                 setCancellable(kContains(m_allUpdaters, [](AbstractBackendUpdater *updater) {
0039                     return updater->isCancelable() && updater->isProgressing();
0040                 }));
0041             });
0042             cancelable |= updater->isCancelable();
0043         }
0044         setCancellable(cancelable);
0045     }
0046 
0047     void processProceedRequest(const QString &title, const QString &message)
0048     {
0049         m_updatersWaitingForFeedback += qobject_cast<AbstractBackendUpdater *>(sender());
0050         Q_EMIT proceedRequest(title, message);
0051     }
0052 
0053     void cancel() override
0054     {
0055         const QVector<AbstractBackendUpdater *> toCancel = m_updatersWaitingForFeedback.isEmpty() ? m_allUpdaters : m_updatersWaitingForFeedback;
0056 
0057         for (auto updater : toCancel) {
0058             updater->cancel();
0059         }
0060     }
0061 
0062     void proceed() override
0063     {
0064         m_updatersWaitingForFeedback.takeFirst()->proceed();
0065     }
0066 
0067     bool isProgressing() const
0068     {
0069         bool progressing = false;
0070         for (AbstractBackendUpdater *upd : qAsConst(m_allUpdaters)) {
0071             progressing |= upd->isProgressing();
0072         }
0073         return progressing;
0074     }
0075 
0076     void slotProgressingChanged()
0077     {
0078         if (status() > SetupStatus && status() < DoneStatus && !isProgressing()) {
0079             setStatus(Transaction::DoneStatus);
0080             Q_EMIT finished();
0081             deleteLater();
0082         }
0083     }
0084 
0085     void slotUpdateProgress()
0086     {
0087         qreal total = 0;
0088         for (AbstractBackendUpdater *updater : qAsConst(m_allUpdaters)) {
0089             total += updater->progress();
0090         }
0091         setProgress(total / m_allUpdaters.count());
0092     }
0093 
0094     void slotDownloadSpeedChanged()
0095     {
0096         quint64 total = 0;
0097         for (AbstractBackendUpdater *updater : qAsConst(m_allUpdaters)) {
0098             total += updater->downloadSpeed();
0099         }
0100         setDownloadSpeed(total);
0101     }
0102 
0103     QVariant icon() const override
0104     {
0105         return QStringLiteral("update-low");
0106     }
0107     QString name() const override
0108     {
0109         return i18n("Updates");
0110     }
0111 
0112 Q_SIGNALS:
0113     void finished();
0114 
0115 private:
0116     QVector<AbstractBackendUpdater *> m_updatersWaitingForFeedback;
0117     const QVector<AbstractBackendUpdater *> m_allUpdaters;
0118 };
0119 
0120 ResourcesUpdatesModel::ResourcesUpdatesModel(QObject *parent)
0121     : QStandardItemModel(parent)
0122     , m_lastIsProgressing(false)
0123     , m_transaction(nullptr)
0124 {
0125     connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, this, &ResourcesUpdatesModel::init);
0126 
0127     init();
0128 }
0129 
0130 void ResourcesUpdatesModel::init()
0131 {
0132     const QVector<AbstractResourcesBackend *> backends = ResourcesModel::global()->backends();
0133     m_lastIsProgressing = false;
0134     for (AbstractResourcesBackend *b : backends) {
0135         AbstractBackendUpdater *updater = b->backendUpdater();
0136         if (updater && !m_updaters.contains(updater)) {
0137             connect(updater, &AbstractBackendUpdater::statusMessageChanged, this, &ResourcesUpdatesModel::message);
0138             connect(updater, &AbstractBackendUpdater::statusDetailChanged, this, &ResourcesUpdatesModel::message);
0139             connect(updater, &AbstractBackendUpdater::downloadSpeedChanged, this, &ResourcesUpdatesModel::downloadSpeedChanged);
0140             connect(updater, &AbstractBackendUpdater::resourceProgressed, this, &ResourcesUpdatesModel::resourceProgressed);
0141             connect(updater, &AbstractBackendUpdater::passiveMessage, this, &ResourcesUpdatesModel::passiveMessage);
0142             connect(updater, &AbstractBackendUpdater::needsRebootChanged, this, &ResourcesUpdatesModel::needsRebootChanged);
0143             connect(updater, &AbstractBackendUpdater::destroyed, this, &ResourcesUpdatesModel::updaterDestroyed);
0144             connect(updater, &AbstractBackendUpdater::errorMessageChanged, this, &ResourcesUpdatesModel::errorMessagesChanged);
0145             m_updaters += updater;
0146 
0147             m_lastIsProgressing |= updater->isProgressing();
0148         }
0149     }
0150 
0151     // To enable from command line use:
0152     // kwriteconfig5 --file discoverrc --group Software --key UseOfflineUpdates true
0153     auto sharedConfig = KSharedConfig::openConfig();
0154     KConfigGroup group(sharedConfig, "Software");
0155     m_offlineUpdates = group.readEntry<bool>("UseOfflineUpdates", false);
0156 
0157     KConfigWatcher::Ptr watcher = KConfigWatcher::create(sharedConfig);
0158     connect(watcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) {
0159         // Ensure it is for the right file
0160         if (!names.contains("UseOfflineUpdates") || group.name() != "Software") {
0161             return;
0162         }
0163 
0164         if (m_offlineUpdates == group.readEntry<bool>("UseOfflineUpdates", false)) {
0165             return;
0166         }
0167         Q_EMIT useUnattendedUpdatesChanged();
0168     });
0169 
0170     auto tm = TransactionModel::global();
0171     const auto transactions = tm->transactions();
0172     for (auto t : transactions) {
0173         auto updateTransaction = qobject_cast<UpdateTransaction *>(t);
0174         if (updateTransaction) {
0175             setTransaction(updateTransaction);
0176         }
0177     }
0178 
0179     Q_EMIT errorMessagesChanged();
0180 }
0181 
0182 void ResourcesUpdatesModel::updaterDestroyed(QObject *obj)
0183 {
0184     for (auto it = m_updaters.begin(); it != m_updaters.end();) {
0185         if (*it == obj)
0186             it = m_updaters.erase(it);
0187         else
0188             ++it;
0189     }
0190 }
0191 
0192 void ResourcesUpdatesModel::message(const QString &msg)
0193 {
0194     if (msg.isEmpty())
0195         return;
0196 
0197     appendRow(new QStandardItem(msg));
0198 }
0199 
0200 void ResourcesUpdatesModel::prepare()
0201 {
0202     if (isProgressing()) {
0203         qCWarning(LIBDISCOVER_LOG) << "trying to set up a running instance";
0204         return;
0205     }
0206 
0207     for (AbstractBackendUpdater *upd : qAsConst(m_updaters)) {
0208         upd->setOfflineUpdates(m_offlineUpdates);
0209         upd->prepare();
0210     }
0211 }
0212 
0213 void ResourcesUpdatesModel::updateAll()
0214 {
0215     if (!m_updaters.isEmpty()) {
0216         delete m_transaction;
0217 
0218         const auto updaters = kFilter<QVector<AbstractBackendUpdater *>>(m_updaters, [](AbstractBackendUpdater *u) {
0219             return u->hasUpdates();
0220         });
0221         if (updaters.isEmpty()) {
0222             return;
0223         }
0224 
0225         m_transaction = new UpdateTransaction(this, updaters);
0226         m_transaction->setStatus(Transaction::SetupStatus);
0227         setTransaction(m_transaction);
0228         TransactionModel::global()->addTransaction(m_transaction);
0229         for (AbstractBackendUpdater *upd : updaters) {
0230             QMetaObject::invokeMethod(upd, &AbstractBackendUpdater::start, Qt::QueuedConnection);
0231         }
0232 
0233         QMetaObject::invokeMethod(
0234             this,
0235             [this]() {
0236                 m_transaction->setStatus(Transaction::CommittingStatus);
0237                 m_transaction->slotProgressingChanged();
0238             },
0239             Qt::QueuedConnection);
0240     }
0241 }
0242 
0243 bool ResourcesUpdatesModel::isProgressing() const
0244 {
0245     return m_transaction && m_transaction->status() < Transaction::DoneStatus;
0246 }
0247 
0248 QList<AbstractResource *> ResourcesUpdatesModel::toUpdate() const
0249 {
0250     QList<AbstractResource *> ret;
0251     for (AbstractBackendUpdater *upd : qAsConst(m_updaters)) {
0252         ret += upd->toUpdate();
0253     }
0254     return ret;
0255 }
0256 
0257 void ResourcesUpdatesModel::addResources(const QList<AbstractResource *> &resources)
0258 {
0259     QHash<AbstractResourcesBackend *, QList<AbstractResource *>> sortedResources;
0260     for (AbstractResource *res : resources) {
0261         sortedResources[res->backend()] += res;
0262     }
0263 
0264     for (auto it = sortedResources.constBegin(), itEnd = sortedResources.constEnd(); it != itEnd; ++it) {
0265         it.key()->backendUpdater()->addResources(*it);
0266     }
0267 }
0268 
0269 void ResourcesUpdatesModel::removeResources(const QList<AbstractResource *> &resources)
0270 {
0271     QHash<AbstractResourcesBackend *, QList<AbstractResource *>> sortedResources;
0272     for (AbstractResource *res : resources) {
0273         sortedResources[res->backend()] += res;
0274     }
0275 
0276     for (auto it = sortedResources.constBegin(), itEnd = sortedResources.constEnd(); it != itEnd; ++it) {
0277         it.key()->backendUpdater()->removeResources(*it);
0278     }
0279 }
0280 
0281 QDateTime ResourcesUpdatesModel::lastUpdate() const
0282 {
0283     QDateTime ret;
0284     for (AbstractBackendUpdater *upd : qAsConst(m_updaters)) {
0285         QDateTime current = upd->lastUpdate();
0286         if (!ret.isValid() || (current.isValid() && current > ret)) {
0287             ret = current;
0288         }
0289     }
0290     return ret;
0291 }
0292 
0293 double ResourcesUpdatesModel::updateSize() const
0294 {
0295     double ret = 0.;
0296     for (AbstractBackendUpdater *upd : m_updaters) {
0297         ret += std::max(0., upd->updateSize());
0298     }
0299     return ret;
0300 }
0301 
0302 qint64 ResourcesUpdatesModel::secsToLastUpdate() const
0303 {
0304     return lastUpdate().secsTo(QDateTime::currentDateTime());
0305 }
0306 
0307 void ResourcesUpdatesModel::setTransaction(UpdateTransaction *transaction)
0308 {
0309     m_transaction = transaction;
0310     connect(transaction, &UpdateTransaction::finished, this, &ResourcesUpdatesModel::finished);
0311     connect(transaction, &UpdateTransaction::finished, this, &ResourcesUpdatesModel::progressingChanged);
0312 
0313     Q_EMIT progressingChanged();
0314 }
0315 
0316 Transaction *ResourcesUpdatesModel::transaction() const
0317 {
0318     return m_transaction.data();
0319 }
0320 
0321 bool ResourcesUpdatesModel::needsReboot() const
0322 {
0323     for (auto upd : m_updaters) {
0324         if (upd->needsReboot())
0325             return true;
0326     }
0327     return false;
0328 }
0329 
0330 bool ResourcesUpdatesModel::readyToReboot() const
0331 {
0332     return kContains(m_updaters, [](AbstractBackendUpdater *updater) {
0333         return !updater->needsReboot() || updater->isReadyToReboot();
0334     });
0335 }
0336 
0337 bool ResourcesUpdatesModel::useUnattendedUpdates() const
0338 {
0339     return m_offlineUpdates;
0340 }
0341 
0342 void ResourcesUpdatesModel::setOfflineUpdates(bool offline)
0343 {
0344     m_offlineUpdates = offline;
0345 }
0346 
0347 QStringList ResourcesUpdatesModel::errorMessages() const
0348 {
0349     QStringList ret;
0350     for (auto updater : m_updaters) {
0351         const auto error = updater->errorMessage();
0352         if (!error.isEmpty()) {
0353             ret << error;
0354         }
0355     }
0356     ret.removeDuplicates();
0357     return ret;
0358 }
0359 
0360 #include "ResourcesUpdatesModel.moc"