File indexing completed on 2024-05-12 05:29:04

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