1 /*
2 * Digital Video Recorder
3 * Copyright (C) 2008 Andreas Öman
4 * Copyright (C) 2014,2015 Jaroslav Kysela
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <pthread.h>
21 #include <sys/stat.h>
22 #include <unistd.h>
23 #include <assert.h>
24 #include <string.h>
25
26 #include "settings.h"
27
28 #include "tvheadend.h"
29 #include "dvr.h"
30 #include "htsp_server.h"
31 #include "streaming.h"
32 #include "intlconv.h"
33 #include "dbus.h"
34 #include "imagecache.h"
35 #include "access.h"
36
37 int dvr_iov_max;
38
39 struct dvr_config_list dvrconfigs;
40 static dvr_config_t *dvrdefaultconfig = NULL;
41
42 static void dvr_config_destroy(dvr_config_t *cfg, int delconf);
43 static void dvr_update_pathname_from_booleans(dvr_config_t *cfg);
44
45 /**
46 * find a dvr config by name, return NULL if not found
47 */
48 dvr_config_t *
dvr_config_find_by_name(const char * name)49 dvr_config_find_by_name(const char *name)
50 {
51 dvr_config_t *cfg;
52
53 if (name == NULL)
54 name = "";
55
56 LIST_FOREACH(cfg, &dvrconfigs, config_link)
57 if (cfg->dvr_enabled && !strcmp(name, cfg->dvr_config_name))
58 return cfg;
59
60 return NULL;
61 }
62
63 /**
64 * find a dvr config by name, return the default config if not found
65 */
66 dvr_config_t *
dvr_config_find_by_name_default(const char * name)67 dvr_config_find_by_name_default(const char *name)
68 {
69 dvr_config_t *cfg;
70
71 if (dvrdefaultconfig == NULL)
72 dvrdefaultconfig = dvr_config_find_by_name(NULL);
73
74 if (dvrdefaultconfig == NULL) {
75 cfg = dvr_config_create("", NULL, NULL);
76 assert(cfg);
77 idnode_changed(&cfg->dvr_id);
78 dvrdefaultconfig = cfg;
79 }
80
81 if (tvh_str_default(name, NULL) == NULL)
82 return dvrdefaultconfig;
83
84 cfg = dvr_config_find_by_name(name);
85
86 if (cfg == NULL) {
87 if (tvh_str_default(name, NULL))
88 tvhwarn(LS_DVR, "Configuration '%s' not found, using default", name);
89 cfg = dvrdefaultconfig;
90 } else if (!cfg->dvr_enabled) {
91 tvhwarn(LS_DVR, "Configuration '%s' not enabled, using default", name);
92 cfg = dvrdefaultconfig;
93 }
94
95 return cfg;
96 }
97
98 /*
99 * find a dvr config by name using a filter list,
100 * return the first config from list if name is not valid
101 * return the default config if not found
102 */
103 dvr_config_t *
dvr_config_find_by_list(htsmsg_t * uuids,const char * name)104 dvr_config_find_by_list(htsmsg_t *uuids, const char *name)
105 {
106 dvr_config_t *cfg, *res = NULL;
107 htsmsg_field_t *f;
108 const char *uuid, *uuid2;
109 char ubuf[UUID_HEX_SIZE];
110
111 cfg = dvr_config_find_by_uuid(name);
112 if (!cfg)
113 cfg = dvr_config_find_by_name(name);
114 uuid = cfg ? idnode_uuid_as_str(&cfg->dvr_id, ubuf) : "";
115 if (uuids) {
116 HTSMSG_FOREACH(f, uuids) {
117 uuid2 = htsmsg_field_get_str(f) ?: "";
118 if (strcmp(uuid, uuid2) == 0)
119 return cfg;
120 if (!res) {
121 res = dvr_config_find_by_uuid(uuid2);
122 if (!res->dvr_enabled)
123 res = NULL;
124 }
125 }
126 } else {
127 if (cfg && cfg->dvr_enabled)
128 res = cfg;
129 }
130 if (!res)
131 res = dvr_config_find_by_name_default(NULL);
132 return res;
133 }
134
135 /**
136 *
137 */
138 static int
dvr_charset_update(dvr_config_t * cfg,const char * charset)139 dvr_charset_update(dvr_config_t *cfg, const char *charset)
140 {
141 const char *s, *id;
142 int change = strcmp(cfg->dvr_charset ?: "", charset ?: "");
143
144 free(cfg->dvr_charset);
145 free(cfg->dvr_charset_id);
146 s = charset ? charset : intlconv_filesystem_charset();
147 id = intlconv_charset_id(s, 1, 1);
148 cfg->dvr_charset = s ? strdup(s) : NULL;
149 cfg->dvr_charset_id = id ? strdup(id) : NULL;
150 return change;
151 }
152
153 /**
154 * create a new named dvr config; the caller is responsible
155 * to avoid duplicates
156 */
157 dvr_config_t *
dvr_config_create(const char * name,const char * uuid,htsmsg_t * conf)158 dvr_config_create(const char *name, const char *uuid, htsmsg_t *conf)
159 {
160 dvr_config_t *cfg;
161
162 if (name == NULL)
163 name = "";
164
165 cfg = calloc(1, sizeof(dvr_config_t));
166 LIST_INIT(&cfg->dvr_entries);
167 LIST_INIT(&cfg->dvr_autorec_entries);
168 LIST_INIT(&cfg->dvr_timerec_entries);
169 LIST_INIT(&cfg->dvr_accesses);
170
171 if (idnode_insert(&cfg->dvr_id, uuid, &dvr_config_class, 0)) {
172 if (uuid)
173 tvherror(LS_DVR, "invalid config uuid '%s'", uuid);
174 free(cfg);
175 return NULL;
176 }
177
178 cfg->dvr_enabled = 1;
179 cfg->dvr_config_name = strdup(name);
180 cfg->dvr_retention_days = DVR_RET_ONREMOVE;
181 cfg->dvr_removal_days = DVR_RET_REM_FOREVER;
182 cfg->dvr_clone = 1;
183 cfg->dvr_tag_files = 1;
184 cfg->dvr_skip_commercials = 1;
185 dvr_charset_update(cfg, intlconv_filesystem_charset());
186 cfg->dvr_warm_time = 30;
187 cfg->dvr_update_window = 24 * 3600;
188 cfg->dvr_pathname = strdup("$t$n.$x");
189 cfg->dvr_cleanup_threshold_free = 1000; // keep 1000 MiB of free space on disk by default
190 cfg->dvr_cleanup_threshold_used = 0; // disabled
191 cfg->dvr_autorec_max_count = 50;
192
193 /* Muxer config */
194 cfg->dvr_muxcnf.m_cache = MC_CACHE_SYSTEM;
195
196 /* Default recording file and directory permissions */
197
198 cfg->dvr_muxcnf.m_file_permissions = 0664;
199 cfg->dvr_muxcnf.m_directory_permissions = 0775;
200
201 if (conf) {
202 idnode_load(&cfg->dvr_id, conf);
203 if (!htsmsg_field_find(conf, "pathname"))
204 dvr_update_pathname_from_booleans(cfg);
205 cfg->dvr_valid = 1;
206 }
207
208 tvhinfo(LS_DVR, "Creating new configuration '%s'", cfg->dvr_config_name);
209
210 if (cfg->dvr_profile == NULL) {
211 cfg->dvr_profile = profile_find_by_name("pass", NULL);
212 assert(cfg->dvr_profile);
213 LIST_INSERT_HEAD(&cfg->dvr_profile->pro_dvr_configs, cfg, profile_link);
214 }
215
216 if (dvr_config_is_default(cfg) && dvr_config_find_by_name(NULL)) {
217 tvherror(LS_DVR, "Unable to create second default config, removing");
218 LIST_INSERT_HEAD(&dvrconfigs, cfg, config_link);
219 dvr_config_destroy(cfg, 0);
220 cfg = NULL;
221 }
222
223 if (cfg) {
224 LIST_INSERT_HEAD(&dvrconfigs, cfg, config_link);
225 if (conf && dvr_config_is_default(cfg))
226 cfg->dvr_enabled = 1;
227 }
228
229 return cfg;
230 }
231
232 /**
233 * destroy a dvr config
234 */
235 static void
dvr_config_destroy(dvr_config_t * cfg,int delconf)236 dvr_config_destroy(dvr_config_t *cfg, int delconf)
237 {
238 char ubuf[UUID_HEX_SIZE];
239
240 idnode_save_check(&cfg->dvr_id, delconf);
241
242 if (delconf) {
243 tvhinfo(LS_DVR, "Deleting configuration '%s'", cfg->dvr_config_name);
244 hts_settings_remove("dvr/config/%s", idnode_uuid_as_str(&cfg->dvr_id, ubuf));
245 }
246 LIST_REMOVE(cfg, config_link);
247 idnode_unlink(&cfg->dvr_id);
248
249 if (cfg->dvr_profile) {
250 LIST_REMOVE(cfg, profile_link);
251 cfg->dvr_profile = NULL;
252 }
253
254 dvr_entry_destroy_by_config(cfg, delconf);
255 access_destroy_by_dvr_config(cfg, delconf);
256 autorec_destroy_by_config(cfg, delconf);
257 timerec_destroy_by_config(cfg, delconf);
258
259 free(cfg->dvr_pathname);
260 free(cfg->dvr_charset_id);
261 free(cfg->dvr_charset);
262 free(cfg->dvr_storage);
263 free(cfg->dvr_config_name);
264 free(cfg->dvr_preproc);
265 free(cfg->dvr_postproc);
266 free(cfg->dvr_postremove);
267 free(cfg);
268 }
269
270 /**
271 *
272 */
273 static void
dvr_config_storage_check(dvr_config_t * cfg)274 dvr_config_storage_check(dvr_config_t *cfg)
275 {
276 char buf[PATH_MAX];
277 struct stat st;
278 const char *homedir;
279
280 if(cfg->dvr_storage != NULL && cfg->dvr_storage[0])
281 return;
282
283 /* Try to figure out a good place to put them videos */
284
285 homedir = getenv("HOME");
286
287 if(homedir != NULL) {
288 snprintf(buf, sizeof(buf), "%s/Videos", homedir);
289 if(stat(buf, &st) == 0 && S_ISDIR(st.st_mode))
290 cfg->dvr_storage = strdup(buf);
291
292 else if(stat(homedir, &st) == 0 && S_ISDIR(st.st_mode))
293 cfg->dvr_storage = strdup(homedir);
294 else
295 cfg->dvr_storage = strdup(getcwd(buf, sizeof(buf)));
296 }
297
298 tvhwarn(LS_DVR,
299 "Output directory for video recording is not yet configured "
300 "for DVR configuration \"%s\". "
301 "Defaulting to to \"%s\". "
302 "This can be changed from the web user interface.",
303 cfg->dvr_config_name, cfg->dvr_storage);
304 }
305
306 /**
307 *
308 */
309 static int
dvr_filename_index(dvr_config_t * cfg)310 dvr_filename_index(dvr_config_t *cfg)
311 {
312 char *str = cfg->dvr_pathname, *p;
313
314 for (str = p = cfg->dvr_pathname; *p; p++) {
315 if (*p == '\\')
316 p++;
317 else if (*p == '/')
318 str = p;
319 }
320
321 return str - cfg->dvr_pathname;
322 }
323
324 /**
325 *
326 */
327 static int
dvr_match_fmtstr(dvr_config_t * cfg,const char * needle,int nodir)328 dvr_match_fmtstr(dvr_config_t *cfg, const char *needle, int nodir)
329 {
330 char *str = cfg->dvr_pathname, *p;
331 const char *x;
332
333 if (needle == NULL)
334 return -1;
335
336 if (nodir)
337 str += dvr_filename_index(cfg);
338
339 while (*str) {
340 if (*str == '\\') {
341 str++;
342 if (*str)
343 str++;
344 continue;
345 }
346 for (p = str, x = needle; *p && *x; p++, x++)
347 if (*p != *x)
348 break;
349 if (*x == '\0')
350 return str - cfg->dvr_pathname;
351 else
352 str++;
353 }
354 return -1;
355 }
356
357 /**
358 *
359 */
360 static void
dvr_insert_fmtstr(dvr_config_t * cfg,int idx,const char * str)361 dvr_insert_fmtstr(dvr_config_t *cfg, int idx, const char *str)
362 {
363 size_t t = strlen(cfg->dvr_pathname);
364 size_t l = strlen(str);
365 char *n = malloc(t + l + 1);
366 memcpy(n, cfg->dvr_pathname, idx);
367 memcpy(n + idx, str, l);
368 memcpy(n + idx + l, cfg->dvr_pathname + idx, t - idx);
369 n[t + l] = '\0';
370 free(cfg->dvr_pathname);
371 cfg->dvr_pathname = n;
372 }
373
374 /**
375 *
376 */
377 static void
dvr_insert_fmtstr_before_extension(dvr_config_t * cfg,const char * str)378 dvr_insert_fmtstr_before_extension(dvr_config_t *cfg, const char *str)
379 {
380 int idx = dvr_match_fmtstr(cfg, "$n", 1);
381 idx = idx < 0 ? dvr_match_fmtstr(cfg, "$x", 1) : idx;
382 if (idx < 0) {
383 idx = strlen(cfg->dvr_pathname);
384 } else {
385 while (idx > 0) {
386 if (cfg->dvr_pathname[idx - 1] != '.')
387 break;
388 idx--;
389 }
390 }
391 dvr_insert_fmtstr(cfg, idx, str);
392 }
393
394 /**
395 *
396 */
397 static void
dvr_remove_fmtstr(dvr_config_t * cfg,int idx,int len)398 dvr_remove_fmtstr(dvr_config_t *cfg, int idx, int len)
399 {
400 char *pathname = cfg->dvr_pathname;
401 size_t l = strlen(pathname);
402 memmove(pathname + idx, pathname + idx + len, l - idx - len);
403 pathname[l - len] = '\0';
404 }
405
406 /**
407 *
408 */
409 static void
dvr_match_and_insert_or_remove(dvr_config_t * cfg,const char * str,int val,int idx)410 dvr_match_and_insert_or_remove(dvr_config_t *cfg, const char *str, int val, int idx)
411 {
412 int i = dvr_match_fmtstr(cfg, str, idx < 0 ? 1 : 0);
413 if (val) {
414 if (i < 0) {
415 if (idx < 0)
416 dvr_insert_fmtstr_before_extension(cfg, str);
417 else
418 dvr_insert_fmtstr(cfg, idx, str);
419 }
420 } else {
421 if (i >= 0) {
422 if (idx < 0 && i >= dvr_filename_index(cfg))
423 dvr_remove_fmtstr(cfg, i, strlen(str));
424 else if (idx >= 0 && i < dvr_filename_index(cfg))
425 dvr_remove_fmtstr(cfg, i, strlen(str));
426 }
427 }
428 }
429
430 /**
431 *
432 */
433 static void
dvr_update_pathname_safe(dvr_config_t * cfg)434 dvr_update_pathname_safe(dvr_config_t *cfg)
435 {
436 if (cfg->dvr_pathname[0] == '\0') {
437 free(cfg->dvr_pathname);
438 cfg->dvr_pathname = strdup("$t$n.$x");
439 }
440
441 if (strstr(cfg->dvr_pathname, "%n"))
442 dvr_insert_fmtstr_before_extension(cfg, "%n");
443 }
444
445 /**
446 *
447 */
448 static void
dvr_update_pathname_from_fmtstr(dvr_config_t * cfg)449 dvr_update_pathname_from_fmtstr(dvr_config_t *cfg)
450 {
451 if (cfg->dvr_pathname == NULL)
452 return;
453
454 dvr_update_pathname_safe(cfg);
455
456 cfg->dvr_dir_per_day = dvr_match_fmtstr(cfg, "%F/", 0) >= 0;
457 cfg->dvr_channel_dir = dvr_match_fmtstr(cfg, "$c/", 0) >= 0;
458 cfg->dvr_title_dir = dvr_match_fmtstr(cfg, "$t/", 0) >= 0;
459
460 cfg->dvr_channel_in_title = dvr_match_fmtstr(cfg, "$-c", 1) >= 0;
461 cfg->dvr_date_in_title = dvr_match_fmtstr(cfg, "%F", 1) >= 0;
462 cfg->dvr_time_in_title = dvr_match_fmtstr(cfg, "%R", 1) >= 0;
463 cfg->dvr_episode_in_title = dvr_match_fmtstr(cfg, "$-e", 1) >= 0;
464 cfg->dvr_subtitle_in_title = dvr_match_fmtstr(cfg, "$.s", 1) >= 0;
465
466 cfg->dvr_omit_title = dvr_match_fmtstr(cfg, "$t", 1) < 0;
467 }
468
469 /**
470 *
471 */
472 static void
dvr_update_pathname_from_booleans(dvr_config_t * cfg)473 dvr_update_pathname_from_booleans(dvr_config_t *cfg)
474 {
475 int i;
476
477 i = dvr_match_fmtstr(cfg, "$t", 1);
478 if (cfg->dvr_omit_title) {
479 if (i >= 0)
480 dvr_remove_fmtstr(cfg, i, 2);
481 } else if (i < 0) {
482 i = dvr_match_fmtstr(cfg, "$n", 1);
483 if (i >= 0)
484 dvr_insert_fmtstr(cfg, i, "$t");
485 else
486 dvr_insert_fmtstr_before_extension(cfg, "$t");
487 }
488
489 dvr_match_and_insert_or_remove(cfg, "$t/", cfg->dvr_title_dir, 0);
490 dvr_match_and_insert_or_remove(cfg, "$c/", cfg->dvr_channel_dir, 0);
491 dvr_match_and_insert_or_remove(cfg, "%F/", cfg->dvr_dir_per_day, 0);
492
493 dvr_match_and_insert_or_remove(cfg, "$-c", cfg->dvr_channel_in_title, -1);
494 dvr_match_and_insert_or_remove(cfg, "$.s", cfg->dvr_subtitle_in_title, -1);
495 dvr_match_and_insert_or_remove(cfg, "%F", cfg->dvr_date_in_title, -1);
496 dvr_match_and_insert_or_remove(cfg, "%R", cfg->dvr_time_in_title, -1);
497 dvr_match_and_insert_or_remove(cfg, "$-e", cfg->dvr_episode_in_title, -1);
498
499 dvr_update_pathname_safe(cfg);
500 }
501
502 /**
503 *
504 */
505 void
dvr_config_delete(const char * name)506 dvr_config_delete(const char *name)
507 {
508 dvr_config_t *cfg;
509
510 cfg = dvr_config_find_by_name(name);
511 if (!dvr_config_is_default(cfg))
512 dvr_config_destroy(cfg, 1);
513 else
514 tvhwarn(LS_DVR, "Attempt to delete default config ignored");
515 }
516
517 /**
518 *
519 */
520 void
dvr_config_changed(dvr_config_t * cfg)521 dvr_config_changed(dvr_config_t *cfg)
522 {
523 if (dvr_config_is_default(cfg))
524 cfg->dvr_enabled = 1;
525 cfg->dvr_valid = 1;
526 if (cfg->dvr_pathname_changed) {
527 cfg->dvr_pathname_changed = 0;
528 dvr_update_pathname_from_fmtstr(cfg);
529 } else {
530 dvr_update_pathname_from_booleans(cfg);
531 }
532 dvr_config_storage_check(cfg);
533 if (cfg->dvr_cleanup_threshold_free < 50)
534 cfg->dvr_cleanup_threshold_free = 50; // as checking is only periodically, lower is not save
535 if (cfg->dvr_removal_days > DVR_RET_REM_FOREVER)
536 cfg->dvr_removal_days = DVR_RET_REM_FOREVER;
537 if (cfg->dvr_retention_days > DVR_RET_REM_FOREVER)
538 cfg->dvr_retention_days = DVR_RET_REM_FOREVER;
539 if (cfg->dvr_profile && !strcmp(profile_get_name(cfg->dvr_profile), "htsp")) // htsp is for streaming only
540 cfg->dvr_profile = profile_find_by_name("pass", NULL);
541 }
542
543
544 /* **************************************************************************
545 * DVR Config Class definition
546 * **************************************************************************/
547
548 static void
dvr_config_class_changed(idnode_t * self)549 dvr_config_class_changed(idnode_t *self)
550 {
551 dvr_config_changed((dvr_config_t *)self);
552 }
553
554 static htsmsg_t *
dvr_config_class_save(idnode_t * self,char * filename,size_t fsize)555 dvr_config_class_save(idnode_t *self, char *filename, size_t fsize)
556 {
557 dvr_config_t *cfg = (dvr_config_t *)self;
558 htsmsg_t *m = htsmsg_create_map();
559 char ubuf[UUID_HEX_SIZE];
560 idnode_save(&cfg->dvr_id, m);
561 snprintf(filename, fsize, "dvr/config/%s", idnode_uuid_as_str(&cfg->dvr_id, ubuf));
562 return m;
563 }
564
565 static void
dvr_config_class_delete(idnode_t * self)566 dvr_config_class_delete(idnode_t *self)
567 {
568 dvr_config_t *cfg = (dvr_config_t *)self;
569 if (!dvr_config_is_default(cfg))
570 dvr_config_destroy(cfg, 1);
571 }
572
573 static int
dvr_config_class_perm(idnode_t * self,access_t * a,htsmsg_t * msg_to_write)574 dvr_config_class_perm(idnode_t *self, access_t *a, htsmsg_t *msg_to_write)
575 {
576 dvr_config_t *cfg = (dvr_config_t *)self;
577 htsmsg_field_t *f;
578 const char *uuid, *my_uuid;
579 char ubuf[UUID_HEX_SIZE];
580
581 if (access_verify2(a, ACCESS_OR|ACCESS_ADMIN|ACCESS_RECORDER))
582 return -1;
583 if (!access_verify2(a, ACCESS_ADMIN))
584 return 0;
585 if (a->aa_dvrcfgs) {
586 my_uuid = idnode_uuid_as_str(&cfg->dvr_id, ubuf);
587 HTSMSG_FOREACH(f, a->aa_dvrcfgs) {
588 uuid = htsmsg_field_get_str(f) ?: "";
589 if (!strcmp(uuid, my_uuid))
590 goto fine;
591 }
592 return -1;
593 }
594 fine:
595 return 0;
596 }
597
598 static int
dvr_config_class_enabled_set(void * o,const void * v)599 dvr_config_class_enabled_set(void *o, const void *v)
600 {
601 dvr_config_t *cfg = (dvr_config_t *)o;
602 if (dvr_config_is_default(cfg) && dvr_config_is_valid(cfg))
603 return 0;
604 if (cfg->dvr_enabled != *(int *)v) {
605 cfg->dvr_enabled = *(int *)v;
606 return 1;
607 }
608 return 0;
609 }
610
611 static uint32_t
dvr_config_class_enabled_opts(void * o)612 dvr_config_class_enabled_opts(void *o)
613 {
614 dvr_config_t *cfg = (dvr_config_t *)o;
615 if (cfg && dvr_config_is_default(cfg) && dvr_config_is_valid(cfg))
616 return PO_RDONLY;
617 return 0;
618 }
619
620 static int
dvr_config_class_name_set(void * o,const void * v)621 dvr_config_class_name_set(void *o, const void *v)
622 {
623 dvr_config_t *cfg = (dvr_config_t *)o;
624 if (dvr_config_is_default(cfg) && dvr_config_is_valid(cfg))
625 return 0;
626 if (strcmp(cfg->dvr_config_name, v ?: "")) {
627 if (dvr_config_is_valid(cfg) && tvh_str_default(v, NULL) == NULL)
628 return 0;
629 free(cfg->dvr_config_name);
630 cfg->dvr_config_name = strdup(v ?: "");
631 return 1;
632 }
633 return 0;
634 }
635
636 static int
dvr_config_class_profile_set(void * o,const void * v)637 dvr_config_class_profile_set(void *o, const void *v)
638 {
639 dvr_config_t *cfg = (dvr_config_t *)o;
640 profile_t *pro;
641
642 pro = v ? profile_find_by_uuid(v) : NULL;
643 pro = pro ?: profile_find_by_name(v, "pass");
644 if (pro == NULL) {
645 if (cfg->dvr_profile) {
646 LIST_REMOVE(cfg, profile_link);
647 cfg->dvr_profile = NULL;
648 return 1;
649 }
650 } else if (cfg->dvr_profile != pro) {
651 if (cfg->dvr_profile)
652 LIST_REMOVE(cfg, profile_link);
653 cfg->dvr_profile = pro;
654 LIST_INSERT_HEAD(&pro->pro_dvr_configs, cfg, profile_link);
655 return 1;
656 }
657 return 0;
658 }
659
660 static const void *
dvr_config_class_profile_get(void * o)661 dvr_config_class_profile_get(void *o)
662 {
663 dvr_config_t *cfg = (dvr_config_t *)o;
664 if (cfg->dvr_profile)
665 idnode_uuid_as_str(&cfg->dvr_profile->pro_id, prop_sbuf);
666 else
667 prop_sbuf[0] = '\0';
668 return &prop_sbuf_ptr;
669 }
670
671 static char *
dvr_config_class_profile_rend(void * o,const char * lang)672 dvr_config_class_profile_rend(void *o, const char *lang)
673 {
674 dvr_config_t *cfg = (dvr_config_t *)o;
675 if (cfg->dvr_profile)
676 return strdup(profile_get_name(cfg->dvr_profile));
677 return NULL;
678 }
679
680 static const char *
dvr_config_class_get_title(idnode_t * self,const char * lang)681 dvr_config_class_get_title (idnode_t *self, const char *lang)
682 {
683 dvr_config_t *cfg = (dvr_config_t *)self;
684 if (!dvr_config_is_default(cfg))
685 return cfg->dvr_config_name;
686 return N_("(Default profile)");
687 }
688
689 static int
dvr_config_class_charset_set(void * o,const void * v)690 dvr_config_class_charset_set(void *o, const void *v)
691 {
692 dvr_config_t *cfg = (dvr_config_t *)o;
693 return dvr_charset_update(cfg, v);
694 }
695
696 static htsmsg_t *
dvr_config_class_charset_list(void * o,const char * lang)697 dvr_config_class_charset_list(void *o, const char *lang)
698 {
699 htsmsg_t *m = htsmsg_create_map();
700 htsmsg_add_str(m, "type", "api");
701 htsmsg_add_str(m, "uri", "intlconv/charsets");
702 return m;
703 }
704
705 static htsmsg_t *
dvr_config_class_cache_list(void * o,const char * lang)706 dvr_config_class_cache_list(void *o, const char *lang)
707 {
708 static struct strtab tab[] = {
709 { N_("Unknown"), MC_CACHE_UNKNOWN },
710 { N_("System"), MC_CACHE_SYSTEM },
711 { N_("Don't keep"), MC_CACHE_DONTKEEP },
712 { N_("Sync"), MC_CACHE_SYNC },
713 { N_("Sync + Don't keep"), MC_CACHE_SYNCDONTKEEP }
714 };
715 return strtab2htsmsg(tab, 1, lang);
716 }
717
718 static htsmsg_t *
dvr_config_class_removal_list(void * o,const char * lang)719 dvr_config_class_removal_list ( void *o, const char *lang )
720 {
721 static const struct strtab_u32 tab[] = {
722 { N_("1 day"), DVR_RET_REM_1DAY },
723 { N_("3 days"), DVR_RET_REM_3DAY },
724 { N_("5 days"), DVR_RET_REM_5DAY },
725 { N_("1 week"), DVR_RET_REM_1WEEK },
726 { N_("2 weeks"), DVR_RET_REM_2WEEK },
727 { N_("3 weeks"), DVR_RET_REM_3WEEK },
728 { N_("1 month"), DVR_RET_REM_1MONTH },
729 { N_("2 months"), DVR_RET_REM_2MONTH },
730 { N_("3 months"), DVR_RET_REM_3MONTH },
731 { N_("6 months"), DVR_RET_REM_6MONTH },
732 { N_("1 year"), DVR_RET_REM_1YEAR },
733 { N_("2 years"), DVR_RET_REM_2YEARS },
734 { N_("3 years"), DVR_RET_REM_3YEARS },
735 { N_("Maintained space"), DVR_REM_SPACE },
736 { N_("Forever"), DVR_RET_REM_FOREVER },
737 };
738 return strtab2htsmsg_u32(tab, 1, lang);
739 }
740
741 static htsmsg_t *
dvr_config_class_retention_list(void * o,const char * lang)742 dvr_config_class_retention_list ( void *o, const char *lang )
743 {
744 static const struct strtab_u32 tab[] = {
745 { N_("1 day"), DVR_RET_REM_1DAY },
746 { N_("3 days"), DVR_RET_REM_3DAY },
747 { N_("5 days"), DVR_RET_REM_5DAY },
748 { N_("1 week"), DVR_RET_REM_1WEEK },
749 { N_("2 weeks"), DVR_RET_REM_2WEEK },
750 { N_("3 weeks"), DVR_RET_REM_3WEEK },
751 { N_("1 month"), DVR_RET_REM_1MONTH },
752 { N_("2 months"), DVR_RET_REM_2MONTH },
753 { N_("3 months"), DVR_RET_REM_3MONTH },
754 { N_("6 months"), DVR_RET_REM_6MONTH },
755 { N_("1 year"), DVR_RET_REM_1YEAR },
756 { N_("2 years"), DVR_RET_REM_2YEARS },
757 { N_("3 years"), DVR_RET_REM_3YEARS },
758 { N_("On file removal"), DVR_RET_ONREMOVE },
759 { N_("Forever"), DVR_RET_REM_FOREVER },
760 };
761 return strtab2htsmsg_u32(tab, 1, lang);
762 }
763
764 static htsmsg_t *
dvr_config_class_extra_list(void * o,const char * lang)765 dvr_config_class_extra_list(void *o, const char *lang)
766 {
767 return dvr_entry_class_duration_list(o,
768 tvh_gettext_lang(lang, N_("Not set (none or channel configuration)")),
769 4*60, 1, lang);
770 }
771
772 static htsmsg_t *
dvr_config_entry_class_update_window_list(void * o,const char * lang)773 dvr_config_entry_class_update_window_list(void *o, const char *lang)
774 {
775 return dvr_entry_class_duration_list(o,
776 tvh_gettext_lang(lang, N_("Update disabled")),
777 24*3600, 60, lang);
778 }
779
780 static int
dvr_config_class_pathname_set(void * o,const void * v)781 dvr_config_class_pathname_set(void *o, const void *v)
782 {
783 dvr_config_t *cfg = (dvr_config_t *)o;
784 const char *s = v;
785 if (strcmp(cfg->dvr_pathname ?: "", s ?: "")) {
786 free(cfg->dvr_pathname);
787 cfg->dvr_pathname = s ? strdup(s) : NULL;
788 cfg->dvr_pathname_changed = 1;
789 return 1;
790 }
791 return 0;
792 }
793
794 CLASS_DOC(dvrconfig)
795 PROP_DOC(preprocessor)
796 PROP_DOC(postprocessor)
797 PROP_DOC(postremove)
798 PROP_DOC(pathname)
799 PROP_DOC(cache_scheme)
800 PROP_DOC(runningstate)
801
802 const idclass_t dvr_config_class = {
803 .ic_class = "dvrconfig",
804 .ic_caption = N_("DVR - Profiles"),
805 .ic_event = "dvrconfig",
806 .ic_doc = tvh_doc_dvrconfig_class,
807 .ic_changed = dvr_config_class_changed,
808 .ic_save = dvr_config_class_save,
809 .ic_get_title = dvr_config_class_get_title,
810 .ic_delete = dvr_config_class_delete,
811 .ic_perm = dvr_config_class_perm,
812 .ic_groups = (const property_group_t[]) {
813 {
814 .name = N_("DVR behavior"),
815 .number = 1,
816 },
817 {
818 .name = N_("Recording file options"),
819 .number = 2,
820 },
821 {
822 .name = N_("Full pathname specification"),
823 .number = 3,
824 },
825 {
826 .name = N_("Subdirectory options"),
827 .number = 4,
828 },
829 {
830 .name = N_("Filename options"),
831 .number = 5,
832 .column = 1,
833 },
834 {
835 .name = "",
836 .number = 6,
837 .parent = 5,
838 .column = 2,
839 },
840 {}
841 },
842 .ic_properties = (const property_t[]){
843 {
844 .type = PT_BOOL,
845 .id = "enabled",
846 .name = N_("Enabled"),
847 .desc = N_("Enable/disable profile."),
848 .set = dvr_config_class_enabled_set,
849 .off = offsetof(dvr_config_t, dvr_enabled),
850 .def.i = 1,
851 .group = 1,
852 .get_opts = dvr_config_class_enabled_opts,
853 },
854 {
855 .type = PT_STR,
856 .id = "name",
857 .name = N_("Configuration name"),
858 .desc = N_("Name of the profile."),
859 .set = dvr_config_class_name_set,
860 .off = offsetof(dvr_config_t, dvr_config_name),
861 .def.s = "! New config",
862 .group = 1,
863 .get_opts = dvr_config_class_enabled_opts,
864 },
865 {
866 .type = PT_STR,
867 .id = "comment",
868 .name = N_("Comment"),
869 .desc = N_("Free-form field, enter whatever you like here."),
870 .off = offsetof(dvr_config_t, dvr_comment),
871 .group = 1,
872 },
873 {
874 .type = PT_STR,
875 .id = "profile",
876 .name = N_("Stream profile"),
877 .desc = N_("The stream profile the DVR profile will use for "
878 "recordings."),
879 .set = dvr_config_class_profile_set,
880 .get = dvr_config_class_profile_get,
881 .rend = dvr_config_class_profile_rend,
882 .list = profile_class_get_list,
883 .opts = PO_ADVANCED,
884 .group = 1,
885 },
886 {
887 .type = PT_INT,
888 .id = "cache",
889 .name = N_("Cache scheme"),
890 .desc = N_("The cache scheme to use/used to store recordings. "
891 "Leave as \"system\" unless you have a special use "
892 "case for one of the others. See Help for details."),
893 .doc = prop_doc_cache_scheme,
894 .off = offsetof(dvr_config_t, dvr_muxcnf.m_cache),
895 .def.i = MC_CACHE_DONTKEEP,
896 .list = dvr_config_class_cache_list,
897 .opts = PO_EXPERT | PO_DOC_NLIST,
898 .group = 1,
899 },
900 {
901 .type = PT_U32,
902 .id = "retention-days",
903 .name = N_("DVR log retention period"),
904 .desc = N_("Number of days to retain information about recordings. Once this period is exceeded, duplicate detection will not be possible anymore."),
905 .off = offsetof(dvr_config_t, dvr_retention_days),
906 .def.u32 = DVR_RET_ONREMOVE,
907 .list = dvr_config_class_retention_list,
908 .opts = PO_EXPERT | PO_DOC_NLIST,
909 .group = 1,
910 },
911 {
912 .type = PT_U32,
913 .id = "removal-days",
914 .name = N_("DVR file retention period"),
915 .desc = N_("Number of days to keep the recorded files."),
916 .off = offsetof(dvr_config_t, dvr_removal_days),
917 .def.u32 = DVR_RET_REM_FOREVER,
918 .list = dvr_config_class_removal_list,
919 .opts = PO_DOC_NLIST,
920 .group = 1,
921 },
922 {
923 .type = PT_BOOL,
924 .id = "clone",
925 .name = N_("Clone scheduled entry on error"),
926 .desc = N_("If an error occurs clone the scheduled entry and "
927 "try to record again (if possible)."),
928 .off = offsetof(dvr_config_t, dvr_clone),
929 .opts = PO_ADVANCED,
930 .def.u32 = 1,
931 .group = 1,
932 },
933 {
934 .type = PT_U32,
935 .id = "rerecord-errors",
936 .name = N_("Schedule a re-recording if more errors than (0=off)"),
937 .desc = N_("If more than x errors occur during a recording "
938 "schedule a re-record (if possible)."),
939 .off = offsetof(dvr_config_t, dvr_rerecord_errors),
940 .opts = PO_ADVANCED,
941 .group = 1,
942 },
943 {
944 .type = PT_U32,
945 .id = "warm-time",
946 .name = N_("Extra warming up time (seconds)"),
947 .desc = N_("Additional time (in seconds) in which to get "
948 "the tuner ready for recording. This is useful for "
949 "those with tuners that take some time to tune "
950 "and/or send garbage data at the beginning. "),
951 .off = offsetof(dvr_config_t, dvr_warm_time),
952 .opts = PO_EXPERT,
953 .group = 1,
954 .def.u32 = 30
955 },
956 {
957 .type = PT_U32,
958 .id = "pre-extra-time",
959 .name = N_("Pre-recording padding"),
960 .desc = N_("Start recording earlier than the defined "
961 "Start recording earlier than the defined start "
962 "time by x minutes: for example, if a program is "
963 "to start at 13:00 and you set a padding of 5 "
964 "minutes it will start recording at 12:54:30 "
965 "(including a warm-up time of 30 seconds). If this "
966 "isn't specified, any pre-recording padding as set "
967 "in the channel or DVR entry will be used."),
968 .off = offsetof(dvr_config_t, dvr_extra_time_pre),
969 .list = dvr_config_class_extra_list,
970 .opts = PO_DOC_NLIST,
971 .group = 1,
972 },
973 {
974 .type = PT_U32,
975 .id = "post-extra-time",
976 .name = N_("Post-recording padding"),
977 .desc = N_("Continue recording for x minutes after scheduled "
978 "stop time."),
979 .off = offsetof(dvr_config_t, dvr_extra_time_post),
980 .list = dvr_config_class_extra_list,
981 .opts = PO_DOC_NLIST,
982 .group = 1,
983 },
984 {
985 .type = PT_U32,
986 .id = "epg-update-window",
987 .name = N_("EPG update window"),
988 .desc = N_("Maximum allowed difference between event start time when "
989 "the EPG event is changed."),
990 .off = offsetof(dvr_config_t, dvr_update_window),
991 .list = dvr_config_entry_class_update_window_list,
992 .def.u32 = 24*3600,
993 .opts = PO_EXPERT | PO_DOC_NLIST,
994 .group = 1,
995 },
996 {
997 .type = PT_BOOL,
998 .id = "epg-running",
999 .name = N_("Use EPG running state"),
1000 .desc = N_("Use EITp/f to decide event start/stop. This is "
1001 "also known as \"Accurate Recording\". See Help "
1002 "for details."),
1003 .doc = prop_doc_runningstate,
1004 .off = offsetof(dvr_config_t, dvr_running),
1005 .opts = PO_ADVANCED,
1006 .def.u32 = 1,
1007 .group = 1,
1008 },
1009 {
1010 .type = PT_U32,
1011 .id = "autorec-maxcount",
1012 .name = N_("Autorec maximum count (0=unlimited)"),
1013 .desc = N_("The maximum number of entries that can be matched."),
1014 .off = offsetof(dvr_config_t, dvr_autorec_max_count),
1015 .opts = PO_ADVANCED,
1016 .def.i = 50,
1017 .group = 1,
1018 },
1019 {
1020 .type = PT_U32,
1021 .id = "autorec-maxsched",
1022 .name = N_("Autorec maximum schedules limit (0=unlimited)"),
1023 .desc = N_("The maximum number of recordings that can be scheduled."),
1024 .off = offsetof(dvr_config_t, dvr_autorec_max_sched_count),
1025 .opts = PO_ADVANCED,
1026 .group = 1,
1027 },
1028 {
1029 .type = PT_STR,
1030 .id = "preproc",
1031 .name = N_("Pre-processor command"),
1032 .desc = N_("Script/program to run when a recording starts "
1033 "(service is subscribed but no filename available)."),
1034 .doc = prop_doc_preprocessor,
1035 .off = offsetof(dvr_config_t, dvr_preproc),
1036 .opts = PO_EXPERT,
1037 .group = 1,
1038 },
1039 {
1040 .type = PT_STR,
1041 .id = "postproc",
1042 .name = N_("Post-processor command"),
1043 .desc = N_("Script/program to run when a recording completes."),
1044 .doc = prop_doc_postprocessor,
1045 .off = offsetof(dvr_config_t, dvr_postproc),
1046 .opts = PO_ADVANCED,
1047 .group = 1,
1048 },
1049 {
1050 .type = PT_STR,
1051 .id = "postremove",
1052 .name = N_("Post-remove command"),
1053 .desc = N_("Script/program to run when a recording gets removed."),
1054 .doc = prop_doc_postremove,
1055 .off = offsetof(dvr_config_t, dvr_postremove),
1056 .opts = PO_EXPERT,
1057 .group = 1,
1058 },
1059 {
1060 .type = PT_STR,
1061 .id = "storage",
1062 .name = N_("Recording system path"),
1063 .desc = N_("Path where the recordings are stored. If "
1064 "components of the path do not exist, "
1065 "Tvheadend will try to create them."),
1066 .off = offsetof(dvr_config_t, dvr_storage),
1067 .group = 2,
1068 },
1069 {
1070 .type = PT_U32,
1071 .id = "storage-mfree",
1072 .name = N_("Maintain free storage space in MiB"),
1073 .desc = N_("Keep x amount of storage space free."),
1074 .off = offsetof(dvr_config_t, dvr_cleanup_threshold_free),
1075 .def.i = 1000,
1076 .opts = PO_ADVANCED,
1077 .group = 2,
1078 },
1079 {
1080 .type = PT_U32,
1081 .id = "storage-mused",
1082 .name = N_("Maintain used storage space in MiB (0=disabled)"),
1083 .desc = N_("Use x amount of storage space."),
1084 .off = offsetof(dvr_config_t, dvr_cleanup_threshold_used),
1085 .def.i = 0,
1086 .opts = PO_EXPERT,
1087 .group = 2,
1088 },
1089 {
1090 .type = PT_PERM,
1091 .id = "file-permissions",
1092 .name = N_("File permissions (octal, e.g. 0664)"),
1093 .desc = N_("Create files using these permissions."),
1094 .off = offsetof(dvr_config_t, dvr_muxcnf.m_file_permissions),
1095 .opts = PO_EXPERT,
1096 .def.u32 = 0664,
1097 .group = 2,
1098 },
1099 {
1100 .type = PT_STR,
1101 .id = "charset",
1102 .name = N_("Filename character set"),
1103 .desc = N_("Use this character set when setting filenames."),
1104 .off = offsetof(dvr_config_t, dvr_charset),
1105 .set = dvr_config_class_charset_set,
1106 .list = dvr_config_class_charset_list,
1107 .opts = PO_EXPERT,
1108 .def.s = "UTF-8",
1109 .group = 2,
1110 },
1111 {
1112 .type = PT_BOOL,
1113 .id = "tag-files",
1114 .name = N_("Tag files with metadata"),
1115 .desc = N_("Create tags in recordings using media containers "
1116 "that support metadata (if possible)."),
1117 .off = offsetof(dvr_config_t, dvr_tag_files),
1118 .opts = PO_ADVANCED,
1119 .def.i = 1,
1120 .group = 2,
1121 },
1122 {
1123 .type = PT_BOOL,
1124 .id = "skip-commercials",
1125 .name = N_("Skip commercials"),
1126 .desc = N_("Commercials will be dropped from the "
1127 "recordings. Commercial detection works using EITp/f "
1128 "(EPG running state) and for the Swedish channel TV4 "
1129 "(using teletext info)."),
1130 .off = offsetof(dvr_config_t, dvr_skip_commercials),
1131 .opts = PO_ADVANCED,
1132 .def.i = 1,
1133 .group = 2,
1134 },
1135 {
1136 .type = PT_STR,
1137 .id = "pathname",
1138 .name = N_("Format string"),
1139 .desc = N_("The string allows you to manually specify the "
1140 "full path generation using predefined "
1141 "modifiers. See Help for full details."),
1142 .doc = prop_doc_pathname,
1143 .set = dvr_config_class_pathname_set,
1144 .off = offsetof(dvr_config_t, dvr_pathname),
1145 .opts = PO_EXPERT,
1146 .group = 3,
1147 },
1148 {
1149 .type = PT_PERM,
1150 .id = "directory-permissions",
1151 .name = N_("Directory permissions (octal, e.g. 0775)"),
1152 .desc = N_("Create directories using these permissions."),
1153 .off = offsetof(dvr_config_t, dvr_muxcnf.m_directory_permissions),
1154 .opts = PO_EXPERT,
1155 .def.u32 = 0775,
1156 .group = 4,
1157 },
1158 {
1159 .type = PT_BOOL,
1160 .id = "day-dir",
1161 .name = N_("Make subdirectories per day"),
1162 .desc = N_("Create a new directory per day in the "
1163 "recording system path. Folders will only be "
1164 "created when something is recorded. The format "
1165 "of the directory will be ISO standard YYYY-MM-DD."),
1166 .off = offsetof(dvr_config_t, dvr_dir_per_day),
1167 .opts = PO_EXPERT,
1168 .group = 4,
1169 },
1170 {
1171 .type = PT_BOOL,
1172 .id = "channel-dir",
1173 .name = N_("Make subdirectories per channel"),
1174 .desc = N_("Create a directory per channel when "
1175 "storing recordings. If both this and the 'directory "
1176 "per day' checkbox is enabled, the date-directory "
1177 "will be the parent of the per-channel directory."),
1178 .off = offsetof(dvr_config_t, dvr_channel_dir),
1179 .opts = PO_EXPERT,
1180 .group = 4,
1181 },
1182 {
1183 .type = PT_BOOL,
1184 .id = "title-dir",
1185 .name = N_("Make subdirectories per title"),
1186 .desc = N_("Create a directory per title when "
1187 "storing recordings. If the day/channel directory "
1188 "checkboxes are also enabled, those directories "
1189 "will be parents of this directory."),
1190 .off = offsetof(dvr_config_t, dvr_title_dir),
1191 .opts = PO_EXPERT,
1192 .group = 4,
1193 },
1194 {
1195 .type = PT_BOOL,
1196 .id = "channel-in-title",
1197 .name = N_("Include channel name in filename"),
1198 .desc = N_("Include the name of the channel in "
1199 "the event title. This applies to both the title "
1200 "stored in the file and to the filename itself."),
1201 .off = offsetof(dvr_config_t, dvr_channel_in_title),
1202 .opts = PO_ADVANCED,
1203 .group = 5,
1204 },
1205 {
1206 .type = PT_BOOL,
1207 .id = "date-in-title",
1208 .name = N_("Include date in filename"),
1209 .desc = N_("Include the date for the recording in "
1210 "the event title. This applies to both the title "
1211 "stored in the file and to the filename itself."),
1212 .off = offsetof(dvr_config_t, dvr_date_in_title),
1213 .opts = PO_ADVANCED,
1214 .group = 5,
1215 },
1216 {
1217 .type = PT_BOOL,
1218 .id = "time-in-title",
1219 .name = N_("Include time in filename"),
1220 .desc = N_("Include the time for the recording in "
1221 "the event title. This applies to both the title "
1222 "stored in the file and to the filename itself."),
1223 .off = offsetof(dvr_config_t, dvr_time_in_title),
1224 .opts = PO_ADVANCED,
1225 .group = 5,
1226 },
1227 {
1228 .type = PT_BOOL,
1229 .id = "episode-in-title",
1230 .name = N_("Include episode in filename"),
1231 .desc = N_("Include the season and episode in the "
1232 "title (if available)."),
1233 .off = offsetof(dvr_config_t, dvr_episode_in_title),
1234 .opts = PO_EXPERT,
1235 .group = 5,
1236 },
1237 {
1238 .type = PT_BOOL,
1239 .id = "subtitle-in-title",
1240 .name = N_("Include subtitle in filename"),
1241 .desc = N_("Include the episode subtitle in the "
1242 "title (if available)."),
1243 .off = offsetof(dvr_config_t, dvr_subtitle_in_title),
1244 .opts = PO_EXPERT,
1245 .group = 6,
1246 },
1247 {
1248 .type = PT_BOOL,
1249 .id = "omit-title",
1250 .name = N_("Don't include title in filename"),
1251 .desc = N_("Don't include the title in the filename."),
1252 .off = offsetof(dvr_config_t, dvr_omit_title),
1253 .opts = PO_EXPERT,
1254 .group = 6,
1255 },
1256 {
1257 .type = PT_BOOL,
1258 .id = "clean-title",
1259 .name = N_("Remove all unsafe characters from filename"),
1260 .desc = N_("All characters that could possibly "
1261 "cause problems for filenaming will be replaced "
1262 "with an underscore. See Help for details."),
1263 .off = offsetof(dvr_config_t, dvr_clean_title),
1264 .opts = PO_EXPERT,
1265 .group = 6,
1266 },
1267 {
1268 .type = PT_BOOL,
1269 .id = "whitespace-in-title",
1270 .name = N_("Replace whitespace in title with '-'"),
1271 .desc = N_("Replaces all whitespace in the title with '-'."),
1272 .off = offsetof(dvr_config_t, dvr_whitespace_in_title),
1273 .opts = PO_EXPERT,
1274 .group = 6,
1275 },
1276 {
1277 .type = PT_BOOL,
1278 .id = "windows-compatible-filenames",
1279 .name = N_("Use Windows-compatible filenames"),
1280 .desc = N_("Characters not supported in Windows filenames "
1281 "(e.g. for an SMB/CIFS share) will be stripped out "
1282 "or converted."),
1283 .off = offsetof(dvr_config_t, dvr_windows_compatible_filenames),
1284 .opts = PO_ADVANCED,
1285 .group = 6,
1286 },
1287 {}
1288 },
1289 };
1290
1291 /**
1292 *
1293 */
1294 void
dvr_config_destroy_by_profile(profile_t * pro,int delconf)1295 dvr_config_destroy_by_profile(profile_t *pro, int delconf)
1296 {
1297 dvr_config_t *cfg;
1298
1299 while((cfg = LIST_FIRST(&pro->pro_dvr_configs)) != NULL) {
1300 LIST_REMOVE(cfg, profile_link);
1301 cfg->dvr_profile = profile_find_by_name(NULL, "pass");
1302 }
1303 }
1304
1305 /**
1306 *
1307 */
1308 void
dvr_config_init(void)1309 dvr_config_init(void)
1310 {
1311 htsmsg_t *m, *l;
1312 htsmsg_field_t *f;
1313 dvr_config_t *cfg;
1314
1315 dvr_iov_max = sysconf(_SC_IOV_MAX);
1316 if( dvr_iov_max == -1 )
1317 #ifdef IOV_MAX
1318 dvr_iov_max = IOV_MAX;
1319 #else
1320 dvr_iov_max = 16;
1321 #endif
1322
1323 /* Default settings */
1324
1325 LIST_INIT(&dvrconfigs);
1326 idclass_register(&dvr_config_class);
1327
1328 if ((l = hts_settings_load("dvr/config")) != NULL) {
1329 HTSMSG_FOREACH(f, l) {
1330 if ((m = htsmsg_get_map_by_field(f)) == NULL) continue;
1331 (void)dvr_config_create(NULL, f->hmf_name, m);
1332 }
1333 htsmsg_destroy(l);
1334 }
1335
1336 /* Create the default entry */
1337
1338 cfg = dvr_config_find_by_name_default(NULL);
1339 assert(cfg);
1340
1341 LIST_FOREACH(cfg, &dvrconfigs, config_link)
1342 dvr_config_storage_check(cfg);
1343 }
1344
1345 void
dvr_init(void)1346 dvr_init(void)
1347 {
1348 #if ENABLE_INOTIFY
1349 dvr_inotify_init();
1350 #endif
1351 dvr_disk_space_boot();
1352 dvr_autorec_init();
1353 dvr_timerec_init();
1354 dvr_entry_init();
1355 dvr_autorec_update();
1356 dvr_timerec_update();
1357 dvr_disk_space_init();
1358 }
1359
1360 /**
1361 *
1362 */
1363 void
dvr_done(void)1364 dvr_done(void)
1365 {
1366 dvr_config_t *cfg;
1367
1368 #if ENABLE_INOTIFY
1369 dvr_inotify_done();
1370 #endif
1371 pthread_mutex_lock(&global_lock);
1372 dvr_entry_done();
1373 while ((cfg = LIST_FIRST(&dvrconfigs)) != NULL)
1374 dvr_config_destroy(cfg, 0);
1375 pthread_mutex_unlock(&global_lock);
1376 dvr_autorec_done();
1377 dvr_timerec_done();
1378 dvr_disk_space_done();
1379 }
1380