imagecache: migrate configuration to new API

I started to do this as there was a possible issue with imagecache as a result
of updates. However this has thus far not been proved, but still a useful
update. Hopefully not too broken!
This commit is contained in:
Adam Sutton 2013-12-02 13:30:59 +00:00
parent 1b784355ad
commit 582e205bc0
13 changed files with 589 additions and 324 deletions

View file

@ -123,6 +123,7 @@ SRCS += \
src/api/api_mpegts.c \
src/api/api_epg.c \
src/api/api_epggrab.c \
src/api/api_imagecache.c
SRCS += \
src/parsers/parsers.c \

View file

@ -123,4 +123,5 @@ void api_init ( void )
api_epg_init();
api_epggrab_init();
api_status_init();
api_imagecache_init();
}

View file

@ -55,15 +55,16 @@ int api_exec ( const char *subsystem, htsmsg_t *args, htsmsg_t **resp );
/*
* Initialise
*/
void api_init ( void );
void api_idnode_init ( void );
void api_input_init ( void );
void api_service_init ( void );
void api_channel_init ( void );
void api_mpegts_init ( void );
void api_epg_init ( void );
void api_epggrab_init ( void );
void api_status_init ( void );
void api_init ( void );
void api_idnode_init ( void );
void api_input_init ( void );
void api_service_init ( void );
void api_channel_init ( void );
void api_mpegts_init ( void );
void api_epg_init ( void );
void api_epggrab_init ( void );
void api_status_init ( void );
void api_imagecache_init ( void );
/*
* IDnode

74
src/api/api_imagecache.c Normal file
View file

@ -0,0 +1,74 @@
/*
* API - Imagecache related calls
*
* Copyright (C) 2013 Adam Sutton
*
* 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; withm 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 "tvheadend.h"
#include "channels.h"
#include "access.h"
#include "api.h"
#include "imagecache.h"
#if ENABLE_IMAGECACHE
static int
api_imagecache_load
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
htsmsg_t *l;
pthread_mutex_lock(&global_lock);
*resp = htsmsg_create_map();
l = htsmsg_create_list();
htsmsg_add_msg(l, NULL, imagecache_get_config());
htsmsg_add_msg(*resp, "entries", l);
pthread_mutex_unlock(&global_lock);
return 0;
}
static int
api_imagecache_save
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
pthread_mutex_lock(&global_lock);
if (imagecache_set_config(args))
imagecache_save();
pthread_mutex_unlock(&global_lock);
*resp = htsmsg_create_map();
htsmsg_add_u32(*resp, "success", 1);
return 0;
}
void
api_imagecache_init ( void )
{
static api_hook_t ah[] = {
{ "imagecache/config/load", ACCESS_ADMIN, api_imagecache_load, NULL },
{ "imagecache/config/save", ACCESS_ADMIN, api_imagecache_save, NULL },
{ NULL },
};
api_register_all(ah);
}
#else /* ENABLE_IMAGECACHE */
void
api_imagecache_init ( void )
{
}
#endif

View file

