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

0001 /*
0002  *   SPDX-FileCopyrightText: 2012 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
0003  *
0004  *   SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "ResourcesModel.h"
0008 #include "libdiscover_debug.h"
0009 #include "utils.h"
0010 #include <KLocalizedString>
0011 #include <QIcon>
0012 #include <Transaction/Transaction.h>
0013 #include <Transaction/TransactionModel.h>
0014 #include <resources/AbstractResource.h>
0015 #include <resources/AbstractResourcesBackend.h>
0016 #include <resources/StandardBackendUpdater.h>
0017 
0018 StandardBackendUpdater::StandardBackendUpdater(AbstractResourcesBackend *parent)
0019     : AbstractBackendUpdater(parent)
0020     , m_backend(parent)
0021     , m_settingUp(false)
0022     , m_progress(0)
0023     , m_lastUpdate(QDateTime())
0024 {
0025     connect(m_backend, &AbstractResourcesBackend::fetchingChanged, this, &StandardBackendUpdater::refreshUpdateable);
0026     connect(m_backend, &AbstractResourcesBackend::resourcesChanged, this, &StandardBackendUpdater::resourcesChanged);
0027     connect(m_backend, &AbstractResourcesBackend::resourceRemoved, this, [this](AbstractResource *resource) {
0028         if (m_upgradeable.remove(resource)) {
0029             Q_EMIT updatesCountChanged(updatesCount());
0030         }
0031         m_toUpgrade.remove(resource);
0032     });
0033     connect(TransactionModel::global(), &TransactionModel::transactionRemoved, this, &StandardBackendUpdater::transactionRemoved);
0034     connect(TransactionModel::global(), &TransactionModel::transactionAdded, this, &StandardBackendUpdater::transactionAdded);
0035 
0036     m_timer.setSingleShot(true);
0037     m_timer.setInterval(10);
0038     connect(&m_timer, &QTimer::timeout, this, &StandardBackendUpdater::refreshUpdateable);
0039 }
0040 
0041 void StandardBackendUpdater::resourcesChanged(AbstractResource *res, const QVector<QByteArray> &props)
0042 {
0043     if (props.contains("state") && (res->state() == AbstractResource::Upgradeable || m_upgradeable.contains(res)))
0044         m_timer.start();
0045 }
0046 
0047 bool StandardBackendUpdater::hasUpdates() const
0048 {
0049     return !m_upgradeable.isEmpty();
0050 }
0051 
0052 void StandardBackendUpdater::start()
0053 {
0054     m_settingUp = true;
0055     Q_EMIT progressingChanged(true);
0056     setProgress(0);
0057     auto upgradeList = m_toUpgrade.values();
0058     std::sort(upgradeList.begin(), upgradeList.end(), [](const AbstractResource *a, const AbstractResource *b) {
0059         return a->name() < b->name();
0060     });
0061 
0062     const bool couldCancel = m_canCancel;
0063     for (AbstractResource *res : qAsConst(upgradeList)) {
0064         m_pendingResources += res;
0065         auto t = m_backend->installApplication(res);
0066         t->setVisible(false);
0067         t->setProperty("updater", QVariant::fromValue<QObject *>(this));
0068         connect(t, &Transaction::downloadSpeedChanged, this, [this]() {
0069             Q_EMIT downloadSpeedChanged(downloadSpeed());
0070         });
0071         connect(this, &StandardBackendUpdater::cancelTransaction, t, &Transaction::cancel);
0072         TransactionModel::global()->addTransaction(t);
0073         m_canCancel |= t->isCancellable();
0074     }
0075     if (m_canCancel != couldCancel) {
0076         Q_EMIT cancelableChanged(m_canCancel);
0077     }
0078     m_settingUp = false;
0079 
0080     if (m_pendingResources.isEmpty()) {
0081         cleanup();
0082     } else {
0083         setProgress(1);
0084     }
0085 }
0086 
0087 void StandardBackendUpdater::cancel()
0088 {
0089     Q_EMIT cancelTransaction();
0090 }
0091 
0092 void StandardBackendUpdater::transactionAdded(Transaction *newTransaction)
0093 {
0094     if (!m_pendingResources.contains(newTransaction->resource()))
0095         return;
0096 
0097     connect(newTransaction, &Transaction::progressChanged, this, &StandardBackendUpdater::transactionProgressChanged);
0098     connect(newTransaction, &Transaction::statusChanged, this, &StandardBackendUpdater::transactionProgressChanged);
0099 }
0100 
0101 AbstractBackendUpdater::State toUpdateState(Transaction *t)
0102 {
0103     switch (t->status()) {
0104     case Transaction::SetupStatus:
0105     case Transaction::QueuedStatus:
0106         return AbstractBackendUpdater::None;
0107     case Transaction::DownloadingStatus:
0108         return AbstractBackendUpdater::Downloading;
0109     case Transaction::CommittingStatus:
0110         return AbstractBackendUpdater::Installing;
0111     case Transaction::DoneStatus:
0112     case Transaction::DoneWithErrorStatus:
0113     case Transaction::CancelledStatus:
0114         return AbstractBackendUpdater::Done;
0115     }
0116     Q_UNREACHABLE();
0117 }
0118 
0119 void StandardBackendUpdater::transactionProgressChanged()
0120 {
0121     Transaction *t = qobject_cast<Transaction *>(sender());
0122     Q_EMIT resourceProgressed(t->resource(), t->progress(), toUpdateState(t));
0123 
0124     refreshProgress();
0125 }
0126 
0127 void StandardBackendUpdater::transactionRemoved(Transaction *t)
0128 {
0129     const bool fromOurBackend = t->resource() && t->resource()->backend() == m_backend;
0130     if (!fromOurBackend) {
0131         return;
0132     }
0133 
0134     const bool found = fromOurBackend && m_pendingResources.remove(t->resource());
0135     m_anyTransactionFailed |= t->status() != Transaction::DoneStatus;
0136 
0137     if (found && !m_settingUp) {
0138         refreshProgress();
0139         if (m_pendingResources.isEmpty()) {
0140             cleanup();
0141             if (needsReboot() && !m_anyTransactionFailed) {
0142                 enableReadyToReboot();
0143             }
0144         }
0145     }
0146     refreshUpdateable();
0147 }
0148 
0149 void StandardBackendUpdater::refreshProgress()
0150 {
0151     if (m_toUpgrade.isEmpty()) {
0152         return;
0153     }
0154 
0155     int allProgresses = (m_toUpgrade.size() - m_pendingResources.size()) * 100;
0156     const auto allTransactions = transactions();
0157     for (auto t : allTransactions) {
0158         allProgresses += t->progress();
0159     }
0160     setProgress(allProgresses / m_toUpgrade.size());
0161 }
0162 
0163 void StandardBackendUpdater::refreshUpdateable()
0164 {
0165     if (m_backend->isFetching() || !m_backend->isValid()) {
0166         return;
0167     }
0168 
0169     if (isProgressing()) {
0170         m_timer.start(1000);
0171         return;
0172     }
0173 
0174     m_settingUp = true;
0175     Q_EMIT progressingChanged(true);
0176     AbstractResourcesBackend::Filters f;
0177     f.state = AbstractResource::Upgradeable;
0178     m_upgradeable.clear();
0179     auto r = m_backend->search(f);
0180     connect(r, &ResultsStream::resourcesFound, this, [this](const QVector<AbstractResource *> &resources) {
0181         for (auto res : resources)
0182             if (res->state() == AbstractResource::Upgradeable)
0183                 m_upgradeable.insert(res);
0184     });
0185     connect(r, &ResultsStream::destroyed, this, [this]() {
0186         m_settingUp = false;
0187         Q_EMIT updatesCountChanged(updatesCount());
0188         Q_EMIT progressingChanged(false);
0189     });
0190 }
0191 
0192 qreal StandardBackendUpdater::progress() const
0193 {
0194     return m_progress;
0195 }
0196 
0197 void StandardBackendUpdater::setProgress(qreal p)
0198 {
0199     if (p > m_progress || p < 0) {
0200         m_progress = p;
0201         Q_EMIT progressChanged(p);
0202     }
0203 }
0204 
0205 void StandardBackendUpdater::prepare()
0206 {
0207     m_lastUpdate = QDateTime::currentDateTime();
0208     m_toUpgrade = m_upgradeable;
0209 }
0210 
0211 int StandardBackendUpdater::updatesCount() const
0212 {
0213     return m_upgradeable.count();
0214 }
0215 
0216 void StandardBackendUpdater::addResources(const QList<AbstractResource *> &apps)
0217 {
0218     const QSet<AbstractResource *> upgradeableApps = kToSet(apps);
0219     Q_ASSERT(m_upgradeable.contains(upgradeableApps));
0220     m_toUpgrade += upgradeableApps;
0221 }
0222 
0223 void StandardBackendUpdater::removeResources(const QList<AbstractResource *> &apps)
0224 {
0225     const QSet<AbstractResource *> upgradeableApps = kToSet(apps);
0226     Q_ASSERT(m_upgradeable.contains(upgradeableApps));
0227     Q_ASSERT(m_toUpgrade.contains(upgradeableApps));
0228     m_toUpgrade -= upgradeableApps;
0229 }
0230 
0231 void StandardBackendUpdater::cleanup()
0232 {
0233     m_lastUpdate = QDateTime::currentDateTime();
0234     m_toUpgrade.clear();
0235 
0236     refreshUpdateable();
0237     Q_EMIT progressingChanged(false);
0238 }
0239 
0240 QList<AbstractResource *> StandardBackendUpdater::toUpdate() const
0241 {
0242     return m_toUpgrade.values();
0243 }
0244 
0245 bool StandardBackendUpdater::isMarked(AbstractResource *res) const
0246 {
0247     return m_toUpgrade.contains(res);
0248 }
0249 
0250 QDateTime StandardBackendUpdater::lastUpdate() const
0251 {
0252     return m_lastUpdate;
0253 }
0254 
0255 bool StandardBackendUpdater::isCancelable() const
0256 {
0257     return m_canCancel;
0258 }
0259 
0260 bool StandardBackendUpdater::isProgressing() const
0261 {
0262     return m_settingUp || !m_pendingResources.isEmpty();
0263 }
0264 
0265 double StandardBackendUpdater::updateSize() const
0266 {
0267     double ret = 0.;
0268     for (AbstractResource *res : m_toUpgrade) {
0269         ret += res->size();
0270     }
0271     return ret;
0272 }
0273 
0274 QVector<Transaction *> StandardBackendUpdater::transactions() const
0275 {
0276     const auto trans = TransactionModel::global()->transactions();
0277     return kFilter<QVector<Transaction *>>(trans, [this](Transaction *t) {
0278         return t->property("updater").value<QObject *>() == this;
0279     });
0280 }
0281 
0282 quint64 StandardBackendUpdater::downloadSpeed() const
0283 {
0284     quint64 ret = 0;
0285     const auto trans = transactions();
0286     for (Transaction *t : trans) {
0287         ret += t->downloadSpeed();
0288     }
0289     return ret;
0290 }