vdr 2.6.4
timers.c
Go to the documentation of this file.
1/*
2 * timers.c: Timer handling
3 *
4 * See the main source file 'vdr.c' for copyright information and
5 * how to reach the author.
6 *
7 * $Id: timers.c 5.18 2022/11/20 10:57:31 kls Exp $
8 */
9
10#include "timers.h"
11#include <ctype.h>
12#include "device.h"
13#include "i18n.h"
14#include "libsi/si.h"
15#include "recording.h"
16#include "remote.h"
17#include "status.h"
18#include "svdrp.h"
19
20// IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d'
21// format characters in order to allow any number of blanks after a numeric
22// value!
23
24// --- cTimer ----------------------------------------------------------------
25
26cTimer::cTimer(bool Instant, bool Pause, const cChannel *Channel)
27{
28 id = 0;
29 startTime = stopTime = 0;
31 deferred = 0;
32 pending = inVpsMargin = false;
33 flags = tfNone;
34 *pattern = 0;
35 *file = 0;
36 aux = NULL;
37 remote = NULL;
38 event = NULL;
39 if (Instant)
42 channel = Channel ? Channel : Channels->GetByNumber(cDevice::CurrentChannel());
43 time_t t = time(NULL);
44 struct tm tm_r;
45 struct tm *now = localtime_r(&t, &tm_r);
46 day = SetTime(t, 0);
47 weekdays = 0;
48 start = now->tm_hour * 100 + now->tm_min;
49 stop = 0;
50 if (!Setup.InstantRecordTime && channel && (Instant || Pause)) {
52 if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) {
53 if (const cEvent *Event = Schedule->GetPresentEvent()) {
54 time_t tstart = Event->StartTime();
55 time_t tstop = Event->EndTime();
56 if (Event->Vps() && Setup.UseVps) {
58 tstart = Event->Vps();
59 }
60 else {
61 int MarginStart = 0;
62 int MarginStop = 0;
63 CalcMargins(MarginStart, MarginStop, Event);
64 tstart -= MarginStart;
65 tstop += MarginStop;
66 }
67 day = SetTime(tstart, 0);
68 struct tm *time = localtime_r(&tstart, &tm_r);
69 start = time->tm_hour * 100 + time->tm_min;
70 time = localtime_r(&tstop, &tm_r);
71 stop = time->tm_hour * 100 + time->tm_min;
73 }
74 }
75 }
76 if (!stop) {
77 stop = now->tm_hour * 60 + now->tm_min + (Setup.InstantRecordTime ? Setup.InstantRecordTime : DEFINSTRECTIME);
78 stop = (stop / 60) * 100 + (stop % 60);
79 }
80 if (stop >= 2400)
81 stop -= 2400;
84 if (Instant && channel)
85 snprintf(file, sizeof(file), "%s%s", Setup.MarkInstantRecord ? "@" : "", *Setup.NameInstantRecord ? Setup.NameInstantRecord : channel->Name());
86}
87
88static bool MatchPattern(const char *Pattern, const char *Title, cString *Before = NULL, cString *Match = NULL, cString *After = NULL)
89{
90 if (Title) {
91 bool AvoidDuplicates = startswith(Pattern, TIMERPATTERN_AVOID);
92 if (AvoidDuplicates)
93 Pattern++;
94 if (strcmp(Pattern, "*") == 0) {
95 if (Before)
96 *Before = "";
97 if (Match)
98 *Match = Title;
99 if (After)
100 *After = "";
101 return true;
102 }
103 bool AnchorBegin = startswith(Pattern, TIMERPATTERN_BEGIN);
104 if (AnchorBegin)
105 Pattern++;
106 bool AnchorEnd = endswith(Pattern, TIMERPATTERN_END);
108 if (AnchorEnd)
109 nt.Set(const_cast<char *>(Pattern + strlen(Pattern) - 1));
110 if (AnchorBegin && AnchorEnd) {
111 if (strcmp(Title, Pattern) == 0) {
112 if (Before)
113 *Before = "";
114 if (Match)
115 *Match = Title;
116 if (After)
117 *After = "";
118 return true;
119 }
120 }
121 else if (AnchorBegin) {
122 if (strstr(Title, Pattern) == Title) {
123 if (Before)
124 *Before = "";
125 if (Match)
126 *Match = Pattern;
127 if (After)
128 *After = cString(Title + strlen(Pattern));
129 return true;
130 }
131 }
132 else if (AnchorEnd) {
133 if (endswith(Title, Pattern)) {
134 if (Before)
135 *Before = cString(Title, Title + strlen(Title) - strlen(Pattern));
136 if (Match)
137 *Match = Pattern;
138 if (After)
139 *After = "";
140 return true;
141 }
142 }
143 else if (const char *p = strstr(Title, Pattern)) {
144 if (Before)
145 *Before = cString(Title, p);
146 if (Match)
147 *Match = Pattern;
148 if (After)
149 *After = cString(p + strlen(Pattern));
150 return true;
151 }
152 }
153 return false;
154}
155
156static cString MakePatternFileName(const char *Pattern, const char *Title, const char *Episode, const char *File)
157{
158 if (!Pattern || !Title || !File)
159 return NULL;
160 cString Before = "";
161 cString Match = "";
162 cString After = "";
163 if (MatchPattern(Pattern, Title, &Before, &Match, &After)) {
164 char *Result = strdup(File);
165 Result = strreplace(Result, TIMERMACRO_TITLE, Title);
166 if (!isempty(Episode)) // the event might not yet have a "short text", so we leave this to the actual recording
167 Result = strreplace(Result, TIMERMACRO_EPISODE, Episode);
168 Result = strreplace(Result, TIMERMACRO_BEFORE, Before);
169 Result = strreplace(Result, TIMERMACRO_MATCH, Match);
170 Result = strreplace(Result, TIMERMACRO_AFTER, After);
171 return cString(Result, true);
172 }
173 return NULL;
174}
175
176cTimer::cTimer(const cEvent *Event, const char *FileName, const cTimer *PatternTimer)
177{
178 id = 0;
179 startTime = stopTime = 0;
181 deferred = 0;
182 pending = inVpsMargin = false;
183 flags = tfActive;
184 *pattern = 0;
185 *file = 0;
186 aux = NULL;
187 remote = NULL;
188 event = NULL;
189 if (!PatternTimer || PatternTimer->HasFlags(tfVps)) {
190 if (Event->Vps() && (PatternTimer || Setup.UseVps))
192 }
194 channel = Channels->GetByChannelID(Event->ChannelID(), true);
195 time_t tstart = (flags & tfVps) ? Event->Vps() : Event->StartTime();
196 time_t tstop = tstart + Event->Duration();
197 if (!(HasFlags(tfVps))) {
198 int MarginStart = 0;
199 int MarginStop = 0;
200 CalcMargins(MarginStart, MarginStop, Event);
201 tstart -= MarginStart;
202 tstop += MarginStop;
203 }
204 struct tm tm_r;
205 struct tm *time = localtime_r(&tstart, &tm_r);
206 day = SetTime(tstart, 0);
207 weekdays = 0;
208 start = time->tm_hour * 100 + time->tm_min;
209 time = localtime_r(&tstop, &tm_r);
210 stop = time->tm_hour * 100 + time->tm_min;
211 if (stop >= 2400)
212 stop -= 2400;
213 priority = PatternTimer ? PatternTimer->Priority() : Setup.DefaultPriority;
214 lifetime = PatternTimer ? PatternTimer->Lifetime() : Setup.DefaultLifetime;
215 if (!FileName)
216 FileName = Event->Title();
217 if (!isempty(FileName))
218 Utf8Strn0Cpy(file, FileName, sizeof(file));
220}
221
223{
224 channel = NULL;
225 aux = NULL;
226 remote = NULL;
227 event = NULL;
228 flags = tfNone;
229 *this = Timer;
230}
231
233{
234 if (event)
235 event->DecNumTimers();
236 free(aux);
237 free(remote);
238}
239
241{
242 if (&Timer != this) {
243 id = Timer.id;
244 startTime = Timer.startTime;
245 stopTime = Timer.stopTime;
247 deferred = 0;
248 pending = Timer.pending;
249 inVpsMargin = Timer.inVpsMargin;
250 flags = Timer.flags;
251 channel = Timer.channel;
252 day = Timer.day;
253 weekdays = Timer.weekdays;
254 start = Timer.start;
255 stop = Timer.stop;
256 priority = Timer.priority;
257 lifetime = Timer.lifetime;
258 strncpy(pattern, Timer.pattern, sizeof(pattern));
259 strncpy(file, Timer.file, sizeof(file));
260 free(aux);
261 aux = Timer.aux ? strdup(Timer.aux) : NULL;
262 free(remote);
263 remote = Timer.remote ? strdup(Timer.remote) : NULL;
264 if (event)
265 event->DecNumTimers();
266 event = Timer.event;
267 if (event)
268 event->IncNumTimers();
269 }
270 return *this;
271}
272
273void cTimer::CalcMargins(int &MarginStart, int &MarginStop, const cEvent *Event)
274{
275 MarginStart = Setup.MarginStart * 60;
276 MarginStop = Setup.MarginStop * 60;
277 // To make sure the timer gets assigned to the correct event, we must
278 // make sure that this is the only event that overlaps 100%:
279 if (const cEvent *e = dynamic_cast<const cEvent *>(Event->Prev()))
280 MarginStart = max(0, min(MarginStart, e->Duration() - 60));
281 if (const cEvent *e = dynamic_cast<const cEvent *>(Event->Next()))
282 MarginStop = max(0, min(MarginStop, e->Duration() - 60));
283}
284
285int cTimer::Compare(const cListObject &ListObject) const
286{
287 const cTimer *ti = (const cTimer *)&ListObject;
288 time_t t1 = StartTime();
289 time_t t2 = ti->StartTime();
290 int r = t1 - t2;
291 if (r == 0)
292 r = ti->priority - priority;
293 if (IsPatternTimer() ^ ti->IsPatternTimer()) {
294 if (IsPatternTimer())
295 r = 1;
296 else
297 r = -1;
298 }
299 else if (IsPatternTimer() && ti->IsPatternTimer())
300 r = strcoll(Pattern(), ti->Pattern());
301 return r;
302}
303
305{
306 if (IsPatternTimer())
307 return cString::sprintf("{%s}%s", pattern, file);
308 return file;
309}
310
311cString cTimer::ToText(bool UseChannelID) const
312{
313 strreplace(pattern, ':', '|');
314 strreplace(file, ':', '|');
315 cString buffer = cString::sprintf("%u:%s:%s:%04d:%04d:%d:%d:%s:%s", flags, UseChannelID ? *Channel()->GetChannelID().ToString() : *itoa(Channel()->Number()), *PrintDay(day, weekdays, true), start, stop, priority, lifetime, *PatternAndFile(), aux ? aux : "");
316 strreplace(pattern, '|', ':');
317 strreplace(file, '|', ':');
318 return buffer;
319}
320
322{
323 return cString::sprintf("%d%s%s (%d %04d-%04d %s'%s')", Id(), remote ? "@" : "", remote ? remote : "", Channel()->Number(), start, stop, HasFlags(tfVps) ? "VPS " : "", *PatternAndFile());
324}
325
327{
328 return (t / 100 * 60 + t % 100) * 60;
329}
330
331bool cTimer::ParseDay(const char *s, time_t &Day, int &WeekDays)
332{
333 // possible formats are:
334 // 19
335 // 2005-03-19
336 // MTWTFSS
337 // MTWTFSS@19
338 // MTWTFSS@2005-03-19
339
340 Day = 0;
341 WeekDays = 0;
342 s = skipspace(s);
343 if (!*s)
344 return false;
345 const char *a = strchr(s, '@');
346 const char *d = a ? a + 1 : isdigit(*s) ? s : NULL;
347 if (d) {
348 if (strlen(d) == 10) {
349 struct tm tm_r;
350 if (3 == sscanf(d, "%d-%d-%d", &tm_r.tm_year, &tm_r.tm_mon, &tm_r.tm_mday)) {
351 tm_r.tm_year -= 1900;
352 tm_r.tm_mon--;
353 tm_r.tm_hour = tm_r.tm_min = tm_r.tm_sec = 0;
354 tm_r.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
355 Day = mktime(&tm_r);
356 }
357 else
358 return false;
359 }
360 else {
361 // handle "day of month" for compatibility with older versions:
362 char *tail = NULL;
363 int day = strtol(d, &tail, 10);
364 if (tail && *tail || day < 1 || day > 31)
365 return false;
366 time_t t = time(NULL);
367 int DaysToCheck = 61; // 61 to handle months with 31/30/31
368 for (int i = -1; i <= DaysToCheck; i++) {
369 time_t t0 = IncDay(t, i);
370 if (GetMDay(t0) == day) {
371 Day = SetTime(t0, 0);
372 break;
373 }
374 }
375 }
376 }
377 if (a || !isdigit(*s)) {
378 if ((a && a - s == 7) || strlen(s) == 7) {
379 for (const char *p = s + 6; p >= s; p--) {
380 WeekDays <<= 1;
381 WeekDays |= (*p != '-');
382 }
383 }
384 else
385 return false;
386 }
387 return true;
388}
389
390cString cTimer::PrintDay(time_t Day, int WeekDays, bool SingleByteChars)
391{
392#define DAYBUFFERSIZE 64
393 char buffer[DAYBUFFERSIZE];
394 char *b = buffer;
395 if (WeekDays) {
396 // TRANSLATORS: the first character of each weekday, beginning with monday
397 const char *w = trNOOP("MTWTFSS");
398 if (!SingleByteChars)
399 w = tr(w);
400 while (*w) {
401 int sl = Utf8CharLen(w);
402 if (WeekDays & 1) {
403 for (int i = 0; i < sl; i++)
404 b[i] = w[i];
405 b += sl;
406 }
407 else
408 *b++ = '-';
409 WeekDays >>= 1;
410 w += sl;
411 }
412 if (Day)
413 *b++ = '@';
414 }
415 if (Day) {
416 struct tm tm_r;
417 localtime_r(&Day, &tm_r);
418 b += strftime(b, DAYBUFFERSIZE - (b - buffer), "%Y-%m-%d", &tm_r);
419 }
420 *b = 0;
421 return buffer;
422}
423
425{
426 if (weekdays) {
427 cString s = PrintDay(day, weekdays, true);
428 if (strlen(s) == 18)
429 return *s + 8;
430 }
431 return ""; // not NULL, so the caller can always use the result
432}
433
434bool cTimer::Parse(const char *s)
435{
436 char *channelbuffer = NULL;
437 char *daybuffer = NULL;
438 char *filebuffer = NULL;
439 free(aux);
440 aux = NULL;
441 //XXX Apparently sscanf() doesn't work correctly if the last %m argument
442 //XXX results in an empty string (this first occurred when the EIT gathering
443 //XXX was put into a separate thread - don't know why this happens...
444 //XXX As a cure we copy the original string and add a blank.
445 //XXX If anybody can shed some light on why sscanf() fails here, I'd love
446 //XXX to hear about that!
447 char *s2 = NULL;
448 int l2 = strlen(s);
449 while (l2 > 0 && isspace(s[l2 - 1]))
450 l2--;
451 if (s[l2 - 1] == ':') {
452 s2 = MALLOC(char, l2 + 3);
453 strcat(strn0cpy(s2, s, l2 + 1), " \n");
454 s = s2;
455 }
456 bool result = false;
457 if (8 <= sscanf(s, "%u :%m[^:]:%m[^:]:%d :%d :%d :%d :%m[^:\n]:%m[^\n]", &flags, &channelbuffer, &daybuffer, &start, &stop, &priority, &lifetime, &filebuffer, &aux)) {
458 if (aux && !*skipspace(aux)) {
459 free(aux);
460 aux = NULL;
461 }
462 //TODO add more plausibility checks
463 result = ParseDay(daybuffer, day, weekdays);
464 char *fb = filebuffer;
465 if (*fb == '{') {
466 if (char *p = strchr(fb, '}')) {
467 *p = 0;
468 Utf8Strn0Cpy(pattern, fb + 1, sizeof(pattern));
469 strreplace(pattern, '|', ':');
470 fb = p + 1;
471 }
472 }
473 else
474 *pattern = 0;
475 Utf8Strn0Cpy(file, fb, sizeof(file));
476 strreplace(file, '|', ':');
478 if (isnumber(channelbuffer))
479 channel = Channels->GetByNumber(atoi(channelbuffer));
480 else
481 channel = Channels->GetByChannelID(tChannelID::FromString(channelbuffer), true, true);
482 if (!channel) {
483 esyslog("ERROR: channel %s not defined", channelbuffer);
484 result = false;
485 }
486 }
487 free(channelbuffer);
488 free(daybuffer);
489 free(filebuffer);
490 free(s2);
491 return result;
492}
493
494bool cTimer::Save(FILE *f)
495{
496 if (!Remote())
497 return fprintf(f, "%s\n", *ToText(true)) > 0;
498 return true;
499}
500
501bool cTimer::IsSingleEvent(void) const
502{
503 return !weekdays;
504}
505
506int cTimer::GetMDay(time_t t)
507{
508 struct tm tm_r;
509 return localtime_r(&t, &tm_r)->tm_mday;
510}
511
512int cTimer::GetWDay(time_t t)
513{
514 struct tm tm_r;
515 int weekday = localtime_r(&t, &tm_r)->tm_wday;
516 return weekday == 0 ? 6 : weekday - 1; // we start with Monday==0!
517}
518
519bool cTimer::DayMatches(time_t t) const
520{
521 return IsSingleEvent() ? SetTime(t, 0) == day : (weekdays & (1 << GetWDay(t))) != 0;
522}
523
524time_t cTimer::IncDay(time_t t, int Days)
525{
526 struct tm tm_r;
527 tm tm = *localtime_r(&t, &tm_r);
528 tm.tm_mday += Days; // now tm_mday may be out of its valid range
529 int h = tm.tm_hour; // save original hour to compensate for DST change
530 tm.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
531 t = mktime(&tm); // normalize all values
532 tm.tm_hour = h; // compensate for DST change
533 return mktime(&tm); // calculate final result
534}
535
536time_t cTimer::SetTime(time_t t, int SecondsFromMidnight)
537{
538 struct tm tm_r;
539 tm tm = *localtime_r(&t, &tm_r);
540 tm.tm_hour = SecondsFromMidnight / 3600;
541 tm.tm_min = (SecondsFromMidnight % 3600) / 60;
542 tm.tm_sec = SecondsFromMidnight % 60;
543 tm.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
544 return mktime(&tm);
545}
546
547void cTimer::SetPattern(const char *Pattern)
548{
550}
551
552void cTimer::SetFile(const char *File)
553{
554 if (!isempty(File))
555 Utf8Strn0Cpy(file, File, sizeof(file));
556}
557
558#define EITPRESENTFOLLOWINGRATE 10 // max. seconds between two occurrences of the "EIT present/following table for the actual multiplex" (2s by the standard, using some more for safety)
559
560bool cTimer::Matches(time_t t, bool Directly, int Margin) const
561{
562 startTime = stopTime = 0;
563 if (t == 0)
564 t = time(NULL);
565
566 int begin = TimeToInt(start); // seconds from midnight
567 int end = TimeToInt(stop);
568 int length = end - begin;
569
570 if (IsSingleEvent()) {
571 time_t t0 = day;
572 startTime = SetTime(t0, begin);
573 if (length < 0)
574 t0 = IncDay(day, 1);
575 stopTime = SetTime(t0, end);
576 }
577 else {
578 time_t d = day ? max(day, t) : t;
579 for (int i = -1; i <= 7; i++) {
580 time_t t0 = IncDay(d, i);
581 if (DayMatches(t0)) {
582 time_t a = SetTime(t0, begin);
583 if (length < 0)
584 t0 = IncDay(d, i + 1);
585 time_t b = SetTime(t0, end);
586 if ((!day || a >= day) && t < b) {
587 startTime = a;
588 stopTime = b;
589 break;
590 }
591 }
592 }
593 if (!startTime)
594 startTime = IncDay(t, 7); // just to have something that's more than a week in the future
595 else if (!Directly && (t > startTime || t > day + SECSINDAY + 3600)) // +3600 in case of DST change
596 day = 0;
597 }
598
599 if (IsPatternTimer())
600 return false; // we only need to have start/stopTime initialized
601
602 if (t < deferred)
603 return false;
604 deferred = 0;
605
606 if (HasFlags(tfActive)) {
607 if (event) {
608 if (HasFlags(tfVps)) {
609 if (event->Vps()) {
610 if (Margin || !Directly) {
611 startTime = event->StartTime();
612 stopTime = event->EndTime();
613 if (!Margin) { // this is an actual check
614 if (event->Schedule()->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) // VPS control can only work with up-to-date events...
615 return event->IsRunning(true);
616 // ...otherwise we fall back to normal timer handling below (note: Margin == 0!)
617 }
618 }
619 }
620 }
621 else if (HasFlags(tfSpawned)) {
622 if (!Margin && !Directly) { // this is an actual check
623 // The spawned timer's start-/stopTimes are adjusted to the event's times in AdjustSpawnedTimer().
624 // However, in order to make sure the timer is set to the correct event, the margins at begin
625 // end end are limited by the durations of the events before and after this timer's event.
626 // The recording, though, shall always use the full start/stop margins, hence this calculation:
627 return event->StartTime() - Setup.MarginStart * 60 <= t && t < event->EndTime() + Setup.MarginStop * 60;
628 }
629 }
630 }
631 return startTime <= t + Margin && t < stopTime; // must stop *before* stopTime to allow adjacent timers
632 }
633 return false;
634}
635
636#define FULLMATCH 1000
637
638eTimerMatch cTimer::Matches(const cEvent *Event, int *Overlap) const
639{
640 // Overlap is the percentage of the Event's duration that is covered by
641 // this timer (based on FULLMATCH for finer granularity than just 100).
642 // To make sure a VPS timer can be distinguished from a plain 100% overlap,
643 // it gets an additional 100 added, and a VPS event that is actually running
644 // gets 200 added to the FULLMATCH.
645 if (channel->GetChannelID() == Event->ChannelID()) {
646 bool UseVps = HasFlags(tfVps) && Event->Vps();
647 if (IsPatternTimer()) {
650 if (*FileName) {
651 const char *p = strgetlast(*FileName, FOLDERDELIMCHAR);
653 return tmNone;
654 }
655 else
656 return tmNone;
657 }
658 else if (!MatchPattern(Pattern(), Event->Title()))
659 return tmNone;
660 UseVps = false;
661 }
662 Matches(UseVps ? Event->Vps() : Event->StartTime(), true);
663 int overlap = 0;
664 if (UseVps) {
665 if (startTime == Event->Vps()) {
666 overlap = FULLMATCH;
667 if (Event->IsRunning())
668 overlap += 200;
670 overlap += 100;
671 }
672 }
673 else {
674 if (startTime <= Event->StartTime() && Event->EndTime() <= stopTime)
675 overlap = FULLMATCH;
676 else if (stopTime <= Event->StartTime() || Event->EndTime() <= startTime)
677 overlap = 0;
678 else {
679 overlap = (min(stopTime, Event->EndTime()) - max(startTime, Event->StartTime())) * FULLMATCH / max(Event->Duration(), 1);
680 if (IsPatternTimer() && overlap > 0)
681 overlap = FULLMATCH;
682 }
683 }
684 startTime = stopTime = 0;
685 if (Overlap)
686 *Overlap = overlap;
687 return overlap >= FULLMATCH ? tmFull : overlap > 0 ? tmPartial : tmNone;
688 }
689 return tmNone;
690}
691
692#define EXPIRELATENCY 60 // seconds (just in case there's a short glitch in the VPS signal)
693
694bool cTimer::Expired(void) const
695{
696 if (IsSingleEvent() && !Recording()) {
697 time_t ExpireTime = StopTimeEvent();
698 if (HasFlags(tfVps))
699 ExpireTime += EXPIRELATENCY;
700 return ExpireTime <= time(NULL);
701 }
702 return false;
703}
704
705time_t cTimer::StartTime(void) const
706{
707 if (!startTime)
708 Matches();
709 return startTime;
710}
711
712time_t cTimer::StopTime(void) const
713{
714 if (!stopTime)
715 Matches();
716 return stopTime;
717}
718
719time_t cTimer::StartTimeEvent(void) const
720{
721 if (event) {
722 if (HasFlags(tfVps) && event->Vps())
723 return event->StartTime();
724 else if (HasFlags(tfSpawned))
725 return event->StartTime() - Setup.MarginStart * 60;
726 }
727 return StartTime();
728}
729
730time_t cTimer::StopTimeEvent(void) const
731{
732 if (event) {
733 if (HasFlags(tfVps) && event->Vps())
734 return event->EndTime();
735 else if (HasFlags(tfSpawned))
736 return event->EndTime() + Setup.MarginStop * 60;
737 }
738 return StopTime();
739}
740
741#define EPGLIMITBEFORE (1 * 3600) // Time in seconds before a timer's start time and
742#define EPGLIMITAFTER (1 * 3600) // after its stop time within which EPG events will be taken into consideration.
743
744void cTimer::SetId(int Id)
745{
746 id = Id;
747}
748
750{
752 isyslog("spawning timer %s for event %s", *ToDescr(), *Event->ToDescr());
753 cTimer *t = new cTimer(Event, FileName, this);
756 t->SetFlags(tfAvoid);
757 Timers->Add(t);
759 return t;
760}
761
762bool cTimer::SpawnPatternTimers(const cSchedules *Schedules, cTimers *Timers)
763{
764 bool TimersSpawned = false;
765 const cSchedule *Schedule = Schedules->GetSchedule(Channel());
766 if (Schedule && Schedule->Events()->First()) {
767 if (Schedule->Modified(scheduleStateSpawn)) {
768 time_t Now = time(NULL);
769 // Find the first event that matches this pattern timer and either already has a spawned
770 // timer, or has not yet ended:
771 for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
772 if (Matches(e) != tmNone) {
773 const cTimer *Timer = Timers->GetTimerForEvent(e, tfSpawned); // a matching event that already has a spawned timer
774 if (!Timer && e->EndTime() > Now) { // only look at events that have not yet ended
775 Timer = SpawnPatternTimer(e, Timers);
776 TimersSpawned = true;
777 }
778 if (Timer) {
779 // Check all following matching events that would start while the first timer
780 // is still recording:
781 bool UseVps = Timer->HasFlags(tfVps);
782 time_t Limit = Timer->StopTimeEvent();
783 if (UseVps)
784 Limit += EXPIRELATENCY;
785 else
786 Limit += Setup.MarginStart * 60;
787 for (e = Schedule->Events()->Next(e); e; e = Schedule->Events()->Next(e)) {
788 if (e->StartTime() <= Limit) {
789 if (!Timers->GetTimerForEvent(e, tfSpawned) && Matches(e) != tmNone) {
790 SpawnPatternTimer(e, Timers);
791 TimersSpawned = true;
792 }
793 if (UseVps)
794 break; // with VPS we only need to check the event immediately following the first one
795 }
796 else
797 break; // no need to check events that are too far in the future
798 }
799 break;
800 }
801 }
802 }
803 }
804 }
805 return TimersSpawned;
806}
807
809{
810 if (Event()) {
811 if (const cSchedule *Schedule = Event()->Schedule()) { // events may be deleted from their schedule in cSchedule::DropOutdated()!
812 if (Schedule->Modified(scheduleStateAdjust)) {
813 // Adjust the timer to shifted start/stop times of the event if necessary:
814 time_t tstart = Event()->StartTime();
815 time_t tstop = Event()->EndTime();
816 int MarginStart = 0;
817 int MarginStop = 0;
818 CalcMargins(MarginStart, MarginStop, Event());
819 tstart -= MarginStart;
820 tstop += MarginStop;
821 // Event start/end times are given in "seconds since the epoch". Some broadcasters use values
822 // that result in full minutes (with zero seconds), while others use any values. VDR's timers
823 // use times given in full minutes, truncating any seconds. Thus we only react if the start/stop
824 // times of the timer are off by at least one minute:
825 if (abs(StartTime() - tstart) >= 60 || abs(StopTime() - tstop) >= 60) {
826 cString OldDescr = ToDescr();
827 struct tm tm_r;
828 struct tm *time = localtime_r(&tstart, &tm_r);
829 SetDay(cTimer::SetTime(tstart, 0));
830 SetStart(time->tm_hour * 100 + time->tm_min);
831 time = localtime_r(&tstop, &tm_r);
832 SetStop(time->tm_hour * 100 + time->tm_min);
833 Matches();
834 isyslog("timer %s times changed to %s-%s", *OldDescr, *TimeString(tstart), *TimeString(tstop));
835 return true;
836 }
837 }
838 }
839 }
840 return false;
841}
842
844{
845 if (Local() && HasFlags(tfSpawned) || IsPatternTimer()) {
846 if (Channel()) {
848 if (const cSchedule *Schedule = Channel()->Schedule()) {
849 dsyslog("triggering respawn for timer %s", *ToDescr());
851 const_cast<cSchedule *>(Schedule)->SetModified();
852 }
853 }
854 }
855}
856
858{
859 if (IsPatternTimer())
860 return SetEvent(NULL);
861 const cSchedule *Schedule = Schedules->GetSchedule(Channel());
862 if (Schedule && Schedule->Events()->First()) {
863 if (Schedule->Modified(scheduleStateSet)) {
864 const cEvent *Event = NULL;
865 if (HasFlags(tfVps) && Schedule->Events()->First()->Vps()) {
866 // VPS timers only match if their start time exactly matches the event's VPS time:
867 for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
868 if (e->StartTime()) {
869 int overlap = 0;
870 if (Matches(e, &overlap) == tmFull) {
871 Event = e;
872 if (overlap > FULLMATCH)
873 break; // take the first matching event
874 }
875 }
876 }
877 }
878 else {
879 // Normal timers match the event they have the most overlap with:
880 int Overlap = 0;
881 // Set up the time frame within which to check events:
882 Matches(0, true);
883 time_t TimeFrameBegin = StartTime() - EPGLIMITBEFORE;
884 time_t TimeFrameEnd = StopTime() + EPGLIMITAFTER;
885 for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
886 if (e->EndTime() < TimeFrameBegin)
887 continue; // skip events way before the timer starts
888 if (e->StartTime() > TimeFrameEnd)
889 break; // the rest is way after the timer ends
890 int overlap = 0;
891 Matches(e, &overlap);
892 if (overlap && overlap >= Overlap) {
893 if (Event && overlap == Overlap && e->Duration() <= Event->Duration())
894 continue; // if overlap is the same, we take the longer event
895 Overlap = overlap;
896 Event = e;
897 }
898 }
899 }
900 return SetEvent(Event);
901 }
902 }
903 return false;
904}
905
906bool cTimer::SetEvent(const cEvent *Event)
907{
908 if (event != Event) {
909 if (event)
910 event->DecNumTimers();
911 if (Event) {
912 isyslog("timer %s set to event %s", *ToDescr(), *Event->ToDescr());
914 Event->Schedule()->Modified(scheduleStateSet); // to get the current state
915 }
916 else {
917 isyslog("timer %s set to no event", *ToDescr());
919 }
920 event = Event;
921 return true;
922 }
923 return false;
924}
925
926void cTimer::SetRecording(bool Recording)
927{
928 if (Recording)
930 else
932 isyslog("timer %s %s", *ToDescr(), Recording ? "start" : "stop");
933}
934
935void cTimer::SetPending(bool Pending)
936{
938}
939
940void cTimer::SetInVpsMargin(bool InVpsMargin)
941{
942 if (InVpsMargin && !inVpsMargin)
943 isyslog("timer %s entered VPS margin", *ToDescr());
945}
946
947void cTimer::SetDay(time_t Day)
948{
949 day = Day;
950}
951
952void cTimer::SetWeekDays(int WeekDays)
953{
955}
956
957void cTimer::SetStart(int Start)
958{
959 start = Start;
960}
961
962void cTimer::SetStop(int Stop)
963{
964 stop = Stop;
965}
966
967void cTimer::SetPriority(int Priority)
968{
970}
971
972void cTimer::SetLifetime(int Lifetime)
973{
975}
976
977void cTimer::SetAux(const char *Aux)
978{
979 free(aux);
980 aux = Aux ? strdup(Aux) : NULL;
981}
982
983void cTimer::SetRemote(const char *Remote)
984{
985 free(remote);
986 remote = Remote ? strdup(Remote) : NULL;
987}
988
989void cTimer::SetDeferred(int Seconds)
990{
991 deferred = time(NULL) + Seconds;
992 isyslog("timer %s deferred for %d seconds", *ToDescr(), Seconds);
993}
994
995void cTimer::SetFlags(uint Flags)
996{
997 flags |= Flags;
998}
999
1000void cTimer::ClrFlags(uint Flags)
1001{
1002 flags &= ~Flags;
1003}
1004
1005void cTimer::InvFlags(uint Flags)
1006{
1007 flags ^= Flags;
1008}
1009
1010bool cTimer::HasFlags(uint Flags) const
1011{
1012 return (flags & Flags) == Flags;
1013}
1014
1016{
1017 day = IncDay(SetTime(StartTime(), 0), 1);
1018 startTime = 0;
1019 SetEvent(NULL);
1020}
1021
1023{
1024 if (IsSingleEvent() || IsPatternTimer())
1026 else if (day) {
1027 day = 0;
1029 }
1030 else if (HasFlags(tfActive))
1031 Skip();
1032 else
1034 SetEvent(NULL);
1035 if (HasFlags(tfActive))
1036 TriggerRespawn(); // have pattern timers spawn if necessary
1037 Matches(); // refresh start and end time
1038}
1039
1040// --- cTimers ---------------------------------------------------------------
1041
1043int cTimers::lastTimerId = 0;
1044
1046:cConfig<cTimer>("1 Timers")
1047{
1049}
1050
1051bool cTimers::Load(const char *FileName)
1052{
1054 Timers->SetExplicitModify();
1055 if (timers.cConfig<cTimer>::Load(FileName)) {
1056 for (cTimer *ti = timers.First(); ti; ti = timers.Next(ti)) {
1057 ti->SetId(NewTimerId());
1058 ti->ClrFlags(tfRecording);
1059 Timers->SetModified();
1060 }
1061 return true;
1062 }
1063 return false;
1064}
1065
1067{
1068 return ++lastTimerId; // no need for locking, the caller must have a lock on the global Timers list
1069}
1070
1071const cTimer *cTimers::GetById(int Id, const char *Remote) const
1072{
1073 for (const cTimer *ti = First(); ti; ti = Next(ti)) {
1074 if (ti->Id() == Id) {
1075 if (!Remote && !ti->Remote() || Remote && ti->Remote() && strcmp(Remote, ti->Remote()) == 0)
1076 return ti;
1077 }
1078 }
1079 return NULL;
1080}
1081
1082const cTimer *cTimers::GetTimer(const cTimer *Timer) const
1083{
1084 for (const cTimer *ti = First(); ti; ti = Next(ti)) {
1085 if (!ti->Remote() &&
1086 ti->Channel() == Timer->Channel() &&
1087 (ti->WeekDays() && ti->WeekDays() == Timer->WeekDays() || !ti->WeekDays() && ti->Day() == Timer->Day()) &&
1088 ti->Start() == Timer->Start() &&
1089 ti->Stop() == Timer->Stop())
1090 return ti;
1091 }
1092 return NULL;
1093}
1094
1095const cTimer *cTimers::GetMatch(time_t t) const
1096{
1097 static int LastPending = -1;
1098 const cTimer *t0 = NULL;
1099 for (const cTimer *ti = First(); ti; ti = Next(ti)) {
1100 if (!ti->Remote() && !ti->Recording() && ti->Matches(t)) {
1101 if (ti->Pending()) {
1102 if (ti->Index() > LastPending) {
1103 LastPending = ti->Index();
1104 return ti;
1105 }
1106 else
1107 continue;
1108 }
1109 if (!t0 || ti->Priority() > t0->Priority())
1110 t0 = ti;
1111 }
1112 }
1113 if (!t0)
1114 LastPending = -1;
1115 return t0;
1116}
1117
1118const cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match) const
1119{
1120 const cTimer *t = NULL;
1121 eTimerMatch m = tmNone;
1122 for (const cTimer *ti = First(); ti; ti = Next(ti)) {
1123 eTimerMatch tm = ti->Matches(Event);
1124 if (tm > m || tm == tmFull && t && (t->Remote() && ti->Local() || t->IsPatternTimer() && ti->HasFlags(tfSpawned))) {
1125 t = ti;
1126 m = tm;
1127 }
1128 }
1129 if (Match)
1130 *Match = m;
1131 return t;
1132}
1133
1134const cTimer *cTimers::GetTimerForEvent(const cEvent *Event, eTimerFlags Flags) const
1135{
1136 if (Event && Event->HasTimer()) {
1137 for (const cTimer *ti = First(); ti; ti = Next(ti)) {
1138 if (ti->Event() == Event && ti->Local() && ti->HasFlags(Flags))
1139 return ti;
1140 }
1141 }
1142 return NULL;
1143}
1144
1146{
1147 int n = -1;
1148 for (const cTimer *ti = First(); ti; ti = Next(ti)) {
1149 if (!ti->Remote() && ti->Recording())
1150 n = max(n, ti->Priority());
1151 }
1152 return n;
1153}
1154
1156{
1157 const cTimer *t0 = NULL;
1158 for (const cTimer *ti = First(); ti; ti = Next(ti)) {
1159 if (!ti->Remote() && !ti->IsPatternTimer()) {
1160 ti->Matches();
1161 if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
1162 t0 = ti;
1163 }
1164 }
1165 return t0;
1166}
1167
1168const cTimers *cTimers::GetTimersRead(cStateKey &StateKey, int TimeoutMs)
1169{
1170 return timers.Lock(StateKey, false, TimeoutMs) ? &timers : NULL;
1171}
1172
1174{
1175 return timers.Lock(StateKey, true, TimeoutMs) ? &timers : NULL;
1176}
1177
1178void cTimers::Add(cTimer *Timer, cTimer *After)
1179{
1180 if (!Timer->Remote())
1181 Timer->SetId(NewTimerId());
1182 cConfig<cTimer>::Add(Timer, After);
1184}
1185
1186void cTimers::Ins(cTimer *Timer, cTimer *Before)
1187{
1188 cConfig<cTimer>::Ins(Timer, Before);
1190}
1191
1192void cTimers::Del(cTimer *Timer, bool DeleteObject)
1193{
1195 cConfig<cTimer>::Del(Timer, DeleteObject);
1196}
1197
1198const cTimer *cTimers::UsesChannel(const cChannel *Channel) const
1199{
1200 for (const cTimer *Timer = First(); Timer; Timer = Next(Timer)) {
1201 if (Timer->Channel() == Channel)
1202 return Timer;
1203 }
1204 return NULL;
1205}
1206
1207bool cTimers::SetEvents(const cSchedules *Schedules)
1208{
1209 bool TimersModified = false;
1210 for (cTimer *ti = First(); ti; ti = Next(ti)) {
1211 if (!ti->IsPatternTimer())
1212 TimersModified |= ti->SetEventFromSchedule(Schedules);
1213 }
1214 return TimersModified;
1215}
1216
1218{
1219 bool TimersModified = false;
1220 for (cTimer *ti = First(); ti; ti = Next(ti)) {
1221 if (ti->IsPatternTimer() && ti->Local()) {
1222 if (ti->HasFlags(tfActive))
1223 TimersModified |= ti->SpawnPatternTimers(Schedules, this);
1224 }
1225 }
1226 return TimersModified;
1227}
1228
1230{
1231 bool TimersModified = false;
1232 for (cTimer *ti = First(); ti; ti = Next(ti)) {
1233 if (ti->Local()) {
1234 if (ti->HasFlags(tfSpawned) && !ti->HasFlags(tfVps))
1235 TimersModified |= ti->AdjustSpawnedTimer();
1236 }
1237 }
1238 return TimersModified;
1239}
1240
1241#define DELETE_EXPIRED_TIMEOUT 30 // seconds
1242
1244{
1245 if (!Force && time(NULL) - lastDeleteExpired < DELETE_EXPIRED_TIMEOUT)
1246 return false;
1247 bool TimersModified = false;
1248 cTimer *ti = First();
1249 while (ti) {
1250 cTimer *next = Next(ti);
1251 if (!ti->Remote() && ti->Expired()) {
1252 ti->SetEvent(NULL); // Del() doesn't call ~cTimer() right away, so this is necessary here
1253 ti->TriggerRespawn(); // in case this is a spawned timer
1254 isyslog("deleting timer %s", *ti->ToDescr());
1255 Del(ti);
1256 TimersModified = true;
1257 }
1258 ti = next;
1259 }
1260 lastDeleteExpired = time(NULL);
1261 return TimersModified;
1262}
1263
1264bool cTimers::StoreRemoteTimers(const char *ServerName, const cStringList *RemoteTimers)
1265{
1266 bool Result = false;
1267 if (!ServerName || !RemoteTimers || RemoteTimers->Size() == 0) {
1268 // Remove remote timers from this list:
1269 cTimer *Timer = First();
1270 while (Timer) {
1271 cTimer *t = Next(Timer);
1272 if (Timer->Remote() && (!ServerName || strcmp(Timer->Remote(), ServerName) == 0)) {
1273 Del(Timer);
1274 Result = true;
1275 }
1276 Timer = t;
1277 }
1278 return Result;
1279 }
1280 // Collect all locally stored remote timers from ServerName:
1281 cStringList tl;
1282 for (cTimer *ti = First(); ti; ti = Next(ti)) {
1283 if (ti->Remote() && strcmp(ti->Remote(), ServerName) == 0)
1284 tl.Append(strdup(cString::sprintf("%d %s", ti->Id(), *ti->ToText(true))));
1285 }
1286 tl.SortNumerically(); // RemoteTimers is also sorted numerically!
1287 // Compare the two lists and react accordingly:
1288 int il = 0; // index into the local ("left") list of remote timers
1289 int ir = 0; // index into the remote ("right") list of timers
1290 int sl = tl.Size();
1291 int sr = RemoteTimers->Size();
1292 for (;;) {
1293 int AddTimer = 0;
1294 int DelTimer = 0;
1295 if (il < sl) { // still have left entries
1296 int nl = atoi(tl[il]);
1297 if (ir < sr) { // still have right entries
1298 // Compare timers:
1299 int nr = atoi((*RemoteTimers)[ir]);
1300 if (nl == nr) // same timer id
1301 AddTimer = DelTimer = nl;
1302 else if (nl < nr) // left entry not in right list
1303 DelTimer = nl;
1304 else // right entry not in left list
1305 AddTimer = nr;
1306 }
1307 else // processed all right entries
1308 DelTimer = nl;
1309 }
1310 else if (ir < sr) { // still have right entries
1311 AddTimer = atoi((*RemoteTimers)[ir]);
1312 if (!AddTimer) {
1313 esyslog("ERROR: %s: error in timer settings: %s", ServerName, (*RemoteTimers)[ir]);
1314 ir++;
1315 continue; // let's see if we can process the rest
1316 }
1317 }
1318 else // processed all left and right entries
1319 break;
1320 if (AddTimer && DelTimer) {
1321 if (strcmp(tl[il], (*RemoteTimers)[ir]) != 0) {
1322 // Overwrite timer:
1323 char *v = (*RemoteTimers)[ir];
1324 while (*v && *v != ' ')
1325 v++; // skip id
1326 if (cTimer *l = GetById(DelTimer, ServerName)) {
1327 cTimer r;
1328 if (r.Parse(v)) {
1329 r.SetRemote(ServerName);
1330 r.SetId(AddTimer);
1331 *l = r;
1332 Result = true;
1333 }
1334 else
1335 esyslog("ERROR: %d@%s: error in timer settings: %s", DelTimer, ServerName, v);
1336 }
1337 }
1338 else // identical timer, nothing to do
1339 ;
1340 il++;
1341 ir++;
1342 }
1343 else if (AddTimer) {
1344 char *v = (*RemoteTimers)[ir];
1345 while (*v && *v != ' ')
1346 v++; // skip id
1347 cTimer *Timer = new cTimer;
1348 if (Timer->Parse(v)) {
1349 Timer->SetRemote(ServerName);
1350 Timer->SetId(AddTimer);
1351 Add(Timer);
1352 Result = true;
1353 }
1354 else {
1355 esyslog("ERROR: %s: error in timer settings: %s", ServerName, v);
1356 delete Timer;
1357 }
1358 ir++;
1359 }
1360 else if (DelTimer) {
1361 if (cTimer *t = GetById(DelTimer, ServerName)) {
1362 Del(t);
1363 Result = true;
1364 }
1365 il++;
1366 }
1367 else {
1368 esyslog("ERROR: oops while storing remote timers!");
1369 break; // let's not get stuck here!
1370 }
1371 }
1372 return Result;
1373}
1374
1375static bool RemoteTimerError(const cTimer *Timer, cString *Msg)
1376{
1377 if (Msg)
1378 *Msg = cString::sprintf("%s %d@%s!", tr("Error while accessing remote timer"), Timer->Id(), Timer->Remote());
1379 return false; // convenience return code
1380}
1381
1382bool HandleRemoteTimerModifications(cTimer *NewTimer, cTimer *OldTimer, cString *Msg)
1383{
1384 cStringList Response;
1385 if (!NewTimer) {
1386 if (OldTimer) { // timer shall be deleted from remote machine
1387 if (OldTimer->Remote() && OldTimer->Id()) {
1388 if (!ExecSVDRPCommand(OldTimer->Remote(), cString::sprintf("DELT %d", OldTimer->Id()), &Response) || SVDRPCode(Response[0]) != 250)
1389 return RemoteTimerError(OldTimer, Msg);
1390 }
1391 isyslog("deleted timer %s", *OldTimer->ToDescr());
1392 }
1393 }
1394 else if (!OldTimer || OldTimer->Local() || !OldTimer->Id()) {
1395 if (NewTimer->Local()) { // timer stays local, nothing to do
1396 if (OldTimer && OldTimer->Id())
1397 isyslog("modified timer %s", *NewTimer->ToDescr());
1398 else
1399 isyslog("added timer %s", *NewTimer->ToDescr());
1400 }
1401 else { // timer is new, or moved from local to remote
1402 if (!ExecSVDRPCommand(NewTimer->Remote(), cString::sprintf("NEWT %s", *NewTimer->ToText(true)), &Response) || SVDRPCode(Response[0]) != 250)
1403 return RemoteTimerError(NewTimer, Msg);
1404 int RemoteId = atoi(SVDRPValue(Response[0]));
1405 if (RemoteId <= 0)
1406 return RemoteTimerError(NewTimer, Msg);
1407 NewTimer->SetId(RemoteId);
1408 if (OldTimer && OldTimer->Id()) {
1409 isyslog("moved timer %d to %s", OldTimer->Id(), *NewTimer->ToDescr());
1410 }
1411 else
1412 isyslog("added timer %s", *NewTimer->ToDescr());
1413 }
1414 }
1415 else if (NewTimer->Local()) { // timer is moved from remote to local
1416 if (!ExecSVDRPCommand(OldTimer->Remote(), cString::sprintf("DELT %d", OldTimer->Id()), &Response) || SVDRPCode(Response[0]) != 250)
1417 return RemoteTimerError(OldTimer, Msg);
1418 NewTimer->SetId(cTimers::NewTimerId());
1419 NewTimer->ClrFlags(tfRecording); // in case it was recording on the remote machine
1420 isyslog("moved timer %d@%s to %s", OldTimer->Id(), OldTimer->Remote(), *NewTimer->ToDescr());
1421 }
1422 else if (strcmp(OldTimer->Remote(), NewTimer->Remote()) == 0) { // timer stays remote on same machine
1423 if (!ExecSVDRPCommand(OldTimer->Remote(), cString::sprintf("MODT %d %s", OldTimer->Id(), *NewTimer->ToText(true)), &Response) || SVDRPCode(Response[0]) != 250)
1424 return RemoteTimerError(NewTimer, Msg);
1425 isyslog("modified timer %s", *NewTimer->ToDescr());
1426 }
1427 else { // timer is moved from one remote machine to an other
1428 if (!ExecSVDRPCommand(NewTimer->Remote(), cString::sprintf("NEWT %s", *NewTimer->ToText(true)), &Response) || SVDRPCode(Response[0]) != 250)
1429 return RemoteTimerError(NewTimer, Msg);
1430 int RemoteId = atoi(SVDRPValue(Response[0]));
1431 if (RemoteId <= 0)
1432 return RemoteTimerError(NewTimer, Msg);
1433 NewTimer->SetId(RemoteId);
1434 if (!ExecSVDRPCommand(OldTimer->Remote(), cString::sprintf("DELT %d", OldTimer->Id()), &Response) || SVDRPCode(Response[0]) != 250)
1435 return RemoteTimerError(OldTimer, Msg);
1436 isyslog("moved timer %d@%s to %s", OldTimer->Id(), OldTimer->Remote(), *NewTimer->ToDescr());
1437 }
1438 return true;
1439}
1440
1441// --- cSortedTimers ---------------------------------------------------------
1442
1443static int CompareTimers(const void *a, const void *b)
1444{
1445 return (*(const cTimer **)a)->Compare(**(const cTimer **)b);
1446}
1447
1449:cVector<const cTimer *>(Timers->Count())
1450{
1451 for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer))
1452 Append(Timer);
1454}
#define LOCK_CHANNELS_READ
Definition channels.h:269
int Number(void) const
Definition channels.h:178
const char * Name(void) const
Definition channels.c:107
tChannelID GetChannelID(void) const
Definition channels.h:190
const char * FileName(void)
Definition config.h:126
static int CurrentChannel(void)
Returns the number of the current channel on the primary device.
Definition device.h:358
bool Contains(const char *Title) const
Definition recording.c:3169
Definition epg.h:73
const char * ShortText(void) const
Definition epg.h:106
cString ToDescr(void) const
Definition epg.c:248
time_t Vps(void) const
Definition epg.h:114
time_t EndTime(void) const
Definition epg.h:112
int RunningStatus(void) const
Definition epg.h:104
bool IsRunning(bool OrAboutToStart=false) const
Definition epg.c:274
void IncNumTimers(void) const
Definition epg.c:256
time_t StartTime(void) const
Definition epg.h:111
tChannelID ChannelID(void) const
Definition epg.c:151
const char * Title(void) const
Definition epg.h:105
const cSchedule * Schedule(void) const
Definition epg.h:100
bool HasTimer(void) const
Definition epg.h:120
int Duration(void) const
Definition epg.h:113
void Ins(cListObject *Object, cListObject *Before=NULL)
Definition tools.c:2204
void Del(cListObject *Object, bool DeleteObject=true)
Definition tools.c:2220
bool Lock(cStateKey &StateKey, bool Write=false, int TimeoutMs=0) const
Tries to get a lock on this list and returns true if successful.
Definition tools.c:2179
void Add(cListObject *Object, cListObject *After=NULL)
Definition tools.c:2188
cListObject * Prev(void) const
Definition tools.h:556
int Index(void) const
Definition tools.c:2108
cListObject * Next(void) const
Definition tools.h:557
const T * First(void) const
Returns the first element in this list, or NULL if the list is empty.
Definition tools.h:653
const T * Next(const T *Object) const
< Returns the element immediately before Object in this list, or NULL if Object is the first element ...
Definition tools.h:660
void Set(char *s)
Definition tools.h:214
bool Modified(int &State) const
Definition epg.h:167
bool PresentSeenWithin(int Seconds) const
Definition epg.h:170
const cList< cEvent > * Events(void) const
Definition epg.h:187
const cSchedule * GetSchedule(tChannelID ChannelID) const
Definition epg.c:1374
int DefaultLifetime
Definition config.h:309
int DefaultPriority
Definition config.h:309
int MarginStart
Definition config.h:291
int MarginStop
Definition config.h:291
int UseVps
Definition config.h:314
int MarkInstantRecord
Definition config.h:274
int PausePriority
Definition config.h:312
char NameInstantRecord[NAME_MAX+1]
Definition config.h:275
int InstantRecordTime
Definition config.h:276
int PauseLifetime
Definition config.h:312
cSortedTimers(const cTimers *Timers)
Definition timers.c:1448
static void MsgTimerChange(const cTimer *Timer, eTimerChange Change)
Definition status.c:32
void SortNumerically(void)
Definition tools.h:860
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition tools.c:1149
int Stop(void) const
Definition timers.h:71
void SetAux(const char *Aux)
Definition timers.c:977
time_t stopTime
the time_t value calculated from 'day', 'start' and 'stop'
Definition timers.h:35
const char * Aux(void) const
Definition timers.h:77
void OnOff(void)
Definition timers.c:1022
void SetLifetime(int Lifetime)
Definition timers.c:972
const char * File(void) const
Definition timers.h:75
cString PrintFirstDay(void) const
Definition timers.c:424
char * aux
Definition timers.h:51
time_t day
midnight of the day this timer shall hit, or of the first day it shall hit in case of a repeating tim...
Definition timers.h:43
int weekdays
bitmask, lowest bits: SSFTWTM (the 'M' is the LSB)
Definition timers.h:44
bool IsSingleEvent(void) const
Definition timers.c:501
void SetPending(bool Pending)
Definition timers.c:935
cTimer(bool Instant=false, bool Pause=false, const cChannel *Channel=NULL)
Definition timers.c:26
time_t StopTime(void) const
the stop time as given by the user
Definition timers.c:712
cString PatternAndFile(void) const
Definition timers.c:304
bool Recording(void) const
Definition timers.h:63
void SetStart(int Start)
Definition timers.c:957
static time_t SetTime(time_t t, int SecondsFromMidnight)
Definition timers.c:536
int priority
Definition timers.h:47
bool Expired(void) const
Definition timers.c:694
void ClrFlags(uint Flags)
Definition timers.c:1000
char file[NAME_MAX *2+1]
Definition timers.h:50
virtual int Compare(const cListObject &ListObject) const
Must return 0 if this object is equal to ListObject, a positive value if it is "greater",...
Definition timers.c:285
void SetFile(const char *File)
Definition timers.c:552
void SetFlags(uint Flags)
Definition timers.c:995
int Start(void) const
Definition timers.h:70
virtual ~cTimer()
Definition timers.c:232
int id
Definition timers.h:34
int start
the start and stop time of this timer as given by the user,
Definition timers.h:45
void SetPriority(int Priority)
Definition timers.c:967
void SetDeferred(int Seconds)
Definition timers.c:989
void SetId(int Id)
Definition timers.c:744
time_t StopTimeEvent(void) const
or by the user (for normal timers)
Definition timers.c:730
bool AdjustSpawnedTimer(void)
Definition timers.c:808
void SetInVpsMargin(bool InVpsMargin)
Definition timers.c:940
bool Save(FILE *f)
Definition timers.c:494
bool IsPatternTimer(void) const
Definition timers.h:95
static int GetWDay(time_t t)
Definition timers.c:512
const char * Pattern(void) const
Definition timers.h:74
int WeekDays(void) const
Definition timers.h:69
static cString PrintDay(time_t Day, int WeekDays, bool SingleByteChars)
Definition timers.c:390
void TriggerRespawn(void)
Definition timers.c:843
bool DayMatches(time_t t) const
Definition timers.c:519
time_t Day(void) const
Definition timers.h:68
void SetDay(time_t Day)
Definition timers.c:947
void SetRemote(const char *Remote)
Definition timers.c:983
bool InVpsMargin(void) const
Definition timers.h:65
char * remote
Definition timers.h:52
bool SetEvent(const cEvent *Event)
Definition timers.c:906
const cChannel * channel
Definition timers.h:42
void InvFlags(uint Flags)
Definition timers.c:1005
void SetStop(int Stop)
Definition timers.c:962
int stop
in the form hhmm, with hh (00..23) and mm (00..59) added as hh*100+mm
Definition timers.h:46
bool Local(void) const
Definition timers.h:79
int scheduleStateSpawn
Definition timers.h:37
const cEvent * Event(void) const
Definition timers.h:84
static bool ParseDay(const char *s, time_t &Day, int &WeekDays)
Definition timers.c:331
uint Flags(void) const
Definition timers.h:66
void Skip(void)
Definition timers.c:1015
cTimer * SpawnPatternTimer(const cEvent *Event, cTimers *Timers)
Definition timers.c:749
const cEvent * event
Definition timers.h:53
time_t StartTime(void) const
the start time as given by the user
Definition timers.c:705
const cChannel * Channel(void) const
Definition timers.h:67
bool Pending(void) const
Definition timers.h:64
void CalcMargins(int &MarginStart, int &MarginStop, const cEvent *Event)
Definition timers.c:273
cString ToDescr(void) const
Definition timers.c:321
int scheduleStateSet
Definition timers.h:36
int scheduleStateAdjust
Definition timers.h:38
bool SetEventFromSchedule(const cSchedules *Schedules)
Definition timers.c:857
int Priority(void) const
Definition timers.h:72
void SetRecording(bool Recording)
Definition timers.c:926
time_t StartTimeEvent(void) const
the start/stop times as given by the event (for VPS timers), by event plus margins (for spawned non-V...
Definition timers.c:719
void SetPattern(const char *Pattern)
Definition timers.c:547
char pattern[NAME_MAX *2+1]
Definition timers.h:49
bool pending
Definition timers.h:40
time_t startTime
Definition timers.h:35
static int TimeToInt(int t)
Definition timers.c:326
time_t deferred
Matches(time_t, ...) will return false if the current time is before this value.
Definition timers.h:39
static int GetMDay(time_t t)
Definition timers.c:506
bool HasFlags(uint Flags) const
Definition timers.c:1010
const char * Remote(void) const
Definition timers.h:78
cTimer & operator=(const cTimer &Timer)
Definition timers.c:240
void SetWeekDays(int WeekDays)
Definition timers.c:952
bool inVpsMargin
Definition timers.h:40
int lifetime
Definition timers.h:48
int Id(void) const
Definition timers.h:62
bool Matches(time_t t=0, bool Directly=false, int Margin=0) const
Definition timers.c:560
int Lifetime(void) const
Definition timers.h:73
bool Parse(const char *s)
Definition timers.c:434
uint flags
Definition timers.h:41
cString ToText(bool UseChannelID=false) const
Definition timers.c:311
static time_t IncDay(time_t t, int Days)
Definition timers.c:524
bool SpawnPatternTimers(const cSchedules *Schedules, cTimers *Timers)
Definition timers.c:762
static cTimers timers
Definition timers.h:136
static bool Load(const char *FileName)
Definition timers.c:1051
int GetMaxPriority(void) const
Returns the maximum priority of all local timers that are currently recording.
Definition timers.c:1145
const cTimer * UsesChannel(const cChannel *Channel) const
Definition timers.c:1198
bool StoreRemoteTimers(const char *ServerName=NULL, const cStringList *RemoteTimers=NULL)
Stores the given list of RemoteTimers, which come from the VDR ServerName, in this list.
Definition timers.c:1264
const cTimer * GetById(int Id, const char *Remote=NULL) const
Definition timers.c:1071
void Add(cTimer *Timer, cTimer *After=NULL)
Definition timers.c:1178
static cTimers * GetTimersWrite(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for write access.
Definition timers.c:1173
void Del(cTimer *Timer, bool DeleteObject=true)
Definition timers.c:1192
static const cTimers * GetTimersRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for read access.
Definition timers.c:1168
const cTimer * GetTimer(const cTimer *Timer) const
Definition timers.c:1082
const cTimer * GetMatch(time_t t) const
Definition timers.c:1095
const cTimer * GetTimerForEvent(const cEvent *Event, eTimerFlags Flags=tfNone) const
Definition timers.c:1134
static int lastTimerId
Definition timers.h:137
void Ins(cTimer *Timer, cTimer *Before=NULL)
Definition timers.c:1186
time_t lastDeleteExpired
Definition timers.h:138
bool SpawnPatternTimers(const cSchedules *Schedules)
Definition timers.c:1217
const cTimer * GetNextActiveTimer(void) const
Definition timers.c:1155
bool DeleteExpired(bool Force)
Definition timers.c:1243
bool SetEvents(const cSchedules *Schedules)
Definition timers.c:1207
bool AdjustSpawnedTimers(void)
Definition timers.c:1229
static int NewTimerId(void)
Definition timers.c:1066
cTimers(void)
Definition timers.c:1045
int Size(void) const
Definition tools.h:764
void Sort(__compar_fn_t Compare)
Definition tools.h:821
virtual void Append(T Data)
Definition tools.h:784
cSetup Setup
Definition config.c:372
#define TIMERMACRO_MATCH
Definition config.h:54
#define TIMERMACRO_AFTER
Definition config.h:55
#define TIMERPATTERN_BEGIN
Definition config.h:58
#define TIMERMACRO_BEFORE
Definition config.h:53
#define TIMERMACRO_EPISODE
Definition config.h:52
#define DEFINSTRECTIME
Definition config.h:49
#define TIMERPATTERN_AVOID
Definition config.h:57
#define TIMERPATTERN_END
Definition config.h:59
#define TIMERMACRO_TITLE
Definition config.h:51
#define LOCK_SCHEDULES_READ
Definition epg.h:233
#define LOCK_SCHEDULES_WRITE
Definition epg.h:234
#define tr(s)
Definition i18n.h:85
#define trNOOP(s)
Definition i18n.h:88
@ RunningStatusNotRunning
Definition si.h:197
cDoneRecordings DoneRecordingsPattern
Definition recording.c:3099
#define FOLDERDELIMCHAR
Definition recording.h:21
@ tcDel
Definition status.h:31
@ tcAdd
Definition status.h:31
static tChannelID FromString(const char *s)
Definition channels.c:23
bool ExecSVDRPCommand(const char *ServerName, const char *Command, cStringList *Response)
Sends the given SVDRP Command string to the remote VDR identified by ServerName and collects all of t...
Definition svdrp.c:2855
const char * SVDRPValue(const char *s)
Returns the actual value of the given SVDRP response string, skipping the three digit reply code and ...
Definition svdrp.h:50
int SVDRPCode(const char *s)
Returns the value of the three digit reply code of the given SVDRP response string.
Definition svdrp.h:47
#define EXPIRELATENCY
Definition timers.c:692
#define FULLMATCH
Definition timers.c:636
#define DELETE_EXPIRED_TIMEOUT
Definition timers.c:1241
static bool RemoteTimerError(const cTimer *Timer, cString *Msg)
Definition timers.c:1375
#define EPGLIMITAFTER
Definition timers.c:742
static cString MakePatternFileName(const char *Pattern, const char *Title, const char *Episode, const char *File)
Definition timers.c:156
static bool MatchPattern(const char *Pattern, const char *Title, cString *Before=NULL, cString *Match=NULL, cString *After=NULL)
Definition timers.c:88
#define EITPRESENTFOLLOWINGRATE
Definition timers.c:558
static int CompareTimers(const void *a, const void *b)
Definition timers.c:1443
bool HandleRemoteTimerModifications(cTimer *NewTimer, cTimer *OldTimer, cString *Msg)
Performs any operations necessary to synchronize changes to a timer between peer VDR machines.
Definition timers.c:1382
#define EPGLIMITBEFORE
Definition timers.c:741
#define DAYBUFFERSIZE
#define LOCK_TIMERS_WRITE
Definition timers.h:245
bool HandleRemoteTimerModifications(cTimer *NewTimer, cTimer *OldTimer=NULL, cString *Msg=NULL)
Performs any operations necessary to synchronize changes to a timer between peer VDR machines.
Definition timers.c:1382
eTimerFlags
Definition timers.h:18
@ tfNone
Definition timers.h:18
@ tfAvoid
Definition timers.h:24
@ tfInstant
Definition timers.h:20
@ tfActive
Definition timers.h:19
@ tfVps
Definition timers.h:21
@ tfRecording
Definition timers.h:22
@ tfSpawned
Definition timers.h:23
eTimerMatch
Definition timers.h:27
@ tmPartial
Definition timers.h:27
@ tmFull
Definition timers.h:27
@ tmNone
Definition timers.h:27
const char * strgetlast(const char *s, char c)
Definition tools.c:213
cString TimeString(time_t t)
Converts the given time to a string of the form "hh:mm".
Definition tools.c:1255
char * Utf8Strn0Cpy(char *Dest, const char *Src, int n)
Copies at most n character bytes from Src to Dest, making sure that the resulting copy ends with a co...
Definition tools.c:899
bool isempty(const char *s)
Definition tools.c:349
char * strreplace(char *s, char c1, char c2)
Definition tools.c:139
bool startswith(const char *s, const char *p)
Definition tools.c:329
int Utf8CharLen(const char *s)
Returns the number of character bytes at the beginning of the given string that form a UTF-8 symbol.
Definition tools.c:811
char * strn0cpy(char *dest, const char *src, size_t n)
Definition tools.c:131
bool endswith(const char *s, const char *p)
Definition tools.c:338
cString itoa(int n)
Definition tools.c:442
bool isnumber(const char *s)
Definition tools.c:364
#define SECSINDAY
Definition tools.h:42
#define dsyslog(a...)
Definition tools.h:37
#define MALLOC(type, size)
Definition tools.h:47
char * skipspace(const char *s)
Definition tools.h:241
T min(T a, T b)
Definition tools.h:63
T max(T a, T b)
Definition tools.h:64
#define esyslog(a...)
Definition tools.h:35
#define isyslog(a...)
Definition tools.h:36