@@ -34,6 +34,8 @@ typedef struct bouquet_download {
3434
3535bouquet_tree_t bouquets ;
3636
37+ static int bouquet_init_completed = 0 ;
38+
3739static void bouquet_remove_service (bouquet_t * bq , service_t * s , int delconf );
3840static void bouquet_download_trigger (bouquet_t * bq );
3941static 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)
424577static void
425578bouquet_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
717892static void
718893bouquet_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
723903static 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
12771466void
0 commit comments