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