tvheadend/src/service_mapper.c

462 lines
11 KiB
C

/*
* Service Mapper functions
* Copyright (C) 2007 Andreas Öman
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "tvheadend.h"
#include "channels.h"
#include "subscriptions.h"
#include "service_mapper.h"
#include "streaming.h"
#include "service.h"
#include "profile.h"
#include "bouquet.h"
#include "api.h"
static service_mapper_status_t service_mapper_stat;
static pthread_cond_t service_mapper_cond;
static struct service_queue service_mapper_queue;
static service_mapper_conf_t service_mapper_conf;
static void *service_mapper_thread ( void *p );
/**
* Initialise
*/
pthread_t service_mapper_tid;
void
service_mapper_init ( void )
{
TAILQ_INIT(&service_mapper_queue);
pthread_cond_init(&service_mapper_cond, NULL);
tvhthread_create(&service_mapper_tid, NULL, service_mapper_thread, NULL);
}
void
service_mapper_done ( void )
{
pthread_cond_signal(&service_mapper_cond);
pthread_join(service_mapper_tid, NULL);
}
/*
* Get status
*/
service_mapper_status_t
service_mapper_status ( void )
{
return service_mapper_stat;
}
/*
* Start a new mapping
*/
void
service_mapper_start ( const service_mapper_conf_t *conf, htsmsg_t *uuids )
{
int e, tr, qd = 0;
service_t *s;
/* Reset stat counters */
if (TAILQ_EMPTY(&service_mapper_queue))
service_mapper_reset_stats();
/* Store config */
service_mapper_conf = *conf;
/* Check each service */
TAILQ_FOREACH(s, &service_all, s_all_link) {
if (uuids) {
htsmsg_field_t *f;
const char *str;
const char *uuid = idnode_uuid_as_str(&s->s_id);
HTSMSG_FOREACH(f, uuids) {
if (!(str = htsmsg_field_get_str(f))) continue;
if (!strcmp(str, uuid)) break;
}
if (!f) continue;
}
tvhtrace("service_mapper", "check service %s (%s)",
s->s_nicename, idnode_uuid_as_str(&s->s_id));
/* Already mapped (or in progress) */
if (s->s_sm_onqueue) continue;
if (LIST_FIRST(&s->s_channels)) continue;
tvhtrace("service_mapper", " not mapped");
service_mapper_stat.total++;
service_mapper_stat.ignore++;
/* Disabled */
if (!s->s_is_enabled(s, 0)) continue;
tvhtrace("service_mapper", " enabled");
/* Get service info */
pthread_mutex_lock(&s->s_stream_mutex);
e = service_is_encrypted(s);
tr = service_is_tv(s) || service_is_radio(s);
pthread_mutex_unlock(&s->s_stream_mutex);
/* Skip non-TV / Radio */
if (!tr) continue;
tvhtrace("service_mapper", " radio or tv");
/* Skip encrypted */
if (!conf->encrypted && e) continue;
service_mapper_stat.ignore--;
/* Queue */
if (conf->check_availability) {
tvhtrace("service_mapper", " queue for checking");
qd = 1;
TAILQ_INSERT_TAIL(&service_mapper_queue, s, s_sm_link);
s->s_sm_onqueue = 1;
/* Process */
} else {
tvhtrace("service_mapper", " process");
service_mapper_process(s, NULL);
}
}
/* Notify */
api_service_mapper_notify();
/* Signal */
if (qd) pthread_cond_signal(&service_mapper_cond);
}
/*
* Stop everything
*/
void
service_mapper_stop ( void )
{
service_t *s;
while ((s = TAILQ_FIRST(&service_mapper_queue))) {
service_mapper_stat.total--;
service_mapper_remove(s);
}
/* Notify */
api_service_mapper_notify();
}
/*
* Remove service
*/
void
service_mapper_remove ( service_t *s )
{
if (s->s_sm_onqueue) {
TAILQ_REMOVE(&service_mapper_queue, s, s_sm_link);
s->s_sm_onqueue = 0;
}
/* Notify */
api_service_mapper_notify();
}
static void
service_mapper_notify ( channel_service_mapping_t *csm, void *origin )
{
if (origin == NULL)
return;
if (origin == csm->csm_svc) {
idnode_notify_simple(&csm->csm_chn->ch_id);
channel_save(csm->csm_chn);
}
if (origin == csm->csm_chn)
idnode_notify_simple(&csm->csm_svc->s_id);
}
/*
* Link service and channel
*/
int
service_mapper_link ( service_t *s, channel_t *c, void *origin )
{
channel_service_mapping_t *csm;
/* Already linked */
LIST_FOREACH(csm, &s->s_channels, csm_svc_link)
if (csm->csm_chn == c) {
csm->csm_mark = 0;
return 0;
}
LIST_FOREACH(csm, &c->ch_services, csm_chn_link)
if (csm->csm_svc == s) {
csm->csm_mark = 0;
return 0;
}
/* Link */
csm = calloc(1, sizeof(channel_service_mapping_t));
csm->csm_chn = c;
csm->csm_svc = s;
LIST_INSERT_HEAD(&s->s_channels, csm, csm_svc_link);
LIST_INSERT_HEAD(&c->ch_services, csm, csm_chn_link);
service_mapped( s );
service_mapper_notify( csm, origin );
return 1;
}
static void
service_mapper_unlink0 ( channel_service_mapping_t *csm, void *origin )
{
LIST_REMOVE(csm, csm_chn_link);
LIST_REMOVE(csm, csm_svc_link);
service_mapper_notify( csm, origin );
free(csm);
}
void
service_mapper_unlink ( service_t *s, channel_t *c, void *origin )
{
channel_service_mapping_t *csm;
/* Unlink */
LIST_FOREACH(csm, &s->s_channels, csm_svc_link) {
if (csm->csm_chn == c) {
service_mapper_unlink0(csm, origin);
break;
}
}
}
int
service_mapper_clean ( service_t *s, channel_t *c, void *origin )
{
int save = 0;
channel_service_mapping_t *csm, *n;
csm = s ? LIST_FIRST(&s->s_channels) : LIST_FIRST(&c->ch_services);
for (; csm != NULL; csm = n ) {
n = s ? LIST_NEXT(csm, csm_svc_link) : LIST_NEXT(csm, csm_chn_link);
if (csm->csm_mark) {
service_mapper_unlink0(csm, origin);
save = 1;
}
}
return save;
}
/*
* Process a service
*/
channel_t *
service_mapper_process ( service_t *s, bouquet_t *bq )
{
channel_t *chn = NULL;
const char *name;
/* Ignore */
if (s->s_status == SERVICE_ZOMBIE) {
if (!bq)
service_mapper_stat.ignore++;
goto exit;
}
/* Safety check (in-case something has been mapped manually in the interim) */
if (!bq && LIST_FIRST(&s->s_channels)) {
service_mapper_stat.ignore++;
goto exit;
}
/* Find existing channel */
name = service_get_channel_name(s);
if (!bq && service_mapper_conf.merge_same_name && name && *name)
chn = channel_find_by_name(name);
if (!chn) {
chn = channel_create(NULL, NULL, NULL);
chn->ch_bouquet = bq;
}
/* Map */
if (chn) {
const char *prov;
service_mapper_link(s, chn, chn);
/* Type tags */
if (service_is_hdtv(s)) {
channel_tag_map(chn, channel_tag_find_by_name("TV channels", 1));
channel_tag_map(chn, channel_tag_find_by_name("HDTV", 1));
} else if (service_is_sdtv(s)) {
channel_tag_map(chn, channel_tag_find_by_name("TV channels", 1));
channel_tag_map(chn, channel_tag_find_by_name("SDTV", 1));
} else if (service_is_radio(s)) {
channel_tag_map(chn, channel_tag_find_by_name("Radio", 1));
}
/* Provider */
if (service_mapper_conf.provider_tags)
if ((prov = s->s_provider_name(s)))
channel_tag_map(chn, channel_tag_find_by_name(prov, 1));
/* save */
idnode_notify_simple(&chn->ch_id);
channel_save(chn);
}
if (!bq) {
service_mapper_stat.ok++;
tvhinfo("service_mapper", "%s: success", s->s_nicename);
} else {
tvhinfo("bouquet", "%s: mapped service from %s", s->s_nicename, bq->bq_name ?: "<unknown>");
}
/* Remove */
exit:
service_mapper_remove(s);
return chn;
}
/**
*
*/
static void *
service_mapper_thread ( void *aux )
{
service_t *s;
profile_chain_t prch;
th_subscription_t *sub;
int run, working = 0;
streaming_queue_t *sq;
streaming_message_t *sm;
const char *err = NULL;
profile_chain_init(&prch, NULL, NULL);
prch.prch_st = &prch.prch_sq.sq_st;
sq = &prch.prch_sq;
pthread_mutex_lock(&global_lock);
while (tvheadend_running) {
/* Wait for work */
while (!(s = TAILQ_FIRST(&service_mapper_queue))) {
if (working) {
working = 0;
tvhinfo("service_mapper", "idle");
}
pthread_cond_wait(&service_mapper_cond, &global_lock);
if (!tvheadend_running)
break;
}
if (!tvheadend_running)
break;
service_mapper_remove(s);
if (!working) {
working = 1;
tvhinfo("service_mapper", "starting");
}
/* Subscribe */
tvhinfo("service_mapper", "checking %s", s->s_nicename);
prch.prch_id = s;
sub = subscription_create_from_service(&prch, SUBSCRIPTION_PRIO_MAPPER,
"service_mapper",
0, NULL, NULL, "service_mapper");
/* Failed */
if (!sub) {
tvhinfo("service_mapper", "%s: could not subscribe", s->s_nicename);
continue;
}
tvhinfo("service_mapper", "waiting for input");
service_ref(s);
service_mapper_stat.active = s;
api_service_mapper_notify();
pthread_mutex_unlock(&global_lock);
/* Wait */
run = 1;
pthread_mutex_lock(&sq->sq_mutex);
while(tvheadend_running && run) {
/* Wait for message */
while((sm = TAILQ_FIRST(&sq->sq_queue)) == NULL) {
pthread_cond_wait(&sq->sq_cond, &sq->sq_mutex);
if (!tvheadend_running)
break;
}
if (!tvheadend_running)
break;
TAILQ_REMOVE(&sq->sq_queue, sm, sm_link);
pthread_mutex_unlock(&sq->sq_mutex);
if(sm->sm_type == SMT_PACKET) {
run = 0;
err = NULL;
} else if (sm->sm_type == SMT_SERVICE_STATUS) {
int status = sm->sm_code;
if(status & TSS_ERRORS) {
run = 0;
err = service_tss2text(status);
}
} else if (sm->sm_type == SMT_NOSTART) {
run = 0;
err = streaming_code2txt(sm->sm_code);
}
streaming_msg_free(sm);
pthread_mutex_lock(&sq->sq_mutex);
}
if (!tvheadend_running)
break;
streaming_queue_clear(&sq->sq_queue);
pthread_mutex_unlock(&sq->sq_mutex);
pthread_mutex_lock(&global_lock);
subscription_unsubscribe(sub);
if(err) {
tvhinfo("service_mapper", "%s: failed [err %s]", s->s_nicename, err);
service_mapper_stat.fail++;
} else
service_mapper_process(s, NULL);
service_unref(s);
service_mapper_stat.active = NULL;
api_service_mapper_notify();
}
pthread_mutex_unlock(&global_lock);
profile_chain_close(&prch);
return NULL;
}
void
service_mapper_reset_stats (void)
{
service_mapper_stat.total = 0;
service_mapper_stat.ok = 0;
service_mapper_stat.ignore = 0;
service_mapper_stat.fail = 0;
service_mapper_stat.active = NULL;
}