Merge pull request #651 from chme/jsonapi_files

[jsonapi] Allow adding items by query language to the queue (plus some small fixes)
This commit is contained in:
chme 2019-01-05 07:41:51 +01:00 committed by GitHub
commit 47bdff255c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 194 additions and 39 deletions

View File

@ -582,8 +582,14 @@ POST /api/queue/items/add
| Parameter | Value |
| --------------- | ----------------------------------------------------------- |
| uris | Comma seperated list of resource identifiers (`track`, `playlist`, `artist` or `album` object `uri`) |
| position | *(Optional)* If a position is given, new items are inserted starting from this position into the queue. |
| uris | Comma seperated list of resource identifiers (`track`, `playlist`, `artist` or `album` object `uri`) |
| expression | A smart playlist query expression identifying the tracks that will be added to the queue. |
| position | *(Optional)* If a position is given, new items are inserted starting from this position into the queue. |
| playback | *(Optional)* If the `playback` parameter is set to `start`, playback will be started after adding the new items. |
| clear | *(Optional)* If the `clear` parameter is set to `true`, the queue will be cleared before adding the new items. |
| shuffle | *(Optional)* If the `shuffle` parameter is set to `true`, the shuffle mode is activated. If it is set to something else, the shuffle mode is deactivated. To leave the shuffle mode untouched the parameter should be ommited. |
Either the `uris` or the `expression` parameter must be set. If both are set the `uris` parameter takes presedence and the `expression` parameter will be ignored.
**Response**
@ -596,6 +602,8 @@ On success returns the HTTP `200 OK` success status response code.
**Example**
Add new items by uri:
```shell
curl -X POST "http://localhost:3689/api/queue/items/add?uris=library:playlist:68,library:artist:2932599850102967727"
```
@ -606,6 +614,18 @@ curl -X POST "http://localhost:3689/api/queue/items/add?uris=library:playlist:68
}
```
Add new items by query language:
```shell
curl -X POST "http://localhost:3689/api/queue/items/add?expression=media_kind+is+music"
```
```json
{
"count": 42
}
```
### Moving a queue item
@ -1778,6 +1798,7 @@ will send a message each time one of the events occurred.
| Type | Description |
| --------------- | ----------------------------------------- |
| update | Library update started or finished |
| database | Library database changed (new/modified/deleted tracks) |
| outputs | An output was enabled or disabled |
| player | Player state changes |
| options | Playback option changes (shuffle, repeat, consume mode) |

View File

@ -597,6 +597,7 @@ metadata_packet_get(struct http_icy_metadata *metadata, AVFormatContext *fmtctx)
{
uint8_t *buffer;
char *icy_token;
char *save_pr;
char *ptr;
char *end;
@ -604,13 +605,13 @@ metadata_packet_get(struct http_icy_metadata *metadata, AVFormatContext *fmtctx)
if (!buffer)
return -1;
icy_token = strtok((char *)buffer, ";");
icy_token = strtok_r((char *)buffer, ";", &save_pr);
while (icy_token != NULL)
{
ptr = strchr(icy_token, '=');
if (!ptr || (ptr[1] == '\0'))
{
icy_token = strtok(NULL, ";");
icy_token = strtok_r(NULL, ";", &save_pr);
continue;
}
@ -647,7 +648,7 @@ metadata_packet_get(struct http_icy_metadata *metadata, AVFormatContext *fmtctx)
if (end)
*end = '\'';
icy_token = strtok(NULL, ";");
icy_token = strtok_r(NULL, ";", &save_pr);
}
av_free(buffer);
@ -663,6 +664,7 @@ metadata_header_get(struct http_icy_metadata *metadata, AVFormatContext *fmtctx)
uint8_t *buffer;
uint8_t *utf;
char *icy_token;
char *save_pr;
char *ptr;
av_opt_get(fmtctx, "icy_metadata_headers", AV_OPT_SEARCH_CHILDREN, &buffer);
@ -677,13 +679,13 @@ metadata_header_get(struct http_icy_metadata *metadata, AVFormatContext *fmtctx)
if (!utf)
return -1;
icy_token = strtok((char *)utf, "\r\n");
icy_token = strtok_r((char *)utf, "\r\n", &save_pr);
while (icy_token != NULL)
{
ptr = strchr(icy_token, ':');
if (!ptr || (ptr[1] == '\0'))
{
icy_token = strtok(NULL, "\r\n");
icy_token = strtok_r(NULL, "\r\n", &save_pr);
continue;
}
@ -698,7 +700,7 @@ metadata_header_get(struct http_icy_metadata *metadata, AVFormatContext *fmtctx)
else if ((strncmp(icy_token, "icy-genre", strlen("icy-genre")) == 0) && !metadata->genre)
metadata->genre = strdup(ptr);
icy_token = strtok(NULL, "\r\n");
icy_token = strtok_r(NULL, "\r\n", &save_pr);
}
free(utf);

View File

@ -632,6 +632,10 @@ jsonapi_reply_config(struct httpd_request *hreq)
json_object *buildopts;
int websocket_port;
char **buildoptions;
cfg_t *lib;
int ndirs;
char *path;
json_object *directories;
int i;
CHECK_NULL(L_WEB, jreply = json_object_new_object());
@ -662,6 +666,17 @@ jsonapi_reply_config(struct httpd_request *hreq)
}
json_object_object_add(jreply, "buildoptions", buildopts);
// Library directories
lib = cfg_getsec(cfg, "library");
ndirs = cfg_size(lib, "directories");
directories = json_object_new_array();
for (i = 0; i < ndirs; i++)
{
path = cfg_getnstr(lib, "directories", i);
json_object_array_add(directories, json_object_new_string(path));
}
json_object_object_add(jreply, "directories", directories);
CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(jreply)));
jparse_free(jreply);
@ -1320,6 +1335,13 @@ play_item_with_id(const char *param)
ret = player_playback_start_byitem(queue_item);
free_queue_item(queue_item, 0);
if (ret < 0)
{
DPRINTF(E_LOG, L_WEB, "Failed to start playback from item with id '%d'\n", item_id);
return HTTP_INTERNAL;
}
return HTTP_NOCONTENT;
}
@ -1353,6 +1375,13 @@ play_item_at_position(const char *param)
ret = player_playback_start_byitem(queue_item);
free_queue_item(queue_item, 0);
if (ret < 0)
{
DPRINTF(E_LOG, L_WEB, "Failed to start playback from position '%d'\n", position);
return HTTP_INTERNAL;
}
return HTTP_NOCONTENT;
}
@ -1737,43 +1766,27 @@ queue_tracks_add_playlist(const char *id, int pos)
}
static int
jsonapi_reply_queue_tracks_add(struct httpd_request *hreq)
queue_tracks_add_byuris(const char *param, int pos, int *total_count)
{
const char *param;
char *uris;
char *uri;
char *ptr;
const char *id;
int pos = -1;
int count = 0;
int ttl_count = 0;
json_object *reply;
int ret = 0;
*total_count = 0;
param = evhttp_find_header(hreq->query, "position");
if (param)
CHECK_NULL(L_WEB, uris = strdup(param));
uri = strtok_r(uris, ",", &ptr);
if (!uri)
{
if (safe_atoi32(param, &pos) < 0)
{
DPRINTF(E_LOG, L_WEB, "Invalid position parameter '%s'\n", param);
return HTTP_BADREQUEST;
}
DPRINTF(E_DBG, L_WEB, "Add tracks starting at position '%d\n", pos);
DPRINTF(E_LOG, L_WEB, "Empty query parameter 'uris'\n");
free(uris);
return -1;
}
param = evhttp_find_header(hreq->query, "uris");
if (!param)
{
DPRINTF(E_LOG, L_WEB, "Missing query parameter 'uris'\n");
return HTTP_BADREQUEST;
}
uris = strdup(param);
uri = strtok(uris, ",");
do
{
count = 0;
@ -1812,16 +1825,117 @@ jsonapi_reply_queue_tracks_add(struct httpd_request *hreq)
if (pos >= 0)
pos += count;
ttl_count += count;
*total_count += count;
}
while ((uri = strtok(NULL, ",")));
while ((uri = strtok_r(NULL, ",", &ptr)));
free(uris);
return ret;
}
static int
queue_tracks_add_byexpression(const char *param, int pos, int *total_count)
{
char *expression;
struct smartpl smartpl_expression;
struct query_params query_params;
struct player_status status;
int ret;
memset(&query_params, 0, sizeof(struct query_params));
query_params.type = Q_ITEMS;
query_params.sort = S_ALBUM;
query_params.idx_type = I_NONE;
memset(&smartpl_expression, 0, sizeof(struct smartpl));
expression = safe_asprintf("\"query\" { %s }", param);
ret = smartpl_query_parse_string(&smartpl_expression, expression);
free(expression);
if (ret < 0)
return -1;
query_params.filter = strdup(smartpl_expression.query_where);
free_smartpl(&smartpl_expression, 1);
player_get_status(&status);
ret = db_queue_add_by_query(&query_params, status.shuffle, status.item_id, pos, total_count, NULL);
free(query_params.filter);
return ret;
}
static int
jsonapi_reply_queue_tracks_add(struct httpd_request *hreq)
{
const char *param_pos;
const char *param_uris;
const char *param_expression;
const char *param;
int pos = -1;
bool shuffle;
int total_count = 0;
json_object *reply;
int ret = 0;
param_pos = evhttp_find_header(hreq->query, "position");
if (param_pos)
{
if (safe_atoi32(param_pos, &pos) < 0)
{
DPRINTF(E_LOG, L_WEB, "Invalid position parameter '%s'\n", param_pos);
return HTTP_BADREQUEST;
}
DPRINTF(E_DBG, L_WEB, "Add tracks starting at position '%d\n", pos);
}
param_uris = evhttp_find_header(hreq->query, "uris");
param_expression = evhttp_find_header(hreq->query, "expression");
if (!param_uris && !param_expression)
{
DPRINTF(E_LOG, L_WEB, "Missing query parameter 'uris' or 'expression'\n");
return HTTP_BADREQUEST;
}
// if query parameter "clear" is "true", stop playback and clear the queue before adding new queue items
param = evhttp_find_header(hreq->query, "clear");
if (param && strcmp(param, "true") == 0)
{
player_playback_stop();
db_queue_clear(0);
}
// if query parameter "shuffle" is present, update the shuffle state before adding new queue items
param = evhttp_find_header(hreq->query, "shuffle");
if (param)
{
shuffle = (strcmp(param, "true") == 0);
player_shuffle_set(shuffle);
}
if (param_uris)
{
ret = queue_tracks_add_byuris(param_uris, pos, &total_count);
}
else
{
ret = queue_tracks_add_byexpression(param_expression, pos, &total_count);
}
if (ret == 0)
{
reply = json_object_new_object();
json_object_object_add(reply, "count", json_object_new_int(ttl_count));
json_object_object_add(reply, "count", json_object_new_int(total_count));
ret = evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(reply));
jparse_free(reply);
@ -1830,6 +1944,13 @@ jsonapi_reply_queue_tracks_add(struct httpd_request *hreq)
if (ret < 0)
return HTTP_INTERNAL;
// If query parameter "playback" is "start", start playback after successfully adding new items
param = evhttp_find_header(hreq->query, "playback");
if (param && strcmp(param, "start") == 0)
{
player_playback_start();
}
return HTTP_OK;
}
@ -2726,7 +2847,7 @@ jsonapi_reply_library_files(struct httpd_request *hreq)
goto error;
query_params.type = Q_ITEMS;
query_params.sort = S_NAME;
query_params.sort = S_VPATH;
query_params.filter = db_mprintf("(f.directory_id = %d)", directory_id);
ret = fetch_tracks(&query_params, tracks_items, &total);
@ -3181,6 +3302,9 @@ jsonapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
status_code = hreq->handler(hreq);
if (status_code >= 400)
DPRINTF(E_LOG, L_WEB, "JSON api request failed with error code %d (%s)\n", status_code, uri_parsed->uri);
switch (status_code)
{
case HTTP_OK: /* 200 OK */

View File

@ -129,6 +129,10 @@ process_notify_request(struct ws_session_data_notify *session_data, void *in, si
{
session_data->events |= LISTENER_UPDATE;
}
else if (0 == strcmp(event_type, "database"))
{
session_data->events |= LISTENER_DATABASE;
}
else if (0 == strcmp(event_type, "pairing"))
{
session_data->events |= LISTENER_PAIRING;
@ -193,6 +197,10 @@ send_notify_reply(short events, struct lws* wsi)
{
json_object_array_add(notify, json_object_new_string("update"));
}
if (events & LISTENER_DATABASE)
{
json_object_array_add(notify, json_object_new_string("database"));
}
if (events & LISTENER_PAIRING)
{
json_object_array_add(notify, json_object_new_string("pairing"));
@ -306,7 +314,7 @@ static struct lws_protocols protocols[] =
static void *
websocket(void *arg)
{
listener_add(listener_cb, LISTENER_UPDATE | LISTENER_PAIRING | LISTENER_SPOTIFY | LISTENER_LASTFM | LISTENER_SPEAKER
listener_add(listener_cb, LISTENER_UPDATE | LISTENER_DATABASE | LISTENER_PAIRING | LISTENER_SPOTIFY | LISTENER_LASTFM | LISTENER_SPEAKER
| LISTENER_PLAYER | LISTENER_OPTIONS | LISTENER_VOLUME | LISTENER_QUEUE);
while(!ws_exit)