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