Skip to content

Commit b0be01c

Browse files
azlm8tFlole998
authored andcommitted
bouquet: Allow merging of services across network bouquet, fixes #5617
In a mixed network environment (such as DVB-T and DVB-S) it is common to receive many of the same channels. However, with "auto map to channels" on a bouquet, you could not merge the channels across the network bouquets. Now we create a separate "Tvheadend Network" in the bouquets and allow "auto map to channels" on it. Any other bouquets that are enabled but do _not_ have "auto map to channels" enabled will be added to the enabled "Tvheadend Network". So, if "DVB-T Network" and "DVB-S Network" in the bouquet tab are both enabled _and_ their tickbox for "auto map to channels" is deselected, then their services will be added to the "Tvheadend Network". That can then be enabled with "merge channels" to produce the merged channels. We call it "Tvheadend Network" instead of "Tvheaded Virtual Bouquet" to be consistent with the other auto-generated names. The new bouquet is lazily updated to ensure the CPU overhead is minimized. This means that when other bouquets are scanned, we wait until the system is quiet before updating the new bouquet with the new channels. Fixes: #5617
1 parent 075e6cd commit b0be01c

1 file changed

Lines changed: 190 additions & 1 deletion

File tree

src/bouquet.c

Lines changed: 190 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ typedef struct bouquet_download {
3434

3535
bouquet_tree_t bouquets;
3636

37+
static int bouquet_init_completed = 0;
38+
3739
static void bouquet_remove_service(bouquet_t *bq, service_t *s, int delconf);
3840
static void bouquet_download_trigger(bouquet_t *bq);
3941
static void bouquet_download_stop(void *aux);
@@ -328,6 +330,151 @@ bouquet_map_channel(bouquet_t *bq, service_t *t)
328330
idnode_changed(&ch->ch_id);
329331
}
330332