@ -33,6 +33,8 @@
#include "imagecache.h"
#include "queue.h"
#include "redblack.h"
#include "notify.h"
#include "prop.h"
#if ENABLE_IMAGECACHE
#define CURL_STATICLIB
@ -40,10 +42,6 @@
#include <curl/easy.h>
#endif
// TODO: icon cache flushing?
// TODO: md5 validation?
// TODO: allow cache to be disabled by users
/*
* Image metadata
*/
@ -64,41 +62,191 @@ typedef struct imagecache_image
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 );
static int imagecache_id;
static RB_HEAD(,imagecache_image) imagecache_by_id;
static RB_HEAD(,imagecache_image) imagecache_by_url;
#if ENABLE_IMAGECACHE
uint32_t imagecache_enabled;
uint32_t imagecache_ok_period;
uint32_t imagecache_fail_period;
uint32_t imagecache_ignore_sslcert;
struct imagecache_config imagecache_conf;
static const property_t imagecache_props[] = {
{
.type = PT_BOOL,
.id = "enabled",
.name = "Enabled",
.off = offsetof(struct imagecache_config, enabled),
},
{
.type = PT_BOOL,
.id = "ignore_sslcert",
.name = "Ignore invalid SSL certificate",
.off = offsetof(struct imagecache_config, ignore_sslcert),
},
{
.type = PT_U32,
.id = "ok_period",
.name = "Re-try period",
.off = offsetof(struct imagecache_config, ok_period),
},
{
.type = PT_U32,
.id = "fail_period",
.name = "Re-try period of failed images",
.off = offsetof(struct imagecache_config, 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 );
static pthread_cond_t imagecache_cond;
static TAILQ_HEAD(, imagecache_image) imagecache_queue;
static gtimer_t imagecache_timer;
#endif
static int _url_cmp ( void *a, void *b )
static int
url_cmp ( imagecache_image_t *a, imagecache_image_t *b )
{
return strcmp(((imagecache_image_t*)a)->url, ((imagecache_image_t*)b)->url);
return strcmp(a->url, b->url);
}
static int _id_cmp ( void *a, void *b )
static int
id_cmp ( imagecache_image_t *a, imagecache_image_t *b )
{
return ((imagecache_image_t*)a)->id - ((imagecache_image_t*)b)->id;
return (a->id - b->id);
}
static void
imagecache_image_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_image_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 int
imagecache_image_fetch ( imagecache_image_t *img )
{
int res = 1;
CURL *curl;
FILE *fp;
char tmp[256], path[256];
/* Open file */
if (hts_settings_buildpath(path, sizeof(path), "imagecache/data/%d",
img->id))
goto error;
if (hts_settings_makedirs(path))
goto error;
snprintf(tmp, sizeof(tmp), "%s.tmp", path);
if (!(fp = fopen(tmp, "wb")))
goto error;
/* Build command */
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);
if (imagecache_conf.ignore_sslcert)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
/* Fetch (release lock, incase of delays) */
pthread_mutex_unlock(&global_lock);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
fclose(fp);
pthread_mutex_lock(&global_lock);
/* Process */
error:
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_image_save(img);
pthread_cond_broadcast(&imagecache_cond);
return res;
};
static void *
imagecache_thread ( void *p )
{
imagecache_image_t *img;
pthread_mutex_lock(&global_lock);
while (1) {
/* Check we're enabled */
if (!imagecache_conf.enabled) {
pthread_cond_wait(&imagecache_cond, &global_lock);
continue;
}
/* Get entry */
if (!(img = TAILQ_FIRST(&imagecache_queue))) {
pthread_cond_wait(&imagecache_cond, &global_lock);
continue;
}
/* Process */
img->state = FETCHING;
TAILQ_REMOVE(&imagecache_queue, img, q_link);
/* Fetch */
(void)imagecache_image_fetch(img);
}
return NULL;
}
static void
imagecache_timer_cb ( void *p )
{
time_t now, when;
imagecache_image_t *img;
time(&now);
RB_FOREACH(img, &imagecache_by_url, url_link) {
if (img->state != IDLE) continue;
when = img->failed ? imagecache_conf.fail_period
: imagecache_conf.ok_period;
when = img->updated + (when * 3600);
if (when < now)
imagecache_image_add(img);
}
}
#endif /* ENABLE_IMAGECACHE */
/*
* Initialise
*/
void imagecache_init ( void )
void
imagecache_init ( void )
{
htsmsg_t *m, *e;
htsmsg_field_t *f;
@ -107,28 +255,24 @@ void imagecache_init ( void )
uint32_t id;
/* Init vars */
_imagecache_id = 0;
imagecache_id = 0;
#if ENABLE_IMAGECACHE
imagecache_enabled = 0;
imagecache_ok_period = 24 * 7; // weekly
imagecache_fail_period = 24; // daily
imagecache_ignore_sslcert = 0;
imagecache_conf.enabled = 0;
imagecache_conf.ok_period = 24 * 7; // weekly
imagecache_conf.fail_period = 24; // daily
imagecache_conf.ignore_sslcert = 0;
#endif
/* Create threads */
pthread_mutex_init(&imagecache_mutex, NULL);
#if ENABLE_IMAGECACHE
pthread_cond_init(&_imagecache_cond, NULL);
TAILQ_INIT(&_imagecache_queue);
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_get_u32(m, "ignore_sslcert", &imagecache_ignore_sslcert);
imagecache_set_config(m);
htsmsg_destroy(m);
}
#endif
@ -141,7 +285,7 @@ void imagecache_init ( void )
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);
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);
@ -149,13 +293,13 @@ void imagecache_init ( void )
free(img);
continue;
}
i = RB_INSERT_SORTED(&_imagecache_by_id, img, id_link, _id_cmp);
i = RB_INSERT_SORTED(&imagecache_by_id, img, id_link, id_cmp);
assert(!i);
if (id > _imagecache_id)
_imagecache_id = id;
if (id > imagecache_id)
imagecache_id = id;
#if ENABLE_IMAGECACHE
if (!img->updated)
_imagecache_add(img);
imagecache_image_add(img);
#endif
}
htsmsg_destroy(m);
@ -165,81 +309,65 @@ void imagecache_init ( void )
#if ENABLE_IMAGECACHE
{
pthread_t tid;
tvhthread_create(&tid, NULL, _imagecache_thread, NULL, 1);
tvhthread_create(&tid, NULL, imagecache_thread, NULL, 1);
}
/* Re-try timer */
// TODO: this could be more efficient by being targetted, however
// the reality its not necessary and I'd prefer to avoid dumping
// 100's of timers into the global pool
gtimer_arm(&imagecache_timer, imagecache_timer_cb, NULL, 600);
#endif
}
/*
* Save settings
*/
#if ENABLE_IMAGECACHE
void imagecache_save ( void )
/*
* Get config
*/
htsmsg_t *
imagecache_get_config ( 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);
htsmsg_add_u32(m, "ignore_sslcert", imagecache_ignore_sslcert);
prop_read_values(&imagecache_conf, imagecache_props, m, 0, NULL);
return m;
}
/*
* Set config
*/
int
imagecache_set_config ( htsmsg_t *m )
{
int save = prop_write_values(&imagecache_conf, imagecache_props, m, 0, NULL);
if (save)
pthread_cond_broadcast(&imagecache_cond);
return save;
}
/*
* Save
*/
void
imagecache_save ( void )
{
htsmsg_t *m = imagecache_get_config();
hts_settings_save(m, "imagecache/config");
notify_reload("imagecache");
}
/*
* 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;
}
/*
* Set ignore SSL cert
*/
int imagecache_set_ignore_sslcert ( uint32_t p )
{
if (p == imagecache_ignore_sslcert)
return 0;
imagecache_ignore_sslcert = p;
return 1;
}
#endif
/*
* Fetch a URLs ID
*/
uint32_t imagecache_get_id ( const char *url )
uint32_t
imagecache_get_id ( const char *url )
{
uint32_t id = 0;
imagecache_image_t *i;
static imagecache_image_t *skel = NULL;
lock_assert(&global_lock);
/* Invalid */
if (!url)
return 0;
@ -256,27 +384,25 @@ uint32_t imagecache_get_id ( const char *url )
skel->url = url;
/* Create/Find */
pthread_mutex_lock(&imagecache_mutex);
i = RB_INSERT_SORTED(&_imagecache_by_url, skel, url_link, _url_cmp);
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);
i->id = ++imagecache_id;
skel = RB_INSERT_SORTED(&imagecache_by_id, i, id_link, id_cmp);
assert(!skel);
#if ENABLE_IMAGECACHE
_imagecache_add(i);
imagecache_image_add(i);
#endif
_imagecache_save(i);
imagecache_image_save(i);
}
#if ENABLE_IMAGECACHE
if (!strncasecmp(url, "file://", 7) || imagecache_enabled)
if (!strncasecmp(url, "file://", 7) || imagecache_conf.enabled)
id = i->id;
#else
if (!strncasecmp(url, "file://", 7))
id = i->id;
#endif
pthread_mutex_unlock(&imagecache_mutex);
return id;
}
@ -284,22 +410,18 @@ uint32_t imagecache_get_id ( const char *url )
/*
* Get data
*/
int imagecache_open ( uint32_t id )
int
imagecache_open ( uint32_t id )
{
imagecache_image_t skel, *i;
int fd = -1;
int e, fd = -1;
pthread_mutex_lock(&imagecache_mutex);
lock_assert(&global_lock);
/* Find */
skel.id = id;
i = RB_FIND(&_imagecache_by_id, &skel, id_link, _id_cmp);
/* Invalid */
if (!i) {
pthread_mutex_unlock(&imagecache_mutex);
if (!(i = RB_FIND(&imagecache_by_id, &skel, id_link, id_cmp)))
return -1;
}
/* Local file */
if (!strncasecmp(i->url, "file://", 7))
@ -307,158 +429,37 @@ int imagecache_open ( uint32_t id )
/* Remote file */
#if ENABLE_IMAGECACHE
else if (imagecache_enabled) {
else if (imagecache_conf.enabled) {
struct timespec ts;
int err;
/* Use existing */
if (i->updated) {
// use existing
/* Wait */
} 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);
ts.tv_nsec = 0;
ts.tv_sec += 5;
err = pthread_cond_timedwait(&imagecache_cond, &global_lock, &ts);
if (err == ETIMEDOUT)
return -1;
}
/* Attempt to fetch */
} else if (i->state == QUEUED) {
i->state = FETCHING;
TAILQ_REMOVE(&_imagecache_queue, i, q_link);
pthread_mutex_unlock(&imagecache_mutex);
if (_imagecache_fetch(i))
TAILQ_REMOVE(&imagecache_queue, i, q_link);
pthread_mutex_unlock(&global_lock);
e = imagecache_image_fetch(i);
pthread_mutex_lock(&global_lock);
if (e)
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) {
uint32_t period;
RB_FOREACH(img, &_imagecache_by_url, url_link) {
if (img->state != IDLE) continue;
period = img->failed ? imagecache_fail_period : imagecache_ok_period;
period *= 3600;
if (period && ((ts.tv_sec - img->updated) > 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;
/* Build command */
pthread_mutex_lock(&imagecache_mutex);
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);
if (imagecache_ignore_sslcert)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
pthread_mutex_unlock(&imagecache_mutex);
/* Fetch */
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
#endif /* ENABLE_IMAGECACHE */

View file

@ -21,25 +21,22 @@
#include <pthread.h>
extern uint32_t imagecache_enabled;
extern uint32_t imagecache_ok_period;
extern uint32_t imagecache_fail_period;
extern uint32_t imagecache_ignore_sslcert;
struct imagecache_config {
int enabled;
int ignore_sslcert;
uint32_t ok_period;
uint32_t fail_period;
};
extern struct imagecache_config imagecache_conf;
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));
int imagecache_set_ignore_sslcert ( uint32_t e )
__attribute__((warn_unused_result));
htsmsg_t *imagecache_get_config ( void );
int imagecache_set_config ( htsmsg_t *c );
void imagecache_save ( void );
// Note: will return 0 if invalid (must serve original URL)
uint32_t imagecache_get_id ( const char *url );

View file

@ -135,7 +135,7 @@ const tvh_caps_t tvheadend_capabilities[] = {
{ "transcoding", &transcoding_enabled },
#endif
#if ENABLE_IMAGECACHE
{ "imagecache", &imagecache_enabled },
{ "imagecache", (uint32_t*)&imagecache_conf.enabled },
#endif
#if ENABLE_TIMESHIFT
{ "timeshift", &timeshift_enabled },

View file

@ -104,6 +104,7 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
"<link rel=\"stylesheet\" type=\"text/css\" href=\"static/livegrid/resources/css/ext-ux-livegrid.css\">\n"
"<link rel=\"stylesheet\" type=\"text/css\" href=\"static/extjs/examples/ux/gridfilters/css/GridFilters.css\">\n"
"<link rel=\"stylesheet\" type=\"text/css\" href=\"static/extjs/examples/ux/gridfilters/css/RangeMenu.css\">\n"
"<link rel=\"stylesheet\" type=\"text/css\" href=\"static/xcheckbox/xcheckbox.css\">\n"
"<link rel=\"stylesheet\" type=\"text/css\" href=\"static/app/ext.css\">\n",
tvheadend_webui_debug ? "-debug" : "",
tvheadend_webui_debug ? "-debug" : "",
@ -119,6 +120,7 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
extjs_load(hq, "static/lovcombo/lovcombo-all.js");
extjs_load(hq, "static/multiselect/multiselect.js");
extjs_load(hq, "static/multiselect/ddview.js");
extjs_load(hq, "static/xcheckbox/xcheckbox.js");
extjs_load(hq, "static/checkcolumn/CheckColumn.js");
extjs_load(hq, "static/extjs/examples/ux/gridfilters/GridFilters.js");
extjs_load(hq, "static/extjs/examples/ux/gridfilters/filter/Filter.js");
@ -1506,16 +1508,6 @@ extjs_config(http_connection_t *hc, const char *remain, void *opaque)
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);
htsmsg_add_u32(m, "imagecache_ignore_sslcert", imagecache_ignore_sslcert);
pthread_mutex_unlock(&imagecache_mutex);
#endif
if (!m) return HTTP_STATUS_BAD_REQUEST;
out = json_single_record(m, "config");
@ -1550,22 +1542,6 @@ extjs_config(http_connection_t *hc, const char *remain, void *opaque)
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));
str = http_arg_get(&hc->hc_req_args, "imagecache_ignore_sslcert");
save |= imagecache_set_ignore_sslcert(!!str);
if (save)
imagecache_save();
pthread_mutex_unlock(&imagecache_mutex);
#endif
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);

