diff options
Diffstat (limited to 'src/SongFilter.cxx')
-rw-r--r-- | src/SongFilter.cxx | 148 |
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)) |