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