From a6d8d55bd42f549214fee3686c418fb35c2daeac Mon Sep 17 00:00:00 2001 From: whatdoineed2do/Ray Date: Thu, 12 Mar 2020 17:10:25 +0000 Subject: [PATCH 1/5] [jsonap] new PUT /api/library/playlists/[[:digit:]]+/tracks endpoint enable playlist tracks play_count updates: 'increment', 'reset', 'played' --- src/httpd_jsonapi.c | 107 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 93 insertions(+), 14 deletions(-) diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 9df5b4b0..5a7e312a 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -658,6 +658,77 @@ query_params_limit_set(struct query_params *query_params, struct httpd_request * return 0; } +static int +play_count_update_byid(int track_id, int play_count, const char *param) +{ + if (strcmp(param, "increment") == 0) + { + db_file_inc_playcount(track_id); + } + else if (strcmp(param, "played") == 0) + { + if (play_count == 0) + db_file_inc_playcount(track_id); + } + else if (strcmp(param, "reset") == 0) + { + db_file_reset_playskip_count(track_id); + } + else + { + DPRINTF(E_WARN, L_WEB, "Ignoring invalid play_count param '%s'\n", param); + return -1; + } + + return 0; +} + +static int +play_count_update(struct httpd_request *hreq, struct query_params *qp) +{ + const char *param; + struct db_media_file_info dbmfi; + int play_count; + int track_id; + int ret; + + param = evhttp_find_header(hreq->query, "play_count"); + if (!param) + { + DPRINTF(E_WARN, L_WEB, "No actions for play_count update\n"); + return HTTP_BADREQUEST; + } + + ret = db_query_start(qp); + if (ret < 0) + goto error; + + while (((ret = db_query_fetch_file(qp, &dbmfi)) == 0) && (dbmfi.id)) + { + ret = safe_atoi32(dbmfi.id, &track_id); + if (ret < 0) + { + DPRINTF(E_WARN, L_WEB, "Invalid track id '%s'\n", dbmfi.id); + continue; + } + + if (safe_atoi32(dbmfi.play_count, &play_count) < 0) + play_count = 0; + + if (play_count_update_byid(track_id, play_count, param) < 0) + { + DPRINTF(E_WARN, L_WEB, "Ignoring invalid play_count value '%s'\n", param); + ret = HTTP_BADREQUEST; + break; + } + } + + error: + db_query_end(qp); + + return ret; +} + /* --------------------------- REPLY HANDLERS ------------------------------- */ /* @@ -3109,20 +3180,7 @@ jsonapi_reply_library_tracks_put_byid(struct httpd_request *hreq) // Update play_count/skip_count param = evhttp_find_header(hreq->query, "play_count"); if (param) - { - if (strcmp(param, "increment") == 0) - { - db_file_inc_playcount(track_id); - } - else if (strcmp(param, "reset") == 0) - { - db_file_reset_playskip_count(track_id); - } - else - { - DPRINTF(E_WARN, L_WEB, "Ignoring invalid play_count value '%s' for track '%d'.\n", param, track_id); - } - } + play_count_update_byid(track_id, 0, param); // Update rating param = evhttp_find_header(hreq->query, "rating"); @@ -3358,6 +3416,26 @@ jsonapi_reply_library_playlist_playlists(struct httpd_request *hreq) return HTTP_OK; } +static int +jsonapi_reply_library_playlist_tracks_put_byid(struct httpd_request *hreq) +{ + int playlist_id; + struct query_params qp; + int ret; + + ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &playlist_id); + if (ret < 0) + return HTTP_INTERNAL; + + memset(&qp, 0, sizeof(struct query_params)); + qp.type = Q_PLITEMS; + qp.id = playlist_id; + + ret = play_count_update(hreq, &qp); + + return ret < 0 ? HTTP_INTERNAL : ret == 0 ? HTTP_OK : ret; +} + static int jsonapi_reply_queue_save(struct httpd_request *hreq) { @@ -3993,6 +4071,7 @@ static struct httpd_uri_map adm_handlers[] = { EVHTTP_REQ_GET, "^/api/library/playlists$", jsonapi_reply_library_playlists }, { EVHTTP_REQ_GET, "^/api/library/playlists/[[:digit:]]+$", jsonapi_reply_library_playlist }, { EVHTTP_REQ_GET, "^/api/library/playlists/[[:digit:]]+/tracks$", jsonapi_reply_library_playlist_tracks }, + { EVHTTP_REQ_PUT, "^/api/library/playlists/[[:digit:]]+/tracks", jsonapi_reply_library_playlist_tracks_put_byid}, // { 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 }, From 42d147af3a1d6729c34de2adaeb2ca334b386e4b Mon Sep 17 00:00:00 2001 From: whatdoineed2do/Ray Date: Thu, 12 Mar 2020 17:58:44 +0000 Subject: [PATCH 2/5] [README] new playlist track update endpoint --- README_JSON_API.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README_JSON_API.md b/README_JSON_API.md index 5bcd03f7..d9c37d98 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 | +| PUT | [/api/library/playlists/{id}/tracks](#update-playlist-tracks) | Update play count 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 | @@ -968,6 +969,34 @@ curl -X GET "http://localhost:3689/api/library/playlists/1/tracks" } ``` +### Update playlist tracks + +Updates the play count for tracks in a playlists + +**Endpoint** + +```http +PUT /api/library/playlists/{id}/tracks +``` + +**Path parameters** + +| Parameter | Value | +| --------------- | -------------------- | +| id | Playlist id | + +**Query parameters** + +| Parameter | Value | +| --------------- | ----------------------------------------------------------- | +| play_count | Either `increment`, `played` or `reset`. `increment` will increment `play_count` and update `time_played`, `played` will be like `increment` but only where `play_count` is 0, `reset` will set `play_count` and `skip_count` to zero and delete `time_played` and `time_skipped` | + + +**Example** + +```shell +curl -X PUT "http://localhost:3689/api/library/playlists/1/tracks?play_count=played" +``` ### List playlists in a playlist folder From 1a912d9808bcfeef94f8404c8d94d9323b6ae792 Mon Sep 17 00:00:00 2001 From: whatdoineed2do/Ray Date: Sat, 14 Mar 2020 20:04:10 +0000 Subject: [PATCH 3/5] [jsonapi] enable album track play_count update --- src/httpd_jsonapi.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 5a7e312a..ab2b29e0 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -3114,6 +3114,25 @@ jsonapi_reply_library_album_tracks(struct httpd_request *hreq) return HTTP_OK; } +static int +jsonapi_reply_library_album_tracks_put_byid(struct httpd_request *hreq) +{ + const char *album_id;; + struct query_params qp; + int ret; + + album_id = hreq->uri_parsed->path_parts[3]; + + memset(&qp, 0, sizeof(struct query_params)); + qp.type = Q_ITEMS; + qp.filter = db_mprintf("(f.songalbumid = %q)", album_id); + + ret = play_count_update(hreq, &qp); + free(qp.filter); + + return ret < 0 ? HTTP_INTERNAL : ret == 0 ? HTTP_OK : ret; +} + static int jsonapi_reply_library_tracks_get_byid(struct httpd_request *hreq) { @@ -4081,6 +4100,7 @@ static struct httpd_uri_map adm_handlers[] = { EVHTTP_REQ_GET, "^/api/library/albums$", jsonapi_reply_library_albums }, { 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_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/genres$", jsonapi_reply_library_genres}, From 286525a85c7b41da9340a3af8021c13f0f34f539 Mon Sep 17 00:00:00 2001 From: whatdoineed2do/Ray Date: Sun, 22 Mar 2020 22:01:04 +0000 Subject: [PATCH 4/5] [db] inc playcount using filter --- src/db.c | 49 +++++++++++++++++++++++++++++++++++++++++++------ src/db.h | 6 ++++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/db.c b/src/db.c index 626f1994..f2e6e3ce 100644 --- a/src/db.c +++ b/src/db.c @@ -2514,10 +2514,10 @@ db_files_get_count(uint32_t *nitems, uint32_t *nstreams, const char *filter) return 0; } -void -db_file_inc_playcount(int id) +static void +db_file_inc_playcount_byfilter(const char *filter) { -#define Q_TMPL "UPDATE files SET play_count = play_count + 1, time_played = %" PRIi64 ", seek = 0 WHERE id = %d;" +#define Q_TMPL "UPDATE files SET play_count = play_count + 1, time_played = %" PRIi64 ", seek = 0 WHERE %s;" /* * Rating calculation is taken from from the beets plugin "mpdstats" (see https://beets.readthedocs.io/en/latest/plugins/mpdstats.html) * and adapted to the forked-daapd rating rage (0 to 100). @@ -2536,14 +2536,16 @@ db_file_inc_playcount(int id) "UPDATE files "\ " SET play_count = play_count + 1, time_played = %" PRIi64 ", seek = 0, "\ " rating = CAST(((play_count + 1.0) / (play_count + skip_count + 2.0) * 100 * 0.75) + ((rating + ((100.0 - rating) / 2.0)) * 0.25) AS INT)" \ - " WHERE id = %d;" + " WHERE %s;" char *query; int ret; + if (db_rating_updates) - query = sqlite3_mprintf(Q_TMPL_WITH_RATING, (int64_t)time(NULL), id); + query = sqlite3_mprintf(Q_TMPL_WITH_RATING, (int64_t)time(NULL), filter); else - query = sqlite3_mprintf(Q_TMPL, (int64_t)time(NULL), id); + query = sqlite3_mprintf(Q_TMPL, (int64_t)time(NULL), filter); + if (!query) { DPRINTF(E_LOG, L_DB, "Out of memory for query string\n"); @@ -2558,6 +2560,41 @@ db_file_inc_playcount(int id) #undef Q_TMPL_WITH_RATING } +void +db_file_inc_playcount_byplid(int id, bool only_unplayed) +{ + char *filter; + + filter = sqlite3_mprintf("path IN (SELECT filepath FROM playlistitems WHERE playlistid = %d) %s", + id, only_unplayed ? "AND play_count = 0" : ""); + + db_file_inc_playcount_byfilter(filter); + sqlite3_free(filter); +} + +void +db_file_inc_playcount_bysongalbumid(int64_t id, bool only_unplayed) +{ + char *filter; + + filter = sqlite3_mprintf("songalbumid = %" PRIi64 " %s", + id, only_unplayed ? "AND play_count = 0" : ""); + + db_file_inc_playcount_byfilter(filter); + sqlite3_free(filter); +} + +void +db_file_inc_playcount(int id) +{ + char *filter; + + filter = sqlite3_mprintf("id = %d", id); + + db_file_inc_playcount_byfilter(filter); + sqlite3_free(filter); +} + void db_file_inc_skipcount(int id) { diff --git a/src/db.h b/src/db.h index 740a3fd7..b1f5a1a8 100644 --- a/src/db.h +++ b/src/db.h @@ -580,6 +580,12 @@ db_files_get_count(uint32_t *nitems, uint32_t *nstreams, const char *filter); void db_file_inc_playcount(int id); +void +db_file_inc_playcount_byplid(int id, bool only_unplayed); + +void +db_file_inc_playcount_bysongalbumid(int64_t id, bool only_unplayed); + void db_file_inc_skipcount(int id); From a31430f67164aba06acb3220b4c6300410044133 Mon Sep 17 00:00:00 2001 From: whatdoineed2do/Ray Date: Mon, 23 Mar 2020 12:12:56 +0000 Subject: [PATCH 5/5] [jsonapi] album/playlist inc uses db specific methods --- src/httpd_jsonapi.c | 141 +++++++++++++++++--------------------------- 1 file changed, 54 insertions(+), 87 deletions(-) diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index ab2b29e0..f24a61d4 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -658,77 +658,6 @@ query_params_limit_set(struct query_params *query_params, struct httpd_request * return 0; } -static int -play_count_update_byid(int track_id, int play_count, const char *param) -{ - if (strcmp(param, "increment") == 0) - { - db_file_inc_playcount(track_id); - } - else if (strcmp(param, "played") == 0) - { - if (play_count == 0) - db_file_inc_playcount(track_id); - } - else if (strcmp(param, "reset") == 0) - { - db_file_reset_playskip_count(track_id); - } - else - { - DPRINTF(E_WARN, L_WEB, "Ignoring invalid play_count param '%s'\n", param); - return -1; - } - - return 0; -} - -static int -play_count_update(struct httpd_request *hreq, struct query_params *qp) -{ - const char *param; - struct db_media_file_info dbmfi; - int play_count; - int track_id; - int ret; - - param = evhttp_find_header(hreq->query, "play_count"); - if (!param) - { - DPRINTF(E_WARN, L_WEB, "No actions for play_count update\n"); - return HTTP_BADREQUEST; - } - - ret = db_query_start(qp); - if (ret < 0) - goto error; - - while (((ret = db_query_fetch_file(qp, &dbmfi)) == 0) && (dbmfi.id)) - { - ret = safe_atoi32(dbmfi.id, &track_id); - if (ret < 0) - { - DPRINTF(E_WARN, L_WEB, "Invalid track id '%s'\n", dbmfi.id); - continue; - } - - if (safe_atoi32(dbmfi.play_count, &play_count) < 0) - play_count = 0; - - if (play_count_update_byid(track_id, play_count, param) < 0) - { - DPRINTF(E_WARN, L_WEB, "Ignoring invalid play_count value '%s'\n", param); - ret = HTTP_BADREQUEST; - break; - } - } - - error: - db_query_end(qp); - - return ret; -} - /* --------------------------- REPLY HANDLERS ------------------------------- */ /* @@ -3117,20 +3046,33 @@ jsonapi_reply_library_album_tracks(struct httpd_request *hreq) static int jsonapi_reply_library_album_tracks_put_byid(struct httpd_request *hreq) { - const char *album_id;; - struct query_params qp; + const char *param; + int64_t album_id;; int ret; - album_id = hreq->uri_parsed->path_parts[3]; + ret = safe_atoi64(hreq->uri_parsed->path_parts[3], &album_id); + if (ret < 0) + return HTTP_INTERNAL; - memset(&qp, 0, sizeof(struct query_params)); - qp.type = Q_ITEMS; - qp.filter = db_mprintf("(f.songalbumid = %q)", album_id); + param = evhttp_find_header(hreq->query, "play_count"); + if (!param) + return HTTP_BADREQUEST; - ret = play_count_update(hreq, &qp); - free(qp.filter); + if (strcmp(param, "increment") == 0) + { + db_file_inc_playcount_bysongalbumid(album_id, false); + } + else if (strcmp(param, "played") == 0) + { + db_file_inc_playcount_bysongalbumid(album_id, true); + } + else + { + DPRINTF(E_WARN, L_WEB, "Ignoring invalid play_count param '%s'\n", param); + return HTTP_BADREQUEST; + } - return ret < 0 ? HTTP_INTERNAL : ret == 0 ? HTTP_OK : ret; + return HTTP_OK; } static int @@ -3199,7 +3141,20 @@ jsonapi_reply_library_tracks_put_byid(struct httpd_request *hreq) // Update play_count/skip_count param = evhttp_find_header(hreq->query, "play_count"); if (param) - play_count_update_byid(track_id, 0, param); + { + if (strcmp(param, "increment") == 0) + { + db_file_inc_playcount(track_id); + } + else if (strcmp(param, "reset") == 0) + { + db_file_reset_playskip_count(track_id); + } + else + { + DPRINTF(E_WARN, L_WEB, "Ignoring invalid play_count value '%s' for track '%d'.\n", param, track_id); + } + } // Update rating param = evhttp_find_header(hreq->query, "rating"); @@ -3438,21 +3393,33 @@ jsonapi_reply_library_playlist_playlists(struct httpd_request *hreq) static int jsonapi_reply_library_playlist_tracks_put_byid(struct httpd_request *hreq) { + const char *param; int playlist_id; - struct query_params qp; int ret; ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &playlist_id); if (ret < 0) return HTTP_INTERNAL; - memset(&qp, 0, sizeof(struct query_params)); - qp.type = Q_PLITEMS; - qp.id = playlist_id; + param = evhttp_find_header(hreq->query, "play_count"); + if (!param) + return HTTP_BADREQUEST; - ret = play_count_update(hreq, &qp); + if (strcmp(param, "increment") == 0) + { + db_file_inc_playcount_byplid(playlist_id, false); + } + else if (strcmp(param, "played") == 0) + { + db_file_inc_playcount_byplid(playlist_id, true); + } + else + { + DPRINTF(E_WARN, L_WEB, "Ignoring invalid play_count param '%s'\n", param); + return HTTP_BADREQUEST; + } - return ret < 0 ? HTTP_INTERNAL : ret == 0 ? HTTP_OK : ret; + return HTTP_OK; } static int