diff --git a/NEWS b/NEWS index f869e6875f..c1d1e3f88e 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,7 @@ ver 0.24 (not yet released) - apply Unicode normalization to case-insensitive filter expressions - stickers on playlists and some tag types - new command "stickernames" + - "sticker find" supports sort and window parameter * database - attribute "added" shows when each song was added to the database - proxy: require MPD 0.21 or later diff --git a/doc/protocol.rst b/doc/protocol.rst index 214ab7520f..86b1c60e21 100644 --- a/doc/protocol.rst +++ b/doc/protocol.rst @@ -1462,15 +1462,17 @@ the database for songs). .. _command_sticker_find: -:command:`sticker find {TYPE} {URI} {NAME}` +:command:`sticker find {TYPE} {URI} {NAME} [sort {SORTTYPE}] [window {START:END}]` Searches the sticker database for stickers with the specified name, below the specified directory (URI). For each matching song, it prints the URI and that one sticker's value. + ``sort`` sorts the result by "``uri``" or "``value``". [#since_0_24]_ + .. _command_sticker_find_value: -:command:`sticker find {TYPE} {URI} {NAME} = {VALUE}` +:command:`sticker find {TYPE} {URI} {NAME} = {VALUE} [sort {SORTTYPE}] [window {START:END}]` Searches for stickers with the given value. Other supported operators are: diff --git a/src/command/StickerCommands.cxx b/src/command/StickerCommands.cxx index 4f74049a08..d08c673daa 100644 --- a/src/command/StickerCommands.cxx +++ b/src/command/StickerCommands.cxx @@ -78,7 +78,8 @@ class DomainHandler { return CommandResult::OK; } - virtual CommandResult Find(const char *uri, const char *name, StickerOperator op, const char *value) { + virtual CommandResult Find(const char *uri, const char *name, StickerOperator op, const char *value, + const char *sort, bool descending, RangeArg window) { auto data = CallbackContext{ .name = name, .sticker_type = sticker_type, @@ -97,6 +98,7 @@ class DomainHandler { uri, name, op, value, + sort, descending, window, callback, &data); return CommandResult::OK; @@ -172,7 +174,8 @@ class SongHandler final : public DomainHandler { database.ReturnSong(song); } - CommandResult Find(const char *uri, const char *name, StickerOperator op, const char *value) override { + CommandResult Find(const char *uri, const char *name, StickerOperator op, const char *value, + const char *sort, bool descending, RangeArg window) override { struct sticker_song_find_data data = { response, name, @@ -180,6 +183,7 @@ class SongHandler final : public DomainHandler { sticker_song_find(sticker_database, database, uri, data.name, op, value, + sort, descending, window, sticker_song_find_print_cb, &data); return CommandResult::OK; @@ -386,7 +390,34 @@ handle_sticker(Client &client, Request args, Response &r) return handler->Delete(uri, sticker_name); /* find */ - if ((args.size() == 4 || args.size() == 6) && StringIsEqual(cmd, "find")) { + if (args.size() >= 4 && StringIsEqual(cmd, "find")) { + RangeArg window = RangeArg::All(); + if (args.size() >= 6 && StringIsEqual(args[args.size() - 2], "window")) { + window = args.ParseRange(args.size() - 1); + args.pop_back(); + args.pop_back(); + } + + auto sort = ""; + bool descending = false; + if (args.size() >= 6 && StringIsEqual(args[args.size() - 2], "sort")) { + const char *s = args.back(); + if (*s == '-') { + descending = true; + ++s; + } + if (StringIsEqual(s, "uri") || StringIsEqual(s, "value")) { + sort = s; + } + else { + r.FmtError(ACK_ERROR_ARG, "Unknown sort tag \"{}\"", s); + return CommandResult::ERROR; + } + + args.pop_back(); + args.pop_back(); + } + bool has_op = args.size() > 4; auto value = has_op ? args[5] : nullptr; StickerOperator op = StickerOperator::EXISTS; @@ -404,7 +435,7 @@ handle_sticker(Client &client, Request args, Response &r) return CommandResult::ERROR; } } - return handler->Find(uri, sticker_name, op, value); + return handler->Find(uri, sticker_name, op, value, sort, descending, window); } r.Error(ACK_ERROR_ARG, "bad request"); diff --git a/src/sticker/Database.cxx b/src/sticker/Database.cxx index c5b3429e22..d259e82672 100644 --- a/src/sticker/Database.cxx +++ b/src/sticker/Database.cxx @@ -10,6 +10,7 @@ #include "util/StringCompare.hxx" #include "util/ScopeExit.hxx" +#include #include #include #include @@ -17,6 +18,15 @@ using namespace Sqlite; +enum sticker_sql_find { + STICKER_SQL_FIND, + STICKER_SQL_FIND_VALUE, + STICKER_SQL_FIND_LT, + STICKER_SQL_FIND_GT, + + STICKER_SQL_FIND_COUNT +}; + enum sticker_sql { STICKER_SQL_GET, STICKER_SQL_LIST, @@ -24,10 +34,6 @@ enum sticker_sql { STICKER_SQL_INSERT, STICKER_SQL_DELETE, STICKER_SQL_DELETE_VALUE, - STICKER_SQL_FIND, - STICKER_SQL_FIND_VALUE, - STICKER_SQL_FIND_LT, - STICKER_SQL_FIND_GT, STICKER_SQL_DISTINCT_TYPE_URI, STICKER_SQL_TRANSACTION_BEGIN, STICKER_SQL_TRANSACTION_COMMIT, @@ -37,6 +43,20 @@ enum sticker_sql { STICKER_SQL_COUNT }; +static constexpr auto sticker_sql_find = std::array { + //[STICKER_SQL_FIND] = + "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?", + + //[STICKER_SQL_FIND_VALUE] = + "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value=?", + + //[STICKER_SQL_FIND_LT] = + "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value?", +}; + static constexpr auto sticker_sql = std::array { //[STICKER_SQL_GET] = "SELECT value FROM sticker WHERE type=? AND uri=? AND name=?", @@ -50,17 +70,6 @@ static constexpr auto sticker_sql = std::array { "DELETE FROM sticker WHERE type=? AND uri=?", //[STICKER_SQL_DELETE_VALUE] = "DELETE FROM sticker WHERE type=? AND uri=? AND name=?", - //[STICKER_SQL_FIND] = - "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?", - - //[STICKER_SQL_FIND_VALUE] = - "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value=?", - - //[STICKER_SQL_FIND_LT] = - "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value?", //[STICKER_SQL_DISTINCT_TYPE_URI] = "SELECT DISTINCT type,uri FROM sticker", @@ -297,7 +306,8 @@ StickerDatabase::Load(const char *type, const char *uri) sqlite3_stmt * StickerDatabase::BindFind(const char *type, const char *base_uri, const char *name, - StickerOperator op, const char *value) + StickerOperator op, const char *value, + const char *sort, bool descending, RangeArg window) { assert(type != nullptr); assert(name != nullptr); @@ -305,25 +315,47 @@ StickerDatabase::BindFind(const char *type, const char *base_uri, if (base_uri == nullptr) base_uri = ""; + auto order_by = sort[0] == '\0' + ? std::string() + : fmt::format("order by {} {}", sort, descending ? "desc" : "asc"); + + auto offset = window.IsAll() + ? std::string() + : window.IsOpenEnded() + ? fmt::format("limit -1 offset {}", window.start) + : fmt::format("limit {} offset {}", window.Count(), window.start); + + std::string sql_str; + sqlite3_stmt *sql; + switch (op) { case StickerOperator::EXISTS: - BindAll(stmt[STICKER_SQL_FIND], type, base_uri, name); - return stmt[STICKER_SQL_FIND]; + sql_str = fmt::format("{} {} {}", + sticker_sql_find[STICKER_SQL_FIND], order_by, offset); + sql = Prepare(db, sql_str.c_str()); + BindAll(sql, type, base_uri, name); + return sql; case StickerOperator::EQUALS: - BindAll(stmt[STICKER_SQL_FIND_VALUE], - type, base_uri, name, value); - return stmt[STICKER_SQL_FIND_VALUE]; + sql_str = fmt::format("{} {} {}", + sticker_sql_find[STICKER_SQL_FIND_VALUE], order_by, offset); + sql = Prepare(db, sql_str.c_str()); + BindAll(sql, type, base_uri, name, value); + return sql; case StickerOperator::LESS_THAN: - BindAll(stmt[STICKER_SQL_FIND_LT], - type, base_uri, name, value); - return stmt[STICKER_SQL_FIND_LT]; + sql_str = fmt::format("{} {} {}", + sticker_sql_find[STICKER_SQL_FIND_LT], order_by, offset); + sql = Prepare(db, sql_str.c_str()); + BindAll(sql, type, base_uri, name, value); + return sql; case StickerOperator::GREATER_THAN: - BindAll(stmt[STICKER_SQL_FIND_GT], - type, base_uri, name, value); - return stmt[STICKER_SQL_FIND_GT]; + sql_str = fmt::format("{} {} {}", + sticker_sql_find[STICKER_SQL_FIND_GT], order_by, offset); + sql = Prepare(db, sql_str.c_str()); + BindAll(sql, type, base_uri, name, value); + return sql; } assert(false); @@ -333,18 +365,18 @@ StickerDatabase::BindFind(const char *type, const char *base_uri, void StickerDatabase::Find(const char *type, const char *base_uri, const char *name, StickerOperator op, const char *value, + const char *sort, bool descending, RangeArg window, void (*func)(const char *uri, const char *value, void *user_data), void *user_data) { assert(func != nullptr); - sqlite3_stmt *const s = BindFind(type, base_uri, name, op, value); + sqlite3_stmt *const s = BindFind(type, base_uri, name, op, value, sort, descending, window); assert(s != nullptr); AtScopeExit(s) { - sqlite3_reset(s); - sqlite3_clear_bindings(s); + sqlite3_finalize(s); }; ExecuteForEach(s, [s, func, user_data](){ diff --git a/src/sticker/Database.hxx b/src/sticker/Database.hxx index 8d7f8d28f7..a7a1987a96 100644 --- a/src/sticker/Database.hxx +++ b/src/sticker/Database.hxx @@ -28,6 +28,7 @@ #include "Match.hxx" #include "lib/sqlite/Database.hxx" +#include "protocol/RangeArg.hxx" #include @@ -46,10 +47,6 @@ class StickerDatabase { SQL_INSERT, SQL_DELETE, SQL_DELETE_VALUE, - SQL_FIND, - SQL_FIND_VALUE, - SQL_FIND_LT, - SQL_FIND_GT, SQL_DISTINCT_TYPE_URI, SQL_TRANSACTION_BEGIN, SQL_TRANSACTION_COMMIT, @@ -59,6 +56,15 @@ class StickerDatabase { SQL_COUNT }; + enum SQL_FIND { + SQL_FIND, + SQL_FIND_VALUE, + SQL_FIND_LT, + SQL_FIND_GT, + + SQL_FIND_COUNT + }; + std::string path; Sqlite::Database db; @@ -143,6 +149,7 @@ public: */ void Find(const char *type, const char *base_uri, const char *name, StickerOperator op, const char *value, + const char *sort, bool descending, RangeArg window, void (*func)(const char *uri, const char *value, void *user_data), void *user_data); @@ -178,7 +185,8 @@ private: sqlite3_stmt *BindFind(const char *type, const char *base_uri, const char *name, - StickerOperator op, const char *value); + StickerOperator op, const char *value, + const char *sort, bool descending, RangeArg window); }; #endif diff --git a/src/sticker/SongSticker.cxx b/src/sticker/SongSticker.cxx index c5a168f54d..401ad81cc8 100644 --- a/src/sticker/SongSticker.cxx +++ b/src/sticker/SongSticker.cxx @@ -92,6 +92,7 @@ void sticker_song_find(StickerDatabase &sticker_database, const Database &db, const char *base_uri, const char *name, StickerOperator op, const char *value, + const char *sort, bool descending, RangeArg window, void (*func)(const LightSong &song, const char *value, void *user_data), void *user_data) @@ -114,5 +115,6 @@ sticker_song_find(StickerDatabase &sticker_database, const Database &db, data.base_uri_length = strlen(data.base_uri); sticker_database.Find("song", data.base_uri, name, op, value, + sort, descending, window, sticker_song_find_cb, &data); } diff --git a/src/sticker/SongSticker.hxx b/src/sticker/SongSticker.hxx index a29e21b3c2..2d6587bb31 100644 --- a/src/sticker/SongSticker.hxx +++ b/src/sticker/SongSticker.hxx @@ -5,6 +5,7 @@ #define MPD_SONG_STICKER_HXX #include "Match.hxx" +#include "protocol/RangeArg.hxx" #include @@ -80,6 +81,7 @@ void sticker_song_find(StickerDatabase &sticker_database, const Database &db, const char *base_uri, const char *name, StickerOperator op, const char *value, + const char *sort, bool descending, RangeArg window, void (*func)(const LightSong &song, const char *value, void *user_data), void *user_data);