1 /*
2  *  tvheadend, Wizard
3  *  Copyright (C) 2015,2016 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 /*
20  * Use http://spec.commonmark.org/ as reference for description formatting.
21  */
22 
23 #include "tvheadend.h"
24 #include "htsbuf.h"
25 #include "config.h"
26 #include "access.h"
27 #include "settings.h"
28 #include "input.h"
29 #include "input/mpegts/iptv/iptv_private.h"
30 #include "service_mapper.h"
31 #include "wizard.h"
32 
33 /*
34  *
35  */
36 
empty_get(void * o)37 static const void *empty_get(void *o)
38 {
39   prop_sbuf[0] = '\0';
40   return &prop_sbuf_ptr;
41 }
42 
icon_get(void * o)43 static const void *icon_get(void *o)
44 {
45   strcpy(prop_sbuf, "static/img/logobig.png");
46   return &prop_sbuf_ptr;
47 }
48 
description_get(wizard_page_t * page,const char ** doc)49 static const void *description_get(wizard_page_t *page, const char **doc)
50 {
51   htsbuf_queue_t q;
52 
53   if (!page->desc) {
54     htsbuf_queue_init(&q, 0);
55     for (; *doc; doc++) {
56       if (*doc[0] == '\xff') {
57         htsbuf_append_str(&q, tvh_gettext_lang(config.language_ui, *doc + 2));
58       } else {
59         htsbuf_append_str(&q, *doc);
60       }
61     }
62     page->desc = htsbuf_to_string(&q);
63     htsbuf_queue_flush(&q);
64   }
65   return &page->desc;
66 }
67 
68 #define SPECIAL_PROP(idval, getfcn) { \
69   .type = PT_STR, \
70   .id   = idval, \
71   .name = "", \
72   .get  = getfcn, \
73   .opts = PO_RDONLY | PO_NOUI \
74 }
75 
76 #define PREV_BUTTON(page) SPECIAL_PROP("page_prev_" STRINGIFY(page), empty_get)
77 #define NEXT_BUTTON(page) SPECIAL_PROP("page_next_" STRINGIFY(page), empty_get)
78 #define LAST_BUTTON()     SPECIAL_PROP("page_last", empty_get)
79 #define ICON()            SPECIAL_PROP("icon", icon_get)
80 #define DESCRIPTION(page) SPECIAL_PROP("description", wizard_description_##page)
81 #define PROGRESS(fcn)     SPECIAL_PROP("progress", fcn)
82 
83 #define DESCRIPTION_FCN(page) \
84 extern const char *tvh_doc_wizard_##page[]; \
85 static const void *wizard_description_##page(void *o) \
86 { \
87   return description_get(o, tvh_doc_wizard_##page); \
88 }
89 
90 #define BASIC_STR_OPS(stru, field) \
91 static const void *wizard_get_value_##field(void *o) \
92 { \
93   wizard_page_t *p = o; \
94   stru *w = p->aux; \
95   snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", w->field); \
96   return &prop_sbuf_ptr; \
97 } \
98 static int wizard_set_value_##field(void *o, const void *v) \
99 { \
100   wizard_page_t *p = o; \
101   stru *w = p->aux; \
102   snprintf(w->field, sizeof(w->field), "%s", (const char *)v); \
103   return 1; \
104 }
105 
106 /*
107  *
108  */
109 
page_free(wizard_page_t * page)110 static void page_free(wizard_page_t *page)
111 {
112   free(page->desc);
113   free(page->aux);
114   free((char *)page->idnode.in_class);
115   free(page);
116 }
117 
page_init(const char * name,const char * class_name,const char * caption)118 static wizard_page_t *page_init
119   (const char *name, const char *class_name, const char *caption)
120 {
121   wizard_page_t *page = calloc(1, sizeof(*page));
122   idclass_t *ic = calloc(1, sizeof(*ic));
123   page->name = name;
124   page->idnode.in_class = ic;
125   ic->ic_caption = caption;
126   ic->ic_class = ic->ic_event = class_name;
127   ic->ic_perm_def = ACCESS_ADMIN;
128   page->free = page_free;
129   return page;
130 }
131 
132 /*
133  * Hello
134  */
135 
136 typedef struct wizard_hello {
137   char ui_lang[32];
138   char epg_lang1[32];
139   char epg_lang2[32];
140   char epg_lang3[32];
141 } wizard_hello_t;
142 
143 
hello_changed(idnode_t * in)144 static void hello_changed(idnode_t *in)
145 {
146   wizard_page_t *p = (wizard_page_t *)in;
147   wizard_hello_t *w = p->aux;
148   char buf[32];
149   size_t l = 0;
150   int save = 0;
151 
152   if (w->ui_lang[0] && strcmp(config.language_ui ?: "", w->ui_lang)) {
153     free(config.language_ui);
154     config.language_ui = strdup(w->ui_lang);
155     save = 1;
156   }
157   buf[0] = '\0';
158   if (w->epg_lang1[0])
159     tvh_strlcatf(buf, sizeof(buf), l, "%s", w->epg_lang1);
160   if (w->epg_lang2[0])
161     tvh_strlcatf(buf, sizeof(buf), l, "%s%s", l > 0 ? "," : "", w->epg_lang2);
162   if (w->epg_lang3[0])
163     tvh_strlcatf(buf, sizeof(buf), l, "%s%s", l > 0 ? "," : "", w->epg_lang3);
164   if (buf[0] && strcmp(buf, config.language ?: "")) {
165     free(config.language);
166     config.language = strdup(buf);
167     save = 1;
168   }
169   if (save)
170     idnode_changed(&config.idnode);
171 }
172 
BASIC_STR_OPS(wizard_hello_t,ui_lang)173 BASIC_STR_OPS(wizard_hello_t, ui_lang)
174 BASIC_STR_OPS(wizard_hello_t, epg_lang1)
175 BASIC_STR_OPS(wizard_hello_t, epg_lang2)
176 BASIC_STR_OPS(wizard_hello_t, epg_lang3)
177 
178 DESCRIPTION_FCN(hello)
179 
180 wizard_page_t *wizard_hello(const char *lang)
181 {
182   static const property_group_t groups[] = {
183     {
184       .name     = N_("Web interface"),
185       .number   = 1,
186     },
187     {
188       .name     = N_("EPG Language (priority order)"),
189       .number   = 2,
190     },
191     {}
192   };
193   static const property_t props[] = {
194     {
195       .type     = PT_STR,
196       .id       = "ui_lang",
197       .name     = N_("Language"),
198       .desc     = N_("Select the default user interface language. "
199                      "This can be overridden later in \"Access Entries\" "
200                      "on a per-user basis."),
201       .get      = wizard_get_value_ui_lang,
202       .set      = wizard_set_value_ui_lang,
203       .list     = language_get_ui_list,
204       .group    = 1
205     },
206     {
207       .type     = PT_STR,
208       .id       = "epg_lang1",
209       .name     = N_("Language 1"),
210       .desc     = N_("Select high priority (default) EPG language."),
211       .get      = wizard_get_value_epg_lang1,
212       .set      = wizard_set_value_epg_lang1,
213       .list     = language_get_list,
214       .group    = 2
215     },
216     {
217       .type     = PT_STR,
218       .id       = "epg_lang2",
219       .name     = N_("Language 2"),
220       .desc     = N_("Select medium priority EPG language."),
221       .get      = wizard_get_value_epg_lang2,
222       .set      = wizard_set_value_epg_lang2,
223       .list     = language_get_list,
224       .group    = 2
225     },
226     {
227       .type     = PT_STR,
228       .id       = "epg_lang3",
229       .name     = N_("Language 3"),
230       .desc     = N_("Select low priority EPG language."),
231       .get      = wizard_get_value_epg_lang3,
232       .set      = wizard_set_value_epg_lang3,
233       .list     = language_get_list,
234       .group    = 2
235     },
236     ICON(),
237     DESCRIPTION(hello),
238     NEXT_BUTTON(login),
239     {}
240   };
241   wizard_page_t *page =
242     page_init("hello", "wizard_hello",
243     N_("Welcome - Tvheadend - your TV streaming server and video recorder"));
244   idclass_t *ic = (idclass_t *)page->idnode.in_class;
245   wizard_hello_t *w;
246   htsmsg_t *m;
247   htsmsg_field_t *f;
248   const char *s;
249   int idx;
250 
251   ic->ic_properties = props;
252   ic->ic_groups = groups;
253   ic->ic_changed = hello_changed;
254   page->aux = w = calloc(1, sizeof(wizard_hello_t));
255 
256   if (config.language_ui)
257     strlcpy(w->ui_lang, config.language_ui, sizeof(w->ui_lang));
258 
259   m = htsmsg_csv_2_list(config.language, ',');
260   f = m ? HTSMSG_FIRST(m) : NULL;
261   for (idx = 0; idx < 3 && f != NULL; idx++) {
262     s = htsmsg_field_get_string(f);
263     if (s == NULL) break;
264     switch (idx) {
265     case 0: strlcpy(w->epg_lang1, s, sizeof(w->epg_lang1)); break;
266     case 1: strlcpy(w->epg_lang2, s, sizeof(w->epg_lang2)); break;
267     case 2: strlcpy(w->epg_lang3, s, sizeof(w->epg_lang3)); break;
268     }
269     f = HTSMSG_NEXT(f);
270   }
271   htsmsg_destroy(m);
272 
273   return page;
274 }
275 
276 /*
277  * Login/Network access
278  */
279 
280 typedef struct wizard_login {
281   char network[256];
282   char admin_username[32];
283   char admin_password[32];
284   char username[32];
285   char password[32];
286 } wizard_login_t;
287 
288 
login_changed(idnode_t * in)289 static void login_changed(idnode_t *in)
290 {
291   wizard_page_t *p = (wizard_page_t *)in;
292   wizard_login_t *w = p->aux;
293   access_entry_t *ae, *ae_next;
294   passwd_entry_t *pw, *pw_next;
295   htsmsg_t *conf, *list;
296   const char *s;
297 
298   for (ae = TAILQ_FIRST(&access_entries); ae; ae = ae_next) {
299     ae_next = TAILQ_NEXT(ae, ae_link);
300     if (ae->ae_wizard)
301       access_entry_destroy(ae, 1);
302   }
303 
304   for (pw = TAILQ_FIRST(&passwd_entries); pw; pw = pw_next) {
305     pw_next = TAILQ_NEXT(pw, pw_link);
306     if (pw->pw_wizard)
307       passwd_entry_destroy(pw, 1);
308   }
309 
310   s = w->admin_username[0] ? w->admin_username : "*";
311   conf = htsmsg_create_map();
312   htsmsg_add_bool(conf, "enabled", 1);
313   htsmsg_add_str(conf, "prefix", w->network);
314   htsmsg_add_str(conf, "username", s);
315   list = htsmsg_create_list();
316   htsmsg_add_str(list, NULL, "basic");
317   htsmsg_add_str(list, NULL, "advanced");
318   htsmsg_add_str(list, NULL, "htsp");
319   htsmsg_add_msg(conf, "streaming", list);
320   list = htsmsg_create_list();
321   htsmsg_add_str(list, NULL, "basic");
322   htsmsg_add_str(list, NULL, "htsp");
323   htsmsg_add_msg(conf, "dvr", list);
324   htsmsg_add_bool(conf, "webui", 1);
325   htsmsg_add_bool(conf, "admin", 1);
326   htsmsg_add_str(conf, "comment", ACCESS_WIZARD_COMMENT);
327   ae = access_entry_create(NULL, conf);
328   if (ae) {
329     ae->ae_wizard = 1;
330     idnode_changed(&ae->ae_id);
331   }
332   htsmsg_destroy(conf);
333 
334   if (s[0] != '*' && w->admin_password[0]) {
335     conf = htsmsg_create_map();
336     htsmsg_add_bool(conf, "enabled", 1);
337     htsmsg_add_str(conf, "username", s);
338     htsmsg_add_str(conf, "password", w->admin_password);
339     pw = passwd_entry_create(NULL, conf);
340     if (pw) {
341       pw->pw_wizard = 1;
342       idnode_changed(&pw->pw_id);
343     }
344     htsmsg_destroy(conf);
345   }
346 
347   if (w->username[0]) {
348     s = w->username[0] ? w->username : "*";
349     conf = htsmsg_create_map();
350     htsmsg_add_bool(conf, "enabled", 1);
351     htsmsg_add_str(conf, "prefix", w->network);
352     htsmsg_add_str(conf, "username", s);
353     list = htsmsg_create_list();
354     htsmsg_add_str(list, NULL, "basic");
355     htsmsg_add_str(list, NULL, "htsp");
356     htsmsg_add_msg(conf, "streaming", list);
357     list = htsmsg_create_list();
358     htsmsg_add_str(list, NULL, "basic");
359     htsmsg_add_str(list, NULL, "htsp");
360     htsmsg_add_msg(conf, "dvr", list);
361     htsmsg_add_bool(conf, "webui", 1);
362     htsmsg_add_str(conf, "comment", ACCESS_WIZARD_COMMENT);
363     ae = access_entry_create(NULL, conf);
364     if (ae) {
365       ae->ae_wizard = 1;
366       idnode_changed(&ae->ae_id);
367     }
368     htsmsg_destroy(conf);
369 
370     if (s[0] != '*' && w->password[0]) {
371       conf = htsmsg_create_map();
372       htsmsg_add_bool(conf, "enabled", 1);
373       htsmsg_add_str(conf, "username", s);
374       htsmsg_add_str(conf, "password", w->password);
375       pw = passwd_entry_create(NULL, conf);
376       if (pw) {
377         pw->pw_wizard = 1;
378         idnode_changed(&pw->pw_id);
379       }
380       htsmsg_destroy(conf);
381     }
382   }
383 }
384 
BASIC_STR_OPS(wizard_login_t,network)385 BASIC_STR_OPS(wizard_login_t, network)
386 BASIC_STR_OPS(wizard_login_t, admin_username)
387 BASIC_STR_OPS(wizard_login_t, admin_password)
388 BASIC_STR_OPS(wizard_login_t, username)
389 BASIC_STR_OPS(wizard_login_t, password)
390 
391 DESCRIPTION_FCN(login)
392 
393 wizard_page_t *wizard_login(const char *lang)
394 {
395   static const property_group_t groups[] = {
396     {
397       .name     = N_("Network access"),
398       .number   = 1,
399     },
400     {
401       .name     = N_("Administrator login"),
402       .number   = 2,
403     },
404     {
405       .name     = N_("User login"),
406       .number   = 3,
407     },
408     {}
409   };
410   static const property_t props[] = {
411     {
412       .type     = PT_STR,
413       .id       = "network",
414       .name     = N_("Allowed network"),
415       .desc     = N_("Enter allowed network prefix(es). You can enter a "
416                      "comma-seperated list of prefixes here."),
417       .get      = wizard_get_value_network,
418       .set      = wizard_set_value_network,
419       .group    = 1
420     },
421     {
422       .type     = PT_STR,
423       .id       = "admin_username",
424       .name     = N_("Admin username"),
425       .desc     = N_("Enter an administrator username. "
426                      "Note: do not use the same username as the "
427                      "superuser backdoor account."),
428       .get      = wizard_get_value_admin_username,
429       .set      = wizard_set_value_admin_username,
430       .group    = 2
431     },
432     {
433       .type     = PT_STR,
434       .id       = "admin_password",
435       .name     = N_("Admin password"),
436       .desc     = N_("Enter an administrator password."),
437       .get      = wizard_get_value_admin_password,
438       .set      = wizard_set_value_admin_password,
439       .group    = 2
440     },
441     {
442       .type     = PT_STR,
443       .id       = "username",
444       .name     = N_("Username"),
445       .desc     = N_("Enter a non-admin user username."),
446       .get      = wizard_get_value_username,
447       .set      = wizard_set_value_username,
448       .group    = 3
449     },
450     {
451       .type     = PT_STR,
452       .id       = "password",
453       .name     = N_("Password"),
454       .desc     = N_("Enter a non-admin user password."),
455       .get      = wizard_get_value_password,
456       .set      = wizard_set_value_password,
457       .group    = 3
458     },
459     ICON(),
460     DESCRIPTION(login),
461     PREV_BUTTON(hello),
462     NEXT_BUTTON(network),
463     {}
464   };
465   wizard_page_t *page =
466     page_init("login", "wizard_login",
467     N_("Welcome - Tvheadend - your TV streaming server and video recorder"));
468   idclass_t *ic = (idclass_t *)page->idnode.in_class;
469   wizard_login_t *w;
470   access_entry_t *ae;
471   passwd_entry_t *pw;
472 
473   ic->ic_properties = props;
474   ic->ic_groups = groups;
475   ic->ic_changed = login_changed;
476   page->aux = w = calloc(1, sizeof(wizard_login_t));
477 
478   TAILQ_FOREACH(ae, &access_entries, ae_link) {
479     if (!ae->ae_wizard)
480       continue;
481     if (ae->ae_admin) {
482       htsmsg_t *c = htsmsg_create_map();
483       idnode_save(&ae->ae_id, c);
484       snprintf(w->admin_username, sizeof(w->admin_username), "%s", ae->ae_username);
485       snprintf(w->network, sizeof(w->network), "%s", htsmsg_get_str(c, "prefix") ?: "");
486       htsmsg_destroy(c);
487     } else {
488       snprintf(w->username, sizeof(w->username), "%s", ae->ae_username);
489     }
490   }
491 
492   TAILQ_FOREACH(pw, &passwd_entries, pw_link) {
493     if (!pw->pw_wizard || !pw->pw_username)
494       continue;
495     if (w->admin_username[0] &&
496         strcmp(w->admin_username, pw->pw_username) == 0) {
497       snprintf(w->admin_password, sizeof(w->admin_password), "%s", pw->pw_password);
498     } else if (w->username[0] && strcmp(w->username, pw->pw_username) == 0) {
499       snprintf(w->password, sizeof(w->password), "%s", pw->pw_password);
500     }
501   }
502 
503   return page;
504 }
505 
506 /*
507  * Network settings
508  */
509 #define WIZARD_NETWORKS 6
510 
511 typedef struct wizard_network {
512   char lang        [64];
513   property_t props [WIZARD_NETWORKS * 3 + 10];
514   char tuner       [WIZARD_NETWORKS][64];
515   char tunerid     [WIZARD_NETWORKS][UUID_HEX_SIZE];
516   char network_type[WIZARD_NETWORKS][64];
517   htsmsg_t *network_types[WIZARD_NETWORKS];
518 } wizard_network_t;
519 
network_free(wizard_page_t * page)520 static void network_free(wizard_page_t *page)
521 {
522   wizard_network_t *w = page->aux;
523   int idx;
524 
525   for (idx = 0; idx < WIZARD_NETWORKS; idx++)
526     htsmsg_destroy(w->network_types[idx]);
527   page_free(page);
528 }
529 
network_changed(idnode_t * in)530 static void network_changed(idnode_t *in)
531 {
532   wizard_page_t *p = (wizard_page_t *)in;
533   wizard_network_t *w = p->aux;
534   mpegts_network_t *mn, *mn_next;
535   tvh_input_t *ti;
536   htsmsg_t *m;
537   int idx;
538 
539   LIST_FOREACH(mn, &mpegts_network_all, mn_global_link)
540     if (mn->mn_wizard)
541       mn->mn_wizard_free = 1;
542   for (idx = 0; idx < WIZARD_NETWORKS; idx++) {
543     if (w->network_type[idx][0] == '\0')
544       continue;
545     ti = tvh_input_find_by_uuid(w->tunerid[idx]);
546     if (ti == NULL || ti->ti_wizard_set == NULL)
547       continue;
548     m = htsmsg_create_map();
549     htsmsg_add_str(m, "mpegts_network_type", w->network_type[idx]);
550     ti->ti_wizard_set(ti, m, w->lang[0] ? w->lang : NULL);
551     htsmsg_destroy(m);
552   }
553   for (mn = LIST_FIRST(&mpegts_network_all); mn != NULL; mn = mn_next) {
554     mn_next = LIST_NEXT(mn, mn_global_link);
555     if (mn->mn_wizard_free)
556       mn->mn_delete(mn, 1);
557   }
558 }
559 
560 #define NETWORK_GROUP(num) { \
561   .name     = N_("Network " STRINGIFY(num)), \
562   .number   = num, \
563 }
564 
565 #define NETWORK(num) { \
566   .type = PT_STR, \
567   .id   = "tuner" STRINGIFY(num), \
568   .name = N_("Tuner"), \
569   .desc = N_("Name of the tuner."), \
570   .get  = network_get_tvalue##num, \
571   .opts = PO_RDONLY, \
572   .group = num, \
573 }, { \
574   .type = PT_STR, \
575   .id   = "tunerid" STRINGIFY(num), \
576   .name = "Tuner", \
577   .desc = N_("Name of the tuner."), \
578   .get  = network_get_tidvalue##num, \
579   .set  = network_set_tidvalue##num, \
580   .opts = PO_PERSIST | PO_NOUI, \
581 }, { \
582   .type = PT_STR, \
583   .id   = "network" STRINGIFY(num), \
584   .name = N_("Network type"), \
585   .desc = N_("Select an available network type for this tuner."), \
586   .get  = network_get_value##num, \
587   .set  = network_set_value##num, \
588   .list = network_get_list##num, \
589   .group = num, \
590 }
591 
592 #define NETWORK_FCN(num) \
593 static const void *network_get_tvalue##num(void *o) \
594 { \
595   wizard_page_t *p = o; \
596   wizard_network_t *w = p->aux; \
597   snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", w->tuner[num-1]); \
598   return &prop_sbuf_ptr; \
599 } \
600 static const void *network_get_tidvalue##num(void *o) \
601 { \
602   wizard_page_t *p = o; \
603   wizard_network_t *w = p->aux; \
604   snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", w->tunerid[num-1]); \
605   return &prop_sbuf_ptr; \
606 } \
607 static int network_set_tidvalue##num(void *o, const void *v) \
608 { \
609   wizard_page_t *p = o; \
610   wizard_network_t *w = p->aux; \
611   snprintf(w->tunerid[num-1], sizeof(w->tunerid[num-1]), "%s", (const char *)v); \
612   return 1; \
613 } \
614 static const void *network_get_value##num(void *o) \
615 { \
616   wizard_page_t *p = o; \
617   wizard_network_t *w = p->aux; \
618   snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", w->network_type[num-1]); \
619   return &prop_sbuf_ptr; \
620 } \
621 static int network_set_value##num(void *o, const void *v) \
622 { \
623   wizard_page_t *p = o; \
624   wizard_network_t *w = p->aux; \
625   snprintf(w->network_type[num-1], sizeof(w->network_type[num-1]), "%s", (const char *)v); \
626   return 1; \
627 } \
628 static htsmsg_t *network_get_list##num(void *o, const char *lang) \
629 { \
630   if (o == NULL) return NULL; \
631   wizard_page_t *p = o; \
632   wizard_network_t *w = p->aux; \
633   return htsmsg_copy(w->network_types[num-1]); \
634 }
635 
636 NETWORK_FCN(1)
637 NETWORK_FCN(2)
638 NETWORK_FCN(3)
639 NETWORK_FCN(4)
640 NETWORK_FCN(5)
641 NETWORK_FCN(6)
642 
DESCRIPTION_FCN(network)643 DESCRIPTION_FCN(network)
644 
645 wizard_page_t *wizard_network(const char *lang)
646 {
647   static const property_group_t groups[] = {
648     NETWORK_GROUP(1),
649     NETWORK_GROUP(2),
650     NETWORK_GROUP(3),
651     NETWORK_GROUP(4),
652     NETWORK_GROUP(5),
653     NETWORK_GROUP(6),
654     {}
655   };
656   static const property_t nprops[] = {
657     NETWORK(1),
658     NETWORK(2),
659     NETWORK(3),
660     NETWORK(4),
661     NETWORK(5),
662     NETWORK(6),
663   };
664   static const property_t props[] = {
665     ICON(),
666     DESCRIPTION(network),
667     PREV_BUTTON(login),
668     NEXT_BUTTON(muxes),
669   };
670   wizard_page_t *page = page_init("network", "wizard_network", N_("Network settings"));
671   idclass_t *ic = (idclass_t *)page->idnode.in_class;
672   wizard_network_t *w;
673   mpegts_network_t *mn;
674   tvh_input_t *ti;
675   const char *name;
676   htsmsg_t *m;
677   int idx, nidx = 0;
678 
679   page->aux = w = calloc(1, sizeof(wizard_network_t));
680   ic->ic_groups = groups;
681   ic->ic_properties = w->props;
682   ic->ic_changed = network_changed;
683   page->free = network_free;
684   snprintf(w->lang, sizeof(w->lang), "%s", lang ?: "");
685 
686   for (idx = 0; idx < ARRAY_SIZE(props); idx++)
687     w->props[idx] = props[idx];
688 
689   for (ti = LIST_LAST(tvh_input_t, &tvh_inputs, ti_link); ti;
690        ti = LIST_PREV(ti, tvh_input_t, &tvh_inputs, ti_link)) {
691     if (ti->ti_wizard_get == NULL)
692       continue;
693     m = ti->ti_wizard_get(ti, lang);
694     if (m == NULL)
695       continue;
696     name = htsmsg_get_str(m, "input_name");
697     if (name) {
698       snprintf(w->tuner[nidx], sizeof(w->tuner[nidx]), "%s", name);
699       idnode_uuid_as_str(&ti->ti_id, w->tunerid[nidx]);
700       mn = mpegts_network_find(htsmsg_get_str(m, "mpegts_network"));
701       if (mn) {
702         snprintf(w->network_type[nidx], sizeof(w->network_type[nidx]), "%s",
703                  mn->mn_id.in_class->ic_class);
704       }
705       w->network_types[nidx] = htsmsg_copy(htsmsg_get_list(m, "mpegts_network_types"));
706       w->props[idx++] = nprops[nidx * 3 + 0];
707       w->props[idx++] = nprops[nidx * 3 + 1];
708       w->props[idx++] = nprops[nidx * 3 + 2];
709       nidx++;
710     }
711     htsmsg_destroy(m);
712     if (nidx >= WIZARD_NETWORKS)
713       break;
714   }
715 
716   assert(idx < ARRAY_SIZE(w->props));
717 
718   return page;
719 }
720 
721 /*
722  * Muxes settings
723  */
724 
725 typedef struct wizard_muxes {
726   char lang        [64];
727   property_t props [WIZARD_NETWORKS * 3 + 10];
728   char network     [WIZARD_NETWORKS][64];
729   char networkid   [WIZARD_NETWORKS][UUID_HEX_SIZE];
730   char muxes       [WIZARD_NETWORKS][64];
731   char iptv_url    [WIZARD_NETWORKS][512];
732 } wizard_muxes_t;
733 
muxes_free(wizard_page_t * page)734 static void muxes_free(wizard_page_t *page)
735 {
736   page_free(page);
737 }
738 
muxes_changed(idnode_t * in)739 static void muxes_changed(idnode_t *in)
740 {
741   wizard_page_t *p = (wizard_page_t *)in;
742   wizard_muxes_t *w = p->aux;
743   mpegts_network_t *mn;
744   int idx;
745 
746   for (idx = 0; idx < WIZARD_NETWORKS; idx++) {
747     if (w->networkid[idx][0] == '\0')
748       continue;
749     mn = mpegts_network_find(w->networkid[idx]);
750     if (mn == NULL || !mn->mn_wizard)
751       continue;
752 #if ENABLE_MPEGTS_DVB
753     if (idnode_is_instance(&mn->mn_id, &dvb_network_class) && w->muxes[idx][0]) {
754       dvb_network_scanfile_set((dvb_network_t *)mn, w->muxes[idx]);
755     }
756 #endif
757 #if ENABLE_IPTV
758     if (idnode_is_instance(&mn->mn_id, &iptv_auto_network_class) &&
759                w->iptv_url[idx][0]) {
760       htsmsg_t *m = htsmsg_create_map();
761       htsmsg_add_str(m, "url", w->iptv_url[idx]);
762       htsmsg_add_u32(m, "max_streams", 1);
763       htsmsg_add_bool(m, "bouquet", 1);
764       idnode_load(&mn->mn_id, m);
765       idnode_changed(&mn->mn_id);
766       htsmsg_destroy(m);
767     }
768 #endif
769   }
770 }
771 
muxes_progress_get(void * o)772 static const void *muxes_progress_get(void *o)
773 {
774   wizard_page_t *p = o;
775   wizard_muxes_t *w = p->aux;
776   snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", tvh_gettext_lang(w->lang, N_("Scan progress")));
777   return &prop_sbuf_ptr;
778 }
779 
780 #define MUXES(num) { \
781   .type = PT_STR, \
782   .id   = "network" STRINGIFY(num), \
783   .name = N_("Network"), \
784   .get  = muxes_get_nvalue##num, \
785   .opts = PO_RDONLY, \
786   .group = num, \
787 }, { \
788   .type = PT_STR, \
789   .id   = "networkid" STRINGIFY(num), \
790   .name = "Network", \
791   .get  = muxes_get_idvalue##num, \
792   .set  = muxes_set_idvalue##num, \
793   .opts = PO_PERSIST | PO_NOUI, \
794 }, { \
795   .type = PT_STR, \
796   .id   = "muxes" STRINGIFY(num), \
797   .name = N_("Pre-defined muxes"), \
798   .get  = muxes_get_value##num, \
799   .set  = muxes_set_value##num, \
800   .list = muxes_get_list##num, \
801   .group = num, \
802 }
803 
804 #define MUXES_IPTV(num) { \
805   .type = PT_STR, \
806   .id   = "network" STRINGIFY(num), \
807   .name = N_("Network"), \
808   .desc = N_("Name of the network."), \
809   .get  = muxes_get_nvalue##num, \
810   .opts = PO_RDONLY, \
811   .group = num, \
812 }, { \
813   .type = PT_STR, \
814   .id   = "networkid" STRINGIFY(num), \
815   .name = "Network", \
816   .desc = N_("ID of the network."), \
817   .get  = muxes_get_idvalue##num, \
818   .set  = muxes_set_idvalue##num, \
819   .opts = PO_PERSIST | PO_NOUI, \
820 }, { \
821   .type = PT_STR, \
822   .id   = "muxes" STRINGIFY(num), \
823   .name = N_("URL"), \
824   .desc = N_("URL of the M3U playlist."), \
825   .get  = muxes_get_iptv_value##num, \
826   .set  = muxes_set_iptv_value##num, \
827   .group = num, \
828 }
829 
830 #define MUXES_FCN(num) \
831 static const void *muxes_get_nvalue##num(void *o) \
832 { \
833   wizard_page_t *p = o; \
834   wizard_muxes_t *w = p->aux; \
835   snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", w->network[num-1]); \
836   return &prop_sbuf_ptr; \
837 } \
838 static const void *muxes_get_idvalue##num(void *o) \
839 { \
840   wizard_page_t *p = o; \
841   wizard_muxes_t *w = p->aux; \
842   snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", w->networkid[num-1]); \
843   return &prop_sbuf_ptr; \
844 } \
845 static int muxes_set_idvalue##num(void *o, const void *v) \
846 { \
847   wizard_page_t *p = o; \
848   wizard_muxes_t *w = p->aux; \
849   snprintf(w->networkid[num-1], sizeof(w->networkid[num-1]), "%s", (const char *)v); \
850   return 1; \
851 }
852 
853 MUXES_FCN(1)
854 MUXES_FCN(2)
855 MUXES_FCN(3)
856 MUXES_FCN(4)
857 MUXES_FCN(5)
858 MUXES_FCN(6)
859 
860 #if ENABLE_MPEGTS_DVB
861 
862 #define MUXES_FCN_DVB(num) \
863 static const void *muxes_get_value##num(void *o) \
864 { \
865   wizard_page_t *p = o; \
866   wizard_muxes_t *w = p->aux; \
867   snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", w->muxes[num-1]); \
868   return &prop_sbuf_ptr; \
869 } \
870 static int muxes_set_value##num(void *o, const void *v) \
871 { \
872   wizard_page_t *p = o; \
873   wizard_muxes_t *w = p->aux; \
874   snprintf(w->muxes[num-1], sizeof(w->muxes[num-1]), "%s", (const char *)v); \
875   return 1; \
876 } \
877 static htsmsg_t *muxes_get_list##num(void *o, const char *lang) \
878 { \
879   if (o == NULL) return NULL; \
880   wizard_page_t *p = o; \
881   wizard_muxes_t *w = p->aux; \
882   mpegts_network_t *mn = mpegts_network_find(w->networkid[num-1]); \
883   return mn ? dvb_network_class_scanfile_list(mn, lang) : NULL; \
884 }
885 
886 
887 MUXES_FCN_DVB(1)
888 MUXES_FCN_DVB(2)
889 MUXES_FCN_DVB(3)
890 MUXES_FCN_DVB(4)
891 MUXES_FCN_DVB(5)
892 MUXES_FCN_DVB(6)
893 
894 #endif
895 
896 #if ENABLE_IPTV
897 
898 #define MUXES_IPTV_FCN(num) \
899 static const void *muxes_get_iptv_value##num(void *o) \
900 { \
901   wizard_page_t *p = o; \
902   wizard_muxes_t *w = p->aux; \
903   snprintf(prop_sbuf, PROP_SBUF_LEN, "%s", w->iptv_url[num-1]); \
904   return &prop_sbuf_ptr; \
905 } \
906 static int muxes_set_iptv_value##num(void *o, const void *v) \
907 { \
908   wizard_page_t *p = o; \
909   wizard_muxes_t *w = p->aux; \
910   snprintf(w->iptv_url[num-1], sizeof(w->iptv_url[num-1]), "%s", (const char *)v); \
911   return 1; \
912 }
913 
914 MUXES_IPTV_FCN(1)
915 MUXES_IPTV_FCN(2)
916 MUXES_IPTV_FCN(3)
917 MUXES_IPTV_FCN(4)
918 MUXES_IPTV_FCN(5)
919 MUXES_IPTV_FCN(6)
920 
921 #endif
922 
DESCRIPTION_FCN(muxes)923 DESCRIPTION_FCN(muxes)
924 
925 wizard_page_t *wizard_muxes(const char *lang)
926 {
927   static const property_group_t groups[] = {
928     NETWORK_GROUP(1),
929     NETWORK_GROUP(2),
930     NETWORK_GROUP(3),
931     NETWORK_GROUP(4),
932     NETWORK_GROUP(5),
933     NETWORK_GROUP(6),
934     {}
935   };
936 #if ENABLE_MPEGTS_DVB
937   static const property_t nprops[] = {
938     MUXES(1),
939     MUXES(2),
940     MUXES(3),
941     MUXES(4),
942     MUXES(5),
943     MUXES(6),
944   };
945 #endif
946 #if ENABLE_IPTV
947   static const property_t iptvprops[] = {
948     MUXES_IPTV(1),
949     MUXES_IPTV(2),
950     MUXES_IPTV(3),
951     MUXES_IPTV(4),
952     MUXES_IPTV(5),
953     MUXES_IPTV(6),
954   };
955 #endif
956   static const property_t props[] = {
957     ICON(),
958     DESCRIPTION(muxes),
959     PREV_BUTTON(network),
960     NEXT_BUTTON(status),
961   };
962   wizard_page_t *page = page_init("muxes", "wizard_muxes", N_("Assign predefined muxes to networks"));
963   idclass_t *ic = (idclass_t *)page->idnode.in_class;
964   wizard_muxes_t *w;
965   mpegts_network_t *mn;
966   int idx, midx = 0;
967 
968   page->aux = w = calloc(1, sizeof(wizard_muxes_t));
969   ic->ic_groups = groups;
970   ic->ic_properties = w->props;
971   ic->ic_changed = muxes_changed;
972   page->free = muxes_free;
973   snprintf(w->lang, sizeof(w->lang), "%s", lang ?: "");
974 
975   for (idx = 0; idx < ARRAY_SIZE(props); idx++)
976     w->props[idx] = props[idx];
977 
978   LIST_FOREACH(mn, &mpegts_network_all, mn_global_link)
979     if (mn->mn_wizard) {
980       mn->mn_display_name(mn, w->network[midx], sizeof(w->network[midx]));
981       idnode_uuid_as_str(&mn->mn_id, w->networkid[midx]);
982 #if ENABLE_MPEGTS_DVB
983       if (idnode_is_instance(&mn->mn_id, &dvb_network_class)) {
984         w->props[idx++] = nprops[midx * 3 + 0];
985         w->props[idx++] = nprops[midx * 3 + 1];
986         w->props[idx++] = nprops[midx * 3 + 2];
987         midx++;
988       }
989 #endif
990 #if ENABLE_IPTV
991       if (idnode_is_instance(&mn->mn_id, &iptv_auto_network_class)) {
992         snprintf(w->iptv_url[midx], sizeof(w->iptv_url[midx]), "%s", ((iptv_network_t *)mn)->in_url ?: "");
993         w->props[idx++] = iptvprops[midx * 3 + 0];
994         w->props[idx++] = iptvprops[midx * 3 + 1];
995         w->props[idx++] = iptvprops[midx * 3 + 2];
996         midx++;
997       }
998 #endif
999     }
1000 
1001   assert(idx < ARRAY_SIZE(w->props));
1002 
1003   return page;
1004 }
1005 
1006 /*
1007  * Status
1008  */
1009 
DESCRIPTION_FCN(status)1010 DESCRIPTION_FCN(status)
1011 
1012 wizard_page_t *wizard_status(const char *lang)
1013 {
1014   static const property_group_t groups[] = {
1015     {
1016       .name     = "",
1017       .number   = 1,
1018     },
1019     {}
1020   };
1021   static const property_t props[] = {
1022     {
1023       .type     = PT_STR,
1024       .id       = "muxes",
1025       .name     = N_("Found muxes"),
1026       .desc     = N_("Number of muxes found."),
1027       .get      = empty_get,
1028       .opts     = PO_RDONLY,
1029       .group    = 1,
1030     },
1031     {
1032       .type     = PT_STR,
1033       .id       = "services",
1034       .name     = N_("Found services"),
1035       .desc     = N_("Total number of services found."),
1036       .get      = empty_get,
1037       .opts     = PO_RDONLY,
1038       .group    = 1,
1039     },
1040     PROGRESS(muxes_progress_get),
1041     ICON(),
1042     DESCRIPTION(status),
1043     PREV_BUTTON(muxes),
1044     NEXT_BUTTON(mapping),
1045     {}
1046   };
1047   wizard_page_t *page = page_init("status", "wizard_status", N_("Scan status"));
1048   idclass_t *ic = (idclass_t *)page->idnode.in_class;
1049   ic->ic_properties = props;
1050   ic->ic_groups = groups;
1051   return page;
1052 }
1053 
1054 /*
1055  * Service Mapping
1056  */
1057 
1058 typedef struct wizard_mapping {
1059   int mapall;
1060   int provtags;
1061   int nettags;
1062 } wizard_mapping_t;
1063 
mapping_changed(idnode_t * in)1064 static void mapping_changed(idnode_t *in)
1065 {
1066   wizard_page_t *p = (wizard_page_t *)in;
1067   wizard_mapping_t *w = p->aux;
1068   service_mapper_conf_t conf;
1069 
1070   if (!w->mapall)
1071     return;
1072   memset(&conf, 0, sizeof(conf));
1073   conf.type_tags = 1;
1074   conf.encrypted = 1;
1075   conf.provider_tags = w->provtags;
1076   conf.network_tags = w->nettags;
1077   service_mapper_start(&conf, NULL);
1078 }
1079 
1080 #define MAPPING_FCN(name) \
1081 static const void *mapping_get_##name(void *o) \
1082 { \
1083   static int n; \
1084   wizard_page_t *p = o; \
1085   wizard_mapping_t *w = p->aux; \
1086   n = w->name; \
1087   return &n; \
1088 } \
1089 static int mapping_set_##name(void *o, const void *v) \
1090 { \
1091   wizard_page_t *p = o; \
1092   wizard_mapping_t *w = p->aux; \
1093   w->name = *(int *)v; \
1094   return 1; \
1095 }
1096 
1097 MAPPING_FCN(mapall)
MAPPING_FCN(provtags)1098 MAPPING_FCN(provtags)
1099 MAPPING_FCN(nettags)
1100 
1101 DESCRIPTION_FCN(mapping)
1102 
1103 wizard_page_t *wizard_mapping(const char *lang)
1104 {
1105   static const property_t props[] = {
1106     {
1107       .type     = PT_BOOL,
1108       .id       = "mapall",
1109       .name     = N_("Map all services"),
1110       .desc     = N_("Automatically map all available services to "
1111                      "channels."),
1112       .get      = mapping_get_mapall,
1113       .set      = mapping_set_mapall,
1114     },
1115     {
1116       .type     = PT_BOOL,
1117       .id       = "provtags",
1118       .name     = N_("Create provider tags"),
1119       .desc     = N_("Create and associate a provider tag to created "
1120                      "channels."),
1121       .get      = mapping_get_provtags,
1122       .set      = mapping_set_provtags,
1123     },
1124     {
1125       .type     = PT_BOOL,
1126       .id       = "nettags",
1127       .name     = N_("Create network tags"),
1128       .desc     = N_("Create and associate a network tag to created "
1129                      "channels."),
1130       .get      = mapping_get_nettags,
1131       .set      = mapping_set_nettags,
1132     },
1133     ICON(),
1134     DESCRIPTION(mapping),
1135     PREV_BUTTON(status),
1136     NEXT_BUTTON(channels),
1137     {}
1138   };
1139   wizard_page_t *page = page_init("mapping", "wizard_mapping", N_("Service mapping"));
1140   idclass_t *ic = (idclass_t *)page->idnode.in_class;
1141   wizard_mapping_t *w;
1142   ic->ic_properties = props;
1143   ic->ic_changed = mapping_changed;
1144   page->aux = w = calloc(1, sizeof(wizard_mapping_t));
1145   w->provtags = service_mapper_conf.d.provider_tags;
1146   w->nettags = service_mapper_conf.d.network_tags;
1147   return page;
1148 }
1149 
1150 /*
1151  * Discovered channels
1152  */
1153 
channels_changed(idnode_t * in)1154 static void channels_changed(idnode_t *in)
1155 {
1156   access_entry_t *ae, *ae_next;
1157 
1158   /* check, if we have another admin account */
1159   TAILQ_FOREACH(ae, &access_entries, ae_link)
1160     if (ae->ae_admin && ae->ae_wizard) break;
1161   if (ae == NULL)
1162     return;
1163   /* remove the default access entry */
1164   for (ae = TAILQ_FIRST(&access_entries); ae; ae = ae_next) {
1165     ae_next = TAILQ_NEXT(ae, ae_link);
1166     if (strcmp(ae->ae_comment, ACCESS_DEFAULT_COMMENT) == 0) {
1167       access_entry_destroy(ae, 1);
1168       break;
1169     }
1170   }
1171 }
1172 
1173 DESCRIPTION_FCN(channels)
DESCRIPTION_FCN(channels2)1174 DESCRIPTION_FCN(channels2)
1175 
1176 wizard_page_t *wizard_channels(const char *lang)
1177 {
1178   static const property_t props[] = {
1179     ICON(),
1180     DESCRIPTION(channels),
1181     PREV_BUTTON(mapping),
1182     LAST_BUTTON(),
1183     {}
1184   };
1185   static const property_t props2[] = {
1186     ICON(),
1187     DESCRIPTION(channels2),
1188     PREV_BUTTON(mapping),
1189     LAST_BUTTON(),
1190     {}
1191   };
1192   wizard_page_t *page = page_init("channels", "wizard_channels", N_("Finished"));
1193   idclass_t *ic = (idclass_t *)page->idnode.in_class;
1194   access_entry_t *ae;
1195 
1196   ic->ic_properties = props;
1197   ic->ic_flags |= IDCLASS_ALWAYS_SAVE;
1198   ic->ic_changed = channels_changed;
1199   /* do we have an admin created by wizard? */
1200   TAILQ_FOREACH(ae, &access_entries, ae_link)
1201     if (ae->ae_admin && ae->ae_wizard) break;
1202   if (ae == NULL)
1203     ic->ic_properties = props2;
1204   return page;
1205 }
1206