From 42901e61400a0b453bd1edb18e1968e988caf663 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 31 Oct 2014 23:25:56 +0100 Subject: [PATCH] bouquets: add auto-map, auto-remove features --- src/api/api_bouquet.c | 23 ++++++ src/bouquet.c | 137 ++++++++++++++++++++++++++++--- src/bouquet.h | 48 +++++------ src/channels.c | 55 +++++++++++-- src/channels.h | 2 + src/input/mpegts.h | 1 + src/input/mpegts/dvb_psi.c | 26 +++--- src/service.h | 5 -- src/service_mapper.c | 31 ++++--- src/service_mapper.h | 5 ++ src/webui/static/app/cteditor.js | 2 +- src/webui/static/app/idnode.js | 1 + 12 files changed, 265 insertions(+), 71 deletions(-) diff --git a/src/api/api_bouquet.c b/src/api/api_bouquet.c index bd3e5e9e..0653435c 100644 --- a/src/api/api_bouquet.c +++ b/src/api/api_bouquet.c @@ -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 }, diff --git a/src/bouquet.c b/src/bouquet.c index 0881f91b..adb779ac 100644 --- a/src/bouquet.c +++ b/src/bouquet.c @@ -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 ?: ""); 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 ?: ""); + 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 ?: ""); + 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); } diff --git a/src/bouquet.h b/src/bouquet.h index 2e2537a5..fa577e60 100644 --- a/src/bouquet.h +++ b/src/bouquet.h @@ -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); diff --git a/src/channels.c b/src/channels.c index aa555c1f..8d9a7be2 100644 --- a/src/channels.c +++ b/src/channels.c @@ -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; } diff --git a/src/channels.h b/src/channels.h index fbc1e773..a1fd54e3 100644 --- a/src/channels.h +++ b/src/channels.h @@ -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; diff --git a/src/input/mpegts.h b/src/input/mpegts.h index 57a5c047..bdc9114a 100644 --- a/src/input/mpegts.h +++ b/src/input/mpegts.h @@ -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; diff --git a/src/input/mpegts/dvb_psi.c b/src/input/mpegts/dvb_psi.c index 8f8a7abd..caa3b161 100644 --- a/src/input/mpegts/dvb_psi.c +++ b/src/input/mpegts/dvb_psi.c @@ -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, §, &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, §, &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: diff --git a/src/service.h b/src/service.h index 226a6251..3097cb30 100644 --- a/src/service.h +++ b/src/service.h @@ -443,11 +443,6 @@ typedef struct service { int64_t s_current_pts; - /* - * - */ - void *s_master_bouquet; - } service_t; diff --git a/src/service_mapper.c b/src/service_mapper.c index c408fdaf..4f8f46a9 100644 --- a/src/service_mapper.c +++ b/src/service_mapper.c @@ -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 ?: ""); + } /* 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; diff --git a/src/service_mapper.h b/src/service_mapper.h index 0d974f70..c7e94ffa 100644 --- a/src/service_mapper.h +++ b/src/service_mapper.h @@ -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 ); diff --git a/src/webui/static/app/cteditor.js b/src/webui/static/app/cteditor.js index 89560c34..bbba0392 100644 --- a/src/webui/static/app/cteditor.js +++ b/src/webui/static/app/cteditor.js @@ -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', diff --git a/src/webui/static/app/idnode.js b/src/webui/static/app/idnode.js index fb6e018c..2ffd8160 100644 --- a/src/webui/static/app/idnode.js +++ b/src/webui/static/app/idnode.js @@ -383,6 +383,7 @@ tvheadend.IdNodeField = function(conf) case 'u32': case 'u16': case 's32': + case 's64': case 'dbl': case 'time': if (this.hexa) {