From 26c4211cd48155ff78feb0c30bee73a0be542d3e Mon Sep 17 00:00:00 2001 From: jcorporation Date: Fri, 30 Aug 2024 20:10:08 +0200 Subject: [PATCH] New command stickernamestypes Lists sticker names with their types and optionally filters by type --- NEWS | 2 +- doc/protocol.rst | 3 ++ src/command/AllCommands.cxx | 1 + src/command/StickerCommands.cxx | 56 +++++++++++++++++++++++++++++++++ src/command/StickerCommands.hxx | 2 ++ src/sticker/Database.cxx | 33 ++++++++++++++++++- src/sticker/Database.hxx | 7 +++++ 7 files changed, 102 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index bf3fba46db..ab17c6a68c 100644 --- a/NEWS +++ b/NEWS @@ -10,7 +10,7 @@ ver 0.24 (not yet released) - show PCRE support in "config" response - apply Unicode normalization to case-insensitive filter expressions - stickers on playlists and some tag types - - new commands "stickernames", "stickertypes", "playlistlength", "searchplaylist" + - new commands "stickernames", "stickertypes", "stickernamestypes", "playlistlength", "searchplaylist" - new "search"/"find" filter "added-since" - allow range in listplaylist and listplaylistinfo - "sticker find" supports sort and window parameter and new sticker compare operators "eq", "lt" and "gt" diff --git a/doc/protocol.rst b/doc/protocol.rst index b938e4ec05..f590630272 100644 --- a/doc/protocol.rst +++ b/doc/protocol.rst @@ -1590,6 +1590,9 @@ Examples: :command:`stickertypes` Shows a list of available sticker types. +:command:`stickernamestypes [TYPE]` + Gets a list of uniq sticker names and their types. + Connection settings =================== diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx index a97bed4cf7..dd889d1011 100644 --- a/src/command/AllCommands.cxx +++ b/src/command/AllCommands.cxx @@ -190,6 +190,7 @@ static constexpr struct command commands[] = { { "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker }, { "stickernames", PERMISSION_ADMIN, 0, 0, handle_sticker_names }, { "stickertypes", PERMISSION_ADMIN, 0, 0, handle_sticker_types }, + { "stickernamestypes", PERMISSION_ADMIN, 0, 1, handle_sticker_names_types }, #endif { "stop", PERMISSION_PLAYER, 0, 0, handle_stop }, { "subscribe", PERMISSION_READ, 1, 1, handle_subscribe }, diff --git a/src/command/StickerCommands.cxx b/src/command/StickerCommands.cxx index 9a2944322c..009756e983 100644 --- a/src/command/StickerCommands.cxx +++ b/src/command/StickerCommands.cxx @@ -124,6 +124,25 @@ class DomainHandler { return CommandResult::OK; } + CommandResult NamesTypes(const char *type) { + auto data = CallbackContext{ + .name = "", + .sticker_type = sticker_type, + .response = response, + .is_song = StringIsEqual("song", sticker_type) + }; + + auto callback = [](const char *found_value, const char *found_type, void *user_data) { + auto context = reinterpret_cast(user_data); + context->response.Fmt("name: {}\n", found_value); + context->response.Fmt("type: {}\n", found_type); + }; + + sticker_database.NamesTypes(type, callback, &data); + + return CommandResult::OK; + } + protected: DomainHandler(Response &_response, const Database &_database, @@ -334,6 +353,43 @@ handle_sticker_names(Client &client, Request args, Response &r) return handler->Names(); } +CommandResult +handle_sticker_names_types(Client &client, Request args, Response &r) +{ + auto &instance = client.GetInstance(); + if (!instance.HasStickerDatabase()) { + r.Error(ACK_ERROR_UNKNOWN, "sticker database is disabled"); + return CommandResult::ERROR; + } + + auto &db = client.GetPartition().GetDatabaseOrThrow(); + auto &sticker_database = *instance.sticker_database; + + auto type = args.GetOptional(0); + std::unique_ptr handler = std::make_unique(r, db, sticker_database); + + if (type == nullptr || + StringIsEqual(type, "song") || + StringIsEqual(type, "playlist") || + StringIsEqual(type, "filter")) { + return handler->NamesTypes(type); + } + auto tag_type = tag_name_parse(type); + if (tag_type == TAG_NUM_OF_ITEM_TYPES) { + r.FmtError(ACK_ERROR_ARG, "no such tag {:?}", type); + return CommandResult::ERROR; + } + else if (sticker_allowed_tags.Test(tag_type)) { + return handler->NamesTypes(type); + } + else { + r.FmtError(ACK_ERROR_ARG, "unsupported tag {:?}", type); + return CommandResult::ERROR; + } + r.FmtError(ACK_ERROR_ARG, "unknown sticker domain {:?}", type); + return CommandResult::ERROR; +} + CommandResult handle_sticker(Client &client, Request args, Response &r) { diff --git a/src/command/StickerCommands.hxx b/src/command/StickerCommands.hxx index 8b5d4efa14..c3371794f6 100644 --- a/src/command/StickerCommands.hxx +++ b/src/command/StickerCommands.hxx @@ -16,5 +16,7 @@ CommandResult handle_sticker_names(Client &client, Request request, Response &response); CommandResult handle_sticker_types(Client &client, Request request, Response &response); +CommandResult +handle_sticker_names_types(Client &client, Request request, Response &response); #endif diff --git a/src/sticker/Database.cxx b/src/sticker/Database.cxx index 71d4308c74..66612a58ec 100644 --- a/src/sticker/Database.cxx +++ b/src/sticker/Database.cxx @@ -46,6 +46,8 @@ enum sticker_sql { STICKER_SQL_TRANSACTION_COMMIT, STICKER_SQL_TRANSACTION_ROLLBACK, STICKER_SQL_NAMES, + STICKER_SQL_NAMES_TYPES, + STICKER_SQL_NAMES_TYPES_BY_TYPE, STICKER_SQL_COUNT }; @@ -106,7 +108,13 @@ static constexpr auto sticker_sql = std::array { "ROLLBACK", //[STICKER_SQL_NAMES] - "SELECT DISTINCT name FROM sticker order by name", + "SELECT DISTINCT name FROM sticker ORDER BY name", + + //[STICKER_SQL_NAMES_TYPES] + "SELECT name,type FROM sticker GROUP BY name,type ORDER BY name", + + //[STICKER_SQL_NAMES_TYPES_BY_TYPE] + "SELECT name,type FROM sticker WHERE type = ? GROUP BY name,type ORDER BY name", }; static constexpr const char sticker_sql_create[] = @@ -478,6 +486,29 @@ StickerDatabase::Names(void (*func)(const char *value, void *user_data), void *u }); } +void +StickerDatabase::NamesTypes(const char *type, void (*func)(const char *value, const char *type, void *user_data), void *user_data) +{ + assert(func != nullptr); + + sqlite3_stmt *const s = type == nullptr + ? stmt[STICKER_SQL_NAMES_TYPES] + : stmt[STICKER_SQL_NAMES_TYPES_BY_TYPE]; + assert(s != nullptr); + + if (type != nullptr) + BindAll(s, type); + + AtScopeExit(s) { + sqlite3_reset(s); + }; + + ExecuteForEach(s, [s, func, user_data](){ + func((const char*)sqlite3_column_text(s, 0), + (const char*)sqlite3_column_text(s, 1), user_data); + }); +} + void StickerDatabase::BatchDeleteNoIdle(const std::list &stickers) { diff --git a/src/sticker/Database.hxx b/src/sticker/Database.hxx index 91281b3d71..cf0958dcae 100644 --- a/src/sticker/Database.hxx +++ b/src/sticker/Database.hxx @@ -52,6 +52,8 @@ class StickerDatabase { SQL_TRANSACTION_COMMIT, SQL_TRANSACTION_ROLLBACK, SQL_NAMES, + SQL_NAMES_TYPES, + SQL_NAMES_TYPES_BY_TYPE, SQL_COUNT }; @@ -166,6 +168,11 @@ public: */ void Names(void (*func)(const char *value, void *user_data), void *user_data); + /** + * Uniq and sorted list of all sticker names by type + */ + void NamesTypes(const char *type, void (*func)(const char *value, const char *type, void *user_data), void *user_data); + using StickerTypeUriPair = std::pair; /**