[mpd/conf] Add support for stored playlist commands (add/save/delete)

This also introduces a new config option to define a default playlist
folder. The default playlist folder is used by forked-daapd if the
playlist is not a full virtual path (simpifies creation of playlists
with mpd clients).
This commit is contained in:
chme 2017-08-09 18:21:46 +02:00
parent d10c3672c6
commit dc3f9443e7
3 changed files with 151 additions and 2 deletions

View File

@ -264,6 +264,11 @@ mpd {
# the playlist like MPD does. Note that some dacp clients do not show # the playlist like MPD does. Note that some dacp clients do not show
# the playqueue if playback is stopped. # the playqueue if playback is stopped.
# clear_queue_on_stop_disable = false # clear_queue_on_stop_disable = false
# A directory in one of the library directories that will be used as the default
# playlist directory. forked-dapd creates new playlists in this directory if only
# a playlist name is provided by the mpd client.
# default_playlist_directory = ""
} }
# SQLite configuration (allows to modify the operation of the SQLite databases) # SQLite configuration (allows to modify the operation of the SQLite databases)

View File

@ -156,6 +156,7 @@ static cfg_opt_t sec_mpd[] =
CFG_INT("port", 6600, CFGF_NONE), CFG_INT("port", 6600, CFGF_NONE),
CFG_INT("http_port", 0, CFGF_NONE), CFG_INT("http_port", 0, CFGF_NONE),
CFG_BOOL("clear_queue_on_stop_disable", cfg_false, CFGF_NONE), CFG_BOOL("clear_queue_on_stop_disable", cfg_false, CFGF_NONE),
CFG_STR("default_playlist_directory", NULL, CFGF_NONE),
CFG_END() CFG_END()
}; };

147
src/mpd.c
View File

@ -70,6 +70,8 @@ static struct evhttp *evhttpd;
struct evconnlistener *listener; struct evconnlistener *listener;
// Virtual path to the default playlist directory
static char *default_pl_dir;
#define COMMAND_ARGV_MAX 37 #define COMMAND_ARGV_MAX 37
@ -185,6 +187,26 @@ struct idle_client
struct idle_client *idle_clients; struct idle_client *idle_clients;
/*
* 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;
}
/* Thread: mpd */ /* Thread: mpd */
static void * static void *
@ -604,6 +626,10 @@ mpd_command_idle(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
{ {
client->events |= LISTENER_OPTIONS; client->events |= LISTENER_OPTIONS;
} }
else if (0 == strcmp(argv[i], "stored_playlist"))
{
client->events |= LISTENER_STORED_PLAYLIST;
}
else else
{ {
DPRINTF(E_DBG, L_MPD, "Idle command for '%s' not supported\n", argv[i]); DPRINTF(E_DBG, L_MPD, "Idle command for '%s' not supported\n", argv[i]);
@ -2172,6 +2198,112 @@ mpd_command_load(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
return 0; return 0;
} }
static int
mpd_command_playlistadd(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
{
char *vp_playlist;
char *vp_item;
int ret;
if (argc < 3)
{
*errmsg = safe_asprintf("Missing argument for command 'playlistadd'");
return ACK_ERROR_ARG;
}
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_add(vp_playlist, vp_item);
free(vp_playlist);
free(vp_item);
if (ret < 0)
{
*errmsg = safe_asprintf("Error saving queue 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)
{
char *virtual_path;
int ret;
if (argc < 2)
{
*errmsg = safe_asprintf("Missing argument for command 'rm'");
return ACK_ERROR_ARG;
}
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)
{
char *virtual_path;
int ret;
if (argc < 2)
{
*errmsg = safe_asprintf("Missing argument for command 'save'");
return ACK_ERROR_ARG;
}
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;
}
static int static int
mpd_get_query_params_find(int argc, char **argv, struct query_params *qp) mpd_get_query_params_find(int argc, char **argv, struct query_params *qp)
{ {
@ -3944,11 +4076,11 @@ static struct mpd_command mpd_handlers[] =
.mpdcommand = "load", .mpdcommand = "load",
.handler = mpd_command_load .handler = mpd_command_load
}, },
/*
{ {
.mpdcommand = "playlistadd", .mpdcommand = "playlistadd",
.handler = mpd_command_playlistadd .handler = mpd_command_playlistadd
}, },
/*
{ {
.mpdcommand = "playlistclear", .mpdcommand = "playlistclear",
.handler = mpd_command_playlistclear .handler = mpd_command_playlistclear
@ -3965,6 +4097,7 @@ static struct mpd_command mpd_handlers[] =
.mpdcommand = "rename", .mpdcommand = "rename",
.handler = mpd_command_rename .handler = mpd_command_rename
}, },
*/
{ {
.mpdcommand = "rm", .mpdcommand = "rm",
.handler = mpd_command_rm .handler = mpd_command_rm
@ -3973,7 +4106,6 @@ static struct mpd_command mpd_handlers[] =
.mpdcommand = "save", .mpdcommand = "save",
.handler = mpd_command_save .handler = mpd_command_save
}, },
*/
/* /*
* The music database * The music database
@ -4512,6 +4644,10 @@ mpd_notify_idle_client(struct idle_client *client, enum listener_event_type type
evbuffer_add(client->evbuffer, "changed: options\n", 17); evbuffer_add(client->evbuffer, "changed: options\n", 17);
break; break;
case LISTENER_STORED_PLAYLIST:
evbuffer_add(client->evbuffer, "changed: stored_playlist\n", 25);
break;
default: default:
DPRINTF(E_WARN, L_MPD, "Unsupported event type (%d) in notify idle clients.\n", type); DPRINTF(E_WARN, L_MPD, "Unsupported event type (%d) in notify idle clients.\n", type);
return -1; return -1;
@ -4706,6 +4842,7 @@ int mpd_init(void)
unsigned short http_port; unsigned short http_port;
const char *http_addr; const char *http_addr;
int v6enabled; int v6enabled;
const char *pl_dir;
int ret; int ret;
@ -4806,6 +4943,10 @@ int mpd_init(void)
} }
} }
pl_dir = cfg_getstr(cfg_getsec(cfg, "mpd"), "default_playlist_directory");
if (pl_dir)
default_pl_dir = safe_asprintf("/file:%s", pl_dir);
DPRINTF(E_INFO, L_MPD, "mpd thread init\n"); DPRINTF(E_INFO, L_MPD, "mpd thread init\n");
ret = pthread_create(&tid_mpd, NULL, mpd, NULL); ret = pthread_create(&tid_mpd, NULL, mpd, NULL);
@ -4884,4 +5025,6 @@ void mpd_deinit(void)
// Free event base (should free events too) // Free event base (should free events too)
event_base_free(evbase_mpd); event_base_free(evbase_mpd);
free(default_pl_dir);
} }