Commit 2cfccc1c authored by Max Kellermann's avatar Max Kellermann

SongFilter: make Item an interface

Prepare to allow more complex expressions.
parent 438366ef
/* /*
* Copyright 2003-2017 The Music Player Daemon Project * Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
...@@ -23,10 +23,13 @@ ...@@ -23,10 +23,13 @@
#include "lib/icu/Compare.hxx" #include "lib/icu/Compare.hxx"
#include "Compiler.h" #include "Compiler.h"
#include <memory>
#include <string> #include <string>
#include <list> #include <list>
#include <chrono> #include <chrono>
#include <stdint.h>
/** /**
* Limit the search to files within the given directory. * Limit the search to files within the given directory.
*/ */
...@@ -42,9 +45,28 @@ ...@@ -42,9 +45,28 @@
#define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20 #define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20
template<typename T> struct ConstBuffer; template<typename T> struct ConstBuffer;
enum TagType : uint8_t;
struct Tag; struct Tag;
struct TagItem; struct TagItem;
struct LightSong; struct LightSong;
class ISongFilter;
using ISongFilterPtr = std::unique_ptr<ISongFilter>;
class ISongFilter {
public:
virtual ~ISongFilter() noexcept {}
virtual ISongFilterPtr Clone() const noexcept = 0;
/**
* Convert this object into an "expression". This is
* only useful for debugging.
*/
virtual std::string ToExpression() const noexcept = 0;
gcc_pure
virtual bool Match(const LightSong &song) const noexcept = 0;
};
class StringFilter { class StringFilter {
std::string value; std::string value;
...@@ -55,8 +77,6 @@ class StringFilter { ...@@ -55,8 +77,6 @@ class StringFilter {
IcuCompare fold_case; IcuCompare fold_case;
public: public:
StringFilter() = default;
template<typename V> template<typename V>
StringFilter(V &&_value, bool _fold_case) StringFilter(V &&_value, bool _fold_case)
:value(std::forward<V>(_value)), :value(std::forward<V>(_value)),
...@@ -80,82 +100,129 @@ public: ...@@ -80,82 +100,129 @@ public:
bool Match(const char *s) const noexcept; bool Match(const char *s) const noexcept;
}; };
class SongFilter { class UriSongFilter final : public ISongFilter {
StringFilter filter;
bool negated;
public: public:
class Item { template<typename V>
unsigned tag; UriSongFilter(V &&_value, bool fold_case, bool _negated)
:filter(std::forward<V>(_value), fold_case),
negated(_negated) {}
const auto &GetValue() const noexcept {
return filter.GetValue();
}
bool GetFoldCase() const {
return filter.GetFoldCase();
}
bool IsNegated() const noexcept {
return negated;
}
ISongFilterPtr Clone() const noexcept override {
return std::make_unique<UriSongFilter>(*this);
}
std::string ToExpression() const noexcept override;
bool Match(const LightSong &song) const noexcept override;
};
bool negated = false; class BaseSongFilter final : public ISongFilter {
std::string value;
StringFilter string_filter; public:
BaseSongFilter(const BaseSongFilter &) = default;
/** template<typename V>
* For #LOCATE_TAG_MODIFIED_SINCE explicit BaseSongFilter(V &&_value)
*/ :value(std::forward<V>(_value)) {}
std::chrono::system_clock::time_point time;
public: const char *GetValue() const noexcept {
Item(unsigned tag, std::string &&_value, bool fold_case=false); return value.c_str();
Item(unsigned tag, std::chrono::system_clock::time_point time); }
/** ISongFilterPtr Clone() const noexcept override {
* Convert this object into an "expression". This is return std::make_unique<BaseSongFilter>(*this);
* only useful for debugging. }
*/
std::string ToExpression() const noexcept; std::string ToExpression() const noexcept override;
bool Match(const LightSong &song) const noexcept override;
};
unsigned GetTag() const { class TagSongFilter final : public ISongFilter {
return tag; TagType type;
}
bool IsNegated() const noexcept { bool negated;
return negated;
}
void SetNegated(bool _negated=true) noexcept { StringFilter filter;
negated = _negated;
}
bool GetFoldCase() const { public:
return string_filter.GetFoldCase(); template<typename V>
} TagSongFilter(TagType _type, V &&_value, bool fold_case, bool _negated)
:type(_type), negated(_negated),
filter(std::forward<V>(_value), fold_case) {}
const char *GetValue() const { TagType GetTagType() const {
return string_filter.GetValue().c_str(); return type;
} }
private: const auto &GetValue() const noexcept {
/* note: the "NN" suffix means "no negation", i.e. the return filter.GetValue();
method pretends negation is unset, and the caller }
is responsibly for considering it */
gcc_pure bool GetFoldCase() const {
bool MatchNN(const TagItem &tag_item) const noexcept; return filter.GetFoldCase();
}
gcc_pure bool IsNegated() const noexcept {
bool MatchNN(const Tag &tag) const noexcept; return negated;
}
gcc_pure ISongFilterPtr Clone() const noexcept override {
bool MatchNN(const LightSong &song) const noexcept; return std::make_unique<TagSongFilter>(*this);
}
public: std::string ToExpression() const noexcept override;
gcc_pure bool Match(const LightSong &song) const noexcept override;
bool Match(const LightSong &song) const noexcept {
return MatchNN(song) != IsNegated();
}
};
private: private:
std::list<Item> items; bool MatchNN(const Tag &tag) const noexcept;
bool MatchNN(const TagItem &tag) const noexcept;
};
class ModifiedSinceSongFilter final : public ISongFilter {
std::chrono::system_clock::time_point value;
public:
explicit ModifiedSinceSongFilter(std::chrono::system_clock::time_point _value) noexcept
:value(_value) {}
ISongFilterPtr Clone() const noexcept override {
return std::make_unique<ModifiedSinceSongFilter>(*this);
}
std::string ToExpression() const noexcept override;
bool Match(const LightSong &song) const noexcept override;
};
class SongFilter {
std::list<ISongFilterPtr> items;
public: public:
SongFilter() = default; SongFilter() = default;
gcc_nonnull(3) gcc_nonnull(3)
SongFilter(unsigned tag, const char *value, bool fold_case=false); SongFilter(TagType tag, const char *value, bool fold_case=false);
~SongFilter(); ~SongFilter();
SongFilter(SongFilter &&) = default;
SongFilter &operator=(SongFilter &&) = default;
/** /**
* Convert this object into an "expression". This is * Convert this object into an "expression". This is
* only useful for debugging. * only useful for debugging.
...@@ -163,7 +230,7 @@ public: ...@@ -163,7 +230,7 @@ public:
std::string ToExpression() const noexcept; std::string ToExpression() const noexcept;
private: private:
const char *ParseExpression(const char *s, bool fold_case=false); ISongFilterPtr ParseExpression(const char *&s, bool fold_case=false);
gcc_nonnull(2,3) gcc_nonnull(2,3)
void Parse(const char *tag, const char *value, bool fold_case=false); void Parse(const char *tag, const char *value, bool fold_case=false);
...@@ -177,7 +244,7 @@ public: ...@@ -177,7 +244,7 @@ public:
gcc_pure gcc_pure
bool Match(const LightSong &song) const noexcept; bool Match(const LightSong &song) const noexcept;
const std::list<Item> &GetItems() const noexcept { const auto &GetItems() const noexcept {
return items; return items;
} }
...@@ -190,13 +257,7 @@ public: ...@@ -190,13 +257,7 @@ public:
* Is there at least one item with "fold case" enabled? * Is there at least one item with "fold case" enabled?
*/ */
gcc_pure gcc_pure
bool HasFoldCase() const noexcept { bool HasFoldCase() const noexcept;
for (const auto &i : items)
if (i.GetFoldCase())
return true;
return false;
}
/** /**
* Does this filter contain constraints other than "base"? * Does this filter contain constraints other than "base"?
......
...@@ -248,7 +248,7 @@ handle_list(Client &client, Request args, Response &r) ...@@ -248,7 +248,7 @@ handle_list(Client &client, Request args, Response &r)
return CommandResult::ERROR; return CommandResult::ERROR;
} }
filter.reset(new SongFilter((unsigned)TAG_ARTIST, filter.reset(new SongFilter(TAG_ARTIST,
args.shift())); args.shift()));
} }
......
...@@ -268,49 +268,53 @@ CheckError(struct mpd_connection *connection) ...@@ -268,49 +268,53 @@ CheckError(struct mpd_connection *connection)
} }
static bool static bool
SendConstraints(mpd_connection *connection, const SongFilter::Item &item) SendConstraints(mpd_connection *connection, const ISongFilter &f)
{ {
switch (item.GetTag()) { if (auto t = dynamic_cast<const TagSongFilter *>(&f)) {
mpd_tag_type tag; if (t->IsNegated())
// TODO implement
#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
case LOCATE_TAG_BASE_TYPE:
if (mpd_connection_cmp_server_version(connection, 0, 18, 0) < 0)
/* requires MPD 0.18 */
return true; return true;
return mpd_search_add_base_constraint(connection, if (t->GetTagType() == TAG_NUM_OF_ITEM_TYPES)
MPD_OPERATOR_DEFAULT, return mpd_search_add_any_tag_constraint(connection,
item.GetValue()); MPD_OPERATOR_DEFAULT,
#endif t->GetValue().c_str());
case LOCATE_TAG_FILE_TYPE:
return mpd_search_add_uri_constraint(connection,
MPD_OPERATOR_DEFAULT,
item.GetValue());
case LOCATE_TAG_ANY_TYPE:
return mpd_search_add_any_tag_constraint(connection,
MPD_OPERATOR_DEFAULT,
item.GetValue());
default: const auto tag = Convert(t->GetTagType());
tag = Convert(TagType(item.GetTag()));
if (tag == MPD_TAG_COUNT) if (tag == MPD_TAG_COUNT)
return true; return true;
return mpd_search_add_tag_constraint(connection, return mpd_search_add_tag_constraint(connection,
MPD_OPERATOR_DEFAULT, MPD_OPERATOR_DEFAULT,
tag, tag,
item.GetValue()); t->GetValue().c_str());
} } else if (auto u = dynamic_cast<const UriSongFilter *>(&f)) {
if (u->IsNegated())
// TODO implement
return true;
return mpd_search_add_uri_constraint(connection,
MPD_OPERATOR_DEFAULT,
u->GetValue().c_str());
#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
} else if (auto b = dynamic_cast<const BaseSongFilter *>(&f)) {
if (mpd_connection_cmp_server_version(connection, 0, 18, 0) < 0)
/* requires MPD 0.18 */
return true;
return mpd_search_add_base_constraint(connection,
MPD_OPERATOR_DEFAULT,
b->GetValue());
#endif
} else
return true;
} }
static bool static bool
SendConstraints(mpd_connection *connection, const SongFilter &filter) SendConstraints(mpd_connection *connection, const SongFilter &filter)
{ {
for (const auto &i : filter.GetItems()) for (const auto &i : filter.GetItems())
if (!SendConstraints(connection, i)) if (!SendConstraints(connection, *i))
return false; return false;
return true; return true;
......
...@@ -254,9 +254,9 @@ UpnpDatabase::SearchSongs(const ContentDirectoryService &server, ...@@ -254,9 +254,9 @@ UpnpDatabase::SearchSongs(const ContentDirectoryService &server,
std::string cond; std::string cond;
for (const auto &item : filter->GetItems()) { for (const auto &item : filter->GetItems()) {
switch (auto tag = item.GetTag()) { if (auto t = dynamic_cast<const TagSongFilter *>(item.get())) {
case LOCATE_TAG_ANY_TYPE: auto tag = t->GetTagType();
{ if (tag == TAG_NUM_OF_ITEM_TYPES) {
if (!cond.empty()) { if (!cond.empty()) {
cond += " and "; cond += " and ";
} }
...@@ -268,29 +268,21 @@ UpnpDatabase::SearchSongs(const ContentDirectoryService &server, ...@@ -268,29 +268,21 @@ UpnpDatabase::SearchSongs(const ContentDirectoryService &server,
else else
cond += " or "; cond += " or ";
cond += cap; cond += cap;
if (item.GetFoldCase()) { if (t->GetFoldCase()) {
cond += " contains "; cond += " contains ";
} else { } else {
cond += " = "; cond += " = ";
} }
dquote(cond, item.GetValue()); dquote(cond, t->GetValue().c_str());
} }
cond += ')'; cond += ')';
continue;
} }
break;
default:
/* Unhandled conditions like
LOCATE_TAG_BASE_TYPE or
LOCATE_TAG_FILE_TYPE won't have a
corresponding upnp prop, so they will be
skipped */
if (tag == TAG_ALBUM_ARTIST) if (tag == TAG_ALBUM_ARTIST)
tag = TAG_ARTIST; tag = TAG_ARTIST;
// TODO: support LOCATE_TAG_ANY_TYPE etc. const char *name = tag_table_lookup(upnp_tags, tag);
const char *name = tag_table_lookup(upnp_tags,
TagType(tag));
if (name == nullptr) if (name == nullptr)
continue; continue;
...@@ -304,13 +296,15 @@ UpnpDatabase::SearchSongs(const ContentDirectoryService &server, ...@@ -304,13 +296,15 @@ UpnpDatabase::SearchSongs(const ContentDirectoryService &server,
case-insensitive, but at least some servers case-insensitive, but at least some servers
have the same convention as mpd (e.g.: have the same convention as mpd (e.g.:
minidlna) */ minidlna) */
if (item.GetFoldCase()) { if (t->GetFoldCase()) {
cond += " contains "; cond += " contains ";
} else { } else {
cond += " = "; cond += " = ";
} }
dquote(cond, item.GetValue()); dquote(cond, t->GetValue().c_str());
} }
// TODO: support other ISongFilter implementations
} }
return server.search(handle, objid, cond.c_str()); return server.search(handle, objid, cond.c_str());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment