Merge pull request #1436 from chme/feat/jsonapi-browse

[jsonapi] Generic browse endpoints
This commit is contained in:
Christian Meffert 2022-04-03 13:57:34 +02:00 committed by GitHub
commit 0f204c66ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 84 additions and 49 deletions

View File

@ -883,12 +883,17 @@ httpd_gen_cb(struct evhttp_request *req, void *arg)
void
httpd_uri_free(struct httpd_uri_parsed *parsed)
{
int i;
if (!parsed)
return;
free(parsed->uri_decoded);
free(parsed->path);
free(parsed->path_parts[0]);
for (i = 0; i < ARRAY_SIZE(parsed->path_parts); i++)
{
free(parsed->path_parts[i]);
}
evhttp_clear_headers(&(parsed->ev_query));
@ -902,8 +907,9 @@ struct httpd_uri_parsed *
httpd_uri_parse(const char *uri)
{
struct httpd_uri_parsed *parsed;
const char *path;
char *path = NULL;
const char *query;
char *path_part;
char *ptr;
int i;
int ret;
@ -937,7 +943,7 @@ httpd_uri_parse(const char *uri)
}
}
path = evhttp_uri_get_path(parsed->ev_uri);
path = strdup(evhttp_uri_get_path(parsed->ev_uri));
if (!path)
{
DPRINTF(E_WARN, L_HTTPD, "No path in request: '%s'\n", parsed->uri);
@ -951,23 +957,26 @@ httpd_uri_parse(const char *uri)
goto error;
}
CHECK_NULL(L_HTTPD, parsed->path_parts[0] = strdup(parsed->path));
strtok_r(parsed->path_parts[0], "/", &ptr);
for (i = 1; (i < sizeof(parsed->path_parts) / sizeof(parsed->path_parts[0])) && parsed->path_parts[i - 1]; i++)
path_part = strtok_r(path, "/", &ptr);
for (i = 0; (i < ARRAY_SIZE(parsed->path_parts) && path_part); i++)
{
parsed->path_parts[i] = strtok_r(NULL, "/", &ptr);
parsed->path_parts[i] = evhttp_uridecode(path_part, 0, NULL);
path_part = strtok_r(NULL, "/", &ptr);
}
if (!parsed->path_parts[0] || parsed->path_parts[i - 1] || (i < 2))
if (path_part)
{
DPRINTF(E_LOG, L_HTTPD, "URI path has too many/few components (%d): '%s'\n", (parsed->path_parts[0]) ? i : 0, parsed->path);
// If "path_part" is not NULL, we have path tokens that could not be parsed into the "parsed->path_parts" array
DPRINTF(E_LOG, L_HTTPD, "URI path has too many components (%d): '%s'\n", i, parsed->path);
goto error;
}
free(path);
return parsed;
error:
free(path);
httpd_uri_free(parsed);
return NULL;
}

View File

