5288 lines
140 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
*/
#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 <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>
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"
2014-12-21 20:41:44 +01:00
enum mpd_type {
MPD_TYPE_INT,
MPD_TYPE_STRING,
MPD_TYPE_SPECIAL,
};
#define MPD_ALL_IDLE_LISTENER_EVENTS (LISTENER_PLAYER | LISTENER_QUEUE | LISTENER_VOLUME | LISTENER_SPEAKER | LISTENER_OPTIONS | LISTENER_DATABASE | LISTENER_UPDATE | LISTENER_STORED_PLAYLIST | LISTENER_RATING)
2017-12-10 09:24:29 +01:00
#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 */
2017-12-10 09:24:29 +01:00
2014-12-21 20:41:44 +01:00
static pthread_t tid_mpd;
2015-12-19 09:09:50 +01:00
static struct event_base *evbase_mpd;
2014-12-21 20:41:44 +01:00
static struct commands_base *cmdbase;
2015-02-21 06:04:17 +01:00
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;
2015-02-21 06:04:17 +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
/**
* 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 COMMAND_ARGV_MAX 70
/**
* config:
* max_command_list_size KBYTES
* The maximum size a command list. Default is 2048 (2 MiB).
*
* https://github.com/MusicPlayerDaemon/MPD/blob/master/src/client/Config.cxx
*/
#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
static struct {
/**
* Max size in bytes allowed in command list.
* "max_command_list_size"
*/
uint32_t MaxCommandListSize;
}
Config = {
.MaxCommandListSize = CLIENT_MAX_COMMAND_LIST_DEFAULT
};
2014-12-21 20:41:44 +01:00
/* MPD error codes (taken from ack.h) */
enum ack
{
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,
};
[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
/**
* Flag for command list state
*/
2014-12-21 20:41:44 +01:00
enum command_list_type
{
COMMAND_LIST = 1,
COMMAND_LIST_OK = 2,
COMMAND_LIST_END = 3,
COMMAND_LIST_NONE = 4
2014-12-21 20:41:44 +01:00
};
/**
* 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
};
struct mpd_tagtype
{
char *tag;
char *field;
char *sort_field;
char *group_field;
enum mpd_type type;
ssize_t mfi_offset;
/*
* This allows omitting the "group" fields in the created group by clause to improve
* performance in the "list" command. For example listing albums and artists already
* groups by their persistent id, an additional group clause by artist/album will
* decrease performance of the select query and will in general not change the result
* (e. g. album persistent id is generated by artist and album and listing albums
* grouped by artist is therefor not necessary).
*/
bool group_in_listcommand;
};
static struct mpd_tagtype tagtypes[] =
{
/* tag | db field | db sort field | db group field | type | media_file offset | group_in_listcommand */
// We treat the artist tag as album artist, this allows grouping over the artist-persistent-id index and increases performance
// { "Artist", "f.artist", "f.artist", "f.artist", MPD_TYPE_STRING, dbmfi_offsetof(artist), },
{ "Artist", "f.album_artist", "f.album_artist_sort, f.album_artist", "f.songartistid", MPD_TYPE_STRING, dbmfi_offsetof(album_artist), false, },
{ "ArtistSort", "f.album_artist_sort", "f.album_artist_sort, f.album_artist", "f.songartistid", MPD_TYPE_STRING, dbmfi_offsetof(album_artist_sort), false, },
{ "AlbumArtist", "f.album_artist", "f.album_artist_sort, f.album_artist", "f.songartistid", MPD_TYPE_STRING, dbmfi_offsetof(album_artist), false, },
{ "AlbumArtistSort", "f.album_artist_sort", "f.album_artist_sort, f.album_artist", "f.songartistid", MPD_TYPE_STRING, dbmfi_offsetof(album_artist_sort), false, },
{ "Album", "f.album", "f.album_sort, f.album", "f.songalbumid", MPD_TYPE_STRING, dbmfi_offsetof(album), false, },
{ "Title", "f.title", "f.title", "f.title", MPD_TYPE_STRING, dbmfi_offsetof(title), true, },
{ "Track", "f.track", "f.track", "f.track", MPD_TYPE_INT, dbmfi_offsetof(track), true, },
{ "Genre", "f.genre", "f.genre", "f.genre", MPD_TYPE_STRING, dbmfi_offsetof(genre), true, },
{ "Disc", "f.disc", "f.disc", "f.disc", MPD_TYPE_INT, dbmfi_offsetof(disc), true, },
{ "Date", "f.year", "f.year", "f.year", MPD_TYPE_INT, dbmfi_offsetof(year), true, },
{ "file", NULL, NULL, NULL, MPD_TYPE_SPECIAL, -1, true, },
{ "base", NULL, NULL, NULL, MPD_TYPE_SPECIAL, -1, true, },
{ "any", NULL, NULL, NULL, MPD_TYPE_SPECIAL, -1, true, },
{ "modified-since", NULL, NULL, NULL, MPD_TYPE_SPECIAL, -1, true, },
};
static struct mpd_tagtype *
find_tagtype(const char *tag)
{
int i;
if (!tag)
return 0;
for (i = 0; i < ARRAY_SIZE(tagtypes); i++)
{
if (strcasecmp(tag, tagtypes[i].tag) == 0)
return &tagtypes[i];
}
return NULL;
}
/*
* MPD client connection data
*/
struct mpd_client_ctx
{
// True if the connection is already authenticated or does not need authentication
bool authenticated;
// The events the client needs to be notified of
short events;
// True if the client is waiting for idle events
bool is_idle;
// The events the client is waiting for (set by the idle command)
short idle_events;
// The current binary limit size
unsigned int binarylimit;
// The output buffer for the client (used to send data to the client)
struct evbuffer *evbuffer;
[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 list type flag:
* is set to:
* COMMAND_LIST_NONE by default and when receiving command_list_end
* COMMAND_LIST when receiving command_list_begin
* COMMAND_OK_LIST when receiving command_list_ok_begin
*/
enum command_list_type cmd_list_type;
/**
* current command list:
* <p>
* When cmd_list_type is either COMMAND_LIST or COMMAND_OK_LIST
* received commands are added to this buffer.
* <p>
* When command_list_end is received, the commands save
* in this buffer are processed, and then the buffer is freed.
*
* @see mpd_command_list_add
*/
struct evbuffer *cmd_list_buffer;
/**
* closing flag
*
* set to true in mpd_read_cb() when we want to close
* the client connection by freeing the evbuffer.
*/
bool is_closing;
struct mpd_client_ctx *next;
2017-11-10 09:55:44 +01:00
};
// List of all connected mpd clients
struct mpd_client_ctx *mpd_clients;
static void
free_mpd_client_ctx(void *ctx)
{
struct mpd_client_ctx *client_ctx = ctx;
struct mpd_client_ctx *client;
struct mpd_client_ctx *prev;
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
if (client_ctx->cmd_list_buffer != NULL)
{
evbuffer_free(client_ctx->cmd_list_buffer);
}
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;
}
free(client_ctx);
}
struct output
{
unsigned short shortid;
uint64_t id;
char *name;
unsigned selected;
};
struct output_get_param
{
unsigned short curid;
unsigned short shortid;
struct output *output;
};
struct output_outputs_param
{
unsigned short nextid;
struct evbuffer *buf;
};
static void
free_output(struct output *output)
{
if (output)
{
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;
}
2014-12-21 20:41:44 +01:00
/* Thread: mpd */
static void *
mpd(void *arg)
{
int ret;
ret = db_perthread_init();
if (ret < 0)
{
DPRINTF(E_LOG, L_MPD, "Error: DB init failed\n");
pthread_exit(NULL);
}
event_base_dispatch(evbase_mpd);
db_perthread_deinit();
pthread_exit(NULL);
}
static void
mpd_time(char *buffer, size_t bufferlen, time_t t)
{
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
/*
* Parses a rage argument of the form START:END (the END item is not included in the range)
* into its start and end position.
*
* @param range the range argument
* @param start_pos set by this method to the start position
[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
* @param end_pos set by this method to the end position
2014-12-21 20:41:44 +01:00
* @return 0 on success, -1 on failure
[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
*
* @see https://github.com/MusicPlayerDaemon/MPD/blob/master/src/protocol/RangeArg.hxx
*
* For "window START:END" The end index can be omitted, which means the range is open-ended.
2014-12-21 20:41:44 +01:00
*/
static int
[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_pars_range_arg(const char *range, int *start_pos, int *end_pos)
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
static char separator = ':';
char* sep_pos = strchr(range, separator);
if (sep_pos)
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
*sep_pos++ = '\0';
// range start
if (safe_atoi32(range, start_pos) != 0)
{
DPRINTF(E_LOG, L_MPD, "Error parsing range argument '%s'\n", range);
return -1;
}
// range end
if (*sep_pos == 0)
{
DPRINTF(E_LOG, L_MPD, "Open ended range not supported: '%s'\n", range);
return -1;
}
else if (safe_atoi32(sep_pos, end_pos) != 0)
{
DPRINTF(E_LOG, L_MPD, "Error parsing range argument '%s'\n", range);
return -1;
}
2014-12-21 20:41:44 +01:00
}
else
{
ret = safe_atoi32(range, start_pos);
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 parsing 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;
}
/*
* Returns the next unquoted string argument from the input string
*/
static char*
mpd_pars_unquoted(char **input)
{
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_pars_quoted(char **input)
{
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;
}
/*
[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),
queue_item->artist,
queue_item->album_artist,
queue_item->artist_sort,
queue_item->album_artist_sort,
queue_item->album,
queue_item->title,
queue_item->track,
queue_item->year,
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),
2014-12-21 20:41:44 +01:00
dbmfi->artist,
dbmfi->album_artist,
dbmfi->artist_sort,
dbmfi->album_artist_sort,
2014-12-21 20:41:44 +01:00
dbmfi->album,
dbmfi->title,
dbmfi->track,
dbmfi->year,
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 void
append_string(char **a, const char *b, const char *separator)
{
char *temp;
if (*a)
temp = db_mprintf("%s%s%s", *a, (separator ? separator : ""), b);
else
temp = db_mprintf("%s", b);
free(*a);
*a = temp;
}
/*
* Sets the filter (where clause) and the window (limit clause) in the given query_params
* based on the given arguments
*
* @param argc Number of arguments in argv
* @param argv Pointer to the first filter parameter
[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
* @param exact_match If true, creates filter for exact matches (e.g. find command) otherwise matches substrings (e.g. search command)
* @param qp Query parameters
*/
static int
parse_filter_window_params(int argc, char **argv, bool exact_match, struct query_params *qp)
{
struct mpd_tagtype *tagtype;
char *c1;
int start_pos;
int end_pos;
int i;
uint32_t num;
int ret;
c1 = NULL;
for (i = 0; i < argc; i += 2)
{
// End of filter key/value pairs reached, if keywords "window" or "group" found
if (0 == strcasecmp(argv[i], "window") || 0 == strcasecmp(argv[i], "group"))
break;
// Process filter key/value pair
if ((i + 1) < argc)
{
tagtype = find_tagtype(argv[i]);
if (!tagtype)
{
DPRINTF(E_WARN, L_MPD, "Parameter '%s' is not supported and will be ignored\n", argv[i]);
continue;
}
if (tagtype->type == MPD_TYPE_STRING)
{
if (exact_match)
c1 = db_mprintf("(%s = '%q')", tagtype->field, argv[i + 1]);
else
c1 = db_mprintf("(%s LIKE '%%%q%%')", tagtype->field, argv[i + 1]);
}
else if (tagtype->type == MPD_TYPE_INT)
{
ret = safe_atou32(argv[i + 1], &num);
if (ret < 0)
DPRINTF(E_WARN, L_MPD, "%s parameter '%s' is not an integer and will be ignored\n", tagtype->tag, argv[i + 1]);
else
c1 = db_mprintf("(%s = %d)", tagtype->field, num);
}
else if (tagtype->type == MPD_TYPE_SPECIAL)
{
if (0 == strcasecmp(tagtype->tag, "any"))
{
c1 = db_mprintf("(f.artist LIKE '%%%q%%' OR f.album LIKE '%%%q%%' OR f.title LIKE '%%%q%%')", argv[i + 1], argv[i + 1], argv[i + 1]);
}
else if (0 == strcasecmp(tagtype->tag, "file"))
{
if (exact_match)
c1 = db_mprintf("(f.virtual_path = '/%q')", argv[i + 1]);
else
c1 = db_mprintf("(f.virtual_path LIKE '%%%q%%')", argv[i + 1]);
}
else if (0 == strcasecmp(tagtype->tag, "base"))
{
c1 = db_mprintf("(f.virtual_path LIKE '/%q%%')", argv[i + 1]);
}
else if (0 == strcasecmp(tagtype->tag, "modified-since"))
{
// according to the mpd protocol specification the value can be a unix timestamp or ISO 8601
if (strchr(argv[i + 1], '-') == NULL)
c1 = db_mprintf("(f.time_modified > strftime('%%s', datetime('%q', 'unixepoch')))", argv[i + 1]);
else
c1 = db_mprintf("(f.time_modified > strftime('%%s', datetime('%q', 'utc')))", argv[i + 1]);
}
else
{
DPRINTF(E_WARN, L_MPD, "Unknown special parameter '%s' will be ignored\n", tagtype->tag);
}
}
}
else if (i == 0 && argc == 1)
{
// Special case: a single token is allowed if listing albums for an artist
c1 = db_mprintf("(f.album_artist = '%q')", argv[i]);
}
else
{
DPRINTF(E_WARN, L_MPD, "Missing value for parameter '%s', ignoring '%s'\n", argv[i], argv[i]);
}
if (c1)
{
append_string(&qp->filter, c1, " AND ");
free(c1);
c1 = NULL;
}
}
if ((i + 1) < argc && 0 == strcasecmp(argv[i], "window"))
{
ret = mpd_pars_range_arg(argv[i + 1], &start_pos, &end_pos);
if (ret == 0)
{
qp->idx_type = I_SUB;
qp->limit = end_pos - start_pos;
qp->offset = start_pos;
}
else
{
DPRINTF(E_LOG, L_MPD, "Window argument doesn't convert to integer or range: '%s'\n", argv[i + 1]);
}
}
return 0;
}
static int
parse_group_params(int argc, char **argv, bool group_in_listcommand, struct query_params *qp, struct mpd_tagtype ***group, int *groupsize)
{
int first_group;
int i;
int j;
struct mpd_tagtype *tagtype;
*groupsize = 0;
*group = NULL;
// Iterate through arguments to the first "group" argument
for (first_group = 0; first_group < argc; first_group++)
{
if (0 == strcasecmp(argv[first_group], "group"))
break;
}
// Early return if no group keyword in arguments (or group keyword not followed by field argument)
if ((first_group + 1) >= argc || (argc - first_group) % 2 != 0)
return 0;
*groupsize = (argc - first_group) / 2;
2022-01-20 20:14:02 +01:00
CHECK_NULL(L_MPD, *group = calloc(*groupsize, sizeof(struct mpd_tagtype *)));
// Now process all group/field arguments
for (j = 0; j < (*groupsize); j++)
{
i = first_group + (j * 2);
if ((i + 1) < argc && 0 == strcasecmp(argv[i], "group"))
{
tagtype = find_tagtype(argv[i + 1]);
if (tagtype && tagtype->type != MPD_TYPE_SPECIAL)
{
if (group_in_listcommand)
append_string(&qp->group, tagtype->group_field, ", ");
(*group)[j] = tagtype;
}
}
}
return 0;
}
2014-12-21 20:41:44 +01:00
/*
* Command handler function for 'currentsong'
*/
static int
mpd_command_currentsong(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, 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(evbuf, queue_item);
free_queue_item(queue_item, 0);
2014-12-21 20:41:44 +01:00
if (ret < 0)
{
*errmsg = safe_asprintf("Error adding media info for file with id: %d", status.id);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
return 0;
}
static int
mpd_notify_idle_client(struct mpd_client_ctx *client_ctx, short events);
/*
* Example input:
* idle "database" "mixer" "options" "output" "player" "playlist" "sticker" "update"
*/
static int
mpd_command_idle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
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 (argc > 1)
{
for (i = 1; i < argc; i++)
{
if (0 == strcmp(argv[i], "database"))
ctx->idle_events |= LISTENER_DATABASE;
else if (0 == strcmp(argv[i], "update"))
ctx->idle_events |= LISTENER_UPDATE;
else if (0 == strcmp(argv[i], "player"))
ctx->idle_events |= LISTENER_PLAYER;
else if (0 == strcmp(argv[i], "playlist"))
ctx->idle_events |= LISTENER_QUEUE;
else if (0 == strcmp(argv[i], "mixer"))
ctx->idle_events |= LISTENER_VOLUME;
else if (0 == strcmp(argv[i], "output"))
ctx->idle_events |= LISTENER_SPEAKER;
else if (0 == strcmp(argv[i], "options"))
ctx->idle_events |= LISTENER_OPTIONS;
else if (0 == strcmp(argv[i], "stored_playlist"))
ctx->idle_events |= LISTENER_STORED_PLAYLIST;
2017-12-05 01:34:27 +01:00
else if (0 == strcmp(argv[i], "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", argv[i]);
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)
[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_notify_idle_client(ctx, ctx->events);
}
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 evbuffer *evbuf, int argc, char **argv, char **errmsg, 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);
2014-12-21 20:41:44 +01:00
evbuffer_add_printf(evbuf,
"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)
{
2014-12-21 20:41:44 +01:00
evbuffer_add_printf(evbuf,
"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(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(evbuf, "updating_db: 1\n", 15);
}
if (itemid > 0)
{
queue_item = db_queue_fetch_next(itemid, status.shuffle);
if (queue_item)
{
evbuffer_add_printf(evbuf,
"nextsong: %d\n"
"nextsongid: %d\n",
queue_item->pos,
queue_item->id);
free_queue_item(queue_item, 0);
}
}
2014-12-21 20:41:44 +01:00
return 0;
}
/*
* Command handler function for 'stats'
*/
static int
mpd_command_stats(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
struct query_params qp;
struct filecount_info fci;
double uptime;
int64_t db_start = 0;
int64_t db_update = 0;
int ret;
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_COUNT_ITEMS;
ret = db_filecount_get(&fci, &qp);
if (ret < 0)
{
*errmsg = safe_asprintf("Could not start query");
return ACK_ERROR_UNKNOWN;
}
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)
2014-12-21 20:41:44 +01:00
evbuffer_add_printf(evbuf,
"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;
}
/*
* 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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
int enable;
int ret;
ret = safe_atoi32(argv[1], &enable);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
return ACK_ERROR_ARG;
}
player_consume_set(enable);
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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
int enable;
int ret;
ret = safe_atoi32(argv[1], &enable);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_ARG;
}
player_shuffle_set(enable);
return 0;
}
/*
* 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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
int enable;
int ret;
ret = safe_atoi32(argv[1], &enable);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_ARG;
}
if (enable == 0)
player_repeat_set(REPEAT_OFF);
else
player_repeat_set(REPEAT_ALL);
return 0;
}
/*
* Command handler function for 'setvol'
* Sets the volume, expects argument argv[1] to be an integer 0-100
*/
static int
mpd_command_setvol(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
int volume;
int ret;
ret = safe_atoi32(argv[1], &volume);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_ARG;
}
player_volume_set(volume);
return 0;
}
/*
* 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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
int enable;
struct player_status status;
int ret;
ret = safe_atoi32(argv[1], &enable);
if (ret < 0)
{
/* 0.21 protocol: accept "oneshot" mode */
if (strcmp(argv[1], "oneshot") == 0)
return 0;
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_ARG;
}
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;
}
/*
* 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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
evbuffer_add(evbuf, "replay_gain_mode: off\n", 22);
return 0;
}
/*
* Command handler function for 'volume'
* Changes the volume by the given amount, expects argument argv[1] to be an integer
*
* According to the mpd protocoll specification this function is deprecated.
*/
static int
mpd_command_volume(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
struct player_status status;
int volume;
int ret;
ret = safe_atoi32(argv[1], &volume);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_ARG;
}
player_get_status(&status);
volume += status.volume;
player_volume_set(volume);
return 0;
}
/*
* Command handler function for 'next'
* Skips to the next song in the playqueue
*/
static int
mpd_command_next(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
int ret;
ret = player_playback_next();
if (ret < 0)
{
*errmsg = safe_asprintf("Failed to skip to next song");
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
ret = player_playback_start();
2014-12-21 20:41:44 +01:00
if (ret < 0)
{
*errmsg = safe_asprintf("Player returned an error for start after nextitem");
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
return 0;
}
/*
* 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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
int pause;
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);
pause = status.status == PLAY_PLAYING ? 1 : 0;
2014-12-21 20:41:44 +01:00
if (argc > 1)
{
ret = safe_atoi32(argv[1], &pause);
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
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
return ACK_ERROR_ARG;
}
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();
2014-12-21 20:41:44 +01:00
if (ret < 0)
2014-12-21 20:41:44 +01:00
{
*errmsg = safe_asprintf("Failed to pause playback");
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
return 0;
}
/*
* 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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
int songpos;
struct player_status status;
struct db_queue_item *queue_item;
2014-12-21 20:41:44 +01:00
int ret;
songpos = -1;
2014-12-21 20:41:44 +01:00
if (argc > 1)
{
ret = safe_atoi32(argv[1], &songpos);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_ARG;
}
}
player_get_status(&status);
if (status.status == PLAY_PLAYING && songpos < 0)
{
DPRINTF(E_DBG, L_MPD, "Ignoring play command with parameter '%s', player is already playing.\n", argv[1]);
return 0;
}
if (status.status == PLAY_PLAYING)
{
// Stop playback, if player is already playing and a valid song position is given (it will be restarted for the given song position)
player_playback_stop();
}
2014-12-21 20:41:44 +01:00
if (songpos > 0)
{
queue_item = db_queue_fetch_bypos(songpos, 0);
if (!queue_item)
{
*errmsg = safe_asprintf("Failed to start playback");
return ACK_ERROR_UNKNOWN;
}
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)
2014-12-21 20:41:44 +01:00
{
*errmsg = safe_asprintf("Failed to start playback");
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
return 0;
}
/*
* 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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
uint32_t id;
struct player_status status;
struct db_queue_item *queue_item;
2014-12-21 20:41:44 +01:00
int ret;
player_get_status(&status);
id = 0;
if (argc > 1)
{
//TODO [mpd] mpd allows passing "-1" as argument and simply ignores it, the server fails to convert "-1" to an unsigned int
2014-12-21 20:41:44 +01:00
ret = safe_atou32(argv[1], &id);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_ARG;
}
}
if (status.status == PLAY_PLAYING)
{
// Stop playback, if player is already playing and a valid item id is given (it will be restarted for the given song)
player_playback_stop();
}
2014-12-21 20:41:44 +01:00
if (id > 0)
{
queue_item = db_queue_fetch_byitemid(id);
if (!queue_item)
{
*errmsg = safe_asprintf("Failed to start playback");
return ACK_ERROR_UNKNOWN;
}
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)
2014-12-21 20:41:44 +01:00
{
*errmsg = safe_asprintf("Failed to start playback");
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
return 0;
}
/*
* Command handler function for 'previous'
* Skips to the previous song in the playqueue
*/
static int
mpd_command_previous(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
int ret;
ret = player_playback_prev();
if (ret < 0)
{
*errmsg = safe_asprintf("Failed to skip to previous song");
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
ret = player_playback_start();
2016-03-12 08:17:12 +01:00
if (ret < 0)
{
*errmsg = safe_asprintf("Player returned an error for start after previtem");
2016-03-12 08:17:12 +01:00
return ACK_ERROR_UNKNOWN;
}
2014-12-21 20:41:44 +01:00
return 0;
}
/*
* Command handler function for 'seekid'
* 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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
uint32_t songpos;
float seek_target_sec;
int seek_target_msec;
int ret;
ret = safe_atou32(argv[1], &songpos);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_ARG;
}
//TODO Allow seeking in songs not currently playing
seek_target_sec = strtof(argv[2], NULL);
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)
{
*errmsg = safe_asprintf("Failed to seek current song to time %d msec", seek_target_msec);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
ret = player_playback_start();
2014-12-21 20:41:44 +01:00
if (ret < 0)
{
*errmsg = safe_asprintf("Player returned an error for start after seekcur");
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
return 0;
}
/*
* 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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
struct player_status status;
uint32_t id;
float seek_target_sec;
int seek_target_msec;
int ret;
ret = safe_atou32(argv[1], &id);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_ARG;
}
//TODO Allow seeking in songs not currently playing
player_get_status(&status);
if (status.item_id != id)
2014-12-21 20:41:44 +01:00
{
*errmsg = safe_asprintf("Given song is not the current playing one, seeking is not supported");
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
seek_target_sec = strtof(argv[2], NULL);
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)
{
*errmsg = safe_asprintf("Failed to seek current song to time %d msec", seek_target_msec);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
ret = player_playback_start();
2016-03-12 08:17:12 +01:00
if (ret < 0)
{
*errmsg = safe_asprintf("Player returned an error for start after seekcur");
2016-03-12 08:17:12 +01:00
return ACK_ERROR_UNKNOWN;
}
2014-12-21 20:41:44 +01:00
return 0;
}
/*
* 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 evbuffer *evbuf, int argc, char **argv, char **errmsg, 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(argv[1], NULL);
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)
{
*errmsg = safe_asprintf("Failed to seek current song to time %d msec", seek_target_msec);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
ret = player_playback_start();
2016-03-12 08:17:12 +01:00
if (ret < 0)
{
*errmsg = safe_asprintf("Player returned an error for start after seekcur");
2016-03-12 08:17:12 +01:00
return ACK_ERROR_UNKNOWN;
}
2014-12-21 20:41:44 +01:00
return 0;
}
/*
* Command handler function for 'stop'
* Stop playback.
*/
static int
mpd_command_stop(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
int ret;
ret = player_playback_stop();
if (ret != 0)
{
*errmsg = safe_asprintf("Failed to stop playback");
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
return 0;
}
/*
* 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;
struct player_status status;
int new_item_id;
int ret;
new_item_id = 0;
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_ITEMS;
qp.idx_type = I_NONE;
qp.sort = S_ARTIST;
if (exact_match)
qp.filter = db_mprintf("f.disabled = 0 AND f.virtual_path LIKE '/%q'", path);
else
qp.filter = db_mprintf("f.disabled = 0 AND f.virtual_path LIKE '/%q%%'", path);
if (!qp.filter)
{
DPRINTF(E_DBG, L_PLAYER, "Out of memory\n");
return -1;
}
player_get_status(&status);
ret = db_queue_add_by_query(&qp, status.shuffle, status.item_id, position, NULL, &new_item_id);
free(qp.filter);
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 evbuffer *evbuf, int argc, char **argv, char **errmsg, 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(argv[1], false, -1);
2014-12-21 20:41:44 +01:00
if (ret < 0)
2014-12-21 20:41:44 +01:00
{
*errmsg = safe_asprintf("Failed to add song '%s' to playlist", argv[1]);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
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(argv[1], -1, status.shuffle, status.item_id, NULL, NULL);
2017-03-13 20:03:25 +01:00
if (ret != LIBRARY_OK)
{
*errmsg = safe_asprintf("Failed to add song '%s' to playlist (unkown path)", argv[1]);
return ACK_ERROR_UNKNOWN;
}
}
2014-12-21 20:41:44 +01:00
return 0;
}
/*
* 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
* it must be an integer representing the position in the playqueue.
*/
static int
mpd_command_addid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
struct player_status status;
int to_pos = -1;
2014-12-21 20:41:44 +01:00
int ret;
if (argc > 2)
{
ret = safe_atoi32(argv[2], &to_pos);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[2]);
return ACK_ERROR_ARG;
}
2014-12-21 20:41:44 +01:00
}
ret = mpd_queue_add(argv[1], true, to_pos);
2014-12-21 20:41:44 +01:00
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(argv[1], to_pos, status.shuffle, status.item_id, NULL, NULL);
2017-03-13 20:03:25 +01:00
if (ret != LIBRARY_OK)
{
*errmsg = safe_asprintf("Failed to add song '%s' to playlist (unkown path)", argv[1]);
return ACK_ERROR_UNKNOWN;
}
}
if (ret < 0)
2014-12-21 20:41:44 +01:00
{
*errmsg = safe_asprintf("Failed to add song '%s' to playlist", argv[1]);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
evbuffer_add_printf(evbuf,
"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;
}
/*
* Command handler function for 'clear'
* Stops playback and removes all songs from the playqueue
*/
static int
mpd_command_clear(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, 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;
}
/*
* 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 evbuffer *evbuf, int argc, char **argv, char **errmsg, 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
2014-12-21 20:41:44 +01:00
if (argc < 2)
{
db_queue_clear(0);
2014-12-21 20:41:44 +01:00
return 0;
}
// If argument argv[1] is present remove only the specified songs
ret = mpd_pars_range_arg(argv[1], &start_pos, &end_pos);
2014-12-21 20:41:44 +01:00
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer or range: '%s'", argv[1]);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_ARG;
}
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)
{
*errmsg = safe_asprintf("Failed to remove %d songs starting at position %d", count, start_pos);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
return 0;
}
/*
* Command handler function for 'deleteid'
* Removes the song with given id from the playqueue. Expects argument argv[1] to be an integer (song id).
*/
static int
mpd_command_deleteid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
uint32_t songid;
int ret;
ret = safe_atou32(argv[1], &songid);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_ARG;
}
ret = db_queue_delete_byitemid(songid);
2014-12-21 20:41:44 +01:00
if (ret < 0)
{
*errmsg = safe_asprintf("Failed to remove song with id '%s'", argv[1]);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
return 0;
}
2016-02-15 20:27:39 +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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
2016-02-15 20:27:39 +01:00
int start_pos;
int end_pos;
uint32_t to_pos;
int ret;
ret = mpd_pars_range_arg(argv[1], &start_pos, &end_pos);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer or range: '%s'", argv[1]);
2016-02-15 20:27:39 +01:00
return ACK_ERROR_ARG;
}
[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 (count > 1)
// DPRINTF(E_WARN, L_MPD, "Moving ranges is not supported, only the first item will be moved\n");
2016-02-15 20:27:39 +01:00
ret = safe_atou32(argv[2], &to_pos);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[2]);
2016-02-15 20:27:39 +01:00
return ACK_ERROR_ARG;
}
[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
uint32_t queue_length;
db_queue_get_count(&queue_length);
int count = end_pos - start_pos;
// valid move pos and range is:
// 0 <= start < queue_len
// start < end <= queue_len
// 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))
{
*errmsg = safe_asprintf((to_pos > queue_length - count)
? "Range too large for target position" : "Bad song index");
return ACK_ERROR_ARG;
}
ret = db_queue_move_bypos_range(start_pos, end_pos, to_pos);
2016-02-15 20:27:39 +01:00
if (ret < 0)
{
*errmsg = safe_asprintf("Failed to move song at position %d to %d", start_pos, to_pos);
2016-02-15 20:27:39 +01:00
return ACK_ERROR_UNKNOWN;
}
return 0;
}
static int
mpd_command_moveid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
uint32_t songid;
uint32_t to_pos;
int ret;
ret = safe_atou32(argv[1], &songid);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
return ACK_ERROR_ARG;
}
ret = safe_atou32(argv[2], &to_pos);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[2]);
return ACK_ERROR_ARG;
}
ret = db_queue_move_byitemid(songid, to_pos, 0);
if (ret < 0)
{
*errmsg = safe_asprintf("Failed to move song with id '%s' to index '%s'", argv[1], argv[2]);
return ACK_ERROR_UNKNOWN;
}
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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
struct query_params query_params;
struct db_queue_item queue_item;
uint32_t songid;
int ret;
songid = 0;
if (argc > 1)
{
ret = safe_atou32(argv[1], &songid);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
return ACK_ERROR_ARG;
}
}
memset(&query_params, 0, sizeof(struct query_params));
if (songid > 0)
query_params.filter = db_mprintf("id = %d", songid);
ret = db_queue_enum_start(&query_params);
if (ret < 0)
{
free(query_params.filter);
*errmsg = safe_asprintf("Failed to start queue enum for command playlistid: '%s'", argv[1]);
return ACK_ERROR_ARG;
}
while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0)
{
ret = mpd_add_db_queue_item(evbuf, &queue_item);
if (ret < 0)
{
*errmsg = safe_asprintf("Error adding media info for file with id: %d", queue_item.file_id);
db_queue_enum_end(&query_params);
free(query_params.filter);
return ACK_ERROR_UNKNOWN;
}
}
db_queue_enum_end(&query_params);
free(query_params.filter);
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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
struct query_params query_params;
struct db_queue_item queue_item;
int start_pos;
int end_pos;
int ret;
start_pos = 0;
end_pos = 0;
memset(&query_params, 0, sizeof(struct query_params));
if (argc > 1)
{
ret = mpd_pars_range_arg(argv[1], &start_pos, &end_pos);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer or range: '%s'", argv[1]);
return ACK_ERROR_ARG;
}
if (start_pos < 0)
DPRINTF(E_DBG, L_MPD, "Command 'playlistinfo' called with pos < 0 (arg = '%s'), ignore arguments and return whole queue\n", argv[1]);
else
query_params.filter = db_mprintf("pos >= %d AND pos < %d", start_pos, end_pos);
}
ret = db_queue_enum_start(&query_params);
if (ret < 0)
{
free(query_params.filter);
*errmsg = safe_asprintf("Failed to start queue enum for command playlistinfo: '%s'", argv[1]);
return ACK_ERROR_ARG;
}
while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0)
{
ret = mpd_add_db_queue_item(evbuf, &queue_item);
if (ret < 0)
{
*errmsg = safe_asprintf("Error adding media info for file with id: %d", queue_item.file_id);
db_queue_enum_end(&query_params);
free(query_params.filter);
return ACK_ERROR_UNKNOWN;
}
}
db_queue_enum_end(&query_params);
free(query_params.filter);
return 0;
}
static int
mpd_command_playlistfind(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
struct query_params query_params;
struct db_queue_item queue_item;
int ret;
memset(&query_params, 0, sizeof(struct query_params));
if (argc < 3 || ((argc - 1) % 2) != 0)
{
*errmsg = safe_asprintf("Missing argument(s) for command 'playlistfind'");
return ACK_ERROR_ARG;
}
parse_filter_window_params(argc - 1, argv + 1, true, &query_params);
ret = db_queue_enum_start(&query_params);
if (ret < 0)
{
free(query_params.filter);
*errmsg = safe_asprintf("Failed to start queue enum for command playlistinfo: '%s'", argv[1]);
return ACK_ERROR_ARG;
}
while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0)
{
ret = mpd_add_db_queue_item(evbuf, &queue_item);
if (ret < 0)
{
*errmsg = safe_asprintf("Error adding media info for file with id: %d", queue_item.file_id);
db_queue_enum_end(&query_params);
free(query_params.filter);
return ACK_ERROR_UNKNOWN;
}
}
db_queue_enum_end(&query_params);
free(query_params.filter);
return 0;
}
static int
mpd_command_playlistsearch(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
struct query_params query_params;
struct db_queue_item queue_item;
2014-12-21 20:41:44 +01:00
int ret;
memset(&query_params, 0, sizeof(struct query_params));
2014-12-21 20:41:44 +01:00
if (argc < 3 || ((argc - 1) % 2) != 0)
2014-12-21 20:41:44 +01:00
{
*errmsg = safe_asprintf("Missing argument(s) for command 'playlistfind'");
return ACK_ERROR_ARG;
}
2015-08-01 11:43:51 +02:00
parse_filter_window_params(argc - 1, argv + 1, false, &query_params);
ret = db_queue_enum_start(&query_params);
if (ret < 0)
2014-12-21 20:41:44 +01:00
{
free(query_params.filter);
*errmsg = safe_asprintf("Failed to start queue enum for command playlistinfo: '%s'", argv[1]);
return ACK_ERROR_ARG;
2014-12-21 20:41:44 +01:00
}
while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0)
2014-12-21 20:41:44 +01:00
{
ret = mpd_add_db_queue_item(evbuf, &queue_item);
2014-12-21 20:41:44 +01:00
if (ret < 0)
{
*errmsg = safe_asprintf("Error adding media info for file with id: %d", queue_item.file_id);
db_queue_enum_end(&query_params);
free(query_params.filter);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
}
db_queue_enum_end(&query_params);
free(query_params.filter);
2014-12-21 20:41:44 +01:00
return 0;
}
static int
plchanges_build_queryparams(struct query_params *query_params, int argc, char **argv, char **errmsg)
{
uint32_t version;
int start_pos;
int end_pos;
int ret;
memset(query_params, 0, sizeof(struct query_params));
ret = safe_atou32(argv[1], &version);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
return ACK_ERROR_ARG;
}
start_pos = 0;
end_pos = 0;
if (argc > 2)
{
ret = mpd_pars_range_arg(argv[2], &start_pos, &end_pos);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer or range: '%s'", argv[2]);
return ACK_ERROR_ARG;
}
if (start_pos < 0)
DPRINTF(E_DBG, L_MPD, "Command 'playlistinfo' called with pos < 0 (arg = '%s'), ignore arguments and return whole queue\n", argv[1]);
}
if (start_pos < 0 || end_pos <= 0)
query_params->filter = db_mprintf("(queue_version > %d)", version);
else
query_params->filter = db_mprintf("(queue_version > %d AND pos >= %d AND pos < %d)", version, start_pos, end_pos);
return 0;
}
/*
* Command handler function for 'plchanges'
* Lists all changed songs in the queue since the given playlist version in argv[1].
*/
static int
mpd_command_plchanges(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
struct query_params query_params;
struct db_queue_item queue_item;
int ret;
ret = plchanges_build_queryparams(&query_params, argc, argv, errmsg);
if (ret != 0)
return ret;
ret = db_queue_enum_start(&query_params);
if (ret < 0)
goto error;
while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0)
{
ret = mpd_add_db_queue_item(evbuf, &queue_item);
if (ret < 0)
{
DPRINTF(E_LOG, L_MPD, "Error adding media info for file with id: %d", queue_item.file_id);
goto error;
}
}
db_queue_enum_end(&query_params);
free_query_params(&query_params, 1);
return 0;
error:
db_queue_enum_end(&query_params);
free_query_params(&query_params, 1);
*errmsg = safe_asprintf("Failed to start queue enum for command plchanges");
return ACK_ERROR_UNKNOWN;
}
/*
* Command handler function for 'plchangesposid'
* Lists all changed songs in the queue since the given playlist version in argv[1] without metadata.
*/
static int
mpd_command_plchangesposid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
struct query_params query_params;
struct db_queue_item queue_item;
int ret;
ret = plchanges_build_queryparams(&query_params, argc, argv, errmsg);
if (ret != 0)
return ret;
ret = db_queue_enum_start(&query_params);
if (ret < 0)
goto error;
while ((ret = db_queue_enum_fetch(&query_params, &queue_item)) == 0 && queue_item.id > 0)
{
evbuffer_add_printf(evbuf,
"cpos: %d\n"
"Id: %d\n",
queue_item.pos,
queue_item.id);
}
db_queue_enum_end(&query_params);
free_query_params(&query_params, 1);
return 0;
error:
db_queue_enum_end(&query_params);
free_query_params(&query_params, 1);
*errmsg = safe_asprintf("Failed to start queue enum for command plchangesposid");
return ACK_ERROR_UNKNOWN;
}
/*
* Command handler function for 'listplaylist'
* Lists all songs in the playlist given by virtual-path in argv[1].
*/
static int
mpd_command_listplaylist(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
char *path;
struct playlist_info *pli;
struct query_params qp;
struct db_media_file_info dbmfi;
int ret;
if (!default_pl_dir || strstr(argv[1], ":/"))
{
// Argument is a virtual path, make sure it starts with a '/'
path = prepend_slash(argv[1]);
}
else
{
// Argument is a playlist name, prepend default playlist directory
path = safe_asprintf("%s/%s", default_pl_dir, argv[1]);
}
pli = db_pl_fetch_byvirtualpath(path);
free(path);
if (!pli)
{
*errmsg = safe_asprintf("Playlist not found for path '%s'", argv[1]);
return ACK_ERROR_ARG;
}
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_PLITEMS;
qp.idx_type = I_NONE;
qp.id = pli->id;
ret = db_query_start(&qp);
if (ret < 0)
{
db_query_end(&qp);
free_pli(pli, 0);
*errmsg = safe_asprintf("Could not start query");
return ACK_ERROR_UNKNOWN;
}
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
{
evbuffer_add_printf(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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
char *path;
struct playlist_info *pli;
struct query_params qp;
struct db_media_file_info dbmfi;
int ret;
if (!default_pl_dir || strstr(argv[1], ":/"))
{
// Argument is a virtual path, make sure it starts with a '/'
path = prepend_slash(argv[1]);
}
else
{
// Argument is a playlist name, prepend default playlist directory
path = safe_asprintf("%s/%s", default_pl_dir, argv[1]);
}
pli = db_pl_fetch_byvirtualpath(path);
free(path);
if (!pli)
{
*errmsg = safe_asprintf("Playlist not found for path '%s'", argv[1]);
return ACK_ERROR_NO_EXIST;
}
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_PLITEMS;
qp.idx_type = I_NONE;
qp.id = pli->id;
ret = db_query_start(&qp);
if (ret < 0)
{
db_query_end(&qp);
free_pli(pli, 0);
*errmsg = safe_asprintf("Could not start query");
return ACK_ERROR_UNKNOWN;
}
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
{
ret = mpd_add_db_media_file_info(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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
struct query_params qp;
struct db_playlist_info dbpli;
char modified[32];
uint32_t time_modified;
int ret;
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_PL;
qp.sort = S_PLAYLIST;
qp.idx_type = I_NONE;
qp.filter = db_mprintf("(f.type = %d OR f.type = %d)", PL_PLAIN, PL_SMART);
ret = db_query_start(&qp);
if (ret < 0)
{
db_query_end(&qp);
free(qp.filter);
*errmsg = safe_asprintf("Could not start query");
return ACK_ERROR_UNKNOWN;
}
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
{
*errmsg = safe_asprintf("Error converting time modified to uint32_t: %s\n", dbpli.db_timestamp);
db_query_end(&qp);
free(qp.filter);
return ACK_ERROR_UNKNOWN;
}
mpd_time(modified, sizeof(modified), time_modified);
evbuffer_add_printf(evbuf,
"playlist: %s\n"
"Last-Modified: %s\n",
(dbpli.virtual_path + 1),
modified);
}
db_query_end(&qp);
free(qp.filter);
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 evbuffer *evbuf, int argc, char **argv, char **errmsg, 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(argv[1], ":/"))
{
// Argument is a virtual path, make sure it starts with a '/'
path = prepend_slash(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, 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)
{
*errmsg = safe_asprintf("Playlist not found for path '%s'", argv[1]);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_ARG;
}
//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)
2014-12-21 20:41:44 +01:00
{
*errmsg = safe_asprintf("Failed to add song '%s' to playlist", argv[1]);
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
return 0;
}
static int
mpd_command_playlistadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
char *vp_playlist;
char *vp_item;
int ret;
if (!allow_modifying_stored_playlists)
{
*errmsg = safe_asprintf("Modifying stored playlists is not enabled");
return ACK_ERROR_PERMISSION;
}
if (!default_pl_dir || strstr(argv[1], ":/"))
{
// Argument is a virtual path, make sure it starts with a '/'
vp_playlist = prepend_slash(argv[1]);
}
else
{
// Argument is a playlist name, prepend default playlist directory
vp_playlist = safe_asprintf("%s/%s", default_pl_dir, argv[1]);
}
vp_item = prepend_slash(argv[2]);
ret = library_playlist_item_add(vp_playlist, vp_item);
free(vp_playlist);
free(vp_item);
if (ret < 0)
{
*errmsg = safe_asprintf("Error adding item to file '%s'", argv[1]);
return ACK_ERROR_ARG;
}
return 0;
}
static int
mpd_command_rm(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
char *virtual_path;
int ret;
if (!allow_modifying_stored_playlists)
{
*errmsg = safe_asprintf("Modifying stored playlists is not enabled");
return ACK_ERROR_PERMISSION;
}
if (!default_pl_dir || strstr(argv[1], ":/"))
{
// Argument is a virtual path, make sure it starts with a '/'
virtual_path = prepend_slash(argv[1]);
}
else
{
// Argument is a playlist name, prepend default playlist directory
virtual_path = safe_asprintf("%s/%s", default_pl_dir, argv[1]);
}
ret = library_playlist_remove(virtual_path);
free(virtual_path);
if (ret < 0)
{
*errmsg = safe_asprintf("Error removing playlist '%s'", argv[1]);
return ACK_ERROR_ARG;
}
return 0;
}
static int
mpd_command_save(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
char *virtual_path;
int ret;
if (!allow_modifying_stored_playlists)
{
*errmsg = safe_asprintf("Modifying stored playlists is not enabled");
return ACK_ERROR_PERMISSION;
}
if (!default_pl_dir || strstr(argv[1], ":/"))
{
// Argument is a virtual path, make sure it starts with a '/'
virtual_path = prepend_slash(argv[1]);
}
else
{
// Argument is a playlist name, prepend default playlist directory
virtual_path = safe_asprintf("%s/%s", default_pl_dir, argv[1]);
}
ret = library_queue_save(virtual_path);
free(virtual_path);
if (ret < 0)
{
*errmsg = safe_asprintf("Error saving queue to file '%s'", argv[1]);
return ACK_ERROR_ARG;
}
return 0;
}
2015-04-06 09:19:48 +02:00
static int
mpd_command_count(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2015-04-06 09:19:48 +02:00
{
struct query_params qp;
2015-05-26 21:24:10 +02:00
struct filecount_info fci;
2015-04-06 09:19:48 +02:00
int ret;
if (argc < 3 || ((argc - 1) % 2) != 0)
{
*errmsg = safe_asprintf("Missing argument(s) for command 'find'");
2015-04-06 09:19:48 +02:00
return ACK_ERROR_ARG;
}
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_COUNT_ITEMS;
parse_filter_window_params(argc - 1, argv + 1, true, &qp);
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(qp.filter);
2015-04-06 09:19:48 +02:00
*errmsg = safe_asprintf("Could not start query");
2015-04-06 09:19:48 +02:00
return ACK_ERROR_UNKNOWN;
}
evbuffer_add_printf(evbuf,
"songs: %d\n"
"playtime: %" PRIu64 "\n",
fci.count,
(fci.length / 1000));
2015-04-06 09:19:48 +02:00
db_query_end(&qp);
free(qp.filter);
2015-04-06 09:19:48 +02:00
return 0;
}
static int
mpd_command_find(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
struct query_params qp;
struct db_media_file_info dbmfi;
int ret;
if (argc < 3 || ((argc - 1) % 2) != 0)
{
*errmsg = safe_asprintf("Missing argument(s) for command 'find'");
return ACK_ERROR_ARG;
}
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_ITEMS;
qp.sort = S_NAME;
qp.idx_type = I_NONE;
parse_filter_window_params(argc - 1, argv + 1, true, &qp);
ret = db_query_start(&qp);
if (ret < 0)
{
db_query_end(&qp);
free(qp.filter);
*errmsg = safe_asprintf("Could not start query");
return ACK_ERROR_UNKNOWN;
}
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
{
ret = mpd_add_db_media_file_info(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(qp.filter);
return 0;
}
static int
mpd_command_findadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
2015-05-01 11:07:03 +02:00
struct query_params qp;
struct player_status status;
2015-05-01 11:07:03 +02:00
int ret;
if (argc < 3 || ((argc - 1) % 2) != 0)
{
*errmsg = safe_asprintf("Missing argument(s) for command 'findadd'");
2015-05-01 11:07:03 +02:00
return ACK_ERROR_ARG;
}
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_ITEMS;
qp.sort = S_ARTIST;
2015-05-01 11:07:03 +02:00
qp.idx_type = I_NONE;
parse_filter_window_params(argc - 1, argv + 1, true, &qp);
2015-05-01 11:07:03 +02:00
player_get_status(&status);
2015-05-01 11:07:03 +02:00
ret = db_queue_add_by_query(&qp, status.shuffle, status.item_id, -1, NULL, NULL);
free(qp.filter);
if (ret < 0)
2015-05-01 11:07:03 +02:00
{
*errmsg = safe_asprintf("Failed to add songs to playlist");
2015-05-01 11:07:03 +02:00
return ACK_ERROR_UNKNOWN;
}
return 0;
}
/*
* 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 void
sanitize_value(char **strval)
{
char *ptr = *strval;
while(*ptr != '\0')
{
if(*ptr == '\n')
{
*ptr = ' ';
}
ptr++;
}
}
static int
mpd_command_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
struct mpd_tagtype *tagtype;
struct query_params qp;
struct mpd_tagtype **group;
int groupsize;
struct db_media_file_info dbmfi;
char **strval;
int i;
int ret;
if (argc < 2 || ((argc % 2) != 0))
{
if (argc != 3 || (0 != strcasecmp(argv[1], "album")))
{
*errmsg = safe_asprintf("Missing argument(s) for command 'list'");
return ACK_ERROR_ARG;
}
}
tagtype = find_tagtype(argv[1]);
if (!tagtype || tagtype->type == MPD_TYPE_SPECIAL) //FIXME allow "file" tagtype
{
DPRINTF(E_WARN, L_MPD, "Unsupported type argument for command 'list': %s\n", argv[1]);
return 0;
}
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_ITEMS;
qp.idx_type = I_NONE;
qp.order = tagtype->sort_field;
qp.group = strdup(tagtype->group_field);
if (argc > 2)
{
parse_filter_window_params(argc - 2, argv + 2, true, &qp);
}
group = NULL;
groupsize = 0;
parse_group_params(argc - 2, argv + 2, tagtype->group_in_listcommand, &qp, &group, &groupsize);
ret = db_query_start(&qp);
if (ret < 0)
{
db_query_end(&qp);
free(qp.filter);
free(qp.group);
free(group);
*errmsg = safe_asprintf("Could not start query");
return ACK_ERROR_UNKNOWN;
}
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
{
strval = (char **) ((char *)&dbmfi + tagtype->mfi_offset);
if (!(*strval) || (**strval == '\0'))
continue;
sanitize_value(strval);
evbuffer_add_printf(evbuf,
"%s: %s\n",
tagtype->tag,
*strval);
if (group && groupsize > 0)
{
for (i = 0; i < groupsize; i++)
{
if (!group[i])
continue;
strval = (char **) ((char *)&dbmfi + group[i]->mfi_offset);
if (!(*strval) || (**strval == '\0'))
continue;
evbuffer_add_printf(evbuf,
"%s: %s\n",
group[i]->tag,
*strval);
}
}
}
db_query_end(&qp);
free(qp.filter);
free(qp.group);
free(group);
2015-02-15 09:51:38 +01:00
return 0;
}
2014-12-21 20:41:44 +01:00
static int
mpd_add_directory(struct evbuffer *evbuf, int directory_id, int listall, int listinfo, char **errmsg)
2014-12-21 20:41:44 +01:00
{
struct directory_info subdir;
2014-12-21 20:41:44 +01:00
struct query_params qp;
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
2014-12-21 20:41:44 +01:00
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_PL;
qp.sort = S_PLAYLIST;
qp.idx_type = I_NONE;
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)
{
db_query_end(&qp);
free(qp.filter);
*errmsg = safe_asprintf("Could not start query");
2014-12-21 20:41:44 +01:00
return ACK_ERROR_UNKNOWN;
}
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(evbuf,
"playlist: %s\n"
"Last-Modified: %s\n",
(dbpli.virtual_path + 1),
modified);
}
else
{
evbuffer_add_printf(evbuf,
"playlist: %s\n",
(dbpli.virtual_path + 1));
}
}
db_query_end(&qp);
free(qp.filter);
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)
{
DPRINTF(E_LOG, L_MPD, "Failed to start directory enum for parent_id %d\n", directory_id);
db_directory_enum_end(&dir_enum);
return -1;
}
while ((ret = db_directory_enum_fetch(&dir_enum, &subdir)) == 0 && subdir.id > 0)
{
if (listinfo)
{
evbuffer_add_printf(evbuf,
"directory: %s\n"
"Last-Modified: %s\n",
(subdir.virtual_path + 1),
"2015-12-01 00:00");
}
else
{
evbuffer_add_printf(evbuf,
"directory: %s\n",
(subdir.virtual_path + 1));
}
if (listall)
{
mpd_add_directory(evbuf, subdir.id, listall, listinfo, errmsg);
}
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)
{
db_query_end(&qp);
free(qp.filter);
*errmsg = safe_asprintf("Could not start query");
return ACK_ERROR_UNKNOWN;
}
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
{
if (listinfo)
{
ret = mpd_add_db_media_file_info(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(evbuf,
"file: %s\n",
(dbmfi.virtual_path + 1));
}
}
2014-12-21 20:41:44 +01:00
db_query_end(&qp);
free(qp.filter);
2014-12-21 20:41:44 +01:00
return 0;
}
static int
mpd_command_listall(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
int dir_id;
char parent[PATH_MAX];
int ret;
if (argc < 2 || strlen(argv[1]) == 0
|| (strncmp(argv[1], "/", 1) == 0 && strlen(argv[1]) == 1))
{
ret = snprintf(parent, sizeof(parent), "/");
}
else if (strncmp(argv[1], "/", 1) == 0)
{
ret = snprintf(parent, sizeof(parent), "%s/", argv[1]);
}
else
{
ret = snprintf(parent, sizeof(parent), "/%s", argv[1]);
}
if ((ret < 0) || (ret >= sizeof(parent)))
{
*errmsg = safe_asprintf("Parent path exceeds PATH_MAX");
return ACK_ERROR_UNKNOWN;
}
// Load dir-id from db for parent-path
dir_id = db_directory_id_byvirtualpath(parent);
if (dir_id == 0)
{
*errmsg = safe_asprintf("Directory info not found for virtual-path '%s'", parent);
return ACK_ERROR_NO_EXIST;
}
ret = mpd_add_directory(evbuf, dir_id, 1, 0, errmsg);
return ret;
}
static int
mpd_command_listallinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
int dir_id;
char parent[PATH_MAX];
int ret;
if (argc < 2 || strlen(argv[1]) == 0
|| (strncmp(argv[1], "/", 1) == 0 && strlen(argv[1]) == 1))
{
ret = snprintf(parent, sizeof(parent), "/");
}
else if (strncmp(argv[1], "/", 1) == 0)
{
ret = snprintf(parent, sizeof(parent), "%s/", argv[1]);
}
else
{
ret = snprintf(parent, sizeof(parent), "/%s", argv[1]);
}
if ((ret < 0) || (ret >= sizeof(parent)))
{
*errmsg = safe_asprintf("Parent path exceeds PATH_MAX");
return ACK_ERROR_UNKNOWN;
}
// Load dir-id from db for parent-path
dir_id = db_directory_id_byvirtualpath(parent);
if (dir_id == 0)
{
*errmsg = safe_asprintf("Directory info not found for virtual-path '%s'", parent);
return ACK_ERROR_NO_EXIST;
}
ret = mpd_add_directory(evbuf, dir_id, 1, 1, errmsg);
return ret;
}
/*
* Command handler function for 'lsinfo'
* Lists the contents of the directory given in argv[1].
*/
static int
mpd_command_lsinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
int dir_id;
char parent[PATH_MAX];
int print_playlists;
int ret;
if (argc < 2 || strlen(argv[1]) == 0
|| (strncmp(argv[1], "/", 1) == 0 && strlen(argv[1]) == 1))
{
ret = snprintf(parent, sizeof(parent), "/");
}
else if (strncmp(argv[1], "/", 1) == 0)
{
ret = snprintf(parent, sizeof(parent), "%s/", argv[1]);
}
else
{
ret = snprintf(parent, sizeof(parent), "/%s", argv[1]);
}
if ((ret < 0) || (ret >= sizeof(parent)))
{
*errmsg = safe_asprintf("Parent path exceeds PATH_MAX");
return ACK_ERROR_UNKNOWN;
}
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)
{
*errmsg = safe_asprintf("Directory info not found for virtual-path '%s'", parent);
return ACK_ERROR_NO_EXIST;
}
ret = mpd_add_directory(evbuf, dir_id, 0, 1, errmsg);
// If the root directory was passed as argument add the stored playlists to the response
if (ret == 0 && print_playlists)
{
return mpd_command_listplaylists(evbuf, argc, argv, errmsg, 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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
return mpd_command_lsinfo(evbuf, argc, argv, errmsg, ctx);
}
2015-02-15 09:51:38 +01:00
/*
* Command handler function for 'search'
* Lists any song that matches the given list of arguments. Arguments are pairs of TYPE and WHAT, where
* TYPE is the tag that contains WHAT (case insensitiv).
*
* TYPE can also be one of the special parameter:
* - any: checks all tags
* - file: checks the virtual_path
* - base: restricts result to the given directory
* - modified-since (not supported)
* - window: limits result to the given range of "START:END"
*
* Example request: "search artist foo album bar"
*/
static int
mpd_command_search(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2015-02-15 09:51:38 +01:00
{
struct query_params qp;
struct db_media_file_info dbmfi;
int ret;
if (argc < 3 || ((argc - 1) % 2) != 0)
{
*errmsg = safe_asprintf("Missing argument(s) for command 'search'");
2015-02-15 09:51:38 +01:00
return ACK_ERROR_ARG;
}
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_ITEMS;
qp.sort = S_NAME;
qp.idx_type = I_NONE;
parse_filter_window_params(argc - 1, argv + 1, false, &qp);
2015-02-15 09:51:38 +01:00
ret = db_query_start(&qp);
if (ret < 0)
{
db_query_end(&qp);
free(qp.filter);
2015-02-15 09:51:38 +01:00
*errmsg = safe_asprintf("Could not start query");
2015-02-15 09:51:38 +01:00
return ACK_ERROR_UNKNOWN;
}
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
2015-02-15 09:51:38 +01:00
{
ret = mpd_add_db_media_file_info(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(qp.filter);
2015-02-15 09:51:38 +01:00
return 0;
}
2015-05-01 11:37:05 +02:00
static int
mpd_command_searchadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2015-05-01 11:37:05 +02:00
{
struct query_params qp;
struct player_status status;
2015-05-01 11:37:05 +02:00
int ret;
if (argc < 3 || ((argc - 1) % 2) != 0)
{
*errmsg = safe_asprintf("Missing argument(s) for command 'search'");
2015-05-01 11:37:05 +02:00
return ACK_ERROR_ARG;
}
memset(&qp, 0, sizeof(struct query_params));
qp.type = Q_ITEMS;
qp.sort = S_ARTIST;
2015-05-01 11:37:05 +02:00
qp.idx_type = I_NONE;
parse_filter_window_params(argc - 1, argv + 1, false, &qp);
2015-05-01 11:37:05 +02:00
player_get_status(&status);
2015-05-01 11:37:05 +02:00
ret = db_queue_add_by_query(&qp, status.shuffle, status.item_id, -1, NULL, NULL);
free(qp.filter);
if (ret < 0)
2015-05-01 11:37:05 +02:00
{
*errmsg = safe_asprintf("Failed to add songs to playlist");
2015-05-01 11:37:05 +02:00
return ACK_ERROR_UNKNOWN;
}
return 0;
}
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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
if (argc > 1 && strlen(argv[1]) > 0)
{
*errmsg = safe_asprintf("Update for specific uri not supported for command 'update'");
2014-12-21 20:41:44 +01:00
return ACK_ERROR_ARG;
}
library_rescan(0);
2014-12-21 20:41:44 +01:00
evbuffer_add(evbuf, "updating_db: 1\n", 15);
return 0;
}
2017-12-05 01:34:27 +01:00
static int
2017-12-10 09:24:29 +01:00
mpd_sticker_get(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, const char *virtual_path)
2017-12-05 01:34:27 +01:00
{
2017-12-10 09:24:29 +01:00
struct media_file_info *mfi = NULL;
uint32_t rating;
2017-12-05 01:34:27 +01:00
2017-12-10 09:24:29 +01:00
if (strcmp(argv[4], "rating") != 0)
2017-12-05 01:34:27 +01:00
{
2017-12-10 09:24:29 +01:00
*errmsg = safe_asprintf("no such sticker");
return ACK_ERROR_NO_EXIST;
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)
{
DPRINTF(E_LOG, L_MPD, "Virtual path not found: %s\n", virtual_path);
*errmsg = safe_asprintf("unknown sticker domain");
return ACK_ERROR_ARG;
}
if (mfi->rating > 0)
2017-12-10 09:24:29 +01:00
{
rating = mfi->rating / MPD_RATING_FACTOR;
evbuffer_add_printf(evbuf, "sticker: rating=%d\n", rating);
}
free_mfi(mfi, 0);
return 0;
2017-12-05 01:34:27 +01:00
}
static int
2017-12-10 09:24:29 +01:00
mpd_sticker_set(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, 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
2017-12-10 09:24:29 +01:00
if (strcmp(argv[4], "rating") != 0)
2017-12-05 01:34:27 +01:00
{
2017-12-10 09:24:29 +01:00
*errmsg = safe_asprintf("no such sticker");
return ACK_ERROR_NO_EXIST;
2017-12-05 01:34:27 +01:00
}
2017-12-10 09:24:29 +01:00
ret = safe_atou32(argv[5], &rating);
if (ret < 0)
{
*errmsg = safe_asprintf("rating '%s' doesn't convert to integer", argv[5]);
return ACK_ERROR_ARG;
}
rating *= MPD_RATING_FACTOR;
if (rating > DB_FILES_RATING_MAX)
{
*errmsg = safe_asprintf("rating '%s' is greater than maximum value allowed", argv[5]);
return ACK_ERROR_ARG;
}
id = db_file_id_byvirtualpath(virtual_path);
if (id <= 0)
2017-12-10 09:24:29 +01:00
{
*errmsg = safe_asprintf("Invalid path '%s'", virtual_path);
return ACK_ERROR_ARG;
}
library_item_attrib_save(id, LIBRARY_ATTRIB_RATING, rating);
2017-12-10 09:24:29 +01:00
return 0;
2017-12-05 01:34:27 +01:00
}
static int
2017-12-10 09:24:29 +01:00
mpd_sticker_delete(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, const char *virtual_path)
2017-12-05 01:34:27 +01:00
{
int id;
2017-12-05 01:34:27 +01:00
2017-12-10 09:24:29 +01:00
if (strcmp(argv[4], "rating") != 0)
2017-12-05 01:34:27 +01:00
{
2017-12-10 09:24:29 +01:00
*errmsg = safe_asprintf("no such sticker");
return ACK_ERROR_NO_EXIST;
2017-12-05 01:34:27 +01:00
}
id = db_file_id_byvirtualpath(virtual_path);
if (id <= 0)
2017-12-10 09:24:29 +01:00
{
*errmsg = safe_asprintf("Invalid path '%s'", virtual_path);
return ACK_ERROR_ARG;
}
library_item_attrib_save(id, LIBRARY_ATTRIB_RATING, 0);
2017-12-10 09:24:29 +01:00
return 0;
2017-12-05 01:34:27 +01:00
}
static int
2017-12-10 09:24:29 +01:00
mpd_sticker_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, const char *virtual_path)
2017-12-05 01:34:27 +01:00
{
2017-12-10 09:24:29 +01:00
struct media_file_info *mfi = NULL;
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)
{
DPRINTF(E_LOG, L_MPD, "Virtual path not found: %s\n", virtual_path);
*errmsg = safe_asprintf("unknown sticker domain");
return ACK_ERROR_ARG;
}
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(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 */
2017-12-10 09:24:29 +01:00
return 0;
2017-12-05 01:34:27 +01:00
}
static int
2017-12-10 09:24:29 +01:00
mpd_sticker_find(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, const char *virtual_path)
2017-12-05 01:34:27 +01:00
{
2017-12-10 09:24:29 +01:00
struct query_params qp;
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;
2017-12-10 09:24:29 +01:00
if (strcmp(argv[4], "rating") != 0)
2017-12-05 01:34:27 +01:00
{
2017-12-10 09:24:29 +01:00
*errmsg = safe_asprintf("no such sticker");
return ACK_ERROR_NO_EXIST;
}
2017-12-05 01:34:27 +01:00
if (argc == 6)
{
*errmsg = safe_asprintf("not enough arguments for 'sticker find'");
return ACK_ERROR_ARG;
}
if (argc > 6)
{
if (strcmp(argv[5], "=") != 0 && strcmp(argv[5], ">") != 0 && strcmp(argv[5], "<") != 0)
{
*errmsg = safe_asprintf("invalid operator '%s' given to 'sticker find'", argv[5]);
return ACK_ERROR_ARG;
}
operator = argv[5];
ret = safe_atou32(argv[6], &rating_arg);
if (ret < 0)
{
*errmsg = safe_asprintf("rating '%s' doesn't convert to integer", argv[6]);
return ACK_ERROR_ARG;
}
rating_arg *= MPD_RATING_FACTOR;
}
else
{
operator = ">";
rating_arg = 0;
}
2017-12-10 09:24:29 +01:00
memset(&qp, 0, sizeof(struct query_params));
2017-12-05 01:34:27 +01:00
2017-12-10 09:24:29 +01:00
qp.type = Q_ITEMS;
qp.sort = S_VPATH;
qp.idx_type = I_NONE;
2017-12-05 01:34:27 +01:00
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-10 09:24:29 +01:00
if (!qp.filter)
{
*errmsg = safe_asprintf("Out of memory");
ret = ACK_ERROR_UNKNOWN;
return ret;
}
2017-12-05 01:34:27 +01:00
2017-12-10 09:24:29 +01:00
ret = db_query_start(&qp);
if (ret < 0)
{
db_query_end(&qp);
free(qp.filter);
2017-12-05 01:34:27 +01:00
2017-12-10 09:24:29 +01:00
*errmsg = safe_asprintf("Could not start query");
ret = ACK_ERROR_UNKNOWN;
return ret;
}
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(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(qp.filter);
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 evbuffer *evbuf, int argc, char **argv, char **errmsg, const char *virtual_path);
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, 5 },
{ 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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
struct mpd_sticker_command *cmd_param = NULL; // Quell compiler warning about uninitialized use of cmd_param
2017-12-05 01:34:27 +01:00
char *virtual_path = NULL;
int i;
int ret;
if (strcmp(argv[2], "song") != 0)
{
*errmsg = safe_asprintf("unknown sticker domain");
return ACK_ERROR_ARG;
}
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];
2017-12-10 09:24:29 +01:00
if (cmd_param->cmd && strcmp(argv[1], cmd_param->cmd) == 0)
2017-12-05 01:34:27 +01:00
break;
}
if (!cmd_param->cmd)
{
*errmsg = safe_asprintf("bad request");
return ACK_ERROR_ARG;
}
if (argc < cmd_param->need_args)
{
*errmsg = safe_asprintf("not enough arguments");
return ACK_ERROR_ARG;
}
virtual_path = prepend_slash(argv[3]);
2017-12-10 09:24:29 +01:00
ret = cmd_param->handler(evbuf, argc, argv, errmsg, virtual_path);
2017-12-05 01:34:27 +01:00
free(virtual_path);
return ret;
}
2014-12-21 20:41:44 +01:00
/*
static int
mpd_command_rescan(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
int ret;
if (argc > 1)
{
DPRINTF(E_LOG, L_MPD, "Rescan for specific uri not supported for command 'rescan'\n");
*errmsg = safe_asprintf("Rescan for specific uri not supported for command 'rescan'");
2014-12-21 20:41:44 +01:00
return ACK_ERROR_ARG;
}
filescanner_trigger_fullrescan();
evbuffer_add(evbuf, "updating_db: 1\n", 15);
return 0;
}
*/
2017-11-10 09:55:44 +01:00
static int
mpd_command_password(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2017-11-10 09:55:44 +01:00
{
char *required_password;
char *supplied_password = "";
int unrequired;
if (argc > 1)
{
supplied_password = argv[1];
}
required_password = cfg_getstr(cfg_getsec(cfg, "library"), "password");
unrequired = !required_password || required_password[0] == '\0';
if (unrequired || strcmp(supplied_password, required_password) == 0)
{
DPRINTF(E_DBG, L_MPD,
2017-12-05 01:34:27 +01:00
"Authentication succeeded with supplied password: %s%s\n",
supplied_password,
unrequired ? " although no password is required" : "");
[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
ctx->authenticated = true;
2017-11-10 09:55:44 +01:00
return 0;
}
DPRINTF(E_LOG, L_MPD,
2017-12-05 01:34:27 +01:00
"Authentication failed with supplied password: %s"
" for required password: %s\n",
supplied_password, required_password);
2017-11-10 09:55:44 +01:00
*errmsg = safe_asprintf("Wrong password. Authentication failed.");
return ACK_ERROR_PASSWORD;
}
static int
mpd_command_binarylimit(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
unsigned int size;
if (safe_atou32(argv[1], &size) < 0)
{
DPRINTF(E_DBG, L_MPD,
"Argument %s to binarylimit is not a number\n",
argv[1]);
return ACK_ERROR_ARG;
}
if (size < MPD_BINARY_SIZE_MIN)
{
*errmsg = safe_asprintf("Value too small");
return ACK_ERROR_ARG;
}
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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
struct output_get_param param;
uint32_t num;
int ret;
ret = safe_atou32(argv[1], &num);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
return ACK_ERROR_ARG;
}
memset(&param, 0, sizeof(struct output_get_param));
param.shortid = num;
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)
{
*errmsg = safe_asprintf("Speakers deactivation failed: %d", num);
return ACK_ERROR_UNKNOWN;
}
}
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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
struct output_get_param param;
uint32_t num;
int ret;
ret = safe_atou32(argv[1], &num);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
return ACK_ERROR_ARG;
}
memset(&param, 0, sizeof(struct output_get_param));
param.shortid = num;
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)
{
*errmsg = safe_asprintf("Speakers deactivation failed: %d", num);
return ACK_ERROR_UNKNOWN;
}
}
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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
struct output_get_param param;
uint32_t num;
int ret;
ret = safe_atou32(argv[1], &num);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
return ACK_ERROR_ARG;
}
memset(&param, 0, sizeof(struct output_get_param));
param.shortid = num;
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)
{
*errmsg = safe_asprintf("Toggle speaker failed: %d", num);
return ACK_ERROR_UNKNOWN;
}
}
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
* outputvolume: 50
* https://mpd.readthedocs.io/en/latest/protocol.html#audio-output-devices
*/
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"
"outputvolume: %d\n",
param->nextid,
spk->name,
plugin,
spk->selected,
spk->absvol);
param->nextid++;
2014-12-21 20:41:44 +01:00
}
/*
* Command handler function for 'output'
* Returns a lists with the avaiable speakers.
*/
2014-12-21 20:41:44 +01:00
static int
mpd_command_outputs(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
struct output_outputs_param param;
/* 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. */
memset(&param, 0, sizeof(param));
param.buf = 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(evbuf,
"outputid: %u\n"
"outputname: MP3 stream\n"
"plugin: httpd\n"
"outputenabled: 1\n",
param.nextid);
param.nextid++;
}
2014-12-21 20:41:44 +01:00
return 0;
}
static int
outputvolume_set(uint32_t shortid, int volume, char **errmsg)
{
struct output_get_param param;
int ret;
memset(&param, 0, sizeof(struct output_get_param));
param.shortid = shortid;
player_speaker_enumerate(output_get_cb, &param);
if (param.output)
{
ret = player_volume_setabs_speaker(param.output->id, volume);
free_output(param.output);
if (ret < 0)
{
*errmsg = safe_asprintf("Setting volume to %d for speaker with short-id %d failed", volume, shortid);
return ACK_ERROR_UNKNOWN;
}
}
else
{
*errmsg = safe_asprintf("No speaker found for short id: %d", shortid);
return ACK_ERROR_UNKNOWN;
}
return 0;
}
static int
mpd_command_outputvolume(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
uint32_t shortid;
int volume;
int ret;
ret = safe_atou32(argv[1], &shortid);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[1]);
return ACK_ERROR_ARG;
}
ret = safe_atoi32(argv[2], &volume);
if (ret < 0)
{
*errmsg = safe_asprintf("Argument doesn't convert to integer: '%s'", argv[2]);
return ACK_ERROR_ARG;
}
ret = outputvolume_set(shortid, volume, errmsg);
return ret;
}
static void
channel_outputvolume(const char *message)
{
uint32_t shortid;
int volume;
char *tmp;
char *ptr;
char *errmsg = NULL;
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;
}
outputvolume_set(shortid, volume, &errmsg);
if (errmsg)
DPRINTF(E_LOG, L_MPD, "Failed to set output volume from message: '%s' (error='%s')\n", message, errmsg);
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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
int i;
for (i = 0; mpd_channels[i].handler; i++)
{
evbuffer_add_printf(evbuf,
"channel: %s\n",
mpd_channels[i].channel);
}
return 0;
}
static int
mpd_command_sendmessage(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
const char *channelname;
const char *message;
struct mpd_channel *channel;
if (argc < 3)
{
*errmsg = safe_asprintf("Missing argument for command 'sendmessage'");
return ACK_ERROR_ARG;
}
channelname = argv[1];
message = 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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
2014-12-21 20:41:44 +01:00
{
//do nothing
DPRINTF(E_DBG, L_MPD, "Ignore command %s\n", argv[0]);
return 0;
}
2015-02-15 09:51:38 +01:00
static int
mpd_command_commands(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx);
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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
int i;
for (i = 0; i < ARRAY_SIZE(tagtypes); i++)
{
if (tagtypes[i].type != MPD_TYPE_SPECIAL)
evbuffer_add_printf(evbuf, "tagtype: %s\n", tagtypes[i].tag);
}
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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
evbuffer_add_printf(evbuf,
"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;
}
/*
* 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 evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
int i;
evbuffer_add_printf(evbuf, "plugin: ffmpeg\n");
for (i = 0; ffmpeg_suffixes[i]; i++)
{
evbuffer_add_printf(evbuf, "suffix: %s\n", ffmpeg_suffixes[i]);
}
for (i = 0; ffmpeg_mime_types[i]; i++)
{
evbuffer_add_printf(evbuf, "mime_type: %s\n", ffmpeg_mime_types[i]);
}
return 0;
}
2015-02-15 09:51:38 +01:00
struct mpd_command
2014-12-21 20:41:44 +01:00
{
/* The command name */
const char *mpdcommand;
/*
* The function to execute the command
*
* @param evbuf the response event buffer
* @param argc number of arguments in argv
* @param argv argument array, first entry is the commandname
* @param errmsg error message set by this function if an error occured
* @return 0 if successful, one of ack values if an error occured
*/
int (*handler)(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx);
2017-12-10 10:58:52 +01:00
int min_argc;
2014-12-21 20:41:44 +01:00
};
static struct mpd_command mpd_handlers[] =
2014-12-21 20:41:44 +01:00
{
2017-12-10 10:58:52 +01:00
/* commandname | handler function | minimum argument count*/
// Commands for querying status
{ "clearerror", mpd_command_ignore, -1 },
{ "currentsong", mpd_command_currentsong, -1 },
{ "idle", mpd_command_idle, -1 },
{ "status", mpd_command_status, -1 },
{ "stats", mpd_command_stats, -1 },
// Playback options
{ "consume", mpd_command_consume, 2 },
{ "crossfade", mpd_command_ignore, -1 },
{ "mixrampdb", mpd_command_ignore, -1 },
{ "mixrampdelay", mpd_command_ignore, -1 },
{ "random", mpd_command_random, 2 },
{ "repeat", mpd_command_repeat, 2 },
{ "setvol", mpd_command_setvol, 2 },
{ "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 },
// Controlling playback
{ "next", mpd_command_next, -1 },
{ "pause", mpd_command_pause, -1 },
{ "play", mpd_command_play, -1 },
{ "playid", mpd_command_playid, -1 },
{ "previous", mpd_command_previous, -1 },
{ "seek", mpd_command_seek, 3 },
{ "seekid", mpd_command_seekid, 3 },
{ "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 },
{ "move", mpd_command_move, 3 },
{ "moveid", mpd_command_moveid, 3 },
{ "playlist", mpd_command_playlistinfo, -1 }, // According to the mpd protocol the use of "playlist" is deprecated
{ "playlistfind", mpd_command_playlistfind, -1 },
{ "playlistid", mpd_command_playlistid, -1 },
{ "playlistinfo", mpd_command_playlistinfo, -1 },
{ "playlistsearch", mpd_command_playlistsearch, -1 },
{ "plchanges", mpd_command_plchanges, 2 },
{ "plchangesposid", mpd_command_plchangesposid, 2 },
// { "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, -1 },
{ "findadd", mpd_command_findadd, -1 },
{ "list", mpd_command_list, -1 },
{ "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 },
{ "search", mpd_command_search, -1 },
{ "searchadd", mpd_command_searchadd, -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_ignore, -1 },
// { "kill", mpd_command_kill, -1 },
{ "password", mpd_command_password, -1 },
{ "ping", mpd_command_ignore, -1 },
{ "binarylimit", mpd_command_binarylimit, 2 },
/* missing: tagtypes */
2017-12-10 10:58:52 +01:00
// Audio output devices
{ "disableoutput", mpd_command_disableoutput, 2 },
{ "enableoutput", mpd_command_enableoutput, 2 },
{ "toggleoutput", mpd_command_toggleoutput, 2 },
2017-12-10 10:58:52 +01:00
{ "outputs", mpd_command_outputs, -1 },
// Reflection
// { "config", mpd_command_config, -1 },
{ "commands", mpd_command_commands, -1 },
{ "notcommands", mpd_command_ignore, -1 },
{ "tagtypes", mpd_command_tagtypes, -1 },
{ "urlhandlers", mpd_command_urlhandlers, -1 },
{ "decoders", mpd_command_decoders, -1 },
// 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, -1 },
// Custom commands (not supported by mpd)
{ "outputvolume", mpd_command_outputvolume, 3 },
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].mpdcommand))
{
return &mpd_handlers[i];
}
}
return NULL;
}
static int
mpd_command_commands(struct evbuffer *evbuf, int argc, char **argv, char **errmsg, struct mpd_client_ctx *ctx)
{
int i;
for (i = 0; mpd_handlers[i].handler; i++)
{
evbuffer_add_printf(evbuf,
2017-12-05 01:34:27 +01:00
"command: %s\n",
mpd_handlers[i].mpdcommand);
}
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
static inline int
mpd_ack_response(struct evbuffer *output, int err_code, int cmd_num, char *cmd_name, char *errmsg)
{
return evbuffer_add_printf(output, "ACK [%d@%d] {%s} %s\n", err_code, cmd_num, cmd_name, errmsg);
}
static inline int
mpd_ok_response(struct evbuffer *output)
{
return evbuffer_add_printf(output, "OK\n");
}
/**
* Add a command line, including null terminator, into the command list buffer.
* These command lines will be processed when command_list_end is received.
*/
static int
mpd_command_list_add(struct mpd_client_ctx *client_ctx, char* line)
{
if (client_ctx->cmd_list_buffer == NULL)
{
client_ctx->cmd_list_buffer = evbuffer_new();
}
return evbuffer_add(client_ctx->cmd_list_buffer, line, strlen(line) + 1);
}
/**
* The result returned by mpd_process_command() and mpd_process_line()
*/
typedef enum mpd_command_result {
/**
* command handled with success.
* the connection should stay open.
* no response was sent.
* the caller of mpd_process_command sends OK or list_OK.
*/
CMD_RESULT_OK = 0,
/**
* command handled with error.
* ack response was send by the command handler.
* the connection should stay open.
*/
CMD_RESULT_ERROR = 1,
/**
* the client entered idle state.
* no response should be sent to the client.
*/
CMD_RESULT_IDLE = 2,
/**
* the client connection should be closed
*/
CMD_RESULT_CLOSE = 3,
}
MpdCommandResult;
typedef enum mpd_parse_args_result {
ARGS_OK,
ARGS_EMPTY,
ARGS_TOO_MANY,
ARGS_ERROR
}
MpdParseArgsResult;
/*
* Parses the argument string into an array of strings.
* Arguments are seperated by a whitespace character and may be wrapped in double quotes.
*
* @param args the arguments
* @param argc the number of arguments in the argument string
* @param argv the array containing the found arguments
*/
static MpdParseArgsResult
mpd_parse_args(char *args, int *argc, char **argv, int argv_size)
{
char *input = args;
int arg_count = 0;
DPRINTF(E_SPAM, L_MPD, "Parse args: args = \"%s\"\n", input);
while (*input != 0 && arg_count < argv_size)
{
// Ignore whitespace characters
if (*input == ' ')
{
input++;
continue;
}
// Check if the parameter is wrapped in double quotes
if (*input == '"')
{
argv[arg_count] = mpd_pars_quoted(&input);
if (argv[arg_count] == NULL)
{
return ARGS_ERROR;
}
arg_count += 1;
}
else
{
argv[arg_count++] = mpd_pars_unquoted(&input);
}
}
DPRINTF(E_SPAM, L_MPD, "Parse args: args count = \"%d\"\n", arg_count);
*argc = arg_count;
if (arg_count == 0)
return ARGS_EMPTY;
if (*input != 0 && arg_count == argv_size)
return ARGS_TOO_MANY;
return ARGS_OK;
}
/**
* Process one command line.
* @param line
* @param output
* @param cmd_num
* @param client_ctx
* @return
*/
static MpdCommandResult
mpd_process_command(char *line, struct evbuffer *output, int cmd_num, struct mpd_client_ctx *client_ctx)
{
char *argv[COMMAND_ARGV_MAX];
int argc = 0;
char *errmsg = NULL;
int mpd_err_code = 0;
char *cmd_name = "unknown";
MpdCommandResult cmd_result = CMD_RESULT_OK;
// Split the read line into command name and arguments
MpdParseArgsResult args_result = mpd_parse_args(line, &argc, argv, COMMAND_ARGV_MAX);
switch (args_result)
{
case ARGS_EMPTY:
DPRINTF(E_LOG, L_MPD, "No command given\n");
errmsg = safe_asprintf("No command given");
mpd_err_code = ACK_ERROR_ARG;
// in this case MPD disconnects the client
cmd_result = CMD_RESULT_CLOSE;
break;
case ARGS_TOO_MANY:
DPRINTF(E_LOG, L_MPD, "Number of arguments exceeds max of %d: %s\n", COMMAND_ARGV_MAX, line);
errmsg = safe_asprintf("Too many arguments: %d allowed", COMMAND_ARGV_MAX);
mpd_err_code = ACK_ERROR_ARG;
// in this case MPD doesn't disconnect the client
cmd_result = CMD_RESULT_ERROR;
break;
case ARGS_ERROR:
// Error handling for argument parsing error
DPRINTF(E_LOG, L_MPD, "Error parsing arguments for MPD message: %s\n", line);
errmsg = safe_asprintf("Error parsing arguments");
mpd_err_code = ACK_ERROR_UNKNOWN;
// in this case MPD disconnects the client
cmd_result = CMD_RESULT_CLOSE;
break;
case ARGS_OK:
cmd_name = argv[0];
/*
* Find the command handler and execute the command function
*/
struct mpd_command *command = mpd_find_command(cmd_name);
if (command == NULL)
{
errmsg = safe_asprintf("unknown command \"%s\"", cmd_name);
mpd_err_code = ACK_ERROR_UNKNOWN;
}
else if (command->min_argc > argc)
{
errmsg = safe_asprintf("Missing argument(s) for command '%s', expected %d, given %d", argv[0],
command->min_argc, argc);
mpd_err_code = ACK_ERROR_ARG;
}
else if (!client_ctx->authenticated && strcmp(cmd_name, "password") != 0)
[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
{
errmsg = safe_asprintf("Not authenticated");
mpd_err_code = ACK_ERROR_PERMISSION;
}
else
{
mpd_err_code = command->handler(output, argc, argv, &errmsg, client_ctx);
}
break;
}
/*
* If an error occurred, add the ACK line to the response buffer
*/
if (mpd_err_code != 0)
{
DPRINTF(E_LOG, L_MPD, "Error executing command '%s': %s\n", line, errmsg);
mpd_ack_response(output, mpd_err_code, cmd_num, cmd_name, errmsg);
if (cmd_result == CMD_RESULT_OK)
{
cmd_result = CMD_RESULT_ERROR;
}
}
else if (0 == strcmp(cmd_name, "idle"))
{
cmd_result = CMD_RESULT_IDLE;
}
else if (0 == strcmp(cmd_name, "close"))
{
cmd_result = CMD_RESULT_CLOSE;
}
if (errmsg != NULL)
free(errmsg);
return cmd_result;
}
static MpdCommandResult
mpd_process_line(char *line, struct evbuffer *output, struct mpd_client_ctx *client_ctx)
{
/*
* "noidle" is ignored unless the client is in idle state
*/
if (0 == strcmp(line, "noidle"))
{
if (client_ctx->is_idle)
{
// leave idle state and send OK
client_ctx->is_idle = false;
mpd_ok_response(output);
}
return CMD_RESULT_OK;
}
/*
* in idle state the only allowed command is "noidle"
*/
if (client_ctx->is_idle)
{
// during idle state client must not send anything except "noidle"
DPRINTF(E_FATAL, L_MPD, "Only \"noidle\" is allowed during idle. received: %s\n", line);
return CMD_RESULT_CLOSE;
}
MpdCommandResult res = CMD_RESULT_OK;
if (client_ctx->cmd_list_type == COMMAND_LIST_NONE)
{
// not in command list
if (0 == strcmp(line, "command_list_begin"))
{
client_ctx->cmd_list_type = COMMAND_LIST;
return CMD_RESULT_OK;
}
if (0 == strcmp(line, "command_list_ok_begin"))
{
client_ctx->cmd_list_type = COMMAND_LIST_OK;
return CMD_RESULT_OK;
}
res = mpd_process_command(line, output, 0, client_ctx);
DPRINTF(E_DBG, L_MPD, "Command \"%s\" returned: %d\n", line, res);
if (res == CMD_RESULT_OK)
{
mpd_ok_response(output);
}
}
else
{
// in command list
if (0 == strcmp(line, "command_list_end"))
{
// end of command list:
// process the commands that were added to client_ctx->cmd_list_buffer
// From MPD documentation (https://mpd.readthedocs.io/en/latest/protocol.html#command-lists):
// It does not execute any commands until the list has ended. The response is
// a concatenation of all individual responses.
// On success for all commands, OK is returned.
// If a command fails, no more commands are executed and the appropriate ACK error is returned.
// If command_list_ok_begin is used, list_OK is returned
// for each successful command executed in the command list.
DPRINTF(E_DBG, L_MPD, "process command list\n");
bool ok_mode = (client_ctx->cmd_list_type == COMMAND_LIST_OK);
struct evbuffer *commands_buffer = client_ctx->cmd_list_buffer;
client_ctx->cmd_list_type = COMMAND_LIST_NONE;
client_ctx->cmd_list_buffer = NULL;
int cmd_num = 0;
char *cmd_line;
if (commands_buffer != NULL)
{
while ((cmd_line = evbuffer_readln(commands_buffer, NULL, EVBUFFER_EOL_NUL)))
{
res = mpd_process_command(cmd_line, output, cmd_num++, client_ctx);
free(cmd_line);
if (res != CMD_RESULT_OK) break;
if (ok_mode)
evbuffer_add_printf(output, "list_OK\n");
}
evbuffer_free(commands_buffer);
}
DPRINTF(E_DBG, L_MPD, "Command list returned: %d\n", res);
if (res == CMD_RESULT_OK)
{
mpd_ok_response(output);
}
}
else
{
// in command list:
// save commands in the client context
if (-1 == mpd_command_list_add(client_ctx, line))
{
DPRINTF(E_FATAL, L_MPD, "Failed to add to command list\n");
res = CMD_RESULT_CLOSE;
}
else if (evbuffer_get_length(client_ctx->cmd_list_buffer) > Config.MaxCommandListSize)
{
DPRINTF(E_FATAL, L_MPD, "Max command list size (%uKB) exceeded\n", (Config.MaxCommandListSize / 1024));
res = CMD_RESULT_CLOSE;
}
else
{
res = CMD_RESULT_OK;
}
}
}
return res;
}
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).
*
* @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 *ctx)
{
struct evbuffer *input;
struct evbuffer *output;
char *line;
struct mpd_client_ctx *client_ctx = (struct mpd_client_ctx *)ctx;
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 (client_ctx->is_closing)
{
// after freeing the bev ignore any reads
return;
}
2014-12-21 20:41:44 +01:00
/* Get the input evbuffer, contains the command sequence received from the client */
input = bufferevent_get_input(bev);
/* Get the output evbuffer, used to send the server response to the client */
output = bufferevent_get_output(bev);
while ((line = evbuffer_readln(input, NULL, EVBUFFER_EOL_ANY)))
{
[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_DBG, L_MPD, "MPD message: \"%s\"\n", line);
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
enum mpd_command_result res = mpd_process_line(line, output, client_ctx);
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
switch (res) {
case CMD_RESULT_ERROR:
case CMD_RESULT_IDLE:
case CMD_RESULT_OK:
break;
case CMD_RESULT_CLOSE:
client_ctx->is_closing = true;
if (client_ctx->cmd_list_buffer != NULL)
{
evbuffer_free(client_ctx->cmd_list_buffer);
client_ctx->cmd_list_buffer = NULL;
}
/*
* Freeing the bufferevent closes the connection, if it was
* opened with BEV_OPT_CLOSE_ON_FREE.
* Since bufferevent is reference-counted, it will happen as
* soon as possible, not necessarily immediately.
*/
bufferevent_free(bev);
break;
}
}
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 = calloc(1, sizeof(struct mpd_client_ctx));
2017-11-10 09:55:44 +01:00
if (!client_ctx)
2017-11-10 09:55:44 +01:00
{
DPRINTF(E_LOG, L_MPD, "Out of memory for command context\n");
bufferevent_free(bev);
return;
}
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;
client_ctx->next = mpd_clients;
mpd_clients = client_ctx;
2014-12-21 20:41:44 +01:00
bev = bufferevent_filter_new(bev, mpd_input_filter, NULL, BEV_OPT_CLOSE_ON_FREE, free_mpd_client_ctx, client_ctx);
bufferevent_setcb(bev, mpd_read_cb, NULL, mpd_event_cb, client_ctx);
2014-12-21 20:41:44 +01:00
bufferevent_enable(bev, EV_READ | EV_WRITE);
/*
* 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.
*/
evbuffer_add(bufferevent_get_output(bev), "OK MPD 0.20.0\n", 14);
client_ctx->evbuffer = bufferevent_get_output(bev);
[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->cmd_list_type = COMMAND_LIST_NONE;
client_ctx->is_idle = false;
client_ctx->is_closing = false;
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));
}
2015-02-21 06:04:17 +01:00
static int
mpd_notify_idle_client(struct mpd_client_ctx *client_ctx, short events)
2015-02-21 06:04:17 +01:00
{
if (!client_ctx->is_idle)
2015-02-21 06:04:17 +01:00
{
client_ctx->events |= events;
2015-02-21 06:04:17 +01:00
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);
2017-12-05 01:34:27 +01:00
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)
2017-12-05 01:34:27 +01:00
evbuffer_add(client_ctx->evbuffer, "changed: sticker\n", 17);
evbuffer_add(client_ctx->evbuffer, "OK\n", 3);
client_ctx->is_idle = false;
client_ctx->idle_events = 0;
client_ctx->events = 0;
2015-02-21 06:04:17 +01:00
return 0;
}
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);
mpd_notify_idle_client(client, event_mask);
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
/* 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;
[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
cfg_t *mpd_section = cfg_getsec(cfg, "mpd");
port = cfg_getint(mpd_section, "port");
if (port <= 0)
{
DPRINTF(E_INFO, L_MPD, "MPD not enabled\n");
return 0;
2015-03-12 21:35:56 +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
long max_command_list_size = cfg_getint(mpd_section, "max_command_list_size");
if (max_command_list_size <= 0 || max_command_list_size > INT_MAX)
{
DPRINTF(E_INFO, L_MPD, "Ignoring invalid config \"max_command_list_size\" (%ld). Will use the default %uKB instead.\n",
max_command_list_size,
(Config.MaxCommandListSize / 1024));
}
else
{
Config.MaxCommandListSize = max_command_list_size * 1024; // from KB to bytes
}
CHECK_NULL(L_MPD, evbase_mpd = event_base_new());
CHECK_NULL(L_MPD, cmdbase = commands_base_new(evbase_mpd, NULL));
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
{
free_mpd_client_ctx(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
}