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