From d2ee9f20fbfd451b78528da7f60dc9270184c04e Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 9 Oct 2021 00:49:29 +0200 Subject: [PATCH] [jsonapi] Add PUT /api/library/tracks endpoint (update multiple tracks) Also fix error in json doc (track->id type) --- README_JSON_API.md | 30 +++++++++++-- src/httpd_jsonapi.c | 101 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 3 deletions(-) diff --git a/README_JSON_API.md b/README_JSON_API.md index 83ece4b6..43cb3ed4 100644 --- a/README_JSON_API.md +++ b/README_JSON_API.md @@ -791,7 +791,8 @@ curl -X PUT "http://localhost:3689/api/queue/items/2" | GET | [/api/library/albums/{id}/tracks](#list-album-tracks) | Get list of tracks for an album | | GET | [/api/library/tracks/{id}](#get-a-track) | Get a track | | GET | [/api/library/tracks/{id}/playlists](#list-playlists-for-a-track) | Get list of playlists for a track | -| PUT | [/api/library/tracks/{id}](#update-track-properties) | Update track properties | +| PUT | [/api/library/tracks](#update-track-properties) | Update multiple track properties | +| PUT | [/api/library/tracks/{id}](#update-track-properties) | Update single track properties | | GET | [/api/library/genres](#list-genres) | Get list of genres | | GET | [/api/library/count](#get-count-of-tracks-artists-and-albums) | Get count of tracks, artists and albums | | GET | [/api/library/files](#list-local-directories) | Get list of directories in the local library | @@ -1602,7 +1603,30 @@ curl -X GET "http://localhost:3689/api/library/tracks/27/playlists" ### Update track properties -Change properties of a specific track (supported properties are "rating", "play_count" and "usermark") +Change properties of one or more tracks (supported properties are "rating", "play_count" and "usermark") + +**Endpoint** + +```http +PUT /api/library/tracks +``` + +**Body parameters** + +| Parameter | Type | Value | +| --------------- | -------- | ----------------------- | +| tracks | array | Array of track objects | + + +**Response** + +On success returns the HTTP `204 No Content` success status response code. + +**Example** + +```shell +curl -X PUT -d '{ "tracks": [ { "id": 1, "rating": 100, "usermark": 4 }, { "id": 2, "usermark": 3 } ] }' "http://localhost:3689/api/library/tracks" +``` **Endpoint** @@ -2536,7 +2560,7 @@ curl --include \ | Key | Type | Value | | ------------------ | -------- | ----------------------------------------- | -| id | string | Track id | +| id | integer | Track id | | title | string | Title | | title_sort | string | Sort title | | artist | string | Track artist name | diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 8b97ac1c..190d28a8 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -334,6 +334,25 @@ track_to_json(struct db_media_file_info *dbmfi) return item; } +// TODO Only partially implemented. A full implementation should use a mapping +// table, which should also be used above in track_to_json(). It should also +// return errors if there are incorrect/mispelled fields, but not sure how to +// walk a json object with json-c. +static int +json_to_track(struct media_file_info *mfi, json_object *json) +{ + if (jparse_contains_key(json, "id", json_type_int)) + mfi->id = jparse_int_from_obj(json, "id"); + if (jparse_contains_key(json, "usermark", json_type_int)) + mfi->usermark = jparse_int_from_obj(json, "usermark"); + if (jparse_contains_key(json, "rating", json_type_int)) + mfi->rating = jparse_int_from_obj(json, "rating"); + if (jparse_contains_key(json, "play_count", json_type_int)) + mfi->play_count = jparse_int_from_obj(json, "play_count"); + + return HTTP_OK; +} + static json_object * playlist_to_json(struct db_playlist_info *dbpli) { @@ -3293,6 +3312,87 @@ jsonapi_reply_library_tracks_get_byid(struct httpd_request *hreq) return HTTP_OK; } +static int +jsonapi_reply_library_tracks_put(struct httpd_request *hreq) +{ + struct evbuffer *in_evbuf; + json_object *request = NULL; + json_object *tracks; + json_object *track = NULL; + struct media_file_info *mfi = NULL; + int ret; + int err; + int32_t track_id; + int i; + + in_evbuf = evhttp_request_get_input_buffer(hreq->req); + request = jparse_obj_from_evbuffer(in_evbuf); + if (!request) + { + DPRINTF(E_LOG, L_WEB, "Failed to read json tracks request\n"); + err = HTTP_BADREQUEST; + goto error; + } + + ret = jparse_array_from_obj(request, "tracks", &tracks); + if (ret < 0) + { + DPRINTF(E_LOG, L_WEB, "Failed to parse json tracks request\n"); + err = HTTP_BADREQUEST; + goto error; + } + + db_transaction_begin(); + i = 0; + while ((track = json_object_array_get_idx(tracks, i))) + { + track_id = jparse_int_from_obj(track, "id"); + if (track_id == 0) + { + DPRINTF(E_LOG, L_WEB, "Invalid or missing track id in json tracks request\n"); + err = HTTP_BADREQUEST; + goto error; + } + + mfi = db_file_fetch_byid(track_id); + if (!mfi) + { + DPRINTF(E_LOG, L_WEB, "Unknown track_id %d in json tracks request\n", track_id); + err = HTTP_NOTFOUND; + goto error; + } + + ret = json_to_track(mfi, track); + if (ret != HTTP_OK) + { + err = ret; + goto error; + } + + ret = library_media_save(mfi); + if (ret < 0) + { + err = HTTP_INTERNAL; + goto error; + } + + free_mfi(mfi, 0); + mfi = NULL; + i++; + } + + jparse_free(request); + db_transaction_end(); + return HTTP_OK; + + error: + jparse_free(request); + if (track) + db_transaction_rollback(); + free_mfi(mfi, 0); + return err; +} + static int jsonapi_reply_library_tracks_put_byid(struct httpd_request *hreq) { @@ -4424,6 +4524,7 @@ static struct httpd_uri_map adm_handlers[] = { EVHTTP_REQ_GET, "^/api/library/albums/[[:digit:]]+$", jsonapi_reply_library_album }, { EVHTTP_REQ_GET, "^/api/library/albums/[[:digit:]]+/tracks$", jsonapi_reply_library_album_tracks }, { EVHTTP_REQ_PUT, "^/api/library/albums/[[:digit:]]+/tracks$", jsonapi_reply_library_album_tracks_put_byid }, + { EVHTTP_REQ_PUT, "^/api/library/tracks$", jsonapi_reply_library_tracks_put }, { EVHTTP_REQ_GET, "^/api/library/tracks/[[:digit:]]+$", jsonapi_reply_library_tracks_get_byid }, { EVHTTP_REQ_PUT, "^/api/library/tracks/[[:digit:]]+$", jsonapi_reply_library_tracks_put_byid }, { EVHTTP_REQ_GET, "^/api/library/tracks/[[:digit:]]+/playlists$", jsonapi_reply_library_track_playlists },