bouquets: add auto-map, auto-remove features

This commit is contained in:
Jaroslav Kysela 2014-10-31 23:25:56 +01:00
parent 7134315e0f
commit 42901e6140
12 changed files with 265 additions and 71 deletions

View file

@ -25,6 +25,28 @@
#include "access.h"
#include "api.h"
static int
api_bouquet_list
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
bouquet_t *bq;
htsmsg_t *l, *e;
l = htsmsg_create_list();
pthread_mutex_lock(&global_lock);
RB_FOREACH(bq, &bouquets, bq_link) {
e = htsmsg_create_map();
htsmsg_add_str(e, "key", idnode_uuid_as_str(&bq->bq_id));
htsmsg_add_str(e, "val", bq->bq_name ?: "");
htsmsg_add_msg(l, NULL, e);
}
pthread_mutex_unlock(&global_lock);
*resp = htsmsg_create_map();
htsmsg_add_msg(*resp, "entries", l);
return 0;
}
static void
api_bouquet_grid
( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf )
@ -57,6 +79,7 @@ api_bouquet_create
void api_bouquet_init ( void )
{
static api_hook_t ah[] = {
{ "bouquet/list", ACCESS_ADMIN, api_bouquet_list, NULL },
{ "bouquet/class", ACCESS_ADMIN, api_idnode_class, (void*)&bouquet_class },
{ "bouquet/grid", ACCESS_ADMIN, api_idnode_grid, api_bouquet_grid },
{ "bouquet/create", ACCESS_ADMIN, api_bouquet_create, NULL },

View file

@ -21,6 +21,8 @@
#include "access.h"
#include "bouquet.h"
#include "service.h"
#include "channels.h"
#include "service_mapper.h"
bouquet_tree_t bouquets;
@ -47,6 +49,7 @@ bouquet_create(const char *uuid, htsmsg_t *conf,
bq = calloc(1, sizeof(bouquet_t));
bq->bq_services = idnode_set_create();
bq->bq_active_services = idnode_set_create();
if (idnode_insert(&bq->bq_id, uuid, &bouquet_class, 0)) {
if (uuid)
@ -93,6 +96,7 @@ bouquet_destroy(bouquet_t *bq)
RB_REMOVE(&bouquets, bq, bq_link);
idnode_unlink(&bq->bq_id);
idnode_set_free(bq->bq_active_services);
idnode_set_free(bq->bq_services);
free(bq->bq_name);
free(bq->bq_src);
@ -136,6 +140,21 @@ bouquet_find_by_source(const char *name, const char *src, int create)
return NULL;
}
/*
*
*/
static void
bouquet_map_channel(bouquet_t *bq, service_t *t)
{
channel_service_mapping_t *csm;
LIST_FOREACH(csm, &t->s_channels, csm_svc_link)
if (csm->csm_chn->ch_bouquet == bq)
break;
if (!csm)
service_mapper_process(t, bq);
}
/*
*
*/
@ -145,8 +164,88 @@ bouquet_add_service(bouquet_t *bq, service_t *s)
lock_assert(&global_lock);
if (!idnode_set_exists(bq->bq_services, &s->s_id)) {
tvhtrace("bouquet", "add service %s to %s", s->s_nicename, bq->bq_name ?: "<unknown>");
idnode_set_add(bq->bq_services, &s->s_id, NULL);
bq->bq_saveflag = 1;
if (bq->bq_enabled && bq->bq_maptoch)
bouquet_map_channel(bq, s);
}
if (!bq->bq_in_load &&
!idnode_set_exists(bq->bq_active_services, &s->s_id))
idnode_set_add(bq->bq_active_services, &s->s_id, NULL);
}
/*
*
*/
static void
bouquet_unmap_channel(bouquet_t *bq, service_t *t)
{
channel_service_mapping_t *csm, *csm_next;
csm = LIST_FIRST(&t->s_channels);
while (csm) {
csm_next = LIST_NEXT(csm, csm_svc_link);
if (csm->csm_chn->ch_bouquet == bq) {
tvhinfo("bouquet", "%s / %s: unmapped from %s",
channel_get_name(csm->csm_chn), t->s_nicename,
bq->bq_name ?: "<unknown>");
channel_delete(csm->csm_chn, 1);
}
csm = csm_next;
}
}
/*
*
*/
static void
bouquet_remove_service(bouquet_t *bq, service_t *s)
{
tvhtrace("bouquet", "remove service %s from %s", s->s_nicename, bq->bq_name ?: "<unknown>");
idnode_set_remove(bq->bq_services, &s->s_id);
}
/*
*
*/
void
bouquet_completed(bouquet_t *bq)
{
idnode_set_t *remove;
size_t z;
tvhtrace("bouquet", "completed: active=%zi old=%zi",
bq->bq_active_services->is_count, bq->bq_services->is_count);
remove = idnode_set_create();
for (z = 0; z < bq->bq_services->is_count; z++)
if (!idnode_set_exists(bq->bq_active_services, bq->bq_services->is_array[z]))
idnode_set_add(remove, bq->bq_services->is_array[z], NULL);
for (z = 0; z < remove->is_count; z++)
bouquet_remove_service(bq, (service_t *)remove->is_array[z]);
idnode_set_free(remove);
idnode_set_free(bq->bq_active_services);
bq->bq_active_services = idnode_set_create();
}
/*
*
*/
void
bouquet_map_to_channels(bouquet_t *bq)
{
service_t *t;
size_t z;
for (z = 0; z < bq->bq_services->is_count; z++) {
t = (service_t *)bq->bq_services->is_array[z];
if (bq->bq_enabled && bq->bq_maptoch) {
bouquet_map_channel(bq, t);
} else {
bouquet_unmap_channel(bq, t);
}
}
}
@ -202,20 +301,27 @@ bouquet_class_get_title (idnode_t *self)
return bq->bq_name ?: "";
}
/* exported for others */
htsmsg_t *
bouquet_class_get_list(void *o)
{
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_str(m, "type", "api");
htsmsg_add_str(m, "uri", "bouquet/list");
htsmsg_add_str(m, "event", "bouquet");
return m;
}
static void
bouquet_class_enabled_notify ( void *obj )
{
bouquet_t *bq = obj;
service_t *s;
size_t z;
bouquet_map_to_channels((bouquet_t *)obj);
}
if (!bq->bq_enabled) {
for (z = 0; z < bq->bq_services->is_count; z++) {
s = (service_t *)bq->bq_services->is_array[z];
if (s->s_master_bouquet == bq)
s->s_master_bouquet = NULL;
}
}
static void
bouquet_class_maptoch_notify ( void *obj )
{
bouquet_map_to_channels((bouquet_t *)obj);
}
static const void *
@ -280,6 +386,13 @@ const idclass_t bouquet_class = {
.off = offsetof(bouquet_t, bq_enabled),
.notify = bouquet_class_enabled_notify,
},
{
.type = PT_BOOL,
.id = "maptoch",
.name = "Auto-Map to Channels",
.off = offsetof(bouquet_t, bq_maptoch),
.notify = bouquet_class_maptoch_notify,
},
{
.type = PT_STR,
.id = "name",
@ -334,6 +447,7 @@ bouquet_init(void)
{
htsmsg_t *c, *m;
htsmsg_field_t *f;
bouquet_t *bq;
RB_INIT(&bouquets);
@ -341,7 +455,8 @@ bouquet_init(void)
if ((c = hts_settings_load("bouquet")) != NULL) {
HTSMSG_FOREACH(f, c) {
if (!(m = htsmsg_field_get_map(f))) continue;
(void)bouquet_create(f->hmf_name, m, NULL, NULL);
bq = bouquet_create(f->hmf_name, m, NULL, NULL);
bq->bq_saveflag = 0;
}
htsmsg_destroy(c);
}

View file

@ -29,13 +29,16 @@ typedef struct bouquet {
int bq_saveflag;
int bq_in_load;
time_t bq_updated;
int bq_shield;
int bq_enabled;
int bq_maptoch;
char *bq_name;
char *bq_src;
char *bq_comment;
idnode_set_t *bq_services;
idnode_set_t *bq_active_services;
htsmsg_t *bq_services_waiting;
uint32_t bq_lcn_offset;
@ -50,37 +53,30 @@ extern const idclass_t bouquet_class;
/**
*
*/
bouquet_t *
bouquet_create(const char *uuid, htsmsg_t *conf,
const char *name, const char *src);
htsmsg_t * bouquet_class_get_list(void *o);
bouquet_t * bouquet_create(const char *uuid, htsmsg_t *conf,
const char *name, const char *src);
void bouquet_destroy_by_service(service_t *t);
static inline bouquet_t *
bouquet_find_by_uuid(const char *uuid)
{ return (bouquet_t *)idnode_find(uuid, &bouquet_class, NULL); }
bouquet_t * bouquet_find_by_source(const char *name, const char *src, int create);
void bouquet_map_to_channels(bouquet_t *bq);
void bouquet_add_service(bouquet_t *bq, service_t *s);
void bouquet_completed(bouquet_t *bq);
void bouquet_save(bouquet_t *bq, int notify);
/**
*
*/
void
bouquet_destroy_by_service(service_t *t);
/**
*
*/
bouquet_t *
bouquet_find_by_source(const char *name, const char *src, int create);
/**
*
*/
void
bouquet_add_service(bouquet_t *bq, service_t *s);
/**
*
*/
void
bouquet_save(bouquet_t *bq, int notify);
/**
*
*/
void bouquet_init(void);
void bouquet_service_resolve(void);
void bouquet_done(void);

View file

@ -42,6 +42,7 @@
#include "imagecache.h"
#include "service_mapper.h"
#include "htsbuf.h"
#include "bouquet.h"
#include "intlconv.h"
struct channel_tree channels;
@ -273,6 +274,33 @@ channel_class_epggrab_list ( void *o )
return m;
}
static const void *
channel_class_bouquet_get ( void *o )
{
static const char *sbuf;
channel_t *ch = o;
if (ch->ch_bouquet)
sbuf = idnode_uuid_as_str(&ch->ch_bouquet->bq_id);
else
sbuf = "";
return &sbuf;
}
static int
channel_class_bouquet_set ( void *o, const void *v )
{
channel_t *ch = o;
bouquet_t *bq = bouquet_find_by_uuid(v);
if (bq == NULL && ch->ch_bouquet) {
ch->ch_bouquet = NULL;
return 1;
} else if (bq != ch->ch_bouquet) {
ch->ch_bouquet = bq;
return 1;
}
return 0;
}
const idclass_t channel_class = {
.ic_class = "channel",
.ic_caption = "Channel",
@ -363,6 +391,15 @@ const idclass_t channel_class = {
.list = channel_tag_class_get_list,
.rend = channel_class_tags_rend
},
{
.type = PT_STR,
.id = "bouquet",
.name = "Bouquet (auto)",
.get = channel_class_bouquet_get,
.set = channel_class_bouquet_set,
.list = bouquet_class_get_list,
.opts = PO_RDONLY
},
{}
}
};
@ -527,12 +564,20 @@ channel_get_name ( channel_t *ch )
int64_t
channel_get_number ( channel_t *ch )
{
int n;
int64_t n = 0;
channel_service_mapping_t *csm;
if (ch->ch_number) return ch->ch_number;
LIST_FOREACH(csm, &ch->ch_services, csm_chn_link)
if ((n = service_get_channel_number(csm->csm_svc)))
return n;
if (ch->ch_number) {
n = ch->ch_number;
} else {
LIST_FOREACH(csm, &ch->ch_services, csm_chn_link)
if ((n = service_get_channel_number(csm->csm_svc)))
break;
}
if (n) {
if (ch->ch_bouquet)
n += (int64_t)ch->ch_bouquet->bq_lcn_offset * CHANNEL_SPLIT;
return n;
}
return 0;
}

View file

@ -23,6 +23,7 @@
#include "idnode.h"
struct access;
struct bouquet;
RB_HEAD(channel_tree, channel);
@ -52,6 +53,7 @@ typedef struct channel
int64_t ch_number;
char *ch_icon;
struct channel_tag_mapping_list ch_ctms;
struct bouquet *ch_bouquet;
/* Service/subscriptions */
LIST_HEAD(, channel_service_mapping) ch_services;

View file

@ -111,6 +111,7 @@ typedef struct mpegts_table_state
int version;
int complete;
uint32_t sections[8];
void *bouquet;
RB_ENTRY(mpegts_table_state) link;
} mpegts_table_state_t;

View file

@ -346,7 +346,7 @@ dvb_desc_service_list
static int
dvb_desc_local_channel
( const char *dstr, const uint8_t *ptr, int len, mpegts_mux_t *mm, bouquet_t *bq )
( const char *dstr, const uint8_t *ptr, int len, mpegts_mux_t *mm )
{
int save = 0;
uint16_t sid, lcn;
@ -358,11 +358,6 @@ dvb_desc_local_channel
if (lcn && mm) {
mpegts_service_t *s = mpegts_service_find(mm, sid, 0, 0, &save);
if (s) {
if (bq && bq->bq_lcn_offset &&
(!s->s_master_bouquet || s->s_master_bouquet == bq)) {
lcn += bq->bq_lcn_offset;
s->s_master_bouquet = bq;
}
if (s->s_dvb_channel_num != lcn) {
s->s_dvb_channel_num = lcn;
s->s_config_save((service_t*)s);
@ -740,6 +735,13 @@ dvb_nit_callback
if (tableid != 0x40 && tableid != 0x41 && tableid != 0x4A && tableid != 0xBC)
return -1;
r = dvb_table_begin(mt, ptr, len, tableid, nbid, 7, &st, &sect, &last, &ver);
if (r == 0) {
if (tableid != 0xBC /* fastscan */) {
RB_FOREACH(st, &mt->mt_state, link)
if (st->bouquet)
bouquet_completed((bouquet_t *)st->bouquet);
}
}
if (r != 1) return r;
/* NIT */
@ -805,9 +807,9 @@ dvb_nit_callback
#if ENABLE_MPEGTS_DVB
dauth[0] = 0;
if (mux && *name && tableid == 0x4A /* BAT */) {
if (idnode_is_instance(&mux->mm_id, &dvb_mux_dvbs_class)) {
dvb_mux_conf_t *mc = &((dvb_mux_t *)mux)->lm_tuning;
if (!bq && *name && tableid == 0x4A /* BAT */) {
if (idnode_is_instance(&mm->mm_id, &dvb_mux_dvbs_class)) {
dvb_mux_conf_t *mc = &((dvb_mux_t *)mm)->lm_tuning;
if (mc->u.dmc_fe_qpsk.orbital_dir) {
int pos = mc->u.dmc_fe_qpsk.orbital_pos;
if (mc->u.dmc_fe_qpsk.orbital_dir == 'W')
@ -825,6 +827,7 @@ dvb_nit_callback
if (bq2 != bq && bq && bq->bq_saveflag)
bouquet_save(bq, 1);
bq = bq2;
st->bouquet = bq;
}
#endif
@ -881,7 +884,7 @@ dvb_nit_callback
mpegts_mux_set_crid_authority(mux, dauth);
break;
case DVB_DESC_LOCAL_CHAN:
if (dvb_desc_local_channel(mt->mt_name, dptr, dlen, mux, bq))
if (dvb_desc_local_channel(mt->mt_name, dptr, dlen, mux))
return -1;
break;
case DVB_DESC_SERVICE_LIST:
@ -1170,6 +1173,7 @@ dvb_fs_sdt_callback
if (tableid != 0xBD)
return -1;
r = dvb_table_begin(mt, ptr, len, tableid, nbid, 7, &st, &sect, &last, &ver);
if (r == 0) bouquet_completed(bq);
if (r != 1) return r;
if (len < 5) return -1;
ptr += 5;
@ -1216,7 +1220,7 @@ dvb_fs_sdt_callback
LIST_FOREACH(mux, &mn->mn_muxes, mm_network_link)
if (mux->mm_onid == onid && mux->mm_tsid == tsid)
break;
if (dvb_desc_local_channel(mt->mt_name, dptr, dlen, mux, bq))
if (dvb_desc_local_channel(mt->mt_name, dptr, dlen, mux))
return -1;
break;
case DVB_DESC_SAT_DEL:

View file

@ -443,11 +443,6 @@ typedef struct service {
int64_t s_current_pts;
/*
*
*/
void *s_master_bouquet;
} service_t;

View file

@ -31,6 +31,7 @@
#include "streaming.h"
#include "service.h"
#include "profile.h"
#include "bouquet.h"
#include "api.h"
static service_mapper_status_t service_mapper_stat;
@ -38,7 +39,6 @@ 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_process ( service_t *s );
static void *service_mapper_thread ( void *p );
/**
@ -80,7 +80,8 @@ service_mapper_start ( const service_mapper_conf_t *conf, htsmsg_t *uuids )
service_t *s;
/* Reset stat counters */
service_mapper_reset_stats();
if (TAILQ_EMPTY(&service_mapper_queue))
service_mapper_reset_stats();
/* Store config */
service_mapper_conf = *conf;
@ -135,7 +136,7 @@ service_mapper_start ( const service_mapper_conf_t *conf, htsmsg_t *uuids )
/* Process */
} else {
tvhtrace("service_mapper", " process");
service_mapper_process(s);
service_mapper_process(s, NULL);
}
}
@ -264,29 +265,32 @@ service_mapper_clean ( service_t *s, channel_t *c, void *origin )
* Process a service
*/
void
service_mapper_process ( service_t *s )
service_mapper_process ( service_t *s, bouquet_t *bq )
{
channel_t *chn = NULL;
const char *name;
/* Ignore */
if (s->s_status == SERVICE_ZOMBIE) {
service_mapper_stat.ignore++;
if (!bq)
service_mapper_stat.ignore++;
goto exit;
}
/* Safety check (in-case something has been mapped manually in the interim) */
if (LIST_FIRST(&s->s_channels)) {
if (!bq && LIST_FIRST(&s->s_channels)) {
service_mapper_stat.ignore++;
goto exit;
}
/* Find existing channel */
name = service_get_channel_name(s);
if (service_mapper_conf.merge_same_name && name && *name)
if (!bq && service_mapper_conf.merge_same_name && name && *name)
chn = channel_find_by_name(name);
if (!chn)
if (!chn) {
chn = channel_create(NULL, NULL, NULL);
chn->ch_bouquet = bq;
}
/* Map */
if (chn) {
@ -313,9 +317,12 @@ service_mapper_process ( service_t *s )
idnode_notify_simple(&chn->ch_id);
channel_save(chn);
}
service_mapper_stat.ok++;
tvhinfo("service_mapper", "%s: success", s->s_nicename);
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:
@ -430,7 +437,7 @@ service_mapper_thread ( void *aux )
tvhinfo("service_mapper", "%s: failed [err %s]", s->s_nicename, err);
service_mapper_stat.fail++;
} else
service_mapper_process(s);
service_mapper_process(s, NULL);
service_unref(s);
service_mapper_stat.active = NULL;

View file

@ -19,6 +19,8 @@
#ifndef __TVH_SERVICE_MAPPER_H__
#define __TVH_SERVICE_MAPPER_H__
struct bouquet;
typedef struct service_mapper_conf
{
int check_availability; ///< Check service is receivable
@ -72,6 +74,9 @@ void service_mapper_unlink ( struct service *s, struct channel *c, void *origin
*/
int service_mapper_clean ( struct service *s, struct channel *ch, void *origin );
// Process one service
void service_mapper_process ( struct service *s, struct bouquet *bq );
// Resets the stat counters
void service_mapper_reset_stats ( void );

View file

@ -32,7 +32,7 @@ tvheadend.cteditor = function(panel, index)
*/
tvheadend.bouquet = function(panel, index)
{
var list = 'enabled,name,source,services_count,comment,lcn_off';
var list = 'enabled,name,maptoch,source,services_count,comment,lcn_off';
tvheadend.idnode_grid(panel, {
url: 'api/bouquet',

View file

@ -383,6 +383,7 @@ tvheadend.IdNodeField = function(conf)
case 'u32':
case 'u16':
case 's32':
case 's64':
case 'dbl':
case 'time':
if (this.hexa) {