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