]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Initial XMLTV support
authorAndreas Öman <andreas@lonelycoder.com>
Fri, 5 Sep 2008 16:02:41 +0000 (16:02 +0000)
committerAndreas Öman <andreas@lonelycoder.com>
Fri, 5 Sep 2008 16:02:41 +0000 (16:02 +0000)
14 files changed:
Makefile
channels.c
channels.h
epg.c
epg.h
epg_xmltv.c [deleted file]
epg_xmltv.h [deleted file]
main.c
tvhead.h
webui/extjs.c
webui/static/app/chconf.js
webui/static/app/ext.css
xmltv.c [new file with mode: 0644]
xmltv.h [new file with mode: 0644]

index 33c9c0f2e7770808cba8fddb73fb52e6ee11bae8..45e5c38af84c01f2181f818dfcdeafad6a4760bf 100644 (file)
--- 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
index c41cd5569b2bcd9edcc1cd20e6ed74ce7c58fae8..a08add9791744fc6b2572a7f34590dc9822c0234 100644 (file)
@@ -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);
 }
index 9f78f0b4c37ac7045fee599a769f5f93bdccaacf..e06a8693e9b4e0d89f1790449b23c008915c48f5 100644 (file)
@@ -19,6 +19,7 @@
 #ifndef CHANNELS_H
 #define CHANNELS_H
 
+
 /*
  * Channel definition
  */ 
@@ -53,11 +54,12 @@ typedef struct channel {
   
   struct autorec_list ch_autorecs;
 
-} channel_t;
-
+  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 23c47f598940a1b7fbee78bd64ed4494b48c06c7..2c678f76dac27980ae46b98ee929d9a588289a7f 100644 (file)
--- 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 cf21a340206caa2a9b80447eb210a365860de10c..b4f02fd5941f955a80c9a74f31536e9f8181112e 100644 (file)
--- 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 (file)
index 27aef91..0000000
+++ /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 <http://www.gnu.org/licenses/>.
- */
-
-#define _GNU_SOURCE
-#include <errno.h>
-#include <string.h>
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <fcntl.h>
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-
-#include <libxml/parser.h>
-#include <libxml/tree.h>
-
-#include <libhts/htscfg.h>
-#include <syslog.h>
-
-#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.</p><p>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 (file)
index e9e59e3..0000000
+++ /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 <http://www.gnu.org/licenses/>.
- */
-
-#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 3540056ea1e8d5073cf0a90cb08a11f8fa6cbd30..b3f3f03f1dfb3b822b66bc031a44016a174c7d67 100644 (file)
--- 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 <libhts/htsparachute.h>
 #include <libhts/htssettings.h>
@@ -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;
+}
+
index 2b77c8bea087f0576e35ea66818c8a43d6b6bb2a..dccd0216445b80c354185dbd135433f7fbab2235 100644 (file)
--- 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 */
index a49fe9f176d97c35d0852cb1b8458ebff2ee84ec..ef505f7f05eb6e927d802ec2c8d8e756dc890f4c 100644 (file)
@@ -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);
 }
index aabb00f91eae1c9efbd99ffc28bbf3a46513c7f0..d3d91eebd995f6484e061261f46998a211b856e2 100644 (file)
@@ -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){
index 549500a4aa2095319b5ff9060861e29c11d455e6..a624e1f6b315c97bcd08e7b83638007d2de524dd 100644 (file)
@@ -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 (file)
index 0000000..f6be35c
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <libhts/htsmsg_xml.h>
+#include <libhts/htssettings.h>
+
+#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 <channel> 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 <programme> 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 (file)
index 0000000..fb95dd1
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#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__ */