diff --git a/libdiscover/backends/KNSBackend/KNSBackend.cpp b/libdiscover/backends/KNSBackend/KNSBackend.cpp index 59bcd7a7..2c30a54c 100644 --- a/libdiscover/backends/KNSBackend/KNSBackend.cpp +++ b/libdiscover/backends/KNSBackend/KNSBackend.cpp @@ -1,397 +1,407 @@ /*************************************************************************** * 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 . * ***************************************************************************/ // Qt includes #include #include #include #include #include #include // Attica includes #include #include // KDE includes +#include #include #include #include #include #include // DiscoverCommon includes #include "Transaction/Transaction.h" #include "Transaction/TransactionModel.h" #include "Category/Category.h" // Own includes #include "KNSBackend.h" #include "KNSResource.h" #include "KNSReviews.h" #include #include "utils.h" class KNSBackendFactory : public AbstractResourcesBackendFactory { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.muon.AbstractResourcesBackendFactory") Q_INTERFACES(AbstractResourcesBackendFactory) public: KNSBackendFactory() { connect(KNSCore::QuestionManager::instance(), &KNSCore::QuestionManager::askQuestion, this, [](KNSCore::Question* q) { qWarning() << q->question() << q->questionType(); q->setResponse(KNSCore::Question::InvalidResponse); }); } QVector newInstance(QObject* parent, const QString &/*name*/) const override { QVector ret; for (const QString &path: QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation)) { QDirIterator dirIt(path, {QStringLiteral("*.knsrc")}, QDir::Files); for(; dirIt.hasNext(); ) { dirIt.next(); auto bk = new KNSBackend(parent, QStringLiteral("plasma"), dirIt.filePath()); ret += bk; } } return ret; } }; KNSBackend::KNSBackend(QObject* parent, const QString& iconName, const QString &knsrc) : AbstractResourcesBackend(parent) , m_fetching(false) , m_isValid(true) - , m_page(0) , m_reviews(new KNSReviews(this)) , m_name(knsrc) , m_iconName(iconName) , m_updater(new StandardBackendUpdater(this)) { const QString fileName = QFileInfo(m_name).fileName(); setName(fileName); setObjectName(knsrc); const KConfig conf(m_name); if (!conf.hasGroup("KNewStuff3")) { markInvalid(QStringLiteral("Config group not found! Check your KNS3 installation.")); return; } m_categories = QStringList{ fileName }; const KConfigGroup group = conf.group("KNewStuff3"); m_extends = group.readEntry("Extends", QStringList()); m_reviews->setProviderUrl(QUrl(group.readEntry("ProvidersUrl", QString()))); setFetching(true); m_engine = new KNSCore::Engine(this); m_engine->init(m_name); +#if KNEWSTUFFCORE_VERSION_MAJOR==5 && KNEWSTUFFCORE_VERSION_MAJOR>=36 + m_engine->setPageSize(100); +#endif // Setting setFetching to false when we get an error ensures we don't end up in an eternally-fetching state connect(m_engine, &KNSCore::Engine::signalError, this, [this](const QString &error) { if(error == QLatin1Literal("All categories are missing")) { markInvalid(error); } m_responsePending = false; Q_EMIT searchFinished(); Q_EMIT availableForQueries(); this->setFetching(false); qWarning() << "kns error" << objectName() << error; }); connect(m_engine, &KNSCore::Engine::signalEntriesLoaded, this, &KNSBackend::receivedEntries, Qt::QueuedConnection); connect(m_engine, &KNSCore::Engine::signalEntryChanged, this, &KNSBackend::statusChanged); connect(m_engine, &KNSCore::Engine::signalEntryDetailsLoaded, this, &KNSBackend::statusChanged); - m_page = -1; connect(m_engine, &KNSCore::Engine::signalProvidersLoaded, m_engine, &KNSCore::Engine::checkForInstalled); + connect(m_engine, &KNSCore::Engine::signalResetView, this, [this](){ + // If KNS tells us we should reset the view, what that means here is to remove + // references to all the resources we've already told the agregator model about + // from the model, as they will be added again... + foreach(AbstractResource* res, m_resourcesByName.values()) { + resourceRemoved(res); + res->deleteLater(); + } + m_resourcesByName.clear(); + }); m_responsePending = true; const QVector> filters = { {CategoryFilter, fileName } }; const QSet backendName = { name() }; QString displayName = group.readEntry("Name", QString()); if (displayName.isEmpty()) { displayName = fileName.mid(0, fileName.indexOf(QLatin1Char('.'))); displayName[0] = displayName[0].toUpper(); } static const QSet knsrcPlasma = { QStringLiteral("aurorae.knsrc"), QStringLiteral("icons.knsrc"), QStringLiteral("kfontinst.knsrc"), QStringLiteral("lookandfeel.knsrc"), QStringLiteral("plasma-themes.knsrc"), QStringLiteral("plasmoids.knsrc"), QStringLiteral("wallpaper.knsrc"), QStringLiteral("xcursor.knsrc"), QStringLiteral("cgcgtk3.knsrc"), QStringLiteral("cgcicon.knsrc"), QStringLiteral("cgctheme.knsrc"), //GTK integration QStringLiteral("kwinswitcher.knsrc"), QStringLiteral("kwineffect.knsrc"), QStringLiteral("kwinscripts.knsrc") //KWin }; auto actualCategory = new Category(displayName, QStringLiteral("plasma"), filters, backendName, {}, QUrl(), true); const auto topLevelName = knsrcPlasma.contains(fileName)? i18n("Plasma Addons") : i18n("Application Addons"); const QUrl decoration(knsrcPlasma.contains(fileName)? QStringLiteral("https://c2.staticflickr.com/4/3148/3042248532_20bd2e38f4_b.jpg") : QStringLiteral("https://c2.staticflickr.com/8/7067/6847903539_d9324dcd19_o.jpg")); auto addonsCategory = new Category(topLevelName, QStringLiteral("plasma"), filters, backendName, {actualCategory}, decoration, true); m_rootCategories = { addonsCategory }; } KNSBackend::~KNSBackend() = default; void KNSBackend::markInvalid(const QString &message) { qWarning() << "invalid kns backend!" << m_name << "because:" << message; m_isValid = false; setFetching(false); } void KNSBackend::setFetching(bool f) { if(m_fetching!=f) { m_fetching = f; emit fetchingChanged(); } } bool KNSBackend::isValid() const { return m_isValid; } KNSResource* KNSBackend::resourceForEntry(const KNSCore::EntryInternal& entry) { KNSResource* r = static_cast(m_resourcesByName.value(entry.uniqueId())); if (!r) { r = new KNSResource(entry, m_categories, this); m_resourcesByName.insert(entry.uniqueId(), r); } else { r->setEntry(entry); } return r; } void KNSBackend::receivedEntries(const KNSCore::EntryInternal::List& entries) { m_responsePending = false; const auto resources = kTransform>(entries, [this](const KNSCore::EntryInternal& entry){ return resourceForEntry(entry); }); if (!resources.isEmpty()) { Q_EMIT receivedResources(resources); } - if(resources.isEmpty() || m_page < 0) { + if(resources.isEmpty()) { Q_EMIT searchFinished(); Q_EMIT availableForQueries(); setFetching(false); return; } -// qDebug() << "received" << objectName() << this << m_page << m_resourcesByName.count(); +// qDebug() << "received" << objectName() << this << m_resourcesByName.count(); if (!m_responsePending) { - ++m_page; // We _have_ to set this first. If we do not, we may run into a situation where the // data request will conclude immediately, causing m_responsePending to remain true // for perpetuity as the slots will be called before the function returns. m_responsePending = true; - m_engine->requestData(m_page, 100); + m_engine->requestMoreData(); } else { Q_EMIT availableForQueries(); } } void KNSBackend::statusChanged(const KNSCore::EntryInternal& entry) { resourceForEntry(entry); } class KNSTransaction : public Transaction { public: KNSTransaction(QObject* parent, KNSResource* res, Transaction::Role role) : Transaction(parent, res, role) , m_id(res->entry().uniqueId()) { TransactionModel::global()->addTransaction(this); setCancellable(false); auto manager = res->knsBackend()->engine(); connect(manager, &KNSCore::Engine::signalEntryChanged, this, &KNSTransaction::anEntryChanged); } void anEntryChanged(const KNSCore::EntryInternal& entry) { if (entry.uniqueId() == m_id) { switch (entry.status()) { case KNS3::Entry::Invalid: qWarning() << "invalid status for" << entry.uniqueId() << entry.status(); break; case KNS3::Entry::Installing: case KNS3::Entry::Updating: setStatus(CommittingStatus); break; case KNS3::Entry::Downloadable: case KNS3::Entry::Installed: case KNS3::Entry::Deleted: case KNS3::Entry::Updateable: if (status() != DoneStatus) { setStatus(DoneStatus); TransactionModel::global()->removeTransaction(this); } break; } } } ~KNSTransaction() override { if (TransactionModel::global()->contains(this)) { qWarning() << "deleting Transaction before it's done"; TransactionModel::global()->removeTransaction(this); } } void cancel() override {} private: const QString m_id; }; void KNSBackend::removeApplication(AbstractResource* app) { auto res = qobject_cast(app); new KNSTransaction(this, res, Transaction::RemoveRole); m_engine->uninstall(res->entry()); } void KNSBackend::installApplication(AbstractResource* app) { auto res = qobject_cast(app); m_engine->install(res->entry()); new KNSTransaction(this, res, Transaction::InstallRole); } void KNSBackend::installApplication(AbstractResource* app, const AddonList& /*addons*/) { installApplication(app); } int KNSBackend::updatesCount() const { return m_updater->updatesCount(); } AbstractReviewsBackend* KNSBackend::reviewsBackend() const { return m_reviews; } static ResultsStream* voidStream() { return new ResultsStream(QStringLiteral("KNS-void"), {}); } ResultsStream* KNSBackend::search(const AbstractResourcesBackend::Filters& filter) { if ((!filter.resourceUrl.isEmpty() && filter.resourceUrl.scheme() != QLatin1String("kns")) || !filter.mimetype.isEmpty()) return voidStream(); if (filter.resourceUrl.scheme() == QLatin1String("kns")) { return findResourceByPackageName(filter.resourceUrl); } else if (filter.state >= AbstractResource::Installed) { QVector ret; foreach(AbstractResource* r, m_resourcesByName) { if(r->state()>=filter.state && (r->name().contains(filter.search, Qt::CaseInsensitive) || r->comment().contains(filter.search, Qt::CaseInsensitive))) ret += r; } return new ResultsStream(QStringLiteral("KNS-installed"), ret); } else if (filter.category && filter.category->matchesCategoryName(m_categories.constFirst())) { return searchStream(filter.search); } else if (!filter.search.isEmpty()) { return searchStream(filter.search); } return voidStream(); } ResultsStream * KNSBackend::searchStream(const QString &searchText) { Q_EMIT startingSearch(); auto stream = new ResultsStream(QStringLiteral("KNS-search-")+name()); - auto start = [this, stream, searchText]() { + connect(this, &KNSBackend::receivedResources, stream, &ResultsStream::resourcesFound); + connect(this, &KNSBackend::searchFinished, stream, &ResultsStream::finish); + connect(this, &KNSBackend::startingSearch, stream, &ResultsStream::finish); + auto start = [this, searchText]() { + // No need to explicitly launch a search, setting the search term already does that for us m_engine->setSearchTerm(searchText); - m_engine->requestData(0, 100); m_responsePending = true; - m_page = 0; - connect(this, &KNSBackend::receivedResources, stream, &ResultsStream::resourcesFound); - connect(this, &KNSBackend::searchFinished, stream, &ResultsStream::finish); - connect(this, &KNSBackend::startingSearch, stream, &ResultsStream::finish); }; if (m_responsePending) { connect(this, &KNSBackend::availableForQueries, stream, start, Qt::QueuedConnection); } else { start(); } return stream; } ResultsStream * KNSBackend::findResourceByPackageName(const QUrl& search) { if (search.scheme() != QLatin1String("kns") || search.host() != name()) return voidStream(); const auto pathParts = search.path().split(QLatin1Char('/'), QString::SkipEmptyParts); if (pathParts.size() != 2) { passiveMessage(i18n("Wrong KNewStuff URI: %1", search.toString())); return voidStream(); } const auto providerid = pathParts.at(0); const auto entryid = pathParts.at(1); auto stream = new ResultsStream(QStringLiteral("KNS-byname-")+entryid); auto start = [this, entryid, stream, providerid]() { m_responsePending = true; m_engine->fetchEntryById(entryid); connect(m_engine, &KNSCore::Engine::signalError, stream, &ResultsStream::finish); connect(m_engine, &KNSCore::Engine::signalEntryDetailsLoaded, stream, [this, stream, entryid, providerid](const KNSCore::EntryInternal &entry) { if (entry.uniqueId() == entryid && providerid == QUrl(entry.providerId()).host()) { stream->resourcesFound({resourceForEntry(entry)}); } m_responsePending = false; QTimer::singleShot(0, this, &KNSBackend::availableForQueries); stream->finish(); }); }; if (m_responsePending) { connect(this, &KNSBackend::availableForQueries, stream, start); } else { start(); } return stream; } bool KNSBackend::isFetching() const { return m_fetching; } AbstractBackendUpdater* KNSBackend::backendUpdater() const { return m_updater; } QString KNSBackend::displayName() const { return QStringLiteral("KNewStuff"); } #include "KNSBackend.moc" diff --git a/libdiscover/backends/KNSBackend/KNSBackend.h b/libdiscover/backends/KNSBackend/KNSBackend.h index 6b64ad01..fa488451 100644 --- a/libdiscover/backends/KNSBackend/KNSBackend.h +++ b/libdiscover/backends/KNSBackend/KNSBackend.h @@ -1,100 +1,99 @@ /*************************************************************************** * 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 KNSBACKEND_H #define KNSBACKEND_H #include #include #include "Transaction/AddonList.h" #include "discovercommon_export.h" class KConfigGroup; class KNSReviews; class KNSResource; class StandardBackendUpdater; namespace KNSCore { class Engine; } class DISCOVERCOMMON_EXPORT KNSBackend : public AbstractResourcesBackend { Q_OBJECT public: explicit KNSBackend(QObject* parent, const QString& iconName, const QString &knsrc); ~KNSBackend() override; void removeApplication(AbstractResource* app) override; void installApplication(AbstractResource* app) override; void installApplication(AbstractResource* app, const AddonList& addons) override; int updatesCount() const override; AbstractReviewsBackend* reviewsBackend() const override; AbstractBackendUpdater* backendUpdater() const override; bool isFetching() const override; QList messageActions() const override { return QList(); } ResultsStream* search(const AbstractResourcesBackend::Filters & filter) override; ResultsStream* findResourceByPackageName(const QUrl & search) override; QVector category() const override { return m_rootCategories; } bool isValid() const override; QStringList extends() const override { return m_extends; } QString iconName() const { return m_iconName; } KNSCore::Engine* engine() const { return m_engine; } void checkForUpdates() override {} QString displayName() const override; Q_SIGNALS: void receivedResources(const QVector &resources); void searchFinished(); void startingSearch(); void availableForQueries(); public Q_SLOTS: void receivedEntries(const KNSCore::EntryInternal::List& entries); void statusChanged(const KNSCore::EntryInternal& entry); private: KNSResource* resourceForEntry(const KNSCore::EntryInternal& entry); void setFetching(bool f); void markInvalid(const QString &message); ResultsStream* searchStream(const QString &searchText); bool m_responsePending = false; bool m_fetching; bool m_isValid; KNSCore::Engine* m_engine; QHash m_resourcesByName; - int m_page; KNSReviews* const m_reviews; QString m_name; QString m_iconName; StandardBackendUpdater* const m_updater; QStringList m_extends; QStringList m_categories; QVector m_rootCategories; }; #endif // KNSBACKEND_H diff --git a/libdiscover/resources/StandardBackendUpdater.cpp b/libdiscover/resources/StandardBackendUpdater.cpp index de05ed77..48439ae5 100644 --- a/libdiscover/resources/StandardBackendUpdater.cpp +++ b/libdiscover/resources/StandardBackendUpdater.cpp @@ -1,234 +1,238 @@ /* * Copyright (C) 2012 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * 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 Library/Lesser General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "ResourcesModel.h" #include #include #include #include #include #include #include StandardBackendUpdater::StandardBackendUpdater(AbstractResourcesBackend* parent) : AbstractBackendUpdater(parent) , m_backend(parent) , m_settingUp(false) , m_progress(0) , m_lastUpdate(QDateTime()) { connect(m_backend, &AbstractResourcesBackend::fetchingChanged, this, &StandardBackendUpdater::refreshUpdateable); connect(m_backend, &AbstractResourcesBackend::resourcesChanged, this, &StandardBackendUpdater::resourcesChanged); + connect(m_backend, &AbstractResourcesBackend::resourceRemoved, this, [this](AbstractResource* resource){ + m_upgradeable.remove(resource); + m_toUpgrade.remove(resource); + }); connect(TransactionModel::global(), &TransactionModel::transactionRemoved, this, &StandardBackendUpdater::transactionRemoved); connect(TransactionModel::global(), &TransactionModel::transactionAdded, this, &StandardBackendUpdater::transactionAdded); } void StandardBackendUpdater::resourcesChanged(AbstractResource* /*res*/, const QVector& props) { if (props.contains("state")) refreshUpdateable(); } bool StandardBackendUpdater::hasUpdates() const { return !m_upgradeable.isEmpty(); } void StandardBackendUpdater::start() { m_settingUp = true; emit progressingChanged(true); setProgress(-1); Q_EMIT progressingChanged(true); foreach(AbstractResource* res, m_toUpgrade) { m_pendingResources += res; m_backend->installApplication(res); } m_settingUp = false; emit statusMessageChanged(statusMessage()); if(m_pendingResources.isEmpty()) { cleanup(); } else { setProgress(1); } } void StandardBackendUpdater::transactionAdded(Transaction* newTransaction) { if (!m_pendingResources.contains(newTransaction->resource())) return; connect(newTransaction, &Transaction::progressChanged, this, &StandardBackendUpdater::transactionProgressChanged); } void StandardBackendUpdater::transactionProgressChanged(int percentage) { Transaction* t = qobject_cast(sender()); Q_EMIT resourceProgressed(t->resource(), percentage); } void StandardBackendUpdater::transactionRemoved(Transaction* t) { const bool fromOurBackend = t->resource() && t->resource()->backend()==m_backend; if (!fromOurBackend) { return; } const bool found = fromOurBackend && m_pendingResources.remove(t->resource()); if(found && !m_settingUp) { setStatusDetail(i18n("%1 has been updated", t->resource()->name())); qreal p = 1-(qreal(m_pendingResources.size())/m_toUpgrade.size()); setProgress(100*p); if(m_pendingResources.isEmpty()) { cleanup(); } } refreshUpdateable(); } void StandardBackendUpdater::refreshUpdateable() { if (m_backend->isFetching()) { return; } m_settingUp = true; Q_EMIT progressingChanged(true); AbstractResourcesBackend::Filters f; f.state = AbstractResource::Upgradeable; m_upgradeable.clear(); auto r = m_backend->search(f); connect(r, &ResultsStream::resourcesFound, this, [this](const QVector &resources){ for(auto res : resources) if (res->state() == AbstractResource::Upgradeable) m_upgradeable.insert(res); }); connect(r, &ResultsStream::destroyed, this, [this](){ m_settingUp = false; Q_EMIT updatesCountChanged(updatesCount()); Q_EMIT progressingChanged(false); }); } qreal StandardBackendUpdater::progress() const { return m_progress; } void StandardBackendUpdater::setProgress(qreal p) { if(p>m_progress || p<0) { m_progress = p; emit progressChanged(p); } } long unsigned int StandardBackendUpdater::remainingTime() const { return 0; } void StandardBackendUpdater::prepare() { m_lastUpdate = QDateTime::currentDateTime(); m_toUpgrade = m_upgradeable; } int StandardBackendUpdater::updatesCount() const { return m_upgradeable.count(); } void StandardBackendUpdater::addResources(const QList< AbstractResource* >& apps) { Q_ASSERT(m_upgradeable.contains(apps.toSet())); m_toUpgrade += apps.toSet(); } void StandardBackendUpdater::removeResources(const QList< AbstractResource* >& apps) { Q_ASSERT(m_upgradeable.contains(apps.toSet())); Q_ASSERT(m_toUpgrade.contains(apps.toSet())); m_toUpgrade -= apps.toSet(); } void StandardBackendUpdater::cleanup() { m_lastUpdate = QDateTime::currentDateTime(); m_toUpgrade.clear(); emit progressingChanged(false); } QList StandardBackendUpdater::toUpdate() const { return m_toUpgrade.toList(); } bool StandardBackendUpdater::isMarked(AbstractResource* res) const { return m_toUpgrade.contains(res); } QDateTime StandardBackendUpdater::lastUpdate() const { return m_lastUpdate; } bool StandardBackendUpdater::isCancelable() const { //We don't really know when we can cancel, so we never let return false; } bool StandardBackendUpdater::isProgressing() const { return m_settingUp || !m_pendingResources.isEmpty(); } QString StandardBackendUpdater::statusDetail() const { return m_statusDetail; } void StandardBackendUpdater::setStatusDetail(const QString& msg) { if (m_statusDetail != msg) { m_statusDetail = msg; emit statusDetailChanged(msg); } } QString StandardBackendUpdater::statusMessage() const { if(m_settingUp) return i18n("Setting up for install..."); else return i18n("Installing..."); } quint64 StandardBackendUpdater::downloadSpeed() const { return 0; }