diff --git a/src/cron.c b/src/cron.c deleted file mode 100644 index 33985976..00000000 --- a/src/cron.c +++ /dev/null @@ -1,178 +0,0 @@ -#include "cron.h" -#include -#include -#include -#include - -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); - 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; - if ( ret && !((b << now.tm_mday) & cron->dom) ) ret = 0; - if ( ret && !((b << now.tm_mon) & cron->mon) ) ret = 0; - if ( ret && !((b << now.tm_wday) & cron->dow) ) ret = 0; - - return ret; -} - -void cron_serialize ( cron_t *cron, htsmsg_t *msg ) -{ - htsmsg_add_str(msg, "cron", cron->str); -} - -cron_t *cron_deserialize ( htsmsg_t *msg ) -{ - 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 deleted file mode 100644 index 025f664b..00000000 --- a/src/cron.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef __CRON_H__ -#define __CRON_H__ - -#include -#include -#include "htsmsg.h" - -#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) - -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 6e8d03ec..8c024314 100644 --- a/src/epggrab.c +++ b/src/epggrab.c @@ -3,6 +3,10 @@ #include #include #include +#include +#include +#include +#include #include "htsmsg.h" #include "settings.h" #include "tvheadend.h" @@ -15,6 +19,7 @@ #include "channels.h" #include "spawn.h" #include "htsmsg_xml.h" +#include "file.h" /* Thread protection */ int epggrab_confver; @@ -22,13 +27,60 @@ pthread_mutex_t epggrab_mutex; pthread_cond_t epggrab_cond; /* Config */ -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; +/* Prototypes */ +static void _epggrab_module_parse + ( epggrab_module_t *mod, htsmsg_t *data ); + +/* ************************************************************************** + * Threads + * *************************************************************************/ + +// TODO: should I put this in a thread +static void _epggrab_socket_handler ( epggrab_module_t *mod, int s ) +{ + size_t outlen; + char *outbuf; + time_t tm1, tm2; + htsmsg_t *data = NULL; + + /* Grab/Translate */ + time(&tm1); + outlen = file_readall(s, &outbuf); + if (outlen) data = mod->trans(mod, outbuf); + time(&tm2); + + /* Process */ + if ( data ) { + tvhlog(LOG_DEBUG, mod->id, "grab took %d seconds", tm2 - tm1); + _epggrab_module_parse(mod, data); + + /* Failed */ + } else { + tvhlog(LOG_ERR, mod->id, "failed to read data"); + } +} + +// TODO: what happens if we terminate early? +static void *_epggrab_socket_thread ( void *p ) +{ + int s; + epggrab_module_t *mod = (epggrab_module_t*)p; + + while ( mod->enabled && mod->sock ) { + tvhlog(LOG_DEBUG, mod->id, "waiting for connection"); + s = accept(mod->sock, NULL, NULL); + if (!s) break; // assume closed + tvhlog(LOG_DEBUG, mod->id, "got connection"); + _epggrab_socket_handler(mod, s); + } + return NULL; +} + /* ************************************************************************** * Modules * *************************************************************************/ @@ -147,162 +199,103 @@ htsmsg_t *epggrab_module_list ( void ) 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_u32(e, "enabled", m->enabled); htsmsg_add_msg(a, NULL, e); } return a; } +// Uses a unix domain socket, but we could extend to a remote interface +// to allow EPG data to be remotely generated (another machine in the local +// network etc...) void epggrab_module_enable_socket ( epggrab_module_t *mod, uint8_t e ) { - // TODO: implement this? + pthread_t tid; + pthread_attr_t tattr; + struct sockaddr_un addr; + assert(mod->path); + assert(mod->flags & EPGGRAB_MODULE_EXTERNAL); + + /* Disable */ + if (!e) { + shutdown(mod->sock, SHUT_RDWR); + close(mod->sock); + unlink(mod->path); + mod->sock = 0; + // TODO: I don't shutdown the thread! + + /* Enable */ + } else { + unlink(mod->path); // just in case! + + mod->sock = socket(AF_UNIX, SOCK_STREAM, 0); + assert(mod->sock); + + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, mod->path, 100); + // TODO: possibly asserts here not a good idea if I make the socket + // path configurable + assert(bind(mod->sock, (struct sockaddr*)&addr, + sizeof(struct sockaddr_un)) == 0); + + assert(listen(mod->sock, 5) == 0); + + pthread_attr_init(&tattr); + pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED); + pthread_create(&tid, &tattr, _epggrab_socket_thread, mod); + tvhlog(LOG_DEBUG, mod->id, "enabled socket"); + } } -htsmsg_t *epggrab_module_grab - ( epggrab_module_t *mod, const char *cmd, const char *opts ) +char *epggrab_module_grab ( epggrab_module_t *mod ) { - int i, outlen; + int 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 ?: ""); + tvhlog(LOG_DEBUG, mod->id, "grab %s", mod->path); /* Grab */ - outlen = spawn_and_store_stdout(cmd, (char*const*)argv, &outbuf); - free(argv); - wordfree(&we); + outlen = spawn_and_store_stdout(mod->path, NULL, &outbuf); if ( outlen < 1 ) { tvhlog(LOG_ERR, "pyepg", "no output detected"); return NULL; } + return outbuf; +} + +htsmsg_t *epggrab_module_trans_xml + ( epggrab_module_t *mod, char *c ) +{ + htsmsg_t *ret; + char errbuf[100]; + + if (!c) return NULL; + /* Extract */ - ret = htsmsg_xml_deserialize(outbuf, errbuf, sizeof(errbuf)); + ret = htsmsg_xml_deserialize(c, errbuf, sizeof(errbuf)); if (!ret) tvhlog(LOG_ERR, "pyepg", "htsmsg_xml_deserialize error %s", errbuf); return ret; } +const char *epggrab_module_socket_path ( epggrab_module_t *mod ) +{ + char *ret = malloc(100); + snprintf(ret, 100, "%s/epggrab/%s.sock", + hts_settings_get_root(), mod->id); + 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; + htsmsg_t *m; const char *str; /* No config */ @@ -310,13 +303,11 @@ static void _epggrab_load ( void ) 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); + // TODO: module states htsmsg_destroy(m); } @@ -330,28 +321,17 @@ void epggrab_save ( void ) /* 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 ) { + // TODO: could use module variable int save = 0; if ( epggrab_eitenabled != eitenabled ) { save = 1; @@ -374,8 +354,11 @@ int epggrab_set_module ( epggrab_module_t *mod ) { int save = 0; if ( mod && epggrab_module != mod ) { - save = 1; + assert(mod->grab); + assert(mod->trans); + assert(mod->parse); epggrab_module = mod; + save = 1; } return save; } @@ -385,135 +368,100 @@ 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 ) +int epggrab_enable_module ( epggrab_module_t *mod, uint8_t e ) { - return _epggrab_schedule_deserialize(sched); + int save = 0; + if ( e != mod->enabled ) { + assert(mod->trans); + assert(mod->parse); + mod->enabled = e; + if (mod->enable) mod->enable(mod, e); + save = 1; + } + return save; } -htsmsg_t *epggrab_get_schedule ( void ) +int epggrab_enable_module_by_id ( const char *id, uint8_t e ) { - return _epggrab_schedule_serialize(); + return epggrab_enable_module(epggrab_module_find_by_id(id), e); } /* ************************************************************************** * Module Execution * *************************************************************************/ +/* + * Run the parse + */ +static void _epggrab_module_parse + ( epggrab_module_t *mod, htsmsg_t *data ) +{ + time_t tm1, tm2; + int save = 0; + epggrab_stats_t stats; + + /* Parse */ + memset(&stats, 0, sizeof(stats)); + pthread_mutex_lock(&global_lock); + time(&tm1); + 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->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->id, " brands tot=%5d new=%5d mod=%5d", + stats.brands.total, stats.brands.created, + stats.brands.modified); + 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->id, " episodes tot=%5d new=%5d mod=%5d", + stats.episodes.total, stats.episodes.created, + stats.episodes.modified); + tvhlog(LOG_DEBUG, mod->id, " broadcasts tot=%5d new=%5d mod=%5d", + stats.broadcasts.total, stats.broadcasts.created, + stats.broadcasts.modified); +} + /* * Grab from module */ -static void _epggrab_module_run - ( epggrab_module_t *mod, const char *icmd, const char *iopts ) +static void _epggrab_module_grab ( epggrab_module_t *mod ) { - int save = 0; time_t tm1, tm2; htsmsg_t *data; - epggrab_stats_t stats; - char *cmd = NULL, *opts = NULL; - /* Check */ - 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(mod, cmd, opts); + printf("mod = %s\n", mod->id); + printf("trans = %p, grab = %p\n", mod->trans, mod->grab); + data = mod->trans(mod, mod->grab(mod)); time(&tm2); /* Process */ if ( data ) { - - /* 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(mod, data, &stats); - time(&tm2); - if (save) epg_updated(); - pthread_mutex_unlock(&global_lock); - htsmsg_destroy(data); - - /* Debug stats */ - 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->id, " brands tot=%5d new=%5d mod=%5d", - stats.brands.total, stats.brands.created, - stats.brands.modified); - 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->id, " episodes tot=%5d new=%5d mod=%5d", - stats.episodes.total, stats.episodes.created, - stats.episodes.modified); - tvhlog(LOG_DEBUG, mod->id, " broadcasts tot=%5d new=%5d mod=%5d", - stats.broadcasts.total, stats.broadcasts.created, - stats.broadcasts.modified); - + _epggrab_module_parse(mod, data); } else { 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 - */ -static time_t _epggrab_thread_simple ( void ) -{ - /* Copy config */ - time_t ret = time(NULL) + epggrab_interval; - epggrab_module_t* mod = epggrab_module; - - /* Run the module */ - _epggrab_module_run(mod, NULL, NULL); - - return ret; -} - -/* - * Advanced run - */ -static time_t _epggrab_thread_advanced ( void ) -{ - time_t now, ret, tmp; - epggrab_sched_t *s; - - /* Determine which to run */ - 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); - } - } - - return ret; -} - -/* - * Thread + * Thread (for internal grabbing) */ static void* _epggrab_thread ( void* p ) { int confver = 0; struct timespec ts; pthread_mutex_lock(&epggrab_mutex); + epggrab_module_t *mod; /* Load */ _epggrab_load(); @@ -529,12 +477,16 @@ static void* _epggrab_thread ( void* p ) int err = pthread_cond_timedwait(&epggrab_cond, &epggrab_mutex, &ts); if ( err == ETIMEDOUT ) break; } - confver = epggrab_confver; + confver = epggrab_confver; + mod = NULL;//epggrab_module; + ts.tv_sec += epggrab_interval; - /* Run grabber */ - ts.tv_sec = epggrab_advanced - ? _epggrab_thread_advanced() - : _epggrab_thread_simple(); + /* Run grabber (without lock) */ + if (mod) { + pthread_mutex_unlock(&epggrab_mutex); + _epggrab_module_grab(mod); + pthread_mutex_lock(&epggrab_mutex); + } } return NULL; @@ -550,11 +502,9 @@ static void* _epggrab_thread ( void* p ) void epggrab_init ( void ) { /* Defaults */ - epggrab_advanced = 0; epggrab_eitenabled = 1; // on air grab enabled epggrab_interval = 12 * 3600; // hours epggrab_module = NULL; // disabled - TAILQ_INIT(&epggrab_schedule); /* Initialise modules */ eit_init(&epggrab_modules); diff --git a/src/epggrab.h b/src/epggrab.h index 6b704427..70f6cb48 100644 --- a/src/epggrab.h +++ b/src/epggrab.h @@ -2,7 +2,6 @@ #define __EPGGRAB_H__ #include -#include "cron.h" #include "channels.h" /* ************************************************************************** @@ -74,14 +73,16 @@ struct epggrab_module const char *name; ///< Module name (for display) const char *path; ///< Module path (for fixed config) const uint8_t flags; ///< Mode flags + uint8_t enabled; ///< Whether the module is enabled + int sock; ///< Socket descriptor epggrab_channel_tree_t *channels; ///< Channel list - /* Enable/Disable for async */ + /* Enable/Disable */ 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 ); + /* Grab/Translate/Parse */ + char* (*grab) ( epggrab_module_t *m ); + htsmsg_t* (*trans) ( epggrab_module_t *m, char *d ); int (*parse) ( epggrab_module_t *m, htsmsg_t *d, epggrab_stats_t *s ); @@ -95,15 +96,17 @@ struct epggrab_module }; /* - * Default module functions + * Default module functions (shared by pyepg and xmltv) */ -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 ); +const char *epggrab_module_socket_path ( epggrab_module_t *m ); +void epggrab_module_enable_socket ( epggrab_module_t *m, uint8_t e ); +char *epggrab_module_grab ( epggrab_module_t *m ); +htsmsg_t *epggrab_module_trans_xml ( epggrab_module_t *m, char *d ); +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 @@ -121,50 +124,33 @@ typedef struct epggrab_module_list epggrab_module_list_t; epggrab_module_t* epggrab_module_find_by_id ( const char *id ); htsmsg_t* epggrab_module_list ( void ); +/* + * Channel list + */ +htsmsg_t* epggrab_channel_list ( void ); + /* ************************************************************************** * Configuration * *************************************************************************/ -/* - * Schedule specification (for advanced config) - */ -typedef struct epggrab_sched -{ - 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 - */ -TAILQ_HEAD(epggrab_sched_list, epggrab_sched); -typedef struct epggrab_sched_list epggrab_sched_list_t; - /* * 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 ); /* * Update */ -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 ); +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_enable_module ( epggrab_module_t *module, uint8_t e ); +int epggrab_enable_module_by_id ( const char *id, uint8_t e ); +void epggrab_save ( void ); /* ************************************************************************** * Global Functions diff --git a/src/epggrab/pyepg.c b/src/epggrab/pyepg.c index 29cd5b08..bbb532c8 100644 --- a/src/epggrab/pyepg.c +++ b/src/epggrab/pyepg.c @@ -367,7 +367,6 @@ static int _pyepg_parse_epg ( htsmsg_t *data, epggrab_stats_t *stats ) * ***********************************************************************/ epggrab_channel_tree_t _pyepg_channels; -epggrab_module_t _pyepg_module; static void _pyepg_save ( epggrab_module_t *mod ) { @@ -396,24 +395,33 @@ static int _pyepg_parse void pyepg_init ( epggrab_module_list_t *list ) { - _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; + epggrab_module_t *mod; - /* Add to list */ - LIST_INSERT_HEAD(list, &_pyepg_module, link); + /* Standard module */ + mod = calloc(1, sizeof(epggrab_module_t)); + mod->id = strdup("pyepg"); + mod->path = strdup("/usr/bin/pyepg"); + mod->name = strdup("PyEPG"); + mod->grab = epggrab_module_grab; + mod->trans = epggrab_module_trans_xml; + mod->parse = _pyepg_parse; + mod->channels = &_pyepg_channels; + mod->ch_save = _pyepg_save; + mod->ch_add = epggrab_module_channel_add; + mod->ch_rem = epggrab_module_channel_rem; + mod->ch_mod = epggrab_module_channel_mod; + *((uint8_t*)&mod->flags) = EPGGRAB_MODULE_SIMPLE; + LIST_INSERT_HEAD(list, mod, link); + + /* External module */ + mod = calloc(1, sizeof(epggrab_module_t)); + mod->id = strdup("pyepg_ext"); + mod->path = epggrab_module_socket_path(mod); + mod->name = strdup("PyEPG"); + mod->enable = epggrab_module_enable_socket; + mod->trans = epggrab_module_trans_xml; + mod->parse = _pyepg_parse; + *((uint8_t*)&mod->flags) = EPGGRAB_MODULE_EXTERNAL; + LIST_INSERT_HEAD(list, mod, link); } diff --git a/src/epggrab/xmltv.c b/src/epggrab/xmltv.c index 9be657fd..18080125 100644 --- a/src/epggrab/xmltv.c +++ b/src/epggrab/xmltv.c @@ -416,17 +416,12 @@ static void _xmltv_load_grabbers ( epggrab_module_list_t *list ) 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); + 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; + *((uint8_t*)&mod->flags) = EPGGRAB_MODULE_SIMPLE; + mod->parse = _xmltv_parse; LIST_INSERT_HEAD(list, mod, link); p = n = i + 1; } else if ( outbuf[i] == '|' ) { @@ -442,14 +437,11 @@ void xmltv_init ( epggrab_module_list_t *list ) { epggrab_module_t *mod; - /* Advanced module */ + /* External 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->name = strdup("XMLTV"); + *((uint8_t*)&mod->flags) = EPGGRAB_MODULE_EXTERNAL; mod->enable = epggrab_module_enable_socket; mod->grab = epggrab_module_grab; mod->parse = _xmltv_parse; diff --git a/src/webui/extjs.c b/src/webui/extjs.c index 49d64430..9db463bb 100644 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -469,8 +469,10 @@ extjs_epggrab(http_connection_t *hc, const char *remain, void *opaque) { htsbuf_queue_t *hq = &hc->hc_reply; const char *op = http_arg_get(&hc->hc_req_args, "op"); - htsmsg_t *out, *array, *r; + htsmsg_t *out, *array, *r, *e; + htsmsg_field_t *f; const char *str; + uint32_t u32; if(op == NULL) return 400; @@ -490,7 +492,6 @@ extjs_epggrab(http_connection_t *hc, const char *remain, void *opaque) 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); @@ -498,7 +499,7 @@ extjs_epggrab(http_connection_t *hc, const char *remain, void *opaque) out = json_single_record(r, "epggrabSettings"); - /* List of modules */ + /* List of modules and currently states */ } else if (!strcmp(op, "moduleList")) { out = htsmsg_create_map(); pthread_mutex_lock(&epggrab_mutex); @@ -506,34 +507,31 @@ extjs_epggrab(http_connection_t *hc, const char *remain, void *opaque) pthread_mutex_unlock(&epggrab_mutex); htsmsg_add_msg(out, "entries", array); - /* 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); + u32 = http_arg_get(&hc->hc_req_args, "eitenabled") ? 1 : 0; + save |= epggrab_set_eitenabled(u32); 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 ( (str = http_arg_get(&hc->hc_req_args, "external")) ) { + printf("got external\n"); if ( (array = htsmsg_json_deserialize(str)) ) { - save |= epggrab_set_schedule(array); - htsmsg_destroy(array); + printf("got array\n"); + HTSMSG_FOREACH(f, array) { + if ( (e = htsmsg_get_map_by_field(f)) ) { + printf("got field\n"); + str = htsmsg_get_str(e, "id"); + printf("id = %s\n", str); + u32 = 0; + htsmsg_get_u32(e, "enabled", &u32); + printf("enabled = %d\n", u32); + if ( str ) save |= epggrab_enable_module_by_id(str, u32); + } + } } } if (save) epggrab_save(); @@ -545,6 +543,7 @@ extjs_epggrab(http_connection_t *hc, const char *remain, void *opaque) return HTTP_STATUS_BAD_REQUEST; } + htsmsg_print(out); htsmsg_json_serialize(out, hq, 0); htsmsg_destroy(out); http_output_content(hc, "text/x-json; charset=UTF-8"); diff --git a/src/webui/static/app/epggrab.js b/src/webui/static/app/epggrab.js index 6f122800..2b641a8d 100644 --- a/src/webui/static/app/epggrab.js +++ b/src/webui/static/app/epggrab.js @@ -8,7 +8,6 @@ tvheadend.epggrab = function() { * 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({ @@ -16,14 +15,11 @@ tvheadend.epggrab = function() { url : 'epggrab', baseParams : { op : 'moduleList' }, autoLoad : true, - fields : [ 'id', 'name', 'path', 'flags' ] + fields : [ 'id', 'name', 'path', 'flags', 'enabled' ] }); 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 }); @@ -34,12 +30,6 @@ tvheadend.epggrab = function() { 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; }); @@ -48,27 +38,6 @@ tvheadend.epggrab = function() { }); }); - /* - * 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 */ @@ -79,7 +48,7 @@ tvheadend.epggrab = function() { ); /* **************************************************************** - * Simple Config + * Basic Fields * ***************************************************************/ /* @@ -164,173 +133,62 @@ tvheadend.epggrab = function() { } }); - var simplePanel = new Ext.form.FieldSet({ - title : 'Simple Config', - height : 120, - width : 900, - items : [ - simpleModule, - intervalValue, - intervalUnit - ] - }); - /* **************************************************************** - * Advanced Config + * Advanced Fields * ***************************************************************/ /* - * Schedule + * External modules */ - var scheduleColumnModel = new Ext.grid.ColumnModel([ + var externalColumnModel = 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', + dataIndex : 'name', width : 200, sortable : false, - editor : new Ext.form.TextField() }, { - dataIndex : 'options', - header : 'Options', + header : 'Path', + dataIndex : 'path', 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; - }; - - 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 + // TODO: editable? }, - 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) + { + dataIndex : 'enabled', + header : 'Enabled', + width : 50, + sortable : false, + editor : new Ext.form.Checkbox(), + // TODO: newer versions of extjs provide proper checkbox in grid + // support, I think! + renderer : function (v) { + if (v) { + return "Y"; + } else { + return "N"; } - }, - 'select' : function(r) { - delButton.setDisabled(false); } } - }); + ]); - var advancedPanel = new Ext.form.FieldSet({ - title : 'Advanced Config', - height : 200, - width : 900, - items : [ - // TODO: external editors - schedulePanel - ] + var externalGrid = new Ext.grid.EditorGridPanel({ + store : externalModuleStore, + cm : externalColumnModel, + width : 450, + height : 150, + frame : false, + viewConfig : { + forceFit : true, + }, + iconCls : 'icon-grid', }); + var advancedPanel = externalGrid; /* **************************************************************** * 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' @@ -352,7 +210,7 @@ tvheadend.epggrab = function() { var confpanel = new Ext.FormPanel({ title : 'EPG Grabber', - iconCls : 'xml', + iconCls : 'xml', border : false, bodyStyle : 'padding:15px', labelAlign : 'left', @@ -363,9 +221,11 @@ tvheadend.epggrab = function() { defaultType : 'textfield', items : [ interval, - advancedCheck, eitCheck, - simplePanel, + simpleModule, + intervalValue, + intervalUnit, + new Ext.form.Label({text: 'External Interfaces'}), advancedPanel ], tbar: [ @@ -375,15 +235,6 @@ tvheadend.epggrab = function() { ] }); - // 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 * ***************************************************************/ @@ -399,17 +250,17 @@ tvheadend.epggrab = function() { }); function saveChanges() { - data = []; - scheduleStore.each(function(r) { - data.push(r.data); - }); - json = Ext.util.JSON.encode(data); + mods = []; + externalModuleStore.each(function(r) { + mods.push({id: r.get('id'), enabled: r.get('enabled') ? 1 : 0}); + }); + mods = Ext.util.JSON.encode(mods); confpanel.getForm().submit({ url : 'epggrab', - params : { op : 'saveSettings', schedule : json }, + params : { op : 'saveSettings', external : mods }, waitMsg : 'Saving Data...', success : function(form, action) { - scheduleStore.commitChanges(); + externalModuleStore.commitChanges(); }, failure : function (form, action) { Ext.Msg.alert('Save failed', action.result.errormsg);