vdr  2.7.6
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.25 2025/01/13 14:44:18 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 
28 cTimer::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) {
61  SetFlags(tfVps);
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;
76  SetEvent(Event);
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 
92 static 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);
111  cNullTerminate nt;
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 
160 static 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 
180 cTimer::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))
199  SetFlags(tfVps);
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));
227  SetEvent(Event);
228 }
229 
230 cTimer::cTimer(const cTimer &Timer)
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 
283 void cTimer::CalcMargins(int &MarginStart, int &MarginStop, const cEvent *Event)
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 
297 int 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 
323 cString 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 
343 bool 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 
402 cString 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 
446 bool 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 
506 bool cTimer::Save(FILE *f)
507 {
508  if (!Remote())
509  return fprintf(f, "%s\n", *ToText(true)) > 0;
510  return true;
511 }
512 
513 bool cTimer::IsSingleEvent(void) const
514 {
515  return !weekdays;
516 }
517 
518 int cTimer::GetMDay(time_t t)
519 {
520  struct tm tm_r;
521  return localtime_r(&t, &tm_r)->tm_mday;
522 }
523 
524 int 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 
531 bool cTimer::DayMatches(time_t t) const
532 {
533  return IsSingleEvent() ? SetTime(t, 0) == day : (weekdays & (1 << GetWDay(t))) != 0;
534 }
535 
536 time_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 
548 time_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 
559 void cTimer::SetPattern(const char *Pattern)
560 {
561  Utf8Strn0Cpy(pattern, Pattern, sizeof(pattern));
562 }
563 
564 void 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 
573 bool cTimer::Matches(time_t t, bool Directly, int Margin) 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  if (length < 0)
597  t0 = IncDay(d, i + 1);
598  time_t b = SetTime(t0, end);
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  else if (!Directly && (t > startTime || t > day + SECSINDAY + 3600)) // +3600 in case of DST change
609  day = 0;
610  }
611 
612  if (IsPatternTimer())
613  return false; // we only need to have start/stopTime initialized
614 
615  if (t < deferred)
616  return false;
617  deferred = 0;
618 
619  if (HasFlags(tfActive) && !Remote()) {
620  if (event) {
621  if (HasFlags(tfVps)) {
622  if (event->Vps()) {
623  if (Margin || !Directly) {
624  startTime = event->StartTime();
625  stopTime = event->EndTime();
626  if (!Margin) { // this is an actual check
627  const cSchedule *Schedule = event->Schedule();
628  if (Schedule && Schedule->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) { // VPS control can only work with up-to-date events...
629  if (!vpsActive) {
630  vpsActive = true;
631  if (Recording())
632  dsyslog("timer %s regained VPS control", *ToDescr());
633  }
634  bool running = event->IsRunning(true);
635  if (!running) {
636  if (Recording() && vpsNotRunning == 0)
637  vpsNotRunning = time(NULL);
638  }
639  else
640  vpsNotRunning = 0;
641  return running || time(NULL) < vpsNotRunning + VPSGRACE;
642  }
643  if (Recording()) {
644  if (Schedule && Schedule->PresentSeenWithin(EITPRESENTFOLLOWINGGRACE))
645  return event->IsRunning(true); // give it a chance to recover - worst case: the recording will be 60 seconds too long
646  if (vpsActive) {
647  vpsActive = false;
648  esyslog("ERROR: timer %s lost VPS control", *ToDescr());
649  }
650  // ...otherwise we fall back to normal timer handling below (note: Margin == 0!)
651  }
652  else
653  return false; // relying on vdr.c to ensure that a transponder is tuned to this channel
654  }
655  }
656  }
657  }
658  else if (HasFlags(tfSpawned)) {
659  if (!Margin && !Directly) { // this is an actual check
660  // The spawned timer's start-/stopTimes are adjusted to the event's times in AdjustSpawnedTimer().
661  // However, in order to make sure the timer is set to the correct event, the margins at begin
662  // end end are limited by the durations of the events before and after this timer's event.
663  // The recording, though, shall always use the full start/stop margins, hence this calculation:
664  return event->StartTime() - Setup.MarginStart * 60 <= t && t < event->EndTime() + Setup.MarginStop * 60;
665  }
666  }
667  }
668  return startTime <= t + Margin && t < stopTime; // must stop *before* stopTime to allow adjacent timers
669  }
670  return false;
671 }
672 
673 #define FULLMATCH 1000
674 
675 eTimerMatch cTimer::Matches(const cEvent *Event, int *Overlap) const
676 {
677  // Overlap is the percentage of the Event's duration that is covered by
678  // this timer (based on FULLMATCH for finer granularity than just 100).
679  // To make sure a VPS timer can be distinguished from a plain 100% overlap,
680  // it gets an additional 100 added, and a VPS event that is actually running
681  // gets 200 added to the FULLMATCH.
682  if (channel->GetChannelID() == Event->ChannelID()) {
683  bool UseVps = HasFlags(tfVps) && Event->Vps();
684  if (IsPatternTimer()) {
687  if (*FileName) {
688  const char *p = strgetlast(*FileName, FOLDERDELIMCHAR);
690  return tmNone;
691  }
692  else
693  return tmNone;
694  }
695  else if (!MatchPattern(Pattern(), Event->Title()))
696  return tmNone;
697  UseVps = false;
698  }
699  Matches(UseVps ? Event->Vps() : Event->StartTime(), true);
700  int overlap = 0;
701  if (UseVps) {
702  if (startTime == Event->Vps()) {
703  overlap = FULLMATCH;
704  if (Event->IsRunning())
705  overlap += 200;
707  overlap += 100;
708  }
709  }
710  else {
711  if (startTime <= Event->StartTime() && Event->EndTime() <= stopTime)
712  overlap = FULLMATCH;
713  else if (stopTime <= Event->StartTime() || Event->EndTime() <= startTime)
714  overlap = 0;
715  else {
716  overlap = (min(stopTime, Event->EndTime()) - max(startTime, Event->StartTime())) * FULLMATCH / max(Event->Duration(), 1);
717  if (IsPatternTimer() && overlap > 0)
718  overlap = FULLMATCH;
719  }
720  }
721  startTime = stopTime = 0;
722  if (Overlap)
723  *Overlap = overlap;
724  return overlap >= FULLMATCH ? tmFull : overlap > 0 ? tmPartial : tmNone;
725  }
726  return tmNone;
727 }
728 
729 #define EXPIRELATENCY 60 // seconds (just in case there's a short glitch in the VPS signal)
730 
731 bool cTimer::Expired(void) const
732 {
733  if (IsSingleEvent() && !Recording()) {
734  time_t Now = time(NULL);
735  time_t ExpireTime = StopTimeEvent();
736  if (HasFlags(tfVps)) {
737  ExpireTime += EXPIRELATENCY;
738  if (ExpireTime <= Now) {
740  const cSchedule *Schedule = event ? event->Schedule() : NULL;
741  const cEvent *FirstEvent = event;
742  if (Schedule)
743  FirstEvent = Schedule->Events()->Next(FirstEvent);
744  else if ((Schedule = Schedules->GetSchedule(Channel())) != NULL) {
745  FirstEvent = Schedule->Events()->First();
746  if (FirstEvent)
747  dsyslog("timer %s had no event, got %s from channel/schedule", *ToDescr(), *FirstEvent->ToDescr());
748  }
749  if (FirstEvent) {
750  if (Schedule) {
751  for (const cEvent *e = FirstEvent; e; e = Schedule->Events()->Next(e)) {
752  if (e->Vps() == startTime) {
753  ExpireTime = e->EndTime() + EXPIRELATENCY;
754  dsyslog("timer %s is waiting for next VPS event %s", *ToDescr(), *e->ToDescr());
755  // no break here - let's play it safe and look at *all* events
756  }
757  }
758  }
759  }
760  else {
761  dsyslog("timer %s has no event, setting expiration to +24h", *ToDescr());
762  ExpireTime += 3600 * 24;
763  }
764  }
765  }
766  return ExpireTime <= Now;
767  }
768  return false;
769 }
770 
771 time_t cTimer::StartTime(void) const
772 {
773  if (!startTime)
774  Matches();
775  return startTime;
776 }
777 
778 time_t cTimer::StopTime(void) const
779 {
780  if (!stopTime)
781  Matches();
782  return stopTime;
783 }
784 
785 time_t cTimer::StartTimeEvent(void) const
786 {
787  if (event) {
788  if (HasFlags(tfVps) && event->Vps())
789  return event->StartTime();
790  else if (HasFlags(tfSpawned))
791  return event->StartTime() - Setup.MarginStart * 60;
792  }
793  return StartTime();
794 }
795 
796 time_t cTimer::StopTimeEvent(void) const
797 {
798  if (event) {
799  if (HasFlags(tfVps) && event->Vps())
800  return event->EndTime();
801  else if (HasFlags(tfSpawned))
802  return event->EndTime() + Setup.MarginStop * 60;
803  }
804  return StopTime();
805 }
806 
807 #define EPGLIMITBEFORE (1 * 3600) // Time in seconds before a timer's start time and
808 #define EPGLIMITAFTER (1 * 3600) // after its stop time within which EPG events will be taken into consideration.
809 
810 void cTimer::SetId(int Id)
811 {
812  id = Id;
813 }
814 
816 {
818  isyslog("spawning timer %s for event %s", *ToDescr(), *Event->ToDescr());
819  cTimer *t = new cTimer(Event, FileName, this);
821  t->SetFlags(tfAvoid);
822  Timers->Add(t);
824  return t;
825 }
826 
827 bool cTimer::SpawnPatternTimers(const cSchedules *Schedules, cTimers *Timers)
828 {
829  bool TimersSpawned = false;
830  const cSchedule *Schedule = Schedules->GetSchedule(Channel());
831  if (Schedule && Schedule->Events()->First()) {
832  if (Schedule->Modified(scheduleStateSpawn)) {
833  time_t Now = time(NULL);
834  // Find the first event that matches this pattern timer and either already has a spawned
835  // timer, or has not yet ended:
836  for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
837  if (Matches(e) != tmNone) {
838  const cTimer *Timer = Timers->GetTimerForEvent(e, tfSpawned); // a matching event that already has a spawned timer
839  if (!Timer && e->EndTime() > Now) { // only look at events that have not yet ended
840  Timer = SpawnPatternTimer(e, Timers);
841  TimersSpawned = true;
842  }
843  if (Timer) {
844  // Check all following matching events that would start while the first timer
845  // is still recording:
846  bool UseVps = Timer->HasFlags(tfVps);
847  time_t Limit = Timer->StopTimeEvent();
848  if (UseVps)
849  Limit += EXPIRELATENCY;
850  else
851  Limit += Setup.MarginStart * 60;
852  for (e = Schedule->Events()->Next(e); e; e = Schedule->Events()->Next(e)) {
853  if (e->StartTime() <= Limit) {
854  if (!Timers->GetTimerForEvent(e, tfSpawned) && Matches(e) != tmNone) {
855  SpawnPatternTimer(e, Timers);
856  TimersSpawned = true;
857  }
858  if (UseVps)
859  break; // with VPS we only need to check the event immediately following the first one
860  }
861  else
862  break; // no need to check events that are too far in the future
863  }
864  break;
865  }
866  }
867  }
868  }
869  }
870  return TimersSpawned;
871 }
872 
874 {
875  if (Event()) {
876  if (const cSchedule *Schedule = Event()->Schedule()) { // events may be deleted from their schedule in cSchedule::DropOutdated()!
877  if (Schedule->Modified(scheduleStateAdjust)) {
878  // Adjust the timer to shifted start/stop times of the event if necessary:
879  time_t tstart = Event()->StartTime();
880  time_t tstop = Event()->EndTime();
881  int MarginStart = 0;
882  int MarginStop = 0;
883  CalcMargins(MarginStart, MarginStop, Event());
884  tstart -= MarginStart;
885  tstop += MarginStop;
886  // Event start/end times are given in "seconds since the epoch". Some broadcasters use values
887  // that result in full minutes (with zero seconds), while others use any values. VDR's timers
888  // use times given in full minutes, truncating any seconds. Thus we only react if the start/stop
889  // times of the timer are off by at least one minute:
890  if (abs(StartTime() - tstart) >= 60 || abs(StopTime() - tstop) >= 60) {
891  cString OldDescr = ToDescr();
892  struct tm tm_r;
893  struct tm *time = localtime_r(&tstart, &tm_r);
894  SetDay(cTimer::SetTime(tstart, 0));
895  SetStart(time->tm_hour * 100 + time->tm_min);
896  time = localtime_r(&tstop, &tm_r);
897  SetStop(time->tm_hour * 100 + time->tm_min);
898  Matches();
899  isyslog("timer %s times changed to %s-%s", *OldDescr, *TimeString(tstart), *TimeString(tstop));
900  return true;
901  }
902  }
903  }
904  }
905  return false;
906 }
907 
909 {
910  if (Local() && HasFlags(tfSpawned) || IsPatternTimer()) {
911  if (Channel()) {
913  if (const cSchedule *Schedule = Channel()->Schedule()) {
914  dsyslog("triggering respawn for timer %s", *ToDescr());
916  const_cast<cSchedule *>(Schedule)->SetModified();
917  }
918  }
919  }
920 }
921 
923 {
924  if (IsPatternTimer())
925  return SetEvent(NULL);
926  const cSchedule *Schedule = Schedules->GetSchedule(Channel());
927  if (Schedule && Schedule->Events()->First()) {
928  if (Schedule->Modified(scheduleStateSet)) {
929  const cEvent *Event = NULL;
930  if (HasFlags(tfVps) && Schedule->Events()->First()->Vps()) {
931  // VPS timers only match if their start time exactly matches the event's VPS time:
932  for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
933  if (e->StartTime()) {
934  int overlap = 0;
935  if (Matches(e, &overlap) == tmFull) {
936  Event = e;
937  if (overlap > FULLMATCH)
938  break; // take the first matching event
939  }
940  }
941  }
942  }
943  else {
944  // Normal timers match the event they have the most overlap with:
945  int Overlap = 0;
946  // Set up the time frame within which to check events:
947  Matches(0, true);
948  time_t TimeFrameBegin = StartTime() - EPGLIMITBEFORE;
949  time_t TimeFrameEnd = StopTime() + EPGLIMITAFTER;
950  for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
951  if (e->EndTime() < TimeFrameBegin)
952  continue; // skip events way before the timer starts
953  if (e->StartTime() > TimeFrameEnd)
954  break; // the rest is way after the timer ends
955  int overlap = 0;
956  Matches(e, &overlap);
957  if (overlap && overlap >= Overlap) {
958  if (Event && overlap == Overlap && e->Duration() <= Event->Duration())
959  continue; // if overlap is the same, we take the longer event
960  Overlap = overlap;
961  Event = e;
962  }
963  }
964  }
965  return SetEvent(Event);
966  }
967  }
968  return false;
969 }
970 
971 bool cTimer::SetEvent(const cEvent *Event)
972 {
973  if (event != Event) {
974  if (event)
975  event->DecNumTimers();
976  if (Event) {
977  isyslog("timer %s set to event %s", *ToDescr(), *Event->ToDescr());
978  Event->IncNumTimers();
979  Event->Schedule()->Modified(scheduleStateSet); // to get the current state
980  }
981  else {
982  isyslog("timer %s set to no event", *ToDescr());
984  }
985  event = Event;
986  return true;
987  }
988  return false;
989 }
990 
991 void cTimer::SetRecording(bool Recording)
992 {
993  if (Recording)
995  else
997  isyslog("timer %s %s", *ToDescr(), Recording ? "start" : "stop");
998 }
999 
1000 void cTimer::SetPending(bool Pending)
1001 {
1002  pending = Pending;
1003 }
1004 
1005 void cTimer::SetInVpsMargin(bool InVpsMargin)
1006 {
1007  if (InVpsMargin && !inVpsMargin)
1008  isyslog("timer %s entered VPS margin", *ToDescr());
1010 }
1011 
1012 void cTimer::SetDay(time_t Day)
1013 {
1014  day = Day;
1015 }
1016 
1017 void cTimer::SetWeekDays(int WeekDays)
1018 {
1019  weekdays = WeekDays;
1020 }
1021 
1022 void cTimer::SetStart(int Start)
1023 {
1024  start = Start;
1025 }
1026 
1027 void cTimer::SetStop(int Stop)
1028 {
1029  stop = Stop;
1030 }
1031 
1032 void cTimer::SetPriority(int Priority)
1033 {
1034  priority = Priority;
1035 }
1036 
1037 void cTimer::SetLifetime(int Lifetime)
1038 {
1039  lifetime = Lifetime;
1040 }
1041 
1042 void cTimer::SetAux(const char *Aux)
1043 {
1044  free(aux);
1045  aux = Aux ? strdup(Aux) : NULL;
1046 }
1047 
1048 void cTimer::SetRemote(const char *Remote)
1049 {
1050  free(remote);
1051  remote = Remote ? strdup(Remote) : NULL;
1052 }
1053 
1054 void cTimer::SetDeferred(int Seconds)
1055 {
1056  deferred = time(NULL) + Seconds;
1057  isyslog("timer %s deferred for %d seconds", *ToDescr(), Seconds);
1058 }
1059 
1060 void cTimer::SetFlags(uint Flags)
1061 {
1062  flags |= Flags;
1063 }
1064 
1065 void cTimer::ClrFlags(uint Flags)
1066 {
1067  flags &= ~Flags;
1068 }
1069 
1070 void cTimer::InvFlags(uint Flags)
1071 {
1072  flags ^= Flags;
1073 }
1074 
1075 bool cTimer::HasFlags(uint Flags) const
1076 {
1077  return (flags & Flags) == Flags;
1078 }
1079 
1080 void cTimer::Skip(void)
1081 {
1082  day = IncDay(SetTime(StartTime(), 0), 1);
1083  startTime = 0;
1084  SetEvent(NULL);
1085 }
1086 
1087 void cTimer::OnOff(void)
1088 {
1089  if (IsSingleEvent() || IsPatternTimer())
1090  InvFlags(tfActive);
1091  else if (day) {
1092  day = 0;
1093  ClrFlags(tfActive);
1094  }
1095  else if (HasFlags(tfActive))
1096  Skip();
1097  else
1098  SetFlags(tfActive);
1099  SetEvent(NULL);
1100  if (HasFlags(tfActive))
1101  TriggerRespawn(); // have pattern timers spawn if necessary
1102  Matches(); // refresh start and end time
1103 }
1104 
1105 // --- cTimers ---------------------------------------------------------------
1106 
1108 int cTimers::lastTimerId = 0;
1109 
1111 :cConfig<cTimer>("1 Timers")
1112 {
1113  lastDeleteExpired = 0;
1114 }
1115 
1116 bool cTimers::Load(const char *FileName)
1117 {
1119  Timers->SetExplicitModify();
1120  if (timers.cConfig<cTimer>::Load(FileName)) {
1121  for (cTimer *ti = timers.First(); ti; ti = timers.Next(ti)) {
1122  ti->SetId(NewTimerId());
1123  ti->ClrFlags(tfRecording);
1124  Timers->SetModified();
1125  }
1126  return true;
1127  }
1128  return false;
1129 }
1130 
1132 {
1133  return ++lastTimerId; // no need for locking, the caller must have a lock on the global Timers list
1134 }
1135 
1136 const cTimer *cTimers::GetById(int Id, const char *Remote) const
1137 {
1138  for (const cTimer *ti = First(); ti; ti = Next(ti)) {
1139  if (ti->Id() == Id) {
1140  if (!Remote && !ti->Remote() || Remote && ti->Remote() && strcmp(Remote, ti->Remote()) == 0)
1141  return ti;
1142  }
1143  }
1144  return NULL;
1145 }
1146 
1147 const cTimer *cTimers::GetTimer(const cTimer *Timer) const
1148 {
1149  for (const cTimer *ti = First(); ti; ti = Next(ti)) {
1150  if (!ti->Remote() &&
1151  ti->Channel() == Timer->Channel() &&
1152  (ti->WeekDays() && ti->WeekDays() == Timer->WeekDays() || !ti->WeekDays() && ti->Day() == Timer->Day()) &&
1153  ti->Start() == Timer->Start() &&
1154  ti->Stop() == Timer->Stop())
1155  return ti;
1156  }
1157  return NULL;
1158 }
1159 
1160 const cTimer *cTimers::GetMatch(time_t t) const
1161 {
1162  static int LastPending = -1;
1163  const cTimer *t0 = NULL;
1165  for (const cTimer *ti = First(); ti; ti = Next(ti)) {
1166  if (!ti->Remote() && !ti->Recording() && ti->Matches(t)) {
1167  if (ti->Pending()) {
1168  if (ti->Index() > LastPending) {
1169  LastPending = ti->Index();
1170  return ti;
1171  }
1172  else
1173  continue;
1174  }
1175  if (!t0 || ti->Priority() > t0->Priority())
1176  t0 = ti;
1177  }
1178  }
1179  if (!t0)
1180  LastPending = -1;
1181  return t0;
1182 }
1183 
1184 const cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match) const
1185 {
1186  const cTimer *t = NULL;
1187  eTimerMatch m = tmNone;
1188  for (const cTimer *ti = First(); ti; ti = Next(ti)) {
1189  eTimerMatch tm = ti->Matches(Event);
1190  if (tm > m || tm == tmFull && t && (t->Remote() && ti->Local() || t->IsPatternTimer() && ti->HasFlags(tfSpawned))) {
1191  t = ti;
1192  m = tm;
1193  }
1194  }
1195  if (Match)
1196  *Match = m;
1197  return t;
1198 }
1199 
1200 const cTimer *cTimers::GetTimerForEvent(const cEvent *Event, eTimerFlags Flags) const
1201 {
1202  if (Event && Event->HasTimer()) {
1203  for (const cTimer *ti = First(); ti; ti = Next(ti)) {
1204  if (ti->Event() == Event && ti->Local() && ti->HasFlags(Flags))
1205  return ti;
1206  }
1207  }
1208  return NULL;
1209 }
1210 
1212 {
1213  int n = -1;
1214  for (const cTimer *ti = First(); ti; ti = Next(ti)) {
1215  if (!ti->Remote() && ti->Recording())
1216  n = max(n, ti->Priority());
1217  }
1218  return n;
1219 }
1220 
1222 {
1223  const cTimer *t0 = NULL;
1224  for (const cTimer *ti = First(); ti; ti = Next(ti)) {
1225  if (!ti->Remote() && !ti->IsPatternTimer()) {
1226  ti->Matches();
1227  if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
1228  t0 = ti;
1229  }
1230  }
1231  return t0;
1232 }
1233 
1234 const cTimers *cTimers::GetTimersRead(cStateKey &StateKey, int TimeoutMs)
1235 {
1236  return timers.Lock(StateKey, false, TimeoutMs) ? &timers : NULL;
1237 }
1238 
1239 cTimers *cTimers::GetTimersWrite(cStateKey &StateKey, int TimeoutMs)
1240 {
1241  return timers.Lock(StateKey, true, TimeoutMs) ? &timers : NULL;
1242 }
1243 
1244 void cTimers::Add(cTimer *Timer, cTimer *After)
1245 {
1246  if (!Timer->Remote())
1247  Timer->SetId(NewTimerId());
1248  cConfig<cTimer>::Add(Timer, After);
1250 }
1251 
1252 void cTimers::Ins(cTimer *Timer, cTimer *Before)
1253 {
1254  cConfig<cTimer>::Ins(Timer, Before);
1256 }
1257 
1258 void cTimers::Del(cTimer *Timer, bool DeleteObject)
1259 {
1261  cConfig<cTimer>::Del(Timer, DeleteObject);
1262 }
1263 
1264 const cTimer *cTimers::UsesChannel(const cChannel *Channel) const
1265 {
1266  for (const cTimer *Timer = First(); Timer; Timer = Next(Timer)) {
1267  if (Timer->Channel() == Channel)
1268  return Timer;
1269  }
1270  return NULL;
1271 }
1272 
1273 bool cTimers::SetEvents(const cSchedules *Schedules)
1274 {
1275  bool TimersModified = false;
1276  for (cTimer *ti = First(); ti; ti = Next(ti)) {
1277  if (!ti->IsPatternTimer())
1278  TimersModified |= ti->SetEventFromSchedule(Schedules);
1279  }
1280  return TimersModified;
1281 }
1282 
1284 {
1285  bool TimersModified = false;
1286  for (cTimer *ti = First(); ti; ti = Next(ti)) {
1287  if (ti->IsPatternTimer() && ti->Local()) {
1288  if (ti->HasFlags(tfActive))
1289  TimersModified |= ti->SpawnPatternTimers(Schedules, this);
1290  }
1291  }
1292  return TimersModified;
1293 }
1294 
1296 {
1297  bool TimersModified = false;
1298  for (cTimer *ti = First(); ti; ti = Next(ti)) {
1299  if (ti->Local()) {
1300  if (ti->HasFlags(tfSpawned) && !ti->HasFlags(tfVps))
1301  TimersModified |= ti->AdjustSpawnedTimer();
1302  }
1303  }
1304  return TimersModified;
1305 }
1306 
1307 #define DELETE_EXPIRED_TIMEOUT 30 // seconds
1308 
1309 bool cTimers::DeleteExpired(bool Force)
1310 {
1311  if (!Force && time(NULL) - lastDeleteExpired < DELETE_EXPIRED_TIMEOUT)
1312  return false;
1313  bool TimersModified = false;
1314  cTimer *ti = First();
1315  while (ti) {
1316  cTimer *next = Next(ti);
1317  if (!ti->Remote() && ti->Expired()) {
1318  ti->SetEvent(NULL); // Del() doesn't call ~cTimer() right away, so this is necessary here
1319  ti->TriggerRespawn(); // in case this is a spawned timer
1320  isyslog("deleting timer %s", *ti->ToDescr());
1321  Del(ti);
1322  TimersModified = true;
1323  }
1324  ti = next;
1325  }
1326  lastDeleteExpired = time(NULL);
1327  return TimersModified;
1328 }
1329 
1330 bool cTimers::StoreRemoteTimers(const char *ServerName, const cStringList *RemoteTimers)
1331 {
1332  bool Result = false;
1333  if (!ServerName || !RemoteTimers || RemoteTimers->Size() == 0) {
1334  // Remove remote timers from this list:
1335  cTimer *Timer = First();
1336  while (Timer) {
1337  cTimer *t = Next(Timer);
1338  if (Timer->Remote() && (!ServerName || strcmp(Timer->Remote(), ServerName) == 0)) {
1339  Del(Timer);
1340  Result = true;
1341  }
1342  Timer = t;
1343  }
1344  return Result;
1345  }
1346  // Collect all locally stored remote timers from ServerName:
1347  cStringList tl;
1348  for (cTimer *ti = First(); ti; ti = Next(ti)) {
1349  if (ti->Remote() && strcmp(ti->Remote(), ServerName) == 0)
1350  tl.Append(strdup(cString::sprintf("%d %s", ti->Id(), *ti->ToText(true))));
1351  }
1352  tl.SortNumerically(); // RemoteTimers is also sorted numerically!
1353  // Compare the two lists and react accordingly:
1354  int il = 0; // index into the local ("left") list of remote timers
1355  int ir = 0; // index into the remote ("right") list of timers
1356  int sl = tl.Size();
1357  int sr = RemoteTimers->Size();
1358  for (;;) {
1359  int AddTimer = 0;
1360  int DelTimer = 0;
1361  if (il < sl) { // still have left entries
1362  int nl = atoi(tl[il]);
1363  if (ir < sr) { // still have right entries
1364  // Compare timers:
1365  int nr = atoi((*RemoteTimers)[ir]);
1366  if (nl == nr) // same timer id
1367  AddTimer = DelTimer = nl;
1368  else if (nl < nr) // left entry not in right list
1369  DelTimer = nl;
1370  else // right entry not in left list
1371  AddTimer = nr;
1372  }
1373  else // processed all right entries
1374  DelTimer = nl;
1375  }
1376  else if (ir < sr) { // still have right entries
1377  AddTimer = atoi((*RemoteTimers)[ir]);
1378  if (!AddTimer) {
1379  esyslog("ERROR: %s: error in timer settings: %s", ServerName, (*RemoteTimers)[ir]);
1380  ir++;
1381  continue; // let's see if we can process the rest
1382  }
1383  }
1384  else // processed all left and right entries
1385  break;
1386  if (AddTimer && DelTimer) {
1387  if (strcmp(tl[il], (*RemoteTimers)[ir]) != 0) {
1388  // Overwrite timer:
1389  char *v = (*RemoteTimers)[ir];
1390  while (*v && *v != ' ')
1391  v++; // skip id
1392  if (cTimer *l = GetById(DelTimer, ServerName)) {
1393  cTimer r;
1394  if (r.Parse(v)) {
1395  r.SetRemote(ServerName);
1396  r.SetId(AddTimer);
1397  *l = r;
1398  Result = true;
1399  }
1400  else
1401  esyslog("ERROR: %d@%s: error in timer settings: %s", DelTimer, ServerName, v);
1402  }
1403  }
1404  else // identical timer, nothing to do
1405  ;
1406  il++;
1407  ir++;
1408  }
1409  else if (AddTimer) {
1410  char *v = (*RemoteTimers)[ir];
1411  while (*v && *v != ' ')
1412  v++; // skip id
1413  cTimer *Timer = new cTimer;
1414  if (Timer->Parse(v)) {
1415  Timer->SetRemote(ServerName);
1416  Timer->SetId(AddTimer);
1417  Add(Timer);
1418  Result = true;
1419  }
1420  else {
1421  esyslog("ERROR: %s: error in timer settings: %s", ServerName, v);
1422  delete Timer;
1423  }
1424  ir++;
1425  }
1426  else if (DelTimer) {
1427  if (cTimer *t = GetById(DelTimer, ServerName)) {
1428  Del(t);
1429  Result = true;
1430  }
1431  il++;
1432  }
1433  else {
1434  esyslog("ERROR: oops while storing remote timers!");
1435  break; // let's not get stuck here!
1436  }
1437  }
1438  return Result;
1439 }
1440 
1441 static bool RemoteTimerError(const cTimer *Timer, cString *Msg)
1442 {
1443  if (Msg)
1444  *Msg = cString::sprintf("%s %d@%s!", tr("Error while accessing remote timer"), Timer->Id(), Timer->Remote());
1445  return false; // convenience return code
1446 }
1447 
1448 bool HandleRemoteTimerModifications(cTimer *NewTimer, cTimer *OldTimer, cString *Msg)
1449 {
1450  cStringList Response;
1451  if (!NewTimer) {
1452  if (OldTimer) { // timer shall be deleted from remote machine
1453  if (OldTimer->Remote() && OldTimer->Id()) {
1454  if (!ExecSVDRPCommand(OldTimer->Remote(), cString::sprintf("DELT %d", OldTimer->Id()), &Response) || SVDRPCode(Response[0]) != 250)
1455  return RemoteTimerError(OldTimer, Msg);
1456  }
1457  isyslog("deleted timer %s", *OldTimer->ToDescr());
1458  }
1459  }
1460  else if (!OldTimer || OldTimer->Local() || !OldTimer->Id()) {
1461  if (NewTimer->Local()) { // timer stays local, nothing to do
1462  if (OldTimer && OldTimer->Id())
1463  isyslog("modified timer %s", *NewTimer->ToDescr());
1464  else
1465  isyslog("added timer %s", *NewTimer->ToDescr());
1466  }
1467  else { // timer is new, or moved from local to remote
1468  if (!ExecSVDRPCommand(NewTimer->Remote(), cString::sprintf("NEWT %s", *NewTimer->ToText(true)), &Response) || SVDRPCode(Response[0]) != 250)
1469  return RemoteTimerError(NewTimer, Msg);
1470  int RemoteId = atoi(SVDRPValue(Response[0]));
1471  if (RemoteId <= 0)
1472  return RemoteTimerError(NewTimer, Msg);
1473  NewTimer->SetId(RemoteId);
1474  if (OldTimer && OldTimer->Id()) {
1475  isyslog("moved timer %d to %s", OldTimer->Id(), *NewTimer->ToDescr());
1476  }
1477  else
1478  isyslog("added timer %s", *NewTimer->ToDescr());
1479  }
1480  }
1481  else if (NewTimer->Local()) { // timer is moved from remote to local
1482  if (!ExecSVDRPCommand(OldTimer->Remote(), cString::sprintf("DELT %d", OldTimer->Id()), &Response) || SVDRPCode(Response[0]) != 250)
1483  return RemoteTimerError(OldTimer, Msg);
1484  NewTimer->SetId(cTimers::NewTimerId());
1485  NewTimer->ClrFlags(tfRecording); // in case it was recording on the remote machine
1486  isyslog("moved timer %d@%s to %s", OldTimer->Id(), OldTimer->Remote(), *NewTimer->ToDescr());
1487  }
1488  else if (strcmp(OldTimer->Remote(), NewTimer->Remote()) == 0) { // timer stays remote on same machine
1489  if (!ExecSVDRPCommand(OldTimer->Remote(), cString::sprintf("MODT %d %s", OldTimer->Id(), *NewTimer->ToText(true)), &Response) || SVDRPCode(Response[0]) != 250)
1490  return RemoteTimerError(NewTimer, Msg);
1491  isyslog("modified timer %s", *NewTimer->ToDescr());
1492  }
1493  else { // timer is moved from one remote machine to an other
1494  if (!ExecSVDRPCommand(NewTimer->Remote(), cString::sprintf("NEWT %s", *NewTimer->ToText(true)), &Response) || SVDRPCode(Response[0]) != 250)
1495  return RemoteTimerError(NewTimer, Msg);
1496  int RemoteId = atoi(SVDRPValue(Response[0]));
1497  if (RemoteId <= 0)
1498  return RemoteTimerError(NewTimer, Msg);
1499  NewTimer->SetId(RemoteId);
1500  if (!ExecSVDRPCommand(OldTimer->Remote(), cString::sprintf("DELT %d", OldTimer->Id()), &Response) || SVDRPCode(Response[0]) != 250)
1501  return RemoteTimerError(OldTimer, Msg);
1502  isyslog("moved timer %d@%s to %s", OldTimer->Id(), OldTimer->Remote(), *NewTimer->ToDescr());
1503  }
1504  return true;
1505 }
1506 
1507 // --- cSortedTimers ---------------------------------------------------------
1508 
1509 static int CompareTimers(const void *a, const void *b)
1510 {
1511  return (*(const cTimer **)a)->Compare(**(const cTimer **)b);
1512 }
1513 
1515 :cVector<const cTimer *>(Timers->Count())
1516 {
1517  for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer))
1518  Append(Timer);
1520 }
#define LOCK_CHANNELS_READ
Definition: channels.h:273
const char * Name(void) const
Definition: channels.c:122
tChannelID GetChannelID(void) const
Definition: channels.h:194
const char * FileName(void)
Definition: config.h:134
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:3377
Definition: epg.h:73
cString ToDescr(void) const
Definition: epg.c:251
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:277
void IncNumTimers(void) const
Definition: epg.c:259
time_t StartTime(void) const
Definition: epg.h:111
tChannelID ChannelID(void) const
Definition: epg.c:154
bool HasTimer(void) const
Definition: epg.h:120
const char * Title(void) const
Definition: epg.h:105
const cSchedule * Schedule(void) const
Definition: epg.h:100
int Duration(void) const
Definition: epg.h:113
const char * ShortText(void) const
Definition: epg.h:106
void Ins(cListObject *Object, cListObject *Before=NULL)
Definition: tools.c:2193
void Del(cListObject *Object, bool DeleteObject=true)
Definition: tools.c:2209
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:2168
void Add(cListObject *Object, cListObject *After=NULL)
Definition: tools.c:2177
cListObject * Prev(void) const
Definition: tools.h:546
cListObject * Next(void) const
Definition: tools.h:547
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:650
const T * First(void) const
Returns the first element in this list, or NULL if the list is empty.
Definition: tools.h:643
void Set(char *s)
Definition: tools.h:217
Definition: epg.h:152
bool Modified(int &State) const
Definition: epg.h:166
const cList< cEvent > * Events(void) const
Definition: epg.h:186
bool PresentSeenWithin(int Seconds) const
Definition: epg.h:169
const cSchedule * GetSchedule(tChannelID ChannelID) const
Definition: epg.c:1381
int DefaultLifetime
Definition: config.h:319
int DefaultPriority
Definition: config.h:319
int MarginStart
Definition: config.h:299
int MarginStop
Definition: config.h:299
int UseVps
Definition: config.h:324
int MarkInstantRecord
Definition: config.h:282
int PausePriority
Definition: config.h:322
char NameInstantRecord[NAME_MAX+1]
Definition: config.h:283
int InstantRecordTime
Definition: config.h:284
int PauseLifetime
Definition: config.h:322
cSortedTimers(const cTimers *Timers)
Definition: timers.c:1514
static void MsgTimerChange(const cTimer *Timer, eTimerChange Change)
Definition: status.c:39
void SortNumerically(void)
Definition: tools.h:850
Definition: tools.h:178
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition: tools.c:1195
Definition: timers.h:31
int Stop(void) const
Definition: timers.h:73
void SetAux(const char *Aux)
Definition: timers.c:1042
time_t stopTime
the time_t value calculated from 'day', 'start' and 'stop'
Definition: timers.h:35
void OnOff(void)
Definition: timers.c:1087
void SetLifetime(int Lifetime)
Definition: timers.c:1037
cString PrintFirstDay(void) const
Definition: timers.c:436
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:513
void SetPending(bool Pending)
Definition: timers.c:1000
const char * Remote(void) const
Definition: timers.h:80
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 as given by the user
Definition: timers.c:778
cString PatternAndFile(void) const
Definition: timers.c:316
bool Recording(void) const
Definition: timers.h:65
void SetStart(int Start)
Definition: timers.c:1022
static time_t SetTime(time_t t, int SecondsFromMidnight)
Definition: timers.c:548
int priority
Definition: timers.h:49
bool Expired(void) const
Definition: timers.c:731
void ClrFlags(uint Flags)
Definition: timers.c:1065
char file[NAME_MAX *2+1]
Definition: timers.h:52
void SetFile(const char *File)
Definition: timers.c:564
void SetFlags(uint Flags)
Definition: timers.c:1060
int Start(void) const
Definition: timers.h:72
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:1032
void SetDeferred(int Seconds)
Definition: timers.c:1054
void SetId(int Id)
Definition: timers.c:810
time_t StopTimeEvent(void) const
or by the user (for normal timers)
Definition: timers.c:796
bool AdjustSpawnedTimer(void)
Definition: timers.c:873
void SetInVpsMargin(bool InVpsMargin)
Definition: timers.c:1005
bool Save(FILE *f)
Definition: timers.c:506
bool IsPatternTimer(void) const
Definition: timers.h:97
static int GetWDay(time_t t)
Definition: timers.c:524
int WeekDays(void) const
Definition: timers.h:71
static cString PrintDay(time_t Day, int WeekDays, bool SingleByteChars)
Definition: timers.c:402
void TriggerRespawn(void)
Definition: timers.c:908
bool DayMatches(time_t t) const
Definition: timers.c:531
time_t Day(void) const
Definition: timers.h:70
void SetDay(time_t Day)
Definition: timers.c:1012
void SetRemote(const char *Remote)
Definition: timers.c:1048
bool InVpsMargin(void) const
Definition: timers.h:67
char * remote
Definition: timers.h:54
const char * Aux(void) const
Definition: timers.h:79
bool SetEvent(const cEvent *Event)
Definition: timers.c:971
const cChannel * channel
Definition: timers.h:44
void InvFlags(uint Flags)
Definition: timers.c:1070
void SetStop(int Stop)
Definition: timers.c:1027
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
static bool ParseDay(const char *s, time_t &Day, int &WeekDays)
Definition: timers.c:343
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:1080
const char * File(void) const
Definition: timers.h:77
cTimer * SpawnPatternTimer(const cEvent *Event, cTimers *Timers)
Definition: timers.c:815
const cEvent * event
Definition: timers.h:55
time_t StartTime(void) const
the start time as given by the user
Definition: timers.c:771
bool Pending(void) const
Definition: timers.h:66
void CalcMargins(int &MarginStart, int &MarginStop, const cEvent *Event)
Definition: timers.c:283
cString ToDescr(void) const
Definition: timers.c:333
int scheduleStateSet
Definition: timers.h:36
const char * Pattern(void) const
Definition: timers.h:76
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:38
bool SetEventFromSchedule(const cSchedules *Schedules)
Definition: timers.c:922
int Priority(void) const
Definition: timers.h:74
void SetRecording(bool Recording)
Definition: timers.c:991
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:785
void SetPattern(const char *Pattern)
Definition: timers.c:559
const cEvent * Event(void) const
Definition: timers.h:86
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:338
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:518
bool HasFlags(uint Flags) const
Definition: timers.c:1075
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:40
void SetWeekDays(int WeekDays)
Definition: timers.c:1017
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:573
int Lifetime(void) const
Definition: timers.h:75
bool Parse(const char *s)
Definition: timers.c:446
uint flags
Definition: timers.h:43
cString ToText(bool UseChannelID=false) const
Definition: timers.c:323
const cChannel * Channel(void) const
Definition: timers.h:69
static time_t IncDay(time_t t, int Days)
Definition: timers.c:536
bool SpawnPatternTimers(const cSchedules *Schedules, cTimers *Timers)
Definition: timers.c:827
static cTimers timers
Definition: timers.h:138
static bool Load(const char *FileName)
Definition: timers.c:1116
int GetMaxPriority(void) const
Returns the maximum priority of all local timers that are currently recording.
Definition: timers.c:1211
const cTimer * UsesChannel(const cChannel *Channel) const
Definition: timers.c:1264
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:1330
const cTimer * GetById(int Id, const char *Remote=NULL) const
Definition: timers.c:1136
void Add(cTimer *Timer, cTimer *After=NULL)
Definition: timers.c:1244
static cTimers * GetTimersWrite(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for write access.
Definition: timers.c:1239
void Del(cTimer *Timer, bool DeleteObject=true)
Definition: timers.c:1258
static const cTimers * GetTimersRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for read access.
Definition: timers.c:1234
const cTimer * GetTimer(const cTimer *Timer) const
Definition: timers.c:1147
const cTimer * GetMatch(time_t t) const
Definition: timers.c:1160
const cTimer * GetTimerForEvent(const cEvent *Event, eTimerFlags Flags=tfNone) const
Definition: timers.c:1200
static int lastTimerId
Definition: timers.h:139
void Ins(cTimer *Timer, cTimer *Before=NULL)
Definition: timers.c:1252
time_t lastDeleteExpired
Definition: timers.h:140
bool SpawnPatternTimers(const cSchedules *Schedules)
Definition: timers.c:1283
const cTimer * GetNextActiveTimer(void) const
Definition: timers.c:1221
bool DeleteExpired(bool Force)
Definition: timers.c:1309
bool SetEvents(const cSchedules *Schedules)
Definition: timers.c:1273
bool AdjustSpawnedTimers(void)
Definition: timers.c:1295
static int NewTimerId(void)
Definition: timers.c:1131
cTimers(void)
Definition: timers.c:1110
Definition: tools.h:701
int Size(void) const
Definition: tools.h:754
void Sort(__compar_fn_t Compare)
Definition: tools.h:811
virtual void Append(T Data)
Definition: tools.h:774
cSetup Setup
Definition: config.c:372
#define TIMERMACRO_MATCH
Definition: config.h:56
#define TIMERMACRO_AFTER
Definition: config.h:57
#define TIMERPATTERN_BEGIN
Definition: config.h:60
#define TIMERMACRO_BEFORE
Definition: config.h:55
#define TIMERMACRO_EPISODE
Definition: config.h:54
#define DEFINSTRECTIME
Definition: config.h:51
#define TIMERPATTERN_AVOID
Definition: config.h:59
#define TIMERPATTERN_END
Definition: config.h:61
#define TIMERMACRO_TITLE
Definition: config.h:53
#define LOCK_SCHEDULES_READ
Definition: epg.h:228
#define LOCK_SCHEDULES_WRITE
Definition: epg.h:229
#define tr(s)
Definition: i18n.h:85
#define trNOOP(s)
Definition: i18n.h:88
static int Utf8CharLen(const char *s)
Definition: si.c:400
@ RunningStatusNotRunning
Definition: si.h:197
cDoneRecordings DoneRecordingsPattern
Definition: recording.c:3307
#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:24
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:2906
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:729
#define FULLMATCH
Definition: timers.c:673
#define DELETE_EXPIRED_TIMEOUT
Definition: timers.c:1307
#define VPSGRACE
Definition: timers.c:24
static bool RemoteTimerError(const cTimer *Timer, cString *Msg)
Definition: timers.c:1441
#define EPGLIMITAFTER
Definition: timers.c:808
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:1509
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:1448
#define EPGLIMITBEFORE
Definition: timers.c:807
#define DAYBUFFERSIZE
#define LOCK_TIMERS_WRITE
Definition: timers.h:247
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
cString TimeString(time_t t)
Converts the given time to a string of the form "hh:mm".
Definition: tools.c:1301
const char * strgetlast(const char *s, char c)
Definition: tools.c:221
bool isempty(const char *s)
Definition: tools.c:357
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:915
bool startswith(const char *s, const char *p)
Definition: tools.c:337
char * strreplace(char *s, char c1, char c2)
Definition: tools.c:142
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:346
cString itoa(int n)
Definition: tools.c:450
bool isnumber(const char *s)
Definition: tools.c:372
char * skipspace(const char *s)
Definition: tools.h:244
#define SECSINDAY
Definition: tools.h:42
#define dsyslog(a...)
Definition: tools.h:37
#define MALLOC(type, size)
Definition: tools.h:47
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