tvheadend/src/epggrab/otamux.c
2014-07-07 16:41:52 +02:00

516 lines
14 KiB
C

/*
* Electronic Program Guide - EPG grabber OTA 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 "tvheadend.h"
#include "queue.h"
#include "settings.h"
#include "epg.h"
#include "epggrab.h"
#include "epggrab/private.h"
#include "input.h"
#include "subscriptions.h"
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
RB_HEAD(,epggrab_ota_mux) epggrab_ota_all;
LIST_HEAD(,epggrab_ota_mux) epggrab_ota_pending;
LIST_HEAD(,epggrab_ota_mux) epggrab_ota_active;
gtimer_t epggrab_ota_pending_timer;
gtimer_t epggrab_ota_active_timer;
SKEL_DECLARE(epggrab_ota_mux_skel, epggrab_ota_mux_t);
static void epggrab_ota_active_timer_cb ( void *p );
static void epggrab_ota_pending_timer_cb ( void *p );
static void epggrab_ota_save ( epggrab_ota_mux_t *ota );
/* **************************************************************************
* Utilities
* *************************************************************************/
static int
om_time_cmp ( epggrab_ota_mux_t *a, epggrab_ota_mux_t *b )
{
return (a->om_when - b->om_when);
}
static int
om_id_cmp ( epggrab_ota_mux_t *a, epggrab_ota_mux_t *b )
{
return strcmp(a->om_mux_uuid, b->om_mux_uuid);
}
#define EPGGRAB_OTA_MIN_PERIOD 300
#define EPGGRAB_OTA_MIN_TIMEOUT 30
static int
epggrab_ota_period ( epggrab_ota_mux_t *ota, int divider )
{
int period = 0;
epggrab_ota_map_t *map;
if (ota->om_interval)
period = ota->om_interval;
else {
LIST_FOREACH(map, &ota->om_modules, om_link)
if (!period || map->om_interval < period)
period = map->om_interval;
}
period /= divider;
if (period < EPGGRAB_OTA_MIN_PERIOD)
period = EPGGRAB_OTA_MIN_PERIOD;
return period;
}
static int
epggrab_ota_timeout ( epggrab_ota_mux_t *ota )
{
int timeout = 0;
epggrab_ota_map_t *map;
if (ota->om_timeout)
timeout = ota->om_timeout;
else {
LIST_FOREACH(map, &ota->om_modules, om_link)
if (map->om_timeout > timeout)
timeout = map->om_timeout;
}
if (timeout < EPGGRAB_OTA_MIN_TIMEOUT)
timeout = EPGGRAB_OTA_MIN_TIMEOUT;
return timeout;
}
static void
epggrab_ota_done ( epggrab_ota_mux_t *ota, int timeout )
{
mpegts_mux_t *mm;
LIST_REMOVE(ota, om_q_link);
ota->om_active = 0;
ota->om_when = dispatch_clock + epggrab_ota_period(ota, 1);
LIST_INSERT_SORTED(&epggrab_ota_pending, ota, om_q_link, om_time_cmp);
/* Remove subscriber */
if ((mm = mpegts_mux_find(ota->om_mux_uuid)))
mpegts_mux_unsubscribe_by_name(mm, "epggrab");
/* Re-arm */
if (LIST_FIRST(&epggrab_ota_pending) == ota)
epggrab_ota_pending_timer_cb(NULL);
/* Remove from active */
if (!timeout)
epggrab_ota_active_timer_cb(NULL);
}
static void
epggrab_ota_start ( epggrab_ota_mux_t *om, int grace )
{
epggrab_ota_map_t *map;
om->om_when = dispatch_clock + epggrab_ota_timeout(om) + grace;
om->om_active = 1;
LIST_INSERT_SORTED(&epggrab_ota_active, om, om_q_link, om_time_cmp);
if (LIST_FIRST(&epggrab_ota_active) == om)
epggrab_ota_active_timer_cb(NULL);
LIST_FOREACH(map, &om->om_modules, om_link) {
map->om_complete = 0;
tvhdebug(map->om_module->id, "grab started");
}
}
/* **************************************************************************
* MPEG-TS listener
* *************************************************************************/
static void
epggrab_mux_start0 ( mpegts_mux_t *mm, int force )
{
epggrab_module_t *m;
epggrab_module_ota_t *om;
epggrab_ota_mux_t *ota;
/* Already started */
if (!force) {
LIST_FOREACH(ota, &epggrab_ota_active, om_q_link)
if (!strcmp(ota->om_mux_uuid, idnode_uuid_as_str(&mm->mm_id)))
return;
}
/* Check if already active */
LIST_FOREACH(m, &epggrab_modules, link) {
if (m->type == EPGGRAB_OTA) {
om = (epggrab_module_ota_t*)m;
if (om->start) om->start(om, mm);
}
}
}
static void
epggrab_mux_start ( mpegts_mux_t *mm, void *p )
{
epggrab_mux_start0(mm, 0);
}
static void
epggrab_mux_stop ( mpegts_mux_t *mm, void *p )
{
epggrab_ota_mux_t *ota;
while ((ota = LIST_FIRST(&epggrab_ota_active)))
epggrab_ota_done(ota, 0);
}
/* **************************************************************************
* Module methods
* *************************************************************************/
epggrab_ota_mux_t *
epggrab_ota_register
( epggrab_module_ota_t *mod, mpegts_mux_t *mm )
{
int save = 0;
int interval = 3600;
int timeout = 240;
epggrab_ota_map_t *map;
epggrab_ota_mux_t *ota;
/* Find mux entry */
const char *uuid = idnode_uuid_as_str(&mm->mm_id);
SKEL_ALLOC(epggrab_ota_mux_skel);
epggrab_ota_mux_skel->om_mux_uuid = (char*)uuid;
ota = RB_INSERT_SORTED(&epggrab_ota_all, epggrab_ota_mux_skel, om_global_link, om_id_cmp);
if (!ota) {
char buf[256];
mm->mm_display_name(mm, buf, sizeof(buf));
tvhinfo(mod->id, "registering mux %s", buf);
ota = epggrab_ota_mux_skel;
SKEL_USED(epggrab_ota_mux_skel);
RB_INIT(&ota->om_svcs);
ota->om_mux_uuid = strdup(uuid);
ota->om_when = dispatch_clock + epggrab_ota_timeout(ota);
ota->om_active = 1;
LIST_INSERT_SORTED(&epggrab_ota_active, ota, om_q_link, om_time_cmp);
if (LIST_FIRST(&epggrab_ota_active) == ota)
epggrab_ota_active_timer_cb(NULL);
save = 1;
}
/* Find module entry */
LIST_FOREACH(map, &ota->om_modules, om_link)
if (map->om_module == mod)
break;
if (!map) {
map = calloc(1, sizeof(epggrab_ota_map_t));
map->om_module = mod;
map->om_timeout = timeout;
map->om_interval = interval;
LIST_INSERT_HEAD(&ota->om_modules, map, om_link);
save = 1;
}
/* Save config */
if (save) epggrab_ota_save(ota);
return ota;
}
void
epggrab_ota_complete
( epggrab_module_ota_t *mod, epggrab_ota_mux_t *ota )
{
int done = 1;
epggrab_ota_map_t *map;
lock_assert(&global_lock);
tvhdebug(mod->id, "grab complete");
/* Mark */
if (!ota->om_complete) {
ota->om_complete = 1;
epggrab_ota_save(ota);
}
/* Test for completion */
LIST_FOREACH(map, &ota->om_modules, om_link) {
if (map->om_module == mod) {
map->om_complete = 1;
} else if (!map->om_complete) {
done = 0;
}
}
if (!done) return;
/* Done */
epggrab_ota_done(ota, 0);
}
/* **************************************************************************
* Timer callback
* *************************************************************************/
static void
epggrab_ota_active_timer_cb ( void *p )
{
epggrab_ota_mux_t *om = LIST_FIRST(&epggrab_ota_active);
gtimer_disarm(&epggrab_ota_active_timer);
lock_assert(&global_lock);
if (!om)
return;
/* Double check */
if (om->om_when > dispatch_clock)
goto done;
/* Re-queue */
epggrab_ota_done(om, 1);
done:
om = LIST_FIRST(&epggrab_ota_active);
if (om)
gtimer_arm_abs(&epggrab_ota_active_timer, epggrab_ota_active_timer_cb,
NULL, om->om_when);
}
static void
epggrab_ota_pending_timer_cb ( void *p )
{
epggrab_ota_map_t *map;
epggrab_ota_mux_t *om = LIST_FIRST(&epggrab_ota_pending);
mpegts_mux_t *mm;
int extra = 0;
gtimer_disarm(&epggrab_ota_pending_timer);
lock_assert(&global_lock);
if (!om)
return;
/* Double check */
if (om->om_when > dispatch_clock)
goto done;
next_one:
LIST_REMOVE(om, om_q_link);
/* Find the mux */
extern const idclass_t mpegts_mux_class;
mm = mpegts_mux_find(om->om_mux_uuid);
if (!mm) {
RB_REMOVE(&epggrab_ota_all, om, om_global_link);
while ((map = LIST_FIRST(&om->om_modules))) {
LIST_REMOVE(map, om_link);
free(map);
}
free(om->om_mux_uuid);
free(om);
goto done;
}
/* Check we have modules attached and enabled */
LIST_FOREACH(map, &om->om_modules, om_link) {
if (map->om_module->enabled &&
map->om_module->tune && map->om_module->tune(map->om_module, om))
break;
}
if (!map) {
char name[256];
mm->mm_display_name(mm, name, sizeof(name));
tvhdebug("epggrab", "no modules attached to %s, check again later", name);
om->om_when = dispatch_clock + epggrab_ota_period(om, 4);
LIST_INSERT_SORTED(&epggrab_ota_pending, om, om_q_link, om_time_cmp);
goto done;
}
/* Subscribe to the mux */
if (mm->mm_is_epg(mm) <= 0 ||
mpegts_mux_subscribe(mm, "epggrab", SUBSCRIPTION_PRIO_EPG)) {
om->om_active = 0;
om->om_when = dispatch_clock + epggrab_ota_period(om, 4) + extra;
LIST_INSERT_SORTED(&epggrab_ota_pending, om, om_q_link, om_time_cmp);
} else {
mpegts_mux_instance_t *mmi = mm->mm_active;
epggrab_ota_start(om, mpegts_input_grace(mmi->mmi_input, mm));
}
done:
om = LIST_FIRST(&epggrab_ota_pending);
if (om) {
if (om->om_when <= dispatch_clock) {
extra += 60; /* differentiate the mux busy requests */
goto next_one;
}
gtimer_arm_abs(&epggrab_ota_pending_timer, epggrab_ota_pending_timer_cb,
NULL, om->om_when);
}
}
/* **************************************************************************
* Config
* *************************************************************************/
static void
epggrab_ota_save ( epggrab_ota_mux_t *ota )
{
epggrab_ota_map_t *map;
htsmsg_t *e, *l, *c = htsmsg_create_map();
htsmsg_add_u32(c, "complete", ota->om_complete);
htsmsg_add_u32(c, "timeout", ota->om_timeout);
htsmsg_add_u32(c, "interval", ota->om_interval);
l = htsmsg_create_list();
LIST_FOREACH(map, &ota->om_modules, om_link) {
e = htsmsg_create_map();
htsmsg_add_str(e, "id", map->om_module->id);
htsmsg_add_u32(e, "timeout", map->om_timeout);
htsmsg_add_u32(e, "interval", map->om_interval);
htsmsg_add_msg(l, NULL, e);
}
htsmsg_add_msg(c, "modules", l);
hts_settings_save(c, "epggrab/otamux/%s", ota->om_mux_uuid);
htsmsg_destroy(c);
}
static void
epggrab_ota_load_one
( const char *uuid, htsmsg_t *c )
{
htsmsg_t *l, *e;
htsmsg_field_t *f;
mpegts_mux_t *mm;
epggrab_module_ota_t *mod;
epggrab_ota_mux_t *ota;
epggrab_ota_map_t *map;
const char *id;
mm = mpegts_mux_find(uuid);
if (!mm) {
hts_settings_remove("epggrab/otamux/%s", uuid);
return;
}
ota = calloc(1, sizeof(epggrab_ota_mux_t));
RB_INIT(&ota->om_svcs);
ota->om_mux_uuid = strdup(uuid);
ota->om_timeout = htsmsg_get_u32_or_default(c, "timeout", 0);
ota->om_interval = htsmsg_get_u32_or_default(c, "interval", 0);
if (RB_INSERT_SORTED(&epggrab_ota_all, ota, om_global_link, om_id_cmp)) {
free(ota->om_mux_uuid);
free(ota);
return;
}
LIST_INSERT_SORTED(&epggrab_ota_pending, ota, om_q_link, om_time_cmp);
if (!(l = htsmsg_get_list(c, "modules"))) return;
HTSMSG_FOREACH(f, l) {
if (!(e = htsmsg_field_get_map(f))) continue;
if (!(id = htsmsg_get_str(e, "id"))) continue;
if (!(mod = (epggrab_module_ota_t*)epggrab_module_find_by_id(id)))
continue;
map = calloc(1, sizeof(epggrab_ota_map_t));
map->om_module = mod;
map->om_timeout = htsmsg_get_u32_or_default(e, "timeout", 0);
map->om_interval = htsmsg_get_u32_or_default(e, "interval", 0);
LIST_INSERT_HEAD(&ota->om_modules, map, om_link);
}
}
void
epggrab_ota_init ( void )
{
htsmsg_t *c, *m;
htsmsg_field_t *f;
char path[1024];
struct stat st;
/* Add listener */
static mpegts_listener_t ml = {
.ml_mux_start = epggrab_mux_start,
.ml_mux_stop = epggrab_mux_stop,
};
mpegts_add_listener(&ml);
/* Delete old config */
hts_settings_buildpath(path, sizeof(path), "epggrab/otamux");
if (!lstat(path, &st))
if (!S_ISDIR(st.st_mode))
hts_settings_remove("epggrab/otamux");
/* Load config */
if ((c = hts_settings_load_r(1, "epggrab/otamux"))) {
HTSMSG_FOREACH(f, c) {
if (!(m = htsmsg_field_get_map(f))) continue;
epggrab_ota_load_one(f->hmf_name, m);
}
htsmsg_destroy(c);
}
/* Init timer (immediate call after full init) */
if (LIST_FIRST(&epggrab_ota_pending))
gtimer_arm_abs(&epggrab_ota_pending_timer, epggrab_ota_pending_timer_cb,
NULL, 0);
}
static void
epggrab_ota_free ( epggrab_ota_mux_t *ota )
{
epggrab_ota_map_t *map;
epggrab_ota_svc_link_t *svcl;
LIST_REMOVE(ota, om_q_link);
while ((map = LIST_FIRST(&ota->om_modules)) != NULL) {
LIST_REMOVE(map, om_link);
free(map);
}
while ((svcl = RB_FIRST(&ota->om_svcs)) != NULL) {
RB_REMOVE(&ota->om_svcs, svcl, link);
free(svcl->uuid);
free(svcl);
}
free(ota->om_mux_uuid);
free(ota);
}
void
epggrab_ota_shutdown ( void )
{
epggrab_ota_mux_t *ota;
pthread_mutex_lock(&global_lock);
while ((ota = LIST_FIRST(&epggrab_ota_active)) != NULL)
epggrab_ota_free(ota);
while ((ota = LIST_FIRST(&epggrab_ota_pending)) != NULL)
epggrab_ota_free(ota);
pthread_mutex_unlock(&global_lock);
SKEL_FREE(epggrab_ota_mux_skel);
}
/******************************************************************************
* Editor Configuration
*
* vim:sts=2:ts=2:sw=2:et
*****************************************************************************/