diff --git a/Makefile b/Makefile index c63cbe58..63629f4a 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ CFLAGS += -Wmissing-prototypes -fms-extensions CFLAGS += -g -funsigned-char -O2 CFLAGS += -D_FILE_OFFSET_BITS=64 CFLAGS += -I${BUILDDIR} -I${CURDIR}/src -I${CURDIR} -LDFLAGS += -lrt -ldl -lpthread -lm -lcurl +LDFLAGS += -lrt -ldl -lpthread -lm # # Other config @@ -109,6 +109,7 @@ SRCS = src/main.c \ src/config2.c \ src/lang_codes.c \ src/lang_str.c \ + src/imagecache.c SRCS += src/epggrab/module.c\ src/epggrab/channel.c\ @@ -140,8 +141,6 @@ SRCS += src/muxer.c \ src/muxer_pass.c \ src/muxer_tvh.c \ -SRCS += src/iconserve.c \ - # # Optional code # diff --git a/configure b/configure index 8e02c1b4..765a9898 100755 --- a/configure +++ b/configure @@ -20,6 +20,7 @@ OPTIONS=( "v4l:yes" "linuxdvb:yes" "dvbscan:yes" + "imagecache:auto" "avahi:auto" "zlib:auto" "bundle:no" @@ -126,7 +127,17 @@ if enabled cwc && enabled dvbcsa; then die "Failed to find dvbcsa support (use --disable-dvbcsa)" LDFLAGS="$LDFLAGS -ldvbcsa" fi - + +# +# Icon caching +# +if enabled_or_auto imagecache; then + if check_pkg libcurl; then + enable imagecache + elif enabled imagecache; then + die "Libcurl support not found (use --disable-imagecache)" + fi +fi # ########################################################################### # Write config diff --git a/src/channels.c b/src/channels.c index 7c8112ec..970da8e6 100644 --- a/src/channels.c +++ b/src/channels.c @@ -39,6 +39,7 @@ #include "notify.h" #include "dvr/dvr.h" #include "htsp_server.h" +#include "imagecache.h" struct channel_tree channel_name_tree; static struct channel_tree channel_identifier_tree; @@ -268,6 +269,7 @@ channel_load_one(htsmsg_t *c, int id) epggrab_channel_add(ch); tvh_str_update(&ch->ch_icon, htsmsg_get_str(c, "icon")); + imagecache_get_id(ch->ch_icon); htsmsg_get_s32(c, "dvr_extra_time_pre", &ch->ch_dvr_extra_time_pre); htsmsg_get_s32(c, "dvr_extra_time_post", &ch->ch_dvr_extra_time_post); @@ -452,6 +454,7 @@ channel_set_icon(channel_t *ch, const char *icon) free(ch->ch_icon); ch->ch_icon = strdup(icon); + imagecache_get_id(icon); channel_save(ch); htsp_channel_update(ch); } diff --git a/src/config2.c b/src/config2.c index b90db4fe..5bcbd0d1 100644 --- a/src/config2.c +++ b/src/config2.c @@ -74,50 +74,3 @@ int config_set_muxconfpath ( const char *path ) } return 0; } - -const char *config_get_iconserve ( void ) -{ - return htsmsg_get_str(config, "iconserve"); -} - -int config_set_iconserve ( const char *setting ) -{ - const char *c = config_get_iconserve(); - if (!c || strcmp(c, setting)) { - if (c) htsmsg_delete_field(config, "iconserve"); - htsmsg_add_str(config, "iconserve", setting); - return 1; - } - return 0; -} -const char *config_get_iconserve_periodicdownload ( void ) -{ - return htsmsg_get_str(config, "iconserve_periodicdownload"); -} - -int config_set_iconserve_periodicdownload ( const char *setting ) -{ - const char *c = config_get_iconserve_periodicdownload(); - if (!c || strcmp(c, setting)) { - if (c) htsmsg_delete_field(config, "iconserve_periodicdownload"); - htsmsg_add_str(config, "iconserve_periodicdownload", setting); - return 1; - } - return 0; -} - -const char *config_get_serverip ( void ) -{ - return htsmsg_get_str(config, "serverip"); -}; - -int config_set_serverip ( const char *setting ) -{ - const char *c = config_get_serverip(); - if (!c || strcmp(c, setting)) { - if (c) htsmsg_delete_field(config, "serverip"); - htsmsg_add_str(config, "serverip", setting); - return 1; - } - return 0; -}; diff --git a/src/config2.h b/src/config2.h index 6e90be6f..cd68e306 100644 --- a/src/config2.h +++ b/src/config2.h @@ -32,18 +32,6 @@ const char *config_get_muxconfpath ( void ); int config_set_muxconfpath ( const char *str ) __attribute__((warn_unused_result)); -const char *config_get_iconserve ( void ); -int config_set_iconserve ( const char *str ) - __attribute__((warn_unused_result)); - -const char *config_get_iconserve_periodicdownload ( void ); -int config_set_iconserve_periodicdownload ( const char *str ) - __attribute__((warn_unused_result)); - -const char *config_get_serverip ( void ); -int config_set_serverip ( const char *str ) - __attribute__((warn_unused_result)); - const char *config_get_language ( void ); int config_set_language ( const char *str ) __attribute__((warn_unused_result)); diff --git a/src/epg.c b/src/epg.c index fafb65dd..6984fa0e 100644 --- a/src/epg.c +++ b/src/epg.c @@ -32,6 +32,7 @@ #include "dvr/dvr.h" #include "htsp_server.h" #include "epggrab.h" +#include "imagecache.h" /* Broadcast hashing */ #define EPG_HASH_WIDTH 1024 @@ -459,8 +460,12 @@ int epg_brand_set_summary int epg_brand_set_image ( epg_brand_t *brand, const char *image, epggrab_module_t *src ) { + int save; if (!brand || !image) return 0; - return _epg_object_set_str(brand, &brand->image, image, src); + save = _epg_object_set_str(brand, &brand->image, image, src); + if (save) + imagecache_get_id(image); + return save; } int epg_brand_set_season_count @@ -628,8 +633,12 @@ int epg_season_set_summary int epg_season_set_image ( epg_season_t *season, const char *image, epggrab_module_t *src ) { + int save; if (!season || !image) return 0; - return _epg_object_set_str(season, &season->image, image, src); + save = _epg_object_set_str(season, &season->image, image, src); + if (save) + imagecache_get_id(image); + return save; } int epg_season_set_episode_count @@ -891,8 +900,12 @@ int epg_episode_set_description int epg_episode_set_image ( epg_episode_t *episode, const char *image, epggrab_module_t *src ) { + int save; if (!episode || !image) return 0; - return _epg_object_set_str(episode, &episode->image, image, src); + save = _epg_object_set_str(episode, &episode->image, image, src); + if (save) + imagecache_get_id(image); + return save; } int epg_episode_set_number diff --git a/src/htsp_server.c b/src/htsp_server.c index ba10139a..243ff77e 100644 --- a/src/htsp_server.c +++ b/src/htsp_server.c @@ -42,8 +42,7 @@ #include "htsmsg_binary.h" #include "epg.h" #include "plumbing/tsfix.h" -#include "iconserve.h" -#include "config2.h" +#include "imagecache.h" #include #include "settings.h" @@ -434,7 +433,7 @@ htsp_file_destroy(htsp_file_t *hf) * */ static htsmsg_t * -htsp_build_channel(channel_t *ch, const char *method) +htsp_build_channel(channel_t *ch, const char *method, htsp_connection_t *htsp) { channel_tag_mapping_t *ctm; channel_tag_t *ct; @@ -449,10 +448,26 @@ htsp_build_channel(channel_t *ch, const char *method) htsmsg_add_u32(out, "channelNumber", ch->ch_number); htsmsg_add_str(out, "channelName", ch->ch_name); - if(ch->ch_icon != NULL) { - htsmsg_add_str(out, "channelIcon", logo_query(ch->ch_id, ch->ch_icon)); - }; + uint32_t id = imagecache_get_id(ch->ch_icon); + if (id) { + size_t p = 0; + char url[256]; + if (htsp->htsp_version <= 7) { + strcpy(url, "http://"); + p = 7; + inet_ntop(AF_INET, &(htsp->htsp_peer->sin_addr), url+p, sizeof(url)-p); + p = strlen(url); + p += snprintf(url+p, sizeof(url)-p, ":%hd", webui_port); + } + if (tvheadend_webroot) + p += snprintf(url+p, sizeof(url)-p, "%s", tvheadend_webroot); + snprintf(url+p, sizeof(url)-p, "/imagecache/%d", id); + htsmsg_add_str(out, "channelIcon", url); + } else { + htsmsg_add_str(out, "channelIcon", ch->ch_icon); + } + } now = ch->ch_epg_now; next = ch->ch_epg_next; @@ -802,7 +817,7 @@ htsp_method_async(htsp_connection_t *htsp, htsmsg_t *in) /* Send all channels */ RB_FOREACH(ch, &channel_name_tree, ch_name_link) - htsp_send_message(htsp, htsp_build_channel(ch, "channelAdd"), NULL); + htsp_send_message(htsp, htsp_build_channel(ch, "channelAdd", htsp), NULL); /* Send all enabled and external tags (now with channel mappings) */ TAILQ_FOREACH(ct, &channel_tags, ct_link) @@ -1880,23 +1895,30 @@ htsp_channel_update_current(channel_t *ch) /** * Called from channel.c when a new channel is created */ +static void +_htsp_channel_update(channel_t *ch, const char *msg) +{ + htsp_connection_t *htsp; + LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link) + if (htsp->htsp_async_mode & HTSP_ASYNC_ON) + htsp_send_message(htsp, htsp_build_channel(ch, msg, htsp), NULL); +} + void htsp_channel_add(channel_t *ch) { - htsp_async_send(htsp_build_channel(ch, "channelAdd"), HTSP_ASYNC_ON); + _htsp_channel_update(ch, "channelAdd"); } - /** * Called from channel.c when a channel is updated */ void htsp_channel_update(channel_t *ch) { - htsp_async_send(htsp_build_channel(ch, "channelUpdate"), HTSP_ASYNC_ON); + _htsp_channel_update(ch, "channelUpdate"); } - /** * Called from channel.c when a channel is deleted */ diff --git a/src/iconserve.c b/src/iconserve.c deleted file mode 100644 index 85f0fe04..00000000 --- a/src/iconserve.c +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Icon file server operations - * Copyright (C) 2012 Andy Brown - * - * 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 3 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, see . - */ - -#define CURL_STATICLIB -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "settings.h" -#include "tvheadend.h" -#include "channels.h" -#include "http.h" -#include "webui/webui.h" -#include "filebundle.h" -#include "iconserve.h" -#include "config2.h" -#include "queue.h" -#include "spawn.h" - -/* Queue, Cond to signal data and Mutex to protect it */ -static TAILQ_HEAD(,iconserve_grab_queue) iconserve_queue; -static pthread_mutex_t iconserve_mutex; -static pthread_cond_t iconserve_cond; - -/** - * https://github.com/andyb2000 Function to provide local icon files - */ -int -page_logo(http_connection_t *hc, const char *remain, void *opaque) -{ - const char *homedir = hts_settings_get_root(); - channel_t *ch = NULL; - char *inpath, *inpath2; - const char *outpath = "none"; - char homepath[254]; - char iconpath[100]; - pthread_mutex_lock(&global_lock); - fb_file *fp; - ssize_t size; - char buf[4096]; - char *mimetest_outbuf; - - if(remain == NULL) { - pthread_mutex_unlock(&global_lock); - return 404; - }; - - if(strstr(remain, "..")) { - pthread_mutex_unlock(&global_lock); - return HTTP_STATUS_BAD_REQUEST; - }; - - ch = channel_find_by_identifier(atoi(remain)); - if (ch == NULL || ch->ch_icon == NULL) { - pthread_mutex_unlock(&global_lock); - return 404; - }; - - snprintf(homepath, sizeof(homepath), "%s/icons", homedir); - inpath = NULL; - inpath2 = NULL; - outpath = NULL; - /* split icon to last component */ - inpath = strdup(ch->ch_icon); - inpath2 = strtok(inpath, "/"); - while (inpath2 != NULL) { - inpath2 = strtok(NULL, "/"); - if (inpath2 != NULL) { - outpath = strdup(inpath2); - }; - }; - snprintf(iconpath, sizeof(iconpath), "%s/%s", homepath, outpath); - fp = fb_open(iconpath, 1, 0); - if (!fp) { - tvhlog(LOG_DEBUG, "page_logo", - "failed to open %s redirecting to http link for icon (%s)", - iconpath, ch->ch_icon); - http_redirect(hc, ch->ch_icon); - iconserve_queue_add ( ch->ch_id, ch->ch_icon ); - } else { - tvhlog(LOG_DEBUG, "page_logo", "File %s opened", iconpath); - size = fb_size(fp); - mimetest_outbuf = strdup("image/jpeg"); - http_send_header(hc, 200, mimetest_outbuf, size, NULL, NULL, 300, 0, NULL); - while (!fb_eof(fp)) { - ssize_t c = fb_read(fp, buf, sizeof(buf)); - if (c < 0) { - break; - }; - if (write(hc->hc_fd, buf, c) != c) { - break; - }; - }; - fb_close(fp); - }; - - pthread_mutex_unlock(&global_lock); - return 0; -} - -/* -* Logo loader functions, called from http htsp -* Will return local cache url instead of icon stored -*/ -const char -*logo_query(int ch_id, const char *ch_icon) -{ - const char *setting = config_get_iconserve(); - const char *serverip = config_get_serverip(); - char outiconpath[255]; - char *return_icon = strdup(ch_icon); - - if (!setting || !*setting || (strcmp(setting, "off") == 0)) { - return return_icon; - }; - - if (!serverip || !*serverip) { - return return_icon; - }; - - snprintf(outiconpath, sizeof(outiconpath), - "http://%s:%d/channellogo/%d", serverip, webui_port, ch_id); - return_icon = strdup(outiconpath); -return return_icon; -}; - -/* - * Icon grabber queue thread - */ -void *iconserve_thread ( void *aux ) -{ - iconserve_grab_queue_t *qe; - pthread_mutex_lock(&iconserve_mutex); - char *inpath, *inpath2; - const char *header_parse = NULL, *header_maxage = NULL; - const char *outpath = "none"; - CURL *curl; - FILE *curl_fp, *curl_fp_header; - CURLcode res; - fb_file *fp; - char iconpath[100], iconpath_header[100]; - char homepath[254]; - const char *homedir = hts_settings_get_root(); - struct stat fileStat; - int trigger_download = 0; - char buf[256]; - int file = 0; - time_t seconds; - int dif, compare_seconds, rc; - const char *periodicdownload = config_get_iconserve_periodicdownload(); - struct timespec timertrigger; - channel_t *ch; - - tvhlog(LOG_INFO, "iconserve_thread", "Thread startup"); - curl = curl_easy_init(); - snprintf(homepath, sizeof(homepath), "%s/icons", homedir); - if(stat(homepath, &fileStat) == 0 || mkdir(homepath, 0700) == 0) { - if (curl) { - while (1) { - - /* Get entry from queue */ - qe = TAILQ_FIRST(&iconserve_queue); - /* Check for queue data */ - if (!qe) { /* Queue Empty */ - periodicdownload = config_get_iconserve_periodicdownload(); - if (!periodicdownload || !*periodicdownload || - (strcmp(periodicdownload, "off") == 0)) { - tvhlog(LOG_DEBUG, "iconserve_thread", "Non-timer wakeup"); - rc = pthread_cond_wait(&iconserve_cond, &iconserve_mutex); - } else { - tvhlog(LOG_DEBUG, "iconserve_thread", "Timer wakeup set"); - timertrigger.tv_sec = time(NULL) + 86400; - timertrigger.tv_nsec = 0; - rc = pthread_cond_timedwait(&iconserve_cond, - &iconserve_mutex, &timertrigger); - }; - if (rc == ETIMEDOUT) { - tvhlog(LOG_INFO, "iconserve_thread", "Thread wakeup by timer"); - RB_FOREACH(ch, &channel_name_tree, ch_name_link) { - if (ch->ch_icon != NULL) { - iconserve_grab_queue_t *qe = calloc(1, sizeof(iconserve_grab_queue_t)); - qe->chan_number = ch->ch_id; - qe->icon_url = ch->ch_icon; - TAILQ_INSERT_TAIL(&iconserve_queue, qe, iconserve_link); - }; - }; - }; - continue; - } - TAILQ_REMOVE(&iconserve_queue, qe, iconserve_link); - pthread_mutex_unlock(&iconserve_mutex); - - inpath = NULL; - inpath2 = NULL; - outpath = NULL; - curl_fp = NULL; - /* split icon to last component */ - inpath = strdup(qe->icon_url); - inpath2 = strtok(inpath, "/"); - while (inpath2 != NULL) { - inpath2 = strtok(NULL, "/"); - if (inpath2 != NULL) - outpath = strdup(inpath2); - }; - if (outpath != NULL) { - snprintf(iconpath, sizeof(iconpath), "%s/%s", homepath, outpath); - snprintf(iconpath_header, sizeof(iconpath_header), "%s/%s.head", - homepath, outpath); - fp = fb_open(iconpath, 0, 1); - if (!fp) { - /* No file exists so grab immediately */ - tvhlog(LOG_INFO, "logo_loader", "No logo, downloading file %s", outpath); - trigger_download = 1; - } else { - /* File exists so compare expiry times to re-grab */ - fb_close(fp); - fp = fb_open(iconpath_header, 0, 0); - while (!fb_eof(fp)) { - memset(buf, 0, sizeof(buf)); - if (!fb_gets(fp, buf, sizeof(buf) - 1)) break; - if (buf[strlen(buf) - 1] == '\n') { - buf[strlen(buf) - 1] = '\0'; - }; - if(strstr(buf, "Cache-Control: ")) { - header_parse = strtok(buf, "="); - header_parse = strtok ( NULL, "="); - header_maxage = strdup(header_parse); - }; - }; - fb_close(fp); - file=open(iconpath, O_RDONLY); - fstat(file,&fileStat); - seconds = time (NULL); - dif = difftime (seconds,fileStat.st_mtime); - compare_seconds=atoi(header_maxage); - if (dif > compare_seconds) { - tvhlog(LOG_DEBUG, "logo_loader", "Logo expired, downloading %s", outpath); - trigger_download = 1; - } else { - tvhlog(LOG_INFO, "logo_loader", "Logo not expired %s", outpath); - }; - close(file); - }; - if (trigger_download == 1) { - curl_fp=fopen(iconpath,"wb"); - curl_fp_header=fopen(iconpath_header,"w"); - curl_easy_setopt(curl, CURLOPT_URL, qe->icon_url); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl_fp); - curl_easy_setopt(curl, CURLOPT_WRITEHEADER, curl_fp_header); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "TVHeadend"); - curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120); - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); - res = curl_easy_perform(curl); - if (res == 0) { - tvhlog(LOG_INFO, "logo_loader", "Downloaded icon via curl (%s)", - qe->icon_url); - } else { - tvhlog(LOG_WARNING, "logo_loader", "Error with curl download (%s)", - qe->icon_url); - }; - fclose(curl_fp); - fclose(curl_fp_header); - trigger_download = 0; - }; - }; - }; /* while loop */ - curl_easy_cleanup(curl); - } else { - tvhlog(LOG_WARNING, "iconserve", "CURL cannot initialise"); - }; - }; - return NULL; -}; - -/* - * Add data to the queue - */ -void iconserve_queue_add ( int chan_number, char *icon_url ) -{ - /* Create entry */ - tvhlog(LOG_DEBUG, "iconserve_queue_add", "Adding chan_number to queue: %i", - chan_number); - iconserve_grab_queue_t *qe = calloc(1, sizeof(iconserve_grab_queue_t)); - qe->chan_number = chan_number; - qe->icon_url = strdup(icon_url); - - pthread_mutex_lock(&iconserve_mutex); - TAILQ_INSERT_TAIL(&iconserve_queue, qe, iconserve_link); - pthread_cond_signal(&iconserve_cond); - pthread_mutex_unlock(&iconserve_mutex); -} - - -/** - * Loader for icons, check config params and pull them in one go - */ -void -logo_loader(void) -{ - channel_t *ch; - const char *setting = config_get_iconserve(); - const char *serverip = config_get_serverip(); - - if (!setting || !*setting || (strcmp(setting, "off") == 0)) { - tvhlog(LOG_DEBUG, "logo_loader", "Disabled by config, skipping"); - return; - }; - - if (!serverip || !*serverip) { - tvhlog(LOG_ALERT, "logo_loader", "No server IP, skipping icon cache"); - return; - }; - - - pthread_t tid; - pthread_mutex_init(&iconserve_mutex, NULL); - pthread_cond_init(&iconserve_cond, NULL); - TAILQ_INIT(&iconserve_queue); - /* Start thread - presumably permanently active */ - pthread_create(&tid, NULL, iconserve_thread, NULL); // last param is passed as aux - // as this is single global - // you can probably use global - // vars - - tvhlog(LOG_INFO, "logo_loader", "Caching logos locally"); - /* loop through channels and load logo files */ - RB_FOREACH(ch, &channel_name_tree, ch_name_link) { - if (ch->ch_icon != NULL) { - iconserve_queue_add ( ch->ch_id, ch->ch_icon ); - }; - }; - pthread_mutex_lock(&iconserve_mutex); - pthread_cond_signal(&iconserve_cond); // tell thread data is available - pthread_mutex_unlock(&iconserve_mutex); -}; diff --git a/src/iconserve.h b/src/iconserve.h deleted file mode 100644 index ff8a0f92..00000000 --- a/src/iconserve.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Icon file serve operations - * Copyright (C) 2012 Andy Brown - * - * 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 3 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, see . - */ - -#ifndef ICONSERVE_H -#define ICONSERVE_H - -#include "http.h" - -/* Struct of entries for icon grabbing - * FIELD: chan_number - * FIELD: icon url - */ -typedef struct iconserve_grab_queue -{ - TAILQ_ENTRY(iconserve_grab_queue) iconserve_link; - int chan_number; - char *icon_url; -} iconserve_grab_queue_t; - - -int page_logo(http_connection_t *hc, const char *remain, void *opaque); -size_t write_data(void *ptr, size_t size, size_t nmemb, FILE *stream); - -void *iconserve_thread ( void *aux ); - -const char *logo_query(int ch_id, const char *ch_icon); - -void iconserve_queue_add ( int chan_number, char *icon_url ); - -void logo_loader(void); - -#endif /* ICONSERVE_H */ diff --git a/src/imagecache.c b/src/imagecache.c new file mode 100644 index 00000000..224c72cc --- /dev/null +++ b/src/imagecache.c @@ -0,0 +1,440 @@ +/* + * Icon file server operations + * Copyright (C) 2012 Andy Brown + * + * 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 3 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, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "settings.h" +#include "tvheadend.h" +#include "filebundle.h" +#include "imagecache.h" +#include "queue.h" +#include "redblack.h" + +#if ENABLE_IMAGECACHE +#define CURL_STATICLIB +#include +#include +#endif + +// TODO: icon cache flushing? +// TODO: md5 validation? +// TODO: allow cache to be disabled by users + +/* + * Image metadata + */ +typedef struct imagecache_image +{ + int id; ///< Internal ID + const char *url; ///< Upstream URL + int failed; ///< Last update failed + time_t updated; ///< Last time the file was checked + enum { + IDLE, + QUEUED, + FETCHING + } state; ///< fetch status + + TAILQ_ENTRY(imagecache_image) q_link; ///< Fetch Q link + RB_ENTRY(imagecache_image) id_link; ///< Index by ID + RB_ENTRY(imagecache_image) url_link; ///< Index by URL +} imagecache_image_t; + +static int _imagecache_id; +static RB_HEAD(,imagecache_image) _imagecache_by_id; +static RB_HEAD(,imagecache_image) _imagecache_by_url; + +pthread_mutex_t imagecache_mutex; + +static void _imagecache_save ( imagecache_image_t *img ); + +#if ENABLE_IMAGECACHE +uint32_t imagecache_enabled; +uint32_t imagecache_ok_period; +uint32_t imagecache_fail_period; + +static pthread_cond_t _imagecache_cond; +static TAILQ_HEAD(, imagecache_image) _imagecache_queue; +static void _imagecache_add ( imagecache_image_t *img ); +static void* _imagecache_thread ( void *p ); +static int _imagecache_fetch ( imagecache_image_t *img ); +#endif + +static int _url_cmp ( void *a, void *b ) +{ + return strcmp(((imagecache_image_t*)a)->url, ((imagecache_image_t*)b)->url); +} + +static int _id_cmp ( void *a, void *b ) +{ + return ((imagecache_image_t*)a)->id - ((imagecache_image_t*)b)->id; +} + +/* + * Initialise + */ +void imagecache_init ( void ) +{ + htsmsg_t *m, *e; + htsmsg_field_t *f; + imagecache_image_t *img, *i; + const char *url; + uint32_t id; + + /* Init vars */ + _imagecache_id = 0; +#if ENABLE_IMAGECACHE + imagecache_enabled = 0; + imagecache_ok_period = 24 * 7; // weekly + imagecache_fail_period = 24; // daily +#endif + + /* Create threads */ + pthread_mutex_init(&imagecache_mutex, NULL); +#if ENABLE_IMAGECACHE + pthread_cond_init(&_imagecache_cond, NULL); + TAILQ_INIT(&_imagecache_queue); +#endif + + /* Load settings */ +#if ENABLE_IMAGECACHE + if ((m = hts_settings_load("imagecache/config"))) { + htsmsg_get_u32(m, "enabled", &imagecache_enabled); + htsmsg_get_u32(m, "ok_period", &imagecache_ok_period); + htsmsg_get_u32(m, "fail_period", &imagecache_fail_period); + htsmsg_destroy(m); + } +#endif + if ((m = hts_settings_load("imagecache/meta"))) { + HTSMSG_FOREACH(f, m) { + if (!(e = htsmsg_get_map_by_field(f))) continue; + if (!(id = atoi(f->hmf_name))) continue; + if (!(url = htsmsg_get_str(e, "url"))) continue; + img = calloc(1, sizeof(imagecache_image_t)); + img->id = id; + img->url = strdup(url); + img->updated = htsmsg_get_s64_or_default(e, "updated", 0); + i = RB_INSERT_SORTED(&_imagecache_by_url, img, url_link, _url_cmp); + if (i) { + hts_settings_remove("imagecache/meta/%d", id); + hts_settings_remove("imagecache/data/%d", id); + free(img); + continue; + } + i = RB_INSERT_SORTED(&_imagecache_by_id, img, id_link, _id_cmp); + assert(!i); + if (id > _imagecache_id) + _imagecache_id = id; +#if ENABLE_IMAGECACHE + if (!img->updated) + _imagecache_add(img); +#endif + } + htsmsg_destroy(m); + } + + /* Start threads */ +#if ENABLE_IMAGECACHE + { + pthread_t tid; + pthread_create(&tid, NULL, _imagecache_thread, NULL); + } +#endif +} + +/* + * Save settings + */ +#if ENABLE_IMAGECACHE +void imagecache_save ( void ) +{ + htsmsg_t *m = htsmsg_create_map(); + htsmsg_add_u32(m, "enabled", imagecache_enabled); + htsmsg_add_u32(m, "ok_period", imagecache_ok_period); + htsmsg_add_u32(m, "fail_period", imagecache_fail_period); + hts_settings_save(m, "imagecache/config"); +} + +/* + * Enable/disable + */ +int imagecache_set_enabled ( uint32_t e ) +{ + if (e == imagecache_enabled) + return 0; + imagecache_enabled = e; + if (e) + pthread_cond_broadcast(&_imagecache_cond); + return 1; +} + +/* + * Set ok period + */ +int imagecache_set_ok_period ( uint32_t p ) +{ + if (p == imagecache_ok_period) + return 0; + imagecache_ok_period = p; + return 1; +} + +/* + * Set fail period + */ +int imagecache_set_fail_period ( uint32_t p ) +{ + if (p == imagecache_fail_period) + return 0; + imagecache_fail_period = p; + return 1; +} +#endif + +/* + * Fetch a URLs ID + */ +uint32_t imagecache_get_id ( const char *url ) +{ + uint32_t id = 0; + imagecache_image_t *i; + static imagecache_image_t *skel = NULL; + + /* Invalid */ + if (!url) + return 0; + + /* Disabled */ +#if !ENABLE_IMAGECACHE + if (strncasecmp(url, "file://", 7)) + return 0; +#endif + + /* Skeleton */ + if (!skel) + skel = calloc(1, sizeof(imagecache_image_t)); + skel->url = url; + + /* Create/Find */ + pthread_mutex_lock(&imagecache_mutex); + i = RB_INSERT_SORTED(&_imagecache_by_url, skel, url_link, _url_cmp); + if (!i) { + i = skel; + i->url = strdup(url); + i->id = ++_imagecache_id; + skel = RB_INSERT_SORTED(&_imagecache_by_id, i, id_link, _id_cmp); + assert(!skel); +#if ENABLE_IMAGECACHE + _imagecache_add(i); +#endif + _imagecache_save(i); + } +#if ENABLE_IMAGECACHE + if (!strncasecmp(url, "file://", 7) || imagecache_enabled) + id = i->id; +#else + if (!strncasecmp(url, "file://", 7)) + id = i->id; +#endif + pthread_mutex_unlock(&imagecache_mutex); + + return id; +} + +/* + * Get data + */ +int imagecache_open ( uint32_t id ) +{ + imagecache_image_t skel, *i; + int fd = -1; + + pthread_mutex_lock(&imagecache_mutex); + + /* Find */ + skel.id = id; + i = RB_FIND(&_imagecache_by_id, &skel, id_link, _id_cmp); + + /* Invalid */ + if (!i) { + pthread_mutex_unlock(&imagecache_mutex); + return -1; + } + + /* Local file */ + if (!strncasecmp(i->url, "file://", 7)) + fd = open(i->url + 7, O_RDONLY); + + /* Remote file */ +#if ENABLE_IMAGECACHE + else if (imagecache_enabled) { + struct timespec ts; + int err; + if (i->updated) { + // use existing + } else if (i->state == FETCHING) { + ts.tv_nsec = 0; + time(&ts.tv_sec); + ts.tv_sec += 10; // TODO: sensible timeout? + err = pthread_cond_timedwait(&_imagecache_cond, &imagecache_mutex, &ts); + if (err == ETIMEDOUT) { + pthread_mutex_unlock(&imagecache_mutex); + return -1; + } + } else if (i->state == QUEUED) { + i->state = FETCHING; + TAILQ_REMOVE(&_imagecache_queue, i, q_link); + pthread_mutex_unlock(&imagecache_mutex); + if (_imagecache_fetch(i)) + return -1; + pthread_mutex_lock(&imagecache_mutex); + } + fd = hts_settings_open_file(0, "imagecache/data/%d", i->id); + } +#endif + pthread_mutex_unlock(&imagecache_mutex); + + return fd; +} + +static void _imagecache_save ( imagecache_image_t *img ) +{ + htsmsg_t *m = htsmsg_create_map(); + + htsmsg_add_str(m, "url", img->url); + if (img->updated) + htsmsg_add_s64(m, "updated", img->updated); + + hts_settings_save(m, "imagecache/meta/%d", img->id); +} + +#if ENABLE_IMAGECACHE +static void _imagecache_add ( imagecache_image_t *img ) +{ + if (strncasecmp("file://", img->url, 7)) { + img->state = QUEUED; + TAILQ_INSERT_TAIL(&_imagecache_queue, img, q_link); + pthread_cond_broadcast(&_imagecache_cond); + } else { + time(&img->updated); + } +} + +static void *_imagecache_thread ( void *p ) +{ + int err; + imagecache_image_t *img; + struct timespec ts; + ts.tv_nsec = 0; + + while (1) { + + /* Get entry */ + pthread_mutex_lock(&imagecache_mutex); + if (!imagecache_enabled) { + pthread_cond_wait(&_imagecache_cond, &imagecache_mutex); + pthread_mutex_unlock(&imagecache_mutex); + continue; + } + img = TAILQ_FIRST(&_imagecache_queue); + if (!img) { + time(&ts.tv_sec); + ts.tv_sec += 60; + err = pthread_cond_timedwait(&_imagecache_cond, &imagecache_mutex, &ts); + if (err == ETIMEDOUT) { + RB_FOREACH(img, &_imagecache_by_url, url_link) { + if (img->state != IDLE) continue; + if ((ts.tv_sec - img->updated) > + (img->failed ? imagecache_fail_period : imagecache_ok_period)) + _imagecache_add(img); + } + } + pthread_mutex_unlock(&imagecache_mutex); + continue; + } + img->state = FETCHING; + TAILQ_REMOVE(&_imagecache_queue, img, q_link); + pthread_mutex_unlock(&imagecache_mutex); + + /* Fetch */ + _imagecache_fetch(img); + } + + return NULL; +} + +static int _imagecache_fetch ( imagecache_image_t *img ) +{ + int res; + CURL *curl; + FILE *fp; + char tmp[256], path[256]; + + /* Open file */ + if (hts_settings_buildpath(path, sizeof(path), "imagecache/data/%d", + img->id)) + return 1; + if (hts_settings_makedirs(path)) + return 1; + snprintf(tmp, sizeof(tmp), "%s.tmp", path); + if (!(fp = fopen(tmp, "wb"))) + return 1; + + /* Fetch file */ + tvhlog(LOG_DEBUG, "imagecache", "fetch %s", img->url); + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, img->url); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "TVHeadend"); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); + res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + fclose(fp); + + /* Process */ + pthread_mutex_lock(&imagecache_mutex); + img->state = IDLE; + time(&img->updated); // even if failed (possibly request sooner?) + if (res) { + img->failed = 1; + unlink(tmp); + tvhlog(LOG_WARNING, "imagecache", "failed to download %s", img->url); + } else { + img->failed = 0; + unlink(path); + rename(tmp, path); + tvhlog(LOG_DEBUG, "imagecache", "downloaded %s", img->url); + } + _imagecache_save(img); + pthread_cond_broadcast(&_imagecache_cond); + pthread_mutex_unlock(&imagecache_mutex); + + return res; +}; +#endif diff --git a/src/imagecache.h b/src/imagecache.h new file mode 100644 index 00000000..5776f46f --- /dev/null +++ b/src/imagecache.h @@ -0,0 +1,57 @@ +/* + * Icon file serve operations + * Copyright (C) 2012 Andy Brown + * + * 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 3 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, see . + */ + +#ifndef __IMAGE_CACHE_H__ +#define __IMAGE_CACHE_H__ + +#include + +extern uint32_t imagecache_enabled; +extern uint32_t imagecache_ok_period; +extern uint32_t imagecache_fail_period; + +extern pthread_mutex_t imagecache_mutex; + +void imagecache_init ( void ); + +void imagecache_save ( void ); + +int imagecache_set_enabled ( uint32_t e ) + __attribute__((warn_unused_result)); +int imagecache_set_ok_period ( uint32_t e ) + __attribute__((warn_unused_result)); +int imagecache_set_fail_period ( uint32_t e ) + __attribute__((warn_unused_result)); + +// Note: will return 0 if invalid (must serve original URL) +uint32_t imagecache_get_id ( const char *url ); + +int imagecache_open ( uint32_t id ); + +#define htsmsg_add_imageurl(_msg, _fld, _fmt, _url)\ + {\ + char _tmp[64];\ + uint32_t _id = imagecache_get_id(_url);\ + if (_id) {\ + snprintf(_tmp, sizeof(_tmp), _fmt, _id);\ + } else {\ + htsmsg_add_str(_msg, _fld, _url);\ + }\ + } + +#endif /* __IMAGE_CACHE_H__ */ diff --git a/src/main.c b/src/main.c index 1bb3b509..d56c63cb 100644 --- a/src/main.c +++ b/src/main.c @@ -60,7 +60,7 @@ #include "ffdecsa/FFdecsa.h" #include "muxes.h" #include "config2.h" -#include "iconserve.h" +#include "imagecache.h" int running; time_t dispatch_clock; @@ -89,6 +89,9 @@ const char *tvheadend_capabilities[] = { #endif #if ENABLE_LINUXDVB "linuxdvb", +#endif +#if ENABLE_IMAGECACHE + "imagecache", #endif NULL }; @@ -466,6 +469,8 @@ main(int argc, char **argv) config_init(); + imagecache_init(); + service_init(); channels_init(); @@ -489,8 +494,6 @@ main(int argc, char **argv) http_server_init(); webui_init(); - logo_loader(); - serviceprobe_init(); #if ENABLE_CWC diff --git a/src/settings.c b/src/settings.c index c98fc2fd..da46c455 100644 --- a/src/settings.c +++ b/src/settings.c @@ -101,7 +101,7 @@ hts_settings_makedirs ( const char *inpath ) * */ static void -hts_settings_buildpath +_hts_settings_buildpath (char *dst, size_t dstsize, const char *fmt, va_list ap, const char *prefix) { char tmp[256]; @@ -120,6 +120,18 @@ hts_settings_buildpath } } +int +hts_settings_buildpath + (char *dst, size_t dstsize, const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + if (!settingspath) + return 1; + _hts_settings_buildpath(dst, dstsize, fmt, va, settingspath); + return 0; +} + /** * */ @@ -139,7 +151,7 @@ hts_settings_save(htsmsg_t *record, const char *pathfmt, ...) /* Clean the path */ va_start(ap, pathfmt); - hts_settings_buildpath(path, sizeof(path), pathfmt, ap, settingspath); + _hts_settings_buildpath(path, sizeof(path), pathfmt, ap, settingspath); va_end(ap); /* Create directories */ @@ -261,16 +273,16 @@ hts_settings_load(const char *pathfmt, ...) /* Try normal path */ va_start(ap, pathfmt); - hts_settings_buildpath(fullpath, sizeof(fullpath), - pathfmt, ap, settingspath); + _hts_settings_buildpath(fullpath, sizeof(fullpath), + pathfmt, ap, settingspath); va_end(ap); ret = _hts_settings_load(fullpath); /* Try bundle path */ if (!ret && *pathfmt != '/') { va_start(ap, pathfmt); - hts_settings_buildpath(fullpath, sizeof(fullpath), - pathfmt, ap, "data/conf"); + _hts_settings_buildpath(fullpath, sizeof(fullpath), + pathfmt, ap, "data/conf"); va_end(ap); ret = _hts_settings_load(fullpath); } @@ -289,7 +301,7 @@ hts_settings_remove(const char *pathfmt, ...) struct stat st; va_start(ap, pathfmt); - hts_settings_buildpath(fullpath, sizeof(fullpath), + _hts_settings_buildpath(fullpath, sizeof(fullpath), pathfmt, ap, settingspath); va_end(ap); if (stat(fullpath, &st) == 0) { @@ -311,7 +323,7 @@ hts_settings_open_file(int for_write, const char *pathfmt, ...) /* Build path */ va_start(ap, pathfmt); - hts_settings_buildpath(path, sizeof(path), pathfmt, ap, settingspath); + _hts_settings_buildpath(path, sizeof(path), pathfmt, ap, settingspath); va_end(ap); /* Create directories */ diff --git a/src/settings.h b/src/settings.h index 7f0bb8d0..a1db839c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -34,6 +34,8 @@ const char *hts_settings_get_root(void); int hts_settings_open_file(int for_write, const char *pathfmt, ...); +int hts_settings_buildpath(char *dst, size_t dstsize, const char *pathfmt, ...); + int hts_settings_makedirs ( const char *path ); #endif /* HTSSETTINGS_H__ */ diff --git a/src/webui/extjs.c b/src/webui/extjs.c index 6999eacf..1fc3a8bf 100644 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -47,7 +47,7 @@ #include "config2.h" #include "lang_codes.h" #include "subscriptions.h" -#include "iconserve.h" +#include "imagecache.h" /** * @@ -859,7 +859,7 @@ extjs_epg(http_connection_t *hc, const char *remain, void *opaque) htsmsg_add_str(m, "channel", ch->ch_name); htsmsg_add_u32(m, "channelid", ch->ch_id); if(ch->ch_icon != NULL) - htsmsg_add_str(m, "chicon", logo_query(ch->ch_id, ch->ch_icon)); + htsmsg_add_imageurl(m, "chicon", "imagecache/%d", ch->ch_icon); if((s = epg_episode_get_title(ee, lang))) htsmsg_add_str(m, "title", s); @@ -941,7 +941,8 @@ extjs_epgrelated(http_connection_t *hc, const char *remain, void *opaque) m = htsmsg_create_map(); htsmsg_add_u32(m, "id", ebc->id); if ( ch->ch_name ) htsmsg_add_str(m, "channel", ch->ch_name); - if ( ch->ch_icon ) htsmsg_add_str(m, "chicon", logo_query(ch->ch_id, ch->ch_icon)); + if (ch->ch_icon) + htsmsg_add_imageurl(m, "chicon", "imagecache/%d", ch->ch_icon); htsmsg_add_u32(m, "start", ebc->start); htsmsg_add_msg(array, NULL, m); } @@ -1340,8 +1341,9 @@ extjs_dvrlist(http_connection_t *hc, const char *remain, void *opaque, if(de->de_channel != NULL) { htsmsg_add_str(m, "channel", de->de_channel->ch_name); - if(de->de_channel->ch_icon != NULL) - htsmsg_add_str(m, "chicon", logo_query(de->de_channel->ch_id, de->de_channel->ch_icon)); + if (de->de_channel->ch_icon) + htsmsg_add_imageurl(m, "chicon", "imagecache/%d", + de->de_channel->ch_icon); } htsmsg_add_str(m, "config_name", de->de_config_name); @@ -1915,40 +1917,54 @@ extjs_config(http_connection_t *hc, const char *remain, void *opaque) pthread_mutex_unlock(&global_lock); - /* Basic settings (not the advanced schedule) */ + /* Basic settings */ if(!strcmp(op, "loadSettings")) { + + /* Misc */ pthread_mutex_lock(&global_lock); m = config_get_all(); pthread_mutex_unlock(&global_lock); + + /* Image cache */ +#if ENABLE_IMAGECACHE + pthread_mutex_lock(&imagecache_mutex); + htsmsg_add_u32(m, "imagecache_enabled", imagecache_enabled); + htsmsg_add_u32(m, "imagecache_ok_period", imagecache_ok_period); + htsmsg_add_u32(m, "imagecache_fail_period", imagecache_fail_period); + pthread_mutex_unlock(&imagecache_mutex); +#endif + if (!m) return HTTP_STATUS_BAD_REQUEST; out = json_single_record(m, "config"); /* Save settings */ } else if (!strcmp(op, "saveSettings") ) { int save = 0; + + /* Misc settings */ pthread_mutex_lock(&global_lock); if ((str = http_arg_get(&hc->hc_req_args, "muxconfpath"))) save |= config_set_muxconfpath(str); if ((str = http_arg_get(&hc->hc_req_args, "language"))) save |= config_set_language(str); - str = http_arg_get(&hc->hc_req_args, "iconserve"); - if (str != NULL) { - save |= config_set_iconserve(str); - } else { - save |= config_set_iconserve("off"); - }; - str = http_arg_get(&hc->hc_req_args, "iconserve_periodicdownload"); - if (str != NULL) { - save |= config_set_iconserve_periodicdownload(str); - } else { - save |= config_set_iconserve_periodicdownload("off"); - }; - if ((str = http_arg_get(&hc->hc_req_args, "serverip"))) - save |= config_set_serverip(str); - if (save) config_save(); - /* trigger the iconserve init routine */ - logo_loader(); + if (save) + config_save(); pthread_mutex_unlock(&global_lock); + + /* Image Cache */ +#if ENABLE_IMAGECACHE + pthread_mutex_lock(&imagecache_mutex); + str = http_arg_get(&hc->hc_req_args, "imagecache_enabled"); + save = imagecache_set_enabled(!!str); + if ((str = http_arg_get(&hc->hc_req_args, "imagecache_ok_period"))) + save |= imagecache_set_ok_period(atoi(str)); + if ((str = http_arg_get(&hc->hc_req_args, "imagecache_fail_period"))) + save |= imagecache_set_fail_period(atoi(str)); + if (save) + imagecache_save(); + pthread_mutex_unlock(&imagecache_mutex); +#endif + out = htsmsg_create_map(); htsmsg_add_u32(out, "success", 1); diff --git a/src/webui/statedump.c b/src/webui/statedump.c index 29425143..603fd158 100644 --- a/src/webui/statedump.c +++ b/src/webui/statedump.c @@ -30,7 +30,6 @@ #include "epg.h" #include "psi.h" #include "channels.h" -#include "iconserve.h" #if ENABLE_LINUXDVB #include "dvr/dvr.h" #include "dvb/dvb.h" @@ -73,7 +72,7 @@ dumpchannels(htsbuf_queue_t *hq) ch->ch_refcount, ch->ch_zombie, ch->ch_number, - logo_query(ch->ch_id, ch->ch_icon) ?: ""); + ch->ch_icon ?: ""); } } diff --git a/src/webui/static/app/config.js b/src/webui/static/app/config.js index a862e0bb..42eb6082 100644 --- a/src/webui/static/app/config.js +++ b/src/webui/static/app/config.js @@ -37,12 +37,18 @@ tvheadend.miscconf = function() { */ var confreader = new Ext.data.JsonReader({ root : 'config' - }, [ 'muxconfpath', 'language', 'iconserve', 'serverip' ]); + }, [ 'muxconfpath', 'language', + 'imagecache_enabled', 'imagecache_ok_period', + 'imagecache_fail_period']); /* **************************************************************** * Form Fields * ***************************************************************/ + /* + * DVB path + */ + var dvbscanPath = new Ext.form.TextField({ fieldLabel : 'DVB scan files path', name : 'muxconfpath', @@ -50,21 +56,9 @@ tvheadend.miscconf = function() { width: 400 }); - var iconServeConfig = new Ext.form.Checkbox({ - name : 'iconserve', - fieldLabel : 'Cache channel icons' - }); - var iconPeriodicDownload = new Ext.form.Checkbox({ - name : 'iconserve_periodicdownload', - fieldLabel : 'Periodically check for updated icons' - }); - var serveripConfig = new Ext.form.TextField({ - fieldLabel : 'TVH Server IP address', - name : 'serverip', - allowBlank : true, - width: 150 - }); - + /* + * Language + */ var language = new Ext.ux.ItemSelector({ name: 'language', @@ -81,6 +75,34 @@ tvheadend.miscconf = function() { fromLegend: 'Available' }); + /* + * Image cache + */ + var imagecacheEnabled = new Ext.form.Checkbox({ + name: 'imagecache_enabled', + fieldLabel: 'Enabled', + }); + + var imagecacheOkPeriod = new Ext.form.NumberField({ + name: 'imagecache_ok_period', + fieldLabel: 'Re-fetch period (hours)' + }); + + var imagecacheFailPeriod = new Ext.form.NumberField({ + name: 'imagecache_fail_period', + fieldLabel: 'Re-try period (hours)', + }); + + var imagecachePanel = new Ext.form.FieldSet({ + title: 'Image Caching', + width: 700, + autoHeight: true, + collapsible: true, + items : [ imagecacheEnabled, imagecacheOkPeriod, imagecacheFailPeriod ] + }); + if (tvheadend.capabilities.indexOf('imagecache') == -1) + imagecachePanel.hide(); + /* **************************************************************** * Form * ***************************************************************/ @@ -111,7 +133,8 @@ tvheadend.miscconf = function() { layout : 'form', defaultType : 'textfield', autoHeight : true, - items : [ language, dvbscanPath, iconServeConfig, iconPeriodicDownload, serveripConfig ], + items : [ language, dvbscanPath, + imagecachePanel ], tbar : [ saveButton, '->', helpButton ] }); diff --git a/src/webui/webui.c b/src/webui/webui.c index 6376c812..69ccadf0 100644 --- a/src/webui/webui.c +++ b/src/webui/webui.c @@ -44,7 +44,7 @@ #include "muxer.h" #include "dvb/dvb.h" #include "dvb/dvb_support.h" -#include "iconserve.h" +#include "imagecache.h" /** * @@ -868,7 +868,47 @@ page_dvrfile(http_connection_t *hc, const char *remain, void *opaque) return 0; } +/** + * Fetch image cache image + */ +/** + * Static download of a file from the filesystem + */ +static int +page_imagecache(http_connection_t *hc, const char *remain, void *opaque) +{ + uint32_t id; + int fd; + char buf[8192]; + struct stat st; + ssize_t c; + if(remain == NULL) + return 404; + + if(sscanf(remain, "%d", &id) != 1) + return HTTP_STATUS_BAD_REQUEST; + + if ((fd = imagecache_open(id)) < 0) + return 404; + if (fstat(fd, &st)) { + close(fd); + return 404; + } + + http_send_header(hc, 200, NULL, st.st_size, 0, NULL, 10, 0, NULL); + + while (1) { + c = read(fd, buf, sizeof(buf)); + if (c <= 0) + break; + if (tvh_write(hc->hc_fd, buf, c)) + break; + } + close(fd); + + return 0; +} /** * @@ -910,7 +950,7 @@ webui_init(void) http_path_add("/stream", NULL, http_stream, ACCESS_STREAMING); - http_path_add("/channellogo", NULL, page_logo, ACCESS_ANONYMOUS); + http_path_add("/imagecache", NULL, page_imagecache, ACCESS_ANONYMOUS); webui_static_content("/static", "src/webui/static"); webui_static_content("/docs", "docs/html");