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 ?: "<unknown>");
idnode_set_add(bq->bq_services, &s->s_id, NULL);
*
*/
void
-bouquet_completed(bouquet_t *bq)
+bouquet_completed(bouquet_t *bq, uint32_t seen)
{
idnode_set_t *remove;
service_t *s;
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);
idnode_set_free(bq->bq_active_services);
bq->bq_active_services = idnode_set_create(1);
+save:
if (bq->bq_saveflag)
bouquet_save(bq, 1);
}
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);
+ }
+ }
}
/*
{
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);
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
.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",
.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",
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;
int bq_shield;
int bq_enabled;
+ int bq_rescan;
int bq_maptoch;
int bq_mapnolcn;
int bq_mapnoname;
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;
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);
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 *
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 );
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;
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);
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
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;
}
}
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;
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) {
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);
}
}
}
- if (regionid && regionid != 0xff) {
+ if (regionid && regionid != 0xffff) {
LIST_FOREACH(fr, &b->fregions, link)
if (fr->regionid == regionid)
break;
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;
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;
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;
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,
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 */
/**
{ "FAIL", MM_SCAN_FAIL },
};
-static int
+int
mpegts_mux_class_scan_state_set ( void *o, const void *p )
{
mpegts_mux_t *mm = o;
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
*****************************************************************************/
*/
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',
tabIndex: index,
columns: {
enabled: { width: 50 },
+ rescan: { width: 50 },
name: { width: 200 },
maptoch: { width: 100 },
mapnolcn: { width: 100 },
chtag: { width: 100 },
source: { width: 200 },
services_count: { width: 100 },
+ services_seen: { width: 100 },
comment: { width: 200 },
},
list: list,