1 /*
2  *  tvheadend, bouquets
3  *  Copyright (C) 2014 Jaroslav Kysela
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "tvheadend.h"
20 #include "settings.h"
21 #include "access.h"
22 #include "bouquet.h"
23 #include "service.h"
24 #include "channels.h"
25 #include "service_mapper.h"
26 #include "download.h"
27 
28 typedef struct bouquet_download {
29   bouquet_t  *bq;
30   download_t  download;
31   mtimer_t    timer;
32 } bouquet_download_t;
33 
34 bouquet_tree_t bouquets;
35 
36 static void bouquet_remove_service(bouquet_t *bq, service_t *s, int delconf);
37 static void bouquet_download_trigger(bouquet_t *bq);
38 static void bouquet_download_stop(void *aux);
39 static int bouquet_download_process(void *aux, const char *last_url, const char *host_url, char *data, size_t len);
40 
41 /**
42  *
43  */
44 static int
_bq_cmp(const void * a,const void * b)45 _bq_cmp(const void *a, const void *b)
46 {
47   return strcmp(((bouquet_t *)a)->bq_src ?: "", ((bouquet_t *)b)->bq_src ?: "");
48 }
49 
50 /**
51  *
52  */
53 static void
bouquet_free(bouquet_t * bq)54 bouquet_free(bouquet_t *bq)
55 {
56   bouquet_download_t *bqd;
57 
58   idnode_save_check(&bq->bq_id, 1);
59   idnode_unlink(&bq->bq_id);
60 
61   if ((bqd = bq->bq_download) != NULL) {
62     bouquet_download_stop(bqd);
63     download_done(&bqd->download);
64     free(bqd);
65   }
66 
67   idnode_set_free(bq->bq_active_services);
68   idnode_set_free(bq->bq_services);
69   htsmsg_destroy(bq->bq_services_waiting);
70   free((char *)bq->bq_chtag_waiting);
71   free(bq->bq_name);
72   free(bq->bq_ext_url);
73   free(bq->bq_src);
74   free(bq->bq_comment);
75   free(bq);
76 }
77 
78 /**
79  *
80  */
81 bouquet_t *
bouquet_create(const char * uuid,htsmsg_t * conf,const char * name,const char * src)82 bouquet_create(const char *uuid, htsmsg_t *conf,
83                const char *name, const char *src)
84 {
85   bouquet_t *bq, *bq2;
86   bouquet_download_t *bqd;
87   char buf[128], ubuf[UUID_HEX_SIZE];
88   int i;
89 
90   lock_assert(&global_lock);
91 
92   bq = calloc(1, sizeof(bouquet_t));
93   bq->bq_services = idnode_set_create(1);
94   bq->bq_active_services = idnode_set_create(1);
95   bq->bq_ext_url_period = 60;
96   bq->bq_mapencrypted = 1;
97   bq->bq_mapradio = 1;
98   bq->bq_maptoch = 1;
99   bq->bq_chtag = 1;
100 
101   if (idnode_insert(&bq->bq_id, uuid, &bouquet_class, 0)) {
102     if (uuid)
103       tvherror(LS_BOUQUET, "invalid uuid '%s'", uuid);
104     bouquet_free(bq);
105     return NULL;
106   }
107 
108   if (conf) {
109     bq->bq_in_load = 1;
110     idnode_load(&bq->bq_id, conf);
111     bq->bq_in_load = 0;
112     if (!htsmsg_get_bool(conf, "shield", &i) && i)
113       bq->bq_shield = 1;
114   }
115 
116   if (name) {
117     free(bq->bq_name);
118     bq->bq_name = strdup(name);
119   }
120 
121   if (src) {
122     free(bq->bq_src);
123     bq->bq_src = strdup(src);
124   }
125 
126   if (bq->bq_ext_url) {
127     if (bq->bq_src && strncmp(bq->bq_src, "exturl://", 9) != 0) {
128       free(bq->bq_ext_url);
129       bq->bq_ext_url = NULL;
130     } else {
131       free(bq->bq_src);
132       snprintf(buf, sizeof(buf), "exturl://%s", idnode_uuid_as_str(&bq->bq_id, ubuf));
133       bq->bq_src = strdup(buf);
134       bq->bq_download = bqd = calloc(1, sizeof(*bqd));
135       bqd->bq = bq;
136       download_init(&bqd->download, LS_BOUQUET);
137       bqd->download.process = bouquet_download_process;
138       bqd->download.stop = bouquet_download_stop;
139       bouquet_change_comment(bq, bq->bq_ext_url, 0);
140     }
141   }
142 
143   bq2 = RB_INSERT_SORTED(&bouquets, bq, bq_link, _bq_cmp);
144   if (bq2) {
145     tvherror(LS_BOUQUET, "found duplicate source id: '%s', remove duplicate config", bq->bq_src);
146     bouquet_free(bq);
147     return NULL;
148   }
149 
150   bq->bq_saveflag = 1;
151 
152   if (bq->bq_ext_url)
153     bouquet_download_trigger(bq);
154 
155   return bq;
156 }
157 
158 /**
159  *
160  */
161 static void
bouquet_destroy(bouquet_t * bq)162 bouquet_destroy(bouquet_t *bq)
163 {
164   if (!bq)
165     return;
166 
167   RB_REMOVE(&bouquets, bq, bq_link);
168 
169   bouquet_free(bq);
170 }
171 
172 /**
173  *
174  */
175 void
bouquet_destroy_by_service(service_t * t,int delconf)176 bouquet_destroy_by_service(service_t *t, int delconf)
177 {
178   bouquet_t *bq;
179   service_lcn_t *sl;
180 
181   lock_assert(&global_lock);
182 
183   RB_FOREACH(bq, &bouquets, bq_link)
184     if (idnode_set_exists(bq->bq_services, &t->s_id))
185       bouquet_remove_service(bq, t, delconf);
186   while ((sl = LIST_FIRST(&t->s_lcns)) != NULL) {
187     LIST_REMOVE(sl, sl_link);
188     free(sl);
189   }
190 }
191 
192 /**
193  *
194  */
195 void
bouquet_destroy_by_channel_tag(channel_tag_t * ct)196 bouquet_destroy_by_channel_tag(channel_tag_t *ct)
197 {
198   bouquet_t *bq;
199 
200   lock_assert(&global_lock);
201 
202   RB_FOREACH(bq, &bouquets, bq_link)
203     if (bq->bq_chtag_ptr == ct)
204       bq->bq_chtag_ptr = NULL;
205 }
206 
207 /*
208  *
209  */
210 bouquet_t *
bouquet_find_by_source(const char * name,const char * src,int create)211 bouquet_find_by_source(const char *name, const char *src, int create)
212 {
213   bouquet_t *bq;
214   bouquet_t bqs;
215 
216   assert(src);
217 
218   lock_assert(&global_lock);
219 
220   bqs.bq_src = (char *)src;
221   bq = RB_FIND(&bouquets, &bqs, bq_link, _bq_cmp);
222   if (bq) {
223     if (name && *name && bq->bq_name && strcmp(name, bq->bq_name)) {
224       tvhwarn(LS_BOUQUET, "bouquet name '%s' changed to '%s'", bq->bq_name ?: "", name);
225       free(bq->bq_name);
226       bq->bq_name = strdup(name);
227       idnode_changed(&bq->bq_id);
228     }
229     return bq;
230   }
231   if (create && name) {
232     bq = bouquet_create(NULL, NULL, name, src);
233     tvhinfo(LS_BOUQUET, "new bouquet '%s'", name);
234     return bq;
235   }
236   return NULL;
237 }
238 
239 /*
240  *
241  */
242 static channel_tag_t *
bouquet_tag(bouquet_t * bq,int create)243 bouquet_tag(bouquet_t *bq, int create)
244 {
245   channel_tag_t *ct;
246   char buf[128];
247 
248   assert(!bq->bq_in_load);
249   if (bq->bq_chtag_waiting) {
250     bq->bq_chtag_ptr = channel_tag_find_by_uuid(bq->bq_chtag_waiting);
251     free((char *)bq->bq_chtag_waiting);
252     bq->bq_chtag_waiting = NULL;
253   }
254   if (bq->bq_chtag_ptr)
255     return bq->bq_chtag_ptr;
256   snprintf(buf, sizeof(buf), "*** %s", bq->bq_name ?: "???");
257   ct = channel_tag_find_by_name(buf, create);
258   if (ct) {
259     bq->bq_chtag_ptr = ct;
260     idnode_changed(&bq->bq_id);
261   }
262   return ct;
263 }
264 
265 /*
266  *
267  */
268 static int
noname(const char * s)269 noname(const char *s)
270 {
271   if (!s)
272     return 1;
273   while (*s) {
274     if (*s > ' ')
275       return 0;
276     s++;
277   }
278   return 1;
279 }
280 
281 /*
282  *
283  */
284 static void
bouquet_map_channel(bouquet_t * bq,service_t * t)285 bouquet_map_channel(bouquet_t *bq, service_t *t)
286 {
287   channel_t *ch = NULL;
288   idnode_list_mapping_t *ilm;
289   static service_mapper_conf_t sm_conf = {
290     .check_availability = 0,
291     .encrypted          = 1,
292     .merge_same_name    = 0,
293     .type_tags          = 0,
294     .provider_tags      = 0,
295     .network_tags       = 0
296   };
297 
298   if (!t->s_enabled)
299     return;
300   if (!bq->bq_mapradio && service_is_radio(t))
301     return;
302   if (!bq->bq_mapnolcn &&
303       bouquet_get_channel_number(bq, t) <= 0)
304     return;
305   if (!bq->bq_mapnoname && noname(service_get_channel_name(t)))
306     return;
307   if (!bq->bq_mapencrypted && service_is_encrypted(t))
308     return;
309   LIST_FOREACH(ilm, &t->s_channels, ilm_in1_link)
310     if (((channel_t *)ilm->ilm_in2)->ch_bouquet == bq)
311       break;
312   if (!ilm) {
313     sm_conf.encrypted = bq->bq_mapencrypted;
314     sm_conf.merge_same_name = bq->bq_mapmergename;
315     sm_conf.type_tags = bq->bq_chtag_type_tags;
316     sm_conf.provider_tags = bq->bq_chtag_provider_tags;
317     sm_conf.network_tags = bq->bq_chtag_network_tags;
318     ch = service_mapper_process(&sm_conf, t, bq);
319   } else
320     ch = (channel_t *)ilm->ilm_in2;
321   if (ch && bq->bq_chtag)
322     if (channel_tag_map(bouquet_tag(bq, 1), ch, ch))
323       idnode_changed(&ch->ch_id);
324 }
325 
326 /*
327  *
328  */
329 void
bouquet_add_service(bouquet_t * bq,service_t * s,uint64_t lcn,const char * tag)330 bouquet_add_service(bouquet_t *bq, service_t *s, uint64_t lcn, const char *tag)
331 {
332   service_lcn_t *tl;
333   idnode_list_mapping_t *ilm;
334 
335   lock_assert(&global_lock);
336 
337   if (!bq->bq_enabled)
338     return;
339 
340   if (!idnode_set_exists(bq->bq_services, &s->s_id)) {
341     tvhtrace(LS_BOUQUET, "add service %s [%p] to %s lcn %"PRIu64"(.%"PRIu64")",
342              s->s_nicename, s, bq->bq_name ?: "<unknown>",
343              lcn / CHANNEL_SPLIT, lcn % CHANNEL_SPLIT);
344     idnode_set_add(bq->bq_services, &s->s_id, NULL, NULL);
345     bq->bq_saveflag = 1;
346   }
347 
348   LIST_FOREACH(tl, &s->s_lcns, sl_link)
349     if (tl->sl_bouquet == bq)
350       break;
351 
352   if (!tl) {
353     tl = calloc(1, sizeof(*tl));
354     tl->sl_bouquet = bq;
355     LIST_INSERT_HEAD(&s->s_lcns, tl, sl_link);
356     bq->bq_saveflag = 1;
357   } else {
358     if (tl->sl_lcn != lcn)
359       bq->bq_saveflag = 1;
360   }
361   if (lcn != tl->sl_lcn) {
362     tl->sl_lcn = lcn;
363     LIST_FOREACH(ilm, &s->s_channels, ilm_in1_link)
364       idnode_notify_changed(ilm->ilm_in2);
365   }
366   tl->sl_seen = 1;
367 
368   if (bq->bq_enabled && bq->bq_maptoch)
369     bouquet_map_channel(bq, s);
370 
371   if (!bq->bq_in_load &&
372       !idnode_set_exists(bq->bq_active_services, &s->s_id))
373     idnode_set_add(bq->bq_active_services, &s->s_id, NULL, NULL);
374 }
375 
376 /*
377  *
378  */
379 static void
bouquet_unmap_channel(bouquet_t * bq,service_t * t)380 bouquet_unmap_channel(bouquet_t *bq, service_t *t)
381 {
382   idnode_list_mapping_t *ilm, *ilm_next;
383 
384   ilm = LIST_FIRST(&t->s_channels);
385   while (ilm) {
386     ilm_next = LIST_NEXT(ilm, ilm_in1_link);
387     if (((channel_t *)ilm->ilm_in2)->ch_bouquet == bq) {
388       tvhinfo(LS_BOUQUET, "%s / %s: unmapped from %s",
389               channel_get_name((channel_t *)ilm->ilm_in2), t->s_nicename,
390               bq->bq_name ?: "<unknown>");
391       channel_delete((channel_t *)ilm->ilm_in2, 1);
392     }
393     ilm = ilm_next;
394   }
395 }
396 
397 /**
398  *
399  */
400 void
bouquet_notify_service_enabled(service_t * t)401 bouquet_notify_service_enabled(service_t *t)
402 {
403   bouquet_t *bq;
404 
405   lock_assert(&global_lock);
406 
407   RB_FOREACH(bq, &bouquets, bq_link)
408     if (idnode_set_exists(bq->bq_services, &t->s_id)) {
409       if (!t->s_enabled)
410         bouquet_unmap_channel(bq, t);
411       else if (bq->bq_enabled && bq->bq_maptoch)
412         bouquet_map_channel(bq, t);
413     }
414 }
415 
416 /*
417  *
418  */
419 static void
bouquet_remove_service(bouquet_t * bq,service_t * s,int delconf)420 bouquet_remove_service(bouquet_t *bq, service_t *s, int delconf)
421 {
422   tvhtrace(LS_BOUQUET, "remove service %s from %s",
423            s->s_nicename, bq->bq_name ?: "<unknown>");
424   idnode_set_remove(bq->bq_services, &s->s_id);
425   if (delconf)
426     bouquet_unmap_channel(bq, s);
427 }
428 
429 /*
430  *
431  */
432 void
bouquet_completed(bouquet_t * bq,uint32_t seen)433 bouquet_completed(bouquet_t *bq, uint32_t seen)
434 {
435   idnode_set_t *remove;
436   service_t *s;
437   service_lcn_t *lcn, *lcn_next;
438   size_t z;
439 
440   if (!bq)
441     return;
442 
443   if (seen != bq->bq_services_seen) {
444     bq->bq_services_seen = seen;
445     bq->bq_saveflag = 1;
446   }
447 
448   tvhtrace(LS_BOUQUET, "%s: completed: enabled=%d active=%zi old=%zi seen=%u",
449             bq->bq_name ?: "", bq->bq_enabled, bq->bq_active_services->is_count,
450             bq->bq_services->is_count, seen);
451 
452   if (!bq->bq_enabled)
453     goto save;
454 
455   /* Add/Remove services */
456   remove = idnode_set_create(0);
457   for (z = 0; z < bq->bq_services->is_count; z++)
458     if (!idnode_set_exists(bq->bq_active_services, bq->bq_services->is_array[z]))
459       idnode_set_add(remove, bq->bq_services->is_array[z], NULL, NULL);
460   for (z = 0; z < remove->is_count; z++)
461     bouquet_remove_service(bq, (service_t *)remove->is_array[z], 1);
462   idnode_set_free(remove);
463 
464   /* Remove no longer used LCNs */
465   for (z = 0; z < bq->bq_services->is_count; z++) {
466     s = (service_t *)bq->bq_services->is_array[z];
467     for (lcn = LIST_FIRST(&s->s_lcns); lcn; lcn = lcn_next) {
468       lcn_next = LIST_NEXT(lcn, sl_link);
469       if (lcn->sl_bouquet != bq) continue;
470       if (!lcn->sl_seen) {
471         LIST_REMOVE(lcn, sl_link);
472         free(lcn);
473       } else {
474         lcn->sl_seen = 0;
475       }
476     }
477   }
478 
479 
480   idnode_set_free(bq->bq_active_services);
481   bq->bq_active_services = idnode_set_create(1);
482 
483 save:
484   if (bq->bq_saveflag) {
485     bq->bq_saveflag = 0;
486     idnode_changed(&bq->bq_id);
487   }
488 }
489 
490 /*
491  *
492  */
493 void
bouquet_map_to_channels(bouquet_t * bq)494 bouquet_map_to_channels(bouquet_t *bq)
495 {
496   service_t *t;
497   size_t z;
498 
499   for (z = 0; z < bq->bq_services->is_count; z++) {
500     t = (service_t *)bq->bq_services->is_array[z];
501     if (bq->bq_enabled && bq->bq_maptoch) {
502       bouquet_map_channel(bq, t);
503     } else {
504       bouquet_unmap_channel(bq, t);
505     }
506   }
507 
508   if (!bq->bq_enabled) {
509     if (bq->bq_services->is_count) {
510       idnode_set_free(bq->bq_services);
511       bq->bq_services = idnode_set_create(1);
512       bq->bq_saveflag = 1;
513     }
514     if (bq->bq_active_services->is_count) {
515       idnode_set_free(bq->bq_active_services);
516       bq->bq_active_services = idnode_set_create(1);
517     }
518   }
519 }
520 
521 /*
522  *
523  */
524 void
bouquet_notify_channels(bouquet_t * bq)525 bouquet_notify_channels(bouquet_t *bq)
526 {
527   idnode_list_mapping_t *ilm;
528   service_t *t;
529   size_t z;
530 
531   for (z = 0; z < bq->bq_services->is_count; z++) {
532     t = (service_t *)bq->bq_services->is_array[z];
533     LIST_FOREACH(ilm, &t->s_channels, ilm_in1_link)
534       if (((channel_t *)ilm->ilm_in2)->ch_bouquet == bq)
535         idnode_notify_changed(ilm->ilm_in2);
536   }
537 }
538 
539 /*
540  *
541  */
542 uint64_t
bouquet_get_channel_number(bouquet_t * bq,service_t * t)543 bouquet_get_channel_number(bouquet_t *bq, service_t *t)
544 {
545   service_lcn_t *tl;
546 
547   LIST_FOREACH(tl, &t->s_lcns, sl_link)
548     if (tl->sl_bouquet == bq)
549       return (int64_t)tl->sl_lcn;
550   return 0;
551 }
552 
553 /*
554  *
555  */
556 static const char *
bouquet_get_tag_name(bouquet_t * bq,service_t * t)557 bouquet_get_tag_name(bouquet_t *bq, service_t *t)
558 {
559   return 0;
560 }
561 
562 /**
563  *
564  */
565 void
bouquet_delete(bouquet_t * bq)566 bouquet_delete(bouquet_t *bq)
567 {
568   char ubuf[UUID_HEX_SIZE];
569   if (bq == NULL) return;
570   bq->bq_enabled = 0;
571   bouquet_map_to_channels(bq);
572   if (!bq->bq_shield) {
573     hts_settings_remove("bouquet/%s", idnode_uuid_as_str(&bq->bq_id, ubuf));
574     bouquet_destroy(bq);
575   } else {
576     idnode_set_free(bq->bq_services);
577     bq->bq_services = idnode_set_create(1);
578     idnode_changed(&bq->bq_id);
579   }
580 }
581 
582 /**
583  *
584  */
585 void
bouquet_change_comment(bouquet_t * bq,const char * comment,int replace)586 bouquet_change_comment ( bouquet_t *bq, const char *comment, int replace )
587 {
588   if (!replace && bq->bq_comment && bq->bq_comment[0])
589     return;
590   free(bq->bq_comment);
591   bq->bq_comment = comment ? strdup(comment) : NULL;
592   bq->bq_saveflag = 1;
593 }
594 
595 /*
596  *
597  */
598 void
bouquet_scan(bouquet_t * bq)599 bouquet_scan ( bouquet_t *bq )
600 {
601   void mpegts_mux_bouquet_rescan ( const char *src, const char *extra );
602   void iptv_bouquet_trigger_by_uuid( const char *uuid );
603 #if ENABLE_IPTV
604   if (bq->bq_src && strncmp(bq->bq_src, "iptv-network://", 15) == 0)
605     return iptv_bouquet_trigger_by_uuid(bq->bq_src + 15);
606 #endif
607   if (bq->bq_src && strncmp(bq->bq_src, "exturl://", 9) == 0)
608     return bouquet_download_trigger(bq);
609   mpegts_mux_bouquet_rescan(bq->bq_src, bq->bq_comment);
610   bq->bq_rescan = 0;
611 }
612 
613 /*
614  *
615  */
616 void
bouquet_detach(channel_t * ch)617 bouquet_detach ( channel_t *ch )
618 {
619   bouquet_t *bq = ch->ch_bouquet;
620   idnode_list_mapping_t *ilm;
621   service_lcn_t *tl;
622   service_t *t;
623   int64_t n = 0;
624 
625   if (!bq)
626     return;
627   LIST_FOREACH(ilm, &ch->ch_services, ilm_in2_link) {
628     t = (service_t *)ilm->ilm_in1;
629     LIST_FOREACH(tl, &t->s_lcns, sl_link)
630       if (tl->sl_bouquet == bq) {
631         n = (int64_t)tl->sl_lcn;
632         goto found;
633       }
634   }
635 found:
636   if (n) {
637     n += (int64_t)ch->ch_bouquet->bq_lcn_offset * CHANNEL_SPLIT;
638     ch->ch_number = n;
639   }
640   ch->ch_bouquet = NULL;
641   idnode_changed(&ch->ch_id);
642 }
643 
644 /* **************************************************************************
645  * Class definition
646  * **************************************************************************/
647 
648 static htsmsg_t *
bouquet_class_save(idnode_t * self,char * filename,size_t fsize)649 bouquet_class_save(idnode_t *self, char *filename, size_t fsize)
650 {
651   bouquet_t *bq = (bouquet_t *)self;
652   htsmsg_t *c = htsmsg_create_map();
653   char ubuf[UUID_HEX_SIZE];
654   idnode_save(&bq->bq_id, c);
655   snprintf(filename, fsize, "bouquet/%s", idnode_uuid_as_str(&bq->bq_id, ubuf));
656   if (bq->bq_shield)
657     htsmsg_add_bool(c, "shield", 1);
658   bq->bq_saveflag = 0;
659   return c;
660 }
661 
662 static void
bouquet_class_delete(idnode_t * self)663 bouquet_class_delete(idnode_t *self)
664 {
665   bouquet_delete((bouquet_t *)self);
666 }
667 
668 static const char *
bouquet_class_get_title(idnode_t * self,const char * lang)669 bouquet_class_get_title (idnode_t *self, const char *lang)
670 {
671   bouquet_t *bq = (bouquet_t *)self;
672 
673   if (bq->bq_comment && bq->bq_comment[0] != '\0')
674     return bq->bq_comment;
675   return bq->bq_name ?: "";
676 }
677 
678 /* exported for others */
679 htsmsg_t *
bouquet_class_get_list(void * o,const char * lang)680 bouquet_class_get_list(void *o, const char *lang)
681 {
682   htsmsg_t *m = htsmsg_create_map();
683   htsmsg_add_str(m, "type",  "api");
684   htsmsg_add_str(m, "uri",   "bouquet/list");
685   htsmsg_add_str(m, "event", "bouquet");
686   return m;
687 }
688 
689 static void
bouquet_class_enabled_notify(void * obj,const char * lang)690 bouquet_class_enabled_notify ( void *obj, const char *lang )
691 {
692   bouquet_t *bq = obj;
693 
694   if (bq->bq_enabled)
695     bouquet_scan(bq);
696   bouquet_map_to_channels(bq);
697 }
698 
699 static void
bouquet_class_maptoch_notify(void * obj,const char * lang)700 bouquet_class_maptoch_notify ( void *obj, const char *lang )
701 {
702   bouquet_map_to_channels((bouquet_t *)obj);
703 }
704 
705 static void
bouquet_class_lcn_offset_notify(void * obj,const char * lang)706 bouquet_class_lcn_offset_notify ( void *obj, const char *lang )
707 {
708   if (((bouquet_t *)obj)->bq_in_load)
709     return;
710   bouquet_notify_channels((bouquet_t *)obj);
711 }
712 
713 static idnode_slist_t bouquest_class_mapopt_slist[] = {
714   {
715     .id   = "mapnolcn",
716     .name = N_("Map zero-numbered channels"),
717     .off  = offsetof(bouquet_t, bq_mapnolcn),
718   },
719   {
720     .id   = "mapnoname",
721     .name = N_("Map unnamed channels"),
722     .off  = offsetof(bouquet_t, bq_mapnoname),
723   },
724   {
725     .id   = "mapradio",
726     .name = N_("Map radio channels"),
727     .off  = offsetof(bouquet_t, bq_mapradio),
728   },
729   {
730     .id   = "encrypted",
731     .name = N_("Map encrypted services"),
732     .off  = offsetof(bouquet_t, bq_mapencrypted),
733   },
734   {
735     .id   = "merge_name",
736     .name = N_("Merge same name"),
737     .off  = offsetof(bouquet_t, bq_mapmergename),
738   },
739   {}
740 };
741 
742 static htsmsg_t *
bouquet_class_mapopt_enum(void * obj,const char * lang)743 bouquet_class_mapopt_enum ( void *obj, const char *lang )
744 {
745   return idnode_slist_enum(obj, bouquest_class_mapopt_slist, lang);
746 }
747 
748 static const void *
bouquet_class_mapopt_get(void * obj)749 bouquet_class_mapopt_get ( void *obj )
750 {
751   return idnode_slist_get(obj, bouquest_class_mapopt_slist);
752 }
753 
754 static char *
bouquet_class_mapopt_rend(void * obj,const char * lang)755 bouquet_class_mapopt_rend ( void *obj, const char *lang )
756 {
757   return idnode_slist_rend(obj, bouquest_class_mapopt_slist, lang);
758 }
759 
760 static int
bouquet_class_mapopt_set(void * obj,const void * p)761 bouquet_class_mapopt_set ( void *obj, const void *p )
762 {
763   return idnode_slist_set(obj, bouquest_class_mapopt_slist, p);
764 }
765 
766 static void
bouquet_class_mapopt_notify(void * obj,const char * lang)767 bouquet_class_mapopt_notify ( void *obj, const char *lang )
768 {
769   bouquet_t *bq = obj;
770   service_t *t;
771   size_t z;
772 
773   if (bq->bq_in_load)
774     return;
775   if (bq->bq_enabled && bq->bq_maptoch) {
776     for (z = 0; z < bq->bq_services->is_count; z++) {
777       t = (service_t *)bq->bq_services->is_array[z];
778       if (!bq->bq_mapradio && service_is_radio(t))
779         bouquet_unmap_channel(bq, t);
780       else if (!bq->bq_mapnoname && noname(service_get_channel_name(t)))
781         bouquet_unmap_channel(bq, t);
782       else if (!bq->bq_mapradio && service_is_radio(t))
783         bouquet_unmap_channel(bq, t);
784       else if (!bq->bq_mapencrypted && service_is_encrypted(t))
785         bouquet_unmap_channel(bq, t);
786     }
787   }
788   bouquet_map_to_channels(bq);
789 }
790 
791 static idnode_slist_t bouquest_class_chtag_slist[] = {
792   {
793     .id   = "bouquet_tag",
794     .name = N_("Create bouquet tag"),
795     .off  = offsetof(bouquet_t, bq_chtag),
796   },
797   {
798     .id   = "type_tags",
799     .name = N_("Create type-based tags"),
800     .off  = offsetof(bouquet_t, bq_chtag_type_tags),
801   },
802   {
803     .id   = "providers_tags",
804     .name = N_("Create provider name tags"),
805     .off  = offsetof(bouquet_t, bq_chtag_provider_tags),
806   },
807   {
808     .id   = "network_tags",
809     .name = N_("Create network name tags"),
810     .off  = offsetof(bouquet_t, bq_chtag_network_tags),
811   },
812   {}
813 };
814 
815 static htsmsg_t *
bouquet_class_chtag_enum(void * obj,const char * lang)816 bouquet_class_chtag_enum ( void *obj, const char *lang )
817 {
818   return idnode_slist_enum(obj, bouquest_class_chtag_slist, lang);
819 }
820 
821 static const void *
bouquet_class_chtag_get(void * obj)822 bouquet_class_chtag_get ( void *obj )
823 {
824   return idnode_slist_get(obj, bouquest_class_chtag_slist);
825 }
826 
827 static char *
bouquet_class_chtag_rend(void * obj,const char * lang)828 bouquet_class_chtag_rend ( void *obj, const char *lang )
829 {
830   return idnode_slist_rend(obj, bouquest_class_chtag_slist, lang);
831 }
832 
833 static int
bouquet_class_chtag_set(void * obj,const void * p)834 bouquet_class_chtag_set ( void *obj, const void *p )
835 {
836   return idnode_slist_set(obj, bouquest_class_chtag_slist, p);
837 }
838 
839 static void
bouquet_class_chtag_notify(void * obj,const char * lang)840 bouquet_class_chtag_notify ( void *obj, const char *lang )
841 {
842   bouquet_t *bq = obj;
843   service_t *t;
844   idnode_list_mapping_t *ilm;
845   channel_tag_t *ct;
846   size_t z;
847 
848   if (bq->bq_in_load)
849     return;
850   if (!bq->bq_chtag && bq->bq_enabled && bq->bq_maptoch) {
851     ct = bouquet_tag(bq, 0);
852     if (!ct)
853       return;
854     for (z = 0; z < bq->bq_services->is_count; z++) {
855       t = (service_t *)bq->bq_services->is_array[z];
856       LIST_FOREACH(ilm, &t->s_channels, ilm_in1_link)
857         if (((channel_t *)ilm->ilm_in2)->ch_bouquet == bq)
858           break;
859       if (ilm)
860         channel_tag_unmap((channel_t *)ilm->ilm_in2, ct);
861     }
862   }
863   bouquet_map_to_channels(bq);
864 }
865 
866 static const void *
bouquet_class_chtag_ref_get(void * obj)867 bouquet_class_chtag_ref_get ( void *obj )
868 {
869   bouquet_t *bq = obj;
870 
871   if (bq->bq_chtag_ptr)
872     idnode_uuid_as_str(&bq->bq_chtag_ptr->ct_id, prop_sbuf);
873   else
874     prop_sbuf[0] = '\0';
875   return &prop_sbuf_ptr;
876 }
877 
878 static char *
bouquet_class_chtag_ref_rend(void * obj,const char * lang)879 bouquet_class_chtag_ref_rend ( void *obj, const char *lang )
880 {
881   bouquet_t *bq = obj;
882   if (bq->bq_chtag_ptr)
883     return strdup(bq->bq_chtag_ptr->ct_name ?: "");
884   else
885     return strdup("");
886 }
887 
888 static int
bouquet_class_chtag_ref_set(void * obj,const void * p)889 bouquet_class_chtag_ref_set ( void *obj, const void *p )
890 {
891   bouquet_t *bq = obj;
892 
893   free((char *)bq->bq_chtag_waiting);
894   bq->bq_chtag_waiting = NULL;
895   if (bq->bq_in_load)
896     bq->bq_chtag_waiting = strdup((const char *)p);
897   return 0;
898 }
899 
900 static const void *
bouquet_class_services_get(void * obj)901 bouquet_class_services_get ( void *obj )
902 {
903   htsmsg_t *m = htsmsg_create_map(), *e;
904   bouquet_t *bq = obj;
905   service_t *t;
906   int64_t lcn;
907   const char *tag;
908   size_t z;
909   char ubuf[UUID_HEX_SIZE];
910 
911   /* Add all */
912   for (z = 0; z < bq->bq_services->is_count; z++) {
913     t = (service_t *)bq->bq_services->is_array[z];
914     e = htsmsg_create_map();
915     if ((lcn = bouquet_get_channel_number(bq, t)) != 0)
916       htsmsg_add_s64(e, "lcn", lcn);
917     if ((tag = bouquet_get_tag_name(bq, t)) != NULL)
918       htsmsg_add_str(e, "tag", tag);
919     htsmsg_add_msg(m, idnode_uuid_as_str(&t->s_id, ubuf), e);
920   }
921 
922   return m;
923 }
924 
925 static char *
bouquet_class_services_rend(void * obj,const char * lang)926 bouquet_class_services_rend ( void *obj, const char *lang )
927 {
928   bouquet_t *bq = obj;
929   const char *sc = N_("Service count %zi");
930   char buf[32];
931   snprintf(buf, sizeof(buf), tvh_gettext_lang(lang, sc), bq->bq_services->is_count);
932   return strdup(buf);
933 }
934 
935 static int
bouquet_class_services_set(void * obj,const void * p)936 bouquet_class_services_set ( void *obj, const void *p )
937 {
938   bouquet_t *bq = obj;
939 
940   if (bq->bq_services_waiting)
941     htsmsg_destroy(bq->bq_services_waiting);
942   bq->bq_services_waiting = NULL;
943   if (bq->bq_in_load)
944     bq->bq_services_waiting = htsmsg_copy((htsmsg_t *)p);
945   return 0;
946 }
947 
948 static const void *
bouquet_class_services_count_get(void * obj)949 bouquet_class_services_count_get ( void *obj )
950 {
951   static uint32_t u32;
952   bouquet_t *bq = obj;
953 
954   u32 = bq->bq_services->is_count;
955   return &u32;
956 }
957 
958 static void
bouquet_class_ext_url_notify(void * obj,const char * lang)959 bouquet_class_ext_url_notify ( void *obj, const char *lang )
960 {
961   bouquet_download_trigger((bouquet_t *)obj);
962 }
963 
964 CLASS_DOC(bouquet)
965 PROP_DOC(bouquet_mapping_options)
966 PROP_DOC(bouquet_tagging)
967 
968 const idclass_t bouquet_class = {
969   .ic_class      = "bouquet",
970   .ic_caption    = N_("Bouquets"),
971   .ic_doc        = tvh_doc_bouquet_class,
972   .ic_event      = "bouquet",
973   .ic_perm_def   = ACCESS_ADMIN,
974   .ic_save       = bouquet_class_save,
975   .ic_get_title  = bouquet_class_get_title,
976   .ic_delete     = bouquet_class_delete,
977   .ic_properties = (const property_t[]){
978     {
979       .type     = PT_BOOL,
980       .id       = "enabled",
981       .name     = N_("Enabled"),
982       .desc     = N_("Enable/disable the bouquet."),
983       .def.i    = 1,
984       .off      = offsetof(bouquet_t, bq_enabled),
985       .notify   = bouquet_class_enabled_notify,
986     },
987     {
988       .type     = PT_BOOL,
989       .id       = "maptoch",
990       .name     = N_("Auto-Map to channels"),
991       .desc     = N_("Automatically map channels defined within the "
992                      "bouquet."),
993       .off      = offsetof(bouquet_t, bq_maptoch),
994       .notify   = bouquet_class_maptoch_notify,
995     },
996     {
997       .type     = PT_INT,
998       .islist   = 1,
999       .id       = "mapopt",
1000       .name     = N_("Channel mapping options"),
1001       .desc     = N_("Options to use/used when mapping - see Help for details."),
1002       .doc      = prop_doc_bouquet_mapping_options,
1003       .notify   = bouquet_class_mapopt_notify,
1004       .list     = bouquet_class_mapopt_enum,
1005       .get      = bouquet_class_mapopt_get,
1006       .set      = bouquet_class_mapopt_set,
1007       .rend     = bouquet_class_mapopt_rend,
1008       .opts     = PO_ADVANCED | PO_DOC_NLIST,
1009     },
1010     {
1011       .type     = PT_INT,
1012       .islist   = 1,
1013       .id       = "chtag",
1014       .name     = N_("Create tags"),
1015       .desc     = N_("Create and link these tags to channels when "
1016                      "mapping."),
1017       .doc      = prop_doc_bouquet_tagging,
1018       .notify   = bouquet_class_chtag_notify,
1019       .list     = bouquet_class_chtag_enum,
1020       .get      = bouquet_class_chtag_get,
1021       .set      = bouquet_class_chtag_set,
1022       .rend     = bouquet_class_chtag_rend,
1023       .opts     = PO_ADVANCED | PO_DOC_NLIST,
1024     },
1025     {
1026       .type     = PT_STR,
1027       .id       = "chtag_ref",
1028       .name     = N_("Channel tag reference"),
1029       .get      = bouquet_class_chtag_ref_get,
1030       .set      = bouquet_class_chtag_ref_set,
1031       .rend     = bouquet_class_chtag_ref_rend,
1032       .opts     = PO_RDONLY | PO_HIDDEN | PO_NOUI,
1033     },
1034     {
1035       .type     = PT_STR,
1036       .id       = "name",
1037       .name     = N_("Name"),
1038       .desc     = N_("Name of the bouquet."),
1039       .off      = offsetof(bouquet_t, bq_name),
1040     },
1041     {
1042       .type     = PT_STR,
1043       .id       = "ext_url",
1044       .name     = N_("External URL"),
1045       .desc     = N_("External URL of the bouquet."),
1046       .off      = offsetof(bouquet_t, bq_ext_url),
1047       .opts     = PO_HIDDEN | PO_MULTILINE,
1048       .notify   = bouquet_class_ext_url_notify,
1049     },
1050     {
1051       .type     = PT_BOOL,
1052       .id       = "ssl_peer_verify",
1053       .name     = N_("SSL verify peer"),
1054       .desc     = N_("Verify the SSL certificate."),
1055       .off      = offsetof(bouquet_t, bq_ssl_peer_verify),
1056       .opts     = PO_ADVANCED | PO_HIDDEN | PO_EXPERT,
1057       .notify   = bouquet_class_ext_url_notify,
1058     },
1059     {
1060       .type     = PT_U32,
1061       .id       = "ext_url_period",
1062       .name     = N_("Re-fetch period (mins)"),
1063       .desc     = N_("Re-fetch the bouquet every x minutes."),
1064       .off      = offsetof(bouquet_t, bq_ext_url_period),
1065       .opts     = PO_ADVANCED | PO_HIDDEN,
1066       .notify   = bouquet_class_ext_url_notify,
1067       .def.i    = 60
1068     },
1069     {
1070       .type     = PT_STR,
1071       .id       = "source",
1072       .name     = N_("Source"),
1073       .desc     = N_("Bouquet source."),
1074       .off      = offsetof(bouquet_t, bq_src),
1075       .opts     = PO_RDONLY | PO_ADVANCED,
1076     },
1077     {
1078       .type     = PT_STR,
1079       .islist   = 1,
1080       .id       = "services",
1081       .name     = N_("Services"),
1082       .desc     = N_("Number of services."),
1083       .get      = bouquet_class_services_get,
1084       .set      = bouquet_class_services_set,
1085       .rend     = bouquet_class_services_rend,
1086       .opts     = PO_RDONLY | PO_HIDDEN,
1087     },
1088     {
1089       .type     = PT_U32,
1090       .id       = "services_seen",
1091       .name     = N_("Services seen"),
1092       .desc     = N_("Total number of services seen."),
1093       .off      = offsetof(bouquet_t, bq_services_seen),
1094       .opts     = PO_RDONLY,
1095     },
1096     {
1097       .type     = PT_U32,
1098       .id       = "services_count",
1099       .name     = N_("Services"),
1100       .desc     = N_("Total number of services."),
1101       .get      = bouquet_class_services_count_get,
1102       .opts     = PO_RDONLY | PO_NOSAVE,
1103     },
1104     {
1105       .type     = PT_STR,
1106       .id       = "comment",
1107       .name     = N_("Comment"),
1108       .desc     = N_("Free-form text field, enter whatever you like."),
1109       .off      = offsetof(bouquet_t, bq_comment),
1110     },
1111     {
1112       .type     = PT_U32,
1113       .id       = "lcn_off",
1114       .name     = N_("Channel number offset"),
1115       .desc     = N_("Offset the mapped channel numbers by x "
1116                      "(value here + channel number)."),
1117       .off      = offsetof(bouquet_t, bq_lcn_offset),
1118       .notify   = bouquet_class_lcn_offset_notify,
1119       .opts     = PO_ADVANCED
1120     },
1121     {}
1122   }
1123 };
1124 
1125 /**
1126  *
1127  */
1128 static void
bouquet_download_trigger0(void * aux)1129 bouquet_download_trigger0(void *aux)
1130 {
1131   bouquet_download_t *bqd = aux;
1132   bouquet_t *bq = bqd->bq;
1133 
1134   download_start(&bqd->download, bq->bq_ext_url, bqd);
1135   mtimer_arm_rel(&bqd->timer, bouquet_download_trigger0, bqd,
1136                  sec2mono(MAX(1, bq->bq_ext_url_period) * 60));
1137 }
1138 
1139 static void
bouquet_download_trigger(bouquet_t * bq)1140 bouquet_download_trigger(bouquet_t *bq)
1141 {
1142   bouquet_download_t *bqd = bq->bq_download;
1143   if (bqd) {
1144     bqd->download.ssl_peer_verify = bq->bq_ssl_peer_verify;
1145     bouquet_download_trigger0(bqd);
1146   }
1147 }
1148 
1149 static void
bouquet_download_stop(void * aux)1150 bouquet_download_stop(void *aux)
1151 {
1152   bouquet_download_t *bqd = aux;
1153   mtimer_disarm(&bqd->timer);
1154 }
1155 
1156 static int
parse_enigma2(bouquet_t * bq,char * data)1157 parse_enigma2(bouquet_t *bq, char *data)
1158 {
1159   extern service_t *mpegts_service_find_e2(uint32_t stype, uint32_t sid,
1160                                            uint32_t tsid, uint32_t onid,
1161                                            uint32_t hash);
1162   char *argv[11], *p, *tagname = NULL, *name;
1163   long lv, stype, sid, tsid, onid, hash;
1164   uint32_t seen = 0;
1165   int n, ver = 2;
1166 
1167   while (*data) {
1168     if (strncmp(data, "#NAME ", 6) == 0) {
1169       for (data += 6, p = data; *data && *data != '\r' && *data != '\n'; data++);
1170       if (*data) { *data = '\0'; data++; }
1171       if (bq->bq_name == NULL || bq->bq_name[0] == '\0')
1172         bq->bq_name = strdup(p);
1173     } if (strncmp(data, "#SERVICE ", 9) == 0) {
1174       data += 9, p = data;
1175 service:
1176       while (*data && *data != '\r' && *data != '\n') data++;
1177       if (*data) { *data = '\0'; data++; }
1178       n = http_tokenize(p, argv, ARRAY_SIZE(argv), ':');
1179       if (n >= 10) {
1180         if (strtol(argv[0], NULL, 0) != 1) goto next;  /* item type */
1181         lv = strtol(argv[1], NULL, 16);                /* service flags? */
1182         if (lv != 0 && lv != 0x64) goto next;
1183         stype = strtol(argv[2], NULL, 16);             /* DVB service type */
1184         sid   = strtol(argv[3], NULL, 16);             /* DVB service ID */
1185         tsid  = strtol(argv[4], NULL, 16);
1186         onid  = strtol(argv[5], NULL, 16);
1187         hash  = strtol(argv[6], NULL, 16);
1188         name  = n > 10 ? argv[10] : NULL;
1189         if (lv == 0) {
1190           service_t *s = mpegts_service_find_e2(stype, sid, tsid, onid, hash);
1191           if (s)
1192             bouquet_add_service(bq, s, ((int64_t)++seen) * CHANNEL_SPLIT, tagname);
1193         } else if (lv == 0x64) {
1194           tagname = name;
1195         }
1196       }
1197     } else if (strncmp(data, "#SERVICE: ", 10) == 0) {
1198       ver = 1, data += 10, p = data;
1199       goto service;
1200     } else {
1201       while (*data && *data != '\r' && *data != '\n') data++;
1202     }
1203 next:
1204     while (*data && (*data == '\r' || *data == '\n')) data++;
1205   }
1206   bouquet_completed(bq, seen);
1207   tvhinfo(LS_BOUQUET, "parsed Enigma%d bouquet %s (%d services)", ver, bq->bq_name, seen);
1208   return 0;
1209 }
1210 
1211 static int
bouquet_download_process(void * aux,const char * last_url,const char * host_url,char * data,size_t len)1212 bouquet_download_process(void *aux, const char *last_url, const char *host_url,
1213                          char *data, size_t len)
1214 {
1215   bouquet_download_t *bqd = aux;
1216   while (*data) {
1217     while (*data && *data < ' ') data++;
1218     if (*data && !strncmp(data, "#NAME ", 6))
1219       return parse_enigma2(bqd->bq, data);
1220   }
1221   return 0;
1222 }
1223 
1224 /**
1225  *
1226  */
1227 void
bouquet_init(void)1228 bouquet_init(void)
1229 {
1230   htsmsg_t *c, *m;
1231   htsmsg_field_t *f;
1232   bouquet_t *bq;
1233 
1234   RB_INIT(&bouquets);
1235   idclass_register(&bouquet_class);
1236 
1237   /* Load */
1238   if ((c = hts_settings_load("bouquet")) != NULL) {
1239     HTSMSG_FOREACH(f, c) {
1240       if (!(m = htsmsg_field_get_map(f))) continue;
1241       bq = bouquet_create(f->hmf_name, m, NULL, NULL);
1242       if (bq)
1243         bq->bq_saveflag = 0;
1244     }
1245     htsmsg_destroy(c);
1246   }
1247 }
1248 
1249 void
bouquet_service_resolve(void)1250 bouquet_service_resolve(void)
1251 {
1252   bouquet_t *bq;
1253   htsmsg_t *e;
1254   htsmsg_field_t *f;
1255   service_t *s;
1256   int64_t lcn;
1257   const char *tag;
1258   int saveflag;
1259 
1260   lock_assert(&global_lock);
1261 
1262   RB_FOREACH(bq, &bouquets, bq_link)  {
1263     if (!bq->bq_services_waiting)
1264       continue;
1265     saveflag = bq->bq_saveflag;
1266     if (bq->bq_enabled) {
1267       HTSMSG_FOREACH(f, bq->bq_services_waiting) {
1268         if ((e = htsmsg_field_get_map(f)) == NULL) continue;
1269         lcn = htsmsg_get_s64_or_default(e, "lcn", 0);
1270         tag = htsmsg_get_str(e, "tag");
1271         s = service_find_by_identifier(f->hmf_name);
1272         if (s)
1273           bouquet_add_service(bq, s, lcn, tag);
1274       }
1275     }
1276     htsmsg_destroy(bq->bq_services_waiting);
1277     bq->bq_services_waiting = NULL;
1278     bq->bq_saveflag = saveflag;
1279   }
1280 }
1281 
1282 void
bouquet_done(void)1283 bouquet_done(void)
1284 {
1285   bouquet_t *bq;
1286 
1287   pthread_mutex_lock(&global_lock);
1288   while ((bq = RB_FIRST(&bouquets)) != NULL)
1289     bouquet_destroy(bq);
1290   pthread_mutex_unlock(&global_lock);
1291 }
1292