diff --git a/src/cron.c b/src/cron.c index 1bb6e553..33985976 100644 --- a/src/cron.c +++ b/src/cron.c @@ -1,16 +1,148 @@ #include "cron.h" #include +#include +#include +#include -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 diff --git a/src/cron.h b/src/cron.h index 9724c84d..025f664b 100644 --- a/src/cron.h +++ b/src/cron.h @@ -5,18 +5,20 @@ #include #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__ */ diff --git a/src/epggrab.c b/src/epggrab.c index 8762f3cb..6e8d03ec 100644 --- a/src/epggrab.c +++ b/src/epggrab.c @@ -1,6 +1,8 @@ #include #include #include +#include +#include #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(); -} diff --git a/src/epggrab.h b/src/epggrab.h index dbf961b3..6b704427 100644 --- a/src/epggrab.h +++ b/src/epggrab.h @@ -3,14 +3,12 @@ #include #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__ */ diff --git a/src/epggrab/eit.c b/src/epggrab/eit.c index 6fea4c9d..106699d3 100644 --- a/src/epggrab/eit.c +++ b/src/epggrab/eit.c @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +#include #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! } diff --git a/src/epggrab/eit.h b/src/epggrab/eit.h index 3b0870c4..c30341e5 100644 --- a/src/epggrab/eit.h +++ b/src/epggrab/eit.h @@ -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, diff --git a/src/epggrab/pyepg.c b/src/epggrab/pyepg.c index 6c52a911..29cd5b08 100644 --- a/src/epggrab/pyepg.c +++ b/src/epggrab/pyepg.c @@ -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); } diff --git a/src/epggrab/pyepg.h b/src/epggrab/pyepg.h index 716baf43..a005c165 100644 --- a/src/epggrab/pyepg.h +++ b/src/epggrab/pyepg.h @@ -21,6 +21,6 @@ #include "epggrab.h" -epggrab_module_t* pyepg_init ( void ); +void pyepg_init ( epggrab_module_list_t *list ); #endif diff --git a/src/epggrab/xmltv.c b/src/epggrab/xmltv.c index 970e8d77..9be657fd 100644 --- a/src/epggrab/xmltv.c +++ b/src/epggrab/xmltv.c @@ -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); +} diff --git a/src/epggrab/xmltv.h b/src/epggrab/xmltv.h index bef9e6b9..b4637a1a 100644 --- a/src/epggrab/xmltv.h +++ b/src/epggrab/xmltv.h @@ -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 diff --git a/src/webui/extjs.c b/src/webui/extjs.c index c4e4c54d..49d64430 100644 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -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); diff --git a/src/webui/static/app/epg.js b/src/webui/static/app/epg.js index 68eaff4f..2c8e5e8c 100644 --- a/src/webui/static/app/epg.js +++ b/src/webui/static/app/epg.js @@ -14,7 +14,9 @@ tvheadend.epgDetails = function(event) { if(event.chicon != null && event.chicon.length > 0) content += ''; - content += '
' + event.title + '
'; + content += '
' + event.title; + if (event.subtitle) content += " : " + event.subtitle; + content += '
'; content += '
' + event.episode + '
'; content += '
' + event.description + '
'; @@ -120,7 +122,7 @@ tvheadend.epgDetails = function(event) { html += '
'; if (ee.episode) html += ee.episode + '   '; html += ee.title; - if (ee.subtitle) html += ':' + ee.subtitle + if (ee.subtitle) html += ' : ' + ee.subtitle html += '
'; } } @@ -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', diff --git a/src/webui/static/app/epggrab.js b/src/webui/static/app/epggrab.js index b4baa0fc..6f122800 100644 --- a/src/webui/static/app/epggrab.js +++ b/src/webui/static/app/epggrab.js @@ -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.
'+ - '
' + - 'To configure manually execute the ' + - 'following command in a shell on the ' + - 'server:
' + - '$ ' + r.data.identifier + - ' --configure
' + - '
' + - '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.'+ - '
' + - '
' + - '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).
' + - 'Make sure that the grabber is properly ' + - 'configured before saving configuration.
'+ - '
' + - 'To configure manually execute the ' + - 'following command in a shell on the ' + - 'server:
' + - '$ ' + r.data.identifier + - ' --configure
' + - '
' + - '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.'+ - '
' + - '
' + - '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; }