Initial XMLTV support
This commit is contained in:
parent
ecec2b38bf
commit
b5ce0f89f9
14 changed files with 750 additions and 953 deletions
9
Makefile
9
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
|
||||
|
|
111
channels.c
111
channels.c
|
@ -38,6 +38,9 @@
|
|||
#include "epg.h"
|
||||
#include "pvr.h"
|
||||
#include "autorec.h"
|
||||
#include "xmltv.h"
|
||||
|
||||
struct channel_list channels_not_xmltv_mapped;
|
||||
|
||||
struct channel_tree channel_name_tree;
|
||||
static struct channel_tree channel_identifier_tree;
|
||||
|
@ -117,7 +120,7 @@ channel_set_name(channel_t *ch, const char *name)
|
|||
if(isalnum(c))
|
||||
*cp++ = c;
|
||||
else
|
||||
*cp++ = '-';
|
||||
*cp++ = '_';
|
||||
}
|
||||
*cp = 0;
|
||||
|
||||
|
@ -128,7 +131,6 @@ channel_set_name(channel_t *ch, const char *name)
|
|||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -147,7 +149,7 @@ channel_create(const char *name)
|
|||
|
||||
ch = calloc(1, sizeof(channel_t));
|
||||
RB_INIT(&ch->ch_epg_events);
|
||||
|
||||
LIST_INSERT_HEAD(&channels_not_xmltv_mapped, ch, ch_xc_link);
|
||||
channel_set_name(ch, name);
|
||||
|
||||
ch->ch_id = id;
|
||||
|
@ -202,10 +204,67 @@ static struct strtab commercial_detect_tab[] = {
|
|||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
static void
|
||||
channel_load_one(htsmsg_t *c, int id)
|
||||
{
|
||||
channel_t *ch;
|
||||
const char *s;
|
||||
const char *name = htsmsg_get_str(c, "name");
|
||||
|
||||
if(name == NULL)
|
||||
return;
|
||||
|
||||
ch = calloc(1, sizeof(channel_t));
|
||||
ch->ch_id = id;
|
||||
if(RB_INSERT_SORTED(&channel_identifier_tree, ch,
|
||||
ch_identifier_link, chidcmp)) {
|
||||
/* ID collision, should not happen unless there is something
|
||||
wrong in the setting storage */
|
||||
free(ch);
|
||||
return;
|
||||
}
|
||||
|
||||
RB_INIT(&ch->ch_epg_events);
|
||||
|
||||
channel_set_name(ch, name);
|
||||
|
||||
if((s = htsmsg_get_str(c, "icon")) != NULL)
|
||||
ch->ch_icon = strdup(s);
|
||||
|
||||
if((s = htsmsg_get_str(c, "xmltv-channel")) != NULL &&
|
||||
(ch->ch_xc = xmltv_channel_find(s, 0)) != NULL)
|
||||
LIST_INSERT_HEAD(&ch->ch_xc->xc_channels, ch, ch_xc_link);
|
||||
else
|
||||
LIST_INSERT_HEAD(&channels_not_xmltv_mapped, ch, ch_xc_link);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
channels_load(void)
|
||||
{
|
||||
htsmsg_t *l, *c;
|
||||
htsmsg_field_t *f;
|
||||
|
||||
if((l = hts_settings_load("channels")) != NULL) {
|
||||
HTSMSG_FOREACH(f, l) {
|
||||
if((c = htsmsg_get_msg_by_field(f)) == NULL)
|
||||
continue;
|
||||
channel_load_one(c, atoi(f->hmf_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
channels_init(void)
|
||||
{
|
||||
channels_load();
|
||||
}
|
||||
|
||||
|
||||
|
@ -220,13 +279,19 @@ channel_save(channel_t *ch)
|
|||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
htsmsg_add_str(m, "name", ch->ch_name);
|
||||
|
||||
if(ch->ch_xc != NULL)
|
||||
htsmsg_add_str(m, "xmltv-channel", ch->ch_xc->xc_identifier);
|
||||
|
||||
if(ch->ch_icon != NULL)
|
||||
htsmsg_add_str(m, "icon", ch->ch_icon);
|
||||
|
||||
htsmsg_add_str(m, "commercial_detect",
|
||||
htsmsg_add_str(m, "commercial-detect",
|
||||
val2str(ch->ch_commercial_detection,
|
||||
commercial_detect_tab) ?: "?");
|
||||
hts_settings_save(m, "channels/%s", ch->ch_name);
|
||||
|
||||
hts_settings_save(m, "channels/%d", ch->ch_id);
|
||||
htsmsg_destroy(m);
|
||||
}
|
||||
|
||||
|
@ -246,8 +311,6 @@ channel_rename(channel_t *ch, const char *newname)
|
|||
tvhlog(LOG_NOTICE, "channels", "Channel \"%s\" renamed to \"%s\"",
|
||||
ch->ch_name, newname);
|
||||
|
||||
hts_settings_remove("channels/%s", ch->ch_name);
|
||||
|
||||
RB_REMOVE(&channel_name_tree, ch, ch_name_link);
|
||||
channel_set_name(ch, newname);
|
||||
|
||||
|
@ -291,11 +354,13 @@ channel_delete(channel_t *ch)
|
|||
|
||||
abort();//autorec_destroy_by_channel(ch);
|
||||
|
||||
hts_settings_remove("channels/%s", ch->ch_name);
|
||||
hts_settings_remove("channels/%d", ch->ch_id);
|
||||
|
||||
RB_REMOVE(&channel_name_tree, ch, ch_name_link);
|
||||
RB_REMOVE(&channel_identifier_tree, ch, ch_identifier_link);
|
||||
|
||||
LIST_REMOVE(ch, ch_xc_link);
|
||||
|
||||
free(ch->ch_name);
|
||||
free(ch->ch_sname);
|
||||
free(ch->ch_icon);
|
||||
|
@ -338,7 +403,33 @@ channel_set_icon(channel_t *ch, const char *icon)
|
|||
{
|
||||
lock_assert(&global_lock);
|
||||
|
||||
if(ch->ch_icon != NULL && !strcmp(ch->ch_icon, icon))
|
||||
return;
|
||||
|
||||
free(ch->ch_icon);
|
||||
ch->ch_icon = icon ? strdup(icon) : NULL;
|
||||
ch->ch_icon = strdup(icon);
|
||||
channel_save(ch);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
channel_set_xmltv_source(channel_t *ch, xmltv_channel_t *xc)
|
||||
{
|
||||
lock_assert(&global_lock);
|
||||
|
||||
if(xc == ch->ch_xc)
|
||||
return;
|
||||
|
||||
LIST_REMOVE(ch, ch_xc_link);
|
||||
|
||||
if(xc == NULL) {
|
||||
LIST_INSERT_HEAD(&channels_not_xmltv_mapped, ch, ch_xc_link);
|
||||
} else {
|
||||
LIST_INSERT_HEAD(&xc->xc_channels, ch, ch_xc_link);
|
||||
}
|
||||
ch->ch_xc = xc;
|
||||
channel_save(ch);
|
||||
}
|
||||
|
|
13
channels.h
13
channels.h
|
@ -19,6 +19,7 @@
|
|||
#ifndef CHANNELS_H
|
||||
#define CHANNELS_H
|
||||
|
||||
|
||||
/*
|
||||
* Channel definition
|
||||
*/
|
||||
|
@ -53,11 +54,12 @@ typedef struct channel {
|
|||
|
||||
struct autorec_list ch_autorecs;
|
||||
|
||||
struct xmltv_channel *ch_xc;
|
||||
LIST_ENTRY(channel) ch_xc_link;
|
||||
|
||||
} channel_t;
|
||||
|
||||
|
||||
|
||||
void channels_load(void);
|
||||
void channels_init(void);
|
||||
|
||||
channel_t *channel_find_by_name(const char *name, int create);
|
||||
|
||||
|
@ -75,4 +77,9 @@ void channel_merge(channel_t *dst, channel_t *src);
|
|||
|
||||
void channel_set_icon(channel_t *ch, const char *icon);
|
||||
|
||||
struct xmltv_channel;
|
||||
void channel_set_xmltv_source(channel_t *ch, struct xmltv_channel *xc);
|
||||
|
||||
extern struct channel_list channels_not_xmltv_mapped;
|
||||
|
||||
#endif /* CHANNELS_H */
|
||||
|
|
37
epg.c
37
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;
|
||||
}
|
||||
|
||||
|
|
2
epg.h
2
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);
|
||||
|
|
828
epg_xmltv.c
828
epg_xmltv.c
|
@ -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();
|
||||
}
|
100
epg_xmltv.h
100
epg_xmltv.h
|
@ -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
20
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;
|
||||
}
|
||||
|
||||
|
|
4
tvhead.h
4
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 */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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){
|
||||
|
|
|
@ -70,4 +70,3 @@
|
|||
.save {
|
||||
background-image:url(../icons/save.gif) !important;
|
||||
}
|
||||
|
440
xmltv.c
Normal file
440
xmltv.c
Normal 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
51
xmltv.h
Normal 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__ */
|
Loading…
Add table
Reference in a new issue