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

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 "ResourcesModel.h"
0008 
0009 #include "AbstractResource.h"
0010 #include "Category/CategoryModel.h"
0011 #include "Transaction/TransactionModel.h"
0012 #include "libdiscover_debug.h"
0013 #include "resources/AbstractBackendUpdater.h"
0014 #include "resources/AbstractResourcesBackend.h"
0015 #include "utils.h"
0016 #include <DiscoverBackendsFactory.h>
0017 #include <KConfigGroup>
0018 #include <KLocalizedString>
0019 #include <KOSRelease>
0020 #include <KSharedConfig>
0021 #include <QCoreApplication>
0022 #include <QIcon>
0023 #include <QMetaProperty>
0024 #include <QThread>
0025 #include <ReviewsBackend/AbstractReviewsBackend.h>
0026 #include <ReviewsBackend/Rating.h>
0027 #include <Transaction/Transaction.h>
0028 #include <functional>
0029 #include <resources/DiscoverAction.h>
0030 
0031 using namespace Qt::StringLiterals;
0032 
0033 ResourcesModel *ResourcesModel::s_self = nullptr;
0034 
0035 ResourcesModel *ResourcesModel::global()
0036 {
0037     if (!s_self) {
0038         s_self = new ResourcesModel;
0039         s_self->init(true);
0040     }
0041     return s_self;
0042 }
0043 
0044 ResourcesModel::ResourcesModel(QObject *parent)
0045     : QObject(parent)
0046     , m_isFetching(false)
0047     , m_initializingBackendsCount(0)
0048     , m_currentApplicationBackend(nullptr)
0049     , m_allInitializedEmitter(new QTimer(this))
0050     , m_updatesCount(
0051           0,
0052           [this] {
0053               int ret = 0;
0054               for (AbstractResourcesBackend *backend : std::as_const(m_backends)) {
0055                   ret += backend->updatesCount();
0056               }
0057               return ret;
0058           },
0059           [this](int count) {
0060               Q_EMIT updatesCountChanged(count);
0061           })
0062     , m_fetchingUpdatesProgress(
0063           0,
0064           [this] {
0065               if (m_backends.isEmpty())
0066                   return 0;
0067 
0068               int sum = 0;
0069               int weights = 0;
0070               for (auto backend : std::as_const(m_backends)) {
0071                   sum += backend->fetchingUpdatesProgress() * backend->fetchingUpdatesProgressWeight();
0072                   weights += backend->fetchingUpdatesProgressWeight();
0073               }
0074               return sum / weights;
0075           },
0076           [this](int progress) {
0077               Q_EMIT fetchingUpdatesProgressChanged(progress);
0078           })
0079 {
0080     connect(this, &ResourcesModel::allInitialized, this, &ResourcesModel::slotFetching);
0081     connect(this, &ResourcesModel::backendsChanged, this, &ResourcesModel::initApplicationsBackend);
0082 }
0083 
0084 void ResourcesModel::init(bool load)
0085 {
0086     Q_ASSERT(QCoreApplication::instance()->thread() == QThread::currentThread());
0087 
0088     m_allInitializedEmitter->setSingleShot(true);
0089     m_allInitializedEmitter->setInterval(0);
0090     connect(m_allInitializedEmitter, &QTimer::timeout, this, [this]() {
0091         if (m_initializingBackendsCount == 0) {
0092             m_isInitializing = false;
0093             Q_EMIT allInitialized();
0094         }
0095     });
0096 
0097     if (load) {
0098         registerAllBackends();
0099     }
0100 
0101     m_updateAction = new DiscoverAction(this);
0102     m_updateAction->setIconName(QStringLiteral("system-software-update"));
0103     m_updateAction->setText(i18n("Refresh"));
0104     connect(this, &ResourcesModel::fetchingChanged, m_updateAction, [this](bool fetching) {
0105         m_updateAction->setEnabled(!fetching);
0106         m_fetchingUpdatesProgress.reevaluate();
0107     });
0108     connect(m_updateAction, &DiscoverAction::triggered, this, &ResourcesModel::checkForUpdates);
0109 
0110     connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater);
0111 }
0112 
0113 ResourcesModel::ResourcesModel(const QString &backendName, QObject *parent)
0114     : ResourcesModel(parent)
0115 {
0116     s_self = this;
0117     registerBackendByName(backendName);
0118     init(false);
0119 }
0120 
0121 ResourcesModel::~ResourcesModel()
0122 {
0123     s_self = nullptr;
0124     qDeleteAll(m_backends);
0125 }
0126 
0127 void ResourcesModel::addResourcesBackend(AbstractResourcesBackend *backend)
0128 {
0129     Q_ASSERT(!m_backends.contains(backend));
0130     if (!backend->isValid()) {
0131         qCWarning(LIBDISCOVER_LOG) << "Discarding invalid backend" << backend->name();
0132         CategoryModel::global()->blacklistPlugin(backend->name());
0133         backend->deleteLater();
0134         return;
0135     }
0136 
0137     m_backends += backend;
0138     if (!backend->isFetching()) {
0139         m_updatesCount.reevaluate();
0140     } else {
0141         m_initializingBackendsCount++;
0142     }
0143 
0144     connect(backend, &AbstractResourcesBackend::fetchingChanged, this, &ResourcesModel::callerFetchingChanged);
0145     connect(backend, &AbstractResourcesBackend::allDataChanged, this, &ResourcesModel::updateCaller);
0146     connect(backend, &AbstractResourcesBackend::resourcesChanged, this, &ResourcesModel::resourceDataChanged);
0147     connect(backend, &AbstractResourcesBackend::updatesCountChanged, this, [this] {
0148         m_updatesCount.reevaluate();
0149     });
0150     connect(backend, &AbstractResourcesBackend::fetchingUpdatesProgressChanged, this, [this] {
0151         m_fetchingUpdatesProgress.reevaluate();
0152     });
0153     connect(backend, &AbstractResourcesBackend::resourceRemoved, this, &ResourcesModel::resourceRemoved);
0154     connect(backend, &AbstractResourcesBackend::passiveMessage, this, &ResourcesModel::passiveMessage);
0155     connect(backend, &AbstractResourcesBackend::inlineMessageChanged, this, &ResourcesModel::setInlineMessage);
0156     connect(backend->backendUpdater(), &AbstractBackendUpdater::progressingChanged, this, &ResourcesModel::slotFetching);
0157     if (backend->reviewsBackend()) {
0158         connect(backend->reviewsBackend(), &AbstractReviewsBackend::error, this, &ResourcesModel::passiveMessage, Qt::UniqueConnection);
0159     }
0160 
0161     // In case this is in fact the first backend to be added, and also happens to be
0162     // pre-filled, we still need for the rest of the backends to be added before trying
0163     // to send out the initialized signal. To ensure this happens, schedule it for the
0164     // start of the next run of the event loop.
0165     if (m_initializingBackendsCount == 0) {
0166         m_allInitializedEmitter->start();
0167     } else {
0168         slotFetching();
0169     }
0170 }
0171 
0172 void ResourcesModel::callerFetchingChanged()
0173 {
0174     AbstractResourcesBackend *backend = qobject_cast<AbstractResourcesBackend *>(sender());
0175 
0176     if (!backend->isValid()) {
0177         qCWarning(LIBDISCOVER_LOG) << "Discarding invalid backend" << backend->name();
0178         int idx = m_backends.indexOf(backend);
0179         Q_ASSERT(idx >= 0);
0180         m_backends.removeAt(idx);
0181         Q_EMIT backendsChanged();
0182         CategoryModel::global()->blacklistPlugin(backend->name());
0183         backend->deleteLater();
0184         slotFetching();
0185         return;
0186     }
0187 
0188     if (backend->isFetching()) {
0189         m_initializingBackendsCount++;
0190         slotFetching();
0191     } else {
0192         m_initializingBackendsCount--;
0193         if (m_initializingBackendsCount == 0)
0194             m_allInitializedEmitter->start();
0195         else
0196             slotFetching();
0197     }
0198 }
0199 
0200 void ResourcesModel::updateCaller(const QVector<QByteArray> &properties)
0201 {
0202     AbstractResourcesBackend *backend = qobject_cast<AbstractResourcesBackend *>(sender());
0203 
0204     Q_EMIT backendDataChanged(backend, properties);
0205 }
0206 
0207 QVector<AbstractResourcesBackend *> ResourcesModel::backends() const
0208 {
0209     return m_backends;
0210 }
0211 
0212 bool ResourcesModel::hasSecurityUpdates() const
0213 {
0214     bool ret = false;
0215 
0216     for (AbstractResourcesBackend *backend : std::as_const(m_backends)) {
0217         ret |= backend->hasSecurityUpdates();
0218     }
0219 
0220     return ret;
0221 }
0222 
0223 void ResourcesModel::installApplication(AbstractResource *app)
0224 {
0225     TransactionModel::global()->addTransaction(app->backend()->installApplication(app));
0226 }
0227 
0228 void ResourcesModel::installApplication(AbstractResource *app, const AddonList &addons)
0229 {
0230     TransactionModel::global()->addTransaction(app->backend()->installApplication(app, addons));
0231 }
0232 
0233 void ResourcesModel::removeApplication(AbstractResource *app)
0234 {
0235     TransactionModel::global()->addTransaction(app->backend()->removeApplication(app));
0236 }
0237 
0238 void ResourcesModel::registerAllBackends()
0239 {
0240     DiscoverBackendsFactory f;
0241     const auto backends = f.allBackends();
0242     if (m_initializingBackendsCount == 0 && backends.isEmpty()) {
0243         qCWarning(LIBDISCOVER_LOG) << "Couldn't find any backends";
0244         m_allInitializedEmitter->start();
0245     } else {
0246         for (AbstractResourcesBackend *b : backends) {
0247             addResourcesBackend(b);
0248         }
0249         Q_EMIT backendsChanged();
0250     }
0251 }
0252 
0253 void ResourcesModel::registerBackendByName(const QString &name)
0254 {
0255     DiscoverBackendsFactory f;
0256     const auto backends = f.backend(name);
0257     for (auto b : backends)
0258         addResourcesBackend(b);
0259 
0260     Q_EMIT backendsChanged();
0261 }
0262 
0263 bool ResourcesModel::isFetching() const
0264 {
0265     return m_isFetching;
0266 }
0267 
0268 bool ResourcesModel::isInitializing() const
0269 {
0270     return m_isInitializing;
0271 }
0272 
0273 void ResourcesModel::slotFetching()
0274 {
0275     bool newFetching = false;
0276     for (AbstractResourcesBackend *b : std::as_const(m_backends)) {
0277         // isFetching should sort of be enough. However, sometimes the backend itself
0278         // will still be operating on things, which from a model point of view would
0279         // still mean something going on. So, interpret that as fetching as well, for
0280         // the purposes of this property.
0281         if (b->isFetching() || (b->backendUpdater() && b->backendUpdater()->isProgressing())) {
0282             newFetching = true;
0283             break;
0284         }
0285     }
0286     if (newFetching != m_isFetching) {
0287         m_isFetching = newFetching;
0288         Q_EMIT fetchingChanged(m_isFetching);
0289     }
0290 }
0291 
0292 bool ResourcesModel::isBusy() const
0293 {
0294     return TransactionModel::global()->rowCount() > 0;
0295 }
0296 
0297 bool ResourcesModel::isExtended(const QString &id)
0298 {
0299     return std::any_of(m_backends.constBegin(), m_backends.constEnd(), [&](AbstractResourcesBackend *backend) {
0300         return backend->extends(id);
0301     });
0302 }
0303 
0304 AggregatedResultsStream::AggregatedResultsStream(const QSet<ResultsStream *> &streams)
0305     : ResultsStream(QStringLiteral("AggregatedResultsStream"))
0306 {
0307     Q_ASSERT(!streams.contains(nullptr));
0308     if (streams.isEmpty()) {
0309         qCWarning(LIBDISCOVER_LOG) << "no streams to aggregate!!";
0310         QTimer::singleShot(0, this, &AggregatedResultsStream::clear);
0311     }
0312 
0313     for (auto stream : streams) {
0314         connect(stream, &ResultsStream::resourcesFound, this, &AggregatedResultsStream::addResults);
0315         connect(stream, &QObject::destroyed, this, &AggregatedResultsStream::streamDestruction);
0316         connect(this, &ResultsStream::fetchMore, stream, &ResultsStream::fetchMore);
0317         m_streams << stream;
0318     }
0319 
0320     m_delayedEmission.setInterval(0);
0321     connect(&m_delayedEmission, &QTimer::timeout, this, &AggregatedResultsStream::emitResults);
0322 }
0323 
0324 AggregatedResultsStream::~AggregatedResultsStream() = default;
0325 
0326 void AggregatedResultsStream::addResults(const QVector<StreamResult> &res)
0327 {
0328     for (auto r : res)
0329         connect(r.resource, &QObject::destroyed, this, &AggregatedResultsStream::resourceDestruction);
0330 
0331     m_results += res;
0332 
0333     m_delayedEmission.start();
0334 }
0335 
0336 void AggregatedResultsStream::emitResults()
0337 {
0338     if (!m_results.isEmpty()) {
0339         Q_EMIT resourcesFound(m_results);
0340         m_results.clear();
0341     }
0342     m_delayedEmission.setInterval(m_delayedEmission.interval() + 100);
0343     m_delayedEmission.stop();
0344 }
0345 
0346 void AggregatedResultsStream::resourceDestruction(QObject *obj)
0347 {
0348     for (auto it = m_results.begin(); it != m_results.end(); ++it) {
0349         if (obj == it->resource)
0350             it = m_results.erase(it);
0351         else
0352             ++it;
0353     }
0354 }
0355 
0356 void AggregatedResultsStream::streamDestruction(QObject *obj)
0357 {
0358     m_streams.remove(obj);
0359     clear();
0360 }
0361 
0362 void AggregatedResultsStream::clear()
0363 {
0364     if (m_streams.isEmpty()) {
0365         emitResults();
0366         Q_EMIT finished();
0367         deleteLater();
0368     }
0369 }
0370 
0371 AggregatedResultsStream *ResourcesModel::search(const AbstractResourcesBackend::Filters &search)
0372 {
0373     if (search.isEmpty()) {
0374         return new AggregatedResultsStream({new ResultsStream(QStringLiteral("emptysearch"), {})});
0375     }
0376 
0377     auto streams = kTransform<QSet<ResultsStream *>>(m_backends, [search](AbstractResourcesBackend *backend) {
0378         return backend->search(search);
0379     });
0380     return new AggregatedResultsStream(streams);
0381 }
0382 
0383 void ResourcesModel::checkForUpdates()
0384 {
0385     for (auto backend : std::as_const(m_backends))
0386         backend->checkForUpdates();
0387 }
0388 
0389 AbstractResourcesBackend *ResourcesModel::currentApplicationBackend() const
0390 {
0391     return m_currentApplicationBackend;
0392 }
0393 
0394 void ResourcesModel::setCurrentApplicationBackend(AbstractResourcesBackend *backend, bool write)
0395 {
0396     if (backend != m_currentApplicationBackend) {
0397         if (write) {
0398             KConfigGroup settings(KSharedConfig::openConfig(), u"ResourcesModel"_s);
0399             if (backend)
0400                 settings.writeEntry("currentApplicationBackend", backend->name());
0401             else
0402                 settings.deleteEntry("currentApplicationBackend");
0403         }
0404 
0405         qCDebug(LIBDISCOVER_LOG) << "setting currentApplicationBackend" << backend;
0406         m_currentApplicationBackend = backend;
0407         Q_EMIT currentApplicationBackendChanged(backend);
0408     }
0409 }
0410 
0411 void ResourcesModel::initApplicationsBackend()
0412 {
0413     const auto name = applicationSourceName();
0414 
0415     auto idx = kIndexOf(m_backends, [name](AbstractResourcesBackend *b) {
0416         return b->hasApplications() && b->name() == name;
0417     });
0418     if (idx < 0) {
0419         idx = kIndexOf(m_backends, [](AbstractResourcesBackend *b) {
0420             return b->hasApplications();
0421         });
0422         qCDebug(LIBDISCOVER_LOG) << "falling back applications backend to" << idx;
0423     }
0424     setCurrentApplicationBackend(m_backends.value(idx, nullptr), false);
0425 }
0426 
0427 QString ResourcesModel::applicationSourceName() const
0428 {
0429     KConfigGroup settings(KSharedConfig::openConfig(), u"ResourcesModel"_s);
0430     return settings.readEntry<QString>("currentApplicationBackend", QStringLiteral("packagekit-backend"));
0431 }
0432 
0433 QString ResourcesModel::distroName() const
0434 {
0435     return KOSRelease().name();
0436 }
0437 
0438 QUrl ResourcesModel::distroBugReportUrl()
0439 {
0440     return QUrl(KOSRelease().bugReportUrl());
0441 }
0442 
0443 void ResourcesModel::setInlineMessage(const QSharedPointer<InlineMessage> &inlineMessage)
0444 {
0445     if (inlineMessage == m_inlineMessage) {
0446         return;
0447     }
0448 
0449     m_inlineMessage = inlineMessage;
0450     Q_EMIT inlineMessageChanged(inlineMessage);
0451 }