View file

@ -32,17 +32,26 @@ tvheadend.comet.on('config', function(m) {
});
tvheadend.miscconf = function() {
/*
* Basic Config
*/
var confreader = new Ext.data.JsonReader({
root : 'config'
}, [ 'muxconfpath', 'language',
'imagecache_enabled', 'imagecache_ok_period',
'imagecache_fail_period', 'imagecache_ignore_sslcert',
'tvhtime_update_enabled', 'tvhtime_ntp_enabled',
'tvhtime_tolerance', 'transcoding_enabled']);
/*
* Imagecache
*/
var imagecache_reader = new Ext.data.JsonReader({
root : 'entries'
},
[
'enabled', 'ok_period', 'fail_period', 'ignore_sslcert',
]);
/* ****************************************************************
* Form Fields
* ***************************************************************/
@ -106,23 +115,23 @@ tvheadend.miscconf = function() {
/*
* Image cache
*/
var imagecacheEnabled = new Ext.form.Checkbox({
name: 'imagecache_enabled',
var imagecacheEnabled = new Ext.ux.form.XCheckbox({
name: 'enabled',
fieldLabel: 'Enabled',
});
var imagecacheOkPeriod = new Ext.form.NumberField({
name: 'imagecache_ok_period',
name: 'ok_period',
fieldLabel: 'Re-fetch period (hours)'
});
var imagecacheFailPeriod = new Ext.form.NumberField({
name: 'imagecache_fail_period',
name: 'fail_period',
fieldLabel: 'Re-try period (hours)',
});
var imagecacheIgnoreSSLCert = new Ext.form.Checkbox({
name: 'imagecache_ignore_sslcert',
var imagecacheIgnoreSSLCert = new Ext.ux.form.XCheckbox({
name: 'ignore_sslcert',
fieldLabel: 'Ignore invalid SSL certificate'
});
@ -137,6 +146,17 @@ tvheadend.miscconf = function() {
if (tvheadend.capabilities.indexOf('imagecache') == -1)
imagecachePanel.hide();
var imagecache_form = new Ext.form.FormPanel({
border : false,
labelAlign : 'left',
labelWidth : 200,
waitMsgTarget : true,
reader: imagecache_reader,
layout : 'form',
defaultType : 'textfield',
autoHeight : true,
items : [ imagecachePanel ]
});
/*
* Transcoding
@ -176,23 +196,29 @@ tvheadend.miscconf = function() {
});
var confpanel = new Ext.form.FormPanel({
title : 'General',
iconCls : 'wrench',
border : false,
bodyStyle : 'padding:15px',
labelAlign : 'left',
labelWidth : 200,
border : false,
waitMsgTarget : true,
reader : confreader,
layout : 'form',
defaultType : 'textfield',
autoHeight : true,
items : [ language, dvbscanPath,
imagecachePanel, tvhtimePanel,
transcodingPanel],
tbar : [ saveButton, '->', helpButton ]
tvhtimePanel,
transcodingPanel]
});
var panel = new Ext.Panel({
title : 'General',
iconCls : 'wrench',
border : false,
bodyStyle : 'padding:15px',
layout : 'form',
items: [confpanel, imagecache_form ],
tbar : [ saveButton, '->', helpButton ]
});
/* ****************************************************************
* Load/Save
* ***************************************************************/
@ -207,6 +233,15 @@ tvheadend.miscconf = function() {
confpanel.enable();
}
});
imagecache_form.getForm().load({
url : 'api/imagecache/config/load',
success : function (form, action) {
imagecache_form.enable();
},
failure : function (form, action) {
alert("FAILED");
}
});
});
function saveChanges() {
@ -220,7 +255,14 @@ tvheadend.miscconf = function() {
Ext.Msg.alert('Save failed', action.result.errormsg);
}
});
imagecache_form.getForm().submit({
url : 'api/imagecache/config/save',
waitMsg : 'Saving data...',
failure : function(form, action) {
Ext.Msg.alert('Imagecache save failed', action.result.errormsg);
}
});
}
return confpanel;
return panel;
}

