diff --git a/owntone.conf.in b/owntone.conf.in index 06e6b7ac..8f44e30a 100644 --- a/owntone.conf.in +++ b/owntone.conf.in @@ -441,11 +441,6 @@ mpd { # Whether to emit an output with plugin type "httpd" to tell clients # that a stream is available for local playback. # enable_httpd_plugin = false - - # The maximum size of a command list in KB. - # It is the sum of lengths of all the command lines between command list begin and end. - # Default is 2048 (2 MiB). -# max_command_list_size = KBYTES } # SQLite configuration (allows to modify the operation of the SQLite databases) diff --git a/src/conffile.c b/src/conffile.c index 31dbe6f4..da49f03d 100644 --- a/src/conffile.c +++ b/src/conffile.c @@ -239,7 +239,6 @@ static cfg_opt_t sec_mpd[] = CFG_INT("port", 6600, CFGF_NONE), CFG_INT("http_port", 0, CFGF_NONE), CFG_BOOL("enable_httpd_plugin", cfg_false, CFGF_NONE), - CFG_INT("max_command_list_size", 2048, CFGF_NONE), CFG_BOOL("clear_queue_on_stop_disable", cfg_false, CFGF_NODEFAULT | CFGF_DEPRECATED), CFG_BOOL("allow_modifying_stored_playlists", cfg_false, CFGF_NODEFAULT | CFGF_DEPRECATED), CFG_STR("default_playlist_directory", NULL, CFGF_NODEFAULT | CFGF_DEPRECATED), diff --git a/src/db.c b/src/db.c index 0c8c70b2..541e4762 100644 --- a/src/db.c +++ b/src/db.c @@ -6005,60 +6005,43 @@ db_queue_move_bypos(int pos_from, int pos_to) int db_queue_move_bypos_range(int range_begin, int range_end, int pos_to) { +#define Q_TMPL "UPDATE queue SET pos = CASE WHEN pos < %d THEN pos + %d ELSE pos - %d END, queue_version = %d WHERE pos >= %d AND pos < %d;" int queue_version; char *query; + int count; + int update_begin; + int update_end; int ret; - int changes = 0; + int cut_off; + int offset_up; + int offset_down; queue_version = queue_transaction_begin(); - int count = range_end - range_begin; - int update_begin = MIN(range_begin, pos_to); - int update_end = MAX(range_begin + count, pos_to + count); - int cut_off, offset_up, offset_down; + count = range_end - range_begin; + update_begin = MIN(range_begin, pos_to); + update_end = MAX(range_begin + count, pos_to + count); - if (range_begin < pos_to) { - cut_off = range_begin + count; - offset_up = pos_to - range_begin; - offset_down = count; - } else { + if (range_begin < pos_to) + { + cut_off = range_begin + count; + offset_up = pos_to - range_begin; + offset_down = count; + } + else + { cut_off = range_begin; offset_up = count; offset_down = range_begin - pos_to; - } + } - DPRINTF(E_DBG, L_DB, "db_queue_move_bypos_range: from = %d, to = %d," - " count = %d, cut_off = %d, offset_up = %d, offset_down = %d," - " begin = %d, end = %d\n", - range_begin, pos_to, count, cut_off, offset_up, offset_down, update_begin, update_end); + query = sqlite3_mprintf(Q_TMPL, cut_off, offset_up, offset_down, queue_version, update_begin, update_end); + ret = db_query_run(query, 1, 0); - query = "UPDATE queue SET pos =" - " CASE" - " WHEN pos < :cut_off THEN pos + :offset_up" - " ELSE pos - :offset_down" - " END," - " queue_version = :queue_version" - " WHERE" - " pos >= :update_begin AND pos < :update_end;"; - - sqlite3_stmt *stmt; - if (SQLITE_OK != (ret = sqlite3_prepare_v2(hdl, query, -1, &stmt, NULL))) goto end_transaction; - - if (SQLITE_OK != (ret = sqlite3_bind_int(stmt, 1, cut_off))) goto end_transaction; - if (SQLITE_OK != (ret = sqlite3_bind_int(stmt, 2, offset_up))) goto end_transaction; - if (SQLITE_OK != (ret = sqlite3_bind_int(stmt, 3, offset_down))) goto end_transaction; - if (SQLITE_OK != (ret = sqlite3_bind_int(stmt, 4, queue_version))) goto end_transaction; - if (SQLITE_OK != (ret = sqlite3_bind_int(stmt, 5, update_begin))) goto end_transaction; - if (SQLITE_OK != (ret = sqlite3_bind_int(stmt, 6, update_end))) goto end_transaction; - - changes = db_statement_run(stmt, 0); - - end_transaction: - DPRINTF(E_LOG, L_DB, "db_queue_move_bypos_range: changes = %d, res = %d: %s\n", - changes, ret, sqlite3_errstr(ret)); queue_transaction_end(ret, queue_version); - return ret == SQLITE_OK && changes != -1 ? 0 : -1; + return ret; +#undef Q_TMPL } /* diff --git a/src/mpd.c b/src/mpd.c index dafea3bd..d5f03f62 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -50,37 +51,6 @@ #include "player.h" #include "remote_pairing.h" - -enum mpd_type { - MPD_TYPE_INT, - MPD_TYPE_STRING, - MPD_TYPE_SPECIAL, -}; - - -#define MPD_ALL_IDLE_LISTENER_EVENTS (LISTENER_PLAYER | LISTENER_QUEUE | LISTENER_VOLUME | LISTENER_SPEAKER | LISTENER_OPTIONS | LISTENER_DATABASE | LISTENER_UPDATE | LISTENER_STORED_PLAYLIST | LISTENER_RATING) -#define MPD_RATING_FACTOR 10.0 -#define MPD_BINARY_SIZE 8192 /* MPD MAX_BINARY_SIZE */ -#define MPD_BINARY_SIZE_MIN 64 /* min size from MPD ClientCommands.cxx */ - -static pthread_t tid_mpd; - -static struct event_base *evbase_mpd; - -static struct commands_base *cmdbase; - -static struct evhttp *evhttpd; - -static struct evconnlistener *mpd_listener; -static int mpd_sockfd; - -static bool mpd_plugin_httpd; - -// Virtual path to the default playlist directory -static char *default_pl_dir; -static bool allow_modifying_stored_playlists; - - /** * from MPD source: * * @@ -92,58 +62,173 @@ static bool allow_modifying_stored_playlists; * * https://github.com/MusicPlayerDaemon/MPD/blob/master/src/command/AllCommands.cxx */ -#define COMMAND_ARGV_MAX 70 +#define MPD_COMMAND_ARGV_MAX 70 /** * config: * max_command_list_size KBYTES - * The maximum size a command list. Default is 2048 (2 MiB). * * https://github.com/MusicPlayerDaemon/MPD/blob/master/src/client/Config.cxx */ -#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024) +#define MPD_MAX_COMMAND_LIST_SIZE (2048*1024) -static struct { - /** - * Max size in bytes allowed in command list. - * "max_command_list_size" - */ - uint32_t MaxCommandListSize; -} -Config = { - .MaxCommandListSize = CLIENT_MAX_COMMAND_LIST_DEFAULT -}; +#define MPD_ALL_IDLE_LISTENER_EVENTS (LISTENER_PLAYER | LISTENER_QUEUE | LISTENER_VOLUME | LISTENER_SPEAKER | LISTENER_OPTIONS | LISTENER_DATABASE | LISTENER_UPDATE | LISTENER_STORED_PLAYLIST | LISTENER_RATING) +#define MPD_RATING_FACTOR 10.0 +#define MPD_BINARY_SIZE 8192 /* MPD MAX_BINARY_SIZE */ +#define MPD_BINARY_SIZE_MIN 64 /* min size from MPD ClientCommands.cxx */ - -/* MPD error codes (taken from ack.h) */ -enum ack +// MPD error codes (taken from ack.h) +enum mpd_ack_error { - ACK_ERROR_NOT_LIST = 1, - ACK_ERROR_ARG = 2, - ACK_ERROR_PASSWORD = 3, - ACK_ERROR_PERMISSION = 4, - ACK_ERROR_UNKNOWN = 5, + ACK_ERROR_NONE = 0, + ACK_ERROR_NOT_LIST = 1, + ACK_ERROR_ARG = 2, + ACK_ERROR_PASSWORD = 3, + ACK_ERROR_PERMISSION = 4, + ACK_ERROR_UNKNOWN = 5, - ACK_ERROR_NO_EXIST = 50, - ACK_ERROR_PLAYLIST_MAX = 51, - ACK_ERROR_SYSTEM = 52, - ACK_ERROR_PLAYLIST_LOAD = 53, - ACK_ERROR_UPDATE_ALREADY = 54, - ACK_ERROR_PLAYER_SYNC = 55, - ACK_ERROR_EXIST = 56, + ACK_ERROR_NO_EXIST = 50, + ACK_ERROR_PLAYLIST_MAX = 51, + ACK_ERROR_SYSTEM = 52, + ACK_ERROR_PLAYLIST_LOAD = 53, + ACK_ERROR_UPDATE_ALREADY = 54, + ACK_ERROR_PLAYER_SYNC = 55, + ACK_ERROR_EXIST = 56, +}; + +enum mpd_type { + MPD_TYPE_INT, + MPD_TYPE_STRING, + MPD_TYPE_SPECIAL, }; -/** - * Flag for command list state - */ enum command_list_type { - COMMAND_LIST = 1, - COMMAND_LIST_OK = 2, - COMMAND_LIST_END = 3, - COMMAND_LIST_NONE = 4 + COMMAND_LIST_NONE = 0, + COMMAND_LIST_BEGIN, + COMMAND_LIST_OK_BEGIN, + COMMAND_LIST_END, + COMMAND_LIST_OK_END, }; +struct mpd_tagtype +{ + char *tag; + char *field; + char *sort_field; + char *group_field; + enum mpd_type type; + ssize_t mfi_offset; + + /* + * This allows omitting the "group" fields in the created group by clause to improve + * performance in the "list" command. For example listing albums and artists already + * groups by their persistent id, an additional group clause by artist/album will + * decrease performance of the select query and will in general not change the result + * (e. g. album persistent id is generated by artist and album and listing albums + * grouped by artist is therefor not necessary). + */ + bool group_in_listcommand; +}; + +struct mpd_client_ctx +{ + // True if the connection is already authenticated or does not need authentication + bool authenticated; + + // The events the client needs to be notified of + short events; + + // True if the client is waiting for idle events + bool is_idle; + + // The events the client is waiting for (set by the idle command) + short idle_events; + + // The current binary limit size + unsigned int binarylimit; + + // The output buffer for the client (used to send data to the client) + struct evbuffer *evbuffer; + + // Equals COMMAND_LIST_NONE unless command_list_begin or command_list_ok_begin + // received. + enum command_list_type cmd_list_type; + + // Current command list + // When cmd_list_type is either COMMAND_LIST_BEGIN or COMMAND_LIST_OK_BEGIN + // received commands are added to this buffer. When command_list_end is + // received, the commands saved in this buffer are processed. + struct evbuffer *cmd_list_buffer; + + // Set to true by handlers and command processing if we must cut the client + // connection. + bool must_disconnect; + + struct mpd_client_ctx *next; +}; + +struct mpd_command +{ + /* The command name */ + const char *mpdcommand; + + /* + * The function to execute the command + * + * @param evbuf the response event buffer + * @param argc number of arguments in argv + * @param argv argument array, first entry is the commandname + * @param errmsg error message set by this function if an error occured + * @return MPD ack error (ACK_ERROR_NONE on succes) + */ + enum mpd_ack_error (*handler)(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx); + int min_argc; +}; + +struct output +{ + unsigned short shortid; + uint64_t id; + char *name; + + unsigned selected; +}; + +struct output_get_param +{ + unsigned short curid; + unsigned short shortid; + struct output *output; +}; + +struct output_outputs_param +{ + unsigned short nextid; + struct evbuffer *buf; +}; + + +/* ---------------------------------- Globals ------------------------------- */ + +static pthread_t tid_mpd; +static struct event_base *evbase_mpd; +static struct commands_base *cmdbase; +static struct evhttp *evhttpd; +static struct evconnlistener *mpd_listener; +static int mpd_sockfd; +static bool mpd_plugin_httpd; + +// Virtual path to the default playlist directory +static char *default_pl_dir; +static bool allow_modifying_stored_playlists; + +// Forward +static struct mpd_command mpd_handlers[]; + +// List of all connected mpd clients +struct mpd_client_ctx *mpd_clients; + /** * This lists for ffmpeg suffixes and mime types are taken from the ffmpeg decoder plugin from mpd * (FfmpegDecoderPlugin.cxx, git revision 9fb351a139a56fc7b1ece549894f8fc31fa887cd). @@ -185,26 +270,6 @@ static const char * const ffmpeg_mime_types[] = { "application/flv", "applicatio NULL }; -struct mpd_tagtype -{ - char *tag; - char *field; - char *sort_field; - char *group_field; - enum mpd_type type; - ssize_t mfi_offset; - - /* - * This allows omitting the "group" fields in the created group by clause to improve - * performance in the "list" command. For example listing albums and artists already - * groups by their persistent id, an additional group clause by artist/album will - * decrease performance of the select query and will in general not change the result - * (e. g. album persistent id is generated by artist and album and listing albums - * grouped by artist is therefor not necessary). - */ - bool group_in_listcommand; -}; - static struct mpd_tagtype tagtypes[] = { /* tag | db field | db sort field | db group field | type | media_file offset | group_in_listcommand */ @@ -228,6 +293,9 @@ static struct mpd_tagtype tagtypes[] = }; + +/* -------------------------------- Helpers --------------------------------- */ + static struct mpd_tagtype * find_tagtype(const char *tag) { @@ -245,79 +313,33 @@ find_tagtype(const char *tag) return NULL; } -/* - * MPD client connection data - */ -struct mpd_client_ctx -{ - // True if the connection is already authenticated or does not need authentication - bool authenticated; - - // The events the client needs to be notified of - short events; - - // True if the client is waiting for idle events - bool is_idle; - - // The events the client is waiting for (set by the idle command) - short idle_events; - - // The current binary limit size - unsigned int binarylimit; - - // The output buffer for the client (used to send data to the client) - struct evbuffer *evbuffer; - - /** - * command list type flag: - * is set to: - * COMMAND_LIST_NONE by default and when receiving command_list_end - * COMMAND_LIST when receiving command_list_begin - * COMMAND_OK_LIST when receiving command_list_ok_begin - */ - enum command_list_type cmd_list_type; - - /** - * current command list: - *

- * When cmd_list_type is either COMMAND_LIST or COMMAND_OK_LIST - * received commands are added to this buffer. - *

- * When command_list_end is received, the commands save - * in this buffer are processed, and then the buffer is freed. - * - * @see mpd_command_list_add - */ - struct evbuffer *cmd_list_buffer; - - /** - * closing flag - * - * set to true in mpd_read_cb() when we want to close - * the client connection by freeing the evbuffer. - */ - bool is_closing; - - struct mpd_client_ctx *next; -}; - -// List of all connected mpd clients -struct mpd_client_ctx *mpd_clients; - static void -free_mpd_client_ctx(void *ctx) +client_ctx_free(struct mpd_client_ctx *client_ctx) { - struct mpd_client_ctx *client_ctx = ctx; - struct mpd_client_ctx *client; - struct mpd_client_ctx *prev; - if (!client_ctx) return; - if (client_ctx->cmd_list_buffer != NULL) - { - evbuffer_free(client_ctx->cmd_list_buffer); - } + evbuffer_free(client_ctx->cmd_list_buffer); + free(client_ctx); +} + +static struct mpd_client_ctx * +client_ctx_new(void) +{ + struct mpd_client_ctx *client_ctx; + + CHECK_NULL(L_MPD, client_ctx = calloc(1, sizeof(struct mpd_client_ctx))); + CHECK_NULL(L_MPD, client_ctx->cmd_list_buffer = evbuffer_new()); + + return client_ctx; +} + +static void +client_ctx_remove(void *arg) +{ + struct mpd_client_ctx *client_ctx = arg; + struct mpd_client_ctx *client; + struct mpd_client_ctx *prev; client = mpd_clients; prev = NULL; @@ -340,39 +362,28 @@ free_mpd_client_ctx(void *ctx) client = client->next; } - free(client_ctx); + client_ctx_free(client_ctx); } -struct output +static struct mpd_client_ctx * +client_ctx_add(void) { - unsigned short shortid; - uint64_t id; - char *name; + struct mpd_client_ctx *client_ctx = client_ctx_new(); - unsigned selected; -}; + client_ctx->next = mpd_clients; + mpd_clients = client_ctx; -struct output_get_param -{ - unsigned short curid; - unsigned short shortid; - struct output *output; -}; - -struct output_outputs_param -{ - unsigned short nextid; - struct evbuffer *buf; -}; + return client_ctx; +} static void free_output(struct output *output) { - if (output) - { - free(output->name); - free(output); - } + if (!output) + return; + + free(output->name); + free(output); } /* @@ -395,28 +406,6 @@ prepend_slash(const char *path) return result; } - -/* Thread: mpd */ -static void * -mpd(void *arg) -{ - int ret; - - ret = db_perthread_init(); - if (ret < 0) - { - DPRINTF(E_LOG, L_MPD, "Error: DB init failed\n"); - - pthread_exit(NULL); - } - - event_base_dispatch(evbase_mpd); - - db_perthread_deinit(); - - pthread_exit(NULL); -} - static void mpd_time(char *buffer, size_t bufferlen, time_t t) { @@ -429,55 +418,36 @@ mpd_time(char *buffer, size_t bufferlen, time_t t) } /* - * Parses a rage argument of the form START:END (the END item is not included in the range) + * Parses a range argument of the form START:END (the END item is not included in the range) * into its start and end position. * * @param range the range argument * @param start_pos set by this method to the start position - * @param end_pos set by this method to the end position + * @param end_pos set by this method to the end postion * @return 0 on success, -1 on failure - * - * @see https://github.com/MusicPlayerDaemon/MPD/blob/master/src/protocol/RangeArg.hxx - * - * For "window START:END" The end index can be omitted, which means the range is open-ended. */ static int mpd_pars_range_arg(const char *range, int *start_pos, int *end_pos) { int ret; - static char separator = ':'; - char* sep_pos = strchr(range, separator); - - if (sep_pos) + if (strchr(range, ':')) { - *sep_pos++ = '\0'; - // range start - if (safe_atoi32(range, start_pos) != 0) - { - DPRINTF(E_LOG, L_MPD, "Error parsing range argument '%s'\n", range); - return -1; - } - // range end - if (*sep_pos == 0) - { - DPRINTF(E_LOG, L_MPD, "Open ended range not supported: '%s'\n", range); - return -1; - } - else if (safe_atoi32(sep_pos, end_pos) != 0) - { - DPRINTF(E_LOG, L_MPD, "Error parsing range argument '%s'\n", range); - return -1; - } + ret = sscanf(range, "%d:%d", start_pos, end_pos); + if (ret < 0) + { + DPRINTF(E_LOG, L_MPD, "Error parsing range argument '%s' (return code = %d)\n", range, ret); + return -1; + } } else { ret = safe_atoi32(range, start_pos); if (ret < 0) - { - DPRINTF(E_LOG, L_MPD, "Error parsing integer argument '%s' (return code = %d)\n", range, ret); - return -1; - } + { + DPRINTF(E_LOG, L_MPD, "Error parsing integer argument '%s' (return code = %d)\n", range, ret); + return -1; + } *end_pos = (*start_pos) + 1; } @@ -887,10 +857,51 @@ parse_group_params(int argc, char **argv, bool group_in_listcommand, struct quer return 0; } -/* - * Command handler function for 'currentsong' - */ static int +notify_idle_client(struct mpd_client_ctx *client_ctx, short events) +{ + if (!client_ctx->is_idle) + { + client_ctx->events |= events; + return 1; + } + + if (!(client_ctx->idle_events & events)) + { + DPRINTF(E_DBG, L_MPD, "Client not listening for events: %d\n", events); + return 1; + } + + if (events & LISTENER_DATABASE) + evbuffer_add(client_ctx->evbuffer, "changed: database\n", 18); + if (events & LISTENER_UPDATE) + evbuffer_add(client_ctx->evbuffer, "changed: update\n", 16); + if (events & LISTENER_QUEUE) + evbuffer_add(client_ctx->evbuffer, "changed: playlist\n", 18); + if (events & LISTENER_PLAYER) + evbuffer_add(client_ctx->evbuffer, "changed: player\n", 16); + if (events & LISTENER_VOLUME) + evbuffer_add(client_ctx->evbuffer, "changed: mixer\n", 15); + if (events & LISTENER_SPEAKER) + evbuffer_add(client_ctx->evbuffer, "changed: output\n", 16); + if (events & LISTENER_OPTIONS) + evbuffer_add(client_ctx->evbuffer, "changed: options\n", 17); + if (events & LISTENER_STORED_PLAYLIST) + evbuffer_add(client_ctx->evbuffer, "changed: stored_playlist\n", 25); + if (events & LISTENER_RATING) + evbuffer_add(client_ctx->evbuffer, "changed: sticker\n", 17); + + client_ctx->is_idle = false; + client_ctx->idle_events = 0; + client_ctx->events = 0; + + return 0; +} + + +/* ----------------------------- Command handlers --------------------------- */ + +static enum mpd_ack_error mpd_command_currentsong(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { @@ -907,7 +918,7 @@ mpd_command_currentsong(struct evbuffer *evbuf, int argc, char **argv, char **er if (!queue_item) { - return 0; + return ACK_ERROR_NONE; } ret = mpd_add_db_queue_item(evbuf, queue_item); @@ -916,21 +927,18 @@ mpd_command_currentsong(struct evbuffer *evbuf, int argc, char **argv, char **er if (ret < 0) { - *errmsg = safe_asprintf("Error adding media info for file with id: %d", status.id); + *errmsg = safe_asprintf("Error setting media info for file with id: %d", status.id); return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } -static int -mpd_notify_idle_client(struct mpd_client_ctx *client_ctx, short events); - /* * Example input: * idle "database" "mixer" "options" "output" "player" "playlist" "sticker" "update" */ -static int +static enum mpd_ack_error mpd_command_idle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int i; @@ -970,11 +978,25 @@ mpd_command_idle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s // If events the client listens to occurred since the last idle call (or since the client connected, // if it is the first idle call), notify immediately. if (ctx->events & ctx->idle_events) - { - mpd_notify_idle_client(ctx, ctx->events); - } + notify_idle_client(ctx, ctx->events); - return 0; + return ACK_ERROR_NONE; +} + +static enum mpd_ack_error +mpd_command_noidle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) +{ + /* + * The protocol specifies: "The idle command can be canceled by + * sending the command noidle (no other commands are allowed). MPD + * will then leave idle mode and print results immediately; might be + * empty at this time." + */ + if (ctx->events) + notify_idle_client(ctx, ctx->events); + + ctx->is_idle = false; + return ACK_ERROR_NONE; } /* @@ -999,7 +1021,7 @@ mpd_command_idle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s * nextsong: 1 * nextsongid: 2 */ -static int +static enum mpd_ack_error mpd_command_status(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct player_status status; @@ -1096,13 +1118,13 @@ mpd_command_status(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, } } - return 0; + return ACK_ERROR_NONE; } /* * Command handler function for 'stats' */ -static int +static enum mpd_ack_error mpd_command_stats(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct query_params qp; @@ -1143,7 +1165,7 @@ mpd_command_stats(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, db_update, 7); - return 0; + return ACK_ERROR_NONE; } /* @@ -1152,7 +1174,7 @@ mpd_command_stats(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, * 0 = disable consume * 1 = enable consume */ -static int +static enum mpd_ack_error mpd_command_consume(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int enable; @@ -1166,7 +1188,7 @@ mpd_command_consume(struct evbuffer *evbuf, int argc, char **argv, char **errmsg } player_consume_set(enable); - return 0; + return ACK_ERROR_NONE; } /* @@ -1175,7 +1197,7 @@ mpd_command_consume(struct evbuffer *evbuf, int argc, char **argv, char **errmsg * 0 = disable shuffle * 1 = enable shuffle */ -static int +static enum mpd_ack_error mpd_command_random(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int enable; @@ -1189,7 +1211,7 @@ mpd_command_random(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, } player_shuffle_set(enable); - return 0; + return ACK_ERROR_NONE; } /* @@ -1198,7 +1220,7 @@ mpd_command_random(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, * 0 = repeat off * 1 = repeat all */ -static int +static enum mpd_ack_error mpd_command_repeat(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int enable; @@ -1216,14 +1238,14 @@ mpd_command_repeat(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, else player_repeat_set(REPEAT_ALL); - return 0; + return ACK_ERROR_NONE; } /* * Command handler function for 'setvol' * Sets the volume, expects argument argv[1] to be an integer 0-100 */ -static int +static enum mpd_ack_error mpd_command_setvol(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int volume; @@ -1238,7 +1260,7 @@ mpd_command_setvol(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, player_volume_set(volume); - return 0; + return ACK_ERROR_NONE; } /* @@ -1258,7 +1280,7 @@ mpd_command_setvol(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, * 1 = repeat song * Thus "oneshot" is accepted, but ignored under all circumstances. */ -static int +static enum mpd_ack_error mpd_command_single(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int enable; @@ -1284,7 +1306,7 @@ mpd_command_single(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, else player_repeat_set(REPEAT_SONG); - return 0; + return ACK_ERROR_NONE; } /* @@ -1292,11 +1314,11 @@ mpd_command_single(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, * The server does not support replay gain, therefor this function returns always * "replay_gain_mode: off". */ -static int +static enum mpd_ack_error mpd_command_replay_gain_status(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { evbuffer_add(evbuf, "replay_gain_mode: off\n", 22); - return 0; + return ACK_ERROR_NONE; } /* @@ -1305,7 +1327,7 @@ mpd_command_replay_gain_status(struct evbuffer *evbuf, int argc, char **argv, ch * * According to the mpd protocoll specification this function is deprecated. */ -static int +static enum mpd_ack_error mpd_command_volume(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct player_status status; @@ -1325,14 +1347,14 @@ mpd_command_volume(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, player_volume_set(volume); - return 0; + return ACK_ERROR_NONE; } /* * Command handler function for 'next' * Skips to the next song in the playqueue */ -static int +static enum mpd_ack_error mpd_command_next(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int ret; @@ -1352,7 +1374,7 @@ mpd_command_next(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } /* @@ -1361,7 +1383,7 @@ mpd_command_next(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s * 0 = play * 1 = pause */ -static int +static enum mpd_ack_error mpd_command_pause(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int pause; @@ -1370,15 +1392,18 @@ mpd_command_pause(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, player_get_status(&status); - pause = status.status == PLAY_PLAYING ? 1 : 0; if (argc > 1) { ret = safe_atoi32(argv[1], &pause); if (ret < 0) - { - *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]); - return ACK_ERROR_ARG; - } + { + *errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]); + return ACK_ERROR_ARG; + } + } + else + { + pause = status.status == PLAY_PLAYING ? 1 : 0; } // MPD ignores pause in stopped state @@ -1386,14 +1411,16 @@ mpd_command_pause(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, ret = player_playback_pause(); else if (pause == 0 && status.status == PLAY_PAUSED) ret = player_playback_start(); + else + ret = 0; if (ret < 0) { - *errmsg = safe_asprintf("Failed to pause playback"); + *errmsg = safe_asprintf("Failed to %s playback", (pause == 1) ? "pause" : "start"); return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } /* @@ -1401,7 +1428,7 @@ mpd_command_pause(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, * Starts playback, the optional argument argv[1] represents the position in the playqueue * where to start playback. */ -static int +static enum mpd_ack_error mpd_command_play(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int songpos; @@ -1425,7 +1452,7 @@ mpd_command_play(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s if (status.status == PLAY_PLAYING && songpos < 0) { DPRINTF(E_DBG, L_MPD, "Ignoring play command with parameter '%s', player is already playing.\n", argv[1]); - return 0; + return ACK_ERROR_NONE; } if (status.status == PLAY_PLAYING) @@ -1455,7 +1482,7 @@ mpd_command_play(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } /* @@ -1463,7 +1490,7 @@ mpd_command_play(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s * Starts playback, the optional argument argv[1] represents the songid of the song * where to start playback. */ -static int +static enum mpd_ack_error mpd_command_playid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { uint32_t id; @@ -1512,14 +1539,14 @@ mpd_command_playid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } /* * Command handler function for 'previous' * Skips to the previous song in the playqueue */ -static int +static enum mpd_ack_error mpd_command_previous(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int ret; @@ -1539,7 +1566,7 @@ mpd_command_previous(struct evbuffer *evbuf, int argc, char **argv, char **errms return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } /* @@ -1547,7 +1574,7 @@ mpd_command_previous(struct evbuffer *evbuf, int argc, char **argv, char **errms * Seeks to song at the given position in argv[1] to the position in seconds given in argument argv[2] * (fractions allowed). */ -static int +static enum mpd_ack_error mpd_command_seek(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { uint32_t songpos; @@ -1582,7 +1609,7 @@ mpd_command_seek(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } /* @@ -1590,7 +1617,7 @@ mpd_command_seek(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s * Seeks to song with id given in argv[1] to the position in seconds given in argument argv[2] * (fractions allowed). */ -static int +static enum mpd_ack_error mpd_command_seekid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct player_status status; @@ -1632,14 +1659,14 @@ mpd_command_seekid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } /* * Command handler function for 'seekcur' * Seeks the current song to the position in seconds given in argument argv[1] (fractions allowed). */ -static int +static enum mpd_ack_error mpd_command_seekcur(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { float seek_target_sec; @@ -1665,14 +1692,14 @@ mpd_command_seekcur(struct evbuffer *evbuf, int argc, char **argv, char **errmsg return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } /* * Command handler function for 'stop' * Stop playback. */ -static int +static enum mpd_ack_error mpd_command_stop(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int ret; @@ -1685,7 +1712,7 @@ mpd_command_stop(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } /* @@ -1738,7 +1765,7 @@ mpd_queue_add(char *path, bool exact_match, int position) * Adds the all songs under the given path to the end of the playqueue (directories add recursively). * Expects argument argv[1] to be a path to a single file or directory. */ -static int +static enum mpd_ack_error mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct player_status status; @@ -1765,7 +1792,7 @@ mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, st } } - return 0; + return ACK_ERROR_NONE; } /* @@ -1774,7 +1801,7 @@ mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, st * Expects argument argv[1] to be a path to a single file. argv[2] is optional, if present * it must be an integer representing the position in the playqueue. */ -static int +static enum mpd_ack_error mpd_command_addid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct player_status status; @@ -1816,14 +1843,14 @@ mpd_command_addid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, "Id: %d\n", ret); // mpd_queue_add returns the item_id of the last inserted queue item - return 0; + return ACK_ERROR_NONE; } /* * Command handler function for 'clear' * Stops playback and removes all songs from the playqueue */ -static int +static enum mpd_ack_error mpd_command_clear(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int ret; @@ -1836,7 +1863,7 @@ mpd_command_clear(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, db_queue_clear(0); - return 0; + return ACK_ERROR_NONE; } /* @@ -1845,7 +1872,7 @@ mpd_command_clear(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, * an integer range {START:END} representing the position of the songs in the playlist, that * should be removed. */ -static int +static enum mpd_ack_error mpd_command_delete(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int start_pos; @@ -1857,7 +1884,7 @@ mpd_command_delete(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, if (argc < 2) { db_queue_clear(0); - return 0; + return ACK_ERROR_NONE; } // If argument argv[1] is present remove only the specified songs @@ -1877,14 +1904,13 @@ mpd_command_delete(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } -/* - * Command handler function for 'deleteid' +/* Command handler function for 'deleteid' * Removes the song with given id from the playqueue. Expects argument argv[1] to be an integer (song id). */ -static int +static enum mpd_ack_error mpd_command_deleteid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { uint32_t songid; @@ -1904,16 +1930,18 @@ mpd_command_deleteid(struct evbuffer *evbuf, int argc, char **argv, char **errms return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } -//Moves the song at FROM or range of songs at START:END to TO in the playlist. -static int +// Moves the song at FROM or range of songs at START:END to TO in the playlist. +static enum mpd_ack_error mpd_command_move(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int start_pos; int end_pos; + int count; uint32_t to_pos; + uint32_t queue_length; int ret; ret = mpd_pars_range_arg(argv[1], &start_pos, &end_pos); @@ -1923,8 +1951,7 @@ mpd_command_move(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s return ACK_ERROR_ARG; } -// if (count > 1) -// DPRINTF(E_WARN, L_MPD, "Moving ranges is not supported, only the first item will be moved\n"); + count = end_pos - start_pos; ret = safe_atou32(argv[2], &to_pos); if (ret < 0) @@ -1933,10 +1960,8 @@ mpd_command_move(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s return ACK_ERROR_ARG; } - uint32_t queue_length; db_queue_get_count(&queue_length); - int count = end_pos - start_pos; // valid move pos and range is: // 0 <= start < queue_len // start < end <= queue_len @@ -1945,8 +1970,7 @@ mpd_command_move(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s && end_pos > start_pos && end_pos <= queue_length && to_pos >= 0 && to_pos <= queue_length - count)) { - *errmsg = safe_asprintf((to_pos > queue_length - count) - ? "Range too large for target position" : "Bad song index"); + *errmsg = safe_asprintf("Range too large for target position %d or bad song index (count %d, length %u)", to_pos, count, queue_length); return ACK_ERROR_ARG; } @@ -1957,10 +1981,10 @@ mpd_command_move(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_command_moveid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { uint32_t songid; @@ -1988,7 +2012,7 @@ mpd_command_moveid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } /* @@ -1998,7 +2022,7 @@ mpd_command_moveid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, * * The order of the songs is always the not shuffled order. */ -static int +static enum mpd_ack_error mpd_command_playlistid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct query_params query_params; @@ -2047,7 +2071,7 @@ mpd_command_playlistid(struct evbuffer *evbuf, int argc, char **argv, char **err db_queue_enum_end(&query_params); free(query_params.filter); - return 0; + return ACK_ERROR_NONE; } @@ -2058,7 +2082,7 @@ mpd_command_playlistid(struct evbuffer *evbuf, int argc, char **argv, char **err * * The order of the songs is always the not shuffled order. */ -static int +static enum mpd_ack_error mpd_command_playlistinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct query_params query_params; @@ -2110,10 +2134,10 @@ mpd_command_playlistinfo(struct evbuffer *evbuf, int argc, char **argv, char **e db_queue_enum_end(&query_params); free(query_params.filter); - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_command_playlistfind(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct query_params query_params; @@ -2154,10 +2178,10 @@ mpd_command_playlistfind(struct evbuffer *evbuf, int argc, char **argv, char **e db_queue_enum_end(&query_params); free(query_params.filter); - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_command_playlistsearch(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct query_params query_params; @@ -2198,10 +2222,10 @@ mpd_command_playlistsearch(struct evbuffer *evbuf, int argc, char **argv, char * db_queue_enum_end(&query_params); free(query_params.filter); - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error plchanges_build_queryparams(struct query_params *query_params, int argc, char **argv, char **errmsg) { uint32_t version; @@ -2238,23 +2262,24 @@ plchanges_build_queryparams(struct query_params *query_params, int argc, char ** else query_params->filter = db_mprintf("(queue_version > %d AND pos >= %d AND pos < %d)", version, start_pos, end_pos); - return 0; + return ACK_ERROR_NONE; } /* * Command handler function for 'plchanges' * Lists all changed songs in the queue since the given playlist version in argv[1]. */ -static int +static enum mpd_ack_error mpd_command_plchanges(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct query_params query_params; struct db_queue_item queue_item; int ret; + enum mpd_ack_error ack_error; - ret = plchanges_build_queryparams(&query_params, argc, argv, errmsg); - if (ret != 0) - return ret; + ack_error = plchanges_build_queryparams(&query_params, argc, argv, errmsg); + if (ack_error != ACK_ERROR_NONE) + return ack_error; ret = db_queue_enum_start(&query_params); if (ret < 0) @@ -2273,7 +2298,7 @@ mpd_command_plchanges(struct evbuffer *evbuf, int argc, char **argv, char **errm db_queue_enum_end(&query_params); free_query_params(&query_params, 1); - return 0; + return ACK_ERROR_NONE; error: db_queue_enum_end(&query_params); @@ -2286,7 +2311,7 @@ mpd_command_plchanges(struct evbuffer *evbuf, int argc, char **argv, char **errm * Command handler function for 'plchangesposid' * Lists all changed songs in the queue since the given playlist version in argv[1] without metadata. */ -static int +static enum mpd_ack_error mpd_command_plchangesposid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct query_params query_params; @@ -2313,7 +2338,7 @@ mpd_command_plchangesposid(struct evbuffer *evbuf, int argc, char **argv, char * db_queue_enum_end(&query_params); free_query_params(&query_params, 1); - return 0; + return ACK_ERROR_NONE; error: db_queue_enum_end(&query_params); @@ -2326,7 +2351,7 @@ mpd_command_plchangesposid(struct evbuffer *evbuf, int argc, char **argv, char * * Command handler function for 'listplaylist' * Lists all songs in the playlist given by virtual-path in argv[1]. */ -static int +static enum mpd_ack_error mpd_command_listplaylist(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { char *path; @@ -2382,14 +2407,14 @@ mpd_command_listplaylist(struct evbuffer *evbuf, int argc, char **argv, char **e free_pli(pli, 0); - return 0; + return ACK_ERROR_NONE; } /* * Command handler function for 'listplaylistinfo' * Lists all songs in the playlist given by virtual-path in argv[1] with metadata. */ -static int +static enum mpd_ack_error mpd_command_listplaylistinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { char *path; @@ -2447,14 +2472,14 @@ mpd_command_listplaylistinfo(struct evbuffer *evbuf, int argc, char **argv, char free_pli(pli, 0); - return 0; + return ACK_ERROR_NONE; } /* * Command handler function for 'listplaylists' * Lists all playlists with their last modified date. */ -static int +static enum mpd_ack_error mpd_command_listplaylists(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct query_params qp; @@ -2502,14 +2527,14 @@ mpd_command_listplaylists(struct evbuffer *evbuf, int argc, char **argv, char ** db_query_end(&qp); free(qp.filter); - return 0; + return ACK_ERROR_NONE; } /* * Command handler function for 'load' * Adds the playlist given by virtual-path in argv[1] to the queue. */ -static int +static enum mpd_ack_error mpd_command_load(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { char *path; @@ -2551,10 +2576,10 @@ mpd_command_load(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_command_playlistadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { char *vp_playlist; @@ -2589,10 +2614,10 @@ mpd_command_playlistadd(struct evbuffer *evbuf, int argc, char **argv, char **er return ACK_ERROR_ARG; } - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_command_rm(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { char *virtual_path; @@ -2623,10 +2648,10 @@ mpd_command_rm(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, str return ACK_ERROR_ARG; } - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_command_save(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { char *virtual_path; @@ -2657,10 +2682,10 @@ mpd_command_save(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s return ACK_ERROR_ARG; } - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_command_count(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct query_params qp; @@ -2695,10 +2720,10 @@ mpd_command_count(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, db_query_end(&qp); free(qp.filter); - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_command_find(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct query_params qp; @@ -2741,10 +2766,10 @@ mpd_command_find(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s db_query_end(&qp); free(qp.filter); - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_command_findadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct query_params qp; @@ -2775,7 +2800,7 @@ mpd_command_findadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } /* @@ -2783,7 +2808,6 @@ mpd_command_findadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg * While they should normally not be included in most ID3 tags, they sometimes * are, so we just change them to space. See #1613 for more details. */ - static void sanitize_value(char **strval) { @@ -2799,7 +2823,7 @@ sanitize_value(char **strval) } } -static int +static enum mpd_ack_error mpd_command_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct mpd_tagtype *tagtype; @@ -2825,7 +2849,7 @@ mpd_command_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s if (!tagtype || tagtype->type == MPD_TYPE_SPECIAL) //FIXME allow "file" tagtype { DPRINTF(E_WARN, L_MPD, "Unsupported type argument for command 'list': %s\n", argv[1]); - return 0; + return ACK_ERROR_NONE; } memset(&qp, 0, sizeof(struct query_params)); @@ -2893,10 +2917,10 @@ mpd_command_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, s free(qp.group); free(group); - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_add_directory(struct evbuffer *evbuf, int directory_id, int listall, int listinfo, char **errmsg) { struct directory_info subdir; @@ -2954,9 +2978,9 @@ mpd_add_directory(struct evbuffer *evbuf, int directory_id, int listall, int lis ret = db_directory_enum_start(&dir_enum); if (ret < 0) { - DPRINTF(E_LOG, L_MPD, "Failed to start directory enum for parent_id %d\n", directory_id); + *errmsg = safe_asprintf("Failed to start directory enum for parent_id %d\n", directory_id); db_directory_enum_end(&dir_enum); - return -1; + return ACK_ERROR_UNKNOWN; } while ((ret = db_directory_enum_fetch(&dir_enum, &subdir)) == 0 && subdir.id > 0) { @@ -3016,10 +3040,10 @@ mpd_add_directory(struct evbuffer *evbuf, int directory_id, int listall, int lis db_query_end(&qp); free(qp.filter); - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_command_listall(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int dir_id; @@ -3054,12 +3078,10 @@ mpd_command_listall(struct evbuffer *evbuf, int argc, char **argv, char **errmsg return ACK_ERROR_NO_EXIST; } - ret = mpd_add_directory(evbuf, dir_id, 1, 0, errmsg); - - return ret; + return mpd_add_directory(evbuf, dir_id, 1, 0, errmsg); } -static int +static enum mpd_ack_error mpd_command_listallinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int dir_id; @@ -3094,22 +3116,21 @@ mpd_command_listallinfo(struct evbuffer *evbuf, int argc, char **argv, char **er return ACK_ERROR_NO_EXIST; } - ret = mpd_add_directory(evbuf, dir_id, 1, 1, errmsg); - - return ret; + return mpd_add_directory(evbuf, dir_id, 1, 1, errmsg); } /* * Command handler function for 'lsinfo' * Lists the contents of the directory given in argv[1]. */ -static int +static enum mpd_ack_error mpd_command_lsinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int dir_id; char parent[PATH_MAX]; int print_playlists; int ret; + enum mpd_ack_error ack_error; if (argc < 2 || strlen(argv[1]) == 0 || (strncmp(argv[1], "/", 1) == 0 && strlen(argv[1]) == 1)) @@ -3151,15 +3172,15 @@ mpd_command_lsinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, return ACK_ERROR_NO_EXIST; } - ret = mpd_add_directory(evbuf, dir_id, 0, 1, errmsg); + ack_error = mpd_add_directory(evbuf, dir_id, 0, 1, errmsg); // If the root directory was passed as argument add the stored playlists to the response - if (ret == 0 && print_playlists) + if (ack_error == ACK_ERROR_NONE && print_playlists) { return mpd_command_listplaylists(evbuf, argc, argv, errmsg, ctx); } - return ret; + return ack_error; } /* @@ -3168,7 +3189,7 @@ mpd_command_lsinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, * This command should list all files including files that are not part of the library. We do not support this * and only report files in the library. */ -static int +static enum mpd_ack_error mpd_command_listfiles(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { return mpd_command_lsinfo(evbuf, argc, argv, errmsg, ctx); @@ -3188,7 +3209,7 @@ mpd_command_listfiles(struct evbuffer *evbuf, int argc, char **argv, char **errm * * Example request: "search artist foo album bar" */ -static int +static enum mpd_ack_error mpd_command_search(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct query_params qp; @@ -3231,10 +3252,10 @@ mpd_command_search(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, db_query_end(&qp); free(qp.filter); - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_command_searchadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct query_params qp; @@ -3265,14 +3286,14 @@ mpd_command_searchadd(struct evbuffer *evbuf, int argc, char **argv, char **errm return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } /* * Command handler function for 'update' * Initiates an init-rescan (scans for new files) */ -static int +static enum mpd_ack_error mpd_command_update(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { if (argc > 1 && strlen(argv[1]) > 0) @@ -3285,10 +3306,10 @@ mpd_command_update(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, evbuffer_add(evbuf, "updating_db: 1\n", 15); - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_sticker_get(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, const char *virtual_path) { struct media_file_info *mfi = NULL; @@ -3316,10 +3337,10 @@ mpd_sticker_get(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, co free_mfi(mfi, 0); - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_sticker_set(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, const char *virtual_path) { uint32_t rating; @@ -3355,10 +3376,10 @@ mpd_sticker_set(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, co library_item_attrib_save(id, LIBRARY_ATTRIB_RATING, rating); - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_sticker_delete(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, const char *virtual_path) { int id; @@ -3378,10 +3399,10 @@ mpd_sticker_delete(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, library_item_attrib_save(id, LIBRARY_ATTRIB_RATING, 0); - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_sticker_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, const char *virtual_path) { struct media_file_info *mfi = NULL; @@ -3404,10 +3425,10 @@ mpd_sticker_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, c free_mfi(mfi, 0); /* |:todo:| real sticker implementation */ - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_sticker_find(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, const char *virtual_path) { struct query_params qp; @@ -3462,8 +3483,7 @@ mpd_sticker_find(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, c if (!qp.filter) { *errmsg = safe_asprintf("Out of memory"); - ret = ACK_ERROR_UNKNOWN; - return ret; + return ACK_ERROR_UNKNOWN; } ret = db_query_start(&qp); @@ -3473,8 +3493,7 @@ mpd_sticker_find(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, c free(qp.filter); *errmsg = safe_asprintf("Could not start query"); - ret = ACK_ERROR_UNKNOWN; - return ret; + return ACK_ERROR_UNKNOWN; } while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0) @@ -3499,12 +3518,12 @@ mpd_sticker_find(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, c db_query_end(&qp); free(qp.filter); - return 0; + return ACK_ERROR_NONE; } struct mpd_sticker_command { const char *cmd; - int (*handler)(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, const char *virtual_path); + enum mpd_ack_error (*handler)(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, const char *virtual_path); int need_args; }; @@ -3536,13 +3555,13 @@ static struct mpd_sticker_command mpd_sticker_handlers[] = * sticker set song "file:/srv/music/VA/The Electro Swing Revolution Vol 3 1 - Hop, Hop, Hop/13 Mr. Hotcut - You Are.mp3" rating "6" * OK */ -static int +static enum mpd_ack_error mpd_command_sticker(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct mpd_sticker_command *cmd_param = NULL; // Quell compiler warning about uninitialized use of cmd_param char *virtual_path = NULL; int i; - int ret; + enum mpd_ack_error ack_error; if (strcmp(argv[2], "song") != 0) { @@ -3569,11 +3588,11 @@ mpd_command_sticker(struct evbuffer *evbuf, int argc, char **argv, char **errmsg virtual_path = prepend_slash(argv[3]); - ret = cmd_param->handler(evbuf, argc, argv, errmsg, virtual_path); + ack_error = cmd_param->handler(evbuf, argc, argv, errmsg, virtual_path); free(virtual_path); - return ret; + return ack_error; } /* @@ -3597,7 +3616,14 @@ mpd_command_rescan(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, } */ -static int +static enum mpd_ack_error +mpd_command_close(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) +{ + ctx->must_disconnect = true; + return ACK_ERROR_NONE; +} + +static enum mpd_ack_error mpd_command_password(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { char *required_password; @@ -3619,7 +3645,7 @@ mpd_command_password(struct evbuffer *evbuf, int argc, char **argv, char **errms supplied_password, unrequired ? " although no password is required" : ""); ctx->authenticated = true; - return 0; + return ACK_ERROR_NONE; } DPRINTF(E_LOG, L_MPD, @@ -3630,7 +3656,7 @@ mpd_command_password(struct evbuffer *evbuf, int argc, char **argv, char **errms return ACK_ERROR_PASSWORD; } -static int +static enum mpd_ack_error mpd_command_binarylimit(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { unsigned int size; @@ -3651,7 +3677,7 @@ mpd_command_binarylimit(struct evbuffer *evbuf, int argc, char **argv, char **er ctx->binarylimit = size; - return 0; + return ACK_ERROR_NONE; } /* @@ -3685,7 +3711,7 @@ output_get_cb(struct player_speaker_info *spk, void *arg) * Command handler function for 'disableoutput' * Expects argument argv[1] to be the id of the speaker to disable. */ -static int +static enum mpd_ack_error mpd_command_disableoutput(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct output_get_param param; @@ -3716,14 +3742,14 @@ mpd_command_disableoutput(struct evbuffer *evbuf, int argc, char **argv, char ** } } - return 0; + return ACK_ERROR_NONE; } /* * Command handler function for 'enableoutput' * Expects argument argv[1] to be the id of the speaker to enable. */ -static int +static enum mpd_ack_error mpd_command_enableoutput(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct output_get_param param; @@ -3754,14 +3780,14 @@ mpd_command_enableoutput(struct evbuffer *evbuf, int argc, char **argv, char **e } } - return 0; + return ACK_ERROR_NONE; } /* * Command handler function for 'toggleoutput' * Expects argument argv[1] to be the id of the speaker to enable/disable. */ -static int +static enum mpd_ack_error mpd_command_toggleoutput(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct output_get_param param; @@ -3796,7 +3822,7 @@ mpd_command_toggleoutput(struct evbuffer *evbuf, int argc, char **argv, char **e } } - return 0; + return ACK_ERROR_NONE; } /* @@ -3848,7 +3874,7 @@ speaker_enum_cb(struct player_speaker_info *spk, void *arg) * Command handler function for 'output' * Returns a lists with the avaiable speakers. */ -static int +static enum mpd_ack_error mpd_command_outputs(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { struct output_outputs_param param; @@ -3876,10 +3902,10 @@ mpd_command_outputs(struct evbuffer *evbuf, int argc, char **argv, char **errmsg param.nextid++; } - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error outputvolume_set(uint32_t shortid, int volume, char **errmsg) { struct output_get_param param; @@ -3907,10 +3933,10 @@ outputvolume_set(uint32_t shortid, int volume, char **errmsg) return ACK_ERROR_UNKNOWN; } - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_command_outputvolume(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { uint32_t shortid; @@ -3931,9 +3957,7 @@ mpd_command_outputvolume(struct evbuffer *evbuf, int argc, char **argv, char **e return ACK_ERROR_ARG; } - ret = outputvolume_set(shortid, volume, errmsg); - - return ret; + return outputvolume_set(shortid, volume, errmsg); } static void @@ -4036,7 +4060,7 @@ mpd_find_channel(const char *name) return NULL; } -static int +static enum mpd_ack_error mpd_command_channels(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int i; @@ -4048,10 +4072,10 @@ mpd_command_channels(struct evbuffer *evbuf, int argc, char **argv, char **errms mpd_channels[i].channel); } - return 0; + return ACK_ERROR_NONE; } -static int +static enum mpd_ack_error mpd_command_sendmessage(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { const char *channelname; @@ -4072,34 +4096,46 @@ mpd_command_sendmessage(struct evbuffer *evbuf, int argc, char **argv, char **er { // Just ignore the message, only log an error message DPRINTF(E_LOG, L_MPD, "Unsupported channel '%s'\n", channelname); - return 0; + return ACK_ERROR_NONE; } channel->handler(message); - return 0; + return ACK_ERROR_NONE; } /* * Dummy function to handle commands that are not supported and should * not raise an error. */ -static int +static enum mpd_ack_error mpd_command_ignore(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { //do nothing DPRINTF(E_DBG, L_MPD, "Ignore command %s\n", argv[0]); - return 0; + return ACK_ERROR_NONE; } -static int -mpd_command_commands(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx); +static enum mpd_ack_error +mpd_command_commands(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) +{ + int i; + + for (i = 0; mpd_handlers[i].handler; i++) + { + evbuffer_add_printf(evbuf, + "command: %s\n", + mpd_handlers[i].mpdcommand); + } + + return ACK_ERROR_NONE; +} /* * Command handler function for 'tagtypes' * Returns a lists with supported tags in the form: * tagtype: Artist */ -static int +static enum mpd_ack_error mpd_command_tagtypes(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int i; @@ -4110,7 +4146,7 @@ mpd_command_tagtypes(struct evbuffer *evbuf, int argc, char **argv, char **errms evbuffer_add_printf(evbuf, "tagtype: %s\n", tagtypes[i].tag); } - return 0; + return ACK_ERROR_NONE; } /* @@ -4118,7 +4154,7 @@ mpd_command_tagtypes(struct evbuffer *evbuf, int argc, char **argv, char **errms * Returns a lists with supported tags in the form: * handler: protocol:// */ -static int +static enum mpd_ack_error mpd_command_urlhandlers(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { evbuffer_add_printf(evbuf, @@ -4141,7 +4177,7 @@ mpd_command_urlhandlers(struct evbuffer *evbuf, int argc, char **argv, char **er // "handler: alsa://\n" ); - return 0; + return ACK_ERROR_NONE; } /* @@ -4151,7 +4187,7 @@ mpd_command_urlhandlers(struct evbuffer *evbuf, int argc, char **argv, char **er * The server only uses libav/ffmepg for decoding and does not support decoder plugins, * therefor the function reports only ffmpeg as available. */ -static int +static enum mpd_ack_error mpd_command_decoders(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { int i; @@ -4168,26 +4204,39 @@ mpd_command_decoders(struct evbuffer *evbuf, int argc, char **argv, char **errms evbuffer_add_printf(evbuf, "mime_type: %s\n", ffmpeg_mime_types[i]); } - return 0; + return ACK_ERROR_NONE; } -struct mpd_command +static enum mpd_ack_error +mpd_command_command_list_begin(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) { - /* The command name */ - const char *mpdcommand; + ctx->cmd_list_type = COMMAND_LIST_BEGIN; + return ACK_ERROR_NONE; +} + +static enum mpd_ack_error +mpd_command_command_list_ok_begin(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) +{ + ctx->cmd_list_type = COMMAND_LIST_OK_BEGIN; + return ACK_ERROR_NONE; +} + +static enum mpd_ack_error +mpd_command_command_list_end(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) +{ + if (ctx->cmd_list_type == COMMAND_LIST_BEGIN) + ctx->cmd_list_type = COMMAND_LIST_END; + else if (ctx->cmd_list_type == COMMAND_LIST_OK_BEGIN) + ctx->cmd_list_type = COMMAND_LIST_OK_END; + else + { + *errmsg = safe_asprintf("Got with command list end without preceeding list start"); + return ACK_ERROR_ARG; + } + + return ACK_ERROR_NONE; +} - /* - * The function to execute the command - * - * @param evbuf the response event buffer - * @param argc number of arguments in argv - * @param argv argument array, first entry is the commandname - * @param errmsg error message set by this function if an error occured - * @return 0 if successful, one of ack values if an error occured - */ - int (*handler)(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx); - int min_argc; -}; static struct mpd_command mpd_handlers[] = { @@ -4197,6 +4246,7 @@ static struct mpd_command mpd_handlers[] = { "clearerror", mpd_command_ignore, -1 }, { "currentsong", mpd_command_currentsong, -1 }, { "idle", mpd_command_idle, -1 }, + { "noidle", mpd_command_noidle, -1 }, { "status", mpd_command_status, -1 }, { "stats", mpd_command_stats, -1 }, @@ -4287,7 +4337,7 @@ static struct mpd_command mpd_handlers[] = { "sticker", mpd_command_sticker, 4 }, // Connection settings - { "close", mpd_command_ignore, -1 }, + { "close", mpd_command_close, -1 }, // { "kill", mpd_command_kill, -1 }, { "password", mpd_command_password, -1 }, { "ping", mpd_command_ignore, -1 }, @@ -4300,13 +4350,8 @@ static struct mpd_command mpd_handlers[] = { "toggleoutput", mpd_command_toggleoutput, 2 }, { "outputs", mpd_command_outputs, -1 }, - // Reflection -// { "config", mpd_command_config, -1 }, - { "commands", mpd_command_commands, -1 }, - { "notcommands", mpd_command_ignore, -1 }, - { "tagtypes", mpd_command_tagtypes, -1 }, - { "urlhandlers", mpd_command_urlhandlers, -1 }, - { "decoders", mpd_command_decoders, -1 }, + // Custom command outputvolume (not supported by mpd) + { "outputvolume", mpd_command_outputvolume, 3 }, // Client to client { "subscribe", mpd_command_ignore, -1 }, @@ -4315,8 +4360,18 @@ static struct mpd_command mpd_handlers[] = { "readmessages", mpd_command_ignore, -1 }, { "sendmessage", mpd_command_sendmessage, -1 }, - // Custom commands (not supported by mpd) - { "outputvolume", mpd_command_outputvolume, 3 }, + // Reflection +// { "config", mpd_command_config, -1 }, + { "commands", mpd_command_commands, -1 }, + { "notcommands", mpd_command_ignore, -1 }, + { "tagtypes", mpd_command_tagtypes, -1 }, + { "urlhandlers", mpd_command_urlhandlers, -1 }, + { "decoders", mpd_command_decoders, -1 }, + + // Command lists + { "command_list_begin", mpd_command_command_list_begin, -1 }, + { "command_list_ok_begin", mpd_command_command_list_ok_begin, -1 }, + { "command_list_end", mpd_command_command_list_end, -1 }, // NULL command to terminate loop { NULL, NULL, -1 } @@ -4344,98 +4399,11 @@ mpd_find_command(const char *name) return NULL; } -static int -mpd_command_commands(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx) -{ - int i; - - for (i = 0; mpd_handlers[i].handler; i++) - { - evbuffer_add_printf(evbuf, - "command: %s\n", - mpd_handlers[i].mpdcommand); - } - - return 0; -} - -static inline int -mpd_ack_response(struct evbuffer *output, int err_code, int cmd_num, char *cmd_name, char *errmsg) -{ - return evbuffer_add_printf(output, "ACK [%d@%d] {%s} %s\n", err_code, cmd_num, cmd_name, errmsg); -} - -static inline int -mpd_ok_response(struct evbuffer *output) -{ - return evbuffer_add_printf(output, "OK\n"); -} - -/** - * Add a command line, including null terminator, into the command list buffer. - * These command lines will be processed when command_list_end is received. - */ -static int -mpd_command_list_add(struct mpd_client_ctx *client_ctx, char* line) -{ - if (client_ctx->cmd_list_buffer == NULL) - { - client_ctx->cmd_list_buffer = evbuffer_new(); - } - - return evbuffer_add(client_ctx->cmd_list_buffer, line, strlen(line) + 1); -} - -/** - * The result returned by mpd_process_command() and mpd_process_line() - */ -typedef enum mpd_command_result { - /** - * command handled with success. - * the connection should stay open. - * no response was sent. - * the caller of mpd_process_command sends OK or list_OK. - */ - CMD_RESULT_OK = 0, - - /** - * command handled with error. - * ack response was send by the command handler. - * the connection should stay open. - */ - CMD_RESULT_ERROR = 1, - - /** - * the client entered idle state. - * no response should be sent to the client. - */ - CMD_RESULT_IDLE = 2, - - /** - * the client connection should be closed - */ - CMD_RESULT_CLOSE = 3, -} -MpdCommandResult; - -typedef enum mpd_parse_args_result { - ARGS_OK, - ARGS_EMPTY, - ARGS_TOO_MANY, - ARGS_ERROR -} -MpdParseArgsResult; - -/* - * Parses the argument string into an array of strings. - * Arguments are seperated by a whitespace character and may be wrapped in double quotes. - * - * @param args the arguments - * @param argc the number of arguments in the argument string - * @param argv the array containing the found arguments - */ -static MpdParseArgsResult -mpd_parse_args(char *args, int *argc, char **argv, int argv_size) +// Parses the argument string into an array of strings. Arguments are separated +// by a whitespace character and may be wrapped in double quotes. ack_error is +// set and errmsg allocated if there is an error. +static enum mpd_ack_error +mpd_parse_args(char **argv, int argv_size, int *argc, char *args, bool *must_disconnect, char **errmsg) { char *input = args; int arg_count = 0; @@ -4446,331 +4414,242 @@ mpd_parse_args(char *args, int *argc, char **argv, int argv_size) { // Ignore whitespace characters if (*input == ' ') - { - input++; - continue; - } + { + input++; + continue; + } // Check if the parameter is wrapped in double quotes if (*input == '"') - { - argv[arg_count] = mpd_pars_quoted(&input); - if (argv[arg_count] == NULL) - { - return ARGS_ERROR; - } - arg_count += 1; - } + { + argv[arg_count] = mpd_pars_quoted(&input); + if (!argv[arg_count]) + goto error; + } else - { - argv[arg_count++] = mpd_pars_unquoted(&input); - } + { + argv[arg_count] = mpd_pars_unquoted(&input); + } + + arg_count++; } DPRINTF(E_SPAM, L_MPD, "Parse args: args count = \"%d\"\n", arg_count); *argc = arg_count; if (arg_count == 0) - return ARGS_EMPTY; + { + *errmsg = safe_asprintf("No command given"); + *must_disconnect = true; // in this case MPD disconnects the client + return ACK_ERROR_ARG; + } if (*input != 0 && arg_count == argv_size) - return ARGS_TOO_MANY; + { + *errmsg = safe_asprintf("Too many arguments: %d allowed", argv_size); + return ACK_ERROR_ARG; // in this case MPD doesn't disconnect the client + } - return ARGS_OK; + return ACK_ERROR_NONE; + + error: + *errmsg = safe_asprintf("Error parsing arguments"); + *must_disconnect = true; // in this case MPD disconnects the client + return ACK_ERROR_UNKNOWN; } -/** - * Process one command line. - * @param line - * @param output - * @param cmd_num - * @param client_ctx - * @return - */ -static MpdCommandResult -mpd_process_command(char *line, struct evbuffer *output, int cmd_num, struct mpd_client_ctx *client_ctx) +static enum mpd_ack_error +mpd_list_add(struct mpd_client_ctx *client_ctx, const char *line) { - char *argv[COMMAND_ARGV_MAX]; - int argc = 0; + size_t sz = strlen(line) + 1; + int ret; + + if (evbuffer_get_length(client_ctx->cmd_list_buffer) + sz > MPD_MAX_COMMAND_LIST_SIZE) + { + DPRINTF(E_LOG, L_MPD, "Max command list size (%uKB) exceeded\n", (MPD_MAX_COMMAND_LIST_SIZE / 1024)); + client_ctx->must_disconnect = true; + return ACK_ERROR_NONE; + } + + ret = evbuffer_add(client_ctx->cmd_list_buffer, line, sz); + if (ret < 0) + { + DPRINTF(E_LOG, L_MPD, "Failed to add to command list\n"); + client_ctx->must_disconnect = true; + return ACK_ERROR_NONE; + } + + return ACK_ERROR_NONE; +} + +static enum mpd_ack_error +mpd_process_command_line(struct evbuffer *output, char *line, int cmd_num, struct mpd_client_ctx *client_ctx) +{ + enum mpd_ack_error ack_error; + bool got_noidle; + char *argv[MPD_COMMAND_ARGV_MAX] = { 0 }; // Zero init just to silence false positive from scan-build + int argc = 0; // Also to silence scan-build + char *cmd_name = NULL; + struct mpd_command *command; char *errmsg = NULL; - int mpd_err_code = 0; - char *cmd_name = "unknown"; - MpdCommandResult cmd_result = CMD_RESULT_OK; + + // We're in command list mode, just add command to buffer and return + if ((client_ctx->cmd_list_type == COMMAND_LIST_BEGIN || client_ctx->cmd_list_type == COMMAND_LIST_OK_BEGIN) && strcmp(line, "command_list_end") != 0) + { + return mpd_list_add(client_ctx, line); + } + + got_noidle = (strcmp(line, "noidle") == 0); + if (got_noidle && !client_ctx->is_idle) + { + return ACK_ERROR_NONE; // Just ignore, don't proceed to send an OK + } + else if (!got_noidle && client_ctx->is_idle) + { + errmsg = safe_asprintf("Only 'noidle' is allowed during idle"); + ack_error = ACK_ERROR_ARG; + client_ctx->must_disconnect = true; + goto error; + } // Split the read line into command name and arguments - MpdParseArgsResult args_result = mpd_parse_args(line, &argc, argv, COMMAND_ARGV_MAX); - - switch (args_result) + ack_error = mpd_parse_args(argv, MPD_COMMAND_ARGV_MAX, &argc, line, &client_ctx->must_disconnect, &errmsg); + if (ack_error != ACK_ERROR_NONE) { - case ARGS_EMPTY: - DPRINTF(E_LOG, L_MPD, "No command given\n"); - errmsg = safe_asprintf("No command given"); - mpd_err_code = ACK_ERROR_ARG; - // in this case MPD disconnects the client - cmd_result = CMD_RESULT_CLOSE; - break; - - case ARGS_TOO_MANY: - DPRINTF(E_LOG, L_MPD, "Number of arguments exceeds max of %d: %s\n", COMMAND_ARGV_MAX, line); - errmsg = safe_asprintf("Too many arguments: %d allowed", COMMAND_ARGV_MAX); - mpd_err_code = ACK_ERROR_ARG; - // in this case MPD doesn't disconnect the client - cmd_result = CMD_RESULT_ERROR; - break; - - case ARGS_ERROR: - // Error handling for argument parsing error - DPRINTF(E_LOG, L_MPD, "Error parsing arguments for MPD message: %s\n", line); - errmsg = safe_asprintf("Error parsing arguments"); - mpd_err_code = ACK_ERROR_UNKNOWN; - // in this case MPD disconnects the client - cmd_result = CMD_RESULT_CLOSE; - break; - - case ARGS_OK: - cmd_name = argv[0]; - /* - * Find the command handler and execute the command function - */ - struct mpd_command *command = mpd_find_command(cmd_name); - - if (command == NULL) - { - errmsg = safe_asprintf("unknown command \"%s\"", cmd_name); - mpd_err_code = ACK_ERROR_UNKNOWN; - } - else if (command->min_argc > argc) - { - errmsg = safe_asprintf("Missing argument(s) for command '%s', expected %d, given %d", argv[0], - command->min_argc, argc); - mpd_err_code = ACK_ERROR_ARG; - } - else if (!client_ctx->authenticated && strcmp(cmd_name, "password") != 0) - { - errmsg = safe_asprintf("Not authenticated"); - mpd_err_code = ACK_ERROR_PERMISSION; - } - else - { - mpd_err_code = command->handler(output, argc, argv, &errmsg, client_ctx); - } - break; + goto error; } - /* - * If an error occurred, add the ACK line to the response buffer - */ - if (mpd_err_code != 0) + CHECK_NULL(L_MPD, argv[0]); + + cmd_name = argv[0]; + if (strcmp(cmd_name, "password") != 0 && !client_ctx->authenticated) { - DPRINTF(E_LOG, L_MPD, "Error executing command '%s': %s\n", line, errmsg); - mpd_ack_response(output, mpd_err_code, cmd_num, cmd_name, errmsg); - if (cmd_result == CMD_RESULT_OK) - { - cmd_result = CMD_RESULT_ERROR; - } - } - else if (0 == strcmp(cmd_name, "idle")) - { - cmd_result = CMD_RESULT_IDLE; - } - else if (0 == strcmp(cmd_name, "close")) - { - cmd_result = CMD_RESULT_CLOSE; + errmsg = safe_asprintf("Not authenticated"); + ack_error = ACK_ERROR_PERMISSION; + goto error; } - if (errmsg != NULL) - free(errmsg); + command = mpd_find_command(cmd_name); + if (!command) + { + errmsg = safe_asprintf("Unknown command"); + ack_error = ACK_ERROR_UNKNOWN; + goto error; + } + else if (command->min_argc > argc) + { + errmsg = safe_asprintf("Missing argument(s), expected %d, given %d", command->min_argc, argc); + ack_error = ACK_ERROR_ARG; + goto error; + } - return cmd_result; + ack_error = command->handler(output, argc, argv, &errmsg, client_ctx); + if (ack_error != ACK_ERROR_NONE) + { + goto error; + } + + if (client_ctx->cmd_list_type == COMMAND_LIST_NONE && !client_ctx->is_idle) + evbuffer_add_printf(output, "OK\n"); + + return ack_error; + + error: + DPRINTF(E_LOG, L_MPD, "Error processing command '%s': %s\n", line, errmsg); + + if (cmd_name) + evbuffer_add_printf(output, "ACK [%d@%d] {%s} %s\n", ack_error, cmd_num, cmd_name, errmsg); + + free(errmsg); + + return ack_error; } -static MpdCommandResult -mpd_process_line(char *line, struct evbuffer *output, struct mpd_client_ctx *client_ctx) +// Process the commands that were added to client_ctx->cmd_list_buffer +// From MPD documentation (https://mpd.readthedocs.io/en/latest/protocol.html#command-lists): +// It does not execute any commands until the list has ended. The response is +// a concatenation of all individual responses. +// If a command fails, no more commands are executed and the appropriate ACK +// error is returned. If command_list_ok_begin is used, list_OK is returned +// for each successful command executed in the command list. +// On success for all commands, OK is returned. +static void +mpd_process_command_list(struct evbuffer *output, struct mpd_client_ctx *client_ctx) { - /* - * "noidle" is ignored unless the client is in idle state - */ - if (0 == strcmp(line, "noidle")) - { - if (client_ctx->is_idle) - { - // leave idle state and send OK - client_ctx->is_idle = false; - mpd_ok_response(output); - } + char *line; + enum mpd_ack_error ack_error = ACK_ERROR_NONE; + int cmd_num = 0; - return CMD_RESULT_OK; + while ((line = evbuffer_readln(client_ctx->cmd_list_buffer, NULL, EVBUFFER_EOL_NUL))) + { + ack_error = mpd_process_command_line(output, line, cmd_num, client_ctx); + cmd_num++; + + free(line); + + if (ack_error != ACK_ERROR_NONE) + break; + + if (client_ctx->cmd_list_type == COMMAND_LIST_OK_END) + evbuffer_add_printf(output, "list_OK\n"); } - /* - * in idle state the only allowed command is "noidle" - */ - if (client_ctx->is_idle) - { - // during idle state client must not send anything except "noidle" - DPRINTF(E_FATAL, L_MPD, "Only \"noidle\" is allowed during idle. received: %s\n", line); - return CMD_RESULT_CLOSE; - } + if (ack_error == ACK_ERROR_NONE) + evbuffer_add_printf(output, "OK\n"); - MpdCommandResult res = CMD_RESULT_OK; - - if (client_ctx->cmd_list_type == COMMAND_LIST_NONE) - { - // not in command list - - if (0 == strcmp(line, "command_list_begin")) - { - client_ctx->cmd_list_type = COMMAND_LIST; - return CMD_RESULT_OK; - } - - if (0 == strcmp(line, "command_list_ok_begin")) - { - client_ctx->cmd_list_type = COMMAND_LIST_OK; - return CMD_RESULT_OK; - } - - res = mpd_process_command(line, output, 0, client_ctx); - - DPRINTF(E_DBG, L_MPD, "Command \"%s\" returned: %d\n", line, res); - - if (res == CMD_RESULT_OK) - { - mpd_ok_response(output); - } - } - else - { - // in command list - if (0 == strcmp(line, "command_list_end")) - { - // end of command list: - // process the commands that were added to client_ctx->cmd_list_buffer - // From MPD documentation (https://mpd.readthedocs.io/en/latest/protocol.html#command-lists): - // It does not execute any commands until the list has ended. The response is - // a concatenation of all individual responses. - // On success for all commands, OK is returned. - // If a command fails, no more commands are executed and the appropriate ACK error is returned. - // If command_list_ok_begin is used, list_OK is returned - // for each successful command executed in the command list. - - DPRINTF(E_DBG, L_MPD, "process command list\n"); - - bool ok_mode = (client_ctx->cmd_list_type == COMMAND_LIST_OK); - struct evbuffer *commands_buffer = client_ctx->cmd_list_buffer; - - client_ctx->cmd_list_type = COMMAND_LIST_NONE; - client_ctx->cmd_list_buffer = NULL; - - int cmd_num = 0; - char *cmd_line; - - if (commands_buffer != NULL) - { - while ((cmd_line = evbuffer_readln(commands_buffer, NULL, EVBUFFER_EOL_NUL))) - { - res = mpd_process_command(cmd_line, output, cmd_num++, client_ctx); - - free(cmd_line); - - if (res != CMD_RESULT_OK) break; - - if (ok_mode) - evbuffer_add_printf(output, "list_OK\n"); - } - - evbuffer_free(commands_buffer); - } - - DPRINTF(E_DBG, L_MPD, "Command list returned: %d\n", res); - - if (res == CMD_RESULT_OK) - { - mpd_ok_response(output); - } - } - else - { - // in command list: - // save commands in the client context - if (-1 == mpd_command_list_add(client_ctx, line)) - { - DPRINTF(E_FATAL, L_MPD, "Failed to add to command list\n"); - res = CMD_RESULT_CLOSE; - } - else if (evbuffer_get_length(client_ctx->cmd_list_buffer) > Config.MaxCommandListSize) - { - DPRINTF(E_FATAL, L_MPD, "Max command list size (%uKB) exceeded\n", (Config.MaxCommandListSize / 1024)); - res = CMD_RESULT_CLOSE; - } - else - { - res = CMD_RESULT_OK; - } - } - } - return res; + // Back to single-command mode + evbuffer_drain(client_ctx->cmd_list_buffer, -1); + client_ctx->cmd_list_type = COMMAND_LIST_NONE; } +/* --------------------------- Server implementation ------------------------ */ + /* - * The read callback function is invoked if a complete command sequence was received from the client - * (see mpd_input_filter function). + * The read callback function is invoked if a complete command sequence was + * received from the client (see mpd_input_filter function). * * @param bev the buffer event * @param ctx used for authentication */ static void -mpd_read_cb(struct bufferevent *bev, void *ctx) +mpd_read_cb(struct bufferevent *bev, void *arg) { + struct mpd_client_ctx *client_ctx = arg; struct evbuffer *input; struct evbuffer *output; char *line; - struct mpd_client_ctx *client_ctx = (struct mpd_client_ctx *)ctx; - if (client_ctx->is_closing) - { - // after freeing the bev ignore any reads - return; - } - - /* Get the input evbuffer, contains the command sequence received from the client */ + // Contains the command sequence received from the client input = bufferevent_get_input(bev); - /* Get the output evbuffer, used to send the server response to the client */ + // Used to send the server response to the client output = bufferevent_get_output(bev); while ((line = evbuffer_readln(input, NULL, EVBUFFER_EOL_ANY))) { - DPRINTF(E_DBG, L_MPD, "MPD message: \"%s\"\n", line); + DPRINTF(E_DBG, L_MPD, "MPD message: '%s'\n", line); - enum mpd_command_result res = mpd_process_line(line, output, client_ctx); + mpd_process_command_line(output, line, 0, client_ctx); free(line); - switch (res) { - case CMD_RESULT_ERROR: - case CMD_RESULT_IDLE: - case CMD_RESULT_OK: - break; + if (client_ctx->cmd_list_type == COMMAND_LIST_END || client_ctx->cmd_list_type == COMMAND_LIST_OK_END) + mpd_process_command_list(output, client_ctx); - case CMD_RESULT_CLOSE: - client_ctx->is_closing = true; - - if (client_ctx->cmd_list_buffer != NULL) - { - evbuffer_free(client_ctx->cmd_list_buffer); - client_ctx->cmd_list_buffer = NULL; - } - - /* - * Freeing the bufferevent closes the connection, if it was - * opened with BEV_OPT_CLOSE_ON_FREE. - * Since bufferevent is reference-counted, it will happen as - * soon as possible, not necessarily immediately. - */ - bufferevent_free(bev); - break; - } + if (client_ctx->must_disconnect) + goto disconnect; } + + return; + + disconnect: + DPRINTF(E_DBG, L_MPD, "Disconnecting client\n"); + + // Freeing the bufferevent closes the connection, since it was opened with + // BEV_OPT_CLOSE_ON_FREE. Bufferevents are internally reference-counted, so if + // the bufferevent has pending deferred callbacks when you free it, it won’t + // be deleted until the callbacks are done. + bufferevent_setcb(bev, NULL, NULL, NULL, NULL); + bufferevent_free(bev); } /* @@ -4817,10 +4696,11 @@ mpd_input_filter(struct evbuffer *src, struct evbuffer *dst, ev_ssize_t lim, enu ret = evbuffer_add_printf(dst, "%s\n", line); if (ret < 0) { - DPRINTF(E_LOG, L_MPD, "Error adding line to buffer: '%s'\n", line); - free(line); - return BEV_ERROR; - } + DPRINTF(E_LOG, L_MPD, "Error adding line to buffer: '%s'\n", line); + free(line); + return BEV_ERROR; + } + free(line); output_count += ret; } @@ -4855,14 +4735,13 @@ mpd_accept_conn_cb(struct evconnlistener *listener, */ struct event_base *base = evconnlistener_get_base(listener); struct bufferevent *bev = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE); - struct mpd_client_ctx *client_ctx = calloc(1, sizeof(struct mpd_client_ctx)); + struct mpd_client_ctx *client_ctx = client_ctx_add(); - if (!client_ctx) - { - DPRINTF(E_LOG, L_MPD, "Out of memory for command context\n"); - bufferevent_free(bev); - return; - } + bev = bufferevent_filter_new(bev, mpd_input_filter, NULL, BEV_OPT_CLOSE_ON_FREE, client_ctx_remove, client_ctx); + bufferevent_setcb(bev, mpd_read_cb, NULL, mpd_event_cb, client_ctx); + bufferevent_enable(bev, EV_READ | EV_WRITE); + + client_ctx->evbuffer = bufferevent_get_output(bev); client_ctx->authenticated = !cfg_getstr(cfg_getsec(cfg, "library"), "password"); if (!client_ctx->authenticated) @@ -4872,22 +4751,11 @@ mpd_accept_conn_cb(struct evconnlistener *listener, client_ctx->binarylimit = MPD_BINARY_SIZE; - client_ctx->next = mpd_clients; - mpd_clients = client_ctx; - - bev = bufferevent_filter_new(bev, mpd_input_filter, NULL, BEV_OPT_CLOSE_ON_FREE, free_mpd_client_ctx, client_ctx); - bufferevent_setcb(bev, mpd_read_cb, NULL, mpd_event_cb, client_ctx); - bufferevent_enable(bev, EV_READ | EV_WRITE); - /* * According to the mpd protocol send "OK MPD \n" to the client, where version is the version * of the supported mpd protocol and not the server version. */ - evbuffer_add(bufferevent_get_output(bev), "OK MPD 0.20.0\n", 14); - client_ctx->evbuffer = bufferevent_get_output(bev); - client_ctx->cmd_list_type = COMMAND_LIST_NONE; - client_ctx->is_idle = false; - client_ctx->is_closing = false; + evbuffer_add(client_ctx->evbuffer, "OK MPD 0.22.4\n", 14); DPRINTF(E_INFO, L_MPD, "New mpd client connection accepted\n"); } @@ -4906,49 +4774,6 @@ mpd_accept_error_cb(struct evconnlistener *listener, void *ctx) DPRINTF(E_LOG, L_MPD, "Error occured %d (%s) on the listener.\n", err, evutil_socket_error_to_string(err)); } -static int -mpd_notify_idle_client(struct mpd_client_ctx *client_ctx, short events) -{ - if (!client_ctx->is_idle) - { - client_ctx->events |= events; - return 1; - } - - if (!(client_ctx->idle_events & events)) - { - DPRINTF(E_DBG, L_MPD, "Client not listening for events: %d\n", events); - return 1; - } - - if (events & LISTENER_DATABASE) - evbuffer_add(client_ctx->evbuffer, "changed: database\n", 18); - if (events & LISTENER_UPDATE) - evbuffer_add(client_ctx->evbuffer, "changed: update\n", 16); - if (events & LISTENER_QUEUE) - evbuffer_add(client_ctx->evbuffer, "changed: playlist\n", 18); - if (events & LISTENER_PLAYER) - evbuffer_add(client_ctx->evbuffer, "changed: player\n", 16); - if (events & LISTENER_VOLUME) - evbuffer_add(client_ctx->evbuffer, "changed: mixer\n", 15); - if (events & LISTENER_SPEAKER) - evbuffer_add(client_ctx->evbuffer, "changed: output\n", 16); - if (events & LISTENER_OPTIONS) - evbuffer_add(client_ctx->evbuffer, "changed: options\n", 17); - if (events & LISTENER_STORED_PLAYLIST) - evbuffer_add(client_ctx->evbuffer, "changed: stored_playlist\n", 25); - if (events & LISTENER_RATING) - evbuffer_add(client_ctx->evbuffer, "changed: sticker\n", 17); - - evbuffer_add(client_ctx->evbuffer, "OK\n", 3); - - client_ctx->is_idle = false; - client_ctx->idle_events = 0; - client_ctx->events = 0; - - return 0; -} - static enum command_state mpd_notify_idle(void *arg, int *retval) { @@ -4965,7 +4790,10 @@ mpd_notify_idle(void *arg, int *retval) { DPRINTF(E_DBG, L_MPD, "Notify client #%d\n", i); - mpd_notify_idle_client(client, event_mask); + notify_idle_client(client, event_mask); + + evbuffer_add_printf(client->evbuffer, "OK\n"); + client = client->next; i++; } @@ -5099,6 +4927,33 @@ artwork_cb(struct evhttp_request *req, void *arg) free(decoded_path); } + +/* -------------------------------- Event loop ------------------------------ */ + +/* Thread: mpd */ +static void * +mpd(void *arg) +{ + int ret; + + ret = db_perthread_init(); + if (ret < 0) + { + DPRINTF(E_LOG, L_MPD, "Error: DB init failed\n"); + + pthread_exit(NULL); + } + + event_base_dispatch(evbase_mpd); + + db_perthread_deinit(); + + pthread_exit(NULL); +} + + +/* -------------------------------- Init/deinit ----------------------------- */ + /* Thread: main */ static int mpd_httpd_init(void) @@ -5145,26 +5000,12 @@ mpd_init(void) const char *pl_dir; int ret; - cfg_t *mpd_section = cfg_getsec(cfg, "mpd"); - - port = cfg_getint(mpd_section, "port"); + port = cfg_getint(cfg_getsec(cfg, "mpd"), "port"); if (port <= 0) { DPRINTF(E_INFO, L_MPD, "MPD not enabled\n"); return 0; } - - long max_command_list_size = cfg_getint(mpd_section, "max_command_list_size"); - if (max_command_list_size <= 0 || max_command_list_size > INT_MAX) - { - DPRINTF(E_INFO, L_MPD, "Ignoring invalid config \"max_command_list_size\" (%ld). Will use the default %uKB instead.\n", - max_command_list_size, - (Config.MaxCommandListSize / 1024)); - } - else - { - Config.MaxCommandListSize = max_command_list_size * 1024; // from KB to bytes - } CHECK_NULL(L_MPD, evbase_mpd = event_base_new()); CHECK_NULL(L_MPD, cmdbase = commands_base_new(evbase_mpd, NULL)); @@ -5271,7 +5112,7 @@ mpd_deinit(void) while (mpd_clients) { - free_mpd_client_ctx(mpd_clients); + client_ctx_remove(mpd_clients); } mpd_httpd_deinit();