Bug Summary

File:epg.c
Location:line 629, column 23
Description:Value stored to 'q' is never read

Annotated Source Code

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