[PR-174] - Replaced user submitted icon cache with more generic image cache.

This allows file:// paths to be specified for channel icons even if image
cache support is disabled.

The image cache functionality is compile time optional (for those without
curl support) and also run-time configurable for those that don't want it.

All images, including EPG ones should be cached.
This commit is contained in:
Adam Sutton 2012-12-30 20:09:27 +00:00
parent acdc094fe7
commit 93fe784960
18 changed files with 713 additions and 537 deletions

View file

@ -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
#

13
configure vendored
View file

@ -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

View file

@ -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);
}

View file

@ -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;
};

View file

@ -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));

View file

@ -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

View file

@ -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 <sys/statvfs.h>
#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
*/

View file

@ -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 <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);
};

View file

@ -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 <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 */

440
src/imagecache.c Normal file
View file

@ -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 <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

57
src/imagecache.h Normal file
View file

@ -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 <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__ */

View file

@ -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

View file

@ -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 */

View file

@ -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__ */

View file

@ -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);

View file

@ -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) ?: "<none set>");
ch->ch_icon ?: "<none set>");
}
}

View file

@ -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 ]
});

View file

@ -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");