diff --git a/libdiscover/resources/ResourcesModel.cpp b/libdiscover/resources/ResourcesModel.cpp index 627b488c..849ba56c 100644 --- a/libdiscover/resources/ResourcesModel.cpp +++ b/libdiscover/resources/ResourcesModel.cpp @@ -1,392 +1,399 @@ /*************************************************************************** * Copyright © 2012 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "ResourcesModel.h" #include "AbstractResource.h" #include "resources/AbstractResourcesBackend.h" #include "resources/AbstractBackendUpdater.h" #include #include #include #include #include "Transaction/TransactionModel.h" #include "Category/CategoryModel.h" #include "utils.h" #include "libdiscover_debug.h" #include #include #include #include #include #include #include ResourcesModel *ResourcesModel::s_self = nullptr; ResourcesModel *ResourcesModel::global() { if(!s_self) s_self = new ResourcesModel; return s_self; } ResourcesModel::ResourcesModel(QObject* parent, bool load) : QObject(parent) , m_isFetching(false) , m_initializingBackends(0) , m_currentApplicationBackend(nullptr) { init(load); connect(this, &ResourcesModel::allInitialized, this, &ResourcesModel::slotFetching); connect(this, &ResourcesModel::backendsChanged, this, &ResourcesModel::initApplicationsBackend); } void ResourcesModel::init(bool load) { Q_ASSERT(!s_self); Q_ASSERT(QCoreApplication::instance()->thread()==QThread::currentThread()); if(load) QMetaObject::invokeMethod(this, "registerAllBackends", Qt::QueuedConnection); m_updateAction = new QAction(this); m_updateAction->setIcon(QIcon::fromTheme(QStringLiteral("system-software-update"))); m_updateAction->setText(i18nc("@action Checks the Internet for updates", "Check for Updates")); m_updateAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R)); connect(this, &ResourcesModel::fetchingChanged, m_updateAction, [this](bool fetching) { m_updateAction->setEnabled(!fetching); }); connect(m_updateAction, &QAction::triggered, this, &ResourcesModel::checkForUpdates); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater); } ResourcesModel::ResourcesModel(const QString& backendName, QObject* parent) : ResourcesModel(parent, false) { s_self = this; registerBackendByName(backendName); } ResourcesModel::~ResourcesModel() { s_self = nullptr; qDeleteAll(m_backends); } void 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; } m_backends += backend; if(!backend->isFetching()) { if (backend->updatesCount() > 0) emit updatesCountChanged(); } else { m_initializingBackends++; } 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, &ResourcesModel::updatesCountChanged); connect(backend, &AbstractResourcesBackend::resourceRemoved, this, &ResourcesModel::resourceRemoved); connect(backend, &AbstractResourcesBackend::passiveMessage, this, &ResourcesModel::passiveMessage); connect(backend->backendUpdater(), &AbstractBackendUpdater::progressingChanged, this, &ResourcesModel::slotFetching); if(m_initializingBackends==0) emit allInitialized(); else slotFetching(); } 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_initializingBackends++; slotFetching(); } else { m_initializingBackends--; if(m_initializingBackends==0) emit allInitialized(); else slotFetching(); } } void ResourcesModel::updateCaller(const QVector& properties) { AbstractResourcesBackend* backend = qobject_cast(sender()); Q_EMIT backendDataChanged(backend, properties); } QVector< AbstractResourcesBackend* > ResourcesModel::backends() const { return m_backends; } int ResourcesModel::updatesCount() const { int ret = 0; foreach(AbstractResourcesBackend* backend, m_backends) { ret += backend->updatesCount(); } return ret; } bool ResourcesModel::hasSecurityUpdates() const { bool ret = false; foreach(AbstractResourcesBackend* backend, m_backends) { ret |= backend->hasSecurityUpdates(); } return ret; } 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_initializingBackends==0 && backends.isEmpty()) { qCWarning(LIBDISCOVER_LOG) << "Couldn't find any backends"; emit allInitialized(); } else { foreach(AbstractResourcesBackend* b, backends) { addResourcesBackend(b); } emit backendsChanged(); } } void ResourcesModel::registerBackendByName(const QString& name) { DiscoverBackendsFactory f; const auto backends = f.backend(name); for(auto b : backends) addResourcesBackend(b); emit backendsChanged(); } bool ResourcesModel::isFetching() const { return m_isFetching; } void ResourcesModel::slotFetching() { bool newFetching = false; foreach(AbstractResourcesBackend* b, 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(b->isFetching() || (b->backendUpdater() && b->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) { bool ret = true; foreach (AbstractResourcesBackend* backend, m_backends) { ret = backend->extends().contains(id); if (ret) break; } return ret; } AggregatedResultsStream::AggregatedResultsStream(const QSet& streams) : ResultsStream(QStringLiteral("AggregatedResultsStream")) { Q_ASSERT(!streams.contains(nullptr)); if (streams.isEmpty()) { qCWarning(LIBDISCOVER_LOG) << "no streams to aggregate!!"; QTimer::singleShot(0, this, &AggregatedResultsStream::clear); } for (auto stream: streams) { connect(stream, &ResultsStream::resourcesFound, this, &AggregatedResultsStream::addResults); connect(stream, &QObject::destroyed, this, &AggregatedResultsStream::destruction); m_streams << stream; } m_delayedEmission.setInterval(0); connect(&m_delayedEmission, &QTimer::timeout, this, &AggregatedResultsStream::emitResults); } +AggregatedResultsStream::~AggregatedResultsStream() = default; + void AggregatedResultsStream::addResults(const QVector& res) { + for(auto r : res) + connect(r, &QObject::destroyed, this, [this, r](){ + m_results.removeAll(r); + }); + m_results += res; 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::destruction(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); } AbstractResource* ResourcesModel::resourceForFile(const QUrl& file) { AbstractResource* ret = nullptr; foreach(auto backend, m_backends) { ret = backend->resourceForFile(file); if (ret) break; } return ret; } void ResourcesModel::checkForUpdates() { for(auto backend: qAsConst(m_backends)) backend->checkForUpdates(); } QVector ResourcesModel::applicationBackends() const { return kFilter>(m_backends, [](AbstractResourcesBackend* b){ return b->hasApplications(); }); } QVariantList ResourcesModel::applicationBackendsVariant() const { return kTransform(applicationBackends(), [](AbstractResourcesBackend* b) {return QVariant::fromValue(b);}); } AbstractResourcesBackend* ResourcesModel::currentApplicationBackend() const { return m_currentApplicationBackend; } void ResourcesModel::setCurrentApplicationBackend(AbstractResourcesBackend* backend, bool write) { if (backend != m_currentApplicationBackend) { if (write) { KConfigGroup settings(KSharedConfig::openConfig(), "ResourcesModel"); if (backend) settings.writeEntry("currentApplicationBackend", backend->name()); else settings.deleteEntry("currentApplicationBackend"); } qCDebug(LIBDISCOVER_LOG) << "setting currentApplicationBackend" << backend; m_currentApplicationBackend = backend; Q_EMIT currentApplicationBackendChanged(backend); } } void ResourcesModel::initApplicationsBackend() { KConfigGroup settings(KSharedConfig::openConfig(), "ResourcesModel"); const QString name = settings.readEntry("currentApplicationBackend", QStringLiteral("packagekit-backend")); const auto backends = applicationBackends(); auto idx = kIndexOf(backends, [name](AbstractResourcesBackend* b) { return b->name() == name; }); if (idx<0) { idx = kIndexOf(backends, [](AbstractResourcesBackend* b) { return b->hasApplications(); }); qCDebug(LIBDISCOVER_LOG) << "falling back applications backend to" << idx; } setCurrentApplicationBackend(backends.value(idx, nullptr), false); } diff --git a/libdiscover/resources/ResourcesModel.h b/libdiscover/resources/ResourcesModel.h index 947340e1..d15955da 100644 --- a/libdiscover/resources/ResourcesModel.h +++ b/libdiscover/resources/ResourcesModel.h @@ -1,129 +1,130 @@ /*************************************************************************** * Copyright ?? 2012 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef RESOURCESMODEL_H #define RESOURCESMODEL_H #include #include #include #include "discovercommon_export.h" #include "AbstractResourcesBackend.h" class QAction; class DISCOVERCOMMON_EXPORT AggregatedResultsStream : public ResultsStream { Q_OBJECT public: AggregatedResultsStream(const QSet& streams); + ~AggregatedResultsStream(); Q_SIGNALS: void finished(); private: void addResults(const QVector& res); void emitResults(); void destruction(QObject* obj); void clear(); QSet m_streams; QVector m_results; QTimer m_delayedEmission; }; class DISCOVERCOMMON_EXPORT ResourcesModel : public QObject { Q_OBJECT Q_PROPERTY(int updatesCount READ updatesCount NOTIFY updatesCountChanged) Q_PROPERTY(bool hasSecurityUpdates READ hasSecurityUpdates NOTIFY updatesCountChanged) Q_PROPERTY(bool isFetching READ isFetching NOTIFY fetchingChanged) Q_PROPERTY(QVariantList applicationBackends READ applicationBackendsVariant NOTIFY backendsChanged) Q_PROPERTY(AbstractResourcesBackend* currentApplicationBackend READ currentApplicationBackend WRITE setCurrentApplicationBackend NOTIFY currentApplicationBackendChanged) Q_PROPERTY(QAction* updateAction READ updateAction CONSTANT) public: /** This constructor should be only used by unit tests. * @p backendName defines what backend will be loaded when the backend is constructed. */ explicit ResourcesModel(const QString& backendName, QObject* parent = nullptr); static ResourcesModel* global(); ~ResourcesModel() override; QVector< AbstractResourcesBackend* > backends() const; int updatesCount() const; bool hasSecurityUpdates() const; bool isBusy() const; bool isFetching() const; Q_SCRIPTABLE bool isExtended(const QString &id); AggregatedResultsStream* search(const AbstractResourcesBackend::Filters &search); AbstractResource* resourceForFile(const QUrl &/*url*/); void checkForUpdates(); QVariantList applicationBackendsVariant() const; QVector applicationBackends() const; void setCurrentApplicationBackend(AbstractResourcesBackend* backend, bool writeConfig = true); AbstractResourcesBackend* currentApplicationBackend() const; QAction* updateAction() const { return m_updateAction; } public Q_SLOTS: void installApplication(AbstractResource* app, const AddonList& addons); void installApplication(AbstractResource* app); void removeApplication(AbstractResource* app); Q_SIGNALS: void fetchingChanged(bool isFetching); void allInitialized(); void backendsChanged(); void updatesCountChanged(); void backendDataChanged(AbstractResourcesBackend* backend, const QVector& properties); void resourceDataChanged(AbstractResource* resource, const QVector& properties); void resourceRemoved(AbstractResource* resource); void passiveMessage(const QString &message); void currentApplicationBackendChanged(AbstractResourcesBackend* currentApplicationBackend); private Q_SLOTS: void callerFetchingChanged(); void updateCaller(const QVector& properties); void registerAllBackends(); private: ///@p initialize tells if all backends load will be triggered on construction explicit ResourcesModel(QObject* parent=nullptr, bool load = true); void init(bool load); void addResourcesBackend(AbstractResourcesBackend* backend); void registerBackendByName(const QString& name); void initApplicationsBackend(); void slotFetching(); bool m_isFetching; QVector< AbstractResourcesBackend* > m_backends; int m_initializingBackends; QAction* m_updateAction = nullptr; AbstractResourcesBackend* m_currentApplicationBackend; static ResourcesModel* s_self; }; #endif // RESOURCESMODEL_H