vdr  2.4.0
epg.c
Go to the documentation of this file.
1 /*
2  * epg.c: Electronic Program Guide
3  *
4  * See the main source file 'vdr.c' for copyright information and
5  * how to reach the author.
6  *
7  * Original version (as used in VDR before 1.3.0) written by
8  * Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
9  *
10  * $Id: epg.c 4.8 2017/05/28 13:08:09 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", Channel->Number(), Channel->Name(), *ToDescr(), 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 + Setup.EPGLinger * 60 >= time(NULL)) {
454  fprintf(f, "%sE %u %ld %d %X %X\n", Prefix, eventID, 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 %ld\n", Prefix, 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(atoi(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  time_t StartTime;
545  int Duration;
546  unsigned int TableID = 0;
547  unsigned int Version = 0xFF; // actual value is ignored
548  int n = sscanf(t, "%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
549  if (n >= 3 && n <= 5) {
550  Event = (cEvent *)Schedule->GetEvent(EventID, StartTime);
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  // VDR can't usefully handle newline characters in the title, shortText or component description of EPG
889  // data, so let's always convert them to blanks (independent of the setting of EPGBugfixLevel):
890  strreplace(title, '\n', ' ');
891  strreplace(shortText, '\n', ' ');
892  if (components) {
893  for (int i = 0; i < components->NumComponents(); i++) {
895  if (p->description)
896  strreplace(p->description, '\n', ' ');
897  }
898  }
899  // Same for control characters:
903 }
904 
905 // --- cSchedule -------------------------------------------------------------
906 
908 
910 {
913  numTimers = 0;
914  hasRunning = false;
915  modified = 0;
916  presentSeen = 0;
917 }
918 
919 void cSchedule::IncNumTimers(void) const
920 {
922  numTimers++;
924 }
925 
926 void cSchedule::DecNumTimers(void) const
927 {
929  numTimers--;
931 }
932 
934 {
935  events.Add(Event);
936  Event->schedule = this;
937  HashEvent(Event);
938  return Event;
939 }
940 
942 {
943  if (Event->schedule == this) {
944  UnhashEvent(Event);
945  events.Del(Event);
946  }
947 }
948 
950 {
951  eventsHashID.Add(Event, Event->EventID());
952  if (Event->StartTime() > 0) // 'StartTime < 0' is apparently used with NVOD channels
953  eventsHashStartTime.Add(Event, Event->StartTime());
954 }
955 
957 {
958  eventsHashID.Del(Event, Event->EventID());
959  if (Event->StartTime() > 0) // 'StartTime < 0' is apparently used with NVOD channels
960  eventsHashStartTime.Del(Event, Event->StartTime());
961 }
962 
964 {
965  const cEvent *pe = NULL;
966  time_t now = time(NULL);
967  for (const cEvent *p = events.First(); p; p = events.Next(p)) {
968  if (p->StartTime() <= now)
969  pe = p;
970  else if (p->StartTime() > now + 3600)
971  break;
972  if (p->SeenWithin(RUNNINGSTATUSTIMEOUT) && p->RunningStatus() >= SI::RunningStatusPausing)
973  return p;
974  }
975  return pe;
976 }
977 
979 {
980  const cEvent *p = GetPresentEvent();
981  if (p)
982  p = events.Next(p);
983  else {
984  time_t now = time(NULL);
985  for (p = events.First(); p; p = events.Next(p)) {
986  if (p->StartTime() >= now)
987  break;
988  }
989  }
990  return p;
991 }
992 
993 const cEvent *cSchedule::GetEvent(tEventID EventID, time_t StartTime) const
994 {
995  // Returns the event info with the given StartTime or, if no actual StartTime
996  // is given, the one with the given EventID.
997  if (StartTime > 0) // 'StartTime < 0' is apparently used with NVOD channels
998  return eventsHashStartTime.Get(StartTime);
999  else
1000  return eventsHashID.Get(EventID);
1001 }
1002 
1003 const cEvent *cSchedule::GetEventAround(time_t Time) const
1004 {
1005  const cEvent *pe = NULL;
1006  time_t delta = INT_MAX;
1007  for (const cEvent *p = events.First(); p; p = events.Next(p)) {
1008  time_t dt = Time - p->StartTime();
1009  if (dt >= 0 && dt < delta && p->EndTime() >= Time) {
1010  delta = dt;
1011  pe = p;
1012  }
1013  }
1014  return pe;
1015 }
1016 
1018 {
1019  hasRunning = false;
1020  for (cEvent *p = events.First(); p; p = events.Next(p)) {
1021  if (p == Event) {
1023  p->SetRunningStatus(RunningStatus, Channel);
1024  break;
1025  }
1026  }
1027  else if (RunningStatus >= SI::RunningStatusPausing && p->StartTime() < Event->StartTime())
1028  p->SetRunningStatus(SI::RunningStatusNotRunning);
1029  if (p->RunningStatus() >= SI::RunningStatusPausing)
1030  hasRunning = true;
1031  }
1032  SetPresentSeen();
1033 }
1034 
1036 {
1037  if (hasRunning) {
1038  for (cEvent *p = events.First(); p; p = events.Next(p)) {
1039  if (p->RunningStatus() >= SI::RunningStatusPausing) {
1040  p->SetRunningStatus(SI::RunningStatusNotRunning, Channel);
1041  hasRunning = false;
1042  break;
1043  }
1044  }
1045  }
1046 }
1047 
1049 {
1050  for (cEvent *p = events.First(); p; p = events.Next(p))
1051  p->SetVersion(0xFF);
1052 }
1053 
1055 {
1056  events.Sort();
1057  // Make sure there are no RunningStatusUndefined before the currently running event:
1058  if (hasRunning) {
1059  for (cEvent *p = events.First(); p; p = events.Next(p)) {
1060  if (p->RunningStatus() >= SI::RunningStatusPausing)
1061  break;
1062  p->SetRunningStatus(SI::RunningStatusNotRunning);
1063  }
1064  }
1065  SetModified();
1066 }
1067 
1068 void cSchedule::DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
1069 {
1070  if (SegmentStart > 0 && SegmentEnd > 0) {
1071  cEvent *p = events.First();
1072  while (p) {
1073  cEvent *n = events.Next(p);
1074  if (p->EndTime() > SegmentStart) {
1075  if (p->StartTime() < SegmentEnd) {
1076  // The event overlaps with the given time segment.
1077  if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) {
1078  // The segment overwrites all events from tables with higher ids, and
1079  // within the same table id all events must have the same version.
1080  DelEvent(p);
1081  }
1082  }
1083  else
1084  break;
1085  }
1086  p = n;
1087  }
1088  }
1089 }
1090 
1092 {
1093  Cleanup(time(NULL));
1094 }
1095 
1096 void cSchedule::Cleanup(time_t Time)
1097 {
1098  cEvent *Event;
1099  while ((Event = events.First()) != NULL) {
1100  if (!Event->HasTimer() && Event->EndTime() + Setup.EPGLinger * 60 < Time)
1101  DelEvent(Event);
1102  else
1103  break;
1104  }
1105 }
1106 
1107 void cSchedule::Dump(const cChannels *Channels, FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) const
1108 {
1109  if (const cChannel *Channel = Channels->GetByChannelID(channelID, true)) {
1110  fprintf(f, "%sC %s %s\n", Prefix, *Channel->GetChannelID().ToString(), Channel->Name());
1111  const cEvent *p;
1112  switch (DumpMode) {
1113  case dmAll: {
1114  for (p = events.First(); p; p = events.Next(p))
1115  p->Dump(f, Prefix);
1116  }
1117  break;
1118  case dmPresent: {
1119  if ((p = GetPresentEvent()) != NULL)
1120  p->Dump(f, Prefix);
1121  }
1122  break;
1123  case dmFollowing: {
1124  if ((p = GetFollowingEvent()) != NULL)
1125  p->Dump(f, Prefix);
1126  }
1127  break;
1128  case dmAtTime: {
1129  if ((p = GetEventAround(AtTime)) != NULL)
1130  p->Dump(f, Prefix);
1131  }
1132  break;
1133  default: esyslog("ERROR: unknown DumpMode %d (%s %d)", DumpMode, __FUNCTION__, __LINE__);
1134  }
1135  fprintf(f, "%sc\n", Prefix);
1136  }
1137 }
1138 
1139 bool cSchedule::Read(FILE *f, cSchedules *Schedules)
1140 {
1141  if (Schedules) {
1142  int Line = 0;
1143  cReadLine ReadLine;
1144  char *s;
1145  while ((s = ReadLine.Read(f)) != NULL) {
1146  Line++;
1147  if (*s == 'C') {
1148  s = skipspace(s + 1);
1149  char *p = strchr(s, ' ');
1150  if (p)
1151  *p = 0; // strips optional channel name
1152  if (*s) {
1154  if (channelID.Valid()) {
1155  if (cSchedule *p = Schedules->AddSchedule(channelID)) {
1156  if (!cEvent::Read(f, p, Line))
1157  return false;
1158  p->Sort();
1159  }
1160  }
1161  else {
1162  esyslog("ERROR: invalid channel ID: %s", s);
1163  return false;
1164  }
1165  }
1166  }
1167  else {
1168  esyslog("ERROR: unexpected tag in line %d while reading EPG data: %s", Line, s);
1169  return false;
1170  }
1171  }
1172  return true;
1173  }
1174  return false;
1175 }
1176 
1177 // --- cEpgDataWriter --------------------------------------------------------
1178 
1179 class cEpgDataWriter : public cThread {
1180 private:
1182  bool dump;
1183 protected:
1184  virtual void Action(void);
1185 public:
1186  cEpgDataWriter(void);
1187  void SetDump(bool Dump) { dump = Dump; }
1188  void Perform(void);
1189  };
1190 
1192 :cThread("epg data writer", true)
1193 {
1194  dump = false;
1195 }
1196 
1198 {
1199  Perform();
1200 }
1201 
1203 {
1204  cMutexLock MutexLock(&mutex); // to make sure fore- and background calls don't cause parellel dumps!
1205  {
1206  cStateKey StateKey;
1207  if (cSchedules *Schedules = cSchedules::GetSchedulesWrite(StateKey, 1000)) {
1208  time_t now = time(NULL);
1209  for (cSchedule *p = Schedules->First(); p; p = Schedules->Next(p))
1210  p->Cleanup(now);
1211  StateKey.Remove();
1212  }
1213  }
1214  if (dump)
1215  cSchedules::Dump();
1216 }
1217 
1219 
1220 // --- cSchedules ------------------------------------------------------------
1221 
1223 char *cSchedules::epgDataFileName = NULL;
1224 time_t cSchedules::lastDump = time(NULL);
1225 
1227 :cList<cSchedule>("5 Schedules")
1228 {
1229 }
1230 
1231 const cSchedules *cSchedules::GetSchedulesRead(cStateKey &StateKey, int TimeoutMs)
1232 {
1233  return schedules.Lock(StateKey, false, TimeoutMs) ? &schedules : NULL;
1234 }
1235 
1237 {
1238  return schedules.Lock(StateKey, true, TimeoutMs) ? &schedules : NULL;
1239 }
1240 
1241 void cSchedules::SetEpgDataFileName(const char *FileName)
1242 {
1243  free(epgDataFileName);
1244  epgDataFileName = FileName ? strdup(FileName) : NULL;
1246 }
1247 
1248 void cSchedules::Cleanup(bool Force)
1249 {
1250  if (Force)
1251  lastDump = 0;
1252  time_t now = time(NULL);
1253  if (now - lastDump > EPGDATAWRITEDELTA) {
1254  if (Force)
1256  else if (!EpgDataWriter.Active())
1257  EpgDataWriter.Start();
1258  lastDump = now;
1259  }
1260 }
1261 
1263 {
1265  for (cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->Next(Schedule))
1266  Schedule->ResetVersions();
1267 }
1268 
1269 bool cSchedules::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime)
1270 {
1271  cSafeFile *sf = NULL;
1272  if (!f) {
1273  sf = new cSafeFile(epgDataFileName);
1274  if (sf->Open())
1275  f = *sf;
1276  else {
1277  LOG_ERROR;
1278  delete sf;
1279  return false;
1280  }
1281  }
1284  for (const cSchedule *p = Schedules->First(); p; p = Schedules->Next(p))
1285  p->Dump(Channels, f, Prefix, DumpMode, AtTime);
1286  if (sf) {
1287  sf->Close();
1288  delete sf;
1289  }
1290  return true;
1291 }
1292 
1293 bool cSchedules::Read(FILE *f)
1294 {
1295  bool OwnFile = f == NULL;
1296  if (OwnFile) {
1297  if (epgDataFileName && access(epgDataFileName, R_OK) == 0) {
1298  dsyslog("reading EPG data from %s", epgDataFileName);
1299  if ((f = fopen(epgDataFileName, "r")) == NULL) {
1300  LOG_ERROR;
1301  return false;
1302  }
1303  }
1304  else
1305  return false;
1306  }
1309  bool result = cSchedule::Read(f, Schedules);
1310  if (OwnFile)
1311  fclose(f);
1312  if (result) {
1313  // Initialize the channels' schedule pointers, so that the first WhatsOn menu will come up faster:
1314  for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel))
1315  Schedules->GetSchedule(Channel);
1316  }
1317  return result;
1318 }
1319 
1321 {
1322  ChannelID.ClrRid();
1323  cSchedule *p = (cSchedule *)GetSchedule(ChannelID);
1324  if (!p) {
1325  p = new cSchedule(ChannelID);
1326  Add(p);
1327  }
1328  return p;
1329 }
1330 
1332 {
1333  ChannelID.ClrRid();
1334  for (const cSchedule *p = First(); p; p = Next(p)) {
1335  if (p->ChannelID() == ChannelID)
1336  return p;
1337  }
1338  return NULL;
1339 }
1340 
1341 const cSchedule *cSchedules::GetSchedule(const cChannel *Channel, bool AddIfMissing) const
1342 {
1343  // This is not very beautiful, but it dramatically speeds up the
1344  // "What's on now/next?" menus.
1345  static cSchedule DummySchedule(tChannelID::InvalidID);
1346  if (!Channel->schedule)
1347  Channel->schedule = GetSchedule(Channel->GetChannelID());
1348  if (!Channel->schedule)
1349  Channel->schedule = &DummySchedule;
1350  if (Channel->schedule == &DummySchedule && AddIfMissing) {
1351  cSchedule *Schedule = new cSchedule(Channel->GetChannelID());
1352  ((cSchedules *)this)->Add(Schedule);
1353  Channel->schedule = Schedule;
1354  }
1355  return Channel->schedule != &DummySchedule? Channel->schedule : NULL;
1356 }
1357 
1358 // --- cEpgDataReader --------------------------------------------------------
1359 
1361 :cThread("epg data reader")
1362 {
1363 }
1364 
1366 {
1367  cSchedules::Read();
1368 }
1369 
1370 // --- cEpgHandler -----------------------------------------------------------
1371 
1373 {
1374  EpgHandlers.Add(this);
1375 }
1376 
1378 {
1379  EpgHandlers.Del(this, false);
1380 }
1381 
1382 // --- cEpgHandlers ----------------------------------------------------------
1383 
1385 
1387 {
1388  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1389  if (eh->IgnoreChannel(Channel))
1390  return true;
1391  }
1392  return false;
1393 }
1394 
1395 bool cEpgHandlers::HandleEitEvent(cSchedule *Schedule, const SI::EIT::Event *EitEvent, uchar TableID, uchar Version)
1396 {
1397  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1398  if (eh->HandleEitEvent(Schedule, EitEvent, TableID, Version))
1399  return true;
1400  }
1401  return false;
1402 }
1403 
1405 {
1406  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1407  if (eh->HandledExternally(Channel))
1408  return true;
1409  }
1410  return false;
1411 }
1412 
1413 bool cEpgHandlers::IsUpdate(tEventID EventID, time_t StartTime, uchar TableID, uchar Version)
1414 {
1415  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1416  if (eh->IsUpdate(EventID, StartTime, TableID, Version))
1417  return true;
1418  }
1419  return false;
1420 }
1421 
1423 {
1424  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1425  if (eh->SetEventID(Event, EventID))
1426  return;
1427  }
1428  Event->SetEventID(EventID);
1429 }
1430 
1431 void cEpgHandlers::SetTitle(cEvent *Event, const char *Title)
1432 {
1433  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1434  if (eh->SetTitle(Event, Title))
1435  return;
1436  }
1437  Event->SetTitle(Title);
1438 }
1439 
1440 void cEpgHandlers::SetShortText(cEvent *Event, const char *ShortText)
1441 {
1442  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1443  if (eh->SetShortText(Event, ShortText))
1444  return;
1445  }
1446  Event->SetShortText(ShortText);
1447 }
1448 
1449 void cEpgHandlers::SetDescription(cEvent *Event, const char *Description)
1450 {
1451  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1452  if (eh->SetDescription(Event, Description))
1453  return;
1454  }
1455  Event->SetDescription(Description);
1456 }
1457 
1458 void cEpgHandlers::SetContents(cEvent *Event, uchar *Contents)
1459 {
1460  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1461  if (eh->SetContents(Event, Contents))
1462  return;
1463  }
1464  Event->SetContents(Contents);
1465 }
1466 
1467 void cEpgHandlers::SetParentalRating(cEvent *Event, int ParentalRating)
1468 {
1469  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1470  if (eh->SetParentalRating(Event, ParentalRating))
1471  return;
1472  }
1473  Event->SetParentalRating(ParentalRating);
1474 }
1475 
1476 void cEpgHandlers::SetStartTime(cEvent *Event, time_t StartTime)
1477 {
1478  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1479  if (eh->SetStartTime(Event, StartTime))
1480  return;
1481  }
1482  Event->SetStartTime(StartTime);
1483 }
1484 
1485 void cEpgHandlers::SetDuration(cEvent *Event, int Duration)
1486 {
1487  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1488  if (eh->SetDuration(Event, Duration))
1489  return;
1490  }
1491  Event->SetDuration(Duration);
1492 }
1493 
1494 void cEpgHandlers::SetVps(cEvent *Event, time_t Vps)
1495 {
1496  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1497  if (eh->SetVps(Event, Vps))
1498  return;
1499  }
1500  Event->SetVps(Vps);
1501 }
1502 
1504 {
1505  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1506  if (eh->SetComponents(Event, Components))
1507  return;
1508  }
1509  Event->SetComponents(Components);
1510 }
1511 
1513 {
1514  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1515  if (eh->FixEpgBugs(Event))
1516  return;
1517  }
1518  Event->FixEpgBugs();
1519 }
1520 
1522 {
1523  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1524  if (eh->HandleEvent(Event))
1525  break;
1526  }
1527 }
1528 
1530 {
1531  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1532  if (eh->SortSchedule(Schedule))
1533  return;
1534  }
1535  Schedule->Sort();
1536 }
1537 
1538 void cEpgHandlers::DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
1539 {
1540  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1541  if (eh->DropOutdated(Schedule, SegmentStart, SegmentEnd, TableID, Version))
1542  return;
1543  }
1544  Schedule->DropOutdated(SegmentStart, SegmentEnd, TableID, Version);
1545 }
1546 
1548 {
1549  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1550  if (!eh->BeginSegmentTransfer(Channel, false))
1551  return false;
1552  }
1553  return true;
1554 }
1555 
1557 {
1558  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
1559  if (eh->EndSegmentTransfer(Modified, false))
1560  return;
1561  }
1562 }
const cChannel * GetByChannelID(tChannelID ChannelID, bool TryWithoutRid=false, bool TryWithoutPolarization=false) const
Definition: channels.c:1018
tChannelID channelIDs[MAXEPGBUGFIXCHANS]
Definition: epg.c:590
unsigned char uchar
Definition: tools.h:31
void Perform(void)
Definition: epg.c:1202
Definition: epg.h:71
void Lock(void)
Definition: thread.c:222
void SetContents(uchar *Contents)
Definition: epg.c:205
void SetContents(cEvent *Event, uchar *Contents)
Definition: epg.c:1458
cEpgDataReader(void)
Definition: epg.c:1360
time_t presentSeen
Definition: epg.h:160
tComponent * GetComponent(int Index, uchar Stream, uchar Type)
Definition: epg.c:97
bool isempty(const char *s)
Definition: tools.c:331
static tChannelID FromString(const char *s)
Definition: channels.c:24
#define dsyslog(a...)
Definition: tools.h:37
bool HandleEitEvent(cSchedule *Schedule, const SI::EIT::Event *EitEvent, uchar TableID, uchar Version)
Definition: epg.c:1395
time_t StartTime(void) const
Definition: epg.h:109
void SetComponent(int Index, const char *s)
Definition: epg.c:77
u_int32_t tEventID
Definition: epg.h:67
bool Close(void)
Definition: tools.c:1750
static void ResetVersions(void)
Definition: epg.c:1262
cEpgHandlers EpgHandlers
Definition: epg.c:1384
#define MAX_USEFUL_EPISODE_LENGTH
void FixEpgBugs(void)
Definition: epg.c:690
Definition: epg.h:40
static const cSchedules * GetSchedulesRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of schedules for read access.
Definition: epg.c:1231
void SetDescription(cEvent *Event, const char *Description)
Definition: epg.c:1449
void SetStartTime(time_t StartTime)
Definition: epg.c:216
cSchedule(tChannelID ChannelID)
Definition: epg.c:909
void SetDuration(int Duration)
Definition: epg.c:227
friend class cSchedule
Definition: epg.h:193
#define LOG_ERROR
Definition: tools.h:39
RunningStatus
Definition: si.h:197
void SetTableID(uchar TableID)
Definition: epg.c:167
void Add(cListObject *Object, cListObject *After=NULL)
Definition: tools.c:2152
cComponents(void)
Definition: epg.c:46
static cMutex numTimersMutex
Definition: epg.h:74
uchar tableID
Definition: epg.h:79
tChannelID ChannelID(void) const
Definition: epg.h:163
int NumComponents(void) const
Definition: epg.h:59
bool Open(void)
Definition: tools.c:1740
u_int16_t numTimers
Definition: epg.h:157
char language[MAXLANGCODE2]
Definition: epg.h:45
static const char * ContentToString(uchar Content)
Definition: epg.c:279
~cComponents(void)
Definition: epg.c:52
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:2143
cSchedules(void)
Definition: epg.c:1226
cString ToDescr(void) const
Definition: epg.c:248
tChannelID channelID
Definition: epg.h:153
~cEvent()
Definition: epg.c:136
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition: tools.c:1127
time_t Vps(void) const
Definition: epg.h:112
tEventID EventID(void) const
Definition: epg.h:99
void Del(cListObject *Object, unsigned int Id)
Definition: tools.c:2359
void ReportEpgBugFixStats(bool Force)
Definition: epg.c:611
char * title
Definition: epg.h:83
void DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
Definition: epg.c:1538
void IncNumTimers(void) const
Definition: epg.c:919
void SetParentalRating(cEvent *Event, int ParentalRating)
Definition: epg.c:1467
bool hasRunning
Definition: epg.h:158
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
Definition: thread.c:859
#define esyslog(a...)
Definition: tools.h:35
void SetTitle(cEvent *Event, const char *Title)
Definition: epg.c:1431
void SetEventID(cEvent *Event, tEventID EventID)
Definition: epg.c:1422
char * strn0cpy(char *dest, const char *src, size_t n)
Definition: tools.c:131
cHash< cEvent > eventsHashID
Definition: epg.h:155
bool Realloc(int Index)
Definition: epg.c:59
cString GetVpsString(void) const
Definition: epg.c:443
bool HasTimer(void) const
Definition: epg.h:178
Definition: tools.h:594
int EPGLinger
Definition: config.h:293
char * aux
Definition: epg.h:92
bool HandledExternally(const cChannel *Channel)
Definition: epg.c:1404
int hits
Definition: epg.c:588
Definition: epg.h:36
static bool Dump(FILE *f=NULL, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0)
Definition: epg.c:1269
void SetDump(bool Dump)
Definition: epg.c:1187
void SetShortText(const char *ShortText)
Definition: epg.c:189
#define LOCK_CHANNELS_WRITE
Definition: channels.h:268
const cSchedule * schedule
Definition: channels.h:133
static time_t lastDump
Definition: epg.h:197
static void SetEpgDataFileName(const char *FileName)
Definition: epg.c:1241
const char * Aux(void) const
Definition: epg.h:115
void ResetVersions(void)
Definition: epg.c:1048
bool BeginSegmentTransfer(const cChannel *Channel)
Definition: epg.c:1547
char * Read(FILE *f)
Definition: tools.c:1459
cString GetEndTimeString(void) const
Definition: epg.c:438
void SetSeen(void)
Definition: epg.c:237
Definition: epg.h:28
uchar Contents(int i=0) const
Definition: epg.h:107
void ClrRunningStatus(cChannel *Channel=NULL)
Definition: epg.c:1035
void Dump(const cChannels *Channels, FILE *f, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0) const
Definition: epg.c:1107
cString GetParentalRatingString(void) const
Definition: epg.c:421
void SetComponents(cComponents *Components)
Definition: epg.c:199
cString GetDateString(void) const
Definition: epg.c:428
time_t startTime
Definition: epg.h:87
void HandleEvent(cEvent *Event)
Definition: epg.c:1521
Definition: epg.h:40
void SetTitle(const char *Title)
Definition: epg.c:184
void SetStartTime(cEvent *Event, time_t StartTime)
Definition: epg.c:1476
cSchedule * schedule
Definition: epg.h:76
bool IsRunning(bool OrAboutToStart=false) const
Definition: epg.c:274
static void EpgBugFixStat(int Number, tChannelID ChannelID)
Definition: epg.c:596
static cEpgDataWriter EpgDataWriter
Definition: epg.c:1218
void DecNumTimers(void) const
Definition: epg.c:926
static void StripControlCharacters(char *s)
Definition: epg.c:670
#define MAXEPGBUGFIXSTATS
Definition: epg.c:585
time_t EndTime(void) const
Definition: epg.h:110
static bool Read(FILE *f, cSchedules *Schedules)
Definition: epg.c:1139
#define LOCK_CHANNELS_READ
Definition: channels.h:267
int duration
Definition: epg.h:88
uchar type
Definition: epg.h:44
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:1365
Definition: epg.h:40
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
Definition: epg.c:1197
void Sort(void)
Definition: tools.c:2276
void SetDuration(cEvent *Event, int Duration)
Definition: epg.c:1485
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
const char * ShortText(void) const
Definition: epg.h:104
static bool Read(FILE *f=NULL)
Definition: epg.c:1293
static char * epgDataFileName
Definition: epg.h:196
cComponents * components
Definition: epg.h:86
const cEvent * GetPresentEvent(void) const
Definition: epg.c:963
cSetup Setup
Definition: config.c:372
void FixEpgBugs(cEvent *Event)
Definition: epg.c:1512
static void Cleanup(bool Force=false)
Definition: epg.c:1248
static int Utf8CharLen(const char *s)
Definition: si.c:417
int Duration(void) const
Definition: epg.h:111
cString ToString(void)
Definition: epg.c:24
const cSchedule * GetSchedule(tChannelID ChannelID) const
Definition: epg.c:1331
const cEvent * GetEvent(tEventID EventID, time_t StartTime=0) const
Definition: epg.c:993
void Cleanup(void)
Definition: epg.c:1091
void SortSchedule(cSchedule *Schedule)
Definition: epg.c:1529
void Sort(void)
Definition: epg.c:1054
Definition: thread.h:67
char * description
Definition: epg.h:46
void SetAux(const char *Aux)
Definition: epg.c:242
cEvent(tEventID EventID)
Definition: epg.c:115
tEpgBugFixStats EpgBugFixStats[MAXEPGBUGFIXSTATS]
Definition: epg.c:594
bool FromString(const char *s)
Definition: epg.c:31
void Dump(FILE *f, const char *Prefix="", bool InfoOnly=false) const
Definition: epg.c:451
void SetPresentSeen(void)
Definition: epg.h:168
cEvent * AddEvent(cEvent *Event)
Definition: epg.c:933
const T * First(void) const
Returns the first element in this list, or NULL if the list is empty.
Definition: tools.h:606
char * shortText
Definition: epg.h:84
bool Parse(char *s)
Definition: epg.c:490
tChannelID ChannelID(void) const
Definition: epg.c:151
const cEvent * GetEventAround(time_t Time) const
Definition: epg.c:1003
void SetVps(cEvent *Event, time_t Vps)
Definition: epg.c:1494
const char * Name(void) const
Definition: channels.c:108
void DecNumTimers(void) const
Definition: epg.c:265
T * Get(unsigned int Id) const
Definition: tools.h:882
bool IgnoreChannel(const cChannel *Channel)
Definition: epg.c:1386
void HashEvent(cEvent *Event)
Definition: epg.c:949
uchar version
Definition: epg.h:80
Definition: epg.h:42
cMutex mutex
Definition: epg.c:1181
const cComponents * Components(void) const
Definition: epg.h:106
int RunningStatus(void) const
Definition: epg.h:102
const char * Title(void) const
Definition: epg.h:103
static cSchedules * GetSchedulesWrite(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of schedules for write access.
Definition: epg.c:1236
void DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
Definition: epg.c:1068
uchar parentalRating
Definition: epg.h:82
void Del(cListObject *Object, bool DeleteObject=true)
Definition: tools.c:2184
bool Valid(void) const
Definition: channels.h:60
void SetParentalRating(int ParentalRating)
Definition: epg.c:211
const cSchedule * Schedule(void) const
Definition: epg.h:98
tChannelID GetChannelID(void) const
Definition: channels.h:190
void SetDescription(const char *Description)
Definition: epg.c:194
bool Active(void)
Checks whether the thread is still alive.
Definition: thread.c:329
Definition: epg.h:150
tComponent * components
Definition: epg.h:54
void SetRunningStatus(int RunningStatus, const cChannel *Channel=NULL)
Definition: epg.c:177
#define MAXEPGBUGFIXCHANS
Definition: epg.c:586
virtual ~cEpgHandler()
Definition: epg.c:1377
#define tr(s)
Definition: i18n.h:85
static cSchedules schedules
Definition: epg.h:195
cEpgHandler(void)
Constructs a new EPG handler and adds it to the list of EPG handlers.
Definition: epg.c:1372
void IncNumTimers(void) const
Definition: epg.c:256
uchar contents[MaxEventContents]
Definition: epg.h:89
cListObject * Next(void) const
Definition: tools.h:510
void DELETENULL(T *&p)
Definition: tools.h:49
char * skipspace(const char *s)
Definition: tools.h:209
void DelEvent(cEvent *Event)
Definition: epg.c:941
time_t seen
Definition: epg.h:91
cString TimeString(time_t t)
Converts the given time to a string of the form "hh:mm".
Definition: tools.c:1233
#define isyslog(a...)
Definition: tools.h:36
char * strcpyrealloc(char *dest, const char *src)
Definition: tools.c:114
uchar Version(void) const
Definition: epg.h:101
int EPGBugfixLevel
Definition: config.h:292
Definition: thread.h:79
static bool Read(FILE *f, cSchedule *Schedule, int &Line)
Definition: epg.c:532
eDumpMode
Definition: epg.h:40
virtual int Compare(const cListObject &ListObject) const
Must return 0 if this object is equal to ListObject, a positive value if it is "greater", and a negative value if it is "smaller".
Definition: epg.c:145
#define EPGDATAWRITEDELTA
Definition: epg.c:20
void SetVersion(uchar Version)
Definition: epg.c:172
void SetVps(time_t Vps)
Definition: epg.c:232
cSchedule * AddSchedule(tChannelID ChannelID)
Definition: epg.c:1320
cString GetTimeString(void) const
Definition: epg.c:433
uchar runningStatus
Definition: epg.h:81
const cEvent * GetFollowingEvent(void) const
Definition: epg.c:978
void SetModified(void)
Definition: epg.h:167
void Add(cListObject *Object, unsigned int Id)
Definition: tools.c:2351
uchar stream
Definition: epg.h:43
static const tChannelID InvalidID
Definition: channels.h:70
#define LOCK_SCHEDULES_WRITE
Definition: epg.h:225
int ParentalRating(void) const
Definition: epg.h:108
#define RUNNINGSTATUSTIMEOUT
Definition: epg.c:19
tEventID eventID
Definition: epg.h:78
char * compactspace(char *s)
Definition: tools.c:213
void SetComponents(cEvent *Event, cComponents *Components)
Definition: epg.c:1503
void SetEventID(tEventID EventID)
Definition: epg.c:156
static cMutex numTimersMutex
Definition: epg.h:152
cHash< cEvent > eventsHashStartTime
Definition: epg.h:156
tComponent * Component(int Index) const
Definition: epg.h:62
#define LOCK_SCHEDULES_READ
Definition: epg.h:224
void SetShortText(cEvent *Event, const char *ShortText)
Definition: epg.c:1440
void SetRunningStatus(cEvent *Event, int RunningStatus, const cChannel *Channel=NULL)
Definition: epg.c:1017
Definition: epg.h:29
bool dump
Definition: epg.c:1182
tEpgBugFixStats(void)
Definition: epg.c:591
uchar TableID(void) const
Definition: epg.h:100
bool IsUpdate(tEventID EventID, time_t StartTime, uchar TableID, uchar Version)
Definition: epg.c:1413
tChannelID & ClrRid(void)
Definition: channels.h:61
char * description
Definition: epg.h:85
void SetUseGarbageCollector(void)
Definition: tools.h:567
void UnhashEvent(cEvent *Event)
Definition: epg.c:956
bool HasTimer(void) const
Definition: epg.h:118
void EndSegmentTransfer(bool Modified)
Definition: epg.c:1556
char * strreplace(char *s, char c1, char c2)
Definition: tools.c:139
time_t vps
Definition: epg.h:90
Definition: tools.h:176
int Number(void) const
Definition: channels.h:179
int numComponents
Definition: epg.h:53
cString DateString(time_t t)
Converts the given time to a string of the form "www dd.mm.yyyy".
Definition: tools.c:1213
cList< cEvent > events
Definition: epg.h:154
int modified
Definition: epg.h:159
u_int16_t numTimers
Definition: epg.h:77
void Unlock(void)
Definition: thread.c:228
const char * Description(void) const
Definition: epg.h:105
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:613
cEpgDataWriter(void)
Definition: epg.c:1191