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,34 @@
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 +69,9 @@
{}
Provider::~Provider()
-{}
+{
+ delete_d(this);
+}
QString Provider::name() const
{
@@ -53,6 +83,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
+