From 5582367d68b4fbd53cb809b4018bc54d25c1cd6c Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@musicpd.org>
Date: Mon, 18 Dec 2017 21:36:47 +0100
Subject: [PATCH] db/DatabasePrint: support sorting by "modified-since"

Closes #172
---
 doc/protocol.xml                 |  3 ++-
 src/SongFilter.hxx               |  5 +++++
 src/command/DatabaseCommands.cxx | 17 ++++++++++++++---
 src/db/DatabasePrint.cxx         | 22 +++++++++++++++-------
 src/db/DatabasePrint.hxx         |  4 ++++
 5 files changed, 40 insertions(+), 11 deletions(-)

diff --git a/doc/protocol.xml b/doc/protocol.xml
index 480fbf90a..16f6b0683 100644
--- a/doc/protocol.xml
+++ b/doc/protocol.xml
@@ -1702,7 +1702,8 @@ OK
               "ArtistSort", "AlbumSort" or "AlbumArtistSort" instead.
               These will automatically fall back to the former if
               "*Sort" doesn't exist.  "AlbumArtist" falls back to just
-              "Artist".
+              "Artist".  The type "Last-Modified" can sort by file
+              modification time.
             </para>
 
             <para>
diff --git a/src/SongFilter.hxx b/src/SongFilter.hxx
index b18d7a184..1dfffeeab 100644
--- a/src/SongFilter.hxx
+++ b/src/SongFilter.hxx
@@ -35,6 +35,11 @@
 #define LOCATE_TAG_BASE_TYPE (TAG_NUM_OF_ITEM_TYPES + 1)
 #define LOCATE_TAG_MODIFIED_SINCE (TAG_NUM_OF_ITEM_TYPES + 2)
 
+/**
+ * Special value for the db_selection_print() sort parameter.
+ */
+#define SORT_TAG_LAST_MODIFIED (TAG_NUM_OF_ITEM_TYPES + 3)
+
 #define LOCATE_TAG_FILE_TYPE	TAG_NUM_OF_ITEM_TYPES+10
 #define LOCATE_TAG_ANY_TYPE     TAG_NUM_OF_ITEM_TYPES+20
 
diff --git a/src/command/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx
index a6e56dfc6..9ab41958b 100644
--- a/src/command/DatabaseCommands.cxx
+++ b/src/command/DatabaseCommands.cxx
@@ -55,6 +55,19 @@ handle_lsinfo2(Client &client, const char *uri, Response &r)
 	return CommandResult::OK;
 }
 
+static TagType
+ParseSortTag(const char *s)
+{
+	if (StringIsEqualIgnoreCase(s, "Last-Modified"))
+		return TagType(SORT_TAG_LAST_MODIFIED);
+
+	TagType tag = tag_name_parse_i(s);
+	if (tag == TAG_NUM_OF_ITEM_TYPES)
+		throw ProtocolError(ACK_ERROR_ARG, "Unknown sort tag");
+
+	return tag;
+}
+
 static CommandResult
 handle_match(Client &client, Request args, Response &r, bool fold_case)
 {
@@ -76,9 +89,7 @@ handle_match(Client &client, Request args, Response &r, bool fold_case)
 			++s;
 		}
 
-		sort = tag_name_parse_i(s);
-		if (sort == TAG_NUM_OF_ITEM_TYPES)
-			throw ProtocolError(ACK_ERROR_ARG, "Unknown sort tag");
+		sort = ParseSortTag(s);
 
 		args.pop_back();
 		args.pop_back();
diff --git a/src/db/DatabasePrint.cxx b/src/db/DatabasePrint.cxx
index a8f32aeb1..45ad05d68 100644
--- a/src/db/DatabasePrint.cxx
+++ b/src/db/DatabasePrint.cxx
@@ -220,13 +220,21 @@ db_selection_print(Response &r, Partition &partition,
 			db.Visit(selection, d, collect_songs, p);
 		}
 
-		std::stable_sort(songs.begin(), songs.end(),
-				 [sort, descending](const DetachedSong &a,
-						    const DetachedSong &b){
-					 return CompareTags(sort, descending,
-							    a.GetTag(),
-							    b.GetTag());
-				 });
+		if (sort == TagType(SORT_TAG_LAST_MODIFIED))
+			std::stable_sort(songs.begin(), songs.end(),
+					 [descending](const DetachedSong &a, const DetachedSong &b){
+						 return descending
+							 ? a.GetLastModified() > b.GetLastModified()
+							 : a.GetLastModified() < b.GetLastModified();
+					 });
+		else
+			std::stable_sort(songs.begin(), songs.end(),
+					 [sort, descending](const DetachedSong &a,
+							    const DetachedSong &b){
+						 return CompareTags(sort, descending,
+								    a.GetTag(),
+								    b.GetTag());
+					 });
 
 		if (window_end < songs.size())
 			songs.erase(std::next(songs.begin(), window_end),
diff --git a/src/db/DatabasePrint.hxx b/src/db/DatabasePrint.hxx
index 7612b9319..7026308b7 100644
--- a/src/db/DatabasePrint.hxx
+++ b/src/db/DatabasePrint.hxx
@@ -38,6 +38,10 @@ db_selection_print(Response &r, Partition &partition,
 		   const DatabaseSelection &selection,
 		   bool full, bool base);
 
+/**
+ * @param sort the sort tag; TAG_NUM_OF_ITEM_TYPES means don't sort;
+ * LOCATE_TAG_MODIFIED_SINCE means sort by file modification time
+ */
 void
 db_selection_print(Response &r, Partition &partition,
 		   const DatabaseSelection &selection,