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