Merge branch 'epg-rewrite-tmp' into epg-rewrite
This commit is contained in:
commit
87c48ae57d
13 changed files with 1353 additions and 805 deletions
175
src/cron.c
175
src/cron.c
|
@ -1,16 +1,148 @@
|
|||
#include "cron.h"
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int cron_is_time ( cron_t *cron )
|
||||
typedef struct cron
|
||||
{
|
||||
char *str; ///< String representation
|
||||
time_t last; ///< Last execution time
|
||||
uint64_t min; ///< Minutes
|
||||
uint32_t hour; ///< Hours
|
||||
uint32_t dom; ///< Day of month
|
||||
uint16_t mon; ///< Month
|
||||
uint8_t dow; ///< Day of week
|
||||
} cron_t;
|
||||
|
||||
static int _cron_parse_field
|
||||
( const char *str, uint64_t *field, uint64_t mask, int offset )
|
||||
{
|
||||
int i = 0, j = 0, sn = -1, en = -1, mn = -1;
|
||||
*field = 0;
|
||||
while ( 1 ) {
|
||||
if ( str[i] == '*' ) {
|
||||
sn = 0;
|
||||
en = 64;
|
||||
j = -1;
|
||||
} else if ( str[i] == ',' || str[i] == ' ' || str[i] == '\0' ) {
|
||||
if (j >= 0)
|
||||
sscanf(str+j, "%d", en == -1 ? (sn == -1 ? &sn : &en) : &mn);
|
||||
if (en == -1) en = sn;
|
||||
if (mn == -1) mn = 1;
|
||||
while (sn <= en) {
|
||||
if ( (sn % mn) == 0 )
|
||||
*field |= (0x1LL << (sn - offset));
|
||||
sn++;
|
||||
}
|
||||
if ( str[i] == '\0' ) break;
|
||||
j = i+1;
|
||||
sn = en = mn = -1;
|
||||
if (str[i] == ' ') break;
|
||||
} else if ( str[i] == '/' ) {
|
||||
if (j >= 0) sscanf(str+j, "%d", sn == -1 ? &sn : &en);
|
||||
j = i+1;
|
||||
mn = 1;
|
||||
} else if ( str[i] == '-' ) {
|
||||
if (j >= 0) sscanf(str+j, "%d", &sn);
|
||||
j = i+1;
|
||||
en = 64;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (str[i] == ' ') i++;
|
||||
*field &= mask;
|
||||
return i;
|
||||
}
|
||||
|
||||
static int _cron_parse ( cron_t *cron, const char *str )
|
||||
{
|
||||
int i = 0;
|
||||
uint64_t u64;
|
||||
|
||||
/* daily */
|
||||
if ( !strcmp(str, "@daily") ) {
|
||||
cron->min = 1;
|
||||
cron->hour = 1;
|
||||
cron->dom = CRON_DOM_MASK;
|
||||
cron->mon = CRON_MON_MASK;
|
||||
cron->dow = CRON_DOW_MASK;
|
||||
|
||||
/* hourly */
|
||||
} else if ( !strcmp(str, "@hourly") ) {
|
||||
cron->min = 1;
|
||||
cron->hour = CRON_HOUR_MASK;
|
||||
cron->dom = CRON_DOM_MASK;
|
||||
cron->mon = CRON_MON_MASK;
|
||||
cron->dow = CRON_DOW_MASK;
|
||||
|
||||
/* standard */
|
||||
} else {
|
||||
i += _cron_parse_field(str+i, &u64, CRON_MIN_MASK, 0);
|
||||
cron->min = u64;
|
||||
i += _cron_parse_field(str+i, &u64, CRON_HOUR_MASK, 0);
|
||||
cron->hour = (uint32_t)u64;
|
||||
i += _cron_parse_field(str+i, &u64, CRON_DOM_MASK, 1);
|
||||
cron->dom = (uint32_t)u64;
|
||||
i += _cron_parse_field(str+i, &u64, CRON_MON_MASK, 1);
|
||||
cron->mon = (uint16_t)u64;
|
||||
i += _cron_parse_field(str+i, &u64, CRON_DOW_MASK, 0);
|
||||
cron->dow = (uint8_t)u64;
|
||||
}
|
||||
|
||||
return 1; // TODO: do proper validation
|
||||
}
|
||||
|
||||
cron_t *cron_create ( const char *str )
|
||||
{
|
||||
cron_t *c = calloc(1, sizeof(cron_t));
|
||||
if (str) cron_set_string(c, str);
|
||||
return c;
|
||||
}
|
||||
|
||||
void cron_destroy ( cron_t *cron )
|
||||
{
|
||||
if (cron->str) free(cron->str);
|
||||
free(cron);
|
||||
}
|
||||
|
||||
const char *cron_get_string ( cron_t *cron )
|
||||
{
|
||||
return cron->str;
|
||||
}
|
||||
|
||||
int cron_set_string ( cron_t *cron, const char *str )
|
||||
{
|
||||
int save = 0;
|
||||
cron_t tmp;
|
||||
if (!cron->str || strcmp(cron->str, str)) {
|
||||
tmp.str = (char*)str;
|
||||
if (_cron_parse(&tmp, str)) {
|
||||
if (cron->str) free(cron->str);
|
||||
*cron = tmp;
|
||||
cron->str = strdup(str);
|
||||
save = 1;
|
||||
}
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
||||
time_t cron_run ( cron_t *cron )
|
||||
{
|
||||
time_t t;
|
||||
struct tm now;
|
||||
int ret = 1;
|
||||
uint64_t b = 0x1;
|
||||
|
||||
/* Get the current time */
|
||||
time_t t = time(NULL);
|
||||
struct tm now;
|
||||
localtime_r(&t, &now);
|
||||
|
||||
time(&t);
|
||||
localtime_t(&t, &now);
|
||||
|
||||
/* Find next event */
|
||||
if ( now->min == cron->last->min) {
|
||||
|
||||
}
|
||||
|
||||
/* Check */
|
||||
if ( ret && !((b << now.tm_min) & cron->min) ) ret = 0;
|
||||
if ( ret && !((b << now.tm_hour) & cron->hour) ) ret = 0;
|
||||
|
@ -21,23 +153,26 @@ int cron_is_time ( cron_t *cron )
|
|||
return ret;
|
||||
}
|
||||
|
||||
void cron_pack ( cron_t *cron, htsmsg_t *msg )
|
||||
void cron_serialize ( cron_t *cron, htsmsg_t *msg )
|
||||
{
|
||||
htsmsg_add_u64(msg, "min", cron->min);
|
||||
htsmsg_add_u32(msg, "hour", cron->hour);
|
||||
htsmsg_add_u32(msg, "dom", cron->dom);
|
||||
htsmsg_add_u32(msg, "mon", cron->mon);
|
||||
htsmsg_add_u32(msg, "dow", cron->dow);
|
||||
htsmsg_add_str(msg, "cron", cron->str);
|
||||
}
|
||||
|
||||
void cron_unpack ( cron_t *cron, htsmsg_t *msg )
|
||||
cron_t *cron_deserialize ( htsmsg_t *msg )
|
||||
{
|
||||
uint32_t u32 = 0;
|
||||
htsmsg_get_u64(msg, "min", &cron->min);
|
||||
htsmsg_get_u32(msg, "hour", &cron->hour);
|
||||
htsmsg_get_u32(msg, "dom", &cron->dom);
|
||||
htsmsg_get_u32(msg, "mon", &u32);
|
||||
cron->mon = (uint16_t)u32;
|
||||
htsmsg_get_u32(msg, "dow", &u32);
|
||||
cron->dow = (uint8_t)u32;
|
||||
return cron_create(htsmsg_get_str(msg, "cron"));
|
||||
}
|
||||
|
||||
#if 0
|
||||
int main ( int argc, char **argv )
|
||||
{
|
||||
cron_t *c = cron_create();
|
||||
cron_set_string(c, argv[1]);
|
||||
printf("min = 0x%016lx\n", c->min);
|
||||
printf("hour = 0x%06x\n", c->hour);
|
||||
printf("dom = 0x%08x\n", c->dom);
|
||||
printf("mon = 0x%03hx\n", c->mon);
|
||||
printf("dow = 0x%0hhx\n", c->dow);
|
||||
cron_destroy(c);
|
||||
}
|
||||
#endif
|
||||
|
|
26
src/cron.h
26
src/cron.h
|
@ -5,18 +5,20 @@
|
|||
#include <sys/types.h>
|
||||
#include "htsmsg.h"
|
||||
|
||||
typedef struct cron
|
||||
{
|
||||
uint64_t min; ///< Minutes
|
||||
uint32_t hour; ///< Hours
|
||||
uint32_t dom; ///< Day of month
|
||||
uint16_t mon; ///< Month
|
||||
uint8_t dow; ///< Day of week
|
||||
} cron_t;
|
||||
#define CRON_MIN_MASK 0x0FFFFFFFFFFFFFFFLL // 60 bits
|
||||
#define CRON_HOUR_MASK 0x00FFFFFF // 24 bits
|
||||
#define CRON_DOM_MASK 0x7FFFFFFF // 31 bits
|
||||
#define CRON_MON_MASK 0x0FFF // 12 bits
|
||||
#define CRON_DOW_MASK 0xFF // 8 bits (allow 0/7 for sunday)
|
||||
|
||||
void cron_set_string ( cron_t *cron, const char *str );
|
||||
int cron_is_time ( cron_t *cron );
|
||||
void cron_pack ( cron_t *cron, htsmsg_t *msg );
|
||||
void cron_unpack ( cron_t *cron, htsmsg_t *msg );
|
||||
typedef struct cron cron_t;
|
||||
|
||||
cron_t *cron_create ( const char *str );
|
||||
void cron_destroy ( cron_t *cron );
|
||||
const char *cron_get_string ( cron_t *cron );
|
||||
int cron_set_string ( cron_t *cron, const char *str );
|
||||
int cron_run ( cron_t *cron, time_t *next );
|
||||
void cron_serialize ( cron_t *cron, htsmsg_t *msg );
|
||||
cron_t *cron_deserialize ( htsmsg_t *msg );
|
||||
|
||||
#endif /* __CRON_H__ */
|
||||
|
|
765
src/epggrab.c
765
src/epggrab.c
|
@ -1,6 +1,8 @@
|
|||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <wordexp.h>
|
||||
#include "htsmsg.h"
|
||||
#include "settings.h"
|
||||
#include "tvheadend.h"
|
||||
|
@ -11,157 +13,503 @@
|
|||
#include "epggrab/xmltv.h"
|
||||
#include "epggrab/pyepg.h"
|
||||
#include "channels.h"
|
||||
#include "spawn.h"
|
||||
#include "htsmsg_xml.h"
|
||||
|
||||
/* Thread protection */
|
||||
int epggrab_confver;
|
||||
pthread_mutex_t epggrab_mutex;
|
||||
pthread_cond_t epggrab_cond;
|
||||
int epggrab_confver;
|
||||
pthread_mutex_t epggrab_mutex;
|
||||
pthread_cond_t epggrab_cond;
|
||||
|
||||
/* Config */
|
||||
uint32_t epggrab_advanced;
|
||||
uint32_t epggrab_eit;
|
||||
uint32_t epggrab_interval;
|
||||
epggrab_module_t* epggrab_module;
|
||||
epggrab_sched_list_t epggrab_schedule;
|
||||
uint32_t epggrab_advanced;
|
||||
uint32_t epggrab_eitenabled;
|
||||
uint32_t epggrab_interval;
|
||||
epggrab_module_t* epggrab_module;
|
||||
epggrab_sched_list_t epggrab_schedule;
|
||||
epggrab_module_list_t epggrab_modules;
|
||||
|
||||
/* Modules */
|
||||
LIST_HEAD(epggrab_module_list, epggrab_module);
|
||||
struct epggrab_module_list epggrab_modules;
|
||||
/* **************************************************************************
|
||||
* Modules
|
||||
* *************************************************************************/
|
||||
|
||||
/* Internal prototypes */
|
||||
static void* _epggrab_thread ( void* );
|
||||
static time_t _epggrab_thread_simple ( void );
|
||||
static time_t _epggrab_thread_advanced ( void );
|
||||
static void _epggrab_load ( void );
|
||||
static void _epggrab_save ( void );
|
||||
static void _epggrab_set_schedule ( int, epggrab_sched_t* );
|
||||
|
||||
/*
|
||||
* Initialise
|
||||
*/
|
||||
void epggrab_init ( void )
|
||||
static int _ch_id_cmp ( void *a, void *b )
|
||||
{
|
||||
/* Defaults */
|
||||
epggrab_advanced = 0;
|
||||
epggrab_eit = 1; // on air grab enabled
|
||||
epggrab_interval = 12 * 3600; // hours
|
||||
epggrab_module = NULL; // disabled
|
||||
|
||||
/* Initialise modules */
|
||||
LIST_INSERT_HEAD(&epggrab_modules, eit_init(), glink);
|
||||
#if TODO_XMLTV_SUPPORT
|
||||
LIST_INSERT_HEAD(&epggrab_modules, xmltv_init(), glink);
|
||||
#endif
|
||||
LIST_INSERT_HEAD(&epggrab_modules, pyepg_init(), glink);
|
||||
|
||||
/* Start thread */
|
||||
pthread_t tid;
|
||||
pthread_attr_t tattr;
|
||||
pthread_attr_init(&tattr);
|
||||
pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
|
||||
pthread_create(&tid, &tattr, _epggrab_thread, NULL);
|
||||
return strcmp(((epggrab_channel_t*)a)->id,
|
||||
((epggrab_channel_t*)b)->id);
|
||||
}
|
||||
|
||||
static int _ch_match ( epggrab_channel_t *ec, channel_t *ch )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void epggrab_module_channels_load ( epggrab_module_t *mod )
|
||||
{
|
||||
char path[100];
|
||||
uint32_t chid;
|
||||
htsmsg_t *m;
|
||||
htsmsg_field_t *f;
|
||||
epggrab_channel_t *ec;
|
||||
channel_t *ch;
|
||||
|
||||
sprintf(path, "epggrab/%s/channels", mod->id);
|
||||
if ((m = hts_settings_load(path))) {
|
||||
HTSMSG_FOREACH(f, m) {
|
||||
if ( !htsmsg_get_u32(m, f->hmf_name, &chid) ) {
|
||||
ch = channel_find_by_identifier(chid);
|
||||
if (ch) {
|
||||
ec = calloc(1, sizeof(epggrab_channel_t));
|
||||
ec->id = strdup(f->hmf_name);
|
||||
ec->channel = ch;
|
||||
RB_INSERT_SORTED(mod->channels, ec, link, _ch_id_cmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void epggrab_module_channels_save
|
||||
( epggrab_module_t *mod, const char *path )
|
||||
{
|
||||
epggrab_channel_t *c;
|
||||
htsmsg_t *m = htsmsg_create_map();
|
||||
|
||||
RB_FOREACH(c, mod->channels, link) {
|
||||
if (c->channel) htsmsg_add_u32(m, c->id, c->channel->ch_id);
|
||||
}
|
||||
hts_settings_save(m, path);
|
||||
}
|
||||
|
||||
int epggrab_module_channel_add ( epggrab_module_t *mod, channel_t *ch )
|
||||
{
|
||||
int save = 0;
|
||||
epggrab_channel_t *egc;
|
||||
RB_FOREACH(egc, mod->channels, link) {
|
||||
if (_ch_match(egc, ch) ) {
|
||||
save = 1;
|
||||
egc->channel = ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
||||
int epggrab_module_channel_rem ( epggrab_module_t *mod, channel_t *ch )
|
||||
{
|
||||
int save = 0;
|
||||
epggrab_channel_t *egc;
|
||||
RB_FOREACH(egc, mod->channels, link) {
|
||||
if (egc->channel == ch) {
|
||||
save = 1;
|
||||
egc->channel = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
||||
int epggrab_module_channel_mod ( epggrab_module_t *mod, channel_t *ch )
|
||||
{
|
||||
return epggrab_module_channel_add(mod, ch);
|
||||
}
|
||||
|
||||
|
||||
epggrab_channel_t *epggrab_module_channel_create ( epggrab_module_t *mod )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
epggrab_channel_t *epggrab_module_channel_find
|
||||
( epggrab_module_t *mod, const char *id )
|
||||
{
|
||||
epggrab_channel_t skel;
|
||||
skel.id = (char*)id;
|
||||
return RB_FIND(mod->channels, &skel, link, _ch_id_cmp);
|
||||
}
|
||||
|
||||
epggrab_module_t* epggrab_module_find_by_id ( const char *id )
|
||||
{
|
||||
epggrab_module_t *m;
|
||||
LIST_FOREACH(m, &epggrab_modules, link) {
|
||||
if ( !strcmp(m->id, id) ) return m;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
htsmsg_t *epggrab_module_list ( void )
|
||||
{
|
||||
epggrab_module_t *m;
|
||||
htsmsg_t *e, *a = htsmsg_create_list();
|
||||
LIST_FOREACH(m, &epggrab_modules, link) {
|
||||
e = htsmsg_create_map();
|
||||
htsmsg_add_str(e, "id", m->id);
|
||||
if(m->name) htsmsg_add_str(e, "name", m->name);
|
||||
if(m->path) htsmsg_add_str(e, "path", m->path);
|
||||
htsmsg_add_u32(e, "flags", m->flags);
|
||||
htsmsg_add_msg(a, NULL, e);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
void epggrab_module_enable_socket ( epggrab_module_t *mod, uint8_t e )
|
||||
{
|
||||
// TODO: implement this?
|
||||
}
|
||||
|
||||
htsmsg_t *epggrab_module_grab
|
||||
( epggrab_module_t *mod, const char *cmd, const char *opts )
|
||||
{
|
||||
int i, outlen;
|
||||
char *outbuf;
|
||||
char errbuf[100];
|
||||
const char **argv = NULL;
|
||||
wordexp_t we;
|
||||
htsmsg_t *ret;
|
||||
|
||||
/* Determine command */
|
||||
if ( !cmd || cmd[0] == '\0' )
|
||||
cmd = mod->path;
|
||||
if ( !cmd ) {
|
||||
tvhlog(LOG_ERR, "epggrab", "invalid configuraton no command specified");
|
||||
return NULL;
|
||||
}
|
||||
printf("cmd = %s, opts = %s\n", cmd, opts);
|
||||
|
||||
/* Parse opts */
|
||||
if (opts) {
|
||||
wordexp(opts, &we, 0);
|
||||
argv = calloc(we.we_wordc+2, sizeof(char*));
|
||||
argv[0] = cmd;
|
||||
for ( i = 0; i < we.we_wordc; i++ ) {
|
||||
argv[i + 1] = we.we_wordv[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Debug */
|
||||
tvhlog(LOG_DEBUG, "pyepg", "grab %s %s", cmd, opts ?: "");
|
||||
|
||||
/* Grab */
|
||||
outlen = spawn_and_store_stdout(cmd, (char*const*)argv, &outbuf);
|
||||
free(argv);
|
||||
wordfree(&we);
|
||||
if ( outlen < 1 ) {
|
||||
tvhlog(LOG_ERR, "pyepg", "no output detected");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Extract */
|
||||
ret = htsmsg_xml_deserialize(outbuf, errbuf, sizeof(errbuf));
|
||||
if (!ret)
|
||||
tvhlog(LOG_ERR, "pyepg", "htsmsg_xml_deserialize error %s", errbuf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* **************************************************************************
|
||||
* Configuration
|
||||
* *************************************************************************/
|
||||
|
||||
static int _update_str ( char **dst, const char *src )
|
||||
{
|
||||
int save = 0;
|
||||
if ( !(*dst) && src ) {
|
||||
*dst = strdup(src);
|
||||
save = 1;
|
||||
} else if ( (*dst) && !src ) {
|
||||
free(*dst);
|
||||
*dst = NULL;
|
||||
save = 1;
|
||||
} else if ( strcmp(*dst, src) ) {
|
||||
free(*dst);
|
||||
*dst = strdup(src);
|
||||
save = 1;
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
||||
static int _epggrab_schedule_deserialize ( htsmsg_t *a )
|
||||
{
|
||||
int save = 0;
|
||||
htsmsg_t *e;
|
||||
htsmsg_field_t *f;
|
||||
epggrab_sched_t *es;
|
||||
epggrab_module_t *mod;
|
||||
const char *str;
|
||||
|
||||
es = TAILQ_FIRST(&epggrab_schedule);
|
||||
HTSMSG_FOREACH(f, a) {
|
||||
if ((e = htsmsg_get_map_by_field(f))) {
|
||||
if (!es) {
|
||||
es = calloc(1, sizeof(epggrab_sched_t));
|
||||
save = 1;
|
||||
TAILQ_INSERT_TAIL(&epggrab_schedule, es, link);
|
||||
}
|
||||
|
||||
mod = NULL;
|
||||
if ((str = htsmsg_get_str(e, "module")))
|
||||
mod = epggrab_module_find_by_id(str);
|
||||
if (es->mod != mod) {
|
||||
es->mod = mod;
|
||||
save = 1;
|
||||
}
|
||||
|
||||
save |= _update_str(&es->cmd, htsmsg_get_str(e, "command"));
|
||||
save |= _update_str(&es->opts, htsmsg_get_str(e, "options"));
|
||||
str = htsmsg_get_str(e, "cron");
|
||||
if (es->cron) {
|
||||
if (!str) {
|
||||
free(es->cron);
|
||||
es->cron = NULL;
|
||||
save = 1;
|
||||
} else {
|
||||
save |= cron_set_string(es->cron, str);
|
||||
}
|
||||
} else if (str) {
|
||||
es->cron = cron_create(str);
|
||||
save = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Next */
|
||||
if (es) es = TAILQ_NEXT(es, link);
|
||||
}
|
||||
|
||||
if (es) {
|
||||
do {
|
||||
TAILQ_REMOVE(&epggrab_schedule, es, link);
|
||||
save = 1;
|
||||
} while ( (es = TAILQ_NEXT(es, link)) );
|
||||
}
|
||||
|
||||
return save;
|
||||
}
|
||||
|
||||
static htsmsg_t *_epggrab_schedule_serialize ( void )
|
||||
{
|
||||
epggrab_sched_t *es;
|
||||
htsmsg_t *e, *a;
|
||||
a = htsmsg_create_list();
|
||||
TAILQ_FOREACH(es, &epggrab_schedule, link) {
|
||||
e = htsmsg_create_map();
|
||||
if ( es->mod ) htsmsg_add_str(e, "module", es->mod->id);
|
||||
if ( es->cmd ) htsmsg_add_str(e, "command", es->cmd);
|
||||
if ( es->opts ) htsmsg_add_str(e, "options", es->opts);
|
||||
if ( es->cron ) cron_serialize(es->cron, e);
|
||||
htsmsg_add_msg(a, NULL, e);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
static void _epggrab_load ( void )
|
||||
{
|
||||
htsmsg_t *m, *a;
|
||||
const char *str;
|
||||
|
||||
/* No config */
|
||||
if ((m = hts_settings_load("epggrab/config")) == NULL)
|
||||
return;
|
||||
|
||||
/* Load settings */
|
||||
htsmsg_get_u32(m, "advanced", &epggrab_advanced);
|
||||
htsmsg_get_u32(m, "eit", &epggrab_eitenabled);
|
||||
htsmsg_get_u32(m, "interval", &epggrab_interval);
|
||||
if ( (str = htsmsg_get_str(m, "module")) )
|
||||
epggrab_module = epggrab_module_find_by_id(str);
|
||||
if ( (a = htsmsg_get_list(m, "schedule")) )
|
||||
_epggrab_schedule_deserialize(a);
|
||||
htsmsg_destroy(m);
|
||||
}
|
||||
|
||||
void epggrab_save ( void )
|
||||
{
|
||||
htsmsg_t *m;
|
||||
|
||||
/* Register */
|
||||
epggrab_confver++;
|
||||
pthread_cond_signal(&epggrab_cond);
|
||||
|
||||
/* Save */
|
||||
m = htsmsg_create_map();
|
||||
htsmsg_add_u32(m, "advanced", epggrab_advanced);
|
||||
htsmsg_add_u32(m, "eitenabled", epggrab_eitenabled);
|
||||
htsmsg_add_u32(m, "interval", epggrab_interval);
|
||||
if ( epggrab_module )
|
||||
htsmsg_add_str(m, "module", epggrab_module->id);
|
||||
htsmsg_add_msg(m, "schedule", _epggrab_schedule_serialize());
|
||||
hts_settings_save(m, "epggrab/config");
|
||||
htsmsg_destroy(m);
|
||||
}
|
||||
|
||||
int epggrab_set_advanced ( uint32_t advanced )
|
||||
{
|
||||
int save = 0;
|
||||
if ( epggrab_advanced != advanced ) {
|
||||
save = 1;
|
||||
epggrab_advanced = advanced;
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
||||
int epggrab_set_eitenabled ( uint32_t eitenabled )
|
||||
{
|
||||
int save = 0;
|
||||
if ( epggrab_eitenabled != eitenabled ) {
|
||||
save = 1;
|
||||
epggrab_eitenabled = eitenabled;
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
||||
int epggrab_set_interval ( uint32_t interval )
|
||||
{
|
||||
int save = 0;
|
||||
if ( epggrab_interval != interval ) {
|
||||
save = 1;
|
||||
epggrab_interval = interval;
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
||||
int epggrab_set_module ( epggrab_module_t *mod )
|
||||
{
|
||||
int save = 0;
|
||||
if ( mod && epggrab_module != mod ) {
|
||||
save = 1;
|
||||
epggrab_module = mod;
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
||||
int epggrab_set_module_by_id ( const char *id )
|
||||
{
|
||||
return epggrab_set_module(epggrab_module_find_by_id(id));
|
||||
}
|
||||
|
||||
int epggrab_set_schedule ( htsmsg_t *sched )
|
||||
{
|
||||
return _epggrab_schedule_deserialize(sched);
|
||||
}
|
||||
|
||||
htsmsg_t *epggrab_get_schedule ( void )
|
||||
{
|
||||
return _epggrab_schedule_serialize();
|
||||
}
|
||||
|
||||
/* **************************************************************************
|
||||
* Module Execution
|
||||
* *************************************************************************/
|
||||
|
||||
/*
|
||||
* Grab from module
|
||||
*/
|
||||
static void _epggrab_module_run ( epggrab_module_t *mod, const char *opts )
|
||||
static void _epggrab_module_run
|
||||
( epggrab_module_t *mod, const char *icmd, const char *iopts )
|
||||
{
|
||||
int save = 0;
|
||||
time_t tm1, tm2;
|
||||
htsmsg_t *data;
|
||||
epggrab_stats_t stats;
|
||||
memset(&stats, 0, sizeof(stats));
|
||||
char *cmd = NULL, *opts = NULL;
|
||||
|
||||
/* Check */
|
||||
if ( !mod ) return;
|
||||
if ( !mod || !mod->grab || !mod->parse ) return;
|
||||
|
||||
/* Dup before unlock */
|
||||
if ( icmd ) cmd = strdup(icmd);
|
||||
if ( iopts ) opts = strdup(iopts);
|
||||
pthread_mutex_unlock(&epggrab_mutex);
|
||||
|
||||
/* Grab */
|
||||
time(&tm1);
|
||||
data = mod->grab(opts);
|
||||
data = mod->grab(mod, cmd, opts);
|
||||
time(&tm2);
|
||||
|
||||
/* Process */
|
||||
if ( data ) {
|
||||
tvhlog(LOG_DEBUG, mod->name(), "grab took %d seconds", tm2 - tm1);
|
||||
|
||||
/* Parse */
|
||||
memset(&stats, 0, sizeof(stats));
|
||||
tvhlog(LOG_DEBUG, mod->id, "grab took %d seconds", tm2 - tm1);
|
||||
pthread_mutex_lock(&global_lock);
|
||||
time(&tm1);
|
||||
save |= mod->parse(data, &stats);
|
||||
save |= mod->parse(mod, data, &stats);
|
||||
time(&tm2);
|
||||
if (save) epg_updated();
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
htsmsg_destroy(data);
|
||||
|
||||
/* Debug stats */
|
||||
tvhlog(LOG_DEBUG, mod->name(), "parse took %d seconds", tm2 - tm1);
|
||||
tvhlog(LOG_DEBUG, mod->name(), " channels tot=%5d new=%5d mod=%5d",
|
||||
tvhlog(LOG_DEBUG, mod->id, "parse took %d seconds", tm2 - tm1);
|
||||
tvhlog(LOG_DEBUG, mod->id, " channels tot=%5d new=%5d mod=%5d",
|
||||
stats.channels.total, stats.channels.created,
|
||||
stats.channels.modified);
|
||||
tvhlog(LOG_DEBUG, mod->name(), " brands tot=%5d new=%5d mod=%5d",
|
||||
tvhlog(LOG_DEBUG, mod->id, " brands tot=%5d new=%5d mod=%5d",
|
||||
stats.brands.total, stats.brands.created,
|
||||
stats.brands.modified);
|
||||
tvhlog(LOG_DEBUG, mod->name(), " seasons tot=%5d new=%5d mod=%5d",
|
||||
tvhlog(LOG_DEBUG, mod->id, " seasons tot=%5d new=%5d mod=%5d",
|
||||
stats.seasons.total, stats.seasons.created,
|
||||
stats.seasons.modified);
|
||||
tvhlog(LOG_DEBUG, mod->name(), " episodes tot=%5d new=%5d mod=%5d",
|
||||
tvhlog(LOG_DEBUG, mod->id, " episodes tot=%5d new=%5d mod=%5d",
|
||||
stats.episodes.total, stats.episodes.created,
|
||||
stats.episodes.modified);
|
||||
tvhlog(LOG_DEBUG, mod->name(), " broadcasts tot=%5d new=%5d mod=%5d",
|
||||
tvhlog(LOG_DEBUG, mod->id, " broadcasts tot=%5d new=%5d mod=%5d",
|
||||
stats.broadcasts.total, stats.broadcasts.created,
|
||||
stats.broadcasts.modified);
|
||||
|
||||
} else {
|
||||
tvhlog(LOG_WARNING, mod->name(), "grab returned no data");
|
||||
tvhlog(LOG_WARNING, mod->id, "grab returned no data");
|
||||
}
|
||||
|
||||
/* Free a re-lock */
|
||||
if (cmd) free(cmd);
|
||||
if (opts) free(opts);
|
||||
pthread_mutex_lock(&epggrab_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* Simple run
|
||||
*/
|
||||
time_t _epggrab_thread_simple ( void )
|
||||
static time_t _epggrab_thread_simple ( void )
|
||||
{
|
||||
/* Copy config */
|
||||
time_t ret = time(NULL) + epggrab_interval;
|
||||
epggrab_module_t* mod = epggrab_module;
|
||||
|
||||
/* Unlock */
|
||||
pthread_mutex_unlock(&epggrab_mutex);
|
||||
|
||||
/* Run the module */
|
||||
_epggrab_module_run(mod, NULL);
|
||||
|
||||
/* Re-lock */
|
||||
pthread_mutex_lock(&epggrab_mutex);
|
||||
_epggrab_module_run(mod, NULL, NULL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Advanced run
|
||||
*
|
||||
* TODO: might be nice to put each module run in another thread, in case
|
||||
* they're scheduled at the same time?
|
||||
*/
|
||||
time_t _epggrab_thread_advanced ( void )
|
||||
static time_t _epggrab_thread_advanced ( void )
|
||||
{
|
||||
time_t now, ret, tmp;
|
||||
epggrab_sched_t *s;
|
||||
|
||||
/* Determine which to run */
|
||||
LIST_FOREACH(s, &epggrab_schedule, es_link) {
|
||||
if ( cron_is_time(&s->cron) ) {
|
||||
_epggrab_module_run(s->mod, s->opts);
|
||||
time(&now);
|
||||
ret = now + 3600; // once an hour if no entries
|
||||
TAILQ_FOREACH(s, &epggrab_schedule, link) {
|
||||
if ( cron_run(s->cron, &tmp) ) {
|
||||
ret = now; // re-run immediately
|
||||
_epggrab_module_run(s->mod, s->cmd, s->opts);
|
||||
// TODO: don't try to interate the list, it'll break due to locking
|
||||
// module (i.e. _epggrab_module_run() unlocks)
|
||||
} else {
|
||||
ret = MIN(ret, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make this driven off next time
|
||||
// get cron to tell us when next call will run
|
||||
return time(NULL) + 60;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Thread
|
||||
*/
|
||||
void* _epggrab_thread ( void* p )
|
||||
static void* _epggrab_thread ( void* p )
|
||||
{
|
||||
int confver = 0;
|
||||
struct timespec ts;
|
||||
|
@ -178,8 +526,10 @@ void* _epggrab_thread ( void* p )
|
|||
|
||||
/* Check for config change */
|
||||
while ( confver == epggrab_confver ) {
|
||||
if ( pthread_cond_timedwait(&epggrab_cond, &epggrab_mutex, &ts) == ETIMEDOUT ) break;
|
||||
int err = pthread_cond_timedwait(&epggrab_cond, &epggrab_mutex, &ts);
|
||||
if ( err == ETIMEDOUT ) break;
|
||||
}
|
||||
confver = epggrab_confver;
|
||||
|
||||
/* Run grabber */
|
||||
ts.tv_sec = epggrab_advanced
|
||||
|
@ -191,259 +541,60 @@ void* _epggrab_thread ( void* p )
|
|||
}
|
||||
|
||||
/* **************************************************************************
|
||||
* Module access
|
||||
* Global Functions
|
||||
* *************************************************************************/
|
||||
|
||||
epggrab_module_t* epggrab_module_find_by_name ( const char *name )
|
||||
/*
|
||||
* Initialise
|
||||
*/
|
||||
void epggrab_init ( void )
|
||||
{
|
||||
epggrab_module_t *m;
|
||||
LIST_FOREACH(m, &epggrab_modules, glink) {
|
||||
if ( !strcmp(m->name(), name) ) return m;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
/* Defaults */
|
||||
epggrab_advanced = 0;
|
||||
epggrab_eitenabled = 1; // on air grab enabled
|
||||
epggrab_interval = 12 * 3600; // hours
|
||||
epggrab_module = NULL; // disabled
|
||||
TAILQ_INIT(&epggrab_schedule);
|
||||
|
||||
/* **************************************************************************
|
||||
* Channel handling
|
||||
*
|
||||
* TODO: this is all a bit messy (too much indirection?) but it works!
|
||||
*
|
||||
* TODO: need to save mappings to disk
|
||||
*
|
||||
* TODO: probably want rules on what can be updated etc...
|
||||
* *************************************************************************/
|
||||
/* Initialise modules */
|
||||
eit_init(&epggrab_modules);
|
||||
xmltv_init(&epggrab_modules);
|
||||
pyepg_init(&epggrab_modules);
|
||||
|
||||
static int _ch_id_cmp ( void *a, void *b )
|
||||
{
|
||||
return strcmp(((epggrab_channel_t*)a)->id,
|
||||
((epggrab_channel_t*)b)->id);
|
||||
}
|
||||
|
||||
static channel_t *_channel_find ( epggrab_channel_t *ch )
|
||||
{
|
||||
channel_t *ret = NULL;
|
||||
if (ch->name) ret = channel_find_by_name(ch->name, 0, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _channel_match ( epggrab_channel_t *egc, channel_t *ch )
|
||||
{
|
||||
/* Already linked */
|
||||
if ( egc->channel ) return 0;
|
||||
|
||||
/* Name match */
|
||||
if ( !strcmp(ch->ch_name, egc->name) ) return 1;
|
||||
|
||||
return 0;
|
||||
/* Start thread */
|
||||
pthread_t tid;
|
||||
pthread_attr_t tattr;
|
||||
pthread_attr_init(&tattr);
|
||||
pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
|
||||
pthread_create(&tid, &tattr, _epggrab_thread, NULL);
|
||||
}
|
||||
|
||||
void epggrab_channel_add ( channel_t *ch )
|
||||
{
|
||||
epggrab_module_t *m;
|
||||
LIST_FOREACH(m, &epggrab_modules, glink) {
|
||||
if (m->ch_add) m->ch_add(ch);
|
||||
LIST_FOREACH(m, &epggrab_modules, link) {
|
||||
if (m->ch_add)
|
||||
if (m->ch_add(m, ch) && m->ch_save)
|
||||
m->ch_save(m);
|
||||
}
|
||||
}
|
||||
|
||||
void epggrab_channel_rem ( channel_t *ch )
|
||||
{
|
||||
epggrab_module_t *m;
|
||||
LIST_FOREACH(m, &epggrab_modules, glink) {
|
||||
if (m->ch_rem) m->ch_rem(ch);
|
||||
LIST_FOREACH(m, &epggrab_modules, link) {
|
||||
if (m->ch_rem)
|
||||
if (m->ch_rem(m, ch) && m->ch_save)
|
||||
m->ch_save(m);
|
||||
}
|
||||
}
|
||||
|
||||
void epggrab_channel_mod ( channel_t *ch )
|
||||
{
|
||||
epggrab_module_t *m;
|
||||
LIST_FOREACH(m, &epggrab_modules, glink) {
|
||||
if (m->ch_mod) m->ch_mod(ch);
|
||||
LIST_FOREACH(m, &epggrab_modules, link) {
|
||||
if (m->ch_mod)
|
||||
if (m->ch_mod(m, ch) && m->ch_save)
|
||||
m->ch_save(m);
|
||||
}
|
||||
}
|
||||
|
||||
epggrab_channel_t *epggrab_module_channel_create
|
||||
( epggrab_channel_tree_t *tree, epggrab_channel_t *iskel )
|
||||
{
|
||||
epggrab_channel_t *egc;
|
||||
static epggrab_channel_t *skel = NULL;
|
||||
if (!skel) skel = calloc(1, sizeof(epggrab_channel_t));
|
||||
skel->id = iskel->id;
|
||||
skel->name = iskel->name;
|
||||
egc = RB_INSERT_SORTED(tree, skel, glink, _ch_id_cmp);
|
||||
if ( egc == NULL ) {
|
||||
skel->id = strdup(skel->id);
|
||||
skel->name = strdup(skel->name);
|
||||
skel->channel = _channel_find(skel);
|
||||
egc = skel;
|
||||
skel = NULL;
|
||||
}
|
||||
return egc;
|
||||
}
|
||||
|
||||
epggrab_channel_t *epggrab_module_channel_find
|
||||
( epggrab_channel_tree_t *tree, const char *id )
|
||||
{
|
||||
epggrab_channel_t skel;
|
||||
skel.id = (char*)id;
|
||||
return RB_FIND(tree, &skel, glink, _ch_id_cmp);
|
||||
}
|
||||
|
||||
void epggrab_module_channel_add ( epggrab_channel_tree_t *tree, channel_t *ch )
|
||||
{
|
||||
epggrab_channel_t *egc;
|
||||
RB_FOREACH(egc, tree, glink) {
|
||||
if (_channel_match(egc, ch) ) {
|
||||
egc->channel = ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void epggrab_module_channel_rem ( epggrab_channel_tree_t *tree, channel_t *ch )
|
||||
{
|
||||
epggrab_channel_t *egc;
|
||||
RB_FOREACH(egc, tree, glink) {
|
||||
if (egc->channel == ch) {
|
||||
egc->channel = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void epggrab_module_channel_mod ( epggrab_channel_tree_t *tree, channel_t *ch )
|
||||
{
|
||||
epggrab_module_channel_add(tree, ch);
|
||||
}
|
||||
|
||||
/* **************************************************************************
|
||||
* Configuration handling
|
||||
* *************************************************************************/
|
||||
|
||||
void _epggrab_load ( void )
|
||||
{
|
||||
htsmsg_t *m, *s, *e, *c;
|
||||
htsmsg_field_t *f;
|
||||
const char *str;
|
||||
epggrab_sched_t *es;
|
||||
|
||||
/* No config */
|
||||
if ((m = hts_settings_load("epggrab/config")) == NULL)
|
||||
return;
|
||||
|
||||
/* Load settings */
|
||||
htsmsg_get_u32(m, "advanced", &epggrab_advanced);
|
||||
htsmsg_get_u32(m, "eit", &epggrab_eit);
|
||||
if ( !epggrab_advanced ) {
|
||||
htsmsg_get_u32(m, "interval", &epggrab_interval);
|
||||
str = htsmsg_get_str(m, "module");
|
||||
if (str) epggrab_module = epggrab_module_find_by_name(str);
|
||||
} else {
|
||||
if ((s = htsmsg_get_list(m, "schedule")) != NULL) {
|
||||
HTSMSG_FOREACH(f, s) {
|
||||
if ((e = htsmsg_get_map_by_field(f)) != NULL) {
|
||||
es = calloc(1, sizeof(epggrab_sched_t));
|
||||
str = htsmsg_get_str(e, "mod");
|
||||
if (str) es->mod = epggrab_module_find_by_name(str);
|
||||
str = htsmsg_get_str(e, "opts");
|
||||
if (str) es->opts = strdup(str);
|
||||
c = htsmsg_get_map(e, "cron");
|
||||
if (f) cron_unpack(&es->cron, c);
|
||||
LIST_INSERT_HEAD(&epggrab_schedule, es, es_link);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
htsmsg_destroy(m);
|
||||
}
|
||||
|
||||
void _epggrab_save ( void )
|
||||
{
|
||||
htsmsg_t *m, *s, *e, *c;
|
||||
epggrab_sched_t *es;
|
||||
|
||||
/* Enable EIT */
|
||||
|
||||
/* Register */
|
||||
epggrab_confver++;
|
||||
pthread_cond_signal(&epggrab_cond);
|
||||
|
||||
/* Save */
|
||||
m = htsmsg_create_map();
|
||||
htsmsg_add_u32(m, "advanced", epggrab_advanced);
|
||||
htsmsg_add_u32(m, "eit", epggrab_eit);
|
||||
if ( !epggrab_advanced ) {
|
||||
htsmsg_add_u32(m, "interval", epggrab_interval);
|
||||
if ( epggrab_module )
|
||||
htsmsg_add_str(m, "module", epggrab_module->name());
|
||||
} else {
|
||||
s = htsmsg_create_list();
|
||||
LIST_FOREACH(es, &epggrab_schedule, es_link) {
|
||||
e = htsmsg_create_map();
|
||||
if ( es->mod ) htsmsg_add_str(e, "mod", es->mod->name());
|
||||
if ( es->opts ) htsmsg_add_str(e, "opts", es->opts);
|
||||
c = htsmsg_create_map();
|
||||
cron_pack(&es->cron, c);
|
||||
htsmsg_add_msg(e, "cron", c);
|
||||
htsmsg_add_msg(s, NULL, e);
|
||||
}
|
||||
htsmsg_add_msg(m, "schedule", s);
|
||||
}
|
||||
hts_settings_save(m, "epggrab/config");
|
||||
htsmsg_destroy(m);
|
||||
}
|
||||
|
||||
void _epggrab_set_schedule ( int count, epggrab_sched_t *sched )
|
||||
{
|
||||
int i;
|
||||
epggrab_sched_t *es;
|
||||
|
||||
/* Remove existing */
|
||||
while ( !LIST_EMPTY(&epggrab_schedule) ) {
|
||||
es = LIST_FIRST(&epggrab_schedule);
|
||||
LIST_REMOVE(es, es_link);
|
||||
if ( es->opts ) free(es->opts);
|
||||
free(es);
|
||||
}
|
||||
|
||||
/* Create new */
|
||||
for ( i = 0; i < count; i++ ) {
|
||||
es = calloc(1, sizeof(epggrab_sched_t));
|
||||
es->mod = sched[i].mod;
|
||||
es->cron = sched[i].cron;
|
||||
if ( sched[i].opts ) es->opts = strdup(sched[i].opts);
|
||||
LIST_INSERT_HEAD(&epggrab_schedule, es, es_link);
|
||||
}
|
||||
}
|
||||
|
||||
void epggrab_set_simple ( uint32_t interval, epggrab_module_t *mod )
|
||||
{
|
||||
/* Set config */
|
||||
lock_assert(&epggrab_mutex);
|
||||
epggrab_advanced = 0;
|
||||
epggrab_interval = interval;
|
||||
epggrab_module = mod;
|
||||
|
||||
/* Save */
|
||||
_epggrab_save();
|
||||
}
|
||||
|
||||
void epggrab_set_advanced ( uint32_t count, epggrab_sched_t *sched )
|
||||
{
|
||||
/* Set config */
|
||||
lock_assert(&epggrab_mutex);
|
||||
epggrab_advanced = 1;
|
||||
_epggrab_set_schedule(count, sched);
|
||||
|
||||
/* Save */
|
||||
_epggrab_save();
|
||||
}
|
||||
|
||||
void epggrab_set_eit ( uint32_t eit )
|
||||
{
|
||||
/* Set config */
|
||||
lock_assert(&epggrab_mutex);
|
||||
epggrab_eit = eit;
|
||||
|
||||
/* Save */
|
||||
_epggrab_save();
|
||||
}
|
||||
|
|
163
src/epggrab.h
163
src/epggrab.h
|
@ -3,14 +3,12 @@
|
|||
|
||||
#include <pthread.h>
|
||||
#include "cron.h"
|
||||
#include "channels.h"
|
||||
|
||||
/* **************************************************************************
|
||||
* Type definitions
|
||||
* Grabber Stats
|
||||
* *************************************************************************/
|
||||
|
||||
/*
|
||||
* Grab statistics
|
||||
*/
|
||||
typedef struct epggrab_stats_part
|
||||
{
|
||||
int created;
|
||||
|
@ -27,86 +25,153 @@ typedef struct epggrab_stats
|
|||
epggrab_stats_part_t broadcasts;
|
||||
} epggrab_stats_t;
|
||||
|
||||
/* **************************************************************************
|
||||
* Grabber Channels
|
||||
* *************************************************************************/
|
||||
|
||||
/*
|
||||
* Grab channel
|
||||
*/
|
||||
typedef struct epggrab_channel
|
||||
{
|
||||
RB_ENTRY(epggrab_channel) link; ///< Global link
|
||||
|
||||
char *id; ///< Grabber's ID
|
||||
char *name; ///< Channel name
|
||||
struct channel *channel; ///< Mapped channel
|
||||
// TODO: I think we might need a list of channels!
|
||||
RB_ENTRY(epggrab_channel) glink; ///< Global link
|
||||
} epggrab_channel_t;
|
||||
|
||||
/*
|
||||
* Channel list
|
||||
*/
|
||||
RB_HEAD(epggrab_channel_tree, epggrab_channel);
|
||||
typedef struct epggrab_channel_tree epggrab_channel_tree_t;
|
||||
|
||||
/* **************************************************************************
|
||||
* Grabber Modules
|
||||
* *************************************************************************/
|
||||
|
||||
typedef struct epggrab_module epggrab_module_t;
|
||||
|
||||
/*
|
||||
* Grabber flags
|
||||
*/
|
||||
#define EPGGRAB_MODULE_SYNC 0x01
|
||||
#define EPGGRAB_MODULE_ASYNC 0x02
|
||||
#define EPGGRAB_MODULE_SIMPLE 0x04
|
||||
#define EPGGRAB_MODULE_ADVANCED 0x08
|
||||
#define EPGGRAB_MODULE_EXTERNAL 0x10
|
||||
|
||||
/*
|
||||
* Grabber base class
|
||||
*/
|
||||
typedef struct epggrab_module
|
||||
struct epggrab_module
|
||||
{
|
||||
const char* (*name) ( void );
|
||||
void (*enable) ( void );
|
||||
void (*disable) ( void );
|
||||
htsmsg_t* (*grab) ( const char *opts );
|
||||
int (*parse) ( htsmsg_t *data, epggrab_stats_t *stats );
|
||||
void (*ch_add) ( struct channel *ch );
|
||||
void (*ch_rem) ( struct channel *ch );
|
||||
void (*ch_mod) ( struct channel *ch );
|
||||
LIST_ENTRY(epggrab_module) link; ///< Global list link
|
||||
|
||||
LIST_ENTRY(epggrab_module) glink; ///< Global list link
|
||||
} epggrab_module_t;
|
||||
const char *id; ///< Module identifier
|
||||
const char *name; ///< Module name (for display)
|
||||
const char *path; ///< Module path (for fixed config)
|
||||
const uint8_t flags; ///< Mode flags
|
||||
epggrab_channel_tree_t *channels; ///< Channel list
|
||||
|
||||
/* Enable/Disable for async */
|
||||
void (*enable) ( epggrab_module_t *m, uint8_t e );
|
||||
|
||||
/* Synchronous grab&parse */
|
||||
htsmsg_t* (*grab) ( epggrab_module_t *m,
|
||||
const char *c, const char *o );
|
||||
int (*parse) ( epggrab_module_t *m,
|
||||
htsmsg_t *d, epggrab_stats_t *s );
|
||||
|
||||
/* Channel listings */
|
||||
int (*ch_add) ( epggrab_module_t *m, channel_t *ch );
|
||||
int (*ch_rem) ( epggrab_module_t *m, channel_t *ch );
|
||||
int (*ch_mod) ( epggrab_module_t *m, channel_t *ch );
|
||||
|
||||
/* Save any settings */
|
||||
void (*ch_save) ( epggrab_module_t *m );
|
||||
};
|
||||
|
||||
/*
|
||||
* Schedule specification
|
||||
* Default module functions
|
||||
*/
|
||||
void epggrab_module_enable_socket ( epggrab_module_t *m, uint8_t e );
|
||||
htsmsg_t *epggrab_module_grab ( epggrab_module_t *m, const char *cmd, const char *opts );
|
||||
void epggrab_module_channels_load ( epggrab_module_t *m );
|
||||
void epggrab_module_channels_save ( epggrab_module_t *m, const char *path );
|
||||
int epggrab_module_channel_add ( epggrab_module_t *m, channel_t *ch );
|
||||
int epggrab_module_channel_rem ( epggrab_module_t *m, channel_t *ch );
|
||||
int epggrab_module_channel_mod ( epggrab_module_t *m, channel_t *ch );
|
||||
epggrab_channel_t *epggrab_module_channel_create
|
||||
( epggrab_module_t *m );
|
||||
epggrab_channel_t *epggrab_module_channel_find
|
||||
( epggrab_module_t *m, const char *id );
|
||||
|
||||
/*
|
||||
* Module list
|
||||
*/
|
||||
LIST_HEAD(epggrab_module_list, epggrab_module);
|
||||
typedef struct epggrab_module_list epggrab_module_list_t;
|
||||
|
||||
/*
|
||||
* Access the Module list
|
||||
*/
|
||||
epggrab_module_t* epggrab_module_find_by_id ( const char *id );
|
||||
htsmsg_t* epggrab_module_list ( void );
|
||||
|
||||
/* **************************************************************************
|
||||
* Configuration
|
||||
* *************************************************************************/
|
||||
|
||||
/*
|
||||
* Schedule specification (for advanced config)
|
||||
*/
|
||||
typedef struct epggrab_sched
|
||||
{
|
||||
LIST_ENTRY(epggrab_sched) es_link;
|
||||
cron_t cron; ///< Cron definition
|
||||
epggrab_module_t *mod; ///< Module
|
||||
char *opts; ///< Extra (advanced) options
|
||||
TAILQ_ENTRY(epggrab_sched) link;
|
||||
cron_t *cron; ///< Cron definition
|
||||
epggrab_module_t *mod; ///< Module
|
||||
char *cmd; ///< Command
|
||||
char *opts; ///< Extra (advanced) options
|
||||
} epggrab_sched_t;
|
||||
|
||||
/*
|
||||
* Schedule list
|
||||
*/
|
||||
LIST_HEAD(epggrab_sched_list, epggrab_sched);
|
||||
TAILQ_HEAD(epggrab_sched_list, epggrab_sched);
|
||||
typedef struct epggrab_sched_list epggrab_sched_list_t;
|
||||
|
||||
/* **************************************************************************
|
||||
* Variables
|
||||
* *************************************************************************/
|
||||
/*
|
||||
* Configuration
|
||||
*/
|
||||
extern pthread_mutex_t epggrab_mutex;
|
||||
extern uint32_t epggrab_advanced;
|
||||
extern uint32_t epggrab_eitenabled;
|
||||
extern uint32_t epggrab_interval;
|
||||
extern epggrab_module_t* epggrab_module;
|
||||
extern epggrab_sched_list_t epggrab_schedule;
|
||||
|
||||
htsmsg_t *epggrab_get_schedule ( void );
|
||||
|
||||
/*
|
||||
* Lock this if accessing configuration
|
||||
* Update
|
||||
*/
|
||||
pthread_mutex_t epggrab_mutex;
|
||||
int epggrab_set_advanced ( uint32_t advanced );
|
||||
int epggrab_set_eitenabled ( uint32_t eitenabled );
|
||||
int epggrab_set_interval ( uint32_t interval );
|
||||
int epggrab_set_module ( epggrab_module_t *module );
|
||||
int epggrab_set_module_by_id ( const char *id );
|
||||
int epggrab_set_schedule ( htsmsg_t *sched );
|
||||
void epggrab_save ( void );
|
||||
|
||||
/* **************************************************************************
|
||||
* Functions
|
||||
* Global Functions
|
||||
* *************************************************************************/
|
||||
|
||||
/*
|
||||
* Setup
|
||||
*/
|
||||
void epggrab_init ( void );
|
||||
|
||||
/*
|
||||
* Access the list of supported modules
|
||||
*/
|
||||
//epggrab_module_t** epggrab_module_list ( void );
|
||||
epggrab_module_t* epggrab_module_find_by_name ( const char *name );
|
||||
|
||||
/*
|
||||
* Set configuration
|
||||
*/
|
||||
void epggrab_set_simple ( uint32_t interval, epggrab_module_t* mod );
|
||||
void epggrab_set_advanced ( uint32_t count, epggrab_sched_t* sched );
|
||||
void epggrab_set_eit ( uint32_t eit );
|
||||
|
||||
/*
|
||||
* Channel handling
|
||||
*/
|
||||
|
@ -114,14 +179,4 @@ void epggrab_channel_add ( struct channel *ch );
|
|||
void epggrab_channel_rem ( struct channel *ch );
|
||||
void epggrab_channel_mod ( struct channel *ch );
|
||||
|
||||
/*
|
||||
* Module specific channel handling
|
||||
*/
|
||||
void epggrab_module_channel_add ( epggrab_channel_tree_t *tree, struct channel *ch );
|
||||
void epggrab_module_channel_rem ( epggrab_channel_tree_t *tree, struct channel *ch );
|
||||
void epggrab_module_channel_mod ( epggrab_channel_tree_t *tree, struct channel *ch );
|
||||
|
||||
epggrab_channel_t *epggrab_module_channel_create ( epggrab_channel_tree_t *tree, epggrab_channel_t *ch );
|
||||
epggrab_channel_t *epggrab_module_channel_find ( epggrab_channel_tree_t *tree, const char *id );
|
||||
|
||||
#endif /* __EPGGRAB_H__ */
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "tvheadend.h"
|
||||
#include "channels.h"
|
||||
#include "epg.h"
|
||||
|
@ -25,13 +26,6 @@
|
|||
* Module Setup
|
||||
* ***********************************************************************/
|
||||
|
||||
static epggrab_module_t eit_module;
|
||||
|
||||
static const char* _eit_name ( void )
|
||||
{
|
||||
return "eit";
|
||||
}
|
||||
|
||||
static void _eit_episode_uri
|
||||
( char *uri, const char *title, const char *summary )
|
||||
{
|
||||
|
@ -55,9 +49,7 @@ void eit_callback ( channel_t *ch, int id, time_t start, time_t stop,
|
|||
if (!ch || !ch->ch_name || !ch->ch_name[0]) return;
|
||||
|
||||
/* Disabled? */
|
||||
#if TODO_REENABLE_THIS
|
||||
if (epggrab_eit_disabled) return;
|
||||
#endif
|
||||
if (!epggrab_eitenabled) return;
|
||||
|
||||
/* Find broadcast */
|
||||
ebc = epg_broadcast_find_by_time(ch, start, stop, 1, &save);
|
||||
|
@ -90,14 +82,17 @@ void eit_callback ( channel_t *ch, int id, time_t start, time_t stop,
|
|||
}
|
||||
}
|
||||
|
||||
epggrab_module_t* eit_init ( void )
|
||||
/* ************************************************************************
|
||||
* Module Setup
|
||||
* ***********************************************************************/
|
||||
|
||||
static epggrab_module_t _eit_mod;
|
||||
|
||||
void eit_init ( epggrab_module_list_t *list )
|
||||
{
|
||||
// Note: the EIT grabber is very different to the others, in that
|
||||
// its asynchronous based on DVB data stream
|
||||
eit_module.enable = NULL;
|
||||
eit_module.disable = NULL;
|
||||
eit_module.grab = NULL;
|
||||
eit_module.parse = NULL;
|
||||
eit_module.name = _eit_name;
|
||||
return &eit_module;
|
||||
_eit_mod.id = strdup("eit");
|
||||
_eit_mod.name = strdup("EIT: On-Air Grabber");
|
||||
*((uint8_t*)&_eit_mod.flags) = EPGGRAB_MODULE_ASYNC;
|
||||
LIST_INSERT_HEAD(list, &_eit_mod, link);
|
||||
// Note: this is mostly ignored anyway as EIT is treated as a special case!
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
#include "epggrab.h"
|
||||
|
||||
epggrab_module_t *eit_init ( void );
|
||||
void eit_init ( epggrab_module_list_t *list );
|
||||
|
||||
void eit_callback ( struct channel *ch, int id, time_t start, time_t stop,
|
||||
const char *title, const char *desc,
|
||||
|
|
|
@ -28,43 +28,17 @@
|
|||
#include "epggrab/pyepg.h"
|
||||
#include "channels.h"
|
||||
|
||||
/* **************************************************************************
|
||||
* Channels
|
||||
* *************************************************************************/
|
||||
|
||||
epggrab_channel_tree_t _pyepg_channels;
|
||||
|
||||
static channel_t *_pyepg_channel_create ( epggrab_channel_t *skel )
|
||||
static channel_t *_pyepg_channel_create ( epggrab_channel_t *skel, int *save )
|
||||
{
|
||||
epggrab_channel_t *ch
|
||||
= epggrab_module_channel_create(&_pyepg_channels, skel);
|
||||
if (ch) return ch->channel;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static channel_t *_pyepg_channel_find ( const char *id )
|
||||
{
|
||||
epggrab_channel_t *ch
|
||||
= epggrab_module_channel_find(&_pyepg_channels, id);
|
||||
if (ch) return ch->channel;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void _pyepg_channel_add ( channel_t *ch )
|
||||
{
|
||||
epggrab_module_channel_add(&_pyepg_channels, ch);
|
||||
}
|
||||
|
||||
static void _pyepg_channel_rem ( channel_t *ch )
|
||||
{
|
||||
epggrab_module_channel_rem(&_pyepg_channels, ch);
|
||||
}
|
||||
|
||||
static void _pyepg_channel_mod ( channel_t *ch )
|
||||
{
|
||||
epggrab_module_channel_mod(&_pyepg_channels, ch);
|
||||
}
|
||||
|
||||
/* **************************************************************************
|
||||
* Parsing
|
||||
* *************************************************************************/
|
||||
|
@ -83,7 +57,7 @@ static int _pyepg_parse_time ( const char *str, time_t *out )
|
|||
|
||||
static int _pyepg_parse_channel ( htsmsg_t *data, epggrab_stats_t *stats )
|
||||
{
|
||||
int save = 0;
|
||||
int save = 0, save2 = 0;
|
||||
htsmsg_t *attr, *tags;
|
||||
const char *icon;
|
||||
channel_t *ch;
|
||||
|
@ -97,14 +71,23 @@ static int _pyepg_parse_channel ( htsmsg_t *data, epggrab_stats_t *stats )
|
|||
skel.name = (char*)htsmsg_xml_get_cdata_str(tags, "name");
|
||||
icon = htsmsg_xml_get_cdata_str(tags, "image");
|
||||
|
||||
ch = _pyepg_channel_create(&skel);
|
||||
ch = _pyepg_channel_create(&skel, &save2);
|
||||
stats->channels.total++;
|
||||
if (save2) {
|
||||
stats->channels.created++;
|
||||
save |= 1;
|
||||
}
|
||||
|
||||
/* Update values */
|
||||
if (ch) {
|
||||
// TODO: set the name
|
||||
//if (skel.name) save |= channel_rename(ch, skel.name);
|
||||
if (skel.name) {
|
||||
if(!ch->ch_name || strcmp(ch->ch_name, skel.name)) {
|
||||
save |= channel_rename(ch, skel.name);
|
||||
}
|
||||
}
|
||||
if (icon) channel_set_icon(ch, icon);
|
||||
}
|
||||
if (save) stats->channels.modified++;
|
||||
|
||||
return save;
|
||||
}
|
||||
|
@ -139,7 +122,7 @@ static int _pyepg_parse_brand ( htsmsg_t *data, epggrab_stats_t *stats )
|
|||
}
|
||||
|
||||
/* Set icon */
|
||||
#if TODO
|
||||
#if TODO_ICON_SUPPORT
|
||||
if ((str = htsmsg_xml_get_cdata_str(tags, "icon"))) {
|
||||
save |= epg_brand_set_icon(brand, str);
|
||||
}
|
||||
|
@ -183,7 +166,7 @@ static int _pyepg_parse_season ( htsmsg_t *data, epggrab_stats_t *stats )
|
|||
}
|
||||
|
||||
/* Set title */
|
||||
#if TODO
|
||||
#if TODO_EXTRA_METADATA
|
||||
if ((str = htsmsg_xml_get_cdata_str(tags, "title"))) {
|
||||
save |= epg_season_set_title(season, str);
|
||||
}
|
||||
|
@ -269,7 +252,7 @@ static int _pyepg_parse_episode ( htsmsg_t *data, epggrab_stats_t *stats )
|
|||
|
||||
/* Genre */
|
||||
// TODO: can actually have multiple!
|
||||
#if TODO
|
||||
#if TODO_GENRE_SUPPORT
|
||||
if ((str = htsmsg_xml_get_cdata_str(tags, "genre"))) {
|
||||
// TODO: conversion?
|
||||
save |= epg_episode_set_genre(episode, str);
|
||||
|
@ -351,7 +334,7 @@ static int _pyepg_parse_schedule ( htsmsg_t *data, epggrab_stats_t *stats )
|
|||
|
||||
static int _pyepg_parse_epg ( htsmsg_t *data, epggrab_stats_t *stats )
|
||||
{
|
||||
int save = 0;
|
||||
int save = 0, chsave = 0;
|
||||
htsmsg_t *tags;
|
||||
htsmsg_field_t *f;
|
||||
|
||||
|
@ -359,7 +342,7 @@ static int _pyepg_parse_epg ( htsmsg_t *data, epggrab_stats_t *stats )
|
|||
|
||||
HTSMSG_FOREACH(f, tags) {
|
||||
if (strcmp(f->hmf_name, "channel") == 0 ) {
|
||||
save |= _pyepg_parse_channel(htsmsg_get_map_by_field(f), stats);
|
||||
chsave |= _pyepg_parse_channel(htsmsg_get_map_by_field(f), stats);
|
||||
} else if (strcmp(f->hmf_name, "brand") == 0 ) {
|
||||
save |= _pyepg_parse_brand(htsmsg_get_map_by_field(f), stats);
|
||||
} else if (strcmp(f->hmf_name, "series") == 0 ) {
|
||||
|
@ -370,14 +353,31 @@ static int _pyepg_parse_epg ( htsmsg_t *data, epggrab_stats_t *stats )
|
|||
save |= _pyepg_parse_schedule(htsmsg_get_map_by_field(f), stats);
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
save |= chsave;
|
||||
if (chsave) pyepg_save();
|
||||
#endif
|
||||
|
||||
return save;
|
||||
}
|
||||
|
||||
static int _pyepg_parse ( htsmsg_t *data, epggrab_stats_t *stats )
|
||||
|
||||
/* ************************************************************************
|
||||
* Module Setup
|
||||
* ***********************************************************************/
|
||||
|
||||
epggrab_channel_tree_t _pyepg_channels;
|
||||
epggrab_module_t _pyepg_module;
|
||||
|
||||
static void _pyepg_save ( epggrab_module_t *mod )
|
||||
{
|
||||
epggrab_module_channels_save(mod, "epggrab/pyepg/channels");
|
||||
}
|
||||
|
||||
static int _pyepg_parse
|
||||
( epggrab_module_t *mod, htsmsg_t *data, epggrab_stats_t *stats )
|
||||
{
|
||||
htsmsg_t *tags, *epg;
|
||||
epggrab_module_t *mod;
|
||||
|
||||
if ((tags = htsmsg_get_map(data, "tags")) == NULL) return 0;
|
||||
|
||||
|
@ -387,79 +387,33 @@ static int _pyepg_parse ( htsmsg_t *data, epggrab_stats_t *stats )
|
|||
|
||||
/* XMLTV format */
|
||||
if ((epg = htsmsg_get_map(tags, "tv")) != NULL) {
|
||||
mod = epggrab_module_find_by_name("xmltv");
|
||||
if (mod) return mod->parse(epg, stats);
|
||||
mod = epggrab_module_find_by_id("xmltv_sync");
|
||||
if (mod) return mod->parse(mod, epg, stats);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ************************************************************************
|
||||
* Module Setup
|
||||
* ***********************************************************************/
|
||||
|
||||
static epggrab_module_t pyepg_module;
|
||||
|
||||
static const char* _pyepg_name ( void )
|
||||
void pyepg_init ( epggrab_module_list_t *list )
|
||||
{
|
||||
return "pyepg";
|
||||
}
|
||||
|
||||
static htsmsg_t* _pyepg_grab ( const char *iopts )
|
||||
{
|
||||
int i, outlen;
|
||||
char *outbuf;
|
||||
char errbuf[100];
|
||||
const char *argv[32]; // 32 args max!
|
||||
char *toksave, *tok;
|
||||
char *opts = NULL;
|
||||
htsmsg_t *ret;
|
||||
|
||||
/* TODO: do something (much) better! */
|
||||
if (iopts) opts = strdup(iopts);
|
||||
i = 1;
|
||||
argv[0] = "/usr/bin/pyepg";
|
||||
if ( opts ) {
|
||||
tok = strtok_r(opts, " ", &toksave);
|
||||
while ( tok != NULL ) {
|
||||
argv[i++] = tok;
|
||||
tok = strtok_r(NULL, " ", &toksave);
|
||||
}
|
||||
}
|
||||
argv[i] = NULL;
|
||||
|
||||
/* Debug */
|
||||
tvhlog(LOG_DEBUG, "pyepg", "grab %s %s", argv[0], iopts ? iopts : "");
|
||||
|
||||
/* Grab */
|
||||
#if 0
|
||||
outlen = spawn_and_store_stdout(argv[0], (char *const*)argv, &outbuf);
|
||||
#else
|
||||
outlen = spawn_and_store_stdout("/home/aps/tmp/epg.sh", NULL, &outbuf);
|
||||
#endif
|
||||
free(opts);
|
||||
if ( outlen < 1 ) {
|
||||
tvhlog(LOG_ERR, "pyepg", "no output detected");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Extract */
|
||||
ret = htsmsg_xml_deserialize(outbuf, errbuf, sizeof(errbuf));
|
||||
if (!ret)
|
||||
tvhlog(LOG_ERR, "pyepg", "htsmsg_xml_deserialize error %s", errbuf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
epggrab_module_t* pyepg_init ( void )
|
||||
{
|
||||
pyepg_module.enable = NULL;
|
||||
pyepg_module.disable = NULL;
|
||||
pyepg_module.grab = _pyepg_grab;
|
||||
pyepg_module.parse = _pyepg_parse;
|
||||
pyepg_module.name = _pyepg_name;
|
||||
pyepg_module.ch_add = _pyepg_channel_add;
|
||||
pyepg_module.ch_rem = _pyepg_channel_rem;
|
||||
pyepg_module.ch_mod = _pyepg_channel_mod;
|
||||
return &pyepg_module;
|
||||
_pyepg_module.id = strdup("pyepg");
|
||||
_pyepg_module.path = strdup("/usr/bin/pyepg");
|
||||
_pyepg_module.name = strdup("PyEPG");
|
||||
*((uint8_t*)&_pyepg_module.flags) = EPGGRAB_MODULE_SYNC
|
||||
| EPGGRAB_MODULE_ASYNC
|
||||
| EPGGRAB_MODULE_ADVANCED
|
||||
| EPGGRAB_MODULE_SIMPLE
|
||||
| EPGGRAB_MODULE_EXTERNAL;
|
||||
_pyepg_module.enable = epggrab_module_enable_socket;
|
||||
_pyepg_module.grab = epggrab_module_grab;
|
||||
_pyepg_module.parse = _pyepg_parse;
|
||||
_pyepg_module.channels = &_pyepg_channels;
|
||||
_pyepg_module.ch_save = _pyepg_save;
|
||||
_pyepg_module.ch_add = epggrab_module_channel_add;
|
||||
_pyepg_module.ch_rem = epggrab_module_channel_rem;
|
||||
_pyepg_module.ch_mod = epggrab_module_channel_mod;
|
||||
|
||||
/* Add to list */
|
||||
LIST_INSERT_HEAD(list, &_pyepg_module, link);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,6 @@
|
|||
|
||||
#include "epggrab.h"
|
||||
|
||||
epggrab_module_t* pyepg_init ( void );
|
||||
void pyepg_init ( epggrab_module_list_t *list );
|
||||
|
||||
#endif
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
#include "xmltv.h"
|
||||
#include "spawn.h"
|
||||
|
||||
#define XMLTV_FIND_GRABBERS "/usr/bin/tv_find_grabbers"
|
||||
|
||||
/* **************************************************************************
|
||||
* Parsing
|
||||
* *************************************************************************/
|
||||
|
@ -365,12 +367,23 @@ _xmltv_parse_tv(htsmsg_t *body, epggrab_stats_t *stats)
|
|||
}
|
||||
return save;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int _xmltv_parse ( htsmsg_t *data, epggrab_stats_t *stats )
|
||||
/* ************************************************************************
|
||||
* Module Setup
|
||||
* ***********************************************************************/
|
||||
|
||||
epggrab_channel_tree_t _xmltv_channels;
|
||||
|
||||
static void _xmltv_save ( epggrab_module_t *mod )
|
||||
{
|
||||
epggrab_module_channels_save(mod, "epggrab/xmltv/channels");
|
||||
}
|
||||
|
||||
static int _xmltv_parse
|
||||
( epggrab_module_t *mod, htsmsg_t *data, epggrab_stats_t *stats )
|
||||
{
|
||||
#if 0
|
||||
htsmsg_t *tags, *tv;
|
||||
|
||||
if((tags = htsmsg_get_map(data, "tags")) == NULL)
|
||||
|
@ -380,51 +393,73 @@ static int _xmltv_parse ( htsmsg_t *data, epggrab_stats_t *stats )
|
|||
return 0;
|
||||
|
||||
return _xmltv_parse_tv(tv, stats);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ************************************************************************
|
||||
* Module Setup
|
||||
* ***********************************************************************/
|
||||
|
||||
// TODO: config
|
||||
// TODO: remove use of hardcoded xmltv script
|
||||
|
||||
static epggrab_module_t xmltv_module;
|
||||
|
||||
static const char* _xmltv_name ( void )
|
||||
static void _xmltv_load_grabbers ( epggrab_module_list_t *list )
|
||||
{
|
||||
return "xmltv";
|
||||
}
|
||||
|
||||
static htsmsg_t* _xmltv_grab ( const char *iopts )
|
||||
{
|
||||
size_t outlen;
|
||||
size_t i, outlen, p, n;
|
||||
char *outbuf;
|
||||
char errbuf[100];
|
||||
const char *cmd = "/home/aps/tmp/epg.sh";//usr/bin/tv_grab_uk_rt";
|
||||
epggrab_module_t *mod;
|
||||
|
||||
/* Debug */
|
||||
tvhlog(LOG_DEBUG, "xmltv", "grab %s", cmd);
|
||||
|
||||
/* Grab */
|
||||
outlen = spawn_and_store_stdout(cmd, NULL, &outbuf);
|
||||
/* Load data */
|
||||
outlen = spawn_and_store_stdout(XMLTV_FIND_GRABBERS, NULL, &outbuf);
|
||||
if ( outlen < 1 ) {
|
||||
tvhlog(LOG_ERR, "xmltv", "no output detected");
|
||||
return NULL;
|
||||
tvhlog(LOG_ERR, "xmltv", "%s failed [%s]", XMLTV_FIND_GRABBERS, errbuf);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Extract */
|
||||
return htsmsg_xml_deserialize(outbuf, errbuf, sizeof(errbuf));
|
||||
/* Process */
|
||||
p = n = i = 0;
|
||||
while ( i < outlen ) {
|
||||
if ( outbuf[i] == '\n' || outbuf[i] == '\0' ) {
|
||||
outbuf[i] = '\0';
|
||||
mod = calloc(1, sizeof(epggrab_module_t));
|
||||
mod->id = mod->path = strdup(&outbuf[p]);
|
||||
mod->name = malloc(200);
|
||||
sprintf((char*)mod->name, "XMLTV: %s", &outbuf[n]);
|
||||
*((uint8_t*)&mod->flags) = EPGGRAB_MODULE_SYNC
|
||||
| EPGGRAB_MODULE_SIMPLE;
|
||||
mod->parse = _xmltv_parse;
|
||||
mod->ch_add = epggrab_module_channel_add;
|
||||
mod->ch_rem = epggrab_module_channel_rem;
|
||||
mod->ch_mod = epggrab_module_channel_mod;
|
||||
mod->ch_save = _xmltv_save;
|
||||
LIST_INSERT_HEAD(list, mod, link);
|
||||
p = n = i + 1;
|
||||
} else if ( outbuf[i] == '|' ) {
|
||||
outbuf[i] = '\0';
|
||||
n = i + 1;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
free(outbuf);
|
||||
}
|
||||
|
||||
epggrab_module_t* xmltv_init ( void )
|
||||
void xmltv_init ( epggrab_module_list_t *list )
|
||||
{
|
||||
xmltv_module.enable = NULL;
|
||||
xmltv_module.disable = NULL;
|
||||
xmltv_module.grab = _xmltv_grab;
|
||||
xmltv_module.parse = _xmltv_parse;
|
||||
xmltv_module.name = _xmltv_name;
|
||||
return &xmltv_module;
|
||||
}
|
||||
epggrab_module_t *mod;
|
||||
|
||||
#endif
|
||||
/* Advanced module */
|
||||
mod = calloc(1, sizeof(epggrab_module_t));
|
||||
mod->id = strdup("xmltv");
|
||||
mod->name = strdup("XMLTV: Advanced");
|
||||
*((uint8_t*)&mod->flags) = EPGGRAB_MODULE_ASYNC
|
||||
| EPGGRAB_MODULE_SYNC
|
||||
| EPGGRAB_MODULE_ADVANCED
|
||||
| EPGGRAB_MODULE_EXTERNAL;
|
||||
mod->enable = epggrab_module_enable_socket;
|
||||
mod->grab = epggrab_module_grab;
|
||||
mod->parse = _xmltv_parse;
|
||||
mod->channels = &_xmltv_channels;
|
||||
mod->ch_add = epggrab_module_channel_add;
|
||||
mod->ch_rem = epggrab_module_channel_rem;
|
||||
mod->ch_mod = epggrab_module_channel_mod;
|
||||
mod->ch_save = _xmltv_save;
|
||||
LIST_INSERT_HEAD(list, mod, link);
|
||||
|
||||
/* Standard modules */
|
||||
_xmltv_load_grabbers(list);
|
||||
}
|
||||
|
|
|
@ -19,8 +19,9 @@
|
|||
#ifndef EPGGRAB_XMLTV_H
|
||||
#define EPGGRAB_XMLTV_H
|
||||
|
||||
#include "htsmsg.h"
|
||||
#include "epggrab.h"
|
||||
|
||||
epggrab_module_t *xmltv_init ( void );
|
||||
void xmltv_init ( epggrab_module_list_t *list );
|
||||
|
||||
#endif
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#include "epggrab.h"
|
||||
#include "epg.h"
|
||||
#include "iptv_input.h"
|
||||
#include "epggrab/xmltv.h"
|
||||
|
||||
static void
|
||||
extjs_load(htsbuf_queue_t *hq, const char *script)
|
||||
|
@ -322,11 +323,6 @@ extjs_channels_update(htsmsg_t *in)
|
|||
if((s = htsmsg_get_str(c, "name")) != NULL)
|
||||
channel_rename(ch, s);
|
||||
|
||||
#if TODO_XMLTV_REMOVE_THIS
|
||||
if((s = htsmsg_get_str(c, "xmltvsrc")) != NULL)
|
||||
channel_set_xmltv_source(ch, xmltv_channel_find_by_displayname(s));
|
||||
#endif
|
||||
|
||||
if((s = htsmsg_get_str(c, "ch_icon")) != NULL)
|
||||
channel_set_icon(ch, s);
|
||||
|
||||
|
@ -376,11 +372,6 @@ extjs_channels(http_connection_t *hc, const char *remain, void *opaque)
|
|||
htsmsg_add_str(c, "name", ch->ch_name);
|
||||
htsmsg_add_u32(c, "chid", ch->ch_id);
|
||||
|
||||
#if TODO_XMLTV_REMOVE_THIS
|
||||
if(ch->ch_xc != NULL)
|
||||
htsmsg_add_str(c, "xmltvsrc", ch->ch_xc->xc_displayname);
|
||||
#endif
|
||||
|
||||
if(ch->ch_icon != NULL)
|
||||
htsmsg_add_str(c, "ch_icon", ch->ch_icon);
|
||||
|
||||
|
@ -476,12 +467,10 @@ json_single_record(htsmsg_t *rec, const char *root)
|
|||
static int
|
||||
extjs_epggrab(http_connection_t *hc, const char *remain, void *opaque)
|
||||
{
|
||||
#if TODO_EPGGRAB_CONFIG
|
||||
htsbuf_queue_t *hq = &hc->hc_reply;
|
||||
const char *op = http_arg_get(&hc->hc_req_args, "op");
|
||||
xmltv_channel_t *xc;
|
||||
htsmsg_t *out, *array, *e, *r;
|
||||
const char *s;
|
||||
htsmsg_t *out, *array, *r;
|
||||
const char *str;
|
||||
|
||||
if(op == NULL)
|
||||
return 400;
|
||||
|
@ -495,67 +484,63 @@ extjs_epggrab(http_connection_t *hc, const char *remain, void *opaque)
|
|||
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
|
||||
if(!strcmp(op, "listChannels")) {
|
||||
/* Basic settings (not the advanced schedule) */
|
||||
if(!strcmp(op, "loadSettings")) {
|
||||
|
||||
pthread_mutex_lock(&epggrab_mutex);
|
||||
r = htsmsg_create_map();
|
||||
htsmsg_add_u32(r, "eitenabled", epggrab_eitenabled);
|
||||
htsmsg_add_u32(r, "advanced", epggrab_advanced);
|
||||
if (epggrab_module)
|
||||
htsmsg_add_str(r, "module", epggrab_module->id);
|
||||
htsmsg_add_u32(r, "interval", epggrab_interval);
|
||||
pthread_mutex_unlock(&epggrab_mutex);
|
||||
|
||||
out = json_single_record(r, "epggrabSettings");
|
||||
|
||||
/* List of modules */
|
||||
} else if (!strcmp(op, "moduleList")) {
|
||||
out = htsmsg_create_map();
|
||||
array = htsmsg_create_list();
|
||||
|
||||
e = htsmsg_create_map();
|
||||
htsmsg_add_str(e, "xcTitle", "None");
|
||||
htsmsg_add_msg(array, NULL, e);
|
||||
|
||||
pthread_mutex_lock(&global_lock);
|
||||
LIST_FOREACH(xc, &xmltv_displaylist, xc_displayname_link) {
|
||||
e = htsmsg_create_map();
|
||||
htsmsg_add_str(e, "xcTitle", xc->xc_displayname);
|
||||
htsmsg_add_msg(array, NULL, e);
|
||||
}
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
|
||||
pthread_mutex_lock(&epggrab_mutex);
|
||||
array = epggrab_module_list();
|
||||
pthread_mutex_unlock(&epggrab_mutex);
|
||||
htsmsg_add_msg(out, "entries", array);
|
||||
|
||||
} else if(!strcmp(op, "loadSettings")) {
|
||||
|
||||
pthread_mutex_lock(&xmltv_mutex);
|
||||
r = htsmsg_create_map();
|
||||
|
||||
if((s = xmltv_get_current_grabber()) != NULL)
|
||||
htsmsg_add_str(r, "grabber", s);
|
||||
|
||||
htsmsg_add_u32(r, "grabinterval", xmltv_grab_interval);
|
||||
htsmsg_add_u32(r, "grabenable", xmltv_grab_enabled);
|
||||
pthread_mutex_unlock(&xmltv_mutex);
|
||||
|
||||
out = json_single_record(r, "xmltvSettings");
|
||||
|
||||
} else if(!strcmp(op, "saveSettings")) {
|
||||
|
||||
pthread_mutex_lock(&xmltv_mutex);
|
||||
|
||||
s = http_arg_get(&hc->hc_req_args, "grabber");
|
||||
xmltv_set_current_grabber(s);
|
||||
|
||||
s = http_arg_get(&hc->hc_req_args, "grabinterval");
|
||||
xmltv_set_grab_interval(atoi(s));
|
||||
|
||||
s = http_arg_get(&hc->hc_req_args, "grabenable");
|
||||
xmltv_set_grab_enable(!!s);
|
||||
|
||||
pthread_mutex_unlock(&xmltv_mutex);
|
||||
/* Advanced schedule */
|
||||
} else if (!strcmp(op, "loadSchedule")) {
|
||||
out = htsmsg_create_map();
|
||||
pthread_mutex_lock(&epggrab_mutex);
|
||||
array = epggrab_get_schedule();
|
||||
pthread_mutex_unlock(&epggrab_mutex);
|
||||
htsmsg_add_msg(out, "entries", array);
|
||||
|
||||
/* Save settings */
|
||||
} else if (!strcmp(op, "saveSettings") ) {
|
||||
int save = 0;
|
||||
pthread_mutex_lock(&epggrab_mutex);
|
||||
if ( http_arg_get(&hc->hc_req_args, "advanced") )
|
||||
save |= epggrab_set_advanced(1);
|
||||
else
|
||||
save |= epggrab_set_advanced(0);
|
||||
if ( http_arg_get(&hc->hc_req_args, "eitenabled") )
|
||||
save |= epggrab_set_eitenabled(1);
|
||||
else
|
||||
save |= epggrab_set_eitenabled(0);
|
||||
if ( (str = http_arg_get(&hc->hc_req_args, "interval")) )
|
||||
save |= epggrab_set_interval(atoi(str));
|
||||
if ( (str = http_arg_get(&hc->hc_req_args, "module")) )
|
||||
save |= epggrab_set_module_by_id(str);
|
||||
if ( (str = http_arg_get(&hc->hc_req_args, "schedule")) ) {
|
||||
if ( (array = htsmsg_json_deserialize(str)) ) {
|
||||
save |= epggrab_set_schedule(array);
|
||||
htsmsg_destroy(array);
|
||||
}
|
||||
}
|
||||
if (save) epggrab_save();
|
||||
pthread_mutex_unlock(&epggrab_mutex);
|
||||
out = htsmsg_create_map();
|
||||
htsmsg_add_u32(out, "success", 1);
|
||||
|
||||
} else if(!strcmp(op, "listGrabbers")) {
|
||||
|
||||
out = htsmsg_create_map();
|
||||
|
||||
pthread_mutex_lock(&xmltv_mutex);
|
||||
array = xmltv_list_grabbers();
|
||||
pthread_mutex_unlock(&xmltv_mutex);
|
||||
if(array != NULL)
|
||||
htsmsg_add_msg(out, "entries", array);
|
||||
|
||||
} else {
|
||||
return HTTP_STATUS_BAD_REQUEST;
|
||||
}
|
||||
|
@ -564,7 +549,6 @@ extjs_epggrab(http_connection_t *hc, const char *remain, void *opaque)
|
|||
htsmsg_destroy(out);
|
||||
http_output_content(hc, "text/x-json; charset=UTF-8");
|
||||
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -722,6 +706,8 @@ extjs_epg(http_connection_t *hc, const char *remain, void *opaque)
|
|||
|
||||
if(ee->title != NULL)
|
||||
htsmsg_add_str(m, "title", ee->title);
|
||||
if(ee->subtitle)
|
||||
htsmsg_add_str(m, "subtitle", ee->subtitle);
|
||||
|
||||
if(ee->description != NULL)
|
||||
htsmsg_add_str(m, "description", ee->description);
|
||||
|
|
|
@ -14,7 +14,9 @@ tvheadend.epgDetails = function(event) {
|
|||
if(event.chicon != null && event.chicon.length > 0)
|
||||
content += '<img class="x-epg-chicon" src="' + event.chicon + '">';
|
||||
|
||||
content += '<div class="x-epg-title">' + event.title + '</div>';
|
||||
content += '<div class="x-epg-title">' + event.title;
|
||||
if (event.subtitle) content += " : " + event.subtitle;
|
||||
content += '</div>';
|
||||
content += '<div class="x-epg-desc">' + event.episode + '</div>';
|
||||
content += '<div class="x-epg-desc">' + event.description + '</div>';
|
||||
|
||||
|
@ -120,7 +122,7 @@ tvheadend.epgDetails = function(event) {
|
|||
html += '<div class="x-epg-desc">';
|
||||
if (ee.episode) html += ee.episode + ' ';
|
||||
html += ee.title;
|
||||
if (ee.subtitle) html += ':' + ee.subtitle
|
||||
if (ee.subtitle) html += ' : ' + ee.subtitle
|
||||
html += '</div>';
|
||||
}
|
||||
}
|
||||
|
@ -180,11 +182,9 @@ tvheadend.epg = function() {
|
|||
{name: 'channel'},
|
||||
{name: 'channelid'},
|
||||
{name: 'title'},
|
||||
{name: 'subtitle'},
|
||||
{name: 'episode'},
|
||||
{name: 'description'},
|
||||
{name: 'ext_desc'},
|
||||
{name: 'ext_item'},
|
||||
{name: 'ext_text'},
|
||||
{name: 'chicon'},
|
||||
{name: 'start', type: 'date', dateFormat: 'U' /* unix time */},
|
||||
{name: 'end', type: 'date', dateFormat: 'U' /* unix time */},
|
||||
|
@ -242,6 +242,12 @@ tvheadend.epg = function() {
|
|||
header: "Title",
|
||||
dataIndex: 'title',
|
||||
renderer: renderText
|
||||
},{
|
||||
width: 250,
|
||||
id:'subtitle',
|
||||
header: "SubTitle",
|
||||
dataIndex: 'subtitle',
|
||||
renderer: renderText
|
||||
},{
|
||||
width: 100,
|
||||
id:'episode',
|
||||
|
|
|
@ -1,194 +1,422 @@
|
|||
tvheadend.grabberStore = new Ext.data.JsonStore({
|
||||
root:'entries',
|
||||
fields: ['identifier','name','version','apiconfig'],
|
||||
url:'xmltv',
|
||||
baseParams: {
|
||||
op: 'listGrabbers'
|
||||
}
|
||||
});
|
||||
|
||||
tvheadend.epggrab = function() {
|
||||
|
||||
/* ****************************************************************
|
||||
* Data
|
||||
* ***************************************************************/
|
||||
|
||||
/*
|
||||
* Module lists (I'm sure there is a better way!)
|
||||
*/
|
||||
var EPGGRAB_MODULE_SIMPLE = 0x04;
|
||||
var EPGGRAB_MODULE_ADVANCED = 0x08;
|
||||
var EPGGRAB_MODULE_EXTERNAL = 0x10;
|
||||
|
||||
var moduleStore = new Ext.data.JsonStore({
|
||||
root : 'entries',
|
||||
url : 'epggrab',
|
||||
baseParams : { op : 'moduleList' },
|
||||
autoLoad : true,
|
||||
fields : [ 'id', 'name', 'path', 'flags' ]
|
||||
});
|
||||
var simpleModuleStore = new Ext.data.Store({
|
||||
recordType: moduleStore.recordType
|
||||
});
|
||||
var advancedModuleStore = new Ext.data.Store({
|
||||
recordType: moduleStore.recordType
|
||||
});
|
||||
var externalModuleStore = new Ext.data.Store({
|
||||
recordType: moduleStore.recordType
|
||||
});
|
||||
moduleStore.on('load', function() {
|
||||
moduleStore.filterBy(function(r) {
|
||||
return r.get('flags') & EPGGRAB_MODULE_SIMPLE;
|
||||
});
|
||||
moduleStore.each(function(r) {
|
||||
simpleModuleStore.add(r.copy());
|
||||
});
|
||||
moduleStore.filterBy(function(r) {
|
||||
return r.get('flags') & (EPGGRAB_MODULE_ADVANCED | EPGGRAB_MODULE_SIMPLE);
|
||||
});
|
||||
moduleStore.each(function(r) {
|
||||
advancedModuleStore.add(r.copy());
|
||||
});
|
||||
moduleStore.filterBy(function(r) {
|
||||
return r.get('flags') & EPGGRAB_MODULE_EXTERNAL;
|
||||
});
|
||||
moduleStore.each(function(r) {
|
||||
externalModuleStore.add(r.copy());
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* Schedule
|
||||
*/
|
||||
|
||||
var scheduleRow = Ext.data.Record.create(
|
||||
[
|
||||
{ name : 'module' },
|
||||
{ name : 'command' },
|
||||
{ name : 'options' },
|
||||
{ name : 'cron' }
|
||||
]
|
||||
);
|
||||
|
||||
var scheduleStore = new Ext.data.JsonStore({
|
||||
root : 'entries',
|
||||
url : 'epggrab',
|
||||
baseParams : { op : 'loadSchedule' },
|
||||
autoLoad : true,
|
||||
fields : [ 'module', 'command', 'options', 'cron' ]
|
||||
});
|
||||
|
||||
/*
|
||||
* Basic Config
|
||||
*/
|
||||
|
||||
var confreader = new Ext.data.JsonReader(
|
||||
{ root: 'epggrabSettings' },
|
||||
[ 'module', 'eitenable', 'advanced' ]
|
||||
[ 'module', 'eitenabled', 'advanced', 'interval' ]
|
||||
);
|
||||
|
||||
var grabberSelect = new Ext.form.ComboBox({
|
||||
loadingText: 'Loading, please wait...',
|
||||
fieldLabel: 'XML-TV Source',
|
||||
name: 'grabber',
|
||||
width: 350,
|
||||
displayField:'name',
|
||||
valueField:'identifier',
|
||||
store: tvheadend.grabberStore,
|
||||
forceSelection: true,
|
||||
editable: false,
|
||||
triggerAction: 'all',
|
||||
mode: 'remote',
|
||||
emptyText: 'Select grabber'
|
||||
});
|
||||
grabberSelect.setVisible(false);
|
||||
/* ****************************************************************
|
||||
* Simple Config
|
||||
* ***************************************************************/
|
||||
|
||||
var advancedCheck = new Ext.form.Checkbox({
|
||||
fieldLabel : 'Advanced Config',
|
||||
name : 'advanced',
|
||||
});
|
||||
|
||||
var moduleSelect = new Ext.form.ComboBox({
|
||||
fieldLabel : 'Grab Module',
|
||||
name : 'module',
|
||||
width : 150,
|
||||
/*
|
||||
* Module selector
|
||||
*/
|
||||
var simpleModule = new Ext.form.ComboBox({
|
||||
fieldLabel : 'Module',
|
||||
hiddenName : 'module',
|
||||
width : 300,
|
||||
valueField : 'id',
|
||||
displayField : 'name',
|
||||
forceSelection : true,
|
||||
editable : false,
|
||||
mode : 'local',
|
||||
triggerAction : 'all',
|
||||
store : simpleModuleStore
|
||||
});
|
||||
|
||||
/*
|
||||
* Interval selector
|
||||
*/
|
||||
var intervalUnits = [
|
||||
[ 86400, 'Days' ],
|
||||
[ 3600, 'Hours' ],
|
||||
[ 60, 'Minutes' ],
|
||||
[ 1, 'Seconds' ]
|
||||
];
|
||||
var intervalValue = new Ext.form.NumberField({
|
||||
width : 300,
|
||||
allowNegative : false,
|
||||
allowDecimals : false,
|
||||
minValue : 1,
|
||||
maxValue : 7,
|
||||
value : 1,
|
||||
fieldLabel : 'Grab interval',
|
||||
name : 'intervalValue',
|
||||
listeners : {
|
||||
'valid' : function (e) {
|
||||
v = e.getValue() * intervalUnit.getValue();
|
||||
interval.setValue(v);
|
||||
}
|
||||
}
|
||||
})
|
||||
var intervalUnit = new Ext.form.ComboBox({
|
||||
name : 'intervalUnit',
|
||||
width : 300,
|
||||
valueField : 'key',
|
||||
displayField : 'value',
|
||||
value : 86400,
|
||||
forceSelection : true,
|
||||
editable : false,
|
||||
triggerAction : 'all',
|
||||
mode : 'local',
|
||||
store : new Ext.data.SimpleStore({
|
||||
fields : [ 'key', 'value' ],
|
||||
data : [
|
||||
[ 'xmltv', 'XMLTV' ],
|
||||
[ 'pyepg', 'PyEPG' ],
|
||||
]
|
||||
})
|
||||
fields : [ 'key', 'value' ],
|
||||
data : intervalUnits
|
||||
}),
|
||||
listeners : {
|
||||
'change' : function (e, n, o) {
|
||||
intervalValue.maxValue = (7 * 86400) / n;
|
||||
intervalValue.validate();
|
||||
}
|
||||
}
|
||||
});
|
||||
var interval = new Ext.form.Hidden({
|
||||
name : 'interval',
|
||||
value : 86400,
|
||||
listeners : {
|
||||
'enable' : function (e) {
|
||||
v = e.getValue();
|
||||
for ( i = 0; i < intervalUnits.length; i++ ) {
|
||||
u = intervalUnits[i][0];
|
||||
if ( (v % u) == 0 ) {
|
||||
intervalUnit.setValue(u);
|
||||
intervalValue.maxValue = (7 * 86400) / u;
|
||||
intervalValue.setValue(v / u);
|
||||
intervalValue.validate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var confpanel = new Ext.FormPanel(
|
||||
{
|
||||
title : 'EPG Grabber',
|
||||
iconCls : 'xml',
|
||||
border : false,
|
||||
bodyStyle : 'padding:15px',
|
||||
labelAlign : 'right',
|
||||
labelWidth : 200,
|
||||
waitMsgTarget : true,
|
||||
reader : confreader,
|
||||
layout : 'form',
|
||||
defaultType : 'textfield',
|
||||
items : [
|
||||
moduleSelect,
|
||||
new Ext.form.Checkbox({
|
||||
fieldLabel : 'Enable EIT',
|
||||
name : 'eitenable'
|
||||
}),
|
||||
advancedCheck,
|
||||
grabberSelect
|
||||
],
|
||||
tbar: [
|
||||
{
|
||||
tooltip: 'Save changes made to configuration below',
|
||||
iconCls:'save',
|
||||
text: "Save configuration",
|
||||
handler: saveChanges
|
||||
},
|
||||
'->',
|
||||
{
|
||||
text: 'Help',
|
||||
handler: function() {
|
||||
new tvheadend.help('XMLTV configuration', 'config_xmltv.html');
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
var simplePanel = new Ext.form.FieldSet({
|
||||
title : 'Simple Config',
|
||||
height : 120,
|
||||
width : 900,
|
||||
items : [
|
||||
simpleModule,
|
||||
intervalValue,
|
||||
intervalUnit
|
||||
]
|
||||
});
|
||||
|
||||
/*
|
||||
* Event handlers
|
||||
/* ****************************************************************
|
||||
* Advanced Config
|
||||
* ***************************************************************/
|
||||
|
||||
/*
|
||||
* Schedule
|
||||
*/
|
||||
advancedCheck.on('enable', function(e) {
|
||||
Ext.MessageBox.alert('Test', 'testing');
|
||||
});
|
||||
|
||||
moduleSelect.on('select', function(c,r,i) {
|
||||
alert('select module');
|
||||
});
|
||||
|
||||
confpanel.on('render', function() {
|
||||
confpanel.getForm().load({
|
||||
url:'xmltv',
|
||||
params:{'op':'loadSettings'},
|
||||
success:function(form, action) {
|
||||
confpanel.enable();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
grabberSelect.on('select', function(c,r,i) {
|
||||
|
||||
Ext.MessageBox.alert('XMLTV',
|
||||
'Make sure that the grabber is properly ' +
|
||||
'configured before saving configuration.<br>'+
|
||||
'<br>' +
|
||||
'To configure manually execute the ' +
|
||||
'following command in a shell on the ' +
|
||||
'server:<br>' +
|
||||
'$ ' + r.data.identifier +
|
||||
' --configure<br>' +
|
||||
'<br>' +
|
||||
'Note: It is important to configure the ' +
|
||||
'grabber using the same userid as tvheadend '+
|
||||
'since most grabbers save their '+
|
||||
'configuration in the users home directory.'+
|
||||
'<br>' +
|
||||
'<br>' +
|
||||
'Grabber version: ' + r.data.version
|
||||
);
|
||||
|
||||
/*
|
||||
if(r.data.apiconfig) {
|
||||
|
||||
Ext.MessageBox.confirm('XMLTV',
|
||||
'Configure grabber? ' +
|
||||
'If you know that the grabber is already '+
|
||||
'set up or if you want to configure it '+
|
||||
'manually you may skip this step',
|
||||
function(button) {
|
||||
Ext.MessageBox.alert('XMLTV',
|
||||
'oops, embedded '+
|
||||
'config not '+
|
||||
'implemeted yet');
|
||||
}
|
||||
);
|
||||
|
||||
} else {
|
||||
Ext.MessageBox.alert('XMLTV',
|
||||
'This grabber does not support being ' +
|
||||
'configured from external application ' +
|
||||
'(such as Tvheadend).<br>' +
|
||||
'Make sure that the grabber is properly ' +
|
||||
'configured before saving configuration.<br>'+
|
||||
'<br>' +
|
||||
'To configure manually execute the ' +
|
||||
'following command in a shell on the ' +
|
||||
'server:<br>' +
|
||||
'$ ' + r.data.identifier +
|
||||
' --configure<br>' +
|
||||
'<br>' +
|
||||
'Note: It is important to configure the ' +
|
||||
'grabber using the same userid as tvheadend '+
|
||||
'since most grabbers save their '+
|
||||
'configuration in the users home directory.'+
|
||||
'<br>' +
|
||||
'<br>' +
|
||||
'Grabber version: ' + r.data.version
|
||||
);
|
||||
}
|
||||
*/
|
||||
});
|
||||
|
||||
function saveChanges() {
|
||||
confpanel.getForm().submit({
|
||||
url:'xmltv',
|
||||
params:{'op':'saveSettings'},
|
||||
waitMsg:'Saving Data...',
|
||||
failure: function(form, action) {
|
||||
Ext.Msg.alert('Save failed', action.result.errormsg);
|
||||
}
|
||||
});
|
||||
var scheduleColumnModel = new Ext.grid.ColumnModel([
|
||||
{
|
||||
header : 'Module',
|
||||
dataIndex : 'module',
|
||||
width : 300,
|
||||
sortable : false,
|
||||
renderer : function (v)
|
||||
{
|
||||
if ( v != "" ) {
|
||||
i = advancedModuleStore.find('id', v);
|
||||
v = advancedModuleStore.getAt(i).get('name');
|
||||
}
|
||||
return v;
|
||||
},
|
||||
editor : new Ext.form.ComboBox({
|
||||
valueField : 'id',
|
||||
displayField : 'name',
|
||||
editable : false,
|
||||
mode : 'local',
|
||||
triggerAction : 'all',
|
||||
store : advancedModuleStore
|
||||
}),
|
||||
},
|
||||
{
|
||||
header : 'Cron',
|
||||
dataIndex : 'cron',
|
||||
width : 150,
|
||||
sortable : false,
|
||||
editor : new Ext.form.TextField()
|
||||
},
|
||||
{
|
||||
header : 'Command',
|
||||
dataIndex : 'command',
|
||||
width : 200,
|
||||
sortable : false,
|
||||
editor : new Ext.form.TextField()
|
||||
},
|
||||
{
|
||||
dataIndex : 'options',
|
||||
header : 'Options',
|
||||
width : 200,
|
||||
sortable : false,
|
||||
editor : new Ext.form.TextField()
|
||||
}
|
||||
]);
|
||||
scheduleColumnModel.isCellEditable = function (ci, ri)
|
||||
{
|
||||
if (ci == 0) return true;
|
||||
m = scheduleStore.getAt(ri).get('module');
|
||||
if (m == "") return false;
|
||||
m = advancedModuleStore.find('id', m);
|
||||
m = advancedModuleStore.getAt(m);
|
||||
if (!(m.get('flags') & EPGGRAB_MODULE_ADVANCED)) {
|
||||
return (ci == 1);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
return confpanel;
|
||||
scheduleSelectModel = new Ext.grid.RowSelectionModel({
|
||||
singleSelect : false,
|
||||
});
|
||||
scheduleSelectModel.on('selectionchange', function(s) {
|
||||
delButton.setDisabled(s.getCount() == 0);
|
||||
});
|
||||
|
||||
var addButton = new Ext.Button({
|
||||
text : 'Add',
|
||||
iconCls : 'add',
|
||||
handler : function () {
|
||||
scheduleStore.add(new scheduleRow({
|
||||
module : '',
|
||||
cron : '',
|
||||
command : '',
|
||||
options : ''}
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
var delButton = new Ext.Button({
|
||||
text : 'Delete',
|
||||
iconCls : 'remove',
|
||||
disabled : true,
|
||||
handler : function () {
|
||||
var s = schedulePanel.getSelectionModel().each(function(r){
|
||||
scheduleStore.remove(r);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var schedulePanel = new Ext.grid.EditorGridPanel({
|
||||
store : scheduleStore,
|
||||
cm : scheduleColumnModel,
|
||||
sm : scheduleSelectModel,
|
||||
width : 850,
|
||||
height : 150,
|
||||
frame : true,
|
||||
viewConfig : {
|
||||
forceFit : true,
|
||||
markDirty : false
|
||||
},
|
||||
iconCls : 'icon-grid',
|
||||
tbar : [
|
||||
addButton,
|
||||
delButton
|
||||
],
|
||||
listeners : {
|
||||
'afteredit' : function (r) {
|
||||
if ( r.field == 'module' ) {
|
||||
d = scheduleStore.getAt(r.row);
|
||||
c = '';
|
||||
if ( r.value != "" ) {
|
||||
i = advancedModuleStore.find('id', r.value);
|
||||
m = advancedModuleStore.getAt(i);
|
||||
c = m.get('path');
|
||||
}
|
||||
d.set('command', c)
|
||||
}
|
||||
},
|
||||
'select' : function(r) {
|
||||
delButton.setDisabled(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var advancedPanel = new Ext.form.FieldSet({
|
||||
title : 'Advanced Config',
|
||||
height : 200,
|
||||
width : 900,
|
||||
items : [
|
||||
// TODO: external editors
|
||||
schedulePanel
|
||||
]
|
||||
});
|
||||
|
||||
/* ****************************************************************
|
||||
* Form
|
||||
* ***************************************************************/
|
||||
|
||||
var advancedCheck = new Ext.form.Checkbox({
|
||||
fieldLabel : 'Advanced Config',
|
||||
name : 'advanced',
|
||||
listeners : {
|
||||
'check' : function (e, v) {
|
||||
simplePanel.setVisible(!v);
|
||||
advancedPanel.setVisible(v);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var eitCheck = new Ext.form.Checkbox({
|
||||
fieldLabel : 'EIT Enabled',
|
||||
name : 'eitenabled'
|
||||
});
|
||||
|
||||
var saveButton = new Ext.Button({
|
||||
text : "Save configuration",
|
||||
tooltip : 'Save changes made to configuration below',
|
||||
iconCls :'save',
|
||||
handler : saveChanges,
|
||||
});
|
||||
|
||||
var helpButton = new Ext.Button({
|
||||
text : 'Help',
|
||||
handler : function() {
|
||||
alert('TODO: help info');
|
||||
}
|
||||
});
|
||||
|
||||
var confpanel = new Ext.FormPanel({
|
||||
title : 'EPG Grabber',
|
||||
iconCls : 'xml',
|
||||
border : false,
|
||||
bodyStyle : 'padding:15px',
|
||||
labelAlign : 'left',
|
||||
labelWidth : 150,
|
||||
waitMsgTarget : true,
|
||||
reader : confreader,
|
||||
layout : 'form',
|
||||
defaultType : 'textfield',
|
||||
items : [
|
||||
interval,
|
||||
advancedCheck,
|
||||
eitCheck,
|
||||
simplePanel,
|
||||
advancedPanel
|
||||
],
|
||||
tbar: [
|
||||
saveButton,
|
||||
'->',
|
||||
helpButton
|
||||
]
|
||||
});
|
||||
|
||||
// TODO: HACK: bug in extjs seems to cause sub-components of the form not to render!
|
||||
confpanel.on('afterlayout', function()
|
||||
{
|
||||
simplePanel.syncSize();
|
||||
advancedPanel.syncSize();
|
||||
simplePanel.setVisible(!advancedCheck.getValue());
|
||||
advancedPanel.setVisible(advancedCheck.getValue());
|
||||
});
|
||||
|
||||
/* ****************************************************************
|
||||
* Load/Save
|
||||
* ***************************************************************/
|
||||
|
||||
confpanel.on('render', function() {
|
||||
confpanel.getForm().load({
|
||||
url : 'epggrab',
|
||||
params : { op : 'loadSettings' },
|
||||
success : function ( form, action ) {
|
||||
confpanel.enable();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function saveChanges() {
|
||||
data = [];
|
||||
scheduleStore.each(function(r) {
|
||||
data.push(r.data);
|
||||
});
|
||||
json = Ext.util.JSON.encode(data);
|
||||
confpanel.getForm().submit({
|
||||
url : 'epggrab',
|
||||
params : { op : 'saveSettings', schedule : json },
|
||||
waitMsg : 'Saving Data...',
|
||||
success : function(form, action) {
|
||||
scheduleStore.commitChanges();
|
||||
},
|
||||
failure : function (form, action) {
|
||||
Ext.Msg.alert('Save failed', action.result.errormsg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return confpanel;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue