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
...@@ -78,74 +78,76 @@ StringFilter::Match(const char *s) const noexcept ...@@ -78,74 +78,76 @@ StringFilter::Match(const char *s) const noexcept
} }
} }
SongFilter::Item::Item(unsigned _tag, std::string &&_value, bool _fold_case) std::string
:tag(_tag), string_filter(std::move(_value), _fold_case) UriSongFilter::ToExpression() const noexcept
{ {
return std::string("(" LOCATE_TAG_FILE_KEY " ") + (negated ? "!=" : "==") + " \"" + filter.GetValue() + "\")";
} }
SongFilter::Item::Item(unsigned _tag, bool
std::chrono::system_clock::time_point _time) UriSongFilter::Match(const LightSong &song) const noexcept
:tag(_tag), time(_time)
{ {
return filter.Match(song.GetURI().c_str()) != negated;
} }
std::string std::string
SongFilter::Item::ToExpression() const noexcept BaseSongFilter::ToExpression() const noexcept
{ {
switch (tag) { return "(base \"" + value + "\")";
case LOCATE_TAG_FILE_TYPE: }
return std::string("(" LOCATE_TAG_FILE_KEY " ") + (IsNegated() ? "!=" : "==") + " \"" + string_filter.GetValue() + "\")";
case LOCATE_TAG_BASE_TYPE:
return "(base \"" + string_filter.GetValue() + "\")";
case LOCATE_TAG_MODIFIED_SINCE: bool
return "(modified-since \"" + string_filter.GetValue() + "\")"; BaseSongFilter::Match(const LightSong &song) const noexcept
{
return uri_is_child_or_same(value.c_str(), song.GetURI().c_str());
}
case LOCATE_TAG_ANY_TYPE: std::string
return std::string("(" LOCATE_TAG_ANY_KEY " ") + (IsNegated() ? "!=" : "==") + " \"" + string_filter.GetValue() + "\")"; TagSongFilter::ToExpression() const noexcept
{
const char *name = type == TAG_NUM_OF_ITEM_TYPES
? LOCATE_TAG_ANY_KEY
: tag_item_names[type];
default: return std::string("(") + name + " " + (negated ? "!=" : "==") + " \"" + filter.GetValue() + "\")";
return std::string("(") + tag_item_names[tag] + " " + (IsNegated() ? "!=" : "==") + " \"" + string_filter.GetValue() + "\")";
}
} }
bool bool
SongFilter::Item::MatchNN(const TagItem &item) const noexcept TagSongFilter::MatchNN(const TagItem &item) const noexcept
{ {
return (tag == LOCATE_TAG_ANY_TYPE || (unsigned)item.type == tag) && return (type == TAG_NUM_OF_ITEM_TYPES || item.type == type) &&
string_filter.Match(item.value); filter.Match(item.value);
} }
bool bool
SongFilter::Item::MatchNN(const Tag &_tag) const noexcept TagSongFilter::MatchNN(const Tag &tag) const noexcept
{ {
bool visited_types[TAG_NUM_OF_ITEM_TYPES]; bool visited_types[TAG_NUM_OF_ITEM_TYPES];
std::fill_n(visited_types, size_t(TAG_NUM_OF_ITEM_TYPES), false); std::fill_n(visited_types, size_t(TAG_NUM_OF_ITEM_TYPES), false);
for (const auto &i : _tag) { for (const auto &i : tag) {
visited_types[i.type] = true; visited_types[i.type] = true;
if (MatchNN(i)) if (MatchNN(i))
return true; return true;
} }
if (tag < TAG_NUM_OF_ITEM_TYPES && !visited_types[tag]) { if (type < TAG_NUM_OF_ITEM_TYPES && !visited_types[type]) {
/* If the search critieron was not visited during the /* If the search critieron was not visited during the
sweep through the song's tag, it means this field sweep through the song's tag, it means this field
is absent from the tag or empty. Thus, if the is absent from the tag or empty. Thus, if the
searched string is also empty searched string is also empty
then it's a match as well and we should return then it's a match as well and we should return
true. */ true. */
if (string_filter.empty()) if (filter.empty())
return true; return true;
if (tag == TAG_ALBUM_ARTIST && visited_types[TAG_ARTIST]) { if (type == TAG_ALBUM_ARTIST && visited_types[TAG_ARTIST]) {
/* if we're looking for "album artist", but /* if we're looking for "album artist", but
only "artist" exists, use that */ only "artist" exists, use that */
for (const auto &item : _tag) for (const auto &item : tag)
if (item.type == TAG_ARTIST && if (item.type == TAG_ARTIST &&
string_filter.Match(item.value)) filter.Match(item.value))
return true; return true;
} }
} }
...@@ -154,28 +156,27 @@ SongFilter::Item::MatchNN(const Tag &_tag) const noexcept ...@@ -154,28 +156,27 @@ SongFilter::Item::MatchNN(const Tag &_tag) const noexcept
} }
bool bool
SongFilter::Item::MatchNN(const LightSong &song) const noexcept TagSongFilter::Match(const LightSong &song) const noexcept
{ {
if (tag == LOCATE_TAG_BASE_TYPE) { return MatchNN(song.tag) != negated;
const auto uri = song.GetURI(); }
return uri_is_child_or_same(string_filter.GetValue().c_str(),
uri.c_str());
}
if (tag == LOCATE_TAG_MODIFIED_SINCE)
return song.mtime >= time;
if (tag == LOCATE_TAG_FILE_TYPE) { std::string
const auto uri = song.GetURI(); ModifiedSinceSongFilter::ToExpression() const noexcept
return string_filter.Match(uri.c_str()); {
} return std::string("(modified-since \"") + FormatISO8601(value).c_str() + "\")";
}
return MatchNN(song.tag); bool
ModifiedSinceSongFilter::Match(const LightSong &song) const noexcept
{
return song.mtime >= value;
} }
SongFilter::SongFilter(unsigned tag, const char *value, bool fold_case) SongFilter::SongFilter(TagType tag, const char *value, bool fold_case)
{ {
items.emplace_back(tag, value, fold_case); items.emplace_back(std::make_unique<TagSongFilter>(tag, value,
fold_case, false));
} }
SongFilter::~SongFilter() SongFilter::~SongFilter()
...@@ -190,14 +191,14 @@ SongFilter::ToExpression() const noexcept ...@@ -190,14 +191,14 @@ SongFilter::ToExpression() const noexcept
const auto end = items.end(); const auto end = items.end();
if (std::next(i) == end) if (std::next(i) == end)
return i->ToExpression(); return (*i)->ToExpression();
std::string e("("); std::string e("(");
e += i->ToExpression(); e += (*i)->ToExpression();
for (++i; i != end; ++i) { for (++i; i != end; ++i) {
e += " AND "; e += " AND ";
e += i->ToExpression(); e += (*i)->ToExpression();
} }
e.push_back(')'); e.push_back(')');
...@@ -273,8 +274,8 @@ ExpectQuoted(const char *&s) ...@@ -273,8 +274,8 @@ ExpectQuoted(const char *&s)
return {begin, end}; return {begin, end};
} }
const char * ISongFilterPtr
SongFilter::ParseExpression(const char *s, bool fold_case) SongFilter::ParseExpression(const char *&s, bool fold_case)
{ {
assert(*s == '('); assert(*s == '(');
...@@ -283,21 +284,21 @@ SongFilter::ParseExpression(const char *s, bool fold_case) ...@@ -283,21 +284,21 @@ SongFilter::ParseExpression(const char *s, bool fold_case)
if (*s == '(') if (*s == '(')
throw std::runtime_error("Nested expressions not yet implemented"); throw std::runtime_error("Nested expressions not yet implemented");
const auto type = ExpectFilterType(s); auto type = ExpectFilterType(s);
if (type == LOCATE_TAG_MODIFIED_SINCE) { if (type == LOCATE_TAG_MODIFIED_SINCE) {
const auto value_s = ExpectQuoted(s); const auto value_s = ExpectQuoted(s);
if (*s != ')') if (*s != ')')
throw std::runtime_error("')' expected"); throw std::runtime_error("')' expected");
items.emplace_back(type, ParseTimeStamp(value_s.c_str())); s = StripLeft(s + 1);
return StripLeft(s + 1); return std::make_unique<ModifiedSinceSongFilter>(ParseTimeStamp(value_s.c_str()));
} else if (type == LOCATE_TAG_BASE_TYPE) { } else if (type == LOCATE_TAG_BASE_TYPE) {
auto value = ExpectQuoted(s); auto value = ExpectQuoted(s);
if (*s != ')') if (*s != ')')
throw std::runtime_error("')' expected"); throw std::runtime_error("')' expected");
s = StripLeft(s + 1);
items.emplace_back(type, std::move(value), fold_case); return std::make_unique<BaseSongFilter>(std::move(value));
return StripLeft(s + 1);
} else { } else {
bool negated = false; bool negated = false;
if (s[0] == '!' && s[1] == '=') if (s[0] == '!' && s[1] == '=')
...@@ -310,9 +311,19 @@ SongFilter::ParseExpression(const char *s, bool fold_case) ...@@ -310,9 +311,19 @@ SongFilter::ParseExpression(const char *s, bool fold_case)
if (*s != ')') if (*s != ')')
throw std::runtime_error("')' expected"); throw std::runtime_error("')' expected");
items.emplace_back(type, std::move(value), fold_case); s = StripLeft(s + 1);
items.back().SetNegated(negated);
return StripLeft(s + 1); if (type == LOCATE_TAG_ANY_TYPE)
type = TAG_NUM_OF_ITEM_TYPES;
if (type == LOCATE_TAG_FILE_TYPE)
return std::make_unique<UriSongFilter>(std::move(value),
fold_case,
negated);
return std::make_unique<TagSongFilter>(TagType(type),
std::move(value),
fold_case, negated);
} }
} }
...@@ -320,21 +331,38 @@ void ...@@ -320,21 +331,38 @@ void
SongFilter::Parse(const char *tag_string, const char *value, bool fold_case) SongFilter::Parse(const char *tag_string, const char *value, bool fold_case)
{ {
unsigned tag = locate_parse_type(tag_string); unsigned tag = locate_parse_type(tag_string);
if (tag == TAG_NUM_OF_ITEM_TYPES)
switch (tag) {
case TAG_NUM_OF_ITEM_TYPES:
throw std::runtime_error("Unknown filter type"); throw std::runtime_error("Unknown filter type");
if (tag == LOCATE_TAG_BASE_TYPE) { case LOCATE_TAG_BASE_TYPE:
if (!uri_safe_local(value)) if (!uri_safe_local(value))
throw std::runtime_error("Bad URI"); throw std::runtime_error("Bad URI");
/* case folding doesn't work with "base" */ items.emplace_back(std::make_unique<BaseSongFilter>(value));
fold_case = false; break;
}
case LOCATE_TAG_MODIFIED_SINCE:
items.emplace_back(std::make_unique<ModifiedSinceSongFilter>(ParseTimeStamp(value)));
break;
if (tag == LOCATE_TAG_MODIFIED_SINCE) case LOCATE_TAG_FILE_TYPE:
items.emplace_back(tag, ParseTimeStamp(value)); items.emplace_back(std::make_unique<UriSongFilter>(value,
else fold_case,
items.emplace_back(tag, value, fold_case); false));
break;
default:
if (tag == LOCATE_TAG_ANY_TYPE)
tag = TAG_NUM_OF_ITEM_TYPES;
items.emplace_back(std::make_unique<TagSongFilter>(TagType(tag),
value,
fold_case,
false));
break;
}
} }
void void
...@@ -346,10 +374,12 @@ SongFilter::Parse(ConstBuffer<const char *> args, bool fold_case) ...@@ -346,10 +374,12 @@ SongFilter::Parse(ConstBuffer<const char *> args, bool fold_case)
do { do {
if (*args.front() == '(') { if (*args.front() == '(') {
const char *s = args.shift(); const char *s = args.shift();
const char *end = ParseExpression(s, fold_case); const char *end = s;
auto f = ParseExpression(end, fold_case);
if (*end != 0) if (*end != 0)
throw std::runtime_error("Unparsed garbage after expression"); throw std::runtime_error("Unparsed garbage after expression");
items.emplace_back(std::move(f));
continue; continue;
} }
...@@ -366,18 +396,36 @@ bool ...@@ -366,18 +396,36 @@ bool
SongFilter::Match(const LightSong &song) const noexcept SongFilter::Match(const LightSong &song) const noexcept
{ {
for (const auto &i : items) for (const auto &i : items)
if (!i.Match(song)) if (!i->Match(song))
return false; return false;
return true; return true;
} }
bool bool
SongFilter::HasFoldCase() const noexcept
{
for (const auto &i : items) {
if (auto t = dynamic_cast<const TagSongFilter *>(i.get())) {
if (t->GetFoldCase())
return true;
} else if (auto u = dynamic_cast<const UriSongFilter *>(i.get())) {
if (u->GetFoldCase())
return true;
}
}
return false;
}
bool
SongFilter::HasOtherThanBase() const noexcept SongFilter::HasOtherThanBase() const noexcept
{ {
for (const auto &i : items) for (const auto &i : items) {
if (i.GetTag() != LOCATE_TAG_BASE_TYPE) const auto *f = dynamic_cast<const BaseSongFilter *>(i.get());
if (f == nullptr)
return true; return true;
}
return false; return false;
} }
...@@ -385,9 +433,11 @@ SongFilter::HasOtherThanBase() const noexcept ...@@ -385,9 +433,11 @@ SongFilter::HasOtherThanBase() const noexcept
const char * const char *
SongFilter::GetBase() const noexcept SongFilter::GetBase() const noexcept
{ {
for (const auto &i : items) for (const auto &i : items) {
if (i.GetTag() == LOCATE_TAG_BASE_TYPE) const auto *f = dynamic_cast<const BaseSongFilter *>(i.get());
return i.GetValue(); if (f != nullptr)
return f->GetValue();
}
return nullptr; return nullptr;
} }
...@@ -399,8 +449,9 @@ SongFilter::WithoutBasePrefix(const char *_prefix) const noexcept ...@@ -399,8 +449,9 @@ SongFilter::WithoutBasePrefix(const char *_prefix) const noexcept
SongFilter result; SongFilter result;
for (const auto &i : items) { for (const auto &i : items) {
if (i.GetTag() == LOCATE_TAG_BASE_TYPE) { const auto *f = dynamic_cast<const BaseSongFilter *>(i.get());
const char *s = StringAfterPrefix(i.GetValue(), prefix); if (f != nullptr) {
const char *s = StringAfterPrefix(f->GetValue(), prefix);
if (s != nullptr) { if (s != nullptr) {
if (*s == 0) if (*s == 0)
continue; continue;
...@@ -409,14 +460,14 @@ SongFilter::WithoutBasePrefix(const char *_prefix) const noexcept ...@@ -409,14 +460,14 @@ SongFilter::WithoutBasePrefix(const char *_prefix) const noexcept
++s; ++s;
if (*s != 0) if (*s != 0)
result.items.emplace_back(LOCATE_TAG_BASE_TYPE, s); result.items.emplace_back(std::make_unique<BaseSongFilter>(s));
continue; continue;
} }
} }
} }
result.items.emplace_back(i); result.items.emplace_back(i->Clone());
} }
return result; return result;
......
/* /*
* 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