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