]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Add system wide scanning of XMLTV grabbers.
authorAndreas Öman <andreas@lonelycoder.com>
Mon, 22 Sep 2008 21:51:09 +0000 (21:51 +0000)
committerAndreas Öman <andreas@lonelycoder.com>
Mon, 22 Sep 2008 21:51:09 +0000 (21:51 +0000)
Add support for selecting XMLTV grabber from web user interface.

webui/extjs.c
webui/static/app/tvheadend.js
webui/static/app/xmltv.js [new file with mode: 0644]
xmltv.c
xmltv.h

index 7b09a3ea30e5c0370dc8bb8806386bedb1f8a965..d0304e4a35860a9cc0e93782497ce6619f2cb33e 100644 (file)
@@ -113,6 +113,7 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
   extjs_load(hq, "static/app/chconf.js");
   extjs_load(hq, "static/app/epg.js");
   extjs_load(hq, "static/app/dvr.js");
+  extjs_load(hq, "static/app/xmltv.js");
 
   /**
    * Finally, the app itself
@@ -717,9 +718,8 @@ 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);
+  htsmsg_t *out, *array, *e, *r;
+  const char *s;
 
   if(!strcmp(op, "listChannels")) {
 
@@ -730,21 +730,55 @@ extjs_xmltv(http_connection_t *hc, const char *remain, void *opaque)
     htsmsg_add_str(e, "xcTitle", "None");
     htsmsg_add_msg(array, NULL, e);
 
+    pthread_mutex_lock(&global_lock);
     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);
     }
+    pthread_mutex_unlock(&global_lock);
 
     htsmsg_add_msg(out, "entries", array);
 
+  } else if(!strcmp(op, "loadSettings")) {
+
+    pthread_mutex_lock(&xmltv_mutex);
+    r = htsmsg_create();
+
+    if((s = xmltv_get_current_grabber()) != NULL)
+      htsmsg_add_str(r, "grabber", s);
+
+    htsmsg_add_u32(r, "grabinterval", xmltv_grab_interval);
+    pthread_mutex_unlock(&xmltv_mutex);
+
+    out = json_single_record(r, "xmltvSettings");
+
+  } else if(!strcmp(op, "saveSettings")) {
+
+    pthread_mutex_lock(&xmltv_mutex);
+
+    s = http_arg_get(&hc->hc_req_args, "grabber");
+    xmltv_set_current_grabber(s);
+
+    pthread_mutex_unlock(&xmltv_mutex);
+
+    out = htsmsg_create();
+    htsmsg_add_u32(out, "success", 1);
+
+  } else if(!strcmp(op, "listGrabbers")) {
+
+    out = htsmsg_create();
+
+    pthread_mutex_lock(&xmltv_mutex);
+    array = xmltv_list_grabbers();
+    pthread_mutex_unlock(&xmltv_mutex);
+    if(array != NULL)
+      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");
@@ -1072,7 +1106,6 @@ extjs_dvrlist(http_connection_t *hc, const char *remain, void *opaque)
 }
 
 
-
 /**
  * WEB user interface
  */
index c61eb7fc0d0467dee5767951acb98c959d34b2e6..eb9806b314f7a0cfa54418077faf9253b6820d29 100644 (file)
@@ -102,6 +102,7 @@ tvheadend.app = function() {
                autoScroll:true, 
                title: 'Configuration', 
                items: [new tvheadend.chconf,
+                       new tvheadend.xmltv,
                        new tvheadend.cteditor,
                        new tvheadend.dvrsettings,
                        new tvheadend.dvb,
@@ -126,10 +127,11 @@ tvheadend.app = function() {
                    },new Ext.TabPanel({
                        region:'center',
                        activeTab:0,
-                       items:[
+                       items:[             
                            new tvheadend.epg,
                            new tvheadend.dvr,
-                           confpanel]
+                           confpanel
+                       ]
                    })
                ]
            });
