vdr  2.2.0
epg.c
Go to the documentation of this file.
1 /*
2  * epg.c: Electronic Program Guide
3  *
4  * See the main source file 'vdr.c' for copyright information and
5  * how to reach the author.
6  *
7  * Original version (as used in VDR before 1.3.0) written by
8  * Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
9  *
10  * $Id: epg.c 3.3 2013/12/28 11:33:08 kls Exp $
11  */
12 
13 #include "epg.h"
14 #include <ctype.h>
15 #include <limits.h>
16 #include <time.h>
17 #include "libsi/si.h"
18 #include "timers.h"
19 
20 #define RUNNINGSTATUSTIMEOUT 30 // seconds before the running status is considered unknown
21 #define EPGDATAWRITEDELTA 600 // seconds between writing the epg.data file
22 
23 // --- tComponent ------------------------------------------------------------
24 
26 {
27  char buffer[256];
28  snprintf(buffer, sizeof(buffer), "%X %02X %s %s", stream, type, language, description ? description : "");
29  return buffer;
30 }
31 
32 bool tComponent::FromString(const char *s)
33 {
34  unsigned int Stream, Type;
35  int n = sscanf(s, "%X %02X %7s %m[^\n]", &Stream, &Type, language, &description); // 7 = MAXLANGCODE2 - 1
36  if (n != 4 || isempty(description)) {
37  free(description);
38  description = NULL;
39  }
40  stream = Stream;
41  type = Type;
42  return n >= 3;
43 }
44 
45 // --- cComponents -----------------------------------------------------------
46 
48 {
49  numComponents = 0;
50  components = NULL;
51 }
52 
54 {
55  for (int i = 0; i < numComponents; i++)
56  free(components[i].description);
57  free(components);
58 }
59 
60 bool cComponents::Realloc(int Index)
61 {
62  if (Index >= numComponents) {
63  Index++;
64  if (tComponent *NewBuffer = (tComponent *)realloc(components, Index * sizeof(tComponent))) {
65  int n = numComponents;
66  numComponents = Index;
67  components = NewBuffer;
68  memset(&components[n], 0, sizeof(tComponent) * (numComponents - n));
69  }
70  else {
71  esyslog("ERROR: out of memory");
72  return false;
73  }
74  }
75  return true;
76 }
77 
78 void cComponents::SetComponent(int Index, const char *s)
79 {
80  if (Realloc(Index))
81  components[Index].FromString(s);
82 }
83 
84 void cComponents::SetComponent(int Index, uchar Stream, uchar Type, const char *Language, const char *Description)
85 {
86  if (!Realloc(Index))
87  return;
88  tComponent *p = &components[Index];
89  p->stream = Stream;
90  p->type = Type;
91  strn0cpy(p->language, Language, sizeof(p->language));
92  char *q = strchr(p->language, ',');
93  if (q)
94  *q = 0; // strips rest of "normalized" language codes
95  p->description = strcpyrealloc(p->description, !isempty(Description) ? Description : NULL);
96 }
97 
99 {
100  for (int i = 0; i < numComponents; i++) {
101  if (components[i].stream == Stream && (
102  Type == 0 || // don't care about the actual Type
103  Stream == 2 && (components[i].type < 5) == (Type < 5) // fallback "Dolby" component according to the "Premiere pseudo standard"
104  )) {
105  if (!Index--)
106  return &components[i];
107  }
108  }
109  return NULL;
110 }
111 
112 // --- cEvent ----------------------------------------------------------------
113 
115 {
116  schedule = NULL;
117  eventID = EventID;
118  tableID = 0xFF; // actual table ids are 0x4E..0x60
119  version = 0xFF; // actual version numbers are 0..31
120  runningStatus = SI::RunningStatusUndefined;
121  title = NULL;
122  shortText = NULL;
123  description = NULL;
124  components = NULL;
125  memset(contents, 0, sizeof(contents));
126  parentalRating = 0;
127  startTime = 0;
128  duration = 0;
129  vps = 0;
130  SetSeen();
131 }
132 
134 {
135  free(title);
136  free(shortText);
137  free(description);
138  delete components;
139 }
140 
141 int cEvent::Compare(const cListObject &ListObject) const
142 {
143  cEvent *e = (cEvent *)&ListObject;
144  return startTime - e->startTime;
145 }
146 
148 {
149  return schedule ? schedule->ChannelID() : tChannelID();
150 }
151 
153 {
154  if (eventID != EventID) {
155  if (schedule)
156  schedule->UnhashEvent(this);
157  eventID = EventID;
158  if (schedule)
159  schedule->HashEvent(this);
160  }
161 }
162 
164 {
165  tableID = TableID;
166 }
167 
169 {
170  version = Version;
171 }
172 
174 {
175  if (Channel && runningStatus != RunningStatus && (RunningStatus > SI::RunningStatusNotRunning || runningStatus > SI::RunningStatusUndefined) && Channel->HasTimer())
176  isyslog("channel %d (%s) event %s status %d", Channel->Number(), Channel->Name(), *ToDescr(), RunningStatus);
177  runningStatus = RunningStatus;
178 }
179 
180 void cEvent::SetTitle(const char *Title)
181 {
182  title = strcpyrealloc(title, Title);
183 }
184 
185 void cEvent::SetShortText(const char *ShortText)
186 {
187  shortText = strcpyrealloc(shortText, ShortText);
188 }
189 
190 void cEvent::SetDescription(const char *Description)
191 {
192  description = strcpyrealloc(description, Description);
193 }
194 
196 {
197  delete components;
198  components = Components;
199 }
200 
201 void cEvent::SetContents(uchar *Contents)
202 {
203  for (int i = 0; i < MaxEventContents; i++)
204  contents[i] = Contents[i];
205 }
206 
207 void cEvent::SetParentalRating(int ParentalRating)
208 {
209  parentalRating = ParentalRating;
210 }
211 
212 void cEvent::SetStartTime(time_t StartTime)
213 {
214  if (startTime != StartTime) {
215  if (schedule)
216  schedule->UnhashEvent(this);
217  startTime = StartTime;
218  if (schedule)
219  schedule->HashEvent(this);
220  }
221 }
222 
223 void cEvent::SetDuration(int Duration)
224 {
225  duration = Duration;
226 }
227 
228 void cEvent::SetVps(time_t Vps)
229 {
230  vps = Vps;
231 }
232 
233 void cEvent::SetSeen(void)
234 {
235  seen = time(NULL);
236 }
237 
239 {
240  char vpsbuf[64] = "";
241  if (Vps())
242  sprintf(vpsbuf, "(VPS: %s) ", *GetVpsString());
243  return cString::sprintf("%s %s-%s %s'%s'", *GetDateString(), *GetTimeString(), *GetEndTimeString(), vpsbuf, Title());
244 }
245 
246 bool cEvent::HasTimer(void) const
247 {
248  for (cTimer *t = Timers.First(); t; t = Timers.Next(t)) {
249  if (t->Event() == this)
250  return true;
251  }
252  return false;
253 }
254 
255 bool cEvent::IsRunning(bool OrAboutToStart) const
256 {
257  return runningStatus >= (OrAboutToStart ? SI::RunningStatusStartsInAFewSeconds : SI::RunningStatusPausing);
258 }
259 
260 const char *cEvent::ContentToString(uchar Content)
261 {
262  switch (Content & 0xF0) {
263  case ecgMovieDrama:
264  switch (Content & 0x0F) {
265  default:
266  case 0x00: return tr("Content$Movie/Drama");
267  case 0x01: return tr("Content$Detective/Thriller");
268  case 0x02: return tr("Content$Adventure/Western/War");
269  case 0x03: return tr("Content$Science Fiction/Fantasy/Horror");
270  case 0x04: return tr("Content$Comedy");
271  case 0x05: return tr("Content$Soap/Melodrama/Folkloric");
272  case 0x06: return tr("Content$Romance");
273  case 0x07: return tr("Content$Serious/Classical/Religious/Historical Movie/Drama");
274  case 0x08: return tr("Content$Adult Movie/Drama");
275  }
276  break;
278  switch (Content & 0x0F) {
279  default:
280  case 0x00: return tr("Content$News/Current Affairs");
281  case 0x01: return tr("Content$News/Weather Report");
282  case 0x02: return tr("Content$News Magazine");
283  case 0x03: return tr("Content$Documentary");
284  case 0x04: return tr("Content$Discussion/Inverview/Debate");
285  }
286  break;
287  case ecgShow:
288  switch (Content & 0x0F) {
289  default:
290  case 0x00: return tr("Content$Show/Game Show");
291  case 0x01: return tr("Content$Game Show/Quiz/Contest");
292  case 0x02: return tr("Content$Variety Show");
293  case 0x03: return tr("Content$Talk Show");
294  }
295  break;
296  case ecgSports:
297  switch (Content & 0x0F) {
298  default:
299  case 0x00: return tr("Content$Sports");
300  case 0x01: return tr("Content$Special Event");
301  case 0x02: return tr("Content$Sport Magazine");
302  case 0x03: return tr("Content$Football/Soccer");
303  case 0x04: return tr("Content$Tennis/Squash");
304  case 0x05: return tr("Content$Team Sports");
305  case 0x06: return tr("Content$Athletics");
306  case 0x07: return tr("Content$Motor Sport");
307  case 0x08: return tr("Content$Water Sport");
308  case 0x09: return tr("Content$Winter Sports");
309  case 0x0A: return tr("Content$Equestrian");
310  case 0x0B: return tr("Content$Martial Sports");
311  }
312  break;
313  case ecgChildrenYouth:
314  switch (Content & 0x0F) {
315  default:
316  case 0x00: return tr("Content$Children's/Youth Programme");
317  case 0x01: return tr("Content$Pre-school Children's Programme");
318  case 0x02: return tr("Content$Entertainment Programme for 6 to 14");
319  case 0x03: return tr("Content$Entertainment Programme for 10 to 16");
320  case 0x04: return tr("Content$Informational/Educational/School Programme");
321  case 0x05: return tr("Content$Cartoons/Puppets");
322  }
323  break;
324  case ecgMusicBalletDance:
325  switch (Content & 0x0F) {
326  default:
327  case 0x00: return tr("Content$Music/Ballet/Dance");
328  case 0x01: return tr("Content$Rock/Pop");
329  case 0x02: return tr("Content$Serious/Classical Music");
330  case 0x03: return tr("Content$Folk/Tradional Music");
331  case 0x04: return tr("Content$Jazz");
332  case 0x05: return tr("Content$Musical/Opera");
333  case 0x06: return tr("Content$Ballet");
334  }
335  break;
336  case ecgArtsCulture:
337  switch (Content & 0x0F) {
338  default:
339  case 0x00: return tr("Content$Arts/Culture");
340  case 0x01: return tr("Content$Performing Arts");
341  case 0x02: return tr("Content$Fine Arts");
342  case 0x03: return tr("Content$Religion");
343  case 0x04: return tr("Content$Popular Culture/Traditional Arts");
344  case 0x05: return tr("Content$Literature");
345  case 0x06: return tr("Content$Film/Cinema");
346  case 0x07: return tr("Content$Experimental Film/Video");
347  case 0x08: return tr("Content$Broadcasting/Press");
348  case 0x09: return tr("Content$New Media");
349  case 0x0A: return tr("Content$Arts/Culture Magazine");
350  case 0x0B: return tr("Content$Fashion");
351  }
352  break;
354  switch (Content & 0x0F) {
355  default:
356  case 0x00: return tr("Content$Social/Political/Economics");
357  case 0x01: return tr("Content$Magazine/Report/Documentary");
358  case 0x02: return tr("Content$Economics/Social Advisory");
359  case 0x03: return tr("Content$Remarkable People");
360  }
361  break;
363  switch (Content & 0x0F) {
364  default:
365  case 0x00: return tr("Content$Education/Science/Factual");
366  case 0x01: return tr("Content$Nature/Animals/Environment");
367  case 0x02: return tr("Content$Technology/Natural Sciences");
368  case 0x03: return tr("Content$Medicine/Physiology/Psychology");
369  case 0x04: return tr("Content$Foreign Countries/Expeditions");
370  case 0x05: return tr("Content$Social/Spiritual Sciences");
371  case 0x06: return tr("Content$Further Education");
372  case 0x07: return tr("Content$Languages");
373  }
374  break;
375  case ecgLeisureHobbies:
376  switch (Content & 0x0F) {
377  default:
378  case 0x00: return tr("Content$Leisure/Hobbies");
379  case 0x01: return tr("Content$Tourism/Travel");
380  case 0x02: return tr("Content$Handicraft");
381  case 0x03: return tr("Content$Motoring");
382  case 0x04: return tr("Content$Fitness & Health");
383  case 0x05: return tr("Content$Cooking");
384  case 0x06: return tr("Content$Advertisement/Shopping");
385  case 0x07: return tr("Content$Gardening");
386  }
387  break;
388  case ecgSpecial:
389  switch (Content & 0x0F) {
390  case 0x00: return tr("Content$Original Language");
391  case 0x01: return tr("Content$Black & White");
392  case 0x02: return tr("Content$Unpublished");
393  case 0x03: return tr("Content$Live Broadcast");
394  default: ;
395  }
396  break;
397  default: ;
398  }
399  return "";
400 }
401 
403 {
404  if (parentalRating)
405  return cString::sprintf(tr("ParentalRating$from %d"), parentalRating);
406  return NULL;
407 }
408 
410 {
411  return DateString(startTime);
412 }
413 
415 {
416  return TimeString(startTime);
417 }
418 
420 {
421  return TimeString(startTime + duration);
422 }
423 
425 {
426  char buf[25];
427  struct tm tm_r;
428  strftime(buf, sizeof(buf), "%d.%m. %R", localtime_r(&vps, &tm_r));
429  return buf;
430 }
431 
432 void cEvent::Dump(FILE *f, const char *Prefix, bool InfoOnly) const
433 {
434  if (InfoOnly || startTime + duration + Setup.EPGLinger * 60 >= time(NULL)) {
435  fprintf(f, "%sE %u %ld %d %X %X\n", Prefix, eventID, startTime, duration, tableID, version);
436  if (!isempty(title))
437  fprintf(f, "%sT %s\n", Prefix, title);
438  if (!isempty(shortText))
439  fprintf(f, "%sS %s\n", Prefix, shortText);
440  if (!isempty(description)) {
441  strreplace(description, '\n', '|');
442  fprintf(f, "%sD %s\n", Prefix, description);
443  strreplace(description, '|', '\n');
444  }
445  if (contents[0]) {
446  fprintf(f, "%sG", Prefix);
447  for (int i = 0; Contents(i); i++)
448  fprintf(f, " %02X", Contents(i));
449  fprintf(f, "\n");
450  }
451  if (parentalRating)
452  fprintf(f, "%sR %d\n", Prefix, parentalRating);
453  if (components) {
454  for (int i = 0; i < components->NumComponents(); i++) {
455  tComponent *p = components->Component(i);
456  fprintf(f, "%sX %s\n", Prefix, *p->ToString());
457  }
458  }
459  if (vps)
460  fprintf(f, "%sV %ld\n", Prefix, vps);
461  if (!InfoOnly)
462  fprintf(f, "%se\n", Prefix);
463  }
464 }
465 
466 bool cEvent::Parse(char *s)
467 {
468  char *t = skipspace(s + 1);
469  switch (*s) {
470  case 'T': SetTitle(t);
471  break;
472  case 'S': SetShortText(t);
473  break;
474  case 'D': strreplace(t, '|', '\n');
475  SetDescription(t);
476  break;
477  case 'G': {
478  memset(contents, 0, sizeof(contents));
479  for (int i = 0; i < MaxEventContents; i++) {
480  char *tail = NULL;
481  int c = strtol(t, &tail, 16);
482  if (0x00 < c && c <= 0xFF) {
483  contents[i] = c;
484  t = tail;
485  }
486  else
487  break;
488  }
489  }
490  break;
491  case 'R': SetParentalRating(atoi(t));
492  break;
493  case 'X': if (!components)
494  components = new cComponents;
495  components->SetComponent(components->NumComponents(), t);
496  break;
497  case 'V': SetVps(atoi(t));
498  break;
499  default: esyslog("ERROR: unexpected tag while reading EPG data: %s", s);
500  return false;
501  }
502  return true;
503 }
504 
505 bool cEvent::Read(FILE *f, cSchedule *Schedule)
506 {
507  if (Schedule) {
508  cEvent *Event = NULL;
509  char *s;
510  int line = 0;
511  cReadLine ReadLine;
512  while ((s = ReadLine.Read(f)) != NULL) {
513  line++;
514  char *t = skipspace(s + 1);
515  switch (*s) {
516  case 'E': if (!Event) {
517  unsigned int EventID;
518  time_t StartTime;
519  int Duration;
520  unsigned int TableID = 0;
521  unsigned int Version = 0xFF; // actual value is ignored
522  int n = sscanf(t, "%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
523  if (n >= 3 && n <= 5) {
524  Event = (cEvent *)Schedule->GetEvent(EventID, StartTime);
525  cEvent *newEvent = NULL;
526  if (Event)
527  DELETENULL(Event->components);
528  if (!Event) {
529  Event = newEvent = new cEvent(EventID);
530  Event->seen = 0;
531  }
532  if (Event) {
533  Event->SetTableID(TableID);
534  Event->SetStartTime(StartTime);
535  Event->SetDuration(Duration);
536  if (newEvent)
537  Schedule->AddEvent(newEvent);
538  }
539  }
540  }
541  break;
542  case 'e': if (Event && !Event->Title())
543  Event->SetTitle(tr("No title"));
544  Event = NULL;
545  break;
546  case 'c': // to keep things simple we react on 'c' here
547  return true;
548  default: if (Event && !Event->Parse(s)) {
549  esyslog("ERROR: EPG data problem in line %d", line);
550  return false;
551  }
552  }
553  }
554  esyslog("ERROR: unexpected end of file while reading EPG data");
555  }
556  return false;
557 }
558 
559 #define MAXEPGBUGFIXSTATS 13
560 #define MAXEPGBUGFIXCHANS 100
562  int hits;
563  int n;
565  tEpgBugFixStats(void) { hits = n = 0; }
566  };
567 
569 
570 static void EpgBugFixStat(int Number, tChannelID ChannelID)
571 {
572  if (0 <= Number && Number < MAXEPGBUGFIXSTATS) {
573  tEpgBugFixStats *p = &EpgBugFixStats[Number];
574  p->hits++;
575  int i = 0;
576  for (; i < p->n; i++) {
577  if (p->channelIDs[i] == ChannelID)
578  break;
579  }
580  if (i == p->n && p->n < MAXEPGBUGFIXCHANS)
581  p->channelIDs[p->n++] = ChannelID;
582  }
583 }
584 
585 void ReportEpgBugFixStats(bool Force)
586 {
587  if (Setup.EPGBugfixLevel > 0) {
588  static time_t LastReport = 0;
589  time_t now = time(NULL);
590  if (now - LastReport > 3600 || Force) {
591  LastReport = now;
592  struct tm tm_r;
593  struct tm *ptm = localtime_r(&now, &tm_r);
594  if (ptm->tm_hour != 5)
595  return;
596  }
597  else
598  return;
599  bool GotHits = false;
600  char buffer[1024];
601  for (int i = 0; i < MAXEPGBUGFIXSTATS; i++) {
602  const char *delim = " ";
603  tEpgBugFixStats *p = &EpgBugFixStats[i];
604  if (p->hits) {
605  bool PrintedStats = false;
606  char *q = buffer;
607  *buffer = 0;
608  for (int c = 0; c < p->n; c++) {
609  cChannel *channel = Channels.GetByChannelID(p->channelIDs[c], true);
610  if (channel) {
611  if (!GotHits) {
612  dsyslog("=====================");
613  dsyslog("EPG bugfix statistics");
614  dsyslog("=====================");
615  dsyslog("IF SOMEBODY WHO IS IN CHARGE OF THE EPG DATA FOR ONE OF THE LISTED");
616  dsyslog("CHANNELS READS THIS: PLEASE TAKE A LOOK AT THE FUNCTION cEvent::FixEpgBugs()");
617  dsyslog("IN VDR/epg.c TO LEARN WHAT'S WRONG WITH YOUR DATA, AND FIX IT!");
618  dsyslog("=====================");
619  dsyslog("Fix Hits Channels");
620  GotHits = true;
621  }
622  if (!PrintedStats) {
623  q += snprintf(q, sizeof(buffer) - (q - buffer), "%-3d %-4d", i, p->hits);
624  PrintedStats = true;
625  }
626  q += snprintf(q, sizeof(buffer) - (q - buffer), "%s%s", delim, channel->Name());
627  delim = ", ";
628  if (q - buffer > 80) {
629  q += snprintf(q, sizeof(buffer) - (q - buffer), "%s...", delim);
630  break;
631  }
632  }
633  }
634  if (*buffer)
635  dsyslog("%s", buffer);
636  }
637  p->hits = p->n = 0;
638  }
639  if (GotHits)
640  dsyslog("=====================");
641  }
642 }
643 
644 static void StripControlCharacters(char *s)
645 {
646  if (s) {
647  int len = strlen(s);
648  while (len > 0) {
649  int l = Utf8CharLen(s);
650  uchar *p = (uchar *)s;
651  if (l == 2 && *p == 0xC2) // UTF-8 sequence
652  p++;
653  if (*p == 0x86 || *p == 0x87) {
654  memmove(s, p + 1, len - l + 1); // we also copy the terminating 0!
655  len -= l;
656  l = 0;
657  }
658  s += l;
659  len -= l;
660  }
661  }
662 }
663 
665 {
666  if (isempty(title)) {
667  // we don't want any "(null)" titles
668  title = strcpyrealloc(title, tr("No title"));
669  EpgBugFixStat(12, ChannelID());
670  }
671 
672  if (Setup.EPGBugfixLevel == 0)
673  goto Final;
674 
675  // Some TV stations apparently have their own idea about how to fill in the
676  // EPG data. Let's fix their bugs as good as we can:
677 
678  // Some channels put the ShortText in quotes and use either the ShortText
679  // or the Description field, depending on how long the string is:
680  //
681  // Title
682  // "ShortText". Description
683  //
684  if ((shortText == NULL) != (description == NULL)) {
685  char *p = shortText ? shortText : description;
686  if (*p == '"') {
687  const char *delim = "\".";
688  char *e = strstr(p + 1, delim);
689  if (e) {
690  *e = 0;
691  char *s = strdup(p + 1);
692  char *d = strdup(e + strlen(delim));
693  free(shortText);
694  free(description);
695  shortText = s;
696  description = d;
697  EpgBugFixStat(1, ChannelID());
698  }
699  }
700  }
701 
702  // Some channels put the Description into the ShortText (preceded
703  // by a blank) if there is no actual ShortText and the Description
704  // is short enough:
705  //
706  // Title
707  // Description
708  //
709  if (shortText && !description) {
710  if (*shortText == ' ') {
711  memmove(shortText, shortText + 1, strlen(shortText));
712  description = shortText;
713  shortText = NULL;
714  EpgBugFixStat(2, ChannelID());
715  }
716  }
717 
718  // Sometimes they repeat the Title in the ShortText:
719  //
720  // Title
721  // Title
722  //
723  if (shortText && strcmp(title, shortText) == 0) {
724  free(shortText);
725  shortText = NULL;
726  EpgBugFixStat(3, ChannelID());
727  }
728 
729  // Some channels put the ShortText between double quotes, which is nothing
730  // but annoying (some even put a '.' after the closing '"'):
731  //
732  // Title
733  // "ShortText"[.]
734  //
735  if (shortText && *shortText == '"') {
736  int l = strlen(shortText);
737  if (l > 2 && (shortText[l - 1] == '"' || (shortText[l - 1] == '.' && shortText[l - 2] == '"'))) {
738  memmove(shortText, shortText + 1, l);
739  char *p = strrchr(shortText, '"');
740  if (p)
741  *p = 0;
742  EpgBugFixStat(4, ChannelID());
743  }
744  }
745 
746  if (Setup.EPGBugfixLevel <= 1)
747  goto Final;
748 
749  // Some channels apparently try to do some formatting in the texts,
750  // which is a bad idea because they have no way of knowing the width
751  // of the window that will actually display the text.
752  // Remove excess whitespace:
753  title = compactspace(title);
754  shortText = compactspace(shortText);
756 
757 #define MAX_USEFUL_EPISODE_LENGTH 40
758  // Some channels put a whole lot of information in the ShortText and leave
759  // the Description totally empty. So if the ShortText length exceeds
760  // MAX_USEFUL_EPISODE_LENGTH, let's put this into the Description
761  // instead:
762  if (!isempty(shortText) && isempty(description)) {
763  if (strlen(shortText) > MAX_USEFUL_EPISODE_LENGTH) {
764  free(description);
765  description = shortText;
766  shortText = NULL;
767  EpgBugFixStat(6, ChannelID());
768  }
769  }
770 
771  // Some channels put the same information into ShortText and Description.
772  // In that case we delete one of them:
773  if (shortText && description && strcmp(shortText, description) == 0) {
774  if (strlen(shortText) > MAX_USEFUL_EPISODE_LENGTH) {
775  free(shortText);
776  shortText = NULL;
777  }
778  else {
779  free(description);
780  description = NULL;
781  }
782  EpgBugFixStat(7, ChannelID());
783  }
784 
785  // Some channels use the ` ("backtick") character, where a ' (single quote)
786  // would be normally used. Actually, "backticks" in normal text don't make
787  // much sense, so let's replace them:
788  strreplace(title, '`', '\'');
789  strreplace(shortText, '`', '\'');
790  strreplace(description, '`', '\'');
791 
792  if (Setup.EPGBugfixLevel <= 2)
793  goto Final;
794 
795  // The stream components have a "description" field which some channels
796  // apparently have no idea of how to set correctly:
797  if (components) {
798  for (int i = 0; i < components->NumComponents(); i++) {
799  tComponent *p = components->Component(i);
800  switch (p->stream) {
801  case 0x01: { // video
802  if (p->description) {
803  if (strcasecmp(p->description, "Video") == 0 ||
804  strcasecmp(p->description, "Bildformat") == 0) {
805  // Yes, we know it's video - that's what the 'stream' code
806  // is for! But _which_ video is it?
807  free(p->description);
808  p->description = NULL;
809  EpgBugFixStat(8, ChannelID());
810  }
811  }
812  if (!p->description) {
813  switch (p->type) {
814  case 0x01:
815  case 0x05: p->description = strdup("4:3"); break;
816  case 0x02:
817  case 0x03:
818  case 0x06:
819  case 0x07: p->description = strdup("16:9"); break;
820  case 0x04:
821  case 0x08: p->description = strdup(">16:9"); break;
822  case 0x09:
823  case 0x0D: p->description = strdup("HD 4:3"); break;
824  case 0x0A:
825  case 0x0B:
826  case 0x0E:
827  case 0x0F: p->description = strdup("HD 16:9"); break;
828  case 0x0C:
829  case 0x10: p->description = strdup("HD >16:9"); break;
830  default: ;
831  }
832  EpgBugFixStat(9, ChannelID());
833  }
834  }
835  break;
836  case 0x02: { // audio
837  if (p->description) {
838  if (strcasecmp(p->description, "Audio") == 0) {
839  // Yes, we know it's audio - that's what the 'stream' code
840  // is for! But _which_ audio is it?
841  free(p->description);
842  p->description = NULL;
843  EpgBugFixStat(10, ChannelID());
844  }
845  }
846  if (!p->description) {
847  switch (p->type) {
848  case 0x05: p->description = strdup("Dolby Digital"); break;
849  default: ; // all others will just display the language
850  }
851  EpgBugFixStat(11, ChannelID());
852  }
853  }
854  break;
855  default: ;
856  }
857  }
858  }
859 
860 Final:
861 
862  // VDR can't usefully handle newline characters in the title, shortText or component description of EPG
863  // data, so let's always convert them to blanks (independent of the setting of EPGBugfixLevel):
864  strreplace(title, '\n', ' ');
865  strreplace(shortText, '\n', ' ');
866  if (components) {
867  for (int i = 0; i < components->NumComponents(); i++) {
868  tComponent *p = components->Component(i);
869  if (p->description)
870  strreplace(p->description, '\n', ' ');
871  }
872  }
873  // Same for control characters:
874  StripControlCharacters(title);
875  StripControlCharacters(shortText);
877 }
878 
879 // --- cSchedule -------------------------------------------------------------
880 
882 {
883  channelID = ChannelID;
884  hasRunning = false;
885  modified = 0;
886  presentSeen = 0;
887 }
888 
890 {
891  events.Add(Event);
892  Event->schedule = this;
893  HashEvent(Event);
894  return Event;
895 }
896 
898 {
899  if (Event->schedule == this) {
900  if (hasRunning && Event->IsRunning())
901  ClrRunningStatus();
902  UnhashEvent(Event);
903  events.Del(Event);
904  }
905 }
906 
908 {
909  eventsHashID.Add(Event, Event->EventID());
910  if (Event->StartTime() > 0) // 'StartTime < 0' is apparently used with NVOD channels
911  eventsHashStartTime.Add(Event, Event->StartTime());
912 }
913 
915 {
916  eventsHashID.Del(Event, Event->EventID());
917  if (Event->StartTime() > 0) // 'StartTime < 0' is apparently used with NVOD channels
918  eventsHashStartTime.Del(Event, Event->StartTime());
919 }
920 
922 {
923  const cEvent *pe = NULL;
924  time_t now = time(NULL);
925  for (cEvent *p = events.First(); p; p = events.Next(p)) {
926  if (p->StartTime() <= now)
927  pe = p;
928  else if (p->StartTime() > now + 3600)
929  break;
930  if (p->SeenWithin(RUNNINGSTATUSTIMEOUT) && p->RunningStatus() >= SI::RunningStatusPausing)
931  return p;
932  }
933  return pe;
934 }
935 
937 {
938  const cEvent *p = GetPresentEvent();
939  if (p)
940  p = events.Next(p);
941  else {
942  time_t now = time(NULL);
943  for (p = events.First(); p; p = events.Next(p)) {
944  if (p->StartTime() >= now)
945  break;
946  }
947  }
948  return p;
949 }
950 
951 const cEvent *cSchedule::GetEvent(tEventID EventID, time_t StartTime) const
952 {
953  // Returns the event info with the given StartTime or, if no actual StartTime
954  // is given, the one with the given EventID.
955  if (StartTime > 0) // 'StartTime < 0' is apparently used with NVOD channels
956  return eventsHashStartTime.Get(StartTime);
957  else
958  return eventsHashID.Get(EventID);
959 }
960 
961 const cEvent *cSchedule::GetEventAround(time_t Time) const
962 {
963  const cEvent *pe = NULL;
964  time_t delta = INT_MAX;
965  for (cEvent *p = events.First(); p; p = events.Next(p)) {
966  time_t dt = Time - p->StartTime();
967  if (dt >= 0 && dt < delta && p->EndTime() >= Time) {
968  delta = dt;
969  pe = p;
970  }
971  }
972  return pe;
973 }
974 
976 {
977  hasRunning = false;
978  for (cEvent *p = events.First(); p; p = events.Next(p)) {
979  if (p == Event) {
980  if (p->RunningStatus() > SI::RunningStatusNotRunning || RunningStatus > SI::RunningStatusNotRunning) {
981  p->SetRunningStatus(RunningStatus, Channel);
982  break;
983  }
984  }
985  else if (RunningStatus >= SI::RunningStatusPausing && p->StartTime() < Event->StartTime())
986  p->SetRunningStatus(SI::RunningStatusNotRunning);
987  if (p->RunningStatus() >= SI::RunningStatusPausing)
988  hasRunning = true;
989  }
990 }
991 
993 {
994  if (hasRunning) {
995  for (cEvent *p = events.First(); p; p = events.Next(p)) {
996  if (p->RunningStatus() >= SI::RunningStatusPausing) {
997  p->SetRunningStatus(SI::RunningStatusNotRunning, Channel);
998  hasRunning = false;
999  break;
1000  }
1001  }
1002  }
1003 }
1004 
1006 {
1007  for (cEvent *p = events.First(); p; p = events.Next(p))
1008  p->SetVersion(0xFF);
1009 }
1010 
1012 {
1013  events.Sort();
1014  // Make sure there are no RunningStatusUndefined before the currently running event:
1015  if (hasRunning) {
1016  for (cEvent *p = events.First(); p; p = events.Next(p)) {
1017  if (p->RunningStatus() >= SI::RunningStatusPausing)
1018  break;
1019  p->SetRunningStatus(SI::RunningStatusNotRunning);
1020  }
1021  }
1022 }
1023 
1024 void cSchedule::DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
1025 {
1026  if (SegmentStart > 0 && SegmentEnd > 0) {
1027  for (cEvent *p = events.First(); p; p = events.Next(p)) {
1028  if (p->EndTime() > SegmentStart) {
1029  if (p->StartTime() < SegmentEnd) {
1030  // The event overlaps with the given time segment.
1031  if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) {
1032  // The segment overwrites all events from tables with higher ids, and
1033  // within the same table id all events must have the same version.
1034  // We can't delete the event right here because a timer might have
1035  // a pointer to it, so let's set its id and start time to 0 to have it
1036  // "phased out":
1037  if (hasRunning && p->IsRunning())
1038  ClrRunningStatus();
1039  UnhashEvent(p);
1040  p->eventID = 0;
1041  p->startTime = 0;
1042  }
1043  }
1044  else
1045  break;
1046  }
1047  }
1048  }
1049 }
1050 
1052 {
1053  Cleanup(time(NULL));
1054 }
1055 
1056 void cSchedule::Cleanup(time_t Time)
1057 {
1058  cEvent *Event;
1059  while ((Event = events.First()) != NULL) {
1060  if (!Event->HasTimer() && Event->EndTime() + Setup.EPGLinger * 60 + 3600 < Time) // adding one hour for safety
1061  DelEvent(Event);
1062  else
1063  break;
1064  }
1065 }
1066 
1067 void cSchedule::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) const
1068 {
1069  cChannel *channel = Channels.GetByChannelID(channelID, true);
1070  if (channel) {
1071  fprintf(f, "%sC %s %s\n", Prefix, *channel->GetChannelID().ToString(), channel->Name());
1072  const cEvent *p;
1073  switch (DumpMode) {
1074  case dmAll: {
1075  for (p = events.First(); p; p = events.Next(p))
1076  p->Dump(f, Prefix);
1077  }
1078  break;
1079  case dmPresent: {
1080  if ((p = GetPresentEvent()) != NULL)
1081  p->Dump(f, Prefix);
1082  }
1083  break;
1084  case dmFollowing: {
1085  if ((p = GetFollowingEvent()) != NULL)
1086  p->Dump(f, Prefix);
1087  }
1088  break;
1089  case dmAtTime: {
1090  if ((p = GetEventAround(AtTime)) != NULL)
1091  p->Dump(f, Prefix);
1092  }
1093  break;
1094  default: esyslog("ERROR: unknown DumpMode %d (%s %d)", DumpMode, __FUNCTION__, __LINE__);
1095  }
1096  fprintf(f, "%sc\n", Prefix);
1097  }
1098 }
1099 
1100 bool cSchedule::Read(FILE *f, cSchedules *Schedules)
1101 {
1102  if (Schedules) {
1103  cReadLine ReadLine;
1104  char *s;
1105  while ((s = ReadLine.Read(f)) != NULL) {
1106  if (*s == 'C') {
1107  s = skipspace(s + 1);
1108  char *p = strchr(s, ' ');
1109  if (p)
1110  *p = 0; // strips optional channel name
1111  if (*s) {
1112  tChannelID channelID = tChannelID::FromString(s);
1113  if (channelID.Valid()) {
1114  cSchedule *p = Schedules->AddSchedule(channelID);
1115  if (p) {
1116  if (!cEvent::Read(f, p))
1117  return false;
1118  p->Sort();
1119  Schedules->SetModified(p);
1120  }
1121  }
1122  else {
1123  esyslog("ERROR: invalid channel ID: %s", s);
1124  return false;
1125  }
1126  }
1127  }
1128  else {
1129  esyslog("ERROR: unexpected tag while reading EPG data: %s", s);
1130  return false;
1131  }
1132  }
1133  return true;
1134  }
1135  return false;
1136 }
1137 
1138 // --- cEpgDataWriter --------------------------------------------------------
1139 
1140 class cEpgDataWriter : public cThread {
1141 private:
1143  bool dump;
1144 protected:
1145  virtual void Action(void);
1146 public:
1147  cEpgDataWriter(void);
1148  void SetDump(bool Dump) { dump = Dump; }
1149  void Perform(void);
1150  };
1151 
1153 :cThread("epg data writer", true)
1154 {
1155  dump = false;
1156 }
1157 
1159 {
1160  Perform();
1161 }
1162 
1164 {
1165  cMutexLock MutexLock(&mutex); // to make sure fore- and background calls don't cause parellel dumps!
1166  {
1167  cSchedulesLock SchedulesLock(true, 1000);
1168  cSchedules *s = (cSchedules *)cSchedules::Schedules(SchedulesLock);
1169  if (s) {
1170  time_t now = time(NULL);
1171  for (cSchedule *p = s->First(); p; p = s->Next(p))
1172  p->Cleanup(now);
1173  }
1174  }
1175  if (dump)
1176  cSchedules::Dump();
1177 }
1178 
1180 
1181 // --- cSchedulesLock --------------------------------------------------------
1182 
1183 cSchedulesLock::cSchedulesLock(bool WriteLock, int TimeoutMs)
1184 {
1185  locked = cSchedules::schedules.rwlock.Lock(WriteLock, TimeoutMs);
1186 }
1187 
1189 {
1190  if (locked)
1192 }
1193 
1194 // --- cSchedules ------------------------------------------------------------
1195 
1197 char *cSchedules::epgDataFileName = NULL;
1198 time_t cSchedules::lastDump = time(NULL);
1199 time_t cSchedules::modified = 0;
1200 
1202 {
1203  return SchedulesLock.Locked() ? &schedules : NULL;
1204 }
1205 
1206 void cSchedules::SetEpgDataFileName(const char *FileName)
1207 {
1208  free(epgDataFileName);
1209  epgDataFileName = FileName ? strdup(FileName) : NULL;
1210  EpgDataWriter.SetDump(epgDataFileName != NULL);
1211 }
1212 
1214 {
1215  Schedule->SetModified();
1216  modified = time(NULL);
1217 }
1218 
1219 void cSchedules::Cleanup(bool Force)
1220 {
1221  if (Force)
1222  lastDump = 0;
1223  time_t now = time(NULL);
1224  if (now - lastDump > EPGDATAWRITEDELTA) {
1225  if (Force)
1226  EpgDataWriter.Perform();
1227  else if (!EpgDataWriter.Active())
1228  EpgDataWriter.Start();
1229  lastDump = now;
1230  }
1231 }
1232 
1234 {
1235  cSchedulesLock SchedulesLock(true);
1236  cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
1237  if (s) {
1238  for (cSchedule *Schedule = s->First(); Schedule; Schedule = s->Next(Schedule))
1239  Schedule->ResetVersions();
1240  }
1241 }
1242 
1244 {
1245  cSchedulesLock SchedulesLock(true, 1000);
1246  cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
1247  if (s) {
1248  for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer))
1249  Timer->SetEvent(NULL);
1250  for (cSchedule *Schedule = s->First(); Schedule; Schedule = s->Next(Schedule))
1251  Schedule->Cleanup(INT_MAX);
1252  return true;
1253  }
1254  return false;
1255 }
1256 
1257 bool cSchedules::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime)
1258 {
1259  cSchedulesLock SchedulesLock;
1260  cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
1261  if (s) {
1262  cSafeFile *sf = NULL;
1263  if (!f) {
1264  sf = new cSafeFile(epgDataFileName);
1265  if (sf->Open())
1266  f = *sf;
1267  else {
1268  LOG_ERROR;
1269  delete sf;
1270  return false;
1271  }
1272  }
1273  for (cSchedule *p = s->First(); p; p = s->Next(p))
1274  p->Dump(f, Prefix, DumpMode, AtTime);
1275  if (sf) {
1276  sf->Close();
1277  delete sf;
1278  }
1279  return true;
1280  }
1281  return false;
1282 }
1283 
1284 bool cSchedules::Read(FILE *f)
1285 {
1286  cSchedulesLock SchedulesLock(true, 1000);
1287  cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
1288  if (s) {
1289  bool OwnFile = f == NULL;
1290  if (OwnFile) {
1291  if (epgDataFileName && access(epgDataFileName, R_OK) == 0) {
1292  dsyslog("reading EPG data from %s", epgDataFileName);
1293  if ((f = fopen(epgDataFileName, "r")) == NULL) {
1294  LOG_ERROR;
1295  return false;
1296  }
1297  }
1298  else
1299  return false;
1300  }
1301  bool result = cSchedule::Read(f, s);
1302  if (OwnFile)
1303  fclose(f);
1304  if (result) {
1305  // Initialize the channels' schedule pointers, so that the first WhatsOn menu will come up faster:
1306  for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel))
1307  s->GetSchedule(Channel);
1308  }
1309  return result;
1310  }
1311  return false;
1312 }
1313 
1315 {
1316  ChannelID.ClrRid();
1317  cSchedule *p = (cSchedule *)GetSchedule(ChannelID);
1318  if (!p) {
1319  p = new cSchedule(ChannelID);
1320  Add(p);
1321  cChannel *channel = Channels.GetByChannelID(ChannelID);
1322  if (channel)
1323  channel->schedule = p;
1324  }
1325  return p;
1326 }
1327 
1329 {
1330  ChannelID.ClrRid();
1331  for (cSchedule *p = First(); p; p = Next(p)) {
1332  if (p->ChannelID() == ChannelID)
1333  return p;
1334  }
1335  return NULL;
1336 }
1337 
1338 const cSchedule *cSchedules::GetSchedule(const cChannel *Channel, bool AddIfMissing) const
1339 {
1340  // This is not very beautiful, but it dramatically speeds up the
1341  // "What's on now/next?" menus.
1342  static cSchedule DummySchedule(tChannelID::InvalidID);
1343  if (!Channel->schedule)
1344  Channel->schedule = GetSchedule(Channel->GetChannelID());
1345  if (!Channel->schedule)
1346  Channel->schedule = &DummySchedule;
1347  if (Channel->schedule == &DummySchedule && AddIfMissing) {
1348  cSchedule *Schedule = new cSchedule(Channel->GetChannelID());
1349  ((cSchedules *)this)->Add(Schedule);
1350  Channel->schedule = Schedule;
1351  }
1352  return Channel->schedule != &DummySchedule? Channel->schedule : NULL;
1353 }
1354 
1355 // --- cEpgDataReader --------------------------------------------------------
1356 
1358 :cThread("epg data reader")
1359 {
1360 }
1361 
1363 {
1364  cSchedules::Read();
1365 }
1366 
1367 // --- cEpgHandler -----------------------------------------------------------
1368 
1370 {
1371  EpgHandlers.Add(this);
1372 }
1373 
1375 {
1376  EpgHandlers.Del(this, false);
1377 }
1378 
1379 // --- cEpgHandlers ----------------------------------------------------------
1380 
1382 
1384 {
1385  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1386  if (eh->IgnoreChannel(Channel))
1387  return true;
1388  }
1389  return false;
1390 }
1391 
1392 bool cEpgHandlers::HandleEitEvent(cSchedule *Schedule, const SI::EIT::Event *EitEvent, uchar TableID, uchar Version)
1393 {
1394  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1395  if (eh->HandleEitEvent(Schedule, EitEvent, TableID, Version))
1396  return true;
1397  }
1398  return false;
1399 }
1400 
1402 {
1403  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1404  if (eh->HandledExternally(Channel))
1405  return true;
1406  }
1407  return false;
1408 }
1409 
1410 bool cEpgHandlers::IsUpdate(tEventID EventID, time_t StartTime, uchar TableID, uchar Version)
1411 {
1412  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1413  if (eh->IsUpdate(EventID, StartTime, TableID, Version))
1414  return true;
1415  }
1416  return false;
1417 }
1418 
1420 {
1421  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1422  if (eh->SetEventID(Event, EventID))
1423  return;
1424  }
1425  Event->SetEventID(EventID);
1426 }
1427 
1428 void cEpgHandlers::SetTitle(cEvent *Event, const char *Title)
1429 {
1430  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1431  if (eh->SetTitle(Event, Title))
1432  return;
1433  }
1434  Event->SetTitle(Title);
1435 }
1436 
1437 void cEpgHandlers::SetShortText(cEvent *Event, const char *ShortText)
1438 {
1439  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1440  if (eh->SetShortText(Event, ShortText))
1441  return;
1442  }
1443  Event->SetShortText(ShortText);
1444 }
1445 
1446 void cEpgHandlers::SetDescription(cEvent *Event, const char *Description)
1447 {
1448  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1449  if (eh->SetDescription(Event, Description))
1450  return;
1451  }
1452  Event->SetDescription(Description);
1453 }
1454 
1455 void cEpgHandlers::SetContents(cEvent *Event, uchar *Contents)
1456 {
1457  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1458  if (eh->SetContents(Event, Contents))
1459  return;
1460  }
1461  Event->SetContents(Contents);
1462 }
1463 
1464 void cEpgHandlers::SetParentalRating(cEvent *Event, int ParentalRating)
1465 {
1466  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1467  if (eh->SetParentalRating(Event, ParentalRating))
1468  return;
1469  }
1470  Event->SetParentalRating(ParentalRating);
1471 }
1472 
1473 void cEpgHandlers::SetStartTime(cEvent *Event, time_t StartTime)
1474 {
1475  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1476  if (eh->SetStartTime(Event, StartTime))
1477  return;
1478  }
1479  Event->SetStartTime(StartTime);
1480 }
1481 
1482 void cEpgHandlers::SetDuration(cEvent *Event, int Duration)
1483 {
1484  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1485  if (eh->SetDuration(Event, Duration))
1486  return;
1487  }
1488  Event->SetDuration(Duration);
1489 }
1490 
1491 void cEpgHandlers::SetVps(cEvent *Event, time_t Vps)
1492 {
1493  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1494  if (eh->SetVps(Event, Vps))
1495  return;
1496  }
1497  Event->SetVps(Vps);
1498 }
1499 
1501 {
1502  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1503  if (eh->SetComponents(Event, Components))
1504  return;
1505  }
1506  Event->SetComponents(Components);
1507 }
1508 
1510 {
1511  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1512  if (eh->FixEpgBugs(Event))
1513  return;
1514  }
1515  Event->FixEpgBugs();
1516 }
1517 
1519 {
1520  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1521  if (eh->HandleEvent(Event))
1522  break;
1523  }
1524 }
1525 
1527 {
1528  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1529  if (eh->SortSchedule(Schedule))
1530  return;
1531  }
1532  Schedule->Sort();
1533 }
1534 
1535 void cEpgHandlers::DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
1536 {
1537  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1538  if (eh->DropOutdated(Schedule, SegmentStart, SegmentEnd, TableID, Version))
1539  return;
1540  }
1541  Schedule->DropOutdated(SegmentStart, SegmentEnd, TableID, Version);
1542 }
1543 
1544 void cEpgHandlers::BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus)
1545 {
1546  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1547  if (eh->BeginSegmentTransfer(Channel, OnlyRunningStatus))
1548  return;
1549  }
1550 }
1551 
1552 void cEpgHandlers::EndSegmentTransfer(bool Modified, bool OnlyRunningStatus)
1553 {
1554  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1555  if (eh->EndSegmentTransfer(Modified, OnlyRunningStatus))
1556  return;
1557  }
1558 }
const cEvent * GetPresentEvent(void) const
Definition: epg.c:921
tChannelID channelIDs[MAXEPGBUGFIXCHANS]
Definition: epg.c:564
unsigned char uchar
Definition: tools.h:30
void Perform(void)
Definition: epg.c:1163
Definition: epg.h:71
void SetContents(uchar *Contents)
Definition: epg.c:201
void SetContents(cEvent *Event, uchar *Contents)
Definition: epg.c:1455
int Number(void) const
Definition: channels.h:197
cEpgDataReader(void)
Definition: epg.c:1357
cChannels Channels
Definition: channels.c:864
tComponent * GetComponent(int Index, uchar Stream, uchar Type)
Definition: epg.c:98
static time_t modified
Definition: epg.h:197
bool isempty(const char *s)
Definition: tools.c:297
static tChannelID FromString(const char *s)
Definition: channels.c:26
#define dsyslog(a...)
Definition: tools.h:36
bool HandleEitEvent(cSchedule *Schedule, const SI::EIT::Event *EitEvent, uchar TableID, uchar Version)
Definition: epg.c:1392
time_t EndTime(void) const
Definition: epg.h:107
void SetComponent(int Index, const char *s)
Definition: epg.c:78
u_int32_t tEventID
Definition: epg.h:67
bool Close(void)
Definition: tools.c:1672
static void ResetVersions(void)
Definition: epg.c:1233
cEpgHandlers EpgHandlers
Definition: epg.c:1381
#define MAX_USEFUL_EPISODE_LENGTH
void FixEpgBugs(void)
Definition: epg.c:664
Definition: epg.h:40
void SetDescription(cEvent *Event, const char *Description)
Definition: epg.c:1446
void SetStartTime(time_t StartTime)
Definition: epg.c:212
cSchedule(tChannelID ChannelID)
Definition: epg.c:881
void SetDuration(int Duration)
Definition: epg.c:223
#define LOG_ERROR
Definition: tools.h:38
RunningStatus
Definition: si.h:197
void SetTableID(uchar TableID)
Definition: epg.c:163
void Add(cListObject *Object, cListObject *After=NULL)
Definition: tools.c:2014
cString ToDescr(void) const
Definition: epg.c:238
cComponents(void)
Definition: epg.c:47
bool Open(void)
Definition: tools.c:1662
char language[MAXLANGCODE2]
Definition: epg.h:45
static const char * ContentToString(uchar Content)
Definition: epg.c:260
~cComponents(void)
Definition: epg.c:53
cString GetParentalRatingString(void) const
Definition: epg.c:402
~cEvent()
Definition: epg.c:133
cTimers Timers
Definition: timers.c:694
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition: tools.c:1080
void ReportEpgBugFixStats(bool Force)
Definition: epg.c:585
static bool Read(FILE *f, cSchedule *Schedule)
Definition: epg.c:505
void DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
Definition: epg.c:1535
void SetParentalRating(cEvent *Event, int ParentalRating)
Definition: epg.c:1464
#define esyslog(a...)
Definition: tools.h:34
void SetTitle(cEvent *Event, const char *Title)
Definition: epg.c:1428
void SetEventID(cEvent *Event, tEventID EventID)
Definition: epg.c:1419
char * strn0cpy(char *dest, const char *src, size_t n)
Definition: tools.c:131
bool Realloc(int Index)
Definition: epg.c:60
int EPGLinger
Definition: config.h:295
time_t StartTime(void) const
Definition: epg.h:106
bool HandledExternally(const cChannel *Channel)
Definition: epg.c:1401
int hits
Definition: epg.c:562
Definition: epg.h:36
static bool Dump(FILE *f=NULL, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0)
Definition: epg.c:1257
void SetDump(bool Dump)
Definition: epg.c:1148
void SetShortText(const char *ShortText)
Definition: epg.c:185
const cSchedule * schedule
Definition: channels.h:149
static time_t lastDump
Definition: epg.h:196
static void SetEpgDataFileName(const char *FileName)
Definition: epg.c:1206
void ResetVersions(void)
Definition: epg.c:1005
static const cSchedules * Schedules(cSchedulesLock &SchedulesLock)
Caller must provide a cSchedulesLock which has to survive the entire time the returned cSchedules is ...
Definition: epg.c:1201
char * Read(FILE *f)
Definition: tools.c:1398
void SetSeen(void)
Definition: epg.c:233
Definition: epg.h:28
void ClrRunningStatus(cChannel *Channel=NULL)
Definition: epg.c:992
void Unlock(void)
Definition: thread.c:170
Definition: timers.h:27
void SetComponents(cComponents *Components)
Definition: epg.c:195
time_t startTime
Definition: epg.h:86
void HandleEvent(cEvent *Event)
Definition: epg.c:1518
Definition: epg.h:40
~cSchedulesLock()
Definition: epg.c:1188
cString GetVpsString(void) const
Definition: epg.c:424
void SetTitle(const char *Title)
Definition: epg.c:180
void SetStartTime(cEvent *Event, time_t StartTime)
Definition: epg.c:1473
cSchedule * schedule
Definition: epg.h:75
const char * Name(void) const
Definition: channels.c:123
static void EpgBugFixStat(int Number, tChannelID ChannelID)
Definition: epg.c:570
T * Next(const T *object) const
Definition: tools.h:495
static cEpgDataWriter EpgDataWriter
Definition: epg.c:1179
static void StripControlCharacters(char *s)
Definition: epg.c:644
#define MAXEPGBUGFIXSTATS
Definition: epg.c:559
cListObject * Next(void) const
Definition: tools.h:468
static bool Read(FILE *f, cSchedules *Schedules)
Definition: epg.c:1100
uchar type
Definition: epg.h:44
tChannelID ChannelID(void) const
Definition: epg.c:147
void SetRunningStatus(int RunningStatus, cChannel *Channel=NULL)
Definition: epg.c:173
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
Definition: epg.c:1362
Definition: epg.h:40
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
Definition: epg.c:1158
const cEvent * GetEvent(tEventID EventID, time_t StartTime=0) const
Definition: epg.c:951
void SetDuration(cEvent *Event, int Duration)
Definition: epg.c:1482
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
Definition: thread.c:273
const cEvent * GetEventAround(time_t Time) const
Definition: epg.c:961
static bool Read(FILE *f=NULL)
Definition: epg.c:1284
static char * epgDataFileName
Definition: epg.h:195
cComponents * components
Definition: epg.h:84
cSetup Setup
Definition: config.c:373
void FixEpgBugs(cEvent *Event)
Definition: epg.c:1509
static void Cleanup(bool Force=false)
Definition: epg.c:1219
static int Utf8CharLen(const char *s)
Definition: si.c:417
tChannelID GetChannelID(void) const
Definition: channels.h:208
bool Lock(bool Write, int TimeoutMs=0)
Definition: thread.c:155
tEventID EventID(void) const
Definition: epg.h:96
cString ToString(void)
Definition: epg.c:25
void SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Channel=NULL)
Definition: epg.c:975
void Cleanup(void)
Definition: epg.c:1051
void SortSchedule(cSchedule *Schedule)
Definition: epg.c:1526
void Sort(void)
Definition: epg.c:1011
Definition: thread.h:63
char * description
Definition: epg.h:46
cEvent(tEventID EventID)
Definition: epg.c:114
tEpgBugFixStats EpgBugFixStats[MAXEPGBUGFIXSTATS]
Definition: epg.c:568
bool FromString(const char *s)
Definition: epg.c:32
static void SetModified(cSchedule *Schedule)
Definition: epg.c:1213
cChannel * GetByChannelID(tChannelID ChannelID, bool TryWithoutRid=false, bool TryWithoutPolarization=false)
Definition: channels.c:1023
cEvent * AddEvent(cEvent *Event)
Definition: epg.c:889
bool HasTimer(void) const
Definition: channels.c:171
const char * Title(void) const
Definition: epg.h:100
bool Valid(void) const
Definition: channels.h:65
bool Parse(char *s)
Definition: epg.c:466
void SetVps(cEvent *Event, time_t Vps)
Definition: epg.c:1491
void BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus)
Definition: epg.c:1544
bool IgnoreChannel(const cChannel *Channel)
Definition: epg.c:1383
void HashEvent(cEvent *Event)
Definition: epg.c:907
Definition: epg.h:42
cMutex mutex
Definition: epg.c:1142
cString GetEndTimeString(void) const
Definition: epg.c:419
cRwLock rwlock
Definition: epg.h:193
const cSchedule * GetSchedule(tChannelID ChannelID) const
Definition: epg.c:1328
void DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
Definition: epg.c:1024
T * First(void) const
Definition: tools.h:492
void Del(cListObject *Object, bool DeleteObject=true)
Definition: tools.c:2046
cString ToString(void) const
Definition: channels.c:43
void SetParentalRating(int ParentalRating)
Definition: epg.c:207
void SetDescription(const char *Description)
Definition: epg.c:190
bool Active(void)
Checks whether the thread is still alive.
Definition: thread.c:298
Definition: epg.h:143
void Dump(FILE *f, const char *Prefix="", bool InfoOnly=false) const
Definition: epg.c:432
#define MAXEPGBUGFIXCHANS
Definition: epg.c:560
virtual ~cEpgHandler()
Definition: epg.c:1374
#define tr(s)
Definition: i18n.h:85
static cSchedules schedules
Definition: epg.h:194
cEpgHandler(void)
Constructs a new EPG handler and adds it to the list of EPG handlers.
Definition: epg.c:1369
void DELETENULL(T *&p)
Definition: tools.h:48
char * skipspace(const char *s)
Definition: tools.h:200
void DelEvent(cEvent *Event)
Definition: epg.c:897
time_t seen
Definition: epg.h:89
cString GetTimeString(void) const
Definition: epg.c:414
static bool ClearAll(void)
Definition: epg.c:1243
cString TimeString(time_t t)
Converts the given time to a string of the form "hh:mm".
Definition: tools.c:1186
#define isyslog(a...)
Definition: tools.h:35
char * strcpyrealloc(char *dest, const char *src)
Definition: tools.c:114
int EPGBugfixLevel
Definition: config.h:294
Definition: thread.h:77
eDumpMode
Definition: epg.h:40
#define EPGDATAWRITEDELTA
Definition: epg.c:21
void SetVersion(uchar Version)
Definition: epg.c:168
void SetVps(time_t Vps)
Definition: epg.c:228
cSchedule * AddSchedule(tChannelID ChannelID)
Definition: epg.c:1314
void SetModified(void)
Definition: epg.h:158
uchar stream
Definition: epg.h:43
static const tChannelID InvalidID
Definition: channels.h:75
#define RUNNINGSTATUSTIMEOUT
Definition: epg.c:20
bool IsRunning(bool OrAboutToStart=false) const
Definition: epg.c:255
char * compactspace(char *s)
Definition: tools.c:213
void SetComponents(cEvent *Event, cComponents *Components)
Definition: epg.c:1500
void SetEventID(tEventID EventID)
Definition: epg.c:152
cString GetDateString(void) const
Definition: epg.c:409
bool HasTimer(void) const
Definition: epg.c:246
bool Locked(void)
Definition: epg.h:186
virtual int Compare(const cListObject &ListObject) const
Must return 0 if this object is equal to ListObject, a positive value if it is "greater", and a negative value if it is "smaller".
Definition: epg.c:141
void SetShortText(cEvent *Event, const char *ShortText)
Definition: epg.c:1437
void Dump(FILE *f, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0) const
Definition: epg.c:1067
Definition: epg.h:29
bool dump
Definition: epg.c:1143
tEpgBugFixStats(void)
Definition: epg.c:565
bool IsUpdate(tEventID EventID, time_t StartTime, uchar TableID, uchar Version)
Definition: epg.c:1410
tChannelID & ClrRid(void)
Definition: channels.h:66
void UnhashEvent(cEvent *Event)
Definition: epg.c:914
char * strreplace(char *s, char c1, char c2)
Definition: tools.c:139
cSchedulesLock(bool WriteLock=false, int TimeoutMs=0)
Definition: epg.c:1183
Definition: tools.h:168
void EndSegmentTransfer(bool Modified, bool OnlyRunningStatus)
Definition: epg.c:1552
cString DateString(time_t t)
Converts the given time to a string of the form "www dd.mm.yyyy".
Definition: tools.c:1166
const cEvent * GetFollowingEvent(void) const
Definition: epg.c:936
cEpgDataWriter(void)
Definition: epg.c:1152