Skip to content

Commit

Permalink
genre search functionality (#559)
Browse files Browse the repository at this point in the history
* [jsonapi] Add support for listing albums for genres
  • Loading branch information
whatdoineed2do authored and ejurgensen committed Sep 2, 2018
1 parent 48f11a8 commit e3ce003
Show file tree
Hide file tree
Showing 2 changed files with 272 additions and 6 deletions.
144 changes: 141 additions & 3 deletions README_JSON_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,7 @@ curl -X PUT "http://localhost:3689/api/queue/items/2"
| GET | [/api/library/albums](#list-albums) | Get a list of albums |
| GET | [/api/library/albums/{id}](#get-an-album) | Get an album |
| GET | [/api/library/albums/{id}/tracks](#list-album-tracks) | Get list of tracks for an album |
| 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 |


Expand Down Expand Up @@ -1159,6 +1160,136 @@ curl -X GET "http://localhost:3689/api/library/albums/1/tracks"
```


### list genres

Get list of genres

**Endpoint**

```
GET /api/library/genres
```
**Response**

| Key | Type | Value |
| --------------- | -------- | ----------------------------------------- |
| items | array | Array of [`genre`](#genre-object) objects |
| total | integer | Total number of genres in the library |
| offset | integer | Requested offset of the first genre |
| limit | integer | Requested maximum number of genres |


**Example**

```
curl -X GET "http://localhost:3689/api/library/genres"
```

```
{
"items": [
{
"name": "Classical"
},
{
"name": "Drum & Bass"
},
{
"name": "Pop"
},
{
"name": "Rock/Pop"
},
{
"name": "'90s Alternative"
}
],
"total": 5,
"offset": 0,
"limit": -1
}
```

### List albums for genre

Lists the albums in a genre

**Endpoint**

```
GET api/search?type=albums&expression=genre+is+\"{genre name}\""
```

**Query parameters**

| Parameter | Value |
| --------------- | ----------------------------------------------------------- |
| genre | genre name (uri encoded and html esc seq for chars: '/&) |
| offset | *(Optional)* Offset of the first album to return |
| limit | *(Optional)* Maximum number of albums to return |

**Response**

| Key | Type | Value |
| --------------- | -------- | ----------------------------------------- |
| items | array | Array of [`album`](#album-object) objects |
| total | integer | Total number of albums in the library |
| offset | integer | Requested offset of the first albums |
| limit | integer | Requested maximum number of albums |


**Example**

```
curl -X GET "http://localhost:3689/api/search?type=albums&expression=genre+is+\"Pop\""
curl -X GET "http://localhost:3689/api/search?type=albums&expression=genre+is+\"Rock%2FPop\"" # Rock/Pop
curl -X GET "http://localhost:3689/api/search?type=albums&expression=genre+is+\"Drum%20%26%20Bass\"" # Drum & Bass
curl -X GET "http://localhost:3689/api/search?type=albums&expression=genre+is+\"%2790s%20Alternative\"" # '90 Alternative
```

```
{
"albums": {
"items": [
{
"id": "320189328729146437",
"name": "Best Ever",
"name_sort": "Best Ever",
"artist": "ABC",
"artist_id": "8760559201889050080",
"track_count": 1,
"length_ms": 3631,
"uri": "library:album:320189328729146437"
},
{
"id": "7964595866631625723",
"name": "Greatest Hits",
"name_sort": "Greatest Hits",
"artist": "Marvin Gaye",
"artist_id": "5261930703203735930",
"track_count": 2,
"length_ms": 7262,
"uri": "library:album:7964595866631625723"
},
{
"id": "3844610748145176456",
"name": "The Very Best of Etta",
"name_sort": "Very Best of Etta",
"artist": "Etta James",
"artist_id": "2627182178555864595",
"track_count": 1,
"length_ms": 177926,
"uri": "library:album:3844610748145176456"
}
],
"total": 3,
"offset": 0,
"limit": -1
}
}
```

### Get count of tracks, artists and albums

Get information about the number of tracks, artists and albums and the total playtime
Expand Down Expand Up @@ -1206,14 +1337,14 @@ curl -X GET "http://localhost:3689/api/library/count?expression=data_kind+is+fil

| Method | Endpoint | Description |
| --------- | ----------------------------------------------------------- | ------------------------------------ |
| GET | [/api/search](#search-by-search-term) | Search for playlists, artists, albums, tracks by a simple search term |
| GET | [/api/search](#search-by-search-term) | Search for playlists, artists, albums, tracks,genres by a simple search term |
| GET | [/api/search](#search-by-query-language) | Search by complex query expression |



### Search by search term

Search for playlists, artists, albums, tracks that include the given query in their title (case insensitive matching).
Search for playlists, artists, albums, tracks, genres that include the given query in their title (case insensitive matching).

**Endpoint**

Expand All @@ -1226,7 +1357,7 @@ GET /api/search
| Parameter | Value |
| --------------- | ----------------------------------------------------------- |
| query | The search keyword |
| type | Comma separated list of the result types (`playlist`, `artist`, `album`, `track`) |
| type | Comma separated list of the result types (`playlist`, `artist`, `album`, `track`, `genre`) |
| media_kind | *(Optional)* Filter results by media kind (`music`, `movie`, `podcast`, `audiobook`, `musicvideo`, `tvshow`). Filter only applies to artist, album and track result types. |
| offset | *(Optional)* Offset of the first item to return for each type |
| limit | *(Optional)* Maximum number of items to return for each type |
Expand Down Expand Up @@ -1594,3 +1725,10 @@ curl --include \
| offset | integer | Requested offset of the first item |
| limit | integer | Requested maximum number of items |


### `genre` object

| Key | Type | Value |
| --------------- | -------- | ----------------------------------------- |
| name | string | Name of genre |

134 changes: 131 additions & 3 deletions src/httpd_jsonapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -234,12 +234,29 @@ playlist_to_json(struct db_playlist_info *dbpli)
return item;
}

static json_object *
genre_to_json(const char *genre)
{
json_object *item;

if (genre == NULL)
{
return NULL;
}

item = json_object_new_object();
safe_json_add_string(item, "name", genre);

return item;
}


static int
fetch_tracks(struct query_params *query_params, json_object *items, int *total)
{
struct db_media_file_info dbmfi;
json_object *item;
int ret = 0;
int ret;

ret = db_query_start(query_params);
if (ret < 0)
Expand Down Expand Up @@ -328,6 +345,7 @@ fetch_artist(const char *artist_id)

error:
db_query_end(&query_params);
free(query_params.filter);

return artist;
}
Expand Down Expand Up @@ -394,6 +412,7 @@ fetch_album(const char *album_id)

error:
db_query_end(&query_params);
free(query_params.filter);

return album;
}
Expand Down Expand Up @@ -456,10 +475,45 @@ fetch_playlist(const char *playlist_id)

error:
db_query_end(&query_params);
free(query_params.filter);

return playlist;
}

static int
fetch_genres(struct query_params *query_params, json_object *items, int *total)
{
json_object *item;
int ret;
char *genre;
char *sort_item;

ret = db_query_start(query_params);
if (ret < 0)
goto error;

while (((ret = db_query_fetch_string_sort(query_params, &genre, &sort_item)) == 0) && (genre))
{
item = genre_to_json(genre);
if (!item)
{
ret = -1;
goto error;
}

json_object_array_add(items, item);
}

if (total)
*total = query_params->results;

error:
db_query_end(query_params);

return ret;
}


static int
query_params_limit_set(struct query_params *query_params, struct httpd_request *hreq)
{
Expand Down Expand Up @@ -567,6 +621,7 @@ jsonapi_reply_library(struct httpd_request *hreq)
struct filecount_info fci;
json_object *jreply;
int ret;
char *s;


CHECK_NULL(L_WEB, jreply = json_object_new_object());
Expand All @@ -586,8 +641,10 @@ jsonapi_reply_library(struct httpd_request *hreq)
DPRINTF(E_LOG, L_WEB, "library: failed to get file count info\n");
}

safe_json_add_time_from_string(jreply, "started_at", db_admin_get(DB_ADMIN_START_TIME), true);
safe_json_add_time_from_string(jreply, "updated_at", db_admin_get(DB_ADMIN_DB_UPDATE), true);
safe_json_add_time_from_string(jreply, "started_at", (s = db_admin_get(DB_ADMIN_START_TIME)), true);
free(s);
safe_json_add_time_from_string(jreply, "updated_at", (s = db_admin_get(DB_ADMIN_DB_UPDATE)), true);
free(s);

json_object_object_add(jreply, "updating", json_object_new_boolean(library_is_scanning()));

Expand Down Expand Up @@ -2356,6 +2413,75 @@ jsonapi_reply_library_playlist_tracks(struct httpd_request *hreq)
return HTTP_OK;
}

static int
jsonapi_reply_library_genres(struct httpd_request *hreq)
{
time_t db_update;
struct query_params query_params;
const char *param;
enum media_kind media_kind;
json_object *reply;
json_object *items;
int total;
int ret;


db_update = (time_t) db_admin_getint64(DB_ADMIN_DB_UPDATE);
if (db_update && httpd_request_not_modified_since(hreq->req, &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;
}
}

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));

ret = query_params_limit_set(&query_params, hreq);
if (ret < 0)
goto error;

query_params.type = Q_BROWSE_GENRES;
query_params.idx_type = I_NONE;

if (media_kind)
query_params.filter = db_mprintf("(f.media_kind = %d)", media_kind);

ret = fetch_genres(&query_params, items, NULL);
if (ret < 0)
goto error;
else
total = json_object_array_length(items);

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));

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");

error:
jparse_free(reply);
free_query_params(&query_params, 1);

if (ret < 0)
return HTTP_INTERNAL;

return HTTP_OK;
}

static int
jsonapi_reply_library_count(struct httpd_request *hreq)
{
Expand Down Expand Up @@ -2679,6 +2805,7 @@ jsonapi_reply_search(struct httpd_request *hreq)
if (param_expression)
{
expression = safe_asprintf("\"query\" { %s }", param_expression);

ret = smartpl_query_parse_string(&smartpl_expression, expression);
free(expression);

Expand Down Expand Up @@ -2779,6 +2906,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_GET, "^/api/library/genres$", jsonapi_reply_library_genres},
{ EVHTTP_REQ_GET, "^/api/library/count$", jsonapi_reply_library_count },

{ EVHTTP_REQ_GET, "^/api/search$", jsonapi_reply_search },
Expand Down

0 comments on commit e3ce003

Please sign in to comment.