Add system wide scanning of XMLTV grabbers.

Add support for selecting XMLTV grabber from web user interface.
This commit is contained in:
Andreas Öman 2008-09-22 21:51:09 +00:00
parent 3f2c453ee8
commit 4010f71b77
5 changed files with 681 additions and 15 deletions

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

View 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
]
})
]
});

194
webui/static/app/xmltv.js Normal file
View file

@ -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;
}

442
xmltv.c
View file

@ -17,11 +17,13 @@
*/
#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>
@ -32,11 +34,47 @@
#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();
}

View file

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