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
*
* This program is free software; you can redistribute it and/or modify
......@@ -23,10 +23,13 @@
#include "lib/icu/Compare.hxx"
#include "Compiler.h"
#include <memory>
#include <string>
#include <list>
#include <chrono>
#include <stdint.h>
/**
* Limit the search to files within the given directory.
*/
......@@ -42,9 +45,28 @@
#define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20
template<typename T> struct ConstBuffer;
enum TagType : uint8_t;
struct Tag;
struct TagItem;
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 {
std::string value;
......@@ -55,8 +77,6 @@ class StringFilter {
IcuCompare fold_case;
public:
StringFilter() = default;
template<typename V>
StringFilter(V &&_value, bool _fold_case)
:value(std::forward<V>(_value)),
......@@ -80,82 +100,129 @@ public:
bool Match(const char *s) const noexcept;
};
class SongFilter {
class UriSongFilter final : public ISongFilter {
StringFilter filter;
bool negated;
public:
class Item {
unsigned tag;
template<typename V>
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;
/**
* For #LOCATE_TAG_MODIFIED_SINCE
*/
std::chrono::system_clock::time_point time;
template<typename V>
explicit BaseSongFilter(V &&_value)
:value(std::forward<V>(_value)) {}
public:
Item(unsigned tag, std::string &&_value, bool fold_case=false);
Item(unsigned tag, std::chrono::system_clock::time_point time);
const char *GetValue() const noexcept {
return value.c_str();
}
/**
* Convert this object into an "expression". This is
* only useful for debugging.
*/
std::string ToExpression() const noexcept;
ISongFilterPtr Clone() const noexcept override {
return std::make_unique<BaseSongFilter>(*this);
}
std::string ToExpression() const noexcept override;
bool Match(const LightSong &song) const noexcept override;
};
unsigned GetTag() const {
return tag;
}
class TagSongFilter final : public ISongFilter {
TagType type;
bool IsNegated() const noexcept {
return negated;
}
bool negated;
void SetNegated(bool _negated=true) noexcept {
negated = _negated;
}
StringFilter filter;
bool GetFoldCase() const {
return string_filter.GetFoldCase();
}
public:
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 {
return string_filter.GetValue().c_str();
}
TagType GetTagType() const {
return type;
}
private:
/* note: the "NN" suffix means "no negation", i.e. the
method pretends negation is unset, and the caller
is responsibly for considering it */
const auto &GetValue() const noexcept {
return filter.GetValue();
}
gcc_pure
bool MatchNN(const TagItem &tag_item) const noexcept;
bool GetFoldCase() const {
return filter.GetFoldCase();
}
gcc_pure
bool MatchNN(const Tag &tag) const noexcept;
bool IsNegated() const noexcept {
return negated;
}
gcc_pure
bool MatchNN(const LightSong &song) const noexcept;
ISongFilterPtr Clone() const noexcept override {
return std::make_unique<TagSongFilter>(*this);
}
public:
gcc_pure
bool Match(const LightSong &song) const noexcept {
return MatchNN(song) != IsNegated();
}
};
std::string ToExpression() const noexcept override;
bool Match(const LightSong &song) const noexcept override;
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:
SongFilter() = default;
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 &&) = default;
SongFilter &operator=(SongFilter &&) = default;
/**
* Convert this object into an "expression". This is
* only useful for debugging.
......@@ -163,7 +230,7 @@ public:
std::string ToExpression() const noexcept;
private:
const char *ParseExpression(const char *s, bool fold_case=false);
ISongFilterPtr ParseExpression(const char *&s, bool fold_case=false);
gcc_nonnull(2,3)
void Parse(const char *tag, const char *value, bool fold_case=false);
......@@ -177,7 +244,7 @@ public:
gcc_pure
bool Match(const LightSong &song) const noexcept;
const std::list<Item> &GetItems() const noexcept {
const auto &GetItems() const noexcept {
return items;
}
......@@ -190,13 +257,7 @@ public:
* Is there at least one item with "fold case" enabled?
*/
gcc_pure
bool HasFoldCase() const noexcept {
for (const auto &i : items)
if (i.GetFoldCase())
return true;
return false;
}
bool HasFoldCase() const noexcept;
/**
* Does this filter contain constraints other than "base"?
......
......@@ -248,7 +248,7 @@ handle_list(Client &client, Request args, Response &r)
return CommandResult::ERROR;
}
filter.reset(new SongFilter((unsigned)TAG_ARTIST,
filter.reset(new SongFilter(TAG_ARTIST,
args.shift()));
}
......
......@@ -268,49 +268,53 @@ CheckError(struct mpd_connection *connection)
}
static bool
SendConstraints(mpd_connection *connection, const SongFilter::Item &item)
SendConstraints(mpd_connection *connection, const ISongFilter &f)
{
switch (item.GetTag()) {
mpd_tag_type tag;
#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 */
if (auto t = dynamic_cast<const TagSongFilter *>(&f)) {
if (t->IsNegated())
// TODO implement
return true;
return mpd_search_add_base_constraint(connection,
MPD_OPERATOR_DEFAULT,
item.GetValue());
#endif
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());
if (t->GetTagType() == TAG_NUM_OF_ITEM_TYPES)
return mpd_search_add_any_tag_constraint(connection,
MPD_OPERATOR_DEFAULT,
t->GetValue().c_str());
default:
tag = Convert(TagType(item.GetTag()));
const auto tag = Convert(t->GetTagType());
if (tag == MPD_TAG_COUNT)
return true;
return mpd_search_add_tag_constraint(connection,
MPD_OPERATOR_DEFAULT,
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
SendConstraints(mpd_connection *connection, const SongFilter &filter)
{
for (const auto &i : filter.GetItems())
if (!SendConstraints(connection, i))
if (!SendConstraints(connection, *i))
return false;
return true;
......
......@@ -254,9 +254,9 @@ UpnpDatabase::SearchSongs(const ContentDirectoryService &server,
std::string cond;
for (const auto &item : filter->GetItems()) {
switch (auto tag = item.GetTag()) {
case LOCATE_TAG_ANY_TYPE:
{
if (auto t = dynamic_cast<const TagSongFilter *>(item.get())) {
auto tag = t->GetTagType();
if (tag == TAG_NUM_OF_ITEM_TYPES) {
if (!cond.empty()) {
cond += " and ";
}
......@@ -268,29 +268,21 @@ UpnpDatabase::SearchSongs(const ContentDirectoryService &server,
else
cond += " or ";
cond += cap;
if (item.GetFoldCase()) {
if (t->GetFoldCase()) {
cond += " contains ";
} else {
cond += " = ";
}
dquote(cond, item.GetValue());
dquote(cond, t->GetValue().c_str());
}
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)
tag = TAG_ARTIST;
// TODO: support LOCATE_TAG_ANY_TYPE etc.
const char *name = tag_table_lookup(upnp_tags,
TagType(tag));
const char *name = tag_table_lookup(upnp_tags, tag);
if (name == nullptr)
continue;
......@@ -304,13 +296,15 @@ UpnpDatabase::SearchSongs(const ContentDirectoryService &server,
case-insensitive, but at least some servers
have the same convention as mpd (e.g.:
minidlna) */
if (item.GetFoldCase()) {
if (t->GetFoldCase()) {
cond += " contains ";
} else {
cond += " = ";
}
dquote(cond, item.GetValue());
dquote(cond, t->GetValue().c_str());
}
// TODO: support other ISongFilter implementations
}
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