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;