From b5ce0f89f985dd63e78325619054b325e9ea4dae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96man?= Date: Fri, 5 Sep 2008 16:02:41 +0000 Subject: [PATCH] Initial XMLTV support --- Makefile | 9 +- channels.c | 111 ++++- channels.h | 13 +- epg.c | 37 ++ epg.h | 2 + epg_xmltv.c | 828 ------------------------------------- epg_xmltv.h | 100 ----- main.c | 20 + tvhead.h | 4 + webui/extjs.c | 53 +++ webui/static/app/chconf.js | 34 +- webui/static/app/ext.css | 1 - xmltv.c | 440 ++++++++++++++++++++ xmltv.h | 51 +++ 14 files changed, 750 insertions(+), 953 deletions(-) delete mode 100644 epg_xmltv.c delete mode 100644 epg_xmltv.h create mode 100644 xmltv.c create mode 100644 xmltv.h diff --git a/Makefile b/Makefile index 33c9c0f2..45e5c38a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -include ../config.mak -SRCS = main.c access.c dtable.c tcp.c http.c notify.c epg.c +SRCS = main.c access.c dtable.c tcp.c http.c notify.c epg.c xmltv.c spawn.c SRCS += buffer.c channels.c subscriptions.c transports.c @@ -41,13 +41,6 @@ DLIBS += $(FFMPEG_DLIBS) SLIBS += $(FFMPEG_SLIBS) CFLAGS += $(FFMPEG_CFLAGS) -# XML - -DLIBS += ${LIBXML2_DLIBS} -SLIBS += ${LIBXML2_SLIBS} -CFLAGS += ${LIBXML2_CFLAGS} - - DLIBS += -lpthread include ../build/prog.mk diff --git a/channels.c b/channels.c index c41cd556..a08add97 100644 --- a/channels.c +++ b/channels.c @@ -38,6 +38,9 @@ #include "epg.h" #include "pvr.h" #include "autorec.h" +#include "xmltv.h" + +struct channel_list channels_not_xmltv_mapped; struct channel_tree channel_name_tree; static struct channel_tree channel_identifier_tree; @@ -117,7 +120,7 @@ channel_set_name(channel_t *ch, const char *name) if(isalnum(c)) *cp++ = c; else - *cp++ = '-'; + *cp++ = '_'; } *cp = 0; @@ -128,7 +131,6 @@ channel_set_name(channel_t *ch, const char *name) } - /** * */ @@ -147,7 +149,7 @@ channel_create(const char *name) ch = calloc(1, sizeof(channel_t)); RB_INIT(&ch->ch_epg_events); - + LIST_INSERT_HEAD(&channels_not_xmltv_mapped, ch, ch_xc_link); channel_set_name(ch, name); ch->ch_id = id; @@ -202,10 +204,67 @@ static struct strtab commercial_detect_tab[] = { /** * */ -void +static void +channel_load_one(htsmsg_t *c, int id) +{ + channel_t *ch; + const char *s; + const char *name = htsmsg_get_str(c, "name"); + + if(name == NULL) + return; + + ch = calloc(1, sizeof(channel_t)); + ch->ch_id = id; + if(RB_INSERT_SORTED(&channel_identifier_tree, ch, + ch_identifier_link, chidcmp)) { + /* ID collision, should not happen unless there is something + wrong in the setting storage */ + free(ch); + return; + } + + RB_INIT(&ch->ch_epg_events); + + channel_set_name(ch, name); + + if((s = htsmsg_get_str(c, "icon")) != NULL) + ch->ch_icon = strdup(s); + + if((s = htsmsg_get_str(c, "xmltv-channel")) != NULL && + (ch->ch_xc = xmltv_channel_find(s, 0)) != NULL) + LIST_INSERT_HEAD(&ch->ch_xc->xc_channels, ch, ch_xc_link); + else + LIST_INSERT_HEAD(&channels_not_xmltv_mapped, ch, ch_xc_link); +} + + +/** + * + */ +static void channels_load(void) { + htsmsg_t *l, *c; + htsmsg_field_t *f; + if((l = hts_settings_load("channels")) != NULL) { + HTSMSG_FOREACH(f, l) { + if((c = htsmsg_get_msg_by_field(f)) == NULL) + continue; + channel_load_one(c, atoi(f->hmf_name)); + } + } +} + + +/** + * + */ +void +channels_init(void) +{ + channels_load(); } @@ -220,13 +279,19 @@ channel_save(channel_t *ch) lock_assert(&global_lock); + htsmsg_add_str(m, "name", ch->ch_name); + + if(ch->ch_xc != NULL) + htsmsg_add_str(m, "xmltv-channel", ch->ch_xc->xc_identifier); + if(ch->ch_icon != NULL) htsmsg_add_str(m, "icon", ch->ch_icon); - htsmsg_add_str(m, "commercial_detect", + htsmsg_add_str(m, "commercial-detect", val2str(ch->ch_commercial_detection, commercial_detect_tab) ?: "?"); - hts_settings_save(m, "channels/%s", ch->ch_name); + + hts_settings_save(m, "channels/%d", ch->ch_id); htsmsg_destroy(m); } @@ -246,8 +311,6 @@ channel_rename(channel_t *ch, const char *newname) tvhlog(LOG_NOTICE, "channels", "Channel \"%s\" renamed to \"%s\"", ch->ch_name, newname); - hts_settings_remove("channels/%s", ch->ch_name); - RB_REMOVE(&channel_name_tree, ch, ch_name_link); channel_set_name(ch, newname); @@ -291,11 +354,13 @@ channel_delete(channel_t *ch) abort();//autorec_destroy_by_channel(ch); - hts_settings_remove("channels/%s", ch->ch_name); + hts_settings_remove("channels/%d", ch->ch_id); RB_REMOVE(&channel_name_tree, ch, ch_name_link); RB_REMOVE(&channel_identifier_tree, ch, ch_identifier_link); + LIST_REMOVE(ch, ch_xc_link); + free(ch->ch_name); free(ch->ch_sname); free(ch->ch_icon); @@ -338,7 +403,33 @@ channel_set_icon(channel_t *ch, const char *icon) { lock_assert(&global_lock); + if(ch->ch_icon != NULL && !strcmp(ch->ch_icon, icon)) + return; + free(ch->ch_icon); - ch->ch_icon = icon ? strdup(icon) : NULL; + ch->ch_icon = strdup(icon); + channel_save(ch); +} + + +/** + * + */ +void +channel_set_xmltv_source(channel_t *ch, xmltv_channel_t *xc) +{ + lock_assert(&global_lock); + + if(xc == ch->ch_xc) + return; + + LIST_REMOVE(ch, ch_xc_link); + + if(xc == NULL) { + LIST_INSERT_HEAD(&channels_not_xmltv_mapped, ch, ch_xc_link); + } else { + LIST_INSERT_HEAD(&xc->xc_channels, ch, ch_xc_link); + } + ch->ch_xc = xc; channel_save(ch); } diff --git a/channels.h b/channels.h index 9f78f0b4..e06a8693 100644 --- a/channels.h +++ b/channels.h @@ -19,6 +19,7 @@ #ifndef CHANNELS_H #define CHANNELS_H + /* * Channel definition */ @@ -53,11 +54,12 @@ typedef struct channel { struct autorec_list ch_autorecs; + struct xmltv_channel *ch_xc; + LIST_ENTRY(channel) ch_xc_link; + } channel_t; - - -void channels_load(void); +void channels_init(void); channel_t *channel_find_by_name(const char *name, int create); @@ -75,4 +77,9 @@ void channel_merge(channel_t *dst, channel_t *src); void channel_set_icon(channel_t *ch, const char *icon); +struct xmltv_channel; +void channel_set_xmltv_source(channel_t *ch, struct xmltv_channel *xc); + +extern struct channel_list channels_not_xmltv_mapped; + #endif /* CHANNELS_H */ diff --git a/epg.c b/epg.c index 23c47f59..2c678f76 100644 --- a/epg.c +++ b/epg.c @@ -37,14 +37,27 @@ e_ch_cmp(const event_t *a, const event_t *b) } +/** + * + */ +static void +epg_event_changed(event_t *e) +{ + /* nothing atm */ +} + + /** * */ void epg_event_set_title(event_t *e, const char *title) { + if(e->e_title != NULL && !strcmp(e->e_title, title)) + return; free((void *)e->e_title); e->e_title = strdup(title); + epg_event_changed(e); } @@ -54,8 +67,25 @@ epg_event_set_title(event_t *e, const char *title) void epg_event_set_desc(event_t *e, const char *desc) { + if(e->e_desc != NULL && !strcmp(e->e_desc, desc)) + return; free((void *)e->e_desc); e->e_desc = strdup(desc); + epg_event_changed(e); +} + + +/** + * + */ +void +epg_event_set_duration(event_t *e, int duration) +{ + if(e->e_duration == duration) + return; + + e->e_duration = duration; + epg_event_changed(e); } @@ -65,12 +95,17 @@ epg_event_set_desc(event_t *e, const char *desc) void epg_event_set_content_type(event_t *e, epg_content_type_t *ect) { + if(e->e_content_type == ect) + return; + if(e->e_content_type != NULL) LIST_REMOVE(e, e_content_type_link); e->e_content_type = ect; if(ect != NULL) LIST_INSERT_HEAD(&ect->ect_events, e, e_content_type_link); + + epg_event_changed(e); } @@ -97,7 +132,9 @@ epg_event_find_by_start(channel_t *ch, time_t start, int create) /* New entry was inserted */ e = skel; skel = NULL; + epg_event_changed(e); } + return e; } diff --git a/epg.h b/epg.h index cf21a340..b4f02fd5 100644 --- a/epg.h +++ b/epg.h @@ -73,6 +73,8 @@ void epg_event_set_title(event_t *e, const char *title); void epg_event_set_desc(event_t *e, const char *desc); +void epg_event_set_duration(event_t *e, int duration); + void epg_event_set_content_type(event_t *e, epg_content_type_t *ect); event_t *epg_event_find_by_start(channel_t *ch, time_t start, int create); diff --git a/epg_xmltv.c b/epg_xmltv.c deleted file mode 100644 index 27aef910..00000000 --- a/epg_xmltv.c +++ /dev/null @@ -1,828 +0,0 @@ -/* - * Electronic Program Guide - xmltv interface - * Copyright (C) 2007 Andreas Öman - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#define _GNU_SOURCE -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include - -#include "tvhead.h" -#include "channels.h" -#include "epg.h" -#include "epg_xmltv.h" -#include "spawn.h" -#include "intercom.h" -#include "notify.h" -#include "dispatch.h" - -int xmltv_globalstatus; -struct xmltv_grabber_list xmltv_grabbers; -struct xmltv_grabber_queue xmltv_grabber_workq; -static pthread_mutex_t xmltv_work_lock = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t xmltv_work_cond = PTHREAD_COND_INITIALIZER; -static void xmltv_config_load(void); - - -static xmltv_channel_t * -xc_find(xmltv_grabber_t *xg, const char *name) -{ - xmltv_channel_t *xc; - - TAILQ_FOREACH(xc, &xg->xg_channels, xc_link) - if(!strcmp(xc->xc_name, name)) - return xc; - - xc = calloc(1, sizeof(xmltv_channel_t)); - xc->xc_name = strdup(name); - TAILQ_INIT(&xc->xc_events); - TAILQ_INSERT_TAIL(&xg->xg_channels, xc, xc_link); - return xc; -} - - - - -#define XML_FOREACH(n) for(; (n) != NULL; (n) = (n)->next) - -static void -xmltv_parse_channel(xmltv_grabber_t *xg, xmlNode *n, char *chid) -{ - char *t, *c; - xmltv_channel_t *xc; - - xc = xc_find(xg, chid); - - XML_FOREACH(n) { - - c = (char *)xmlNodeGetContent(n); - - if(!strcmp((char *)n->name, "display-name")) { - free((void *)xc->xc_displayname); - xc->xc_displayname = strdup(c); - } - - if(!strcmp((char *)n->name, "icon")) { - t = (char *)xmlGetProp(n, (unsigned char *)"src"); - - free(xc->xc_icon_url); - xc->xc_icon_url = strdup(t); - xmlFree(t); - } - xmlFree(c); - } -} - - -/* 20060427110000 */ -/* 0123456789abcd */ - -static time_t -str2time(char *str) -{ - char str0[30]; - struct tm tim; - - strcpy(str0, str); - - tim.tm_sec = atoi(str0 + 0xc); - str0[0xc] = 0; - tim.tm_min = atoi(str0 + 0xa); - str0[0xa] = 0; - tim.tm_hour = atoi(str0 + 0x8); - str0[0x8] = 0; - tim.tm_mday = atoi(str0 + 0x6); - str0[0x6] = 0; - tim.tm_mon = atoi(str0 + 0x4) - 1; - str0[0x4] = 0; - tim.tm_year = atoi(str0) - 1900; - - tim.tm_isdst = -1; - return mktime(&tim); - -} - - -static void -xmltv_parse_programme(xmltv_grabber_t *xg, xmlNode *n, - char *chid, char *startstr, char *stopstr) -{ - char *c; - event_t *e; - time_t start, stop; - int duration; - xmltv_channel_t *xc; - - xc = xc_find(xg, chid); - xc->xc_updated = 1; - start = str2time(startstr); - stop = str2time(stopstr); - - duration = stop - start; - - e = epg_event_build(&xc->xc_events, start, duration); - if(e == NULL) - return; - - XML_FOREACH(n) { - - c = (char *)xmlNodeGetContent(n); - - if(!strcmp((char *)n->name, "title")) { - epg_event_set_title(e, c); - } else if(!strcmp((char *)n->name, "desc")) { - epg_event_set_desc(e, c); - } - xmlFree(c); - } -} - - -static void -xmltv_parse_tv(xmltv_grabber_t *xg, xmlNode *n) -{ - char *chid; - char *start; - char *stop; - - XML_FOREACH(n) { - - if(n->type != XML_ELEMENT_NODE) - continue; - - if(!strcmp((char *)n->name, "channel")) { - chid = (char *)xmlGetProp(n, (unsigned char *)"id"); - if(chid != NULL) { - xmltv_parse_channel(xg, n->children, chid); - xmlFree(chid); - } - } else if(!strcmp((char *)n->name, "programme")) { - chid = (char *)xmlGetProp(n, (unsigned char *)"channel"); - start = (char *)xmlGetProp(n, (unsigned char *)"start"); - stop = (char *)xmlGetProp(n, (unsigned char *)"stop"); - - xmltv_parse_programme(xg, n->children, chid, start, stop); - - xmlFree(chid); - xmlFree(start); - xmlFree(stop); - } - } -} - - - -static void -xmltv_parse_root(xmltv_grabber_t *xg, xmlNode *n) -{ - XML_FOREACH(n) { - if(n->type == XML_ELEMENT_NODE && !strcmp((char *)n->name, "tv")) - xmltv_parse_tv(xg, n->children); - } -} - - -/* - * - */ -static void -xmltv_flush_events(xmltv_grabber_t *xg) -{ - xmltv_channel_t *xc; - event_t *e; - - TAILQ_FOREACH(xc, &xg->xg_channels, xc_link) { - while((e = TAILQ_FIRST(&xc->xc_events)) != NULL) { - TAILQ_REMOVE(&xc->xc_events, e, e_channel_link); - epg_event_free(e); - } - } -} - - - - -/* - * - */ -static channel_t * -xmltv_resolve_by_events(xmltv_channel_t *xc) -{ - channel_t *ch; - event_t *ec, *ex; - time_t now; - int cnt, i; - - time(&now); - - for(i = 0; i < 4; i++) { - now += 7200; - - ex = epg_event_find_by_time0(&xc->xc_events, now); - if(ex == NULL) - break; - - RB_FOREACH(ch, &channel_name_tree, ch_name_link) { - ec = epg_event_find_by_time0(&ch->ch_epg_events, now); - cnt = 0; - - while(1) { - if((cnt >= 15 && ec == NULL) || cnt >= 30) - return ch; - - if(ec == NULL || ex == NULL) - break; - - if(ec->e_start != ex->e_start || ec->e_duration != ex->e_duration) - break; - - ec = TAILQ_NEXT(ec, e_channel_link); - ex = TAILQ_NEXT(ex, e_channel_link); - cnt++; - } - } - } - return NULL; -} - - -/* - * - */ -static void -xmltv_transfer_events(xmltv_grabber_t *xg) -{ - xmltv_channel_t *xc; - channel_t *ch; - int how; - - TAILQ_FOREACH(xc, &xg->xg_channels, xc_link) { - if(xc->xc_disabled) - continue; - - if(xc->xc_channel != NULL) { - ch = channel_find_by_name(xc->xc_channel, 0); - if(ch == NULL) - continue; - - } else { - - how = 0; - ch = channel_find_by_name(xc->xc_displayname, 0); - if(ch == NULL) { - ch = xmltv_resolve_by_events(xc); - - if(ch == NULL) - continue; - how = 1; - } - if(strcmp(xc->xc_bestmatch ?: "", ch->ch_name)) { - tvhlog(LOG_DEBUG, "xmltv", - "xmltv: mapped \"%s\" (%s) to \"%s\" by %s", - xc->xc_displayname, xc->xc_name, ch->ch_name, - how ? "consequtive-event-matching" : "name"); - free(xc->xc_bestmatch); - xc->xc_bestmatch = strdup(ch->ch_name); - xmltv_config_save(); - } - } - - if(xc->xc_updated == 0) - continue; - - xc->xc_updated = 0; - epg_transfer_events(ch, &xc->xc_events, xc->xc_name, xc->xc_icon_url); - } -} - - -/* - * Execute grabber and parse result, - * - * Return nextstate - */ -static int -xmltv_grab(xmltv_grabber_t *xg) -{ - xmlDoc *doc = NULL; - xmlNode *root_element; - const char *prog; - char *outbuf; - int outlen; - - prog = xg->xg_path; - - syslog(LOG_INFO, "xmltv: Starting grabber \"%s\"", prog); - - outlen = spawn_and_store_stdout(prog, NULL, &outbuf); - if(outlen < 1) { - syslog(LOG_ERR, "xmltv: No output from \"%s\"", prog); - return XMLTV_GRAB_UNCONFIGURED; - } - - doc = xmlParseMemory(outbuf, outlen); - if(doc == NULL) { - syslog(LOG_ERR, "xmltv: Error while parsing output from \"%s\"", prog); - free(outbuf); - return XMLTV_GRAB_DYSFUNCTIONAL; - } - - pthread_mutex_lock(&xg->xg_mutex); - - xmltv_flush_events(xg); - - root_element = xmlDocGetRootElement(doc); - xmltv_parse_root(xg, root_element); - - pthread_mutex_unlock(&xg->xg_mutex); - - xmlFreeDoc(doc); - xmlCleanupParser(); - syslog(LOG_INFO, "xmltv: EPG sucessfully loaded and parsed"); - free(outbuf); - return XMLTV_GRAB_OK; -} - - - - -/** - * - */ -static void -xmltv_thread_grabber_init_done(icom_t *ic, const char *payload) -{ - htsmsg_t *m = htsmsg_create(); - - htsmsg_add_u32(m, "initialized", 1); - if(payload != NULL) - htsmsg_add_str(m, "payload", payload); - icom_send_msg_from_thread(ic, m); - htsmsg_destroy(m); -} - - - -/** - * - */ -static int -grabbercmp(xmltv_grabber_t *a, xmltv_grabber_t *b) -{ - return strcasecmp(a->xg_title, b->xg_title); -} - - -/** - * Enlist all active grabbers (for user to choose among) - * - * We send all these in a message back to the main thread - */ -static int -xmltv_find_grabbers(icom_t *ic, const char *prog) -{ - char *outbuf; - int outlen; - - outlen = spawn_and_store_stdout(prog, NULL, &outbuf); - if(outlen < 1) - return -1; - - xmltv_thread_grabber_init_done(ic, outbuf); - free(outbuf); - return 0; -} - - -/** - * Parse list of all grabbers - */ -static void -xmltv_parse_grabbers(char *list) -{ - char *s, *a, *b; - xmltv_grabber_t *xg; - int n = 0; - char buf[40]; - - s = list; - - while(1) { - a = s; - - while(*s && *s != '|') - s++; - - if(!*s) - break; - *s++ = 0; - - b = s; - - while(*s > 31) - s++; - - if(!*s) - break; - *s++ = 0; - - while(*s < 31 && *s > 0) - s++; - - LIST_FOREACH(xg, &xmltv_grabbers, xg_link) - if(!strcmp(b, xg->xg_title)) - break; - - if(xg != NULL) - continue; - - xg = calloc(1, sizeof(xmltv_grabber_t)); - pthread_mutex_init(&xg->xg_mutex, NULL); - - TAILQ_INIT(&xg->xg_channels); - xg->xg_path = strdup(a); - xg->xg_title = strdup(b); - - snprintf(buf, sizeof(buf), "xmlgrabber_%d", n++); - xg->xg_identifier = strdup(buf); - - LIST_INSERT_SORTED(&xmltv_grabbers, xg, xg_link, grabbercmp); - } - xmltv_config_load(); -} - - - -/* - * - */ -static void * -xmltv_thread(void *aux) -{ - icom_t *ic = aux; - htsmsg_t *m; - xmltv_grabber_t *xg; - int r; - - /* Start by finding all available grabbers, we try with a few - different locations */ - - if(xmltv_find_grabbers(ic, "/usr/bin/tv_find_grabbers") && - xmltv_find_grabbers(ic, "/bin/tv_find_grabbers") && - xmltv_find_grabbers(ic, "/usr/local/bin/tv_find_grabbers")) { - xmltv_thread_grabber_init_done(ic, NULL); - return NULL; - } - - pthread_mutex_lock(&xmltv_work_lock); - - while(1) { - - while((xg = TAILQ_FIRST(&xmltv_grabber_workq)) == NULL) - pthread_cond_wait(&xmltv_work_cond, &xmltv_work_lock); - - xg->xg_on_work_link = 0; - TAILQ_REMOVE(&xmltv_grabber_workq, xg, xg_work_link); - pthread_mutex_unlock(&xmltv_work_lock); - r = xmltv_grab(xg); - - m = htsmsg_create(); - htsmsg_add_u32(m, "grab_completed", r); - htsmsg_add_str(m, "identifier", xg->xg_identifier); - icom_send_msg_from_thread(ic, m); - htsmsg_destroy(m); - - pthread_mutex_lock(&xmltv_work_lock); - } - return NULL; -} - -/** - * - */ -static void -xmltv_grabber_enqueue(xmltv_grabber_t *xg) -{ - pthread_mutex_lock(&xmltv_work_lock); - - if(!xg->xg_on_work_link) { - xg->xg_on_work_link = 1; - TAILQ_INSERT_TAIL(&xmltv_grabber_workq, xg, xg_work_link); - pthread_cond_signal(&xmltv_work_cond); - } - pthread_mutex_unlock(&xmltv_work_lock); -} - - -/** - * Enqueue a new grabing - */ -static void -regrab(void *aux, int64_t now) -{ - xmltv_grabber_t *xg = aux; - xmltv_grabber_enqueue(xg); -} - -/** - * - */ -static void -xmltv_xfer(void *aux, int64_t now) -{ - xmltv_grabber_t *xg = aux; - int t; - - /* We don't want to stall waiting for the xml decoding which - can take quite some time, instead retry in a second if we fail - to obtain mutex */ - - if(pthread_mutex_trylock(&xg->xg_mutex) == EBUSY) { - t = 1; - } else { - xmltv_transfer_events(xg); - pthread_mutex_unlock(&xg->xg_mutex); - t = 60; - } - - dtimer_arm(&xg->xg_xfer_timer, xmltv_xfer, xg, t); -} - -/** - * - */ -static void -icom_cb(void *opaque, htsmsg_t *m) -{ - const char *s; - char *t; - xmltv_grabber_t *xg; - uint32_t v; - - if(!htsmsg_get_u32(m, "initialized", &v)) { - /* Grabbers loaded */ - - if((s = htsmsg_get_str(m, "payload")) != NULL) { - t = strdupa(s); - xmltv_parse_grabbers(t); - xmltv_globalstatus = XMLTVSTATUS_RUNNING; - - LIST_FOREACH(xg, &xmltv_grabbers, xg_link) - if(xg->xg_enabled) - xmltv_grabber_enqueue(xg); - - } else { - xmltv_globalstatus = XMLTVSTATUS_FIND_GRABBERS_NOT_FOUND; - } - } else if(!htsmsg_get_u32(m, "grab_completed", &v) && - (s = htsmsg_get_str(m, "identifier")) != NULL && - (xg = xmltv_grabber_find(s)) != NULL) { - - xg->xg_status = v; - dtimer_arm(&xg->xg_grab_timer, regrab, xg, v == XMLTV_GRAB_OK ? 3600 : 60); - dtimer_arm(&xg->xg_xfer_timer, xmltv_xfer, xg, 1); - // notify_xmltv_grabber_status_change(xg); - } - htsmsg_destroy(m); -} - -/** - * Locate a grabber by its id - */ -xmltv_grabber_t * -xmltv_grabber_find(const char *id) -{ - xmltv_grabber_t *xg; - LIST_FOREACH(xg, &xmltv_grabbers, xg_link) - if(!strcmp(id, xg->xg_identifier)) - return xg; - return NULL; -} - - - -/** - * - */ -void -xmltv_init(void) -{ - pthread_t ptid; - icom_t *ic = icom_create(icom_cb, NULL); - - TAILQ_INIT(&xmltv_grabber_workq); - - pthread_create(&ptid, NULL, xmltv_thread, ic); -} - -/** - * - */ -const char * -xmltv_grabber_status_long(xmltv_grabber_t *xg) -{ - static char buf[1000]; - - if(xg->xg_enabled == 0) { - return "Disabled"; - } else if(xg->xg_on_work_link) { - return "Enqueued for grabbing, please wait"; - } else switch(xg->xg_status) { - - case XMLTV_GRAB_WORKING: - return "Grabbing, please wait"; - - case XMLTV_GRAB_UNCONFIGURED: - snprintf(buf, sizeof(buf), - "This grabber is not configured.

