diff --git a/NEWS b/NEWS index 2672c84d5..4351f3d99 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ ver 0.19 (not yet released) - "idle" with unrecognized event name fails - "list" on album artist falls back to the artist tag - "list" and "count" allow grouping + - new "search"/"find" filter "modified-since" * database - proxy: forward "idle" events - proxy: forward the "update" command diff --git a/doc/protocol.xml b/doc/protocol.xml index afeffd1ae..cff685060 100644 --- a/doc/protocol.xml +++ b/doc/protocol.xml @@ -1553,7 +1553,7 @@ OK Finds songs in the db that are exactly WHAT. TYPE can - be any tag supported by MPD, or one of the three special + be any tag supported by MPD, or one of the special parameters: @@ -1578,6 +1578,14 @@ OK music directory) + + + + modified-since compares the + file's time stamp with the given value (ISO 8601 or + UNIX time stamp) + + diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx index 7a8e6fd12..794cb9208 100644 --- a/src/SongFilter.cxx +++ b/src/SongFilter.cxx @@ -48,6 +48,9 @@ locate_parse_type(const char *str) 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); } @@ -66,6 +69,11 @@ 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 { @@ -128,6 +136,9 @@ 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()); @@ -142,6 +153,9 @@ SongFilter::Item::Match(const LightSong &song) const 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()); @@ -160,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) { @@ -175,6 +241,15 @@ 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; } diff --git a/src/SongFilter.hxx b/src/SongFilter.hxx index ca7d7bd90..f51bd85c6 100644 --- a/src/SongFilter.hxx +++ b/src/SongFilter.hxx @@ -26,11 +26,13 @@ #include #include +#include /** * 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 @@ -51,9 +53,15 @@ public: std::string value; + /** + * For #LOCATE_TAG_MODIFIED_SINCE + */ + time_t time; + public: gcc_nonnull(3) Item(unsigned tag, const char *value, bool fold_case=false); + Item(unsigned tag, time_t time); Item(const Item &other) = delete; Item(Item &&) = default;