diff --git a/webui/static/app/xmltv.js b/webui/static/app/xmltv.js
new file mode 100644 (file)
index 0000000..930caa5
--- /dev/null
@@ -0,0 +1,194 @@
+tvheadend.grabberStore = new Ext.data.JsonStore({
+    root:'entries',
+    fields: ['identifier','name','version','apiconfig'],
+    url:'xmltv',
+    baseParams: {op: 'listGrabbers'},
+});
+
+/*
+
+tvheadend.xmltvsetup = function() {
+
+    var deck1info = new Ext.form.Label({
+       fieldLabel: 'Version',
+       html:'',
+    });
+
+    var deck1cb = new Ext.form.ComboBox({
+       loadingText: 'Scanning for XMLTV grabbers, please wait...',
+       fieldLabel: 'XML-TV Source',
+       name: 'xmltvchannel',
+       width: 350,
+       displayField:'name',
+       valueField:'identifier',
+       store: tvheadend.grabberStore,
+       forceSelection: true,
+       editable: false,
+       triggerAction: 'all',
+       mode: 'remote',
+       emptyText: 'Select grabber'
+    });
+
+    var deck1 = new Ext.FormPanel({
+       labelAlign: 'right',
+       labelWidth: 100,
+       bodyStyle: 'padding: 5px',
+       defaultType: 'label',
+       layout: 'form',
+       border:false,
+       items: [deck1cb, deck1info]
+    });
+    
+
+
+    var win = new Ext.Window({
+       title: 'Configure XMLTV grabbers',
+       bodyStyle: 'padding: 5px',
+        layout: 'fit',
+        width: 500,
+        height: 500,
+       constrainHeader: true,
+       buttonAlign: 'center',
+       items: [deck1],
+       bbar: [
+           {
+               id: 'move-back',
+               text: 'Back',
+               disabled: true
+           },
+           '->',
+           {
+               id: 'move-next',
+               text: 'Next'
+           }
+       ]
+    });
+
+    win.show();
+
+    deck1cb.on('select', function(c,r,i) {
+       deck1info.setText(r.data.version);
+    });
+}
+*/
+
+
+
+
+tvheadend.xmltv = function() {
+
+    var confreader = new Ext.data.JsonReader({
+       root: 'xmltvSettings',
+    }, ['grabber','grabinterval']);
+
+    var grabberSelect = new Ext.form.ComboBox({
+       loadingText: 'Loading, please wait...',
+       fieldLabel: 'XML-TV Source',
+       name: 'grabber',
+       width: 350,
+       displayField:'name',
+       valueField:'identifier',
+       store: tvheadend.grabberStore,
+       forceSelection: true,
+       editable: false,
+       triggerAction: 'all',
+       mode: 'remote',
+       emptyText: 'Select grabber'
+    });
+
+    var confpanel = new Ext.FormPanel({
+       title:'XML TV',
+       border:false,
+       bodyStyle:'padding:15px',
+       labelAlign: 'right',
+       labelWidth: 200,
+       waitMsgTarget: true,
+       reader: confreader,
+       layout: 'form',
+       defaultType: 'textfield',
+       items: [
+           grabberSelect,
+           new Ext.form.NumberField({
+               allowNegative: false,
+               allowDecimals: false,
+               minValue: 1,
+               maxValue: 100,
+               fieldLabel: 'Grab interval (hours)',
+               name: 'grabinterval'
+           })
+       ],
+       tbar: [{
+           tooltip: 'Save changes made to configuration below',
+           iconCls:'save',
+           text: "Save configuration",
+           handler: saveChanges
+       }],
+       
+    });
+
+    confpanel.on('render', function() {
+       confpanel.getForm().load({
+           url:'/xmltv', 
+           params:{'op':'loadSettings'},
+           success:function(form, action) {
+               confpanel.enable();
+           }
+       });
+    });
+
+
+    grabberSelect.on('select', function(c,r,i) {
+       if(r.data.apiconfig) {
+
+           Ext.MessageBox.confirm('XMLTV',
+                                  'Configure grabber? ' +
+                                  'If you know that the grabber is already '+
+                                  'set up or if you want to configure it '+
+                                  'manually you may skip this step',
+                                  function(button) {
+                                      Ext.MessageBox.alert('XMLTV',
+                                                           'oops, embedded '+
+                                                           'config not '+
+                                                           'implemeted yet');
+                                  }
+                                 );
+
+       } else {
+           Ext.MessageBox.alert('XMLTV',
+                                'This grabber does not support being ' +
+                                'configured from external application ' +
+                                '(such as Tvheadend).<br>' +
+                                'Make sure that the grabber is properly ' +
+                                'configured before saving configuration.<br>'+
+                                '<br>' +
+                                'To configure manually execute the ' +
+                                'following command in a shell on the ' +
+                                'server:<br>' +
+                                '$ ' + r.data.identifier + 
+                                ' --configure<br>' +
+                                '<br>' +
+                                'Note: It is important to configure the ' +
+                                'grabber using the same userid as tvheadend '+
+                                'since most grabbers save their '+
+                                'configuration in the users home directory.'+
+                                '<br>' +
+                                '<br>' +
+                                'Grabber version: ' + r.data.version
+                               );
+       }
+    });
+
+    function saveChanges() {
+       confpanel.getForm().submit({
+           url:'/xmltv', 
+           params:{'op':'saveSettings'},
+           waitMsg:'Saving Data...',
+           failure: function(form, action) {
+               Ext.Msg.alert('Save failed', action.result.errormsg);
+           }
+       });
+    }
+
+    return confpanel;
+}
+
diff --git a/xmltv.c b/xmltv.c
index 5693a7cc414382d64eb73ac9d0bf1d08f60b3733..0a6f99366c719e808c8fb2d136a8c6780bc44109 100644 (file)
--- a/xmltv.c
+++ b/xmltv.c
  */
 
 #include <assert.h>
