diff options
Diffstat (limited to 'src/command.c')
-rw-r--r-- | src/command.c | 1038 |
1 files changed, 579 insertions, 459 deletions
diff --git a/src/command.c b/src/command.c index 64f161805..7d4403399 100644 --- a/src/command.c +++ b/src/command.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,17 +19,19 @@ #include "config.h" #include "command.h" +#include "protocol/argparser.h" +#include "protocol/result.h" #include "player_control.h" #include "playlist.h" #include "playlist_print.h" #include "playlist_save.h" #include "playlist_queue.h" +#include "playlist_error.h" #include "queue_print.h" #include "ls.h" #include "uri.h" #include "decoder_print.h" #include "directory.h" -#include "directory_print.h" #include "database.h" #include "update.h" #include "volume.h" @@ -42,18 +44,28 @@ #include "output_print.h" #include "locate.h" #include "dbUtils.h" +#include "db_error.h" +#include "db_print.h" +#include "db_selection.h" +#include "db_lock.h" #include "tag.h" #include "client.h" +#include "client_idle.h" +#include "client_internal.h" +#include "client_subscribe.h" +#include "client_file.h" #include "tag_print.h" #include "path.h" #include "replay_gain_config.h" #include "idle.h" +#include "mapper.h" +#include "song.h" +#include "song_print.h" #ifdef ENABLE_SQLITE #include "sticker.h" #include "sticker_print.h" #include "song_sticker.h" -#include "song_print.h" #endif #include <assert.h> @@ -98,221 +110,6 @@ struct command { enum command_return (*handler)(struct client *client, int argc, char **argv); }; -/* this should really be "need a non-negative integer": */ -static const char need_positive[] = "need a positive integer"; /* no-op */ -static const char need_range[] = "need a range"; - -/* FIXME: redundant error messages */ -static const char check_integer[] = "\"%s\" is not a integer"; -static const char need_integer[] = "need an integer"; - -static const char *current_command; -static int command_list_num; - -void command_success(struct client *client) -{ - client_puts(client, "OK\n"); -} - -static void command_error_v(struct client *client, enum ack error, - const char *fmt, va_list args) -{ - assert(client != NULL); - assert(current_command != NULL); - - client_printf(client, "ACK [%i@%i] {%s} ", - (int)error, command_list_num, current_command); - client_vprintf(client, fmt, args); - client_puts(client, "\n"); - - current_command = NULL; -} - -G_GNUC_PRINTF(3, 4) static void command_error(struct client *client, enum ack error, - const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - command_error_v(client, error, fmt, args); - va_end(args); -} - -static bool G_GNUC_PRINTF(4, 5) -check_uint32(struct client *client, uint32_t *dst, - const char *s, const char *fmt, ...) -{ - char *test; - - *dst = strtoul(s, &test, 10); - if (*test != '\0') { - va_list args; - va_start(args, fmt); - command_error_v(client, ACK_ERROR_ARG, fmt, args); - va_end(args); - return false; - } - return true; -} - -static bool G_GNUC_PRINTF(4, 5) -check_int(struct client *client, int *value_r, - const char *s, const char *fmt, ...) -{ - char *test; - long value; - - value = strtol(s, &test, 10); - if (*test != '\0') { - va_list args; - va_start(args, fmt); - command_error_v(client, ACK_ERROR_ARG, fmt, args); - va_end(args); - return false; - } - -#if G_MAXLONG > G_MAXINT - if (value < G_MININT || value > G_MAXINT) { - command_error(client, ACK_ERROR_ARG, - "Number too large: %s", s); - return false; - } -#endif - - *value_r = (int)value; - return true; -} - -static bool G_GNUC_PRINTF(5, 6) -check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, - const char *s, const char *fmt, ...) -{ - char *test, *test2; - long value; - - value = strtol(s, &test, 10); - if (*test != '\0' && *test != ':') { - va_list args; - va_start(args, fmt); - command_error_v(client, ACK_ERROR_ARG, fmt, args); - va_end(args); - return false; - } - - if (value == -1 && *test == 0) { - /* compatibility with older MPD versions: specifying - "-1" makes MPD display the whole list */ - *value_r1 = 0; - *value_r2 = G_MAXUINT; - return true; - } - - if (value < 0) { - command_error(client, ACK_ERROR_ARG, - "Number is negative: %s", s); - return false; - } - -#if G_MAXLONG > G_MAXUINT - if (value > G_MAXUINT) { - command_error(client, ACK_ERROR_ARG, - "Number too large: %s", s); - return false; - } -#endif - - *value_r1 = (unsigned)value; - - if (*test == ':') { - value = strtol(++test, &test2, 10); - if (*test2 != '\0') { - va_list args; - va_start(args, fmt); - command_error_v(client, ACK_ERROR_ARG, fmt, args); - va_end(args); - return false; - } - - if (test == test2) - value = G_MAXUINT; - - if (value < 0) { - command_error(client, ACK_ERROR_ARG, - "Number is negative: %s", s); - return false; - } - -#if G_MAXLONG > G_MAXUINT - if (value > G_MAXUINT) { - command_error(client, ACK_ERROR_ARG, - "Number too large: %s", s); - return false; - } -#endif - *value_r2 = (unsigned)value; - } else { - *value_r2 = (unsigned)value + 1; - } - - return true; -} - -static bool -check_unsigned(struct client *client, unsigned *value_r, const char *s) -{ - unsigned long value; - char *endptr; - - value = strtoul(s, &endptr, 10); - if (*endptr != 0) { - command_error(client, ACK_ERROR_ARG, - "Integer expected: %s", s); - return false; - } - - if (value > G_MAXUINT) { - command_error(client, ACK_ERROR_ARG, - "Number too large: %s", s); - return false; - } - - *value_r = (unsigned)value; - return true; -} - -static bool -check_bool(struct client *client, bool *value_r, const char *s) -{ - long value; - char *endptr; - - value = strtol(s, &endptr, 10); - if (*endptr != 0 || (value != 0 && value != 1)) { - command_error(client, ACK_ERROR_ARG, - "Boolean (0/1) expected: %s", s); - return false; - } - - *value_r = !!value; - return true; -} - -static bool -check_float(struct client *client, float *value_r, const char *s) -{ - float value; - char *endptr; - - value = strtof(s, &endptr); - if (*endptr != 0 && endptr == s) { - command_error(client, ACK_ERROR_ARG, - "Float expected: %s", s); - return false; - } - - *value_r = value; - return true; -} - static enum command_return print_playlist_result(struct client *client, enum playlist_result result) @@ -322,11 +119,12 @@ print_playlist_result(struct client *client, return COMMAND_RETURN_OK; case PLAYLIST_RESULT_ERRNO: - command_error(client, ACK_ERROR_SYSTEM, "%s", strerror(errno)); + command_error(client, ACK_ERROR_SYSTEM, "%s", + g_strerror(errno)); return COMMAND_RETURN_ERROR; case PLAYLIST_RESULT_DENIED: - command_error(client, ACK_ERROR_NO_EXIST, "Access denied"); + command_error(client, ACK_ERROR_PERMISSION, "Access denied"); return COMMAND_RETURN_ERROR; case PLAYLIST_RESULT_NO_SUCH_SONG: @@ -372,6 +170,50 @@ print_playlist_result(struct client *client, return COMMAND_RETURN_ERROR; } +/** + * Send the GError to the client and free the GError. + */ +static enum command_return +print_error(struct client *client, GError *error) +{ + assert(client != NULL); + assert(error != NULL); + + g_warning("%s", error->message); + + if (error->domain == playlist_quark()) { + enum playlist_result result = error->code; + g_error_free(error); + return print_playlist_result(client, result); + } else if (error->domain == ack_quark()) { + command_error(client, error->code, "%s", error->message); + g_error_free(error); + return COMMAND_RETURN_ERROR; + } else if (error->domain == db_quark()) { + switch ((enum db_error)error->code) { + case DB_DISABLED: + command_error(client, ACK_ERROR_NO_EXIST, "%s", + error->message); + g_error_free(error); + return COMMAND_RETURN_ERROR; + + case DB_NOT_FOUND: + g_error_free(error); + command_error(client, ACK_ERROR_NO_EXIST, "Not found"); + return COMMAND_RETURN_ERROR; + } + } else if (error->domain == g_file_error_quark()) { + command_error(client, ACK_ERROR_SYSTEM, "%s", + g_strerror(error->code)); + g_error_free(error); + return COMMAND_RETURN_ERROR; + } + + g_error_free(error); + command_error(client, ACK_ERROR_UNKNOWN, "error"); + return COMMAND_RETURN_ERROR; +} + static void print_spl_list(struct client *client, GPtrArray *list) { @@ -404,7 +246,7 @@ static enum command_return handle_urlhandlers(struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - if (client_get_uid(client) > 0) + if (client_is_local(client)) client_puts(client, "handler: file://\n"); print_supported_uri_schemes(client); return COMMAND_RETURN_OK; @@ -432,9 +274,9 @@ handle_play(struct client *client, int argc, char *argv[]) int song = -1; enum playlist_result result; - if (argc == 2 && !check_int(client, &song, argv[1], need_positive)) + if (argc == 2 && !check_int(client, &song, argv[1])) return COMMAND_RETURN_ERROR; - result = playlist_play(&g_playlist, song); + result = playlist_play(&g_playlist, client->player_control, song); return print_playlist_result(client, result); } @@ -444,10 +286,10 @@ handle_playid(struct client *client, int argc, char *argv[]) int id = -1; enum playlist_result result; - if (argc == 2 && !check_int(client, &id, argv[1], need_positive)) + if (argc == 2 && !check_int(client, &id, argv[1])) return COMMAND_RETURN_ERROR; - result = playlist_play_id(&g_playlist, id); + result = playlist_play_id(&g_playlist, client->player_control, id); return print_playlist_result(client, result); } @@ -455,7 +297,7 @@ static enum command_return handle_stop(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - playlist_stop(&g_playlist); + playlist_stop(&g_playlist, client->player_control); return COMMAND_RETURN_OK; } @@ -476,9 +318,9 @@ handle_pause(struct client *client, if (!check_bool(client, &pause_flag, argv[1])) return COMMAND_RETURN_ERROR; - pc_set_pause(pause_flag); + pc_set_pause(client->player_control, pause_flag); } else - pc_pause(); + pc_pause(client->player_control); return COMMAND_RETURN_OK; } @@ -493,7 +335,7 @@ handle_status(struct client *client, char *error; int song; - pc_get_status(&player_status); + pc_get_status(client->player_control, &player_status); switch (player_status.state) { case PLAYER_STATE_STOP: @@ -526,9 +368,9 @@ handle_status(struct client *client, playlist_get_consume(&g_playlist), playlist_get_version(&g_playlist), playlist_get_length(&g_playlist), - (int)(pc_get_cross_fade() + 0.5), - pc_get_mixramp_db(), - pc_get_mixramp_delay(), + (int)(pc_get_cross_fade(client->player_control) + 0.5), + pc_get_mixramp_db(client->player_control), + pc_get_mixramp_delay(client->player_control), state); song = playlist_get_current_song(&g_playlist); @@ -561,7 +403,7 @@ handle_status(struct client *client, updateJobId); } - error = pc_get_error_message(); + error = pc_get_error_message(client->player_control); if (error != NULL) { client_printf(client, COMMAND_STATUS_ERROR ": %s\n", @@ -601,13 +443,16 @@ handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) enum playlist_result result; if (strncmp(uri, "file:///", 8) == 0) { -#ifdef WIN32 - result = PLAYLIST_RESULT_DENIED; -#else + const char *path = uri + 7; + + GError *error = NULL; + if (!client_allow_file(client, path, &error)) + return print_error(client, error); + result = playlist_append_file(&g_playlist, - uri + 7, client_get_uid(client), + client->player_control, + path, NULL); -#endif return print_playlist_result(client, result); } @@ -618,18 +463,16 @@ handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - result = playlist_append_uri(&g_playlist, uri, NULL); + result = playlist_append_uri(&g_playlist, + client->player_control, + uri, NULL); return print_playlist_result(client, result); } - result = addAllIn(uri); - if (result == (enum playlist_result)-1) { - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); - return COMMAND_RETURN_ERROR; - } - - return print_playlist_result(client, result); + GError *error = NULL; + return addAllIn(client->player_control, uri, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return @@ -640,13 +483,16 @@ handle_addid(struct client *client, int argc, char *argv[]) enum playlist_result result; if (strncmp(uri, "file:///", 8) == 0) { -#ifdef WIN32 - result = PLAYLIST_RESULT_DENIED; -#else - result = playlist_append_file(&g_playlist, uri + 7, - client_get_uid(client), + const char *path = uri + 7; + + GError *error = NULL; + if (!client_allow_file(client, path, &error)) + return print_error(client, error); + + result = playlist_append_file(&g_playlist, + client->player_control, + path, &added_id); -#endif } else { if (uri_has_scheme(uri) && !uri_supported_scheme(uri)) { command_error(client, ACK_ERROR_NO_EXIST, @@ -654,21 +500,25 @@ handle_addid(struct client *client, int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - result = playlist_append_uri(&g_playlist, uri, &added_id); + result = playlist_append_uri(&g_playlist, + client->player_control, + uri, &added_id); } if (result != PLAYLIST_RESULT_SUCCESS) return print_playlist_result(client, result); if (argc == 3) { - int to; - if (!check_int(client, &to, argv[2], check_integer, argv[2])) + unsigned to; + if (!check_unsigned(client, &to, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_move_id(&g_playlist, added_id, to); + result = playlist_move_id(&g_playlist, client->player_control, + added_id, to); if (result != PLAYLIST_RESULT_SUCCESS) { enum command_return ret = print_playlist_result(client, result); - playlist_delete_id(&g_playlist, added_id); + playlist_delete_id(&g_playlist, client->player_control, + added_id); return ret; } } @@ -683,23 +533,24 @@ handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) unsigned start, end; enum playlist_result result; - if (!check_range(client, &start, &end, argv[1], need_range)) + if (!check_range(client, &start, &end, argv[1])) return COMMAND_RETURN_ERROR; - result = playlist_delete_range(&g_playlist, start, end); + result = playlist_delete_range(&g_playlist, client->player_control, + start, end); return print_playlist_result(client, result); } static enum command_return handle_deleteid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int id; + unsigned id; enum playlist_result result; - if (!check_int(client, &id, argv[1], need_positive)) + if (!check_unsigned(client, &id, argv[1])) return COMMAND_RETURN_ERROR; - result = playlist_delete_id(&g_playlist, id); + result = playlist_delete_id(&g_playlist, client->player_control, id); return print_playlist_result(client, result); } @@ -716,11 +567,10 @@ handle_shuffle(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { unsigned start = 0, end = queue_length(&g_playlist.queue); - if (argc == 2 && !check_range(client, &start, &end, - argv[1], need_range)) + if (argc == 2 && !check_range(client, &start, &end, argv[1])) return COMMAND_RETURN_ERROR; - playlist_shuffle(&g_playlist, start, end); + playlist_shuffle(&g_playlist, client->player_control, start, end); return COMMAND_RETURN_OK; } @@ -728,7 +578,7 @@ static enum command_return handle_clear(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - playlist_clear(&g_playlist); + playlist_clear(&g_playlist, client->player_control); return COMMAND_RETURN_OK; } @@ -743,16 +593,40 @@ handle_save(struct client *client, } static enum command_return -handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +handle_load(struct client *client, int argc, char *argv[]) { + unsigned start_index, end_index; + + if (argc < 3) { + start_index = 0; + end_index = G_MAXUINT; + } else if (!check_range(client, &start_index, &end_index, argv[2])) + return COMMAND_RETURN_ERROR; + enum playlist_result result; - result = playlist_open_into_queue(argv[1], &g_playlist); + result = playlist_open_into_queue(argv[1], + start_index, end_index, + &g_playlist, + client->player_control, true); if (result != PLAYLIST_RESULT_NO_SUCH_LIST) return print_playlist_result(client, result); - result = playlist_load_spl(&g_playlist, argv[1]); - return print_playlist_result(client, result); + GError *error = NULL; + if (playlist_load_spl(&g_playlist, client->player_control, + argv[1], start_index, end_index, + &error)) + return COMMAND_RETURN_OK; + + if (error->domain == playlist_quark() && + error->code == PLAYLIST_RESULT_BAD_NAME) + /* the message for BAD_NAME is confusing when the + client wants to load a playlist file from the music + directory; patch the GError object to show "no such + playlist" instead */ + error->code = PLAYLIST_RESULT_NO_SUCH_LIST; + + return print_error(client, error); } static enum command_return @@ -761,15 +635,10 @@ handle_listplaylist(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (playlist_file_print(client, argv[1], false)) return COMMAND_RETURN_OK; - bool ret; - - ret = spl_print(client, argv[1], false); - if (!ret) { - command_error(client, ACK_ERROR_NO_EXIST, "No such playlist"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; + GError *error = NULL; + return spl_print(client, argv[1], false, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return @@ -779,22 +648,16 @@ handle_listplaylistinfo(struct client *client, if (playlist_file_print(client, argv[1], true)) return COMMAND_RETURN_OK; - bool ret; - - ret = spl_print(client, argv[1], true); - if (!ret) { - command_error(client, ACK_ERROR_NO_EXIST, "No such playlist"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; + GError *error = NULL; + return spl_print(client, argv[1], true, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return handle_lsinfo(struct client *client, int argc, char *argv[]) { const char *uri; - const struct directory *directory; if (argc == 2) uri = argv[1]; @@ -802,17 +665,35 @@ handle_lsinfo(struct client *client, int argc, char *argv[]) /* default is root directory */ uri = ""; - directory = db_get_directory(uri); - if (directory == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "directory not found"); - return COMMAND_RETURN_ERROR; + if (strncmp(uri, "file:///", 8) == 0) { + /* print information about an arbitrary local file */ + const char *path = uri + 7; + + GError *error = NULL; + if (!client_allow_file(client, path, &error)) + return print_error(client, error); + + struct song *song = song_file_load(path, NULL); + if (song == NULL) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such file"); + return COMMAND_RETURN_ERROR; + } + + song_print_info(client, song); + song_free(song); + return COMMAND_RETURN_OK; } - directory_print(client, directory); + struct db_selection selection; + db_selection_init(&selection, uri, false); + + GError *error = NULL; + if (!db_selection_print(client, &selection, true, &error)) + return print_error(client, error); if (isRootDirectory(uri)) { - GPtrArray *list = spl_list(); + GPtrArray *list = spl_list(NULL); if (list != NULL) { print_spl_list(client, list); spl_list_free(list); @@ -825,19 +706,19 @@ handle_lsinfo(struct client *client, int argc, char *argv[]) static enum command_return handle_rm(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - enum playlist_result result; - - result = spl_delete(argv[1]); - return print_playlist_result(client, result); + GError *error = NULL; + return spl_delete(argv[1], &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return handle_rename(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - enum playlist_result result; - - result = spl_rename(argv[1], argv[2]); - return print_playlist_result(client, result); + GError *error = NULL; + return spl_rename(argv[1], argv[2], &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return @@ -845,7 +726,7 @@ handle_plchanges(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { uint32_t version; - if (!check_uint32(client, &version, argv[1], need_positive)) + if (!check_uint32(client, &version, argv[1])) return COMMAND_RETURN_ERROR; playlist_print_changes_info(client, &g_playlist, version); @@ -857,7 +738,7 @@ handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[ { uint32_t version; - if (!check_uint32(client, &version, argv[1], need_positive)) + if (!check_uint32(client, &version, argv[1])) return COMMAND_RETURN_ERROR; playlist_print_changes_position(client, &g_playlist, version); @@ -870,8 +751,7 @@ handle_playlistinfo(struct client *client, int argc, char *argv[]) unsigned start = 0, end = G_MAXUINT; bool ret; - if (argc == 2 && !check_range(client, &start, &end, - argv[1], need_range)) + if (argc == 2 && !check_range(client, &start, &end, argv[1])) return COMMAND_RETURN_ERROR; ret = playlist_print_info(client, &g_playlist, start, end); @@ -885,12 +765,11 @@ handle_playlistinfo(struct client *client, int argc, char *argv[]) static enum command_return handle_playlistid(struct client *client, int argc, char *argv[]) { - int id = -1; - - if (argc == 2 && !check_int(client, &id, argv[1], need_positive)) - return COMMAND_RETURN_ERROR; + if (argc >= 2) { + unsigned id; + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; - if (id >= 0) { bool ret = playlist_print_id(client, &g_playlist, id); if (!ret) return print_playlist_result(client, @@ -905,7 +784,6 @@ handle_playlistid(struct client *client, int argc, char *argv[]) static enum command_return handle_find(struct client *client, int argc, char *argv[]) { - int ret; struct locate_item_list *list = locate_item_list_parse(argv + 1, argc - 1); @@ -917,10 +795,10 @@ handle_find(struct client *client, int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - ret = findSongsIn(client, NULL, list); - if (ret == -1) - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); + GError *error = NULL; + enum command_return ret = findSongsIn(client, "", list, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); locate_item_list_free(list); @@ -930,7 +808,6 @@ handle_find(struct client *client, int argc, char *argv[]) static enum command_return handle_findadd(struct client *client, int argc, char *argv[]) { - int ret; struct locate_item_list *list = locate_item_list_parse(argv + 1, argc - 1); if (list == NULL || list->length == 0) { @@ -941,10 +818,11 @@ handle_findadd(struct client *client, int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - ret = findAddIn(client, NULL, list); - if (ret == -1) - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); + GError *error = NULL; + enum command_return ret = + findAddIn(client->player_control, "", list, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); locate_item_list_free(list); @@ -954,7 +832,6 @@ handle_findadd(struct client *client, int argc, char *argv[]) static enum command_return handle_search(struct client *client, int argc, char *argv[]) { - int ret; struct locate_item_list *list = locate_item_list_parse(argv + 1, argc - 1); @@ -966,10 +843,10 @@ handle_search(struct client *client, int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - ret = searchForSongsIn(client, NULL, list); - if (ret == -1) - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); + GError *error = NULL; + enum command_return ret = searchForSongsIn(client, "", list, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); locate_item_list_free(list); @@ -979,7 +856,6 @@ handle_search(struct client *client, int argc, char *argv[]) static enum command_return handle_count(struct client *client, int argc, char *argv[]) { - int ret; struct locate_item_list *list = locate_item_list_parse(argv + 1, argc - 1); @@ -991,10 +867,11 @@ handle_count(struct client *client, int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - ret = searchStatsForSongsIn(client, NULL, list); - if (ret == -1) - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); + GError *error = NULL; + enum command_return ret = + searchStatsForSongsIn(client, "", list, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); locate_item_list_free(list); @@ -1047,30 +924,32 @@ static enum command_return handle_playlistdelete(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { char *playlist = argv[1]; - int from; - enum playlist_result result; + unsigned from; - if (!check_int(client, &from, argv[2], check_integer, argv[2])) + if (!check_unsigned(client, &from, argv[2])) return COMMAND_RETURN_ERROR; - result = spl_remove_index(playlist, from); - return print_playlist_result(client, result); + GError *error = NULL; + return spl_remove_index(playlist, from, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return handle_playlistmove(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { char *playlist = argv[1]; - int from, to; - enum playlist_result result; + unsigned from, to; - if (!check_int(client, &from, argv[2], check_integer, argv[2])) + if (!check_unsigned(client, &from, argv[2])) return COMMAND_RETURN_ERROR; - if (!check_int(client, &to, argv[3], check_integer, argv[3])) + if (!check_unsigned(client, &to, argv[3])) return COMMAND_RETURN_ERROR; - result = spl_move_index(playlist, from, to); - return print_playlist_result(client, result); + GError *error = NULL; + return spl_move_index(playlist, from, to, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return @@ -1141,7 +1020,7 @@ handle_next(G_GNUC_UNUSED struct client *client, int single = g_playlist.queue.single; g_playlist.queue.single = false; - playlist_next(&g_playlist); + playlist_next(&g_playlist, client->player_control); g_playlist.queue.single = single; return COMMAND_RETURN_OK; @@ -1151,37 +1030,96 @@ static enum command_return handle_previous(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - playlist_previous(&g_playlist); + playlist_previous(&g_playlist, client->player_control); + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_prio(struct client *client, int argc, char *argv[]) +{ + unsigned priority; + + if (!check_unsigned(client, &priority, argv[1])) + return COMMAND_RETURN_ERROR; + + if (priority > 0xff) { + command_error(client, ACK_ERROR_ARG, + "Priority out of range: %s", argv[1]); + return COMMAND_RETURN_ERROR; + } + + for (int i = 2; i < argc; ++i) { + unsigned start_position, end_position; + if (!check_range(client, &start_position, &end_position, + argv[i])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + playlist_set_priority(&g_playlist, + client->player_control, + start_position, end_position, + priority); + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + } + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_prioid(struct client *client, int argc, char *argv[]) +{ + unsigned priority; + + if (!check_unsigned(client, &priority, argv[1])) + return COMMAND_RETURN_ERROR; + + if (priority > 0xff) { + command_error(client, ACK_ERROR_ARG, + "Priority out of range: %s", argv[1]); + return COMMAND_RETURN_ERROR; + } + + for (int i = 2; i < argc; ++i) { + unsigned song_id; + if (!check_unsigned(client, &song_id, argv[i])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + playlist_set_priority_id(&g_playlist, + client->player_control, + song_id, priority); + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + } + return COMMAND_RETURN_OK; } static enum command_return handle_listall(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - char *directory = NULL; - int ret; + const char *directory = ""; if (argc == 2) directory = argv[1]; - ret = printAllIn(client, directory); - if (ret == -1) - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); - - return ret; + GError *error = NULL; + return printAllIn(client, directory, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int level; + unsigned level; bool success; - if (!check_int(client, &level, argv[1], need_integer)) + if (!check_unsigned(client, &level, argv[1])) return COMMAND_RETURN_ERROR; - if (level < 0 || level > 100) { + if (level > 100) { command_error(client, ACK_ERROR_ARG, "Invalid volume value"); return COMMAND_RETURN_ERROR; } @@ -1199,52 +1137,31 @@ handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) static enum command_return handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int status; - - if (!check_int(client, &status, argv[1], need_integer)) - return COMMAND_RETURN_ERROR; - - if (status != 0 && status != 1) { - command_error(client, ACK_ERROR_ARG, - "\"%i\" is not 0 or 1", status); + bool status; + if (!check_bool(client, &status, argv[1])) return COMMAND_RETURN_ERROR; - } - playlist_set_repeat(&g_playlist, status); + playlist_set_repeat(&g_playlist, client->player_control, status); return COMMAND_RETURN_OK; } static enum command_return handle_single(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int status; - - if (!check_int(client, &status, argv[1], need_integer)) + bool status; + if (!check_bool(client, &status, argv[1])) return COMMAND_RETURN_ERROR; - if (status != 0 && status != 1) { - command_error(client, ACK_ERROR_ARG, - "\"%i\" is not 0 or 1", status); - return COMMAND_RETURN_ERROR; - } - - playlist_set_single(&g_playlist, status); + playlist_set_single(&g_playlist, client->player_control, status); return COMMAND_RETURN_OK; } static enum command_return handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int status; - - if (!check_int(client, &status, argv[1], need_integer)) - return COMMAND_RETURN_ERROR; - - if (status != 0 && status != 1) { - command_error(client, ACK_ERROR_ARG, - "\"%i\" is not 0 or 1", status); + bool status; + if (!check_bool(client, &status, argv[1])) return COMMAND_RETURN_ERROR; - } playlist_set_consume(&g_playlist, status); return COMMAND_RETURN_OK; @@ -1253,18 +1170,11 @@ handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) static enum command_return handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int status; - - if (!check_int(client, &status, argv[1], need_integer)) + bool status; + if (!check_bool(client, &status, argv[1])) return COMMAND_RETURN_ERROR; - if (status != 0 && status != 1) { - command_error(client, ACK_ERROR_ARG, - "\"%i\" is not 0 or 1", status); - return COMMAND_RETURN_ERROR; - } - - playlist_set_random(&g_playlist, status); + playlist_set_random(&g_playlist, client->player_control, status); return COMMAND_RETURN_OK; } @@ -1279,7 +1189,7 @@ static enum command_return handle_clearerror(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - pc_clear_error(); + pc_clear_error(client->player_control); return COMMAND_RETURN_OK; } @@ -1288,7 +1198,6 @@ handle_list(struct client *client, int argc, char *argv[]) { struct locate_item_list *conditionals; int tagType = locate_parse_type(argv[1]); - int ret; if (tagType < 0) { command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]); @@ -1325,14 +1234,14 @@ handle_list(struct client *client, int argc, char *argv[]) } } - ret = listAllUniqueTags(client, tagType, conditionals); + GError *error = NULL; + enum command_return ret = + listAllUniqueTags(client, tagType, conditionals, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); locate_item_list_free(conditionals); - if (ret == -1) - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); - return ret; } @@ -1343,102 +1252,120 @@ handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) int to; enum playlist_result result; - if (!check_range(client, &start, &end, - argv[1], need_range)) + if (!check_range(client, &start, &end, argv[1])) return COMMAND_RETURN_ERROR; - if (!check_int(client, &to, argv[2], check_integer, argv[2])) + if (!check_int(client, &to, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_move_range(&g_playlist, start, end, to); + result = playlist_move_range(&g_playlist, client->player_control, + start, end, to); return print_playlist_result(client, result); } static enum command_return handle_moveid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int id, to; + unsigned id; + int to; enum playlist_result result; - if (!check_int(client, &id, argv[1], check_integer, argv[1])) + if (!check_unsigned(client, &id, argv[1])) return COMMAND_RETURN_ERROR; - if (!check_int(client, &to, argv[2], check_integer, argv[2])) + if (!check_int(client, &to, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_move_id(&g_playlist, id, to); + result = playlist_move_id(&g_playlist, client->player_control, + id, to); return print_playlist_result(client, result); } static enum command_return handle_swap(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int song1, song2; + unsigned song1, song2; enum playlist_result result; - if (!check_int(client, &song1, argv[1], check_integer, argv[1])) + if (!check_unsigned(client, &song1, argv[1])) return COMMAND_RETURN_ERROR; - if (!check_int(client, &song2, argv[2], check_integer, argv[2])) + if (!check_unsigned(client, &song2, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_swap_songs(&g_playlist, song1, song2); + result = playlist_swap_songs(&g_playlist, client->player_control, + song1, song2); return print_playlist_result(client, result); } static enum command_return handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int id1, id2; + unsigned id1, id2; enum playlist_result result; - if (!check_int(client, &id1, argv[1], check_integer, argv[1])) + if (!check_unsigned(client, &id1, argv[1])) return COMMAND_RETURN_ERROR; - if (!check_int(client, &id2, argv[2], check_integer, argv[2])) + if (!check_unsigned(client, &id2, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_swap_songs_id(&g_playlist, id1, id2); + result = playlist_swap_songs_id(&g_playlist, client->player_control, + id1, id2); return print_playlist_result(client, result); } static enum command_return handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int song, seek_time; + unsigned song, seek_time; enum playlist_result result; - if (!check_int(client, &song, argv[1], check_integer, argv[1])) + if (!check_unsigned(client, &song, argv[1])) return COMMAND_RETURN_ERROR; - if (!check_int(client, &seek_time, argv[2], check_integer, argv[2])) + if (!check_unsigned(client, &seek_time, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_seek_song(&g_playlist, song, seek_time); + result = playlist_seek_song(&g_playlist, client->player_control, + song, seek_time); return print_playlist_result(client, result); } static enum command_return handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int id, seek_time; + unsigned id, seek_time; enum playlist_result result; - if (!check_int(client, &id, argv[1], check_integer, argv[1])) + if (!check_unsigned(client, &id, argv[1])) return COMMAND_RETURN_ERROR; - if (!check_int(client, &seek_time, argv[2], check_integer, argv[2])) + if (!check_unsigned(client, &seek_time, argv[2])) + return COMMAND_RETURN_ERROR; + + result = playlist_seek_song_id(&g_playlist, client->player_control, + id, seek_time); + return print_playlist_result(client, result); +} + +static enum command_return +handle_seekcur(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *p = argv[1]; + bool relative = *p == '+' || *p == '-'; + int seek_time; + if (!check_int(client, &seek_time, p)) return COMMAND_RETURN_ERROR; - result = playlist_seek_song_id(&g_playlist, id, seek_time); + enum playlist_result result = + playlist_seek_current(&g_playlist, client->player_control, + seek_time, relative); return print_playlist_result(client, result); } static enum command_return handle_listallinfo(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - char *directory = NULL; - int ret; + const char *directory = ""; if (argc == 2) directory = argv[1]; - ret = printInfoForAllIn(client, directory); - if (ret == -1) - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); - - return ret; + GError *error = NULL; + return printInfoForAllIn(client, directory, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return @@ -1470,7 +1397,7 @@ handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_unsigned(client, &xfade_time, argv[1])) return COMMAND_RETURN_ERROR; - pc_set_cross_fade(xfade_time); + pc_set_cross_fade(client->player_control, xfade_time); return COMMAND_RETURN_OK; } @@ -1482,7 +1409,7 @@ handle_mixrampdb(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_float(client, &db, argv[1])) return COMMAND_RETURN_ERROR; - pc_set_mixramp_db(db); + pc_set_mixramp_db(client->player_control, db); return COMMAND_RETURN_OK; } @@ -1494,7 +1421,7 @@ handle_mixrampdelay(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_float(client, &delay_secs, argv[1])) return COMMAND_RETURN_ERROR; - pc_set_mixramp_delay(delay_secs); + pc_set_mixramp_delay(client->player_control, delay_secs); return COMMAND_RETURN_OK; } @@ -1556,12 +1483,29 @@ handle_not_commands(struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]); static enum command_return -handle_playlistclear(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +handle_config(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - enum playlist_result result; + if (!client_is_local(client)) { + command_error(client, ACK_ERROR_PERMISSION, + "Command only permitted to local clients"); + return COMMAND_RETURN_ERROR; + } - result = spl_clear(argv[1]); - return print_playlist_result(client, result); + const char *path = mapper_get_music_directory(); + if (path != NULL) + client_printf(client, "music_directory: %s\n", path); + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_playlistclear(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + GError *error = NULL; + return spl_clear(argv[1], &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return @@ -1569,8 +1513,9 @@ handle_playlistadd(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { char *playlist = argv[1]; char *uri = argv[2]; - enum playlist_result result; + bool success; + GError *error = NULL; if (uri_has_scheme(uri)) { if (!uri_supported_scheme(uri)) { command_error(client, ACK_ERROR_NO_EXIST, @@ -1578,29 +1523,27 @@ handle_playlistadd(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - result = spl_append_uri(uri, playlist); + success = spl_append_uri(argv[1], playlist, &error); } else - result = addAllInToStoredPlaylist(uri, playlist); + success = addAllInToStoredPlaylist(uri, playlist, &error); - if (result == (enum playlist_result)-1) { + if (!success && error == NULL) { command_error(client, ACK_ERROR_NO_EXIST, "directory or file not found"); return COMMAND_RETURN_ERROR; } - return print_playlist_result(client, result); + return success ? COMMAND_RETURN_OK : print_error(client, error); } static enum command_return handle_listplaylists(struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - GPtrArray *list = spl_list(); - if (list == NULL) { - command_error(client, ACK_ERROR_SYSTEM, - "failed to get list of stored playlists"); - return COMMAND_RETURN_ERROR; - } + GError *error = NULL; + GPtrArray *list = spl_list(&error); + if (list == NULL) + return print_error(client, error); print_spl_list(client, list); spl_list_free(list); @@ -1774,8 +1717,10 @@ handle_sticker_song(struct client *client, int argc, char *argv[]) .name = argv[4], }; + db_lock(); directory = db_get_directory(argv[3]); if (directory == NULL) { + db_unlock(); command_error(client, ACK_ERROR_NO_EXIST, "no such directory"); return COMMAND_RETURN_ERROR; @@ -1783,6 +1728,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[]) success = sticker_song_find(directory, data.name, sticker_song_find_print_cb, &data); + db_unlock(); if (!success) { command_error(client, ACK_ERROR_SYSTEM, "failed to set search sticker database"); @@ -1817,6 +1763,172 @@ handle_sticker(struct client *client, int argc, char *argv[]) } #endif +static enum command_return +handle_subscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + assert(argc == 2); + + switch (client_subscribe(client, argv[1])) { + case CLIENT_SUBSCRIBE_OK: + return COMMAND_RETURN_OK; + + case CLIENT_SUBSCRIBE_INVALID: + command_error(client, ACK_ERROR_ARG, + "invalid channel name"); + return COMMAND_RETURN_ERROR; + + case CLIENT_SUBSCRIBE_ALREADY: + command_error(client, ACK_ERROR_EXIST, + "already subscribed to this channel"); + return COMMAND_RETURN_ERROR; + + case CLIENT_SUBSCRIBE_FULL: + command_error(client, ACK_ERROR_EXIST, + "subscription list is full"); + return COMMAND_RETURN_ERROR; + } + + /* unreachable */ + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_unsubscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + assert(argc == 2); + + if (client_unsubscribe(client, argv[1])) + return COMMAND_RETURN_OK; + else { + command_error(client, ACK_ERROR_NO_EXIST, + "not subscribed to this channel"); + return COMMAND_RETURN_ERROR; + } +} + +struct channels_context { + GStringChunk *chunk; + + GHashTable *channels; +}; + +static void +collect_channels(gpointer data, gpointer user_data) +{ + struct channels_context *context = user_data; + const struct client *client = data; + + for (GSList *i = client->subscriptions; i != NULL; + i = g_slist_next(i)) { + const char *channel = i->data; + + if (g_hash_table_lookup(context->channels, channel) == NULL) { + char *channel2 = g_string_chunk_insert(context->chunk, + channel); + g_hash_table_insert(context->channels, channel2, + context); + } + } +} + +static void +print_channel(gpointer key, G_GNUC_UNUSED gpointer value, gpointer user_data) +{ + struct client *client = user_data; + const char *channel = key; + + client_printf(client, "channel: %s\n", channel); +} + +static enum command_return +handle_channels(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 1); + + struct channels_context context = { + .chunk = g_string_chunk_new(1024), + .channels = g_hash_table_new(g_str_hash, g_str_equal), + }; + + client_list_foreach(collect_channels, &context); + + g_hash_table_foreach(context.channels, print_channel, client); + + g_hash_table_destroy(context.channels); + g_string_chunk_free(context.chunk); + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_read_messages(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 1); + + GSList *messages = client_read_messages(client); + + for (GSList *i = messages; i != NULL; i = g_slist_next(i)) { + struct client_message *msg = i->data; + + client_printf(client, "channel: %s\nmessage: %s\n", + msg->channel, msg->message); + client_message_free(msg); + } + + g_slist_free(messages); + + return COMMAND_RETURN_OK; +} + +struct send_message_context { + struct client_message msg; + + bool sent; +}; + +static void +send_message(gpointer data, gpointer user_data) +{ + struct send_message_context *context = user_data; + struct client *client = data; + + if (client_push_message(client, &context->msg)) + context->sent = true; +} + +static enum command_return +handle_send_message(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 3); + + if (!client_message_valid_channel_name(argv[1])) { + command_error(client, ACK_ERROR_ARG, + "invalid channel name"); + return COMMAND_RETURN_ERROR; + } + + struct send_message_context context = { + .sent = false, + }; + + client_message_init(&context.msg, argv[1], argv[2]); + + client_list_foreach(send_message, &context); + + client_message_deinit(&context.msg); + + if (context.sent) + return COMMAND_RETURN_OK; + else { + command_error(client, ACK_ERROR_NO_EXIST, + "nobody is subscribed to this channel"); + return COMMAND_RETURN_ERROR; + } +} + /** * The command registry. * @@ -1825,10 +1937,12 @@ handle_sticker(struct client *client, int argc, char *argv[]) static const struct command commands[] = { { "add", PERMISSION_ADD, 1, 1, handle_add }, { "addid", PERMISSION_ADD, 1, 2, handle_addid }, + { "channels", PERMISSION_READ, 0, 0, handle_channels }, { "clear", PERMISSION_CONTROL, 0, 0, handle_clear }, { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror }, { "close", PERMISSION_NONE, -1, -1, handle_close }, { "commands", PERMISSION_NONE, 0, 0, handle_commands }, + { "config", PERMISSION_ADMIN, 0, 0, handle_config }, { "consume", PERMISSION_CONTROL, 1, 1, handle_consume }, { "count", PERMISSION_READ, 2, -1, handle_count }, { "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade }, @@ -1848,7 +1962,7 @@ static const struct command commands[] = { { "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist }, { "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo }, { "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists }, - { "load", PERMISSION_ADD, 1, 1, handle_load }, + { "load", PERMISSION_ADD, 1, 2, handle_load }, { "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo }, { "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb }, { "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay }, @@ -1874,19 +1988,24 @@ static const struct command commands[] = { { "plchanges", PERMISSION_READ, 1, 1, handle_plchanges }, { "plchangesposid", PERMISSION_READ, 1, 1, handle_plchangesposid }, { "previous", PERMISSION_CONTROL, 0, 0, handle_previous }, + { "prio", PERMISSION_CONTROL, 2, -1, handle_prio }, + { "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid }, { "random", PERMISSION_CONTROL, 1, 1, handle_random }, + { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages }, { "rename", PERMISSION_CONTROL, 2, 2, handle_rename }, { "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat }, { "replay_gain_mode", PERMISSION_CONTROL, 1, 1, handle_replay_gain_mode }, { "replay_gain_status", PERMISSION_READ, 0, 0, handle_replay_gain_status }, - { "rescan", PERMISSION_ADMIN, 0, 1, handle_rescan }, + { "rescan", PERMISSION_CONTROL, 0, 1, handle_rescan }, { "rm", PERMISSION_CONTROL, 1, 1, handle_rm }, { "save", PERMISSION_CONTROL, 1, 1, handle_save }, { "search", PERMISSION_READ, 2, -1, handle_search }, { "seek", PERMISSION_CONTROL, 2, 2, handle_seek }, + { "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur }, { "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid }, + { "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message }, { "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol }, { "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle }, { "single", PERMISSION_CONTROL, 1, 1, handle_single }, @@ -1896,10 +2015,12 @@ static const struct command commands[] = { { "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker }, #endif { "stop", PERMISSION_CONTROL, 0, 0, handle_stop }, + { "subscribe", PERMISSION_READ, 1, 1, handle_subscribe }, { "swap", PERMISSION_CONTROL, 2, 2, handle_swap }, { "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid }, { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes }, - { "update", PERMISSION_ADMIN, 0, 1, handle_update }, + { "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe }, + { "update", PERMISSION_CONTROL, 0, 1, handle_update }, { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers }, }; @@ -2029,10 +2150,9 @@ static const struct command * command_checked_lookup(struct client *client, unsigned permission, int argc, char *argv[]) { - static char unknown[] = ""; const struct command *cmd; - current_command = unknown; + current_command = ""; if (argc == 0) return NULL; |