333+
334+
static const char *
335+
bouquet_get_global_bouquet_src(void)
336+
{
337+
static const char src[] = "tvh-network://global-bouquet";
338+
return src;
339+
}
340+
341+
342+
/// The global bouquet is a virtual bouquet. It contains services from
343+
/// other bouquets which are enabled and have maptoch disabled. If the
344+
/// global bouquet is enabled and has maptoch enabled then it means
345+
/// mappings such as "merge same name" can be done using services from
346+
/// multiple bouquets (which may be from different sources such as
347+
/// DVB-T and DVB-S).
348+
static bouquet_t *
349+
bouquet_get_global_bouquet(void)
350+
{
351+
static const char name[] = "Tvheadend Network";
352+
bouquet_t *global_bq;
353+
enum { BOUQUET_CREATE = 1 };
354+
355+
/* Still initializing, so don't do anything with a global bouquet yet otherwise
356+
* we run the risk of creating a new global bouquet before we load the config
357+
* from disk.
358+
*/
359+
if (!bouquet_init_completed)
360+
return NULL;
361+
362+
global_bq = bouquet_find_by_source(name, bouquet_get_global_bouquet_src(), BOUQUET_CREATE);
363+
if (!global_bq->bq_comment)
364+
bouquet_change_comment(global_bq, "Tvheadend", 1);
365+
return global_bq;
366+
}
367+
368+
/// Internal function to scan a single bouquet and collect all
369+
/// services on that bouquet in to active_svcs and add them to the
370+
/// global bouquet.
371+
static void
372+
bouquet_global_rescan_single_bouquet(const bouquet_t *bq, idnode_set_t *active_svcs)
373+
{
374+
size_t z;
375+
bouquet_t *global_bq = bouquet_get_global_bouquet();
376+
service_t *s;
377+
const service_lcn_t *lcn;
378+
379+
/* Global bouquet must be enabled and mapping to channels to have
380+
* any services.
381+
*/
382+
if (!global_bq || !global_bq->bq_enabled || !global_bq->bq_maptoch)
383+
return;
384+
385+
/* Don't want to include disabled bouquets or ones already mapping
386+
* to channels for themselves.
387+
*/
388+
if (!bq || !bq->bq_enabled || bq->bq_maptoch || bq == global_bq)
389+
return;
390+
391+
for (z = 0; z < bq->bq_services->is_count; z++) {
392+
s = (service_t *)bq->bq_services->is_array[z];
393+
LIST_FOREACH(lcn, &s->s_lcns, sl_link) {
394+
if (lcn->sl_bouquet != bq) continue;
395+
bouquet_add_service(global_bq, s, (int64_t)lcn->sl_lcn, NULL);
396+
idnode_set_add(active_svcs, &s->s_id, NULL, NULL);
397+
}
398+
}
399+
}
400+
401+
402+
/// Callback function to rescan the global bouquet
403+
/// for active services and update the stats.
404+
static void
405+
bouquet_global_rescan_cb(void *unused)
406+
{
407+
bouquet_t *global_bq = bouquet_get_global_bouquet();
408+
if (!global_bq)
409+
return;
410+
411+
size_t z;
412+
const bouquet_t *bq;
413+
idnode_set_t *active_svcs = idnode_set_create(1);
414+
service_t *s;
415+
416+
tvhtrace(LS_BOUQUET, "Rescanning global bouquet");
417+
418+
/* We rescan every bouquet to build up a list of which
419+
* services are still active. We only scan bouquets
420+
* that are enabled and do not have maptoch set
421+
* (since they are doing their own mappings).
422+
*/
423+
424+
/* If our bouquet is disabled/not mapping channels then we can't
425+
* have any services
426+
*/
427+
if (global_bq->bq_enabled && global_bq->bq_maptoch) {
428+
RB_FOREACH(bq, &bouquets, bq_link) {
429+
bouquet_global_rescan_single_bouquet(bq, active_svcs);
430+
}
431+
}
432+
433+
/* Now we have our list of services, remove any that should no longer exist in the
434+
* global bouquet.
435+
*/
436+
for (z = 0; z < global_bq->bq_services->is_count; z++) {
437+
s = (service_t *)global_bq->bq_services->is_array[z];
438+
if (!idnode_set_exists(active_svcs, &s->s_id)) {
439+
bouquet_remove_service(global_bq, s, 1);
440+
}
441+
}
442+
443+
bouquet_completed(global_bq, active_svcs->is_count);
444+
idnode_set_free(active_svcs);
445+
}
446+
447+
/// We only want to rescan after a short delay. So, if a lot of events
448+
/// are happening such as deleting lots of services or lots of
449+
/// networks complete scanning at the same time, we can wait a short
450+
/// while for the global bouquet to be updated rather than wasting CPU
451+
/// doing multiple scans. For example, my system can have 20 bouquets
452+
/// a second complete scanning, so we wait until the system is less
453+
/// busy.
454+
static void
455+
bouquet_global_rescan_i(int64_t mono)
456+
{
457+
static mtimer_t bouquet_global_bouquet_rescan_timer;
458+
mtimer_arm_rel(&bouquet_global_bouquet_rescan_timer, bouquet_global_rescan_cb, NULL, mono);
459+
}
460+
461+
static void
462+
bouquet_global_rescan(void)
463+
{
464+
bouquet_global_rescan_i(sec2mono(5));
465+
}
466+
467+
468+
/// Even though we say "now", it could get delayed
469+
/// if the system is busy.
470+
static void
471+
bouquet_global_rescan_now(void)
472+
{
473+
bouquet_global_rescan_i(sec2mono(0));
474+
}
475+
476+
477+
331478
/*
332479
*
333480
*/
@@ -336,6 +483,7 @@ bouquet_add_service(bouquet_t *bq, service_t *s, uint64_t lcn, const char *tag)
336483
{
337484
service_lcn_t *tl;
338485
idnode_list_mapping_t *ilm;
486+
bouquet_t *global_bq = NULL;
339487

340488
lock_assert(&global_lock);
341489

@@ -376,6 +524,11 @@ bouquet_add_service(bouquet_t *bq, service_t *s, uint64_t lcn, const char *tag)
376524
if (!bq->bq_in_load &&
377525
!idnode_set_exists(bq->bq_active_services, &s->s_id))
378526
idnode_set_add(bq->bq_active_services, &s->s_id, NULL, NULL);
527+
528+
global_bq = bouquet_get_global_bouquet();
529+
if (global_bq && bq != global_bq && global_bq->bq_enabled) {
530+
bouquet_global_rescan();
531+
}
379532
}
380533

