This commit is contained in:
ejurgensen 2021-10-14 23:02:44 +02:00
parent 383146eb35
commit 1683085c63
1 changed files with 284 additions and 182 deletions

View File

@ -60,6 +60,21 @@
# include "inputs/spotify.h"
#endif
struct convert_map
{
const char *key;
// Function that adds a json object with input key/val. src_offset is the
// offset to where value is stored, e.g. dbmfi_offsetof(id).
void (*to_json_fn)(json_object *obj, const char *key, const char *val);
size_t src_offset;
// Function that retrieves a value from a json object, checks it and writes
// it to the memory pointed to by ptr. dst_offset is the offset to where the
// value should be written, e.g. mfi_offsetof(id)
int (*from_json_fn)(void *ptr, const char *key, json_object *obj);
size_t dst_offset;
};
static bool allow_modifying_stored_playlists;
static char *default_playlist_directory;
@ -176,94 +191,235 @@ safe_json_add_date_from_string(json_object *obj, const char *key, const char *va
json_object_object_add(obj, key, json_object_new_string(result));
}
static json_object *
artist_to_json(struct db_group_info *dbgri)
static void
json_add_in_progress(json_object *obj, const char *key, const char *val)
{
json_object *item;
int intval;
char uri[100];
char artwork_url[100];
int32_t intval;
int ret;
item = json_object_new_object();
ret = safe_atoi32(val, &intval);
if (ret < 0)
return;
safe_json_add_string(item, "id", dbgri->persistentid);
safe_json_add_string(item, "name", dbgri->itemname);
safe_json_add_string(item, "name_sort", dbgri->itemname_sort);
safe_json_add_int_from_string(item, "album_count", dbgri->groupalbumcount);
safe_json_add_int_from_string(item, "track_count", dbgri->itemcount);
safe_json_add_int_from_string(item, "length_ms", dbgri->song_length);
safe_json_add_time_from_string(item, "time_played", dbgri->time_played);
safe_json_add_time_from_string(item, "time_added", dbgri->time_added);
ret = safe_atoi32(dbgri->seek, &intval);
if (ret == 0)
json_object_object_add(item, "in_progress", json_object_new_boolean(intval > 0));
ret = safe_atoi32(dbgri->media_kind, &intval);
if (ret == 0)
safe_json_add_string(item, "media_kind", db_media_kind_label(intval));
ret = safe_atoi32(dbgri->data_kind, &intval);
if (ret == 0)
safe_json_add_string(item, "data_kind", db_data_kind_label(intval));
ret = snprintf(uri, sizeof(uri), "%s:%s:%s", "library", "artist", dbgri->persistentid);
if (ret < sizeof(uri))
json_object_object_add(item, "uri", json_object_new_string(uri));
ret = snprintf(artwork_url, sizeof(artwork_url), "./artwork/group/%s", dbgri->id);
if (ret < sizeof(artwork_url))
json_object_object_add(item, "artwork_url", json_object_new_string(artwork_url));
return item;
json_object_object_add(obj, key, json_object_new_boolean(intval > 0));
}
static json_object *
album_to_json(struct db_group_info *dbgri)
static void
json_add_label(json_object *obj, const char *key, const char *val)
{
int32_t intval;
int ret;
ret = safe_atoi32(val, &intval);
if (ret < 0)
return;
if (strcmp(key, "media_kind") == 0)
safe_json_add_string(obj, key, db_media_kind_label(intval));
else if (strcmp(key, "data_kind") == 0)
safe_json_add_string(obj, key, db_data_kind_label(intval));
}
static void
json_generic_add_uri(json_object *obj, const char *key, const char *val, const char *type)
{
json_object *item;
int intval;
char uri[100];
int ret;
ret = snprintf(uri, sizeof(uri), "%s:%s:%s", "library", type, val);
if (ret < 0 || ret >= sizeof(uri))
return;
json_object_object_add(obj, key, json_object_new_string(uri));
}
static void
json_artist_add_uri(json_object *obj, const char *key, const char *val)
{
json_generic_add_uri(obj, key, val, "artist");
}
static void
json_track_add_uri(json_object *obj, const char *key, const char *val)
{
json_generic_add_uri(obj, key, val, "track");
}
static void
json_playlist_add_uri(json_object *obj, const char *key, const char *val)
{
json_generic_add_uri(obj, key, val, "playlist");
}
static void
json_generic_add_artwork_url(json_object *obj, const char *key, const char *val, const char *type)
{
char artwork_url[100];
int ret;
ret = snprintf(artwork_url, sizeof(artwork_url), "/artwork/%s/%s", type, val);
if (ret < 0 || ret >= sizeof(artwork_url))
return;
json_object_object_add(obj, key, json_object_new_string(artwork_url));
}
static void
json_item_add_artwork_url(json_object *obj, const char *key, const char *val)
{
json_generic_add_artwork_url(obj, key, val, "item");
}
static void
json_group_add_artwork_url(json_object *obj, const char *key, const char *val)
{
json_generic_add_artwork_url(obj, key, val, "group");
}
static void
json_playlist_add_playlist_type(json_object *obj, const char *key, const char *val)
{
int32_t intval;
int ret;
ret = safe_atoi32(val, &intval);
if (ret < 0)
return;
if (strcmp(key, "type") == 0)
safe_json_add_string(obj, key, db_pl_type_label(intval));
else if (strcmp(key, "smart_playlist") == 0)
json_object_object_add(obj, key, json_object_new_boolean(intval == PL_SMART));
else if (strcmp(key, "folder") == 0)
json_object_object_add(obj, key, json_object_new_boolean(intval == PL_FOLDER));
}
static void
json_playlist_add_query_order(json_object *obj, const char *key, const char *val)
{
bool boolval = val && strcasestr(val, "random");
json_object_object_add(obj, key, json_object_new_boolean(boolval));
}
static int
safe_json_get_uint(void *ptr, const char *key, json_object *obj)
{
uint32_t *uintval = ptr;
if (json_object_get_type(obj) != json_type_int)
return -1;
*uintval = (uint32_t)json_object_get_int(obj);
return 0;
}
struct convert_map track_map[] =
{
{ "id", safe_json_add_int_from_string, dbmfi_offsetof(id), safe_json_get_uint, mfi_offsetof(id) },
{ "title", safe_json_add_string, dbmfi_offsetof(title), NULL, mfi_offsetof(title) },
{ "title_sort", safe_json_add_string, dbmfi_offsetof(title_sort), NULL, mfi_offsetof(title_sort) },
{ "artist", safe_json_add_string, dbmfi_offsetof(artist), NULL, mfi_offsetof(artist) },
{ "artist_sort", safe_json_add_string, dbmfi_offsetof(artist_sort), NULL, mfi_offsetof(artist_sort) },
{ "album", safe_json_add_string, dbmfi_offsetof(album), NULL, mfi_offsetof(album) },
{ "album_sort", safe_json_add_string, dbmfi_offsetof(album_sort), NULL, mfi_offsetof(album_sort) },
{ "album_id", safe_json_add_string, dbmfi_offsetof(songalbumid), NULL, mfi_offsetof(songalbumid) },
{ "album_artist", safe_json_add_string, dbmfi_offsetof(album_artist), NULL, mfi_offsetof(album_artist) },
{ "album_artist_sort", safe_json_add_string, dbmfi_offsetof(album_artist_sort),NULL, mfi_offsetof(album_artist_sort) },
{ "album_artist_id", safe_json_add_string, dbmfi_offsetof(songartistid), NULL, mfi_offsetof(songartistid) },
{ "composer", safe_json_add_string, dbmfi_offsetof(composer), NULL, mfi_offsetof(composer) },
{ "genre", safe_json_add_string, dbmfi_offsetof(genre), NULL, mfi_offsetof(genre) },
{ "comment", safe_json_add_string, dbmfi_offsetof(comment), NULL, mfi_offsetof(comment) },
{ "year", safe_json_add_int_from_string, dbmfi_offsetof(year), safe_json_get_uint, mfi_offsetof(year) },
{ "track_number", safe_json_add_int_from_string, dbmfi_offsetof(track), safe_json_get_uint, mfi_offsetof(track) },
{ "disc_number", safe_json_add_int_from_string, dbmfi_offsetof(disc), safe_json_get_uint, mfi_offsetof(disc) },
{ "length_ms", safe_json_add_int_from_string, dbmfi_offsetof(song_length), safe_json_get_uint, mfi_offsetof(song_length) },
{ "rating", safe_json_add_int_from_string, dbmfi_offsetof(rating), safe_json_get_uint, mfi_offsetof(rating) },
{ "play_count", safe_json_add_int_from_string, dbmfi_offsetof(play_count), safe_json_get_uint, mfi_offsetof(play_count) },
{ "skip_count", safe_json_add_int_from_string, dbmfi_offsetof(skip_count), safe_json_get_uint, mfi_offsetof(skip_count) },
{ "time_played", safe_json_add_time_from_string, dbmfi_offsetof(time_played), NULL, mfi_offsetof(time_played) },
{ "time_skipped", safe_json_add_time_from_string, dbmfi_offsetof(time_skipped), NULL, mfi_offsetof(time_skipped) },
{ "time_added", safe_json_add_time_from_string, dbmfi_offsetof(time_added), NULL, mfi_offsetof(time_added) },
{ "date_released", safe_json_add_time_from_string, dbmfi_offsetof(date_released), NULL, mfi_offsetof(date_released) },
{ "seek_ms", safe_json_add_int_from_string, dbmfi_offsetof(seek), safe_json_get_uint, mfi_offsetof(seek) },
{ "type", safe_json_add_string, dbmfi_offsetof(type), NULL, mfi_offsetof(type) },
{ "samplerate", safe_json_add_int_from_string, dbmfi_offsetof(samplerate), safe_json_get_uint, mfi_offsetof(samplerate) },
{ "bitrate", safe_json_add_int_from_string, dbmfi_offsetof(bitrate), safe_json_get_uint, mfi_offsetof(bitrate) },
{ "channels", safe_json_add_int_from_string, dbmfi_offsetof(channels), safe_json_get_uint, mfi_offsetof(channels) },
{ "usermark", safe_json_add_int_from_string, dbmfi_offsetof(usermark), safe_json_get_uint, mfi_offsetof(usermark) },
{ "media_kind", json_add_label, dbmfi_offsetof(media_kind), NULL, mfi_offsetof(media_kind) },
{ "data_kind", json_add_label, dbmfi_offsetof(data_kind), NULL, mfi_offsetof(data_kind) },
{ "path", safe_json_add_string, dbmfi_offsetof(path), NULL, mfi_offsetof(path) },
{ "uri", json_track_add_uri, dbmfi_offsetof(id), NULL, 0 },
{ "artwork_url", json_item_add_artwork_url, dbmfi_offsetof(id), NULL, 0 },
};
struct convert_map artist_map[] =
{
{ "id", safe_json_add_string, dbgri_offsetof(persistentid), NULL, 0 },
{ "name", safe_json_add_string, dbgri_offsetof(itemname), NULL, 0 },
{ "name_sort", safe_json_add_string, dbgri_offsetof(itemname_sort), NULL, 0 },
{ "album_count", safe_json_add_int_from_string, dbgri_offsetof(groupalbumcount), NULL, 0 },
{ "track_count", safe_json_add_int_from_string, dbgri_offsetof(itemcount), NULL, 0 },
{ "length_ms", safe_json_add_int_from_string, dbgri_offsetof(song_length), NULL, 0 },
{ "time_played", safe_json_add_int_from_string, dbgri_offsetof(time_played), NULL, 0 },
{ "time_added", safe_json_add_int_from_string, dbgri_offsetof(time_added), NULL, 0 },
{ "in_progress", json_add_in_progress, dbgri_offsetof(seek), NULL, 0 },
{ "media_kind", json_add_label, dbgri_offsetof(media_kind), NULL, 0 },
{ "data_kind", json_add_label, dbgri_offsetof(data_kind), NULL, 0 },
{ "uri", json_artist_add_uri, dbgri_offsetof(persistentid), NULL, 0 },
{ "artwork_url", json_group_add_artwork_url, dbgri_offsetof(id), NULL, 0 },
};
struct convert_map album_map[] =
{
{ "id", safe_json_add_string, dbgri_offsetof(persistentid), NULL, 0 },
{ "name", safe_json_add_string, dbgri_offsetof(itemname), NULL, 0 },
{ "name_sort", safe_json_add_string, dbgri_offsetof(itemname_sort), NULL, 0 },
{ "artist", safe_json_add_string, dbgri_offsetof(songalbumartist), NULL, 0 },
{ "artist_id", safe_json_add_string, dbgri_offsetof(songartistid), NULL, 0 },
{ "track_count", safe_json_add_int_from_string, dbgri_offsetof(itemcount), NULL, 0 },
{ "length_ms", safe_json_add_int_from_string, dbgri_offsetof(song_length), NULL, 0 },
{ "time_played", safe_json_add_int_from_string, dbgri_offsetof(time_played), NULL, 0 },
{ "time_added", safe_json_add_int_from_string, dbgri_offsetof(time_added), NULL, 0 },
{ "in_progress", json_add_in_progress, dbgri_offsetof(seek), NULL, 0 },
{ "media_kind", json_add_label, dbgri_offsetof(media_kind), NULL, 0 },
{ "data_kind", json_add_label, dbgri_offsetof(data_kind), NULL, 0 },
{ "date_released", safe_json_add_date_from_string, dbgri_offsetof(date_released), NULL, 0 },
{ "year", safe_json_add_int_from_string, dbgri_offsetof(year), NULL, 0 },
{ "uri", json_artist_add_uri, dbgri_offsetof(persistentid), NULL, 0 },
{ "artwork_url", json_group_add_artwork_url, dbgri_offsetof(id), NULL, 0 },
};
struct convert_map playlist_map[] =
{
{ "id", safe_json_add_int_from_string, dbpli_offsetof(id), NULL, 0 },
{ "name", safe_json_add_string, dbpli_offsetof(title), NULL, 0 },
{ "path", safe_json_add_string, dbpli_offsetof(path), NULL, 0 },
{ "parent_id", safe_json_add_string, dbpli_offsetof(parent_id), NULL, 0 },
{ "type", json_playlist_add_playlist_type, dbpli_offsetof(type), NULL, 0 },
{ "smart_playlist", json_playlist_add_playlist_type, dbpli_offsetof(type), NULL, 0 },
{ "random", json_playlist_add_query_order, dbpli_offsetof(query_order), NULL, 0 },
{ "folder", json_playlist_add_playlist_type, dbpli_offsetof(type), NULL, 0 },
{ "uri", json_playlist_add_uri, dbpli_offsetof(id), NULL, 0 },
};
static json_object *
generic_to_json(void *src, struct convert_map *map, size_t map_size)
{
json_object *item;
char **strptr;
int i;
item = json_object_new_object();
safe_json_add_string(item, "id", dbgri->persistentid);
safe_json_add_string(item, "name", dbgri->itemname);
safe_json_add_string(item, "name_sort", dbgri->itemname_sort);
safe_json_add_string(item, "artist", dbgri->songalbumartist);
safe_json_add_string(item, "artist_id", dbgri->songartistid);
safe_json_add_int_from_string(item, "track_count", dbgri->itemcount);
safe_json_add_int_from_string(item, "length_ms", dbgri->song_length);
safe_json_add_time_from_string(item, "time_played", dbgri->time_played);
safe_json_add_time_from_string(item, "time_added", dbgri->time_added);
ret = safe_atoi32(dbgri->seek, &intval);
if (ret == 0)
json_object_object_add(item, "in_progress", json_object_new_boolean(intval > 0));
ret = safe_atoi32(dbgri->media_kind, &intval);
if (ret == 0)
safe_json_add_string(item, "media_kind", db_media_kind_label(intval));
ret = safe_atoi32(dbgri->data_kind, &intval);
if (ret == 0)
safe_json_add_string(item, "data_kind", db_data_kind_label(intval));
safe_json_add_date_from_string(item, "date_released", dbgri->date_released);
safe_json_add_int_from_string(item, "year", dbgri->year);
ret = snprintf(uri, sizeof(uri), "%s:%s:%s", "library", "album", dbgri->persistentid);
if (ret < sizeof(uri))
json_object_object_add(item, "uri", json_object_new_string(uri));
ret = snprintf(artwork_url, sizeof(artwork_url), "./artwork/group/%s", dbgri->id);
if (ret < sizeof(artwork_url))
json_object_object_add(item, "artwork_url", json_object_new_string(artwork_url));
for (i = 0; i < map_size; i++)
{
strptr = (char **)(src + map[i].src_offset);
map[i].to_json_fn(item, map[i].key, *strptr);
}
return item;
}
@ -271,120 +427,25 @@ album_to_json(struct db_group_info *dbgri)
static json_object *
track_to_json(struct db_media_file_info *dbmfi)
{
json_object *item;
char uri[100];
char artwork_url[100];
int intval;
int ret;
item = json_object_new_object();
safe_json_add_int_from_string(item, "id", dbmfi->id);
safe_json_add_string(item, "title", dbmfi->title);
safe_json_add_string(item, "title_sort", dbmfi->title_sort);
safe_json_add_string(item, "artist", dbmfi->artist);
safe_json_add_string(item, "artist_sort", dbmfi->artist_sort);
safe_json_add_string(item, "album", dbmfi->album);
safe_json_add_string(item, "album_sort", dbmfi->album_sort);
safe_json_add_string(item, "album_id", dbmfi->songalbumid);
safe_json_add_string(item, "album_artist", dbmfi->album_artist);
safe_json_add_string(item, "album_artist_sort", dbmfi->album_artist_sort);
safe_json_add_string(item, "album_artist_id", dbmfi->songartistid);
safe_json_add_string(item, "composer", dbmfi->composer);
safe_json_add_string(item, "genre", dbmfi->genre);
safe_json_add_string(item, "comment", dbmfi->comment);
safe_json_add_int_from_string(item, "year", dbmfi->year);
safe_json_add_int_from_string(item, "track_number", dbmfi->track);
safe_json_add_int_from_string(item, "disc_number", dbmfi->disc);
safe_json_add_int_from_string(item, "length_ms", dbmfi->song_length);
safe_json_add_int_from_string(item, "rating", dbmfi->rating);
safe_json_add_int_from_string(item, "play_count", dbmfi->play_count);
safe_json_add_int_from_string(item, "skip_count", dbmfi->skip_count);
safe_json_add_time_from_string(item, "time_played", dbmfi->time_played);
safe_json_add_time_from_string(item, "time_skipped", dbmfi->time_skipped);
safe_json_add_time_from_string(item, "time_added", dbmfi->time_added);
safe_json_add_date_from_string(item, "date_released", dbmfi->date_released);
safe_json_add_int_from_string(item, "seek_ms", dbmfi->seek);
safe_json_add_string(item, "type", dbmfi->type);
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)
safe_json_add_string(item, "media_kind", db_media_kind_label(intval));
ret = safe_atoi32(dbmfi->data_kind, &intval);
if (ret == 0)
safe_json_add_string(item, "data_kind", db_data_kind_label(intval));
safe_json_add_string(item, "path", dbmfi->path);
ret = snprintf(uri, sizeof(uri), "%s:%s:%s", "library", "track", dbmfi->id);
if (ret < sizeof(uri))
json_object_object_add(item, "uri", json_object_new_string(uri));
ret = snprintf(artwork_url, sizeof(artwork_url), "/artwork/item/%s", dbmfi->id);
if (ret < sizeof(artwork_url))
json_object_object_add(item, "artwork_url", json_object_new_string(artwork_url));
return item;
return generic_to_json(dbmfi, track_map, ARRAY_SIZE(track_map));
}
// 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)
static json_object *
artist_to_json(struct db_group_info *dbgri)
{
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 generic_to_json(dbgri, artist_map, ARRAY_SIZE(artist_map));
}
return HTTP_OK;
static json_object *
album_to_json(struct db_group_info *dbgri)
{
return generic_to_json(dbgri, album_map, ARRAY_SIZE(album_map));
}
static json_object *
playlist_to_json(struct db_playlist_info *dbpli)
{
json_object *item;
char uri[100];
int intval;
bool boolval;
int ret;
item = json_object_new_object();
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)
{
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));
boolval = dbpli->query_order && strcasestr(dbpli->query_order, "random");
json_object_object_add(item, "random", json_object_new_boolean(boolval));
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))
json_object_object_add(item, "uri", json_object_new_string(uri));
return item;
return generic_to_json(dbpli, playlist_map, ARRAY_SIZE(playlist_map));
}
static json_object *
@ -421,6 +482,47 @@ directory_to_json(struct directory_info *directory_info)
return item;
}
static int
json_to_track(struct media_file_info *mfi, json_object *json)
{
struct json_object_iterator obj;
struct json_object_iterator end;
const char *key;
struct json_object *item;
void *ptr;
int ret;
int i;
end = json_object_iter_end(json);
for (obj = json_object_iter_begin(json); !json_object_iter_equal(&obj, &end); json_object_iter_next(&obj))
{
key = json_object_iter_peek_name(&obj);
item = json_object_iter_peek_value(&obj);
for (i = 0; i < ARRAY_SIZE(track_map); i++)
{
if (strcmp(key, track_map[i].key) == 0)
break;
}
if (i == ARRAY_SIZE(track_map))
goto error_unknown_key;
ptr = (void *)mfi + track_map[i].dst_offset;
ret = track_map[i].from_json_fn(ptr, track_map[i].key, item);
if (ret < 0)
goto error_value;
}
return HTTP_OK;
error_unknown_key:
return HTTP_NOTIMPLEMENTED;
error_value:
return HTTP_BADREQUEST;
}
static int
fetch_tracks(struct query_params *query_params, json_object *items, int *total)
@ -3270,8 +3372,8 @@ jsonapi_reply_library_tracks_get_byid(struct httpd_request *hreq)
json_object *reply = NULL;
int ret = 0;
if (!is_modified(hreq->req, DB_ADMIN_DB_MODIFIED))
return HTTP_NOTMODIFIED;
// if (!is_modified(hreq->req, DB_ADMIN_DB_MODIFIED))
// return HTTP_NOTMODIFIED;
track_id = hreq->uri_parsed->path_parts[3];