From d28f7f43b79e8ba1f95c3f57e83a23f86240fc0c Mon Sep 17 00:00:00 2001 From: whatdoineed2do/Ray Date: Wed, 10 Apr 2019 20:04:00 +0100 Subject: [PATCH 1/5] [jsonapi,mpd,conf] save playlist via JSON api - new endpoint api/queue/save?name= to .m3u via library_save() - refact for common cfg for playlist save options --- forked-daapd.conf.in | 20 +++++++------- src/conffile.c | 4 +-- src/httpd_jsonapi.c | 64 ++++++++++++++++++++++++++++++++++++++++++++ src/mpd.c | 4 +-- 4 files changed, 78 insertions(+), 14 deletions(-) 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); From ed9f05ac30c39c317747f561adf901621a667975 Mon Sep 17 00:00:00 2001 From: whatdoineed2do/Ray Date: Thu, 11 Apr 2019 20:25:05 +0100 Subject: [PATCH 2/5] [web-src] 'save playlist' from PageQueue functionality --- .../components/ModalDialogPlaylistSave.vue | 53 +++++++++++++++++++ web-src/src/pages/PageQueue.vue | 15 +++++- web-src/src/webapi/index.js | 7 +++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 web-src/src/components/ModalDialogPlaylistSave.vue diff --git a/web-src/src/components/ModalDialogPlaylistSave.vue b/web-src/src/components/ModalDialogPlaylistSave.vue new file mode 100644 index 00000000..6d4f4d54 --- /dev/null +++ b/web-src/src/components/ModalDialogPlaylistSave.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/web-src/src/pages/PageQueue.vue b/web-src/src/pages/PageQueue.vue index a468b3d5..63159b12 100644 --- a/web-src/src/pages/PageQueue.vue +++ b/web-src/src/pages/PageQueue.vue @@ -38,6 +38,12 @@ Clear + + + + + Save + @@ -68,13 +75,14 @@ import ContentWithHeading from '@/templates/ContentWithHeading' import ListItemQueueItem from '@/components/ListItemQueueItem' import ModalDialogQueueItem from '@/components/ModalDialogQueueItem' import ModalDialogAddUrlStream from '@/components/ModalDialogAddUrlStream' +import ModalDialogPlaylistSave from '@/components/ModalDialogPlaylistSave' import webapi from '@/webapi' import * as types from '@/store/mutation_types' import draggable from 'vuedraggable' export default { name: 'PageQueue', - components: { ContentWithHeading, ListItemQueueItem, draggable, ModalDialogQueueItem, ModalDialogAddUrlStream }, + components: { ContentWithHeading, ListItemQueueItem, draggable, ModalDialogQueueItem, ModalDialogAddUrlStream, ModalDialogPlaylistSave }, data () { return { @@ -82,6 +90,7 @@ export default { show_details_modal: false, show_url_modal: false, + show_pls_save_modal: false, selected_item: {} } }, @@ -135,6 +144,10 @@ export default { open_add_stream_dialog: function (item) { this.show_url_modal = true + }, + + save_dialog: function (item) { + this.show_pls_save_modal = true } } } diff --git a/web-src/src/webapi/index.js b/web-src/src/webapi/index.js index 82f49c9f..93fe6d75 100644 --- a/web-src/src/webapi/index.js +++ b/web-src/src/webapi/index.js @@ -83,6 +83,13 @@ export default { }) }, + queue_save_playlist (name) { + return axios.post('/api/queue/save', undefined, { params: { 'name': name } }).then((response) => { + store.dispatch('add_notification', { text: 'playlist saved', type: 'info', timeout: 2000 }) + return Promise.resolve(response) + }) + }, + player_status () { return axios.get('/api/player') }, From 2b4b9c747f5cfb6fb45952b0af286389abe986f2 Mon Sep 17 00:00:00 2001 From: chme Date: Thu, 30 May 2019 08:46:36 +0200 Subject: [PATCH 3/5] [jsonapi] Add config options for saving playlists to config reply --- src/httpd_jsonapi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 0e58be13..5c56709c 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -712,6 +712,10 @@ jsonapi_reply_config(struct httpd_request *hreq) } json_object_object_add(jreply, "directories", directories); + // Config for creating/modifying stored playlists + json_object_object_add(jreply, "allow_modifying_stored_playlists", json_object_new_boolean(cfg_getbool(lib, "allow_modifying_stored_playlists"))); + json_object_object_add(jreply, "default_playlist_directory", json_object_new_string(cfg_getstr(lib, "default_playlist_directory"))); + CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(jreply))); jparse_free(jreply); From 3650a7573fd1cf1ca272b8caa91d081ecc02a05e Mon Sep 17 00:00:00 2001 From: chme Date: Thu, 30 May 2019 09:05:27 +0200 Subject: [PATCH 4/5] [web-src] Restyling of queue save modal dialog --- .../components/ModalDialogPlaylistSave.vue | 77 ++++++++++++++----- web-src/src/pages/PageQueue.vue | 7 +- web-src/src/webapi/index.js | 2 +- 3 files changed, 63 insertions(+), 23 deletions(-) diff --git a/web-src/src/components/ModalDialogPlaylistSave.vue b/web-src/src/components/ModalDialogPlaylistSave.vue index 6d4f4d54..c31b64b9 100644 --- a/web-src/src/components/ModalDialogPlaylistSave.vue +++ b/web-src/src/components/ModalDialogPlaylistSave.vue @@ -3,23 +3,37 @@