#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 )
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 },
#include "access.h"
#include "bouquet.h"
#include "service.h"
+#include "channels.h"
+#include "service_mapper.h"
bouquet_tree_t bouquets;
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)
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);
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);
+}
+
/*
*
*/
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);
+ }
}
}
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 *
.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",
{
htsmsg_t *c, *m;
htsmsg_field_t *f;
+ bouquet_t *bq;
RB_INIT(&bouquets);
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);
}
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;
/**
*
*/
-bouquet_t *
-bouquet_create(const char *uuid, htsmsg_t *conf,
- const char *name, const char *src);
-/**
- *
- */
-void
-bouquet_destroy_by_service(service_t *t);
+htsmsg_t * bouquet_class_get_list(void *o);
-/**
- *
- */
-bouquet_t *
-bouquet_find_by_source(const char *name, const char *src, int create);
+bouquet_t * bouquet_create(const char *uuid, htsmsg_t *conf,
+ const char *name, const char *src);
-/**
- *
- */
-void
-bouquet_add_service(bouquet_t *bq, service_t *s);
+void bouquet_destroy_by_service(service_t *t);
-/**
- *
- */
-void
-bouquet_save(bouquet_t *bq, int notify);
+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_init(void);
void bouquet_service_resolve(void);
void bouquet_done(void);
#include "imagecache.h"
#include "service_mapper.h"
#include "htsbuf.h"
+#include "bouquet.h"
#include "intlconv.h"
struct channel_tree channels;
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",
.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
+ },
{}
}
};
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;
}
#include "idnode.h"
struct access;
+struct bouquet;
RB_HEAD(channel_tree, 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;
int version;
int complete;
uint32_t sections[8];
+ void *bouquet;
RB_ENTRY(mpegts_table_state) link;
} mpegts_table_state_t;
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;
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);
if (tableid != 0x40 && tableid != 0x41 && tableid != 0x4A && tableid != 0xBC)
return -1;
r = dvb_table_begin(mt, ptr, len, tableid, nbid, 7, &st, §, &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 */
#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')
if (bq2 != bq && bq && bq->bq_saveflag)
bouquet_save(bq, 1);
bq = bq2;
+ st->bouquet = bq;
}
#endif
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:
if (tableid != 0xBD)
return -1;
r = dvb_table_begin(mt, ptr, len, tableid, nbid, 7, &st, §, &last, &ver);
+ if (r == 0) bouquet_completed(bq);
if (r != 1) return r;
if (len < 5) return -1;
ptr += 5;
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:
int64_t s_current_pts;
- /*
- *
- */
- void *s_master_bouquet;
-
} service_t;
#include "streaming.h"
#include "service.h"
#include "profile.h"
+#include "bouquet.h"
#include "api.h"
static service_mapper_status_t service_mapper_stat;
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 );
/**
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;
/* Process */
} else {
tvhtrace("service_mapper", " process");
- service_mapper_process(s);
+ service_mapper_process(s, NULL);
}
}
* 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) {
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:
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;
#ifndef __TVH_SERVICE_MAPPER_H__
#define __TVH_SERVICE_MAPPER_H__
+struct bouquet;
+
typedef struct service_mapper_conf
{
int check_availability; ///< Check service is receivable
*/
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 );
*/
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',
case 'u32':
case 'u16':
case 's32':
+ case 's64':
case 'dbl':
case 'time':
if (this.hexa) {