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
src/config2.c \
src/lang_codes.c \
src/lang_str.c \
+ src/imagecache.c
SRCS += src/epggrab/module.c\
src/epggrab/channel.c\
src/muxer_pass.c \
src/muxer_tvh.c \
-SRCS += src/iconserve.c \
-
#
# Optional code
#
"v4l:yes"
"linuxdvb:yes"
"dvbscan:yes"
+ "imagecache:auto"
"avahi:auto"
"zlib:auto"
"bundle:no"
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
#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;
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);
free(ch->ch_icon);
ch->ch_icon = strdup(icon);
+ imagecache_get_id(icon);
channel_save(ch);
htsp_channel_update(ch);
}
}
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;
-};
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));
#include "dvr/dvr.h"
#include "htsp_server.h"
#include "epggrab.h"
+#include "imagecache.h"
/* Broadcast hashing */
#define EPG_HASH_WIDTH 1024
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
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
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
#include "htsmsg_binary.h"
#include "epg.h"
#include "plumbing/tsfix.h"
-#include "iconserve.h"
-#include "config2.h"
+#include "imagecache.h"
#include <sys/statvfs.h>
#include "settings.h"
*
*/
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;
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;
/* 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)
/**
* 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
*/
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-#define CURL_STATICLIB
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <string.h>
-#include <curl/curl.h>
-#include <curl/easy.h>
-#include <unistd.h>
-#include <pthread.h>
-#include <stdlib.h>
-#include <time.h>
-
-#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);
-};
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-#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 */
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <time.h>
+#include <assert.h>
+
+#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 <curl/curl.h>
+#include <curl/easy.h>
+#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
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __IMAGE_CACHE_H__
+#define __IMAGE_CACHE_H__
+
+#include <pthread.h>
+
+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__ */
#include "ffdecsa/FFdecsa.h"
#include "muxes.h"
#include "config2.h"
-#include "iconserve.h"
+#include "imagecache.h"
int running;
time_t dispatch_clock;
#endif
#if ENABLE_LINUXDVB
"linuxdvb",
+#endif
+#if ENABLE_IMAGECACHE
+ "imagecache",
#endif
NULL
};
config_init();
+ imagecache_init();
+
service_init();
channels_init();
http_server_init();
webui_init();
- logo_loader();
-
serviceprobe_init();
#if ENABLE_CWC
*
*/
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];
}
}
+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;
+}
+
/**
*
*/
/* 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 */
/* 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);
}
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) {
/* 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 */
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__ */
#include "config2.h"
#include "lang_codes.h"
#include "subscriptions.h"
-#include "iconserve.h"
+#include "imagecache.h"
/**
*
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);
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);
}
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);
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);
#include "epg.h"
#include "psi.h"
#include "channels.h"
-#include "iconserve.h"
#if ENABLE_LINUXDVB
#include "dvr/dvr.h"
#include "dvb/dvb.h"
ch->ch_refcount,
ch->ch_zombie,
ch->ch_number,
- logo_query(ch->ch_id, ch->ch_icon) ?: "<none set>");
+ ch->ch_icon ?: "<none set>");
}
}
*/
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',
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',
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
* ***************************************************************/
layout : 'form',
defaultType : 'textfield',
autoHeight : true,
- items : [ language, dvbscanPath, iconServeConfig, iconPeriodicDownload, serveripConfig ],
+ items : [ language, dvbscanPath,
+ imagecachePanel ],
tbar : [ saveButton, '->', helpButton ]
});
#include "muxer.h"
#include "dvb/dvb.h"
#include "dvb/dvb_support.h"
-#include "iconserve.h"
+#include "imagecache.h"
/**
*
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;
+}
/**
*
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");