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 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); diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 9df5b4b0..f24a61d4 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -3043,6 +3043,38 @@ 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 *param; + int64_t album_id;; + int ret; + + ret = safe_atoi64(hreq->uri_parsed->path_parts[3], &album_id); + if (ret < 0) + return HTTP_INTERNAL; + + param = evhttp_find_header(hreq->query, "play_count"); + if (!param) + return HTTP_BADREQUEST; + + 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 HTTP_OK; +} + static int jsonapi_reply_library_tracks_get_byid(struct httpd_request *hreq) { @@ -3358,6 +3390,38 @@ 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) +{ + const char *param; + int playlist_id; + int ret; + + ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &playlist_id); + if (ret < 0) + return HTTP_INTERNAL; + + param = evhttp_find_header(hreq->query, "play_count"); + if (!param) + return HTTP_BADREQUEST; + + 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 HTTP_OK; +} + static int jsonapi_reply_queue_save(struct httpd_request *hreq) { @@ -3993,6 +4057,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 }, @@ -4002,6 +4067,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},