From 35b4d6f196ffbbb4d7603c997f685672a166b4f8 Mon Sep 17 00:00:00 2001 From: jcorporation Date: Sat, 23 Dec 2023 15:00:17 +0100 Subject: [PATCH] New sticker find api with support for new MPD 0.24 protocol - Arguments sort and window - Filter operators contains, starts_with - Support for integer sticker values This commit moves also some common search functions from src/search.c to src/isearch.c. --- NEWS | 1 + include/mpd/sticker.h | 111 ++++++++++++++++++++++++++ libmpdclient.ld | 6 ++ meson.build | 1 + src/isearch.c | 166 +++++++++++++++++++++++++++++++++++++++ src/isearch.h | 39 +++++++++ src/search.c | 178 ++++++------------------------------------ src/sticker.c | 130 ++++++++++++++++++++++++++++++ 8 files changed, 478 insertions(+), 154 deletions(-) create mode 100644 src/isearch.c create mode 100644 src/isearch.h diff --git a/NEWS b/NEWS index 5ebb35f..3fa1fed 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,7 @@ libmpdclient 2.23 (not yet released) - allow window for listplaylist and listplaylistinfo - command "playlistlength" * Support open end for mpd_search_add_window +* new sticker find api libmpdclient 2.22 (2023/12/22) * drop the unmaintained Vala bindings diff --git a/include/mpd/sticker.h b/include/mpd/sticker.h index 80a0be9..69e5e84 100644 --- a/include/mpd/sticker.h +++ b/include/mpd/sticker.h @@ -19,6 +19,31 @@ struct mpd_connection; +/** + * Comparison operators for sticker search api + */ +enum mpd_sticker_operator { + MPD_STICKER_OP_UNKOWN = -1, + MPD_STICKER_OP_EQ, + MPD_STICKER_OP_GT, + MPD_STICKER_OP_LT, + MPD_STICKER_OP_EQ_INT, + MPD_STICKER_OP_GT_INT, + MPD_STICKER_OP_LT_INT, + MPD_STICKER_OP_CONTAINS, + MPD_STICKER_OP_STARTS_WITH, +}; + +/** + * Sort by settings for sticker search api + */ +enum mpd_sticker_sort { + MPD_STICKER_SORT_UNKOWN = -1, + MPD_STICKER_SORT_URI, + MPD_STICKER_SORT_VALUE, + MPD_STICKER_SORT_VALUE_INT, +}; + #ifdef __cplusplus extern "C" { #endif @@ -198,6 +223,92 @@ mpd_return_sticker(struct mpd_connection *connection, struct mpd_pair *pair); bool mpd_send_stickernames(struct mpd_connection *connection); +/** + * Search for stickers in the database. + * Constraints may be specified with mpd_search_add_tag_constraint(). + * Send the search command with mpd_search_commit(), and read the + * response items with mpd_recv_song(). + * + * @param connection the connection to MPD + * @param type the object type, e.g. "song" + * @param base_uri the base URI to start the search, e.g. a directory; + * NULL to search for all objects of the specified type + * @param name the name of the sticker + * @return true on success, false on error + * + * @since libmpdclient 2.23, MPD 0.24 + */ +bool +mpd_sticker_search_begin(struct mpd_connection *connection, const char *type, + const char *base_uri, const char *name); + +/** + * Adds the value constraint to the search + * @param connection a #mpd_connection + * @param oper compare operator + * @param value value to compare against + * @return true on success, else false + * + * @since libmpdclient 2.23, MPD 0.24 + */ +bool +mpd_sticker_search_add_value_constraint(struct mpd_connection *connection, + enum mpd_sticker_operator oper, + const char *value); + +/** + * Sort the results by the specified named attribute. + * + * @param connection a #mpd_connection + * @param sort sort by field + * @param descending sort in reverse order? + * @return true on success, false on error + * + * @since libmpdclient 2.23, MPD 0.24 + */ +bool +mpd_sticker_search_add_sort(struct mpd_connection *connection, + enum mpd_sticker_sort sort, bool descending); + +/** + * Request only a portion of the result set. + * + * @param connection a #mpd_connection + * @param start the start offset (including) + * @param end the end offset (not including) + * value "UINT_MAX" makes the end of the range open + * @return true on success, false on error + * + * @since libmpdclient 2.23, MPD 0.24 + */ +bool +mpd_sticker_search_add_window(struct mpd_connection *connection, + unsigned start, unsigned end); + +/** + * Starts the real search with constraints added with + * mpd_sticker_search_add_constraint(). + * + * @param connection the connection to MPD + * @return true on success, false on error + * + * @since libmpdclient 2.23, MPD 0.24 + */ +bool +mpd_sticker_search_commit(struct mpd_connection *connection); + +/** + * Cancels the search request before you have called + * mpd_sticker_search_commit(). Call this to clear the current search + * request. + * + * @param connection the connection to MPD + * + * @since libmpdclient 2.23, MPD 0.24 + */ +void +mpd_sticker_search_cancel(struct mpd_connection *connection); + #ifdef __cplusplus } #endif diff --git a/libmpdclient.ld b/libmpdclient.ld index 6376947..ce15ad5 100644 --- a/libmpdclient.ld +++ b/libmpdclient.ld @@ -465,6 +465,12 @@ global: mpd_recv_sticker; mpd_return_sticker; mpd_send_stickernames; + mpd_sticker_search_begin; + mpd_sticker_search_add_value_constraint; + mpd_sticker_search_add_sort; + mpd_sticker_search_add_window; + mpd_sticker_search_commit; + mpd_sticker_search_cancel; /* mpd/fingerprint.h */ mpd_parse_fingerprint_type; diff --git a/meson.build b/meson.build index 7edc1b1..70a2129 100644 --- a/meson.build +++ b/meson.build @@ -131,6 +131,7 @@ libmpdclient = library('mpdclient', 'src/replay_gain.c', 'src/response.c', 'src/run.c', + 'src/isearch.c', 'src/search.c', 'src/send.c', 'src/socket.c', diff --git a/src/isearch.c b/src/isearch.c new file mode 100644 index 0000000..360e180 --- /dev/null +++ b/src/isearch.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright The Music Player Daemon Project + +#include "isearch.h" + +#include + +#include "internal.h" + +#include +#include +#include +#include +#include + +char * +mpd_sanitize_arg(const char *src) +{ + assert(src != NULL); + + /* instead of counting in that loop above, just + * use a bit more memory and half running time + */ + char *result = malloc(strlen(src) * 2 + 1); + if (result == NULL) + return NULL; + + char *dest = result; + char ch; + do { + ch = *src++; + if (ch == '"' || ch == '\\') + *dest++ = '\\'; + *dest++ = ch; + } while (ch != 0); + + return result; +} + +bool +mpd_request_begin(struct mpd_connection *connection) +{ + assert(connection != NULL); + + if (mpd_error_is_defined(&connection->error)) + return false; + + if (connection->request) { + mpd_error_code(&connection->error, MPD_ERROR_STATE); + mpd_error_message(&connection->error, + "search already in progress"); + return false; + } + + return true; +} + +bool +mpd_request_command(struct mpd_connection *connection, const char *cmd) +{ + connection->request = strdup(cmd); + if (connection->request == NULL) { + mpd_error_code(&connection->error, MPD_ERROR_OOM); + return false; + } + + return true; +} + +char * +mpd_request_prepare_append(struct mpd_connection *connection, + size_t add_length) +{ + assert(connection != NULL); + + if (mpd_error_is_defined(&connection->error)) + return NULL; + + if (connection->request == NULL) { + mpd_error_code(&connection->error, MPD_ERROR_STATE); + mpd_error_message(&connection->error, + "no search in progress"); + return NULL; + } + + const size_t old_length = strlen(connection->request); + char *new_request = realloc(connection->request, + old_length + add_length + 1); + if (new_request == NULL) { + mpd_error_code(&connection->error, MPD_ERROR_OOM); + return NULL; + } + + connection->request = new_request; + return new_request + old_length; +} + +bool +mpd_request_add_sort(struct mpd_connection *connection, + const char *name, bool descending) +{ + assert(connection != NULL); + + const size_t size = 64; + char *dest = mpd_request_prepare_append(connection, size); + if (dest == NULL) + return false; + + snprintf(dest, size, " sort %s%s", + descending ? "-" : "", + name); + return true; +} + +bool +mpd_request_add_window(struct mpd_connection *connection, + unsigned start, unsigned end) +{ + assert(connection != NULL); + assert(start <= end); + + const size_t size = 64; + char *dest = mpd_request_prepare_append(connection, size); + if (dest == NULL) + return false; + + if (end == UINT_MAX) + /* the special value -1 means "open end" */ + snprintf(dest, size, " window %u:", start); + else + snprintf(dest, size, " window %u:%u", start, end); + return true; +} + +bool +mpd_request_commit(struct mpd_connection *connection) +{ + assert(connection != NULL); + + if (mpd_error_is_defined(&connection->error)) { + mpd_request_cancel(connection); + return false; + } + + if (connection->request == NULL) { + mpd_error_code(&connection->error, MPD_ERROR_STATE); + mpd_error_message(&connection->error, + "no search in progress"); + return false; + } + + bool success = mpd_send_command(connection, connection->request, NULL); + free(connection->request); + connection->request = NULL; + + return success; +} + +void +mpd_request_cancel(struct mpd_connection *connection) +{ + assert(connection != NULL); + + free(connection->request); + connection->request = NULL; +} diff --git a/src/isearch.h b/src/isearch.h new file mode 100644 index 0000000..59afb42 --- /dev/null +++ b/src/isearch.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright The Music Player Daemon Project + +#ifndef MPD_ISEARCH_H +#define MPD_ISEARCH_H + +#include +#include + +struct mpd_connection; + +char * +mpd_sanitize_arg(const char *src); + +bool +mpd_request_begin(struct mpd_connection *connection); + +bool +mpd_request_command(struct mpd_connection *connection, const char *cmd); + +char * +mpd_request_prepare_append(struct mpd_connection *connection, + size_t add_length); + +bool +mpd_request_add_sort(struct mpd_connection *connection, + const char *name, bool descending); + +bool +mpd_request_add_window(struct mpd_connection *connection, + unsigned start, unsigned end); + +bool +mpd_request_commit(struct mpd_connection *connection); + +void +mpd_request_cancel(struct mpd_connection *connection); + +#endif diff --git a/src/search.c b/src/search.c index d706208..48d541a 100644 --- a/src/search.c +++ b/src/search.c @@ -6,6 +6,7 @@ #include #include #include "internal.h" +#include "isearch.h" #include "iso8601.h" #include @@ -14,50 +15,28 @@ #include #include -static bool -mpd_search_init(struct mpd_connection *connection, const char *cmd) -{ - assert(connection != NULL); - assert(cmd != NULL); - - if (mpd_error_is_defined(&connection->error)) - return false; - - if (connection->request) { - mpd_error_code(&connection->error, MPD_ERROR_STATE); - mpd_error_message(&connection->error, - "search already in progress"); - return false; - } - - connection->request = strdup(cmd); - if (connection->request == NULL) { - mpd_error_code(&connection->error, MPD_ERROR_OOM); - return false; - } - - return true; -} - bool mpd_search_db_songs(struct mpd_connection *connection, bool exact) { - return mpd_search_init(connection, - exact ? "find" : "search"); + return mpd_request_begin(connection) && + mpd_request_command(connection, + exact ? "find" : "search"); } bool mpd_search_add_db_songs(struct mpd_connection *connection, bool exact) { - return mpd_search_init(connection, - exact ? "findadd" : "searchadd"); + return mpd_request_begin(connection) && + mpd_request_command(connection, + exact ? "findadd" : "searchadd"); } bool mpd_search_queue_songs(struct mpd_connection *connection, bool exact) { - return mpd_search_init(connection, - exact ? "playlistfind" : "playlistsearch"); + return mpd_request_begin(connection) && + mpd_request_command(connection, + exact ? "playlistfind" : "playlistsearch"); } bool @@ -65,16 +44,9 @@ mpd_search_db_tags(struct mpd_connection *connection, enum mpd_tag_type type) { assert(connection != NULL); - if (mpd_error_is_defined(&connection->error)) + if (!mpd_request_begin(connection)) return false; - if (connection->request) { - mpd_error_code(&connection->error, MPD_ERROR_STATE); - mpd_error_message(&connection->error, - "search already in progress"); - return false; - } - const char *strtype = mpd_tag_name(type); if (strtype == NULL) { mpd_error_code(&connection->error, MPD_ERROR_ARGUMENT); @@ -100,7 +72,8 @@ mpd_count_db_songs(struct mpd_connection *connection) { assert(connection != NULL); - return mpd_search_init(connection, "count"); + return mpd_request_begin(connection) && + mpd_request_command(connection, "count"); } bool @@ -108,59 +81,8 @@ mpd_searchcount_db_songs(struct mpd_connection *connection) { assert(connection != NULL); - return mpd_search_init(connection, "searchcount"); -} - -static char * -mpd_search_prepare_append(struct mpd_connection *connection, - size_t add_length) -{ - assert(connection != NULL); - - if (mpd_error_is_defined(&connection->error)) - return NULL; - - if (connection->request == NULL) { - mpd_error_code(&connection->error, MPD_ERROR_STATE); - mpd_error_message(&connection->error, - "no search in progress"); - return NULL; - } - - const size_t old_length = strlen(connection->request); - char *new_request = realloc(connection->request, - old_length + add_length + 1); - if (new_request == NULL) { - mpd_error_code(&connection->error, MPD_ERROR_OOM); - return NULL; - } - - connection->request = new_request; - return new_request + old_length; -} - -static char * -mpd_sanitize_arg(const char *src) -{ - assert(src != NULL); - - /* instead of counting in that loop above, just - * use a bit more memory and half running time - */ - char *result = malloc(strlen(src) * 2 + 1); - if (result == NULL) - return NULL; - - char *dest = result; - char ch; - do { - ch = *src++; - if (ch == '"' || ch == '\\') - *dest++ = '\\'; - *dest++ = ch; - } while (ch != 0); - - return result; + return mpd_request_begin(connection) && + mpd_request_command(connection, "searchcount"); } static bool @@ -181,7 +103,7 @@ mpd_search_add_constraint(struct mpd_connection *connection, const size_t add_length = 1 + strlen(name) + 2 + strlen(arg) + 1; - char *dest = mpd_search_prepare_append(connection, add_length); + char *dest = mpd_request_prepare_append(connection, add_length); if (dest == NULL) { free(arg); return false; @@ -284,7 +206,7 @@ mpd_search_add_expression(struct mpd_connection *connection, const size_t add_length = 2 + strlen(arg) + 1; - char *dest = mpd_search_prepare_append(connection, add_length); + char *dest = mpd_request_prepare_append(connection, add_length); if (dest == NULL) { free(arg); return false; @@ -303,7 +225,7 @@ mpd_search_add_group_tag(struct mpd_connection *connection, assert(connection != NULL); const size_t size = 64; - char *dest = mpd_search_prepare_append(connection, size); + char *dest = mpd_request_prepare_append(connection, size); if (dest == NULL) return false; @@ -315,17 +237,7 @@ bool mpd_search_add_sort_name(struct mpd_connection *connection, const char *name, bool descending) { - assert(connection != NULL); - - const size_t size = 64; - char *dest = mpd_search_prepare_append(connection, size); - if (dest == NULL) - return false; - - snprintf(dest, size, " sort %s%s", - descending ? "-" : "", - name); - return true; + return mpd_request_add_sort(connection, name, descending); } bool @@ -341,21 +253,7 @@ bool mpd_search_add_window(struct mpd_connection *connection, unsigned start, unsigned end) { - assert(connection != NULL); - assert(start <= end); - - const size_t size = 64; - char *dest = mpd_search_prepare_append(connection, size); - if (dest == NULL) - return false; - - if (end == UINT_MAX) - /* the special value -1 means "open end" */ - snprintf(dest, size, " window %u:", start); - else - snprintf(dest, size, " window %u:%u", start, end); - - return true; + return mpd_request_add_window(connection, start, end); } bool @@ -365,7 +263,7 @@ mpd_search_add_position(struct mpd_connection *connection, assert(connection != NULL); const size_t size = 64; - char *dest = mpd_search_prepare_append(connection, size); + char *dest = mpd_request_prepare_append(connection, size); if (dest == NULL) return false; @@ -378,34 +276,13 @@ mpd_search_add_position(struct mpd_connection *connection, bool mpd_search_commit(struct mpd_connection *connection) { - assert(connection != NULL); - - if (mpd_error_is_defined(&connection->error)) { - mpd_search_cancel(connection); - return false; - } - - if (connection->request == NULL) { - mpd_error_code(&connection->error, MPD_ERROR_STATE); - mpd_error_message(&connection->error, - "no search in progress"); - return false; - } - - bool success = mpd_send_command(connection, connection->request, NULL); - free(connection->request); - connection->request = NULL; - - return success; + return mpd_request_commit(connection); } void mpd_search_cancel(struct mpd_connection *connection) { - assert(connection != NULL); - - free(connection->request); - connection->request = NULL; + mpd_request_cancel(connection); } struct mpd_pair * @@ -427,16 +304,9 @@ mpd_search_add_db_songs_to_playlist(struct mpd_connection *connection, assert(connection != NULL); assert(playlist_name != NULL); - if (mpd_error_is_defined(&connection->error)) + if (!mpd_request_begin(connection)) return false; - if (connection->request) { - mpd_error_code(&connection->error, MPD_ERROR_STATE); - mpd_error_message(&connection->error, - "search already in progress"); - return false; - } - char *arg = mpd_sanitize_arg(playlist_name); if (arg == NULL) { mpd_error_code(&connection->error, MPD_ERROR_OOM); diff --git a/src/sticker.c b/src/sticker.c index 4127aef..496733d 100644 --- a/src/sticker.c +++ b/src/sticker.c @@ -7,9 +7,13 @@ #include #include #include +#include "internal.h" +#include "isearch.h" #include "run.h" #include +#include +#include #include bool @@ -143,3 +147,129 @@ mpd_send_stickernames(struct mpd_connection *connection) return mpd_send_command(connection, "stickernames", NULL); } + +bool +mpd_sticker_search_begin(struct mpd_connection *connection, const char *type, + const char *base_uri, const char *name) +{ + assert(name != NULL); + + if (!mpd_request_begin(connection)) + return false; + + if (base_uri == NULL) + base_uri = ""; + + char *arg_base_uri = mpd_sanitize_arg(base_uri); + if (arg_base_uri == NULL) { + mpd_error_code(&connection->error, MPD_ERROR_OOM); + return false; + } + + char *arg_name = mpd_sanitize_arg(name); + if (arg_name == NULL) { + mpd_error_code(&connection->error, MPD_ERROR_OOM); + free(arg_base_uri); + return false; + } + + const size_t size = 13 + strlen(type) + 2 + strlen(arg_base_uri) + 3 + strlen(arg_name) + 2; + connection->request = malloc(size); + if (connection->request == NULL) { + mpd_error_code(&connection->error, MPD_ERROR_OOM); + free(arg_base_uri); + free(arg_name); + return false; + } + + snprintf(connection->request, size, "sticker find %s \"%s\" \"%s\"", + type, arg_base_uri, arg_name); + + free(arg_base_uri); + free(arg_name); + return true; +} + +static const char *get_sticker_oper_str(enum mpd_sticker_operator oper) { + switch(oper) { + case MPD_STICKER_OP_EQ: return "="; + case MPD_STICKER_OP_GT: return ">"; + case MPD_STICKER_OP_LT: return "<"; + case MPD_STICKER_OP_EQ_INT: return "eq"; + case MPD_STICKER_OP_GT_INT: return "gt"; + case MPD_STICKER_OP_LT_INT: return "lt"; + case MPD_STICKER_OP_CONTAINS: return "contains"; + case MPD_STICKER_OP_STARTS_WITH: return "starts_with"; + case MPD_STICKER_OP_UNKOWN: return NULL; + } + return NULL; +} + +bool +mpd_sticker_search_add_value_constraint(struct mpd_connection *connection, + enum mpd_sticker_operator oper, + const char *value) +{ + assert(connection != NULL); + assert(value != NULL); + + char *arg = mpd_sanitize_arg(value); + if (arg == NULL) { + mpd_error_code(&connection->error, MPD_ERROR_OOM); + return false; + } + + const char *oper_str = get_sticker_oper_str(oper); + if (oper_str == NULL) + return false; + + const size_t size = 1 + strlen(oper_str) + 2 + strlen(arg) + 2; + char *dest = mpd_request_prepare_append(connection, size); + if (dest == NULL) { + free(arg); + return false; + } + + snprintf(dest, size, " %s \"%s\"", + oper_str, + arg); + free(arg); + return true; +} + +static const char *get_sticker_sort_name(enum mpd_sticker_sort sort) { + switch(sort) { + case MPD_STICKER_SORT_URI: return "uri"; + case MPD_STICKER_SORT_VALUE: return "value"; + case MPD_STICKER_SORT_VALUE_INT: return "value_int"; + case MPD_STICKER_SORT_UNKOWN: return NULL; + } + return NULL; +} + +bool +mpd_sticker_search_add_sort(struct mpd_connection *connection, + enum mpd_sticker_sort sort, bool descending) +{ + const char *sort_str = get_sticker_sort_name(sort); + return mpd_request_add_sort(connection, sort_str, descending); +} + +bool +mpd_sticker_search_add_window(struct mpd_connection *connection, + unsigned start, unsigned end) +{ + return mpd_request_add_window(connection, start, end); +} + +bool +mpd_sticker_search_commit(struct mpd_connection *connection) +{ + return mpd_request_commit(connection); +} + +void +mpd_sticker_search_cancel(struct mpd_connection *connection) +{ + mpd_request_cancel(connection); +}