4470 lines
125 KiB
C
Raw Normal View History

2014-12-21 20:41:44 +01:00
/*
* Copyright (C) 2016 Christian Meffert <christian.meffert@googlemail.com>
2014-12-21 20:41:44 +01:00
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* To test with raw command input use:
* echo "command" | nc -q0 localhost 6600
* Or interactive with:
* socat - TCP:localhost:6600
*/
2014-12-21 20:41:44 +01:00
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdbool.h>
2014-12-21 20:41:44 +01:00
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
2014-12-21 20:41:44 +01:00
#include <unistd.h>
#include <string.h>
#include <ctype.h>
2014-12-21 20:41:44 +01:00
#include <fcntl.h>
#include <limits.h>
#include <errno.h>
#include <pthread.h>
#include <inttypes.h>
#include <netinet/in.h>
#include <ctype.h> // isdigit
2014-12-21 20:41:44 +01:00
#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/http.h>
#include <event2/listener.h>
#include "artwork.h"
#include "commands.h"
2014-12-21 20:41:44 +01:00
#include "conffile.h"
#include "db.h"
#include "library.h"
2015-02-21 06:04:17 +01:00
#include "listener.h"
#include "logger.h"
#include "misc.h"
2014-12-21 20:41:44 +01:00
#include "player.h"
#include "remote_pairing.h"
#include "parsers/mpd_parser.h"
// TODO
// optimize queries (map albumartist/album groupings to songalbumid/artistid in db.c)
// support for group in count, e.g. count group artist
// empty group value is a negation
// Command handlers should use this for returning errors to make sure both
// ack_error and errmsg are correctly set
#define RETURN_ERROR(r, ...) \
do { out->ack_error = (r); free(out->errmsg); out->errmsg = safe_asprintf(__VA_ARGS__); return -1; } while(0)
2014-12-21 20:41:44 +01:00
2024-09-12 16:58:52 +02:00
// According to the mpd protocol send "OK MPD <version>\n" to the client, where
// version is the version of the supported mpd protocol and not the server version
#define MPD_PROTOCOL_VERSION_OK "OK MPD 0.22.4\n"
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
/**
* from MPD source:
* *
* * The most we ever use is for search/find, and that limits it to the
* * number of tags we can have. Add one for the command, and one extra
* * to catch errors clients may send us
* *
* static constexpr std::size_t COMMAND_ARGV_MAX = 2 + TAG_NUM_OF_ITEM_TYPES * 2;
*
* https://github.com/MusicPlayerDaemon/MPD/blob/master/src/command/AllCommands.cxx
*/
#define MPD_COMMAND_ARGV_MAX 70
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
/**
* config:
* max_command_list_size KBYTES
*
* https://github.com/MusicPlayerDaemon/MPD/blob/master/src/client/Config.cxx
*/
#define MPD_MAX_COMMAND_LIST_SIZE (2048*1024)
#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 mpd_ack_error
{
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,
};
enum command_list_type
{
COMMAND_LIST_NONE = 0,
COMMAND_LIST_BEGIN,
COMMAND_LIST_OK_BEGIN,
COMMAND_LIST_END,
COMMAND_LIST_OK_END,
};
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;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
};
#define MPD_WANTS_NUM_ARGV_MIN 1
#define MPD_WANTS_NUM_ARGV_MAX 3
enum command_wants_num
{
MPD_WANTS_NUM_NONE = 0,
MPD_WANTS_NUM_ARG1_IVAL = (1 << (0 + MPD_WANTS_NUM_ARGV_MIN)),
MPD_WANTS_NUM_ARG2_IVAL = (1 << (1 + MPD_WANTS_NUM_ARGV_MIN)),
MPD_WANTS_NUM_ARG3_IVAL = (1 << (2 + MPD_WANTS_NUM_ARGV_MIN)),
MPD_WANTS_NUM_ARG1_UVAL = (1 << (0 + MPD_WANTS_NUM_ARGV_MIN + MPD_WANTS_NUM_ARGV_MAX)),
MPD_WANTS_NUM_ARG2_UVAL = (1 << (1 + MPD_WANTS_NUM_ARGV_MIN + MPD_WANTS_NUM_ARGV_MAX)),
MPD_WANTS_NUM_ARG3_UVAL = (1 << (2 + MPD_WANTS_NUM_ARGV_MIN + MPD_WANTS_NUM_ARGV_MAX)),
};
struct mpd_command_input
{
// Raw argument line
const char *args_raw;
// Argument line unescaped and split
char *args_split;
char *argv[MPD_COMMAND_ARGV_MAX];
int argc;
int has_num;
int32_t argv_i32val[MPD_COMMAND_ARGV_MAX];
uint32_t argv_u32val[MPD_COMMAND_ARGV_MAX];
};
struct mpd_command_output
{
struct evbuffer *evbuf;
char *errmsg;
enum mpd_ack_error ack_error;
};
struct mpd_command
{
const char *name;
int (*handler)(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx);
int min_argc;
int wants_num;
};
2014-12-21 20:41:44 +01:00
struct output
2014-12-21 20:41:44 +01:00
{
unsigned short shortid;
uint64_t id;
char *name;
2014-12-21 20:41:44 +01:00
unsigned selected;
2014-12-21 20:41:44 +01:00
};
struct output_get_param
2014-12-21 20:41:44 +01:00
{
unsigned short curid;
unsigned short shortid;
struct output *output;
2014-12-21 20:41:44 +01:00
};
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).
*
* The server does not support different decoders and always uses ffmpeg or libav for decoding.
* Some clients rely on a response for the decoder commands (e.g. ncmpccp) therefor return something
* valid for this command.
*/
static const char * const ffmpeg_suffixes[] = { "16sv", "3g2", "3gp", "4xm", "8svx", "aa3", "aac", "ac3", "afc", "aif",
"aifc", "aiff", "al", "alaw", "amr", "anim", "apc", "ape", "asf", "atrac", "au", "aud", "avi", "avm2", "avs", "bap",
"bfi", "c93", "cak", "cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa", "eac3", "film", "flac",
"flc", "fli", "fll", "flx", "flv", "g726", "gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts", "m4a", "m4b", "m4v",
"mad", "mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+", "mp1", "mp2", "mp3", "mp4", "mpc",
"mpeg", "mpg", "mpga", "mpp", "mpu", "mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv", "ogx",
"oma", "ogg", "omg", "psp", "pva", "qcp", "qt", "r3d", "ra", "ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn",
"smk", "snd", "sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts", "tsp", "tta", "xa", "xvid", "uv",
"uv2", "vb", "vid", "vob", "voc", "vp6", "vmd", "wav", "webm", "wma", "wmv", "wsaud", "wsvga", "wv", "wve",
NULL
};
static const char * const ffmpeg_mime_types[] = { "application/flv", "application/m4a", "application/mp4",
"application/octet-stream", "application/ogg", "application/x-ms-wmz", "application/x-ms-wmd", "application/x-ogg",
"application/x-shockwave-flash", "application/x-shorten", "audio/8svx", "audio/16sv", "audio/aac", "audio/ac3",
"audio/aiff", "audio/amr", "audio/basic", "audio/flac", "audio/m4a", "audio/mp4", "audio/mpeg", "audio/musepack",
"audio/ogg", "audio/qcelp", "audio/vorbis", "audio/vorbis+ogg", "audio/x-8svx", "audio/x-16sv", "audio/x-aac",
"audio/x-ac3", "audio/x-aiff", "audio/x-alaw", "audio/x-au", "audio/x-dca", "audio/x-eac3", "audio/x-flac",
"audio/x-gsm", "audio/x-mace", "audio/x-matroska", "audio/x-monkeys-audio", "audio/x-mpeg", "audio/x-ms-wma",
"audio/x-ms-wax", "audio/x-musepack", "audio/x-ogg", "audio/x-vorbis", "audio/x-vorbis+ogg", "audio/x-pn-realaudio",
"audio/x-pn-multirate-realaudio", "audio/x-speex", "audio/x-tta", "audio/x-voc", "audio/x-wav", "audio/x-wma",
"audio/x-wv", "video/anim", "video/quicktime", "video/msvideo", "video/ogg", "video/theora", "video/webm",
"video/x-dv", "video/x-flv", "video/x-matroska", "video/x-mjpeg", "video/x-mpeg", "video/x-ms-asf",
"video/x-msvideo", "video/x-ms-wmv", "video/x-ms-wvx", "video/x-ms-wm", "video/x-ms-wmx", "video/x-nut",
"video/x-pva", "video/x-theora", "video/x-vid", "video/x-wmv", "video/x-xvid",
/* special value for the "ffmpeg" input plugin: all streams by
the "ffmpeg" input plugin shall be decoded by this
plugin */
"audio/x-mpd-ffmpeg",
NULL
};
/* -------------------------------- Helpers --------------------------------- */
/*
* Some MPD clients crash if the tag value includes the newline character.
* 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 char *
sanitize(char *strval)
{
char *ptr = strval;
if (!strval)
return "";
while (*ptr != '\0')
{
if (*ptr == '\n')
*ptr = ' ';
ptr++;
}
return strval;
}
static void
client_ctx_free(struct mpd_client_ctx *client_ctx)
{
if (!client_ctx)
return;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
evbuffer_free(client_ctx->cmd_list_buffer);
free(client_ctx);
}
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
static struct mpd_client_ctx *
client_ctx_new(void)
{
struct mpd_client_ctx *client_ctx;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
CHECK_NULL(L_MPD, client_ctx = calloc(1, sizeof(struct mpd_client_ctx)));
CHECK_NULL(L_MPD, client_ctx->cmd_list_buffer = evbuffer_new());
2017-11-10 09:55:44 +01:00
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;
while (client)
{
if (client == client_ctx)
{
DPRINTF(E_DBG, L_MPD, "Removing mpd client\n");
if (prev)
prev->next = client->next;
else
mpd_clients = client->next;
break;
}
prev = client;
client = client->next;
}
client_ctx_free(client_ctx);
}
static struct mpd_client_ctx *
client_ctx_add(void)
{
struct mpd_client_ctx *client_ctx = client_ctx_new();
client_ctx->next = mpd_clients;
mpd_clients = client_ctx;
return client_ctx;
}
static void
free_output(struct output *output)
{
if (!output)
return;
free(output->name);
free(output);
}
/*
* Creates a new string for the given path that starts with a '/'.
* If 'path' already starts with a '/' the returned string is a duplicate
* of 'path'.
*
* The returned string needs to be freed by the caller.
*/
static char *
prepend_slash(const char *path)
{
char *result;
if (path[0] == '/')
result = strdup(path);
else
result = safe_asprintf("/%s", path);
return result;
}
static void
mpd_time(char *buffer, size_t bufferlen, time_t t)
{
struct tm tm;
const struct tm *tm2 = gmtime_r(&t, &tm);
if (tm2 == NULL)
return;
strftime(buffer, bufferlen, "%FT%TZ", tm2);
}
2014-12-21 20:41:44 +01:00
/*
* Splits a range argument of the form START:END (the END item is not included in the range)
2014-12-21 20:41:44 +01:00
* into its start and end position.
*
* @param start_pos set by this method to the start position
* @param end_pos set by this method to the end postion
* @param range the range argument
2014-12-21 20:41:44 +01:00
* @return 0 on success, -1 on failure
*/
static int
range_pos_from_arg(int *start_pos, int *end_pos, const char *range)
2014-12-21 20:41:44 +01:00
{
int ret;
if (strchr(range, ':'))
2014-12-21 20:41:44 +01:00
{
ret = sscanf(range, "%d:%d", start_pos, end_pos);
if (ret < 0)
{
DPRINTF(E_LOG, L_MPD, "Error splitting range argument '%s' (return code = %d)\n", range, ret);
return -1;
}
2014-12-21 20:41:44 +01:00
}
else
{
ret = safe_atoi32(range, start_pos);
if (ret < 0)
{
DPRINTF(E_LOG, L_MPD, "Error spitting integer argument '%s' (return code = %d)\n", range, ret);
return -1;
}
2014-12-21 20:41:44 +01:00
*end_pos = (*start_pos) + 1;
}
return 0;
}
/*
* Converts a TO arg, which is either an absolute position "X", or a position
* relative to currently playing song "+X"/"-X", into absolute queue pos.
*
* @param to_pos set by this method to absolute queue pos
* @param to_arg string with an integer (abs) or prefixed with +/- (rel)
* @return 0 on success, -1 on failure
*/
static int
to_pos_from_arg(int *to_pos, const char *to_arg)
{
struct player_status status;
struct db_queue_item *queue_item;
int ret;
*to_pos = -1;
if (!to_arg)
return 0; // If no to_arg, assume end of queue (to_pos = -1)
ret = safe_atoi32(to_arg, to_pos); // +12 will become 12, -12 becomes -12
if (ret < 0)
return -1;
if (to_arg[0] != '+' && to_arg[0] != '-')
return 0;
ret = player_get_status(&status);
if (ret < 0)
return -1;
queue_item = (status.status == PLAY_STOPPED) ? db_queue_fetch_bypos(0, status.shuffle) : db_queue_fetch_byitemid(status.item_id);
if (!queue_item)
return -1;
*to_pos += queue_item->pos;
free_queue_item(queue_item, 0);
return 0;
}
2014-12-21 20:41:44 +01:00
/*
* Returns the next unquoted string argument from the input string
*/
static char*
mpd_next_unquoted(char **input)
2014-12-21 20:41:44 +01:00
{
char *arg;
arg = *input;
while (**input != 0)
{
if (**input == ' ')
{
**input = '\0';
(*input)++;
return arg;
}
(*input)++;
}
return arg;
}
/*
* Returns the next quoted string argument from the input string
* with the quotes removed
*/
static char*
mpd_next_quoted(char **input)
2014-12-21 20:41:44 +01:00
{
char *arg;
2017-11-08 23:03:32 +01:00
char *src;
char *dst;
char ch;
2014-12-21 20:41:44 +01:00
// skip double quote character
(*input)++;
2017-11-08 23:03:32 +01:00
src = dst = arg = *input;
while ((ch = *src) != '"')
2014-12-21 20:41:44 +01:00
{
2017-11-08 23:03:32 +01:00
// A backslash character escapes the following character and should be removed
if (ch == '\\')
2014-12-21 20:41:44 +01:00
{
2017-12-05 01:34:27 +01:00
ch = *(++src);
2014-12-21 20:41:44 +01:00
}
2017-11-08 23:03:32 +01:00
*dst++ = ch;
2014-12-21 20:41:44 +01:00
2017-11-08 23:03:32 +01:00
if (ch == 0)
2014-12-21 20:41:44 +01:00
{
// Error handling for missing double quote at end of parameter
DPRINTF(E_LOG, L_MPD, "Error missing closing double quote in argument\n");
2017-12-05 01:34:27 +01:00
*input = src;
2014-12-21 20:41:44 +01:00
return NULL;
}
2017-11-08 23:03:32 +01:00
++src;
2014-12-21 20:41:44 +01:00
}
2017-11-08 23:03:32 +01:00
*dst = '\0';
*input = ++src;
2014-12-21 20:41:44 +01:00
return arg;
}
// Splits the argument string into an array of strings. Arguments are separated
// by a whitespace character and may be wrapped in double quotes.
static int
mpd_split_args(char **argv, int argv_size, int *argc, char **split, const char *line)
{
char *ptr;
int arg_count = 0;
*split = safe_strdup(line);
ptr = *split;
while (*ptr != 0 && arg_count < argv_size)
{
// Ignore whitespace characters
if (*ptr == ' ')
{
ptr++;
continue;
}
// Check if the parameter is wrapped in double quotes
if (*ptr == '"')
argv[arg_count] = mpd_next_quoted(&ptr);
else
argv[arg_count] = mpd_next_unquoted(&ptr);
if (!argv[arg_count])
goto error;
arg_count++;
}
*argc = arg_count;
// No args or too many args
if (arg_count == 0 || (*ptr != 0 && arg_count == argv_size))
goto error;
return 0;
error:
free(*split);
*split = NULL;
return -1;
}
2014-12-21 20:41:44 +01:00
/*
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
* Adds the information (path, id, tags, etc.) for the given song to the given buffer
* with additional information for the position of this song in the player queue.
2014-12-21 20:41:44 +01:00
*
* Example output:
* file: foo/bar/song.mp3
* Last-Modified: 2013-07-14T06:57:59Z
* Time: 172
* Artist: foo
* AlbumArtist: foo
* ArtistSort: foo
* AlbumArtistSort: foo
* Title: song
* Album: bar
* Track: 1/11
* Date: 2012-09-11
* Genre: Alternative
* Disc: 1/1
* MUSICBRAINZ_ALBUMARTISTID: c5c2ea1c-4bde-4f4d-bd0b-47b200bf99d6
* MUSICBRAINZ_ARTISTID: c5c2ea1c-4bde-4f4d-bd0b-47b200bf99d6
* MUSICBRAINZ_ALBUMID: 812f4b87-8ad9-41bd-be79-38151f17a2b4
* MUSICBRAINZ_TRACKID: fde95c39-ee51-48f6-a7f9-b5631c2ed156
* Pos: 0
* Id: 1
*
* @param evbuf the response event buffer
* @param queue_item queue item information
2014-12-21 20:41:44 +01:00
* @return the number of bytes added if successful, or -1 if an error occurred.
*/
static int
mpd_add_db_queue_item(struct evbuffer *evbuf, struct db_queue_item *queue_item)
2014-12-21 20:41:44 +01:00
{
char modified[32];
2014-12-21 20:41:44 +01:00
int ret;
mpd_time(modified, sizeof(modified), queue_item->time_modified);
ret = evbuffer_add_printf(evbuf,
"file: %s\n"
"Last-Modified: %s\n"
"Time: %d\n"
"Artist: %s\n"
"AlbumArtist: %s\n"
"ArtistSort: %s\n"
"AlbumArtistSort: %s\n"
"Album: %s\n"
"Title: %s\n"
"Track: %d\n"
"Date: %d\n"
"Genre: %s\n"
"Disc: %d\n"
"Pos: %d\n"
"Id: %d\n",
(queue_item->virtual_path + 1),
modified,
(queue_item->song_length / 1000),
sanitize(queue_item->artist),
sanitize(queue_item->album_artist),
sanitize(queue_item->artist_sort),
sanitize(queue_item->album_artist_sort),
sanitize(queue_item->album),
sanitize(queue_item->title),
queue_item->track,
queue_item->year,
sanitize(queue_item->genre),
queue_item->disc,
queue_item->pos,
queue_item->id);
2014-12-21 20:41:44 +01:00
return ret;
}
/*
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
* Adds the information (path, id, tags, etc.) for the given song to the given buffer.
2014-12-21 20:41:44 +01:00
*
* Example output:
* file: foo/bar/song.mp3
* Last-Modified: 2013-07-14T06:57:59Z
* Time: 172
* Artist: foo
* AlbumArtist: foo
* ArtistSort: foo
* AlbumArtistSort: foo
* Title: song
* Album: bar
* Track: 1/11
* Date: 2012-09-11
* Genre: Alternative
* Disc: 1/1
* MUSICBRAINZ_ALBUMARTISTID: c5c2ea1c-4bde-4f4d-bd0b-47b200bf99d6
* MUSICBRAINZ_ARTISTID: c5c2ea1c-4bde-4f4d-bd0b-47b200bf99d6
* MUSICBRAINZ_ALBUMID: 812f4b87-8ad9-41bd-be79-38151f17a2b4
* MUSICBRAINZ_TRACKID: fde95c39-ee51-48f6-a7f9-b5631c2ed156
*
* @param evbuf the response event buffer
* @param mfi media information
* @return the number of bytes added if successful, or -1 if an error occurred.
*/
2015-02-15 09:51:38 +01:00
static int
2014-12-21 20:41:44 +01:00
mpd_add_db_media_file_info(struct evbuffer *evbuf, struct db_media_file_info *dbmfi)
{
char modified[32];
uint32_t time_modified;
2014-12-21 20:41:44 +01:00
uint32_t songlength;
int ret;
if (safe_atou32(dbmfi->time_modified, &time_modified) != 0)
{
DPRINTF(E_LOG, L_MPD, "Error converting time modified to uint32_t: %s\n", dbmfi->time_modified);
return -1;
}
mpd_time(modified, sizeof(modified), time_modified);
2014-12-21 20:41:44 +01:00
if (safe_atou32(dbmfi->song_length, &songlength) != 0)
{
DPRINTF(E_LOG, L_MPD, "Error converting song length to uint32_t: %s\n", dbmfi->song_length);
return -1;
}
ret = evbuffer_add_printf(evbuf,
"file: %s\n"
"Last-Modified: %s\n"
2014-12-21 20:41:44 +01:00
"Time: %d\n"
"duration: %.3f\n"
2014-12-21 20:41:44 +01:00
"Artist: %s\n"
"AlbumArtist: %s\n"
"ArtistSort: %s\n"
"AlbumArtistSort: %s\n"
2014-12-21 20:41:44 +01:00
"Album: %s\n"
"Title: %s\n"
"Track: %s\n"
"Date: %s\n"
"Genre: %s\n"
"Disc: %s\n",
2015-02-15 09:51:38 +01:00
(dbmfi->virtual_path + 1),
modified,
2014-12-21 20:41:44 +01:00
(songlength / 1000),
((float) songlength / 1000),
sanitize(dbmfi->artist),
sanitize(dbmfi->album_artist),
sanitize(dbmfi->artist_sort),
sanitize(dbmfi->album_artist_sort),
sanitize(dbmfi->album),
sanitize(dbmfi->title),
dbmfi->track,
dbmfi->year,
sanitize(dbmfi->genre),
dbmfi->disc);
2014-12-21 20:41:44 +01:00
return ret;
2015-02-15 09:51:38 +01:00
}
2014-12-21 20:41:44 +01:00
static bool
is_filter_end(const char *arg)
{
return (strcasecmp(arg, "sort") == 0 || strcasecmp(arg, "group") == 0 || strcasecmp(arg, "window") == 0);
}
// The bison/flex parser only works with the filter format used after version
// 0.20 e.g. "(TAG == 'value') instead of TAG VALUE.
static int
args_reassemble(char *args, size_t args_size, int argc, char **argv)
{
const char *op;
int filter_start;
int filter_end;
int i;
// "list" has the filter as second arg, the rest have it as first
filter_start = (strcmp(argv[0], "list") == 0) ? 2 : 1;
op = strstr(argv[0], "search") ? " contains " : " == ";
for (i = filter_start; i < argc && !is_filter_end(argv[i]); i++)
;
filter_end = i;
snprintf(args, args_size, "%s", argv[0]);
for (i = 1; i < filter_start; i++)
safe_snprintf_cat(args, args_size, " %s", argv[i]);
for (i = filter_start; i < filter_end; i++)
{
if (*argv[i] == '(')
{
safe_snprintf_cat(args, args_size, " %s", argv[i]);
}
else if (i + 1 < filter_end) // Legacy filter format (0.20 and before), we will convert
{
safe_snprintf_cat(args, args_size, " %s%s%s\"%s\"%s", i == filter_start ? "((" : "AND (", argv[i], op, argv[i + 1], i + 2 == filter_end ? "))" : ")");
i++;
}
else if (filter_end == filter_start + 1) // Special case: a legacy single token is allowed if listing albums for an artist
{
safe_snprintf_cat(args, args_size, " (AlbumArtist%s\"%s\")", op, argv[i]);
}
}
for (i = filter_end; i < argc; i++)
safe_snprintf_cat(args, args_size, " %s", argv[i]);
// Return an error if the buffer was filled and thus probably truncated
return (strlen(args) + 1 < args_size) ? 0 : -1;
}
/*
* Invokes a lexer/parser to read a supported command
*
* @param qp Must be initialized by caller (zeroed or set to default values)
* @param pos Allocated string with TO position parameter, e.g. "+5" (or NULL)
* @param type Allocated string with TYPE parameter, e.g. "albums" (or NULL)
* @param in The command input
*/
static int
parse_command(struct query_params *qp, char **pos, char **tagtype, struct mpd_command_input *in)
{
struct mpd_result result;
char args_reassembled[8192];
int ret;
if (pos)
*pos = NULL;
if (tagtype)
*tagtype = NULL;
if (in->argc < 2)
return 0; // Nothing to parse
ret = args_reassemble(args_reassembled, sizeof(args_reassembled), in->argc, in->argv);
if (ret < 0)
{
DPRINTF(E_LOG, L_MPD, "Could not prepare '%s' for parsing\n", in->args_raw);
return -1;
}
DPRINTF(E_DBG, L_MPD, "Parse mpd query input '%s'\n", args_reassembled);
ret = mpd_lex_parse(&result, args_reassembled);
if (ret != 0)
{
DPRINTF(E_LOG, L_MPD, "Could not parse '%s': %s\n", args_reassembled, result.errmsg);
return -1;
}
2022-01-20 20:14:02 +01:00
qp->filter = safe_strdup(result.where);
qp->order = safe_strdup(result.order);
qp->group = safe_strdup(result.group);
qp->limit = result.limit;
qp->offset = result.offset;
qp->idx_type = (qp->limit || qp->offset) ? I_SUB : I_NONE;
if (pos)
*pos = safe_strdup(result.position);
if (tagtype)
*tagtype = safe_strdup(result.tagtype);
return 0;
}
static int
notify_idle_client(struct mpd_client_ctx *client_ctx, short events, bool add_ok)
{
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);
if (add_ok)
evbuffer_add(client_ctx->evbuffer, "OK\n", 3);
client_ctx->is_idle = false;
client_ctx->idle_events = 0;
client_ctx->events = 0;
return 0;
}
/* ----------------------------- Command handlers --------------------------- */
static int
mpd_command_currentsong(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
struct player_status status;
struct db_queue_item *queue_item;
2014-12-21 20:41:44 +01:00
int ret;
player_get_status(&status);
if (status.status == PLAY_STOPPED)
queue_item = db_queue_fetch_bypos(0, status.shuffle);
else
queue_item = db_queue_fetch_byitemid(status.item_id);
2014-12-21 20:41:44 +01:00
if (!queue_item)
return 0;
ret = mpd_add_db_queue_item(out->evbuf, queue_item);
free_queue_item(queue_item, 0);
2014-12-21 20:41:44 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Error setting media info for file with id: %d", status.id);
2014-12-21 20:41:44 +01:00
return 0;
}
/*
* Example input:
* idle "database" "mixer" "options" "output" "player" "playlist" "sticker" "update"
*/
static int
mpd_command_idle(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
char *key;
2015-02-21 06:04:17 +01:00
int i;
ctx->idle_events = 0;
ctx->is_idle = true;
2015-02-21 06:04:17 +01:00
if (in->argc > 1)
2015-02-21 06:04:17 +01:00
{
for (i = 1; i < in->argc; i++)
2015-02-21 06:04:17 +01:00
{
key = in->argv[i];
if (0 == strcmp(key, "database"))
ctx->idle_events |= LISTENER_DATABASE;
else if (0 == strcmp(key, "update"))
ctx->idle_events |= LISTENER_UPDATE;
else if (0 == strcmp(key, "player"))
ctx->idle_events |= LISTENER_PLAYER;
else if (0 == strcmp(key, "playlist"))
ctx->idle_events |= LISTENER_QUEUE;
else if (0 == strcmp(key, "mixer"))
ctx->idle_events |= LISTENER_VOLUME;
else if (0 == strcmp(key, "output"))
ctx->idle_events |= LISTENER_SPEAKER;
else if (0 == strcmp(key, "options"))
ctx->idle_events |= LISTENER_OPTIONS;
else if (0 == strcmp(key, "stored_playlist"))
ctx->idle_events |= LISTENER_STORED_PLAYLIST;
else if (0 == strcmp(key, "sticker"))
ctx->idle_events |= LISTENER_RATING;
2015-02-21 06:04:17 +01:00
else
DPRINTF(E_DBG, L_MPD, "Idle command for '%s' not supported\n", key);
2015-02-21 06:04:17 +01:00
}
}
else
2017-12-10 09:24:29 +01:00
ctx->idle_events = MPD_ALL_IDLE_LISTENER_EVENTS;
2015-02-21 06:04:17 +01:00
// 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)
notify_idle_client(ctx, ctx->events, false);
return 0;
}
static int
mpd_command_noidle(struct mpd_command_output *out, struct mpd_command_input *in, 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, false);
ctx->is_idle = false;
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'status'
*
* Example output:
* volume: -1
* repeat: 0
* random: 0
* single: 0
* consume: 0
* playlist: 2
* playlistlength: 34
* mixrampdb: 0.000000
* state: stop
* song: 0
* songid: 1
* time: 28:306
* elapsed: 28.178
* bitrate: 278
* audio: 44100:f:2
* nextsong: 1
* nextsongid: 2
*/
static int
mpd_command_status(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
struct player_status status;
uint32_t queue_length = 0;
int queue_version = 0;
2014-12-21 20:41:44 +01:00
char *state;
uint32_t itemid = 0;
struct db_queue_item *queue_item;
2014-12-21 20:41:44 +01:00
player_get_status(&status);
switch (status.status)
{
case PLAY_PAUSED:
state = "pause";
break;
case PLAY_PLAYING:
state = "play";
break;
default:
state = "stop";
break;
}
db_admin_getint(&queue_version, DB_ADMIN_QUEUE_VERSION);
db_queue_get_count(&queue_length);
evbuffer_add_printf(out->evbuf,
2014-12-21 20:41:44 +01:00
"volume: %d\n"
"repeat: %d\n"
"random: %d\n"
"single: %d\n"
"consume: %d\n"
"playlist: %d\n"
"playlistlength: %d\n"
"mixrampdb: 0.000000\n"
"state: %s\n",
status.volume,
(status.repeat == REPEAT_OFF ? 0 : 1),
status.shuffle,
(status.repeat == REPEAT_SONG ? 1 : 0),
status.consume,
queue_version,
queue_length,
2014-12-21 20:41:44 +01:00
state);
if (status.status != PLAY_STOPPED)
queue_item = db_queue_fetch_byitemid(status.item_id);
else
queue_item = db_queue_fetch_bypos(0, status.shuffle);
if (queue_item)
{
evbuffer_add_printf(out->evbuf,
2014-12-21 20:41:44 +01:00
"song: %d\n"
"songid: %d\n",
queue_item->pos,
queue_item->id);
itemid = queue_item->id;
free_queue_item(queue_item, 0);
}
if (status.status != PLAY_STOPPED)
{
evbuffer_add_printf(out->evbuf,
2014-12-21 20:41:44 +01:00
"time: %d:%d\n"
"elapsed: %#.3f\n"
"bitrate: 128\n"
"audio: 44100:16:2\n",
(status.pos_ms / 1000), (status.len_ms / 1000),
(status.pos_ms / 1000.0));
}
2014-12-21 20:41:44 +01:00
if (library_is_scanning())
{
evbuffer_add(out->evbuf, "updating_db: 1\n", 15);
}
if (itemid > 0)
{
queue_item = db_queue_fetch_next(itemid, status.shuffle);
if (queue_item)
{
evbuffer_add_printf(out->evbuf,
"nextsong: %d\n"
"nextsongid: %d\n",
queue_item->pos,
queue_item->id);
free_queue_item(queue_item, 0);
}
}
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'stats'
*/
static int
mpd_command_stats(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
struct query_params qp = { .type = Q_COUNT_ITEMS };
struct filecount_info fci;
double uptime;
int64_t db_start = 0;
int64_t db_update = 0;
int ret;
ret = db_filecount_get(&fci, &qp);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Could not start query");
db_admin_getint64(&db_start, DB_ADMIN_START_TIME);
uptime = difftime(time(NULL), (time_t) db_start);
db_admin_getint64(&db_update, DB_ADMIN_DB_UPDATE);
//TODO [mpd] Implement missing stats attributes (playtime)
evbuffer_add_printf(out->evbuf,
2014-12-21 20:41:44 +01:00
"artists: %d\n"
"albums: %d\n"
"songs: %d\n"
"uptime: %.f\n" //in seceonds
"db_playtime: %" PRIi64 "\n"
"db_update: %" PRIi64 "\n"
2014-12-21 20:41:44 +01:00
"playtime: %d\n",
fci.artist_count,
fci.album_count,
fci.count,
uptime,
(fci.length / 1000),
db_update,
7);
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'consume'
* Sets the consume mode, expects argument argv[1] to be an integer with
* 0 = disable consume
* 1 = enable consume
*/
static int
mpd_command_consume(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
player_consume_set(in->argv_u32val[1]);
return 0;
}
2014-12-21 20:41:44 +01:00
/*
* Command handler function for 'random'
* Sets the shuffle mode, expects argument argv[1] to be an integer with
* 0 = disable shuffle
* 1 = enable shuffle
*/
static int
mpd_command_random(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
player_shuffle_set(in->argv_u32val[1]);
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'repeat'
* Sets the repeat mode, expects argument argv[1] to be an integer with
* 0 = repeat off
* 1 = repeat all
*/
static int
mpd_command_repeat(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
if (in->argv_u32val[1] == 0)
2014-12-21 20:41:44 +01:00
player_repeat_set(REPEAT_OFF);
else
player_repeat_set(REPEAT_ALL);
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'setvol'
* Sets the volume, expects argument argv[1] to be an integer 0-100
*/
static int
mpd_command_setvol(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
player_volume_set(in->argv_u32val[1]);
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'single'
* Sets the repeat mode, expects argument argv[1] to be an integer or
* "oneshot" for 0.21 protocol.
* The server only allows single-mode in combination with repeat, therefore
* the command single translates (depending on the current repeat mode) into:
2014-12-21 20:41:44 +01:00
* a) if repeat off:
* 0 = repeat off
* 1 = repeat song
* b) if repeat all:
* 0 = repeat all
* 1 = repeat song
* c) if repeat song:
* 0 = repeat all
* 1 = repeat song
* Thus "oneshot" is accepted, but ignored under all circumstances.
2014-12-21 20:41:44 +01:00
*/
static int
mpd_command_single(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
bool has_enable = (in->has_num & MPD_WANTS_NUM_ARG1_UVAL);
uint32_t enable = in->argv_u32val[1];
2014-12-21 20:41:44 +01:00
struct player_status status;
// 0.21 protocol: accept "oneshot" mode
if (strcmp(in->argv[1], "oneshot") == 0)
return 0;
if (!has_enable)
RETURN_ERROR(ACK_ERROR_ARG, "Command 'single' expects integer or 'oneshot' argument");
2014-12-21 20:41:44 +01:00
player_get_status(&status);
if (enable == 0 && status.repeat != REPEAT_OFF)
player_repeat_set(REPEAT_ALL);
else if (enable == 0)
player_repeat_set(REPEAT_OFF);
else
player_repeat_set(REPEAT_SONG);
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'replay_gain_status'
* The server does not support replay gain, therefor this function returns always
2014-12-21 20:41:44 +01:00
* "replay_gain_mode: off".
*/
static int
mpd_command_replay_gain_status(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
evbuffer_add(out->evbuf, "replay_gain_mode: off\n", 22);
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'volume'
* Changes the volume by the given amount, expects argument argv[1] to be an integer
*/
static int
mpd_command_volume(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
struct player_status status;
int32_t volume = in->argv_i32val[1];
2014-12-21 20:41:44 +01:00
player_get_status(&status);
volume += status.volume;
player_volume_set(volume);
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'next'
* Skips to the next song in the playqueue
*/
static int
mpd_command_next(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
int ret;
ret = player_playback_next();
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to skip to next song");
2014-12-21 20:41:44 +01:00
ret = player_playback_start();
2014-12-21 20:41:44 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Player returned an error for start after nextitem");
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'pause'
* Toggles pause/play, if the optional argument argv[1] is present, it must be an integer with
* 0 = play
* 1 = pause
*/
static int
mpd_command_pause(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
uint32_t pause;
2014-12-21 20:41:44 +01:00
struct player_status status;
int ret;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
player_get_status(&status);
if (in->has_num & MPD_WANTS_NUM_ARG1_UVAL)
pause = in->argv_u32val[1];
else
pause = (status.status == PLAY_PLAYING) ? 1 : 0;
2014-12-21 20:41:44 +01:00
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
// MPD ignores pause in stopped state
if (pause == 1 && status.status == PLAY_PLAYING)
2014-12-21 20:41:44 +01:00
ret = player_playback_pause();
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
else if (pause == 0 && status.status == PLAY_PAUSED)
ret = player_playback_start();
else
ret = 0;
2014-12-21 20:41:44 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to %s playback", (pause == 1) ? "pause" : "start");
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'play'
* Starts playback, the optional argument argv[1] represents the position in the playqueue
* where to start playback.
*/
static int
mpd_command_play(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
bool has_songpos = (in->has_num & MPD_WANTS_NUM_ARG1_UVAL);
uint32_t songpos = in->argv_u32val[1];
2014-12-21 20:41:44 +01:00
struct player_status status;
struct db_queue_item *queue_item;
2014-12-21 20:41:44 +01:00
int ret;
player_get_status(&status);
if (status.status == PLAY_PLAYING && !has_songpos)
return 0;
// Stop playback, if player is already playing and a valid song position is
// given (it will be restarted for the given song position)
if (status.status == PLAY_PLAYING)
player_playback_stop();
if (has_songpos)
{
queue_item = db_queue_fetch_bypos(songpos, 0);
if (!queue_item)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to start playback, unknown song position");
ret = player_playback_start_byitem(queue_item);
free_queue_item(queue_item, 0);
}
2014-12-21 20:41:44 +01:00
else
ret = player_playback_start();
2014-12-21 20:41:44 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to start playback (queue empty?)");
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'playid'
* Starts playback, the optional argument argv[1] represents the songid of the song
* where to start playback.
*/
static int
mpd_command_playid(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
bool has_id = (in->has_num & MPD_WANTS_NUM_ARG1_UVAL);
uint32_t id = in->argv_u32val[1];
2014-12-21 20:41:44 +01:00
struct player_status status;
struct db_queue_item *queue_item;
2014-12-21 20:41:44 +01:00
int ret;
player_get_status(&status);
// Stop playback, if player is already playing and a valid item id is given
// (it will be restarted for the given song)
if (status.status == PLAY_PLAYING)
player_playback_stop();
if (has_id)
{
queue_item = db_queue_fetch_byitemid(id);
if (!queue_item)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to start playback, unknown song id");
ret = player_playback_start_byitem(queue_item);
free_queue_item(queue_item, 0);
}
2014-12-21 20:41:44 +01:00
else
ret = player_playback_start();
2014-12-21 20:41:44 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to start playback (queue empty?)");
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'previous'
* Skips to the previous song in the playqueue
*/
static int
mpd_command_previous(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
int ret;
ret = player_playback_prev();
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to skip to previous song");
2014-12-21 20:41:44 +01:00
ret = player_playback_start();
2016-03-12 08:17:12 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Player returned an error for start after previtem");
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'seek'
2014-12-21 20:41:44 +01:00
* Seeks to song at the given position in argv[1] to the position in seconds given in argument argv[2]
* (fractions allowed).
*/
static int
mpd_command_seek(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
float seek_target_sec;
int seek_target_msec;
int ret;
//TODO Allow seeking in songs not currently playing
seek_target_sec = strtof(in->argv[2], NULL);
2014-12-21 20:41:44 +01:00
seek_target_msec = seek_target_sec * 1000;
ret = player_playback_seek(seek_target_msec, PLAYER_SEEK_POSITION);
2014-12-21 20:41:44 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to seek current song to time %d msec", seek_target_msec);
2014-12-21 20:41:44 +01:00
ret = player_playback_start();
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to start playback");
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'seekid'
* Seeks to song with id given in argv[1] to the position in seconds given in argument argv[2]
* (fractions allowed).
*/
static int
mpd_command_seekid(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
struct player_status status;
float seek_target_sec;
int seek_target_msec;
int ret;
//TODO Allow seeking in songs not currently playing
player_get_status(&status);
if (status.item_id != in->argv_u32val[1])
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Given song is not the current playing one, seeking is not supported");
2014-12-21 20:41:44 +01:00
seek_target_sec = strtof(in->argv[2], NULL);
2014-12-21 20:41:44 +01:00
seek_target_msec = seek_target_sec * 1000;
ret = player_playback_seek(seek_target_msec, PLAYER_SEEK_POSITION);
2014-12-21 20:41:44 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to seek current song to time %d msec", seek_target_msec);
2014-12-21 20:41:44 +01:00
ret = player_playback_start();
2016-03-12 08:17:12 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to start playback");
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'seekcur'
* Seeks the current song to the position in seconds given in argument argv[1] (fractions allowed).
*/
static int
mpd_command_seekcur(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
float seek_target_sec;
int seek_target_msec;
int ret;
seek_target_sec = strtof(in->argv[1], NULL);
2014-12-21 20:41:44 +01:00
seek_target_msec = seek_target_sec * 1000;
// TODO If prefixed by '+' or '-', then the time is relative to the current playing position.
ret = player_playback_seek(seek_target_msec, PLAYER_SEEK_POSITION);
2014-12-21 20:41:44 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to seek current song to time %d msec", seek_target_msec);
2014-12-21 20:41:44 +01:00
ret = player_playback_start();
2016-03-12 08:17:12 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to start playback");
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'stop'
* Stop playback.
*/
static int
mpd_command_stop(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
int ret;
ret = player_playback_stop();
if (ret != 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to stop playback");
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Add media file item with given virtual path to the queue
*
* @param path The virtual path
* @param exact_match If TRUE add only item with exact match, otherwise add all items virtual path start with the given path
* @return The queue item id of the last inserted item or -1 on failure
*/
static int
mpd_queue_add(char *path, bool exact_match, int position)
{
struct query_params qp = { .type = Q_ITEMS, .idx_type = I_NONE, .sort = S_ARTIST };
struct player_status status;
int new_item_id;
int ret;
new_item_id = 0;
if (exact_match)
CHECK_NULL(L_MPD, qp.filter = db_mprintf("f.disabled = 0 AND f.virtual_path LIKE '/%q'", path));
else
CHECK_NULL(L_MPD, qp.filter = db_mprintf("f.disabled = 0 AND f.virtual_path LIKE '/%q%%'", path));
player_get_status(&status);
ret = db_queue_add_by_query(&qp, status.shuffle, status.item_id, position, NULL, &new_item_id);
free_query_params(&qp, 1);
if (ret == 0)
return new_item_id;
return ret;
}
2014-12-21 20:41:44 +01:00
/*
* Command handler function for 'add'
* 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
mpd_command_add(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
struct player_status status;
2014-12-21 20:41:44 +01:00
int ret;
ret = mpd_queue_add(in->argv[1], false, -1);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to add song '%s' to playlist", in->argv[1]);
2014-12-21 20:41:44 +01:00
if (ret == 0)
{
player_get_status(&status);
// Given path is not in the library, check if it is possible to add as a non-library queue item
ret = library_queue_item_add(in->argv[1], -1, status.shuffle, status.item_id, NULL, NULL);
2017-03-13 20:03:25 +01:00
if (ret != LIBRARY_OK)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to add song '%s' to playlist (unknown path)", in->argv[1]);
}
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'addid'
* Adds the song under the given path to the end or to the given position of the playqueue.
* Expects argument argv[1] to be a path to a single file. argv[2] is optional, if present
* as int is the absolute new position, if present as "+x" og "-x" it is relative.
2014-12-21 20:41:44 +01:00
*/
static int
mpd_command_addid(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
int to_pos = -1;
struct player_status status;
char *path = in->argv[1];
2014-12-21 20:41:44 +01:00
int ret;
if (in->argc > 2)
2014-12-21 20:41:44 +01:00
{
ret = to_pos_from_arg(&to_pos, in->argv[2]);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Invalid TO argument: '%s'", in->argv[2]);
2014-12-21 20:41:44 +01:00
}
ret = mpd_queue_add(path, true, to_pos);
if (ret == 0)
{
player_get_status(&status);
// Given path is not in the library, directly add it as a new queue item
ret = library_queue_item_add(path, to_pos, status.shuffle, status.item_id, NULL, NULL);
2017-03-13 20:03:25 +01:00
if (ret != LIBRARY_OK)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to add song '%s' to playlist (unknown path)", path);
}
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to add song '%s' to playlist", path);
2014-12-21 20:41:44 +01:00
evbuffer_add_printf(out->evbuf,
2014-12-21 20:41:44 +01:00
"Id: %d\n",
ret); // mpd_queue_add returns the item_id of the last inserted queue item
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'clear'
* Stops playback and removes all songs from the playqueue
*/
static int
mpd_command_clear(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
int ret;
ret = player_playback_stop();
if (ret != 0)
{
DPRINTF(E_DBG, L_MPD, "Failed to stop playback\n");
}
db_queue_clear(0);
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'delete'
* Removes songs from the playqueue. Expects argument argv[1] (optional) to be an integer or
* an integer range {START:END} representing the position of the songs in the playlist, that
* should be removed.
*/
static int
mpd_command_delete(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
int start_pos;
int end_pos;
int count;
2014-12-21 20:41:44 +01:00
int ret;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
// If argv[1] is omitted clear the whole queue
if (in->argc < 2)
2014-12-21 20:41:44 +01:00
{
db_queue_clear(0);
return 0;
2014-12-21 20:41:44 +01:00
}
// If argument argv[1] is present remove only the specified songs
ret = range_pos_from_arg(&start_pos, &end_pos, in->argv[1]);
2014-12-21 20:41:44 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Argument doesn't convert to integer or range: '%s'", in->argv[1]);
2014-12-21 20:41:44 +01:00
count = end_pos - start_pos;
2014-12-21 20:41:44 +01:00
ret = db_queue_delete_bypos(start_pos, count);
2014-12-21 20:41:44 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to remove %d songs starting at position %d", count, start_pos);
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
/* Command handler function for 'deleteid'
2014-12-21 20:41:44 +01:00
* Removes the song with given id from the playqueue. Expects argument argv[1] to be an integer (song id).
*/
static int
mpd_command_deleteid(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
uint32_t songid = in->argv_u32val[1];
2014-12-21 20:41:44 +01:00
int ret;
ret = db_queue_delete_byitemid(songid);
2014-12-21 20:41:44 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to remove song with id '%s'", in->argv[1]);
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
// Moves the song at FROM or range of songs at START:END to TO in the playlist.
static int
mpd_command_move(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
2016-02-15 20:27:39 +01:00
int start_pos;
int end_pos;
int count;
int to_pos;
uint32_t queue_length;
2016-02-15 20:27:39 +01:00
int ret;
ret = range_pos_from_arg(&start_pos, &end_pos, in->argv[1]);
2016-02-15 20:27:39 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Argument doesn't convert to integer or range: '%s'", in->argv[1]);
2016-02-15 20:27:39 +01:00
count = end_pos - start_pos;
2016-02-15 20:27:39 +01:00
ret = to_pos_from_arg(&to_pos, in->argv[2]);
2016-02-15 20:27:39 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Invalid argument: '%s'", in->argv[2]);
2016-02-15 20:27:39 +01:00
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
db_queue_get_count(&queue_length);
// valid move pos and range is:
// 0 <= start < queue_len
// start < end <= queue_len
// 0 <= to <= queue_len - count
if (!(start_pos >= 0 && start_pos < queue_length
&& end_pos > start_pos && end_pos <= queue_length
&& to_pos >= 0 && to_pos <= queue_length - count))
RETURN_ERROR(ACK_ERROR_ARG, "Range too large for target position %d or bad song index (count %d, length %u)", to_pos, count, queue_length);
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
ret = db_queue_move_bypos_range(start_pos, end_pos, to_pos);
2016-02-15 20:27:39 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to move song at position %d to %d", start_pos, to_pos);
2016-02-15 20:27:39 +01:00
return 0;
}
static int
mpd_command_moveid(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
uint32_t songid = in->argv_u32val[1];
int to_pos;
int ret;
ret = to_pos_from_arg(&to_pos, in->argv[2]);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Invalid TO argument: '%s'", in->argv[2]);
ret = db_queue_move_byitemid(songid, to_pos, 0);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to move song with id '%u' to index '%u'", songid, to_pos);
return 0;
}
2015-02-15 09:51:38 +01:00
/*
* Command handler function for 'playlistid'
* Displays a list of all songs in the queue, or if the optional argument is given, displays information
* only for the song with ID.
*
* The order of the songs is always the not shuffled order.
*/
static int
mpd_command_playlistid(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
bool has_songid = (in->has_num & MPD_WANTS_NUM_ARG1_UVAL);
uint32_t songid = in->argv_u32val[1];
struct query_params qp = { 0 };
struct db_queue_item queue_item;
int ret;
if (has_songid)
qp.filter = db_mprintf("id = %d", songid);
ret = db_queue_enum_start(&qp);
if (ret < 0)
{
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_ARG, "Failed to start queue enum for command playlistid");
}
while ((ret = db_queue_enum_fetch(&qp, &queue_item)) == 0 && queue_item.id > 0)
{
ret = mpd_add_db_queue_item(out->evbuf, &queue_item);
if (ret < 0)
{
db_queue_enum_end(&qp);
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Error adding media info for file");
}
}
db_queue_enum_end(&qp);
free_query_params(&qp, 1);
return 0;
}
2014-12-21 20:41:44 +01:00
/*
* Command handler function for 'playlistinfo'
* Displays a list of all songs in the queue, or if the optional argument is given, displays information
* only for the song SONGPOS or the range of songs START:END given in argv[1].
*
* The order of the songs is always the not shuffled order.
*/
static int
mpd_command_playlistinfo(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
struct query_params qp = { 0 };
struct db_queue_item queue_item;
int start_pos;
int end_pos;
int ret;
start_pos = 0;
end_pos = 0;
if (in->argc > 1)
{
ret = range_pos_from_arg(&start_pos, &end_pos, in->argv[1]);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Argument doesn't convert to integer or range: '%s'", in->argv[1]);
if (start_pos < 0)
DPRINTF(E_DBG, L_MPD, "Command 'playlistinfo' called with pos < 0 (arg = '%s'), ignore arguments and return whole queue\n", in->argv[1]);
else
qp.filter = db_mprintf("pos >= %d AND pos < %d", start_pos, end_pos);
}
ret = db_queue_enum_start(&qp);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Failed to start queue enum for command playlistinfo: '%s'", in->argv[1]);
while ((ret = db_queue_enum_fetch(&qp, &queue_item)) == 0 && queue_item.id > 0)
{
ret = mpd_add_db_queue_item(out->evbuf, &queue_item);
if (ret < 0)
{
db_queue_enum_end(&qp);
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Error adding media info");
}
}
db_queue_enum_end(&qp);
free_query_params(&qp, 1);
return 0;
}
/*
* playlistfind {FILTER} [sort {TYPE}] [window {START:END}]
* Searches for songs that match in the queue
* TODO add support for window (currently not supported by db_queue_enum_x)
*/
static int
mpd_command_playlistfind(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
struct query_params qp = { 0 };
struct db_queue_item queue_item;
int ret;
ret = parse_command(&qp, NULL, NULL, in);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Unknown argument(s) for command 'playlistfind'");
ret = db_queue_enum_start(&qp);
if (ret < 0)
{
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_ARG, "Failed to start queue enum for command playlistinfo: '%s'", in->argv[1]);
}
while ((ret = db_queue_enum_fetch(&qp, &queue_item)) == 0 && queue_item.id > 0)
{
ret = mpd_add_db_queue_item(out->evbuf, &queue_item);
if (ret < 0)
{
db_queue_enum_end(&qp);
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Error adding media info");
}
}
db_queue_enum_end(&qp);
free_query_params(&qp, 1);
return 0;
}
/*
* playlistsearch {FILTER} [sort {TYPE}] [window {START:END}]
*/
static int
mpd_command_playlistsearch(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
struct query_params qp = { 0 };
struct db_queue_item queue_item;
2014-12-21 20:41:44 +01:00
int ret;
ret = parse_command(&qp, NULL, NULL, in);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Unknown argument(s) for command 'playlistsearch'");
ret = db_queue_enum_start(&qp);
if (ret < 0)
2014-12-21 20:41:44 +01:00
{
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_ARG, "Failed to start queue enum for command playlistinfo: '%s'", in->argv[1]);
2014-12-21 20:41:44 +01:00
}
while ((ret = db_queue_enum_fetch(&qp, &queue_item)) == 0 && queue_item.id > 0)
2014-12-21 20:41:44 +01:00
{
ret = mpd_add_db_queue_item(out->evbuf, &queue_item);
2014-12-21 20:41:44 +01:00
if (ret < 0)
{
db_queue_enum_end(&qp);
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Error adding media info");
2014-12-21 20:41:44 +01:00
}
}
db_queue_enum_end(&qp);
free_query_params(&qp, 1);
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
static int
plchanges_build_queryparams(struct query_params *qp, uint32_t version, const char *range)
{
int start_pos;
int end_pos;
int ret;
memset(qp, 0, sizeof(struct query_params));
start_pos = 0;
end_pos = 0;
if (range)
{
ret = range_pos_from_arg(&start_pos, &end_pos, range);
if (ret < 0)
return -1;
if (start_pos < 0)
DPRINTF(E_DBG, L_MPD, "Invalid range '%s', will return entire queue\n", range);
}
if (start_pos < 0 || end_pos <= 0)
qp->filter = db_mprintf("(queue_version > %d)", version);
else
qp->filter = db_mprintf("(queue_version > %d AND pos >= %d AND pos < %d)", version, start_pos, end_pos);
return 0;
}
/*
* plchanges {VERSION} [START:END]
* Lists all changed songs in the queue since the given playlist version in argv[1].
*/
static int
mpd_command_plchanges(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
uint32_t version = in->argv_u32val[1];
const char *range = (in->argc > 2) ? in->argv[2] : NULL;
struct query_params qp;
struct db_queue_item queue_item;
int ret;
ret = plchanges_build_queryparams(&qp, version, range);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Invalid range '%s' provided to plchanges", range);
ret = db_queue_enum_start(&qp);
if (ret < 0)
{
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to start queue enum for command plchangesposid");
}
while ((ret = db_queue_enum_fetch(&qp, &queue_item)) == 0 && queue_item.id > 0)
{
ret = mpd_add_db_queue_item(out->evbuf, &queue_item);
if (ret < 0)
{
db_queue_enum_end(&qp);
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Error adding media info");
}
}
db_queue_enum_end(&qp);
free_query_params(&qp, 1);
return 0;
}
/*
* plchangesposid {VERSION} [START:END]
* Lists all changed songs in the queue since the given playlist version in
* argv[1] without metadata.
*/
static int
mpd_command_plchangesposid(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
uint32_t version = in->argv_u32val[1];
const char *range = (in->argc > 2) ? in->argv[2] : NULL;
struct query_params qp;
struct db_queue_item queue_item;
int ret;
ret = plchanges_build_queryparams(&qp, version, range);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Invalid range '%s' provided to plchangesposid", range);
ret = db_queue_enum_start(&qp);
if (ret < 0)
{
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to start queue enum for command plchangesposid");
}
while ((ret = db_queue_enum_fetch(&qp, &queue_item)) == 0 && queue_item.id > 0)
{
evbuffer_add_printf(out->evbuf,
"cpos: %d\n"
"Id: %d\n",
queue_item.pos,
queue_item.id);
}
db_queue_enum_end(&qp);
free_query_params(&qp, 1);
return 0;
}
/*
* Command handler function for 'listplaylist'
* Lists all songs in the playlist given by virtual-path in argv[1].
*/
static int
mpd_command_listplaylist(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
char *path;
struct playlist_info *pli;
struct query_params qp = { .type = Q_PLITEMS, .idx_type = I_NONE };
struct db_media_file_info dbmfi;
int ret;
if (!default_pl_dir || strstr(in->argv[1], ":/"))
{
// Argument is a virtual path, make sure it starts with a '/'
path = prepend_slash(in->argv[1]);
}
else
{
// Argument is a playlist name, prepend default playlist directory
path = safe_asprintf("%s/%s", default_pl_dir, in->argv[1]);
}
pli = db_pl_fetch_byvirtualpath(path);
free(path);
if (!pli)
RETURN_ERROR(ACK_ERROR_ARG, "Playlist not found for path '%s'", in->argv[1]);
qp.id = pli->id;
ret = db_query_start(&qp);
if (ret < 0)
{
free_pli(pli, 0);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Could not start query");
}
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
{
evbuffer_add_printf(out->evbuf,
"file: %s\n",
(dbmfi.virtual_path + 1));
}
db_query_end(&qp);
free_pli(pli, 0);
return 0;
}
/*
* Command handler function for 'listplaylistinfo'
* Lists all songs in the playlist given by virtual-path in argv[1] with metadata.
*/
static int
mpd_command_listplaylistinfo(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
char *path;
struct playlist_info *pli;
struct query_params qp = { .type = Q_PLITEMS, .idx_type = I_NONE };
struct db_media_file_info dbmfi;
int ret;
if (!default_pl_dir || strstr(in->argv[1], ":/"))
{
// Argument is a virtual path, make sure it starts with a '/'
path = prepend_slash(in->argv[1]);
}
else
{
// Argument is a playlist name, prepend default playlist directory
path = safe_asprintf("%s/%s", default_pl_dir, in->argv[1]);
}
pli = db_pl_fetch_byvirtualpath(path);
free(path);
if (!pli)
RETURN_ERROR(ACK_ERROR_NO_EXIST, "Playlist not found for path '%s'", in->argv[1]);
qp.id = pli->id;
ret = db_query_start(&qp);
if (ret < 0)
{
free_pli(pli, 0);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Could not start query");
}
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
{
ret = mpd_add_db_media_file_info(out->evbuf, &dbmfi);
if (ret < 0)
{
DPRINTF(E_LOG, L_MPD, "Error adding song to the evbuffer, song id: %s\n", dbmfi.id);
}
}
db_query_end(&qp);
free_pli(pli, 0);
return 0;
}
/*
* Command handler function for 'listplaylists'
* Lists all playlists with their last modified date.
*/
static int
mpd_command_listplaylists(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
struct query_params qp = { .type = Q_PL, .idx_type = I_NONE, .sort = S_PLAYLIST };
struct db_playlist_info dbpli;
char modified[32];
uint32_t time_modified;
int ret;
qp.filter = db_mprintf("(f.type = %d OR f.type = %d)", PL_PLAIN, PL_SMART);
ret = db_query_start(&qp);
if (ret < 0)
{
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Could not start query");
}
while (((ret = db_query_fetch_pl(&dbpli, &qp)) == 0) && (dbpli.id))
{
if (safe_atou32(dbpli.db_timestamp, &time_modified) != 0)
2017-12-05 01:34:27 +01:00
{
db_query_end(&qp);
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Error converting time modified to uint32_t");
2017-12-05 01:34:27 +01:00
}
mpd_time(modified, sizeof(modified), time_modified);
evbuffer_add_printf(out->evbuf,
"playlist: %s\n"
"Last-Modified: %s\n",
(dbpli.virtual_path + 1),
modified);
}
db_query_end(&qp);
free_query_params(&qp, 1);
return 0;
}
2014-12-21 20:41:44 +01:00
/*
* Command handler function for 'load'
* Adds the playlist given by virtual-path in argv[1] to the queue.
*/
static int
mpd_command_load(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
char *path;
2014-12-21 20:41:44 +01:00
struct playlist_info *pli;
struct player_status status;
struct query_params qp = { .type = Q_PLITEMS };
2014-12-21 20:41:44 +01:00
int ret;
if (!default_pl_dir || strstr(in->argv[1], ":/"))
{
// Argument is a virtual path, make sure it starts with a '/'
path = prepend_slash(in->argv[1]);
}
2014-12-21 20:41:44 +01:00
else
{
// Argument is a playlist name, prepend default playlist directory
path = safe_asprintf("%s/%s", default_pl_dir, in->argv[1]);
2014-12-21 20:41:44 +01:00
}
pli = db_pl_fetch_byvirtualpath(path);
free(path);
2014-12-21 20:41:44 +01:00
if (!pli)
RETURN_ERROR(ACK_ERROR_ARG, "Playlist not found for path '%s'", in->argv[1]);
2014-12-21 20:41:44 +01:00
//TODO If a second parameter is given only add the specified range of songs to the playqueue
qp.id = pli->id;
free_pli(pli, 0);
player_get_status(&status);
2014-12-21 20:41:44 +01:00
ret = db_queue_add_by_query(&qp, status.shuffle, status.item_id, -1, NULL, NULL);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to add song '%s' to playlist", in->argv[1]);
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
static int
mpd_command_playlistadd(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
char *vp_playlist;
char *vp_item;
int ret;
if (!allow_modifying_stored_playlists)
RETURN_ERROR(ACK_ERROR_PERMISSION, "Modifying stored playlists is not enabled");
if (!default_pl_dir || strstr(in->argv[1], ":/"))
{
// Argument is a virtual path, make sure it starts with a '/'
vp_playlist = prepend_slash(in->argv[1]);
}
else
{
// Argument is a playlist name, prepend default playlist directory
vp_playlist = safe_asprintf("%s/%s", default_pl_dir, in->argv[1]);
}
vp_item = prepend_slash(in->argv[2]);
ret = library_playlist_item_add(vp_playlist, vp_item);
free(vp_playlist);
free(vp_item);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Error adding item to file '%s'", in->argv[1]);
return 0;
}
static int
mpd_command_rm(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
char *virtual_path;
int ret;
if (!allow_modifying_stored_playlists)
RETURN_ERROR(ACK_ERROR_PERMISSION, "Modifying stored playlists is not enabled");
if (!default_pl_dir || strstr(in->argv[1], ":/"))
{
// Argument is a virtual path, make sure it starts with a '/'
virtual_path = prepend_slash(in->argv[1]);
}
else
{
// Argument is a playlist name, prepend default playlist directory
virtual_path = safe_asprintf("%s/%s", default_pl_dir, in->argv[1]);
}
ret = library_playlist_remove(virtual_path);
free(virtual_path);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Error removing playlist '%s'", in->argv[1]);
return 0;
}
static int
mpd_command_save(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
char *virtual_path;
int ret;
if (!allow_modifying_stored_playlists)
RETURN_ERROR(ACK_ERROR_PERMISSION, "Modifying stored playlists is not enabled");
if (!default_pl_dir || strstr(in->argv[1], ":/"))
{
// Argument is a virtual path, make sure it starts with a '/'
virtual_path = prepend_slash(in->argv[1]);
}
else
{
// Argument is a playlist name, prepend default playlist directory
virtual_path = safe_asprintf("%s/%s", default_pl_dir, in->argv[1]);
}
ret = library_queue_save(virtual_path);
free(virtual_path);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Error saving queue to file '%s'", in->argv[1]);
return 0;
}
/*
* count [FILTER] [group {GROUPTYPE}]
*
* TODO Support for groups (the db interface doesn't have method for this). Note
* that mpd only supports one group. Mpd has filter as optional. Without filter,
* but with group, mpd returns:
*
* Album: Welcome
* songs: 1
* playtime: 249
*/
static int
mpd_command_count(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2015-04-06 09:19:48 +02:00
{
struct query_params qp = { .type = Q_COUNT_ITEMS };
2015-05-26 21:24:10 +02:00
struct filecount_info fci;
2015-04-06 09:19:48 +02:00
int ret;
ret = parse_command(&qp, NULL, NULL, in);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Unknown argument(s) for command 'count'");
2015-04-06 09:19:48 +02:00
ret = db_filecount_get(&fci, &qp);
2015-04-06 09:19:48 +02:00
if (ret < 0)
{
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Could not start query");
2015-04-06 09:19:48 +02:00
}
evbuffer_add_printf(out->evbuf,
2015-04-06 09:19:48 +02:00
"songs: %d\n"
"playtime: %" PRIu64 "\n",
fci.count,
(fci.length / 1000));
2015-04-06 09:19:48 +02:00
db_query_end(&qp);
free_query_params(&qp, 1);
return 0;
2015-04-06 09:19:48 +02:00
}
/*
* find "albumartist" "Blue Foundation" "album" "Life Of A Ghost" "date" "2007" "window" "0:1"
* search "(modified-since '2024-06-04T22:49:41Z')"
* find "((Album == 'No Sign Of Bad') AND (AlbumArtist == 'Led Zeppelin'))" window 0:1
* find "(Artist == \"foo\\'bar\\\"\")"
*
* TODO not sure if we support this correctly: "An empty value string means:
* match only if the given tag type does not exist at all; this implies that
* negation with an empty value checks for the existence of the given tag type."
* MaximumMPD (search function):
* count "((Artist == \"Blue Foundation\") AND (album == \"\"))"
*/
static int
mpd_command_find(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
struct query_params qp = { .type = Q_ITEMS, .idx_type = I_NONE, .sort = S_ARTIST };
struct db_media_file_info dbmfi;
int ret;
ret = parse_command(&qp, NULL, NULL, in);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Unknown argument(s) in '%s'", in->args_raw);
ret = db_query_start(&qp);
if (ret < 0)
{
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Could not start query");
}
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
{
ret = mpd_add_db_media_file_info(out->evbuf, &dbmfi);
if (ret < 0)
{
DPRINTF(E_LOG, L_MPD, "Error adding song to the evbuffer, song id: %s\n", dbmfi.id);
}
}
db_query_end(&qp);
free_query_params(&qp, 1);
return 0;
}
/*
* findadd "albumartist" "Blue Foundation" "album" "Life Of A Ghost" "date" "2007" "window" "3:4" position 0
*/
static int
mpd_command_findadd(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
struct query_params qp = { .type = Q_ITEMS, .idx_type = I_NONE, .sort = S_ARTIST };
char *pos = NULL;
int to_pos;
2015-05-01 11:07:03 +02:00
int ret;
ret = parse_command(&qp, &pos, NULL, in);
if (ret < 0)
goto error;
2015-05-01 11:07:03 +02:00
ret = to_pos_from_arg(&to_pos, pos);
if (ret < 0)
goto error;
2015-05-01 11:07:03 +02:00
ret = db_queue_add_by_query(&qp, 0, 0, to_pos, NULL, NULL);
if (ret < 0)
goto error;
2015-05-01 11:07:03 +02:00
free_query_params(&qp, 1);
free(pos);
return 0;
error:
free_query_params(&qp, 1);
free(pos);
RETURN_ERROR(ACK_ERROR_ARG, "Invalid arguments");
}
static void
groups_from_dbcols(struct mpd_tag_map *groups[], size_t sz, const char *dbcols)
{
char *copy = strdup(dbcols);
char *saveptr;
char *col;
int i;
for (col = strtok_r(copy, ",", &saveptr), i = 0; col && i < sz - 1; col = strtok_r(NULL, ",", &saveptr), i++)
{
trim(col);
groups[i] = mpd_parser_tag_from_dbcol(col);
}
groups[i] = NULL;
free(copy);
}
/*
* list {TYPE} {FILTER} [group {GROUPTYPE} [group {GROUPTYPE} [...]]]
*
* Examples
* Rygelian: list Album group Date group AlbumArtistSort group AlbumArtist
* Plattenalbum: list "albumsort" "albumartist" "Bob Dylan" "group" "date" "group" "album"
* list Album "(Artist starts_with \"K\")" group AlbumArtist
*
* TODO Note that the below repeats group tags like so:
* AlbumArtist: Kasabian
* Album: Empire
* AlbumArtist: Kasabian
* Album: West Ryder Pauper Lunatic Asylum
* mpd doesn't repeat them:
* AlbumArtist: Kasabian
* Album: Empire
* Album: West Ryder Pauper Lunatic Asylum
*/
static int
mpd_command_list(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
struct query_params qp = { .type = Q_ITEMS, .sort = S_ARTIST };
struct db_media_file_info dbmfi;
struct mpd_tag_map *groups[16];
char **strval;
int ret;
int i;
ret = parse_command(&qp, NULL, NULL, in);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Unknown argument(s) in: '%s'", in->args_raw);
// qp.group should at least include the tag type field
if (!qp.group)
{
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Bug! Unknown or unsupported tag type/groups: '%s'", in->args_raw);
}
groups_from_dbcols(groups, ARRAY_SIZE(groups), qp.group);
ret = db_query_start(&qp);
if (ret < 0)
{
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Could not start query");
}
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
{
for (i = 0; i < ARRAY_SIZE(groups) && groups[i]; i++)
{
strval = (char **) ((char *)&dbmfi + groups[i]->dbmfi_offset);
if (!(*strval) || (**strval == '\0'))
continue;
evbuffer_add_printf(out->evbuf, "%s: %s\n", groups[i]->name, sanitize(*strval));
}
}
db_query_end(&qp);
free_query_params(&qp, 1);
2015-02-15 09:51:38 +01:00
return 0;
2015-02-15 09:51:38 +01:00
}
static int
mpd_add_directory(struct mpd_command_output *out, int directory_id, int listall, int listinfo)
2014-12-21 20:41:44 +01:00
{
struct directory_info subdir;
struct query_params qp = { .type = Q_PL, .idx_type = I_NONE, .sort = S_PLAYLIST };
struct directory_enum dir_enum;
struct db_playlist_info dbpli;
char modified[32];
uint32_t time_modified;
struct db_media_file_info dbmfi;
2014-12-21 20:41:44 +01:00
int ret;
// Load playlists for dir-id
qp.filter = db_mprintf("(f.directory_id = %d AND (f.type = %d OR f.type = %d))", directory_id, PL_PLAIN, PL_SMART);
ret = db_query_start(&qp);
2014-12-21 20:41:44 +01:00
if (ret < 0)
{
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Could not start query");
2014-12-21 20:41:44 +01:00
}
while (((ret = db_query_fetch_pl(&dbpli, &qp)) == 0) && (dbpli.id))
2014-12-21 20:41:44 +01:00
{
if (safe_atou32(dbpli.db_timestamp, &time_modified) != 0)
2014-12-21 20:41:44 +01:00
{
DPRINTF(E_LOG, L_MPD, "Error converting time modified to uint32_t: %s\n", dbpli.db_timestamp);
2014-12-21 20:41:44 +01:00
}
if (listinfo)
{
mpd_time(modified, sizeof(modified), time_modified);
evbuffer_add_printf(out->evbuf,
"playlist: %s\n"
"Last-Modified: %s\n",
(dbpli.virtual_path + 1),
modified);
}
else
{
evbuffer_add_printf(out->evbuf,
"playlist: %s\n",
(dbpli.virtual_path + 1));
}
}
db_query_end(&qp);
free_query_params(&qp, 1);
2014-12-21 20:41:44 +01:00
// Load sub directories for dir-id
memset(&dir_enum, 0, sizeof(struct directory_enum));
dir_enum.parent_id = directory_id;
ret = db_directory_enum_start(&dir_enum);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Failed to start directory enum");
while ((ret = db_directory_enum_fetch(&dir_enum, &subdir)) == 0 && subdir.id > 0)
{
if (listinfo)
{
evbuffer_add_printf(out->evbuf,
"directory: %s\n"
"Last-Modified: %s\n",
(subdir.virtual_path + 1),
"2015-12-01 00:00");
}
else
{
evbuffer_add_printf(out->evbuf,
"directory: %s\n",
(subdir.virtual_path + 1));
}
if (listall)
{
mpd_add_directory(out, subdir.id, listall, listinfo);
}
2014-12-21 20:41:44 +01:00
}
db_directory_enum_end(&dir_enum);
2014-12-21 20:41:44 +01:00
// Load files for dir-id
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_ITEMS;
qp.sort = S_ARTIST;
qp.idx_type = I_NONE;
qp.filter = db_mprintf("(f.directory_id = %d)", directory_id);
ret = db_query_start(&qp);
if (ret < 0)
{
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Could not start query");
}
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
{
if (listinfo)
{
ret = mpd_add_db_media_file_info(out->evbuf, &dbmfi);
if (ret < 0)
{
DPRINTF(E_LOG, L_MPD, "Error adding song to the evbuffer, song id: %s\n", dbmfi.id);
}
}
else
{
evbuffer_add_printf(out->evbuf,
"file: %s\n",
(dbmfi.virtual_path + 1));
}
}
2014-12-21 20:41:44 +01:00
db_query_end(&qp);
free_query_params(&qp, 1);
2014-12-21 20:41:44 +01:00
return 0;
}
static int
mpd_command_listall(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
int dir_id;
char parent[PATH_MAX];
int ret;
if (in->argc < 2 || strlen(in->argv[1]) == 0
|| (strncmp(in->argv[1], "/", 1) == 0 && strlen(in->argv[1]) == 1))
{
ret = snprintf(parent, sizeof(parent), "/");
}
else if (strncmp(in->argv[1], "/", 1) == 0)
{
ret = snprintf(parent, sizeof(parent), "%s/", in->argv[1]);
}
else
{
ret = snprintf(parent, sizeof(parent), "/%s", in->argv[1]);
}
if ((ret < 0) || (ret >= sizeof(parent)))
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Parent path exceeds PATH_MAX");
// Load dir-id from db for parent-path
dir_id = db_directory_id_byvirtualpath(parent);
if (dir_id == 0)
RETURN_ERROR(ACK_ERROR_NO_EXIST, "Directory info not found for virtual-path '%s'", parent);
return mpd_add_directory(out, dir_id, 1, 0);
}
static int
mpd_command_listallinfo(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
int dir_id;
char parent[PATH_MAX];
int ret;
if (in->argc < 2 || strlen(in->argv[1]) == 0
|| (strncmp(in->argv[1], "/", 1) == 0 && strlen(in->argv[1]) == 1))
{
ret = snprintf(parent, sizeof(parent), "/");
}
else if (strncmp(in->argv[1], "/", 1) == 0)
{
ret = snprintf(parent, sizeof(parent), "%s/", in->argv[1]);
}
else
{
ret = snprintf(parent, sizeof(parent), "/%s", in->argv[1]);
}
if ((ret < 0) || (ret >= sizeof(parent)))
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Parent path exceeds PATH_MAX");
// Load dir-id from db for parent-path
dir_id = db_directory_id_byvirtualpath(parent);
if (dir_id == 0)
RETURN_ERROR(ACK_ERROR_NO_EXIST, "Directory info not found for virtual-path '%s'", parent);
return mpd_add_directory(out, dir_id, 1, 1);
}
/*
* Command handler function for 'lsinfo'
* Lists the contents of the directory given in argv[1].
*/
static int
mpd_command_lsinfo(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
int dir_id;
char parent[PATH_MAX];
int print_playlists;
int ret;
if (in->argc < 2 || strlen(in->argv[1]) == 0
|| (strncmp(in->argv[1], "/", 1) == 0 && strlen(in->argv[1]) == 1))
{
ret = snprintf(parent, sizeof(parent), "/");
}
else if (strncmp(in->argv[1], "/", 1) == 0)
{
ret = snprintf(parent, sizeof(parent), "%s/", in->argv[1]);
}
else
{
ret = snprintf(parent, sizeof(parent), "/%s", in->argv[1]);
}
if ((ret < 0) || (ret >= sizeof(parent)))
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Parent path exceeds PATH_MAX");
print_playlists = 0;
if ((strncmp(parent, "/", 1) == 0 && strlen(parent) == 1))
{
/*
* Special handling necessary if the root directory '/' is given.
* In this case additional to the directory contents the stored playlists will be returned.
* This behavior is deprecated in the mpd protocol but clients like ncmpccp or ympd uses it.
*/
print_playlists = 1;
}
// Load dir-id from db for parent-path
dir_id = db_directory_id_byvirtualpath(parent);
if (dir_id == 0)
RETURN_ERROR(ACK_ERROR_NO_EXIST, "Directory info not found for virtual-path '%s'", parent);
ret = mpd_add_directory(out, dir_id, 0, 1);
// If the root directory was passed as argument add the stored playlists to the response
if (ret == 0 && print_playlists)
{
return mpd_command_listplaylists(out, in, ctx);
}
return ret;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'listfiles'
*
* 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
mpd_command_listfiles(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2015-05-01 11:37:05 +02:00
{
return mpd_command_lsinfo(out, in, ctx);
2015-05-01 11:37:05 +02:00
}
2014-12-21 20:41:44 +01:00
/*
* Command handler function for 'update'
* Initiates an init-rescan (scans for new files)
*/
static int
mpd_command_update(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
if (in->argc > 1 && strlen(in->argv[1]) > 0)
RETURN_ERROR(ACK_ERROR_ARG, "Update for specific uri not supported for command 'update'");
2014-12-21 20:41:44 +01:00
library_rescan(0);
2014-12-21 20:41:44 +01:00
evbuffer_add(out->evbuf, "updating_db: 1\n", 15);
2014-12-21 20:41:44 +01:00
return 0;
2014-12-21 20:41:44 +01:00
}
/*
* sticker get song "/file:/path/to/song.wav" rating
*/
static int
mpd_sticker_get(struct mpd_command_output *out, struct mpd_command_input *in, const char *virtual_path)
2017-12-05 01:34:27 +01:00
{
struct media_file_info *mfi;
2017-12-10 09:24:29 +01:00
uint32_t rating;
2017-12-05 01:34:27 +01:00
if (strcmp(in->argv[4], "rating") != 0)
RETURN_ERROR(ACK_ERROR_NO_EXIST, "No such sticker");
2017-12-05 01:34:27 +01:00
2017-12-10 09:24:29 +01:00
mfi = db_file_fetch_byvirtualpath(virtual_path);
if (!mfi)
RETURN_ERROR(ACK_ERROR_ARG, "Unknown sticker domain");
2017-12-10 09:24:29 +01:00
if (mfi->rating > 0)
2017-12-10 09:24:29 +01:00
{
rating = mfi->rating / MPD_RATING_FACTOR;
evbuffer_add_printf(out->evbuf, "sticker: rating=%d\n", rating);
2017-12-10 09:24:29 +01:00
}
free_mfi(mfi, 0);
return 0;
2017-12-05 01:34:27 +01:00
}
/*
* sticker set song "/file:/path/to/song.wav" rating 10
*/
static int
mpd_sticker_set(struct mpd_command_output *out, struct mpd_command_input *in, const char *virtual_path)
2017-12-05 01:34:27 +01:00
{
2017-12-10 09:24:29 +01:00
uint32_t rating;
int id;
int ret;
2017-12-05 01:34:27 +01:00
if (strcmp(in->argv[4], "rating") != 0)
RETURN_ERROR(ACK_ERROR_NO_EXIST, "No such sticker");
2017-12-05 01:34:27 +01:00
ret = safe_atou32(in->argv[5], &rating);
2017-12-10 09:24:29 +01:00
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Rating '%s' doesn't convert to integer", in->argv[5]);
2017-12-10 09:24:29 +01:00
rating *= MPD_RATING_FACTOR;
if (rating > DB_FILES_RATING_MAX)
RETURN_ERROR(ACK_ERROR_ARG, "Rating '%s' is greater than maximum value allowed", in->argv[5]);
id = db_file_id_byvirtualpath(virtual_path);
if (id <= 0)
RETURN_ERROR(ACK_ERROR_ARG, "Invalid path '%s'", virtual_path);
2017-12-10 09:24:29 +01:00
library_item_attrib_save(id, LIBRARY_ATTRIB_RATING, rating);
return 0;
2017-12-05 01:34:27 +01:00
}
static int
mpd_sticker_delete(struct mpd_command_output *out, struct mpd_command_input *in, const char *virtual_path)
2017-12-05 01:34:27 +01:00
{
int id;
2017-12-05 01:34:27 +01:00
if (strcmp(in->argv[4], "rating") != 0)
RETURN_ERROR(ACK_ERROR_NO_EXIST, "No such sticker");
2017-12-05 01:34:27 +01:00
id = db_file_id_byvirtualpath(virtual_path);
if (id <= 0)
RETURN_ERROR(ACK_ERROR_ARG, "Invalid path '%s'", virtual_path);
library_item_attrib_save(id, LIBRARY_ATTRIB_RATING, 0);
return 0;
2017-12-05 01:34:27 +01:00
}
/*
* sticker list song "/file:/path/to/song.wav"
*/
static int
mpd_sticker_list(struct mpd_command_output *out, struct mpd_command_input *in, const char *virtual_path)
2017-12-05 01:34:27 +01:00
{
struct media_file_info *mfi;
2017-12-10 09:24:29 +01:00
uint32_t rating;
2017-12-05 01:34:27 +01:00
2017-12-10 09:24:29 +01:00
mfi = db_file_fetch_byvirtualpath(virtual_path);
if (!mfi)
RETURN_ERROR(ACK_ERROR_ARG, "Unknown sticker domain");
2017-12-10 09:24:29 +01:00
if (mfi->rating > 0)
2017-12-05 01:34:27 +01:00
{
2017-12-10 09:24:29 +01:00
rating = mfi->rating / MPD_RATING_FACTOR;
evbuffer_add_printf(out->evbuf, "sticker: rating=%d\n", rating);
2017-12-05 01:34:27 +01:00
}
2017-12-10 09:24:29 +01:00
free_mfi(mfi, 0);
2017-12-05 01:34:27 +01:00
/* |:todo:| real sticker implementation */
return 0;
2017-12-05 01:34:27 +01:00
}
/*
* sticker find {TYPE} {URI} {NAME} [sort {SORTTYPE}] [window {START:END}]
* sticker find {TYPE} {URI} {NAME} = {VALUE} [sort {SORTTYPE}] [window {START:END}]
*
* Example:
* sticker find song "/file:/path" rating = 10
*/
static int
mpd_sticker_find(struct mpd_command_output *out, struct mpd_command_input *in, const char *virtual_path)
2017-12-05 01:34:27 +01:00
{
struct query_params qp = { .type = Q_ITEMS, .idx_type = I_NONE, .sort = S_VPATH };
2017-12-10 09:24:29 +01:00
struct db_media_file_info dbmfi;
uint32_t rating = 0;
uint32_t rating_arg = 0;
const char *operator;
2017-12-05 01:34:27 +01:00
int ret = 0;
if (strcmp(in->argv[4], "rating") != 0)
RETURN_ERROR(ACK_ERROR_NO_EXIST, "No such sticker");
2017-12-05 01:34:27 +01:00
if (in->argc > 6)
{
if (strcmp(in->argv[5], "=") != 0 && strcmp(in->argv[5], ">") != 0 && strcmp(in->argv[5], "<") != 0)
RETURN_ERROR(ACK_ERROR_ARG, "Invalid operator '%s' given to 'sticker find'", in->argv[5]);
operator = in->argv[5];
ret = safe_atou32(in->argv[6], &rating_arg);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_ARG, "Rating '%s' doesn't convert to integer", in->argv[6]);
rating_arg *= MPD_RATING_FACTOR;
}
else
{
operator = ">";
rating_arg = 0;
}
qp.filter = db_mprintf("(f.virtual_path LIKE '%s%%' AND f.rating > 0 AND f.rating %s %d)", virtual_path, operator, rating_arg);
2017-12-05 01:34:27 +01:00
2017-12-10 09:24:29 +01:00
ret = db_query_start(&qp);
if (ret < 0)
{
free_query_params(&qp, 1);
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Could not start query");
2017-12-10 09:24:29 +01:00
}
2017-12-05 01:34:27 +01:00
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
2017-12-10 09:24:29 +01:00
{
ret = safe_atou32(dbmfi.rating, &rating);
if (ret < 0)
2017-12-05 01:34:27 +01:00
{
2017-12-10 09:24:29 +01:00
DPRINTF(E_LOG, L_MPD, "Error rating=%s doesn't convert to integer, song id: %s\n",
dbmfi.rating, dbmfi.id);
continue;
2017-12-05 01:34:27 +01:00
}
2017-12-10 09:24:29 +01:00
rating /= MPD_RATING_FACTOR;
ret = evbuffer_add_printf(out->evbuf,
"file: %s\n"
2017-12-10 09:24:29 +01:00
"sticker: rating=%d\n",
(dbmfi.virtual_path + 1),
2017-12-10 09:24:29 +01:00
rating);
if (ret < 0)
DPRINTF(E_LOG, L_MPD, "Error adding song to the evbuffer, song id: %s\n", dbmfi.id);
2017-12-05 01:34:27 +01:00
}
2017-12-10 09:24:29 +01:00
db_query_end(&qp);
free_query_params(&qp, 1);
return 0;
2017-12-05 01:34:27 +01:00
}
2017-12-10 09:24:29 +01:00
struct mpd_sticker_command {
const char *cmd;
int (*handler)(struct mpd_command_output *out, struct mpd_command_input *in, const char *virtual_path);
2017-12-10 09:24:29 +01:00
int need_args;
};
static struct mpd_sticker_command mpd_sticker_handlers[] =
{
/* sticker command | handler function | minimum argument count */
{ "get", mpd_sticker_get, 5 },
{ "set", mpd_sticker_set, 6 },
{ "delete", mpd_sticker_delete, 5 },
{ "list", mpd_sticker_list, 4 },
{ "find", mpd_sticker_find, 6 },
{ NULL, NULL, 0 },
};
2017-12-05 01:34:27 +01:00
/*
* Command handler function for 'sticker'
*
* sticker get "noth here" rating
* ACK [2@0] {sticker} unknown sticker domain
*
* sticker get song "Al Cohn & Shorty Rogers/East Coast - West Coast Scene/04 Shorty Rogers - Cool Sunshine.flac" rating
* ACK [50@0] {sticker} no such sticker
*
* sticker get song "Al Cohn & Shorty Rogers/East Coast - West Coast Scene/03 Al Cohn - Serenade For Kathy.flac" rating
* sticker: rating=8
* OK
*
* From cantata:
* 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
mpd_command_sticker(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2017-12-05 01:34:27 +01:00
{
struct mpd_sticker_command *cmd_param = NULL; // Quell compiler warning about uninitialized use of cmd_param
char *virtual_path;
int ret;
2017-12-05 01:34:27 +01:00
int i;
if (strcmp(in->argv[2], "song") != 0)
RETURN_ERROR(ACK_ERROR_ARG, "Unknown sticker domain");
2017-12-05 01:34:27 +01:00
2017-12-10 13:42:41 +01:00
for (i = 0; i < (sizeof(mpd_sticker_handlers) / sizeof(struct mpd_sticker_command)); ++i)
2017-12-05 01:34:27 +01:00
{
cmd_param = &mpd_sticker_handlers[i];
if (cmd_param->cmd && strcmp(in->argv[1], cmd_param->cmd) == 0)
2017-12-05 01:34:27 +01:00
break;
}
2017-12-05 01:34:27 +01:00
if (!cmd_param->cmd)
RETURN_ERROR(ACK_ERROR_ARG, "Bad request");
2017-12-05 01:34:27 +01:00
if (in->argc < cmd_param->need_args)
RETURN_ERROR(ACK_ERROR_ARG, "Not enough arguments");
2017-12-05 01:34:27 +01:00
virtual_path = prepend_slash(in->argv[3]);
2017-12-05 01:34:27 +01:00
ret = cmd_param->handler(out, in, virtual_path);
2017-12-05 01:34:27 +01:00
free(virtual_path);
return ret;
2017-12-05 01:34:27 +01:00
}
2014-12-21 20:41:44 +01:00
static int
mpd_command_close(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
ctx->must_disconnect = true;
return 0;
}
static int
mpd_command_password(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2017-11-10 09:55:44 +01:00
{
const char *required_password;
const char *supplied_password = (in->argc > 1) ? in->argv[1] : "";
bool password_is_required;
2017-11-10 09:55:44 +01:00
required_password = cfg_getstr(cfg_getsec(cfg, "library"), "password");
password_is_required = required_password && required_password[0] != '\0';
if (password_is_required && strcmp(supplied_password, required_password) != 0)
RETURN_ERROR(ACK_ERROR_PASSWORD, "Wrong password. Authentication failed.");
2017-11-10 09:55:44 +01:00
ctx->authenticated = true;
return 0;
2017-11-10 09:55:44 +01:00
}
static int
mpd_command_binarylimit(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
uint32_t size = in->argv_u32val[1];
if (size < MPD_BINARY_SIZE_MIN)
RETURN_ERROR(ACK_ERROR_ARG, "Value too small");
ctx->binarylimit = size;
return 0;
}
2014-12-21 20:41:44 +01:00
/*
* Callback function for the 'player_speaker_enumerate' function.
* Expect a struct output_get_param as argument and allocates a struct output if
* the shortid of output_get_param matches the given speaker/output spk.
*/
static void
output_get_cb(struct player_speaker_info *spk, void *arg)
{
struct output_get_param *param = arg;
if (!param->output
&& param->shortid == param->curid)
{
2022-01-20 20:14:02 +01:00
CHECK_NULL(L_MPD, param->output = calloc(1, sizeof(struct output)));
param->output->id = spk->id;
param->output->shortid = param->shortid;
param->output->name = strdup(spk->name);
param->output->selected = spk->selected;
param->curid++;
DPRINTF(E_DBG, L_MPD, "Output found: shortid %d, id %" PRIu64 ", name '%s', selected %d\n",
param->output->shortid, param->output->id, param->output->name, param->output->selected);
}
}
/*
* Command handler function for 'disableoutput'
* Expects argument argv[1] to be the id of the speaker to disable.
*/
static int
mpd_command_disableoutput(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
uint32_t num = in->argv_u32val[1];
struct output_get_param param = { .shortid = num };
int ret;
player_speaker_enumerate(output_get_cb, &param);
if (param.output && param.output->selected)
{
ret = player_speaker_disable(param.output->id);
free_output(param.output);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Speakers deactivation failed: %d", num);
}
return 0;
}
/*
* Command handler function for 'enableoutput'
* Expects argument argv[1] to be the id of the speaker to enable.
*/
static int
mpd_command_enableoutput(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
uint32_t num = in->argv_u32val[1];
struct output_get_param param = { .shortid = num };
int ret;
player_speaker_enumerate(output_get_cb, &param);
if (param.output && !param.output->selected)
{
ret = player_speaker_enable(param.output->id);
free_output(param.output);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Speakers deactivation failed: %d", num);
}
return 0;
}
/*
* Command handler function for 'toggleoutput'
* Expects argument argv[1] to be the id of the speaker to enable/disable.
*/
static int
mpd_command_toggleoutput(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
uint32_t num = in->argv_u32val[1];
struct output_get_param param = { .shortid = num };
int ret;
player_speaker_enumerate(output_get_cb, &param);
if (param.output)
{
if (param.output->selected)
ret = player_speaker_disable(param.output->id);
else
ret = player_speaker_enable(param.output->id);
free_output(param.output);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Toggle speaker failed: %d", num);
}
return 0;
}
/*
* Callback function for the 'outputs' command.
* Gets called for each available speaker and prints the speaker information to the evbuffer given in *arg.
*
* Example output:
* outputid: 0
* outputname: Computer
* plugin: alsa
* outputenabled: 1
* https://mpd.readthedocs.io/en/latest/protocol.html#command-outputs
*/
2014-12-21 20:41:44 +01:00
static void
speaker_enum_cb(struct player_speaker_info *spk, void *arg)
2014-12-21 20:41:44 +01:00
{
struct output_outputs_param *param = arg;
struct evbuffer *evbuf = param->buf;
char plugin[sizeof(spk->output_type)];
char *p;
char *q;
/* MPD outputs lowercase plugin (audio_output:type) so convert to
* lowercase, convert spaces to underscores to make it a single word */
for (p = spk->output_type, q = plugin; *p != '\0'; p++, q++)
{
*q = tolower(*p);
if (*q == ' ')
*q = '_';
}
*q = '\0';
2014-12-21 20:41:44 +01:00
evbuffer_add_printf(evbuf,
"outputid: %u\n"
"outputname: %s\n"
"plugin: %s\n"
"outputenabled: %d\n",
param->nextid,
spk->name,
plugin,
spk->selected);
param->nextid++;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'output'
* Returns a lists with the avaiable speakers.
*/
static int
mpd_command_outputs(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
struct output_outputs_param param = { 0 };
/* Reference:
* https://mpd.readthedocs.io/en/latest/protocol.html#audio-output-devices
* the ID returned by mpd may change between excutions, so what we do
* is simply enumerate the speakers, and for get/set commands we count
* ID times to the output referenced. */
param.buf = out->evbuf;
player_speaker_enumerate(speaker_enum_cb, &param);
2014-12-21 20:41:44 +01:00
/* streaming output is not in the speaker list, so add it as pseudo
* element when configured to do so */
if (mpd_plugin_httpd)
{
evbuffer_add_printf(out->evbuf,
"outputid: %u\n"
"outputname: MP3 stream\n"
"plugin: httpd\n"
"outputenabled: 1\n",
param.nextid);
param.nextid++;
}
return 0;
2014-12-21 20:41:44 +01:00
}
static int
outputvolume_set(uint32_t shortid, int volume)
{
struct output_get_param param = { .shortid = shortid };
int ret;
player_speaker_enumerate(output_get_cb, &param);
if (!param.output)
return -1;
ret = player_volume_setabs_speaker(param.output->id, volume);
free_output(param.output);
return ret;
}
static int
mpd_command_outputvolume(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
uint32_t shortid = in->argv_u32val[1];
int32_t volume = in->argv_i32val[2];
int ret;
ret = outputvolume_set(shortid, volume);
if (ret < 0)
RETURN_ERROR(ACK_ERROR_UNKNOWN, "Could not set volume for speaker with id: %d", shortid);
return 0;
}
static void
channel_outputvolume(const char *message)
{
uint32_t shortid;
int volume;
char *tmp;
char *ptr;
int ret;
tmp = strdup(message);
ptr = strrchr(tmp, ':');
if (!ptr)
{
free(tmp);
DPRINTF(E_LOG, L_MPD, "Failed to parse output id and volume from message '%s' (expected format: \"output-id:volume\"\n", message);
return;
}
*ptr = '\0';
ret = safe_atou32(tmp, &shortid);
if (ret < 0)
{
free(tmp);
DPRINTF(E_LOG, L_MPD, "Failed to parse output id from message: '%s'\n", message);
return;
}
ret = safe_atoi32((ptr + 1), &volume);
if (ret < 0)
{
free(tmp);
DPRINTF(E_LOG, L_MPD, "Failed to parse volume from message: '%s'\n", message);
return;
}
ret = outputvolume_set(shortid, volume);
if (ret < 0)
DPRINTF(E_LOG, L_MPD, "Failed to set output volume from message: '%s'\n", message);
free(tmp);
}
static void
channel_pairing(const char *message)
{
remote_pairing_kickoff((char **)&message);
}
static void
channel_verification(const char *message)
{
player_raop_verification_kickoff((char **)&message);
}
struct mpd_channel
{
/* The channel name */
const char *channel;
/*
* The function to execute the sendmessage command for a specific channel
*
* @param message message received on this channel
*/
void (*handler)(const char *message);
};
static struct mpd_channel mpd_channels[] =
{
/* channel | handler function */
{ "outputvolume", channel_outputvolume },
{ "pairing", channel_pairing },
{ "verification", channel_verification },
{ NULL, NULL },
};
/*
* Finds the channel handler for the given channel name
*
* @param name channel name from sendmessage command
* @return the channel or NULL if it is an unknown/unsupported channel
*/
static struct mpd_channel *
mpd_find_channel(const char *name)
{
int i;
for (i = 0; mpd_channels[i].handler; i++)
{
if (0 == strcmp(name, mpd_channels[i].channel))
{
return &mpd_channels[i];
}
}
return NULL;
}
static int
mpd_command_channels(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
int i;
for (i = 0; mpd_channels[i].handler; i++)
{
evbuffer_add_printf(out->evbuf,
"channel: %s\n",
mpd_channels[i].channel);
}
return 0;
}
static int
mpd_command_sendmessage(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
const char *channelname;
const char *message;
struct mpd_channel *channel;
channelname = in->argv[1];
message = in->argv[2];
channel = mpd_find_channel(channelname);
if (!channel)
{
// Just ignore the message, only log an error message
DPRINTF(E_LOG, L_MPD, "Unsupported channel '%s'\n", channelname);
return 0;
}
channel->handler(message);
return 0;
}
2014-12-21 20:41:44 +01:00
/*
* Dummy function to handle commands that are not supported and should
2014-12-21 20:41:44 +01:00
* not raise an error.
*/
static int
mpd_command_ignore(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
//do nothing
DPRINTF(E_DBG, L_MPD, "Ignore command %s\n", in->argv[0]);
return 0;
2014-12-21 20:41:44 +01:00
}
static int
mpd_command_commands(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
int i;
for (i = 0; mpd_handlers[i].handler; i++)
{
evbuffer_add_printf(out->evbuf,
"command: %s\n",
mpd_handlers[i].name);
}
return 0;
}
static void
tagtypes_enum(struct mpd_tag_map *tag, void *arg)
{
struct evbuffer *evbuf = arg;
if (tag->type != MPD_TYPE_SPECIAL)
evbuffer_add_printf(evbuf, "tagtype: %s\n", tag->name);
}
2015-02-15 09:51:38 +01:00
/*
* Command handler function for 'tagtypes'
* Returns a lists with supported tags in the form:
* tagtype: Artist
*/
static int
mpd_command_tagtypes(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
mpd_parser_enum_tagtypes(tagtypes_enum, out->evbuf);
return 0;
}
2017-12-24 02:46:40 +01:00
/*
* Command handler function for 'urlhandlers'
* Returns a lists with supported tags in the form:
* handler: protocol://
*/
static int
mpd_command_urlhandlers(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2017-12-24 02:46:40 +01:00
{
evbuffer_add_printf(out->evbuf,
2017-12-24 02:46:40 +01:00
"handler: http://\n"
// handlers supported by MPD 0.19.12
// "handler: https://\n"
// "handler: mms://\n"
// "handler: mmsh://\n"
// "handler: mmst://\n"
// "handler: mmsu://\n"
// "handler: gopher://\n"
// "handler: rtp://\n"
// "handler: rtsp://\n"
// "handler: rtmp://\n"
// "handler: rtmpt://\n"
// "handler: rtmps://\n"
// "handler: smb://\n"
// "handler: nfs://\n"
// "handler: cdda://\n"
// "handler: alsa://\n"
);
return 0;
2017-12-24 02:46:40 +01:00
}
/*
* Command handler function for 'decoders'
* MPD returns the decoder plugins with their supported suffix and mime types.
*
* The server only uses libav/ffmepg for decoding and does not support decoder plugins,
* therefor the function reports only ffmpeg as available.
*/
static int
mpd_command_decoders(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
int i;
evbuffer_add_printf(out->evbuf, "plugin: ffmpeg\n");
for (i = 0; ffmpeg_suffixes[i]; i++)
{
evbuffer_add_printf(out->evbuf, "suffix: %s\n", ffmpeg_suffixes[i]);
}
for (i = 0; ffmpeg_mime_types[i]; i++)
{
evbuffer_add_printf(out->evbuf, "mime_type: %s\n", ffmpeg_mime_types[i]);
}
return 0;
}
2015-02-15 09:51:38 +01:00
static int
mpd_command_command_list_begin(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
ctx->cmd_list_type = COMMAND_LIST_BEGIN;
return 0;
}
static int
mpd_command_command_list_ok_begin(struct mpd_command_output *out, struct mpd_command_input *in, struct mpd_client_ctx *ctx)
{
ctx->cmd_list_type = COMMAND_LIST_OK_BEGIN;
return 0;
}
static int
mpd_command_command_list_end(struct mpd_command_output *out, struct mpd_command_input *in, 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
RETURN_ERROR(ACK_ERROR_ARG, "Got with command list end without preceeding list start");
return 0;
}
2014-12-21 20:41:44 +01:00
static struct mpd_command mpd_handlers[] =
2014-12-21 20:41:44 +01:00
{
/* commandname | handler function | min arg count | handler requires int args */
2017-12-10 10:58:52 +01:00
// Commands for querying status
{ "clearerror", mpd_command_ignore, -1 },
{ "currentsong", mpd_command_currentsong, -1 },
{ "idle", mpd_command_idle, -1 },
{ "noidle", mpd_command_noidle, -1 },
2017-12-10 10:58:52 +01:00
{ "status", mpd_command_status, -1 },
{ "stats", mpd_command_stats, -1 },
// Playback options
{ "consume", mpd_command_consume, 2, MPD_WANTS_NUM_ARG1_UVAL },
2017-12-10 10:58:52 +01:00
{ "crossfade", mpd_command_ignore, -1 },
{ "mixrampdb", mpd_command_ignore, -1 },
{ "mixrampdelay", mpd_command_ignore, -1 },
{ "random", mpd_command_random, 2, MPD_WANTS_NUM_ARG1_UVAL },
{ "repeat", mpd_command_repeat, 2, MPD_WANTS_NUM_ARG1_UVAL },
{ "setvol", mpd_command_setvol, 2, MPD_WANTS_NUM_ARG1_UVAL },
2017-12-10 10:58:52 +01:00
{ "single", mpd_command_single, 2 },
{ "replay_gain_mode", mpd_command_ignore, -1 },
{ "replay_gain_status", mpd_command_replay_gain_status, -1 },
{ "volume", mpd_command_volume, 2, MPD_WANTS_NUM_ARG1_IVAL },
2017-12-10 10:58:52 +01:00
// Controlling playback
{ "next", mpd_command_next, -1 },
{ "pause", mpd_command_pause, 1, MPD_WANTS_NUM_ARG1_UVAL },
{ "play", mpd_command_play, 1, MPD_WANTS_NUM_ARG1_UVAL },
{ "playid", mpd_command_playid, 1, MPD_WANTS_NUM_ARG1_UVAL },
2017-12-10 10:58:52 +01:00
{ "previous", mpd_command_previous, -1 },
{ "seek", mpd_command_seek, 3, MPD_WANTS_NUM_ARG1_UVAL },
{ "seekid", mpd_command_seekid, 3, MPD_WANTS_NUM_ARG1_UVAL },
2017-12-10 10:58:52 +01:00
{ "seekcur", mpd_command_seekcur, 2 },
{ "stop", mpd_command_stop, -1 },
// The current playlist
{ "add", mpd_command_add, 2 },
{ "addid", mpd_command_addid, 2 },
{ "clear", mpd_command_clear, -1 },
{ "delete", mpd_command_delete, -1 },
{ "deleteid", mpd_command_deleteid, 2, MPD_WANTS_NUM_ARG1_UVAL },
2017-12-10 10:58:52 +01:00
{ "move", mpd_command_move, 3 },
{ "moveid", mpd_command_moveid, 3, MPD_WANTS_NUM_ARG1_UVAL },
2017-12-10 10:58:52 +01:00
{ "playlist", mpd_command_playlistinfo, -1 }, // According to the mpd protocol the use of "playlist" is deprecated
{ "playlistfind", mpd_command_playlistfind, 2 },
{ "playlistid", mpd_command_playlistid, 1, MPD_WANTS_NUM_ARG1_UVAL },
2017-12-10 10:58:52 +01:00
{ "playlistinfo", mpd_command_playlistinfo, -1 },
{ "playlistsearch", mpd_command_playlistsearch, 2 },
{ "plchanges", mpd_command_plchanges, 2, MPD_WANTS_NUM_ARG1_UVAL },
{ "plchangesposid", mpd_command_plchangesposid, 2, MPD_WANTS_NUM_ARG1_UVAL },
2017-12-10 10:58:52 +01:00
// { "prio", mpd_command_prio, -1 },
// { "prioid", mpd_command_prioid, -1 },
// { "rangeid", mpd_command_rangeid, -1 },
// { "shuffle", mpd_command_shuffle, -1 },
// { "swap", mpd_command_swap, -1 },
// { "swapid", mpd_command_swapid, -1 },
// { "addtagid", mpd_command_addtagid, -1 },
// { "cleartagid", mpd_command_cleartagid, -1 },
// Stored playlists
{ "listplaylist", mpd_command_listplaylist, 2 },
{ "listplaylistinfo", mpd_command_listplaylistinfo, 2 },
{ "listplaylists", mpd_command_listplaylists, -1 },
{ "load", mpd_command_load, 2 },
{ "playlistadd", mpd_command_playlistadd, 3 },
// { "playlistclear", mpd_command_playlistclear, -1 },
// { "playlistdelete", mpd_command_playlistdelete, -1 },
// { "playlistmove", mpd_command_playlistmove, -1 },
// { "rename", mpd_command_rename, -1 },
{ "rm", mpd_command_rm, 2 },
{ "save", mpd_command_save, 2 },
// The music database
{ "count", mpd_command_count, -1 },
{ "find", mpd_command_find, 2 },
{ "findadd", mpd_command_findadd, 2 },
{ "search", mpd_command_find, 2 },
{ "searchadd", mpd_command_findadd, 2 },
{ "list", mpd_command_list, 2 },
2017-12-10 10:58:52 +01:00
{ "listall", mpd_command_listall, -1 },
{ "listallinfo", mpd_command_listallinfo, -1 },
{ "listfiles", mpd_command_listfiles, -1 },
2017-12-10 10:58:52 +01:00
{ "lsinfo", mpd_command_lsinfo, -1 },
// { "readcomments", mpd_command_readcomments, -1 },
// { "searchaddpl", mpd_command_searchaddpl, -1 },
{ "update", mpd_command_update, -1 },
// { "rescan", mpd_command_rescan, -1 },
// Mounts and neighbors
// { "mount", mpd_command_mount, -1 },
// { "unmount", mpd_command_unmount, -1 },
// { "listmounts", mpd_command_listmounts, -1 },
// { "listneighbors", mpd_command_listneighbors, -1 },
// Stickers
{ "sticker", mpd_command_sticker, 4 },
// Connection settings
{ "close", mpd_command_close, -1 },
2017-12-10 10:58:52 +01:00
// { "kill", mpd_command_kill, -1 },
{ "password", mpd_command_password, -1 },
{ "ping", mpd_command_ignore, -1 },
{ "binarylimit", mpd_command_binarylimit, 2, MPD_WANTS_NUM_ARG1_UVAL },
2017-12-10 10:58:52 +01:00
// Audio output devices
{ "disableoutput", mpd_command_disableoutput, 2, MPD_WANTS_NUM_ARG1_UVAL },
{ "enableoutput", mpd_command_enableoutput, 2, MPD_WANTS_NUM_ARG1_UVAL },
{ "toggleoutput", mpd_command_toggleoutput, 2, MPD_WANTS_NUM_ARG1_UVAL },
2017-12-10 10:58:52 +01:00
{ "outputs", mpd_command_outputs, -1 },
// Custom command outputvolume (not supported by mpd)
{ "outputvolume", mpd_command_outputvolume, 3, MPD_WANTS_NUM_ARG1_UVAL | MPD_WANTS_NUM_ARG2_IVAL },
2017-12-10 10:58:52 +01:00
// Client to client
{ "subscribe", mpd_command_ignore, -1 },
{ "unsubscribe", mpd_command_ignore, -1 },
{ "channels", mpd_command_channels, -1 },
{ "readmessages", mpd_command_ignore, -1 },
{ "sendmessage", mpd_command_sendmessage, 3 },
2017-12-10 10:58:52 +01:00
// 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 },
2017-12-10 10:58:52 +01:00
// NULL command to terminate loop
{ NULL, NULL, -1 }
2014-12-21 20:41:44 +01:00
};
/*
* Finds the command handler for the given command name
*
* @param name the name of the command
* @return the command or NULL if it is an unknown/unsupported command
*/
static struct mpd_command*
2014-12-21 20:41:44 +01:00
mpd_find_command(const char *name)
{
int i;
for (i = 0; mpd_handlers[i].handler; i++)
{
if (0 == strcmp(name, mpd_handlers[i].name))
2014-12-21 20:41:44 +01:00
{
return &mpd_handlers[i];
}
}
return NULL;
}
static void
mpd_command_input_free(struct mpd_command_input *input)
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
{
if (!input)
return;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
free(input->args_split);
free(input);
}
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
static int
mpd_command_input_create(struct mpd_command_input **out, const char *line)
{
struct mpd_command_input *in;
int ret;
int i;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
CHECK_NULL(L_MPD, in = calloc(1, sizeof(struct mpd_command_input)));
in->args_raw = line;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
ret = mpd_split_args(in->argv, sizeof(in->argv), &in->argc, &in->args_split, line);
if (ret < 0)
goto error;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
// Many of the handlers need numeric input. If you change this, then also
// review command_has_num().
for (i = MPD_WANTS_NUM_ARGV_MIN; i < in->argc && i <= MPD_WANTS_NUM_ARGV_MAX; i++)
{
if (!isdigit(in->argv[i][0]) && in->argv[i][0] != '-')
continue; // Save some cycles if clearly not a number
if (safe_atoi32(in->argv[i], &in->argv_i32val[i]) == 0)
in->has_num |= 1 << i;
if (safe_atou32(in->argv[i], &in->argv_u32val[i]) == 0)
in->has_num |= 1 << (i + MPD_WANTS_NUM_ARGV_MAX);
}
*out = in;
return 0;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
error:
mpd_command_input_free(in);
return -1;
}
// Check if input has the numeric arguments required for the command, taking
// into account that some commands have optional numeric args (purpose of mask)
static bool
command_has_num(int wants_num, int has_num, int argc)
{
int ival_mask = (1 << argc) - 1; // If argc == 2 becomes ...00000011
int uval_mask = (ival_mask << MPD_WANTS_NUM_ARGV_MAX); // If ..MAX == 3 becomes 00011000
int mask = (ival_mask | uval_mask); // becomes 00011011
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
return (wants_num & mask) == (has_num & wants_num & mask);
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
}
static bool
mpd_must_process_command_now(const char *line, struct mpd_client_ctx *client_ctx)
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
{
size_t line_len = strlen(line);
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
// 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)
{
if (evbuffer_get_length(client_ctx->cmd_list_buffer) + line_len + 1 > 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 false;
}
evbuffer_add(client_ctx->cmd_list_buffer, line, line_len + 1);
return false;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
}
if (strcmp(line, "noidle") == 0 && !client_ctx->is_idle)
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
{
return false; // Just ignore, don't proceed to send an OK
}
return true;
}
static enum mpd_ack_error
mpd_process_command_line(struct evbuffer *evbuf, const char *line, int cmd_num, struct mpd_client_ctx *client_ctx)
{
struct mpd_command_input *in = NULL;
struct mpd_command_output out = { .evbuf = evbuf, .ack_error = ACK_ERROR_NONE };
struct mpd_command *command;
const char *cmd_name = NULL;
int ret;
if (!mpd_must_process_command_now(line, client_ctx))
{
return ACK_ERROR_NONE;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
}
ret = mpd_command_input_create(&in, line);
if (ret < 0)
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
{
client_ctx->must_disconnect = true; // This is what MPD does
out.errmsg = safe_asprintf("Could not read command: '%s'", line);
out.ack_error = ACK_ERROR_ARG;
goto error;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
}
cmd_name = in->argv[0];
if (strcmp(cmd_name, "noidle") != 0 && client_ctx->is_idle)
{
client_ctx->must_disconnect = true;
out.errmsg = safe_asprintf("Only 'noidle' is allowed during idle");
out.ack_error = ACK_ERROR_ARG;
goto error;
}
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
if (strcmp(cmd_name, "password") != 0 && !client_ctx->authenticated)
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
{
out.errmsg = safe_asprintf("Not authenticated");
out.ack_error = ACK_ERROR_PERMISSION;
goto error;
}
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
command = mpd_find_command(cmd_name);
if (!command)
{
out.errmsg = safe_asprintf("Unknown command");
out.ack_error = ACK_ERROR_UNKNOWN;
goto error;
}
else if (command->min_argc > in->argc)
{
out.errmsg = safe_asprintf("Missing argument(s), expected %d, given %d", command->min_argc - 1, in->argc - 1);
out.ack_error = ACK_ERROR_ARG;
goto error;
}
else if (!command_has_num(command->wants_num, in->has_num, in->argc))
{
out.errmsg = safe_asprintf("Missing or invalid numeric values in command: '%s'", line);
out.ack_error = ACK_ERROR_ARG;
goto error;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
}
ret = command->handler(&out, in, client_ctx);
if (ret < 0)
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
{
goto error;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
}
if (client_ctx->cmd_list_type == COMMAND_LIST_NONE && !client_ctx->is_idle)
evbuffer_add_printf(out.evbuf, "OK\n");
mpd_command_input_free(in);
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
return out.ack_error;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
error:
DPRINTF(E_LOG, L_MPD, "Error processing command '%s': %s\n", line, out.errmsg);
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
if (cmd_name)
evbuffer_add_printf(out.evbuf, "ACK [%d@%d] {%s} %s\n", out.ack_error, cmd_num, cmd_name, out.errmsg);
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
mpd_command_input_free(in);
free(out.errmsg);
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
return out.ack_error;
}
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
// 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 *evbuf, struct mpd_client_ctx *client_ctx)
{
char *line;
enum mpd_ack_error ack_error = ACK_ERROR_NONE;
int cmd_num = 0;
while ((line = evbuffer_readln(client_ctx->cmd_list_buffer, NULL, EVBUFFER_EOL_NUL)))
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
{
ack_error = mpd_process_command_line(evbuf, 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(evbuf, "list_OK\n");
}
if (ack_error == ACK_ERROR_NONE)
evbuffer_add_printf(evbuf, "OK\n");
// Back to single-command mode
evbuffer_drain(client_ctx->cmd_list_buffer, -1);
client_ctx->cmd_list_type = COMMAND_LIST_NONE;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
}
/* --------------------------- Server implementation ------------------------ */
2014-12-21 20:41:44 +01:00
/*
* The read callback function is invoked if a complete command sequence was
* received from the client (see mpd_input_filter function).
2014-12-21 20:41:44 +01:00
*
* @param bev the buffer event
2017-11-10 09:55:44 +01:00
* @param ctx used for authentication
2014-12-21 20:41:44 +01:00
*/
static void
mpd_read_cb(struct bufferevent *bev, void *arg)
2014-12-21 20:41:44 +01:00
{
struct mpd_client_ctx *client_ctx = arg;
2014-12-21 20:41:44 +01:00
struct evbuffer *input;
struct evbuffer *output;
char *line;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
// Contains the command sequence received from the client
2014-12-21 20:41:44 +01:00
input = bufferevent_get_input(bev);
// Used to send the server response to the client
2014-12-21 20:41:44 +01:00
output = bufferevent_get_output(bev);
while ((line = evbuffer_readln(input, NULL, EVBUFFER_EOL_ANY)))
{
DPRINTF(E_DBG, L_MPD, "MPD message: '%s'\n", line);
2014-12-21 20:41:44 +01:00
mpd_process_command_line(output, line, 0, client_ctx);
2014-12-21 20:41:44 +01:00
free(line);
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);
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 wont
// be deleted until the callbacks are done.
bufferevent_setcb(bev, NULL, NULL, NULL, NULL);
bufferevent_free(bev);
2014-12-21 20:41:44 +01:00
}
/*
* Callback when an event occurs on the bufferevent
*/
static void
mpd_event_cb(struct bufferevent *bev, short events, void *ctx)
{
if (events & BEV_EVENT_ERROR)
{
DPRINTF(E_LOG, L_MPD, "Error from bufferevent: %s\n",
evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
}
2014-12-21 20:41:44 +01:00
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR))
{
2014-12-21 20:41:44 +01:00
bufferevent_free(bev);
}
2014-12-21 20:41:44 +01:00
}
/*
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
* The input filter buffer callback.
*
* Pass complete lines.
2014-12-21 20:41:44 +01:00
*
* @param src evbuffer to read data from (contains the data received from the client)
* @param dst evbuffer to write data to (this is the evbuffer for the read callback)
* @param lim the upper bound of bytes to add to destination
* @param state write mode
* @param ctx (not used)
* @return BEV_OK if a complete command sequence was received otherwise BEV_NEED_MORE
*/
static enum bufferevent_filter_result
mpd_input_filter(struct evbuffer *src, struct evbuffer *dst, ev_ssize_t lim, enum bufferevent_flush_mode state, void *ctx)
{
char *line;
int ret;
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
// Filter functions must return BEV_OK
// if any data was successfully written to the destination buffer
int output_count = 0;
2014-12-21 20:41:44 +01:00
while ((line = evbuffer_readln(src, NULL, EVBUFFER_EOL_ANY)))
{
ret = evbuffer_add_printf(dst, "%s\n", line);
if (ret < 0)
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
{
DPRINTF(E_LOG, L_MPD, "Error adding line to buffer: '%s'\n", line);
free(line);
return BEV_ERROR;
}
2014-12-21 20:41:44 +01:00
free(line);
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
output_count += ret;
2014-12-21 20:41:44 +01:00
}
[mpd,db] MPD protocol fixes to handling of idle/noidle command and command list. Command handling: 1. Changed mpd_read_cb() to delegate to mpd_process_line() for each received command line. 2. mpd_process_line() handles idle state and command list state and delegates to mpd_process_command() to handle each command line. If the command was successful it sends OK to the client according the the command list state. Error responses are sent by mpd_process_command(). 3. mpd_process_command() parses the args and delegates to the individual command handler. mpd_input_filter: 1. Removed handling of command lists. They are handled by mpd_process_line(). 2. Return BEV_OK if there's at least one complete line of input even if there's more data in the input buffer. Idle/noidle: 1. Changed mpd_command_idle() to never write OK to the output buffer. Instead it is the responsibility of the caller to decide on the response. 2. Removed mpd_command_noidle() instead it is handled in mpd_process_line(). If the client is not in idle state noidle is ignored (no response sent) If the client is in idle state then it changes idle state to false and sends OK as the response to the idle command. Command lists: 1. Added command list state to the client context so commands in the list are buffered and only executed after receiving command_list_end. Connection state: 1. Added is_closing flag in the client context to ignore messages received after freeing the events buffer in intent to close the client connection. Command arguments parsing: 1. Updated COMMAND_ARGV_MAX to 70 to match current MPD. 2. Changed mpd_pars_range_arg to handle open-ended range. Command pause: 1. pause is ignored in stopped state instead returning error. Command move: 1. Changed mpd_command_move() to support moving a range. 2. Added db_queue_move_bypos_range() to support moving a range. Command password: 1. Password authentication flag set in handler mpd_command_password() instead of in command processor. Config: 1. Added config value: "max_command_list_size". The maximum allowed size of buffered commands in command list.
2023-05-17 19:47:25 +03:00
if (output_count == 0)
2014-12-21 20:41:44 +01:00
{
DPRINTF(E_DBG, L_MPD, "Message incomplete, waiting for more data\n");
return BEV_NEED_MORE;
}
return BEV_OK;
}
/*
* The connection listener callback function is invoked when a new connection was received.
*
* @param listener the connection listener that received the connection
* @param sock the new socket
* @param address the address from which the connection was received
* @param socklen the length of that address
* @param ctx (not used)
*/
static void
mpd_accept_conn_cb(struct evconnlistener *listener,
evutil_socket_t sock, struct sockaddr *address, int socklen,
void *ctx)
{
/*
* For each new connection setup a new buffer event and wrap it around a filter event.
* The filter event ensures, that the read callback on the buffer event is only invoked if a complete
* command sequence from the client was received.
*/
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 = client_ctx_add();
2017-11-10 09:55:44 +01:00
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);
2017-11-10 09:55:44 +01:00
client_ctx->authenticated = !cfg_getstr(cfg_getsec(cfg, "library"), "password");
if (!client_ctx->authenticated)
2017-11-14 01:50:06 +01:00
{
client_ctx->authenticated = net_peer_address_is_trusted((union net_sockaddr *)address);
2017-11-14 01:50:06 +01:00
}
2017-12-05 01:34:27 +01:00
client_ctx->binarylimit = MPD_BINARY_SIZE;
2024-09-12 16:58:52 +02:00
// No zero terminator (newline in the string is terminator)
evbuffer_add(client_ctx->evbuffer, MPD_PROTOCOL_VERSION_OK, strlen(MPD_PROTOCOL_VERSION_OK));
DPRINTF(E_INFO, L_MPD, "New mpd client connection accepted\n");
2014-12-21 20:41:44 +01:00
}
/*
* Error callback that gets called whenever an accept() call fails on the listener
* @param listener the connection listener that received the connection
* @param ctx (not used)
*/
static void
mpd_accept_error_cb(struct evconnlistener *listener, void *ctx)
{
int err;
err = EVUTIL_SOCKET_ERROR();
DPRINTF(E_LOG, L_MPD, "Error occured %d (%s) on the listener.\n", err, evutil_socket_error_to_string(err));
}
static enum command_state
mpd_notify_idle(void *arg, int *retval)
2015-02-21 06:04:17 +01:00
{
short event_mask;
struct mpd_client_ctx *client;
2015-02-21 06:04:17 +01:00
int i;
event_mask = *(short *)arg;
DPRINTF(E_DBG, L_MPD, "Notify clients waiting for idle results: %d\n", event_mask);
2015-02-21 06:04:17 +01:00
i = 0;
client = mpd_clients;
2015-02-21 06:04:17 +01:00
while (client)
{
DPRINTF(E_DBG, L_MPD, "Notify client #%d\n", i);
notify_idle_client(client, event_mask, true);
client = client->next;
2015-02-21 06:04:17 +01:00
i++;
}
*retval = 0;
return COMMAND_END;
2015-02-21 06:04:17 +01:00
}
static void
mpd_listener_cb(short event_mask)
2015-02-21 06:04:17 +01:00
{
short *ptr;
2015-02-21 06:04:17 +01:00
2017-12-09 17:12:13 +01:00
ptr = (short *)malloc(sizeof(short));
*ptr = event_mask;
DPRINTF(E_DBG, L_MPD, "Asynchronous listener callback called with event type %d.\n", event_mask);
commands_exec_async(cmdbase, mpd_notify_idle, ptr);
2015-02-21 06:04:17 +01:00
}
/*
* Callback function that handles http requests for artwork files
*
* Some MPD clients allow retrieval of local artwork by making http request for artwork
* files.
*
* A request for the artwork of an item with virtual path "file:/path/to/example.mp3" looks
* like:
* GET http://<host>:<port>/path/to/cover.jpg
*
* Artwork is found by taking the uri and removing everything after the last '/'. The first
* item in the library with a virtual path that matches *path/to* is used to read the artwork
* file through the default artwork logic.
*/
static void
artwork_cb(struct evhttp_request *req, void *arg)
{
struct evbuffer *evbuffer;
struct evhttp_uri *decoded;
const char *uri;
const char *path;
char *decoded_path;
char *last_slash;
int itemid;
int format;
if (evhttp_request_get_command(req) != EVHTTP_REQ_GET)
{
DPRINTF(E_LOG, L_MPD, "Unsupported request type for artwork\n");
evhttp_send_error(req, HTTP_BADMETHOD, "Method not allowed");
return;
}
uri = evhttp_request_get_uri(req);
DPRINTF(E_DBG, L_MPD, "Got artwork request with uri '%s'\n", uri);
decoded = evhttp_uri_parse(uri);
if (!decoded)
{
DPRINTF(E_LOG, L_MPD, "Bad artwork request with uri '%s'\n", uri);
evhttp_send_error(req, HTTP_BADREQUEST, 0);
return;
}
path = evhttp_uri_get_path(decoded);
if (!path)
{
DPRINTF(E_LOG, L_MPD, "Invalid path from artwork request with uri '%s'\n", uri);
evhttp_send_error(req, HTTP_BADREQUEST, 0);
evhttp_uri_free(decoded);
return;
}
decoded_path = evhttp_uridecode(path, 0, NULL);
if (!decoded_path)
{
DPRINTF(E_LOG, L_MPD, "Error decoding path from artwork request with uri '%s'\n", uri);
evhttp_send_error(req, HTTP_BADREQUEST, 0);
evhttp_uri_free(decoded);
return;
}
last_slash = strrchr(decoded_path, '/');
if (last_slash)
*last_slash = '\0';
DPRINTF(E_DBG, L_MPD, "Artwork request for path: %s\n", decoded_path);
itemid = db_file_id_byvirtualpath_match(decoded_path);
if (!itemid)
{
DPRINTF(E_WARN, L_MPD, "No item found for path '%s' from request uri '%s'\n", decoded_path, uri);
evhttp_send_error(req, HTTP_NOTFOUND, "Document was not found");
evhttp_uri_free(decoded);
free(decoded_path);
return;
}
evbuffer = evbuffer_new();
if (!evbuffer)
{
DPRINTF(E_LOG, L_MPD, "Could not allocate an evbuffer for artwork request\n");
evhttp_send_error(req, HTTP_INTERNAL, "Document was not found");
evhttp_uri_free(decoded);
free(decoded_path);
return;
}
format = artwork_get_item(evbuffer, itemid, ART_DEFAULT_WIDTH, ART_DEFAULT_HEIGHT, 0);
if (format < 0)
{
evhttp_send_error(req, HTTP_NOTFOUND, "Document was not found");
}
else
{
switch (format)
{
case ART_FMT_PNG:
evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "image/png");
break;
default:
evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "image/jpeg");
break;
}
evhttp_send_reply(req, HTTP_OK, "OK", evbuffer);
}
evbuffer_free(evbuffer);
evhttp_uri_free(decoded);
free(decoded_path);
}
2014-12-21 20:41:44 +01:00
/* -------------------------------- 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 ----------------------------- */
2014-12-21 20:41:44 +01:00
/* Thread: main */
2021-05-28 16:50:52 +02:00
static int
mpd_httpd_init(void)
2014-12-21 20:41:44 +01:00
{
unsigned short http_port;
2014-12-21 20:41:44 +01:00
int ret;
http_port = cfg_getint(cfg_getsec(cfg, "mpd"), "http_port");
if (http_port == 0)
return 0;
2015-03-12 21:35:56 +01:00
evhttpd = evhttp_new(evbase_mpd);
if (!evhttpd)
return -1;
ret = net_evhttp_bind(evhttpd, http_port, "mpd artwork");
if (ret < 0)
2014-12-21 20:41:44 +01:00
{
evhttp_free(evhttpd);
evhttpd = NULL;
return -1;
2014-12-21 20:41:44 +01:00
}
evhttp_set_gencb(evhttpd, artwork_cb, NULL);
2015-03-12 21:35:56 +01:00
return 0;
}
2014-12-21 20:41:44 +01:00
/* Thread: main */
2021-05-28 16:50:52 +02:00
static void
mpd_httpd_deinit(void)
{
if (evhttpd)
evhttp_free(evhttpd);
2015-02-21 06:04:17 +01:00
evhttpd = NULL;
}
/* Thread: main */
2021-05-28 16:50:52 +02:00
int
mpd_init(void)
{
unsigned short port;
const char *pl_dir;
int ret;
port = cfg_getint(cfg_getsec(cfg, "mpd"), "port");
if (port <= 0)
{
DPRINTF(E_INFO, L_MPD, "MPD not enabled\n");
return 0;
2015-03-12 21:35:56 +01:00
}
CHECK_NULL(L_MPD, evbase_mpd = event_base_new());
CHECK_NULL(L_MPD, cmdbase = commands_base_new(evbase_mpd, NULL));
2014-12-21 20:41:44 +01:00
mpd_sockfd = net_bind(&port, SOCK_STREAM, "mpd");
if (mpd_sockfd < 0)
{
DPRINTF(E_LOG, L_MPD, "Could not bind mpd server to port %hu\n", port);
goto bind_fail;
}
2014-12-21 20:41:44 +01:00
mpd_listener = evconnlistener_new(evbase_mpd, mpd_accept_conn_cb, NULL, 0, -1, mpd_sockfd);
if (!mpd_listener)
{
DPRINTF(E_LOG, L_MPD, "Could not create connection listener for mpd clients on port %d\n", port);
goto connew_fail;
2014-12-21 20:41:44 +01:00
}
evconnlistener_set_error_cb(mpd_listener, mpd_accept_error_cb);
2014-12-21 20:41:44 +01:00
ret = mpd_httpd_init();
if (ret < 0)
{
DPRINTF(E_LOG, L_MPD, "Could not initialize HTTP artwork server\n");
goto httpd_fail;
}
allow_modifying_stored_playlists = cfg_getbool(cfg_getsec(cfg, "library"), "allow_modifying_stored_playlists");
pl_dir = cfg_getstr(cfg_getsec(cfg, "library"), "default_playlist_directory");
if (pl_dir)
default_pl_dir = safe_asprintf("/file:%s", pl_dir);
mpd_plugin_httpd = cfg_getbool(cfg_getsec(cfg, "mpd"), "enable_httpd_plugin");
/* Handle deprecated config options */
if (0 < cfg_opt_size(cfg_getopt(cfg_getsec(cfg, "mpd"), "allow_modifying_stored_playlists")))
{
DPRINTF(E_LOG, L_MPD, "Found deprecated option 'allow_modifying_stored_playlists' in section 'mpd', please update configuration file (move option to section 'library').\n");
allow_modifying_stored_playlists = cfg_getbool(cfg_getsec(cfg, "mpd"), "allow_modifying_stored_playlists");
}
if (0 < cfg_opt_size(cfg_getopt(cfg_getsec(cfg, "mpd"), "default_playlist_directory")))
{
DPRINTF(E_LOG, L_MPD, "Found deprecated option 'default_playlist_directory' in section 'mpd', please update configuration file (move option to section 'library').\n");
free(default_pl_dir);
pl_dir = cfg_getstr(cfg_getsec(cfg, "mpd"), "default_playlist_directory");
if (pl_dir)
default_pl_dir = safe_asprintf("/file:%s", pl_dir);
}
2015-05-09 06:40:30 +02:00
DPRINTF(E_INFO, L_MPD, "mpd thread init\n");
2014-12-21 20:41:44 +01:00
ret = pthread_create(&tid_mpd, NULL, mpd, NULL);
if (ret < 0)
{
2021-05-28 16:50:52 +02:00
DPRINTF(E_LOG, L_MPD, "Could not spawn MPD thread: %s\n", strerror(errno));
2014-12-21 20:41:44 +01:00
goto thread_fail;
}
thread_setname(tid_mpd, "mpd");
mpd_clients = NULL;
2017-12-10 09:24:29 +01:00
listener_add(mpd_listener_cb, MPD_ALL_IDLE_LISTENER_EVENTS);
2015-02-21 06:04:17 +01:00
2014-12-21 20:41:44 +01:00
return 0;
thread_fail:
mpd_httpd_deinit();
httpd_fail:
evconnlistener_free(mpd_listener);
2014-12-21 20:41:44 +01:00
connew_fail:
close(mpd_sockfd);
bind_fail:
commands_base_free(cmdbase);
2014-12-21 20:41:44 +01:00
event_base_free(evbase_mpd);
evbase_mpd = NULL;
return -1;
}
/* Thread: main */
2021-05-28 16:50:52 +02:00
void
mpd_deinit(void)
2014-12-21 20:41:44 +01:00
{
unsigned short port;
int ret;
port = cfg_getint(cfg_getsec(cfg, "mpd"), "port");
if (port <= 0)
{
DPRINTF(E_INFO, L_MPD, "MPD not enabled\n");
return;
}
commands_base_destroy(cmdbase);
2014-12-21 20:41:44 +01:00
ret = pthread_join(tid_mpd, NULL);
if (ret != 0)
{
2021-05-28 16:50:52 +02:00
DPRINTF(E_FATAL, L_MPD, "Could not join MPD thread: %s\n", strerror(errno));
2014-12-21 20:41:44 +01:00
return;
}
2015-05-03 08:18:26 +02:00
listener_remove(mpd_listener_cb);
while (mpd_clients)
2015-02-21 06:04:17 +01:00
{
client_ctx_remove(mpd_clients);
2015-02-21 06:04:17 +01:00
}
mpd_httpd_deinit();
evconnlistener_free(mpd_listener);
close(mpd_sockfd);
2014-12-21 20:41:44 +01:00
// Free event base (should free events too)
event_base_free(evbase_mpd);
free(default_pl_dir);
2014-12-21 20:41:44 +01:00
}