Another significant reworking (cannot make my mind up ;) ) of the epggrab framework. This time its MUCH simpler. All clever stuff is defered to a unix domain socket interface which allows users to define schedules using system cron and do whatever the hell they like as long as they send the data to the socket (using nc etc.).

This commit is contained in:
Adam Sutton 2012-06-07 12:42:57 +01:00
parent 7a09f91ec7
commit e652b27822
8 changed files with 322 additions and 738 deletions

View file

@ -1,178 +0,0 @@
#include "cron.h"
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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

View file

@ -1,24 +0,0 @@
#ifndef __CRON_H__
#define __CRON_H__
#include <stdint.h>
#include <sys/types.h>
#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__ */

View file

@ -3,6 +3,10 @@
#include <string.h>
#include <assert.h>
#include <wordexp.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/socket.h>
#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);

View file

@ -2,7 +2,6 @@
#define __EPGGRAB_H__
#include <pthread.h>
#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

View file

@ -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);
}

View file

@ -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;

View file

@ -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");

View file

@ -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);