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