Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
2608 lines (2350 sloc) 80.7 KB
/*
* Electronic Program Guide - Common functions
* Copyright (C) 2012 Adam Sutton
*
* 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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <inttypes.h>
#include <time.h>
#include "tvheadend.h"
#include "config.h"
#include "queue.h"
#include "channels.h"
#include "settings.h"
#include "epg.h"
#include "dvr/dvr.h"
#include "htsp_server.h"
#include "epggrab.h"
#include "imagecache.h"
#include "notify.h"
#include "string_list.h"
/* Broadcast hashing */
#define EPG_HASH_WIDTH 1024
#define EPG_HASH_MASK (EPG_HASH_WIDTH - 1)
/* Objects tree */
epg_object_tree_t epg_objects[EPG_HASH_WIDTH];
/* URI lists */
epg_set_tree_t epg_serieslinks;
epg_set_tree_t epg_episodelinks;
/* Other special case lists */
epg_object_list_t epg_object_unref;
epg_object_list_t epg_object_updated;
int epg_in_load;
/* Global counter */
static uint32_t _epg_object_idx = 0;
/*
*
*/
static inline epg_object_tree_t *epg_id_tree( epg_object_t *eo )
{
return &epg_objects[eo->id & EPG_HASH_MASK];
}
/* **************************************************************************
* Comparators / Ordering
* *************************************************************************/
static int _id_cmp ( const void *a, const void *b )
{
return ((epg_object_t*)a)->id - ((epg_object_t*)b)->id;
}
static int _ebc_start_cmp ( const void *a, const void *b )
{
return ((epg_broadcast_t*)a)->start - ((epg_broadcast_t*)b)->start;
}
void epg_updated ( void )
{
epg_object_t *eo;
lock_assert(&global_lock);
/* Remove unref'd */
while ((eo = LIST_FIRST(&epg_object_unref))) {
tvhtrace(LS_EPG,
"unref'd object %u created during update", eo->id);
LIST_REMOVE(eo, un_link);
eo->ops->destroy(eo);
}
// Note: we do things this way around since unref'd objects are not likely
// to be useful to DVR since they will relate to episodes
// with no valid broadcasts etc..
/* Update updated */
while ((eo = LIST_FIRST(&epg_object_updated))) {
eo->ops->update(eo);
LIST_REMOVE(eo, up_link);
eo->_updated = 0;
eo->_created = 1;
}
}
/* **************************************************************************
* Object (Generic routines)
* *************************************************************************/
static void _epg_object_destroy
( epg_object_t *eo, epg_object_tree_t *tree )
{
assert(eo->refcount == 0);
tvhtrace(LS_EPG, "eo [%p, %u, %d] destroy",
eo, eo->id, eo->type);
if (eo->_updated) LIST_REMOVE(eo, up_link);
RB_REMOVE(epg_id_tree(eo), eo, id_link);
}
static void _epg_object_getref ( void *o )
{
epg_object_t *eo = o;
tvhtrace(LS_EPG, "eo [%p, %u, %d] getref %d",
eo, eo->id, eo->type, eo->refcount+1);
if (eo->refcount == 0) LIST_REMOVE(eo, un_link);
eo->refcount++;
}
static int _epg_object_putref ( void *o )
{
epg_object_t *eo = o;
tvhtrace(LS_EPG, "eo [%p, %u, %d] putref %d",
eo, eo->id, eo->type, eo->refcount-1);
assert(eo->refcount>0);
eo->refcount--;
if (!eo->refcount) {
eo->ops->destroy(eo);
return 1;
}
return 0;
}
static void _epg_object_set_updated0 ( void *o )
{
epg_object_t *eo = o;
tvhtrace(LS_EPG, "eo [%p, %u, %d] updated",
eo, eo->id, eo->type);
eo->_updated = 1;
eo->updated = gclk();
LIST_INSERT_HEAD(&epg_object_updated, eo, up_link);
}
static inline void _epg_object_set_updated ( void *o )
{
if (!((epg_object_t *)o)->_updated)
_epg_object_set_updated0(o);
}
static int _epg_object_can_remove ( void *_old, void *_new )
{
epggrab_module_t *ograb, *ngrab;
ngrab = ((epg_object_t *)_new)->grabber;
if (ngrab == NULL) return 0;
ograb = ((epg_object_t *)_old)->grabber;
if (ograb == NULL || ograb == ngrab) return 1;
if (ngrab->priority > ograb->priority) return 1;
return 0;
}
static int _epg_object_set_grabber ( void *o, epggrab_module_t *ngrab )
{
epggrab_module_t *ograb;
if (!ngrab) return 1; // grab=NULL is override
ograb = ((epg_object_t *)o)->grabber;
if (ograb == ngrab) return 1;
if (ograb && ograb->priority >= ngrab->priority) return 0;
((epg_object_t *)o)->grabber = ngrab;
return 1;
}
static void _epg_object_create ( void *o )
{
epg_object_t *eo = o;
uint32_t id = eo->id;
if (!id) eo->id = ++_epg_object_idx;
if (!eo->id) eo->id = ++_epg_object_idx;
tvhtrace(LS_EPG, "eo [%p, %u, %d] created",
eo, eo->id, eo->type);
_epg_object_set_updated(eo);
LIST_INSERT_HEAD(&epg_object_unref, eo, un_link);
while (1) {
if (!RB_INSERT_SORTED(epg_id_tree(eo), eo, id_link, _id_cmp))
break;
if (id) {
tvherror(LS_EPG, "fatal error, duplicate EPG ID");
abort();
}
eo->id = ++_epg_object_idx;
if (!eo->id) eo->id = ++_epg_object_idx;
}
}
epg_object_t *epg_object_find_by_id ( uint32_t id, epg_object_type_t type )
{
epg_object_t *eo, temp;
temp.id = id;
eo = RB_FIND(epg_id_tree(&temp), &temp, id_link, _id_cmp);
if (eo && eo->type == type)
return eo;
return NULL;
}
static htsmsg_t * _epg_object_serialize ( void *o )
{
htsmsg_t *m;
epg_object_t *eo = o;
tvhtrace(LS_EPG, "eo [%p, %u, %d] serialize",
eo, eo->id, eo->type);
if (!eo->id || !eo->type) return NULL;
m = htsmsg_create_map();
htsmsg_add_u32(m, "id", eo->id);
htsmsg_add_u32(m, "tp", eo->type);
if (eo->grabber)
htsmsg_add_str(m, "gr", eo->grabber->id);
htsmsg_add_s64(m, "up", eo->updated);
return m;
}
static epg_object_t *_epg_object_deserialize ( htsmsg_t *m, epg_object_t *eo )
{
int64_t s64;
uint32_t u32;
const char *s;
if (htsmsg_get_u32(m, "id", &eo->id)) return NULL;
if (htsmsg_get_u32(m, "tp", &u32)) return NULL;
if (u32 != eo->type) return NULL;
if ((s = htsmsg_get_str(m, "gr")))
eo->grabber = epggrab_module_find_by_id(s);
if (!htsmsg_get_s64(m, "up", &s64)) {
_epg_object_set_updated(eo);
eo->updated = s64;
}
tvhtrace(LS_EPG, "eo [%p, %u, %d, %s, %s] deserialize",
eo, eo->id, eo->type, s, eo->grabber ? eo->grabber->id : NULL);
return eo;
}
static int _epg_object_set_str
( void *o, char **old, const char *newstr,
epg_changes_t *changed, epg_changes_t cflag )
{
int save = 0;
epg_object_t *eo = o;
if (!eo) return 0;
if (changed) *changed |= cflag;
if (!*old && !newstr) return 0;
if (!*old || !newstr || strcmp(*old, newstr)) {
free(*old);
*old = newstr ? strdup(newstr) : NULL;
_epg_object_set_updated(eo);
save = 1;
}
return save;
}
/* "Template" for setting objects. */
#define EPG_OBJECT_SET_FN(FNNAME,TYPE,DESTROY,COMPARE,COPY) \
static int FNNAME \
( void *o, TYPE **old, const TYPE *new, \
epg_changes_t *changed, epg_changes_t cflag ) \
{ \
if (!o) return 0; \
if (changed) *changed |= cflag; \
if (!*old) { \
if (!new) \
return 0; \
} \
if (!new) { \
DESTROY(*old); \
*old = NULL; \
_epg_object_set_updated(o); \
return 1; \
} \
if (COMPARE(*old, new)) { \
DESTROY(*old); \
*old = COPY(new); \
_epg_object_set_updated(o); \
return 1; \
} \
return 0; \
}
EPG_OBJECT_SET_FN(_epg_object_set_lang_str, lang_str_t,
lang_str_destroy, lang_str_compare, lang_str_copy)
EPG_OBJECT_SET_FN(_epg_object_set_string_list, string_list_t,
string_list_destroy, string_list_cmp, string_list_copy)
EPG_OBJECT_SET_FN(_epg_object_set_htsmsg, htsmsg_t,
htsmsg_destroy, htsmsg_cmp, htsmsg_copy)
#undef EPG_OBJECT_SET_FN
#define EPG_OBJECT_SET_FN(FNNAME,TYPE) \
static int FNNAME \
( void *o, TYPE *old, const TYPE nval, \
epg_changes_t *changed, epg_changes_t cflag ) \
{ \
int save; \
if (!o) return 0; \
if (changed) *changed |= cflag; \
if ((save = (*old != nval)) != 0) { \
*old = nval; \
_epg_object_set_updated(o); \
} \
return save; \
}
EPG_OBJECT_SET_FN(_epg_object_set_u8, uint8_t)
EPG_OBJECT_SET_FN(_epg_object_set_u16, uint16_t)
#undef EPG_OBJECT_SET_FN
htsmsg_t *epg_object_serialize ( epg_object_t *eo )
{
if (!eo) return NULL;
switch (eo->type) {
case EPG_BROADCAST:
return epg_broadcast_serialize((epg_broadcast_t*)eo);
default:
return NULL;
}
}
epg_object_t *epg_object_deserialize ( htsmsg_t *msg, int create, int *save )
{
uint32_t type;
if (!msg) return NULL;
type = htsmsg_get_u32_or_default(msg, "type", 0);
switch (type) {
case EPG_BROADCAST:
return (epg_object_t*)epg_broadcast_deserialize(msg, create, save);
}
return NULL;
}
/* **************************************************************************
* Episode
* *************************************************************************/
htsmsg_t *epg_episode_epnum_serialize ( epg_episode_num_t *num )
{
htsmsg_t *m;
if (!num) return NULL;
if (!num->e_num && !num->e_cnt &&
!num->s_num && !num->s_cnt &&
!num->p_num && !num->p_cnt &&
!num->text) return NULL;
m = htsmsg_create_map();
if (num->e_num)
htsmsg_add_u32(m, "enum", num->e_num);
if (num->e_cnt)
htsmsg_add_u32(m, "ecnt", num->e_cnt);
if (num->s_num)
htsmsg_add_u32(m, "snum", num->s_num);
if (num->s_cnt)
htsmsg_add_u32(m, "scnt", num->s_cnt);
if (num->p_num)
htsmsg_add_u32(m, "pnum", num->p_num);
if (num->p_cnt)
htsmsg_add_u32(m, "pcnt", num->p_cnt);
if (num->text)
htsmsg_add_str(m, "text", num->text);
return m;
}
void epg_episode_epnum_deserialize
( htsmsg_t *m, epg_episode_num_t *num )
{
const char *str;
uint32_t u32;
assert(m && num);
memset(num, 0, sizeof(epg_episode_num_t));
if (!htsmsg_get_u32(m, "enum", &u32) ||
!htsmsg_get_u32(m, "e_num", &u32))
num->e_num = u32;
if (!htsmsg_get_u32(m, "ecnt", &u32) ||
!htsmsg_get_u32(m, "e_cnt", &u32))
num->e_cnt = u32;
if (!htsmsg_get_u32(m, "snum", &u32) ||
!htsmsg_get_u32(m, "s_num", &u32))
num->s_num = u32;
if (!htsmsg_get_u32(m, "scnt", &u32) ||
!htsmsg_get_u32(m, "s_cnt", &u32))
num->s_cnt = u32;
if (!htsmsg_get_u32(m, "pnum", &u32) ||
!htsmsg_get_u32(m, "p_num", &u32))
num->p_num = u32;
if (!htsmsg_get_u32(m, "pcnt", &u32) ||
!htsmsg_get_u32(m, "p_cnt", &u32))
num->p_cnt = u32;
if ((str = htsmsg_get_str(m, "text")))
num->text = strdup(str);
}
size_t epg_episode_num_format
( epg_episode_num_t *epnum, char *buf, size_t len,
const char *pre, const char *sfmt,
const char *sep, const char *efmt,
const char *cfmt )
{
size_t i = 0;
if (!epnum || !buf || !len) return 0;
buf[0] = '\0';
if (epnum->e_num || epnum->s_num) {
if (pre) tvh_strlcatf(buf, len, i, "%s", pre);
if (sfmt && epnum->s_num) {
tvh_strlcatf(buf, len, i, sfmt, epnum->s_num);
if (cfmt && epnum->s_cnt)
tvh_strlcatf(buf, len, i, cfmt, epnum->s_cnt);
if (sep && efmt && epnum->e_num) tvh_strlcatf(buf, len, i, "%s", sep);
}
if (efmt && epnum->e_num)
tvh_strlcatf(buf, len, i, efmt, epnum->e_num);
if (cfmt && epnum->e_cnt)
tvh_strlcatf(buf, len, i, cfmt, epnum->e_cnt);
} else if (epnum->text) {
if (pre) tvh_strlcatf(buf, len, i, "%s", pre);
tvh_strlcatf(buf, len, i, "%s", epnum->text);
}
return i;
}
int epg_episode_number_cmp ( const epg_episode_num_t *a, const epg_episode_num_t *b )
{
if (a->e_num) {
if (a->s_num != b->s_num) {
return a->s_num - b->s_num;
} else if (a->e_num != b->e_num) {
return a->e_num - b->e_num;
} else {
return a->p_num - b->p_num;
}
} else if (a->text && b->text) {
return strcmp(a->text, b->text);
}
return 0;
}
int epg_episode_number_cmpfull ( const epg_episode_num_t *a, const epg_episode_num_t *b )
{
int i = a->s_cnt - b->s_cnt;
if (i) return i;
i = a->s_num - b->s_num;
if (i) return i;
i = a->e_cnt - b->e_cnt;
if (i) return i;
i = a->e_num - b->e_num;
if (i) return i;
i = a->p_cnt - b->p_cnt;
if (i) return i;
i = a->p_num - b->p_num;
if (i) return i;
return strcasecmp(a->text ?: "", b->text ?: "");
}
/* **************************************************************************
* Channel
* *************************************************************************/
int epg_channel_ignore_broadcast(channel_t *ch, time_t start)
{
if (ch->ch_epg_limit && start >= gclk() + ch->ch_epg_limit * 3600 * 24)
return 1;
return 0;
}
static void _epg_channel_rem_broadcast
( channel_t *ch, epg_broadcast_t *ebc, epg_broadcast_t *ebc_new )
{
RB_REMOVE(&ch->ch_epg_schedule, ebc, sched_link);
if (ch->ch_epg_now == ebc) ch->ch_epg_now = NULL;
if (ch->ch_epg_next == ebc) ch->ch_epg_next = NULL;
if (ebc_new) {
dvr_event_replaced(ebc, ebc_new);
} else {
dvr_event_removed(ebc);
}
_epg_object_putref(ebc);
}
static void _epg_channel_timer_callback ( void *p )
{
time_t next = 0;
epg_broadcast_t *ebc, *cur, *nxt;
channel_t *ch = (channel_t*)p;
char tm1[32];
/* Clear now/next */
if ((cur = ch->ch_epg_now)) {
if (cur->running != EPG_RUNNING_STOP && cur->running != EPG_RUNNING_NOTSET) {
/* running? don't do anything */
gtimer_arm_rel(&ch->ch_epg_timer, _epg_channel_timer_callback, ch, 2);
return;
}
cur->ops->getref(cur);
}
if ((nxt = ch->ch_epg_next))
nxt->ops->getref(nxt);
ch->ch_epg_now = ch->ch_epg_next = NULL;
/* Check events */
while ((ebc = RB_FIRST(&ch->ch_epg_schedule))) {
/* Expire */
if ( ebc->stop <= gclk() ) {
tvhdebug(LS_EPG, "expire event %u (%s) from %s",
ebc->id, epg_broadcast_get_title(ebc, NULL),
channel_get_name(ch, channel_blank_name));
_epg_channel_rem_broadcast(ch, ebc, NULL);
continue; // skip to next
/* No now */
} else if (ebc->start > gclk()) {
ch->ch_epg_next = ebc;
next = ebc->start;
/* Now/Next */
} else {
ch->ch_epg_now = ebc;
ch->ch_epg_next = RB_NEXT(ebc, sched_link);
next = ebc->stop;
}
break;
}
/* Change (update HTSP) */
if (cur != ch->ch_epg_now || nxt != ch->ch_epg_next) {
tvhdebug(LS_EPG, "now/next %u/%u set on %s",
ch->ch_epg_now ? ch->ch_epg_now->id : 0,
ch->ch_epg_next ? ch->ch_epg_next->id : 0,
channel_get_name(ch, channel_blank_name));
tvhdebug(LS_EPG, "inform HTSP of now event change on %s",
channel_get_name(ch, channel_blank_name));
htsp_channel_update_nownext(ch);
}
/* re-arm */
if (next) {
tvhdebug(LS_EPG, "arm channel timer @ %s for %s",
gmtime2local(next, tm1, sizeof(tm1)), channel_get_name(ch, channel_blank_name));
gtimer_arm_absn(&ch->ch_epg_timer, _epg_channel_timer_callback, ch, next);
}
/* Remove refs */
if (cur) cur->ops->putref(cur);
if (nxt) nxt->ops->putref(nxt);
}
static epg_broadcast_t *_epg_channel_add_broadcast
( channel_t *ch, epg_broadcast_t **bcast, epggrab_module_t *src,
int create, int *save, epg_changes_t *changed )
{
int timer = 0;
epg_broadcast_t *ebc, *ret;
char tm1[32], tm2[32];
if (!src) {
tvherror(LS_EPG, "skipped event (!grabber) %u (%s) on %s @ %s to %s",
(*bcast)->id, epg_broadcast_get_title(*bcast, NULL),
channel_get_name(ch, channel_blank_name),
gmtime2local((*bcast)->start, tm1, sizeof(tm1)),
gmtime2local((*bcast)->stop, tm2, sizeof(tm2)));
return NULL;
}
/* Set channel */
(*bcast)->channel = ch;
/* Find (only) */
if ( !create ) {
return RB_FIND(&ch->ch_epg_schedule, *bcast, sched_link, _ebc_start_cmp);
/* Find/Create */
} else {
ret = RB_INSERT_SORTED(&ch->ch_epg_schedule, *bcast, sched_link, _ebc_start_cmp);
/* New */
if (!ret) {
if (changed) *changed |= EPG_CHANGED_CREATE;
*save = 1;
ret = *bcast;
*bcast = NULL;
_epg_object_create(ret);
// Note: sets updated
_epg_object_getref(ret);
ret->grabber = src;
tvhtrace(LS_EPG, "added event %u (%s) on %s @ %s to %s (grabber %s)",
ret->id, epg_broadcast_get_title(ret, NULL),
channel_get_name(ch, channel_blank_name),
gmtime2local(ret->start, tm1, sizeof(tm1)),
gmtime2local(ret->stop, tm2, sizeof(tm2)),
src->id);
/* Existing */
} else {
if (!_epg_object_set_grabber(ret, src))
return NULL;
/* No time change */
if (ret->stop == (*bcast)->stop) {
return ret;
/* Extend in time */
} else {
ret->stop = (*bcast)->stop;
_epg_object_set_updated(ret);
tvhtrace(LS_EPG, "updated event %u (%s) on %s @ %s to %s (grabber %s)",
ret->id, epg_broadcast_get_title(ret, NULL),
channel_get_name(ch, channel_blank_name),
gmtime2local(ret->start, tm1, sizeof(tm1)),
gmtime2local(ret->stop, tm2, sizeof(tm2)),
src->id);
}
}
}
/* Changed */
*save |= 1;
/* Remove overlapping (before) */
while ((ebc = RB_PREV(ret, sched_link)) != NULL) {
if (ebc->stop <= ret->start) break;
if (!_epg_object_can_remove(ebc, ret)) {
tvhtrace(LS_EPG, "grabber %s for event %u has higher priority than overlap (b), removing added",
ebc->grabber->id, ebc->id);
_epg_channel_rem_broadcast(ch, ret, NULL);
return NULL;
}
if (config.epg_cut_window && ebc->stop - ebc->start > config.epg_cut_window * 2 &&
ebc->stop - ret->start <= config.epg_cut_window) {
tvhtrace(LS_EPG, "cut stop for overlap (b) event %u (%s) on %s @ %s to %s",
ebc->id, epg_broadcast_get_title(ebc, NULL),
channel_get_name(ch, channel_blank_name),
gmtime2local(ebc->start, tm1, sizeof(tm1)),
gmtime2local(ebc->stop, tm2, sizeof(tm2)));
ebc->stop = ret->start;
_epg_object_set_updated(ebc);
continue;
}
tvhtrace(LS_EPG, "remove overlap (b) event %u (%s) on %s @ %s to %s",
ebc->id, epg_broadcast_get_title(ebc, NULL),
channel_get_name(ch, channel_blank_name),
gmtime2local(ebc->start, tm1, sizeof(tm1)),
gmtime2local(ebc->stop, tm2, sizeof(tm2)));
_epg_channel_rem_broadcast(ch, ebc, ret);
}
/* Remove overlapping (after) */
while ((ebc = RB_NEXT(ret, sched_link)) != NULL) {
if (ebc->start >= ret->stop) break;
if (!_epg_object_can_remove(ebc, ret)) {
tvhtrace(LS_EPG, "grabber %s for event %u has higher priority than overlap (a), removing added",
ebc->grabber->id, ebc->id);
_epg_channel_rem_broadcast(ch, ret, NULL);
return NULL;
}
if (config.epg_cut_window && ret->stop - ret->start > config.epg_cut_window * 2 &&
ret->stop - ebc->start <= config.epg_cut_window) {
tvhtrace(LS_EPG, "cut stop for overlap (a) event %u (%s) on %s @ %s to %s",
ebc->id, epg_broadcast_get_title(ebc, NULL),
channel_get_name(ch, channel_blank_name),
gmtime2local(ebc->start, tm1, sizeof(tm1)),
gmtime2local(ebc->stop, tm2, sizeof(tm2)));
ret->stop = ebc->start;
_epg_object_set_updated(ebc);
continue;
}
tvhtrace(LS_EPG, "remove overlap (a) event %u (%s) on %s @ %s to %s",
ebc->id, epg_broadcast_get_title(ebc, NULL),
channel_get_name(ch, channel_blank_name),
gmtime2local(ebc->start, tm1, sizeof(tm1)),
gmtime2local(ebc->stop, tm2, sizeof(tm2)));
_epg_channel_rem_broadcast(ch, ebc, ret);
}
/* Check now/next change */
if (RB_FIRST(&ch->ch_epg_schedule) == ret) {
timer = 1;
} else if (ch->ch_epg_now &&
RB_NEXT(ch->ch_epg_now, sched_link) == ret) {
timer = 1;
}
/* Reset timer - it might free return event! */
ret->ops->getref(ret);
if (timer) _epg_channel_timer_callback(ch);
if (ret->ops->putref(ret)) return NULL;
return ret;
}
void epg_channel_unlink ( channel_t *ch )
{
epg_broadcast_t *ebc;
while ((ebc = RB_FIRST(&ch->ch_epg_schedule)))
_epg_channel_rem_broadcast(ch, ebc, NULL);
gtimer_disarm(&ch->ch_epg_timer);
}
static int epg_match_event_fuzzy(epg_broadcast_t *a, epg_broadcast_t *b)
{
time_t t1, t2;
const char *title1, *title2;
epg_episode_num_t num1, num2;
if (a == NULL || b == NULL)
return 0;
/* Matching ID */
if (a->dvb_eid) {
if (b->dvb_eid && a->dvb_eid == b->dvb_eid)
return 1;
return 0;
}
/* Wrong length (+/-20%) */
t1 = a->stop - a->start;
t2 = b->stop - b->start;
if (labs((long)(t2 - t1)) > (t1 / 5))
return 0;
/* No title */
if (!(title1 = epg_broadcast_get_title(a, NULL)))
return 0;
if (!(title2 = epg_broadcast_get_title(b, NULL)))
return 0;
/* Outside of window */
if ((int64_t)llabs(b->start - a->start) > config.epg_update_window)
return 0;
/* Title match (or contains?) */
if (strcasecmp(title1, title2))
return 0;
/* episode check */
epg_broadcast_get_epnum(a, &num1);
epg_broadcast_get_epnum(b, &num2);
if (epg_episode_number_cmp(&num1, &num2) == 0)
return 1;
return 0;
}
epg_broadcast_t *epg_match_now_next ( channel_t *ch, epg_broadcast_t *ebc )
{
epg_broadcast_t *ret;
if (epg_match_event_fuzzy(ch->ch_epg_now, ebc))
ret = ch->ch_epg_now;
else if (epg_match_event_fuzzy(ch->ch_epg_next, ebc))
ret = ch->ch_epg_next;
else
return NULL;
/* update eid for further lookups */
if (ret->dvb_eid != ebc->dvb_eid && ret->dvb_eid == 0 && ebc->dvb_eid)
ret->dvb_eid = ebc->dvb_eid;
return ret;
}
/* **************************************************************************
* Broadcast set
* *************************************************************************/
static int _set_uri_cmp(const void *_a, const void *_b)
{
const epg_set_t *a = _a, *b = _b;
return strcmp(a->uri, b->uri);
}
epg_set_t *epg_set_broadcast_find_by_uri
(epg_set_tree_t *tree, const char *uri)
{
epg_set_t *dummy;
if (tree == NULL || uri == NULL || uri[0] == '\0')
return NULL;
dummy = (epg_set_t *)(uri - offsetof(epg_set_t, uri));
return RB_FIND(tree, dummy, set_link, _set_uri_cmp);
}
epg_set_t *epg_set_broadcast_insert
(epg_set_tree_t *tree, epg_broadcast_t *b, const char *uri)
{
epg_set_t *es;
epg_set_item_t *item;
if (tree == NULL || b == NULL)
return NULL;
if (uri == NULL || uri[0] == '\0')
return NULL;
es = epg_set_broadcast_find_by_uri(tree, uri);
if (!es) {
es = malloc(sizeof(*es) + strlen(uri) + 1);
LIST_INIT(&es->broadcasts);
strcpy(es->uri, uri);
RB_INSERT_SORTED(tree, es, set_link, _set_uri_cmp);
}
item = malloc(sizeof(*item));
item->broadcast = b;
LIST_INSERT_HEAD(&es->broadcasts, item, item_link);
return es;
}
void epg_set_broadcast_remove
(epg_set_tree_t *tree, epg_set_t *set, epg_broadcast_t *b)
{
epg_set_item_t *item;
if (tree == NULL || set == NULL)
return;
LIST_FOREACH(item, &set->broadcasts, item_link)
if (item->broadcast == b) {
LIST_REMOVE(item, item_link);
free(item);
if (LIST_EMPTY(&set->broadcasts)) {
RB_REMOVE(tree, set, set_link);
free(set);
}
return;
}
assert(0);
}
/* **************************************************************************
* Broadcast
* *************************************************************************/
static void _epg_broadcast_destroy ( void *eo )
{
epg_broadcast_t *ebc = eo;
epg_genre_t *eg;
char id[16];
if (ebc->_created) {
htsp_event_delete(ebc);
snprintf(id, sizeof(id), "%u", ebc->id);
notify_delayed(id, "epg", "delete");
}
if (ebc->title) lang_str_destroy(ebc->title);
if (ebc->subtitle) lang_str_destroy(ebc->subtitle);
if (ebc->summary) lang_str_destroy(ebc->summary);
if (ebc->description) lang_str_destroy(ebc->description);
while ((eg = LIST_FIRST(&ebc->genre))) {
LIST_REMOVE(eg, link);
free(eg);
}
free(ebc->image);
free(ebc->epnum.text);
if (ebc->credits) htsmsg_destroy(ebc->credits);
if (ebc->credits_cached) lang_str_destroy(ebc->credits_cached);
if (ebc->category) string_list_destroy(ebc->category);
if (ebc->keyword) string_list_destroy(ebc->keyword);
if (ebc->keyword_cached) lang_str_destroy(ebc->keyword_cached);
epg_set_broadcast_remove(&epg_serieslinks, ebc->serieslink, ebc);
epg_set_broadcast_remove(&epg_episodelinks, ebc->episodelink, ebc);
_epg_object_destroy(eo, NULL);
assert(LIST_EMPTY(&ebc->dvr_entries));
free(ebc);
}
static void _epg_broadcast_update_running ( epg_broadcast_t *broadcast )
{
channel_t *ch;
epg_broadcast_t *now;
int orunning = broadcast->running;
broadcast->running = broadcast->update_running;
ch = broadcast->channel;
now = ch ? ch->ch_epg_now : NULL;
if (broadcast->update_running == EPG_RUNNING_STOP) {
if (now == broadcast && orunning == broadcast->running)
broadcast->stop = gclk() - 1;
} else {
if (broadcast != now && now) {
now->running = EPG_RUNNING_STOP;
dvr_event_running(now, EPG_RUNNING_STOP);
}
}
dvr_event_running(broadcast, broadcast->running);
broadcast->update_running = EPG_RUNNING_NOTSET;
}
static void _epg_broadcast_updated ( void *eo )
{
epg_broadcast_t *ebc = eo;
char id[16];
if (!epg_in_load)
snprintf(id, sizeof(id), "%u", ebc->id);
else
id[0] = '\0';
if (ebc->_created) {
htsp_event_update(eo);
notify_delayed(id, "epg", "update");
} else {
htsp_event_add(eo);
notify_delayed(id, "epg", "create");
}
if (ebc->channel) {
dvr_event_updated(eo);
if (ebc->update_running != EPG_RUNNING_NOTSET)
_epg_broadcast_update_running(ebc);
dvr_autorec_check_event(eo);
channel_event_updated(eo);
}
}
static epg_object_ops_t _epg_broadcast_ops = {
.getref = _epg_object_getref,
.putref = _epg_object_putref,
.destroy = _epg_broadcast_destroy,
.update = _epg_broadcast_updated,
};
static epg_broadcast_t **_epg_broadcast_skel ( void )
{
static epg_broadcast_t *skel = NULL;
if (!skel) {
skel = calloc(1, sizeof(epg_broadcast_t));
skel->type = EPG_BROADCAST;
skel->ops = &_epg_broadcast_ops;
}
return &skel;
}
epg_broadcast_t *epg_broadcast_find_by_time
( channel_t *channel, epggrab_module_t *src,
time_t start, time_t stop, int create, int *save, epg_changes_t *changed )
{
epg_broadcast_t **ebc;
if (!channel || !start || !stop) return NULL;
if (stop <= start) return NULL;
if (stop <= gclk()) return NULL;
ebc = _epg_broadcast_skel();
(*ebc)->start = start;
(*ebc)->stop = stop;
return _epg_channel_add_broadcast(channel, ebc, src, create, save, changed);
}
int epg_broadcast_change_finish
( epg_broadcast_t *broadcast, epg_changes_t changes, int merge )
{
int save = 0;
if (merge) return 0;
if (changes & EPG_CHANGED_CREATE) return 0;
if (!(changes & EPG_CHANGED_SERIESLINK))
save |= epg_broadcast_set_serieslink_uri(broadcast, NULL, NULL);
if (!(changes & EPG_CHANGED_EPISODE))
save |= epg_broadcast_set_episodelink_uri(broadcast, NULL, NULL);
if (!(changes & EPG_CHANGED_DVB_EID))
save |= epg_broadcast_set_dvb_eid(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_IS_WIDESCREEN))
save |= epg_broadcast_set_is_widescreen(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_IS_HD))
save |= epg_broadcast_set_is_hd(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_LINES))
save |= epg_broadcast_set_lines(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_ASPECT))
save |= epg_broadcast_set_aspect(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_DEAFSIGNED))
save |= epg_broadcast_set_is_deafsigned(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_SUBTITLED))
save |= epg_broadcast_set_is_subtitled(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_AUDIO_DESC))
save |= epg_broadcast_set_is_audio_desc(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_IS_NEW))
save |= epg_broadcast_set_is_new(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_IS_REPEAT))
save |= epg_broadcast_set_is_repeat(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_IS_BW))
save |= epg_broadcast_set_is_bw(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_STAR_RATING))
save |= epg_broadcast_set_star_rating(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_AGE_RATING))
save |= epg_broadcast_set_age_rating(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_IMAGE))
save |= epg_broadcast_set_image(broadcast, NULL, NULL);
if (!(changes & EPG_CHANGED_GENRE))
save |= epg_broadcast_set_genre(broadcast, NULL, NULL);
if (!(changes & EPG_CHANGED_TITLE))
save |= epg_broadcast_set_title(broadcast, NULL, NULL);
if (!(changes & EPG_CHANGED_SUBTITLE))
save |= epg_broadcast_set_subtitle(broadcast, NULL, NULL);
if (!(changes & EPG_CHANGED_SUMMARY))
save |= epg_broadcast_set_summary(broadcast, NULL, NULL);
if (!(changes & EPG_CHANGED_DESCRIPTION))
save |= epg_broadcast_set_description(broadcast, NULL, NULL);
if (!(changes & EPG_CHANGED_EPSER_NUM))
save |= _epg_object_set_u16(broadcast, &broadcast->epnum.s_num, 0, NULL, 0);
if (!(changes & EPG_CHANGED_EPSER_CNT))
save |= _epg_object_set_u16(broadcast, &broadcast->epnum.s_cnt, 0, NULL, 0);
if (!(changes & EPG_CHANGED_EPNUM_NUM))
save |= _epg_object_set_u16(broadcast, &broadcast->epnum.e_num, 0, NULL, 0);
if (!(changes & EPG_CHANGED_EPNUM_CNT))
save |= _epg_object_set_u16(broadcast, &broadcast->epnum.e_cnt, 0, NULL, 0);
if (!(changes & EPG_CHANGED_EPPAR_NUM))
save |= _epg_object_set_u16(broadcast, &broadcast->epnum.p_num, 0, NULL, 0);
if (!(changes & EPG_CHANGED_EPPAR_CNT))
save |= _epg_object_set_u16(broadcast, &broadcast->epnum.p_cnt, 0, NULL, 0);
if (!(changes & EPG_CHANGED_EPTEXT))
save |= _epg_object_set_str(broadcast, &broadcast->epnum.text, NULL, NULL, 0);
if (!(changes & EPG_CHANGED_FIRST_AIRED))
save |= epg_broadcast_set_first_aired(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_COPYRIGHT_YEAR))
save |= epg_broadcast_set_copyright_year(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_CREDITS))
save |= epg_broadcast_set_credits(broadcast, NULL, NULL);
if (!(changes & EPG_CHANGED_CATEGORY))
save |= epg_broadcast_set_category(broadcast, NULL, NULL);
if (!(changes & EPG_CHANGED_KEYWORD))
save |= epg_broadcast_set_keyword(broadcast, NULL, NULL);
return save;
}
epg_broadcast_t *epg_broadcast_clone
( channel_t *channel, epg_broadcast_t *src, int *save )
{
epg_broadcast_t *ebc;
epg_changes_t changes = 0;
if (!src) return NULL;
ebc = epg_broadcast_find_by_time(channel, src->grabber,
src->start, src->stop,
1, save, &changes);
if (ebc) {
/* Copy metadata */
*save |= epg_broadcast_set_is_widescreen(ebc, src->is_widescreen, &changes);
*save |= epg_broadcast_set_is_hd(ebc, src->is_hd, &changes);
*save |= epg_broadcast_set_is_bw(ebc, src->is_bw, &changes);
*save |= epg_broadcast_set_lines(ebc, src->lines, &changes);
*save |= epg_broadcast_set_aspect(ebc, src->aspect, &changes);
*save |= epg_broadcast_set_is_deafsigned(ebc, src->is_deafsigned, &changes);
*save |= epg_broadcast_set_is_subtitled(ebc, src->is_subtitled, &changes);
*save |= epg_broadcast_set_is_audio_desc(ebc, src->is_audio_desc, &changes);
*save |= epg_broadcast_set_is_new(ebc, src->is_new, &changes);
*save |= epg_broadcast_set_is_repeat(ebc, src->is_repeat, &changes);
*save |= epg_broadcast_set_star_rating(ebc, src->star_rating, &changes);
*save |= epg_broadcast_set_age_rating(ebc, src->age_rating, &changes);
*save |= epg_broadcast_set_image(ebc, src->image, &changes);
*save |= epg_broadcast_set_genre(ebc, &src->genre, &changes);
*save |= epg_broadcast_set_title(ebc, src->title, &changes);
*save |= epg_broadcast_set_subtitle(ebc, src->subtitle, &changes);
*save |= epg_broadcast_set_summary(ebc, src->summary, &changes);
*save |= epg_broadcast_set_description(ebc, src->description, &changes);
*save |= epg_broadcast_set_epnum(ebc, &src->epnum, &changes);
*save |= epg_broadcast_set_credits(ebc, src->credits, &changes);
*save |= epg_broadcast_set_category(ebc, src->category, &changes);
*save |= epg_broadcast_set_keyword(ebc, src->keyword, &changes);
*save |= epg_broadcast_set_description(ebc, src->description, &changes);
*save |= epg_broadcast_set_serieslink_uri
(ebc, src->serieslink ? src->serieslink->uri : NULL, &changes);
*save |= epg_broadcast_set_episodelink_uri
(ebc, src->episodelink ? src->episodelink->uri : NULL, &changes);
*save |= epg_broadcast_set_first_aired(ebc, src->first_aired, &changes);
*save |= epg_broadcast_set_copyright_year(ebc, src->copyright_year, &changes);
_epg_object_set_grabber(ebc, src->grabber);
*save |= epg_broadcast_change_finish(ebc, changes, 0);
}
return ebc;
}
epg_broadcast_t *epg_broadcast_find_by_id ( uint32_t id )
{
return (epg_broadcast_t*)epg_object_find_by_id(id, EPG_BROADCAST);
}
epg_broadcast_t *epg_broadcast_find_by_eid ( channel_t *ch, uint16_t eid )
{
epg_broadcast_t *e;
RB_FOREACH(e, &ch->ch_epg_schedule, sched_link) {
if (e->dvb_eid == eid) return e;
}
return NULL;
}
int epg_broadcast_set_running
( epg_broadcast_t *broadcast, epg_running_t running )
{
int save = 0;
if (running != broadcast->running) {
broadcast->update_running = running;
_epg_object_set_updated(broadcast);
save = 1;
}
return save;
}
static int _epg_broadcast_set_set
( epg_broadcast_t *ebc, const char *uri,
epg_set_tree_t *tree, epg_set_t **set )
{
if (*set == NULL) {
if (uri == NULL || uri[0] == '\0')
return 0;
} else if (strcmp((*set)->uri ?: "", uri ?: "")) {
epg_set_broadcast_remove(tree, *set, ebc);
} else {
return 0;
}
*set = epg_set_broadcast_insert(tree, ebc, uri);
return 1;
}
int epg_broadcast_set_serieslink_uri
( epg_broadcast_t *ebc, const char *uri, epg_changes_t *changed )
{
if (!ebc) return 0;
if (changed) *changed |= EPG_CHANGED_SERIESLINK;
return _epg_broadcast_set_set(ebc, uri, &epg_serieslinks, &ebc->serieslink);
}
int epg_broadcast_set_episodelink_uri
( epg_broadcast_t *ebc, const char *uri, epg_changes_t *changed )
{
if (!ebc) return 0;
if (changed) *changed |= EPG_CHANGED_EPISODE;
return _epg_broadcast_set_set(ebc, uri, &epg_episodelinks, &ebc->episodelink);
}
int epg_broadcast_set_dvb_eid
( epg_broadcast_t *b, uint16_t dvb_eid, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_u16(b, &b->dvb_eid, dvb_eid,
changed, EPG_CHANGED_DVB_EID);
}
int epg_broadcast_set_is_widescreen
( epg_broadcast_t *b, uint8_t ws, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_u8(b, &b->is_widescreen, ws,
changed, EPG_CHANGED_IS_WIDESCREEN);
}
int epg_broadcast_set_is_hd
( epg_broadcast_t *b, uint8_t hd, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_u8(b, &b->is_hd, hd,
changed, EPG_CHANGED_IS_HD);
}
int epg_broadcast_set_lines
( epg_broadcast_t *b, uint16_t lines, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_u16(b, &b->lines, lines,
changed, EPG_CHANGED_LINES);
}
int epg_broadcast_set_aspect
( epg_broadcast_t *b, uint16_t aspect, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_u16(b, &b->aspect, aspect,
changed, EPG_CHANGED_ASPECT);
}
int epg_broadcast_set_is_deafsigned
( epg_broadcast_t *b, uint8_t ds, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_u8(b, &b->is_deafsigned, ds,
changed, EPG_CHANGED_DEAFSIGNED);
}
int epg_broadcast_set_is_subtitled
( epg_broadcast_t *b, uint8_t st, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_u8(b, &b->is_subtitled, st,
changed, EPG_CHANGED_SUBTITLED);
}
int epg_broadcast_set_is_audio_desc
( epg_broadcast_t *b, uint8_t ad, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_u8(b, &b->is_audio_desc, ad,
changed, EPG_CHANGED_AUDIO_DESC);
}
int epg_broadcast_set_is_new
( epg_broadcast_t *b, uint8_t n, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_u8(b, &b->is_new, n,
changed, EPG_CHANGED_IS_NEW);
}
int epg_broadcast_set_is_repeat
( epg_broadcast_t *b, uint8_t r, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_u8(b, &b->is_repeat, r,
changed, EPG_CHANGED_IS_REPEAT);
}
int epg_broadcast_set_is_bw
( epg_broadcast_t *b, uint8_t bw, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_u8(b, &b->is_bw, bw,
changed, EPG_CHANGED_IS_BW);
}
int epg_broadcast_set_star_rating
( epg_broadcast_t *b, uint8_t stars, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_u8(b, &b->star_rating, stars,
changed, EPG_CHANGED_STAR_RATING);
}
int epg_broadcast_set_title
( epg_broadcast_t *b, const lang_str_t *title, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_lang_str(b, &b->title, title,
changed, EPG_CHANGED_TITLE);
}
int epg_broadcast_set_subtitle
( epg_broadcast_t *b, const lang_str_t *subtitle, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_lang_str(b, &b->subtitle,
subtitle, changed, EPG_CHANGED_SUBTITLE);
}
int epg_broadcast_set_summary
( epg_broadcast_t *b, const lang_str_t *str, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_lang_str(b, &b->summary, str,
changed, EPG_CHANGED_SUMMARY);
}
int epg_broadcast_set_description
( epg_broadcast_t *b, const lang_str_t *str, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_lang_str(b, &b->description, str,
changed, EPG_CHANGED_DESCRIPTION);
}
int epg_broadcast_set_credits
( epg_broadcast_t *b, const htsmsg_t *credits, epg_changes_t *changed )
{
if (!b) return 0;
const int mod = _epg_object_set_htsmsg(b, &b->credits, credits, changed, EPG_CHANGED_CREDITS);
if (mod) {
/* Copy in to cached csv for regex searching in autorec/GUI.
* We use just one string (rather than regex across each entry
* separately) so you could do a regex of "Douglas.*Stallone"
* to match the movies with the two actors.
*/
if (!b->credits_cached) {
b->credits_cached = lang_str_create();
}
lang_str_set(&b->credits_cached, "", NULL);
if (b->credits) {
int add_sep = 0;
htsmsg_field_t *f;
HTSMSG_FOREACH(f, b->credits) {
if (add_sep) {
lang_str_append(b->credits_cached, ", ", NULL);
} else {
add_sep = 1;
}
lang_str_append(b->credits_cached, htsmsg_field_name(f), NULL);
}
} else {
if (b->credits_cached) {
lang_str_destroy(b->credits_cached);
b->credits_cached = NULL;
}
}
}
return mod;
}
int epg_broadcast_set_category
( epg_broadcast_t *b, const string_list_t *msg, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_string_list(b, &b->category, msg, changed, EPG_CHANGED_CATEGORY);
}
int epg_broadcast_set_keyword
( epg_broadcast_t *b, const string_list_t *msg, epg_changes_t *changed )
{
if (!b) return 0;
const int mod = _epg_object_set_string_list(b, &b->keyword, msg, changed, EPG_CHANGED_KEYWORD);
if (mod) {
/* Copy in to cached csv for regex searching in autorec/GUI. */
if (msg) {
/* 1==>human readable */
char *str = string_list_2_csv(msg, ',', 1);
lang_str_set(&b->keyword_cached, str, NULL);
free(str);
} else {
if (b->keyword_cached) {
lang_str_destroy(b->keyword_cached);
b->keyword_cached = NULL;
}
}
}
return mod;
}
int epg_broadcast_set_image
( epg_broadcast_t *b, const char *image, epg_changes_t *changed )
{
int save;
if (!b) return 0;
save = _epg_object_set_str(b, &b->image, image,
changed, EPG_CHANGED_IMAGE);
if (save)
imagecache_get_id(image);
return save;
}
int epg_broadcast_set_epnumber
( epg_broadcast_t *b, uint16_t number, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_u16(b, &b->epnum.e_num, number,
changed, EPG_CHANGED_EPNUM_NUM);
}
int epg_broadcast_set_eppart
( epg_broadcast_t *b, uint16_t part, uint16_t count,
epg_changes_t *changed )
{
int save = 0;
if (!b) return 0;
save |= _epg_object_set_u16(b, &b->epnum.p_num, part,
changed, EPG_CHANGED_EPPAR_NUM);
save |= _epg_object_set_u16(b, &b->epnum.p_cnt, count,
changed, EPG_CHANGED_EPPAR_CNT);
return save;
}
int epg_broadcast_set_epnum
( epg_broadcast_t *b, epg_episode_num_t *num, epg_changes_t *changed )
{
int save = 0;
static epg_episode_num_t _zero = { 0 };
if (!b)
return 0;
if (!num)
num = &_zero;
if (num->s_num)
save |= _epg_object_set_u16(b, &b->epnum.s_num,
num->s_num, changed, EPG_CHANGED_EPSER_NUM);
if (num->s_cnt)
save |= _epg_object_set_u16(b, &b->epnum.s_cnt,
num->s_cnt, changed, EPG_CHANGED_EPSER_CNT);
if (num->e_num)
save |= _epg_object_set_u16(b, &b->epnum.e_num,
num->e_num, changed, EPG_CHANGED_EPNUM_NUM);
if (num->e_cnt)
save |= _epg_object_set_u16(b, &b->epnum.e_cnt,
num->e_cnt, changed, EPG_CHANGED_EPNUM_CNT);
if (num->p_num)
save |= _epg_object_set_u16(b, &b->epnum.p_num,
num->p_num, changed, EPG_CHANGED_EPPAR_NUM);
if (num->p_cnt)
save |= _epg_object_set_u16(b, &b->epnum.p_cnt,
num->p_cnt, changed, EPG_CHANGED_EPPAR_CNT);
if (num->text)
save |= _epg_object_set_str(b, &b->epnum.text,
num->text, changed, EPG_CHANGED_EPTEXT);
return save;
}
int epg_broadcast_set_genre
( epg_broadcast_t *b, epg_genre_list_t *genre, epg_changes_t *changed )
{
int save = 0;
epg_genre_t *g1, *g2;
if (!b) return 0;
if (changed) *changed |= EPG_CHANGED_GENRE;
g1 = LIST_FIRST(&b->genre);
/* Remove old */
while (g1) {
g2 = LIST_NEXT(g1, link);
if (!epg_genre_list_contains(genre, g1, 0)) {
LIST_REMOVE(g1, link);
free(g1);
save = 1;
}
g1 = g2;
}
/* Insert all entries */
if (genre) {
LIST_FOREACH(g1, genre, link)
save |= epg_genre_list_add(&b->genre, g1);
}
return save;
}
int epg_broadcast_set_copyright_year
( epg_broadcast_t *b, uint16_t year, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_u16(b, &b->copyright_year, year,
changed, EPG_CHANGED_COPYRIGHT_YEAR);
}
int epg_broadcast_set_age_rating
( epg_broadcast_t *b, uint8_t age, epg_changes_t *changed )
{
if (!b) return 0;
return _epg_object_set_u8(b, &b->age_rating, age,
changed, EPG_CHANGED_AGE_RATING);
}
int epg_broadcast_set_first_aired
( epg_broadcast_t *b, time_t aired, epg_changes_t *changed )
{
if (!b) return 0;
if (changed) *changed |= EPG_CHANGED_FIRST_AIRED;
if (b->first_aired != aired) {
b->first_aired = aired;
_epg_object_set_updated(b);
return 1;
}
return 0;
}
epg_broadcast_t *epg_broadcast_get_prev ( epg_broadcast_t *b )
{
if (!b) return NULL;
return RB_PREV(b, sched_link);
}
epg_broadcast_t *epg_broadcast_get_next ( epg_broadcast_t *b )
{
if (!b) return NULL;
return RB_NEXT(b, sched_link);
}
const char *epg_broadcast_get_title ( const epg_broadcast_t *b, const char *lang )
{
if (!b || !b->title) return NULL;
return lang_str_get(b->title, lang);
}
const char *epg_broadcast_get_subtitle ( epg_broadcast_t *b, const char *lang )
{
if (!b || !b->subtitle) return NULL;
return lang_str_get(b->subtitle, lang);
}
const char *epg_broadcast_get_summary ( epg_broadcast_t *b, const char *lang )
{
if (!b || !b->summary) return NULL;
return lang_str_get(b->summary, lang);
}
const char *epg_broadcast_get_credits_cached ( epg_broadcast_t *b, const char *lang)
{
if (!b || !b->credits_cached) return NULL;
return lang_str_get(b->credits_cached, lang);
}
const char *epg_broadcast_get_keyword_cached ( epg_broadcast_t *b, const char *lang)
{
if (!b || !b->keyword_cached) return NULL;
return lang_str_get(b->keyword_cached, lang);
}
const char *epg_broadcast_get_description ( epg_broadcast_t *b, const char *lang )
{
if (!b || !b->description) return NULL;
return lang_str_get(b->description, lang);
}
void epg_broadcast_get_epnum ( const epg_broadcast_t *b, epg_episode_num_t *num )
{
if (!b || !num) {
if (num)
memset(num, 0, sizeof(*num));
return;
}
*num = b->epnum;
}
size_t epg_broadcast_epnumber_format
( epg_broadcast_t *b, char *buf, size_t len,
const char *pre, const char *sfmt,
const char *sep, const char *efmt,
const char *cfmt )
{
if (!b) return 0;
epg_episode_num_t num;
epg_broadcast_get_epnum(b, &num);
return epg_episode_num_format(&num, buf, len, pre,
sfmt, sep, efmt, cfmt);
}
htsmsg_t *epg_broadcast_serialize ( epg_broadcast_t *broadcast )
{
htsmsg_t *m, *a;
epg_genre_t *eg;
char ubuf[UUID_HEX_SIZE];
if (!broadcast) return NULL;
if (!(m = _epg_object_serialize((epg_object_t*)broadcast))) return NULL;
htsmsg_add_s64(m, "start", broadcast->start);
htsmsg_add_s64(m, "stop", broadcast->stop);
if (broadcast->channel)
htsmsg_add_str(m, "ch", channel_get_uuid(broadcast->channel, ubuf));
if (broadcast->dvb_eid)
htsmsg_add_u32(m, "eid", broadcast->dvb_eid);
if (broadcast->is_widescreen)
htsmsg_add_u32(m, "is_wd", 1);
if (broadcast->is_hd)
htsmsg_add_u32(m, "is_hd", 1);
if (broadcast->is_bw)
htsmsg_add_u32(m, "is_bw", 1);
if (broadcast->lines)
htsmsg_add_u32(m, "lines", broadcast->lines);
if (broadcast->aspect)
htsmsg_add_u32(m, "aspect", broadcast->aspect);
if (broadcast->is_deafsigned)
htsmsg_add_u32(m, "is_de", 1);
if (broadcast->is_subtitled)
htsmsg_add_u32(m, "is_st", 1);
if (broadcast->is_audio_desc)
htsmsg_add_u32(m, "is_ad", 1);
if (broadcast->is_new)
htsmsg_add_u32(m, "is_n", 1);
if (broadcast->is_repeat)
htsmsg_add_u32(m, "is_r", 1);
if (broadcast->star_rating)
htsmsg_add_u32(m, "star", broadcast->star_rating);
if (broadcast->age_rating)
htsmsg_add_u32(m, "age", broadcast->age_rating);
if (broadcast->image)
htsmsg_add_str(m, "img", broadcast->image);
if (broadcast->title)
lang_str_serialize(broadcast->title, m, "tit");
if (broadcast->subtitle)
lang_str_serialize(broadcast->subtitle, m, "sti");
if (broadcast->summary)
lang_str_serialize(broadcast->summary, m, "sum");
if (broadcast->description)
lang_str_serialize(broadcast->description, m, "des");
if ((a = epg_episode_epnum_serialize(&broadcast->epnum)) != NULL)
htsmsg_add_msg(m, "epn", a);
a = NULL;
LIST_FOREACH(eg, &broadcast->genre, link) {
if (!a) a = htsmsg_create_list();
htsmsg_add_u32(a, NULL, eg->code);
}
if (a) htsmsg_add_msg(m, "genre", a);
if (broadcast->copyright_year)
htsmsg_add_u32(m, "cyear", broadcast->copyright_year);
if (broadcast->first_aired)
htsmsg_add_s64(m, "fair", broadcast->first_aired);
if (broadcast->credits)
htsmsg_add_msg(m, "cred", htsmsg_copy(broadcast->credits));
/* No need to serialize credits_cached since it is rebuilt from credits. */
if (broadcast->category)
string_list_serialize(broadcast->category, m, "cat");
if (broadcast->keyword)
string_list_serialize(broadcast->keyword, m, "key");
/* No need to serialize keyword_cached since it is rebuilt from keyword */
if (broadcast->serieslink)
htsmsg_add_str(m, "slink", broadcast->serieslink->uri);
if (broadcast->episodelink)
htsmsg_add_str(m, "elink", broadcast->episodelink->uri);
return m;
}
epg_broadcast_t *epg_broadcast_deserialize
( htsmsg_t *m, int create, int *save )
{
channel_t *ch = NULL;
epg_broadcast_t *ebc, **skel = _epg_broadcast_skel();
lang_str_t *ls;
htsmsg_t *hm;
htsmsg_field_t *f;
string_list_t *sl;
const char *str;
uint32_t eid, u32;
epg_changes_t changes = 0;
int64_t start, stop, s64;
epg_episode_num_t num;
if (htsmsg_get_s64(m, "start", &start)) return NULL;
if (htsmsg_get_s64(m, "stop", &stop)) return NULL;
if (!start || !stop) return NULL;
if (stop <= start) return NULL;
if (stop <= gclk()) return NULL;
_epg_object_deserialize(m, (epg_object_t*)*skel);
/* Set properties */
(*skel)->start = start;
(*skel)->stop = stop;
/* Get channel */
if ((str = htsmsg_get_str(m, "ch")))
ch = channel_find(str);
if (!ch) return NULL;
/* Create */
ebc = _epg_channel_add_broadcast(ch, skel, (*skel)->grabber, create, save, &changes);
if (!ebc) return NULL;
/* Get metadata */
if (!htsmsg_get_u32(m, "eid", &eid))
*save |= epg_broadcast_set_dvb_eid(ebc, eid, &changes);
if (!htsmsg_get_u32(m, "is_wd", &u32))
*save |= epg_broadcast_set_is_widescreen(ebc, u32, &changes);
if (!htsmsg_get_u32(m, "is_hd", &u32))
*save |= epg_broadcast_set_is_hd(ebc, u32, &changes);
if (!htsmsg_get_u32(m, "is_bw", &u32))
*save |= epg_broadcast_set_is_bw(ebc, u32, &changes);
if (!htsmsg_get_u32(m, "lines", &u32))
*save |= epg_broadcast_set_lines(ebc, u32, &changes);
if (!htsmsg_get_u32(m, "aspect", &u32))
*save |= epg_broadcast_set_aspect(ebc, u32, &changes);
if (!htsmsg_get_u32(m, "is_de", &u32))
*save |= epg_broadcast_set_is_deafsigned(ebc, u32, &changes);
if (!htsmsg_get_u32(m, "is_st", &u32))
*save |= epg_broadcast_set_is_subtitled(ebc, u32, &changes);
if (!htsmsg_get_u32(m, "is_ad", &u32))
*save |= epg_broadcast_set_is_audio_desc(ebc, u32, &changes);
if (!htsmsg_get_u32(m, "is_n", &u32))
*save |= epg_broadcast_set_is_new(ebc, u32, &changes);
if (!htsmsg_get_u32(m, "is_r", &u32))
*save |= epg_broadcast_set_is_repeat(ebc, u32, &changes);
if (!htsmsg_get_u32(m, "star", &u32))
*save |= epg_broadcast_set_star_rating(ebc, u32, &changes);
if (!htsmsg_get_u32(m, "age", &u32))
*save |= epg_broadcast_set_age_rating(ebc, u32, &changes);
if ((str = htsmsg_get_str(m, "img")))
*save |= epg_broadcast_set_image(ebc, str, &changes);
if ((hm = htsmsg_get_list(m, "genre"))) {
epg_genre_list_t *egl = calloc(1, sizeof(epg_genre_list_t));
HTSMSG_FOREACH(f, hm) {
epg_genre_t genre;
genre.code = (uint8_t)f->hmf_s64;
epg_genre_list_add(egl, &genre);
}
*save |= epg_broadcast_set_genre(ebc, egl, &changes);
epg_genre_list_destroy(egl);
}
if ((ls = lang_str_deserialize(m, "tit"))) {
*save |= epg_broadcast_set_title(ebc, ls, &changes);
lang_str_destroy(ls);
}
if ((ls = lang_str_deserialize(m, "sti"))) {
*save |= epg_broadcast_set_subtitle(ebc, ls, &changes);
lang_str_destroy(ls);
}
if ((ls = lang_str_deserialize(m, "sum"))) {
*save |= epg_broadcast_set_summary(ebc, ls, &changes);
lang_str_destroy(ls);
}
if ((ls = lang_str_deserialize(m, "des"))) {
*save |= epg_broadcast_set_description(ebc, ls, &changes);
lang_str_destroy(ls);
}
if ((hm = htsmsg_get_map(m, "epn"))) {
epg_episode_epnum_deserialize(hm, &num);
*save |= epg_broadcast_set_epnum(ebc, &num, &changes);
if (num.text) free(num.text);
}
if (!htsmsg_get_u32(m, "cyear", &u32))
*save |= epg_broadcast_set_copyright_year(ebc, u32, &changes);
if (!htsmsg_get_s64(m, "fair", &s64))
*save |= epg_broadcast_set_first_aired(ebc, (time_t)s64, &changes);
if ((hm = htsmsg_get_map(m, "cred")))
*save |= epg_broadcast_set_credits(ebc, hm, &changes);
if ((sl = string_list_deserialize(m, "key"))) {
*save |= epg_broadcast_set_keyword(ebc, sl, &changes);
string_list_destroy(sl);
}
if ((sl = string_list_deserialize(m, "cat"))) {
*save |= epg_broadcast_set_category(ebc, sl, &changes);
string_list_destroy(sl);
}
/* Series link */
if ((str = htsmsg_get_str(m, "slink")))
*save |= epg_broadcast_set_serieslink_uri(ebc, str, &changes);
if ((str = htsmsg_get_str(m, "elink")))
*save |= epg_broadcast_set_episodelink_uri(ebc, str, &changes);
*save |= epg_broadcast_change_finish(ebc, changes, 0);
return ebc;
}
/* **************************************************************************
* Genre
* *************************************************************************/
// TODO: make this configurable
// FULL(ish) list from EN 300 468, I've excluded the last category
// that relates more to broadcast content than what I call a "genre"
// these will be handled elsewhere as broadcast metadata
// Reference (Sept 2016):
// http://www.etsi.org/deliver/etsi_en/300400_300499/300468/01.11.01_60/en_300468v011101p.pdf
#define C_ (const char *[])
static const char **_epg_genre_names[16][16] = {
{ /* 00 */
C_{ "", NULL }
},
{ /* 01 */
C_{ N_("Movie"), N_("Drama"), NULL },
C_{ N_("Detective"), N_("Thriller"), NULL },
C_{ N_("Adventure"), N_("Western"), N_("War"), NULL },
C_{ N_("Science fiction"), N_("Fantasy"), N_("Horror"), NULL },
C_{ N_("Comedy"), NULL },
C_{ N_("Soap"), N_("Melodrama"), N_("Folkloric"), NULL },
C_{ N_("Romance"), NULL },
C_{ N_("Serious"), N_("Classical"), N_("Religious"), N_("Historical movie"), N_("Drama"), NULL },
C_{ N_("Adult movie"), N_("Drama"), NULL },
C_{ N_("Movie / drama"), NULL },
C_{ N_("Movie / drama"), NULL },
C_{ N_("Movie / drama"), NULL },
C_{ N_("Movie / drama"), NULL },
C_{ N_("Movie / drama"), NULL },
C_{ N_("Movie / drama"), NULL },
C_{ N_("Movie / drama"), NULL },
},
{ /* 02 */
C_{ N_("News"), N_("Current affairs"), NULL },
C_{ N_("News"), N_("Weather report"), NULL },
C_{ N_("News magazine"), NULL },
C_{ N_("Documentary"), NULL },
C_{ N_("Discussion"), N_("Interview"), N_("Debate"), NULL },
C_{ N_("News / Current Affairs"), NULL },
C_{ N_("News / Current Affairs"), NULL },
C_{ N_("News / Current Affairs"), NULL },
C_{ N_("News / Current Affairs"), NULL },
C_{ N_("News / Current Affairs"), NULL },
C_{ N_("News / Current Affairs"), NULL },
C_{ N_("News / Current Affairs"), NULL },
C_{ N_("News / Current Affairs"), NULL },
C_{ N_("News / Current Affairs"), NULL },
C_{ N_("News / Current Affairs"), NULL },
C_{ N_("News / Current Affairs"), NULL },
},
{ /* 03 */
C_{ N_("Show"), N_("Game show"), NULL },
C_{ N_("Game show"), N_("Quiz"), N_("Contest"), NULL },
C_{ N_("Variety show"), NULL },
C_{ N_("Talk show"), NULL },
C_{ N_("Show / Game show"), NULL },
C_{ N_("Show / Game show"), NULL },
C_{ N_("Show / Game show"), NULL },
C_{ N_("Show / Game show"), NULL },
C_{ N_("Show / Game show"), NULL },
C_{ N_("Show / Game show"), NULL },
C_{ N_("Show / Game show"), NULL },
C_{ N_("Show / Game show"), NULL },
C_{ N_("Show / Game show"), NULL },
C_{ N_("Show / Game show"), NULL },
C_{ N_("Show / Game show"), NULL },
C_{ N_("Show / Game show"), NULL },
},
{ /* 04 */
C_{ N_("Sports"), NULL },
C_{ N_("Special events (Olympic Games, World Cup, etc.)"), NULL },
C_{ N_("Sports magazines"), NULL },
C_{ N_("Football"), N_("Soccer"), NULL },
C_{ N_("Tennis"), N_("Squash"), NULL },
C_{ N_("Team sports (excluding football)"), NULL },
C_{ N_("Athletics"), NULL },
C_{ N_("Motor sport"), NULL },
C_{ N_("Water sport"), NULL },
C_{ N_("Winter sports"), NULL },
C_{ N_("Equestrian"), NULL },
C_{ N_("Martial sports"), NULL },
C_{ N_("Sports"), NULL },
C_{ N_("Sports"), NULL },
C_{ N_("Sports"), NULL },
C_{ N_("Sports"), NULL },
},
{ /* 05 */
C_{ N_("Children's / Youth programs"), NULL },
C_{ N_("Pre-school children's programs"), NULL },
C_{ N_("Entertainment programs for 6 to 14"), NULL },
C_{ N_("Entertainment programs for 10 to 16"), NULL },
C_{ N_("Informational"), N_("Educational"), N_("School programs"), NULL },
C_{ N_("Cartoons"), N_("Puppets"), NULL },
C_{ N_("Children's / Youth Programs"), NULL },
C_{ N_("Children's / Youth Programs"), NULL },
C_{ N_("Children's / Youth Programs"), NULL },
C_{ N_("Children's / Youth Programs"), NULL },
C_{ N_("Children's / Youth Programs"), NULL },
C_{ N_("Children's / Youth Programs"), NULL },
C_{ N_("Children's / Youth Programs"), NULL },
C_{ N_("Children's / Youth Programs"), NULL },
C_{ N_("Children's / Youth Programs"), NULL },
C_{ N_("Children's / Youth Programs"), NULL },
},
{ /* 06 */
C_{ N_("Music"), N_("Ballet"), N_("Dance"), NULL },
C_{ N_("Rock"), N_("Pop"), NULL },
C_{ N_("Serious music"), N_("Classical music"), NULL },
C_{ N_("Folk"), N_("Traditional music"), NULL },
C_{ N_("Jazz"), NULL },
C_{ N_("Musical"), N_("Opera"), NULL },
C_{ N_("Ballet"), NULL },
C_{ N_("Music / Ballet / Dance"), NULL },
C_{ N_("Music / Ballet / Dance"), NULL },
C_{ N_("Music / Ballet / Dance"), NULL },
C_{ N_("Music / Ballet / Dance"), NULL },
C_{ N_("Music / Ballet / Dance"), NULL },
C_{ N_("Music / Ballet / Dance"), NULL },
C_{ N_("Music / Ballet / Dance"), NULL },
C_{ N_("Music / Ballet / Dance"), NULL },
C_{ N_("Music / Ballet / Dance"), NULL },
},
{ /* 07 */
C_{ N_("Arts"), N_("Culture (without music)"), NULL },
C_{ N_("Performing arts"), NULL },
C_{ N_("Fine arts"), NULL },
C_{ N_("Religion"), NULL },
C_{ N_("Popular culture"), N_("Traditional arts"), NULL },
C_{ N_("Literature"), NULL },
C_{ N_("Film"), N_("Cinema"), NULL },
C_{ N_("Experimental film"), N_("Video"), NULL },
C_{ N_("Broadcasting"), N_("Press"), NULL },
C_{ N_("New media"), NULL },
C_{ N_("Arts magazines"), N_("Culture magazines"), NULL },
C_{ N_("Fashion"), NULL },
C_{ N_("Arts / Culture (without music)"), NULL },
C_{ N_("Arts / Culture (without music)"), NULL },
C_{ N_("Arts / Culture (without music)"), NULL },
C_{ N_("Arts / Culture (without music)"), NULL },
},
{ /* 08 */
C_{ N_("Social"), N_("Political issues"), N_("Economics"), NULL },
C_{ N_("Magazines"), N_("Reports"), N_("Documentary"), NULL },
C_{ N_("Economics"), N_("Social advisory"), NULL },
C_{ N_("Remarkable people"), NULL },
C_{ N_("Social / Political issues / Economics"), NULL },
C_{ N_("Social / Political issues / Economics"), NULL },
C_{ N_("Social / Political issues / Economics"), NULL },
C_{ N_("Social / Political issues / Economics"), NULL },
C_{ N_("Social / Political issues / Economics"), NULL },
C_{ N_("Social / Political issues / Economics"), NULL },
C_{ N_("Social / Political issues / Economics"), NULL },
C_{ N_("Social / Political issues / Economics"), NULL },
C_{ N_("Social / Political issues / Economics"), NULL },
C_{ N_("Social / Political issues / Economics"), NULL },
C_{ N_("Social / Political issues / Economics"), NULL },
C_{ N_("Social / Political issues / Economics"), NULL },
},
{ /* 09 */
C_{ N_("Education"), N_("Science"), N_("Factual topics"), NULL },
C_{ N_("Nature"), N_("Animals"), N_("Environment"), NULL },
C_{ N_("Technology"), N_("Natural sciences"), NULL },
C_{ N_("Medicine"), N_("Physiology"), N_("Psychology"), NULL },
C_{ N_("Foreign countries"), N_("Expeditions"), NULL },
C_{ N_("Social"), N_("Spiritual sciences"), NULL },
C_{ N_("Further education"), NULL },
C_{ N_("Languages"), NULL },
C_{ N_("Education / Science / Factual topics"), NULL },
C_{ N_("Education / Science / Factual topics"), NULL },
C_{ N_("Education / Science / Factual topics"), NULL },
C_{ N_("Education / Science / Factual topics"), NULL },
C_{ N_("Education / Science / Factual topics"), NULL },
C_{ N_("Education / Science / Factual topics"), NULL },
C_{ N_("Education / Science / Factual topics"), NULL },
C_{ N_("Education / Science / Factual topics"), NULL },
},
{ /* 10 */
C_{ N_("Leisure hobbies"), NULL },
C_{ N_("Tourism / Travel"), NULL },
C_{ N_("Handicraft"), NULL },
C_{ N_("Motoring"), NULL },
C_{ N_("Fitness and health"), NULL },
C_{ N_("Cooking"), NULL },
C_{ N_("Advertisement / Shopping"), NULL },
C_{ N_("Gardening"), NULL },
C_{ N_("Leisure hobbies"), NULL },
C_{ N_("Leisure hobbies"), NULL },
C_{ N_("Leisure hobbies"), NULL },
C_{ N_("Leisure hobbies"), NULL },
C_{ N_("Leisure hobbies"), NULL },
C_{ N_("Leisure hobbies"), NULL },
C_{ N_("Leisure hobbies"), NULL },
C_{ N_("Leisure hobbies"), NULL },
}
};
static const char *_genre_get_name(int a, int b, const char *lang)
{
static __thread char name[64];
size_t l = 0;
const char **p = _epg_genre_names[a][b];
name[0] = '\0';
if (p == NULL)
return NULL;
for ( ; *p; p++)
tvh_strlcatf(name, sizeof(name), l, "%s%s", l ? " / " : "",
lang ? tvh_gettext_lang(lang, *p) : *p);
return name;
}
// match strings, ignoring case and whitespace
// Note: | 0x20 is a cheats (fast) way of lowering case
static int _genre_str_match ( const char *a, const char *b )
{
if (!a || !b) return 0;
while (*a != '\0' || *b != '\0') {
while (*a == ' ') a++;
while (*b == ' ') b++;
if ((*a | 0x20) != (*b | 0x20)) return 0;
a++; b++;
}
return (*a == '\0' && *b == '\0'); // end of string(both)
}
static uint8_t _epg_genre_find_by_name ( const char *name, const char *lang )
{
uint8_t a, b;
const char *s;
for (a = 1; a < 11; a++) {
for (b = 0; b < 16; b++) {
s = _genre_get_name(a, b, lang);
if (_genre_str_match(name, s))
return (a << 4) | b;
}
}
return 0; // undefined
}
uint8_t epg_genre_get_eit ( const epg_genre_t *genre )
{
if (!genre) return 0;
return genre->code;
}
size_t epg_genre_get_str ( const epg_genre_t *genre, int major_only,
int major_prefix, char *buf, size_t len,
const char *lang )
{
const char *s;
int maj, min;
size_t ret = 0;
if (!genre || !buf) return 0;
maj = (genre->code >> 4) & 0xf;
s = _genre_get_name(maj, 0, lang);
if (s[0] == '\0') return 0;
min = major_only ? 0 : (genre->code & 0xf);
if (!min || major_prefix ) {
tvh_strlcatf(buf, len, ret, "%s", s);
}
if (min) {
s = _genre_get_name(maj, min, lang);
if (s[0]) {
tvh_strlcatf(buf, len, ret, " : ");
tvh_strlcatf(buf, len, ret, "%s", s);
}
}
return ret;
}
int epg_genre_list_add ( epg_genre_list_t *list, epg_genre_t *genre )
{
epg_genre_t *g1, *g2;
if (!list || !genre || !genre->code) return 0;
g1 = LIST_FIRST(list);
if (!g1) {
g2 = calloc(1, sizeof(epg_genre_t));
g2->code = genre->code;
LIST_INSERT_HEAD(list, g2, link);
} else {
while (g1) {
/* Already exists */
if (g1->code == genre->code) return 0;
/* Update a major only entry */
if (g1->code == (genre->code & 0xF0)) {
g1->code = genre->code;
break;
}
/* Insert before */
if (g1->code > genre->code) {
g2 = calloc(1, sizeof(epg_genre_t));
g2->code = genre->code;
LIST_INSERT_BEFORE(g1, g2, link);
break;
}
/* Insert after (end) */
if (!(g2 = LIST_NEXT(g1, link))) {
g2 = calloc(1, sizeof(epg_genre_t));
g2->code = genre->code;
LIST_INSERT_AFTER(g1, g2, link);
break;
}
/* Next */
g1 = g2;
}
}
return 1;
}
int epg_genre_list_add_by_eit ( epg_genre_list_t *list, uint8_t eit )
{
epg_genre_t g;
g.code = eit;
return epg_genre_list_add(list, &g);
}
int epg_genre_list_add_by_str ( epg_genre_list_t *list, const char *str, const char *lang )
{
epg_genre_t g;
g.code = _epg_genre_find_by_name(str, lang);
return epg_genre_list_add(list, &g);
}
// Note: if partial=1 and genre is a major only category then all minor
// entries will also match
int epg_genre_list_contains
( epg_genre_list_t *list, epg_genre_t *genre, int partial )
{
uint8_t mask = 0xFF;
epg_genre_t *g;
if (!list || !genre) return 0;
if (partial && !(genre->code & 0x0F)) mask = 0xF0;
LIST_FOREACH(g, list, link) {
if ((g->code & mask) == genre->code) break;
}
return g ? 1 : 0;
}
void epg_genre_list_destroy ( epg_genre_list_t *list )
{
epg_genre_t *g;
while ((g = LIST_FIRST(list))) {
LIST_REMOVE(g, link);
free(g);
}
free(list);
}
htsmsg_t *epg_genres_list_all ( int major_only, int major_prefix, const char *lang )
{
int i, j;
htsmsg_t *e, *m;
const char *s;
m = htsmsg_create_list();
for (i = 0; i < 16; i++ ) {
for (j = 0; j < (major_only ? 1 : 16); j++) {
if (_epg_genre_names[i][j]) {
e = htsmsg_create_map();
htsmsg_add_u32(e, "key", (i << 4) | (major_only ? 0 : j));
s = _genre_get_name(i, j, lang);
htsmsg_add_str(e, "val", s);
// TODO: use major_prefix
htsmsg_add_msg(m, NULL, e);
}
}
}
return m;
}
/* **************************************************************************
* Querying
* *************************************************************************/
static inline int
_eq_comp_num ( epg_filter_num_t *f, int64_t val )
{
switch (f->comp) {
case EC_EQ: return val != f->val1;
case EC_LT: return val > f->val1;
case EC_GT: return val < f->val1;
case EC_RG: return val < f->val1 || val > f->val2;
default: return 0;
}
}
static inline int
_eq_comp_str ( epg_filter_str_t *f, const char *str )
{
if (!str) return 0;
switch (f->comp) {
case EC_EQ: return strcmp(str, f->str);
case EC_LT: return strcmp(str, f->str) > 0;
case EC_GT: return strcmp(str, f->str) < 0;
case EC_IN: return strstr(str, f->str) != NULL;
case EC_RE: return regex_match(&f->re, str) != 0;
default: return 0;
}
}
static void
_eq_add ( epg_query_t *eq, epg_broadcast_t *e )
{
const char *s, *lang = eq->lang;
int fulltext = eq->stitle && eq->fulltext;
/* Filtering */
if (e == NULL) return;
if (e->stop < gclk()) return;
if (_eq_comp_num(&eq->start, e->start)) return;
if (_eq_comp_num(&eq->stop, e->stop)) return;
if (eq->duration.comp != EC_NO) {
int64_t duration = (int64_t)e->stop - (int64_t)e->start;
if (_eq_comp_num(&eq->duration, duration)) return;
}
if (eq->stars.comp != EC_NO)
if (_eq_comp_num(&eq->stars, e->star_rating)) return;
if (eq->age.comp != EC_NO)
if (_eq_comp_num(&eq->age, e->age_rating)) return;
if (eq->channel_num.comp != EC_NO)
if (_eq_comp_num(&eq->channel_num, channel_get_number(e->channel))) return;
if (eq->channel_name.comp != EC_NO)
if (_eq_comp_str(&eq->channel_name, channel_get_name(e->channel, NULL))) return;
if (eq->cat1 && *eq->cat1) {
/* No category? Can't match our requested category */
if (!e->category)
return;
if (!string_list_contains_string(e->category, eq->cat1))
return;
}
if (eq->cat2 && *eq->cat2) {
/* No category? Can't match our requested category */
if (!e->category)
return;
if (!string_list_contains_string(e->category, eq->cat2))
return;
}
if (eq->cat3 && *eq->cat3) {
/* No category? Can't match our requested category */
if (!e->category)
return;
if (!string_list_contains_string(e->category, eq->cat3))
return;
}
if (eq->genre_count) {
epg_genre_t genre;
uint32_t i, r = 0;
for (i = 0; i < eq->genre_count; i++) {
genre.code = eq->genre[i];
if (genre.code == 0) continue;
if (epg_genre_list_contains(&e->genre, &genre, 1)) r++;
}
if (!r) return;
}
if (eq->new_only) {
if (!e->is_new)
return;
}
if (fulltext) {
if ((s = epg_broadcast_get_title(e, lang)) == NULL ||
regex_match(&eq->stitle_re, s)) {
if ((s = epg_broadcast_get_subtitle(e, lang)) == NULL ||
regex_match(&eq->stitle_re, s)) {
if ((s = epg_broadcast_get_summary(e, lang)) == NULL ||
regex_match(&eq->stitle_re, s)) {
if ((s = epg_broadcast_get_description(e, lang)) == NULL ||
regex_match(&eq->stitle_re, s)) {
if ((s = epg_broadcast_get_credits_cached(e, lang)) == NULL ||
regex_match(&eq->stitle_re, s)) {
if ((s = epg_broadcast_get_keyword_cached(e, lang)) == NULL ||
regex_match(&eq->stitle_re, s)) {
return;
}
}
}
}
}
}
}
if (eq->title.comp != EC_NO || (eq->stitle && !fulltext)) {
if ((s = epg_broadcast_get_title(e, lang)) == NULL) return;
if (eq->stitle && !fulltext && regex_match(&eq->stitle_re, s)) return;
if (eq->title.comp != EC_NO && _eq_comp_str(&eq->title, s)) return;
}
if (eq->subtitle.comp != EC_NO) {
if ((s = epg_broadcast_get_subtitle(e, lang)) == NULL) return;
if (_eq_comp_str(&eq->subtitle, s)) return;
}
if (eq->summary.comp != EC_NO) {
if ((s = epg_broadcast_get_summary(e, lang)) == NULL) return;
if (_eq_comp_str(&eq->summary, s)) return;
}
if (eq->description.comp != EC_NO) {
if ((s = epg_broadcast_get_description(e, lang)) == NULL) return;
if (_eq_comp_str(&eq->description, s)) return;
}
/* More space */
if (eq->entries == eq->allocated) {
eq->allocated = MAX(100, eq->allocated + 100);
eq->result = realloc(eq->result, eq->allocated * sizeof(epg_broadcast_t *));
}
/* Store */
eq->result[eq->entries++] = e;
}
static void
_eq_add_channel ( epg_query_t *eq, channel_t *ch )
{
epg_broadcast_t *ebc;
RB_FOREACH(ebc, &ch->ch_epg_schedule, sched_link)
_eq_add(eq, ebc);
}
static int
_eq_init_str( epg_filter_str_t *f )
{
if (f->comp != EC_RE) return 0;
return regex_compile(&f->re, f->str, TVHREGEX_CASELESS, LS_EPG);
}
static void
_eq_done_str( epg_filter_str_t *f )
{
if (f->comp == EC_RE)
regex_free(&f->re);
free(f->str);
f->str = NULL;
}
static int _epg_sort_start_ascending ( const void *a, const void *b, void *eq )
{
return (*(epg_broadcast_t**)a)->start - (*(epg_broadcast_t**)b)->start;
}
static int _epg_sort_start_descending ( const void *a, const void *b, void *eq )
{
return (*(epg_broadcast_t**)b)->start - (*(epg_broadcast_t**)a)->start;
}
static int _epg_sort_stop_ascending ( const void *a, const void *b, void *eq )
{
return (*(epg_broadcast_t**)a)->stop - (*(epg_broadcast_t**)b)->stop;
}
static int _epg_sort_stop_descending ( const void *a, const void *b, void *eq )
{
return (*(epg_broadcast_t**)b)->stop - (*(epg_broadcast_t**)a)->stop;
}
static inline int64_t _epg_sort_duration( const epg_broadcast_t *b )
{
return b->stop - b->start;
}
static int _epg_sort_duration_ascending ( const void *a, const void *b, void *eq )
{
return _epg_sort_duration(*(epg_broadcast_t**)a) - _epg_sort_duration(*(epg_broadcast_t**)b);
}
static int _epg_sort_duration_descending ( const void *a, const void *b, void *eq )
{
return _epg_sort_duration(*(epg_broadcast_t**)b) - _epg_sort_duration(*(epg_broadcast_t**)a);
}
static int _epg_sort_title_ascending ( const void *a, const void *b, void *eq )
{
const char *s1 = epg_broadcast_get_title(*(epg_broadcast_t**)a, ((epg_query_t *)eq)->lang);
const char *s2 = epg_broadcast_get_title(*(epg_broadcast_t**)b, ((epg_query_t *)eq)->lang);
if (s1 == NULL && s2 == NULL) return 0;
if (s1 == NULL && s2) return 1;
if (s1 && s2 == NULL) return -1;
int r = strcmp(s1, s2);
if (r == 0) {
s1 = epg_broadcast_get_subtitle(*(epg_broadcast_t**)a, ((epg_query_t *)eq)->lang);
s2 = epg_broadcast_get_subtitle(*(epg_broadcast_t**)b, ((epg_query_t *)eq)->lang);
if (s1 == NULL && s2 == NULL) return 0;
if (s1 == NULL && s2) return 1;
if (s1 && s2 == NULL) return -1;
r = strcmp(s1, s2);
}
return r;
}
static int _epg_sort_title_descending ( const void *a, const void *b, void *eq )
{
return _epg_sort_title_ascending(a, b, eq) * -1;
}
static int _epg_sort_subtitle_ascending ( const void *a, const void *b, void *eq )
{
const char *s1 = epg_broadcast_get_subtitle(*(epg_broadcast_t**)a, ((epg_query_t *)eq)->lang);
const char *s2 = epg_broadcast_get_subtitle(*(epg_broadcast_t**)b, ((epg_query_t *)eq)->lang);
if (s1 == NULL && s2 == NULL) return 0;
if (s1 == NULL && s2) return 1;
if (s1 && s2 == NULL) return -1;
return strcmp(s1, s2);
}
static int _epg_sort_subtitle_descending ( const void *a, const void *b, void *eq )
{
return _epg_sort_subtitle_ascending(a, b, eq) * -1;
}
static int _epg_sort_summary_ascending ( const void *a, const void *b, void *eq )
{
const char *s1 = epg_broadcast_get_summary(*(epg_broadcast_t**)a, ((epg_query_t *)eq)->lang);
const char *s2 = epg_broadcast_get_summary(*(epg_broadcast_t**)b, ((epg_query_t *)eq)->lang);
if (s1 == NULL && s2 == NULL) return 0;
if (s1 == NULL && s2) return 1;
if (s1 && s2 == NULL) return -1;
return strcmp(s1, s2);
}
static int _epg_sort_summary_descending ( const void *a, const void *b, void *eq )
{
return _epg_sort_summary_ascending(a, b, eq) * -1;
}
static int _epg_sort_description_ascending ( const void *a, const void *b, void *eq )
{
const char *s1 = epg_broadcast_get_description(*(epg_broadcast_t**)a, ((epg_query_t *)eq)->lang);
const char *s2 = epg_broadcast_get_description(*(epg_broadcast_t**)b, ((epg_query_t *)eq)->lang);
if (s1 == NULL && s2 == NULL) return 0;
if (s1 == NULL && s2) return 1;
if (s1 && s2 == NULL) return -1;
return strcmp(s1, s2);
}
static int _epg_sort_description_descending ( const void *a, const void *b, void *eq )
{
return _epg_sort_description_ascending(a, b, eq) * -1;
}
static int _epg_sort_extratext_ascending ( const void *a, const void *b, void *eq )
{
int r = _epg_sort_subtitle_ascending(a, b, eq);
if (r == 0) {
r = _epg_sort_summary_ascending(a, b, eq);
if (r == 0)
return _epg_sort_description_ascending(a, b, eq);
}
return r;
}
static int _epg_sort_extratext_descending ( const void *a, const void *b, void *eq )
{
return _epg_sort_extratext_ascending(a, b, eq) * -1;
}
static int _epg_sort_channel_ascending ( const void *a, const void *b, void *eq )
{
char *s1 = strdup(channel_get_name((*(epg_broadcast_t**)a)->channel, ""));
const char *s2 = channel_get_name((*(epg_broadcast_t**)b)->channel, "");
int r = strcmp(s1, s2);
free(s1);
return r;
}
static int _epg_sort_channel_descending ( const void *a, const void *b, void *eq )
{
return _epg_sort_description_ascending(a, b, eq) * -1;
}
static int _epg_sort_channel_num_ascending ( const void *a, const void *b, void *eq )
{
int64_t v1 = channel_get_number((*(epg_broadcast_t**)a)->channel);
int64_t v2 = channel_get_number((*(epg_broadcast_t**)b)->channel);
return v1 - v2;
}
static int _epg_sort_channel_num_descending ( const void *a, const void *b, void *eq )
{
const int64_t v1 = channel_get_number((*(epg_broadcast_t**)a)->channel);
const int64_t v2 = channel_get_number((*(epg_broadcast_t**)b)->channel);
return v2 - v1;
}
static int _epg_sort_stars_ascending ( const void *a, const void *b, void *eq )
{
return (*(epg_broadcast_t**)a)->star_rating - (*(epg_broadcast_t**)b)->star_rating;
}
static int _epg_sort_stars_descending ( const void *a, const void *b, void *eq )
{
return (*(epg_broadcast_t**)b)->star_rating - (*(epg_broadcast_t**)a)->star_rating;
}
static int _epg_sort_age_ascending ( const void *a, const void *b, void *eq )
{
return (*(epg_broadcast_t**)a)->age_rating - (*(epg_broadcast_t**)b)->age_rating;
}
static int _epg_sort_age_descending ( const void *a, const void *b, void *eq )
{
return (*(epg_broadcast_t**)b)->age_rating - (*(epg_broadcast_t**)a)->age_rating;
}
static uint64_t _epg_sort_genre_hash( epg_broadcast_t *b )
{
uint64_t h = 0, t;
epg_genre_t *g;
LIST_FOREACH(g, &b->genre, link) {
t = h >> 28;
h <<= 8;
h += (uint64_t)g->code + t;
}
return h;
}
static int _epg_sort_genre_ascending ( const void *a, const void *b, void *eq )
{
const uint64_t v1 = _epg_sort_genre_hash(*(epg_broadcast_t**)a);
const uint64_t v2 = _epg_sort_genre_hash(*(epg_broadcast_t**)b);
return v1 - v2;
}
static int _epg_sort_genre_descending ( const void *a, const void *b, void *eq )
{
return _epg_sort_genre_ascending(a, b, eq) * -1;
}
epg_broadcast_t **
epg_query ( epg_query_t *eq, access_t *perm )
{
channel_t *channel;
channel_tag_t *tag;
int (*fcn)(const void *, const void *, void *) = NULL;
/* Setup exp */
if (_eq_init_str(&eq->title)) goto fin;
if (_eq_init_str(&eq->subtitle)) goto fin;
if (_eq_init_str(&eq->summary)) goto fin;
if (_eq_init_str(&eq->description)) goto fin;
if (_eq_init_str(&eq->extratext)) goto fin;
if (_eq_init_str(&eq->channel_name)) goto fin;
if (eq->stitle)
if (regex_compile(&eq->stitle_re, eq->stitle, TVHREGEX_CASELESS, LS_EPG))
goto fin;
channel = channel_find_by_uuid(eq->channel) ?:
channel_find_by_name(eq->channel);
tag = channel_tag_find_by_uuid(eq->channel_tag) ?:
channel_tag_find_by_name(eq->channel_tag, 0);
/* Single channel */
if (channel && tag == NULL) {
if (channel_access(channel, perm, 0))
_eq_add_channel(eq, channel);
/* Tag based */
} else if (tag) {
idnode_list_mapping_t *ilm;
channel_t *ch2;
LIST_FOREACH(ilm, &tag->ct_ctms, ilm_in1_link) {
ch2 = (channel_t *)ilm->ilm_in2;
if(ch2 == channel || channel == NULL)
if (channel_access(ch2, perm, 0))
_eq_add_channel(eq, ch2);
}
/* All channels */
} else {
CHANNEL_FOREACH(channel)
if (channel_access(channel, perm, 0))
_eq_add_channel(eq, channel);
}
switch (eq->sort_dir) {
case ES_ASC:
switch (eq->sort_key) {
case ESK_START: fcn = _epg_sort_start_ascending; break;
case ESK_STOP: fcn = _epg_sort_stop_ascending; break;
case ESK_DURATION: fcn = _epg_sort_duration_ascending; break;
case ESK_TITLE: fcn = _epg_sort_title_ascending; break;
case ESK_SUBTITLE: fcn = _epg_sort_subtitle_ascending; break;
case ESK_SUMMARY: fcn = _epg_sort_summary_ascending; break;
case ESK_DESCRIPTION: fcn = _epg_sort_description_ascending; break;
case ESK_EXTRATEXT: fcn = _epg_sort_extratext_ascending; break;
case ESK_CHANNEL: fcn = _epg_sort_channel_ascending; break;
case ESK_CHANNEL_NUM: fcn = _epg_sort_channel_num_ascending; break;
case ESK_STARS: fcn = _epg_sort_stars_ascending; break;
case ESK_AGE: fcn = _epg_sort_age_ascending; break;
case ESK_GENRE: fcn = _epg_sort_genre_ascending; break;
}
break;
case ES_DSC:
switch (eq->sort_key) {
case ESK_START: fcn = _epg_sort_start_descending; break;
case ESK_STOP: fcn = _epg_sort_stop_descending; break;
case ESK_DURATION: fcn = _epg_sort_duration_descending; break;
case ESK_TITLE: fcn = _epg_sort_title_descending; break;
case ESK_SUBTITLE: fcn = _epg_sort_subtitle_descending; break;
case ESK_SUMMARY: fcn = _epg_sort_summary_descending; break;
case ESK_DESCRIPTION: fcn = _epg_sort_description_descending; break;
case ESK_EXTRATEXT: fcn = _epg_sort_extratext_descending; break;
case ESK_CHANNEL: fcn = _epg_sort_channel_descending; break;
case ESK_CHANNEL_NUM: fcn = _epg_sort_channel_num_descending; break;
case ESK_STARS: fcn = _epg_sort_stars_descending; break;
case ESK_AGE: fcn = _epg_sort_age_descending; break;
case ESK_GENRE: fcn = _epg_sort_genre_descending; break;
}
break;
}
tvh_qsort_r(eq->result, eq->entries, sizeof(epg_broadcast_t *), fcn, eq);
fin:
_eq_done_str(&eq->title);
_eq_done_str(&eq->subtitle);
_eq_done_str(&eq->summary);
_eq_done_str(&eq->description);
_eq_done_str(&eq->extratext);
_eq_done_str(&eq->channel_name);
if (eq->stitle)
regex_free(&eq->stitle_re);
free(eq->lang); eq->lang = NULL;
free(eq->channel); eq->channel = NULL;
free(eq->channel_tag); eq->channel_tag = NULL;
free(eq->stitle); eq->stitle = NULL;
free(eq->cat1); eq->cat1 = NULL;
free(eq->cat2); eq->cat2 = NULL;
free(eq->cat3); eq->cat3 = NULL;
if (eq->genre != eq->genre_static)
free(eq->genre);
eq->genre = NULL;
return eq->result;
}
void epg_query_free(epg_query_t *eq)
{
free(eq->result); eq->result = NULL;
}
/* **************************************************************************
* Miscellaneous
* *************************************************************************/
htsmsg_t *epg_config_serialize( void )
{
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_u32(m, "last_id", _epg_object_idx);
return m;
}
int epg_config_deserialize( htsmsg_t *m )
{
if (htsmsg_get_u32(m, "last_id", &_epg_object_idx))
return 0;
return 1; /* ok */
}
void epg_skel_done(void)
{
epg_broadcast_t **broad;
broad = _epg_broadcast_skel();
free(*broad); *broad = NULL;
assert(RB_FIRST(&epg_serieslinks) == NULL);
assert(RB_FIRST(&epg_episodelinks) == NULL);
}
You can’t perform that action at this time.