From 36ebf7d06c62e599ef91bf44e8ca41e797dc6dfb Mon Sep 17 00:00:00 2001 From: chme Date: Sun, 23 Feb 2020 11:40:39 +0100 Subject: [PATCH 1/2] [jsonapi] Add support for playlist folders --- src/db.c | 14 ++++++++++ src/db.h | 4 +++ src/httpd_jsonapi.c | 67 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/db.c b/src/db.c index 9fd0fe60..b0f19ab3 100644 --- a/src/db.c +++ b/src/db.c @@ -510,6 +510,20 @@ db_data_kind_label(enum data_kind data_kind) return NULL; } +/* Keep in sync with enum pl_type */ +static char *pl_type_label[] = { "special", "folder", "smart", "plain" }; + +const char * +db_pl_type_label(enum pl_type pl_type) +{ + if (pl_type < ARRAY_SIZE(pl_type_label)) + { + return pl_type_label[pl_type]; + } + + return NULL; +} + /* Shuffle RNG state */ struct rng_ctx shuffle_rng; diff --git a/src/db.h b/src/db.h index c900057e..740a3fd7 100644 --- a/src/db.h +++ b/src/db.h @@ -227,6 +227,7 @@ struct media_file_info { #define mfi_offsetof(field) offsetof(struct media_file_info, field) +/* Keep in sync with pl_type_label[] */ /* PL_SPECIAL value must be in sync with type value in Q_PL* in db_init.c */ enum pl_type { PL_SPECIAL = 0, @@ -236,6 +237,9 @@ enum pl_type { PL_MAX, }; +const char * +db_pl_type_label(enum pl_type pl_type); + struct playlist_info { uint32_t id; /* integer id (miid) */ char *title; /* playlist name as displayed in iTunes (minm) */ diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 0787d777..f8ff402f 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -281,9 +281,14 @@ playlist_to_json(struct db_playlist_info *dbpli) safe_json_add_int_from_string(item, "id", dbpli->id); safe_json_add_string(item, "name", dbpli->title); safe_json_add_string(item, "path", dbpli->path); + safe_json_add_string(item, "parent_id", dbpli->parent_id); ret = safe_atoi32(dbpli->type, &intval); if (ret == 0) - json_object_object_add(item, "smart_playlist", json_object_new_boolean(intval == PL_SMART)); + { + safe_json_add_string(item, "type", db_pl_type_label(intval)); + json_object_object_add(item, "smart_playlist", json_object_new_boolean(intval == PL_SMART)); + json_object_object_add(item, "folder", json_object_new_boolean(intval == PL_FOLDER)); + } ret = snprintf(uri, sizeof(uri), "%s:%s:%s", "library", "playlist", dbpli->id); if (ret < sizeof(uri)) @@ -3265,6 +3270,65 @@ jsonapi_reply_library_playlist_tracks(struct httpd_request *hreq) return HTTP_OK; } +static int +jsonapi_reply_library_playlist_playlists(struct httpd_request *hreq) +{ + struct query_params query_params; + json_object *reply; + json_object *items; + int playlist_id; + int total; + int ret = 0; + + if (!is_modified(hreq->req, DB_ADMIN_DB_MODIFIED)) + return HTTP_NOTMODIFIED; + + + ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &playlist_id); + if (ret < 0) + { + DPRINTF(E_LOG, L_WEB, "No valid playlist id given '%s'\n", hreq->uri_parsed->path); + + return HTTP_BADREQUEST; + } + + reply = json_object_new_object(); + items = json_object_new_array(); + json_object_object_add(reply, "items", items); + + memset(&query_params, 0, sizeof(struct query_params)); + + ret = query_params_limit_set(&query_params, hreq); + if (ret < 0) + goto error; + + query_params.type = Q_PL; + query_params.sort = S_PLAYLIST; + query_params.filter = db_mprintf("f.parent_id = %d AND (f.type = %d OR f.type = %d OR f.type = %d)", + playlist_id, PL_PLAIN, PL_SMART, PL_FOLDER); + + ret = fetch_playlists(&query_params, items, &total); + if (ret < 0) + goto error; + + json_object_object_add(reply, "total", json_object_new_int(total)); + json_object_object_add(reply, "offset", json_object_new_int(query_params.offset)); + json_object_object_add(reply, "limit", json_object_new_int(query_params.limit)); + + ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply)); + if (ret < 0) + DPRINTF(E_LOG, L_WEB, "playlist tracks: Couldn't add tracks to response buffer.\n"); + + error: + free_query_params(&query_params, 1); + jparse_free(reply); + + if (ret < 0) + return HTTP_INTERNAL; + + return HTTP_OK; +} + static int jsonapi_reply_queue_save(struct httpd_request *hreq) { @@ -3902,6 +3966,7 @@ static struct httpd_uri_map adm_handlers[] = { EVHTTP_REQ_GET, "^/api/library/playlists/[[:digit:]]+/tracks$", jsonapi_reply_library_playlist_tracks }, // { EVHTTP_REQ_POST, "^/api/library/playlists/[[:digit:]]+/tracks$", jsonapi_reply_library_playlists_tracks }, // { EVHTTP_REQ_DELETE, "^/api/library/playlists/[[:digit:]]+$", jsonapi_reply_library_playlist_tracks }, + { EVHTTP_REQ_GET, "^/api/library/playlists/[[:digit:]]+/playlists", jsonapi_reply_library_playlist_playlists }, { EVHTTP_REQ_GET, "^/api/library/artists$", jsonapi_reply_library_artists }, { EVHTTP_REQ_GET, "^/api/library/artists/[[:digit:]]+$", jsonapi_reply_library_artist }, { EVHTTP_REQ_GET, "^/api/library/artists/[[:digit:]]+/albums$", jsonapi_reply_library_artist_albums }, From 73a9ac16d69d669a78481f27caa6cd5f342bbac9 Mon Sep 17 00:00:00 2001 From: chme Date: Sun, 23 Feb 2020 11:40:44 +0100 Subject: [PATCH 2/2] [README] Update JSON API docs with playlist folders support --- README_JSON_API.md | 78 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/README_JSON_API.md b/README_JSON_API.md index 316c47f7..5bcd03f7 100644 --- a/README_JSON_API.md +++ b/README_JSON_API.md @@ -747,6 +747,7 @@ curl -X PUT "http://localhost:3689/api/queue/items/2" | GET | [/api/library/playlists](#list-playlists) | Get a list of playlists | | GET | [/api/library/playlists/{id}](#get-a-playlist) | Get a playlist | | GET | [/api/library/playlists/{id}/tracks](#list-playlist-tracks) | Get list of tracks for a playlist | +| GET | [/api/library/playlists/{id}/playlists](#list-playlists-in-a-playlist-folder) | Get list of playlists for a playlist folder | | GET | [/api/library/artists](#list-artists) | Get a list of artists | | GET | [/api/library/artists/{id}](#get-an-artist) | Get an artist | | GET | [/api/library/artists/{id}/albums](#list-artist-albums) | Get list of albums for an artist | @@ -807,7 +808,7 @@ curl -X GET "http://localhost:3689/api/library" ### List playlists -Lists the playlists in your library +Lists all playlists in your library (does not return playlist folders) **Endpoint** @@ -968,6 +969,76 @@ curl -X GET "http://localhost:3689/api/library/playlists/1/tracks" ``` +### List playlists in a playlist folder + +Lists the playlists in a playlist folder + +**Note**: The root playlist folder has `id` 0. + +**Endpoint** + +```http +GET /api/library/playlists/{id}/playlists +``` + +**Path parameters** + +| Parameter | Value | +| --------------- | -------------------- | +| id | Playlist id | + +**Query parameters** + +| Parameter | Value | +| --------------- | ----------------------------------------------------------- | +| offset | *(Optional)* Offset of the first playlist to return | +| limit | *(Optional)* Maximum number of playlist to return | + +**Response** + +| Key | Type | Value | +| --------------- | -------- | ------------------------------------------------ | +| items | array | Array of [`playlist`](#playlist-object) objects | +| total | integer | Total number of playlists in the playlist folder | +| offset | integer | Requested offset of the first playlist | +| limit | integer | Requested maximum number of playlist | + + +**Example** + +```shell +curl -X GET "http://localhost:3689/api/library/playlists/0/tracks" +``` + +```json +{ + "items": [ + { + "id": 11, + "name": "Spotify", + "path": "spotify:playlistfolder", + "parent_id": "0", + "smart_playlist": false, + "folder": true, + "uri": "library:playlist:11" + }, + { + "id": 8, + "name": "bytefm", + "path": "/srv/music/Playlists/bytefm.m3u", + "parent_id": "0", + "smart_playlist": false, + "folder": false, + "uri": "library:playlist:8" + } + ], + "total": 2, + "offset": 0, + "limit": -1 +} +``` + + ### List artists Lists the artists in your library @@ -2203,7 +2274,10 @@ curl --include \ | id | string | Playlist id | | name | string | Playlist name | | path | string | Path | -| smart_playlist | boolean | `true` if playlist is a smart playlist | +| parent_id | integer | Playlist id of the parent (folder) playlist | +| type | string | Type of this playlist: `special`, `folder`, `smart`, `plain` | +| smart_playlist | boolean | `true` if playlist is a smart playlist | +| folder | boolean | `true` if it is a playlist folder | | uri | string | Resource identifier |