Initial XMLTV support

This commit is contained in:
Andreas Öman 2008-09-05 16:02:41 +00:00
parent ecec2b38bf
commit b5ce0f89f9
14 changed files with 750 additions and 953 deletions

View file

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

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

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

37
epg.c
View file

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

2
epg.h
View file

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

View file

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

View file

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

20
main.c
View file

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

View file

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

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

View 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){

View file

@ -70,4 +70,3 @@
.save {
background-image:url(../icons/save.gif) !important;
}

440
xmltv.c Normal file
View file

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

51
xmltv.h Normal file
View file

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