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__ */