+#include <sys/stat.h>
 #include <errno.h>
 #include <string.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <stdlib.h>
+#include <dirent.h>
 
 #include <libhts/htsmsg_xml.h>
 #include <libhts/htssettings.h>
 #include "xmltv.h"
 #include "spawn.h"
 
+
+/**
+ *
+ */
+LIST_HEAD(xmltv_grabber_list, xmltv_grabber);
+static struct xmltv_grabber_list xmltv_grabbers;
+
+typedef struct xmltv_grabber {
+  LIST_ENTRY(xmltv_grabber) xg_link;
+  char *xg_path;
+  time_t xg_mtime;
+
+  char *xg_version;
+  char *xg_description;
+
+  uint32_t xg_capabilities;
+#define XG_CAP_BASELINE     0x1
+#define XG_CAP_MANUALCONFIG 0x2
+#define XG_CAP_CACHE        0x4
+#define XG_CAP_APICONFIG    0x8
+
+  int xg_dirty;
+
+} xmltv_grabber_t;
+
+static xmltv_grabber_t *xg_current;
+
+
+uint32_t xmltv_grab_interval;
+
 /* xmltv_channels is protected by global_lock */
 static struct xmltv_channel_tree xmltv_channels;
 struct xmltv_channel_list xmltv_displaylist;
 
+pthread_mutex_t xmltv_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t  xmltv_cond;
+static int xmltv_confver;
 
+static void xmltv_grabbers_index(void);
+static void xmltv_grabbers_load(void);
+static void xmltv_grabbers_save(void);
 
 /**
  *
@@ -382,16 +420,13 @@ xmltv_parse(htsmsg_t *body)
  *
  */
 static void
-xmltv_grab(void)
+xmltv_grab(const char *prog)
 {
-  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);
@@ -419,9 +454,50 @@ xmltv_grab(void)
 static void *
 xmltv_thread(void *aux)
 {
+  int confver = 0;
+  struct timespec ts;
+  char *p;
+
+  pthread_mutex_lock(&xmltv_mutex);
+
+  xmltv_grabbers_load();
+
+  xmltv_grabbers_index();
+
+  ts.tv_nsec = 0;
+  ts.tv_sec = 0;
+
   while(1) {
-    xmltv_grab();
-    sleep(3600);
+
+    while(confver == xmltv_confver) {
+
+      if(xg_current == NULL) {
+       pthread_cond_wait(&xmltv_cond, &xmltv_mutex);
+       continue;
+      }
+      if(pthread_cond_timedwait(&xmltv_cond, &xmltv_mutex, &ts) == ETIMEDOUT)
+       break;
+    }
+
+    ts.tv_sec = time(NULL) + xmltv_grab_interval * 3600;
+
+    confver = xmltv_confver;
+
+    if(xg_current != NULL) {
+      tvhlog(LOG_INFO, "xmltv",
+            "Grabbing \"%s\" using command \"%s\"",
+            xg_current->xg_description, xg_current->xg_path);
+
+      /* Dup path so we can unlock */
+      p = strdup(xg_current->xg_path);
+
+      pthread_mutex_unlock(&xmltv_mutex);
+      
+      xmltv_grab(p);
+
+      pthread_mutex_lock(&xmltv_mutex);
+      free(p);
+    }
   }
 }
 
