mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-27 15:45:56 -05:00
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:
commit
47bdff255c
@ -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) |
|
||||
|
14
src/http.c
14
src/http.c
@ -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);
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user