diff --git a/src/bouquet.c b/src/bouquet.c index 9bd677b5..7cca266b 100644 --- a/src/bouquet.c +++ b/src/bouquet.c @@ -251,6 +251,9 @@ bouquet_add_service(bouquet_t *bq, service_t *s, uint64_t lcn) lock_assert(&global_lock); + if (!bq->bq_enabled) + return; + 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); @@ -317,7 +320,7 @@ bouquet_remove_service(bouquet_t *bq, service_t *s) * */ void -bouquet_completed(bouquet_t *bq) +bouquet_completed(bouquet_t *bq, uint32_t seen) { idnode_set_t *remove; service_t *s; @@ -327,9 +330,17 @@ bouquet_completed(bouquet_t *bq) if (!bq) return; - tvhtrace("bouquet", "%s: completed: active=%zi old=%zi", - bq->bq_name ?: "", bq->bq_active_services->is_count, - bq->bq_services->is_count); + if (seen != bq->bq_services_seen) { + bq->bq_services_seen = seen; + bq->bq_saveflag = 1; + } + + tvhtrace("bouquet", "%s: completed: enabled=%d active=%zi old=%zi seen=%u", + bq->bq_name ?: "", bq->bq_enabled, bq->bq_active_services->is_count, + bq->bq_services->is_count, seen); + + if (!bq->bq_enabled) + goto save; /* Add/Remove services */ remove = idnode_set_create(0); @@ -359,6 +370,7 @@ bouquet_completed(bouquet_t *bq) idnode_set_free(bq->bq_active_services); bq->bq_active_services = idnode_set_create(1); +save: if (bq->bq_saveflag) bouquet_save(bq, 1); } @@ -380,6 +392,18 @@ bouquet_map_to_channels(bouquet_t *bq) bouquet_unmap_channel(bq, t); } } + + if (!bq->bq_enabled) { + if (bq->bq_services->is_count) { + idnode_set_free(bq->bq_services); + bq->bq_services = idnode_set_create(1); + bq->bq_saveflag = 1; + } + if (bq->bq_active_services->is_count) { + idnode_set_free(bq->bq_active_services); + bq->bq_active_services = idnode_set_create(1); + } + } } /* @@ -446,6 +470,8 @@ bouquet_class_delete(idnode_t *self) { bouquet_t *bq = (bouquet_t *)self; + bq->bq_enabled = 0; + bouquet_map_to_channels(bq); if (!bq->bq_shield) { hts_settings_remove("bouquet/%s", idnode_uuid_as_str(&bq->bq_id)); bouquet_destroy(bq); @@ -477,10 +503,25 @@ bouquet_class_get_list(void *o) return m; } +static void +bouquet_class_rescan_notify ( void *obj ) +{ + void mpegts_mux_bouquet_rescan ( const char *src, const char *extra ); + bouquet_t *bq = obj; + + if (bq->bq_rescan) + mpegts_mux_bouquet_rescan(bq->bq_src, bq->bq_comment); + bq->bq_rescan = 0; +} + static void bouquet_class_enabled_notify ( void *obj ) { - bouquet_map_to_channels((bouquet_t *)obj); + bouquet_t *bq = obj; + + if (bq->bq_enabled) + bouquet_class_rescan_notify(obj); + bouquet_map_to_channels(bq); } static void @@ -687,6 +728,14 @@ const idclass_t bouquet_class = { .off = offsetof(bouquet_t, bq_enabled), .notify = bouquet_class_enabled_notify, }, + { + .type = PT_BOOL, + .id = "rescan", + .name = "Rescan", + .off = offsetof(bouquet_t, bq_rescan), + .notify = bouquet_class_rescan_notify, + .opts = PO_NOSAVE, + }, { .type = PT_BOOL, .id = "maptoch", @@ -754,6 +803,13 @@ const idclass_t bouquet_class = { .rend = bouquet_class_services_rend, .opts = PO_RDONLY | PO_HIDDEN, }, + { + .type = PT_U32, + .id = "services_seen", + .name = "# Seen Services", + .off = offsetof(bouquet_t, bq_services_seen), + .opts = PO_RDONLY, + }, { .type = PT_U32, .id = "services_count", @@ -816,11 +872,13 @@ bouquet_service_resolve(void) if (!bq->bq_services_waiting) continue; saveflag = bq->bq_saveflag; - HTSMSG_FOREACH(f, bq->bq_services_waiting) { - if (htsmsg_field_get_u32(f, &lcn)) continue; - s = service_find_by_identifier(f->hmf_name); - if (s) - bouquet_add_service(bq, s, lcn); + if (bq->bq_enabled) { + HTSMSG_FOREACH(f, bq->bq_services_waiting) { + if (htsmsg_field_get_u32(f, &lcn)) continue; + s = service_find_by_identifier(f->hmf_name); + if (s) + bouquet_add_service(bq, s, lcn); + } } htsmsg_destroy(bq->bq_services_waiting); bq->bq_services_waiting = NULL; diff --git a/src/bouquet.h b/src/bouquet.h index 1e2a59bd..dcea306a 100644 --- a/src/bouquet.h +++ b/src/bouquet.h @@ -34,6 +34,7 @@ typedef struct bouquet { int bq_shield; int bq_enabled; + int bq_rescan; int bq_maptoch; int bq_mapnolcn; int bq_mapnoname; @@ -47,6 +48,8 @@ typedef struct bouquet { idnode_set_t *bq_services; idnode_set_t *bq_active_services; htsmsg_t *bq_services_waiting; + uint32_t bq_services_seen; + uint32_t bq_services_tmp; /* for fastscan tables */ uint32_t bq_lcn_offset; } bouquet_t; @@ -78,7 +81,7 @@ bouquet_t * bouquet_find_by_source(const char *name, const char *src, int create void bouquet_map_to_channels(bouquet_t *bq); void bouquet_notify_channels(bouquet_t *bq); void bouquet_add_service(bouquet_t *bq, service_t *s, uint64_t lcn); -void bouquet_completed(bouquet_t *bq); +void bouquet_completed(bouquet_t *bq, uint32_t seen); uint64_t bouquet_get_channel_number(bouquet_t *bq, service_t *t); diff --git a/src/input/mpegts.h b/src/input/mpegts.h index eb39160d..38154ed9 100644 --- a/src/input/mpegts.h +++ b/src/input/mpegts.h @@ -752,8 +752,15 @@ void mpegts_mux_unsubscribe_by_name(mpegts_mux_t *mm, const char *name); void mpegts_mux_scan_done ( mpegts_mux_t *mm, const char *buf, int res ); +void mpegts_mux_bouquet_rescan ( const char *src, const char *extra ); + void mpegts_mux_nice_name( mpegts_mux_t *mm, char *buf, size_t len ); +int mpegts_mux_class_scan_state_set ( void *, const void * ); + +static inline int mpegts_mux_scan_state_set ( mpegts_mux_t *m, int state ) + { return mpegts_mux_class_scan_state_set ( m, &state ); } + mpegts_pid_t *mpegts_mux_find_pid_(mpegts_mux_t *mm, int pid, int create); static inline mpegts_pid_t * diff --git a/src/input/mpegts/dvb.h b/src/input/mpegts/dvb.h index 01b5c3a8..fcc5c0f2 100644 --- a/src/input/mpegts/dvb.h +++ b/src/input/mpegts/dvb.h @@ -505,6 +505,8 @@ int dvb_sat_position( const dvb_mux_conf_t *mc ); const char *dvb_sat_position_to_str( int position, char *buf, size_t buflen ); +const int dvb_sat_position_from_str( const char *buf ); + #endif /* ENABLE_MPEGTS_DVB */ void dvb_done ( void ); diff --git a/src/input/mpegts/dvb_psi.c b/src/input/mpegts/dvb_psi.c index d8eacf8a..ef2a0679 100644 --- a/src/input/mpegts/dvb_psi.c +++ b/src/input/mpegts/dvb_psi.c @@ -63,6 +63,7 @@ typedef struct dvb_bat_id { uint32_t freesat:1; uint32_t bskyb:1; uint16_t nbid; + uint32_t services_count; char name[32]; mpegts_mux_t *mm; TAILQ_HEAD(,dvb_bat_svc) services; @@ -389,6 +390,8 @@ dvb_desc_service_list sid = (ptr[i] << 8) | ptr[i+1]; stype = ptr[i+2]; tvhdebug(dstr, " service %04X (%d) type %d", sid, sid, stype); + if (bi) + bi->services_count++; if (mm) { int save = 0; s = mpegts_service_find(mm, sid, 0, 1, &save); @@ -524,7 +527,7 @@ dvb_freesat_add_service snprintf(name, sizeof(name), "%s: %s", bi->name, fr->name); fr->bouquet = bouquet_find_by_source(name, src, 1); } - bouquet_add_service(fr->bouquet, (service_t *)s, lcn * CHANNEL_SPLIT); + bouquet_add_service(fr->bouquet, (service_t *)s, (int64_t)lcn * CHANNEL_SPLIT); } static void @@ -590,7 +593,7 @@ dvb_freesat_completed TAILQ_REMOVE(&fr->services, fs, region_link); if (fr->bouquet) { dvb_bouquet_comment(fr->bouquet, bi->mm); - bouquet_completed(fr->bouquet); + bouquet_completed(fr->bouquet, total); fr->bouquet = NULL; } } @@ -653,9 +656,9 @@ dvb_bskyb_local_channels if (len < 2) return; - regionid = ptr[1]; + regionid = (ptr[1] != 0xff) ? ptr[1] : 0xffff; - if (regionid != 0xff && regionid != 0 && regionid != 1) { + if (regionid != 0xffff && regionid != 0 && regionid != 1) { if ((str = getenv("TVHEADEND_BSKYB_REGIONID")) != NULL) { if (regionid != atoi(str)) return; @@ -667,7 +670,7 @@ dvb_bskyb_local_channels len -= 2; ptr += 2; - tvhtrace(dstr, " region id %02X (%d) unknown %02X (%d)", + tvhtrace(dstr, " region id %04X (%d) unknown %02X (%d)", regionid, regionid, ptr[0], ptr[0]); while (len > 8) { @@ -687,7 +690,7 @@ dvb_bskyb_local_channels if (!fs) { fs = calloc(1, sizeof(*fs)); fs->sid = sid; - fs->regionid = regionid != 0xff ? regionid : 0xffff; + fs->regionid = regionid; fs->lcn = lcn != 0xffff ? lcn : 0; TAILQ_INSERT_TAIL(&b->fservices, fs, link); } @@ -704,7 +707,7 @@ dvb_bskyb_local_channels } } - if (regionid && regionid != 0xff) { + if (regionid && regionid != 0xffff) { LIST_FOREACH(fr, &b->fregions, link) if (fr->regionid == regionid) break; @@ -1153,7 +1156,7 @@ dvb_bat_completed TAILQ_FOREACH(bs, &bi->services, link) bouquet_add_service(bq, (service_t *)bs->svc, 0); - bouquet_completed(bq); + bouquet_completed(bq, bi->services_count); complete: bi->complete = 1; @@ -1678,7 +1681,7 @@ dvb_fs_sdt_callback if (r == 0) { mt->mt_working -= st->working; st->working = 0; - bouquet_completed(bq); + bouquet_completed(bq, bq->bq_services_tmp); } if (r != 1) return r; if (len < 5) return -1; @@ -1730,6 +1733,7 @@ dvb_fs_sdt_callback tvhtrace(mt->mt_name, " dtag %02X dlen %d", dtag, dlen); switch (dtag) { case DVB_DESC_SERVICE: + bq->bq_services_tmp++; if (dvb_desc_service(dptr, dlen, &stype, sprov, sizeof(sprov), sname, sizeof(sname), charset)) return -1; @@ -2301,6 +2305,7 @@ static void psi_tables_dvb_fastscan( void *aux, bouquet_t *bq, const char *name, int pid ) { tvhtrace("fastscan", "adding table %04X (%i) for '%s'", pid, pid, name); + bq->bq_services_tmp = 0; mpegts_table_add(aux, DVB_FASTSCAN_NIT_BASE, DVB_FASTSCAN_MASK, dvb_nit_callback, bq, "fs_nit", MT_CRC, pid); mpegts_table_add(aux, DVB_FASTSCAN_SDT_BASE, DVB_FASTSCAN_MASK, diff --git a/src/input/mpegts/dvb_support.c b/src/input/mpegts/dvb_support.c index 7689eb0a..5d127fe2 100644 --- a/src/input/mpegts/dvb_support.c +++ b/src/input/mpegts/dvb_support.c @@ -868,6 +868,31 @@ dvb_sat_position_to_str(int position, char *buf, size_t buflen) return buf; } +const int +dvb_sat_position_from_str( const char *buf ) +{ + const char *s = buf; + int min, maj; + char c; + + if (!buf) + return INT_MAX; + maj = atoi(s); + while (*s && *s != '.') + s++; + min = *s == '.' ? atoi(s + 1) : 0; + do { + c = *s++; + } while (c && c != 'W' && c != 'E'); + if (!c) + return INT_MAX; + if (maj > 180 || maj < 0) + return INT_MAX; + if (min > 9 || min < 0) + return INT_MAX; + return (maj * 10 + min) * (c == 'W' ? -1 : 1); +} + #endif /* ENABLE_MPEGTS_DVB */ /** diff --git a/src/input/mpegts/mpegts_mux.c b/src/input/mpegts/mpegts_mux.c index e1e41404..d260b99a 100644 --- a/src/input/mpegts/mpegts_mux.c +++ b/src/input/mpegts/mpegts_mux.c @@ -257,7 +257,7 @@ scan_result_tab[] = { { "FAIL", MM_SCAN_FAIL }, }; -static int +int mpegts_mux_class_scan_state_set ( void *o, const void *p ) { mpegts_mux_t *mm = o; diff --git a/src/input/mpegts/mpegts_network_scan.c b/src/input/mpegts/mpegts_network_scan.c index 94646ecd..1c86b262 100644 --- a/src/input/mpegts/mpegts_network_scan.c +++ b/src/input/mpegts/mpegts_network_scan.c @@ -213,6 +213,102 @@ mpegts_network_scan_queue_add ( mpegts_mux_t *mm, int weight ) mpegts_network_scan_notify(mm); } +/****************************************************************************** + * Bouquet helper + *****************************************************************************/ + +static ssize_t +startswith( const char *str, const char *start ) +{ + size_t len = strlen(start); + if (!strncmp(str, start, len)) + return len; + return -1; +} + +void +mpegts_mux_bouquet_rescan ( const char *src, const char *extra ) +{ + mpegts_network_t *mn; + mpegts_mux_t *mm; + ssize_t l; +#if ENABLE_MPEGTS_DVB + const idclass_t *ic; + uint32_t freq; + int satpos; +#endif + + if (!src) + return; +#if ENABLE_MPEGTS_DVB + if ((l = startswith(src, "dvb-bouquet://dvbs,")) > 0) { + uint32_t tsid, nbid; + src += l; + if ((satpos = dvb_sat_position_from_str(src)) == INT_MAX) + return; + while (*src && *src != ',') + src++; + if (sscanf(src, ",%x,%x", &tsid, &nbid) != 2) + return; + LIST_FOREACH(mn, &mpegts_network_all, mn_global_link) + LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link) + if (idnode_is_instance(&mm->mm_id, &dvb_mux_dvbs_class) && + mm->mm_tsid == tsid && + dvb_sat_position(&((dvb_mux_t *)mm)->lm_tuning) == satpos) + mpegts_mux_scan_state_set(mm, MM_SCAN_STATE_PEND); + return; + } + if ((l = startswith(src, "dvb-bouquet://dvbt,")) > 0) { + uint32_t tsid, nbid; + if (sscanf(src, "%x,%x", &tsid, &nbid) != 2) + return; + ic = &dvb_mux_dvbt_class; + goto tsid_lookup; + } + if ((l = startswith(src, "dvb-bouquet://dvbc,")) > 0) { + uint32_t tsid, nbid; + if (sscanf(src, "%x,%x", &tsid, &nbid) != 2) + return; + ic = &dvb_mux_dvbc_class; +tsid_lookup: + LIST_FOREACH(mn, &mpegts_network_all, mn_global_link) + LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link) + if (idnode_is_instance(&mm->mm_id, ic) && + mm->mm_tsid == tsid) + mpegts_mux_scan_state_set(mm, MM_SCAN_STATE_PEND); + return; + } + if ((l = startswith(src, "dvb-bskyb://dvbs,")) > 0 || + (l = startswith(src, "dvb-freesat://dvbs,")) > 0) { + if ((satpos = dvb_sat_position_from_str(src + l)) == INT_MAX) + return; + /* a bit tricky, but we don't have other info */ + if (!extra) + return; + freq = strtod(extra, NULL) * 1000; + goto freq; + } + if ((l = startswith(src, "dvb-fastscan://dvbs,")) > 0) { + uint32_t pid; + src += l; + if ((satpos = dvb_sat_position_from_str(src)) == INT_MAX) + return; + while (*src && *src != ',') + src++; + if (sscanf(src, ",%u,%u", &freq, &pid) != 2) + return; +freq: + LIST_FOREACH(mn, &mpegts_network_all, mn_global_link) + LIST_FOREACH(mm, &mn->mn_muxes, mm_network_link) + if (idnode_is_instance(&mm->mm_id, &dvb_mux_dvbs_class) && + ((dvb_mux_t *)mm)->lm_tuning.dmc_fe_freq == freq && + dvb_sat_position(&((dvb_mux_t *)mm)->lm_tuning) == satpos) + mpegts_mux_scan_state_set(mm, MM_SCAN_STATE_PEND); + return; + } +#endif +} + /****************************************************************************** * Subsystem setup / tear down *****************************************************************************/ diff --git a/src/webui/static/app/cteditor.js b/src/webui/static/app/cteditor.js index 389623cb..3f94550a 100644 --- a/src/webui/static/app/cteditor.js +++ b/src/webui/static/app/cteditor.js @@ -32,8 +32,8 @@ tvheadend.cteditor = function(panel, index) */ tvheadend.bouquet = function(panel, index) { - var list = 'enabled,name,maptoch,mapnolcn,lcn_off,mapnoname,mapradio,' + - 'chtag,source,services_count,comment'; + var list = 'enabled,rescan,name,maptoch,mapnolcn,lcn_off,mapnoname,mapradio,' + + 'chtag,source,services_count,services_seen,comment'; tvheadend.idnode_grid(panel, { url: 'api/bouquet', @@ -43,6 +43,7 @@ tvheadend.bouquet = function(panel, index) tabIndex: index, columns: { enabled: { width: 50 }, + rescan: { width: 50 }, name: { width: 200 }, maptoch: { width: 100 }, mapnolcn: { width: 100 }, @@ -52,6 +53,7 @@ tvheadend.bouquet = function(panel, index) chtag: { width: 100 }, source: { width: 200 }, services_count: { width: 100 }, + services_seen: { width: 100 }, comment: { width: 200 }, }, list: list,