vdr  1.7.31
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 2.12 2012/09/15 13:34:03 kls Exp $
8  */
9 
10 #include "timers.h"
11 #include <ctype.h>
12 #include "channels.h"
13 #include "device.h"
14 #include "i18n.h"
15 #include "libsi/si.h"
16 #include "recording.h"
17 #include "remote.h"
18 #include "status.h"
19 
20 #define VFAT_MAX_FILENAME 40 // same as MAX_SUBTITLE_LENGTH in recording.c
21 
22 // IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d'
23 // format characters in order to allow any number of blanks after a numeric
24 // value!
25 
26 // --- cTimer ----------------------------------------------------------------
27 
28 cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
29 {
30  startTime = stopTime = 0;
31  lastSetEvent = 0;
32  deferred = 0;
33  recording = pending = inVpsMargin = false;
34  flags = tfNone;
35  *file = 0;
36  aux = NULL;
37  event = NULL;
38  if (Instant)
40  channel = Channel ? Channel : Channels.GetByNumber(cDevice::CurrentChannel());
41  time_t t = time(NULL);
42  struct tm tm_r;
43  struct tm *now = localtime_r(&t, &tm_r);
44  day = SetTime(t, 0);
45  weekdays = 0;
46  start = now->tm_hour * 100 + now->tm_min;
47  stop = 0;
49  cSchedulesLock SchedulesLock;
50  if (const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock)) {
51  if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) {
52  if (const cEvent *Event = Schedule->GetPresentEvent()) {
53  time_t tstart = Event->StartTime();
54  time_t tstop = Event->EndTime();
55  if (Event->Vps() && Setup.UseVps) {
56  SetFlags(tfVps);
57  tstart = Event->Vps();
58  }
59  else {
60  tstop += Setup.MarginStop * 60;
61  tstart -= Setup.MarginStart * 60;
62  }
63  day = SetTime(tstart, 0);
64  struct tm *time = localtime_r(&tstart, &tm_r);
65  start = time->tm_hour * 100 + time->tm_min;
66  time = localtime_r(&tstop, &tm_r);
67  stop = time->tm_hour * 100 + time->tm_min;
68  SetEvent(Event);
69  }
70  }
71  }
72  }
73  if (!stop) {
74  stop = now->tm_hour * 60 + now->tm_min + (Setup.InstantRecordTime ? Setup.InstantRecordTime : DEFINSTRECTIME);
75  stop = (stop / 60) * 100 + (stop % 60);
76  }
77  if (stop >= 2400)
78  stop -= 2400;
81  if (Instant && channel)
82  snprintf(file, sizeof(file), "%s%s", Setup.MarkInstantRecord ? "@" : "", *Setup.NameInstantRecord ? Setup.NameInstantRecord : channel->Name());
84  dsyslog("timer file name too long for VFAT file system: '%s'", file);
86  dsyslog("timer file name truncated to '%s'", file);
87  }
88 }
89 
90 cTimer::cTimer(const cEvent *Event)
91 {
92  startTime = stopTime = 0;
93  lastSetEvent = 0;
94  deferred = 0;
95  recording = pending = inVpsMargin = false;
96  flags = tfActive;
97  *file = 0;
98  aux = NULL;
99  event = NULL;
100  if (Event->Vps() && Setup.UseVps)
101  SetFlags(tfVps);
102  channel = Channels.GetByChannelID(Event->ChannelID(), true);
103  time_t tstart = (flags & tfVps) ? Event->Vps() : Event->StartTime();
104  time_t tstop = tstart + Event->Duration();
105  if (!(HasFlags(tfVps))) {
106  tstop += Setup.MarginStop * 60;
107  tstart -= Setup.MarginStart * 60;
108  }
109  struct tm tm_r;
110  struct tm *time = localtime_r(&tstart, &tm_r);
111  day = SetTime(tstart, 0);
112  weekdays = 0;
113  start = time->tm_hour * 100 + time->tm_min;
114  time = localtime_r(&tstop, &tm_r);
115  stop = time->tm_hour * 100 + time->tm_min;
116  if (stop >= 2400)
117  stop -= 2400;
120  const char *Title = Event->Title();
121  if (!isempty(Title))
122  Utf8Strn0Cpy(file, Event->Title(), sizeof(file));
124  dsyslog("timer file name too long for VFAT file system: '%s'", file);
126  dsyslog("timer file name truncated to '%s'", file);
127  }
128  SetEvent(Event);
129 }
130 
131 cTimer::cTimer(const cTimer &Timer)
132 {
133  channel = NULL;
134  aux = NULL;
135  event = NULL;
136  flags = tfNone;
137  *this = Timer;
138 }
139 
141 {
142  free(aux);
143 }
144 
146 {
147  if (&Timer != this) {
148  uint OldFlags = flags & tfRecording;
149  startTime = Timer.startTime;
150  stopTime = Timer.stopTime;
151  lastSetEvent = 0;
152  deferred = 0;
153  recording = Timer.recording;
154  pending = Timer.pending;
155  inVpsMargin = Timer.inVpsMargin;
156  flags = Timer.flags | OldFlags;
157  channel = Timer.channel;
158  day = Timer.day;
159  weekdays = Timer.weekdays;
160  start = Timer.start;
161  stop = Timer.stop;
162  priority = Timer.priority;
163  lifetime = Timer.lifetime;
164  strncpy(file, Timer.file, sizeof(file));
165  free(aux);
166  aux = Timer.aux ? strdup(Timer.aux) : NULL;
167  event = NULL;
168  }
169  return *this;
170 }
171 
172 int cTimer::Compare(const cListObject &ListObject) const
173 {
174  const cTimer *ti = (const cTimer *)&ListObject;
175  time_t t1 = StartTime();
176  time_t t2 = ti->StartTime();
177  int r = t1 - t2;
178  if (r == 0)
179  r = ti->priority - priority;
180  return r;
181 }
182 
183 cString cTimer::ToText(bool UseChannelID) const
184 {
185  strreplace(file, ':', '|');
186  cString buffer = cString::sprintf("%u:%s:%s:%04d:%04d:%d:%d:%s:%s\n", flags, UseChannelID ? *Channel()->GetChannelID().ToString() : *itoa(Channel()->Number()), *PrintDay(day, weekdays, true), start, stop, priority, lifetime, file, aux ? aux : "");
187  strreplace(file, '|', ':');
188  return buffer;
189 }
190 
192 {
193  return cString::sprintf("%d (%d %04d-%04d %s'%s')", Index() + 1, Channel()->Number(), start, stop, HasFlags(tfVps) ? "VPS " : "", file);
194 }
195 
197 {
198  return (t / 100 * 60 + t % 100) * 60;
199 }
200 
201 bool cTimer::ParseDay(const char *s, time_t &Day, int &WeekDays)
202 {
203  // possible formats are:
204  // 19
205  // 2005-03-19
206  // MTWTFSS
207  // MTWTFSS@19
208  // MTWTFSS@2005-03-19
209 
210  Day = 0;
211  WeekDays = 0;
212  s = skipspace(s);
213  if (!*s)
214  return false;
215  const char *a = strchr(s, '@');
216  const char *d = a ? a + 1 : isdigit(*s) ? s : NULL;
217  if (d) {
218  if (strlen(d) == 10) {
219  struct tm tm_r;
220  if (3 == sscanf(d, "%d-%d-%d", &tm_r.tm_year, &tm_r.tm_mon, &tm_r.tm_mday)) {
221  tm_r.tm_year -= 1900;
222  tm_r.tm_mon--;
223  tm_r.tm_hour = tm_r.tm_min = tm_r.tm_sec = 0;
224  tm_r.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
225  Day = mktime(&tm_r);
226  }
227  else
228  return false;
229  }
230  else {
231  // handle "day of month" for compatibility with older versions:
232  char *tail = NULL;
233  int day = strtol(d, &tail, 10);
234  if (tail && *tail || day < 1 || day > 31)
235  return false;
236  time_t t = time(NULL);
237  int DaysToCheck = 61; // 61 to handle months with 31/30/31
238  for (int i = -1; i <= DaysToCheck; i++) {
239  time_t t0 = IncDay(t, i);
240  if (GetMDay(t0) == day) {
241  Day = SetTime(t0, 0);
242  break;
243  }
244  }
245  }
246  }
247  if (a || !isdigit(*s)) {
248  if ((a && a - s == 7) || strlen(s) == 7) {
249  for (const char *p = s + 6; p >= s; p--) {
250  WeekDays <<= 1;
251  WeekDays |= (*p != '-');
252  }
253  }
254  else
255  return false;
256  }
257  return true;
258 }
259 
260 cString cTimer::PrintDay(time_t Day, int WeekDays, bool SingleByteChars)
261 {
262 #define DAYBUFFERSIZE 64
263  char buffer[DAYBUFFERSIZE];
264  char *b = buffer;
265  if (WeekDays) {
266  // TRANSLATORS: the first character of each weekday, beginning with monday
267  const char *w = trNOOP("MTWTFSS");
268  if (!SingleByteChars)
269  w = tr(w);
270  while (*w) {
271  int sl = Utf8CharLen(w);
272  if (WeekDays & 1) {
273  for (int i = 0; i < sl; i++)
274  b[i] = w[i];
275  b += sl;
276  }
277  else
278  *b++ = '-';
279  WeekDays >>= 1;
280  w += sl;
281  }
282  if (Day)
283  *b++ = '@';
284  }
285  if (Day) {
286  struct tm tm_r;
287  localtime_r(&Day, &tm_r);
288  b += strftime(b, DAYBUFFERSIZE - (b - buffer), "%Y-%m-%d", &tm_r);
289  }
290  *b = 0;
291  return buffer;
292 }
293 
295 {
296  if (weekdays) {
297  cString s = PrintDay(day, weekdays, true);
298  if (strlen(s) == 18)
299  return *s + 8;
300  }
301  return ""; // not NULL, so the caller can always use the result
302 }
303 
304 bool cTimer::Parse(const char *s)
305 {
306  char *channelbuffer = NULL;
307  char *daybuffer = NULL;
308  char *filebuffer = NULL;
309  free(aux);
310  aux = NULL;
311  //XXX Apparently sscanf() doesn't work correctly if the last %a argument
312  //XXX results in an empty string (this first occurred when the EIT gathering
313  //XXX was put into a separate thread - don't know why this happens...
314  //XXX As a cure we copy the original string and add a blank.
315  //XXX If anybody can shed some light on why sscanf() failes here, I'd love
316  //XXX to hear about that!
317  char *s2 = NULL;
318  int l2 = strlen(s);
319  while (l2 > 0 && isspace(s[l2 - 1]))
320  l2--;
321  if (s[l2 - 1] == ':') {
322  s2 = MALLOC(char, l2 + 3);
323  strcat(strn0cpy(s2, s, l2 + 1), " \n");
324  s = s2;
325  }
326  bool result = false;
327  if (8 <= sscanf(s, "%u :%a[^:]:%a[^:]:%d :%d :%d :%d :%a[^:\n]:%a[^\n]", &flags, &channelbuffer, &daybuffer, &start, &stop, &priority, &lifetime, &filebuffer, &aux)) {
329  if (aux && !*skipspace(aux)) {
330  free(aux);
331  aux = NULL;
332  }
333  //TODO add more plausibility checks
334  result = ParseDay(daybuffer, day, weekdays);
335  if (VfatFileSystem) {
336  char *p = strrchr(filebuffer, FOLDERDELIMCHAR);
337  if (p)
338  p++;
339  else
340  p = filebuffer;
341  if (Utf8StrLen(p) > VFAT_MAX_FILENAME) {
342  dsyslog("timer file name too long for VFAT file system: '%s'", p);
343  p[Utf8SymChars(p, VFAT_MAX_FILENAME)] = 0;
344  dsyslog("timer file name truncated to '%s'", p);
345  }
346  }
347  Utf8Strn0Cpy(file, filebuffer, sizeof(file));
348  strreplace(file, '|', ':');
349  if (isnumber(channelbuffer))
350  channel = Channels.GetByNumber(atoi(channelbuffer));
351  else
352  channel = Channels.GetByChannelID(tChannelID::FromString(channelbuffer), true, true);
353  if (!channel) {
354  esyslog("ERROR: channel %s not defined", channelbuffer);
355  result = false;
356  }
357  }
358  free(channelbuffer);
359  free(daybuffer);
360  free(filebuffer);
361  free(s2);
362  return result;
363 }
364 
365 bool cTimer::Save(FILE *f)
366 {
367  return fprintf(f, "%s", *ToText(true)) > 0;
368 }
369 
370 bool cTimer::IsSingleEvent(void) const
371 {
372  return !weekdays;
373 }
374 
375 int cTimer::GetMDay(time_t t)
376 {
377  struct tm tm_r;
378  return localtime_r(&t, &tm_r)->tm_mday;
379 }
380 
381 int cTimer::GetWDay(time_t t)
382 {
383  struct tm tm_r;
384  int weekday = localtime_r(&t, &tm_r)->tm_wday;
385  return weekday == 0 ? 6 : weekday - 1; // we start with Monday==0!
386 }
387 
388 bool cTimer::DayMatches(time_t t) const
389 {
390  return IsSingleEvent() ? SetTime(t, 0) == day : (weekdays & (1 << GetWDay(t))) != 0;
391 }
392 
393 time_t cTimer::IncDay(time_t t, int Days)
394 {
395  struct tm tm_r;
396  tm tm = *localtime_r(&t, &tm_r);
397  tm.tm_mday += Days; // now tm_mday may be out of its valid range
398  int h = tm.tm_hour; // save original hour to compensate for DST change
399  tm.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
400  t = mktime(&tm); // normalize all values
401  tm.tm_hour = h; // compensate for DST change
402  return mktime(&tm); // calculate final result
403 }
404 
405 time_t cTimer::SetTime(time_t t, int SecondsFromMidnight)
406 {
407  struct tm tm_r;
408  tm tm = *localtime_r(&t, &tm_r);
409  tm.tm_hour = SecondsFromMidnight / 3600;
410  tm.tm_min = (SecondsFromMidnight % 3600) / 60;
411  tm.tm_sec = SecondsFromMidnight % 60;
412  tm.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
413  return mktime(&tm);
414 }
415 
416 void cTimer::SetFile(const char *File)
417 {
418  if (!isempty(File))
419  Utf8Strn0Cpy(file, File, sizeof(file));
420 }
421 
422 #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)
423 
424 bool cTimer::Matches(time_t t, bool Directly, int Margin) const
425 {
426  startTime = stopTime = 0;
427  if (t == 0)
428  t = time(NULL);
429 
430  int begin = TimeToInt(start); // seconds from midnight
431  int length = TimeToInt(stop) - begin;
432  if (length < 0)
433  length += SECSINDAY;
434 
435  if (IsSingleEvent()) {
436  startTime = SetTime(day, begin);
437  stopTime = startTime + length;
438  }
439  else {
440  for (int i = -1; i <= 7; i++) {
441  time_t t0 = IncDay(day ? max(day, t) : t, i);
442  if (DayMatches(t0)) {
443  time_t a = SetTime(t0, begin);
444  time_t b = a + length;
445  if ((!day || a >= day) && t < b) {
446  startTime = a;
447  stopTime = b;
448  break;
449  }
450  }
451  }
452  if (!startTime)
453  startTime = IncDay(t, 7); // just to have something that's more than a week in the future
454  else if (!Directly && (t > startTime || t > day + SECSINDAY + 3600)) // +3600 in case of DST change
455  day = 0;
456  }
457 
458  if (t < deferred)
459  return false;
460  deferred = 0;
461 
462  if (HasFlags(tfActive)) {
463  if (HasFlags(tfVps) && event && event->Vps()) {
464  if (Margin || !Directly) {
465  startTime = event->StartTime();
466  stopTime = event->EndTime();
467  if (!Margin) { // this is an actual check
468  if (event->Schedule()->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) // VPS control can only work with up-to-date events...
469  return event->IsRunning(true);
470  else
471  return startTime <= t && t < stopTime; // ...otherwise we fall back to normal timer handling
472  }
473  }
474  }
475  return startTime <= t + Margin && t < stopTime; // must stop *before* stopTime to allow adjacent timers
476  }
477  return false;
478 }
479 
480 #define FULLMATCH 1000
481 
482 int cTimer::Matches(const cEvent *Event, int *Overlap) const
483 {
484  // Overlap is the percentage of the Event's duration that is covered by
485  // this timer (based on FULLMATCH for finer granularity than just 100).
486  // To make sure a VPS timer can be distinguished from a plain 100% overlap,
487  // it gets an additional 100 added, and a VPS event that is actually running
488  // gets 200 added to the FULLMATCH.
489  if (HasFlags(tfActive) && channel->GetChannelID() == Event->ChannelID()) {
490  bool UseVps = HasFlags(tfVps) && Event->Vps();
491  Matches(UseVps ? Event->Vps() : Event->StartTime(), true);
492  int overlap = 0;
493  if (UseVps)
494  overlap = (startTime == Event->Vps()) ? FULLMATCH + (Event->IsRunning() ? 200 : 100) : 0;
495  if (!overlap) {
496  if (startTime <= Event->StartTime() && Event->EndTime() <= stopTime)
497  overlap = FULLMATCH;
498  else if (stopTime <= Event->StartTime() || Event->EndTime() <= startTime)
499  overlap = 0;
500  else
501  overlap = (min(stopTime, Event->EndTime()) - max(startTime, Event->StartTime())) * FULLMATCH / max(Event->Duration(), 1);
502  }
503  startTime = stopTime = 0;
504  if (Overlap)
505  *Overlap = overlap;
506  if (UseVps)
507  return overlap > FULLMATCH ? tmFull : tmNone;
508  return overlap >= FULLMATCH ? tmFull : overlap > 0 ? tmPartial : tmNone;
509  }
510  return tmNone;
511 }
512 
513 #define EXPIRELATENCY 60 // seconds (just in case there's a short glitch in the VPS signal)
514 
515 bool cTimer::Expired(void) const
516 {
517  return IsSingleEvent() && !Recording() && StopTime() + EXPIRELATENCY <= time(NULL) && (!HasFlags(tfVps) || !event || !event->Vps());
518 }
519 
520 time_t cTimer::StartTime(void) const
521 {
522  if (!startTime)
523  Matches();
524  return startTime;
525 }
526 
527 time_t cTimer::StopTime(void) const
528 {
529  if (!stopTime)
530  Matches();
531  return stopTime;
532 }
533 
534 #define EPGLIMITBEFORE (1 * 3600) // Time in seconds before a timer's start time and
535 #define EPGLIMITAFTER (1 * 3600) // after its stop time within which EPG events will be taken into consideration.
536 
538 {
539  cSchedulesLock SchedulesLock;
540  if (!Schedules) {
541  lastSetEvent = 0; // forces setting the event, even if the schedule hasn't been modified
542  if (!(Schedules = cSchedules::Schedules(SchedulesLock)))
543  return;
544  }
545  const cSchedule *Schedule = Schedules->GetSchedule(Channel());
546  if (Schedule && Schedule->Events()->First()) {
547  time_t now = time(NULL);
548  if (!lastSetEvent || Schedule->Modified() >= lastSetEvent) {
549  lastSetEvent = now;
550  const cEvent *Event = NULL;
551  if (HasFlags(tfVps) && Schedule->Events()->First()->Vps()) {
552  if (event && Recording())
553  return; // let the recording end first
554  // VPS timers only match if their start time exactly matches the event's VPS time:
555  for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
556  if (e->StartTime() && e->RunningStatus() != SI::RunningStatusNotRunning) { // skip outdated events
557  int overlap = 0;
558  Matches(e, &overlap);
559  if (overlap > FULLMATCH) {
560  Event = e;
561  break; // take the first matching event
562  }
563  }
564  }
565  if (!Event && event && (now <= event->EndTime() || Matches(0, true)))
566  return; // stay with the old event until the timer has completely expired
567  }
568  else {
569  // Normal timers match the event they have the most overlap with:
570  int Overlap = 0;
571  // Set up the time frame within which to check events:
572  Matches(0, true);
573  time_t TimeFrameBegin = StartTime() - EPGLIMITBEFORE;
574  time_t TimeFrameEnd = StopTime() + EPGLIMITAFTER;
575  for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
576  if (e->EndTime() < TimeFrameBegin)
577  continue; // skip events way before the timer starts
578  if (e->StartTime() > TimeFrameEnd)
579  break; // the rest is way after the timer ends
580  int overlap = 0;
581  Matches(e, &overlap);
582  if (overlap && overlap >= Overlap) {
583  if (Event && overlap == Overlap && e->Duration() <= Event->Duration())
584  continue; // if overlap is the same, we take the longer event
585  Overlap = overlap;
586  Event = e;
587  }
588  }
589  }
590  SetEvent(Event);
591  }
592  }
593 }
594 
595 void cTimer::SetEvent(const cEvent *Event)
596 {
597  if (event != Event) { //XXX TODO check event data, too???
598  if (Event)
599  isyslog("timer %s set to event %s", *ToDescr(), *Event->ToDescr());
600  else
601  isyslog("timer %s set to no event", *ToDescr());
602  event = Event;
603  }
604 }
605 
606 void cTimer::SetRecording(bool Recording)
607 {
609  if (recording)
611  else
613  isyslog("timer %s %s", *ToDescr(), recording ? "start" : "stop");
614 }
615 
616 void cTimer::SetPending(bool Pending)
617 {
618  pending = Pending;
619 }
620 
621 void cTimer::SetInVpsMargin(bool InVpsMargin)
622 {
623  if (InVpsMargin && !inVpsMargin)
624  isyslog("timer %s entered VPS margin", *ToDescr());
626 }
627 
628 void cTimer::SetDay(time_t Day)
629 {
630  day = Day;
631 }
632 
633 void cTimer::SetWeekDays(int WeekDays)
634 {
635  weekdays = WeekDays;
636 }
637 
638 void cTimer::SetStart(int Start)
639 {
640  start = Start;
641 }
642 
643 void cTimer::SetStop(int Stop)
644 {
645  stop = Stop;
646 }
647 
648 void cTimer::SetPriority(int Priority)
649 {
650  priority = Priority;
651 }
652 
653 void cTimer::SetLifetime(int Lifetime)
654 {
655  lifetime = Lifetime;
656 }
657 
658 void cTimer::SetAux(const char *Aux)
659 {
660  free(aux);
661  aux = strdup(Aux);
662 }
663 
664 void cTimer::SetDeferred(int Seconds)
665 {
666  deferred = time(NULL) + Seconds;
667  isyslog("timer %s deferred for %d seconds", *ToDescr(), Seconds);
668 }
669 
670 void cTimer::SetFlags(uint Flags)
671 {
672  flags |= Flags;
673 }
674 
675 void cTimer::ClrFlags(uint Flags)
676 {
677  flags &= ~Flags;
678 }
679 
680 void cTimer::InvFlags(uint Flags)
681 {
682  flags ^= Flags;
683 }
684 
685 bool cTimer::HasFlags(uint Flags) const
686 {
687  return (flags & Flags) == Flags;
688 }
689 
690 void cTimer::Skip(void)
691 {
692  day = IncDay(SetTime(StartTime(), 0), 1);
693  startTime = 0;
694  SetEvent(NULL);
695 }
696 
697 void cTimer::OnOff(void)
698 {
699  if (IsSingleEvent())
701  else if (day) {
702  day = 0;
704  }
705  else if (HasFlags(tfActive))
706  Skip();
707  else
709  SetEvent(NULL);
710  Matches(); // refresh start and end time
711 }
712 
713 // --- cTimers ---------------------------------------------------------------
714 
716 
718 {
719  state = 0;
720  beingEdited = 0;;
721  lastSetEvents = 0;
722  lastDeleteExpired = 0;
723 }
724 
726 {
727  for (cTimer *ti = First(); ti; ti = Next(ti)) {
728  if (ti->Channel() == Timer->Channel() &&
729  (ti->WeekDays() && ti->WeekDays() == Timer->WeekDays() || !ti->WeekDays() && ti->Day() == Timer->Day()) &&
730  ti->Start() == Timer->Start() &&
731  ti->Stop() == Timer->Stop())
732  return ti;
733  }
734  return NULL;
735 }
736 
738 {
739  static int LastPending = -1;
740  cTimer *t0 = NULL;
741  for (cTimer *ti = First(); ti; ti = Next(ti)) {
742  if (!ti->Recording() && ti->Matches(t)) {
743  if (ti->Pending()) {
744  if (ti->Index() > LastPending)
745  LastPending = ti->Index();
746  else
747  continue;
748  }
749  if (!t0 || ti->Priority() > t0->Priority())
750  t0 = ti;
751  }
752  }
753  if (!t0)
754  LastPending = -1;
755  return t0;
756 }
757 
758 cTimer *cTimers::GetMatch(const cEvent *Event, int *Match)
759 {
760  cTimer *t = NULL;
761  int m = tmNone;
762  for (cTimer *ti = First(); ti; ti = Next(ti)) {
763  int tm = ti->Matches(Event);
764  if (tm > m) {
765  t = ti;
766  m = tm;
767  if (m == tmFull)
768  break;
769  }
770  }
771  if (Match)
772  *Match = m;
773  return t;
774 }
775 
777 {
778  cTimer *t0 = NULL;
779  for (cTimer *ti = First(); ti; ti = Next(ti)) {
780  ti->Matches();
781  if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
782  t0 = ti;
783  }
784  return t0;
785 }
786 
788 {
790  state++;
791 }
792 
793 void cTimers::Add(cTimer *Timer, cTimer *After)
794 {
795  cConfig<cTimer>::Add(Timer, After);
797 }
798 
799 void cTimers::Ins(cTimer *Timer, cTimer *Before)
800 {
801  cConfig<cTimer>::Ins(Timer, Before);
803 }
804 
805 void cTimers::Del(cTimer *Timer, bool DeleteObject)
806 {
808  cConfig<cTimer>::Del(Timer, DeleteObject);
809 }
810 
811 bool cTimers::Modified(int &State)
812 {
813  bool Result = state != State;
814  State = state;
815  return Result;
816 }
817 
819 {
820  if (time(NULL) - lastSetEvents < 5)
821  return;
822  cSchedulesLock SchedulesLock(false, 100);
823  const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
824  if (Schedules) {
825  if (!lastSetEvents || Schedules->Modified() >= lastSetEvents) {
826  for (cTimer *ti = First(); ti; ti = Next(ti)) {
827  if (cRemote::HasKeys())
828  return; // react immediately on user input
829  ti->SetEventFromSchedule(Schedules);
830  }
831  }
832  }
833  lastSetEvents = time(NULL);
834 }
835 
837 {
838  if (time(NULL) - lastDeleteExpired < 30)
839  return;
840  cTimer *ti = First();
841  while (ti) {
842  cTimer *next = Next(ti);
843  if (ti->Expired()) {
844  isyslog("deleting timer %s", *ti->ToDescr());
845  Del(ti);
846  SetModified();
847  }
848  ti = next;
849  }
850  lastDeleteExpired = time(NULL);
851 }
852 
853 // --- cSortedTimers ---------------------------------------------------------
854 
855 static int CompareTimers(const void *a, const void *b)
856 {
857  return (*(const cTimer **)a)->Compare(**(const cTimer **)b);
858 }
859 
861 :cVector<const cTimer *>(Timers.Count())
862 {
863  for (const cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer))
864  Append(Timer);
866 }