diff --git a/src/Makefile.am b/src/Makefile.am index f6fa1f4a..c441591e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -115,6 +115,7 @@ forked_daapd_SOURCES = main.c \ httpd_jsonapi.c httpd_jsonapi.h \ httpd_streaming.c httpd_streaming.h \ httpd_oauth.c httpd_oauth.h \ + httpd_artworkapi.c httpd_artworkapi.h \ http.c http.h \ dmap_common.c dmap_common.h \ transcode.h artwork.h \ diff --git a/src/httpd.c b/src/httpd.c index 7f7af451..5a9fe26d 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -63,6 +63,7 @@ #include "httpd_jsonapi.h" #include "httpd_streaming.h" #include "httpd_oauth.h" +#include "httpd_artworkapi.h" #include "transcode.h" #ifdef LASTFM # include "lastfm.h" @@ -842,6 +843,11 @@ httpd_gen_cb(struct evhttp_request *req, void *arg) jsonapi_request(req, parsed); goto out; } + else if (artworkapi_is_request(parsed->path)) + { + artworkapi_request(req, parsed); + goto out; + } else if (streaming_is_request(parsed->path)) { streaming_request(req, parsed); @@ -1679,6 +1685,14 @@ httpd_init(const char *webroot) goto jsonapi_fail; } + ret = artworkapi_init(); + if (ret < 0) + { + DPRINTF(E_FATAL, L_HTTPD, "Artwork init failed\n"); + + goto artworkapi_fail; + } + ret = oauth_init(); if (ret < 0) { @@ -1814,6 +1828,8 @@ httpd_init(const char *webroot) #endif oauth_deinit(); oauth_fail: + artworkapi_deinit(); + artworkapi_fail: jsonapi_deinit(); jsonapi_fail: dacp_deinit(); diff --git a/src/httpd_artworkapi.c b/src/httpd_artworkapi.c new file mode 100644 index 00000000..df6d0827 --- /dev/null +++ b/src/httpd_artworkapi.c @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2018 Espen Jürgensen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include + +#include "httpd_artworkapi.h" +#include "logger.h" +#include "misc.h" +#include "player.h" +#include "artwork.h" + +static int +request_process(struct httpd_request *hreq, uint32_t *max_w, uint32_t *max_h) +{ + const char *param; + int ret; + + *max_w = 0; + *max_h = 0; + + param = evhttp_find_header(hreq->query, "maxwidth"); + if (param) + { + ret = safe_atou32(param, max_w); + if (ret < 0) + DPRINTF(E_LOG, L_WEB, "Invalid width in request: '%s'\n", hreq->uri_parsed->uri); + } + + param = evhttp_find_header(hreq->query, "maxheight"); + if (param) + { + ret = safe_atou32(param, max_h); + if (ret < 0) + DPRINTF(E_LOG, L_WEB, "Invalid height in request: '%s'\n", hreq->uri_parsed->uri); + } + + return 0; +} + +static int +response_process(struct httpd_request *hreq, int format) +{ + struct evkeyvalq *headers; + + headers = evhttp_request_get_output_headers(hreq->req); + + if (format == ART_FMT_PNG) + evhttp_add_header(headers, "Content-Type", "image/png"); + else if (format == ART_FMT_JPEG) + evhttp_add_header(headers, "Content-Type", "image/jpeg"); + else + return HTTP_NOCONTENT; + + return HTTP_OK; +} + +static int +artworkapi_reply_nowplaying(struct httpd_request *hreq) +{ + uint32_t max_w; + uint32_t max_h; + uint32_t id; + int ret; + + ret = request_process(hreq, &max_w, &max_h); + if (ret != 0) + return ret; + + ret = player_now_playing(&id); + if (ret != 0) + return HTTP_NOTFOUND; + + ret = artwork_get_item(hreq->reply, id, max_w, max_h); + + return response_process(hreq, ret); +} + +static int +artworkapi_reply_item(struct httpd_request *hreq) +{ + uint32_t max_w; + uint32_t max_h; + uint32_t id; + int ret; + + ret = request_process(hreq, &max_w, &max_h); + if (ret != 0) + return ret; + + ret = safe_atou32(hreq->uri_parsed->path_parts[2], &id); + if (ret != 0) + return HTTP_BADREQUEST; + + ret = artwork_get_item(hreq->reply, id, max_w, max_h); + + return response_process(hreq, ret); +} + +static int +artworkapi_reply_group(struct httpd_request *hreq) +{ + uint32_t max_w; + uint32_t max_h; + uint32_t id; + int ret; + + ret = request_process(hreq, &max_w, &max_h); + if (ret != 0) + return ret; + + ret = safe_atou32(hreq->uri_parsed->path_parts[2], &id); + if (ret != 0) + return HTTP_BADREQUEST; + + ret = artwork_get_group(hreq->reply, id, max_w, max_h); + + return response_process(hreq, ret); +} + +static struct httpd_uri_map artworkapi_handlers[] = +{ + { EVHTTP_REQ_GET, "^/artwork/nowplaying$", artworkapi_reply_nowplaying }, + { EVHTTP_REQ_GET, "^/artwork/item/[[:digit:]]+$", artworkapi_reply_item }, + { EVHTTP_REQ_GET, "^/artwork/group/[[:digit:]]+$", artworkapi_reply_group }, + { 0, NULL, NULL } +}; + + +/* ------------------------------- API --------------------------------- */ +void +artworkapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed) +{ + struct httpd_request *hreq; + int status_code; + + DPRINTF(E_DBG, L_WEB, "Artwork api request: '%s'\n", uri_parsed->uri); + + if (!httpd_admin_check_auth(req)) + return; + + hreq = httpd_request_parse(req, uri_parsed, NULL, artworkapi_handlers); + if (!hreq) + { + DPRINTF(E_LOG, L_WEB, "Unrecognized path '%s' in artwork api request: '%s'\n", uri_parsed->path, uri_parsed->uri); + + httpd_send_error(req, HTTP_BADREQUEST, "Bad Request"); + return; + } + + CHECK_NULL(L_WEB, hreq->reply = evbuffer_new()); + + status_code = hreq->handler(hreq); + + switch (status_code) + { + case HTTP_OK: /* 200 OK */ + httpd_send_reply(req, status_code, "OK", hreq->reply, HTTPD_SEND_NO_GZIP); + break; + case HTTP_NOCONTENT: /* 204 No Content */ + httpd_send_reply(req, status_code, "No Content", hreq->reply, HTTPD_SEND_NO_GZIP); + break; + case HTTP_NOTMODIFIED: /* 304 Not Modified */ + httpd_send_reply(req, HTTP_NOTMODIFIED, NULL, NULL, HTTPD_SEND_NO_GZIP); + break; + case HTTP_BADREQUEST: /* 400 Bad Request */ + httpd_send_error(req, status_code, "Bad Request"); + break; + case HTTP_NOTFOUND: /* 404 Not Found */ + httpd_send_error(req, status_code, "Not Found"); + break; + case HTTP_INTERNAL: /* 500 Internal Server Error */ + default: + httpd_send_error(req, HTTP_INTERNAL, "Internal Server Error"); + } + + evbuffer_free(hreq->reply); + free(hreq); +} + +int +artworkapi_is_request(const char *path) +{ + if (strncmp(path, "/artwork/", strlen("/artwork/")) == 0) + return 1; + + return 0; +} + +int +artworkapi_init(void) +{ + char buf[64]; + int i; + int ret; + + for (i = 0; artworkapi_handlers[i].handler; i++) + { + ret = regcomp(&artworkapi_handlers[i].preg, artworkapi_handlers[i].regexp, REG_EXTENDED | REG_NOSUB); + if (ret != 0) + { + regerror(ret, &artworkapi_handlers[i].preg, buf, sizeof(buf)); + + DPRINTF(E_FATAL, L_WEB, "artwork api init failed; regexp error: %s\n", buf); + return -1; + } + } + + return 0; +} + +void +artworkapi_deinit(void) +{ + int i; + + for (i = 0; artworkapi_handlers[i].handler; i++) + regfree(&artworkapi_handlers[i].preg); +} diff --git a/src/httpd_artworkapi.h b/src/httpd_artworkapi.h new file mode 100644 index 00000000..7c7163c5 --- /dev/null +++ b/src/httpd_artworkapi.h @@ -0,0 +1,18 @@ +#ifndef __HTTPD_ARTWORK_H__ +#define __HTTPD_ARTWORK_H__ + +#include "httpd.h" + +int +artworkapi_init(void); + +void +artworkapi_deinit(void); + +void +artworkapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed); + +int +artworkapi_is_request(const char *path); + +#endif diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 3c683cfa..d2b598c0 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -117,6 +117,7 @@ artist_to_json(struct db_group_info *dbgri) { json_object *item; char uri[100]; + char artwork_url[100]; int ret; item = json_object_new_object(); @@ -132,6 +133,10 @@ artist_to_json(struct db_group_info *dbgri) 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; } @@ -140,6 +145,7 @@ album_to_json(struct db_group_info *dbgri) { json_object *item; char uri[100]; + char artwork_url[100]; int ret; item = json_object_new_object(); @@ -156,6 +162,10 @@ album_to_json(struct db_group_info *dbgri) 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; } @@ -164,6 +174,7 @@ track_to_json(struct db_media_file_info *dbmfi) { json_object *item; char uri[100]; + char artwork_url[100]; int intval; int ret; @@ -207,6 +218,10 @@ track_to_json(struct db_media_file_info *dbmfi) 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; } @@ -1466,6 +1481,7 @@ jsonapi_reply_player(struct httpd_request *hreq) json_object_object_add(reply, "item_id", json_object_new_int(status.item_id)); json_object_object_add(reply, "item_length_ms", json_object_new_int(status.len_ms)); json_object_object_add(reply, "item_progress_ms", json_object_new_int(status.pos_ms)); + json_object_object_add(reply, "artwork_url", json_object_new_string("/artwork/nowplaying")); } else { @@ -1498,6 +1514,7 @@ queue_item_to_json(struct db_queue_item *queue_item, char shuffle) { json_object *item; char uri[100]; + char artwork_url[100]; int ret; item = json_object_new_object(); @@ -1534,10 +1551,15 @@ queue_item_to_json(struct db_queue_item *queue_item, char shuffle) ret = snprintf(uri, sizeof(uri), "%s:%s:%d", "library", "track", queue_item->file_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/%d", queue_item->file_id); + if (ret < sizeof(artwork_url)) + json_object_object_add(item, "artwork_url", json_object_new_string(artwork_url)); } else { safe_json_add_string(item, "uri", queue_item->path); + safe_json_add_string(item, "artwork_url", queue_item->artwork_url); } return item;