View file

@ -214,7 +214,7 @@ tvheadend.IdNodeField = function (conf)
} else {
switch (this.type) {
case 'bool':
cons = Ext.form.Checkbox;
cons = Ext.ux.form.XCheckbox;
break;
case 'int':
@ -330,7 +330,7 @@ tvheadend.idnode_editor_field = function(f, create)
break;
case 'bool':
return new Ext.form.Checkbox({
return new Ext.ux.form.XCheckbox({
fieldLabel : f.caption,
name : f.id,
checked : value,
@ -865,7 +865,7 @@ tvheadend.idnode_grid = function(panel, conf)
}
/* Grid Panel */
var auto = new Ext.form.Checkbox({
var auto = new Ext.ux.form.XCheckbox({
checked : true,
listeners : {
check : function ( s, c ) {

View file

@ -0,0 +1,25 @@
.xcheckbox-wrap {
line-height: 18px;
padding-top:2px;
}
.xcheckbox-wrap a {
display:block;
width:16px;
height:16px;
float:left;
}
.x-toolbar .xcheckbox-wrap {
padding: 0 0 2px 0;
}
.xcheckbox-on {
background:transparent url(../extjs/resources/images/default/menu/checked.gif) no-repeat 0 0;
}
.xcheckbox-off {
background:transparent url(../extjs/resources/images/default/menu/unchecked.gif) no-repeat 0 0;
}
.xcheckbox-disabled {
opacity: 0.5;
-moz-opacity: 0.5;
filter: alpha(opacity=50);
cursor:default;
}

View file

@ -0,0 +1,141 @@
// vim: ts=4:sw=4:nu:fdc=2:nospell
/**
* Ext.ux.form.XCheckbox - nice checkbox with configurable submit values
*
* @author Ing. Jozef Sakalos
* @version $Id: Ext.ux.form.XCheckbox.js 81 2008-03-20 11:13:36Z jozo $
* @date 10. February 2008
*
*
* @license Ext.ux.form.XCheckbox is licensed under the terms of
* the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
* that the code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
* License details: http://www.gnu.org/licenses/lgpl.html
*/
/**
* Default css:
* .xcheckbox-wrap {
* line-height: 18px;
* padding-top:2px;
* }
* .xcheckbox-wrap a {
* display:block;
* width:16px;
* height:16px;
* float:left;
* }
* .x-toolbar .xcheckbox-wrap {
* padding: 0 0 2px 0;
* }
* .xcheckbox-on {
* background:transparent url(./ext/resources/images/default/menu/checked.gif) no-repeat 0 0;
* }
* .xcheckbox-off {
* background:transparent url(./ext/resources/images/default/menu/unchecked.gif) no-repeat 0 0;
* }
* .xcheckbox-disabled {
* opacity: 0.5;
* -moz-opacity: 0.5;
* filter: alpha(opacity=50);
* cursor:default;
* }
*
* @class Ext.ux.XCheckbox
* @extends Ext.form.Checkbox
*/
Ext.ns('Ext.ux.form');
Ext.ux.form.XCheckbox = Ext.extend(Ext.form.Checkbox, {
offCls:'xcheckbox-off'
,onCls:'xcheckbox-on'
,disabledClass:'xcheckbox-disabled'
,submitOffValue:'false'
,submitOnValue:'true'
,checked:false
,onRender:function(ct) {
// call parent
Ext.ux.form.XCheckbox.superclass.onRender.apply(this, arguments);
// save tabIndex remove & re-create this.el
var tabIndex = this.el.dom.tabIndex;
var id = this.el.dom.id;
this.el.remove();
this.el = ct.createChild({tag:'input', type:'hidden', name:this.name, id:id});
// update value of hidden field
this.updateHidden();
// adjust wrap class and create link with bg image to click on
this.wrap.replaceClass('x-form-check-wrap', 'xcheckbox-wrap');
this.cbEl = this.wrap.createChild({tag:'a', href:'#', cls:this.checked ? this.onCls : this.offCls});
// reposition boxLabel if any
var boxLabel = this.wrap.down('label');
if(boxLabel) {
this.wrap.appendChild(boxLabel);
}
// support tooltip
if(this.tooltip) {
this.cbEl.set({qtip:this.tooltip});
}
// install event handlers
this.wrap.on({click:{scope:this, fn:this.onClick, delegate:'a'}});
this.wrap.on({keyup:{scope:this, fn:this.onClick, delegate:'a'}});
// restore tabIndex
this.cbEl.dom.tabIndex = tabIndex;
} // eo function onRender
,onClick:function(e) {
if(this.disabled || this.readOnly) {
return;
}
if(!e.isNavKeyPress()) {
this.setValue(!this.checked);
}
} // eo function onClick
,onDisable:function() {
this.cbEl.addClass(this.disabledClass);
this.el.dom.disabled = true;
} // eo function onDisable
,onEnable:function() {
this.cbEl.removeClass(this.disabledClass);
this.el.dom.disabled = false;
} // eo function onEnable
,setValue:function(val) {
if('string' == typeof val) {
this.checked = val === this.submitOnValue;
}
else {
this.checked = !(!val);
}
if(this.rendered && this.cbEl) {
this.updateHidden();
this.cbEl.removeClass([this.offCls, this.onCls]);
this.cbEl.addClass(this.checked ? this.onCls : this.offCls);
}
this.fireEvent('check', this, this.checked);
} // eo function setValue
,updateHidden:function() {
this.el.dom.value = this.checked ? this.submitOnValue : this.submitOffValue;
} // eo function updateHidden
,getValue:function() {
return this.checked;
} // eo function getValue
}); // eo extend
// register xtype
Ext.reg('xcheckbox', Ext.ux.form.XCheckbox);

View file

@ -1044,7 +1044,13 @@ page_imagecache(http_connection_t *hc, const char *remain, void *opaque)
if(sscanf(remain, "%d", &id) != 1)
return HTTP_STATUS_BAD_REQUEST;
if ((fd = imagecache_open(id)) < 0)
/* Fetch details */
pthread_mutex_lock(&global_lock);
fd = imagecache_open(id);
pthread_mutex_unlock(&global_lock);
/* Check result */
if (fd < 0)
return 404;
if (fstat(fd, &st)) {
close(fd);