diff --git a/libdiscover/Category/Category.cpp b/libdiscover/Category/Category.cpp index 77a57686..b10e53f2 100644 --- a/libdiscover/Category/Category.cpp +++ b/libdiscover/Category/Category.cpp @@ -1,291 +1,304 @@ /*************************************************************************** * Copyright © 2010 Jonathan Thomas * * * * 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 "Category.h" #include #include #include #include #include "libdiscover_debug.h" #include Category::Category(QSet pluginName, QObject* parent) : QObject(parent) , m_iconString(QStringLiteral("applications-other")) , m_plugins(std::move(pluginName)) {} Category::Category(const QString& name, const QString& iconName, const QVector >& orFilters, const QSet &pluginName, const QVector& subCategories, const QUrl& decoration, bool isAddons) : QObject(nullptr) , m_name(name) , m_iconString(iconName) , m_decoration(decoration) , m_orFilters(orFilters) , m_subCategories(subCategories) , m_plugins(pluginName) , m_isAddons(isAddons) { setObjectName(m_name); } Category::~Category() = default; void Category::parseData(const QString& path, const QDomNode& data) { for(QDomNode node = data.firstChild(); !node.isNull(); node = node.nextSibling()) { if(!node.isElement()) { if(!node.isComment()) qCWarning(LIBDISCOVER_LOG) << "unknown node found at " << QStringLiteral("%1:%2").arg(path).arg(node.lineNumber()); continue; } QDomElement tempElement = node.toElement(); if (tempElement.tagName() == QLatin1String("Name")) { m_name = i18nc("Category", tempElement.text().toUtf8().constData()); setObjectName(m_name); } else if (tempElement.tagName() == QLatin1String("Menu")) { m_subCategories << new Category(m_plugins, this); m_subCategories.last()->parseData(path, node); } else if (tempElement.tagName() == QLatin1String("Image")) { m_decoration = QUrl(tempElement.text()); if (m_decoration.isRelative()) { m_decoration = QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("discover/") + tempElement.text())); if (m_decoration.isEmpty()) qCWarning(LIBDISCOVER_LOG) << "couldn't find category decoration" << tempElement.text(); } } else if (tempElement.tagName() == QLatin1String("Addons")) { m_isAddons = true; } else if (tempElement.tagName() == QLatin1String("Icon") && tempElement.hasChildNodes()) { m_iconString = tempElement.text(); } else if (tempElement.tagName() == QLatin1String("Include")) { //previous muon format parseIncludes(tempElement); } else if (tempElement.tagName() == QLatin1String("Categories")) { //as provided by appstream parseIncludes(tempElement); } } } QVector > Category::parseIncludes(const QDomNode &data) { QDomNode node = data.firstChild(); QVector > filter; while(!node.isNull()) { QDomElement tempElement = node.toElement(); if (tempElement.tagName() == QLatin1String("And")) { // Parse children m_andFilters.append(parseIncludes(node)); } else if (tempElement.tagName() == QLatin1String("Or")) { m_orFilters.append(parseIncludes(node)); } else if (tempElement.tagName() == QLatin1String("Not")) { m_notFilters.append(parseIncludes(node)); } else if (tempElement.tagName() == QLatin1String("PkgSection")) { filter.append({ PkgSectionFilter, tempElement.text() }); } else if (tempElement.tagName() == QLatin1String("Category")) { filter.append({ CategoryFilter, tempElement.text() }); } else if (tempElement.tagName() == QLatin1String("PkgWildcard")) { filter.append({ PkgWildcardFilter, tempElement.text() }); } else if (tempElement.tagName() == QLatin1String("AppstreamIdWildcard")) { filter.append({ AppstreamIdWildcardFilter, tempElement.text() }); } else if (tempElement.tagName() == QLatin1String("PkgName")) { filter.append({ PkgNameFilter, tempElement.text() }); } else { qCWarning(LIBDISCOVER_LOG) << "unknown" << tempElement.tagName(); } node = node.nextSibling(); } return filter; } QString Category::name() const { return m_name; } void Category::setName(const QString& name) { m_name = name; Q_EMIT nameChanged(); } QString Category::icon() const { return m_iconString; } QVector > Category::andFilters() const { return m_andFilters; } void Category::setAndFilter(QVector > filters) { m_andFilters = filters; } QVector > Category::orFilters() const { return m_orFilters; } QVector > Category::notFilters() const { return m_notFilters; } QVector Category::subCategories() const { return m_subCategories; } bool Category::categoryLessThan(Category *c1, const Category *c2) { return (!c1->isAddons() && c2->isAddons()) || (c1->isAddons()==c2->isAddons() && QString::localeAwareCompare(c1->name(), c2->name()) < 0); } static bool isSorted(const QVector& vector) { Category *last = nullptr; for(auto a: vector) { if (last && !Category::categoryLessThan(last, a)) return false; last = a; } return true; } void Category::sortCategories(QVector& cats) { std::sort(cats.begin(), cats.end(), &categoryLessThan); for(auto cat: cats) { sortCategories(cat->m_subCategories); } Q_ASSERT(isSorted(cats)); } void Category::addSubcategory(QVector< Category* >& list, Category* newcat) { Q_ASSERT(isSorted(list)); auto it = std::lower_bound(list.begin(), list.end(), newcat, &categoryLessThan); if (it == list.end()) { list << newcat; return; } auto c = *it; if(c->name() == newcat->name()) { if(c->icon() != newcat->icon() || c->m_andFilters != newcat->m_andFilters || c->m_isAddons != newcat->m_isAddons ) { qCWarning(LIBDISCOVER_LOG) << "the following categories seem to be the same but they're not entirely" << c->icon() << newcat->icon() << "--" << c->name() << newcat->name() << "--" << c->andFilters() << newcat->andFilters() << "--" << c->isAddons() << newcat->isAddons(); } else { c->m_orFilters += newcat->orFilters(); c->m_notFilters += newcat->notFilters(); c->m_plugins.unite(newcat->m_plugins); Q_FOREACH (Category* nc, newcat->subCategories()) { addSubcategory(c->m_subCategories, nc); } return; } } list.insert(it, newcat); Q_ASSERT(isSorted(list)); } +void Category::addSubcategory(Category* cat) +{ + int i = 0; + for(Category* subCat : m_subCategories) { + if(!categoryLessThan(subCat, cat)) { + break; + } + ++i; + } + m_subCategories.insert(i, cat); + Q_ASSERT(isSorted(m_subCategories)); +} + bool Category::blacklistPluginsInVector(const QSet& pluginNames, QVector& subCategories) { bool ret = false; for(QVector::iterator it = subCategories.begin(); it!=subCategories.end(); ) { if ((*it)->blacklistPlugins(pluginNames)) { delete *it; it = subCategories.erase(it); ret = true; } else ++it; } return ret; } bool Category::blacklistPlugins(const QSet& pluginNames) { if (m_plugins.subtract(pluginNames).isEmpty()) { return true; } if (blacklistPluginsInVector(pluginNames, m_subCategories)) Q_EMIT subCategoriesChanged(); return false; } QUrl Category::decoration() const { if (m_decoration.isEmpty()) { Category* c = qobject_cast(parent()); return c ? c->decoration() : QUrl(); } else { Q_ASSERT(!m_decoration.isLocalFile() || QFile::exists(m_decoration.toLocalFile())); return m_decoration; } } QVariantList Category::subCategoriesVariant() const { return kTransform(m_subCategories, [](Category* cat){ return QVariant::fromValue(cat); }); } bool Category::matchesCategoryName(const QString& name) const { for(const auto &filter: m_orFilters) { if (filter.first == CategoryFilter && filter.second == name) return true; } return false; } bool Category::contains(Category* cat) const { const bool ret = cat == this || (cat && contains(qobject_cast(cat->parent()))); return ret; } bool Category::contains(const QVariantList& cats) const { bool ret = false; for(const auto &itCat : cats) { if (contains(qobject_cast(itCat.value()))) { ret = true; break; } } return ret; } diff --git a/libdiscover/Category/Category.h b/libdiscover/Category/Category.h index 21d74126..eabfdd5e 100644 --- a/libdiscover/Category/Category.h +++ b/libdiscover/Category/Category.h @@ -1,101 +1,107 @@ /*************************************************************************** * Copyright © 2010 Jonathan Thomas * * * * 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 CATEGORY_H #define CATEGORY_H #include #include #include #include #include #include "discovercommon_export.h" class QDomNode; enum FilterType { InvalidFilter, CategoryFilter, PkgSectionFilter, PkgWildcardFilter, PkgNameFilter, AppstreamIdWildcardFilter }; class DISCOVERCOMMON_EXPORT Category : public QObject { Q_OBJECT public: Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString icon READ icon CONSTANT) Q_PROPERTY(QObject* parent READ parent CONSTANT) Q_PROPERTY(QUrl decoration READ decoration CONSTANT) Q_PROPERTY(QVariantList subcategories READ subCategoriesVariant NOTIFY subCategoriesChanged) explicit Category(QSet pluginNames, QObject* parent = nullptr); Category(const QString& name, const QString& iconName, const QVector< QPair< FilterType, QString > >& orFilters, const QSet &pluginName, const QVector& subCategories, const QUrl& decoration, bool isAddons); ~Category() override; QString name() const; // You should never attempt to change the name of anything that is not a leaf category // as the results could be potentially detremental to the function of the category filters void setName(const QString& name); QString icon() const; QVector > andFilters() const; void setAndFilter(QVector > filters); QVector > orFilters() const; QVector > notFilters() const; QVector subCategories() const; QVariantList subCategoriesVariant() const; static void sortCategories(QVector& cats); static void addSubcategory(QVector& cats, Category* cat); + /** + * Add a subcategory to this category. This function should only + * be used during the initialisation stage, before adding the local + * root category to the global root category model. + */ + void addSubcategory(Category* cat); void parseData(const QString& path, const QDomNode& data); bool blacklistPlugins(const QSet& pluginName); bool isAddons() const { return m_isAddons; } QUrl decoration() const; bool matchesCategoryName(const QString &name) const; Q_SCRIPTABLE bool contains(Category* cat) const; Q_SCRIPTABLE bool contains(const QVariantList &cats) const; static bool categoryLessThan(Category *c1, const Category *c2); static bool blacklistPluginsInVector(const QSet& pluginNames, QVector& subCategories); Q_SIGNALS: void subCategoriesChanged(); void nameChanged(); private: QString m_name; QString m_iconString; QUrl m_decoration; QVector > m_andFilters; QVector > m_orFilters; QVector > m_notFilters; QVector m_subCategories; QVector > parseIncludes(const QDomNode &data); QSet m_plugins; bool m_isAddons = false; }; #endif diff --git a/libdiscover/backends/KNSBackend/KNSBackend.cpp b/libdiscover/backends/KNSBackend/KNSBackend.cpp index e8cf1e96..1892a601 100644 --- a/libdiscover/backends/KNSBackend/KNSBackend.cpp +++ b/libdiscover/backends/KNSBackend/KNSBackend.cpp @@ -1,584 +1,595 @@ /*************************************************************************** * 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; #if KNEWSTUFFCORE_VERSION_MAJOR==5 && KNEWSTUFFCORE_VERSION_MINOR>=57 QStringList locations = KNSCore::Engine::configSearchLocations(); #else QStringList locations = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation); #endif for (const QString &path: locations) { QDirIterator dirIt(path, {QStringLiteral("*.knsrc")}, QDir::Files); for(; dirIt.hasNext(); ) { dirIt.next(); auto bk = new KNSBackend(parent, QStringLiteral("plasma"), dirIt.filePath()); if (bk->isValid()) ret += bk; else delete bk; } } return ret; } }; Q_DECLARE_METATYPE(KNSCore::EntryInternal) KNSBackend::KNSBackend(QObject* parent, const QString& iconName, const QString &knsrc) : AbstractResourcesBackend(parent) , m_fetching(false) , m_isValid(true) , 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); // This ensures we have something to track when checking after the initialization timeout connect(this, &KNSBackend::initialized, this, [this](){ m_initialized = true; }); // If we have not initialized in 60 seconds, consider this KNS backend invalid QTimer::singleShot(60000, this, [this](){ if(!m_initialized) { markInvalid(i18n("Backend %1 took too long to initialize", m_displayName)); m_responsePending = false; Q_EMIT searchFinished(); Q_EMIT availableForQueries(); } }); const QVector> filters = { {CategoryFilter, fileName } }; const QSet backendName = { name() }; m_displayName = group.readEntry("Name", QString()); if (m_displayName.isEmpty()) { m_displayName = fileName.mid(0, fileName.indexOf(QLatin1Char('.'))); m_displayName[0] = m_displayName[0].toUpper(); } const QStringList cats = group.readEntry("Categories", QStringList{}); QVector categories; if (cats.count() > 1) { m_categories += cats; for(const auto &cat: cats) categories << new Category(cat, {}, { {CategoryFilter, cat } }, backendName, {}, {}, true); } + QVector topCategories{categories}; + for (const auto &cat: categories) { + const QString catName = cat->name().append(QLatin1Char('/')); + for (const auto& potentialSubCat: categories) { + if(potentialSubCat->name().startsWith(catName)) { + cat->addSubcategory(potentialSubCat); + topCategories.removeOne(potentialSubCat); + } + } + } + m_engine = new KNSCore::Engine(this); connect(m_engine, &KNSCore::Engine::signalErrorCode, this, &KNSBackend::signalErrorCode); connect(m_engine, &KNSCore::Engine::signalEntriesLoaded, this, &KNSBackend::receivedEntries, Qt::QueuedConnection); connect(m_engine, &KNSCore::Engine::signalEntryChanged, this, &KNSBackend::statusChanged, Qt::QueuedConnection); connect(m_engine, &KNSCore::Engine::signalEntryDetailsLoaded, this, &KNSBackend::statusChanged); connect(m_engine, &KNSCore::Engine::signalProvidersLoaded, this, &KNSBackend::fetchInstalled); connect(m_engine, &KNSCore::Engine::signalCategoriesMetadataLoded, this, [categories](const QList< KNSCore::Provider::CategoryMetadata>& categoryMetadatas){ for (const KNSCore::Provider::CategoryMetadata& category : categoryMetadatas) { for (Category* cat : categories) { if (cat->orFilters().count() > 0 && cat->orFilters().first().second == category.name) { cat->setName(category.displayName); break; } } } }); m_engine->setPageSize(100); m_engine->init(m_name); static const QString knsrcApplications = QLatin1String("storekdeapps.knsrc"); if(knsrcApplications == fileName) { m_hasApplications = true; auto actualCategory = new Category(m_displayName, QStringLiteral("plasma"), filters, backendName, categories, QUrl(), false); auto applicationCategory = new Category(i18n("Applications"), QStringLiteral("applications-internet"), filters, backendName, { actualCategory }, QUrl(), false); applicationCategory->setAndFilter({ {CategoryFilter, QLatin1String("Application")} }); m_categories.append(applicationCategory->name()); m_rootCategories = { applicationCategory }; // Make sure we filter out any apps which won't run on the current system architecture QStringList tagFilter = m_engine->tagFilter(); if(QSysInfo::currentCpuArchitecture() == QLatin1String("arm")) { tagFilter << QLatin1String("application##architecture=armhf"); } else if(QSysInfo::currentCpuArchitecture() == QLatin1String("arm64")) { tagFilter << QLatin1String("application##architecture=arm64"); } else if(QSysInfo::currentCpuArchitecture() == QLatin1String("i386")) { tagFilter << QLatin1String("application##architecture=x86"); } else if(QSysInfo::currentCpuArchitecture() == QLatin1String("ia64")) { tagFilter << QLatin1String("application##architecture=x86-64"); } else if(QSysInfo::currentCpuArchitecture() == QLatin1String("x86_64")) { tagFilter << QLatin1String("application##architecture=x86"); tagFilter << QLatin1String("application##architecture=x86-64"); } m_engine->setTagFilter(tagFilter); } else { 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 QStringLiteral("comic.knsrc"), QStringLiteral("colorschemes.knsrc"), QStringLiteral("emoticons.knsrc"), QStringLiteral("plymouth.knsrc"), QStringLiteral("sddmtheme.knsrc"), QStringLiteral("wallpaperplugin.knsrc") }; auto actualCategory = new Category(m_displayName, QStringLiteral("plasma"), filters, backendName, categories, QUrl(), true); const auto topLevelName = knsrcPlasma.contains(fileName)? i18n("Plasma Addons") : i18n("Application Addons"); auto addonsCategory = new Category(topLevelName, QStringLiteral("plasma"), filters, backendName, {actualCategory}, QUrl(), true); m_rootCategories = { addonsCategory }; } } KNSBackend::~KNSBackend() { qDeleteAll(m_rootCategories); } void KNSBackend::markInvalid(const QString &message) { m_rootCategories.clear(); qWarning() << "invalid kns backend!" << m_name << "because:" << message; m_isValid = false; setFetching(false); Q_EMIT initialized(); } void KNSBackend::fetchInstalled() { auto search = new OneTimeAction([this]() { Q_EMIT startingSearch(); m_onePage = true; m_responsePending = true; m_engine->checkForInstalled(); }, this); if (m_responsePending) { connect(this, &KNSBackend::availableForQueries, search, &OneTimeAction::trigger, Qt::QueuedConnection); } else { search->trigger(); } } void KNSBackend::setFetching(bool f) { if(m_fetching!=f) { m_fetching = f; emit fetchingChanged(); if (!m_fetching) { Q_EMIT initialized(); } } } 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) { QStringList categories{name(), m_rootCategories.first()->name()}; const auto cats = m_engine->categoriesMetadata(); const int catIndex = kIndexOf(cats, [&entry](const KNSCore::Provider::CategoryMetadata& cat){ return entry.category() == cat.id; }); if (catIndex > -1) { categories << cats.at(catIndex).name; } if(m_hasApplications) { categories << QLatin1String("Application"); } r = new KNSResource(entry, 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 filtered = kFilter(entries, [](const KNSCore::EntryInternal& entry){ return entry.isValid(); }); const auto resources = kTransform>(filtered, [this](const KNSCore::EntryInternal& entry){ return resourceForEntry(entry); }); if (!resources.isEmpty()) { Q_EMIT receivedResources(resources); } else { Q_EMIT searchFinished(); Q_EMIT availableForQueries(); setFetching(false); return; } // qDebug() << "received" << objectName() << this << m_resourcesByName.count(); if (m_onePage) { Q_EMIT availableForQueries(); setFetching(false); } } void KNSBackend::fetchMore() { if (m_responsePending) return; // 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->requestMoreData(); } void KNSBackend::statusChanged(const KNSCore::EntryInternal& entry) { resourceForEntry(entry); } void KNSBackend::signalErrorCode(const KNSCore::ErrorCode& errorCode, const QString& message, const QVariant& metadata) { QString error = message; qDebug() << "KNS error in" << m_displayName << ":" << errorCode << message << metadata; bool invalidFile = false; switch(errorCode) { case KNSCore::ErrorCode::UnknownError: // This is not supposed to be hit, of course, but any error coming to this point should be non-critical and safely ignored. break; case KNSCore::ErrorCode::NetworkError: // If we have a network error, we need to tell the user about it. This is almost always fatal, so mark invalid and tell the user. error = i18n("Network error in backend %1: %2", m_displayName, metadata.toInt()); markInvalid(error); invalidFile = true; break; case KNSCore::ErrorCode::OcsError: if(metadata.toInt() == 200) { // Too many requests, try again in a couple of minutes - perhaps we can simply postpone it automatically, and give a message? error = i18n("Too many requests sent to the server for backend %1. Please try again in a few minutes.", m_displayName); } else { // Unknown API error, usually something critical, mark as invalid and cry a lot error = i18n("Invalid %1 backend, contact your distributor.", m_displayName); markInvalid(error); invalidFile = true; } break; case KNSCore::ErrorCode::ConfigFileError: error = i18n("Invalid %1 backend, contact your distributor.", m_displayName); markInvalid(error); invalidFile = true; break; case KNSCore::ErrorCode::ProviderError: error = i18n("Invalid %1 backend, contact your distributor.", m_displayName); markInvalid(error); invalidFile = true; break; case KNSCore::ErrorCode::InstallationError: // This error is handled already, by forwarding the KNS engine's installer error message. break; case KNSCore::ErrorCode::ImageError: // Image fetching errors are not critical as such, but may lead to weird layout issues, might want handling... error = i18n("Could not fetch screenshot for the entry %1 in backend %2", metadata.toList().at(0).toString(), m_displayName); break; default: // Having handled all current error values, we should by all rights never arrive here, but for good order and future safety... error = i18n("Unhandled error in %1 backend. Contact your distributor.", m_displayName); break; } m_responsePending = false; Q_EMIT searchFinished(); Q_EMIT availableForQueries(); // Setting setFetching to false when we get an error ensures we don't end up in an eternally-fetching state this->setFetching(false); qWarning() << "kns error" << objectName() << error; if (!invalidFile) Q_EMIT passiveMessage(i18n("%1: %2", name(), error)); } class KNSTransaction : public Transaction { public: KNSTransaction(QObject* parent, KNSResource* res, Transaction::Role role) : Transaction(parent, res, role) , m_id(res->entry().uniqueId()) { setCancellable(false); auto manager = res->knsBackend()->engine(); connect(manager, &KNSCore::Engine::signalEntryChanged, this, &KNSTransaction::anEntryChanged); TransactionModel::global()->addTransaction(this); std::function actionFunction; auto engine = res->knsBackend()->engine(); if(role == RemoveRole) actionFunction = [res, engine]() { engine->uninstall(res->entry()); }; else if (res->linkIds().isEmpty()) actionFunction = [res, engine]() { engine->install(res->entry()); }; else actionFunction = [res, engine]() { const auto links = res->linkIds(); for(auto i : links) engine->install(res->entry(), i); }; QTimer::singleShot(0, res, actionFunction); } 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); } break; } } } void cancel() override {} private: const QString m_id; }; Transaction* KNSBackend::removeApplication(AbstractResource* app) { auto res = qobject_cast(app); return new KNSTransaction(this, res, Transaction::RemoveRole); } Transaction* KNSBackend::installApplication(AbstractResource* app) { auto res = qobject_cast(app); return new KNSTransaction(this, res, Transaction::InstallRole); } Transaction* KNSBackend::installApplication(AbstractResource* app, const AddonList& /*addons*/) { return 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 (!m_isValid || (!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) { auto stream = new ResultsStream(QStringLiteral("KNS-installed")); const auto start = [this, stream, filter]() { if (m_isValid) { auto filterFunction = [&filter](AbstractResource* r) { return r->state()>=filter.state && (r->name().contains(filter.search, Qt::CaseInsensitive) || r->comment().contains(filter.search, Qt::CaseInsensitive)); }; const auto ret = kFilter>(m_resourcesByName, filterFunction); if (!ret.isEmpty()) Q_EMIT stream->resourcesFound(ret); } stream->finish(); }; if (isFetching()) { connect(this, &KNSBackend::initialized, stream, start); } else { QTimer::singleShot(0, stream, start); } return stream; } else if ((m_hasApplications && !filter.category) // If there is no category defined, we are searching in the root, and should include only application results // If there /is/ a category, make sure we actually are one of those requested before searching || (filter.category && kContains(m_categories, [&filter](const QString& cat) { return filter.category->matchesCategoryName(cat); }))) { auto r = new ResultsStream(QStringLiteral("KNS-search-")+name()); searchStream(r, filter.search); return r; } return voidStream(); } void KNSBackend::searchStream(ResultsStream* stream, const QString &searchText) { Q_EMIT startingSearch(); auto start = [this, stream, searchText]() { Q_ASSERT(!isFetching()); if (!m_isValid) { stream->finish(); return; } // No need to explicitly launch a search, setting the search term already does that for us m_engine->setSearchTerm(searchText); m_onePage = false; m_responsePending = true; connect(stream, &ResultsStream::fetchMore, this, &KNSBackend::fetchMore); 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 if (isFetching()) { connect(this, &KNSBackend::initialized, stream, start); } else { QTimer::singleShot(0, stream, start); } } 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) { Q_EMIT 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); m_onePage = false; 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()) { Q_EMIT stream->resourcesFound({resourceForEntry(entry)}); } else qWarning() << "found invalid" << entryid << entry.uniqueId() << providerid << QUrl(entry.providerId()).host(); 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"