You need to configure it " - "manually by running '%s --configure' in a shell", - xg->xg_path); - return buf; - - case XMLTV_GRAB_DYSFUNCTIONAL: - snprintf(buf, sizeof(buf), - "This grabber does not produce valid XML, please check " - "manually by running '%s' in a shell", - xg->xg_path); - return buf; - - case XMLTV_GRAB_OK: - return "Idle"; - } - - return "Unknown status"; -} - - -/** - * - */ -const char * -xmltv_grabber_status(xmltv_grabber_t *xg) -{ - if(xg->xg_enabled == 0) { - return "Disabled"; - } else if(xg->xg_on_work_link) { - return "Grabbing"; - } else switch(xg->xg_status) { - case XMLTV_GRAB_WORKING: - return "Grabbing"; - - case XMLTV_GRAB_UNCONFIGURED: - return "Unconfigured"; - case XMLTV_GRAB_DYSFUNCTIONAL: - return "Dysfunctional"; - case XMLTV_GRAB_OK: - return "Idle"; - } - return "Unknown"; -} - - -/* - * Write grabber configuration - */ -void -xmltv_config_save(void) -{ - char buf[PATH_MAX]; - xmltv_grabber_t *xg; - xmltv_channel_t *xc; - FILE *fp; - - snprintf(buf, sizeof(buf), "%s/xmltv-settings.cfg", settings_dir); - - if((fp = settings_open_for_write(buf)) != NULL) { - LIST_FOREACH(xg, &xmltv_grabbers, xg_link) { - - fprintf(fp, "grabber {\n"); - fprintf(fp, "\ttitle = %s\n", xg->xg_title); - fprintf(fp, "\tenabled = %d\n", xg->xg_enabled); - - - TAILQ_FOREACH(xc, &xg->xg_channels, xc_link) { - fprintf(fp, "\tchannel {\n"); - fprintf(fp, "\t\tdisplayname = %s\n", xc->xc_displayname); - if(xc->xc_icon_url != NULL) - fprintf(fp, "\t\ticon = %s\n", xc->xc_icon_url); - if(xc->xc_bestmatch != NULL) - fprintf(fp, "\t\tbestmatch = %s\n", xc->xc_bestmatch); - fprintf(fp, "\t\tname = %s\n", xc->xc_name); - if(xc->xc_disabled) - fprintf(fp, "\t\tmapping = disabled\n"); - else - fprintf(fp, "\t\tmapping = %s\n", xc->xc_channel ?: "auto"); - fprintf(fp, "\t}\n"); - } - fprintf(fp, "}\n"); - } - fclose(fp); - } -} - -/** - * - */ -static void -xmltv_config_load(void) -{ - struct config_head cl; - config_entry_t *ce, *ce2; - char buf[PATH_MAX]; - const char *title, *name, *s; - xmltv_channel_t *xc; - xmltv_grabber_t *xg; - - TAILQ_INIT(&cl); - - snprintf(buf, sizeof(buf), "%s/xmltv-settings.cfg", settings_dir); - - config_read_file0(buf, &cl); - - pthread_mutex_lock(&xmltv_work_lock); - - TAILQ_FOREACH(ce, &cl, ce_link) { - if(ce->ce_type != CFG_SUB || strcasecmp("grabber", ce->ce_key)) - continue; - - if((title = config_get_str_sub(&ce->ce_sub, "title", NULL)) == NULL) - continue; - - LIST_FOREACH(xg, &xmltv_grabbers, xg_link) - if(!strcmp(title, xg->xg_title)) - break; - if(xg == NULL) - continue; - - xg->xg_enabled = atoi(config_get_str_sub(&ce->ce_sub, "enabled", "0")); - - TAILQ_FOREACH(ce2, &ce->ce_sub, ce_link) { - if(ce2->ce_type != CFG_SUB || strcasecmp("channel", ce2->ce_key)) - continue; - - if((name = config_get_str_sub(&ce2->ce_sub, "name", NULL)) == NULL) - continue; - - xc = xc_find(xg, name); - - if((s = config_get_str_sub(&ce2->ce_sub, "displayname", NULL)) != NULL) - xc->xc_displayname = strdup(s); - - if((s = config_get_str_sub(&ce2->ce_sub, "bestmatch", NULL)) != NULL) - xc->xc_bestmatch = strdup(s); - - if((s = config_get_str_sub(&ce2->ce_sub, "icon", NULL)) != NULL) - xc->xc_icon_url = strdup(s); - - if((s = config_get_str_sub(&ce2->ce_sub, "mapping", NULL)) != NULL) { - xc->xc_channel = NULL; - if(!strcmp(s, "disabled")) { - xc->xc_disabled = 1; - } else if(!strcmp(s, "auto")) { - } else { - xc->xc_channel = strdup(s); - } - } - } - } - - pthread_mutex_unlock(&xmltv_work_lock); - - config_free0(&cl); -} - -void -xmltv_grabber_enable(xmltv_grabber_t *xg) -{ - xg->xg_enabled = 1; - xmltv_grabber_enqueue(xg); - xmltv_config_save(); -} diff --git a/epg_xmltv.h b/epg_xmltv.h deleted file mode 100644 index e9e59e33..00000000 --- a/epg_xmltv.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Electronic Program Guide - xmltv interface - * Copyright (C) 2007 Andreas Öman - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef XMLTV_H -#define XMLTV_H - -#define XMLTV_GRAB_WORKING 0 -#define XMLTV_GRAB_UNCONFIGURED 1 -#define XMLTV_GRAB_DYSFUNCTIONAL 2 -#define XMLTV_GRAB_OK 3 - - - -LIST_HEAD(xmltv_grabber_list, xmltv_grabber); -TAILQ_HEAD(xmltv_grabber_queue, xmltv_grabber); - -TAILQ_HEAD(xmltv_channel_queue, xmltv_channel); - -typedef struct xmltv_grabber { - LIST_ENTRY(xmltv_grabber) xg_link; - char *xg_path; - char *xg_title; - char *xg_identifier; - - TAILQ_ENTRY(xmltv_grabber) xg_work_link; - int xg_on_work_link; - - int xg_enabled; - - int xg_status; - - dtimer_t xg_grab_timer; - dtimer_t xg_xfer_timer; - - struct xmltv_channel_queue xg_channels; - - pthread_mutex_t xg_mutex; - -} xmltv_grabber_t; - - - -/** - * A channel in the XML-TV world - */ -typedef struct xmltv_channel { - TAILQ_ENTRY(xmltv_channel) xc_link; - char *xc_name; - char *xc_displayname; - - char *xc_bestmatch; /* Best matching channel */ - - char *xc_channel; - - int xc_disabled; - - char *xc_icon_url; - - struct event_queue xc_events; - - int xc_updated; - -} xmltv_channel_t; - - -#define XMLTVSTATUS_STARTING 0 -#define XMLTVSTATUS_FIND_GRABBERS_NOT_FOUND 1 -#define XMLTVSTATUS_RUNNING 2 - -extern int xmltv_globalstatus; -extern struct xmltv_grabber_list xmltv_grabbers; - -void xmltv_init(void); - -const char *xmltv_grabber_status(xmltv_grabber_t *xg); - -void xmltv_grabber_enable(xmltv_grabber_t *xg); - -xmltv_grabber_t *xmltv_grabber_find(const char *id); - -const char *xmltv_grabber_status_long(xmltv_grabber_t *xg); - -void xmltv_config_save(void); - -#endif /* __XMLTV_H__ */ diff --git a/main.c b/main.c index 3540056e..b3f3f03f 100644 --- a/main.c +++ b/main.c @@ -42,6 +42,8 @@ #include "http.h" #include "webui/webui.h" #include "dvb/dvb.h" +#include "xmltv.h" +#include "spawn.h" #include #include @@ -136,6 +138,8 @@ mainloop(void) while(running) { sleep(1); + spawn_reaper(); + time(&dispatch_clock); comet_flush(); /* Flush idle comet mailboxes */ @@ -243,6 +247,10 @@ main(int argc, char **argv) // signal(SIGTERM, doexit); // signal(SIGINT, doexit); + xmltv_init(); /* Must be initialized before channels */ + + channels_init(); + access_init(); tcp_server_init(); @@ -388,3 +396,15 @@ tvhlog(int severity, const char *subsys, const char *fmt, ...) comet_mailbox_add_message(m); htsmsg_destroy(m); } + + +/** + * + */ +void +tvh_str_set(char **strp, const char *src) +{ + free(*strp); + *strp = src ? strdup(src) : NULL; +} + diff --git a/tvhead.h b/tvhead.h index 2b77c8be..dccd0216 100644 --- a/tvhead.h +++ b/tvhead.h @@ -84,6 +84,7 @@ void gtimer_disarm(gtimer_t *gti); LIST_HEAD(th_subscription_list, th_subscription); RB_HEAD(channel_tree, channel); TAILQ_HEAD(channel_queue, channel); +LIST_HEAD(channel_list, channel); TAILQ_HEAD(th_dvb_adapter_queue, th_dvb_adapter); LIST_HEAD(th_v4l_adapter_list, th_v4l_adapter); LIST_HEAD(event_list, event); @@ -768,6 +769,8 @@ static inline unsigned int tvh_strhash(const char *s, unsigned int mod) #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define MAX(a,b) ((a) > (b) ? (a) : (b)) +void tvh_str_set(char **strp, const char *src); + void tvhlog(int severity, const char *subsys, const char *fmt, ...); #define LOG_EMERG 0 /* system is unusable */ @@ -791,4 +794,5 @@ getclock_hires(void) return now; } + #endif /* TV_HEAD_H */ diff --git a/webui/extjs.c b/webui/extjs.c index a49fe9f1..ef505f7f 100644 --- a/webui/extjs.c +++ b/webui/extjs.c @@ -40,6 +40,7 @@ #include "dvb/dvb_preconf.h" #include "transports.h" #include "serviceprobe.h" +#include "xmltv.h" extern const char *htsversion; @@ -578,6 +579,10 @@ extjs_channel(http_connection_t *hc, const char *remain, void *opaque) htsmsg_add_u32(r, "id", ch->ch_id); htsmsg_add_str(r, "name", ch->ch_name); htsmsg_add_str(r, "comdetect", "tt192"); + + if(ch->ch_xc != NULL) + htsmsg_add_str(r, "xmltvchannel", ch->ch_xc->xc_displayname); + out = json_single_record(r, "channels"); } else if(!strcmp(op, "gettransports")) { @@ -614,6 +619,9 @@ extjs_channel(http_connection_t *hc, const char *remain, void *opaque) } else if(!strcmp(op, "save")) { + s = http_arg_get(&hc->hc_req_args, "xmltvchannel"); + channel_set_xmltv_source(ch, s?xmltv_channel_find_by_displayname(s):NULL); + if((s = http_arg_get(&hc->hc_req_args, "name")) != NULL && strcmp(s, ch->ch_name)) { @@ -646,6 +654,50 @@ extjs_channel(http_connection_t *hc, const char *remain, void *opaque) return 0; } +/** + * + */ +static int +extjs_xmltv(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"); + xmltv_channel_t *xc; + htsmsg_t *out, *array, *e; + + pthread_mutex_lock(&global_lock); + + if(!strcmp(op, "listChannels")) { + + out = htsmsg_create(); + array = htsmsg_create_array(); + + e = htsmsg_create(); + htsmsg_add_str(e, "xcTitle", "None"); + htsmsg_add_msg(array, NULL, e); + + LIST_FOREACH(xc, &xmltv_displaylist, xc_displayname_link) { + e = htsmsg_create(); + htsmsg_add_str(e, "xcTitle", xc->xc_displayname); + htsmsg_add_msg(array, NULL, e); + } + + htsmsg_add_msg(out, "entries", array); + + } else { + pthread_mutex_unlock(&global_lock); + return HTTP_STATUS_BAD_REQUEST; + } + + pthread_mutex_unlock(&global_lock); + + htsmsg_json_serialize(out, hq, 0); + htsmsg_destroy(out); + http_output_content(hc, "text/x-json; charset=UTF-8"); + return 0; + +} + /** * WEB user interface */ @@ -659,4 +711,5 @@ extjs_start(void) http_path_add("/dvbnetworks", NULL, extjs_dvbnetworks, ACCESS_WEB_INTERFACE); http_path_add("/chlist", NULL, extjs_chlist, ACCESS_WEB_INTERFACE); http_path_add("/channel", NULL, extjs_channel, ACCESS_WEB_INTERFACE); + http_path_add("/xmltv", NULL, extjs_xmltv, ACCESS_WEB_INTERFACE); } diff --git a/webui/static/app/chconf.js b/webui/static/app/chconf.js index aabb00f9..d3d91eeb 100644 --- a/webui/static/app/chconf.js +++ b/webui/static/app/chconf.js @@ -89,7 +89,15 @@ tvheadend.channeldetails = function(chid, chname) { var confreader = new Ext.data.JsonReader({ root: 'channels', - }, ['name', 'comdetect']); + }, ['name', 'comdetect','xmltvchannel']); + + + var xmltvChannels = new Ext.data.JsonStore({ + root:'entries', + fields: [{name: 'xcTitle'}, {name: 'xcIcon'}], + url:'xmltv', + baseParams: {op: 'listChannels'} + }); var confpanel = new Ext.FormPanel({ border:false, @@ -114,7 +122,21 @@ tvheadend.channeldetails = function(chid, chname) { fieldLabel: 'Channel name', name: 'name', - } + }, + new Ext.form.ComboBox({ + loadingText: 'Loading...', + fieldLabel: 'XML-TV Source', + name: 'xmltvchannel', + width: 300, + displayField:'xcTitle', + valueField:'xcTitle', + store: xmltvChannels, + forceSelection: true, + mode: 'remote', + editable: false, + triggerAction: 'all', + emptyText: 'None' + }) /* , new Ext.form.ComboBox({ @@ -309,7 +331,13 @@ tvheadend.chconf = function() { border: false, title:'Channels', layout:'border', - items: [chlist, details] + items: [chlist, details], + tbar: [{ + tooltip: 'Configure XMLTV grabber', + iconCls: 'option', + text: 'Setup XML-TV' + }] + }); function handleActivate(tab){ diff --git a/webui/static/app/ext.css b/webui/static/app/ext.css index 549500a4..a624e1f6 100644 --- a/webui/static/app/ext.css +++ b/webui/static/app/ext.css @@ -70,4 +70,3 @@ .save { background-image:url(../icons/save.gif) !important; } - \ No newline at end of file diff --git a/xmltv.c b/xmltv.c new file mode 100644 index 00000000..f6be35c1 --- /dev/null +++ b/xmltv.c @@ -0,0 +1,440 @@ +/* + * Electronic Program Guide - xmltv interface + * Copyright (C) 2007 Andreas Öman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "tvhead.h" +#include "channels.h" +#include "epg.h" +#include "xmltv.h" +#include "spawn.h" + +/* xmltv_channels is protected by global_lock */ +static struct xmltv_channel_tree xmltv_channels; +struct xmltv_channel_list xmltv_displaylist; + + + +/** + * + */ +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) { + HTSMSG_FOREACH(f, l) { + if((c = htsmsg_get_msg_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")); + } + } +} + + +/** + * + */ +static void +xmltv_save(xmltv_channel_t *xc) +{ + htsmsg_t *m = htsmsg_create(); + 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); + } + } +} + + +/** + * + */ +static time_t +xmltv_str2time(const char *str) +{ + char str0[30]; + struct tm tim; + + snprintf(str0, sizeof(str0), "%s", str); + + tim.tm_sec = atoi(str0 + 0xc); + str0[0xc] = 0; + tim.tm_min = atoi(str0 + 0xa); + str0[0xa] = 0; + tim.tm_hour = atoi(str0 + 0x8); + str0[0x8] = 0; + tim.tm_mday = atoi(str0 + 0x6); + str0[0x6] = 0; + tim.tm_mon = atoi(str0 + 0x4) - 1; + str0[0x4] = 0; + tim.tm_year = atoi(str0) - 1900; + + tim.tm_isdst = -1; + return mktime(&tim); +} + + +/** + * + */ +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(xc->xc_displayname && !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_msg(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_msg(body, "attrib")) == NULL) + return; + + tags = htsmsg_get_msg(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_msg(tags, "icon")) != NULL && + (attribs = htsmsg_get_msg(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); +} + +/** + * 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) +{ + 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"); + + LIST_FOREACH(ch, &xc->xc_channels, ch_xc_link) { + e = epg_event_find_by_start(ch, start, 1); + + epg_event_set_duration(e, stop - start); + + if(title != NULL) epg_event_set_title(e, title); + if(desc != NULL) epg_event_set_title(e, desc); + } +} + + +/** + * Parse a tag from xmltv + */ +static void +xmltv_parse_programme(htsmsg_t *body) +{ + htsmsg_t *attribs, *tags; + const char *s, *chid; + time_t start, stop; + xmltv_channel_t *xc; + + if(body == NULL) + return; + + if((attribs = htsmsg_get_msg(body, "attrib")) == NULL) + return; + + if((tags = htsmsg_get_msg(body, "tags")) == NULL) + return; + + if((chid = htsmsg_get_str(attribs, "channel")) == NULL) + return; + + if((s = htsmsg_get_str(attribs, "start")) == NULL) + return; + start = xmltv_str2time(s); + + if((s = htsmsg_get_str(attribs, "stop")) == NULL) + return; + stop = xmltv_str2time(s); + + if(stop <= start) + return; + + if((xc = xmltv_channel_find(chid, 0)) != NULL) { + xmltv_parse_programme_tags(xc, tags, start, stop); + } +} + +/** + * + */ +static void +xmltv_parse_tv(htsmsg_t *body) +{ + htsmsg_t *tags; + htsmsg_field_t *f; + + if((tags = htsmsg_get_msg(body, "tags")) == NULL) + return; + + HTSMSG_FOREACH(f, tags) { + if(!strcmp(f->hmf_name, "channel")) { + xmltv_parse_channel(htsmsg_get_msg_by_field(f)); + } else if(!strcmp(f->hmf_name, "programme")) { + xmltv_parse_programme(htsmsg_get_msg_by_field(f)); + } + } +} + + +/** + * + */ +static void +xmltv_parse(htsmsg_t *body) +{ + htsmsg_t *tags, *tv; + + if((tags = htsmsg_get_msg(body, "tags")) == NULL) + return; + + if((tv = htsmsg_get_msg(tags, "tv")) == NULL) + return; + + xmltv_parse_tv(tv); +} + +/** + * + */ +static void +xmltv_grab(void) +{ + const char *prog = "/usr/bin/tv_grab_se_swedb"; + int outlen; + char *outbuf; + htsmsg_t *body; + char errbuf[100]; + + tvhlog(LOG_INFO, "xmltv", "Starting grabber \"%s\"", prog); + + outlen = spawn_and_store_stdout(prog, NULL, &outbuf); + if(outlen < 1) { + tvhlog(LOG_ERR, "xmltv", "No output from \"%s\"", prog); + return; + } + + 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); + pthread_mutex_unlock(&global_lock); + + htsmsg_destroy(body); +} + + +/** + * + */ +static void * +xmltv_thread(void *aux) +{ + while(1) { + xmltv_grab(); + sleep(3600); + } +} + + +/** + * + */ +void +xmltv_init(void) +{ + pthread_t ptid; + xmltv_load(); + pthread_create(&ptid, NULL, xmltv_thread, NULL); +} diff --git a/xmltv.h b/xmltv.h new file mode 100644 index 00000000..fb95dd1a --- /dev/null +++ b/xmltv.h @@ -0,0 +1,51 @@ +/* + * Electronic Program Guide - xmltv interface + * Copyright (C) 2007 Andreas Öman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EPG_XMLTV_H +#define EPG_XMLTV_H + +#include "channels.h" + +RB_HEAD(xmltv_channel_tree, xmltv_channel); +LIST_HEAD(xmltv_channel_list, xmltv_channel); + +/** + * 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); + +extern struct xmltv_channel_list xmltv_displaylist; + +#endif /* EPG_XMLTV_H__ */