Significant update to the EPG code, now have reference counting and timeouts (not tested) and some general simplifications to the API.
This commit is contained in:
parent
b044969297
commit
b7575b18e5
2 changed files with 353 additions and 249 deletions
516
src/epg.c
516
src/epg.c
|
@ -16,6 +16,16 @@
|
|||
* 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>
|
||||
|
@ -42,11 +52,15 @@ struct epg_object_tree epg_episodes;
|
|||
struct epg_object_tree epg_channels;
|
||||
struct epg_object_tree epg_broadcasts;
|
||||
|
||||
/* Unlinked channels */
|
||||
LIST_HEAD(epg_unlinked_channel_list1, epg_channel);
|
||||
LIST_HEAD(epg_unlinked_channel_list2, channel);
|
||||
struct epg_unlinked_channel_list1 epg_unlinked_channels1;
|
||||
struct epg_unlinked_channel_list2 epg_unlinked_channels2;
|
||||
/* 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 */
|
||||
LIST_HEAD(epg_object_unref_list, epg_object);
|
||||
struct epg_object_unref_list epg_object_unref;
|
||||
|
||||
/* Global counters */
|
||||
static uint64_t _epg_object_idx = 0;
|
||||
|
@ -177,11 +191,10 @@ static int _epg_write_sect ( int fd, const char *sect )
|
|||
|
||||
void epg_save ( void )
|
||||
{
|
||||
// TODO: skip unref'd objects?
|
||||
int fd;
|
||||
epg_object_t *eo, *ec;
|
||||
|
||||
// TODO: requires markers in the file or some other means of
|
||||
// determining where the various object types are?
|
||||
fd = hts_settings_open_file(1, "epgdb");
|
||||
|
||||
/* Channels */
|
||||
|
@ -218,7 +231,7 @@ void epg_init ( void )
|
|||
char *sect = NULL;
|
||||
const char *s;
|
||||
epggrab_stats_t stats;
|
||||
|
||||
|
||||
/* Map file to memory */
|
||||
fd = hts_settings_open_file(0, "epgdb");
|
||||
if ( fd < 0 ) {
|
||||
|
@ -313,45 +326,38 @@ void epg_init ( void )
|
|||
|
||||
void epg_updated ( void )
|
||||
{
|
||||
epg_object_t *eo;
|
||||
LIST_FOREACH(eo, &epg_object_unref, ulink) {
|
||||
printf("unref'd object %lu\n", eo->id);
|
||||
}
|
||||
|
||||
// TODO: remove this
|
||||
if (0)_epg_dump();
|
||||
}
|
||||
|
||||
void epg_add_channel ( channel_t *ch )
|
||||
{
|
||||
#if TODO_REDO_CHANNEL_LINKING
|
||||
epg_channel_t *ec;
|
||||
LIST_INSERT_HEAD(&epg_unlinked_channels2, ch, ch_eulink);
|
||||
LIST_FOREACH(ec, &epg_unlinked_channels1, ulink) {
|
||||
if ( _epg_channel_cmp(ec, ch) ) {
|
||||
epg_channel_set_channel(ec, ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void epg_rem_channel ( channel_t *ch )
|
||||
{
|
||||
#if TODO_REDO_CHANNEL_LINKING
|
||||
if ( ch->ch_epg_channel ) {
|
||||
ch->ch_epg_channel->channel = NULL;
|
||||
// TODO: free the channel?
|
||||
} else {
|
||||
LIST_REMOVE(ch, ch_eulink);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void epg_mod_channel ( channel_t *ch )
|
||||
{
|
||||
if ( !ch->ch_epg_channel ) epg_add_channel(ch);
|
||||
}
|
||||
|
||||
|
||||
/* **************************************************************************
|
||||
* Object
|
||||
* *************************************************************************/
|
||||
|
||||
static void _epg_object_destroy ( epg_object_t *eo )
|
||||
{
|
||||
if (eo->uri) free(eo->uri);
|
||||
}
|
||||
|
||||
static void _epg_object_getref ( epg_object_t *eo )
|
||||
{
|
||||
if (eo->refcount == 0) LIST_REMOVE(eo, ulink);
|
||||
eo->refcount++;
|
||||
}
|
||||
|
||||
static void _epg_object_putref ( epg_object_t *eo )
|
||||
{
|
||||
assert(eo->refcount>0); // Sanity!
|
||||
eo->refcount--;
|
||||
// TODO: do this here or defer to the epg_updated call?
|
||||
if (!eo->refcount) eo->destroy(eo);
|
||||
}
|
||||
|
||||
static epg_object_t *_epg_object_find
|
||||
( int create, int *save, epg_object_tree_t *tree,
|
||||
epg_object_t **skel, int (*cmp) (const void*,const void*))
|
||||
|
@ -369,7 +375,10 @@ static epg_object_t *_epg_object_find
|
|||
*save |= 1;
|
||||
eo = *skel;
|
||||
*skel = NULL;
|
||||
if (!eo->getref) eo->getref = _epg_object_getref;
|
||||
if (!eo->putref) eo->putref = _epg_object_putref;
|
||||
_epg_object_idx++;
|
||||
LIST_INSERT_HEAD(&epg_object_unref, eo, ulink);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -378,13 +387,15 @@ static epg_object_t *_epg_object_find
|
|||
|
||||
static epg_object_t *_epg_object_find_by_uri
|
||||
( const char *uri, int create, int *save,
|
||||
epg_object_tree_t *tree, size_t size )
|
||||
epg_object_tree_t *tree, size_t size,
|
||||
void (*destroy) (epg_object_t*) )
|
||||
{
|
||||
int save2 = 0;
|
||||
epg_object_t *eo;
|
||||
static epg_object_t* skel = NULL;
|
||||
static size_t skelsz = 0;
|
||||
|
||||
assert(destroy != NULL); // sanity
|
||||
lock_assert(&global_lock); // pointless!
|
||||
|
||||
if ( !skel || size > skelsz ) {
|
||||
|
@ -398,7 +409,8 @@ static epg_object_t *_epg_object_find_by_uri
|
|||
|
||||
eo = _epg_object_find(create, &save2, tree, &skel, _uri_cmp);
|
||||
if (save2) {
|
||||
eo->uri = strdup(uri);
|
||||
eo->uri = strdup(uri);
|
||||
eo->destroy = destroy;
|
||||
*save |= 1;
|
||||
|
||||
/* Shrink - not necessary (but saves RAM) */
|
||||
|
@ -422,12 +434,30 @@ static epg_object_t *_epg_object_find_by_id
|
|||
* Brand
|
||||
* *************************************************************************/
|
||||
|
||||
static void _epg_brand_destroy ( epg_object_t *eo )
|
||||
{
|
||||
epg_brand_t *eb = (epg_brand_t*)eo;
|
||||
if (RB_FIRST(&eb->seasons)) {
|
||||
tvhlog(LOG_CRIT, "epg", "attempt to destroy brand with seasons");
|
||||
assert(0);
|
||||
}
|
||||
if (RB_FIRST(&eb->episodes)) {
|
||||
tvhlog(LOG_CRIT, "epg", "attempt to destroy brand with episodes");
|
||||
assert(0);
|
||||
}
|
||||
_epg_object_destroy(eo);
|
||||
if (eb->title) free(eb->title);
|
||||
if (eb->summary) free(eb->summary);
|
||||
free(eb);
|
||||
}
|
||||
|
||||
epg_brand_t* epg_brand_find_by_uri
|
||||
( const char *uri, int create, int *save )
|
||||
{
|
||||
return (epg_brand_t*)
|
||||
_epg_object_find_by_uri(uri, create, save,
|
||||
&epg_brands, sizeof(epg_brand_t));
|
||||
&epg_brands, sizeof(epg_brand_t),
|
||||
_epg_brand_destroy);
|
||||
}
|
||||
|
||||
epg_brand_t *epg_brand_find_by_id ( uint64_t id )
|
||||
|
@ -471,58 +501,28 @@ int epg_brand_set_season_count ( epg_brand_t *brand, uint16_t count )
|
|||
return save;
|
||||
}
|
||||
|
||||
int epg_brand_add_season ( epg_brand_t *brand, epg_season_t *season, int u )
|
||||
static void _epg_brand_add_season
|
||||
( epg_brand_t *brand, epg_season_t *season )
|
||||
{
|
||||
int save = 0;
|
||||
epg_season_t *es;
|
||||
if ( !brand || !season ) return 0;
|
||||
es = RB_INSERT_SORTED(&brand->seasons, season, blink, _uri_cmp);
|
||||
if ( es == NULL ) {
|
||||
if ( u ) save |= epg_season_set_brand(season, brand, 0);
|
||||
save = 1;
|
||||
}
|
||||
return save;
|
||||
RB_INSERT_SORTED(&brand->seasons, season, blink, _uri_cmp);
|
||||
}
|
||||
|
||||
int epg_brand_rem_season ( epg_brand_t *brand, epg_season_t *season, int u )
|
||||
static void _epg_brand_rem_season
|
||||
( epg_brand_t *brand, epg_season_t *season )
|
||||
{
|
||||
int save = 0;
|
||||
epg_season_t *es;
|
||||
if ( !brand || !season ) return 0;
|
||||
es = RB_FIND(&brand->seasons, season, blink, _uri_cmp);
|
||||
if ( es != NULL ) {
|
||||
if ( u ) save |= epg_season_set_brand(season, NULL, 0); // TODO: this will do nothing
|
||||
RB_REMOVE(&brand->seasons, season, blink);
|
||||
save = 1;
|
||||
}
|
||||
return save;
|
||||
RB_REMOVE(&brand->seasons, season, blink);
|
||||
}
|
||||
|
||||
int epg_brand_add_episode ( epg_brand_t *brand, epg_episode_t *episode, int u )
|
||||
static void _epg_brand_add_episode
|
||||
( epg_brand_t *brand, epg_episode_t *episode )
|
||||
{
|
||||
int save = 0;
|
||||
epg_episode_t *ee;
|
||||
if ( !brand || !episode ) return 0;
|
||||
ee = RB_INSERT_SORTED(&brand->episodes, episode, blink, _uri_cmp);
|
||||
if ( ee == NULL ) {
|
||||
if ( u ) save |= epg_episode_set_brand(episode, brand, 0);
|
||||
save = 1;
|
||||
}
|
||||
return save;
|
||||
RB_INSERT_SORTED(&brand->episodes, episode, blink, _uri_cmp);
|
||||
}
|
||||
|
||||
int epg_brand_rem_episode ( epg_brand_t *brand, epg_episode_t *episode, int u )
|
||||
static void _epg_brand_rem_episode
|
||||
( epg_brand_t *brand, epg_episode_t *episode )
|
||||
{
|
||||
int save = 0;
|
||||
epg_episode_t *ee;
|
||||
if ( !brand || !episode ) return 0;
|
||||
ee = RB_FIND(&brand->episodes, episode, blink, _uri_cmp);
|
||||
if ( ee != NULL ) {
|
||||
if ( u ) save |= epg_episode_set_brand(episode, NULL, 0); // TODO: will do nothing
|
||||
RB_REMOVE(&brand->episodes, episode, blink);
|
||||
save = 1;
|
||||
}
|
||||
return save;
|
||||
RB_REMOVE(&brand->episodes, episode, blink);
|
||||
}
|
||||
|
||||
htsmsg_t *epg_brand_serialize ( epg_brand_t *brand )
|
||||
|
@ -565,12 +565,29 @@ epg_brand_t *epg_brand_deserialize ( htsmsg_t *m, int create, int *save )
|
|||
* Season
|
||||
* *************************************************************************/
|
||||
|
||||
static void _epg_season_destroy ( epg_object_t *eo )
|
||||
{
|
||||
epg_season_t *es = (epg_season_t*)eo;
|
||||
if (RB_FIRST(&es->episodes)) {
|
||||
tvhlog(LOG_CRIT, "epg", "attempt to destory season with episodes");
|
||||
assert(0);
|
||||
}
|
||||
_epg_object_destroy(eo);
|
||||
if (es->brand) {
|
||||
_epg_brand_rem_season(es->brand, es);
|
||||
es->brand->_.putref((epg_object_t*)es->brand);
|
||||
}
|
||||
if (es->summary) free(es->summary);
|
||||
free(es);
|
||||
}
|
||||
|
||||
epg_season_t* epg_season_find_by_uri
|
||||
( const char *uri, int create, int *save )
|
||||
{
|
||||
return (epg_season_t*)
|
||||
_epg_object_find_by_uri(uri, create, save,
|
||||
&epg_seasons, sizeof(epg_season_t));
|
||||
&epg_seasons, sizeof(epg_season_t),
|
||||
_epg_season_destroy);
|
||||
}
|
||||
|
||||
epg_season_t *epg_season_find_by_id ( uint64_t id )
|
||||
|
@ -618,43 +635,28 @@ int epg_season_set_brand ( epg_season_t *season, epg_brand_t *brand, int u )
|
|||
int save = 0;
|
||||
if ( !season || !brand ) return 0;
|
||||
if ( season->brand != brand ) {
|
||||
if ( u ) save |= epg_brand_rem_season(season->brand, season, 0);
|
||||
if ( season->brand ) {
|
||||
_epg_brand_rem_season(season->brand, season);
|
||||
season->brand->_.putref((epg_object_t*)season->brand);
|
||||
}
|
||||
season->brand = brand;
|
||||
if ( u ) save |= epg_brand_add_season(season->brand, season, 0);
|
||||
_epg_brand_add_season(brand, season);
|
||||
season->_.getref((epg_object_t*)season);
|
||||
save = 1;
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
||||
int epg_season_add_episode
|
||||
( epg_season_t *season, epg_episode_t *episode, int u )
|
||||
static void _epg_season_add_episode
|
||||
( epg_season_t *season, epg_episode_t *episode )
|
||||
{
|
||||
int save = 0;
|
||||
epg_episode_t *ee;
|
||||
if ( !season || !episode ) return 0;
|
||||
ee = RB_INSERT_SORTED(&season->episodes, episode, slink, _uri_cmp);
|
||||
if ( ee == NULL ) {
|
||||
if ( u ) save |= epg_episode_set_season(episode, season, 0);
|
||||
save = 1;
|
||||
}
|
||||
// TODO?: else assert(ee == episode)
|
||||
return save;
|
||||
RB_INSERT_SORTED(&season->episodes, episode, slink, _uri_cmp);
|
||||
}
|
||||
|
||||
int epg_season_rem_episode
|
||||
( epg_season_t *season, epg_episode_t *episode, int u )
|
||||
static void _epg_season_rem_episode
|
||||
( epg_season_t *season, epg_episode_t *episode )
|
||||
{
|
||||
int save = 0;
|
||||
epg_episode_t *ee;
|
||||
if ( !season || !episode ) return 0;
|
||||
ee = RB_FIND(&season->episodes, episode, slink, _uri_cmp);
|
||||
if ( ee != NULL ) {
|
||||
// TODO?: assert(ee == episode)
|
||||
if ( u ) save |= epg_episode_set_season(episode, NULL, 0); // does nothing
|
||||
RB_REMOVE(&season->episodes, episode, slink);
|
||||
save = 1;
|
||||
}
|
||||
return save;
|
||||
RB_REMOVE(&season->episodes, episode, slink);
|
||||
}
|
||||
|
||||
htsmsg_t *epg_season_serialize ( epg_season_t *season )
|
||||
|
@ -704,12 +706,36 @@ epg_season_t *epg_season_deserialize ( htsmsg_t *m, int create, int *save )
|
|||
* Episode
|
||||
* *************************************************************************/
|
||||
|
||||
static void _epg_episode_destroy ( epg_object_t *eo )
|
||||
{
|
||||
epg_episode_t *ee = (epg_episode_t*)eo;
|
||||
if (RB_FIRST(&ee->broadcasts)) {
|
||||
tvhlog(LOG_CRIT, "epg", "attempt to destroy episode with broadcasts");
|
||||
assert(0);
|
||||
}
|
||||
_epg_object_destroy(eo);
|
||||
if (ee->brand) {
|
||||
_epg_brand_rem_episode(ee->brand, ee);
|
||||
ee->brand->_.putref((epg_object_t*)ee->brand);
|
||||
}
|
||||
if (ee->season) {
|
||||
_epg_season_rem_episode(ee->season, ee);
|
||||
ee->season->_.putref((epg_object_t*)ee->season);
|
||||
}
|
||||
if (ee->title) free(ee->title);
|
||||
if (ee->subtitle) free(ee->subtitle);
|
||||
if (ee->summary) free(ee->summary);
|
||||
if (ee->description) free(ee->description);
|
||||
free(ee);
|
||||
}
|
||||
|
||||
epg_episode_t* epg_episode_find_by_uri
|
||||
( const char *uri, int create, int *save )
|
||||
{
|
||||
return (epg_episode_t*)
|
||||
_epg_object_find_by_uri(uri, create, save,
|
||||
&epg_episodes, sizeof(epg_episode_t));
|
||||
&epg_episodes, sizeof(epg_episode_t),
|
||||
_epg_episode_destroy);
|
||||
}
|
||||
|
||||
epg_episode_t *epg_episode_find_by_id ( uint64_t id )
|
||||
|
@ -792,63 +818,51 @@ int epg_episode_set_part ( epg_episode_t *episode, uint16_t part, uint16_t count
|
|||
return save;
|
||||
}
|
||||
|
||||
int epg_episode_set_brand ( epg_episode_t *episode, epg_brand_t *brand, int u )
|
||||
int epg_episode_set_brand ( epg_episode_t *episode, epg_brand_t *brand )
|
||||
{
|
||||
int save = 0;
|
||||
if ( !episode || !brand ) return 0;
|
||||
if ( episode->brand != brand ) {
|
||||
if ( u ) save |= epg_brand_rem_episode(episode->brand, episode, 0); /// does nothing
|
||||
if ( episode->brand ) {
|
||||
_epg_brand_rem_episode(episode->brand, episode);
|
||||
episode->brand->_.putref((epg_object_t*)episode->brand);
|
||||
}
|
||||
episode->brand = brand;
|
||||
if ( u ) save |= epg_brand_add_episode(episode->brand, episode, 0);
|
||||
save |= 1;
|
||||
_epg_brand_add_episode(brand, episode);
|
||||
brand->_.getref((epg_object_t*)brand);
|
||||
save = 1;
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
||||
int epg_episode_set_season ( epg_episode_t *episode, epg_season_t *season, int u )
|
||||
int epg_episode_set_season ( epg_episode_t *episode, epg_season_t *season )
|
||||
{
|
||||
int save = 0;
|
||||
if ( !episode || !season ) return 0;
|
||||
if ( episode->season != season ) {
|
||||
if ( u ) save |= epg_season_rem_episode(episode->season, episode, 0);
|
||||
episode->season = season;
|
||||
if ( u && episode->season ) {
|
||||
save |= epg_season_add_episode(episode->season, episode, 0);
|
||||
save |= epg_brand_add_episode(episode->season->brand, episode, 0);
|
||||
// TODO: is this the right place?
|
||||
if ( episode->season ) {
|
||||
_epg_season_rem_episode(episode->season, episode);
|
||||
episode->season->_.putref((epg_object_t*)episode->season);
|
||||
}
|
||||
episode->season = season;
|
||||
_epg_season_add_episode(season, episode);
|
||||
season->_.getref((epg_object_t*)season);
|
||||
if ( season->brand ) save |= epg_episode_set_brand(episode, season->brand);
|
||||
save = 1;
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
||||
int epg_episode_add_broadcast
|
||||
( epg_episode_t *episode, epg_broadcast_t *broadcast, int u )
|
||||
static void _epg_episode_add_broadcast
|
||||
( epg_episode_t *episode, epg_broadcast_t *broadcast )
|
||||
{
|
||||
int save = 0;
|
||||
epg_broadcast_t *eb;
|
||||
if ( !episode || !broadcast ) return 0;
|
||||
eb = RB_INSERT_SORTED(&episode->broadcasts, broadcast, elink, _id_cmp);
|
||||
if ( eb == NULL ) {
|
||||
if ( u ) save |= epg_broadcast_set_episode(broadcast, episode, 0);
|
||||
save = 1;
|
||||
}
|
||||
return save;
|
||||
RB_INSERT_SORTED(&episode->broadcasts, broadcast, elink, _id_cmp);
|
||||
}
|
||||
|
||||
int epg_episode_rem_broadcast
|
||||
( epg_episode_t *episode, epg_broadcast_t *broadcast, int u )
|
||||
static void _epg_episode_rem_broadcast
|
||||
( epg_episode_t *episode, epg_broadcast_t *broadcast )
|
||||
{
|
||||
int save = 0;
|
||||
epg_broadcast_t *eb;
|
||||
if ( !episode || !broadcast ) return 0;
|
||||
eb = RB_FIND(&episode->broadcasts, broadcast, elink, _id_cmp);
|
||||
if ( eb != NULL ) {
|
||||
if ( u ) save |= epg_broadcast_set_episode(broadcast, episode, 0);
|
||||
RB_REMOVE(&episode->broadcasts, broadcast, elink);
|
||||
save = 1;
|
||||
}
|
||||
return save;
|
||||
RB_REMOVE(&episode->broadcasts, broadcast, elink);
|
||||
}
|
||||
|
||||
int epg_episode_get_number_onscreen
|
||||
|
@ -920,10 +934,10 @@ epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save )
|
|||
|
||||
if ( (str = htsmsg_get_str(m, "brand")) )
|
||||
if ( (eb = epg_brand_find_by_uri(str, 0, NULL)) )
|
||||
*save |= epg_episode_set_brand(ee, eb, 1);
|
||||
*save |= epg_episode_set_brand(ee, eb);
|
||||
if ( (str = htsmsg_get_str(m, "season")) )
|
||||
if ( (es = epg_season_find_by_uri(str, 0, NULL)) )
|
||||
*save |= epg_episode_set_season(ee, es, 1);
|
||||
*save |= epg_episode_set_season(ee, es);
|
||||
|
||||
return ee;
|
||||
}
|
||||
|
@ -932,43 +946,50 @@ epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save )
|
|||
* Broadcast
|
||||
* *************************************************************************/
|
||||
|
||||
static void _epg_broadcast_destroy ( epg_object_t *eo )
|
||||
{
|
||||
epg_broadcast_t *ebc = (epg_broadcast_t*)eo;
|
||||
if (ebc->episode) {
|
||||
_epg_episode_rem_broadcast(ebc->episode, ebc);
|
||||
ebc->episode->_.putref((epg_object_t*)ebc->episode);
|
||||
}
|
||||
free(ebc);
|
||||
}
|
||||
|
||||
epg_broadcast_t* epg_broadcast_find_by_time
|
||||
( epg_channel_t *channel, time_t start, time_t stop, int create, int *save )
|
||||
{
|
||||
static epg_broadcast_t *skel;
|
||||
if ( !channel || !start || !stop ) return 0;
|
||||
if ( stop < start ) return 0;
|
||||
|
||||
if ( skel == NULL ) skel = calloc(1, sizeof(epg_broadcast_t));
|
||||
skel->channel = channel;
|
||||
skel->start = start;
|
||||
skel->stop = stop;
|
||||
skel->_.id = _epg_object_idx; // TODO: prefer if this wasn't here!
|
||||
|
||||
return (epg_broadcast_t*)
|
||||
_epg_object_find(create, save, &channel->schedule,
|
||||
(epg_object_t**)&skel, _ebc_win_cmp);
|
||||
return epg_channel_get_broadcast(channel, start, stop, create, save);
|
||||
}
|
||||
|
||||
// TODO: optional channel?
|
||||
epg_broadcast_t *epg_broadcast_find_by_id ( uint64_t id )
|
||||
// TODO: allow optional channel parameter?
|
||||
epg_broadcast_t *epg_broadcast_find_by_id ( uint64_t id, epg_channel_t *ec )
|
||||
{
|
||||
epg_object_t *eo, *ec;
|
||||
RB_FOREACH(ec, &epg_channels, glink) {
|
||||
epg_object_t *eo = NULL, *eoc;
|
||||
if ( ec ) {
|
||||
eo = _epg_object_find_by_id(id, &((epg_channel_t*)ec)->schedule);
|
||||
if (eo) return (epg_broadcast_t*)eo;
|
||||
} else {
|
||||
RB_FOREACH(eoc, &epg_channels, glink) {
|
||||
eo = _epg_object_find_by_id(id, &((epg_channel_t*)eoc)->schedule);
|
||||
if (eo) break;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
return (epg_broadcast_t*)eo;
|
||||
}
|
||||
|
||||
int epg_broadcast_set_episode
|
||||
( epg_broadcast_t *broadcast, epg_episode_t *episode, int u )
|
||||
( epg_broadcast_t *broadcast, epg_episode_t *episode )
|
||||
{
|
||||
int save = 0;
|
||||
if ( !broadcast || !episode ) return 0;
|
||||
if ( broadcast->episode != episode ) {
|
||||
if ( u ) save |= epg_episode_add_broadcast(episode, broadcast, 0);
|
||||
if ( broadcast->episode ) {
|
||||
_epg_episode_rem_broadcast(broadcast->episode, broadcast);
|
||||
broadcast->episode->_.putref((epg_object_t*)broadcast->episode);
|
||||
}
|
||||
_epg_episode_add_broadcast(episode, broadcast);
|
||||
broadcast->episode = episode;
|
||||
episode->_.getref((epg_object_t*)episode);
|
||||
save = 1;
|
||||
}
|
||||
return save;
|
||||
|
@ -1023,7 +1044,7 @@ epg_broadcast_t *epg_broadcast_deserialize
|
|||
ebc = epg_broadcast_find_by_time(ec, start, stop, create, save);
|
||||
if ( !ebc ) return NULL;
|
||||
|
||||
*save |= epg_broadcast_set_episode(ebc, ee, 1);
|
||||
*save |= epg_broadcast_set_episode(ebc, ee);
|
||||
|
||||
/* Bodge the ID - keep them the same */
|
||||
ebc->_.id = id;
|
||||
|
@ -1042,12 +1063,61 @@ epg_broadcast_t *epg_broadcast_deserialize
|
|||
* Channel
|
||||
* *************************************************************************/
|
||||
|
||||
static void _epg_channel_expire_callback ( void *p )
|
||||
{
|
||||
time_t next = 0;
|
||||
epg_object_t *eo;
|
||||
epg_broadcast_t *ebc;
|
||||
epg_channel_t *ec = (epg_channel_t*)p;
|
||||
while ( (eo = RB_FIRST(&ec->schedule)) ) {
|
||||
ebc = (epg_broadcast_t*)eo;
|
||||
if ( ebc->stop < dispatch_clock ) {
|
||||
RB_REMOVE(&ec->schedule, eo, glink);
|
||||
eo->putref(eo);
|
||||
} else {
|
||||
next = ebc->stop;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* re-arm */
|
||||
if ( next )
|
||||
gtimer_arm_abs(&ec->expire, _epg_channel_expire_callback, ec, next);
|
||||
}
|
||||
|
||||
static void _epg_channel_destroy ( epg_object_t *eo )
|
||||
{
|
||||
epg_channel_t *ec = (epg_channel_t*)eo;
|
||||
if (ec->channel) {
|
||||
tvhlog(LOG_CRIT, "epg", "attempt to destroy mapped channel");
|
||||
assert(0);
|
||||
}
|
||||
if (RB_FIRST(&ec->schedule)) {
|
||||
tvhlog(LOG_CRIT, "epg", "attempt to destroy channel with schedule");
|
||||
assert(0);
|
||||
}
|
||||
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 )
|
||||
{
|
||||
return (epg_channel_t*)
|
||||
_epg_object_find_by_uri(uri, create, save,
|
||||
&epg_channels, sizeof(epg_channel_t));
|
||||
int save2 = 0;
|
||||
epg_channel_t *ec = (epg_channel_t*)
|
||||
_epg_object_find_by_uri(uri, create, &save2,
|
||||
&epg_channels, sizeof(epg_channel_t),
|
||||
_epg_channel_destroy);
|
||||
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 )
|
||||
|
@ -1062,16 +1132,16 @@ int epg_channel_set_name ( epg_channel_t *channel, const char *name )
|
|||
if ( !channel || !name ) return 0;
|
||||
if ( !channel->name || strcmp(channel->name, name) ) {
|
||||
channel->name = strdup(name);
|
||||
// NOTE: does not remap
|
||||
if ( !channel->channel ) {
|
||||
LIST_FOREACH(ch, &epg_unlinked_channels2, ch_eulink) {
|
||||
LIST_FOREACH(ch, &channel_unmapped, ch_eulink) {
|
||||
if ( _epg_channel_cmp(channel, ch) ) {
|
||||
epg_channel_set_channel(channel, ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: should be try to override?
|
||||
save = 1;
|
||||
save |= 1;
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
@ -1079,22 +1149,60 @@ int epg_channel_set_name ( epg_channel_t *channel, const char *name )
|
|||
int epg_channel_set_channel ( epg_channel_t *ec, channel_t *ch )
|
||||
{
|
||||
int save = 0;
|
||||
#if TODO_REDO_CHANNEL_LINKING
|
||||
if ( !ec || !ch ) return 0;
|
||||
if ( !ec ) return 0;
|
||||
if ( ec->channel != ch ) {
|
||||
if (!ec->channel) LIST_REMOVE(ec, ulink);
|
||||
if (!ch->ch_epg_channel) LIST_REMOVE(ch, ch_eulink);
|
||||
// TODO: should it be possible to "change" it
|
||||
//if(ec->channel)
|
||||
// channel_set_epg_source(ec->channel, NULL)
|
||||
// LIST_INSERT_HEAD(&epg_unlinked_channels2, ec->channel, ch_eulink);
|
||||
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;
|
||||
channel_set_epg_source(ch, ec);
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
return save;
|
||||
}
|
||||
|
||||
epg_broadcast_t *epg_channel_get_broadcast
|
||||
( epg_channel_t *channel, 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 ( stop <= start ) return NULL;
|
||||
if ( stop < dispatch_clock ) return NULL;
|
||||
|
||||
if ( skel == NULL ) skel = calloc(1, sizeof(epg_broadcast_t));
|
||||
skel->channel = channel;
|
||||
skel->start = start;
|
||||
skel->stop = stop;
|
||||
skel->_.id = _epg_object_idx;
|
||||
skel->_.destroy = _epg_broadcast_destroy;
|
||||
|
||||
ebc = (epg_broadcast_t*)
|
||||
_epg_object_find(create, &save2, &channel->schedule,
|
||||
(epg_object_t**)&skel, _ebc_win_cmp);
|
||||
if (save2) {
|
||||
ebc->_.getref((epg_object_t*)ebc);
|
||||
if (RB_FIRST(&channel->schedule) == (epg_object_t*)ebc)
|
||||
_epg_channel_expire_callback(channel);
|
||||
*save |= 1;
|
||||
}
|
||||
return ebc;
|
||||
}
|
||||
|
||||
epg_broadcast_t *epg_channel_get_current_broadcast ( epg_channel_t *channel )
|
||||
{
|
||||
// TODO: its not really the head!
|
||||
|
@ -1137,6 +1245,38 @@ epg_channel_t *epg_channel_deserialize ( htsmsg_t *m, int create, int *save )
|
|||
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
|
||||
* *************************************************************************/
|
||||
|
|
86
src/epg.h
86
src/epg.h
|
@ -62,12 +62,12 @@ typedef struct epg_broadcast_tree epg_broadcast_tree_t;
|
|||
/* Object */
|
||||
typedef struct epg_object
|
||||
{
|
||||
RB_ENTRY(epg_object) glink; ///< Global list link
|
||||
RB_ENTRY(epg_object) ulink; ///< Global unref'd link
|
||||
RB_ENTRY(epg_object) glink; ///< Global list link
|
||||
LIST_ENTRY(epg_object) ulink; ///< Global unref'd link
|
||||
|
||||
char *uri; ///< Unique ID (from grabber)
|
||||
uint64_t id; ///< Internal ID
|
||||
int refcount; ///< Reference counting
|
||||
char *uri; ///< Unique ID (from grabber)
|
||||
uint64_t id; ///< Internal ID
|
||||
int refcount; ///< Reference counting
|
||||
|
||||
void (*getref) ( epg_object_t* ); ///< Get a reference
|
||||
void (*putref) ( epg_object_t* ); ///< Release a reference
|
||||
|
@ -105,14 +105,6 @@ int epg_brand_set_summary ( epg_brand_t *b, const char *summary )
|
|||
__attribute__((warn_unused_result));
|
||||
int epg_brand_set_season_count ( epg_brand_t *b, uint16_t season_count )
|
||||
__attribute__((warn_unused_result));
|
||||
int epg_brand_add_season ( epg_brand_t *b, epg_season_t *s, int u )
|
||||
__attribute__((warn_unused_result));
|
||||
int epg_brand_rem_season ( epg_brand_t *b, epg_season_t *s, int u )
|
||||
__attribute__((warn_unused_result));
|
||||
int epg_brand_add_episode ( epg_brand_t *b, epg_episode_t *s, int u )
|
||||
__attribute__((warn_unused_result));
|
||||
int epg_brand_rem_episode ( epg_brand_t *b, epg_episode_t *s, int u )
|
||||
__attribute__((warn_unused_result));
|
||||
|
||||
/* Serialization */
|
||||
htsmsg_t *epg_brand_serialize ( epg_brand_t *b );
|
||||
|
@ -151,10 +143,6 @@ int epg_season_set_episode_count ( epg_season_t *s, uint16_t episode_count )
|
|||
__attribute__((warn_unused_result));
|
||||
int epg_season_set_brand ( epg_season_t *s, epg_brand_t *b, int u )
|
||||
__attribute__((warn_unused_result));
|
||||
int epg_season_add_episode ( epg_season_t *s, epg_episode_t *e, int u )
|
||||
__attribute__((warn_unused_result));
|
||||
int epg_season_rem_episode ( epg_season_t *s, epg_episode_t *e, int u )
|
||||
__attribute__((warn_unused_result));
|
||||
|
||||
/* Serialization */
|
||||
htsmsg_t *epg_season_serialize ( epg_season_t *b );
|
||||
|
@ -207,13 +195,9 @@ int epg_episode_set_number ( epg_episode_t *e, uint16_t number )
|
|||
int epg_episode_set_part ( epg_episode_t *e,
|
||||
uint16_t number, uint16_t count )
|
||||
__attribute__((warn_unused_result));
|
||||
int epg_episode_set_brand ( epg_episode_t *e, epg_brand_t *b, int u )
|
||||
int epg_episode_set_brand ( epg_episode_t *e, epg_brand_t *b )
|
||||
__attribute__((warn_unused_result));
|
||||
int epg_episode_set_season ( epg_episode_t *e, epg_season_t *s, int u )
|
||||
__attribute__((warn_unused_result));
|
||||
int epg_episode_add_broadcast ( epg_episode_t *e, epg_broadcast_t *b, int u )
|
||||
__attribute__((warn_unused_result));
|
||||
int epg_episode_rem_broadcast ( epg_episode_t *e, epg_broadcast_t *b, int u )
|
||||
int epg_episode_set_season ( epg_episode_t *e, epg_season_t *s )
|
||||
__attribute__((warn_unused_result));
|
||||
|
||||
/* Acessors */
|
||||
|
@ -261,13 +245,14 @@ typedef struct epg_broadcast
|
|||
/* Lookup */
|
||||
epg_broadcast_t *epg_broadcast_find_by_time
|
||||
( epg_channel_t *ch, time_t start, time_t stop, int create, int *save );
|
||||
epg_broadcast_t *epg_broadcast_find_by_id ( uint64_t id );
|
||||
epg_broadcast_t *epg_broadcast_find_by_id ( uint64_t id, epg_channel_t *ch );
|
||||
|
||||
/* Mutators */
|
||||
int epg_broadcast_set_episode ( epg_broadcast_t *b, epg_episode_t *e, int u )
|
||||
int epg_broadcast_set_episode ( epg_broadcast_t *b, epg_episode_t *e )
|
||||
__attribute__((warn_unused_result));
|
||||
|
||||
/* Accessors */
|
||||
// TODO: remove this!
|
||||
epg_broadcast_t *epg_broadcast_get_next ( epg_broadcast_t *b );
|
||||
|
||||
/* Serialization */
|
||||
|
@ -288,8 +273,11 @@ typedef struct epg_channel
|
|||
char **sname; ///< DVB svc names (to map)
|
||||
int **sid; ///< DVB svc ids (to map)
|
||||
|
||||
epg_object_tree_t schedule; ///< List of broadcasts
|
||||
LIST_ENTRY(epg_channel) umlink; ///< Unmapped channel link
|
||||
channel_t *channel; ///< Link to real channel
|
||||
epg_object_tree_t schedule; ///< Schedule (broadcasts)
|
||||
|
||||
gtimer_t expire; ///< Expiration timer
|
||||
} epg_channel_t;
|
||||
|
||||
/* Lookup */
|
||||
|
@ -304,11 +292,18 @@ int epg_channel_set_channel ( epg_channel_t *c, channel_t *ch );
|
|||
|
||||
/* Accessors */
|
||||
epg_broadcast_t *epg_channel_get_current_broadcast ( epg_channel_t *c );
|
||||
epg_broadcast_t *epg_channel_get_broadcast
|
||||
( epg_channel_t *ch, time_t start, time_t stop, int create, int *save );
|
||||
|
||||
/* Serialization */
|
||||
htsmsg_t *epg_channel_serialize ( epg_channel_t *b );
|
||||
epg_channel_t *epg_channel_deserialize ( htsmsg_t *m, int create, int *save );
|
||||
|
||||
/* Channel mapping */
|
||||
void epg_channel_map_add ( channel_t *ch );
|
||||
void epg_channel_map_rem ( channel_t *ch );
|
||||
void epg_channel_map_mod ( channel_t *ch );
|
||||
|
||||
/* ************************************************************************
|
||||
* Querying
|
||||
* ***********************************************************************/
|
||||
|
@ -336,43 +331,12 @@ void epg_query(epg_query_result_t *eqr, const char *channel, const char *tag,
|
|||
|
||||
|
||||
/* ************************************************************************
|
||||
* Function prototypes
|
||||
* Setup/Shutdown
|
||||
* ***********************************************************************/
|
||||
|
||||
/*
|
||||
* Load/Save
|
||||
*/
|
||||
|
||||
void epg_init(void);
|
||||
void epg_save(void);
|
||||
|
||||
void epg_updated ( void );
|
||||
|
||||
/*
|
||||
* Channel linking
|
||||
*/
|
||||
void epg_add_channel ( channel_t *ch );
|
||||
void epg_rem_channel ( channel_t *ch );
|
||||
void epg_mod_channel ( channel_t *ch );
|
||||
|
||||
/*
|
||||
* Simple lookup
|
||||
*/
|
||||
|
||||
|
||||
epg_broadcast_t *epg_event_find_by_id(int eventid);
|
||||
|
||||
/*
|
||||
* Advanced Query
|
||||
*/
|
||||
|
||||
/*
|
||||
* Genres?
|
||||
*/
|
||||
uint8_t epg_content_group_find_by_name(const char *name);
|
||||
|
||||
const char *epg_content_group_get_name(uint8_t type);
|
||||
|
||||
void epg_init (void);
|
||||
void epg_save (void);
|
||||
void epg_updated (void);
|
||||
|
||||
/* ************************************************************************
|
||||
* Compatibility code
|
||||
|
|
Loading…
Add table
Reference in a new issue