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 #include "notify.h"
37 #include "compat.h"
38 
39 struct dvr_entry_list dvrentries;
40 static int dvr_in_init;
41 
42 #if ENABLE_DBUS_1
43 static mtimer_t dvr_dbus_timer;
44 #endif
45 
46 static void dvr_entry_deferred_destroy(dvr_entry_t *de);
47 static void dvr_entry_set_timer(dvr_entry_t *de);
48 static void dvr_timer_rerecord(void *aux);
49 static void dvr_timer_expire(void *aux);
50 static void dvr_timer_disarm(void *aux);
51 static void dvr_timer_remove_files(void *aux);
52 static void dvr_entry_start_recording(dvr_entry_t *de, int clone);
53 static void dvr_timer_start_recording(void *aux);
54 static void dvr_timer_stop_recording(void *aux);
55 static int dvr_entry_rerecord(dvr_entry_t *de);
56 static time_t dvr_entry_get_segment_stop_extra(dvr_entry_t *de);
57 
58 /*
59  *
60  */
61 
62 static struct strtab schedstatetab[] = {
63   { "SCHEDULED",  DVR_SCHEDULED },
64   { "RECORDING",  DVR_RECORDING },
65   { "COMPLETED",  DVR_COMPLETED },
66   { "NOSTATE",    DVR_NOSTATE   },
67   { "MISSEDTM",   DVR_MISSED_TIME }
68 };
69 
70 const char *
dvr_entry_sched_state2str(dvr_entry_sched_state_t s)71 dvr_entry_sched_state2str(dvr_entry_sched_state_t s)
72 {
73   return val2str(s, schedstatetab) ?: "INVALID";
74 }
75 
76 static struct strtab rsstatetab[] = {
77   { "PENDING",    DVR_RS_PENDING },
78   { "WAIT",       DVR_RS_WAIT_PROGRAM_START },
79   { "RUNNING",    DVR_RS_RUNNING },
80   { "COMMERCIAL", DVR_RS_COMMERCIAL },
81   { "ERROR",      DVR_RS_ERROR },
82   { "EPGWAIT",    DVR_RS_EPG_WAIT },
83   { "FINISHED",   DVR_RS_FINISHED }
84 };
85 
86 const char *
dvr_entry_rs_state2str(dvr_rs_state_t s)87 dvr_entry_rs_state2str(dvr_rs_state_t s)
88 {
89   return val2str(s, rsstatetab) ?: "INVALID";
90 }
91 
92 void
dvr_entry_trace_(const char * file,int line,dvr_entry_t * de,const char * fmt,...)93 dvr_entry_trace_(const char *file, int line, dvr_entry_t *de, const char *fmt, ...)
94 {
95   char buf[512], ubuf[UUID_HEX_SIZE];
96   va_list args;
97   va_start(args, fmt);
98   snprintf(buf, sizeof(buf), "entry %s - %s",
99                              idnode_uuid_as_str(&de->de_id, ubuf),
100                              fmt);
101   tvhlogv(file, line, LOG_TRACE, LS_DVR, buf, &args);
102   va_end(args);
103 }
104 
105 void
dvr_entry_trace_time2_(const char * file,int line,dvr_entry_t * de,const char * t1name,time_t t1,const char * t2name,time_t t2,const char * fmt,...)106 dvr_entry_trace_time2_(const char *file, int line,
107                        dvr_entry_t *de,
108                        const char *t1name, time_t t1,
109                        const char *t2name, time_t t2,
110                        const char *fmt, ...)
111 {
112   char buf[512], ubuf[UUID_HEX_SIZE], t1buf[32], t2buf[32];
113   va_list args;
114   va_start(args, fmt);
115   snprintf(buf, sizeof(buf), "entry %s%s%s%s%s%s%s%s%s - %s",
116                              idnode_uuid_as_str(&de->de_id, ubuf),
117                              t1name ? " " : "",
118                              t1name ? t1name : "",
119                              t1name ? " " : "",
120                              t1name ? gmtime2local(t1, t1buf, sizeof(t1buf)) : "",
121                              t2name ? " " : "",
122                              t2name ? t2name : "",
123                              t2name ? " " : "",
124                              t2name ? gmtime2local(t2, t2buf, sizeof(t2buf)) : "",
125                              fmt);
126   tvhlogv(file, line, LOG_TRACE, LS_DVR, buf, &args);
127   va_end(args);
128 }
129 
dvr_entry_is_upcoming(dvr_entry_t * entry)130 int dvr_entry_is_upcoming(dvr_entry_t *entry)
131 {
132   dvr_entry_sched_state_t state = entry->de_sched_state;
133   return state == DVR_RECORDING || state == DVR_SCHEDULED || state == DVR_NOSTATE;
134 }
135 
dvr_entry_is_finished(dvr_entry_t * entry,int flags)136 int dvr_entry_is_finished(dvr_entry_t *entry, int flags)
137 {
138   if (dvr_entry_is_upcoming(entry))
139     return 0;
140   if (!flags || (flags & DVR_FINISHED_ALL))
141     return 1;
142 
143   int removed = entry->de_file_removed ||                                               /* Removed by tvheadend */
144       (entry->de_sched_state != DVR_MISSED_TIME && dvr_get_filesize(entry, 0) == -1);   /* Removed externally? */
145   int success = entry->de_sched_state == DVR_COMPLETED;
146 
147   if (success && entry->de_last_error != SM_CODE_FORCE_OK)
148       success = entry->de_last_error == SM_CODE_OK &&
149                 entry->de_data_errors < DVR_MAX_DATA_ERRORS;
150 
151   if ((flags & DVR_FINISHED_REMOVED_SUCCESS) && removed && success)
152     return 1;
153   if ((flags & DVR_FINISHED_REMOVED_FAILED) && removed && !success)
154     return 1;
155   if ((flags & DVR_FINISHED_SUCCESS) && success && !removed)
156     return 1;
157   if ((flags & DVR_FINISHED_FAILED) && !success && !removed)
158     return 1;
159   return 0;
160 }
161 
162 /*
163  *
164  */
165 int
dvr_entry_verify(dvr_entry_t * de,access_t * a,int readonly)166 dvr_entry_verify(dvr_entry_t *de, access_t *a, int readonly)
167 {
168   if (access_verify2(a, ACCESS_FAILED_RECORDER) &&
169       dvr_entry_is_finished(de, DVR_FINISHED_FAILED))
170     return -1;
171 
172   if (readonly && !access_verify2(a, ACCESS_ALL_RECORDER))
173     return 0;
174 
175   if (!access_verify2(a, ACCESS_ALL_RW_RECORDER))
176     return 0;
177 
178   if (strcmp(de->de_owner ?: "", a->aa_username ?: ""))
179     return -1;
180   return 0;
181 }
182 
183 /*
184  *
185  */
186 void
dvr_entry_changed_notify(dvr_entry_t * de)187 dvr_entry_changed_notify(dvr_entry_t *de)
188 {
189   idnode_changed(&de->de_id);
190   htsp_dvr_entry_update(de);
191 }
192 
193 /*
194  *
195  */
196 int
dvr_entry_set_state(dvr_entry_t * de,dvr_entry_sched_state_t state,dvr_rs_state_t rec_state,int error_code)197 dvr_entry_set_state(dvr_entry_t *de, dvr_entry_sched_state_t state,
198                     dvr_rs_state_t rec_state, int error_code)
199 {
200   char id[16];
201   if (de->de_sched_state != state ||
202       de->de_rec_state != rec_state ||
203       de->de_last_error != error_code) {
204     dvr_entry_trace(de, "set state - state %s rec_state %s error '%s'",
205                     dvr_entry_sched_state2str(state),
206                     dvr_entry_rs_state2str(rec_state),
207                     streaming_code2txt(error_code));
208     if (de->de_bcast) {
209       snprintf(id, sizeof(id), "%u", de->de_bcast->id);
210       notify_delayed(id, "epg", "dvr_update");
211     }
212     de->de_sched_state = state;
213     de->de_rec_state = rec_state;
214     de->de_last_error = error_code;
215     idnode_notify_changed(&de->de_id);
216     htsp_dvr_entry_update(de);
217     return 1;
218   }
219   return 0;
220 }
221 
222 /*
223  *
224  */
225 static int
dvr_entry_assign_broadcast(dvr_entry_t * de,epg_broadcast_t * bcast)226 dvr_entry_assign_broadcast(dvr_entry_t *de, epg_broadcast_t *bcast)
227 {
228   char id[16];
229   if (bcast != de->de_bcast) {
230     if (de->de_bcast) {
231       snprintf(id, sizeof(id), "%u", de->de_bcast->id);
232       dvr_entry_trace(de, "unassign broadcast %s", id);
233       de->de_bcast->ops->putref((epg_object_t*)de->de_bcast);
234       notify_delayed(id, "epg", "dvr_delete");
235       de->de_bcast = NULL;
236       de->de_dvb_eid = 0;
237     }
238     if (bcast) {
239       bcast->ops->getref((epg_object_t*)bcast);
240       de->de_bcast = bcast;
241       snprintf(id, sizeof(id), "%u", bcast->id);
242       dvr_entry_trace(de, "assign broadcast %s", id);
243       notify_delayed(id, "epg", "dvr_update");
244     }
245     return 1;
246   }
247   return 0;
248 }
249 
250 /*
251  *
252  */
253 static void
dvr_entry_dont_rerecord(dvr_entry_t * de,int dont_rerecord)254 dvr_entry_dont_rerecord(dvr_entry_t *de, int dont_rerecord)
255 {
256   dont_rerecord = dont_rerecord ? 1 : 0;
257   if (de->de_dont_rerecord ? 1 : 0 != dont_rerecord) {
258     dvr_entry_trace(de, "don't rerecord change %d", dont_rerecord);
259     de->de_dont_rerecord = dont_rerecord;
260     dvr_entry_changed_notify(de);
261   }
262 }
263 
264 /*
265  *
266  */
267 static int
dvr_entry_change_parent_child(dvr_entry_t * parent,dvr_entry_t * child,void * origin,int save)268 dvr_entry_change_parent_child(dvr_entry_t *parent, dvr_entry_t *child, void *origin, int save)
269 {
270   dvr_entry_t *p;
271 
272   if (parent == NULL && child == NULL)
273     return 0;
274   if (parent && parent->de_child == child) {
275     assert(child == NULL || child->de_parent == parent);
276     return 0;
277   }
278   if (child && child->de_parent == parent) {
279     assert(parent == NULL || parent->de_child == child);
280     return 0;
281   }
282   if (child == NULL) {
283     if (parent->de_child) {
284       p = parent->de_child->de_parent;
285       parent->de_child->de_parent = NULL;
286       if (save && p && p != origin) idnode_changed(&p->de_id);
287       parent->de_child = NULL;
288       if (save && origin != parent) idnode_changed(&parent->de_id);
289       return 1;
290     }
291     return 0;
292   }
293   if (parent == NULL) {
294     if (child->de_parent) {
295       p = child->de_parent->de_child;
296       child->de_parent->de_child = NULL;
297       if (save && p && p != origin) idnode_changed(&p->de_id);
298       child->de_parent = NULL;
299       if (save && origin != child) idnode_changed(&child->de_id);
300       return 1;
301     }
302     return 0;
303   }
304   if (parent->de_child) {
305     p = parent->de_child->de_parent;
306     parent->de_child->de_parent = NULL;
307     if (save && p) idnode_changed(&p->de_id);
308   }
309   if (child->de_parent) {
310     p = child->de_parent->de_child;
311     child->de_parent->de_child = NULL;
312     if (save && p) idnode_changed(&p->de_id);
313   }
314   parent->de_child = child;
315   child->de_parent = parent;
316   if (save && origin != parent) idnode_changed(&parent->de_id);
317   if (save && origin != child) idnode_changed(&child->de_id);
318   return 1;
319 }
320 
321 /*
322  * Start / stop time calculators
323  */
extra_valid(time_t extra)324 static inline int extra_valid(time_t extra)
325 {
326   return extra != 0 && extra != (time_t)-1;
327 }
328 
329 static uint32_t
dvr_entry_warm_time(dvr_entry_t * de)330 dvr_entry_warm_time( dvr_entry_t *de )
331 {
332   return MIN(de->de_config->dvr_warm_time, 600);
333 }
334 
335 time_t
dvr_entry_get_start_time(dvr_entry_t * de,int warm)336 dvr_entry_get_start_time( dvr_entry_t *de, int warm )
337 {
338   int64_t b = (dvr_entry_get_extra_time_pre(de)) +
339               (warm ? dvr_entry_warm_time(de) : 0);
340   if (de->de_start < b)
341     return 0;
342   return de->de_start - b;
343 }
344 
345 time_t
dvr_entry_get_stop_time(dvr_entry_t * de)346 dvr_entry_get_stop_time( dvr_entry_t *de )
347 {
348   return time_t_out_of_range((int64_t)de->de_stop + dvr_entry_get_segment_stop_extra(de) + dvr_entry_get_extra_time_post(de));
349 }
350 
351 time_t
dvr_entry_get_extra_time_pre(dvr_entry_t * de)352 dvr_entry_get_extra_time_pre( dvr_entry_t *de )
353 {
354   time_t extra = de->de_start_extra;
355 
356   if (de->de_timerec)
357     return 0;
358   if (!extra_valid(extra)) {
359     if (de->de_channel)
360       extra = de->de_channel->ch_dvr_extra_time_pre;
361     if (!extra_valid(extra))
362       extra = de->de_config->dvr_extra_time_pre;
363   }
364   return MINMAX(extra, 0, 24*60) * 60;
365 }
366 
367 time_t
dvr_entry_get_extra_time_post(dvr_entry_t * de)368 dvr_entry_get_extra_time_post( dvr_entry_t *de )
369 {
370   time_t extra = de->de_stop_extra;
371 
372   if (de->de_timerec)
373     return 0;
374   if (!extra_valid(extra)) {
375     if (de->de_channel)
376       extra = de->de_channel->ch_dvr_extra_time_post;
377     if (!extra_valid(extra))
378       extra = de->de_config->dvr_extra_time_post;
379   }
380   return MINMAX(extra, 0, 24*60) * 60;
381 }
382 
383 char *
dvr_entry_get_retention_string(dvr_entry_t * de)384 dvr_entry_get_retention_string ( dvr_entry_t *de )
385 {
386   char buf[24];
387   uint32_t retention = dvr_entry_get_retention_days(de);
388 
389   if (retention < DVR_RET_ONREMOVE)
390     snprintf(buf, sizeof(buf), "%i days", retention);
391   else if (retention == DVR_RET_ONREMOVE)
392     return strdup("On file removal");
393   else
394     return strdup("Forever");
395 
396   return strdup(buf);
397 }
398 
399 char *
dvr_entry_get_removal_string(dvr_entry_t * de)400 dvr_entry_get_removal_string ( dvr_entry_t *de )
401 {
402   char buf[24];
403   uint32_t removal = dvr_entry_get_removal_days(de);
404 
405   if (removal < DVR_REM_SPACE)
406     snprintf(buf, sizeof(buf), "%i days", removal);
407   else if (removal == DVR_REM_SPACE)
408     return strdup("Until space needed");
409   else
410     return strdup("Forever");
411 
412   return strdup(buf);
413 }
414 
415 uint32_t
dvr_entry_get_retention_days(dvr_entry_t * de)416 dvr_entry_get_retention_days( dvr_entry_t *de )
417 {
418   if (de->de_retention > 0) {
419     if (de->de_retention > DVR_RET_REM_FOREVER)
420       return DVR_RET_REM_FOREVER;
421 
422     return de->de_retention;
423   }
424   return dvr_retention_cleanup(de->de_config->dvr_retention_days);
425 }
426 
427 uint32_t
dvr_entry_get_removal_days(dvr_entry_t * de)428 dvr_entry_get_removal_days ( dvr_entry_t *de )
429 {
430   if (de->de_removal > 0) {
431     if (de->de_removal > DVR_RET_REM_FOREVER)
432       return DVR_RET_REM_FOREVER;
433 
434     return de->de_removal;
435   }
436   return dvr_retention_cleanup(de->de_config->dvr_removal_days);
437 }
438 
439 uint32_t
dvr_entry_get_rerecord_errors(dvr_entry_t * de)440 dvr_entry_get_rerecord_errors( dvr_entry_t *de )
441 {
442   return de->de_config->dvr_rerecord_errors;
443 }
444 
445 int
dvr_entry_get_epg_running(dvr_entry_t * de)446 dvr_entry_get_epg_running( dvr_entry_t *de )
447 {
448   channel_t *ch;
449 
450   if (de->de_dvb_eid == 0)
451     return 0;
452   if ((ch = de->de_channel) == NULL)
453     return 0;
454   if (ch->ch_epg_running < 0)
455     return de->de_config->dvr_running;
456   return ch->ch_epg_running > 0;
457 }
458 
459 /*
460  * DBUS next dvr start notifications
461  */
462 #if ENABLE_DBUS_1
463 static void
dvr_dbus_timer_cb(void * aux)464 dvr_dbus_timer_cb( void *aux )
465 {
466   dvr_entry_t *de;
467   time_t result, start, max = 0;
468   static time_t last_result = 0;
469 
470   lock_assert(&global_lock);
471 
472   /* find the maximum value */
473   LIST_FOREACH(de, &dvrentries, de_global_link) {
474     if (de->de_sched_state != DVR_SCHEDULED)
475       continue;
476     start = dvr_entry_get_start_time(de, 1);
477     if (gclk() < start && start > max)
478       max = start;
479   }
480   /* lower the maximum value */
481   result = max;
482   LIST_FOREACH(de, &dvrentries, de_global_link) {
483     if (de->de_sched_state != DVR_SCHEDULED)
484       continue;
485     start = dvr_entry_get_start_time(de, 1);
486     if (gclk() < start && start < result)
487       result = start;
488   }
489   /* different? send it.... */
490   if (result && result != last_result) {
491     dbus_emit_signal_s64("/dvr", "next", result);
492     last_result = result;
493   }
494 }
495 #endif
496 
497 /*
498  *
499  */
500 static void
dvr_entry_retention_arm(dvr_entry_t * de,gti_callback_t * cb,time_t when)501 dvr_entry_retention_arm(dvr_entry_t *de, gti_callback_t *cb, time_t when)
502 {
503   uint32_t rerecord = dvr_entry_get_rerecord_errors(de);
504   if (rerecord && (when - gclk()) > 3600) {
505     when = gclk() + 3600;
506     cb = dvr_timer_rerecord;
507     dvr_entry_trace_time1(de, "when", when, "retention arm - rerecord");
508   } else {
509     dvr_entry_trace_time1(de, "when", when, "retention arm", "when");
510   }
511   gtimer_arm_absn(&de->de_timer, cb, de, when);
512 }
513 
514 /*
515  * Returns 1 if the database entry should be deleted on file removal
516  * NOTE: retention can be postponed when retention < removal
517  */
518 static int
dvr_entry_delete_retention_expired(dvr_entry_t * de)519 dvr_entry_delete_retention_expired(dvr_entry_t *de)
520 {
521   uint32_t retention = dvr_entry_get_retention_days(de);
522   time_t stop;
523 
524   if (retention == DVR_RET_ONREMOVE)
525     return 1;
526   if (retention < DVR_RET_ONREMOVE) {
527     stop = time_t_out_of_range((int64_t)de->de_stop + retention * (int64_t)86400);
528     if (stop <= gclk())
529       return 1;
530   }
531   return 0;
532 }
533 
534 /*
535  *
536  */
537 static void
dvr_entry_retention_timer(dvr_entry_t * de)538 dvr_entry_retention_timer(dvr_entry_t *de)
539 {
540   time_t stop = de->de_stop;
541   uint32_t removal = dvr_entry_get_removal_days(de);
542   uint32_t retention = dvr_entry_get_retention_days(de);
543   int save;
544 
545   if ((removal > 0 || retention == 0) && removal < DVR_REM_SPACE && dvr_get_filesize(de, 0) > 0) {
546     stop = time_t_out_of_range((int64_t)de->de_stop + removal * (int64_t)86400);
547     if (stop > gclk()) {
548       dvr_entry_retention_arm(de, dvr_timer_remove_files, stop);
549       return;
550     }
551     save = 0;
552     if (dvr_get_filename(de))
553       save = dvr_entry_delete(de);                // delete actual file
554     if (dvr_entry_delete_retention_expired(de)) { // in case retention was postponed (retention < removal)
555       dvr_entry_deferred_destroy(de);             // also remove database entry
556       return;
557     }
558     if (save)
559       dvr_entry_changed_notify(de);
560   }
561 
562   if (retention < DVR_RET_ONREMOVE &&
563       (dvr_get_filesize(de, 0) < 0 || removal == DVR_RET_REM_FOREVER)) { // we need the database entry for later file removal
564     stop = time_t_out_of_range((int64_t)de->de_stop + retention * (int64_t)86400);
565     dvr_entry_retention_arm(de, dvr_timer_expire, stop);
566   }
567   else {
568     dvr_entry_retention_arm(de, dvr_timer_disarm,
569         dvr_entry_get_rerecord_errors(de) ? INT_MAX : 0); // extend disarm to keep the rerecord logic running
570   }
571 }
572 
573 /*
574  * No state
575  */
576 static void
dvr_entry_nostate(dvr_entry_t * de,int error_code)577 dvr_entry_nostate(dvr_entry_t *de, int error_code)
578 {
579   dvr_entry_set_state(de, DVR_NOSTATE, DVR_RS_FINISHED, error_code);
580   dvr_entry_retention_timer(de);
581 }
582 
583 /*
584  * Missed time
585  */
586 static void
dvr_entry_missed_time(dvr_entry_t * de,int error_code)587 dvr_entry_missed_time(dvr_entry_t *de, int error_code)
588 {
589   dvr_autorec_entry_t *dae = de->de_autorec;
590 
591   dvr_entry_set_state(de, DVR_MISSED_TIME, DVR_RS_FINISHED, error_code);
592   dvr_entry_retention_timer(de);
593 
594   // Trigger autorec update in case of max schedules limit
595   if (dae && dvr_autorec_get_max_sched_count(dae) > 0)
596     dvr_autorec_changed(dae, 0);
597 }
598 
599 /*
600  * Completed
601  */
602 static int
dvr_entry_completed(dvr_entry_t * de,int error_code)603 dvr_entry_completed(dvr_entry_t *de, int error_code)
604 {
605   int change;
606   change = dvr_entry_set_state(de, DVR_COMPLETED, DVR_RS_FINISHED, error_code);
607 #if ENABLE_INOTIFY
608   dvr_inotify_add(de);
609 #endif
610   dvr_entry_retention_timer(de);
611   if (de->de_autorec)
612     dvr_autorec_completed(de->de_autorec, error_code);
613   return change;
614 }
615 
616 /**
617  * Return printable status for a dvr entry
618  */
619 const char *
dvr_entry_status(dvr_entry_t * de)620 dvr_entry_status(dvr_entry_t *de)
621 {
622   switch(de->de_sched_state) {
623   case DVR_SCHEDULED:
624     return N_("Scheduled for recording");
625 
626   case DVR_RECORDING:
627 
628     switch(de->de_rec_state) {
629     case DVR_RS_PENDING:
630       return N_("Waiting for stream");
631     case DVR_RS_WAIT_PROGRAM_START:
632       return N_("Waiting for program start");
633     case DVR_RS_RUNNING:
634       return N_("Running");
635     case DVR_RS_COMMERCIAL:
636       return N_("Commercial break");
637     case DVR_RS_ERROR:
638       return streaming_code2txt(de->de_last_error);
639     case DVR_RS_EPG_WAIT:
640       return N_("Waiting for EPG running flag");
641     case DVR_RS_FINISHED:
642       return N_("Finished");
643     default:
644       return N_("Invalid");
645     }
646 
647   case DVR_COMPLETED:
648     switch(de->de_last_error) {
649       case SM_CODE_INVALID_TARGET:
650         return N_("File not created");
651       case SM_CODE_USER_ACCESS:
652         return N_("User access error");
653       case SM_CODE_USER_LIMIT:
654         return N_("User limit reached");
655       case SM_CODE_NO_SPACE:
656         return streaming_code2txt(de->de_last_error);
657       default:
658         break;
659     }
660     if (dvr_get_filesize(de, 0) == -1 && !de->de_file_removed)
661       return N_("File missing");
662     if(de->de_last_error != SM_CODE_FORCE_OK &&
663        de->de_data_errors >= DVR_MAX_DATA_ERRORS) /* user configurable threshold? */
664       return N_("Too many data errors");
665     if(de->de_last_error)
666       return streaming_code2txt(de->de_last_error);
667     else
668       return N_("Completed OK");
669 
670   case DVR_MISSED_TIME:
671     if (de->de_last_error == SM_CODE_SVC_NOT_ENABLED || de->de_last_error == SM_CODE_NO_SPACE)
672       return streaming_code2txt(de->de_last_error);
673     return N_("Time missed");
674 
675   default:
676     return N_("Invalid");
677   }
678 }
679 
680 
681 /**
682  *
683  */
684 const char *
dvr_entry_schedstatus(dvr_entry_t * de)685 dvr_entry_schedstatus(dvr_entry_t *de)
686 {
687   const char *s;
688   uint32_t rerecord;
689 
690   switch(de->de_sched_state) {
691   case DVR_SCHEDULED:
692     s = "scheduled";
693     break;
694   case DVR_RECORDING:
695     s = de->de_last_error ? "recordingError" : "recording";
696     break;
697   case DVR_COMPLETED:
698     s = "completed";
699     if(de->de_last_error ||
700       (dvr_get_filesize(de, 0) == -1 && !de->de_file_removed))
701       s = "completedError";
702     rerecord = de->de_dont_rerecord ? 0 : dvr_entry_get_rerecord_errors(de);
703     if(rerecord && (de->de_errors || de->de_data_errors > rerecord))
704       s = "completedRerecord";
705     break;
706   case DVR_MISSED_TIME:
707     s = "completedError";
708     if (de->de_last_error == SM_CODE_SVC_NOT_ENABLED)
709       s = "completedWarning";
710     rerecord = de->de_dont_rerecord ? 0 : dvr_entry_get_rerecord_errors(de);
711     if(rerecord)
712       s = "completedRerecord";
713     break;
714   default:
715     s = "unknown";
716   }
717   return s;
718 }
719 
720 /**
721  *
722  */
723 uint32_t
dvr_usage_count(access_t * aa)724 dvr_usage_count(access_t *aa)
725 {
726   dvr_entry_t *de;
727   uint32_t used = 0;
728 
729   LIST_FOREACH(de, &dvrentries, de_global_link) {
730     if (de->de_sched_state != DVR_RECORDING)
731       continue;
732     if (de->de_owner && de->de_owner[0]) {
733       if (!strcmp(de->de_owner, aa->aa_username ?: ""))
734         used++;
735     } else if (!strcmp(de->de_creator ?: "", aa->aa_representative ?: "")) {
736       used++;
737     }
738   }
739 
740   return used;
741 }
742 
743 void
dvr_entry_set_timer(dvr_entry_t * de)744 dvr_entry_set_timer(dvr_entry_t *de)
745 {
746   time_t now = gclk(), start, stop;
747 
748   if (dvr_in_init)
749     return;
750 
751   start = dvr_entry_get_start_time(de, 1);
752   stop  = dvr_entry_get_stop_time(de);
753 
754   dvr_entry_trace_time2(de, "start", start, "stop", stop, "set timer");
755 
756   if (now >= stop || de->de_dont_reschedule) {
757 
758     /* EPG thinks that the program is running */
759     if(de->de_running_start > de->de_running_stop && !de->de_dont_reschedule) {
760       stop = now + 10;
761       if (de->de_sched_state == DVR_RECORDING) {
762         dvr_entry_trace_time1(de, "stop", stop, "set timer - running+");
763         goto recording;
764       }
765     }
766 
767     if (de->de_sched_state == DVR_RECORDING) {
768       dvr_stop_recording(de, de->de_last_error, 1, 0);
769       return;
770     }
771 
772     /* Files are missing and job was completed */
773     if(htsmsg_is_empty(de->de_files) && !de->de_dont_reschedule)
774       dvr_entry_missed_time(de, de->de_last_error);
775     else
776       dvr_entry_completed(de, de->de_last_error);
777 
778     if(dvr_entry_rerecord(de))
779       return;
780 
781   } else if (de->de_sched_state == DVR_RECORDING)  {
782     if (!de->de_enabled) {
783       dvr_stop_recording(de, SM_CODE_ABORTED, 1, 0);
784       return;
785     }
786 
787 recording:
788     dvr_entry_trace_time1(de, "stop", stop, "set timer - arm");
789     gtimer_arm_absn(&de->de_timer, dvr_timer_stop_recording, de, stop);
790 
791   } else if (de->de_channel && de->de_channel->ch_enabled) {
792 
793     dvr_entry_set_state(de, DVR_SCHEDULED, DVR_RS_PENDING, de->de_last_error);
794 
795     dvr_entry_trace_time1(de, "start", start, "set timer - schedule");
796     gtimer_arm_absn(&de->de_timer, dvr_timer_start_recording, de, start);
797 #if ENABLE_DBUS_1
798     mtimer_arm_rel(&dvr_dbus_timer, dvr_dbus_timer_cb, NULL, sec2mono(5));
799 #endif
800 
801   } else {
802 
803     dvr_entry_trace(de, "set timer - nostate");
804     dvr_entry_nostate(de, de->de_last_error);
805 
806   }
807 }
808 
809 
810 /**
811  * Get episode name
812  */
813 static char *
dvr_entry_get_episode(epg_broadcast_t * bcast,char * buf,int len)814 dvr_entry_get_episode(epg_broadcast_t *bcast, char *buf, int len)
815 {
816   if (!bcast || !bcast->episode)
817     return NULL;
818   if (epg_episode_number_format(bcast->episode,
819                                 buf, len, NULL,
820                                 _("Season %d"), ".", _("Episode %d"), "/%d"))
821     return buf;
822   return NULL;
823 }
824 
825 /**
826  * Find dvr entry using 'fuzzy' search
827  */
828 static int
dvr_entry_fuzzy_match(dvr_entry_t * de,epg_broadcast_t * e,uint16_t eid,int64_t time_window)829 dvr_entry_fuzzy_match(dvr_entry_t *de, epg_broadcast_t *e, uint16_t eid, int64_t time_window)
830 {
831   time_t t1, t2;
832   const char *title1, *title2;
833   char buf[64];
834 
835   /* Wrong length (+/-20%) */
836   t1 = de->de_stop - de->de_start;
837   t2 = e->stop - e->start;
838   if (labs((long)(t2 - t1)) > (t1 / 5))
839     return 0;
840 
841   /* Matching ID */
842   if (de->de_dvb_eid && eid && de->de_dvb_eid == eid)
843     return 1;
844 
845   /* No title */
846   if (!(title1 = epg_broadcast_get_title(e, NULL)))
847     return 0;
848   if (!(title2 = lang_str_get(de->de_title, NULL)))
849     return 0;
850 
851   /* Outside of window */
852   if ((int64_t)llabs(e->start - de->de_start) > time_window)
853     return 0;
854 
855   /* Title match (or contains?) */
856   if (strcmp(title1, title2))
857     return 0;
858 
859   /* episode check */
860   if (dvr_entry_get_episode(e, buf, sizeof(buf)) && de->de_episode)
861     if (strcmp(buf, de->de_episode))
862       return 0;
863 
864   return 1;
865 }
866 
867 /**
868  * Create the event from config
869  */
870 dvr_entry_t *
dvr_entry_create(const char * uuid,htsmsg_t * conf,int clone)871 dvr_entry_create(const char *uuid, htsmsg_t *conf, int clone)
872 {
873   dvr_entry_t *de, *de2;
874   int64_t start, stop;
875   htsmsg_t *m;
876   char ubuf[UUID_HEX_SIZE];
877 
878   if (conf) {
879     if (htsmsg_get_s64(conf, "start", &start))
880       return NULL;
881     if (htsmsg_get_s64(conf, "stop", &stop))
882       return NULL;
883     if ((htsmsg_get_str(conf, "channel")) == NULL &&
884         (htsmsg_get_str(conf, "channelname")) == NULL)
885       return NULL;
886   }
887 
888   de = calloc(1, sizeof(*de));
889 
890   if (idnode_insert(&de->de_id, uuid, &dvr_entry_class, IDNODE_SHORT_UUID)) {
891     if (uuid)
892       tvhwarn(LS_DVR, "invalid entry uuid '%s'", uuid);
893     free(de);
894     return NULL;
895   }
896 
897   de->de_enabled = 1;
898   de->de_config = dvr_config_find_by_name_default(NULL);
899   if (de->de_config)
900     LIST_INSERT_HEAD(&de->de_config->dvr_entries, de, de_config_link);
901 
902   idnode_load(&de->de_id, conf);
903 
904   /* filenames */
905   m = htsmsg_get_list(conf, "files");
906   if (m)
907     de->de_files = htsmsg_copy(m);
908 
909   de->de_refcnt = 1;
910 
911   LIST_INSERT_HEAD(&dvrentries, de, de_global_link);
912 
913   if (de->de_channel && !de->de_dont_reschedule && !clone) {
914     LIST_FOREACH(de2, &de->de_channel->ch_dvrs, de_channel_link)
915       if(de2 != de &&
916          de2->de_config == de->de_config &&
917          de2->de_start == de->de_start &&
918          de2->de_sched_state != DVR_COMPLETED &&
919          de2->de_sched_state != DVR_MISSED_TIME &&
920          strcmp(de2->de_owner ?: "", de->de_owner ?: "") == 0) {
921         tvhinfo(LS_DVR, "delete entry %s \"%s\" on \"%s\" start time %"PRId64", "
922            "scheduled for recording by \"%s\" (duplicate with %s)",
923           idnode_uuid_as_str(&de->de_id, ubuf),
924           lang_str_get(de->de_title, NULL), DVR_CH_NAME(de),
925           (int64_t)de2->de_start, de->de_creator ?: "",
926           idnode_uuid_as_str(&de2->de_id, ubuf));
927         dvr_entry_destroy(de, 1);
928         return NULL;
929       }
930   }
931 
932   if (!clone)
933     dvr_entry_set_timer(de);
934   htsp_dvr_entry_add(de);
935   dvr_vfs_refresh_entry(de);
936 
937   return de;
938 }
939 
940 /**
941  * Create the event
942  */
943 dvr_entry_t *
dvr_entry_create_(int enabled,const char * config_uuid,epg_broadcast_t * e,channel_t * ch,time_t start,time_t stop,time_t start_extra,time_t stop_extra,const char * title,const char * subtitle,const char * description,const char * lang,epg_genre_t * content_type,const char * owner,const char * creator,dvr_autorec_entry_t * dae,dvr_timerec_entry_t * dte,dvr_prio_t pri,int retention,int removal,const char * comment)944 dvr_entry_create_(int enabled, const char *config_uuid, epg_broadcast_t *e,
945                   channel_t *ch, time_t start, time_t stop,
946                   time_t start_extra, time_t stop_extra,
947                   const char *title, const char* subtitle, const char *description,
948                   const char *lang, epg_genre_t *content_type,
949                   const char *owner,
950                   const char *creator, dvr_autorec_entry_t *dae,
951                   dvr_timerec_entry_t *dte,
952                   dvr_prio_t pri, int retention, int removal,
953                   const char *comment)
954 {
955   dvr_entry_t *de;
956   char tbuf[64], *s;
957   struct tm tm;
958   time_t t;
959   lang_str_t *l;
960   htsmsg_t *conf;
961   char ubuf[UUID_HEX_SIZE];
962 
963   conf = htsmsg_create_map();
964   if (enabled >= 0)
965     htsmsg_add_u32(conf, "enabled", !!enabled);
966   htsmsg_add_s64(conf, "start", start);
967   htsmsg_add_s64(conf, "stop", stop);
968   htsmsg_add_str(conf, "channel", idnode_uuid_as_str(&ch->ch_id, ubuf));
969   htsmsg_add_u32(conf, "pri", pri);
970   htsmsg_add_u32(conf, "retention", retention);
971   htsmsg_add_u32(conf, "removal", removal);
972   htsmsg_add_str(conf, "config_name", config_uuid ?: "");
973   htsmsg_add_s64(conf, "start_extra", start_extra);
974   htsmsg_add_s64(conf, "stop_extra", stop_extra);
975   htsmsg_add_str(conf, "owner", owner ?: "");
976   htsmsg_add_str(conf, "creator", creator ?: "");
977   htsmsg_add_str(conf, "comment", comment ?: "");
978   if (e) {
979     htsmsg_add_u32(conf, "dvb_eid", e->dvb_eid);
980     if (e->episode && e->episode->title)
981       lang_str_serialize(e->episode->title, conf, "title");
982     if (e->episode && e->episode->subtitle)
983       lang_str_serialize(e->episode->subtitle, conf, "subtitle");
984     if (e->description)
985       lang_str_serialize(e->description, conf, "description");
986     else if (e->episode && e->episode->description)
987       lang_str_serialize(e->episode->description, conf, "description");
988     else if (e->summary)
989       lang_str_serialize(e->summary, conf, "description");
990     else if (e->episode && e->episode->summary)
991       lang_str_serialize(e->episode->summary, conf, "description");
992     if (e->episode && (s = dvr_entry_get_episode(e, tbuf, sizeof(tbuf))))
993       htsmsg_add_str(conf, "episode", s);
994   } else if (title) {
995     l = lang_str_create();
996     lang_str_add(l, title, lang, 0);
997     lang_str_serialize(l, conf, "title");
998     lang_str_destroy(l);
999     if (description) {
1000       l = lang_str_create();
1001       lang_str_add(l, description, lang, 0);
1002       lang_str_serialize(l, conf, "description");
1003       lang_str_destroy(l);
1004     }
1005     if (subtitle) {
1006       l = lang_str_create();
1007       lang_str_add(l, subtitle, lang, 0);
1008       lang_str_serialize(l, conf, "subtitle");
1009       lang_str_destroy(l);
1010     }
1011   }
1012   if (content_type)
1013     htsmsg_add_u32(conf, "content_type", content_type->code / 16);
1014   if (e)
1015     htsmsg_add_u32(conf, "broadcast", e->id);
1016   if (dae)
1017   {
1018     htsmsg_add_str(conf, "autorec", idnode_uuid_as_str(&dae->dae_id, ubuf));
1019     htsmsg_add_str(conf, "directory", dae->dae_directory ?: "");
1020   }
1021   if (dte)
1022   {
1023     htsmsg_add_str(conf, "timerec", idnode_uuid_as_str(&dte->dte_id, ubuf));
1024     htsmsg_add_str(conf, "directory", dte->dte_directory ?: "");
1025   }
1026 
1027   de = dvr_entry_create(NULL, conf, 0);
1028 
1029   htsmsg_destroy(conf);
1030 
1031   if (de == NULL)
1032     return NULL;
1033 
1034   t = dvr_entry_get_start_time(de, 1);
1035   localtime_r(&t, &tm);
1036   if (strftime(tbuf, sizeof(tbuf), "%F %T", &tm) <= 0)
1037     *tbuf = 0;
1038 
1039   tvhinfo(LS_DVR, "entry %s \"%s\" on \"%s\" starting at %s, "
1040 	 "scheduled for recording by \"%s\"",
1041          idnode_uuid_as_str(&de->de_id, ubuf),
1042 	 lang_str_get(de->de_title, NULL), DVR_CH_NAME(de), tbuf, creator ?: "");
1043 
1044   idnode_changed(&de->de_id);
1045   return de;
1046 }
1047 
1048 /**
1049  *
1050  */
1051 dvr_entry_t *
dvr_entry_create_htsp(int enabled,const char * config_uuid,channel_t * ch,time_t start,time_t stop,time_t start_extra,time_t stop_extra,const char * title,const char * subtitle,const char * description,const char * lang,epg_genre_t * content_type,const char * owner,const char * creator,dvr_autorec_entry_t * dae,dvr_prio_t pri,int retention,int removal,const char * comment)1052 dvr_entry_create_htsp(int enabled, const char *config_uuid,
1053                       channel_t *ch, time_t start, time_t stop,
1054                       time_t start_extra, time_t stop_extra,
1055                       const char *title, const char* subtitle,
1056                       const char *description, const char *lang,
1057                       epg_genre_t *content_type,
1058                       const char *owner,
1059                       const char *creator, dvr_autorec_entry_t *dae,
1060                       dvr_prio_t pri, int retention, int removal,
1061                       const char *comment)
1062 {
1063   char ubuf[UUID_HEX_SIZE];
1064   dvr_config_t *cfg = dvr_config_find_by_uuid(config_uuid);
1065   if (!cfg)
1066     cfg = dvr_config_find_by_name(config_uuid);
1067   return dvr_entry_create_(enabled,
1068                            cfg ? idnode_uuid_as_str(&cfg->dvr_id, ubuf) : NULL,
1069                            NULL,
1070                            ch, start, stop, start_extra, stop_extra,
1071                            title, subtitle, description, lang, content_type,
1072                            owner, creator, dae, NULL, pri, retention, removal,
1073                            comment);
1074 }
1075 
1076 dvr_entry_t *
dvr_entry_create_by_event(int enabled,const char * config_uuid,epg_broadcast_t * e,time_t start_extra,time_t stop_extra,const char * owner,const char * creator,dvr_autorec_entry_t * dae,dvr_prio_t pri,int retention,int removal,const char * comment)1077 dvr_entry_create_by_event(int enabled, const char *config_uuid,
1078                           epg_broadcast_t *e,
1079                           time_t start_extra, time_t stop_extra,
1080                           const char *owner,
1081                           const char *creator, dvr_autorec_entry_t *dae,
1082                           dvr_prio_t pri, int retention, int removal,
1083                           const char *comment)
1084 {
1085   if(!e->channel || !e->episode || !e->episode->title)
1086     return NULL;
1087   return dvr_entry_create_(enabled, config_uuid, e,
1088                            e->channel, e->start, e->stop,
1089                            start_extra, stop_extra,
1090                            NULL, NULL, NULL, NULL,
1091                            LIST_FIRST(&e->episode->genre),
1092                            owner, creator, dae, NULL, pri,
1093                            retention, removal, comment);
1094 }
1095 
1096 /**
1097  * Return the event for the last segment.
1098  */
dvr_entry_get_segment_last(dvr_entry_t * de)1099 static epg_broadcast_t *dvr_entry_get_segment_last( dvr_entry_t *de )
1100 {
1101   time_t start, stop, maximum_stop_time;
1102   int max_progs_to_check;
1103   epg_broadcast_t *e, *next, *ret;
1104 
1105   if (!de)
1106     return NULL;
1107 
1108   /* If we have no broadcast data then can not search for matches */
1109   e = de->de_bcast;
1110   if (!e)
1111     return NULL;
1112 
1113   const char *ep_uri = e->episode->uri;
1114 
1115   /* If not a segmented programme then no segment extra time */
1116   if (!ep_uri || strncmp(ep_uri, "crid://", 7) || !strstr(ep_uri, "#"))
1117     return 0;
1118 
1119   /* This URI is a CRID (from EIT) which contains an IMI so the
1120    * programme is segmented such as part1, <5 minute news>, part2. So
1121    * we need to check if the next few programmes have the same
1122    * crid+imi in which case we extend our stop time to include that.
1123    *
1124    * We record the "news" programme too since often these segmented
1125    * programmes have incorrect start/stop times.  This is also
1126    * consistent with xmltv that normally just merges these programmes
1127    * together.
1128    *
1129    * The Freeview NZ documents say we only need to check for segments
1130    * on the same channel and where each segment is within three hours
1131    * of the end of the previous segment. We'll use that as best
1132    * practice.
1133    *
1134    * We also put a couple of safety checks on the number of programmes
1135    * we check in case broadcast data is bad.
1136    */
1137   enum
1138   {
1139     THREE_HOURS   = 3 * 60 * 60,
1140     MAX_STOP_TIME = 9 * 60 * 60
1141   };
1142 
1143   ret = NULL;
1144   start = e->start;
1145   stop  = e->stop;
1146   maximum_stop_time = start + MAX_STOP_TIME;
1147   max_progs_to_check = 10;
1148   for (next = epg_broadcast_get_next(e);
1149        --max_progs_to_check && stop < maximum_stop_time &&
1150          next && next->episode && next->start < stop + THREE_HOURS;
1151        next = epg_broadcast_get_next(next)) {
1152     const char *next_uri = next->episode->uri;
1153     if (next_uri && strcmp(ep_uri, next_uri) == 0) {
1154       /* Identical CRID+IMI. So that means that programme is a
1155        * segment part of this programme. So extend our stop time
1156        * to include this programme.
1157        */
1158       ret = next;
1159     }
1160   }
1161 
1162   return ret;
1163 }
1164 
1165 /**
1166  * Determine stop time for the broadcast taking in
1167  * to account segmented programmes via EIT.
1168  */
dvr_entry_get_segment_stop_extra(dvr_entry_t * de)1169 static time_t dvr_entry_get_segment_stop_extra( dvr_entry_t *de )
1170 {
1171   time_t segment_stop_extra;
1172   epg_broadcast_t *e;
1173 
1174   if (!de)
1175     return 0;
1176 
1177   /* Return any cached value we have previous calculated. */
1178   if (de->de_segment_stop_extra)
1179     return de->de_segment_stop_extra;
1180 
1181   /* Do not update stop extra without the last segment */
1182   e = dvr_entry_get_segment_last(de);
1183   if (!e)
1184     return 0;
1185 
1186   segment_stop_extra = e->stop - de->de_bcast->stop;
1187   if (segment_stop_extra != de->de_segment_stop_extra) {
1188     tvhinfo(LS_DVR, "Increasing stop for \"%s\" on \"%s\" \"%s\" by %"PRId64
1189                     " seconds at start %"PRId64" and original stop %"PRId64,
1190                     lang_str_get(e->episode->title, NULL), DVR_CH_NAME(de), e->episode->uri,
1191                     (int64_t)segment_stop_extra, (int64_t)de->de_bcast->start,
1192                     (int64_t)de->de_bcast->stop);
1193     de->de_segment_stop_extra = segment_stop_extra;
1194   }
1195 
1196   return segment_stop_extra;
1197 }
1198 
1199 /**
1200  * Clone existing DVR entry and stop the previous
1201  */
1202 dvr_entry_t *
dvr_entry_clone(dvr_entry_t * de)1203 dvr_entry_clone(dvr_entry_t *de)
1204 {
1205   htsmsg_t *conf = htsmsg_create_map();
1206   dvr_entry_t *n;
1207 
1208   lock_assert(&global_lock);
1209 
1210   idnode_save(&de->de_id, conf);
1211   n = dvr_entry_create(NULL, conf, 1);
1212   htsmsg_destroy(conf);
1213 
1214   if (n) {
1215     if(de->de_sched_state == DVR_RECORDING) {
1216       dvr_stop_recording(de, SM_CODE_SOURCE_RECONFIGURED, 1, 1);
1217       dvr_rec_migrate(de, n);
1218       n->de_start = gclk();
1219       dvr_entry_start_recording(n, 1);
1220     } else {
1221       dvr_entry_set_timer(n);
1222     }
1223     idnode_changed(&n->de_id);
1224   }
1225 
1226   return n == NULL ? de : n;
1227 }
1228 
1229 /**
1230  *
1231  */
1232 static int
dvr_entry_rerecord(dvr_entry_t * de)1233 dvr_entry_rerecord(dvr_entry_t *de)
1234 {
1235   uint32_t rerecord;
1236   epg_broadcast_t *e, *ev;
1237   dvr_entry_t *de2;
1238   char cfg_uuid[UUID_HEX_SIZE];
1239   char buf[512];
1240   int64_t fsize1, fsize2;
1241   time_t pre;
1242 
1243   if (dvr_in_init || de->de_dont_rerecord)
1244     return 0;
1245   rerecord = dvr_entry_get_rerecord_errors(de);
1246   if (rerecord == 0)
1247     return 0;
1248   if ((de2 = de->de_parent) != NULL) {
1249     if (de->de_sched_state == DVR_COMPLETED &&
1250         de->de_errors == 0 &&
1251         de->de_data_errors < de->de_parent->de_data_errors) {
1252       fsize1 = dvr_get_filesize(de, DVR_FILESIZE_TOTAL);
1253       fsize2 = dvr_get_filesize(de2, DVR_FILESIZE_TOTAL);
1254       if (fsize1 / 5 < fsize2 / 6) {
1255         goto not_so_good;
1256       } else {
1257         dvr_entry_cancel_delete(de2, 1);
1258       }
1259     } else if (de->de_sched_state == DVR_COMPLETED) {
1260       if(dvr_get_filesize(de, 0) == -1) {
1261 delete_me:
1262         dvr_entry_cancel_delete(de, 0);
1263         dvr_entry_rerecord(de2);
1264         return 1;
1265       }
1266 not_so_good:
1267       de->de_retention = DVR_RET_ONREMOVE;
1268       de->de_removal = DVR_RET_REM_1DAY;
1269       dvr_entry_change_parent_child(de->de_parent, NULL, NULL, 1);
1270       dvr_entry_completed(de, SM_CODE_WEAK_STREAM);
1271       return 0;
1272     } else if (de->de_sched_state == DVR_MISSED_TIME) {
1273       goto delete_me;
1274     }
1275   }
1276   if (de->de_child)
1277     return 0;
1278   if (de->de_enabled == 0)
1279     return 0;
1280   if (de->de_channel == NULL)
1281     return 0;
1282   if (de->de_sched_state == DVR_SCHEDULED ||
1283       de->de_sched_state == DVR_RECORDING)
1284     return 0;
1285   if (de->de_sched_state == DVR_COMPLETED &&
1286       de->de_last_error == SM_CODE_WEAK_STREAM)
1287     return 0;
1288   /* rerecord if - DVR_NOSTATE, DVR_MISSED_TIME */
1289   if (de->de_sched_state == DVR_COMPLETED &&
1290       rerecord > de->de_data_errors && de->de_errors <= 0)
1291     return 0;
1292 
1293   e = NULL;
1294   pre = (60 * dvr_entry_get_extra_time_pre(de)) -
1295         dvr_entry_warm_time(de);
1296   RB_FOREACH(ev, &de->de_channel->ch_epg_schedule, sched_link) {
1297     if (de->de_bcast == ev) continue;
1298     if (ev->start - pre < gclk()) continue;
1299     if (dvr_entry_fuzzy_match(de, ev, 0, INT64_MAX))
1300       if (!e || e->start > ev->start)
1301         e = ev;
1302   }
1303 
1304   if (e == NULL)
1305     return 0;
1306 
1307   dvr_entry_trace_time2(de, "start", e->start, "stop", e->stop,
1308                         "rerecord event %s on %s",
1309                         epg_broadcast_get_title(e, NULL),
1310                         channel_get_name(e->channel));
1311 
1312   idnode_uuid_as_str(&de->de_config->dvr_id, cfg_uuid);
1313   snprintf(buf, sizeof(buf), _("Re-record%s%s"),
1314            de->de_comment ? ": " : "", de->de_comment ?: "");
1315 
1316   de2 = dvr_entry_create_by_event(1, cfg_uuid, e,
1317                                   de->de_start_extra, de->de_stop_extra,
1318                                   de->de_owner, de->de_creator, NULL,
1319                                   de->de_pri, de->de_retention, de->de_removal,
1320                                   buf);
1321   if (de2) {
1322     dvr_entry_change_parent_child(de, de2, NULL, 1);
1323   } else {
1324     /* we have already queued similar recordings, mark as resolved */
1325     de->de_dont_rerecord = 1;
1326     idnode_changed(&de->de_id);
1327   }
1328 
1329   return 0;
1330 }
1331 
1332 /**
1333  *
1334  */
1335 typedef int (*_dvr_duplicate_fcn_t)(dvr_entry_t *de, dvr_entry_t *de2, void **aux);
1336 
_dvr_duplicate_epnum(dvr_entry_t * de,dvr_entry_t * de2,void ** aux)1337 static int _dvr_duplicate_epnum(dvr_entry_t *de, dvr_entry_t *de2, void **aux)
1338 {
1339   return !strempty(de2->de_episode) && !strcmp(de->de_episode, de2->de_episode);
1340 }
1341 
_dvr_duplicate_title(dvr_entry_t * de,dvr_entry_t * de2,void ** aux)1342 static int _dvr_duplicate_title(dvr_entry_t *de, dvr_entry_t *de2, void **aux)
1343 {
1344   return !lang_str_compare(de->de_title, de2->de_title);
1345 }
1346 
_dvr_duplicate_subtitle(dvr_entry_t * de,dvr_entry_t * de2,void ** aux)1347 static int _dvr_duplicate_subtitle(dvr_entry_t *de, dvr_entry_t *de2, void **aux)
1348 {
1349   return !lang_str_compare(de->de_subtitle, de2->de_subtitle);
1350 }
1351 
_dvr_duplicate_desc(dvr_entry_t * de,dvr_entry_t * de2,void ** aux)1352 static int _dvr_duplicate_desc(dvr_entry_t *de, dvr_entry_t *de2, void **aux)
1353 {
1354   return !lang_str_compare(de->de_desc, de2->de_desc);
1355 }
1356 
_dvr_duplicate_per_month(dvr_entry_t * de,dvr_entry_t * de2,void ** aux)1357 static int _dvr_duplicate_per_month(dvr_entry_t *de, dvr_entry_t *de2, void **aux)
1358 {
1359   struct tm *de1_start = *aux, de2_start;
1360   if (de1_start == NULL) {
1361     de1_start = calloc(1, sizeof(*de1_start));
1362     localtime_r(&de->de_start, de1_start);
1363     *aux = de1_start;
1364   }
1365   localtime_r(&de2->de_start, &de2_start);
1366   return de1_start->tm_year == de2_start.tm_year &&
1367          de1_start->tm_mon == de2_start.tm_mon;
1368 }
1369 
_dvr_duplicate_per_week(dvr_entry_t * de,dvr_entry_t * de2,void ** aux)1370 static int _dvr_duplicate_per_week(dvr_entry_t *de, dvr_entry_t *de2, void **aux)
1371 {
1372   struct tm *de1_start = *aux, de2_start;
1373   if (de1_start == NULL) {
1374     de1_start = calloc(1, sizeof(*de1_start));
1375     localtime_r(&de->de_start, de1_start);
1376     de1_start->tm_mday -= (de1_start->tm_wday + 6) % 7; // week = mon-sun
1377     mktime(de1_start); // adjusts de_start
1378     *aux = de1_start;
1379   }
1380   localtime_r(&de2->de_start, &de2_start);
1381   de2_start.tm_mday -= (de2_start.tm_wday + 6) % 7; // week = mon-sun
1382   mktime(&de2_start); // adjusts de2_start
1383   return de1_start->tm_year == de2_start.tm_year &&
1384          de1_start->tm_yday == de2_start.tm_yday;
1385 }
1386 
_dvr_duplicate_per_day(dvr_entry_t * de,dvr_entry_t * de2,void ** aux)1387 static int _dvr_duplicate_per_day(dvr_entry_t *de, dvr_entry_t *de2, void **aux)
1388 {
1389   struct tm *de1_start = *aux, de2_start;
1390   if (de1_start == NULL) {
1391     de1_start = calloc(1, sizeof(*de1_start));
1392     localtime_r(&de->de_start, de1_start);
1393     *aux = de1_start;
1394   }
1395   localtime_r(&de2->de_start, &de2_start);
1396   return de1_start->tm_year == de2_start.tm_year &&
1397          de1_start->tm_yday == de2_start.tm_yday;
1398 }
1399 
1400 /**
1401  *
1402  */
_dvr_duplicate_event(dvr_entry_t * de)1403 static dvr_entry_t *_dvr_duplicate_event(dvr_entry_t *de)
1404 {
1405   static _dvr_duplicate_fcn_t fcns[] = {
1406     [DVR_AUTOREC_RECORD_DIFFERENT_EPISODE_NUMBER]  = _dvr_duplicate_epnum,
1407     [DVR_AUTOREC_LRECORD_DIFFERENT_EPISODE_NUMBER] = _dvr_duplicate_epnum,
1408     [DVR_AUTOREC_LRECORD_DIFFERENT_TITLE]          = _dvr_duplicate_title,
1409     [DVR_AUTOREC_RECORD_DIFFERENT_SUBTITLE]        = _dvr_duplicate_subtitle,
1410     [DVR_AUTOREC_LRECORD_DIFFERENT_SUBTITLE]       = _dvr_duplicate_subtitle,
1411     [DVR_AUTOREC_RECORD_DIFFERENT_DESCRIPTION]     = _dvr_duplicate_desc,
1412     [DVR_AUTOREC_LRECORD_DIFFERENT_DESCRIPTION]    = _dvr_duplicate_desc,
1413     [DVR_AUTOREC_RECORD_ONCE_PER_MONTH]            = _dvr_duplicate_per_month,
1414     [DVR_AUTOREC_LRECORD_ONCE_PER_MONTH]           = _dvr_duplicate_per_month,
1415     [DVR_AUTOREC_RECORD_ONCE_PER_WEEK]             = _dvr_duplicate_per_week,
1416     [DVR_AUTOREC_LRECORD_ONCE_PER_WEEK]            = _dvr_duplicate_per_week,
1417     [DVR_AUTOREC_RECORD_ONCE_PER_DAY]              = _dvr_duplicate_per_day,
1418     [DVR_AUTOREC_LRECORD_ONCE_PER_DAY]             = _dvr_duplicate_per_day,
1419   };
1420   dvr_entry_t *de2;
1421  _dvr_duplicate_fcn_t match;
1422   int record;
1423   void *aux;
1424 
1425   if (!de->de_autorec)
1426     return NULL;
1427 
1428   // title not defined, can't be deduped
1429   if (lang_str_empty(de->de_title))
1430     return NULL;
1431 
1432   record = de->de_autorec->dae_record;
1433 
1434   switch (record) {
1435     case DVR_AUTOREC_RECORD_ALL:
1436       return NULL;
1437     case DVR_AUTOREC_RECORD_DIFFERENT_EPISODE_NUMBER:
1438     case DVR_AUTOREC_LRECORD_DIFFERENT_EPISODE_NUMBER:
1439       if (strempty(de->de_episode))
1440         return NULL;
1441       break;
1442     case DVR_AUTOREC_RECORD_DIFFERENT_SUBTITLE:
1443     case DVR_AUTOREC_LRECORD_DIFFERENT_SUBTITLE:
1444       if (lang_str_empty(de->de_subtitle))
1445         return NULL;
1446       break;
1447     case DVR_AUTOREC_RECORD_DIFFERENT_DESCRIPTION:
1448     case DVR_AUTOREC_LRECORD_DIFFERENT_DESCRIPTION:
1449       if (lang_str_empty(de->de_desc))
1450         return NULL;
1451       break;
1452     case DVR_AUTOREC_RECORD_ONCE_PER_DAY:
1453     case DVR_AUTOREC_LRECORD_ONCE_PER_DAY:
1454       break;
1455     case DVR_AUTOREC_RECORD_ONCE_PER_WEEK:
1456     case DVR_AUTOREC_LRECORD_ONCE_PER_WEEK:
1457       break;
1458     case DVR_AUTOREC_RECORD_ONCE_PER_MONTH:
1459     case DVR_AUTOREC_LRECORD_ONCE_PER_MONTH:
1460       break;
1461     case DVR_AUTOREC_LRECORD_DIFFERENT_TITLE:
1462       break;
1463    default:
1464       abort();
1465   }
1466 
1467   match = fcns[record];
1468   aux   = NULL;
1469 
1470   assert(match);
1471 
1472   if (record < DVR_AUTOREC_LRECORD_DIFFERENT_EPISODE_NUMBER) {
1473     LIST_FOREACH(de2, &dvrentries, de_global_link) {
1474       if (de == de2)
1475         continue;
1476 
1477       // check for valid states
1478       if (de2->de_sched_state == DVR_NOSTATE ||
1479           de2->de_sched_state == DVR_MISSED_TIME)
1480         continue;
1481 
1482       // only earlier recordings qualify as master
1483       if (de2->de_start > de->de_start)
1484         continue;
1485 
1486       // only enabled upcoming recordings
1487       if (de2->de_sched_state == DVR_SCHEDULED && !de2->de_enabled)
1488         continue;
1489 
1490       // only successful earlier recordings qualify as master
1491       if (dvr_entry_is_finished(de2, DVR_FINISHED_FAILED | DVR_FINISHED_REMOVED_FAILED))
1492         continue;
1493 
1494       // if titles are not defined or do not match, don't dedup
1495       if (lang_str_compare(de->de_title, de2->de_title))
1496         continue;
1497 
1498       if (match(de, de2, &aux)) {
1499         free(aux);
1500         return de2;
1501       }
1502     }
1503   } else {
1504     LIST_FOREACH(de2, &de->de_autorec->dae_spawns, de_autorec_link) {
1505       if (de == de2)
1506         continue;
1507 
1508       // check for valid states
1509       if (de2->de_sched_state == DVR_NOSTATE ||
1510           de2->de_sched_state == DVR_MISSED_TIME)
1511         continue;
1512 
1513       // only earlier recordings qualify as master
1514       if (de2->de_start > de->de_start)
1515         continue;
1516 
1517       // only enabled upcoming recordings
1518       if (de2->de_sched_state == DVR_SCHEDULED && !de2->de_enabled)
1519         continue;
1520 
1521       // only successful earlier recordings qualify as master
1522       if (dvr_entry_is_finished(de2, DVR_FINISHED_FAILED | DVR_FINISHED_REMOVED_FAILED))
1523         continue;
1524 
1525       // if titles are not defined or do not match, don't dedup
1526       if (record != DVR_AUTOREC_LRECORD_DIFFERENT_TITLE &&
1527           lang_str_compare(de->de_title, de2->de_title))
1528         continue;
1529 
1530       if (match(de, de2, &aux)) {
1531         free(aux);
1532         return de2;
1533       }
1534     }
1535   }
1536   free(aux);
1537   return NULL;
1538 }
1539 
1540 /**
1541  *
1542  */
1543 void
dvr_entry_create_by_autorec(int enabled,epg_broadcast_t * e,dvr_autorec_entry_t * dae)1544 dvr_entry_create_by_autorec(int enabled, epg_broadcast_t *e, dvr_autorec_entry_t *dae)
1545 {
1546   char buf[512];
1547   char ubuf[UUID_HEX_SIZE];
1548   const char *s;
1549   dvr_entry_t *de;
1550   uint32_t count = 0, max_count;
1551 
1552   /* Identical duplicate detection
1553      NOTE: Semantic duplicate detection is deferred to the start time of recording and then done using _dvr_duplicate_event by dvr_timer_start_recording. */
1554   LIST_FOREACH(de, &dvrentries, de_global_link) {
1555     if (de->de_bcast == e || (de->de_bcast && de->de_bcast->episode == e->episode))
1556       if (strcmp(dae->dae_owner ?: "", de->de_owner ?: "") == 0)
1557         return;
1558   }
1559 
1560   /* Handle max schedules limit for autorrecord */
1561   if ((max_count = dvr_autorec_get_max_sched_count(dae)) > 0){
1562     count = 0;
1563     LIST_FOREACH(de, &dae->dae_spawns, de_autorec_link)
1564       if ((de->de_sched_state == DVR_SCHEDULED) ||
1565           (de->de_sched_state == DVR_RECORDING)) count++;
1566 
1567     if (count >= max_count) {
1568       tvhinfo(LS_DVR, "Autorecord \"%s\": Not scheduling \"%s\" because of autorecord max schedules limit reached",
1569               dae->dae_name, lang_str_get(e->episode->title, NULL));
1570       return;
1571     }
1572   }
1573 
1574   /* Prefer the recording comment or the name of the rule to an empty string */
1575   s = dae->dae_comment && *dae->dae_comment ? dae->dae_comment : dae->dae_name;
1576   snprintf(buf, sizeof(buf), _("Auto recording%s%s"), s ? ": " : "", s ?: "");
1577 
1578   dvr_entry_create_by_event(enabled, idnode_uuid_as_str(&dae->dae_config->dvr_id, ubuf),
1579                             e, dae->dae_start_extra, dae->dae_stop_extra,
1580                             dae->dae_owner, dae->dae_creator, dae, dae->dae_pri,
1581                             dae->dae_retention, dae->dae_removal, buf);
1582 }
1583 
1584 /**
1585  *
1586  */
1587 void
dvr_entry_dec_ref(dvr_entry_t * de)1588 dvr_entry_dec_ref(dvr_entry_t *de)
1589 {
1590   lock_assert(&global_lock);
1591 
1592   if(de->de_refcnt > 1) {
1593     de->de_refcnt--;
1594     return;
1595   }
1596 
1597   idnode_save_check(&de->de_id, 1);
1598 
1599   idnode_unlink(&de->de_id);
1600 
1601   if(de->de_autorec != NULL)
1602     LIST_REMOVE(de, de_autorec_link);
1603 
1604   if (de->de_timerec) {
1605     de->de_timerec->dte_spawn = NULL;
1606     de->de_timerec = NULL;
1607   }
1608 
1609   if(de->de_config != NULL)
1610     LIST_REMOVE(de, de_config_link);
1611 
1612   htsmsg_destroy(de->de_files);
1613   free(de->de_directory);
1614   free(de->de_owner);
1615   free(de->de_creator);
1616   free(de->de_comment);
1617   if (de->de_title) lang_str_destroy(de->de_title);
1618   if (de->de_subtitle)  lang_str_destroy(de->de_subtitle);
1619   if (de->de_desc)  lang_str_destroy(de->de_desc);
1620   dvr_entry_assign_broadcast(de, NULL);
1621   free(de->de_channel_name);
1622   free(de->de_episode);
1623 
1624   free(de);
1625 }
1626 
1627 /**
1628  *
1629  */
1630 void
dvr_entry_destroy(dvr_entry_t * de,int delconf)1631 dvr_entry_destroy(dvr_entry_t *de, int delconf)
1632 {
1633   char ubuf[UUID_HEX_SIZE];
1634 
1635   idnode_save_check(&de->de_id, delconf);
1636 
1637   if (delconf)
1638     hts_settings_remove("dvr/log/%s", idnode_uuid_as_str(&de->de_id, ubuf));
1639 
1640   htsp_dvr_entry_delete(de);
1641 
1642 #if ENABLE_INOTIFY
1643   dvr_inotify_del(de);
1644 #endif
1645 
1646   gtimer_disarm(&de->de_timer);
1647   mtimer_disarm(&de->de_deferred_timer);
1648 #if ENABLE_DBUS_1
1649   mtimer_arm_rel(&dvr_dbus_timer, dvr_dbus_timer_cb, NULL, sec2mono(2));
1650 #endif
1651 
1652   if (de->de_channel)
1653     LIST_REMOVE(de, de_channel_link);
1654   LIST_REMOVE(de, de_global_link);
1655   de->de_channel = NULL;
1656 
1657   if (de->de_parent)
1658     dvr_entry_change_parent_child(de->de_parent, NULL, de, delconf);
1659   if (de->de_child)
1660     dvr_entry_change_parent_child(de, NULL, de, delconf);
1661 
1662   dvr_entry_dec_ref(de);
1663 }
1664 
1665 /**
1666  *
1667  */
_deferred_destroy_cb(void * aux)1668 static void _deferred_destroy_cb(void *aux)
1669 {
1670   dvr_entry_destroy(aux, 1);
1671 }
1672 
1673 static void
dvr_entry_deferred_destroy(dvr_entry_t * de)1674 dvr_entry_deferred_destroy(dvr_entry_t *de)
1675 {
1676   mtimer_arm_rel(&de->de_deferred_timer, _deferred_destroy_cb, de, 0);
1677 }
1678 
1679 /**
1680  *
1681  */
1682 void
dvr_entry_destroy_by_config(dvr_config_t * cfg,int delconf)1683 dvr_entry_destroy_by_config(dvr_config_t *cfg, int delconf)
1684 {
1685   dvr_entry_t *de;
1686   dvr_config_t *def = NULL;
1687 
1688   while ((de = LIST_FIRST(&cfg->dvr_entries)) != NULL) {
1689     LIST_REMOVE(de, de_config_link);
1690     if (def == NULL && delconf)
1691       def = dvr_config_find_by_name_default(NULL);
1692     de->de_config = def;
1693     if (def)
1694       LIST_INSERT_HEAD(&def->dvr_entries, de, de_config_link);
1695     if (delconf)
1696       idnode_changed(&de->de_id);
1697   }
1698 }
1699 
1700 /**
1701  *
1702  */
1703 static void
dvr_timer_rerecord(void * aux)1704 dvr_timer_rerecord(void *aux)
1705 {
1706   dvr_entry_t *de = aux;
1707   if (dvr_entry_rerecord(de))
1708     return;
1709   dvr_entry_retention_timer(de);
1710 }
1711 
1712 
1713 /**
1714  *
1715  */
1716 static void
dvr_timer_expire(void * aux)1717 dvr_timer_expire(void *aux)
1718 {
1719   dvr_entry_t *de = aux;
1720   dvr_entry_destroy(de, 1);
1721 }
1722 
1723 
1724 /**
1725  *
1726  */
1727 static void
dvr_timer_disarm(void * aux)1728 dvr_timer_disarm(void *aux)
1729 {
1730   dvr_entry_t *de = aux;
1731   dvr_entry_trace(de, "retention timer - disarm");
1732   gtimer_disarm(&de->de_timer);
1733 }
1734 
1735 /**
1736  *
1737  */
1738 static void
dvr_timer_remove_files(void * aux)1739 dvr_timer_remove_files(void *aux)
1740 {
1741   dvr_entry_t *de = aux;
1742   dvr_entry_retention_timer(de);
1743 }
1744 
1745 /**
1746  *
1747  */
1748 
1749 #define DVR_UPDATED_ENABLED      (1<<0)
1750 #define DVR_UPDATED_CHANNEL      (1<<1)
1751 #define DVR_UPDATED_STOP         (1<<2)
1752 #define DVR_UPDATED_STOP_EXTRA   (1<<3)
1753 #define DVR_UPDATED_START        (1<<4)
1754 #define DVR_UPDATED_START_EXTRA  (1<<5)
1755 #define DVR_UPDATED_TITLE        (1<<6)
1756 #define DVR_UPDATED_SUBTITLE     (1<<7)
1757 #define DVR_UPDATED_SUMMARY      (1<<8)
1758 #define DVR_UPDATED_DESCRIPTION  (1<<9)
1759 #define DVR_UPDATED_PRIO         (1<<10)
1760 #define DVR_UPDATED_GENRE        (1<<11)
1761 #define DVR_UPDATED_RETENTION    (1<<12)
1762 #define DVR_UPDATED_REMOVAL      (1<<13)
1763 #define DVR_UPDATED_EID          (1<<14)
1764 #define DVR_UPDATED_BROADCAST    (1<<15)
1765 #define DVR_UPDATED_EPISODE      (1<<16)
1766 #define DVR_UPDATED_CONFIG       (1<<17)
1767 #define DVR_UPDATED_PLAYPOS      (1<<18)
1768 #define DVR_UPDATED_PLAYCOUNT    (1<<19)
1769 
dvr_updated_str(char * buf,size_t buflen,int flags)1770 static char *dvr_updated_str(char *buf, size_t buflen, int flags)
1771 {
1772   static const char *x = "ecoOsStumdpgrviBEC";
1773   const char *p = x;
1774   char *w = buf, *end = buf + buflen;
1775 
1776   for (p = x; *p && flags && w != end; p++, flags >>= 1)
1777     if (flags & 1)
1778       *w++ = *p;
1779   if (w == end)
1780     *(w - 1) = '\0';
1781   else
1782     *w ='\0';
1783   return buf;
1784 }
1785 
1786 /**
1787  *
1788  */
_dvr_entry_update(dvr_entry_t * de,int enabled,const char * dvr_config_uuid,epg_broadcast_t * e,channel_t * ch,const char * title,const char * subtitle,const char * desc,const char * lang,time_t start,time_t stop,time_t start_extra,time_t stop_extra,dvr_prio_t pri,int retention,int removal,int playcount,int playposition)1789 static dvr_entry_t *_dvr_entry_update
1790   ( dvr_entry_t *de, int enabled, const char *dvr_config_uuid,
1791     epg_broadcast_t *e, channel_t *ch,
1792     const char *title, const char *subtitle, const char *desc,
1793     const char *lang, time_t start, time_t stop,
1794     time_t start_extra, time_t stop_extra,
1795     dvr_prio_t pri, int retention, int removal,
1796     int playcount, int playposition)
1797 {
1798   char buf[40];
1799   int save = 0, updated = 0;
1800 
1801   if (enabled >= 0) {
1802     enabled = !!enabled;
1803     if (de->de_enabled != enabled) {
1804       de->de_enabled = enabled;
1805       save |= DVR_UPDATED_ENABLED;
1806     }
1807   }
1808 
1809   if (!dvr_entry_is_editable(de)) {
1810     if (stop > 0) {
1811       if (stop < gclk())
1812         stop = gclk();
1813       if (stop < de->de_start)
1814         stop = de->de_start;
1815       if (stop != de->de_stop) {
1816         de->de_stop = stop;
1817         save |= DVR_UPDATED_STOP;
1818       }
1819     }
1820     if (stop_extra && (stop_extra != de->de_stop_extra)) {
1821       de->de_stop_extra = stop_extra;
1822       save |= DVR_UPDATED_STOP_EXTRA;
1823     }
1824     if (save & (DVR_UPDATED_STOP|DVR_UPDATED_STOP_EXTRA|DVR_UPDATED_ENABLED)) {
1825       updated = 1;
1826       dvr_entry_set_timer(de);
1827     }
1828     if (de->de_sched_state == DVR_RECORDING || de->de_sched_state == DVR_COMPLETED) {
1829       if (playcount >= 0 && playcount != de->de_playcount) {
1830         de->de_playcount = playcount;
1831         save |= DVR_UPDATED_PLAYCOUNT;
1832       }
1833       if (playposition >= 0 && playposition != de->de_playposition) {
1834         de->de_playposition = playposition;
1835         save |= DVR_UPDATED_PLAYPOS;
1836       }
1837     }
1838     goto dosave;
1839   }
1840 
1841   /* Configuration */
1842   if (dvr_config_uuid) {
1843     de->de_config = dvr_config_find_by_name_default(dvr_config_uuid);
1844     save |= DVR_UPDATED_CONFIG;
1845   }
1846 
1847   /* Channel */
1848   if (ch && (ch != de->de_channel)) {
1849     de->de_channel = ch;
1850     save |= DVR_UPDATED_CHANNEL;
1851   }
1852 
1853   /* Start/Stop */
1854   if (e) {
1855     start = e->start;
1856     stop  = e->stop;
1857   }
1858   if (start && (start != de->de_start)) {
1859     de->de_start = start;
1860     save |= DVR_UPDATED_START;
1861   }
1862   if (stop && (stop != de->de_stop)) {
1863     de->de_stop = stop;
1864     save |= DVR_UPDATED_STOP;
1865   }
1866   if (start_extra && (start_extra != de->de_start_extra)) {
1867     de->de_start_extra = start_extra;
1868     save |= DVR_UPDATED_START_EXTRA;
1869   }
1870   if (stop_extra && (stop_extra != de->de_stop_extra)) {
1871     de->de_stop_extra = stop_extra;
1872     save |= DVR_UPDATED_STOP_EXTRA;
1873   }
1874   if (pri != DVR_PRIO_NOTSET && (pri != de->de_pri)) {
1875     de->de_pri = pri;
1876     save |= DVR_UPDATED_PRIO;
1877   }
1878   if (retention && (retention != de->de_retention)) {
1879     de->de_retention = retention;
1880     save |= DVR_UPDATED_RETENTION;
1881   }
1882   if (removal && (removal != de->de_removal)) {
1883     de->de_removal = removal;
1884     save |= DVR_UPDATED_REMOVAL;
1885   }
1886   if (save) {
1887     updated = 1;
1888     dvr_entry_set_timer(de);
1889   }
1890 
1891   /* Title */
1892   if (e && e->episode && e->episode->title) {
1893     save |= lang_str_set2(&de->de_title, e->episode->title) ? DVR_UPDATED_TITLE : 0;
1894   } else if (title) {
1895     save |= lang_str_set(&de->de_title, title, lang) ? DVR_UPDATED_TITLE : 0;
1896   }
1897 
1898   /* Subtitle */
1899   if (e && e->episode && e->episode->subtitle) {
1900     save |= lang_str_set2(&de->de_subtitle, e->episode->subtitle) ? DVR_UPDATED_SUBTITLE : 0;
1901   } else if (subtitle) {
1902     save |= lang_str_set(&de->de_subtitle, subtitle, lang) ? DVR_UPDATED_SUBTITLE : 0;
1903   }
1904 
1905   /* EID */
1906   if (e && e->dvb_eid != de->de_dvb_eid) {
1907     de->de_dvb_eid = e->dvb_eid;
1908     save |= DVR_UPDATED_EID;
1909   }
1910 
1911   /* Description */
1912   if (e && e->description) {
1913     save |= lang_str_set2(&de->de_desc, e->description) ? DVR_UPDATED_DESCRIPTION : 0;
1914   } else if (e && e->episode && e->episode->description) {
1915     save |= lang_str_set2(&de->de_desc, e->episode->description) ? DVR_UPDATED_DESCRIPTION : 0;
1916   } else if (e && e->summary) {
1917     save |= lang_str_set2(&de->de_desc, e->summary) ? DVR_UPDATED_DESCRIPTION : 0;
1918   } else if (e && e->episode && e->episode->summary) {
1919     save |= lang_str_set2(&de->de_desc, e->episode->summary) ? DVR_UPDATED_DESCRIPTION : 0;
1920   } else if (desc) {
1921     save |= lang_str_set(&de->de_desc, desc, lang) ? DVR_UPDATED_DESCRIPTION : 0;
1922   }
1923 
1924   /* Genre */
1925   if (e && e->episode) {
1926     epg_genre_t *g = LIST_FIRST(&e->episode->genre);
1927     if (g && (g->code / 16) != de->de_content_type) {
1928       de->de_content_type = g->code / 16;
1929       save |= DVR_UPDATED_GENRE;
1930     }
1931   }
1932 
1933   /* Broadcast */
1934   if (e && (de->de_bcast != e)) {
1935     dvr_entry_assign_broadcast(de, e);
1936     save |= DVR_UPDATED_BROADCAST;
1937   }
1938 
1939   /* Episode */
1940   if (!dvr_entry_get_episode(de->de_bcast, buf, sizeof(buf)))
1941     buf[0] = '\0';
1942   if (strcmp(de->de_episode ?: "", buf)) {
1943     free(de->de_episode);
1944     de->de_episode = strdup(buf);
1945     save |= DVR_UPDATED_EPISODE;
1946   }
1947 
1948   /* Save changes */
1949 dosave:
1950   if (save) {
1951     dvr_entry_changed_notify(de);
1952     if (tvhlog_limit(&de->de_update_limit, 60)) {
1953       tvhinfo(LS_DVR, "\"%s\" on \"%s\": Updated%s (%s)",
1954               lang_str_get(de->de_title, NULL), DVR_CH_NAME(de),
1955               updated ? " Timer" : "",
1956               dvr_updated_str(buf, sizeof(buf), save));
1957     } else {
1958       tvhtrace(LS_DVR, "\"%s\" on \"%s\": Updated%s (%s)",
1959                lang_str_get(de->de_title, NULL), DVR_CH_NAME(de),
1960                updated ? " Timer" : "",
1961                dvr_updated_str(buf, sizeof(buf), save));
1962     }
1963   }
1964 
1965   return de;
1966 }
1967 
1968 /**
1969  *
1970  */
1971 dvr_entry_t *
dvr_entry_update(dvr_entry_t * de,int enabled,const char * dvr_config_uuid,channel_t * ch,const char * title,const char * subtitle,const char * desc,const char * lang,time_t start,time_t stop,time_t start_extra,time_t stop_extra,dvr_prio_t pri,int retention,int removal,int playcount,int playposition)1972 dvr_entry_update
1973   ( dvr_entry_t *de, int enabled,
1974     const char *dvr_config_uuid, channel_t *ch,
1975     const char *title, const char *subtitle,
1976     const char *desc, const char *lang,
1977     time_t start, time_t stop,
1978     time_t start_extra, time_t stop_extra,
1979     dvr_prio_t pri, int retention, int removal, int playcount, int playposition )
1980 {
1981   return _dvr_entry_update(de, enabled, dvr_config_uuid,
1982                            NULL, ch, title, subtitle, desc, lang,
1983                            start, stop, start_extra, stop_extra,
1984                            pri, retention, removal, playcount, playposition);
1985 }
1986 
1987 /**
1988  * Used to notify the DVR that an event has been replaced in the EPG
1989  */
1990 void
dvr_event_replaced(epg_broadcast_t * e,epg_broadcast_t * new_e)1991 dvr_event_replaced(epg_broadcast_t *e, epg_broadcast_t *new_e)
1992 {
1993   dvr_entry_t *de, *de_next;
1994   channel_t *ch = e->channel;
1995   epg_broadcast_t *e2;
1996   char t1buf[32], t2buf[32];
1997 
1998   assert(e != NULL);
1999   assert(new_e != NULL);
2000 
2001   /* Ignore */
2002   if (ch == NULL || e == new_e) return;
2003 
2004   /* Existing entry */
2005   for (de = LIST_FIRST(&ch->ch_dvrs); de; de = de_next) {
2006     de_next = LIST_NEXT(de, de_channel_link);
2007 
2008     if (de->de_bcast != e)
2009       continue;
2010 
2011     dvr_entry_trace_time2(de, "start", e->start, "stop", e->stop,
2012                           "event replaced %s on %s",
2013                           epg_broadcast_get_title(e, NULL),
2014                           channel_get_name(ch));
2015 
2016     /* Ignore - already in progress */
2017     if (de->de_sched_state != DVR_SCHEDULED)
2018       return;
2019 
2020     /* If this was created by autorec - just remove it, it'll get recreated */
2021     if (de->de_autorec) {
2022 
2023       dvr_entry_assign_broadcast(de, NULL);
2024       dvr_entry_destroy(de, 1);
2025 
2026     /* Find match */
2027     } else {
2028 
2029       RB_FOREACH(e2, &ch->ch_epg_schedule, sched_link) {
2030         if (dvr_entry_fuzzy_match(de, e2, e2->dvb_eid,
2031                                   de->de_config->dvr_update_window)) {
2032           tvhtrace(LS_DVR, "  replacement event %s on %s @ start %s stop %s",
2033                           epg_broadcast_get_title(e2, NULL),
2034                           channel_get_name(ch),
2035                           gmtime2local(e2->start, t1buf, sizeof(t1buf)),
2036                           gmtime2local(e2->stop, t2buf, sizeof(t2buf)));
2037           _dvr_entry_update(de, -1, NULL, e2, NULL, NULL, NULL, NULL, NULL,
2038                             0, 0, 0, 0, DVR_PRIO_NOTSET, 0, 0, -1, -1);
2039           return;
2040         }
2041       }
2042       dvr_entry_assign_broadcast(de, NULL);
2043 
2044     }
2045   }
2046 }
2047 
2048 /**
2049  * Used to notify the DVR that an event has been removed
2050  */
2051 void
dvr_event_removed(epg_broadcast_t * e)2052 dvr_event_removed(epg_broadcast_t *e)
2053 {
2054   dvr_entry_t *de;
2055 
2056   if (e->channel == NULL)
2057     return;
2058   LIST_FOREACH(de, &e->channel->ch_dvrs, de_channel_link) {
2059     if (de->de_bcast != e)
2060       continue;
2061     dvr_entry_assign_broadcast(de, NULL);
2062     idnode_changed(&de->de_id);
2063   }
2064 }
2065 
2066 /**
2067  * Event was updated in epg
2068  */
dvr_event_updated(epg_broadcast_t * e)2069 void dvr_event_updated(epg_broadcast_t *e)
2070 {
2071   dvr_entry_t *de;
2072   int found = 0;
2073 
2074   if (e->channel == NULL)
2075     return;
2076   LIST_FOREACH(de, &e->channel->ch_dvrs, de_channel_link) {
2077     if (de->de_bcast != e)
2078       continue;
2079     _dvr_entry_update(de, -1, NULL, e, NULL, NULL, NULL, NULL,
2080                       NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0, 0, -1, -1);
2081     found++;
2082   }
2083   if (found == 0) {
2084     LIST_FOREACH(de, &e->channel->ch_dvrs, de_channel_link) {
2085       if (de->de_sched_state != DVR_SCHEDULED) continue;
2086       if (de->de_bcast) continue;
2087       if (dvr_entry_fuzzy_match(de, e, e->dvb_eid,
2088                                 de->de_config->dvr_update_window)) {
2089         dvr_entry_trace_time2(de, "start", e->start, "stop", e->stop,
2090                               "link to event %s on %s",
2091                               epg_broadcast_get_title(e, NULL),
2092                               channel_get_name(e->channel));
2093         _dvr_entry_update(de, -1, NULL, e, NULL, NULL, NULL, NULL,
2094                           NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0, 0, -1, -1);
2095         break;
2096       }
2097     }
2098   }
2099 }
2100 
2101 /**
2102  * Event running status is updated
2103  */
dvr_event_running(epg_broadcast_t * e,epg_source_t esrc,epg_running_t running)2104 void dvr_event_running(epg_broadcast_t *e, epg_source_t esrc, epg_running_t running)
2105 {
2106   dvr_entry_t *de;
2107   epg_broadcast_t *e2;
2108   const char *srcname;
2109   char ubuf[UUID_HEX_SIZE];
2110 
2111   if (esrc != EPG_SOURCE_EIT || e->dvb_eid == 0 || e->channel == NULL)
2112     return;
2113   tvhtrace(LS_DVR, "dvr event running check for %s on %s running %d",
2114            epg_broadcast_get_title(e, NULL),
2115            channel_get_name(e->channel),
2116            running);
2117   LIST_FOREACH(de, &e->channel->ch_dvrs, de_channel_link) {
2118     if (running == EPG_RUNNING_NOW && de->de_dvb_eid == e->dvb_eid) {
2119       if (de->de_running_pause) {
2120         tvhdebug(LS_DVR, "dvr entry %s event %s on %s - EPG unpause",
2121                  idnode_uuid_as_str(&de->de_id, ubuf),
2122                  epg_broadcast_get_title(e, NULL),
2123                  channel_get_name(e->channel));
2124         atomic_set_time_t(&de->de_running_pause, 0);
2125         atomic_add(&de->de_running_change, 1);
2126       }
2127       if (!de->de_running_start) {
2128         tvhdebug(LS_DVR, "dvr entry %s event %s on %s - EPG marking start",
2129                  idnode_uuid_as_str(&de->de_id, ubuf),
2130                  epg_broadcast_get_title(e, NULL),
2131                  channel_get_name(e->channel));
2132         atomic_set_time_t(&de->de_running_start, gclk());
2133         atomic_add(&de->de_running_change, 1);
2134       }
2135       if (dvr_entry_get_start_time(de, 1) > gclk()) {
2136         atomic_set_time_t(&de->de_start, gclk());
2137         atomic_add(&de->de_running_change, 1);
2138         dvr_entry_set_timer(de);
2139         tvhdebug(LS_DVR, "dvr entry %s event %s on %s - EPG start",
2140                  idnode_uuid_as_str(&de->de_id, ubuf),
2141                  epg_broadcast_get_title(e, NULL),
2142                  channel_get_name(e->channel));
2143       }
2144     } else if ((running == EPG_RUNNING_STOP && de->de_dvb_eid == e->dvb_eid) ||
2145                 running == EPG_RUNNING_NOW) {
2146       /* Don't stop recording if we are a segmented programme since
2147        * (by definition) the first segment will be marked as stop long
2148        * before the second segment is marked as started. Otherwise if we
2149        * processed the stop then we will stop the dvr_thread from
2150        * recording tv packets. But if the event is behind the last segment,
2151        * stop it.
2152        */
2153       if (de->de_segment_stop_extra) {
2154         e2 = dvr_entry_get_segment_last(de);
2155         for ( ; e2; e2 = epg_broadcast_get_prev(e2))
2156           if (e == e2)
2157             break;
2158         if (e2)
2159           continue;
2160       }
2161       /*
2162        * make checking more robust
2163        * sometimes, the running bits are parsed randomly for a few moments
2164        * so don't expect that the broacasting has only 5 seconds
2165        */
2166       if (de->de_running_start + 5 > gclk())
2167         continue;
2168 
2169       srcname = de->de_dvb_eid == e->dvb_eid ? "event" : "other running event";
2170       if (!de->de_running_stop ||
2171           de->de_running_start > de->de_running_stop) {
2172         atomic_add(&de->de_running_change, 1);
2173         tvhdebug(LS_DVR, "dvr entry %s %s %s on %s - EPG marking stop",
2174                  idnode_uuid_as_str(&de->de_id, ubuf), srcname,
2175                  epg_broadcast_get_title(e, NULL),
2176                  channel_get_name(de->de_channel));
2177       }
2178       atomic_set_time_t(&de->de_running_stop, gclk());
2179       atomic_set_time_t(&de->de_running_pause, 0);
2180       if (de->de_sched_state == DVR_RECORDING && de->de_running_start) {
2181         if (dvr_entry_get_epg_running(de)) {
2182           dvr_stop_recording(de, SM_CODE_OK, 0, 0);
2183           tvhdebug(LS_DVR, "dvr entry %s %s %s on %s - EPG stop",
2184                    idnode_uuid_as_str(&de->de_id, ubuf), srcname,
2185                    epg_broadcast_get_title(e, NULL),
2186                    channel_get_name(de->de_channel));
2187         }
2188       }
2189     } else if (running == EPG_RUNNING_PAUSE && de->de_dvb_eid == e->dvb_eid) {
2190       if (!de->de_running_pause) {
2191         tvhdebug(LS_DVR, "dvr entry %s event %s on %s - EPG pause",
2192                  idnode_uuid_as_str(&de->de_id, ubuf),
2193                  epg_broadcast_get_title(e, NULL),
2194                  channel_get_name(e->channel));
2195         atomic_set_time_t(&de->de_running_pause, gclk());
2196         atomic_add(&de->de_running_change, 1);
2197       }
2198     }
2199   }
2200 }
2201 
2202 /**
2203  *
2204  */
2205 void
dvr_stop_recording(dvr_entry_t * de,int stopcode,int saveconf,int clone)2206 dvr_stop_recording(dvr_entry_t *de, int stopcode, int saveconf, int clone)
2207 {
2208   dvr_rs_state_t rec_state = de->de_rec_state;
2209   dvr_autorec_entry_t *dae = de->de_autorec;
2210 
2211   if (!clone)
2212     dvr_rec_unsubscribe(de);
2213 
2214   de->de_dont_reschedule = 1;
2215 
2216   if (stopcode != SM_CODE_INVALID_TARGET &&
2217       (rec_state == DVR_RS_PENDING ||
2218        rec_state == DVR_RS_WAIT_PROGRAM_START ||
2219        htsmsg_is_empty(de->de_files)))
2220     dvr_entry_missed_time(de, stopcode);
2221   else
2222     dvr_entry_completed(de, stopcode);
2223 
2224   tvhinfo(LS_DVR, "\"%s\" on \"%s\": "
2225 	  "End of program: %s",
2226 	  lang_str_get(de->de_title, NULL), DVR_CH_NAME(de),
2227 	  dvr_entry_status(de));
2228 
2229   if (dvr_entry_rerecord(de))
2230     return;
2231 
2232   if (saveconf)
2233     idnode_changed(&de->de_id);
2234 
2235   dvr_entry_retention_timer(de);
2236 
2237   // Trigger autorecord update in case of schedules limit
2238   if (dae && dvr_autorec_get_max_sched_count(dae) > 0)
2239     dvr_autorec_changed(de->de_autorec, 0);
2240 }
2241 
2242 
2243 /**
2244  *
2245  */
2246 static void
dvr_timer_stop_recording(void * aux)2247 dvr_timer_stop_recording(void *aux)
2248 {
2249   dvr_entry_t *de = aux;
2250   if(de->de_sched_state != DVR_RECORDING)
2251     return;
2252   dvr_entry_trace_time2(de,
2253                         "rstart", de->de_running_start,
2254                         "rstop", de->de_running_stop,
2255                         "stop recording timer called");
2256   /* EPG thinks that the program is running */
2257   if (de->de_segment_stop_extra) {
2258     const time_t now = gclk();
2259     const time_t stop = dvr_entry_get_stop_time(de);
2260     tvhinfo(LS_DVR, "dvr_timer_stop_recording - forcing stop of programme \"%s\" on \"%s\" due to exceeding stop time with running_start %"PRId64" and running_stop %"PRId64" segment stop %"PRId64" now %"PRId64" stop %"PRId64,
2261             lang_str_get(de->de_title, NULL), DVR_CH_NAME(de), (int64_t)de->de_running_start, (int64_t)de->de_running_stop, (int64_t)de->de_segment_stop_extra, (int64_t)now, (int64_t)stop);
2262     /* no-op. We fall through to the final dvr_stop_recording below.
2263     * This path is here purely to get a log.
2264     */
2265   } else if (de->de_running_start > de->de_running_stop) {
2266     gtimer_arm_rel(&de->de_timer, dvr_timer_stop_recording, de, 10);
2267     return;
2268   }
2269   dvr_stop_recording(aux, SM_CODE_OK, 1, 0);
2270 }
2271 
2272 
2273 
2274 /**
2275  *
2276  */
2277 static void
dvr_entry_start_recording(dvr_entry_t * de,int clone)2278 dvr_entry_start_recording(dvr_entry_t *de, int clone)
2279 {
2280   int r;
2281 
2282   if (!de->de_enabled) {
2283     dvr_entry_missed_time(de, SM_CODE_SVC_NOT_ENABLED);
2284     return;
2285   }
2286 
2287   dvr_entry_set_state(de, DVR_RECORDING, DVR_RS_PENDING, SM_CODE_OK);
2288 
2289   tvhinfo(LS_DVR, "\"%s\" on \"%s\" recorder starting",
2290 	  lang_str_get(de->de_title, NULL), DVR_CH_NAME(de));
2291 
2292   /*
2293    * The running flag is updated only on the event change. When the DVR
2294    * entry is added too late, the running timers might not be updated.
2295    */
2296   if (de->de_bcast && de->de_running_start == 0 && de->de_running_stop == 0) {
2297     epg_broadcast_t *ebc = epg_broadcast_get_prev(de->de_bcast);
2298     if (de->de_bcast->running != EPG_RUNNING_STOP ||
2299         (ebc && ebc->running != EPG_RUNNING_STOP))
2300       de->de_running_stop = gclk();
2301   }
2302 
2303   if (!clone && (r = dvr_rec_subscribe(de)) < 0) {
2304     dvr_entry_completed(de, r == -EPERM ? SM_CODE_USER_ACCESS :
2305                            (r == -EOVERFLOW ? SM_CODE_USER_LIMIT :
2306                             SM_CODE_BAD_SOURCE));
2307     return;
2308   }
2309 
2310   /* Reset cached value for segment stopping so it can be recalculated
2311    * immediately at start of recording. This handles case where a
2312    * programme is split in to three-or-more segments and we want to
2313    * ensure we correctly pick up all segments at the start of a
2314    * recording. Otherwise if a broadcaster has an EIT window of say
2315    * 24h and we get the first two segments when we first calculate the
2316    * stop time then we would not recalculate the stop time when
2317    * further segments enter the EIT window.
2318    */
2319   de->de_segment_stop_extra = 0;
2320   const time_t stop = dvr_entry_get_stop_time(de);
2321   tvhinfo(LS_DVR, "About to set stop timer for \"%s\" on \"%s\" at start %"PRId64" and original stop %"PRId64" and overall stop at %"PRId64,
2322           lang_str_get(de->de_title, NULL), DVR_CH_NAME(de), (int64_t)de->de_start, (int64_t)de->de_stop, (int64_t)stop);
2323 
2324   gtimer_arm_absn(&de->de_timer, dvr_timer_stop_recording, de,
2325                   dvr_entry_get_stop_time(de));
2326 }
2327 
2328 
2329 /**
2330  *
2331  */
2332 static void
dvr_timer_start_recording(void * aux)2333 dvr_timer_start_recording(void *aux)
2334 {
2335   dvr_entry_t *de = aux;
2336 
2337   if (de->de_channel == NULL || !de->de_channel->ch_enabled) {
2338     dvr_entry_set_state(de, DVR_NOSTATE, DVR_RS_PENDING, de->de_last_error);
2339     return;
2340   }
2341 
2342   // if duplicate, then delete it now, don't record!
2343   if (_dvr_duplicate_event(de)) {
2344     dvr_entry_cancel_delete(de, 1);
2345     return;
2346   }
2347 
2348   dvr_entry_start_recording(de, 0);
2349 }
2350 
2351 
2352 /**
2353  *
2354  */
2355 dvr_entry_t *
dvr_entry_find_by_id(int id)2356 dvr_entry_find_by_id(int id)
2357 {
2358   dvr_entry_t *de;
2359   LIST_FOREACH(de, &dvrentries, de_global_link)
2360     if(idnode_get_short_uuid(&de->de_id) == id)
2361       break;
2362   return de;
2363 }
2364 
2365 
2366 /**
2367  * Unconditionally remove an entry
2368  */
2369 static void
dvr_entry_purge(dvr_entry_t * de,int delconf)2370 dvr_entry_purge(dvr_entry_t *de, int delconf)
2371 {
2372   int dont_reschedule;
2373 
2374   if(de->de_sched_state == DVR_RECORDING) {
2375     dont_reschedule = de->de_dont_reschedule;
2376     dvr_stop_recording(de, SM_CODE_SOURCE_DELETED, delconf, 0);
2377     if (!delconf)
2378       de->de_dont_reschedule = dont_reschedule;
2379   }
2380 }
2381 
2382 
2383 /* **************************************************************************
2384  * DVR Entry Class definition
2385  * **************************************************************************/
2386 
2387 static void
dvr_entry_class_changed(idnode_t * self)2388 dvr_entry_class_changed(idnode_t *self)
2389 {
2390   dvr_entry_t *de = (dvr_entry_t *)self;
2391   if (de->de_in_unsubscribe)
2392     return;
2393   if (dvr_entry_is_valid(de))
2394     dvr_entry_set_timer(de);
2395   htsp_dvr_entry_update(de);
2396 }
2397 
2398 static htsmsg_t *
dvr_entry_class_save(idnode_t * self,char * filename,size_t fsize)2399 dvr_entry_class_save(idnode_t *self, char *filename, size_t fsize)
2400 {
2401   dvr_entry_t *de = (dvr_entry_t *)self;
2402   htsmsg_t *m = htsmsg_create_map(), *e, *l, *c, *info;
2403   htsmsg_field_t *f;
2404   char ubuf[UUID_HEX_SIZE];
2405   const char *filename2;
2406   int64_t s64;
2407 
2408   idnode_save(&de->de_id, m);
2409   if (de->de_files) {
2410     l = htsmsg_create_list();
2411     HTSMSG_FOREACH(f, de->de_files)
2412       if ((e = htsmsg_field_get_map(f)) != NULL) {
2413         filename2 = htsmsg_get_str(e, "filename");
2414         info = htsmsg_get_list(e, "info");
2415         if (filename2) {
2416           c = htsmsg_create_map();
2417           htsmsg_add_str(c, "filename", filename2);
2418           if (info)
2419             htsmsg_add_msg(c, "info", htsmsg_copy(info));
2420           if (!htsmsg_get_s64(e, "start", &s64))
2421             htsmsg_add_s64(c, "start", s64);
2422           if (!htsmsg_get_s64(e, "stop", &s64))
2423             htsmsg_add_s64(c, "stop", s64);
2424           htsmsg_add_msg(l, NULL, c);
2425         }
2426       }
2427     htsmsg_add_msg(m, "files", l);
2428   }
2429   snprintf(filename, fsize, "dvr/log/%s", idnode_uuid_as_str(&de->de_id, ubuf));
2430   return m;
2431 }
2432 
2433 static void
dvr_entry_class_delete(idnode_t * self)2434 dvr_entry_class_delete(idnode_t *self)
2435 {
2436   dvr_entry_t *de = (dvr_entry_t *)self;
2437   dvr_entry_cancel_delete(de, 0);
2438 }
2439 
2440 static int
dvr_entry_class_perm(idnode_t * self,access_t * a,htsmsg_t * msg_to_write)2441 dvr_entry_class_perm(idnode_t *self, access_t *a, htsmsg_t *msg_to_write)
2442 {
2443   dvr_entry_t *de = (dvr_entry_t *)self;
2444 
2445   if (access_verify2(a, ACCESS_OR|ACCESS_ADMIN|ACCESS_RECORDER))
2446     return -1;
2447   if (!access_verify2(a, ACCESS_ADMIN))
2448     return 0;
2449   if (dvr_entry_verify(de, a, msg_to_write == NULL ? 1 : 0))
2450     return -1;
2451   return 0;
2452 }
2453 
2454 static const char *
dvr_entry_class_get_title(idnode_t * self,const char * lang)2455 dvr_entry_class_get_title (idnode_t *self, const char *lang)
2456 {
2457   dvr_entry_t *de = (dvr_entry_t *)self;
2458   const char *s;
2459   s = lang_str_get(de->de_title, lang);
2460   if (tvh_str_default(s, NULL) == NULL) {
2461     s = lang ? lang_str_get(de->de_title, NULL) : NULL;
2462     if (tvh_str_default(s, NULL) == NULL) {
2463       s = lang ? lang_str_get(de->de_desc, lang) : NULL;
2464       if (tvh_str_default(s, NULL) == NULL)
2465         s = lang_str_get(de->de_desc, NULL);
2466     }
2467   }
2468   return s;
2469 }
2470 
2471 static int
dvr_entry_class_time_set(dvr_entry_t * de,time_t * v,time_t nv)2472 dvr_entry_class_time_set(dvr_entry_t *de, time_t *v, time_t nv)
2473 {
2474   if (!dvr_entry_is_editable(de))
2475     return 0;
2476   if (nv != *v) {
2477     *v = nv;
2478     return 1;
2479   }
2480   return 0;
2481 }
2482 
2483 static int
dvr_entry_class_int_set(dvr_entry_t * de,int * v,int nv)2484 dvr_entry_class_int_set(dvr_entry_t *de, int *v, int nv)
2485 {
2486   if (!dvr_entry_is_editable(de))
2487     return 0;
2488   if (nv != *v) {
2489     *v = nv;
2490     return 1;
2491   }
2492   return 0;
2493 }
2494 
2495 static int
dvr_entry_class_start_set(void * o,const void * v)2496 dvr_entry_class_start_set(void *o, const void *v)
2497 {
2498   dvr_entry_t *de = (dvr_entry_t *)o;
2499   return dvr_entry_class_time_set(de, &de->de_start, *(time_t *)v);
2500 }
2501 
2502 static uint32_t
dvr_entry_class_start_opts(void * o)2503 dvr_entry_class_start_opts(void *o)
2504 {
2505   dvr_entry_t *de = (dvr_entry_t *)o;
2506   if (de && !dvr_entry_is_editable(de))
2507     return PO_RDONLY;
2508   return 0;
2509 }
2510 
2511 static uint32_t
dvr_entry_class_config_name_opts(void * o)2512 dvr_entry_class_config_name_opts(void *o)
2513 {
2514   dvr_entry_t *de = (dvr_entry_t *)o;
2515   if (de && !dvr_entry_is_editable(de))
2516     return PO_RDONLY | PO_ADVANCED;
2517   return PO_ADVANCED;
2518 }
2519 
2520 static uint32_t
dvr_entry_class_owner_opts(void * o)2521 dvr_entry_class_owner_opts(void *o)
2522 {
2523   dvr_entry_t *de = (dvr_entry_t *)o;
2524   if (de && de->de_id.in_access &&
2525       !access_verify2(de->de_id.in_access, ACCESS_ADMIN))
2526     return PO_ADVANCED;
2527   return PO_RDONLY | PO_ADVANCED;
2528 }
2529 
2530 static uint32_t
dvr_entry_class_start_extra_opts(void * o)2531 dvr_entry_class_start_extra_opts(void *o)
2532 {
2533   dvr_entry_t *de = (dvr_entry_t *)o;
2534   if (de && !dvr_entry_is_editable(de))
2535     return PO_RDONLY | PO_DURATION | PO_ADVANCED;
2536   return PO_DURATION | PO_ADVANCED | PO_DOC_NLIST;
2537 }
2538 
2539 static int
dvr_entry_class_start_extra_set(void * o,const void * v)2540 dvr_entry_class_start_extra_set(void *o, const void *v)
2541 {
2542   dvr_entry_t *de = (dvr_entry_t *)o;
2543   return dvr_entry_class_time_set(de, &de->de_start_extra, *(time_t *)v);
2544 }
2545 
2546 static int
dvr_entry_class_stop_set(void * o,const void * _v)2547 dvr_entry_class_stop_set(void *o, const void *_v)
2548 {
2549   dvr_entry_t *de = (dvr_entry_t *)o;
2550   time_t v = *(time_t *)_v;
2551 
2552   if (!dvr_entry_is_editable(de)) {
2553     if (v < gclk())
2554       v = gclk();
2555   }
2556   if (v < de->de_start)
2557     v = de->de_start;
2558   if (v != de->de_stop) {
2559     de->de_stop = v;
2560     return 1;
2561   }
2562   return 0;
2563 }
2564 
2565 static int
dvr_entry_class_config_name_set(void * o,const void * v)2566 dvr_entry_class_config_name_set(void *o, const void *v)
2567 {
2568   dvr_entry_t *de = (dvr_entry_t *)o;
2569   dvr_config_t *cfg;
2570 
2571   if (!dvr_entry_is_editable(de))
2572     return 0;
2573   cfg = v ? dvr_config_find_by_uuid(v) : NULL;
2574   if (!cfg)
2575     cfg = dvr_config_find_by_name_default(v);
2576   if (cfg == NULL) {
2577     if (de->de_config) {
2578       LIST_REMOVE(de, de_config_link);
2579       de->de_config = NULL;
2580       return 1;
2581     }
2582   } else if (de->de_config != cfg) {
2583     if (de->de_config)
2584       LIST_REMOVE(de, de_config_link);
2585     de->de_config = cfg;
2586     LIST_INSERT_HEAD(&cfg->dvr_entries, de, de_config_link);
2587     return 1;
2588   }
2589   return 0;
2590 }
2591 
2592 static const void *
dvr_entry_class_config_name_get(void * o)2593 dvr_entry_class_config_name_get(void *o)
2594 {
2595   dvr_entry_t *de = (dvr_entry_t *)o;
2596   if (de->de_config)
2597     idnode_uuid_as_str(&de->de_config->dvr_id, prop_sbuf);
2598   else
2599     prop_sbuf[0] = '\0';
2600   return &prop_sbuf_ptr;
2601 }
2602 
2603 htsmsg_t *
dvr_entry_class_config_name_list(void * o,const char * lang)2604 dvr_entry_class_config_name_list(void *o, const char *lang)
2605 {
2606   htsmsg_t *m = htsmsg_create_map();
2607   htsmsg_t *p = htsmsg_create_map();
2608   htsmsg_add_str(m, "type",  "api");
2609   htsmsg_add_str(m, "uri",   "idnode/load");
2610   htsmsg_add_str(m, "event", "dvrconfig");
2611   htsmsg_add_u32(p, "enum",  1);
2612   htsmsg_add_str(p, "class", dvr_config_class.ic_class);
2613   htsmsg_add_msg(m, "params", p);
2614   return m;
2615 }
2616 
2617 static char *
dvr_entry_class_config_name_rend(void * o,const char * lang)2618 dvr_entry_class_config_name_rend(void *o, const char *lang)
2619 {
2620   dvr_entry_t *de = (dvr_entry_t *)o;
2621   if (de->de_config)
2622     return strdup(de->de_config->dvr_config_name);
2623   return NULL;
2624 }
2625 
2626 static const void *
dvr_entry_class_filename_get(void * o)2627 dvr_entry_class_filename_get(void *o)
2628 {
2629   dvr_entry_t *de = (dvr_entry_t *)o;
2630   prop_ptr = dvr_get_filename(de) ?: "";
2631   return &prop_ptr;
2632 }
2633 
2634 static int
dvr_entry_class_channel_set(void * o,const void * v)2635 dvr_entry_class_channel_set(void *o, const void *v)
2636 {
2637   dvr_entry_t *de = (dvr_entry_t *)o;
2638   channel_t   *ch;
2639 
2640   if (!dvr_entry_is_editable(de))
2641     return 0;
2642   ch = v ? channel_find_by_uuid(v) : NULL;
2643   if (ch == NULL) {
2644     if (de->de_channel) {
2645       LIST_REMOVE(de, de_channel_link);
2646       free(de->de_channel_name);
2647       de->de_channel_name = NULL;
2648       de->de_channel = NULL;
2649       return 1;
2650     }
2651   } else if (de->de_channel != ch) {
2652     if (de->de_id.in_access &&
2653         !channel_access(ch, de->de_id.in_access, 1))
2654       return 0;
2655     if (de->de_channel)
2656       LIST_REMOVE(de, de_channel_link);
2657     free(de->de_channel_name);
2658     de->de_channel_name = strdup(channel_get_name(ch));
2659     de->de_channel = ch;
2660     LIST_INSERT_HEAD(&ch->ch_dvrs, de, de_channel_link);
2661     return 1;
2662   }
2663   return 0;
2664 }
2665 
2666 static const void *
dvr_entry_class_channel_get(void * o)2667 dvr_entry_class_channel_get(void *o)
2668 {
2669   dvr_entry_t *de = (dvr_entry_t *)o;
2670   if (de->de_channel)
2671     idnode_uuid_as_str(&de->de_channel->ch_id, prop_sbuf);
2672   else
2673     prop_sbuf[0] = '\0';
2674   return &prop_sbuf_ptr;
2675 }
2676 
2677 static char *
dvr_entry_class_channel_rend(void * o,const char * lang)2678 dvr_entry_class_channel_rend(void *o, const char *lang)
2679 {
2680   dvr_entry_t *de = (dvr_entry_t *)o;
2681   if (de->de_channel)
2682     return strdup(channel_get_name(de->de_channel));
2683   return NULL;
2684 }
2685 
2686 static int
dvr_entry_class_channel_name_set(void * o,const void * v)2687 dvr_entry_class_channel_name_set(void *o, const void *v)
2688 {
2689   dvr_entry_t *de = (dvr_entry_t *)o;
2690   channel_t   *ch;
2691   char ubuf[UUID_HEX_SIZE];
2692   if (!dvr_entry_is_editable(de))
2693     return 0;
2694   if (!strcmp(de->de_channel_name ?: "", v ?: ""))
2695     return 0;
2696   ch = v ? channel_find_by_name(v) : NULL;
2697   if (ch) {
2698     return dvr_entry_class_channel_set(o, idnode_uuid_as_str(&ch->ch_id, ubuf));
2699   } else {
2700     free(de->de_channel_name);
2701     de->de_channel_name = v ? strdup(v) : NULL;
2702     return 1;
2703   }
2704 }
2705 
2706 static const void *
dvr_entry_class_channel_name_get(void * o)2707 dvr_entry_class_channel_name_get(void *o)
2708 {
2709   dvr_entry_t *de = (dvr_entry_t *)o;
2710   if (de->de_channel)
2711     prop_ptr = channel_get_name(de->de_channel);
2712   else
2713     prop_ptr = de->de_channel_name;
2714   return &prop_ptr;
2715 }
2716 
2717 static int
dvr_entry_class_pri_set(void * o,const void * v)2718 dvr_entry_class_pri_set(void *o, const void *v)
2719 {
2720   dvr_entry_t *de = (dvr_entry_t *)o;
2721   return dvr_entry_class_int_set(de, &de->de_pri, *(int *)v);
2722 }
2723 
2724 htsmsg_t *
dvr_entry_class_pri_list(void * o,const char * lang)2725 dvr_entry_class_pri_list ( void *o, const char *lang )
2726 {
2727   static const struct strtab tab[] = {
2728     { N_("Not set"),        DVR_PRIO_NOTSET },
2729     { N_("Important"),      DVR_PRIO_IMPORTANT },
2730     { N_("High"),           DVR_PRIO_HIGH, },
2731     { N_("Normal"),         DVR_PRIO_NORMAL },
2732     { N_("Low"),            DVR_PRIO_LOW },
2733     { N_("Unimportant"),    DVR_PRIO_UNIMPORTANT },
2734   };
2735   return strtab2htsmsg(tab, 1, lang);
2736 }
2737 
2738 htsmsg_t *
dvr_entry_class_retention_list(void * o,const char * lang)2739 dvr_entry_class_retention_list ( void *o, const char *lang )
2740 {
2741   static const struct strtab_u32 tab[] = {
2742     { N_("DVR configuration"),  DVR_RET_REM_DVRCONFIG },
2743     { N_("1 day"),              DVR_RET_REM_1DAY },
2744     { N_("3 days"),             DVR_RET_REM_3DAY },
2745     { N_("5 days"),             DVR_RET_REM_5DAY },
2746     { N_("1 week"),             DVR_RET_REM_1WEEK },
2747     { N_("2 weeks"),            DVR_RET_REM_2WEEK },
2748     { N_("3 weeks"),            DVR_RET_REM_3WEEK },
2749     { N_("1 month"),            DVR_RET_REM_1MONTH },
2750     { N_("2 months"),           DVR_RET_REM_2MONTH },
2751     { N_("3 months"),           DVR_RET_REM_3MONTH },
2752     { N_("6 months"),           DVR_RET_REM_6MONTH },
2753     { N_("1 year"),             DVR_RET_REM_1YEAR },
2754     { N_("2 years"),            DVR_RET_REM_2YEARS },
2755     { N_("3 years"),            DVR_RET_REM_3YEARS },
2756     { N_("On file removal"),    DVR_RET_ONREMOVE },
2757     { N_("Forever"),            DVR_RET_REM_FOREVER },
2758   };
2759   return strtab2htsmsg_u32(tab, 1, lang);
2760 }
2761 
2762 htsmsg_t *
dvr_entry_class_removal_list(void * o,const char * lang)2763 dvr_entry_class_removal_list ( void *o, const char *lang )
2764 {
2765   static const struct strtab_u32 tab[] = {
2766     { N_("DVR configuration"),  DVR_RET_REM_DVRCONFIG },
2767     { N_("1 day"),              DVR_RET_REM_1DAY },
2768     { N_("3 days"),             DVR_RET_REM_3DAY },
2769     { N_("5 days"),             DVR_RET_REM_5DAY },
2770     { N_("1 week"),             DVR_RET_REM_1WEEK },
2771     { N_("2 weeks"),            DVR_RET_REM_2WEEK },
2772     { N_("3 weeks"),            DVR_RET_REM_3WEEK },
2773     { N_("1 month"),            DVR_RET_REM_1MONTH },
2774     { N_("2 months"),           DVR_RET_REM_2MONTH },
2775     { N_("3 months"),           DVR_RET_REM_3MONTH },
2776     { N_("6 months"),           DVR_RET_REM_6MONTH },
2777     { N_("1 year"),             DVR_RET_REM_1YEAR },
2778     { N_("2 years"),            DVR_RET_REM_2YEARS },
2779     { N_("3 years"),            DVR_RET_REM_3YEARS },
2780     { N_("Maintained space"),   DVR_REM_SPACE },
2781     { N_("Forever"),            DVR_RET_REM_FOREVER },
2782   };
2783   return strtab2htsmsg_u32(tab, 1, lang);
2784 }
2785 
2786 static int
dvr_entry_class_autorec_set(void * o,const void * v)2787 dvr_entry_class_autorec_set(void *o, const void *v)
2788 {
2789   dvr_entry_t *de = (dvr_entry_t *)o;
2790   dvr_autorec_entry_t *dae;
2791   if (!dvr_entry_is_editable(de))
2792     return 0;
2793   dae = v ? dvr_autorec_find_by_uuid(v) : NULL;
2794   if (dae == NULL) {
2795     if (de->de_autorec) {
2796       LIST_REMOVE(de, de_autorec_link);
2797       de->de_autorec = NULL;
2798       return 1;
2799     }
2800   } else if (de->de_autorec != dae) {
2801     de->de_autorec = dae;
2802     LIST_INSERT_HEAD(&dae->dae_spawns, de, de_autorec_link);
2803     return 1;
2804   }
2805   return 0;
2806 }
2807 
2808 static const void *
dvr_entry_class_autorec_get(void * o)2809 dvr_entry_class_autorec_get(void *o)
2810 {
2811   dvr_entry_t *de = (dvr_entry_t *)o;
2812   if (de->de_autorec)
2813     idnode_uuid_as_str(&de->de_autorec->dae_id, prop_sbuf);
2814   else
2815     prop_sbuf[0] = '\0';
2816   return &prop_sbuf_ptr;
2817 }
2818 
2819 static const void *
dvr_entry_class_autorec_caption_get(void * o)2820 dvr_entry_class_autorec_caption_get(void *o)
2821 {
2822   dvr_entry_t *de = (dvr_entry_t *)o;
2823   dvr_autorec_entry_t *dae = de->de_autorec;
2824   if (dae) {
2825     const int comment = !strempty(dae->dae_comment);
2826     snprintf(prop_sbuf, PROP_SBUF_LEN, "%s%s%s%s",
2827              dae->dae_name ?: "",
2828              comment ? " (" : "",
2829              comment ? dae->dae_comment : "",
2830              comment ? ")" : "");
2831   } else
2832     prop_sbuf[0] = '\0';
2833   return &prop_sbuf_ptr;
2834 }
2835 
2836 static int
dvr_entry_class_timerec_set(void * o,const void * v)2837 dvr_entry_class_timerec_set(void *o, const void *v)
2838 {
2839   dvr_entry_t *de = (dvr_entry_t *)o;
2840   dvr_timerec_entry_t *dte;
2841   if (!dvr_entry_is_editable(de))
2842     return 0;
2843   dte = v ? dvr_timerec_find_by_uuid(v) : NULL;
2844   if (dte == NULL) {
2845     if (de->de_timerec) {
2846       de->de_timerec->dte_spawn = NULL;
2847       de->de_timerec = NULL;
2848       return 1;
2849     }
2850   } else if (de->de_timerec != dte) {
2851     de->de_timerec = dte;
2852     dte->dte_spawn = de;
2853     return 1;
2854   }
2855   return 0;
2856 }
2857 
2858 static const void *
dvr_entry_class_timerec_get(void * o)2859 dvr_entry_class_timerec_get(void *o)
2860 {
2861   dvr_entry_t *de = (dvr_entry_t *)o;
2862   if (de->de_timerec)
2863     idnode_uuid_as_str(&de->de_timerec->dte_id, prop_sbuf);
2864   else
2865     prop_sbuf[0] = '\0';
2866   return &prop_sbuf_ptr;
2867 }
2868 
2869 static const void *
dvr_entry_class_timerec_caption_get(void * o)2870 dvr_entry_class_timerec_caption_get(void *o)
2871 {
2872   dvr_entry_t *de = (dvr_entry_t *)o;
2873   dvr_timerec_entry_t *dte = de->de_timerec;
2874   if (dte) {
2875     snprintf(prop_sbuf, PROP_SBUF_LEN, "%s%s%s%s",
2876              dte->dte_name ?: "",
2877              dte->dte_comment ? " (" : "",
2878              dte->dte_comment,
2879              dte->dte_comment ? ")" : "");
2880   } else
2881     prop_sbuf[0] = '\0';
2882   return &prop_sbuf_ptr;
2883 }
2884 
2885 static int
dvr_entry_class_parent_set(void * o,const void * v)2886 dvr_entry_class_parent_set(void *o, const void *v)
2887 {
2888   dvr_entry_t *de = (dvr_entry_t *)o, *de2;
2889   if (!dvr_entry_is_editable(de))
2890     return 0;
2891   de2 = v ? dvr_entry_find_by_uuid(v) : NULL;
2892   return dvr_entry_change_parent_child(de2, de, de, 1);
2893 }
2894 
2895 static const void *
dvr_entry_class_parent_get(void * o)2896 dvr_entry_class_parent_get(void *o)
2897 {
2898   dvr_entry_t *de = (dvr_entry_t *)o;
2899   if (de->de_parent)
2900     idnode_uuid_as_str(&de->de_parent->de_id, prop_sbuf);
2901   else
2902     prop_sbuf[0] = '\0';
2903   return &prop_sbuf_ptr;
2904 }
2905 
2906 static int
dvr_entry_class_child_set(void * o,const void * v)2907 dvr_entry_class_child_set(void *o, const void *v)
2908 {
2909   dvr_entry_t *de = (dvr_entry_t *)o, *de2;
2910   if (!dvr_entry_is_editable(de))
2911     return 0;
2912   de2 = v ? dvr_entry_find_by_uuid(v) : NULL;
2913   return dvr_entry_change_parent_child(de, de2, de, 1);
2914 }
2915 
2916 static const void *
dvr_entry_class_child_get(void * o)2917 dvr_entry_class_child_get(void *o)
2918 {
2919   dvr_entry_t *de = (dvr_entry_t *)o;
2920   if (de->de_child)
2921     idnode_uuid_as_str(&de->de_child->de_id, prop_sbuf);
2922   else
2923     prop_sbuf[0] = '\0';
2924   return &prop_sbuf_ptr;
2925 }
2926 
2927 static int
dvr_entry_class_broadcast_set(void * o,const void * v)2928 dvr_entry_class_broadcast_set(void *o, const void *v)
2929 {
2930   dvr_entry_t *de = (dvr_entry_t *)o;
2931   uint32_t id = *(uint32_t *)v;
2932   epg_broadcast_t *bcast;
2933   if (!dvr_entry_is_editable(de))
2934     return 0;
2935   bcast = epg_broadcast_find_by_id(id);
2936   return dvr_entry_assign_broadcast(de, bcast);
2937 }
2938 
2939 static const void *
dvr_entry_class_broadcast_get(void * o)2940 dvr_entry_class_broadcast_get(void *o)
2941 {
2942   static uint32_t id;
2943   dvr_entry_t *de = (dvr_entry_t *)o;
2944   if (de->de_bcast)
2945     id = de->de_bcast->id;
2946   else
2947     id = 0;
2948   return &id;
2949 }
2950 
2951 static int
dvr_entry_class_disp_title_set(void * o,const void * v)2952 dvr_entry_class_disp_title_set(void *o, const void *v)
2953 {
2954   dvr_entry_t *de = (dvr_entry_t *)o;
2955   const char *lang = idnode_lang(&de->de_id);
2956   const char *s = "";
2957   v = tvh_str_default(v, "UnknownTitle");
2958   if (de->de_title)
2959     s = lang_str_get(de->de_title, lang);
2960   if (strcmp(s, v)) {
2961     lang_str_set(&de->de_title, v, lang);
2962     return 1;
2963   }
2964   return 0;
2965 }
2966 
2967 static const void *
dvr_entry_class_disp_title_get(void * o)2968 dvr_entry_class_disp_title_get(void *o)
2969 {
2970   dvr_entry_t *de = (dvr_entry_t *)o;
2971   if (de->de_title)
2972     prop_ptr = lang_str_get(de->de_title, idnode_lang(o));
2973   else
2974     prop_ptr = NULL;
2975   if (prop_ptr == NULL)
2976     prop_ptr = "";
2977   return &prop_ptr;
2978 }
2979 
2980 static int
dvr_entry_class_disp_subtitle_set(void * o,const void * v)2981 dvr_entry_class_disp_subtitle_set(void *o, const void *v)
2982 {
2983   dvr_entry_t *de = (dvr_entry_t *)o;
2984   const char *lang = idnode_lang(o);
2985   const char *s = "";
2986   v = tvh_str_default(v, "UnknownSubtitle");
2987   if (de->de_subtitle)
2988     s = lang_str_get(de->de_subtitle, lang);
2989   if (strcmp(s, v)) {
2990     lang_str_set(&de->de_subtitle, v, lang);
2991     return 1;
2992   }
2993   return 0;
2994 }
2995 
2996 static const void *
dvr_entry_class_disp_subtitle_get(void * o)2997 dvr_entry_class_disp_subtitle_get(void *o)
2998 {
2999   dvr_entry_t *de = (dvr_entry_t *)o;
3000   if (de->de_subtitle)
3001     prop_ptr = lang_str_get(de->de_subtitle, idnode_lang(o));
3002   else
3003     prop_ptr = NULL;
3004   if (prop_ptr == NULL)
3005     prop_ptr = "";
3006   return &prop_ptr;
3007 }
3008 
3009 static const void *
dvr_entry_class_disp_description_get(void * o)3010 dvr_entry_class_disp_description_get(void *o)
3011 {
3012   dvr_entry_t *de = (dvr_entry_t *)o;
3013   if (de->de_desc)
3014     prop_ptr = lang_str_get(de->de_desc, idnode_lang(o));
3015   else
3016     prop_ptr = NULL;
3017   if (prop_ptr == NULL)
3018     prop_ptr = "";
3019   return &prop_ptr;
3020 }
3021 
3022 static const void *
dvr_entry_class_url_get(void * o)3023 dvr_entry_class_url_get(void *o)
3024 {
3025   dvr_entry_t *de = (dvr_entry_t *)o;
3026   char ubuf[UUID_HEX_SIZE];
3027   prop_sbuf[0] = '\0';
3028   if (de->de_sched_state == DVR_COMPLETED ||
3029       de->de_sched_state == DVR_RECORDING)
3030     snprintf(prop_sbuf, PROP_SBUF_LEN, "dvrfile/%s", idnode_uuid_as_str(&de->de_id, ubuf));
3031   return &prop_sbuf_ptr;
3032 }
3033 
3034 static const void *
dvr_entry_class_filesize_get(void * o)3035 dvr_entry_class_filesize_get(void *o)
3036 {
3037   static int64_t size;
3038   dvr_entry_t *de = (dvr_entry_t *)o;
3039   if (de->de_sched_state == DVR_COMPLETED ||
3040       de->de_sched_state == DVR_RECORDING) {
3041     size = dvr_get_filesize(de, DVR_FILESIZE_UPDATE);
3042     if (size < 0)
3043       size = 0;
3044   } else
3045     size = 0;
3046   return &size;
3047 }
3048 
3049 static const void *
dvr_entry_class_start_real_get(void * o)3050 dvr_entry_class_start_real_get(void *o)
3051 {
3052   static time_t tm;
3053   dvr_entry_t *de = (dvr_entry_t *)o;
3054   tm = dvr_entry_get_start_time(de, 1);
3055   return &tm;
3056 }
3057 
3058 static const void *
dvr_entry_class_stop_real_get(void * o)3059 dvr_entry_class_stop_real_get(void *o)
3060 {
3061   static time_t tm;
3062   dvr_entry_t *de = (dvr_entry_t *)o;
3063   tm  = dvr_entry_get_stop_time(de);
3064   return &tm;
3065 }
3066 
3067 static const void *
dvr_entry_class_duration_get(void * o)3068 dvr_entry_class_duration_get(void *o)
3069 {
3070   static time_t tm;
3071   time_t start, stop;
3072   dvr_entry_t *de = (dvr_entry_t *)o;
3073   start = dvr_entry_get_start_time(de, 0);
3074   stop  = dvr_entry_get_stop_time(de);
3075   if (stop > start)
3076     tm = stop - start;
3077   else
3078     tm = 0;
3079   return &tm;
3080 }
3081 
3082 static const void *
dvr_entry_class_status_get(void * o)3083 dvr_entry_class_status_get(void *o)
3084 {
3085   dvr_entry_t *de = (dvr_entry_t *)o;
3086   strlcpy(prop_sbuf, dvr_entry_status(de), PROP_SBUF_LEN);
3087   return &prop_sbuf_ptr;
3088 }
3089 
3090 static const void *
dvr_entry_class_sched_status_get(void * o)3091 dvr_entry_class_sched_status_get(void *o)
3092 {
3093   dvr_entry_t *de = (dvr_entry_t *)o;
3094   strlcpy(prop_sbuf, dvr_entry_schedstatus(de), PROP_SBUF_LEN);
3095   return &prop_sbuf_ptr;
3096 }
3097 
3098 static const void *
dvr_entry_class_channel_icon_url_get(void * o)3099 dvr_entry_class_channel_icon_url_get(void *o)
3100 {
3101   dvr_entry_t *de = (dvr_entry_t *)o;
3102   channel_t *ch = de->de_channel;
3103   if (ch == NULL) {
3104     prop_ptr = NULL;
3105   } else {
3106     prop_ptr = channel_get_icon(ch);
3107   }
3108   if (prop_ptr == NULL)
3109     prop_ptr = "";
3110   return &prop_ptr;
3111 }
3112 
3113 static const void *
dvr_entry_class_duplicate_get(void * o)3114 dvr_entry_class_duplicate_get(void *o)
3115 {
3116   static time_t null = 0;
3117   dvr_entry_t *de = (dvr_entry_t *)o;
3118   de = _dvr_duplicate_event(de);
3119   return de ? &de->de_start : &null;
3120 }
3121 
3122 htsmsg_t *
dvr_entry_class_duration_list(void * o,const char * not_set,int max,int step,const char * lang)3123 dvr_entry_class_duration_list(void *o, const char *not_set, int max, int step, const char *lang)
3124 {
3125   int i;
3126   htsmsg_t *e, *l = htsmsg_create_list();
3127   const char *hrs  = tvh_gettext_lang(lang, N_("hrs"));
3128   const char *min  = tvh_gettext_lang(lang, N_("min"));
3129   const char *mins = tvh_gettext_lang(lang, N_("mins"));
3130   char buf[32];
3131   e = htsmsg_create_map();
3132   htsmsg_add_u32(e, "key", 0);
3133   htsmsg_add_str(e, "val", not_set);
3134   htsmsg_add_msg(l, NULL, e);
3135   for (i = 1; i <= 120;  i++) {
3136     snprintf(buf, sizeof(buf), "%d %s", i, i > 1 ? mins : min);
3137     e = htsmsg_create_map();
3138     htsmsg_add_u32(e, "key", i * step);
3139     htsmsg_add_str(e, "val", buf);
3140     htsmsg_add_msg(l, NULL, e);
3141   }
3142   for (i = 120; i <= max; i += 30) {
3143     if ((i % 60) == 0)
3144       snprintf(buf, sizeof(buf), "%d %s", i / 60, hrs);
3145     else
3146       snprintf(buf, sizeof(buf), "%d %s %d %s", i / 60, hrs, i % 60, (i % 60) > 0 ? mins : min);
3147     e = htsmsg_create_map();
3148     htsmsg_add_u32(e, "key", i * step);
3149     htsmsg_add_str(e, "val", buf);
3150     htsmsg_add_msg(l, NULL, e);
3151   }
3152   return l;
3153 }
3154 
3155 static htsmsg_t *
dvr_entry_class_extra_list(void * o,const char * lang)3156 dvr_entry_class_extra_list(void *o, const char *lang)
3157 {
3158   const char *msg = N_("Not set (use channel or DVR configuration)");
3159   return dvr_entry_class_duration_list(o, tvh_gettext_lang(lang, msg), 4*60, 1, lang);
3160 }
3161 
3162 static htsmsg_t *
dvr_entry_class_content_type_list(void * o,const char * lang)3163 dvr_entry_class_content_type_list(void *o, const char *lang)
3164 {
3165   htsmsg_t *m = htsmsg_create_map();
3166   htsmsg_add_str(m, "type",  "api");
3167   htsmsg_add_str(m, "uri",   "epg/content_type/list");
3168   return m;
3169 }
3170 
3171 CLASS_DOC(dvrentry)
3172 PROP_DOC(dvr_status)
3173 PROP_DOC(dvr_start_extra)
3174 PROP_DOC(dvr_stop_extra)
3175 
3176 const idclass_t dvr_entry_class = {
3177   .ic_class     = "dvrentry",
3178   .ic_caption   = N_("Digital Video Recorder"),
3179   .ic_event     = "dvrentry",
3180   .ic_doc       = tvh_doc_dvrentry_class,
3181   .ic_changed   = dvr_entry_class_changed,
3182   .ic_save      = dvr_entry_class_save,
3183   .ic_get_title = dvr_entry_class_get_title,
3184   .ic_delete    = dvr_entry_class_delete,
3185   .ic_perm      = dvr_entry_class_perm,
3186   .ic_properties = (const property_t[]) {
3187     {
3188       .type     = PT_BOOL,
3189       .id       = "enabled",
3190       .name     = N_("Enabled"),
3191       .desc     = N_("Enable/disable the entry."),
3192       .off      = offsetof(dvr_entry_t, de_enabled),
3193     },
3194     {
3195       .type     = PT_TIME,
3196       .id       = "start",
3197       .name     = N_("Start time"),
3198       .desc     = N_("The start time of the recording."),
3199       .set      = dvr_entry_class_start_set,
3200       .off      = offsetof(dvr_entry_t, de_start),
3201       .get_opts = dvr_entry_class_start_opts,
3202     },
3203     {
3204       .type     = PT_TIME,
3205       .id       = "start_extra",
3206       .name     = N_("Pre-recording padding"),
3207       .desc     = N_("Start recording earlier than the "
3208                      "EPG/timer-defined start time by x minutes."),
3209       .doc      = prop_doc_dvr_start_extra,
3210       .off      = offsetof(dvr_entry_t, de_start_extra),
3211       .set      = dvr_entry_class_start_extra_set,
3212       .list     = dvr_entry_class_extra_list,
3213       .get_opts = dvr_entry_class_start_extra_opts,
3214     },
3215     {
3216       .type     = PT_TIME,
3217       .id       = "start_real",
3218       .name     = N_("Scheduled start time"),
3219       .desc     = N_("The scheduled start time, including any padding."),
3220       .get      = dvr_entry_class_start_real_get,
3221       .opts     = PO_RDONLY | PO_NOSAVE,
3222     },
3223     {
3224       .type     = PT_TIME,
3225       .id       = "stop",
3226       .name     = N_("Stop time"),
3227       .desc     = N_("The time the entry stops/stopped being recorded."),
3228       .set      = dvr_entry_class_stop_set,
3229       .off      = offsetof(dvr_entry_t, de_stop),
3230     },
3231     {
3232       .type     = PT_TIME,
3233       .id       = "stop_extra",
3234       .name     = N_("Post-recording padding"),
3235       .desc     = N_("Continue recording for x minutes after scheduled "
3236                      "stop time."),
3237       .doc      = prop_doc_dvr_stop_extra,
3238       .off      = offsetof(dvr_entry_t, de_stop_extra),
3239       .list     = dvr_entry_class_extra_list,
3240       .opts     = PO_SORTKEY | PO_ADVANCED | PO_DOC_NLIST,
3241     },
3242     {
3243       .type     = PT_TIME,
3244       .id       = "stop_real",
3245       .name     = N_("Scheduled stop time"),
3246       .desc     = N_("The scheduled stop time, including any padding."),
3247       .get      = dvr_entry_class_stop_real_get,
3248       .opts     = PO_RDONLY | PO_NOSAVE,
3249     },
3250     {
3251       .type     = PT_TIME,
3252       .id       = "duration",
3253       .name     = N_("Scheduled Duration"),
3254       .desc     = N_("The total scheduled duration."),
3255       .get      = dvr_entry_class_duration_get,
3256       .opts     = PO_RDONLY | PO_NOSAVE | PO_DURATION,
3257     },
3258     {
3259       .type     = PT_STR,
3260       .id       = "channel",
3261       .name     = N_("Channel"),
3262       .desc     = N_("The channel name the entry will record from."),
3263       .set      = dvr_entry_class_channel_set,
3264       .get      = dvr_entry_class_channel_get,
3265       .rend     = dvr_entry_class_channel_rend,
3266       .list     = channel_class_get_list,
3267       .get_opts = dvr_entry_class_start_opts,
3268     },
3269     {
3270       .type     = PT_STR,
3271       .id       = "channel_icon",
3272       .name     = N_("Channel icon"),
3273       .desc     = N_("Channel icon URL."),
3274       .get      = dvr_entry_class_channel_icon_url_get,
3275       .opts     = PO_HIDDEN | PO_RDONLY | PO_NOSAVE | PO_NOUI,
3276     },
3277     {
3278       .type     = PT_STR,
3279       .id       = "channelname",
3280       .name     = N_("Channel name"),
3281       .desc     = N_("Name of channel the entry recorded from."),
3282       .get      = dvr_entry_class_channel_name_get,
3283       .set      = dvr_entry_class_channel_name_set,
3284       .off      = offsetof(dvr_entry_t, de_channel_name),
3285       .opts     = PO_RDONLY,
3286     },
3287     {
3288       .type     = PT_LANGSTR,
3289       .id       = "title",
3290       .name     = N_("Title"),
3291       .desc     = N_("Title of the program."),
3292       .off      = offsetof(dvr_entry_t, de_title),
3293       .opts     = PO_RDONLY,
3294     },
3295     {
3296       .type     = PT_STR,
3297       .id       = "disp_title",
3298       .name     = N_("Title"),
3299       .desc     = N_("Title of the program (display only)."),
3300       .get      = dvr_entry_class_disp_title_get,
3301       .set      = dvr_entry_class_disp_title_set,
3302       .opts     = PO_NOSAVE,
3303     },
3304     {
3305       .type     = PT_LANGSTR,
3306       .id       = "subtitle",
3307       .name     = N_("Subtitle"),
3308       .desc     = N_("Subtitle of the program (if any)."),
3309       .off      = offsetof(dvr_entry_t, de_subtitle),
3310       .opts     = PO_RDONLY,
3311     },
3312     {
3313       .type     = PT_STR,
3314       .id       = "disp_subtitle",
3315       .name     = N_("Subtitle"),
3316       .desc     = N_("Subtitle of the program (if any) (display only)."),
3317       .get      = dvr_entry_class_disp_subtitle_get,
3318       .set      = dvr_entry_class_disp_subtitle_set,
3319       .opts     = PO_NOSAVE,
3320     },
3321     {
3322       .type     = PT_LANGSTR,
3323       .id       = "description",
3324       .name     = N_("Description"),
3325       .desc     = N_("Program synopsis."),
3326       .off      = offsetof(dvr_entry_t, de_desc),
3327       .opts     = PO_RDONLY,
3328     },
3329     {
3330       .type     = PT_STR,
3331       .id       = "disp_description",
3332       .name     = N_("Description"),
3333       .desc     = N_("Program synopsis (display only)."),
3334       .get      = dvr_entry_class_disp_description_get,
3335       .opts     = PO_RDONLY | PO_NOSAVE | PO_HIDDEN,
3336     },
3337     {
3338       .type     = PT_INT,
3339       .id       = "pri",
3340       .name     = N_("Priority"),
3341       .desc     = N_("Priority of the recording. Higher priority entries will take precedence and cancel lower-priority events."),
3342       .off      = offsetof(dvr_entry_t, de_pri),
3343       .def.i    = DVR_PRIO_NORMAL,
3344       .set      = dvr_entry_class_pri_set,
3345       .list     = dvr_entry_class_pri_list,
3346       .opts     = PO_SORTKEY | PO_DOC_NLIST | PO_ADVANCED,
3347     },
3348     {
3349       .type     = PT_U32,
3350       .id       = "retention",
3351       .name     = N_("DVR log retention"),
3352       .desc     = N_("Number of days to retain entry information."),
3353       .off      = offsetof(dvr_entry_t, de_retention),
3354       .def.i    = DVR_RET_REM_DVRCONFIG,
3355       .list     = dvr_entry_class_retention_list,
3356       .opts     = PO_HIDDEN | PO_EXPERT | PO_DOC_NLIST,
3357     },
3358     {
3359       .type     = PT_U32,
3360       .id       = "removal",
3361       .name     = N_("DVR file retention period"),
3362       .desc     = N_("Number of days to keep the file."),
3363       .off      = offsetof(dvr_entry_t, de_removal),
3364       .def.i    = DVR_RET_REM_DVRCONFIG,
3365       .list     = dvr_entry_class_removal_list,
3366       .opts     = PO_HIDDEN | PO_ADVANCED | PO_DOC_NLIST,
3367     },
3368     {
3369       .type     = PT_U32,
3370       .id       = "playposition",
3371       .name     = N_("Last played position"),
3372       .desc     = N_("Last played position when the recording isn't fully watched yet."),
3373       .off      = offsetof(dvr_entry_t, de_playposition),
3374       .def.i    = 0,
3375       .opts     = PO_HIDDEN | PO_NOUI | PO_DOC_NLIST,
3376     },
3377     {
3378       .type     = PT_U32,
3379       .id       = "playcount",
3380       .name     = N_("Recording play count"),
3381       .desc     = N_("Number of times this recording was played."),
3382       .off      = offsetof(dvr_entry_t, de_playcount),
3383       .def.i    = 0,
3384       .opts     = PO_HIDDEN | PO_EXPERT | PO_DOC_NLIST,
3385     },
3386     {
3387       .type     = PT_STR,
3388       .id       = "config_name",
3389       .name     = N_("DVR configuration"),
3390       .desc     = N_("The DVR profile to be used/used by the recording."),
3391       .set      = dvr_entry_class_config_name_set,
3392       .get      = dvr_entry_class_config_name_get,
3393       .list     = dvr_entry_class_config_name_list,
3394       .rend     = dvr_entry_class_config_name_rend,
3395       .get_opts = dvr_entry_class_config_name_opts,
3396     },
3397     {
3398       .type     = PT_STR,
3399       .id       = "owner",
3400       .name     = N_("Owner"),
3401       .desc     = N_("Owner of the entry."),
3402       .off      = offsetof(dvr_entry_t, de_owner),
3403       .list     = user_get_userlist,
3404       .get_opts = dvr_entry_class_owner_opts,
3405       .opts     = PO_SORTKEY,
3406     },
3407     {
3408       .type     = PT_STR,
3409       .id       = "creator",
3410       .name     = N_("Creator"),
3411       .desc     = N_("The user who created the recording, or the "
3412                      "auto-recording source and IP address if scheduled "
3413                      "by a matching rule."),
3414       .off      = offsetof(dvr_entry_t, de_creator),
3415       .get_opts = dvr_entry_class_owner_opts,
3416       .opts     = PO_SORTKEY,
3417     },
3418     {
3419       .type     = PT_STR,
3420       .id       = "filename",
3421       .name     = N_("Filename"),
3422       .desc     = N_("Filename used by the entry."),
3423       .get      = dvr_entry_class_filename_get,
3424       .opts     = PO_RDONLY | PO_NOSAVE | PO_NOUI,
3425     },
3426     {
3427       .type     = PT_STR,
3428       .id       = "directory",
3429       .name     = N_("Directory"),
3430       .desc     = N_("Directory used by the entry."),
3431       .off      = offsetof(dvr_entry_t, de_directory),
3432       .opts     = PO_RDONLY | PO_NOUI,
3433     },
3434     {
3435       .type     = PT_U32,
3436       .id       = "errorcode",
3437       .name     = N_("Error code"),
3438       .desc     = N_("Error code of entry."),
3439       .off      = offsetof(dvr_entry_t, de_last_error),
3440       .opts     = PO_RDONLY | PO_NOUI,
3441     },
3442     {
3443       .type     = PT_U32,
3444       .id       = "errors",
3445       .name     = N_("Errors"),
3446       .desc     = N_("Number of errors that occurred during recording."),
3447       .off      = offsetof(dvr_entry_t, de_errors),
3448       .opts     = PO_RDONLY | PO_ADVANCED,
3449     },
3450     {
3451       .type     = PT_U32,
3452       .id       = "data_errors",
3453       .name     = N_("Data errors"),
3454       .desc     = N_("Number of errors that occurred during recording "
3455                      "(Transport errors)."),
3456       .off      = offsetof(dvr_entry_t, de_data_errors),
3457       .opts     = PO_RDONLY | PO_ADVANCED,
3458     },
3459     {
3460       .type     = PT_U16,
3461       .id       = "dvb_eid",
3462       .name     = N_("DVB EPG ID"),
3463       .desc     = N_("The EPG ID used by the entry."),
3464       .off      = offsetof(dvr_entry_t, de_dvb_eid),
3465       .opts     = PO_RDONLY | PO_EXPERT,
3466     },
3467     {
3468       .type     = PT_BOOL,
3469       .id       = "noresched",
3470       .name     = N_("Don't reschedule"),
3471       .desc     = N_("Don't re-schedule if recording fails."),
3472       .off      = offsetof(dvr_entry_t, de_dont_reschedule),
3473       .opts     = PO_HIDDEN | PO_NOUI,
3474     },
3475     {
3476       .type     = PT_BOOL,
3477       .id       = "norerecord",
3478       .name     = N_("Don't re-record"),
3479       .desc     = N_("Don't re-record if recording fails."),
3480       .off      = offsetof(dvr_entry_t, de_dont_rerecord),
3481       .opts     = PO_HIDDEN | PO_ADVANCED,
3482     },
3483     {
3484       .type     = PT_U32,
3485       .id       = "fileremoved",
3486       .name     = N_("File removed"),
3487       .desc     = N_("The recorded file was removed intentionally"),
3488       .off      = offsetof(dvr_entry_t, de_file_removed),
3489       .opts     = PO_HIDDEN | PO_NOUI,
3490     },
3491     {
3492       .type     = PT_STR,
3493       .id       = "autorec",
3494       .name     = N_("Auto record"),
3495       .desc     = N_("Automatically record."),
3496       .set      = dvr_entry_class_autorec_set,
3497       .get      = dvr_entry_class_autorec_get,
3498       .opts     = PO_RDONLY | PO_NOUI,
3499     },
3500     {
3501       .type     = PT_STR,
3502       .id       = "autorec_caption",
3503       .name     = N_("Auto record caption"),
3504       .desc     = N_("Automatic recording caption."),
3505       .get      = dvr_entry_class_autorec_caption_get,
3506       .opts     = PO_RDONLY | PO_NOSAVE | PO_HIDDEN | PO_NOUI,
3507     },
3508     {
3509       .type     = PT_STR,
3510       .id       = "timerec",
3511       .name     = N_("Auto time record"),
3512       .desc     = N_("Timer-based automatic recording."),
3513       .set      = dvr_entry_class_timerec_set,
3514       .get      = dvr_entry_class_timerec_get,
3515       .opts     = PO_RDONLY | PO_EXPERT,
3516     },
3517     {
3518       .type     = PT_STR,
3519       .id       = "timerec_caption",
3520       .name     = N_("Time record caption"),
3521       .desc     = N_("Timer-based automatic record caption."),
3522       .get      = dvr_entry_class_timerec_caption_get,
3523       .opts     = PO_RDONLY | PO_NOSAVE | PO_HIDDEN | PO_NOUI,
3524     },
3525     {
3526       .type     = PT_STR,
3527       .id       = "parent",
3528       .name     = N_("Parent entry"),
3529       .desc     = N_("Parent entry."),
3530       .set      = dvr_entry_class_parent_set,
3531       .get      = dvr_entry_class_parent_get,
3532       .opts     = PO_RDONLY | PO_NOUI,
3533     },
3534     {
3535       .type     = PT_STR,
3536       .id       = "child",
3537       .name     = N_("Slave entry"),
3538       .desc     = N_("Slave entry."),
3539       .set      = dvr_entry_class_child_set,
3540       .get      = dvr_entry_class_child_get,
3541       .opts     = PO_RDONLY | PO_NOUI,
3542     },
3543     {
3544       .type     = PT_U32,
3545       .id       = "content_type",
3546       .name     = N_("Content type"),
3547       .desc     = N_("Content type."),
3548       .list     = dvr_entry_class_content_type_list,
3549       .off      = offsetof(dvr_entry_t, de_content_type),
3550       .opts     = PO_RDONLY | PO_SORTKEY,
3551     },
3552     {
3553       .type     = PT_U32,
3554       .id       = "broadcast",
3555       .name     = N_("Broadcast"),
3556       .desc     = N_("Broadcast."),
3557       .set      = dvr_entry_class_broadcast_set,
3558       .get      = dvr_entry_class_broadcast_get,
3559       .opts     = PO_RDONLY | PO_NOUI,
3560     },
3561     {
3562       .type     = PT_STR,
3563       .id       = "episode",
3564       .name     = N_("Episode"),
3565       .desc     = N_("Episode number/ID."),
3566       .off      = offsetof(dvr_entry_t, de_episode),
3567       .opts     = PO_RDONLY | PO_HIDDEN,
3568     },
3569     {
3570       .type     = PT_STR,
3571       .id       = "url",
3572       .name     = N_("URL"),
3573       .desc     = N_("URL."),
3574       .get      = dvr_entry_class_url_get,
3575       .opts     = PO_RDONLY | PO_NOSAVE | PO_NOUI,
3576     },
3577     {
3578       .type     = PT_S64,
3579       .id       = "filesize",
3580       .name     = N_("File size"),
3581       .desc     = N_("Recording file size."),
3582       .get      = dvr_entry_class_filesize_get,
3583       .opts     = PO_RDONLY | PO_NOSAVE,
3584     },
3585     {
3586       .type     = PT_STR,
3587       .id       = "status",
3588       .name     = N_("Status"),
3589       .doc      = prop_doc_dvr_status,
3590       .desc     = N_("The recording/entry status."),
3591       .get      = dvr_entry_class_status_get,
3592       .opts     = PO_RDONLY | PO_NOSAVE | PO_LOCALE,
3593     },
3594     {
3595       .type     = PT_STR,
3596       .id       = "sched_status",
3597       .name     = N_("Schedule status"),
3598       .desc     = N_("Schedule status."),
3599       .get      = dvr_entry_class_sched_status_get,
3600       .opts     = PO_RDONLY | PO_NOSAVE | PO_HIDDEN | PO_NOUI,
3601     },
3602     {
3603       .type     = PT_TIME,
3604       .id       = "duplicate",
3605       .name     = N_("Rerun of"),
3606       .desc     = N_("Name (or date) of program the entry is a rerun of."),
3607       .get      = dvr_entry_class_duplicate_get,
3608       .opts     = PO_RDONLY | PO_NOSAVE | PO_ADVANCED,
3609     },
3610     {
3611       .type     = PT_STR,
3612       .id       = "comment",
3613       .name     = N_("Comment"),
3614       .desc     = N_("Free-form text field, enter whatever you like here."),
3615       .off      = offsetof(dvr_entry_t, de_comment),
3616     },
3617     {}
3618   }
3619 };
3620 
3621 /**
3622  *
3623  */
3624 void
dvr_destroy_by_channel(channel_t * ch,int delconf)3625 dvr_destroy_by_channel(channel_t *ch, int delconf)
3626 {
3627   dvr_entry_t *de;
3628 
3629   while((de = LIST_FIRST(&ch->ch_dvrs)) != NULL) {
3630     LIST_REMOVE(de, de_channel_link);
3631     de->de_channel = NULL;
3632     free(de->de_channel_name);
3633     de->de_channel_name = strdup(channel_get_name(ch));
3634     dvr_entry_purge(de, delconf);
3635   }
3636 }
3637 
3638 /**
3639  *
3640  */
3641 const char *
dvr_get_filename(dvr_entry_t * de)3642 dvr_get_filename(dvr_entry_t *de)
3643 {
3644   htsmsg_field_t *f;
3645   htsmsg_t *m;
3646   const char *s;
3647   if ((f = htsmsg_field_last(de->de_files)) != NULL &&
3648       (m = htsmsg_field_get_map(f)) != NULL &&
3649       (s = htsmsg_get_str(m, "filename")) != NULL)
3650     return s;
3651   else
3652     return NULL;
3653 }
3654 
3655 /**
3656  *
3657  */
3658 int64_t
dvr_get_filesize(dvr_entry_t * de,int flags)3659 dvr_get_filesize(dvr_entry_t *de, int flags)
3660 {
3661   htsmsg_field_t *f;
3662   htsmsg_t *m;
3663   const char *filename;
3664   int first = 1;
3665   int64_t res = 0, size;
3666 
3667   if (de->de_files == NULL)
3668     return -1;
3669 
3670   HTSMSG_FOREACH(f, de->de_files)
3671     if ((m = htsmsg_field_get_map(f)) != NULL) {
3672       filename = htsmsg_get_str(m, "filename");
3673       if (flags & DVR_FILESIZE_UPDATE)
3674         size = dvr_vfs_update_filename(filename, m);
3675       else
3676         size = htsmsg_get_s64_or_default(m, "size", -1);
3677       if (filename && size >= 0) {
3678         res = (flags & DVR_FILESIZE_TOTAL) ? (res + size) : size;
3679         first = 0;
3680       }
3681     }
3682 
3683   return first ? -1 : res;
3684 }
3685 
3686 /**
3687  *
3688  */
3689 static struct strtab priotab[] = {
3690   { "important",   DVR_PRIO_IMPORTANT },
3691   { "high",        DVR_PRIO_HIGH },
3692   { "normal",      DVR_PRIO_NORMAL },
3693   { "low",         DVR_PRIO_LOW },
3694   { "unimportant", DVR_PRIO_UNIMPORTANT }
3695 };
3696 
3697 dvr_prio_t
dvr_pri2val(const char * s)3698 dvr_pri2val(const char *s)
3699 {
3700   return str2val_def(s, priotab, DVR_PRIO_NORMAL);
3701 }
3702 
3703 const char *
dvr_val2pri(dvr_prio_t v)3704 dvr_val2pri(dvr_prio_t v)
3705 {
3706   return val2str(v, priotab) ?: "invalid";
3707 }
3708 
3709 /**
3710  *
3711  */
3712 int
dvr_entry_delete(dvr_entry_t * de)3713 dvr_entry_delete(dvr_entry_t *de)
3714 {
3715   dvr_config_t *cfg = de->de_config;
3716   htsmsg_t *m;
3717   htsmsg_field_t *f;
3718   time_t t;
3719   struct tm tm;
3720   const char *filename;
3721   char *str1, *str2;
3722   char tbuf[64], ubuf[UUID_HEX_SIZE], *rdir, *cmd;
3723   int r, ret = 0;
3724 
3725   t = dvr_entry_get_start_time(de, 1);
3726   localtime_r(&t, &tm);
3727   if (strftime(tbuf, sizeof(tbuf), "%F %T", &tm) <= 0)
3728     *tbuf = 0;
3729 
3730   str1 = dvr_entry_get_retention_string(de);
3731   str2 = dvr_entry_get_removal_string(de);
3732   tvhinfo(LS_DVR, "delete entry %s \"%s\" on \"%s\" start time %s, "
3733 	  "scheduled for recording by \"%s\", retention \"%s\" removal \"%s\"",
3734           idnode_uuid_as_str(&de->de_id, ubuf),
3735 	  lang_str_get(de->de_title, NULL), DVR_CH_NAME(de), tbuf,
3736 	  de->de_creator ?: "", str1, str2);
3737   free(str2);
3738   free(str1);
3739 
3740   if(!htsmsg_is_empty(de->de_files)) {
3741 #if ENABLE_INOTIFY
3742     dvr_inotify_del(de);
3743 #endif
3744     rdir = NULL;
3745     if(cfg->dvr_title_dir || cfg->dvr_channel_dir || cfg->dvr_dir_per_day || de->de_directory)
3746       rdir = cfg->dvr_storage;
3747 
3748     dvr_vfs_remove_entry(de);
3749     HTSMSG_FOREACH(f, de->de_files) {
3750       m = htsmsg_field_get_map(f);
3751       if (m == NULL) continue;
3752       filename = htsmsg_get_str(m, "filename");
3753       if (filename == NULL) continue;
3754       r = deferred_unlink(filename, rdir);
3755       if(r && r != -ENOENT)
3756         tvhwarn(LS_DVR, "Unable to remove file '%s' from disk -- %s",
3757   	        filename, strerror(-errno));
3758 
3759       cmd = de->de_config->dvr_postremove;
3760       if (cmd && cmd[0])
3761         dvr_spawn_cmd(de, cmd, filename, 0);
3762       htsmsg_delete_field(m, "filename");
3763       ret = 1;
3764     }
3765     de->de_file_removed = 1;
3766   }
3767 
3768   return ret;
3769 }
3770 
3771 /**
3772  *
3773  */
3774 void
dvr_entry_set_rerecord(dvr_entry_t * de,int cmd)3775 dvr_entry_set_rerecord(dvr_entry_t *de, int cmd)
3776 {
3777   if (cmd < 0) { /* toggle */
3778     if (de->de_parent) return;
3779     cmd = de->de_dont_rerecord ? 1 : 0;
3780   }
3781   if (cmd == 0 && !de->de_dont_rerecord) {
3782     de->de_dont_rerecord = 1;
3783     if (de->de_child)
3784       dvr_entry_cancel_delete(de->de_child, 0);
3785   } else {
3786     de->de_dont_rerecord = 0;
3787     dvr_entry_rerecord(de);
3788   }
3789 }
3790 
3791 /**
3792  *
3793  */
3794 void
dvr_entry_move(dvr_entry_t * de,int to_failed)3795 dvr_entry_move(dvr_entry_t *de, int to_failed)
3796 {
3797   if(de->de_sched_state == DVR_COMPLETED)
3798     if (dvr_entry_completed(de, to_failed ? SM_CODE_USER_REQUEST :
3799                                             SM_CODE_FORCE_OK))
3800       idnode_changed(&de->de_id);
3801 }
3802 
3803 /**
3804  *
3805  */
3806 dvr_entry_t *
dvr_entry_stop(dvr_entry_t * de)3807 dvr_entry_stop(dvr_entry_t *de)
3808 {
3809   if(de->de_sched_state == DVR_RECORDING) {
3810     dvr_stop_recording(de, SM_CODE_OK, 1, 0);
3811     return de;
3812   }
3813 
3814   return de;
3815 }
3816 
3817 /**
3818  * Cancels an upcoming or active recording
3819  */
3820 dvr_entry_t *
dvr_entry_cancel(dvr_entry_t * de,int rerecord)3821 dvr_entry_cancel(dvr_entry_t *de, int rerecord)
3822 {
3823   dvr_entry_t *parent = de->de_parent;
3824 
3825   switch(de->de_sched_state) {
3826   case DVR_RECORDING:
3827     dvr_stop_recording(de, SM_CODE_ABORTED, 1, 0);
3828     break;
3829   /* Cancel is not valid for finished recordings */
3830   case DVR_COMPLETED:
3831   case DVR_MISSED_TIME:
3832     return de;
3833   case DVR_SCHEDULED:
3834   case DVR_NOSTATE:
3835     dvr_entry_destroy(de, 1);
3836     de = NULL;
3837     break;
3838 
3839   default:
3840     abort();
3841   }
3842 
3843   if (parent) {
3844     if (!rerecord)
3845       dvr_entry_dont_rerecord(parent, 1);
3846     else
3847       dvr_entry_rerecord(parent);
3848   }
3849 
3850   return de;
3851 }
3852 
3853 /**
3854  * Called by 'dvr_entry_cancel_remove' and 'dvr_entry_cancel_delete'
3855  * delete = 0 -> remove finished and active recordings (visible as removed)
3856  * delete = 1 -> delete finished and active recordings (not visible anymore)
3857  */
3858 static void
dvr_entry_cancel_delete_remove(dvr_entry_t * de,int rerecord,int _delete)3859 dvr_entry_cancel_delete_remove(dvr_entry_t *de, int rerecord, int _delete)
3860 {
3861   dvr_entry_t *parent = de->de_parent;
3862   dvr_autorec_entry_t *dae = de->de_autorec;
3863   int save;
3864 
3865   switch(de->de_sched_state) {
3866   case DVR_RECORDING:
3867     dvr_stop_recording(de, SM_CODE_ABORTED, 1, 0);
3868   case DVR_MISSED_TIME:
3869   case DVR_COMPLETED:
3870     save = dvr_entry_delete(de);                           /* Remove files */
3871     if (_delete || dvr_entry_delete_retention_expired(de)) /* In case retention was postponed (retention < removal) */
3872       dvr_entry_destroy(de, 1);                            /* Delete database */
3873     else if (save) {
3874       dvr_entry_changed_notify(de);
3875       dvr_entry_retention_timer(de);                       /* As retention timer depends on file removal */
3876     }
3877     break;
3878   case DVR_SCHEDULED:
3879   case DVR_NOSTATE:
3880     dvr_entry_destroy(de, 1);
3881     break;
3882 
3883   default:
3884     abort();
3885   }
3886 
3887   if (parent) {
3888     if (!rerecord)
3889       dvr_entry_dont_rerecord(parent, 1);
3890     else
3891       dvr_entry_rerecord(parent);
3892   }
3893 
3894   // Trigger autorec update in case of max sched count limit
3895   if (dae && dae->dae_max_sched_count > 0)
3896     dvr_autorec_changed(dae, 0);
3897 }
3898 
3899 /**
3900  * Upcoming recording  -> cancel
3901  * Active recording    -> cancel + remove
3902  * Finished recordings -> remove
3903  * The latter 2 will be visible as "removed recording"
3904  */
3905 void
dvr_entry_cancel_remove(dvr_entry_t * de,int rerecord)3906 dvr_entry_cancel_remove(dvr_entry_t *de, int rerecord)
3907 {
3908   dvr_entry_cancel_delete_remove(de, rerecord, 0);
3909 }
3910 
3911 /**
3912  * Upcoming recording  -> cancel
3913  * Active recording    -> abort + delete
3914  * Finished recordings -> delete
3915  * The latter 2 will NOT be visible as "removed recording"
3916  */
3917 void
dvr_entry_cancel_delete(dvr_entry_t * de,int rerecord)3918 dvr_entry_cancel_delete(dvr_entry_t *de, int rerecord)
3919 {
3920   dvr_entry_cancel_delete_remove(de, rerecord, 1);
3921 }
3922 
3923 /**
3924  *
3925  */
3926 int
dvr_entry_file_moved(const char * src,const char * dst)3927 dvr_entry_file_moved(const char *src, const char *dst)
3928 {
3929   dvr_entry_t *de;
3930   htsmsg_t *m;
3931   htsmsg_field_t *f;
3932   const char *filename;
3933   int r = -1, chg;
3934 
3935   if (!src || !dst || src[0] == '\0' || dst[0] == '\0' || access(dst, R_OK))
3936     return r;
3937   pthread_mutex_lock(&global_lock);
3938   LIST_FOREACH(de, &dvrentries, de_global_link) {
3939     if (htsmsg_is_empty(de->de_files)) continue;
3940     chg = 0;
3941     HTSMSG_FOREACH(f, de->de_files)
3942       if ((m = htsmsg_field_get_map(f)) != NULL) {
3943         filename = htsmsg_get_str(m, "filename");
3944         if (strcmp(filename, src) == 0) {
3945           htsmsg_set_str(m, "filename", dst);
3946           dvr_vfs_refresh_entry(de);
3947           r = 0;
3948           chg++;
3949         }
3950       }
3951     if (chg)
3952       idnode_changed(&de->de_id);
3953   }
3954   pthread_mutex_unlock(&global_lock);
3955   return r;
3956 }
3957 
3958 /**
3959  *
3960  */
3961 void
dvr_entry_init(void)3962 dvr_entry_init(void)
3963 {
3964   htsmsg_t *l, *c, *rere;
3965   htsmsg_field_t *f;
3966   const char *parent, *child;
3967   dvr_entry_t *de1, *de2;
3968 
3969   dvr_in_init = 1;
3970   idclass_register(&dvr_entry_class);
3971   rere = htsmsg_create_map();
3972   /* load config, but remove parent/child fields */
3973   if((l = hts_settings_load("dvr/log")) != NULL) {
3974     HTSMSG_FOREACH(f, l) {
3975       if((c = htsmsg_get_map_by_field(f)) == NULL)
3976 	continue;
3977       parent = htsmsg_get_str(c, "parent");
3978       child = htsmsg_get_str(c, "child");
3979       if (parent && parent[0])
3980         htsmsg_set_str(rere, parent, f->hmf_name);
3981       if (child && child[0])
3982         htsmsg_set_str(rere, f->hmf_name, child);
3983       htsmsg_delete_field(c, "parent");
3984       htsmsg_delete_field(c, "child");
3985       (void)dvr_entry_create(f->hmf_name, c, 0);
3986     }
3987     htsmsg_destroy(l);
3988   }
3989   dvr_in_init = 0;
3990   /* process parent/child mapping */
3991   HTSMSG_FOREACH(f, rere) {
3992     if((child = htsmsg_field_get_str(f)) == NULL)
3993       continue;
3994     parent = f->hmf_name;
3995     de1 = dvr_entry_find_by_uuid(parent);
3996     de2 = dvr_entry_find_by_uuid(child);
3997     dvr_entry_change_parent_child(de1, de2, NULL, 0);
3998   }
3999   htsmsg_destroy(rere);
4000   /* update the entry timers, call rerecord */
4001   for (de1 = LIST_FIRST(&dvrentries); de1; de1 = de2) {
4002     de2 = LIST_NEXT(de1, de_global_link);
4003     dvr_entry_set_timer(de1);
4004   }
4005 }
4006 
4007 void
dvr_entry_done(void)4008 dvr_entry_done(void)
4009 {
4010   dvr_entry_t *de;
4011   lock_assert(&global_lock);
4012   while ((de = LIST_FIRST(&dvrentries)) != NULL) {
4013     if (de->de_sched_state == DVR_RECORDING)
4014       dvr_rec_unsubscribe(de);
4015     dvr_entry_destroy(de, 0);
4016   }
4017 }
4018