[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:
parent
acdc094fe7
commit
93fe784960
18 changed files with 713 additions and 537 deletions
5
Makefile
5
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
|
||||
#
|
||||
|
|
13
configure
vendored
13
configure
vendored
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
|
|
19
src/epg.c
19
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
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
358
src/iconserve.c
358
src/iconserve.c
|
@ -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);
|
||||
};
|
|
@ -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
440
src/imagecache.c
Normal 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
57
src/imagecache.h
Normal 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__ */
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ]
|
||||
});
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Add table
Reference in a new issue