@ -23,10 +23,9 @@ enum httpd_send_flags
*
* We are interested in the path and the query, so they are disassembled to
* path_parts and ev_query. If the request is http://x:3689/foo/bar?key1=val1,
* then part_parts[1] is "foo", [2] is "bar" and the rest is null (the first
* element points to the copy of the path so it can be freed).
* then part_parts[0] is "foo", [1] is "bar" and the rest is null.
*
* The allocated strings are URI decoded.
* Each path_part is an allocated URI decoded string.
*/
struct httpd_uri_parsed
{

View File

@ -3959,10 +3959,11 @@ jsonapi_reply_queue_save(struct httpd_request *hreq)
}
static int
jsonapi_reply_library_genres(struct httpd_request *hreq)
jsonapi_reply_library_browse(struct httpd_request *hreq)
{
struct query_params query_params;
const char *param;
const char *browse_type;
enum media_kind media_kind;
json_object *reply;
json_object *items;
@ -3972,6 +3973,9 @@ jsonapi_reply_library_genres(struct httpd_request *hreq)
if (!is_modified(hreq->req, DB_ADMIN_DB_UPDATE))
return HTTP_NOTMODIFIED;
browse_type = hreq->uri_parsed->path_parts[2];
DPRINTF(E_DBG, L_WEB, "Browse query with type '%s'\n", browse_type);
media_kind = 0;
param = evhttp_find_header(hreq->query, "media_kind");
if (param)
@ -3994,8 +3998,23 @@ jsonapi_reply_library_genres(struct httpd_request *hreq)
if (ret < 0)
goto error;
query_params.type = Q_BROWSE_GENRES;
query_params.idx_type = I_NONE;
if (strcmp(browse_type, "genres") == 0)
{
query_params.type = Q_BROWSE_GENRES;
query_params.sort = S_GENRE;
query_params.idx_type = I_NONE;
}
else if (strcmp(browse_type, "composers") == 0)
{
query_params.type = Q_BROWSE_COMPOSERS;
query_params.sort = S_COMPOSER;
query_params.idx_type = I_NONE;
}
else
{
DPRINTF(E_LOG, L_WEB, "Invalid browse type '%s'\n", browse_type);
goto error;
}
if (media_kind)
query_params.filter = db_mprintf("(f.media_kind = %d)", media_kind);
@ -4010,7 +4029,7 @@ jsonapi_reply_library_genres(struct httpd_request *hreq)
ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "browse: Couldn't add genres to response buffer.\n");
DPRINTF(E_LOG, L_WEB, "browse: Couldn't add browse items to response buffer.\n");
error:
jparse_free(reply);
@ -4023,34 +4042,23 @@ jsonapi_reply_library_genres(struct httpd_request *hreq)
}
static int
jsonapi_reply_library_composers(struct httpd_request *hreq)
jsonapi_reply_library_browseitem(struct httpd_request *hreq)
{
struct query_params query_params;
const char *param;
enum media_kind media_kind;
const char *browse_type;
const char *item_name;
struct db_browse_info dbbi;
json_object *reply;
json_object *items;
int total;
int ret;
if (!is_modified(hreq->req, DB_ADMIN_DB_UPDATE))
return HTTP_NOTMODIFIED;
media_kind = 0;
param = evhttp_find_header(hreq->query, "media_kind");
if (param)
{
media_kind = db_media_kind_enum(param);
if (!media_kind)
{
DPRINTF(E_LOG, L_WEB, "Invalid media kind '%s'\n", param);
return HTTP_BADREQUEST;
}
}
browse_type = hreq->uri_parsed->path_parts[2];
item_name = hreq->uri_parsed->path_parts[3];
DPRINTF(E_DBG, L_WEB, "Browse item query with type '%s'\n", browse_type);
reply = json_object_new_object();
items = json_object_new_array();
json_object_object_add(reply, "items", items);
memset(&query_params, 0, sizeof(struct query_params));
@ -4058,26 +4066,46 @@ jsonapi_reply_library_composers(struct httpd_request *hreq)
if (ret < 0)
goto error;
query_params.type = Q_BROWSE_COMPOSERS;
query_params.sort = S_COMPOSER;
query_params.idx_type = I_NONE;
if (strcmp(browse_type, "genres") == 0)
{
query_params.type = Q_BROWSE_GENRES;
query_params.sort = S_GENRE;
query_params.idx_type = I_NONE;
query_params.filter = db_mprintf("(f.genre = %Q)", item_name);
}
else if (strcmp(browse_type, "composers") == 0)
{
query_params.type = Q_BROWSE_COMPOSERS;
query_params.sort = S_COMPOSER;
query_params.idx_type = I_NONE;
query_params.filter = db_mprintf("(f.composer = %Q)", item_name);
}
else
{
DPRINTF(E_LOG, L_WEB, "Invalid browse type '%s'\n", browse_type);
goto error;
}
if (media_kind)
query_params.filter = db_mprintf("(f.media_kind = %d)", media_kind);
ret = fetch_browse_info(&query_params, items, &total);
ret = db_query_start(&query_params);
if (ret < 0)
goto error;
json_object_object_add(reply, "total", json_object_new_int(total));
json_object_object_add(reply, "offset", json_object_new_int(query_params.offset));
json_object_object_add(reply, "limit", json_object_new_int(query_params.limit));
if ((ret = db_query_fetch_browse(&dbbi, &query_params)) == 0)
{
reply = browse_info_to_json(&dbbi);
}
if (!reply)
{
ret = -1;
goto error;
}
ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
if (ret < 0)
DPRINTF(E_LOG, L_WEB, "browse: Couldn't add composers to response buffer.\n");
DPRINTF(E_LOG, L_WEB, "browse: Couldn't add browse item to response buffer.\n");
error:
db_query_end(&query_params);
jparse_free(reply);
free_query_params(&query_params, 1);
@ -4087,7 +4115,6 @@ jsonapi_reply_library_composers(struct httpd_request *hreq)
return HTTP_OK;
}
static int
jsonapi_reply_library_count(struct httpd_request *hreq)
{
@ -4737,8 +4764,8 @@ static struct httpd_uri_map adm_handlers[] =
{ 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 },
{ EVHTTP_REQ_GET, "^/api/library/genres$", jsonapi_reply_library_genres},
{ EVHTTP_REQ_GET, "^/api/library/composers$", jsonapi_reply_library_composers },
{ EVHTTP_REQ_GET, "^/api/library/(genres|composers)$", jsonapi_reply_library_browse },
{ EVHTTP_REQ_GET, "^/api/library/(genres|composers)/.*$", jsonapi_reply_library_browseitem },
{ EVHTTP_REQ_GET, "^/api/library/count$", jsonapi_reply_library_count },
{ EVHTTP_REQ_GET, "^/api/library/files$", jsonapi_reply_library_files },
{ EVHTTP_REQ_POST, "^/api/library/add$", jsonapi_reply_library_add },