Started work on migrating EPG schedules into the main channel_t structure.
This commit is contained in:
parent
a373a3b4d8
commit
74cfefd965
9 changed files with 298 additions and 323 deletions
345
src/epg.c
345
src/epg.c
|
@ -16,16 +16,6 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* TODO: list:
|
||||
*
|
||||
* - sanity checks on _destroy calls could just do what we're told and
|
||||
* unreference all links?
|
||||
*
|
||||
* - does the broadcast <-> channel referencing need to be 2 way?
|
||||
* i.e. do we need to hold onto the CHANNEL info for DVR held broadcasts?
|
||||
*/
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdio.h>
|
||||
|
@ -54,13 +44,6 @@ epg_object_list_t epg_objects[EPG_HASH_WIDTH];
|
|||
epg_object_tree_t epg_brands;
|
||||
epg_object_tree_t epg_seasons;
|
||||
epg_object_tree_t epg_episodes;
|
||||
epg_object_tree_t epg_channels;
|
||||
|
||||
/* Unmapped */
|
||||
LIST_HEAD(epg_channel_unmapped_list, epg_channel);
|
||||
LIST_HEAD(channel_unmapped_list, channel);
|
||||
struct epg_channel_unmapped_list epg_channel_unmapped;
|
||||
struct channel_unmapped_list channel_unmapped;
|
||||
|
||||
/* Unreferenced */
|
||||
epg_object_list_t epg_object_unref;
|
||||
|
@ -89,13 +72,6 @@ static int _ebc_win_cmp ( const void *a, const void *b )
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int _epg_channel_cmp ( epg_channel_t *ec, channel_t *ch )
|
||||
{
|
||||
int ret = 0;
|
||||
if ( ec->name && !strcmp(ec->name, ch->ch_name) ) ret = 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* **************************************************************************
|
||||
* Setup / Update
|
||||
* *************************************************************************/
|
||||
|
@ -113,6 +89,8 @@ static int _epg_write ( int fd, htsmsg_t *m )
|
|||
free(msgdata);
|
||||
if(w == msglen) ret = 0;
|
||||
}
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
if(ret) {
|
||||
tvhlog(LOG_DEBUG, "epg", "failed to store epg to disk");
|
||||
|
@ -132,18 +110,13 @@ static int _epg_write_sect ( int fd, const char *sect )
|
|||
void epg_save ( void )
|
||||
{
|
||||
int fd;
|
||||
epg_object_t *eo, *ec;
|
||||
epg_object_t *eo;
|
||||
channel_t *ch;
|
||||
epggrab_stats_t stats;
|
||||
|
||||
fd = hts_settings_open_file(1, "epgdb");
|
||||
|
||||
/* Channels */
|
||||
memset(&stats, 0, sizeof(stats));
|
||||
if ( _epg_write_sect(fd, "channels") ) return;
|
||||
RB_FOREACH(eo, &epg_channels, glink) {
|
||||
if (_epg_write(fd, epg_channel_serialize((epg_channel_t*)eo))) return;
|
||||
stats.channels.total++;
|
||||
}
|
||||
if ( _epg_write_sect(fd, "brands") ) return;
|
||||
RB_FOREACH(eo, &epg_brands, glink) {
|
||||
if (_epg_write(fd, epg_brand_serialize((epg_brand_t*)eo))) return;
|
||||
|
@ -160,8 +133,8 @@ void epg_save ( void )
|
|||
stats.episodes.total++;
|
||||
}
|
||||
if ( _epg_write_sect(fd, "broadcasts") ) return;
|
||||
RB_FOREACH(ec, &epg_channels, glink) {
|
||||
RB_FOREACH(eo, &((epg_channel_t*)ec)->schedule, glink) {
|
||||
RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
|
||||
RB_FOREACH(eo, &ch->ch_epg_schedule, glink) {
|
||||
if (_epg_write(fd, epg_broadcast_serialize((epg_broadcast_t*)eo))) return;
|
||||
stats.broadcasts.total++;
|
||||
}
|
||||
|
@ -169,7 +142,6 @@ void epg_save ( void )
|
|||
|
||||
/* Stats */
|
||||
tvhlog(LOG_DEBUG, "epg", "database saved");
|
||||
tvhlog(LOG_DEBUG, "epg", " channels %d", stats.channels.total);
|
||||
tvhlog(LOG_DEBUG, "epg", " brands %d", stats.brands.total);
|
||||
tvhlog(LOG_DEBUG, "epg", " seasons %d", stats.seasons.total);
|
||||
tvhlog(LOG_DEBUG, "epg", " episodes %d", stats.episodes.total);
|
||||
|
@ -230,10 +202,6 @@ void epg_init ( void )
|
|||
if (sect) free(sect);
|
||||
sect = strdup(s);
|
||||
|
||||
/* Channel */
|
||||
} else if ( !strcmp(sect, "channels") ) {
|
||||
if (epg_channel_deserialize(m, 1, &save)) stats.channels.total++;
|
||||
|
||||
/* Brand */
|
||||
} else if ( !strcmp(sect, "brands") ) {
|
||||
if (epg_brand_deserialize(m, 1, &save)) stats.brands.total++;
|
||||
|
@ -297,8 +265,10 @@ void epg_updated ( void )
|
|||
|
||||
static void _epg_object_destroy ( epg_object_t *eo, epg_object_tree_t *tree )
|
||||
{
|
||||
assert(eo->refcount == 0);
|
||||
if (eo->uri) free(eo->uri);
|
||||
if (tree) RB_REMOVE(tree, eo, glink);
|
||||
LIST_REMOVE(eo, hlink);
|
||||
}
|
||||
|
||||
static void _epg_object_getref ( epg_object_t *eo )
|
||||
|
@ -335,7 +305,7 @@ static epg_object_t *_epg_object_find
|
|||
if (!eo->putref) eo->putref = _epg_object_putref;
|
||||
_epg_object_idx++;
|
||||
LIST_INSERT_HEAD(&epg_object_unref, eo, ulink);
|
||||
LIST_INSERT_HEAD(&epg_objects[eo->id & EPG_HASH_WIDTH], eo, hlink);
|
||||
LIST_INSERT_HEAD(&epg_objects[eo->id & EPG_HASH_MASK], eo, hlink);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -366,7 +336,7 @@ static epg_object_t *_epg_object_find_by_uri
|
|||
static epg_object_t *_epg_object_find_by_id2 ( uint64_t id )
|
||||
{
|
||||
epg_object_t *eo;
|
||||
LIST_FOREACH(eo, &epg_objects[id & EPG_HASH_WIDTH], hlink) {
|
||||
LIST_FOREACH(eo, &epg_objects[id & EPG_HASH_MASK], hlink) {
|
||||
if (eo->id == id) return eo;
|
||||
}
|
||||
return NULL;
|
||||
|
@ -410,6 +380,7 @@ epg_brand_t* epg_brand_find_by_uri
|
|||
static epg_object_t *skel = NULL;
|
||||
if ( !skel ) {
|
||||
skel = calloc(1, sizeof(epg_brand_t));
|
||||
skel->type = EPG_BRAND;
|
||||
skel->destroy = _epg_brand_destroy;
|
||||
}
|
||||
return (epg_brand_t*)
|
||||
|
@ -543,6 +514,7 @@ epg_season_t* epg_season_find_by_uri
|
|||
static epg_object_t *skel = NULL;
|
||||
if ( !skel ) {
|
||||
skel = calloc(1, sizeof(epg_season_t));
|
||||
skel->type = EPG_SEASON;
|
||||
skel->destroy = _epg_season_destroy;
|
||||
}
|
||||
return (epg_season_t*)
|
||||
|
@ -694,6 +666,7 @@ epg_episode_t* epg_episode_find_by_uri
|
|||
static epg_object_t *skel = NULL;
|
||||
if ( !skel ) {
|
||||
skel = calloc(1, sizeof(epg_episode_t));
|
||||
skel->type = EPG_EPISODE;
|
||||
skel->destroy = _epg_episode_destroy;
|
||||
}
|
||||
return (epg_episode_t*)
|
||||
|
@ -939,17 +912,17 @@ static void _epg_broadcast_destroy ( epg_object_t *eo )
|
|||
}
|
||||
|
||||
epg_broadcast_t* epg_broadcast_find_by_time
|
||||
( epg_channel_t *channel, time_t start, time_t stop, int create, int *save )
|
||||
( channel_t *channel, time_t start, time_t stop, int create, int *save )
|
||||
{
|
||||
return epg_channel_get_broadcast(channel, start, stop, create, save);
|
||||
}
|
||||
|
||||
// TODO: allow optional channel parameter?
|
||||
epg_broadcast_t *epg_broadcast_find_by_id ( uint64_t id, epg_channel_t *ec )
|
||||
epg_broadcast_t *epg_broadcast_find_by_id ( uint64_t id, channel_t *ch )
|
||||
{
|
||||
epg_object_t *eo = NULL;
|
||||
if ( ec ) {
|
||||
eo = _epg_object_find_by_id(id, &((epg_channel_t*)ec)->schedule);
|
||||
if ( ch ) {
|
||||
eo = _epg_object_find_by_id(id, &ch->ch_epg_schedule);
|
||||
} else {
|
||||
eo = _epg_object_find_by_id2(id);
|
||||
}
|
||||
|
@ -984,31 +957,27 @@ htsmsg_t *epg_broadcast_serialize ( epg_broadcast_t *broadcast )
|
|||
{
|
||||
htsmsg_t *m;
|
||||
if (!broadcast) return NULL;
|
||||
if (!broadcast->channel || !broadcast->channel->_.uri) return NULL;
|
||||
if (!broadcast->episode || !broadcast->episode->_.uri) return NULL;
|
||||
m = htsmsg_create_map();
|
||||
|
||||
htsmsg_add_u32(m, "id", broadcast->_.id);
|
||||
htsmsg_add_u64(m, "id", broadcast->_.id);
|
||||
htsmsg_add_u32(m, "start", broadcast->start);
|
||||
htsmsg_add_u32(m, "stop", broadcast->stop);
|
||||
if (broadcast->channel)
|
||||
htsmsg_add_str(m, "channel", broadcast->channel->_.uri);
|
||||
htsmsg_add_u32(m, "channel", broadcast->channel->ch_id);
|
||||
htsmsg_add_str(m, "episode", broadcast->episode->_.uri);
|
||||
|
||||
if (broadcast->dvb_id)
|
||||
htsmsg_add_u32(m, "dvb-id", broadcast->dvb_id);
|
||||
// TODO: add other metadata fields
|
||||
return m;
|
||||
}
|
||||
|
||||
epg_broadcast_t *epg_broadcast_deserialize
|
||||
( htsmsg_t *m, int create, int *save )
|
||||
{
|
||||
channel_t *ch;
|
||||
epg_broadcast_t *ebc;
|
||||
epg_channel_t *ec;
|
||||
epg_episode_t *ee;
|
||||
const char *str;
|
||||
uint32_t start, stop;
|
||||
uint32_t chid, start, stop;
|
||||
uint64_t id;
|
||||
|
||||
// TODO: need to handle broadcasts without a channel
|
||||
|
@ -1018,12 +987,12 @@ epg_broadcast_t *epg_broadcast_deserialize
|
|||
if ( htsmsg_get_u64(m, "id", &id) ) return NULL;
|
||||
if ( htsmsg_get_u32(m, "start", &start) ) return NULL;
|
||||
if ( htsmsg_get_u32(m, "stop", &stop) ) return NULL;
|
||||
if ( !(str = htsmsg_get_str(m, "channel")) ) return NULL;
|
||||
if ( !(ec = epg_channel_find_by_uri(str, 0, NULL)) ) return NULL;
|
||||
if ( htsmsg_get_u32(m, "channel", &chid) ) return NULL;
|
||||
if ( !(ch = channel_find_by_identifier(chid)) ) return NULL;
|
||||
if ( !(str = htsmsg_get_str(m, "episode")) ) return NULL;
|
||||
if ( !(ee = epg_episode_find_by_uri(str, 0, NULL)) ) return NULL;
|
||||
|
||||
ebc = epg_broadcast_find_by_time(ec, start, stop, create, save);
|
||||
ebc = epg_broadcast_find_by_time(ch, start, stop, create, save);
|
||||
if ( !ebc ) return NULL;
|
||||
|
||||
*save |= epg_broadcast_set_episode(ebc, ee);
|
||||
|
@ -1032,11 +1001,7 @@ epg_broadcast_t *epg_broadcast_deserialize
|
|||
ebc->_.id = id;
|
||||
if ( id >= _epg_object_idx ) _epg_object_idx = id + 1;
|
||||
|
||||
#if TODO_BROADCAST_METADATA
|
||||
if ( !htsmsg_get_u32(m, "dvb-id", &u32) )
|
||||
save |= epg_broadcast_set_dvb_id(ebc, u32);
|
||||
// TODO: more metadata
|
||||
#endif
|
||||
|
||||
return ebc;
|
||||
}
|
||||
|
@ -1050,281 +1015,101 @@ static void _epg_channel_timer_callback ( void *p )
|
|||
time_t next = 0;
|
||||
epg_object_t *eo;
|
||||
epg_broadcast_t *ebc, *cur;
|
||||
epg_channel_t *ec = (epg_channel_t*)p;
|
||||
channel_t *ch = (channel_t*)p;
|
||||
|
||||
/* Clear now/next */
|
||||
cur = ec->now;
|
||||
ec->now = ec->next = NULL;
|
||||
cur = ch->ch_epg_now;
|
||||
ch->ch_epg_now = ch->ch_epg_next = NULL;
|
||||
|
||||
/* Check events */
|
||||
while ( (eo = RB_FIRST(&ec->schedule)) ) {
|
||||
while ( (eo = RB_FIRST(&ch->ch_epg_schedule)) ) {
|
||||
ebc = (epg_broadcast_t*)eo;
|
||||
|
||||
/* Expire */
|
||||
if ( ebc->stop <= dispatch_clock ) {
|
||||
RB_REMOVE(&ec->schedule, eo, glink);
|
||||
RB_REMOVE(&ch->ch_epg_schedule, eo, glink);
|
||||
tvhlog(LOG_DEBUG, "epg", "expire event %lu from %s",
|
||||
eo->id, ec->_.uri);
|
||||
eo->id, ch->ch_name);
|
||||
eo->putref(eo);
|
||||
continue; // skip to next
|
||||
|
||||
/* No now */
|
||||
} else if ( ebc->start > dispatch_clock ) {
|
||||
ec->next = ebc;
|
||||
next = ebc->start;
|
||||
ch->ch_epg_next = ebc;
|
||||
next = ebc->start;
|
||||
|
||||
/* Now/Next */
|
||||
} else {
|
||||
ec->now = ebc;
|
||||
ec->next = (epg_broadcast_t*)RB_NEXT(eo, glink);
|
||||
next = ebc->stop;
|
||||
ch->ch_epg_now = ebc;
|
||||
ch->ch_epg_next = (epg_broadcast_t*)RB_NEXT(eo, glink);
|
||||
next = ebc->stop;
|
||||
}
|
||||
break;
|
||||
}
|
||||
tvhlog(LOG_DEBUG, "epg", "now/next %lu/%lu set on %s",
|
||||
ec->now ? ec->now->_.id : 0,
|
||||
ec->next ? ec->next->_.id : 0,
|
||||
ec->_.uri);
|
||||
ch->ch_epg_now ?: 0,
|
||||
ch->ch_epg_next ?: 0,
|
||||
ch->ch_name);
|
||||
|
||||
/* re-arm */
|
||||
if ( next ) {
|
||||
tvhlog(LOG_DEBUG, "epg", "arm channel timer @ %lu for %s",
|
||||
next, ec->_.uri);
|
||||
gtimer_arm_abs(&ec->expire, _epg_channel_timer_callback, ec, next);
|
||||
next, ch->ch_name);
|
||||
gtimer_arm_abs(&ch->ch_epg_timer, _epg_channel_timer_callback, ch, next);
|
||||
}
|
||||
|
||||
/* Update HTSP */
|
||||
if ( (cur != ec->now) && ec->channel ) {
|
||||
if ( cur != ch->ch_epg_now ) {
|
||||
tvhlog(LOG_DEBUG, "epg", "inform HTSP of now event change on %s",
|
||||
ec->_.uri);
|
||||
htsp_channel_update_current(ec->channel);
|
||||
ch->ch_name);
|
||||
htsp_channel_update_current(ch);
|
||||
}
|
||||
}
|
||||
|
||||
static void _epg_channel_destroy ( epg_object_t *eo )
|
||||
void epg_channel_unlink ( channel_t *ch )
|
||||
{
|
||||
epg_object_t *ebc;
|
||||
epg_channel_t *ec = (epg_channel_t*)eo;
|
||||
if (ec->channel) {
|
||||
tvhlog(LOG_CRIT, "epg", "attempt to destroy mapped channel");
|
||||
assert(0);
|
||||
epg_object_t *eo;
|
||||
while ( (eo = RB_FIRST(&ch->ch_epg_schedule)) ) {
|
||||
RB_REMOVE(&ch->ch_epg_schedule, eo, glink);
|
||||
}
|
||||
#if TODO_WHAT_SHOULD_BE_DONE
|
||||
if (RB_FIRST(&ec->schedule)) {
|
||||
tvhlog(LOG_CRIT, "epg", "attempt to destroy channel with schedule");
|
||||
assert(0);
|
||||
}
|
||||
#endif
|
||||
_epg_object_destroy(eo, &epg_channels);
|
||||
// TODO: should we be doing this?
|
||||
while ((ebc = RB_FIRST(&ec->schedule))) {
|
||||
RB_REMOVE(&ec->schedule, ebc, glink);
|
||||
ebc->putref(ebc);
|
||||
}
|
||||
gtimer_disarm(&ec->expire);
|
||||
if (ec->name) free(ec->name);
|
||||
#if TODO_NOT_IMPLEMENTED
|
||||
if (ec->sname) free(ec->sname);
|
||||
if (ec->sid) free(ec->sid);
|
||||
#endif
|
||||
free(ec);
|
||||
}
|
||||
|
||||
epg_channel_t* epg_channel_find_by_uri
|
||||
( const char *uri, int create, int *save )
|
||||
{
|
||||
int save2 = 0;
|
||||
static epg_object_t *skel = NULL;
|
||||
if ( !skel ) {
|
||||
skel = calloc(1, sizeof(epg_channel_t));
|
||||
skel->destroy = _epg_channel_destroy;
|
||||
}
|
||||
epg_channel_t *ec = (epg_channel_t*)
|
||||
_epg_object_find_by_uri(uri, create, &save2,
|
||||
&epg_channels, &skel);
|
||||
if (save2) {
|
||||
LIST_INSERT_HEAD(&epg_channel_unmapped, ec, umlink);
|
||||
*save |= 1;
|
||||
}
|
||||
return ec;
|
||||
}
|
||||
|
||||
epg_channel_t *epg_channel_find_by_id ( uint64_t id )
|
||||
{
|
||||
return (epg_channel_t*)_epg_object_find_by_id(id, &epg_channels);
|
||||
}
|
||||
|
||||
int epg_channel_set_name ( epg_channel_t *channel, const char *name )
|
||||
{
|
||||
int save = 0;
|
||||
channel_t *ch;
|
||||
if ( !channel || !name ) return 0;
|
||||
if ( !channel->name || strcmp(channel->name, name) ) {
|
||||
if (channel->name) free(channel->name);
|
||||
channel->name = strdup(name);
|
||||
// TODO: need to allow overriding of EIT created channels
|
||||
if ( !channel->channel ) {
|
||||
LIST_FOREACH(ch, &channel_unmapped, ch_eulink) {
|
||||
if ( _epg_channel_cmp(channel, ch) ) {
|
||||
epg_channel_set_channel(channel, ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// pass through
|
||||
if ( channel->channel ) {
|
||||
save |= channel_rename(channel->channel, name);
|
||||
}
|
||||
save |= 1;
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
||||
int epg_channel_set_icon ( epg_channel_t *channel, const char *icon )
|
||||
{
|
||||
int save = 0;
|
||||
if ( !channel | !icon ) return 0;
|
||||
if ( !channel->icon || strcmp(channel->icon, icon) ) {
|
||||
if ( channel->icon ) free(channel->icon);
|
||||
channel->icon = strdup(icon);
|
||||
// pass through
|
||||
if ( channel->channel ) {
|
||||
channel_set_icon(channel->channel, icon);
|
||||
}
|
||||
save = 1;
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
||||
int epg_channel_set_channel ( epg_channel_t *ec, channel_t *ch )
|
||||
{
|
||||
int save = 0;
|
||||
if ( !ec ) return 0;
|
||||
if ( ec->channel != ch ) {
|
||||
if (ec->channel) {
|
||||
tvhlog(LOG_DEBUG, "epg", "unlink channels %-30s -> %s",
|
||||
ec->_.uri, ec->channel->ch_name);
|
||||
channel_set_epg_source(ec->channel, NULL);
|
||||
LIST_INSERT_HEAD(&channel_unmapped, ec->channel, ch_eulink);
|
||||
} else {
|
||||
LIST_REMOVE(ec, umlink);
|
||||
}
|
||||
ec->channel = ch;
|
||||
if (!ch) {
|
||||
LIST_INSERT_HEAD(&epg_channel_unmapped, ec, umlink);
|
||||
ec->_.putref((epg_object_t*)ec);
|
||||
} else {
|
||||
tvhlog(LOG_DEBUG, "epg", "link channels %-30s -> %s",
|
||||
ec->_.uri, ch->ch_name);
|
||||
channel_set_epg_source(ch, ec);
|
||||
ec->_.getref((epg_object_t*)ec);
|
||||
}
|
||||
save |= 1;
|
||||
}
|
||||
return save;
|
||||
gtimer_disarm(&ch->ch_epg_timer);
|
||||
}
|
||||
|
||||
epg_broadcast_t *epg_channel_get_broadcast
|
||||
( epg_channel_t *channel, time_t start, time_t stop, int create, int *save )
|
||||
( channel_t *ch, time_t start, time_t stop, int create, int *save )
|
||||
{
|
||||
int save2 = 0;
|
||||
epg_broadcast_t *ebc;
|
||||
static epg_broadcast_t *skel;
|
||||
if ( !channel || !start || !stop ) return NULL;
|
||||
if ( !ch || !start || !stop ) return NULL;
|
||||
if ( stop <= start ) return NULL;
|
||||
if ( stop < dispatch_clock ) return NULL;
|
||||
|
||||
if ( skel == NULL ) skel = calloc(1, sizeof(epg_broadcast_t));
|
||||
skel->channel = channel;
|
||||
skel->channel = ch;
|
||||
skel->start = start;
|
||||
skel->stop = stop;
|
||||
skel->_.id = _epg_object_idx;
|
||||
skel->_.type = EPG_BROADCAST;
|
||||
skel->_.destroy = _epg_broadcast_destroy;
|
||||
|
||||
ebc = (epg_broadcast_t*)
|
||||
_epg_object_find(create, &save2, &channel->schedule,
|
||||
_epg_object_find(create, &save2, &ch->ch_epg_schedule,
|
||||
(epg_object_t**)&skel, _ebc_win_cmp);
|
||||
if (save2) {
|
||||
ebc->_.getref((epg_object_t*)ebc);
|
||||
|
||||
/* New current/next */
|
||||
if ( (RB_FIRST(&channel->schedule) == (epg_object_t*)ebc) ||
|
||||
(channel->now &&
|
||||
RB_NEXT((epg_object_t*)channel->now, glink) == (epg_object_t*)ebc) ) {
|
||||
_epg_channel_timer_callback(channel);
|
||||
if ( (RB_FIRST(&ch->ch_epg_schedule) == (epg_object_t*)ebc) ||
|
||||
(ch->ch_epg_now &&
|
||||
RB_NEXT((epg_object_t*)ch->ch_epg_now, glink) == (epg_object_t*)ebc) ) {
|
||||
_epg_channel_timer_callback(ch);
|
||||
}
|
||||
*save |= 1;
|
||||
}
|
||||
return ebc;
|
||||
}
|
||||
|
||||
htsmsg_t *epg_channel_serialize ( epg_channel_t *channel )
|
||||
{
|
||||
htsmsg_t *m;
|
||||
if (!channel || !channel->_.uri) return NULL;
|
||||
m = htsmsg_create_map();
|
||||
htsmsg_add_str(m, "uri", channel->_.uri);
|
||||
if (channel->name)
|
||||
htsmsg_add_str(m, "name", channel->name);
|
||||
if (channel->channel)
|
||||
htsmsg_add_u32(m, "channel", channel->channel->ch_id);
|
||||
// TODO: other data
|
||||
return m;
|
||||
}
|
||||
|
||||
epg_channel_t *epg_channel_deserialize ( htsmsg_t *m, int create, int *save )
|
||||
{
|
||||
epg_channel_t *ec;
|
||||
channel_t *ch;
|
||||
uint32_t u32;
|
||||
const char *str;
|
||||
|
||||
if ( !(str = htsmsg_get_str(m, "uri")) ) return NULL;
|
||||
if ( !(ec = epg_channel_find_by_uri(str, create, save)) ) return NULL;
|
||||
|
||||
if ( (str = htsmsg_get_str(m, "name")) )
|
||||
*save |= epg_channel_set_name(ec, str);
|
||||
|
||||
if ( !htsmsg_get_u32(m, "channel", &u32) )
|
||||
if ( (ch = channel_find_by_identifier(u32)) )
|
||||
epg_channel_set_channel(ec, ch);
|
||||
// TODO: this call needs updating
|
||||
|
||||
return ec;
|
||||
}
|
||||
|
||||
/* **************************************************************************
|
||||
* Channel mapping
|
||||
* *************************************************************************/
|
||||
|
||||
void epg_channel_map_add ( channel_t *ch )
|
||||
{
|
||||
epg_channel_t *ec;
|
||||
LIST_FOREACH(ec, &epg_channel_unmapped, umlink) {
|
||||
if ( _epg_channel_cmp(ec, ch) ) {
|
||||
epg_channel_set_channel(ec, ch);
|
||||
return;
|
||||
}
|
||||
}
|
||||
LIST_INSERT_HEAD(&channel_unmapped, ch, ch_eulink);
|
||||
}
|
||||
|
||||
void epg_channel_map_rem ( channel_t *ch )
|
||||
{
|
||||
if (ch->ch_epg_channel) {
|
||||
epg_channel_set_channel(ch->ch_epg_channel, NULL);
|
||||
} else {
|
||||
LIST_REMOVE(ch, ch_eulink);
|
||||
}
|
||||
}
|
||||
|
||||
void epg_channel_map_mod ( channel_t *ch )
|
||||
{
|
||||
// If already mapped, ignore
|
||||
if (!ch->ch_epg_channel) epg_channel_map_add(ch);
|
||||
}
|
||||
|
||||
|
||||
/* **************************************************************************
|
||||
* Querying
|
||||
* *************************************************************************/
|
||||
|
@ -1344,13 +1129,13 @@ static void _eqr_add ( epg_query_result_t *eqr, epg_broadcast_t *e )
|
|||
}
|
||||
|
||||
static void _eqr_add_channel
|
||||
( epg_query_result_t *eqr, epg_channel_t *ec )
|
||||
( epg_query_result_t *eqr, channel_t *ch )
|
||||
{
|
||||
epg_object_t *eo;
|
||||
epg_broadcast_t *ebc;
|
||||
RB_FOREACH(eo, &ec->schedule, glink) {
|
||||
RB_FOREACH(eo, &ch->ch_epg_schedule, glink) {
|
||||
ebc = (epg_broadcast_t*)eo;
|
||||
if ( ebc->episode && ebc->channel ) _eqr_add(eqr, ebc);
|
||||
if ( ebc->episode ) _eqr_add(eqr, ebc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1358,20 +1143,18 @@ void epg_query0
|
|||
( epg_query_result_t *eqr, channel_t *channel, channel_tag_t *tag,
|
||||
uint8_t contentgroup, const char *title )
|
||||
{
|
||||
epg_object_t *ec;
|
||||
|
||||
/* Clear (just incase) */
|
||||
memset(eqr, 0, sizeof(epg_query_result_t));
|
||||
|
||||
/* All channels */
|
||||
if (!channel) {
|
||||
RB_FOREACH(ec, &epg_channels, glink) {
|
||||
_eqr_add_channel(eqr, (epg_channel_t*)ec);
|
||||
RB_FOREACH(channel, &channel_name_tree, ch_name_link) {
|
||||
_eqr_add_channel(eqr, channel);
|
||||
}
|
||||
|
||||
/* Single channel */
|
||||
} else if ( channel->ch_epg_channel ) {
|
||||
_eqr_add_channel(eqr, channel->ch_epg_channel);
|
||||
} else if ( channel ) {
|
||||
_eqr_add_channel(eqr, channel);
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
13
src/epg.h
13
src/epg.h
|
@ -63,6 +63,16 @@ typedef struct epg_broadcast_tree epg_broadcast_tree_t;
|
|||
* Generic Object
|
||||
* ***********************************************************************/
|
||||
|
||||
/* Object type */
|
||||
typedef enum epg_object_type
|
||||
{
|
||||
EPG_UNDEF,
|
||||
EPG_BRAND,
|
||||
EPG_SEASON,
|
||||
EPG_EPISODE,
|
||||
EPG_BROADCAST
|
||||
} epg_object_type_t;
|
||||
|
||||
/* Object */
|
||||
typedef struct epg_object
|
||||
{
|
||||
|
@ -70,8 +80,9 @@ typedef struct epg_object
|
|||
LIST_ENTRY(epg_object) hlink; ///< Global (ID) hash link
|
||||
LIST_ENTRY(epg_object) ulink; ///< Global unref'd link
|
||||
|
||||
char *uri; ///< Unique ID (from grabber)
|
||||
epg_object_type_t type; ///< Specific object type
|
||||
uint64_t id; ///< Internal ID
|
||||
char *uri; ///< Unique ID (from grabber)
|
||||
int refcount; ///< Reference counting
|
||||
|
||||
void (*getref) ( epg_object_t* ); ///< Get a reference
|
||||
|
|
134
src/epggrab.c
134
src/epggrab.c
|
@ -4,10 +4,13 @@
|
|||
#include "htsmsg.h"
|
||||
#include "settings.h"
|
||||
#include "tvheadend.h"
|
||||
#include "src/queue.h"
|
||||
#include "epg.h"
|
||||
#include "epggrab.h"
|
||||
#include "epggrab/pyepg.h"
|
||||
#include "epggrab/eit.h"
|
||||
#include "epggrab/xmltv.h"
|
||||
#include "epggrab/pyepg.h"
|
||||
#include "channels.h"
|
||||
|
||||
/* Thread protection */
|
||||
int epggrab_confver;
|
||||
|
@ -22,8 +25,8 @@ epggrab_module_t* epggrab_module;
|
|||
epggrab_sched_list_t epggrab_schedule;
|
||||
|
||||
/* Modules */
|
||||
epggrab_module_t* epggrab_module_pyepg;
|
||||
epggrab_module_t* epggrab_module_xmltv;
|
||||
LIST_HEAD(epggrab_module_list, epggrab_module);
|
||||
struct epggrab_module_list epggrab_modules;
|
||||
|
||||
/* Internal prototypes */
|
||||
static void* _epggrab_thread ( void* );
|
||||
|
@ -45,8 +48,11 @@ void epggrab_init ( void )
|
|||
epggrab_module = NULL; // disabled
|
||||
|
||||
/* Initialise modules */
|
||||
epggrab_module_pyepg = pyepg_init();
|
||||
epggrab_module_xmltv = xmltv_init();
|
||||
LIST_INSERT_HEAD(&epggrab_modules, eit_init(), glink);
|
||||
#if TODO_XMLTV_SUPPORT
|
||||
LIST_INSERT_HEAD(&epggrab_modules, xmltv_init(), glink);
|
||||
#endif
|
||||
LIST_INSERT_HEAD(&epggrab_modules, pyepg_init(), glink);
|
||||
|
||||
/* Start thread */
|
||||
pthread_t tid;
|
||||
|
@ -190,11 +196,125 @@ void* _epggrab_thread ( void* p )
|
|||
|
||||
epggrab_module_t* epggrab_module_find_by_name ( const char *name )
|
||||
{
|
||||
if ( strcmp(name, "pyepg") == 0 ) return epggrab_module_pyepg;
|
||||
if ( strcmp(name, "xmltv") == 0 ) return epggrab_module_xmltv;
|
||||
epggrab_module_t *m;
|
||||
LIST_FOREACH(m, &epggrab_modules, glink) {
|
||||
if ( !strcmp(m->name(), name) ) return m;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* **************************************************************************
|
||||
* Channel handling
|
||||
*
|
||||
* TODO: this is all a bit messy (too much indirection?) but it works!
|
||||
*
|
||||
* TODO: need to save mappings to disk
|
||||
*
|
||||
* TODO: probably want rules on what can be updated etc...
|
||||
* *************************************************************************/
|
||||
|
||||
static int _ch_id_cmp ( void *a, void *b )
|
||||
{
|
||||
return strcmp(((epggrab_channel_t*)a)->id,
|
||||
((epggrab_channel_t*)b)->id);
|
||||
}
|
||||
|
||||
static channel_t *_channel_find ( epggrab_channel_t *ch )
|
||||
{
|
||||
channel_t *ret = NULL;
|
||||
if (ch->name) ret = channel_find_by_name(ch->name, 0, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _channel_match ( epggrab_channel_t *egc, channel_t *ch )
|
||||
{
|
||||
/* Already linked */
|
||||
if ( egc->channel ) return 0;
|
||||
|
||||
/* Name match */
|
||||
if ( !strcmp(ch->ch_name, egc->name) ) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void epggrab_channel_add ( channel_t *ch )
|
||||
{
|
||||
epggrab_module_t *m;
|
||||
LIST_FOREACH(m, &epggrab_modules, glink) {
|
||||
if (m->ch_add) m->ch_add(ch);
|
||||
}
|
||||
}
|
||||
|
||||
void epggrab_channel_rem ( channel_t *ch )
|
||||
{
|
||||
epggrab_module_t *m;
|
||||
LIST_FOREACH(m, &epggrab_modules, glink) {
|
||||
if (m->ch_rem) m->ch_rem(ch);
|
||||
}
|
||||
}
|
||||
|
||||
void epggrab_channel_mod ( channel_t *ch )
|
||||
{
|
||||
epggrab_module_t *m;
|
||||
LIST_FOREACH(m, &epggrab_modules, glink) {
|
||||
if (m->ch_mod) m->ch_mod(ch);
|
||||
}
|
||||
}
|
||||
|
||||
epggrab_channel_t *epggrab_module_channel_create
|
||||
( epggrab_channel_tree_t *tree, epggrab_channel_t *iskel )
|
||||
{
|
||||
epggrab_channel_t *egc;
|
||||
static epggrab_channel_t *skel = NULL;
|
||||
if (!skel) skel = calloc(1, sizeof(epggrab_channel_t));
|
||||
skel->id = iskel->id;
|
||||
skel->name = iskel->name;
|
||||
egc = RB_INSERT_SORTED(tree, skel, glink, _ch_id_cmp);
|
||||
if ( egc == NULL ) {
|
||||
skel->id = strdup(skel->id);
|
||||
skel->name = strdup(skel->name);
|
||||
skel->channel = _channel_find(skel);
|
||||
egc = skel;
|
||||
skel = NULL;
|
||||
}
|
||||
return egc;
|
||||
}
|
||||
|
||||
epggrab_channel_t *epggrab_module_channel_find
|
||||
( epggrab_channel_tree_t *tree, const char *id )
|
||||
{
|
||||
epggrab_channel_t skel;
|
||||
skel.id = (char*)id;
|
||||
return RB_FIND(tree, &skel, glink, _ch_id_cmp);
|
||||
}
|
||||
|
||||
void epggrab_module_channel_add ( epggrab_channel_tree_t *tree, channel_t *ch )
|
||||
{
|
||||
epggrab_channel_t *egc;
|
||||
RB_FOREACH(egc, tree, glink) {
|
||||
if (_channel_match(egc, ch) ) {
|
||||
egc->channel = ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void epggrab_module_channel_rem ( epggrab_channel_tree_t *tree, channel_t *ch )
|
||||
{
|
||||
epggrab_channel_t *egc;
|
||||
RB_FOREACH(egc, tree, glink) {
|
||||
if (egc->channel == ch) {
|
||||
egc->channel = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void epggrab_module_channel_mod ( epggrab_channel_tree_t *tree, channel_t *ch )
|
||||
{
|
||||
epggrab_module_channel_add(tree, ch);
|
||||
}
|
||||
|
||||
/* **************************************************************************
|
||||
* Configuration handling
|
||||
* *************************************************************************/
|
||||
|
|
|
@ -27,6 +27,21 @@ typedef struct epggrab_stats
|
|||
epggrab_stats_part_t broadcasts;
|
||||
} epggrab_stats_t;
|
||||
|
||||
/*
|
||||
* Grab channel
|
||||
*/
|
||||
typedef struct epggrab_channel
|
||||
{
|
||||
char *id; ///< Grabber's ID
|
||||
char *name; ///< Channel name
|
||||
struct channel *channel; ///< Mapped channel
|
||||
// TODO: I think we might need a list of channels!
|
||||
RB_ENTRY(epggrab_channel) glink; ///< Global link
|
||||
} epggrab_channel_t;
|
||||
|
||||
RB_HEAD(epggrab_channel_tree, epggrab_channel);
|
||||
typedef struct epggrab_channel_tree epggrab_channel_tree_t;
|
||||
|
||||
/*
|
||||
* Grabber base class
|
||||
*/
|
||||
|
@ -40,6 +55,8 @@ typedef struct epggrab_module
|
|||
void (*ch_add) ( struct channel *ch );
|
||||
void (*ch_rem) ( struct channel *ch );
|
||||
void (*ch_mod) ( struct channel *ch );
|
||||
|
||||
LIST_ENTRY(epggrab_module) glink; ///< Global list link
|
||||
} epggrab_module_t;
|
||||
|
||||
/*
|
||||
|
@ -97,4 +114,14 @@ void epggrab_channel_add ( struct channel *ch );
|
|||
void epggrab_channel_rem ( struct channel *ch );
|
||||
void epggrab_channel_mod ( struct channel *ch );
|
||||
|
||||
/*
|
||||
* Module specific channel handling
|
||||
*/
|
||||
void epggrab_module_channel_add ( epggrab_channel_tree_t *tree, struct channel *ch );
|
||||
void epggrab_module_channel_rem ( epggrab_channel_tree_t *tree, struct channel *ch );
|
||||
void epggrab_module_channel_mod ( epggrab_channel_tree_t *tree, struct channel *ch );
|
||||
|
||||
epggrab_channel_t *epggrab_module_channel_create ( epggrab_channel_tree_t *tree, epggrab_channel_t *ch );
|
||||
epggrab_channel_t *epggrab_module_channel_find ( epggrab_channel_tree_t *tree, const char *id );
|
||||
|
||||
#endif /* __EPGGRAB_H__ */
|
||||
|
|
|
@ -43,7 +43,6 @@ void eit_callback ( channel_t *ch, int id, time_t start, time_t stop,
|
|||
const char *extitem, const char *extdesc,
|
||||
const char *exttext ) {
|
||||
int save = 0;
|
||||
epg_channel_t *ec;
|
||||
epg_broadcast_t *ebc;
|
||||
epg_episode_t *ee;
|
||||
const char *summary = NULL;
|
||||
|
@ -53,17 +52,8 @@ void eit_callback ( channel_t *ch, int id, time_t start, time_t stop,
|
|||
/* Disabled? */
|
||||
//if (epggrab_eit_disabled) return;
|
||||
|
||||
/* Channel */
|
||||
ec = ch->ch_epg_channel;
|
||||
if (!ec) {
|
||||
ec = epg_channel_find_by_uri(ch->ch_name, 1, &save);
|
||||
if (ec)
|
||||
epg_channel_set_channel(ec, ch);
|
||||
}
|
||||
if (!ec) return;
|
||||
|
||||
/* Find broadcast */
|
||||
ebc = epg_channel_get_broadcast(ch->ch_epg_channel, start, stop, 1, &save);
|
||||
ebc = epg_channel_get_broadcast(ch, start, stop, 1, &save);
|
||||
if (!ebc) return;
|
||||
|
||||
/* Create episode URI */
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
epggrab_module_t *eit_init ( void );
|
||||
|
||||
void eit_callback ( channel_t *ch, int id, time_t start, time_t stop,
|
||||
void eit_callback ( struct channel *ch, int id, time_t start, time_t stop,
|
||||
const char *title, const char *desc,
|
||||
const char *extitem, const char *extdesc,
|
||||
const char *exttext );
|
||||
|
|
|
@ -26,6 +26,44 @@
|
|||
#include "spawn.h"
|
||||
#include "epg.h"
|
||||
#include "epggrab/pyepg.h"
|
||||
#include "channels.h"
|
||||
|
||||
/* **************************************************************************
|
||||
* Channels
|
||||
* *************************************************************************/
|
||||
|
||||
epggrab_channel_tree_t _pyepg_channels;
|
||||
|
||||
static channel_t *_pyepg_channel_create ( epggrab_channel_t *skel )
|
||||
{
|
||||
epggrab_channel_t *ch
|
||||
= epggrab_module_channel_create(&_pyepg_channels, skel);
|
||||
if (ch) return ch->channel;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static channel_t *_pyepg_channel_find ( const char *id )
|
||||
{
|
||||
epggrab_channel_t *ch
|
||||
= epggrab_module_channel_find(&_pyepg_channels, id);
|
||||
if (ch) return ch->channel;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void _pyepg_channel_add ( channel_t *ch )
|
||||
{
|
||||
epggrab_module_channel_add(&_pyepg_channels, ch);
|
||||
}
|
||||
|
||||
static void _pyepg_channel_rem ( channel_t *ch )
|
||||
{
|
||||
epggrab_module_channel_rem(&_pyepg_channels, ch);
|
||||
}
|
||||
|
||||
static void _pyepg_channel_mod ( channel_t *ch )
|
||||
{
|
||||
epggrab_module_channel_mod(&_pyepg_channels, ch);
|
||||
}
|
||||
|
||||
/* **************************************************************************
|
||||
* Parsing
|
||||
|
@ -47,25 +85,26 @@ static int _pyepg_parse_channel ( htsmsg_t *data, epggrab_stats_t *stats )
|
|||
{
|
||||
int save = 0;
|
||||
htsmsg_t *attr, *tags;
|
||||
const char *id, *name = NULL;
|
||||
epg_channel_t *channel;
|
||||
const char *icon;
|
||||
channel_t *ch;
|
||||
epggrab_channel_t skel;
|
||||
|
||||
if ( data == NULL ) return 0;
|
||||
|
||||
if ((attr = htsmsg_get_map(data, "attrib")) == NULL) return 0;
|
||||
if ((id = htsmsg_get_str(attr, "id")) == NULL) return 0;
|
||||
if ((tags = htsmsg_get_map(data, "tags")) == NULL) return 0;
|
||||
if ((attr = htsmsg_get_map(data, "attrib")) == NULL) return 0;
|
||||
if ((skel.id = (char*)htsmsg_get_str(attr, "id")) == NULL) return 0;
|
||||
if ((tags = htsmsg_get_map(data, "tags")) == NULL) return 0;
|
||||
skel.name = (char*)htsmsg_xml_get_cdata_str(tags, "name");
|
||||
icon = htsmsg_xml_get_cdata_str(tags, "image");
|
||||
|
||||
/* Find channel */
|
||||
if ((channel = epg_channel_find_by_uri(id, 1, &save)) == NULL) return 0;
|
||||
stats->channels.total++;
|
||||
if (save) stats->channels.created++;
|
||||
ch = _pyepg_channel_create(&skel);
|
||||
|
||||
/* Set name */
|
||||
name = htsmsg_xml_get_cdata_str(tags, "name");
|
||||
if ( name ) save |= epg_channel_set_name(channel, name);
|
||||
|
||||
if (save) stats->channels.modified++;
|
||||
/* Update values */
|
||||
if (ch) {
|
||||
// TODO: set the name
|
||||
//if (skel.name) save |= channel_rename(ch, skel.name);
|
||||
if (icon) channel_set_icon(ch, icon);
|
||||
}
|
||||
|
||||
return save;
|
||||
}
|
||||
|
@ -245,7 +284,7 @@ static int _pyepg_parse_episode ( htsmsg_t *data, epggrab_stats_t *stats )
|
|||
}
|
||||
|
||||
static int _pyepg_parse_broadcast
|
||||
( htsmsg_t *data, epg_channel_t *channel, epggrab_stats_t *stats )
|
||||
( htsmsg_t *data, channel_t *channel, epggrab_stats_t *stats )
|
||||
{
|
||||
int save = 0;
|
||||
htsmsg_t *attr;//, *tags;
|
||||
|
@ -289,19 +328,21 @@ static int _pyepg_parse_schedule ( htsmsg_t *data, epggrab_stats_t *stats )
|
|||
int save = 0;
|
||||
htsmsg_t *attr, *tags;
|
||||
htsmsg_field_t *f;
|
||||
epg_channel_t *channel;
|
||||
channel_t *channel;
|
||||
const char *str;
|
||||
|
||||
if ( data == NULL ) return 0;
|
||||
|
||||
if ((attr = htsmsg_get_map(data, "attrib")) == NULL) return 0;
|
||||
if ((str = htsmsg_get_str(attr, "channel")) == NULL) return 0;
|
||||
if ((channel = epg_channel_find_by_uri(str, 0, NULL)) == NULL) return 0;
|
||||
if ((channel = _pyepg_channel_find(str)) == NULL) return 0;
|
||||
if ((tags = htsmsg_get_map(data, "tags")) == NULL) return 0;
|
||||
stats->channels.total++;
|
||||
|
||||
HTSMSG_FOREACH(f, tags) {
|
||||
if (strcmp(f->hmf_name, "broadcast") == 0) {
|
||||
save |= _pyepg_parse_broadcast(htsmsg_get_map_by_field(f), channel, stats);
|
||||
save |= _pyepg_parse_broadcast(htsmsg_get_map_by_field(f),
|
||||
channel, stats);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -333,7 +374,6 @@ static int _pyepg_parse_epg ( htsmsg_t *data, epggrab_stats_t *stats )
|
|||
return save;
|
||||
}
|
||||
|
||||
// TODO: add stats updating
|
||||
static int _pyepg_parse ( htsmsg_t *data, epggrab_stats_t *stats )
|
||||
{
|
||||
htsmsg_t *tags, *epg;
|
||||
|
@ -341,8 +381,6 @@ static int _pyepg_parse ( htsmsg_t *data, epggrab_stats_t *stats )
|
|||
|
||||
if ((tags = htsmsg_get_map(data, "tags")) == NULL) return 0;
|
||||
|
||||
// TODO: might be a better way to do this using DTD definition?
|
||||
|
||||
/* PyEPG format */
|
||||
if ((epg = htsmsg_get_map(tags, "epg")) != NULL)
|
||||
return _pyepg_parse_epg(epg, stats);
|
||||
|
@ -419,6 +457,9 @@ epggrab_module_t* pyepg_init ( void )
|
|||
pyepg_module.grab = _pyepg_grab;
|
||||
pyepg_module.parse = _pyepg_parse;
|
||||
pyepg_module.name = _pyepg_name;
|
||||
pyepg_module.ch_add = _pyepg_channel_add;
|
||||
pyepg_module.ch_rem = _pyepg_channel_rem;
|
||||
pyepg_module.ch_mod = _pyepg_channel_mod;
|
||||
return &pyepg_module;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
/* **************************************************************************
|
||||
* Parsing
|
||||
* *************************************************************************/
|
||||
#if TODO_XMLTV_GRABBER
|
||||
|
||||
/**
|
||||
* Hash the description to get a URI for episode
|
||||
|
@ -425,3 +426,5 @@ epggrab_module_t* xmltv_init ( void )
|
|||
xmltv_module.name = _xmltv_name;
|
||||
return &xmltv_module;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -404,8 +404,8 @@ main(int argc, char **argv)
|
|||
|
||||
capmt_init();
|
||||
|
||||
epg_init();
|
||||
epggrab_init();
|
||||
epg_init();
|
||||
|
||||
dvr_init();
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue