DetachedSong, db/LightSong, SongFilter: move to src/song/

This commit is contained in:
Max Kellermann
2018-08-02 13:45:43 +02:00
parent a31da51fd9
commit 90201e9970
67 changed files with 89 additions and 90 deletions
+79
View File
@@ -0,0 +1,79 @@
/*
* Copyright 2003-2017 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "song/DetachedSong.hxx"
#include "song/LightSong.hxx"
#include "util/UriUtil.hxx"
#include "fs/Traits.hxx"
DetachedSong::DetachedSong(const LightSong &other)
:uri(other.GetURI()),
real_uri(other.real_uri != nullptr ? other.real_uri : ""),
tag(other.tag),
mtime(other.mtime),
start_time(other.start_time),
end_time(other.end_time) {}
DetachedSong::operator LightSong() const noexcept
{
LightSong result(uri.c_str(), tag);
result.directory = nullptr;
result.real_uri = real_uri.empty() ? nullptr : real_uri.c_str();
result.mtime = mtime;
result.start_time = start_time;
result.end_time = end_time;
return result;
}
bool
DetachedSong::IsRemote() const noexcept
{
return uri_has_scheme(GetRealURI());
}
bool
DetachedSong::IsAbsoluteFile() const noexcept
{
return PathTraitsUTF8::IsAbsolute(GetRealURI());
}
bool
DetachedSong::IsInDatabase() const noexcept
{
/* here, we use GetURI() and not GetRealURI() because
GetRealURI() is never relative */
const char *_uri = GetURI();
return !uri_has_scheme(_uri) && !PathTraitsUTF8::IsAbsolute(_uri);
}
SignedSongTime
DetachedSong::GetDuration() const noexcept
{
SongTime a = start_time, b = end_time;
if (!b.IsPositive()) {
if (tag.duration.IsNegative())
return tag.duration;
b = SongTime(tag.duration);
}
return SignedSongTime(b - a);
}
+248
View File
@@ -0,0 +1,248 @@
/*
* Copyright 2003-2017 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_DETACHED_SONG_HXX
#define MPD_DETACHED_SONG_HXX
#include "check.h"
#include "tag/Tag.hxx"
#include "Chrono.hxx"
#include "Compiler.h"
#include <chrono>
#include <string>
#include <utility>
struct LightSong;
class Storage;
class Path;
class DetachedSong {
friend DetachedSong DatabaseDetachSong(const Storage &db,
const LightSong &song);
/**
* An UTF-8-encoded URI referring to the song file. This can
* be one of:
*
* - an absolute URL with a scheme
* (e.g. "http://example.com/foo.mp3")
*
* - an absolute file name
*
* - a file name relative to the music directory
*/
std::string uri;
/**
* The "real" URI, the one to be used for opening the
* resource. If this attribute is empty, then #uri shall be
* used.
*
* This attribute is used for songs from the database which
* have a relative URI.
*/
std::string real_uri;
Tag tag;
/**
* The time stamp of the last file modification. A negative
* value means that this is unknown/unavailable.
*/
std::chrono::system_clock::time_point mtime =
std::chrono::system_clock::time_point::min();
/**
* Start of this sub-song within the file.
*/
SongTime start_time = SongTime::zero();
/**
* End of this sub-song within the file.
* Unused if zero.
*/
SongTime end_time = SongTime::zero();
public:
explicit DetachedSong(const char *_uri)
:uri(_uri) {}
explicit DetachedSong(const std::string &_uri)
:uri(_uri) {}
explicit DetachedSong(std::string &&_uri)
:uri(std::move(_uri)) {}
template<typename U>
DetachedSong(U &&_uri, Tag &&_tag)
:uri(std::forward<U>(_uri)),
tag(std::move(_tag)) {}
/**
* Copy data from a #LightSong instance. Usually, you should
* call DatabaseDetachSong() instead, which initializes
* #real_uri properly using Storage::MapUTF8().
*/
explicit DetachedSong(const LightSong &other);
gcc_noinline
~DetachedSong() = default;
/* these are declared because the user-defined destructor
above prevents them from being generated implicitly */
explicit DetachedSong(const DetachedSong &) = default;
DetachedSong(DetachedSong &&) = default;
DetachedSong &operator=(DetachedSong &&) = default;
gcc_pure
explicit operator LightSong() const noexcept;
gcc_pure
const char *GetURI() const noexcept {
return uri.c_str();
}
template<typename T>
void SetURI(T &&_uri) {
uri = std::forward<T>(_uri);
}
/**
* Does this object have a "real" URI different from the
* displayed URI?
*/
gcc_pure
bool HasRealURI() const noexcept {
return !real_uri.empty();
}
/**
* Returns "real" URI (#real_uri) and falls back to just
* GetURI().
*/
gcc_pure
const char *GetRealURI() const noexcept {
return (HasRealURI() ? real_uri : uri).c_str();
}
template<typename T>
void SetRealURI(T &&_uri) {
real_uri = std::forward<T>(_uri);
}
/**
* Returns true if both objects refer to the same physical
* song.
*/
gcc_pure
bool IsSame(const DetachedSong &other) const noexcept {
return uri == other.uri &&
start_time == other.start_time &&
end_time == other.end_time;
}
gcc_pure gcc_nonnull_all
bool IsURI(const char *other_uri) const noexcept {
return uri == other_uri;
}
gcc_pure
bool IsRemote() const noexcept;
gcc_pure
bool IsFile() const noexcept {
return !IsRemote();
}
gcc_pure
bool IsAbsoluteFile() const noexcept;
gcc_pure
bool IsInDatabase() const noexcept;
const Tag &GetTag() const noexcept {
return tag;
}
Tag &WritableTag() noexcept {
return tag;
}
void SetTag(const Tag &_tag) {
tag = Tag(_tag);
}
void SetTag(Tag &&_tag) {
tag = std::move(_tag);
}
void MoveTagFrom(DetachedSong &&other) {
tag = std::move(other.tag);
}
/**
* Similar to the MoveTagFrom(), but move only the #TagItem
* array.
*/
void MoveTagItemsFrom(DetachedSong &&other) {
tag.MoveItemsFrom(std::move(other.tag));
}
std::chrono::system_clock::time_point GetLastModified() const {
return mtime;
}
void SetLastModified(std::chrono::system_clock::time_point _value) {
mtime = _value;
}
SongTime GetStartTime() const {
return start_time;
}
void SetStartTime(SongTime _value) {
start_time = _value;
}
SongTime GetEndTime() const {
return end_time;
}
void SetEndTime(SongTime _value) {
end_time = _value;
}
gcc_pure
SignedSongTime GetDuration() const noexcept;
/**
* Update the #tag and #mtime.
*
* @return true on success
*/
bool Update() noexcept;
/**
* Load #tag and #mtime from a local file.
*/
bool LoadFile(Path path) noexcept;
};
#endif
+539
View File
@@ -0,0 +1,539 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "Filter.hxx"
#include "LightSong.hxx"
#include "tag/ParseName.hxx"
#include "tag/Tag.hxx"
#include "util/CharUtil.hxx"
#include "util/ChronoUtil.hxx"
#include "util/ConstBuffer.hxx"
#include "util/RuntimeError.hxx"
#include "util/StringAPI.hxx"
#include "util/StringCompare.hxx"
#include "util/StringStrip.hxx"
#include "util/StringView.hxx"
#include "util/ASCII.hxx"
#include "util/TimeISO8601.hxx"
#include "util/UriUtil.hxx"
#include "lib/icu/CaseFold.hxx"
#include <exception>
#include <assert.h>
#include <stdlib.h>
#define LOCATE_TAG_FILE_KEY "file"
#define LOCATE_TAG_FILE_KEY_OLD "filename"
#define LOCATE_TAG_ANY_KEY "any"
/**
* Limit the search to files within the given directory.
*/
#define LOCATE_TAG_BASE_TYPE (TAG_NUM_OF_ITEM_TYPES + 1)
#define LOCATE_TAG_MODIFIED_SINCE (TAG_NUM_OF_ITEM_TYPES + 2)
#define LOCATE_TAG_FILE_TYPE TAG_NUM_OF_ITEM_TYPES+10
#define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20
/**
* @return #TAG_NUM_OF_ITEM_TYPES on error
*/
gcc_pure
static unsigned
locate_parse_type(const char *str) noexcept
{
if (StringEqualsCaseASCII(str, LOCATE_TAG_FILE_KEY) ||
StringEqualsCaseASCII(str, LOCATE_TAG_FILE_KEY_OLD))
return LOCATE_TAG_FILE_TYPE;
if (StringEqualsCaseASCII(str, LOCATE_TAG_ANY_KEY))
return LOCATE_TAG_ANY_TYPE;
if (strcmp(str, "base") == 0)
return LOCATE_TAG_BASE_TYPE;
if (strcmp(str, "modified-since") == 0)
return LOCATE_TAG_MODIFIED_SINCE;
return tag_name_parse_i(str);
}
bool
StringFilter::Match(const char *s) const noexcept
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(s != nullptr);
#endif
if (fold_case) {
return fold_case.IsIn(s);
} else {
return StringIsEqual(s, value.c_str());
}
}
std::string
UriSongFilter::ToExpression() const noexcept
{
return std::string("(" LOCATE_TAG_FILE_KEY " ") + (negated ? "!=" : "==") + " \"" + filter.GetValue() + "\")";
}
bool
UriSongFilter::Match(const LightSong &song) const noexcept
{
return filter.Match(song.GetURI().c_str()) != negated;
}
std::string
BaseSongFilter::ToExpression() const noexcept
{
return "(base \"" + value + "\")";
}
bool
BaseSongFilter::Match(const LightSong &song) const noexcept
{
return uri_is_child_or_same(value.c_str(), song.GetURI().c_str());
}
std::string
TagSongFilter::ToExpression() const noexcept
{
const char *name = type == TAG_NUM_OF_ITEM_TYPES
? LOCATE_TAG_ANY_KEY
: tag_item_names[type];
return std::string("(") + name + " " + (negated ? "!=" : "==") + " \"" + filter.GetValue() + "\")";
}
bool
TagSongFilter::MatchNN(const TagItem &item) const noexcept
{
return (type == TAG_NUM_OF_ITEM_TYPES || item.type == type) &&
filter.Match(item.value);
}
bool
TagSongFilter::MatchNN(const Tag &tag) const noexcept
{
bool visited_types[TAG_NUM_OF_ITEM_TYPES];
std::fill_n(visited_types, size_t(TAG_NUM_OF_ITEM_TYPES), false);
for (const auto &i : tag) {
visited_types[i.type] = true;
if (MatchNN(i))
return true;
}
if (type < TAG_NUM_OF_ITEM_TYPES && !visited_types[type]) {
/* If the search critieron was not visited during the
sweep through the song's tag, it means this field
is absent from the tag or empty. Thus, if the
searched string is also empty
then it's a match as well and we should return
true. */
if (filter.empty())
return true;
if (type == TAG_ALBUM_ARTIST && visited_types[TAG_ARTIST]) {
/* if we're looking for "album artist", but
only "artist" exists, use that */
for (const auto &item : tag)
if (item.type == TAG_ARTIST &&
filter.Match(item.value))
return true;
}
}
return false;
}
bool
TagSongFilter::Match(const LightSong &song) const noexcept
{
return MatchNN(song.tag) != negated;
}
std::string
ModifiedSinceSongFilter::ToExpression() const noexcept
{
return std::string("(modified-since \"") + FormatISO8601(value).c_str() + "\")";
}
bool
ModifiedSinceSongFilter::Match(const LightSong &song) const noexcept
{
return song.mtime >= value;
}
ISongFilterPtr
AndSongFilter::Clone() const noexcept
{
auto result = std::make_unique<AndSongFilter>();
for (const auto &i : items)
result->items.emplace_back(i->Clone());
return result;
}
std::string
AndSongFilter::ToExpression() const noexcept
{
auto i = items.begin();
const auto end = items.end();
if (std::next(i) == end)
return (*i)->ToExpression();
std::string e("(");
e += (*i)->ToExpression();
for (++i; i != end; ++i) {
e += " AND ";
e += (*i)->ToExpression();
}
e.push_back(')');
return e;
}
bool
AndSongFilter::Match(const LightSong &song) const noexcept
{
for (const auto &i : items)
if (!i->Match(song))
return false;
return true;
}
SongFilter::SongFilter(TagType tag, const char *value, bool fold_case)
{
and_filter.AddItem(std::make_unique<TagSongFilter>(tag, value,
fold_case, false));
}
SongFilter::~SongFilter()
{
/* this destructor exists here just so it won't get inlined */
}
std::string
SongFilter::ToExpression() const noexcept
{
return and_filter.ToExpression();
}
static std::chrono::system_clock::time_point
ParseTimeStamp(const char *s)
{
assert(s != nullptr);
char *endptr;
unsigned long long value = strtoull(s, &endptr, 10);
if (*endptr == 0 && endptr > s)
/* it's an integral UNIX time stamp */
return std::chrono::system_clock::from_time_t((time_t)value);
/* try ISO 8601 */
return ParseISO8601(s);
}
static constexpr bool
IsTagNameChar(char ch) noexcept
{
return IsAlphaASCII(ch) || ch == '_' || ch == '-';
}
static const char *
FirstNonTagNameChar(const char *s) noexcept
{
while (IsTagNameChar(*s))
++s;
return s;
}
static auto
ExpectWord(const char *&s)
{
const char *begin = s;
const char *end = FirstNonTagNameChar(s);
if (end == s)
throw std::runtime_error("Word expected");
s = StripLeft(end);
return std::string(begin, end);
}
static auto
ExpectFilterType(const char *&s)
{
const auto name = ExpectWord(s);
const auto type = locate_parse_type(name.c_str());
if (type == TAG_NUM_OF_ITEM_TYPES)
throw FormatRuntimeError("Unknown filter type: %s",
name.c_str());
return type;
}
static constexpr bool
IsQuote(char ch) noexcept
{
return ch == '"' || ch == '\'';
}
static std::string
ExpectQuoted(const char *&s)
{
const char quote = *s++;
if (!IsQuote(quote))
throw std::runtime_error("Quoted string expected");
const char *begin = s;
const char *end = strchr(s, quote);
if (end == nullptr)
throw std::runtime_error("Closing quote not found");
s = StripLeft(end + 1);
return {begin, end};
}
ISongFilterPtr
SongFilter::ParseExpression(const char *&s, bool fold_case)
{
assert(*s == '(');
s = StripLeft(s + 1);
if (*s == '(') {
auto first = ParseExpression(s, fold_case);
if (*s == ')') {
++s;
return first;
}
if (ExpectWord(s) != "AND")
throw std::runtime_error("'AND' expected");
auto and_filter = std::make_unique<AndSongFilter>();
and_filter->AddItem(std::move(first));
while (true) {
and_filter->AddItem(ParseExpression(s, fold_case));
if (*s == ')') {
++s;
return and_filter;
}
if (ExpectWord(s) != "AND")
throw std::runtime_error("'AND' expected");
}
}
auto type = ExpectFilterType(s);
if (type == LOCATE_TAG_MODIFIED_SINCE) {
const auto value_s = ExpectQuoted(s);
if (*s != ')')
throw std::runtime_error("')' expected");
s = StripLeft(s + 1);
return std::make_unique<ModifiedSinceSongFilter>(ParseTimeStamp(value_s.c_str()));
} else if (type == LOCATE_TAG_BASE_TYPE) {
auto value = ExpectQuoted(s);
if (*s != ')')
throw std::runtime_error("')' expected");
s = StripLeft(s + 1);
return std::make_unique<BaseSongFilter>(std::move(value));
} else {
bool negated = false;
if (s[0] == '!' && s[1] == '=')
negated = true;
else if (s[0] != '=' || s[1] != '=')
throw std::runtime_error("'==' or '!=' expected");
s = StripLeft(s + 2);
auto value = ExpectQuoted(s);
if (*s != ')')
throw std::runtime_error("')' expected");
s = 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);
}
}
void
SongFilter::Parse(const char *tag_string, const char *value, bool fold_case)
{
unsigned tag = locate_parse_type(tag_string);
switch (tag) {
case TAG_NUM_OF_ITEM_TYPES:
throw std::runtime_error("Unknown filter type");
case LOCATE_TAG_BASE_TYPE:
if (!uri_safe_local(value))
throw std::runtime_error("Bad URI");
and_filter.AddItem(std::make_unique<BaseSongFilter>(value));
break;
case LOCATE_TAG_MODIFIED_SINCE:
and_filter.AddItem(std::make_unique<ModifiedSinceSongFilter>(ParseTimeStamp(value)));
break;
case LOCATE_TAG_FILE_TYPE:
and_filter.AddItem(std::make_unique<UriSongFilter>(value,
fold_case,
false));
break;
default:
if (tag == LOCATE_TAG_ANY_TYPE)
tag = TAG_NUM_OF_ITEM_TYPES;
and_filter.AddItem(std::make_unique<TagSongFilter>(TagType(tag),
value,
fold_case,
false));
break;
}
}
void
SongFilter::Parse(ConstBuffer<const char *> args, bool fold_case)
{
if (args.empty())
throw std::runtime_error("Incorrect number of filter arguments");
do {
if (*args.front() == '(') {
const char *s = args.shift();
const char *end = s;
auto f = ParseExpression(end, fold_case);
if (*end != 0)
throw std::runtime_error("Unparsed garbage after expression");
and_filter.AddItem(std::move(f));
continue;
}
if (args.size < 2)
throw std::runtime_error("Incorrect number of filter arguments");
const char *tag = args.shift();
const char *value = args.shift();
Parse(tag, value, fold_case);
} while (!args.empty());
}
bool
SongFilter::Match(const LightSong &song) const noexcept
{
return and_filter.Match(song);
}
bool
SongFilter::HasFoldCase() const noexcept
{
for (const auto &i : and_filter.GetItems()) {
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
{
for (const auto &i : and_filter.GetItems()) {
const auto *f = dynamic_cast<const BaseSongFilter *>(i.get());
if (f == nullptr)
return true;
}
return false;
}
const char *
SongFilter::GetBase() const noexcept
{
for (const auto &i : and_filter.GetItems()) {
const auto *f = dynamic_cast<const BaseSongFilter *>(i.get());
if (f != nullptr)
return f->GetValue();
}
return nullptr;
}
SongFilter
SongFilter::WithoutBasePrefix(const char *_prefix) const noexcept
{
const StringView prefix(_prefix);
SongFilter result;
for (const auto &i : and_filter.GetItems()) {
const auto *f = dynamic_cast<const BaseSongFilter *>(i.get());
if (f != nullptr) {
const char *s = StringAfterPrefix(f->GetValue(), prefix);
if (s != nullptr) {
if (*s == 0)
continue;
if (*s == '/') {
++s;
if (*s != 0)
result.and_filter.AddItem(std::make_unique<BaseSongFilter>(s));
continue;
}
}
}
result.and_filter.AddItem(i->Clone());
}
return result;
}
+301
View File
@@ -0,0 +1,301 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_SONG_FILTER_HXX
#define MPD_SONG_FILTER_HXX
#include "lib/icu/Compare.hxx"
#include "Compiler.h"
#include <memory>
#include <string>
#include <list>
#include <chrono>
#include <stdint.h>
/**
* Special value for the db_selection_print() sort parameter.
*/
#define SORT_TAG_LAST_MODIFIED (TAG_NUM_OF_ITEM_TYPES + 3)
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;
/**
* This value is only set if case folding is enabled.
*/
IcuCompare fold_case;
public:
template<typename V>
StringFilter(V &&_value, bool _fold_case)
:value(std::forward<V>(_value)),
fold_case(_fold_case
? IcuCompare(value.c_str())
: IcuCompare()) {}
bool empty() const noexcept {
return value.empty();
}
const auto &GetValue() const noexcept {
return value;
}
bool GetFoldCase() const noexcept {
return fold_case;
}
gcc_pure
bool Match(const char *s) const noexcept;
};
class UriSongFilter final : public ISongFilter {
StringFilter filter;
bool negated;
public:
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;
};
class BaseSongFilter final : public ISongFilter {
std::string value;
public:
BaseSongFilter(const BaseSongFilter &) = default;
template<typename V>
explicit BaseSongFilter(V &&_value)
:value(std::forward<V>(_value)) {}
const char *GetValue() const noexcept {
return value.c_str();
}
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;
};
class TagSongFilter final : public ISongFilter {
TagType type;
bool negated;
StringFilter filter;
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) {}
TagType GetTagType() const {
return type;
}
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<TagSongFilter>(*this);
}
std::string ToExpression() const noexcept override;
bool Match(const LightSong &song) const noexcept override;
private:
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;
};
/**
* Combine multiple #ISongFilter instances with logical "and".
*/
class AndSongFilter final : public ISongFilter {
std::list<ISongFilterPtr> items;
public:
const auto &GetItems() const noexcept {
return items;
}
template<typename I>
void AddItem(I &&_item) {
items.emplace_back(std::forward<I>(_item));
}
gcc_pure
bool IsEmpty() const noexcept {
return items.empty();
}
/* virtual methods from ISongFilter */
ISongFilterPtr Clone() const noexcept override;
std::string ToExpression() const noexcept override;
bool Match(const LightSong &song) const noexcept override;
};
class SongFilter {
AndSongFilter and_filter;
public:
SongFilter() = default;
gcc_nonnull(3)
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.
*/
std::string ToExpression() const noexcept;
private:
static 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);
public:
/**
* Throws on error.
*/
void Parse(ConstBuffer<const char *> args, bool fold_case=false);
gcc_pure
bool Match(const LightSong &song) const noexcept;
const auto &GetItems() const noexcept {
return and_filter.GetItems();
}
gcc_pure
bool IsEmpty() const noexcept {
return and_filter.IsEmpty();
}
/**
* Is there at least one item with "fold case" enabled?
*/
gcc_pure
bool HasFoldCase() const noexcept;
/**
* Does this filter contain constraints other than "base"?
*/
gcc_pure
bool HasOtherThanBase() const noexcept;
/**
* Returns the "base" specification (if there is one) or
* nullptr.
*/
gcc_pure
const char *GetBase() const noexcept;
/**
* Create a copy of the filter with the given prefix stripped
* from all #LOCATE_TAG_BASE_TYPE items. This is used to
* filter songs in mounted databases.
*/
SongFilter WithoutBasePrefix(const char *prefix) const noexcept;
};
#endif
+35
View File
@@ -0,0 +1,35 @@
/*
* Copyright 2003-2017 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "LightSong.hxx"
#include "tag/Tag.hxx"
SignedSongTime
LightSong::GetDuration() const noexcept
{
SongTime a = start_time, b = end_time;
if (!b.IsPositive()) {
if (tag.duration.IsNegative())
return tag.duration;
b = SongTime(tag.duration);
}
return SignedSongTime(b - a);
}
+106
View File
@@ -0,0 +1,106 @@
/*
* Copyright 2003-2017 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_LIGHT_SONG_HXX
#define MPD_LIGHT_SONG_HXX
#include "Chrono.hxx"
#include "AudioFormat.hxx"
#include "Compiler.h"
#include <string>
#include <chrono>
struct Tag;
/**
* A reference to a song file. Unlike the other "Song" classes in the
* MPD code base, this one consists only of pointers. It is supposed
* to be as light as possible while still providing all the
* information MPD has about a song file. This class does not manage
* any memory, and the pointers become invalid quickly. Only to be
* used to pass around during well-defined situations.
*/
struct LightSong {
/**
* If this is not nullptr, then it denotes a prefix for the
* #uri. To build the full URI, join directory and uri with a
* slash.
*/
const char *directory = nullptr;
const char *uri;
/**
* The "real" URI, the one to be used for opening the
* resource. If this attribute is nullptr, then #uri (and
* #directory) shall be used.
*
* This attribute is used for songs from the database which
* have a relative URI.
*/
const char *real_uri = nullptr;
/**
* Metadata.
*/
const Tag &tag;
/**
* The time stamp of the last file modification. A negative
* value means that this is unknown/unavailable.
*/
std::chrono::system_clock::time_point mtime = std::chrono::system_clock::time_point::min();
/**
* Start of this sub-song within the file.
*/
SongTime start_time = SongTime::zero();
/**
* End of this sub-song within the file.
* Unused if zero.
*/
SongTime end_time = SongTime::zero();
/**
* The audio format of the song, if given by the decoder
* plugin. May be undefined if unknown.
*/
AudioFormat audio_format = AudioFormat::Undefined();
LightSong(const char *_uri, const Tag &_tag) noexcept
:uri(_uri), tag(_tag) {}
gcc_pure
std::string GetURI() const noexcept {
if (directory == nullptr)
return std::string(uri);
std::string result(directory);
result.push_back('/');
result.append(uri);
return result;
}
gcc_pure
SignedSongTime GetDuration() const noexcept;
};
#endif