381534
/*
@@ -424,11 +577,19 @@ bouquet_notify_service_enabled(service_t *t)
424577
static void
425578
bouquet_remove_service(bouquet_t *bq, service_t *s, int delconf)
426579
{
580+
bouquet_t *global_bq;
427581
tvhtrace(LS_BOUQUET, "remove service %s from %s",
428582
s->s_nicename, bq->bq_name ?: "<unknown>");
429583
idnode_set_remove(bq->bq_services, &s->s_id);
430584
if (delconf)
431585
bouquet_unmap_channel(bq, s);
586+
/* Also schedule global bouquet to check which services should be
587+
* available.
588+
*/
589+
global_bq = bouquet_get_global_bouquet();
590+
if (global_bq && bq != global_bq && global_bq->bq_enabled) {
591+
bouquet_global_rescan();
592+
}
432593
}
433594

434595
/*
@@ -582,6 +743,12 @@ bouquet_delete(bouquet_t *bq)
582743
bq->bq_services = idnode_set_create(1);
583744
idnode_changed(&bq->bq_id);
584745
}
746+
747+
/* Do a full rescan of global bouquet to ensure it no longer has
748+
* entries from this deleted bouquet.
749+
*/
750+
if (bq != bouquet_get_global_bouquet())
751+
bouquet_global_rescan();
585752
}
586753

587754
/**
@@ -614,6 +781,8 @@ bouquet_scan ( bouquet_t *bq )
614781
mpegts_network_uuid = bq->bq_src + 17;
615782
else if (strncmp(bq->bq_src, "exturl://", 9) == 0)
616783
return bouquet_download_trigger(bq);
784+
else if (strcmp(bq->bq_src, bouquet_get_global_bouquet_src()) == 0)
785+
return bouquet_global_rescan_now();
617786

618787
if (mpegts_network_uuid) {
619788
mpegts_network_t *mn = mpegts_network_find(mpegts_network_uuid);
@@ -712,12 +881,23 @@ bouquet_class_enabled_notify ( void *obj, const char *lang )
712881
if (bq->bq_enabled)
713882
bouquet_scan(bq);
714883
bouquet_map_to_channels(bq);
884+
885+
/* We have to do the scan even for global bouquet since if global bouquet
886+
* is disabled then it has to remove the services it had, and if it is
887+
* enabled then we want to update the service list.
888+
*/
889+
bq == bouquet_get_global_bouquet() ? bouquet_global_rescan_now() : bouquet_global_rescan();
715890
}
716891

717892
static void
718893
bouquet_class_maptoch_notify ( void *obj, const char *lang )
719894
{
720-
bouquet_map_to_channels((bouquet_t *)obj);
895+
bouquet_t *bq = obj;
896+
bouquet_map_to_channels(bq);
897+
/* Ensure any service changes are reflected in the global bouquet */
898+
if (bq != bouquet_get_global_bouquet()) {
899+
bouquet_global_rescan();
900+
}
721901
}
722902

723903
static void
@@ -1272,6 +1452,15 @@ bouquet_init(void)
12721452
}
12731453
htsmsg_destroy(c);
12741454
}
1455+
1456+
/* Now indicate init has completed. Otherwise what happens is we
1457+
* load a bouquet, internally create a global bouquet to map
1458+
* services from that bouquet, then later load the global bouquet
1459+
* from disk. So, we flag when init has completed to indicate that
1460+
* we can now proceed with the global bouquet if it does not exist.
1461+
*/
1462+
bouquet_init_completed = 1;
1463+
bouquet_global_rescan_now();
12751464
}
12761465

12771466
void

0 commit comments

Comments
 (0)