diff --git a/forked-daapd.conf.in b/forked-daapd.conf.in index 29a6f082..329be07f 100644 --- a/forked-daapd.conf.in +++ b/forked-daapd.conf.in @@ -191,6 +191,16 @@ library { # (played or skipped). Both results are combined with a mix-factor of 0.75: # new rating = 0.75 * stable rating + 0.25 * rolling rating) # rating_updates = false + + # Allows creating, deleting and modifying m3u playlists in the library directories. + # Defaults to being disabled. +# allow_modifying_stored_playlists = 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 (requires "allow_modify_stored_playlists" + # set to true). +# default_playlist_directory = "" } # Local audio output @@ -320,16 +330,6 @@ mpd { # the playlist like MPD does. Note that some dacp clients do not show # the playqueue if playback is stopped. # clear_queue_on_stop_disable = false - - # Allows creating, deleting and modifying m3u playlists in the library directories. - # Defaults to being disabled. -# allow_modifying_stored_playlists = 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 (requires "allow_modify_stored_playlists" - # set to true). -# default_playlist_directory = "" } # SQLite configuration (allows to modify the operation of the SQLite databases) diff --git a/src/conffile.c b/src/conffile.c index 42a26040..51210cee 100644 --- a/src/conffile.c +++ b/src/conffile.c @@ -103,6 +103,8 @@ static cfg_opt_t sec_library[] = CFG_INT("pipe_sample_rate", 44100, CFGF_NONE), CFG_INT("pipe_bits_per_sample", 16, CFGF_NONE), CFG_BOOL("rating_updates", cfg_false, CFGF_NONE), + CFG_BOOL("allow_modifying_stored_playlists", cfg_false, CFGF_NONE), + CFG_STR("default_playlist_directory", NULL, CFGF_NONE), CFG_END() }; @@ -178,8 +180,6 @@ static cfg_opt_t sec_mpd[] = CFG_INT("port", 6600, CFGF_NONE), CFG_INT("http_port", 0, CFGF_NONE), CFG_BOOL("clear_queue_on_stop_disable", cfg_false, CFGF_NONE), - CFG_BOOL("allow_modifying_stored_playlists", cfg_false, CFGF_NONE), - CFG_STR("default_playlist_directory", NULL, CFGF_NONE), CFG_END() }; diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index a2cad527..0e58be13 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -30,6 +30,8 @@ # include #endif +#include +#include #include #include #include @@ -57,6 +59,10 @@ # include "spotify.h" #endif +static bool allow_modifying_stored_playlists; +static char *default_pl_dir; + + /* -------------------------------- HELPERS --------------------------------- */ static inline void @@ -2903,6 +2909,44 @@ jsonapi_reply_library_playlist_tracks(struct httpd_request *hreq) return HTTP_OK; } +static int +jsonapi_reply_queue_save(struct httpd_request *hreq) +{ + const char *param; + int ret = 0; + char buf[PATH_MAX+7]; + char *plsname = NULL; + + if ((param = evhttp_find_header(hreq->query, "name")) == NULL) + { + DPRINTF(E_LOG, L_WEB, "Invalid argument, missing 'name'\n"); + return HTTP_BADREQUEST; + } + + if (!allow_modifying_stored_playlists) + { + DPRINTF(E_LOG, L_WEB, "Saving playlists disabled in cfg, ignoring request\n"); + return 403; + } + + if (access(default_pl_dir, W_OK) < 0) + { + DPRINTF(E_LOG, L_WEB, "Invalid playlist save directory=%s\n", default_pl_dir); + return 403; + } + + plsname = atrim(param); + snprintf(buf, PATH_MAX+7, "/file:%s/%s", default_pl_dir, plsname); + free(plsname); + + ret = library_queue_save(buf); + + if (ret < 0) + return HTTP_INTERNAL; + + return HTTP_OK; +} + static int jsonapi_reply_library_genres(struct httpd_request *hreq) { @@ -3486,6 +3530,7 @@ static struct httpd_uri_map adm_handlers[] = { EVHTTP_REQ_POST, "^/api/queue/items/add$", jsonapi_reply_queue_tracks_add }, { EVHTTP_REQ_PUT, "^/api/queue/items/[[:digit:]]+$", jsonapi_reply_queue_tracks_move }, { EVHTTP_REQ_DELETE, "^/api/queue/items/[[:digit:]]+$", jsonapi_reply_queue_tracks_delete }, + { EVHTTP_REQ_POST, "^/api/queue/save$", jsonapi_reply_queue_save}, { EVHTTP_REQ_GET, "^/api/library/playlists$", jsonapi_reply_library_playlists }, { EVHTTP_REQ_GET, "^/api/library/playlists/[[:digit:]]+$", jsonapi_reply_library_playlist }, @@ -3557,6 +3602,9 @@ jsonapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) case HTTP_BADREQUEST: /* 400 Bad Request */ httpd_send_error(req, status_code, "Bad Request"); break; + case 403: + httpd_send_error(req, status_code, "Forbidden"); + break; case HTTP_NOTFOUND: /* 404 Not Found */ httpd_send_error(req, status_code, "Not Found"); break; @@ -3600,6 +3648,22 @@ jsonapi_init(void) } } + default_pl_dir = NULL; + allow_modifying_stored_playlists = cfg_getbool(cfg_getsec(cfg, "library"), "allow_modifying_stored_playlists"); + if (allow_modifying_stored_playlists) + { + default_pl_dir = cfg_getstr(cfg_getsec(cfg, "library"), "default_playlist_directory"); + if (default_pl_dir == NULL) + { + allow_modifying_stored_playlists = false; + DPRINTF(E_LOG, L_WEB, "Invalid playlist save directory, disabling\n"); + } + else if (access(default_pl_dir, W_OK) < 0) + { + DPRINTF(E_WARN, L_WEB, "Non-writable playlist save directory=%s\n", default_pl_dir); + } + } + return 0; } diff --git a/src/mpd.c b/src/mpd.c index 7240a63b..5ac07544 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -4843,8 +4843,8 @@ int mpd_init(void) } } - allow_modifying_stored_playlists = cfg_getbool(cfg_getsec(cfg, "mpd"), "allow_modifying_stored_playlists"); - pl_dir = cfg_getstr(cfg_getsec(cfg, "mpd"), "default_playlist_directory"); + 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);