From 988eb8133af472e6957bdfa73b613f475f5a73c9 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Tue, 22 May 2012 10:15:05 +0100 Subject: [PATCH] Start work on epg load/save and also added back in initial xmltv parser. --- Makefile | 4 +- src/epg.c | 170 +++++++- src/epg.h | 19 + src/epggrab.c | 4 + src/epggrab/pyepg.h | 5 + src/epggrab/xmltv.c | 985 ++++++-------------------------------------- src/epggrab/xmltv.h | 45 +- 7 files changed, 335 insertions(+), 897 deletions(-) diff --git a/Makefile b/Makefile index 3712c0a9..37088972 100644 --- a/Makefile +++ b/Makefile @@ -73,7 +73,9 @@ SRCS = src/main.c \ src/iptv_input.c \ src/avc.c \ -SRCS += src/epggrab/pyepg.c +SRCS += src/epggrab/pyepg.c\ + src/epggrab/xmltv.c + SRCS += src/plumbing/tsfix.c \ diff --git a/src/epg.c b/src/epg.c index f34a52a0..d4a466e4 100644 --- a/src/epg.c +++ b/src/epg.c @@ -148,12 +148,85 @@ static void _epg_dump ( void ) * Setup / Update * *************************************************************************/ -void epg_init ( void ) +static int _epg_write ( int fd, htsmsg_t *m ) { + int ret = 1; + size_t msglen; + void *msgdata; + if (m) { + int r = htsmsg_binary_serialize(m, &msgdata, &msglen, 0x10000); + htsmsg_destroy(m); + if (!r) { + ssize_t w = write(fd, msgdata, msglen); + free(msgdata); + if(w == msglen) ret = 0; + } + } + if(ret) { + tvhlog(LOG_DEBUG, "epg", "failed to store epg to disk"); + close(fd); + hts_settings_remove("epgdb"); + } + return ret; } void epg_save ( void ) { + int fd; + epg_brand_t *eb; + epg_season_t *es; + epg_episode_t *ee; + epg_channel_t *ec; + epg_broadcast_t *ebc; + + printf("EPG save\n"); + // TODO: requires markers in the file or some other means of + // determining where the various object types are? + fd = hts_settings_open_file(1, "epgdb"); + RB_FOREACH(ec, &epg_channels, ec_link) { + if (_epg_write(fd, epg_channel_serialize(ec))) return; + } + RB_FOREACH(eb, &epg_brands, eb_link) { + if (_epg_write(fd, epg_brand_serialize(eb))) return; + } + RB_FOREACH(es, &epg_seasons, es_link) { + if (_epg_write(fd, epg_season_serialize(es))) return; + } + RB_FOREACH(ee, &epg_episodes, ee_link) { + if (_epg_write(fd, epg_episode_serialize(ee))) return; + } + RB_FOREACH(ec, &epg_channels, ec_link) { + RB_FOREACH(ebc, &ec->ec_schedule, eb_slink) { + if (_epg_write(fd, epg_broadcast_serialize(ebc))) return; + } + } +} + +void epg_init ( void ) +{ + struct stat st; + int fd = hts_settings_open_file(0, "epgdb"); + size_t remain; + uint8_t *mem, *rp; + fstat(fd, &st); + rp = mem = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + //TODO:if ( mem == MAP_FAILED ) return; + remain = st.st_size; + while ( remain > 4 ) { + int msglen = (rp[0] << 24) | (rp[1] << 16) | (rp[2] << 8) | rp[3]; + printf("msglen = %d\n", msglen); + remain -= 4; + rp += 4; + htsmsg_t *m = htsmsg_binary_deserialize(rp, msglen, NULL); + if(m) { + htsmsg_print(m); + htsmsg_destroy(m); + } + rp += msglen; + remain -= msglen; + } + munmap(mem, st.st_size); + close(fd); } void epg_updated ( void ) @@ -306,6 +379,23 @@ int epg_brand_rem_episode ( epg_brand_t *brand, epg_episode_t *episode, int u ) return save; } +htsmsg_t *epg_brand_serialize ( epg_brand_t *brand ) +{ + htsmsg_t *m; + if ( !brand ) return NULL; + m = htsmsg_create_map(); + // TODO: ID + if (brand->eb_uri) + htsmsg_add_str(m, "uri", brand->eb_uri); + if (brand->eb_title) + htsmsg_add_str(m, "title", brand->eb_title); + if (brand->eb_summary) + htsmsg_add_str(m, "summary", brand->eb_summary); + if (brand->eb_season_count) + htsmsg_add_u32(m, "season-count", brand->eb_season_count); + return m; +} + /* ************************************************************************** * Season * *************************************************************************/ @@ -416,6 +506,25 @@ int epg_season_rem_episode return save; } +htsmsg_t *epg_season_serialize ( epg_season_t *season ) +{ + htsmsg_t *m; + if (!season) return NULL; + m = htsmsg_create_map(); + if (season->es_uri) + htsmsg_add_str(m, "uri", season->es_uri); + if (season->es_summary) + htsmsg_add_str(m, "summary", season->es_summary); + if (season->es_number) + htsmsg_add_u32(m, "number", season->es_number); + if (season->es_episode_count) + htsmsg_add_u32(m, "episode-count", season->es_episode_count); + if (season->es_brand) + htsmsg_add_str(m, "brand-id", season->es_brand->eb_uri); + // TODO: change to ID + return m; +} + /* ************************************************************************** * Episode * *************************************************************************/ @@ -596,6 +705,35 @@ int epg_episode_get_number_onscreen return i; } +htsmsg_t *epg_episode_serialize ( epg_episode_t *episode ) +{ + htsmsg_t *m; + if (!episode) return NULL; + m = htsmsg_create_map(); + if (episode->ee_uri) + htsmsg_add_str(m, "uri", episode->ee_uri); + if (episode->ee_title) + htsmsg_add_str(m, "title", episode->ee_title); + if (episode->ee_subtitle) + htsmsg_add_str(m, "subtitle", episode->ee_subtitle); + if (episode->ee_summary) + htsmsg_add_str(m, "summary", episode->ee_summary); + if (episode->ee_description) + htsmsg_add_str(m, "description", episode->ee_description); + if (episode->ee_number) + htsmsg_add_u32(m, "number", episode->ee_number); + if (episode->ee_part_count && episode->ee_part_count) { + htsmsg_add_u32(m, "part-number", episode->ee_part_number); + htsmsg_add_u32(m, "part-count", episode->ee_part_count); + } + // TODO: use ids + if (episode->ee_brand) + htsmsg_add_str(m, "brand-id", episode->ee_brand->eb_uri); + if (episode->ee_season) + htsmsg_add_str(m, "season-id", episode->ee_season->es_uri); + return m; +} + /* ************************************************************************** * Broadcast * *************************************************************************/ @@ -672,6 +810,23 @@ epg_broadcast_t *epg_broadcast_get_next ( epg_broadcast_t *broadcast ) return RB_NEXT(broadcast, eb_slink); } +htsmsg_t *epg_broadcast_serialize ( epg_broadcast_t *broadcast ) +{ + htsmsg_t *m; + if (!broadcast) return NULL; + m = htsmsg_create_map(); + htsmsg_add_u32(m, "id", broadcast->eb_id); + htsmsg_add_u32(m, "start", broadcast->eb_start); + htsmsg_add_u32(m, "stop", broadcast->eb_stop); + if (broadcast->eb_dvb_id) + htsmsg_add_u32(m, "dvb-id", broadcast->eb_dvb_id); + // TODO: add other metadata fields + // TODO: use ID + if (broadcast->eb_episode) + htsmsg_add_str(m, "episode-id", broadcast->eb_episode->ee_uri); + return m; +} + /* ************************************************************************** * Channel * *************************************************************************/ @@ -732,6 +887,19 @@ epg_broadcast_t *epg_channel_get_current_broadcast ( epg_channel_t *channel ) return RB_FIRST(&channel->ec_schedule); } +htsmsg_t *epg_channel_serialize ( epg_channel_t *channel ) +{ + htsmsg_t *m; + if (!channel) return NULL; + m = htsmsg_create_map(); + if (channel->ec_uri) + htsmsg_add_str(m, "uri", channel->ec_uri); + if (channel->ec_channel) + htsmsg_add_u32(m, "channel-id", channel->ec_channel->ch_id); + // TODO: other data + return m; +} + /* ************************************************************************** * Querying * *************************************************************************/ diff --git a/src/epg.h b/src/epg.h index af6537b9..354ba9be 100644 --- a/src/epg.h +++ b/src/epg.h @@ -80,6 +80,10 @@ int epg_brand_add_episode ( epg_brand_t *b, epg_episode_t *s, int u ) int epg_brand_rem_episode ( epg_brand_t *b, epg_episode_t *s, int u ) __attribute__((warn_unused_result)); +/* Serialization */ +htsmsg_t *epg_brand_serialize ( epg_brand_t *b ); +epg_brand_t *epg_brand_deserialize ( htsmsg_t *m, int create ); + /* ************************************************************************ * Season * ***********************************************************************/ @@ -119,6 +123,10 @@ int epg_season_add_episode ( epg_season_t *s, epg_episode_t *e, int u ) int epg_season_rem_episode ( epg_season_t *s, epg_episode_t *e, int u ) __attribute__((warn_unused_result)); +/* Serialization */ +htsmsg_t *epg_season_serialize ( epg_season_t *b ); +epg_season_t *epg_season_deserialize ( htsmsg_t *m, int create ); + /* ************************************************************************ * Episode * @@ -178,6 +186,9 @@ int epg_episode_rem_broadcast ( epg_episode_t *e, epg_broadcast_t *b, int u ) /* Acessors */ int epg_episode_get_number_onscreen ( epg_episode_t *e, char *b, int c ); +/* Serialization */ +htsmsg_t *epg_episode_serialize ( epg_episode_t *b ); +epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create ); /* ************************************************************************ * Broadcast - specific airing (channel & time) of an episode @@ -226,6 +237,10 @@ int epg_broadcast_set_episode ( epg_broadcast_t *b, epg_episode_t *e, int u ) /* Accessors */ epg_broadcast_t *epg_broadcast_get_next ( epg_broadcast_t *b ); +/* Serialization */ +htsmsg_t *epg_broadcast_serialize ( epg_broadcast_t *b ); +epg_broadcast_t *epg_broadcast_deserialize ( htsmsg_t *m, int create ); + /* ************************************************************************ * Channel - provides mapping from EPG channels to real channels * ***********************************************************************/ @@ -257,6 +272,10 @@ int epg_channel_set_name ( epg_channel_t *c, const char *n ) /* Accessors */ epg_broadcast_t *epg_channel_get_current_broadcast ( epg_channel_t *c ); +/* Serialization */ +htsmsg_t *epg_channel_serialize ( epg_channel_t *b ); +epg_channel_t *epg_channel_deserialize ( htsmsg_t *m, int create ); + /* ************************************************************************ * Querying * ***********************************************************************/ diff --git a/src/epggrab.c b/src/epggrab.c index b811291a..9196d1a5 100644 --- a/src/epggrab.c +++ b/src/epggrab.c @@ -7,6 +7,7 @@ #include "epg.h" #include "epggrab.h" #include "epggrab/pyepg.h" +#include "epggrab/xmltv.h" /* Thread protection */ int epggrab_confver; @@ -22,6 +23,7 @@ epggrab_sched_list_t epggrab_schedule; /* Modules */ epggrab_module_t* epggrab_module_pyepg; +epggrab_module_t* epggrab_module_xmltv; /* Internal prototypes */ static void* _epggrab_thread ( void* ); @@ -44,6 +46,7 @@ void epggrab_init ( void ) /* Initialise modules */ epggrab_module_pyepg = pyepg_init(); + epggrab_module_xmltv = xmltv_init(); /* Start thread */ pthread_t tid; @@ -163,6 +166,7 @@ void* _epggrab_thread ( void* p ) epggrab_module_t* epggrab_module_find_by_name ( const char *name ) { if ( strcmp(name, "pyepg") == 0 ) return epggrab_module_pyepg; + if ( strcmp(name, "xmltv") == 0 ) return epggrab_module_xmltv; return NULL; } diff --git a/src/epggrab/pyepg.h b/src/epggrab/pyepg.h index d7afe9bc..5e42a565 100644 --- a/src/epggrab/pyepg.h +++ b/src/epggrab/pyepg.h @@ -4,4 +4,9 @@ #include "epggrab.h" +#ifndef EPGGRAB_PYEPG_H +#define EPGGRAB_PYEPG_H + epggrab_module_t* pyepg_init ( void ); + +#endif diff --git a/src/epggrab/xmltv.c b/src/epggrab/xmltv.c index 73b5580c..004012ed 100644 --- a/src/epggrab/xmltv.c +++ b/src/epggrab/xmltv.c @@ -34,158 +34,15 @@ #include "xmltv.h" #include "spawn.h" -/** - * - */ -LIST_HEAD(xmltv_grabber_list, xmltv_grabber); -static struct xmltv_grabber_list xmltv_grabbers; - -typedef struct xmltv_grabber { - LIST_ENTRY(xmltv_grabber) xg_link; - char *xg_path; - time_t xg_mtime; - - char *xg_version; - char *xg_description; - - uint32_t xg_capabilities; -#define XG_CAP_BASELINE 0x1 -#define XG_CAP_MANUALCONFIG 0x2 -#define XG_CAP_CACHE 0x4 -#define XG_CAP_APICONFIG 0x8 - - int xg_dirty; - -} xmltv_grabber_t; - - -typedef struct parse_stats { - int ps_channels; - int ps_programmes; - int ps_events_created; - -} parse_stats_t; - - - - -static xmltv_grabber_t *xg_current; - - -uint32_t xmltv_grab_interval; -uint32_t xmltv_grab_enabled; - -/* xmltv_channels is protected by global_lock */ -static struct xmltv_channel_tree xmltv_channels; -struct xmltv_channel_list xmltv_displaylist; - -pthread_mutex_t xmltv_mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t xmltv_cond; -static int xmltv_confver; - -static void xmltv_grabbers_index(void); -static void xmltv_grabbers_load(void); -static void xmltv_grabbers_save(void); - -/** - * - */ -static int -xc_dn_cmp(const xmltv_channel_t *a, const xmltv_channel_t *b) -{ - return strcmp(a->xc_displayname, b->xc_displayname); -} - - -/** - * - */ -static void -xc_set_displayname(xmltv_channel_t *xc, const char *name) -{ - if(xc->xc_displayname != NULL) { - LIST_REMOVE(xc, xc_displayname_link); - free(xc->xc_displayname); - } - if(name == NULL) { - xc->xc_displayname = NULL; - return; - } - - xc->xc_displayname = strdup(name); - LIST_INSERT_SORTED(&xmltv_displaylist, xc, xc_displayname_link, xc_dn_cmp); -} - -/** - * - */ -static void -xmltv_load(void) -{ - htsmsg_t *l, *c; - htsmsg_field_t *f; - xmltv_channel_t *xc; - - if((l = hts_settings_load("xmltv/channels")) == NULL) - return; - - HTSMSG_FOREACH(f, l) { - if((c = htsmsg_get_map_by_field(f)) == NULL) - continue; - - xc = xmltv_channel_find(f->hmf_name, 1); - tvh_str_set(&xc->xc_icon, htsmsg_get_str(c, "icon")); - - xc_set_displayname(xc, htsmsg_get_str(c, "displayname")); - } - htsmsg_destroy(l); -} - - -/** - * - */ -static void -xmltv_save(xmltv_channel_t *xc) -{ - htsmsg_t *m = htsmsg_create_map(); - - if(xc->xc_displayname != NULL) - htsmsg_add_str(m, "displayname", xc->xc_displayname); - - if(xc->xc_icon != NULL) - htsmsg_add_str(m, "icon", xc->xc_icon); - - hts_settings_save(m, "xmltv/channels/%s", xc->xc_identifier); - htsmsg_destroy(m); -} - - -/** - * - */ -static void -xmltv_map_to_channel_by_name(xmltv_channel_t *xc) -{ - channel_t *ch, *next; - - for(ch = LIST_FIRST(&channels_not_xmltv_mapped); ch != NULL; ch = next) { - next = LIST_NEXT(ch, ch_xc_link); - assert(ch != next); - if(!strcasecmp(xc->xc_displayname, ch->ch_name)) { - tvhlog(LOG_NOTICE, "xmltv", "Channel \"%s\" automapped to xmltv-channel " - "\"%s\" (name matches)", ch->ch_name, xc->xc_displayname); - channel_set_xmltv_source(ch, xc); - } - } -} - +/* ************************************************************************** + * Parsing + * *************************************************************************/ /** * */ static time_t -xmltv_str2time(const char *str) +_xmltv_str2time(const char *str) { struct tm tm; int tz, r; @@ -212,129 +69,7 @@ xmltv_str2time(const char *str) } } - -/** - * - */ -static int -xc_id_cmp(const xmltv_channel_t *a, const xmltv_channel_t *b) -{ - return strcmp(a->xc_identifier, b->xc_identifier); -} - - -/** - * - */ -xmltv_channel_t * -xmltv_channel_find(const char *id, int create) -{ - xmltv_channel_t *xc; - static xmltv_channel_t *skel; - - lock_assert(&global_lock); - - if(skel == NULL) - skel = calloc(1, sizeof(xmltv_channel_t)); - - skel->xc_identifier = (char *)id; - if(!create) - return RB_FIND(&xmltv_channels, skel, xc_link, xc_id_cmp); - - xc = RB_INSERT_SORTED(&xmltv_channels, skel, xc_link, xc_id_cmp); - if(xc == NULL) { - /* New entry was inserted */ - xc = skel; - skel = NULL; - xc->xc_identifier = strdup(id); - } - return xc; -} - - -/** - * - */ -xmltv_channel_t * -xmltv_channel_find_by_displayname(const char *name) -{ - xmltv_channel_t *xc; - - lock_assert(&global_lock); - - LIST_FOREACH(xc, &xmltv_displaylist, xc_displayname_link) - if(!strcmp(xc->xc_displayname, name)) - break; - return xc; -} - - -/** - * XXX: Move to libhts? - */ -static const char * -xmltv_get_cdata_by_tag(htsmsg_t *tags, const char *name) -{ - htsmsg_t *sub; - if((sub = htsmsg_get_map(tags, name)) == NULL) - return NULL; - return htsmsg_get_str(sub, "cdata"); -} - - -/** - * Parse a tag from xmltv - */ -static void -xmltv_parse_channel(htsmsg_t *body) -{ - htsmsg_t *attribs, *tags, *subtag; - const char *id, *name, *icon; - xmltv_channel_t *xc; - channel_t *ch; - int save = 0; - - if(body == NULL) - return; - - if((attribs = htsmsg_get_map(body, "attrib")) == NULL) - return; - - tags = htsmsg_get_map(body, "tags"); - - if((id = htsmsg_get_str(attribs, "id")) == NULL) - return; - - xc = xmltv_channel_find(id, 1); - - if(tags == NULL) - return; - - if((name = xmltv_get_cdata_by_tag(tags, "display-name")) != NULL) { - - if(xc->xc_displayname == NULL || strcmp(xc->xc_displayname, name)) { - xc_set_displayname(xc, name); - xmltv_map_to_channel_by_name(xc); - save = 1; - } - } - - if((subtag = htsmsg_get_map(tags, "icon")) != NULL && - (attribs = htsmsg_get_map(subtag, "attrib")) != NULL && - (icon = htsmsg_get_str(attribs, "src")) != NULL) { - - if(xc->xc_icon == NULL || strcmp(xc->xc_icon, icon)) { - tvh_str_set(&xc->xc_icon, icon); - - LIST_FOREACH(ch, &xc->xc_channels, ch_xc_link) - channel_set_icon(ch, icon); - save = 1; - } - } - - if(save) - xmltv_save(xc); -} +#if TODO_XMLTV_EP_NUMBERING /** @@ -471,654 +206,194 @@ get_episode_info(htsmsg_t *tags, epg_episode_t *ee) } } +#endif + /** * Parse tags inside of a programme */ -static void -xmltv_parse_programme_tags(xmltv_channel_t *xc, htsmsg_t *tags, - time_t start, time_t stop, parse_stats_t *ps) +static int +_xmltv_parse_programme_tags(epg_channel_t *xc, htsmsg_t *tags, + time_t start, time_t stop) { - event_t *e; - channel_t *ch; - const char *title = xmltv_get_cdata_by_tag(tags, "title"); - const char *desc = xmltv_get_cdata_by_tag(tags, "desc"); - const char *category = xmltv_get_cdata_by_tag(tags, "category"); - int created; - epg_episode_t episode; + int save = 0; + epg_broadcast_t *ebc; + epg_episode_t *ee; + const char *title = htsmsg_xml_get_cdata_str(tags, "title"); + const char *desc = htsmsg_xml_get_cdata_str(tags, "desc"); +#if TODO_EPG_GENRE + const char *category = htsmsg_xml_get_cdata_str(tags, "category"); +#endif - get_episode_info(tags, &episode); + /* Generate an episode */ + // TODO: hash the description? + ee = epg_episode_find_by_uri("TODO", 1); + if (title) save |= epg_episode_set_title(ee, title); + if (desc) save |= epg_episode_set_description(ee, desc); +#if TODO_EPG_GENRE + if (category) save |= epg_episode_set_genre(ee, category); +#endif +#if TODO_XMLTV_EP_NUMBERING + _get_episode_info(tags, &episode); +#endif - LIST_FOREACH(ch, &xc->xc_channels, ch_xc_link) { - if((e = epg_event_create(ch, start, stop, -1, &created)) == NULL) - continue; - - if(created) - ps->ps_events_created++; - - int changed = 0; - - if(title != NULL) - changed |= epg_event_set_title(e, title); - - if(desc != NULL) - changed |= epg_event_set_desc(e, desc); - - if(category != NULL) { - uint8_t type = epg_content_group_find_by_name(category); - if(type) - changed |= epg_event_set_content_type(e, type); - } - - changed |= epg_event_set_episode(e, &episode); - - if(changed) - epg_event_updated(e); - } - - free(episode.ee_onscreen); + /* Create broadcast */ + ebc = epg_broadcast_find_by_time(xc, start, stop, 1); + save |= epg_broadcast_set_episode(ebc, ee, 1); + + return save; } /** * Parse a tag from xmltv */ -static void -xmltv_parse_programme(htsmsg_t *body, parse_stats_t *ps) +static int +_xmltv_parse_programme(htsmsg_t *body) { + int save = 0; htsmsg_t *attribs, *tags; const char *s, *chid; time_t start, stop; - xmltv_channel_t *xc; + epg_channel_t *xc; - if(body == NULL) - return; + if(body == NULL) return 0; - if((attribs = htsmsg_get_map(body, "attrib")) == NULL) - return; + if((attribs = htsmsg_get_map(body, "attrib")) == NULL) return 0; + if((tags = htsmsg_get_map(body, "tags")) == NULL) return 0; + if((chid = htsmsg_get_str(attribs, "channel")) == NULL) return 0; + if((xc = epg_channel_find_by_uri(chid, 0)) == NULL) return 0; + if((s = htsmsg_get_str(attribs, "start")) == NULL) return 0; + start = _xmltv_str2time(s); + if((s = htsmsg_get_str(attribs, "stop")) == NULL) return 0; + stop = _xmltv_str2time(s); - if((tags = htsmsg_get_map(body, "tags")) == NULL) - return; + if(stop <= start || stop < dispatch_clock) return 0; - if((chid = htsmsg_get_str(attribs, "channel")) == NULL) - return; + _xmltv_parse_programme_tags(xc, tags, start, stop); + return save; +} - if((s = htsmsg_get_str(attribs, "start")) == NULL) - return; - start = xmltv_str2time(s); +/** + * Parse a tag from xmltv + */ +static int +_xmltv_parse_channel(htsmsg_t *body) +{ + int save =0; + htsmsg_t *attribs, *tags;/*, *subtag;*/ + const char *id;/*TODO, *name, *icon;*/ + epg_channel_t *xc; - if((s = htsmsg_get_str(attribs, "stop")) == NULL) - return; - stop = xmltv_str2time(s); + if(body == NULL) return 0; - if(stop <= start || stop < dispatch_clock) - return; - - if((xc = xmltv_channel_find(chid, 0)) != NULL) { - xmltv_parse_programme_tags(xc, tags, start, stop, ps); + if((attribs = htsmsg_get_map(body, "attrib")) == NULL) return 0; + if((id = htsmsg_get_str(attribs, "id")) == NULL) return 0; + if((xc = epg_channel_find_by_uri(id, 1)) == NULL) return 0; + if((tags = htsmsg_get_map(body, "tags")) == NULL) return 0; + // TODO: save in find_by_uri + +#if TODO_EPG_CHANNEL_META + if((name = xmltv_get_cdata_by_tag(tags, "display-name")) != NULL) { + save |= epg_channel_set_name(xc, name); } + + if((subtag = htsmsg_get_map(tags, "icon")) != NULL && + (attribs = htsmsg_get_map(subtag, "attrib")) != NULL && + (icon = htsmsg_get_str(attribs, "src")) != NULL) { + + if(xc->xc_icon == NULL || strcmp(xc->xc_icon, icon)) { + tvh_str_set(&xc->xc_icon, icon); + + LIST_FOREACH(ch, &xc->xc_channels, ch_xc_link) + channel_set_icon(ch, icon); + save = 1; + } + } +#endif + return save; } /** * */ -static void -xmltv_parse_tv(htsmsg_t *body, parse_stats_t *ps) +static int +_xmltv_parse_tv(htsmsg_t *body) { + int save = 0; htsmsg_t *tags; htsmsg_field_t *f; if((tags = htsmsg_get_map(body, "tags")) == NULL) - return; + return 0; HTSMSG_FOREACH(f, tags) { if(!strcmp(f->hmf_name, "channel")) { - xmltv_parse_channel(htsmsg_get_map_by_field(f)); - ps->ps_channels++; + save |= _xmltv_parse_channel(htsmsg_get_map_by_field(f)); } else if(!strcmp(f->hmf_name, "programme")) { - xmltv_parse_programme(htsmsg_get_map_by_field(f), ps); - ps->ps_programmes++; + save |= _xmltv_parse_programme(htsmsg_get_map_by_field(f)); } } + return save; } /** * */ -static void -xmltv_parse(htsmsg_t *body, parse_stats_t *ps) +static int _xmltv_parse ( htsmsg_t *data ) { htsmsg_t *tags, *tv; - if((tags = htsmsg_get_map(body, "tags")) == NULL) - return; + if((tags = htsmsg_get_map(data, "tags")) == NULL) + return 0; if((tv = htsmsg_get_map(tags, "tv")) == NULL) - return; + return 0; - xmltv_parse_tv(tv, ps); + return _xmltv_parse_tv(tv); } -/** - * - */ -static void -xmltv_grab(const char *prog) +/* ************************************************************************ + * Module Setup + * ***********************************************************************/ + +// TODO: config + +static epggrab_module_t xmltv_module; + +static const char* _xmltv_name ( void ) { - int outlen; + return "xmltv"; +} + +static htsmsg_t* _xmltv_grab ( const char *iopts ) +{ + size_t outlen; char *outbuf; - htsmsg_t *body; char errbuf[100]; - time_t t1, t2; - parse_stats_t ps = {0}; + const char *cmd = "/usr/bin/tv_grab_uk_rt"; - time(&t1); - outlen = spawn_and_store_stdout(prog, NULL, &outbuf); - if(outlen < 1) { - tvhlog(LOG_ERR, "xmltv", "No output from \"%s\"", prog); - return; - } + /* Debug */ + tvhlog(LOG_DEBUG, "xmltv", "grab %s", cmd); - time(&t2); - - tvhlog(LOG_DEBUG, "xmltv", - "%s: completed, took %ld seconds", - prog, t2 - t1); - - body = htsmsg_xml_deserialize(outbuf, errbuf, sizeof(errbuf)); - if(body == NULL) { - tvhlog(LOG_ERR, "xmltv", "Unable to parse output from \"%s\": %s", - prog, errbuf); - return; - } - - - pthread_mutex_lock(&global_lock); - xmltv_parse(body, &ps); - pthread_mutex_unlock(&global_lock); - - tvhlog(LOG_INFO, "xmltv", - "%s: Parsing completed. XML contained %d channels, %d events, " - "%d new events injected in EPG", - prog, - ps.ps_channels, - ps.ps_programmes, - ps.ps_events_created); - - htsmsg_destroy(body); -} - - -/** - * - */ -static void * -xmltv_thread(void *aux) -{ - int confver = 0; - struct timespec ts; - char *p; - - pthread_mutex_lock(&xmltv_mutex); - - xmltv_grabbers_load(); - - xmltv_grabbers_index(); - - ts.tv_nsec = 0; - ts.tv_sec = 0; - - while(1) { - - while(confver == xmltv_confver) { - - if(xg_current == NULL || !xmltv_grab_enabled) { - pthread_cond_wait(&xmltv_cond, &xmltv_mutex); - continue; - } - if(pthread_cond_timedwait(&xmltv_cond, &xmltv_mutex, &ts) == ETIMEDOUT) - break; - } - - confver = xmltv_confver; - - if(xmltv_grab_enabled == 0) - continue; - - ts.tv_sec = time(NULL) + xmltv_grab_interval * 3600; - - if(xg_current != NULL) { - tvhlog(LOG_INFO, "xmltv", - "Grabbing \"%s\" using command \"%s\"", - xg_current->xg_description, xg_current->xg_path); - - /* Dup path so we can unlock */ - p = strdup(xg_current->xg_path); - - pthread_mutex_unlock(&xmltv_mutex); - - xmltv_grab(p); - - pthread_mutex_lock(&xmltv_mutex); - free(p); - } - } - return NULL; -} - - -/** - * - */ -void -xmltv_init(void) -{ - pthread_t ptid; - - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - - xmltv_grab_interval = 12; /* Default half a day */ - xmltv_grab_enabled = 1; /* Default on */ - - /* Load all channels */ - xmltv_load(); - - pthread_create(&ptid, &attr, xmltv_thread, NULL); -} - - -/** - * - */ -static void -xmltv_grabber_destroy(xmltv_grabber_t *xg) -{ - LIST_REMOVE(xg, xg_link); - free(xg->xg_path); - free(xg->xg_version); - free(xg->xg_description); - free(xg); -} - -/** - * - */ -static char * -xmltv_grabber_get_description(const char *prog) -{ - int i, outlen; - char *outbuf; - const char *args[] = {prog, "--description", NULL}; - - if((outlen = spawn_and_store_stdout(prog, (void *)args, &outbuf)) < 1) + /* Grab */ + /* TODO: using hardcoded xmltv command at the moment */ + outlen = spawn_and_store_stdout(cmd, NULL, &outbuf); + if ( outlen < 1 ) { + tvhlog(LOG_ERR, "xmltv", "no output detected"); return NULL; - - for(i = 0; i < outlen; i++) - if(outbuf[i] < 32) - outbuf[i] = 0; - return outbuf; -} - - - -/** - * - */ -static char * -xmltv_grabber_get_version(const char *prog) -{ - int outlen; - char *outbuf; - const char *args[] = {prog, "--version", NULL}; - - if((outlen = spawn_and_store_stdout(prog, (void *)args, &outbuf)) < 1) - return NULL; - return outbuf; -} - -/** - * - */ -static int -xmltv_grabber_get_capabilities(const char *prog) -{ - int outlen; - char *outbuf; - int r; - - const char *args[] = {prog, "--capabilities", NULL}; - - if((outlen = spawn_and_store_stdout(prog, (void *)args, &outbuf)) < 1) - return 0; - - r = strstr(outbuf, "baseline") ? XG_CAP_BASELINE : 0; - r |= strstr(outbuf, "manualconfig") ? XG_CAP_MANUALCONFIG : 0; - r |= strstr(outbuf, "cache") ? XG_CAP_CACHE : 0; - r |= strstr(outbuf, "apiconfig") ? XG_CAP_APICONFIG : 0; - return r; -} - -/** - * - */ -static int -xg_desc_cmp(const xmltv_grabber_t *a, const xmltv_grabber_t *b) -{ - return strcmp(a->xg_description, b->xg_description); -} - -/** - * - */ -static int -xmltv_grabber_add(const char *path, time_t mtime) -{ - char *desc; - xmltv_grabber_t *xg; - int wasthisgrabber = 0; - - LIST_FOREACH(xg, &xmltv_grabbers, xg_link) - if(!strcmp(xg->xg_path, path)) - break; - - if(xg != NULL) { - if(xg->xg_mtime == mtime) { - xg->xg_dirty = 0; - return 0; /* Already there */ - } - wasthisgrabber = xg == xg_current; - xmltv_grabber_destroy(xg); - xg_current = NULL; - } - if((desc = xmltv_grabber_get_description(path)) == NULL) - return 0; /* Probably not a working grabber */ - - xg = calloc(1, sizeof(xmltv_grabber_t)); - xg->xg_path = strdup(path); - xg->xg_mtime = mtime; - xg->xg_description = strdup(desc); - xg->xg_capabilities = xmltv_grabber_get_capabilities(path); - tvh_str_set(&xg->xg_version, xmltv_grabber_get_version(path)); - LIST_INSERT_SORTED(&xmltv_grabbers, xg, xg_link, xg_desc_cmp); - if(wasthisgrabber) - xg_current = xg; - return 1; -} - - -/** - * - */ -static xmltv_grabber_t * -xmltv_grabber_find_by_path(const char *path) -{ - xmltv_grabber_t *xg; - - LIST_FOREACH(xg, &xmltv_grabbers, xg_link) - if(!strcmp(xg->xg_path, path)) - break; - return xg; -} - - -/** - * - */ -static int -xmltv_grabber_filter(const struct dirent *d) -{ - if(strncmp(d->d_name, "tv_grab_", 8)) - return 0; - return 1; - -} - - -/** - * - */ -static int -xmltv_scan_grabbers(const char *path) -{ - struct dirent **namelist; - int n, change = 0; - char fname[300]; - struct stat st; - - if((n = scandir(path, &namelist, xmltv_grabber_filter, NULL)) < 0) - return 0; - - while(n--) { - snprintf(fname, sizeof(fname), "%s/%s", path, namelist[n]->d_name); - if(!stat(fname, &st)) - change |= xmltv_grabber_add(fname, st.st_mtime); - free(namelist[n]); - } - free(namelist); - return change; -} - - -/** - * - */ -static void -xmltv_grabbers_index(void) -{ - xmltv_grabber_t *xg, *next; - int change; - char *path, *p, *s; - - LIST_FOREACH(xg, &xmltv_grabbers, xg_link) - xg->xg_dirty = 1; - - change = xmltv_scan_grabbers("/bin"); - change |= xmltv_scan_grabbers("/usr/bin"); - change |= xmltv_scan_grabbers("/usr/local/bin"); - // Arch linux puts xmltv grabbers in /usr/bin/perlbin/vendor - change |= xmltv_scan_grabbers("/usr/bin/perlbin/vendor"); - - if((path = getenv("PATH")) != NULL) { - p = path = strdup(path); - - while((s = strsep(&p, ":")) != NULL) - change |= xmltv_scan_grabbers(s); - free(path); } - for(xg = LIST_FIRST(&xmltv_grabbers); xg != NULL; xg = next) { - next = LIST_NEXT(xg, xg_link); - if(xg->xg_dirty) { - change = 1; - xmltv_grabber_destroy(xg); - } - } - - if(change) - xmltv_grabbers_save(); + /* Extract */ + return htsmsg_xml_deserialize(outbuf, errbuf, sizeof(errbuf)); } -/** - * - */ -static void -xmltv_grabbers_load(void) +epggrab_module_t* xmltv_init ( void ) { - xmltv_grabber_t *xg; - htsmsg_t *l, *c, *m; - htsmsg_field_t *f; - const char *path, *desc; - uint32_t u32; - - if((m = hts_settings_load("xmltv/config")) == NULL) - return; - - htsmsg_get_u32(m, "grab-interval", &xmltv_grab_interval); - htsmsg_get_u32(m, "grab-enabled", &xmltv_grab_enabled); - - if((l = htsmsg_get_list(m, "grabbers")) != NULL) { - - HTSMSG_FOREACH(f, l) { - if((c = htsmsg_get_map_by_field(f)) == NULL) - continue; - - path = htsmsg_get_str(c, "path"); - desc = htsmsg_get_str(c, "description"); - if(path == NULL || desc == NULL) - continue; - - xg = calloc(1, sizeof(xmltv_grabber_t)); - xg->xg_path = strdup(path); - xg->xg_description = strdup(desc); - - if(!htsmsg_get_u32(c, "mtime", &u32)) - xg->xg_mtime = u32; - - tvh_str_set(&xg->xg_version, htsmsg_get_str(c, "version")); - htsmsg_get_u32(c, "capabilities", &xg->xg_capabilities); - LIST_INSERT_SORTED(&xmltv_grabbers, xg, xg_link, xg_desc_cmp); - } - } - - if((path = htsmsg_get_str(m, "current-grabber")) != NULL) { - if((xg = xmltv_grabber_find_by_path(path)) != NULL) - xg_current = xg; - } - - htsmsg_destroy(m); -} - - -static void -xmltv_grabbers_save(void) -{ - htsmsg_t *array, *e, *m; - xmltv_grabber_t *xg; - - m = htsmsg_create_map(); - - array = htsmsg_create_list(); - - LIST_FOREACH(xg, &xmltv_grabbers, xg_link) { - e = htsmsg_create_map(); - htsmsg_add_str(e, "path", xg->xg_path); - htsmsg_add_str(e, "description", xg->xg_description); - if(xg->xg_version != NULL) - htsmsg_add_str(e, "version", xg->xg_version); - htsmsg_add_u32(e, "mtime", xg->xg_mtime); - htsmsg_add_u32(e, "capabilities", xg->xg_capabilities); - - htsmsg_add_msg(array, NULL, e); - } - - htsmsg_add_msg(m, "grabbers", array); - - htsmsg_add_u32(m, "grab-interval", xmltv_grab_interval); - htsmsg_add_u32(m, "grab-enabled", xmltv_grab_enabled); - if(xg_current != NULL) - htsmsg_add_str(m, "current-grabber", xg_current->xg_path); - - hts_settings_save(m, "xmltv/config"); - htsmsg_destroy(m); -} - - -/** - * - */ -htsmsg_t * -xmltv_list_grabbers(void) -{ - htsmsg_t *array, *m; - xmltv_grabber_t *xg; - - lock_assert(&xmltv_mutex); - - xmltv_grabbers_index(); - - array = htsmsg_create_list(); - - LIST_FOREACH(xg, &xmltv_grabbers, xg_link) { - m = htsmsg_create_map(); - htsmsg_add_str(m, "identifier", xg->xg_path); - htsmsg_add_str(m, "name", xg->xg_description); - htsmsg_add_str(m, "version", xg->xg_version ?: "Unknown version"); - htsmsg_add_u32(m, "apiconfig", !!(xg->xg_capabilities & XG_CAP_APICONFIG)); - htsmsg_add_msg(array, NULL, m); - } - - return array; -} - - -/** - * - */ -const char * -xmltv_get_current_grabber(void) -{ - lock_assert(&xmltv_mutex); - return xg_current ? xg_current->xg_description : NULL; -} - - -/** - * - */ -void -xmltv_set_current_grabber(const char *desc) -{ - xmltv_grabber_t *xg; - - lock_assert(&xmltv_mutex); - - if(desc != NULL) { - LIST_FOREACH(xg, &xmltv_grabbers, xg_link) - if(!strcmp(xg->xg_description, desc)) - break; - if(xg == NULL) - return; - } else { - xg = NULL; - } - - if(xg_current == xg) - return; - - xg_current = xg; - - xmltv_confver++; - pthread_cond_signal(&xmltv_cond); - xmltv_grabbers_save(); -} - -/** - * - */ -void -xmltv_set_grab_interval(int s) -{ - lock_assert(&xmltv_mutex); - - xmltv_grab_interval = s; - xmltv_confver++; - pthread_cond_signal(&xmltv_cond); - xmltv_grabbers_save(); - -} - -/** - * - */ -void -xmltv_set_grab_enable(int on) -{ - lock_assert(&xmltv_mutex); - - xmltv_grab_enabled = on; - xmltv_confver++; - pthread_cond_signal(&xmltv_cond); - xmltv_grabbers_save(); + 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; } diff --git a/src/epggrab/xmltv.h b/src/epggrab/xmltv.h index 10819219..3c501a13 100644 --- a/src/epggrab/xmltv.h +++ b/src/epggrab/xmltv.h @@ -16,46 +16,11 @@ * along with this program. If not, see . */ -#ifndef EPG_XMLTV_H -#define EPG_XMLTV_H +#ifndef EPGGRAB_XMLTV_H +#define EPGGRAB_XMLTV_H -#include "channels.h" +#include "epggrab.h" -RB_HEAD(xmltv_channel_tree, xmltv_channel); -LIST_HEAD(xmltv_channel_list, xmltv_channel); +epggrab_module_t *xmltv_init ( void ); -/** - * A channel in the XML-TV world - */ -typedef struct xmltv_channel { - RB_ENTRY(xmltv_channel) xc_link; - char *xc_identifier; - - LIST_ENTRY(xmltv_channel) xc_displayname_link; - char *xc_displayname; - - char *xc_icon; - - struct channel_list xc_channels; /* Target channel(s) */ - -} xmltv_channel_t; - -void xmltv_init(void); - -xmltv_channel_t *xmltv_channel_find(const char *id, int create); - -xmltv_channel_t *xmltv_channel_find_by_displayname(const char *name); - -htsmsg_t *xmltv_list_grabbers(void); - -const char *xmltv_get_current_grabber(void); -void xmltv_set_current_grabber(const char *path); -void xmltv_set_grab_interval(int s); -void xmltv_set_grab_enable(int on); - -extern struct xmltv_channel_list xmltv_displaylist; -extern uint32_t xmltv_grab_interval; -extern uint32_t xmltv_grab_enabled; -extern pthread_mutex_t xmltv_mutex; - -#endif /* EPG_XMLTV_H__ */ +#endif