File: | epg.c |
Location: | line 629, column 23 |
Description: | Value stored to 'q' is never read |
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 | |
25 | cString 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 | |
32 | bool tComponent::FromString(const char *s) |
33 | { |
34 | unsigned int Stream, Type; |
35 | int n = sscanf(s, "%X %02X %7s %m[^\n]", &Stream, &Type, language, &description); // 7 = MAXLANGCODE2 - 1 |
36 | if (n != 4 || isempty(description)) { |
37 | free(description); |
38 | description = NULL__null; |
39 | } |
40 | stream = Stream; |
41 | type = Type; |
42 | return n >= 3; |
43 | } |
44 | |
45 | // --- cComponents ----------------------------------------------------------- |
46 | |
47 | cComponents::cComponents(void) |
48 | { |
49 | numComponents = 0; |
50 | components = NULL__null; |
51 | } |
52 | |
53 | cComponents::~cComponents(void) |
54 | { |
55 | for (int i = 0; i < numComponents; i++) |
56 | free(components[i].description); |
57 | free(components); |
58 | } |
59 | |
60 | bool cComponents::Realloc(int Index) |
61 | { |
62 | if (Index >= numComponents) { |
63 | Index++; |
64 | if (tComponent *NewBuffer = (tComponent *)realloc(components, Index * sizeof(tComponent))) { |
65 | int n = numComponents; |
66 | numComponents = Index; |
67 | components = NewBuffer; |
68 | memset(&components[n], 0, sizeof(tComponent) * (numComponents - n)); |
69 | } |
70 | else { |
71 | esyslog("ERROR: out of memory")void( (SysLogLevel > 0) ? syslog_with_tid(3, "ERROR: out of memory" ) : void() ); |
72 | return false; |
73 | } |
74 | } |
75 | return true; |
76 | } |
77 | |
78 | void cComponents::SetComponent(int Index, const char *s) |
79 | { |
80 | if (Realloc(Index)) |
81 | components[Index].FromString(s); |
82 | } |
83 | |
84 | void cComponents::SetComponent(int Index, uchar Stream, uchar Type, const char *Language, const char *Description) |
85 | { |
86 | if (!Realloc(Index)) |
87 | return; |
88 | tComponent *p = &components[Index]; |
89 | p->stream = Stream; |
90 | p->type = Type; |
91 | strn0cpy(p->language, Language, sizeof(p->language)); |
92 | char *q = strchr(p->language, ','); |
93 | if (q) |
94 | *q = 0; // strips rest of "normalized" language codes |
95 | p->description = strcpyrealloc(p->description, !isempty(Description) ? Description : NULL__null); |
96 | } |
97 | |
98 | tComponent *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 | |
114 | cEvent::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 | |
133 | cEvent::~cEvent() |
134 | { |
135 | free(title); |
136 | free(shortText); |
137 | free(description); |
138 | delete components; |
139 | } |
140 | |
141 | int cEvent::Compare(const cListObject &ListObject) const |
142 | { |
143 | cEvent *e = (cEvent *)&ListObject; |
144 | return startTime - e->startTime; |
145 | } |
146 | |
147 | tChannelID cEvent::ChannelID(void) const |
148 | { |
149 | return schedule ? schedule->ChannelID() : tChannelID(); |
150 | } |
151 | |
152 | void 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 | |
163 | void cEvent::SetTableID(uchar TableID) |
164 | { |
165 | tableID = TableID; |
166 | } |
167 | |
168 | void cEvent::SetVersion(uchar Version) |
169 | { |
170 | version = Version; |
171 | } |
172 | |
173 | void 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 | |
180 | void cEvent::SetTitle(const char *Title) |
181 | { |
182 | title = strcpyrealloc(title, Title); |
183 | } |
184 | |
185 | void cEvent::SetShortText(const char *ShortText) |
186 | { |
187 | shortText = strcpyrealloc(shortText, ShortText); |
188 | } |
189 | |
190 | void cEvent::SetDescription(const char *Description) |
191 | { |
192 | description = strcpyrealloc(description, Description); |
193 | } |
194 | |
195 | void cEvent::SetComponents(cComponents *Components) |
196 | { |
197 | delete components; |
198 | components = Components; |
199 | } |
200 | |
201 | void cEvent::SetContents(uchar *Contents) |
202 | { |
203 | for (int i = 0; i < MaxEventContents; i++) |
204 | contents[i] = Contents[i]; |
205 | } |
206 | |
207 | void cEvent::SetParentalRating(int ParentalRating) |
208 | { |
209 | parentalRating = ParentalRating; |
210 | } |
211 | |
212 | void cEvent::SetStartTime(time_t StartTime) |
213 | { |
214 | if (startTime != StartTime) { |
215 | if (schedule) |
216 | schedule->UnhashEvent(this); |
217 | startTime = StartTime; |
218 | if (schedule) |
219 | schedule->HashEvent(this); |
220 | } |
221 | } |
222 | |
223 | void cEvent::SetDuration(int Duration) |
224 | { |
225 | duration = Duration; |
226 | } |
227 | |
228 | void cEvent::SetVps(time_t Vps) |
229 | { |
230 | vps = Vps; |
231 | } |
232 | |
233 | void cEvent::SetSeen(void) |
234 | { |
235 | seen = time(NULL__null); |
236 | } |
237 | |
238 | cString 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 | |
246 | bool cEvent::HasTimer(void) const |
247 | { |
248 | for (cTimer *t = Timers.First(); t; t = Timers.Next(t)) { |
249 | if (t->Event() == this) |
250 | return true; |
251 | } |
252 | return false; |
253 | } |
254 | |
255 | bool cEvent::IsRunning(bool OrAboutToStart) const |
256 | { |
257 | return runningStatus >= (OrAboutToStart ? SI::RunningStatusStartsInAFewSeconds : SI::RunningStatusPausing); |
258 | } |
259 | |
260 | const char *cEvent::ContentToString(uchar Content) |
261 | { |
262 | switch (Content & 0xF0) { |
263 | case ecgMovieDrama: |
264 | switch (Content & 0x0F) { |
265 | default: |
266 | case 0x00: return tr("Content$Movie/Drama")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 | |
402 | cString 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 | |
409 | cString cEvent::GetDateString(void) const |
410 | { |
411 | return DateString(startTime); |
412 | } |
413 | |
414 | cString cEvent::GetTimeString(void) const |
415 | { |
416 | return TimeString(startTime); |
417 | } |
418 | |
419 | cString cEvent::GetEndTimeString(void) const |
420 | { |
421 | return TimeString(startTime + duration); |
422 | } |
423 | |
424 | cString 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 | |
432 | void 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 | |
466 | bool cEvent::Parse(char *s) |
467 | { |
468 | char *t = skipspace(s + 1); |
469 | switch (*s) { |
470 | case 'T': SetTitle(t); |
471 | break; |
472 | case 'S': SetShortText(t); |
473 | break; |
474 | case 'D': strreplace(t, '|', '\n'); |
475 | SetDescription(t); |
476 | break; |
477 | case 'G': { |
478 | memset(contents, 0, sizeof(contents)); |
479 | for (int i = 0; i < MaxEventContents; i++) { |
480 | char *tail = NULL__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 | |
505 | bool 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 |
561 | struct tEpgBugFixStats { |
562 | int hits; |
563 | int n; |
564 | tChannelID channelIDs[MAXEPGBUGFIXCHANS100]; |
565 | tEpgBugFixStats(void) { hits = n = 0; } |
566 | }; |
567 | |
568 | tEpgBugFixStats EpgBugFixStats[MAXEPGBUGFIXSTATS13]; |
569 | |
570 | static 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 | |
585 | void 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 | |
644 | static void StripControlCharacters(char *s) |
645 | { |
646 | if (s) { |
647 | int len = strlen(s); |
648 | while (len > 0) { |
649 | int l = Utf8CharLen(s); |
650 | uchar *p = (uchar *)s; |
651 | if (l == 2 && *p == 0xC2) // UTF-8 sequence |
652 | p++; |
653 | if (*p == 0x86 || *p == 0x87) { |
654 | memmove(s, p + 1, len - l + 1); // we also copy the terminating 0! |
655 | len -= l; |
656 | l = 0; |
657 | } |
658 | s += l; |
659 | len -= l; |
660 | } |
661 | } |
662 | } |
663 | |
664 | void 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 | |
860 | Final: |
861 | |
862 | // VDR can't usefully handle newline characters in the title, shortText or component description of EPG |
863 | // data, so let's always convert them to blanks (independent of the setting of EPGBugfixLevel): |
864 | strreplace(title, '\n', ' '); |
865 | strreplace(shortText, '\n', ' '); |
866 | if (components) { |
867 | for (int i = 0; i < components->NumComponents(); i++) { |
868 | tComponent *p = components->Component(i); |
869 | if (p->description) |
870 | strreplace(p->description, '\n', ' '); |
871 | } |
872 | } |
873 | // Same for control characters: |
874 | StripControlCharacters(title); |
875 | StripControlCharacters(shortText); |
876 | StripControlCharacters(description); |
877 | } |
878 | |
879 | // --- cSchedule ------------------------------------------------------------- |
880 | |
881 | cSchedule::cSchedule(tChannelID ChannelID) |
882 | { |
883 | channelID = ChannelID; |
884 | hasRunning = false; |
885 | modified = 0; |
886 | presentSeen = 0; |
887 | } |
888 | |
889 | cEvent *cSchedule::AddEvent(cEvent *Event) |
890 | { |
891 | events.Add(Event); |
892 | Event->schedule = this; |
893 | HashEvent(Event); |
894 | return Event; |
895 | } |
896 | |
897 | void 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 | |
907 | void 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 | |
914 | void 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 | |
921 | const 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 | |
936 | const 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 | |
951 | const cEvent *cSchedule::GetEvent(tEventID EventID, time_t StartTime) const |
952 | { |
953 | // Returns the event info with the given StartTime or, if no actual StartTime |
954 | // is given, the one with the given EventID. |
955 | if (StartTime > 0) // 'StartTime < 0' is apparently used with NVOD channels |
956 | return eventsHashStartTime.Get(StartTime); |
957 | else |
958 | return eventsHashID.Get(EventID); |
959 | } |
960 | |
961 | const cEvent *cSchedule::GetEventAround(time_t Time) const |
962 | { |
963 | const cEvent *pe = NULL__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 | |
975 | void 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 | |
992 | void 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 | |
1005 | void cSchedule::ResetVersions(void) |
1006 | { |
1007 | for (cEvent *p = events.First(); p; p = events.Next(p)) |
1008 | p->SetVersion(0xFF); |
1009 | } |
1010 | |
1011 | void 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 | |
1024 | void cSchedule::DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version) |
1025 | { |
1026 | if (SegmentStart > 0 && SegmentEnd > 0) { |
1027 | for (cEvent *p = events.First(); p; p = events.Next(p)) { |
1028 | if (p->EndTime() > SegmentStart) { |
1029 | if (p->StartTime() < SegmentEnd) { |
1030 | // The event overlaps with the given time segment. |
1031 | if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) { |
1032 | // The segment overwrites all events from tables with higher ids, and |
1033 | // within the same table id all events must have the same version. |
1034 | // We can't delete the event right here because a timer might have |
1035 | // a pointer to it, so let's set its id and start time to 0 to have it |
1036 | // "phased out": |
1037 | if (hasRunning && p->IsRunning()) |
1038 | ClrRunningStatus(); |
1039 | UnhashEvent(p); |
1040 | p->eventID = 0; |
1041 | p->startTime = 0; |
1042 | } |
1043 | } |
1044 | else |
1045 | break; |
1046 | } |
1047 | } |
1048 | } |
1049 | } |
1050 | |
1051 | void cSchedule::Cleanup(void) |
1052 | { |
1053 | Cleanup(time(NULL__null)); |
1054 | } |
1055 | |
1056 | void 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 | |
1067 | void cSchedule::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) const |
1068 | { |
1069 | cChannel *channel = Channels.GetByChannelID(channelID, true); |
1070 | if (channel) { |
1071 | fprintf(f, "%sC %s %s\n", Prefix, *channel->GetChannelID().ToString(), channel->Name()); |
1072 | const cEvent *p; |
1073 | switch (DumpMode) { |
1074 | case dmAll: { |
1075 | for (p = events.First(); p; p = events.Next(p)) |
1076 | p->Dump(f, Prefix); |
1077 | } |
1078 | break; |
1079 | case dmPresent: { |
1080 | if ((p = GetPresentEvent()) != NULL__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 | |
1100 | bool cSchedule::Read(FILE *f, cSchedules *Schedules) |
1101 | { |
1102 | if (Schedules) { |
1103 | cReadLine ReadLine; |
1104 | char *s; |
1105 | while ((s = ReadLine.Read(f)) != NULL__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 | |
1140 | class cEpgDataWriter : public cThread { |
1141 | private: |
1142 | cMutex mutex; |
1143 | bool dump; |
1144 | protected: |
1145 | virtual void Action(void); |
1146 | public: |
1147 | cEpgDataWriter(void); |
1148 | void SetDump(bool Dump) { dump = Dump; } |
1149 | void Perform(void); |
1150 | }; |
1151 | |
1152 | cEpgDataWriter::cEpgDataWriter(void) |
1153 | :cThread("epg data writer", true) |
1154 | { |
1155 | dump = false; |
1156 | } |
1157 | |
1158 | void cEpgDataWriter::Action(void) |
1159 | { |
1160 | Perform(); |
1161 | } |
1162 | |
1163 | void 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 | |
1179 | static cEpgDataWriter EpgDataWriter; |
1180 | |
1181 | // --- cSchedulesLock -------------------------------------------------------- |
1182 | |
1183 | cSchedulesLock::cSchedulesLock(bool WriteLock, int TimeoutMs) |
1184 | { |
1185 | locked = cSchedules::schedules.rwlock.Lock(WriteLock, TimeoutMs); |
1186 | } |
1187 | |
1188 | cSchedulesLock::~cSchedulesLock() |
1189 | { |
1190 | if (locked) |
1191 | cSchedules::schedules.rwlock.Unlock(); |
1192 | } |
1193 | |
1194 | // --- cSchedules ------------------------------------------------------------ |
1195 | |
1196 | cSchedules cSchedules::schedules; |
1197 | char *cSchedules::epgDataFileName = NULL__null; |
1198 | time_t cSchedules::lastDump = time(NULL__null); |
1199 | time_t cSchedules::modified = 0; |
1200 | |
1201 | const cSchedules *cSchedules::Schedules(cSchedulesLock &SchedulesLock) |
1202 | { |
1203 | return SchedulesLock.Locked() ? &schedules : NULL__null; |
1204 | } |
1205 | |
1206 | void cSchedules::SetEpgDataFileName(const char *FileName) |
1207 | { |
1208 | free(epgDataFileName); |
1209 | epgDataFileName = FileName ? strdup(FileName) : NULL__null; |
1210 | EpgDataWriter.SetDump(epgDataFileName != NULL__null); |
1211 | } |
1212 | |
1213 | void cSchedules::SetModified(cSchedule *Schedule) |
1214 | { |
1215 | Schedule->SetModified(); |
1216 | modified = time(NULL__null); |
1217 | } |
1218 | |
1219 | void 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 | |
1233 | void 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 | |
1243 | bool 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 | |
1257 | bool cSchedules::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) |
1258 | { |
1259 | cSchedulesLock SchedulesLock; |
1260 | cSchedules *s = (cSchedules *)Schedules(SchedulesLock); |
1261 | if (s) { |
1262 | cSafeFile *sf = NULL__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 | |
1284 | bool cSchedules::Read(FILE *f) |
1285 | { |
1286 | cSchedulesLock SchedulesLock(true, 1000); |
1287 | cSchedules *s = (cSchedules *)Schedules(SchedulesLock); |
1288 | if (s) { |
1289 | bool OwnFile = f == NULL__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 | |
1314 | cSchedule *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 | |
1328 | const 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 | |
1338 | const cSchedule *cSchedules::GetSchedule(const cChannel *Channel, bool AddIfMissing) const |
1339 | { |
1340 | // This is not very beautiful, but it dramatically speeds up the |
1341 | // "What's on now/next?" menus. |
1342 | static cSchedule DummySchedule(tChannelID::InvalidID); |
1343 | if (!Channel->schedule) |
1344 | Channel->schedule = GetSchedule(Channel->GetChannelID()); |
1345 | if (!Channel->schedule) |
1346 | Channel->schedule = &DummySchedule; |
1347 | if (Channel->schedule == &DummySchedule && AddIfMissing) { |
1348 | cSchedule *Schedule = new cSchedule(Channel->GetChannelID()); |
1349 | ((cSchedules *)this)->Add(Schedule); |
1350 | Channel->schedule = Schedule; |
1351 | } |
1352 | return Channel->schedule != &DummySchedule? Channel->schedule : NULL__null; |
1353 | } |
1354 | |
1355 | // --- cEpgDataReader -------------------------------------------------------- |
1356 | |
1357 | cEpgDataReader::cEpgDataReader(void) |
1358 | :cThread("epg data reader") |
1359 | { |
1360 | } |
1361 | |
1362 | void cEpgDataReader::Action(void) |
1363 | { |
1364 | cSchedules::Read(); |
1365 | } |
1366 | |
1367 | // --- cEpgHandler ----------------------------------------------------------- |
1368 | |
1369 | cEpgHandler::cEpgHandler(void) |
1370 | { |
1371 | EpgHandlers.Add(this); |
1372 | } |
1373 | |
1374 | cEpgHandler::~cEpgHandler() |
1375 | { |
1376 | EpgHandlers.Del(this, false); |
1377 | } |
1378 | |
1379 | // --- cEpgHandlers ---------------------------------------------------------- |
1380 | |
1381 | cEpgHandlers EpgHandlers; |
1382 | |
1383 | bool 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 | |
1392 | bool cEpgHandlers::HandleEitEvent(cSchedule *Schedule, const SI::EIT::Event *EitEvent, uchar TableID, uchar Version) |
1393 | { |
1394 | for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { |
1395 | if (eh->HandleEitEvent(Schedule, EitEvent, TableID, Version)) |
1396 | return true; |
1397 | } |
1398 | return false; |
1399 | } |
1400 | |
1401 | bool 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 | |
1410 | bool cEpgHandlers::IsUpdate(tEventID EventID, time_t StartTime, uchar TableID, uchar Version) |
1411 | { |
1412 | for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { |
1413 | if (eh->IsUpdate(EventID, StartTime, TableID, Version)) |
1414 | return true; |
1415 | } |
1416 | return false; |
1417 | } |
1418 | |
1419 | void 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 | |
1428 | void cEpgHandlers::SetTitle(cEvent *Event, const char *Title) |
1429 | { |
1430 | for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { |
1431 | if (eh->SetTitle(Event, Title)) |
1432 | return; |
1433 | } |
1434 | Event->SetTitle(Title); |
1435 | } |
1436 | |
1437 | void cEpgHandlers::SetShortText(cEvent *Event, const char *ShortText) |
1438 | { |
1439 | for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { |
1440 | if (eh->SetShortText(Event, ShortText)) |
1441 | return; |
1442 | } |
1443 | Event->SetShortText(ShortText); |
1444 | } |
1445 | |
1446 | void cEpgHandlers::SetDescription(cEvent *Event, const char *Description) |
1447 | { |
1448 | for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { |
1449 | if (eh->SetDescription(Event, Description)) |
1450 | return; |
1451 | } |
1452 | Event->SetDescription(Description); |
1453 | } |
1454 | |
1455 | void cEpgHandlers::SetContents(cEvent *Event, uchar *Contents) |
1456 | { |
1457 | for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { |
1458 | if (eh->SetContents(Event, Contents)) |
1459 | return; |
1460 | } |
1461 | Event->SetContents(Contents); |
1462 | } |
1463 | |
1464 | void cEpgHandlers::SetParentalRating(cEvent *Event, int ParentalRating) |
1465 | { |
1466 | for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { |
1467 | if (eh->SetParentalRating(Event, ParentalRating)) |
1468 | return; |
1469 | } |
1470 | Event->SetParentalRating(ParentalRating); |
1471 | } |
1472 | |
1473 | void cEpgHandlers::SetStartTime(cEvent *Event, time_t StartTime) |
1474 | { |
1475 | for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { |
1476 | if (eh->SetStartTime(Event, StartTime)) |
1477 | return; |
1478 | } |
1479 | Event->SetStartTime(StartTime); |
1480 | } |
1481 | |
1482 | void cEpgHandlers::SetDuration(cEvent *Event, int Duration) |
1483 | { |
1484 | for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { |
1485 | if (eh->SetDuration(Event, Duration)) |
1486 | return; |
1487 | } |
1488 | Event->SetDuration(Duration); |
1489 | } |
1490 | |
1491 | void cEpgHandlers::SetVps(cEvent *Event, time_t Vps) |
1492 | { |
1493 | for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { |
1494 | if (eh->SetVps(Event, Vps)) |
1495 | return; |
1496 | } |
1497 | Event->SetVps(Vps); |
1498 | } |
1499 | |
1500 | void 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 | |
1509 | void 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 | |
1518 | void cEpgHandlers::HandleEvent(cEvent *Event) |
1519 | { |
1520 | for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { |
1521 | if (eh->HandleEvent(Event)) |
1522 | break; |
1523 | } |
1524 | } |
1525 | |
1526 | void 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 | |
1535 | void cEpgHandlers::DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version) |
1536 | { |
1537 | for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { |
1538 | if (eh->DropOutdated(Schedule, SegmentStart, SegmentEnd, TableID, Version)) |
1539 | return; |
1540 | } |
1541 | Schedule->DropOutdated(SegmentStart, SegmentEnd, TableID, Version); |
1542 | } |
1543 | |
1544 | void cEpgHandlers::BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus) |
1545 | { |
1546 | for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { |
1547 | if (eh->BeginSegmentTransfer(Channel, OnlyRunningStatus)) |
1548 | return; |
1549 | } |
1550 | } |
1551 | |
1552 | void cEpgHandlers::EndSegmentTransfer(bool Modified, bool OnlyRunningStatus) |
1553 | { |
1554 | for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { |
1555 | if (eh->EndSegmentTransfer(Modified, OnlyRunningStatus)) |
1556 | return; |
1557 | } |
1558 | } |