@@ -433,6 +509,360 @@ void
 xmltv_init(void)
 {
   pthread_t ptid;
+  xmltv_grab_interval = 12; /* Default half a day */
+
+  /* Load all channels */
   xmltv_load();
   pthread_create(&ptid, NULL, xmltv_thread, NULL);
 }
+
+
+/**
+ *
+ */
+static void
+xmltv_grabber_destroy(xmltv_grabber_t *xg)
+{
+  LIST_REMOVE(xg, xg_link);
+  free(xg->xg_path);
+  free(xg->xg_version);
+  free(xg->xg_description);
+  free(xg);
+}
+
+/**
+ *
+ */
+static char *
+xmltv_grabber_get_description(const char *prog)
+{
+  int i, outlen;
+  char *outbuf;
+  const char *args[] = {prog, "--description", NULL};
+
+  if((outlen = spawn_and_store_stdout(prog, (void *)args, &outbuf)) < 1)
+    return NULL;
+
+  for(i = 0; i < outlen; i++)
+    if(outbuf[i] < 32)
+      outbuf[i] = 0;
+  return outbuf;
+}
+
+
+
+/**
+ *
+ */
+static char *
+xmltv_grabber_get_version(const char *prog)
+{
+  int outlen;
+  char *outbuf;
+  const char *args[] = {prog, "--version", NULL};
+
+  if((outlen = spawn_and_store_stdout(prog, (void *)args, &outbuf)) < 1)
+    return NULL;
+  return outbuf;
+}
+
+/**
+ *
+ */
+static int
+xmltv_grabber_get_capabilities(const char *prog)
+{
+  int outlen;
+  char *outbuf;
+  int r;
+
+  const char *args[] = {prog, "--capabilities", NULL};
+
+  if((outlen = spawn_and_store_stdout(prog, (void *)args, &outbuf)) < 1)
+    return 0;
+
+  r  = strstr(outbuf, "baseline")     ? XG_CAP_BASELINE     : 0;
+  r |= strstr(outbuf, "manualconfig") ? XG_CAP_MANUALCONFIG : 0;
+  r |= strstr(outbuf, "cache")        ? XG_CAP_CACHE        : 0;
+  r |= strstr(outbuf, "apiconfig")    ? XG_CAP_APICONFIG    : 0;
+  return r;
+}
+
+/**
+ *
+ */
+static int
+xg_desc_cmp(const xmltv_grabber_t *a, const xmltv_grabber_t *b)
+{
+  return strcmp(a->xg_description, b->xg_description);
+}
+
+/**
+ *
+ */
+static int
+xmltv_grabber_add(const char *path, time_t mtime)
+{
+  char *desc;
+  xmltv_grabber_t *xg;
+  int wasthisgrabber = 0;
+
+  LIST_FOREACH(xg, &xmltv_grabbers, xg_link)
+    if(!strcmp(xg->xg_path, path))
+      break;
+
+  if(xg != NULL) {
+    if(xg->xg_mtime == mtime) {
+      xg->xg_dirty = 0;
+      return 0; /* Already there */
+    }
+    wasthisgrabber = xg == xg_current;
+    xmltv_grabber_destroy(xg);
+    xg_current = NULL;
+  }
+  if((desc = xmltv_grabber_get_description(path)) == NULL)
+    return 0; /* Probably not a working grabber */
+
+  xg = calloc(1, sizeof(xmltv_grabber_t));
+  xg->xg_path = strdup(path);
+  xg->xg_mtime = mtime;
+  xg->xg_description = strdup(desc);
+  xg->xg_capabilities = xmltv_grabber_get_capabilities(path);
+  tvh_str_set(&xg->xg_version, xmltv_grabber_get_version(path));
+  LIST_INSERT_SORTED(&xmltv_grabbers, xg, xg_link, xg_desc_cmp);
+  if(wasthisgrabber)
+    xg_current = xg;
+  return 1;
+}
+
+
+/**
+ *
+ */
+static xmltv_grabber_t *
+xmltv_grabber_find_by_path(const char *path)
+{
+  xmltv_grabber_t *xg;
+
+  LIST_FOREACH(xg, &xmltv_grabbers, xg_link)
+    if(!strcmp(xg->xg_path, path))
+      break;
+  return xg;
+}
+
+
+/**
+ *
+ */
+static int
+xmltv_grabber_filter(const struct dirent *d)
+{
+  if(strncmp(d->d_name, "tv_grab_", 8))
+    return 0;
+  return 1;
+
+}
+
+
+/**
+ *
+ */
+static int
+xmltv_scan_grabbers(const char *path)
+{
+  struct dirent **namelist;
+  int n, change = 0;
+  char fname[300];
+  struct stat st;
+
+  if((n = scandir(path, &namelist, xmltv_grabber_filter, NULL)) < 0)
+    return 0;
+
+  while(n--) {
+    snprintf(fname, sizeof(fname), "%s/%s", path, namelist[n]->d_name);
+    if(!stat(fname, &st))
+      change |= xmltv_grabber_add(fname, st.st_mtime);
+  }
+  free(namelist);
+  return change;
+}
+
+
+/**
+ *
+ */
+static void
+xmltv_grabbers_index(void)
+{
+  xmltv_grabber_t *xg, *next;
+  int change;
+
+  LIST_FOREACH(xg, &xmltv_grabbers, xg_link)
+    xg->xg_dirty = 1;
+
+  change =  xmltv_scan_grabbers("/bin");
+  change |= xmltv_scan_grabbers("/usr/bin");
+  change |= xmltv_scan_grabbers("/usr/local/bin");
+
+  for(xg = LIST_FIRST(&xmltv_grabbers); xg != NULL; xg = next) {
+    next = LIST_NEXT(xg, xg_link);
+    if(xg->xg_dirty) {
+      change = 1;
+      xmltv_grabber_destroy(xg);
+    }
+  }
+
+  if(change)
+    xmltv_grabbers_save();
+}
+
+/**
+ *
+ */
+static void
+xmltv_grabbers_load(void)
+{
+  xmltv_grabber_t *xg;
+  htsmsg_t *l, *c, *m;
+  htsmsg_field_t *f;
+  const char *path, *desc;
+  uint32_t u32;
+
+  if((m = hts_settings_load("xmltv/config")) == NULL)
+    return;
+
+  htsmsg_get_u32(m, "grab-interval", &xmltv_grab_interval);
+
+  if((l = htsmsg_get_array(m, "grabbers")) != NULL) {
+
+    HTSMSG_FOREACH(f, l) {
+      if((c = htsmsg_get_msg_by_field(f)) == NULL)
+       continue;
+
+      path = htsmsg_get_str(c, "path");
+      desc = htsmsg_get_str(c, "description");
+      if(path == NULL || desc == NULL)
+       continue;
+
+      xg = calloc(1, sizeof(xmltv_grabber_t));
+      xg->xg_path = strdup(path);
+      xg->xg_description = strdup(desc);
+
+      if(!htsmsg_get_u32(c, "mtime", &u32))
+       xg->xg_mtime = u32;
+
+      tvh_str_set(&xg->xg_version, htsmsg_get_str(c, "version"));
+      htsmsg_get_u32(c, "capabilities", &xg->xg_capabilities);
+      LIST_INSERT_SORTED(&xmltv_grabbers, xg, xg_link, xg_desc_cmp);
+    }
+  }
+
+  if((path = htsmsg_get_str(m, "current-grabber")) != NULL) {
+    if((xg = xmltv_grabber_find_by_path(path)) != NULL)
+      xg_current = xg;
+  }
+}
+
+
+static void
+xmltv_grabbers_save(void)
+{
+  htsmsg_t *array, *e, *m;
+  xmltv_grabber_t *xg;
+
+  m = htsmsg_create();
+
+  array = htsmsg_create_array();
+
+  LIST_FOREACH(xg, &xmltv_grabbers, xg_link) {
+    e = htsmsg_create();
+    htsmsg_add_str(e, "path", xg->xg_path);
+    htsmsg_add_str(e, "description", xg->xg_description);
+    if(xg->xg_version != NULL)
+      htsmsg_add_str(e, "version", xg->xg_version);
+    htsmsg_add_u32(e, "mtime", xg->xg_mtime);
+    htsmsg_add_u32(e, "capabilities", xg->xg_capabilities);
+
+    htsmsg_add_msg(array, NULL, e);
+  }
+
+  htsmsg_add_msg(m, "grabbers", array);
+
+  htsmsg_add_u32(m, "grab-interval", xmltv_grab_interval);
+  if(xg_current != NULL)
+    htsmsg_add_str(m, "current-grabber", xg_current->xg_path);
+
+  hts_settings_save(m, "xmltv/config");
+  htsmsg_destroy(m);
+}
+
+
+/**
+ *
+ */
+htsmsg_t *
+xmltv_list_grabbers(void)
+{
+  htsmsg_t *array, *m;
+  xmltv_grabber_t *xg;
+
+  lock_assert(&xmltv_mutex);
+
+  xmltv_grabbers_index();
+
+  array = htsmsg_create_array();
+
+  LIST_FOREACH(xg, &xmltv_grabbers, xg_link) {
+    m = htsmsg_create();
+    htsmsg_add_str(m, "identifier", xg->xg_path);
+    htsmsg_add_str(m, "name", xg->xg_description);
+    htsmsg_add_str(m, "version", xg->xg_version ?: "Unknown version");
+    htsmsg_add_u32(m, "apiconfig", !!(xg->xg_capabilities & XG_CAP_APICONFIG));
+    htsmsg_add_msg(array, NULL, m);
+  }
+
+  return array;
+}
+
+
+/**
+ *
+ */
+const char *
+xmltv_get_current_grabber(void)
+{
+  lock_assert(&xmltv_mutex);
+  return xg_current ? xg_current->xg_description : NULL;
+}
+
+
+/**
+ *
+ */
+void
+xmltv_set_current_grabber(const char *desc)
+{
+  xmltv_grabber_t *xg;
+
+  lock_assert(&xmltv_mutex);
+
+  if(desc != NULL) {
+    LIST_FOREACH(xg, &xmltv_grabbers, xg_link)
+      if(!strcmp(xg->xg_description, desc))
+       break;
+    if(xg == NULL)
+      return;
+  } else {
+    xg = NULL;
+  }
+
+  if(xg_current == xg)
+    return;
+
+  xg_current = xg;
+
+  xmltv_confver++;
+  pthread_cond_signal(&xmltv_cond);
+  xmltv_grabbers_save();
+}
diff --git a/xmltv.h b/xmltv.h
index fb95dd1afeee63179102c45263e06c180b0940a6..f89f108f4d1fc76e52f47a8b76efea242c7934fe 100644 (file)
--- a/xmltv.h
+++ b/xmltv.h
@@ -46,6 +46,13 @@ xmltv_channel_t *xmltv_channel_find(const char *id, int create);
 
 xmltv_channel_t *xmltv_channel_find_by_displayname(const char *name);
 
+htsmsg_t *xmltv_list_grabbers(void);
+
+const char *xmltv_get_current_grabber(void);
+void xmltv_set_current_grabber(const char *path);
+
 extern struct xmltv_channel_list xmltv_displaylist;
+extern uint32_t xmltv_grab_interval;
+extern pthread_mutex_t xmltv_mutex;
 
 #endif /* EPG_XMLTV_H__ */