diff --git a/README_JSON_API.md b/README_JSON_API.md index 20071e15..83ece4b6 100644 --- a/README_JSON_API.md +++ b/README_JSON_API.md @@ -791,7 +791,7 @@ 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 a tracks properties (rating, play_count) | +| PUT | [/api/library/tracks/{id}](#update-track-properties) | Update 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 | @@ -1528,6 +1528,7 @@ curl -X GET "http://localhost:3689/api/library/track/1" "disc_number": 1, "length_ms": 223170, "rating": 0, + "usermark": 0, "play_count": 0, "skip_count": 0, "time_added": "2019-01-20T11:58:29Z", @@ -1601,7 +1602,7 @@ curl -X GET "http://localhost:3689/api/library/tracks/27/playlists" ### Update track properties -Change properties of a specific track (supported properties are "rating" and "play_count") +Change properties of a specific track (supported properties are "rating", "play_count" and "usermark") **Endpoint** @@ -1621,6 +1622,7 @@ PUT /api/library/tracks/{id} | --------------- | ----------------------------------------------------------- | | rating | The new rating (0 - 100) | | play_count | Either `increment` or `reset`. `increment` will increment `play_count` and update `time_played`, `reset` will set `play_count` and `skip_count` to zero and delete `time_played` and `time_skipped` | +| usermark | The new usermark (>= 0) | **Response** @@ -2565,6 +2567,7 @@ curl --include \ | path | string | Path | | uri | string | Resource identifier | | artwork_url | string | *(optional)* [Artwork url](#artwork-urls) | +| usermark | integer | User review marking of track (ranges from 0) | ### `paging` object diff --git a/src/SMARTPL.g b/src/SMARTPL.g index 127cea42..7e055aab 100644 --- a/src/SMARTPL.g +++ b/src/SMARTPL.g @@ -102,6 +102,7 @@ INTTAG : 'play_count' | 'bits_per_sample' | 'samplerate' | 'song_length' + | 'usermark' ; DATETAG : 'time_added' diff --git a/src/db.c b/src/db.c index 73a40809..644223b8 100644 --- a/src/db.c +++ b/src/db.c @@ -227,6 +227,7 @@ static const struct col_type_map mfi_cols_map[] = { "album_artist_sort", mfi_offsetof(album_artist_sort), DB_TYPE_STRING, DB_FIXUP_ALBUM_ARTIST_SORT }, { "composer_sort", mfi_offsetof(composer_sort), DB_TYPE_STRING, DB_FIXUP_COMPOSER_SORT }, { "channels", mfi_offsetof(channels), DB_TYPE_INT }, + { "usermark", mfi_offsetof(usermark), DB_TYPE_INT }, }; /* This list must be kept in sync with @@ -365,6 +366,7 @@ static const ssize_t dbmfi_cols_map[] = dbmfi_offsetof(album_artist_sort), dbmfi_offsetof(composer_sort), dbmfi_offsetof(channels), + dbmfi_offsetof(usermark), }; /* This list must be kept in sync with @@ -3320,6 +3322,27 @@ db_file_rating_update_byvirtualpath(const char *virtual_path, uint32_t rating) #undef Q_TMPL } +int +db_file_usermark_update_byid(uint32_t id, uint32_t usermark) +{ +#define Q_TMPL "UPDATE files SET usermark = %d WHERE id = %d;" + char *query; + int ret; + + query = sqlite3_mprintf(Q_TMPL, usermark, id); + + ret = db_query_run(query, 1, 0); + + if (ret == 0) + { + db_admin_setint64(DB_ADMIN_DB_MODIFIED, (int64_t) time(NULL)); + listener_notify(LISTENER_UPDATE); + } + + return ((ret < 0) ? -1 : sqlite3_changes(hdl)); +#undef Q_TMPL +} + void db_file_delete_bypath(const char *path) { diff --git a/src/db.h b/src/db.h index f23f3097..dda94c1e 100644 --- a/src/db.h +++ b/src/db.h @@ -141,6 +141,16 @@ enum data_kind { const char * db_data_kind_label(enum data_kind data_kind); + +/* Indicates user marked status on a track - values can be bitwise enumerated */ +enum usermark { + USERMARK_NA = 0, + USERMARK_DELETE = 1, + USERMARK_REXCODE = 2, + USERMARK_REVIEW = 4, +}; + + /* Note that fields marked as integers in the metadata map in filescanner_ffmpeg must be uint32_t here */ struct media_file_info { uint32_t id; @@ -198,6 +208,7 @@ struct media_file_info { uint32_t time_skipped; int64_t disabled; // Long because it stores up to INOTIFY_FAKE_COOKIE + uint32_t usermark; // See enum user_mark { } uint64_t sample_count; //TODO [unused] sample count is never set and therefor always 0 char *codectype; /* song.codectype, 4 chars max (32 bits) */ @@ -393,6 +404,7 @@ struct db_media_file_info { char *album_artist_sort; char *composer_sort; char *channels; + char *usermark; }; #define dbmfi_offsetof(field) offsetof(struct db_media_file_info, field) @@ -651,6 +663,9 @@ db_file_seek_update(int id, uint32_t seek); int db_file_rating_update_byid(uint32_t id, uint32_t rating); +int +db_file_usermark_update_byid(uint32_t id, uint32_t usermark); + int db_file_rating_update_byvirtualpath(const char *virtual_path, uint32_t rating); diff --git a/src/db_init.c b/src/db_init.c index 5e358c7a..f49d846e 100644 --- a/src/db_init.c +++ b/src/db_init.c @@ -96,7 +96,8 @@ " album_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ " album_artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ " composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \ - " channels INTEGER DEFAULT 0" \ + " channels INTEGER DEFAULT 0," \ + " usermark INTEGER DEFAULT 0" \ ");" #define T_PL \ diff --git a/src/db_init.h b/src/db_init.h index 5a12b602..f196770b 100644 --- a/src/db_init.h +++ b/src/db_init.h @@ -26,7 +26,7 @@ * is a major upgrade. In other words minor version upgrades permit downgrading * the server after the database was upgraded. */ #define SCHEMA_VERSION_MAJOR 21 -#define SCHEMA_VERSION_MINOR 06 +#define SCHEMA_VERSION_MINOR 07 int db_init_indices(sqlite3 *hdl); diff --git a/src/db_upgrade.c b/src/db_upgrade.c index efd4c87d..f48c99b7 100644 --- a/src/db_upgrade.c +++ b/src/db_upgrade.c @@ -1160,6 +1160,19 @@ static const struct db_upgrade_query db_upgrade_v2106_queries[] = { U_v2106_SCVER_MINOR, "set schema_version_minor to 06" }, }; +/* ---------------------------- 21.06 -> 21.07 ------------------------------ */ +#define U_v2107_ALTER_FILES_USERMARK \ + "ALTER TABLE files ADD COLUMN usermark INTEGER DEFAULT 0;" +#define U_v2107_SCVER_MINOR \ + "UPDATE admin SET value = '07' WHERE key = 'schema_version_minor';" + +static const struct db_upgrade_query db_upgrade_v2107_queries[] = + { + { U_v2107_ALTER_FILES_USERMARK, "update files adding usermark" }, + + { U_v2107_SCVER_MINOR, "set schema_version_minor to 07" }, + }; + /* -------------------------- Main upgrade handler -------------------------- */ @@ -1357,6 +1370,13 @@ db_upgrade(sqlite3 *hdl, int db_ver) if (ret < 0) return -1; + /* FALLTHROUGH */ + + case 2106: + ret = db_generic_upgrade(hdl, db_upgrade_v2107_queries, ARRAY_SIZE(db_upgrade_v2107_queries)); + if (ret < 0) + return -1; + /* Last case statement is the only one that ends with a break statement! */ break; diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 89fdf63f..8b97ac1c 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -311,6 +311,7 @@ track_to_json(struct db_media_file_info *dbmfi) safe_json_add_int_from_string(item, "samplerate", dbmfi->samplerate); safe_json_add_int_from_string(item, "bitrate", dbmfi->bitrate); safe_json_add_int_from_string(item, "channels", dbmfi->channels); + safe_json_add_int_from_string(item, "usermark", dbmfi->usermark); ret = safe_atoi32(dbmfi->media_kind, &intval); if (ret == 0) @@ -3297,14 +3298,13 @@ jsonapi_reply_library_tracks_put_byid(struct httpd_request *hreq) { int track_id; const char *param; - int val; + uint32_t val; int ret; ret = safe_atoi32(hreq->uri_parsed->path_parts[3], &track_id); if (ret < 0) return HTTP_INTERNAL; - // Update play_count/skip_count param = evhttp_find_header(hreq->query, "play_count"); if (param) { @@ -3319,22 +3319,37 @@ jsonapi_reply_library_tracks_put_byid(struct httpd_request *hreq) else { DPRINTF(E_WARN, L_WEB, "Ignoring invalid play_count value '%s' for track '%d'.\n", param, track_id); + return HTTP_BADREQUEST; } } - // Update rating param = evhttp_find_header(hreq->query, "rating"); if (param) { - ret = safe_atoi32(param, &val); + ret = safe_atou32(param, &val); + if (ret < 0 || val > DB_FILES_RATING_MAX) + { + DPRINTF(E_WARN, L_WEB, "Invalid rating value '%s' for track '%d'.\n", param, track_id); + return HTTP_BADREQUEST; + } + + ret = db_file_rating_update_byid(track_id, val); if (ret < 0) - return HTTP_BADREQUEST; + return HTTP_INTERNAL; + } - if (val >= 0 && val <= DB_FILES_RATING_MAX) - ret = db_file_rating_update_byid(track_id, val); - else - DPRINTF(E_WARN, L_WEB, "Ignoring invalid rating value '%d' for track '%d'.\n", val, track_id); + // Retreive marked tracks via "/api/search?type=tracks&expression=usermark+=+1" + param = evhttp_find_header(hreq->query, "usermark"); + if (param) + { + ret = safe_atou32(param, &val); + if (ret < 0) + { + DPRINTF(E_WARN, L_WEB, "Invalid usermark value '%s' for track '%d'.\n", param, track_id); + return HTTP_BADREQUEST; + } + ret = db_file_usermark_update_byid(track_id, val); if (ret < 0) return HTTP_INTERNAL; }