/* * SPDX-FileCopyrightText: 2012 Aleix Pol Gonzalez * * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL */ #include "ResourcesModel.h" #include "AbstractResource.h" #include "Category/CategoryModel.h" #include "Transaction/TransactionModel.h" #include "libdiscover_debug.h" #include "resources/AbstractBackendUpdater.h" #include "resources/AbstractResourcesBackend.h" #include "utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Qt::StringLiterals; ResourcesModel *ResourcesModel::s_self = nullptr; ResourcesModel *ResourcesModel::global() { if (!s_self) { s_self = new ResourcesModel; s_self->init(true); } return s_self; } ResourcesModel::ResourcesModel(QObject *parent) : QObject(parent) , m_isFetching(false) , m_initializingBackendsCount(0) , m_currentApplicationBackend(nullptr) , m_updatesCount( 0, [this] { int ret = 0; for (AbstractResourcesBackend *backend : std::as_const(m_backends)) { ret += backend->updatesCount(); } return ret; }, [this](int count) { Q_EMIT updatesCountChanged(count); }) , m_fetchingUpdatesProgress( 0, [this] { if (m_backends.isEmpty()) { return 0; } int sum = 0; int weights = 0; for (auto backend : std::as_const(m_backends)) { sum += backend->fetchingUpdatesProgress() * backend->fetchingUpdatesProgressWeight(); weights += backend->fetchingUpdatesProgressWeight(); } return sum / weights; }, [this](int progress) { Q_EMIT fetchingUpdatesProgressChanged(progress); }) { m_allInitializedEmitter.setObjectName(u"allInitializedEmitter"_s); connect(this, &ResourcesModel::allInitialized, this, &ResourcesModel::slotFetching); connect(this, &ResourcesModel::backendsChanged, this, &ResourcesModel::initApplicationsBackend); } void ResourcesModel::init(bool load) { Q_ASSERT(QCoreApplication::instance()->thread() == QThread::currentThread()); m_allInitializedEmitter.setSingleShot(true); m_allInitializedEmitter.setInterval(0); connect(&m_allInitializedEmitter, &QTimer::timeout, this, [this]() { if (m_initializingBackendsCount == 0) { m_isInitializing = false; Q_EMIT allInitialized(); } }); if (load) { registerAllBackends(); } m_updateAction = new DiscoverAction(this); m_updateAction->setIconName(QStringLiteral("system-software-update")); m_updateAction->setText(i18n("Refresh")); connect(this, &ResourcesModel::fetchingChanged, m_updateAction, [this](bool fetching) { m_updateAction->setEnabled(!fetching); m_fetchingUpdatesProgress.reevaluate(); }); connect(m_updateAction, &DiscoverAction::triggered, this, &ResourcesModel::checkForUpdates); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater); } ResourcesModel::ResourcesModel(const QString &backendName, QObject *parent) : ResourcesModel(parent) { s_self = this; registerBackendByName(backendName); init(false); } ResourcesModel::~ResourcesModel() { s_self = nullptr; qDeleteAll(m_backends); } void ResourcesModel::addResourcesBackends(const QList &backends) { auto added = false; for (const auto backend : backends) { added |= addResourcesBackend(backend); } if (added) { Q_EMIT backendsChanged(); } } bool ResourcesModel::addResourcesBackend(AbstractResourcesBackend *backend) { Q_ASSERT(!m_backends.contains(backend)); if (!backend->isValid()) { qCWarning(LIBDISCOVER_LOG) << "Discarding invalid backend" << backend->name(); CategoryModel::global()->blacklistPlugin(backend->name()); backend->deleteLater(); return false; } m_backends += backend; if (!backend->isFetching()) { m_updatesCount.reevaluate(); } else { m_initializingBackendsCount++; } connect(backend, &AbstractResourcesBackend::fetchingChanged, this, &ResourcesModel::callerFetchingChanged); connect(backend, &AbstractResourcesBackend::allDataChanged, this, &ResourcesModel::updateCaller); connect(backend, &AbstractResourcesBackend::resourcesChanged, this, &ResourcesModel::resourceDataChanged); connect(backend, &AbstractResourcesBackend::updatesCountChanged, this, [this] { m_updatesCount.reevaluate(); }); connect(backend, &AbstractResourcesBackend::fetchingUpdatesProgressChanged, this, [this] { m_fetchingUpdatesProgress.reevaluate(); }); connect(backend, &AbstractResourcesBackend::resourceRemoved, this, &ResourcesModel::resourceRemoved); connect(backend, &AbstractResourcesBackend::passiveMessage, this, &ResourcesModel::passiveMessage); connect(backend, &AbstractResourcesBackend::inlineMessageChanged, this, &ResourcesModel::setInlineMessage); connect(backend->backendUpdater(), &AbstractBackendUpdater::progressingChanged, this, &ResourcesModel::slotFetching); if (auto reviewsBackend = backend->reviewsBackend()) { connect(reviewsBackend, &AbstractReviewsBackend::error, this, &ResourcesModel::passiveMessage, Qt::UniqueConnection); } // In case this is in fact the first backend to be added, and also happens to be // pre-filled, we still need for the rest of the backends to be added before trying // to send out the initialized signal. To ensure this happens, schedule it for the // start of the next run of the event loop. if (m_initializingBackendsCount == 0) { m_allInitializedEmitter.start(); } else { slotFetching(); } return true; } void ResourcesModel::callerFetchingChanged() { AbstractResourcesBackend *backend = qobject_cast(sender()); if (!backend->isValid()) { qCWarning(LIBDISCOVER_LOG) << "Discarding invalid backend" << backend->name(); int idx = m_backends.indexOf(backend); Q_ASSERT(idx >= 0); m_backends.removeAt(idx); Q_EMIT backendsChanged(); CategoryModel::global()->blacklistPlugin(backend->name()); backend->deleteLater(); slotFetching(); return; } if (backend->isFetching()) { m_initializingBackendsCount++; slotFetching(); } else { m_initializingBackendsCount--; if (m_initializingBackendsCount == 0) { m_allInitializedEmitter.start(); } else { slotFetching(); } } } void ResourcesModel::updateCaller(const QVector &properties) { AbstractResourcesBackend *backend = qobject_cast(sender()); Q_EMIT backendDataChanged(backend, properties); } QVector ResourcesModel::backends() const { return m_backends; } bool ResourcesModel::hasSecurityUpdates() const { return std::any_of(m_backends.constBegin(), m_backends.constEnd(), [](const AbstractResourcesBackend *backend) { return backend->hasSecurityUpdates(); }); } void ResourcesModel::installApplication(AbstractResource *app) { TransactionModel::global()->addTransaction(app->backend()->installApplication(app)); } void ResourcesModel::installApplication(AbstractResource *app, const AddonList &addons) { TransactionModel::global()->addTransaction(app->backend()->installApplication(app, addons)); } void ResourcesModel::removeApplication(AbstractResource *app) { TransactionModel::global()->addTransaction(app->backend()->removeApplication(app)); } void ResourcesModel::registerAllBackends() { DiscoverBackendsFactory f; const auto backends = f.allBackends(); if (m_initializingBackendsCount == 0 && backends.isEmpty()) { qCWarning(LIBDISCOVER_LOG) << "Couldn't find any backends"; m_allInitializedEmitter.start(); } else { addResourcesBackends(backends); } } void ResourcesModel::registerBackendByName(const QString &name) { DiscoverBackendsFactory f; const auto backends = f.backend(name); addResourcesBackends(backends); } bool ResourcesModel::isFetching() const { return m_isFetching; } bool ResourcesModel::isInitializing() const { return m_isInitializing; } void ResourcesModel::slotFetching() { bool newFetching = false; for (const auto backend : std::as_const(m_backends)) { // isFetching should sort of be enough. However, sometimes the backend itself // will still be operating on things, which from a model point of view would // still mean something going on. So, interpret that as fetching as well, for // the purposes of this property. if (backend->isFetching() || (backend->backendUpdater() && backend->backendUpdater()->isProgressing())) { newFetching = true; break; } } if (newFetching != m_isFetching) { m_isFetching = newFetching; Q_EMIT fetchingChanged(m_isFetching); } } bool ResourcesModel::isBusy() const { return TransactionModel::global()->rowCount() > 0; } bool ResourcesModel::isExtended(const QString &id) { return std::any_of(m_backends.constBegin(), m_backends.constEnd(), [&](AbstractResourcesBackend *backend) { return backend->extends(id); }); } AggregatedResultsStream::AggregatedResultsStream(const QSet &streams) : ResultsStream(QStringLiteral("AggregatedResultsStream")) { Q_ASSERT(!streams.contains(nullptr)); if (streams.isEmpty()) { qCWarning(LIBDISCOVER_LOG) << "AggregatedResultsStream: No streams to aggregate!"; QTimer::singleShot(0, this, &AggregatedResultsStream::clear); } for (const auto &stream : streams) { connect(stream, &ResultsStream::resourcesFound, this, &AggregatedResultsStream::addResults); connect(stream, &QObject::destroyed, this, &AggregatedResultsStream::streamDestruction); connect(this, &ResultsStream::fetchMore, stream, &ResultsStream::fetchMore); m_streams << stream; } m_delayedEmission.setInterval(0); connect(&m_delayedEmission, &QTimer::timeout, this, &AggregatedResultsStream::emitResults); } AggregatedResultsStream::~AggregatedResultsStream() = default; void AggregatedResultsStream::addResults(const QVector &streams) { for (const auto &stream : streams) { connect(stream.resource, &QObject::destroyed, this, &AggregatedResultsStream::resourceDestruction); } m_results += streams; m_delayedEmission.start(); } void AggregatedResultsStream::emitResults() { if (!m_results.isEmpty()) { Q_EMIT resourcesFound(m_results); m_results.clear(); } m_delayedEmission.setInterval(m_delayedEmission.interval() + 100); m_delayedEmission.stop(); } void AggregatedResultsStream::resourceDestruction(QObject *obj) { for (auto it = m_results.begin(); it != m_results.end(); ++it) { if (obj == it->resource) { it = m_results.erase(it); } else { ++it; } } } void AggregatedResultsStream::streamDestruction(QObject *obj) { m_streams.remove(obj); clear(); } void AggregatedResultsStream::clear() { if (m_streams.isEmpty()) { emitResults(); Q_EMIT finished(); deleteLater(); } } AggregatedResultsStream *ResourcesModel::search(const AbstractResourcesBackend::Filters &search) { if (search.isEmpty()) { return new AggregatedResultsStream({new ResultsStream(QStringLiteral("emptysearch"), {})}); } auto streams = kTransform>(m_backends, [search](AbstractResourcesBackend *backend) { return backend->search(search); }); return new AggregatedResultsStream(streams); } void ResourcesModel::checkForUpdates() { for (const auto backend : std::as_const(m_backends)) { backend->checkForUpdates(); } } AbstractResourcesBackend *ResourcesModel::currentApplicationBackend() const { return m_currentApplicationBackend; } void ResourcesModel::setCurrentApplicationBackend(AbstractResourcesBackend *backend, bool write) { if (backend != m_currentApplicationBackend) { if (write) { KConfigGroup settings(KSharedConfig::openConfig(), u"ResourcesModel"_s); if (backend) { settings.writeEntry("currentApplicationBackend", backend->name()); } else { settings.deleteEntry("currentApplicationBackend"); } } qCDebug(LIBDISCOVER_LOG) << "setting currentApplicationBackend to" << backend; m_currentApplicationBackend = backend; Q_EMIT currentApplicationBackendChanged(backend); } } void ResourcesModel::initApplicationsBackend() { const auto name = applicationSourceName(); auto idx = kIndexOf(m_backends, [name](AbstractResourcesBackend *backend) { return backend->hasApplications() && backend->name() == name; }); if (idx < 0) { idx = kIndexOf(m_backends, [](AbstractResourcesBackend *backend) { return backend->hasApplications(); }); qCDebug(LIBDISCOVER_LOG) << "falling back applications backend to" << idx; } setCurrentApplicationBackend(m_backends.value(idx, nullptr), false); } QString ResourcesModel::applicationSourceName() const { KConfigGroup settings(KSharedConfig::openConfig(), u"ResourcesModel"_s); return settings.readEntry("currentApplicationBackend", QStringLiteral("packagekit-backend")); } QString ResourcesModel::distroName() const { return KOSRelease().name(); } QUrl ResourcesModel::distroBugReportUrl() { return QUrl(KOSRelease().bugReportUrl()); } void ResourcesModel::setInlineMessage(const QSharedPointer &inlineMessage) { if (inlineMessage == m_inlineMessage) { return; } m_inlineMessage = inlineMessage; Q_EMIT inlineMessageChanged(inlineMessage); } #include "moc_ResourcesModel.cpp"