diff --git a/autotests/knewstuffentrytest.cpp b/autotests/knewstuffentrytest.cpp --- a/autotests/knewstuffentrytest.cpp +++ b/autotests/knewstuffentrytest.cpp @@ -44,6 +44,7 @@ "https://testpreview" "http://testpayload" "" "" "installed" "" "" +"ghns_exclude=1" ""; const QString name = QStringLiteral("Name"); diff --git a/src/attica/atticaprovider.cpp b/src/attica/atticaprovider.cpp --- a/src/attica/atticaprovider.cpp +++ b/src/attica/atticaprovider.cpp @@ -18,6 +18,7 @@ #include "atticaprovider_p.h" #include "question.h" +#include "tagsfilterchecker.h" #include #include @@ -270,9 +271,29 @@ Content::List contents = listJob->itemList(); EntryInternal::List entries; + TagsFilterChecker checker(tagFilter()); + TagsFilterChecker downloadschecker(downloadTagFilter()); Q_FOREACH (const Content &content, contents) { - mCachedContent.insert(content.id(), content); - entries.append(entryFromAtticaContent(content)); + if (checker.filterAccepts(content.tags())) { + bool filterAcceptsDownloads = true; + if (content.downloads() > 0) { + filterAcceptsDownloads = false; + for (const Attica::DownloadDescription &dli : content.downloadUrlDescriptions()) { + if (downloadschecker.filterAccepts(dli.tags())) { + filterAcceptsDownloads = true; + break; + } + } + } + if (filterAcceptsDownloads) { + mCachedContent.insert(content.id(), content); + entries.append(entryFromAtticaContent(content)); + } else { + qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on download filter" << downloadTagFilter(); + } + } else { + qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on entry filter" << tagFilter(); + } } qCDebug(KNEWSTUFFCORE) << "loaded: " << mCurrentRequest.hashForRequest() << " count: " << entries.size(); @@ -482,6 +503,7 @@ entry.setSummary(content.description()); entry.setShortSummary(content.summary()); entry.setChangelog(content.changelog()); + entry.setTags(content.tags()); entry.clearDownloadLinkInformation(); QList descs = content.downloadUrlDescriptions(); @@ -494,6 +516,7 @@ info.id = desc.id(); info.size = desc.size(); info.isDownloadtypeLink = desc.type() == Attica::DownloadDescription::LinkDownload; + info.tags = desc.tags(); entry.appendDownloadLinkInformation(info); } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -9,6 +9,7 @@ itemsmodel.cpp provider.cpp security.cpp + tagsfilterchecker.cpp xmlloader.cpp # A system by which queries can be passed to the user, and responses @@ -77,6 +78,7 @@ QuestionListener QuestionManager Security + TagsFilterChecker XmlLoader REQUIRED_HEADERS KNewStuffCore_HEADERS diff --git a/src/core/engine.h b/src/core/engine.h --- a/src/core/engine.h +++ b/src/core/engine.h @@ -179,6 +179,102 @@ void requestMoreData(); void requestData(int page, int pageSize); + /** + * Set a filter for results, which filters out all entries which do not match + * the filter, as applied to the tags for the entry. This filters only on the + * tags specified for the entry itself. To filter the downloadlinks, use + * setDownloadTagFilter(QStringList). + * + * @note The default filter if one is not set from your knsrc file will filter + * out entries marked as ghns_exclude=1. To retain this when setting a custom + * filter, add "ghns_exclude!=1" as one of the filters. + * + * @note Some tags provided by OCS do not supply a value (and are simply passed + * as a key). These will be interpreted as having the value 1 for filtering + * purposes. An example of this might be ghns_exclude, which in reality will + * generally be passed through ocs as "ghns_exclude" rather than "ghns_exclude=1" + * + * == Examples of use == + * Value for tag "tagname" must be exactly "tagdata": + * tagname==tagdata + * + * Value for tag "tagname" must be different from "tagdata": + * tagname!=tagdata + * + * == KNSRC entry == + * A tag filter line in a .knsrc file, which is a comma semarated list of + * tag/value pairs, might look like: + * + * TagFilter=ghns_exclude!=1,data##mimetype==application/cbr+zip,data##mimetype==application/cbr+rar + * which would honour the exclusion and filter out anything that does not + * include a comic book archive in either zip or rar format in one or more + * of the download items. + * Notice in particular that there are two data##mimetype entries. Use this + * for when a tag may have multiple values. + * + * TagFilter=application##architecture==x86_64 + * which would not honour the exclusion, and would filter out all entries + * which do not mark themselves as having a 64bit application binary in at + * least one download item. + * + * The value does not current suppport wildcards. The list should be considered + * a binary AND operation (that is, all filter entries must match for the data + * entry to be included in the return data) + * + * @param filter The filter in the form of a list of strings + * @see setDownloadTagFilter(QStringList) + * @since 5.51 + */ + void setTagFilter(const QStringList &filter); + /** + * Gets the current tag filter list + * @see setTagFilter(QStringList) + * @since 5.51 + */ + QStringList tagFilter() const; + /** + * Add a single filter entry to the entry tag filter. The filter should be in + * the same form as the filter lines in the list used by setTagFilter(QStringList) + * @param filter The filter in the form of a string + * @see setTagFilter(QStringList) + * @since 5.51 + */ + void addTagFilter(const QString &filter); + /** + * Sets a filter to be applied to the downloads for an entry. The logic is the + * same as used in setTagFilter(QStringList), but vitally, only one downloadlink + * is required to match the filter for the list to be valid. If you do not wish + * to show the others in your client, you must hide them yourself. + * + * For an entry to be accepted when a download tag filter is set, it must also + * be accepted by the entry filter (so, for example, while a list of downloads + * might be accepted, if the entry has ghns_exclude set, and the default entry + * filter is set, the entry will still be filtered out). + * + * In your knsrc file, set DownloadTagFilter to the filter you wish to apply, + * using the same logic as described for the entry tagfilter. + * + * @param filter The filter in the form of a list of strings + * @see setTagFilter(QStringList) + * @since 5.51 + */ + void setDownloadTagFilter(const QStringList &filter); + /** + * Gets the current downloadlink tag filter list + * @see setDownloadTagFilter(QStringList) + * @since 5.51 + */ + QStringList downloadTagFilter() const; + /** + * Add a single filter entry to the download tag filter. The filter should be in + * the same form as the filter lines in the list used by setDownloadsTagFilter(QStringList) + * @param filter The filter in the form of a string + * @see setTagFilter(QStringList) + * @see setDownloadTagFilter(QStringList) + * @since 5.51 + */ + void addDownloadTagFilter(const QString &filter); + /** * Request for packages that are installed and need update * diff --git a/src/core/engine.cpp b/src/core/engine.cpp --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -61,6 +61,8 @@ public: QList categoriesMetadata; Attica::ProviderManager *m_atticaProviderManager = nullptr; + QStringList tagFilter; + QStringList downloadTagFilter; }; Engine::Engine(QObject *parent) @@ -126,6 +128,12 @@ qCDebug(KNEWSTUFFCORE) << "Categories: " << m_categories; m_providerFileUrl = group.readEntry("ProvidersUrl", QString()); + d->tagFilter = group.readEntry("TagFilter", QStringList()); + if (d->tagFilter.isEmpty()) { + d->tagFilter.append(QStringLiteral("ghns_exclude!=1")); + } + d->downloadTagFilter = group.readEntry("DownloadTagFilter", QStringList()); + const QString configFileName = QFileInfo(QDir::isAbsolutePath(configfile) ? configfile : QStandardPaths::locate(QStandardPaths::GenericConfigLocation, configfile)).baseName(); // let installation read install specific config if (!m_installation->readConfig(group)) { @@ -252,6 +260,8 @@ { qCDebug(KNEWSTUFFCORE) << "Engine addProvider called with provider with id " << provider->id(); m_providers.insert(provider->id(), provider); + provider->setTagFilter(d->tagFilter); + provider->setDownloadTagFilter(d->downloadTagFilter); connect(provider.data(), &Provider::providerInitialized, this, &Engine::providerInitialized); connect(provider.data(), &Provider::loadingFinished, this, &Engine::slotEntriesLoaded); connect(provider.data(), &Provider::entryDetailsLoaded, this, &Engine::slotEntryDetailsLoaded); @@ -395,6 +405,48 @@ } } +void Engine::setTagFilter(const QStringList &filter) +{ + d->tagFilter = filter; + foreach (const QSharedPointer &p, m_providers) { + p->setTagFilter(d->tagFilter); + } +} + +QStringList Engine::tagFilter() const +{ + return d->tagFilter; +} + +void KNSCore::Engine::addTagFilter(const QString &filter) +{ + d->tagFilter << filter; + foreach (const QSharedPointer &p, m_providers) { + p->setTagFilter(d->tagFilter); + } +} + +void Engine::setDownloadTagFilter(const QStringList &filter) +{ + d->downloadTagFilter = filter; + foreach (const QSharedPointer &p, m_providers) { + p->setDownloadTagFilter(d->downloadTagFilter); + } +} + +QStringList Engine::downloadTagFilter() const +{ + return d->downloadTagFilter; +} + +void Engine::addDownloadTagFilter(const QString &filter) +{ + d->downloadTagFilter << filter; + foreach (const QSharedPointer &p, m_providers) { + p->setDownloadTagFilter(d->downloadTagFilter); + } +} + void Engine::slotSearchTimerExpired() { reloadEntries(); diff --git a/src/core/entryinternal.h b/src/core/entryinternal.h --- a/src/core/entryinternal.h +++ b/src/core/entryinternal.h @@ -89,6 +89,7 @@ int id; bool isDownloadtypeLink; quint64 size = 0; + QStringList tags; }; /** @@ -440,6 +441,22 @@ */ void setDonationLink(const QString &link); + /** + * The set of tags assigned specifically to this content item. This does not include + * tags for the download links. To get those, you must concatenate the lists yourself. + * @see downloadLinkInformationList() + * @see DownloadLinkInformation + * @see Engine::setTagFilter(QStringList) + * @since 5.51 + */ + QStringList tags() const; + /** + * Set the tags for the content item. + * @param tags A string list containing the tags for this entry + * @since 5.51 + */ + void setTags(const QStringList &tags); + /** The id of the provider this entry belongs to */ diff --git a/src/core/entryinternal.cpp b/src/core/entryinternal.cpp --- a/src/core/entryinternal.cpp +++ b/src/core/entryinternal.cpp @@ -79,6 +79,7 @@ QString mProviderId; QStringList mUnInstalledFiles; QString mDonationLink; + QStringList mTags; QString mChecksum; QString mSignature; @@ -156,6 +157,16 @@ d->mProviderId = id; } +QStringList KNSCore::EntryInternal::tags() const +{ + return d->mTags; +} + +void KNSCore::EntryInternal::setTags(const QStringList &tags) +{ + d->mTags = tags; +} + QString EntryInternal::category() const { return d->mCategory; @@ -481,7 +492,7 @@ bool KNSCore::EntryInternal::setEntryXML(QXmlStreamReader& reader) { if (reader.name() != QLatin1String("stuff")) { - qWarning() << "Parsing Entry from invalid XML"; + qCWarning(KNEWSTUFFCORE) << "Parsing Entry from invalid XML. Reader tag name was expected to be \"stuff\", but was found as:" << reader.name(); return false; } @@ -540,6 +551,8 @@ d->mInstalledFiles.append(reader.readElementText(QXmlStreamReader::SkipChildElements)); } else if (reader.name() == QLatin1String("id")) { d->mUniqueId = reader.readElementText(QXmlStreamReader::SkipChildElements); + } else if (reader.name() == QLatin1String("tags")) { + d->mTags = reader.readElementText(QXmlStreamReader::SkipChildElements).split(QChar(',')); } else if (reader.name() == QLatin1String("status")) { const auto statusText = readText(&reader); if (statusText == QLatin1String("installed")) { @@ -551,7 +564,7 @@ if (reader.tokenType() == QXmlStreamReader::Characters) readNextSkipComments(&reader); } - Q_ASSERT(reader.tokenType() == QXmlStreamReader::EndElement); + Q_ASSERT_X(reader.tokenType() == QXmlStreamReader::EndElement, Q_FUNC_INFO, QString("token name was %1 and the type was %2").arg(reader.name().toString()).arg(reader.tokenString()).toLocal8Bit().data()); } // Validation @@ -633,6 +646,8 @@ d->mInstalledFiles.append(e.text()); } else if (e.tagName() == QLatin1String("id")) { d->mUniqueId = e.text(); + } else if (e.tagName() == QLatin1String("tags")) { + d->mTags = e.text().split(QChar(',')); } else if (e.tagName() == QLatin1String("status")) { QString statusText = e.text(); if (statusText == QLatin1String("installed")) { @@ -724,6 +739,7 @@ e = addElement(doc, el, QStringLiteral("preview"), d->mPreviewUrl[PreviewSmall1]); e = addElement(doc, el, QStringLiteral("previewBig"), d->mPreviewUrl[PreviewBig1]); e = addElement(doc, el, QStringLiteral("payload"), d->mPayload); + e = addElement(doc, el, QStringLiteral("tags"), d->mTags.join(QChar(','))); if (d->mStatus == KNS3::Entry::Installed) { (void)addElement(doc, el, QStringLiteral("status"), QStringLiteral("installed")); diff --git a/src/core/provider.h b/src/core/provider.h --- a/src/core/provider.h +++ b/src/core/provider.h @@ -162,6 +162,35 @@ Q_UNUSED(entry) } + /** + * Set the tag filter used for entries by this provider + * @param tagFilter The new list of filters + * @see Engine::setTagFilter(QStringList) + * @since 5.51 + */ + void setTagFilter(const QStringList &tagFilter); + /** + * The tag filter used for downloads by this provider + * @return The list of filters + * @see Engine::setTagFilter(QStringList) + * @since 5.51 + */ + QStringList tagFilter() const; + /** + * Set the tag filter used for download items by this provider + * @param downloadTagFilter The new list of filters + * @see Engine::setDownloadTagFilter(QStringList) + * @since 5.51 + */ + void setDownloadTagFilter(const QStringList &downloadTagFilter); + /** + * The tag filter used for downloads by this provider + * @return The list of filters + * @see Engine::setDownloadTagFilter(QStringList) + * @since 5.51 + */ + QStringList downloadTagFilter() const; + Q_SIGNALS: void providerInitialized(KNSCore::Provider *); diff --git a/src/core/provider.cpp b/src/core/provider.cpp --- a/src/core/provider.cpp +++ b/src/core/provider.cpp @@ -28,6 +28,32 @@ namespace KNSCore { +// BCI: Add a real d-pointer +class ProviderPrivate { +public: + QStringList tagFilter; + QStringList downloadTagFilter; +}; +typedef QHash ProviderPrivateHash; +Q_GLOBAL_STATIC(ProviderPrivateHash, d_func) + +static ProviderPrivate *d(const Provider *provider) +{ + ProviderPrivate *ret = d_func()->value(provider); + if (!ret) { + ret = new ProviderPrivate; + d_func()->insert(provider, ret); + } + return ret; +} + +static void delete_d(const Provider *provider) +{ + if (auto d = d_func()) { + delete d->take(provider); + } +} + QString Provider::SearchRequest::hashForRequest() const { return QString(QString::number((int)sortMode) + QLatin1Char(',') @@ -41,7 +67,9 @@ {} Provider::~Provider() -{} +{ + delete_d(this); +} QString Provider::name() const { @@ -53,6 +81,26 @@ return mIcon; } +void Provider::setTagFilter(const QStringList &tagFilter) +{ + d(this)->tagFilter = tagFilter; +} + +QStringList Provider::tagFilter() const +{ + return d(this)->tagFilter; +} + +void Provider::setDownloadTagFilter(const QStringList &downloadTagFilter) +{ + d(this)->downloadTagFilter = downloadTagFilter; +} + +QStringList Provider::downloadTagFilter() const +{ + return d(this)->downloadTagFilter; +} + QDebug operator<<(QDebug dbg, const Provider::SearchRequest & search) { QDebugStateSaver saver(dbg); diff --git a/src/core/tagsfilterchecker.h b/src/core/tagsfilterchecker.h new file mode 100644 --- /dev/null +++ b/src/core/tagsfilterchecker.h @@ -0,0 +1,83 @@ +/* + Copyright (c) 2018 Dan Leinir Turthra Jensen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef KNSCORE_TAGSFILTERCHECKER_H +#define KNSCORE_TAGSFILTERCHECKER_H + +#include +#include "knewstuffcore_export.h" + +namespace KNSCore { + +/** + * @brief Apply simple filtering logic to a list of tags + * + * == Examples of specifying tag filters == + * Value for tag "tagname" must be exactly "tagdata": + * tagname==tagdata + * + * Value for tag "tagname" must be different from "tagdata": + * tagname!=tagdata + * + * == Tag filter list == + * A tag filter list is a string list of filters as shown above, and a combination + * of which might look like: + * + * - ghns_exclude!=1 + * - data##mimetype==application/cbr+zip + * - data##mimetype==application/cbr+rar + * + * which would filter out anything which has ghns_exclude set to 1, and + * anything where the value of data##mimetype does not equal either + * "application/cbr+zip" or "application/cbr+rar". + * Notice in particular the two data##mimetype entries. Use this + * for when a tag may have multiple values. + * + * The value does not current suppport wildcards. The list should be considered + * a binary AND operation (that is, all filter entries must match for the data + * entry to be included in the return data) + * @since 5.51 + */ +class KNEWSTUFFCORE_EXPORT TagsFilterChecker { +public: + /** + * Constructs an instance of the tags filter checker, prepopulated + * with the list of tag filters in the tagFilter parameter. + * + * @param tagFilter The list of tag filters + * @since 5.51 + */ + TagsFilterChecker(const QStringList &tagFilter); + ~TagsFilterChecker(); + + /** + * Check whether the filter list accepts the passed list of tags + * + * @param tags A list of tags in the form of key=value strings + * @return True if the filter accepts the list, false if not + * @since 5.51 + */ + bool filterAccepts(const QStringList &tags); + +private: + class Private; + Private *d; +}; + +} + +#endif//KNSCORE_TAGSFILTERCHECKER_H diff --git a/src/core/tagsfilterchecker.cpp b/src/core/tagsfilterchecker.cpp new file mode 100644 --- /dev/null +++ b/src/core/tagsfilterchecker.cpp @@ -0,0 +1,168 @@ +/* + Copyright (c) 2018 Dan Leinir Turthra Jensen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "tagsfilterchecker.h" + +#include + +#include + +namespace KNSCore +{ + +class TagsFilterChecker::Private +{ +public: + Private() {} + ~Private() + { + qDeleteAll(validators); + } + class Validator; + // If people start using a LOT of validators (>20ish), we can always change it, but + // for now it seems reasonable that QMap is better than QHash here... + QMap validators; + + class Validator + { + public: + Validator(const QString &tag, const QString &value) + { + m_tag = tag; + if (!value.isNull()) { + m_acceptedValues << value; + } + } + virtual ~Validator() {}; + virtual bool filterAccepts(const QString &tag, const QString &value) = 0; + protected: + friend class TagsFilterChecker::Private; + QString m_tag; + QStringList m_acceptedValues; + }; + + // Will only accept entries which have one of the accepted values set for the tag key + class EqualityValidator : public Validator + { + public: + EqualityValidator(const QString &tag, const QString &value) + : Validator(tag, value) + {} + ~EqualityValidator() override {} + bool filterAccepts(const QString &tag, const QString &value) override + { + bool result = true; + if (tag == m_tag && !m_acceptedValues.contains(value)) { + result = false; + } + return result; + } + }; + + // Will only accept entries which have none of the values set for the tag key + class InequalityValidator : public Validator + { + public: + InequalityValidator(const QString &tag, const QString &value) + : Validator(tag, value) + {} + ~InequalityValidator() override {} + bool filterAccepts(const QString &tag, const QString &value) override + { + bool result = true; + if (tag == m_tag && m_acceptedValues.contains(value)) { + result = false; + } + return result; + } + }; + + void addValidator(const QString &filter) + { + int pos = 0; + if ((pos = filter.indexOf(QStringLiteral("=="))) > -1) { + QString tag = filter.left(pos); + QString value = filter.mid(tag.length() + 2); + Validator *val = validators.value(tag, nullptr); + if (!val) { + val = new EqualityValidator(tag, QString()); + validators.insert(tag, val); + } + val->m_acceptedValues << value; + qCDebug(KNEWSTUFFCORE) << "Created EqualityValidator for tag" << tag << "with value" << value; + } else if ((pos = filter.indexOf(QStringLiteral("!="))) > -1) { + QString tag = filter.left(pos); + QString value = filter.mid(tag.length() + 2); + Validator *val = validators.value(tag, nullptr); + if (!val) { + val = new InequalityValidator(tag, QString()); + validators.insert(tag, val); + } + val->m_acceptedValues << value; + qCDebug(KNEWSTUFFCORE) << "Created InequalityValidator for tag" << tag << "with value" << value; + } else { + qCDebug(KNEWSTUFFCORE) << "Critical error attempting to create tag filter validators. The filter is defined as" << filter << "which is not in the accepted formats key==value or key!=value"; + } + } +}; + +TagsFilterChecker::TagsFilterChecker(const QStringList &tagFilter) + : d(new TagsFilterChecker::Private) +{ + for (const QString &filter : tagFilter) { + d->addValidator(filter); + } +} + +TagsFilterChecker::~TagsFilterChecker() +{ + delete d; +} + +bool TagsFilterChecker::filterAccepts(const QStringList &tags) +{ + // if any tag in the content matches any of the tag filters, skip this entry + qCDebug(KNEWSTUFFCORE) << "Checking tags list" << tags << "against validators with keys" << d->validators.keys(); + for (const QString &tag : tags) { + if (tag.isEmpty()) { + // This happens when you do a split on an empty string (not an empty list, a list with one empty element... because reasons). + // Also handy for other things, i guess, though, so let's just catch it here. + continue; + } + QStringList current = tag.split(QLatin1Char('=')); + if (current.length() > 2) { + qCDebug(KNEWSTUFFCORE) << "Critical error attempting to filter tags. Entry has tag defined as" << tag << "which is not in the format \"key=value\" or \"key\"."; + return false; + } else if (current.length() == 1) { + // If the tag is defined simply as a key, we give it the value "1", just to make our filtering work simpler + current << QStringLiteral("1"); + } + QMap::const_iterator i = d->validators.constBegin(); + while (i != d->validators.constEnd()) { + if (!i.value()->filterAccepts(current.at(0), current.at(1))) { + return false; + } + ++i; + } + } + // If we have arrived here, nothing has filtered the entry + // out (by being either incorrectly tagged or a filter rejecting + // it), and consequently it is an acceptable entry. + return true; +} + +} diff --git a/src/staticxml/staticxmlprovider.cpp b/src/staticxml/staticxmlprovider.cpp --- a/src/staticxml/staticxmlprovider.cpp +++ b/src/staticxml/staticxmlprovider.cpp @@ -27,6 +27,7 @@ #include #include +#include namespace KNSCore @@ -200,6 +201,8 @@ const Provider::Filter filter = loader->property("filter").value(); const QString searchTerm = loader->property("searchTerm").toString(); + TagsFilterChecker checker(tagFilter()); + TagsFilterChecker downloadschecker(downloadTagFilter()); element = doc.documentElement(); QDomElement n; for (n = element.firstChildElement(); !n.isNull(); n = n.nextSiblingElement()) { @@ -225,27 +228,45 @@ } cacheEntry = entry; } - mCachedEntries.append(entry); - - if (searchIncludesEntry(entry)) { - switch(filter) { - case Installed: - //This is dealth with in loadEntries separately - Q_UNREACHABLE(); - case Updates: - if (entry.status() == KNS3::Entry::Updateable) { - entries << entry; + + if (checker.filterAccepts(entry.tags())) { + bool filterAcceptsDownloads = true; + if (entry.downloadCount() > 0) { + for (const KNSCore::EntryInternal::DownloadLinkInformation &dli : entry.downloadLinkInformationList()) { + if (downloadschecker.filterAccepts(dli.tags)) { + filterAcceptsDownloads = true; + break; } - break; - case ExactEntryId: - if (entry.uniqueId() == searchTerm) { - entries << entry; + } + } + if (filterAcceptsDownloads) { + mCachedEntries.append(entry); + + if (searchIncludesEntry(entry)) { + switch(filter) { + case Installed: + //This is dealth with in loadEntries separately + Q_UNREACHABLE(); + case Updates: + if (entry.status() == KNS3::Entry::Updateable) { + entries << entry; + } + break; + case ExactEntryId: + if (entry.uniqueId() == searchTerm) { + entries << entry; + } + break; + case None: + entries << entry; + break; } - break; - case None: - entries << entry; - break; + } + } else { + qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << entry.name() << "on download filter" << downloadTagFilter(); } + } else { + qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << entry.name() << "on entry filter" << tagFilter(); } } emit loadingFinished(mCurrentRequest, entries); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,25 +1,27 @@ include(ECMMarkAsTest) -find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Test Widgets) # Widgets for KMoreTools +find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Test Widgets Gui Quick) # Widgets for KMoreTools and Quick for the interactive KNS test + +configure_file(khotnewstuff_test.knsrc.in khotnewstuff_test.knsrc @ONLY) macro(knewstuff_executable_tests) foreach(_testname ${ARGN}) - add_executable(${_testname} ${_testname}.cpp ../src/knewstuff_debug.cpp) - target_link_libraries(${_testname} KF5::NewStuffCore KF5::NewStuff KF5::I18n Qt5::Xml Qt5::Test) + add_executable(${_testname} ${_testname}.cpp ../src/knewstuff_debug.cpp ../src/core/knewstuffcore_debug.cpp ../src/staticxml/staticxmlprovider.cpp) + target_link_libraries(${_testname} KF5::NewStuffCore KF5::NewStuff KF5::I18n Qt5::Xml Qt5::Test Qt5::Quick Qt5::Gui) target_compile_definitions(${_testname} PRIVATE - KNSSRCDIR="\\"${CMAKE_CURRENT_SOURCE_DIR}/\\"" - KNSBUILDDIR="\\"${CMAKE_CURRENT_BINARY_DIR}\\"") + KNSSRCDIR="${CMAKE_CURRENT_SOURCE_DIR}/" + KNSBUILDDIR="${CMAKE_CURRENT_BINARY_DIR}") endforeach() endmacro() knewstuff_executable_tests( khotnewstuff khotnewstuff_upload + khotnewstuff_test ) # FIXME: port to new API #knewstuff_executable_tests( -# knewstuff2_test # knewstuff2_download # knewstuff2_standard # knewstuff2_cache diff --git a/tests/khotnewstuff_test-ui/main.qml b/tests/khotnewstuff_test-ui/main.qml new file mode 100644 --- /dev/null +++ b/tests/khotnewstuff_test-ui/main.qml @@ -0,0 +1,67 @@ +import QtQuick 2.7 +import org.kde.kirigami 2.4 as Kirigami + +Kirigami.ApplicationWindow { + id: root; + + globalDrawer: Kirigami.GlobalDrawer { + title: "KNewStuff Test" + titleIcon: "applications-development" + drawerOpen: true; + modal: false; + + actions: [ + Kirigami.Action { + text: "Run Engine test" + onTriggered: testObject.engineTest(); + iconName: "run-build" + }, + Kirigami.Action { + text: "Test entry download as well" + onTriggered: testObject.testAll = !testObject.testAll + iconName: typeof(testObject) !== "undefined" ? (testObject.testAll ? "checkmark" : "") : "" + }, + Kirigami.Action {}, + Kirigami.Action { + text: "Run Entry test" + onTriggered: testObject.entryTest(); + iconName: "run-build" + }, + Kirigami.Action { + text: "Run Provider test" + onTriggered: testObject.providerTest(); + iconName: "run-build" + } + ] + } + contextDrawer: Kirigami.ContextDrawer { + id: contextDrawer + } + + pageStack.initialPage: mainPageComponent + + Component { + id: mainPageComponent + Kirigami.ScrollablePage { + title: "Welcome" + ListView { + id: messageView; + model: testObject.messages(); + onCountChanged: { + messageView.currentIndex = messageView.count - 1; + } + delegate: Kirigami.BasicListItem { + id: listItem + + reserveSpaceForIcon: true + label: model.display + icon: model.whatsThis + + Accessible.role: Accessible.MenuItem + onClicked: {} + highlighted: focus && ListView.isCurrentItem + } + } + } + } +} diff --git a/tests/khotnewstuff_test-ui/main.qmlc b/tests/khotnewstuff_test-ui/main.qmlc new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see . -*/ - -#ifndef KNEWSTUFF2_TEST_TEST_H -#define KNEWSTUFF2_TEST_TEST_H - -#include -#include - -#include - -namespace KNS -{ -class CoreEngine; -} - -class KNewStuff2Test : public QObject -{ - Q_OBJECT -public: - KNewStuff2Test(); - void setTestAll(bool testall); - void entryTest(); - void providerTest(); - void engineTest(); -public Q_SLOTS: - void slotProviderLoaded(KNS::Provider *provider); - void slotProvidersFailed(); - void slotEntryLoaded(KNS::Entry *entry, const KNS::Feed *feed, const KNS::Provider *provider); - void slotEntriesFailed(); - void slotEntriesFinished(); - void slotPayloadLoaded(QUrl payload); - void slotPayloadFailed(); - void slotInstallationFinished(); - void slotInstallationFailed(); -private: - void quitTest(); - KNS::CoreEngine *m_engine; - bool m_testall; -}; - -#endif diff --git a/tests/knewstuff2_test.cpp b/tests/knewstuff2_test.cpp deleted file mode 100644 --- a/tests/knewstuff2_test.cpp +++ /dev/null @@ -1,278 +0,0 @@ -/* - This file is part of KNewStuff2. - Copyright (c) 2007 Josef Spillner - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see . -*/ - -#include "knewstuff2_test.h" - -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include // for exit() -#include // for stdout - -KNewStuff2Test::KNewStuff2Test() - : QObject() -{ - m_engine = NULL; - m_testall = false; -} - -void KNewStuff2Test::setTestAll(bool testall) -{ - m_testall = testall; -} - -void KNewStuff2Test::entryTest() -{ - // qCDebug(KNEWSTUFF) << "-- test kns2 entry class"; - - QDomDocument doc; - QFile f(QString("%1/testdata/entry.xml").arg(KNSSRCDIR)); - if (!f.open(QIODevice::ReadOnly)) { - // qCDebug(KNEWSTUFF) << "Error loading entry file."; - quitTest(); - } - if (!doc.setContent(&f)) { - // qCDebug(KNEWSTUFF) << "Error parsing entry file."; - f.close(); - quitTest(); - } - f.close(); - - KNS::EntryHandler eh(doc.documentElement()); - KNS::Entry e = eh.entry(); - - // qCDebug(KNEWSTUFF) << "-- xml->entry test result: " << eh.isValid(); - - KNS::EntryHandler eh2(e); - QDomElement exml = eh2.entryXML(); - - // qCDebug(KNEWSTUFF) << "-- entry->xml test result: " << eh.isValid(); - - if (!eh.isValid()) { - quitTest(); - } else { - QTextStream out(stdout); - out << exml; - } -} - -void KNewStuff2Test::providerTest() -{ - // qCDebug(KNEWSTUFF) << "-- test kns2 provider class"; - - QDomDocument doc; - QFile f(QString("%1/testdata/provider.xml").arg(KNSSRCDIR)); - if (!f.open(QIODevice::ReadOnly)) { - // qCDebug(KNEWSTUFF) << "Error loading provider file."; - quitTest(); - } - if (!doc.setContent(&f)) { - // qCDebug(KNEWSTUFF) << "Error parsing provider file."; - f.close(); - quitTest(); - } - f.close(); - - KNS::ProviderHandler ph(doc.documentElement()); - KNS::Provider p = ph.provider(); - - // qCDebug(KNEWSTUFF) << "-- xml->provider test result: " << ph.isValid(); - - KNS::ProviderHandler ph2(p); - QDomElement pxml = ph2.providerXML(); - - // qCDebug(KNEWSTUFF) << "-- provider->xml test result: " << ph.isValid(); - - if (!ph.isValid()) { - quitTest(); - } else { - QTextStream out(stdout); - out << pxml; - } -} - -void KNewStuff2Test::engineTest() -{ - // qCDebug(KNEWSTUFF) << "-- test kns2 engine"; - - m_engine = new KNS::CoreEngine(NULL); - bool ret = m_engine->init("knewstuff2_test.knsrc"); - - // qCDebug(KNEWSTUFF) << "-- engine test result: " << ret; - - if (ret) { - connect(m_engine, - &KNS::CoreEngine::signalProviderLoaded, - this, &KNewStuff2Test::slotProviderLoaded); - connect(m_engine, - &KNS::CoreEngine::signalProvidersFailed, - this, &KNewStuff2Test::slotProvidersFailed); - connect(m_engine, - &KNS::CoreEngine::signalEntryLoaded, - this, &KNewStuff2Test::slotEntryLoaded); - connect(m_engine, - &KNS::CoreEngine::signalEntriesFinished, - this, &KNewStuff2Test::slotEntriesFinished); - connect(m_engine, - &KNS::CoreEngine::signalEntriesFailed, - this, &KNewStuff2Test::slotEntriesFailed); - connect(m_engine, - &KNS::CoreEngine::signalPayloadLoaded, - this, &KNewStuff2Test::slotPayloadLoaded); - connect(m_engine, - &KNS::CoreEngine::signalPayloadFailed, - this, &KNewStuff2Test::slotPayloadFailed); - connect(m_engine, - &KNS::CoreEngine::signalInstallationFinished, - this, &KNewStuff2Test::slotInstallationFinished); - connect(m_engine, - &KNS::CoreEngine::signalInstallationFailed, - this, &KNewStuff2Test::slotInstallationFailed); - - m_engine->start(); - } else { - qWarning() << "ACHTUNG: you probably need to 'make install' the knsrc file first."; - qWarning() << "Although this is not required anymore, so something went really wrong."; - quitTest(); - } -} - -void KNewStuff2Test::slotProviderLoaded(KNS::Provider *provider) -{ - // qCDebug(KNEWSTUFF) << "SLOT: slotProviderLoaded"; - // qCDebug(KNEWSTUFF) << "-- provider: " << provider->name().representation(); - - m_engine->loadEntries(provider); -} - -void KNewStuff2Test::slotEntryLoaded(KNS::Entry *entry, const KNS::Feed *feed, const KNS::Provider *provider) -{ - Q_UNUSED(feed); - Q_UNUSED(provider); - - // qCDebug(KNEWSTUFF) << "SLOT: slotEntryLoaded"; - // qCDebug(KNEWSTUFF) << "-- entry: " << entry->name().representation(); - - if (m_testall) { - // qCDebug(KNEWSTUFF) << "-- now, download the entry's preview and payload file"; - - if (!entry->preview().isEmpty()) { - m_engine->downloadPreview(entry); - } - if (!entry->payload().isEmpty()) { - m_engine->downloadPayload(entry); - } - } -} - -void KNewStuff2Test::slotEntriesFinished() -{ - // Wait for installation if requested - if (!m_testall) { - quitTest(); - } -} - -void KNewStuff2Test::slotPayloadLoaded(QUrl payload) -{ - // qCDebug(KNEWSTUFF) << "-- entry downloaded successfully"; - // qCDebug(KNEWSTUFF) << "-- downloaded to " << payload.prettyUrl(); - - // qCDebug(KNEWSTUFF) << "-- run installation"; - - bool ret = m_engine->install(payload.path()); - - // qCDebug(KNEWSTUFF) << "-- installation result: " << ret; - // qCDebug(KNEWSTUFF) << "-- now, wait for installation to finish..."; -} - -void KNewStuff2Test::slotPayloadFailed() -{ - // qCDebug(KNEWSTUFF) << "SLOT: slotPayloadFailed"; - quitTest(); -} - -void KNewStuff2Test::slotProvidersFailed() -{ - // qCDebug(KNEWSTUFF) << "SLOT: slotProvidersFailed"; - quitTest(); -} - -void KNewStuff2Test::slotEntriesFailed() -{ - // qCDebug(KNEWSTUFF) << "SLOT: slotEntriesFailed"; - quitTest(); -} - -void KNewStuff2Test::slotInstallationFinished() -{ - // qCDebug(KNEWSTUFF) << "SLOT: slotInstallationFinished"; - // qCDebug(KNEWSTUFF) << "-- OK, finish test"; - quitTest(); -} - -void KNewStuff2Test::slotInstallationFailed() -{ - // qCDebug(KNEWSTUFF) << "SLOT: slotInstallationFailed"; - quitTest(); -} - -void KNewStuff2Test::quitTest() -{ - // qCDebug(KNEWSTUFF) << "-- quitting now..."; - if (1 == 0) { - // this would be the soft way out... - delete m_engine; - deleteLater(); - qApp->quit(); - } else { - exit(1); - } -} - -int main(int argc, char **argv) -{ - //options.add("testall", qi18n("Downloads all previews and payloads")); - - QApplication app(argc, argv); - - // Take source directory into account - // qCDebug(KNEWSTUFF) << "-- adding source directory " << KNSSRCDIR; - // qCDebug(KNEWSTUFF) << "-- adding build directory " << KNSBUILDDIR; - KGlobal::dirs()->addResourceDir("config", KNSSRCDIR); - KGlobal::dirs()->addResourceDir("config", KNSBUILDDIR); - - KNewStuff2Test *test = new KNewStuff2Test(); - if (app.arguments().contains("--testall")) { - test->setTestAll(true); - test->entryTest(); - test->providerTest(); - } - test->engineTest(); - - return app.exec(); -} - diff --git a/tests/testdata/entry.xml b/tests/testdata/entry.xml --- a/tests/testdata/entry.xml +++ b/tests/testdata/entry.xml @@ -1,14 +1,55 @@ - - Some Name - Anonymous Guy - GPL - 1.0 - 2005-06-17 - This is what it is all about. - http://some.http.server/preview.png - http://some.http.server/coolstuff.tar.gz - 10 - 0 - - + + + Entry 1 (ghns excluded) + Anonymous Guy + GPL + 1.0 + 2005-06-17 + This is what it is all about. + http://some.http.server/preview.png + http://some.http.server/coolstuff.tar.gz + 10 + 0 + ghns_exclude=1 + + + Entry 2 (ghns included) + Anonymous Guy + GPL + 2.1git2 + 2018-06-05 + A short description in English (not ghns excluded). + http://some.http.server/preview.png + http://some.http.server/coolstuff.tar.gz + 10 + 0 + + + + Entry 3 (ghns excluded) + Anonymous Guy + GPL + 1.0 + 2005-06-17 + This is what it is all about. + http://some.http.server/preview.png + http://some.http.server/coolstuff.tar.gz + 10 + 0 + ghns_exclude=1 + + + Entry 4 (ghns included) + Anonymous Guy + GPL + 2.1git2 + 2018-06-05 + A short description in English (not ghns excluded). + http://some.http.server/preview.png + http://some.http.server/coolstuff.tar.gz + 10 + 0 + + + diff --git a/tests/testdata/provider.xml b/tests/testdata/provider.xml --- a/tests/testdata/provider.xml +++ b/tests/testdata/provider.xml @@ -1,12 +1,22 @@ - + + Some cool stuff Viele neue Dinge +