aboutsummaryrefslogtreecommitdiffstats
path: root/src/SongFilter.cxx
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/SongFilter.cxx148
1 files changed, 116 insertions, 32 deletions
diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx
index 235dfe7a0..794cb9208 100644
--- a/src/SongFilter.cxx
+++ b/src/SongFilter.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,12 +19,13 @@
#include "config.h"
#include "SongFilter.hxx"
-#include "Song.hxx"
+#include "db/LightSong.hxx"
+#include "DetachedSong.hxx"
#include "tag/Tag.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/ASCII.hxx"
#include "util/UriUtil.hxx"
-
-#include <glib.h>
+#include "lib/icu/Collate.hxx"
#include <assert.h>
#include <string.h>
@@ -47,17 +48,10 @@ locate_parse_type(const char *str)
if (strcmp(str, "base") == 0)
return LOCATE_TAG_BASE_TYPE;
- return tag_name_parse_i(str);
-}
+ if (strcmp(str, "modified-since") == 0)
+ return LOCATE_TAG_MODIFIED_SINCE;
-gcc_pure
-static std::string
-CaseFold(const char *p)
-{
- char *q = g_utf8_casefold(p, -1);
- std::string result(q);
- g_free(q);
- return result;
+ return tag_name_parse_i(str);
}
gcc_pure
@@ -65,7 +59,7 @@ static std::string
ImportString(const char *p, bool fold_case)
{
return fold_case
- ? CaseFold(p)
+ ? IcuCaseFold(p)
: std::string(p);
}
@@ -75,16 +69,19 @@ SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case)
{
}
+SongFilter::Item::Item(unsigned _tag, time_t _time)
+ :tag(_tag), time(_time)
+{
+}
+
bool
SongFilter::Item::StringMatch(const char *s) const
{
assert(s != nullptr);
if (fold_case) {
- char *p = g_utf8_casefold(s, -1);
- const bool result = strstr(p, value.c_str()) != NULL;
- g_free(p);
- return result;
+ const std::string folded = IcuCaseFold(s);
+ return folded.find(value) != folded.npos;
} else {
return s == value;
}
@@ -103,10 +100,10 @@ SongFilter::Item::Match(const Tag &_tag) const
bool visited_types[TAG_NUM_OF_ITEM_TYPES];
std::fill_n(visited_types, size_t(TAG_NUM_OF_ITEM_TYPES), false);
- for (unsigned i = 0; i < _tag.num_items; i++) {
- visited_types[_tag.items[i]->type] = true;
+ for (const auto &i : _tag) {
+ visited_types[i.type] = true;
- if (Match(*_tag.items[i]))
+ if (Match(i))
return true;
}
@@ -123,12 +120,10 @@ SongFilter::Item::Match(const Tag &_tag) const
if (tag == TAG_ALBUM_ARTIST && visited_types[TAG_ARTIST]) {
/* if we're looking for "album artist", but
only "artist" exists, use that */
- for (unsigned i = 0; i < _tag.num_items; i++) {
- const TagItem &item = *_tag.items[i];
+ for (const auto &item : _tag)
if (item.type == TAG_ARTIST &&
StringMatch(item.value))
return true;
- }
}
}
@@ -136,19 +131,37 @@ SongFilter::Item::Match(const Tag &_tag) const
}
bool
-SongFilter::Item::Match(const Song &song) const
+SongFilter::Item::Match(const DetachedSong &song) const
+{
+ if (tag == LOCATE_TAG_BASE_TYPE)
+ return uri_is_child_or_same(value.c_str(), song.GetURI());
+
+ if (tag == LOCATE_TAG_MODIFIED_SINCE)
+ return song.GetLastModified() >= time;
+
+ if (tag == LOCATE_TAG_FILE_TYPE)
+ return StringMatch(song.GetURI());
+
+ return Match(song.GetTag());
+}
+
+bool
+SongFilter::Item::Match(const LightSong &song) const
{
if (tag == LOCATE_TAG_BASE_TYPE) {
const auto uri = song.GetURI();
return uri_is_child_or_same(value.c_str(), uri.c_str());
}
+ if (tag == LOCATE_TAG_MODIFIED_SINCE)
+ return song.mtime >= time;
+
if (tag == LOCATE_TAG_FILE_TYPE) {
const auto uri = song.GetURI();
return StringMatch(uri.c_str());
}
- return song.tag != NULL && Match(*song.tag);
+ return Match(*song.tag);
}
SongFilter::SongFilter(unsigned tag, const char *value, bool fold_case)
@@ -161,6 +174,58 @@ SongFilter::~SongFilter()
/* this destructor exists here just so it won't get inlined */
}
+#if !defined(__GLIBC__) && !defined(WIN32)
+
+/**
+ * Determine the time zone offset in a portable way.
+ */
+gcc_const
+static time_t
+GetTimeZoneOffset()
+{
+ time_t t = 1234567890;
+ struct tm tm;
+ tm.tm_isdst = 0;
+ gmtime_r(&t, &tm);
+ return t - mktime(&tm);
+}
+
+#endif
+
+gcc_pure
+static time_t
+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 (time_t)value;
+
+#ifdef WIN32
+ /* TODO: emulate strptime()? */
+ return 0;
+#else
+ /* try ISO 8601 */
+
+ struct tm tm;
+ const char *end = strptime(s, "%FT%TZ", &tm);
+ if (end == nullptr || *end != 0)
+ return 0;
+
+#ifdef __GLIBC__
+ /* timegm() is a GNU extension */
+ return timegm(&tm);
+#else
+ tm.tm_isdst = 0;
+ return mktime(&tm) + GetTimeZoneOffset();
+#endif /* !__GLIBC__ */
+
+#endif /* !WIN32 */
+}
+
bool
SongFilter::Parse(const char *tag_string, const char *value, bool fold_case)
{
@@ -176,25 +241,44 @@ SongFilter::Parse(const char *tag_string, const char *value, bool fold_case)
fold_case = false;
}
+ if (tag == LOCATE_TAG_MODIFIED_SINCE) {
+ time_t t = ParseTimeStamp(value);
+ if (t == 0)
+ return false;
+
+ items.push_back(Item(tag, t));
+ return true;
+ }
+
items.push_back(Item(tag, value, fold_case));
return true;
}
bool
-SongFilter::Parse(unsigned argc, char *argv[], bool fold_case)
+SongFilter::Parse(ConstBuffer<const char *> args, bool fold_case)
{
- if (argc == 0 || argc % 2 != 0)
+ if (args.size == 0 || args.size % 2 != 0)
return false;
- for (unsigned i = 0; i < argc; i += 2)
- if (!Parse(argv[i], argv[i + 1], fold_case))
+ for (unsigned i = 0; i < args.size; i += 2)
+ if (!Parse(args[i], args[i + 1], fold_case))
+ return false;
+
+ return true;
+}
+
+bool
+SongFilter::Match(const DetachedSong &song) const
+{
+ for (const auto &i : items)
+ if (!i.Match(song))
return false;
return true;
}
bool
-SongFilter::Match(const Song &song) const
+SongFilter::Match(const LightSong &song) const
{
for (const auto &i : items)
if (!i.Match(song))