vdr
1.7.27
|
00001 /* 00002 * epg.c: Electronic Program Guide 00003 * 00004 * See the main source file 'vdr.c' for copyright information and 00005 * how to reach the author. 00006 * 00007 * Original version (as used in VDR before 1.3.0) written by 00008 * Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>. 00009 * 00010 * $Id: epg.c 2.12 2012/03/10 13:14:27 kls Exp $ 00011 */ 00012 00013 #include "epg.h" 00014 #include <ctype.h> 00015 #include <limits.h> 00016 #include <time.h> 00017 #include "libsi/si.h" 00018 #include "timers.h" 00019 00020 #define RUNNINGSTATUSTIMEOUT 30 // seconds before the running status is considered unknown 00021 00022 // --- tComponent ------------------------------------------------------------ 00023 00024 cString tComponent::ToString(void) 00025 { 00026 char buffer[256]; 00027 snprintf(buffer, sizeof(buffer), "%X %02X %s %s", stream, type, language, description ? description : ""); 00028 return buffer; 00029 } 00030 00031 bool tComponent::FromString(const char *s) 00032 { 00033 unsigned int Stream, Type; 00034 int n = sscanf(s, "%X %02X %7s %a[^\n]", &Stream, &Type, language, &description); // 7 = MAXLANGCODE2 - 1 00035 if (n != 4 || isempty(description)) { 00036 free(description); 00037 description = NULL; 00038 } 00039 stream = Stream; 00040 type = Type; 00041 return n >= 3; 00042 } 00043 00044 // --- cComponents ----------------------------------------------------------- 00045 00046 cComponents::cComponents(void) 00047 { 00048 numComponents = 0; 00049 components = NULL; 00050 } 00051 00052 cComponents::~cComponents(void) 00053 { 00054 for (int i = 0; i < numComponents; i++) 00055 free(components[i].description); 00056 free(components); 00057 } 00058 00059 bool cComponents::Realloc(int Index) 00060 { 00061 if (Index >= numComponents) { 00062 Index++; 00063 if (tComponent *NewBuffer = (tComponent *)realloc(components, Index * sizeof(tComponent))) { 00064 int n = numComponents; 00065 numComponents = Index; 00066 components = NewBuffer; 00067 memset(&components[n], 0, sizeof(tComponent) * (numComponents - n)); 00068 } 00069 else { 00070 esyslog("ERROR: out of memory"); 00071 return false; 00072 } 00073 } 00074 return true; 00075 } 00076 00077 void cComponents::SetComponent(int Index, const char *s) 00078 { 00079 if (Realloc(Index)) 00080 components[Index].FromString(s); 00081 } 00082 00083 void cComponents::SetComponent(int Index, uchar Stream, uchar Type, const char *Language, const char *Description) 00084 { 00085 if (!Realloc(Index)) 00086 return; 00087 tComponent *p = &components[Index]; 00088 p->stream = Stream; 00089 p->type = Type; 00090 strn0cpy(p->language, Language, sizeof(p->language)); 00091 char *q = strchr(p->language, ','); 00092 if (q) 00093 *q = 0; // strips rest of "normalized" language codes 00094 p->description = strcpyrealloc(p->description, !isempty(Description) ? Description : NULL); 00095 } 00096 00097 tComponent *cComponents::GetComponent(int Index, uchar Stream, uchar Type) 00098 { 00099 for (int i = 0; i < numComponents; i++) { 00100 if (components[i].stream == Stream && ( 00101 Type == 0 || // don't care about the actual Type 00102 Stream == 2 && (components[i].type < 5) == (Type < 5) // fallback "Dolby" component according to the "Premiere pseudo standard" 00103 )) { 00104 if (!Index--) 00105 return &components[i]; 00106 } 00107 } 00108 return NULL; 00109 } 00110 00111 // --- cEvent ---------------------------------------------------------------- 00112 00113 cEvent::cEvent(tEventID EventID) 00114 { 00115 schedule = NULL; 00116 eventID = EventID; 00117 tableID = 0xFF; // actual table ids are 0x4E..0x60 00118 version = 0xFF; // actual version numbers are 0..31 00119 runningStatus = SI::RunningStatusUndefined; 00120 title = NULL; 00121 shortText = NULL; 00122 description = NULL; 00123 components = NULL; 00124 memset(contents, 0, sizeof(contents)); 00125 parentalRating = 0; 00126 startTime = 0; 00127 duration = 0; 00128 vps = 0; 00129 SetSeen(); 00130 } 00131 00132 cEvent::~cEvent() 00133 { 00134 free(title); 00135 free(shortText); 00136 free(description); 00137 delete components; 00138 } 00139 00140 int cEvent::Compare(const cListObject &ListObject) const 00141 { 00142 cEvent *e = (cEvent *)&ListObject; 00143 return startTime - e->startTime; 00144 } 00145 00146 tChannelID cEvent::ChannelID(void) const 00147 { 00148 return schedule ? schedule->ChannelID() : tChannelID(); 00149 } 00150 00151 void cEvent::SetEventID(tEventID EventID) 00152 { 00153 if (eventID != EventID) { 00154 if (schedule) 00155 schedule->UnhashEvent(this); 00156 eventID = EventID; 00157 if (schedule) 00158 schedule->HashEvent(this); 00159 } 00160 } 00161 00162 void cEvent::SetTableID(uchar TableID) 00163 { 00164 tableID = TableID; 00165 } 00166 00167 void cEvent::SetVersion(uchar Version) 00168 { 00169 version = Version; 00170 } 00171 00172 void cEvent::SetRunningStatus(int RunningStatus, cChannel *Channel) 00173 { 00174 if (Channel && runningStatus != RunningStatus && (RunningStatus > SI::RunningStatusNotRunning || runningStatus > SI::RunningStatusUndefined) && Channel->HasTimer()) 00175 isyslog("channel %d (%s) event %s status %d", Channel->Number(), Channel->Name(), *ToDescr(), RunningStatus); 00176 runningStatus = RunningStatus; 00177 } 00178 00179 void cEvent::SetTitle(const char *Title) 00180 { 00181 title = strcpyrealloc(title, Title); 00182 } 00183 00184 void cEvent::SetShortText(const char *ShortText) 00185 { 00186 shortText = strcpyrealloc(shortText, ShortText); 00187 } 00188 00189 void cEvent::SetDescription(const char *Description) 00190 { 00191 description = strcpyrealloc(description, Description); 00192 } 00193 00194 void cEvent::SetComponents(cComponents *Components) 00195 { 00196 delete components; 00197 components = Components; 00198 } 00199 00200 void cEvent::SetContents(uchar *Contents) 00201 { 00202 for (int i = 0; i < MaxEventContents; i++) 00203 contents[i] = Contents[i]; 00204 } 00205 00206 void cEvent::SetParentalRating(int ParentalRating) 00207 { 00208 parentalRating = ParentalRating; 00209 } 00210 00211 void cEvent::SetStartTime(time_t StartTime) 00212 { 00213 if (startTime != StartTime) { 00214 if (schedule) 00215 schedule->UnhashEvent(this); 00216 startTime = StartTime; 00217 if (schedule) 00218 schedule->HashEvent(this); 00219 } 00220 } 00221 00222 void cEvent::SetDuration(int Duration) 00223 { 00224 duration = Duration; 00225 } 00226 00227 void cEvent::SetVps(time_t Vps) 00228 { 00229 vps = Vps; 00230 } 00231 00232 void cEvent::SetSeen(void) 00233 { 00234 seen = time(NULL); 00235 } 00236 00237 cString cEvent::ToDescr(void) const 00238 { 00239 char vpsbuf[64] = ""; 00240 if (Vps()) 00241 sprintf(vpsbuf, "(VPS: %s) ", *GetVpsString()); 00242 return cString::sprintf("%s %s-%s %s'%s'", *GetDateString(), *GetTimeString(), *GetEndTimeString(), vpsbuf, Title()); 00243 } 00244 00245 bool cEvent::HasTimer(void) const 00246 { 00247 for (cTimer *t = Timers.First(); t; t = Timers.Next(t)) { 00248 if (t->Event() == this) 00249 return true; 00250 } 00251 return false; 00252 } 00253 00254 bool cEvent::IsRunning(bool OrAboutToStart) const 00255 { 00256 return runningStatus >= (OrAboutToStart ? SI::RunningStatusStartsInAFewSeconds : SI::RunningStatusPausing); 00257 } 00258 00259 const char *cEvent::ContentToString(uchar Content) 00260 { 00261 switch (Content & 0xF0) { 00262 case ecgMovieDrama: 00263 switch (Content & 0x0F) { 00264 default: 00265 case 0x00: return tr("Content$Movie/Drama"); 00266 case 0x01: return tr("Content$Detective/Thriller"); 00267 case 0x02: return tr("Content$Adventure/Western/War"); 00268 case 0x03: return tr("Content$Science Fiction/Fantasy/Horror"); 00269 case 0x04: return tr("Content$Comedy"); 00270 case 0x05: return tr("Content$Soap/Melodrama/Folkloric"); 00271 case 0x06: return tr("Content$Romance"); 00272 case 0x07: return tr("Content$Serious/Classical/Religious/Historical Movie/Drama"); 00273 case 0x08: return tr("Content$Adult Movie/Drama"); 00274 } 00275 break; 00276 case ecgNewsCurrentAffairs: 00277 switch (Content & 0x0F) { 00278 default: 00279 case 0x00: return tr("Content$News/Current Affairs"); 00280 case 0x01: return tr("Content$News/Weather Report"); 00281 case 0x02: return tr("Content$News Magazine"); 00282 case 0x03: return tr("Content$Documentary"); 00283 case 0x04: return tr("Content$Discussion/Inverview/Debate"); 00284 } 00285 break; 00286 case ecgShow: 00287 switch (Content & 0x0F) { 00288 default: 00289 case 0x00: return tr("Content$Show/Game Show"); 00290 case 0x01: return tr("Content$Game Show/Quiz/Contest"); 00291 case 0x02: return tr("Content$Variety Show"); 00292 case 0x03: return tr("Content$Talk Show"); 00293 } 00294 break; 00295 case ecgSports: 00296 switch (Content & 0x0F) { 00297 default: 00298 case 0x00: return tr("Content$Sports"); 00299 case 0x01: return tr("Content$Special Event"); 00300 case 0x02: return tr("Content$Sport Magazine"); 00301 case 0x03: return tr("Content$Football/Soccer"); 00302 case 0x04: return tr("Content$Tennis/Squash"); 00303 case 0x05: return tr("Content$Team Sports"); 00304 case 0x06: return tr("Content$Athletics"); 00305 case 0x07: return tr("Content$Motor Sport"); 00306 case 0x08: return tr("Content$Water Sport"); 00307 case 0x09: return tr("Content$Winter Sports"); 00308 case 0x0A: return tr("Content$Equestrian"); 00309 case 0x0B: return tr("Content$Martial Sports"); 00310 } 00311 break; 00312 case ecgChildrenYouth: 00313 switch (Content & 0x0F) { 00314 default: 00315 case 0x00: return tr("Content$Children's/Youth Programme"); 00316 case 0x01: return tr("Content$Pre-school Children's Programme"); 00317 case 0x02: return tr("Content$Entertainment Programme for 6 to 14"); 00318 case 0x03: return tr("Content$Entertainment Programme for 10 to 16"); 00319 case 0x04: return tr("Content$Informational/Educational/School Programme"); 00320 case 0x05: return tr("Content$Cartoons/Puppets"); 00321 } 00322 break; 00323 case ecgMusicBalletDance: 00324 switch (Content & 0x0F) { 00325 default: 00326 case 0x00: return tr("Content$Music/Ballet/Dance"); 00327 case 0x01: return tr("Content$Rock/Pop"); 00328 case 0x02: return tr("Content$Serious/Classical Music"); 00329 case 0x03: return tr("Content$Folk/Tradional Music"); 00330 case 0x04: return tr("Content$Jazz"); 00331 case 0x05: return tr("Content$Musical/Opera"); 00332 case 0x06: return tr("Content$Ballet"); 00333 } 00334 break; 00335 case ecgArtsCulture: 00336 switch (Content & 0x0F) { 00337 default: 00338 case 0x00: return tr("Content$Arts/Culture"); 00339 case 0x01: return tr("Content$Performing Arts"); 00340 case 0x02: return tr("Content$Fine Arts"); 00341 case 0x03: return tr("Content$Religion"); 00342 case 0x04: return tr("Content$Popular Culture/Traditional Arts"); 00343 case 0x05: return tr("Content$Literature"); 00344 case 0x06: return tr("Content$Film/Cinema"); 00345 case 0x07: return tr("Content$Experimental Film/Video"); 00346 case 0x08: return tr("Content$Broadcasting/Press"); 00347 case 0x09: return tr("Content$New Media"); 00348 case 0x0A: return tr("Content$Arts/Culture Magazine"); 00349 case 0x0B: return tr("Content$Fashion"); 00350 } 00351 break; 00352 case ecgSocialPoliticalEconomics: 00353 switch (Content & 0x0F) { 00354 default: 00355 case 0x00: return tr("Content$Social/Political/Economics"); 00356 case 0x01: return tr("Content$Magazine/Report/Documentary"); 00357 case 0x02: return tr("Content$Economics/Social Advisory"); 00358 case 0x03: return tr("Content$Remarkable People"); 00359 } 00360 break; 00361 case ecgEducationalScience: 00362 switch (Content & 0x0F) { 00363 default: 00364 case 0x00: return tr("Content$Education/Science/Factual"); 00365 case 0x01: return tr("Content$Nature/Animals/Environment"); 00366 case 0x02: return tr("Content$Technology/Natural Sciences"); 00367 case 0x03: return tr("Content$Medicine/Physiology/Psychology"); 00368 case 0x04: return tr("Content$Foreign Countries/Expeditions"); 00369 case 0x05: return tr("Content$Social/Spiritual Sciences"); 00370 case 0x06: return tr("Content$Further Education"); 00371 case 0x07: return tr("Content$Languages"); 00372 } 00373 break; 00374 case ecgLeisureHobbies: 00375 switch (Content & 0x0F) { 00376 default: 00377 case 0x00: return tr("Content$Leisure/Hobbies"); 00378 case 0x01: return tr("Content$Tourism/Travel"); 00379 case 0x02: return tr("Content$Handicraft"); 00380 case 0x03: return tr("Content$Motoring"); 00381 case 0x04: return tr("Content$Fitness & Health"); 00382 case 0x05: return tr("Content$Cooking"); 00383 case 0x06: return tr("Content$Advertisement/Shopping"); 00384 case 0x07: return tr("Content$Gardening"); 00385 } 00386 break; 00387 case ecgSpecial: 00388 switch (Content & 0x0F) { 00389 case 0x00: return tr("Content$Original Language"); 00390 case 0x01: return tr("Content$Black & White"); 00391 case 0x02: return tr("Content$Unpublished"); 00392 case 0x03: return tr("Content$Live Broadcast"); 00393 default: ; 00394 } 00395 break; 00396 default: ; 00397 } 00398 return ""; 00399 } 00400 00401 cString cEvent::GetParentalRatingString(void) const 00402 { 00403 if (parentalRating) 00404 return cString::sprintf(tr("ParentalRating$from %d"), parentalRating); 00405 return NULL; 00406 } 00407 00408 cString cEvent::GetDateString(void) const 00409 { 00410 return DateString(startTime); 00411 } 00412 00413 cString cEvent::GetTimeString(void) const 00414 { 00415 return TimeString(startTime); 00416 } 00417 00418 cString cEvent::GetEndTimeString(void) const 00419 { 00420 return TimeString(startTime + duration); 00421 } 00422 00423 cString cEvent::GetVpsString(void) const 00424 { 00425 char buf[25]; 00426 struct tm tm_r; 00427 strftime(buf, sizeof(buf), "%d.%m. %R", localtime_r(&vps, &tm_r)); 00428 return buf; 00429 } 00430 00431 void cEvent::Dump(FILE *f, const char *Prefix, bool InfoOnly) const 00432 { 00433 if (InfoOnly || startTime + duration + Setup.EPGLinger * 60 >= time(NULL)) { 00434 fprintf(f, "%sE %u %ld %d %X %X\n", Prefix, eventID, startTime, duration, tableID, version); 00435 if (!isempty(title)) 00436 fprintf(f, "%sT %s\n", Prefix, title); 00437 if (!isempty(shortText)) 00438 fprintf(f, "%sS %s\n", Prefix, shortText); 00439 if (!isempty(description)) { 00440 strreplace(description, '\n', '|'); 00441 fprintf(f, "%sD %s\n", Prefix, description); 00442 strreplace(description, '|', '\n'); 00443 } 00444 if (contents[0]) { 00445 fprintf(f, "%sG", Prefix); 00446 for (int i = 0; Contents(i); i++) 00447 fprintf(f, " %02X", Contents(i)); 00448 fprintf(f, "\n"); 00449 } 00450 if (parentalRating) 00451 fprintf(f, "%sR %d\n", Prefix, parentalRating); 00452 if (components) { 00453 for (int i = 0; i < components->NumComponents(); i++) { 00454 tComponent *p = components->Component(i); 00455 fprintf(f, "%sX %s\n", Prefix, *p->ToString()); 00456 } 00457 } 00458 if (vps) 00459 fprintf(f, "%sV %ld\n", Prefix, vps); 00460 if (!InfoOnly) 00461 fprintf(f, "%se\n", Prefix); 00462 } 00463 } 00464 00465 bool cEvent::Parse(char *s) 00466 { 00467 char *t = skipspace(s + 1); 00468 switch (*s) { 00469 case 'T': SetTitle(t); 00470 break; 00471 case 'S': SetShortText(t); 00472 break; 00473 case 'D': strreplace(t, '|', '\n'); 00474 SetDescription(t); 00475 break; 00476 case 'G': { 00477 memset(contents, 0, sizeof(contents)); 00478 for (int i = 0; i < MaxEventContents; i++) { 00479 char *tail = NULL; 00480 int c = strtol(t, &tail, 16); 00481 if (0x00 < c && c <= 0xFF) { 00482 contents[i] = c; 00483 t = tail; 00484 } 00485 else 00486 break; 00487 } 00488 } 00489 break; 00490 case 'R': SetParentalRating(atoi(t)); 00491 break; 00492 case 'X': if (!components) 00493 components = new cComponents; 00494 components->SetComponent(components->NumComponents(), t); 00495 break; 00496 case 'V': SetVps(atoi(t)); 00497 break; 00498 default: esyslog("ERROR: unexpected tag while reading EPG data: %s", s); 00499 return false; 00500 } 00501 return true; 00502 } 00503 00504 bool cEvent::Read(FILE *f, cSchedule *Schedule) 00505 { 00506 if (Schedule) { 00507 cEvent *Event = NULL; 00508 char *s; 00509 int line = 0; 00510 cReadLine ReadLine; 00511 while ((s = ReadLine.Read(f)) != NULL) { 00512 line++; 00513 char *t = skipspace(s + 1); 00514 switch (*s) { 00515 case 'E': if (!Event) { 00516 unsigned int EventID; 00517 time_t StartTime; 00518 int Duration; 00519 unsigned int TableID = 0; 00520 unsigned int Version = 0xFF; // actual value is ignored 00521 int n = sscanf(t, "%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version); 00522 if (n >= 3 && n <= 5) { 00523 Event = (cEvent *)Schedule->GetEvent(EventID, StartTime); 00524 cEvent *newEvent = NULL; 00525 if (Event) 00526 DELETENULL(Event->components); 00527 if (!Event) { 00528 Event = newEvent = new cEvent(EventID); 00529 Event->seen = 0; 00530 } 00531 if (Event) { 00532 Event->SetTableID(TableID); 00533 Event->SetStartTime(StartTime); 00534 Event->SetDuration(Duration); 00535 if (newEvent) 00536 Schedule->AddEvent(newEvent); 00537 } 00538 } 00539 } 00540 break; 00541 case 'e': if (Event && !Event->Title()) 00542 Event->SetTitle(tr("No title")); 00543 Event = NULL; 00544 break; 00545 case 'c': // to keep things simple we react on 'c' here 00546 return true; 00547 default: if (Event && !Event->Parse(s)) { 00548 esyslog("ERROR: EPG data problem in line %d", line); 00549 return false; 00550 } 00551 } 00552 } 00553 esyslog("ERROR: unexpected end of file while reading EPG data"); 00554 } 00555 return false; 00556 } 00557 00558 #define MAXEPGBUGFIXSTATS 13 00559 #define MAXEPGBUGFIXCHANS 100 00560 struct tEpgBugFixStats { 00561 int hits; 00562 int n; 00563 tChannelID channelIDs[MAXEPGBUGFIXCHANS]; 00564 tEpgBugFixStats(void) { hits = n = 0; } 00565 }; 00566 00567 tEpgBugFixStats EpgBugFixStats[MAXEPGBUGFIXSTATS]; 00568 00569 static void EpgBugFixStat(int Number, tChannelID ChannelID) 00570 { 00571 if (0 <= Number && Number < MAXEPGBUGFIXSTATS) { 00572 tEpgBugFixStats *p = &EpgBugFixStats[Number]; 00573 p->hits++; 00574 int i = 0; 00575 for (; i < p->n; i++) { 00576 if (p->channelIDs[i] == ChannelID) 00577 break; 00578 } 00579 if (i == p->n && p->n < MAXEPGBUGFIXCHANS) 00580 p->channelIDs[p->n++] = ChannelID; 00581 } 00582 } 00583 00584 void ReportEpgBugFixStats(bool Reset) 00585 { 00586 if (Setup.EPGBugfixLevel > 0) { 00587 bool GotHits = false; 00588 char buffer[1024]; 00589 for (int i = 0; i < MAXEPGBUGFIXSTATS; i++) { 00590 const char *delim = " "; 00591 tEpgBugFixStats *p = &EpgBugFixStats[i]; 00592 if (p->hits) { 00593 bool PrintedStats = false; 00594 char *q = buffer; 00595 *buffer = 0; 00596 for (int c = 0; c < p->n; c++) { 00597 cChannel *channel = Channels.GetByChannelID(p->channelIDs[c], true); 00598 if (channel) { 00599 if (!GotHits) { 00600 dsyslog("====================="); 00601 dsyslog("EPG bugfix statistics"); 00602 dsyslog("====================="); 00603 dsyslog("IF SOMEBODY WHO IS IN CHARGE OF THE EPG DATA FOR ONE OF THE LISTED"); 00604 dsyslog("CHANNELS READS THIS: PLEASE TAKE A LOOK AT THE FUNCTION cEvent::FixEpgBugs()"); 00605 dsyslog("IN VDR/epg.c TO LEARN WHAT'S WRONG WITH YOUR DATA, AND FIX IT!"); 00606 dsyslog("====================="); 00607 dsyslog("Fix Hits Channels"); 00608 GotHits = true; 00609 } 00610 if (!PrintedStats) { 00611 q += snprintf(q, sizeof(buffer) - (q - buffer), "%-3d %-4d", i, p->hits); 00612 PrintedStats = true; 00613 } 00614 q += snprintf(q, sizeof(buffer) - (q - buffer), "%s%s", delim, channel->Name()); 00615 delim = ", "; 00616 if (q - buffer > 80) { 00617 q += snprintf(q, sizeof(buffer) - (q - buffer), "%s...", delim); 00618 break; 00619 } 00620 } 00621 } 00622 if (*buffer) 00623 dsyslog("%s", buffer); 00624 } 00625 if (Reset) 00626 p->hits = p->n = 0; 00627 } 00628 if (GotHits) 00629 dsyslog("====================="); 00630 } 00631 } 00632 00633 void cEvent::FixEpgBugs(void) 00634 { 00635 if (isempty(title)) { 00636 // we don't want any "(null)" titles 00637 title = strcpyrealloc(title, tr("No title")); 00638 EpgBugFixStat(12, ChannelID()); 00639 } 00640 00641 if (Setup.EPGBugfixLevel == 0) 00642 goto Final; 00643 00644 // Some TV stations apparently have their own idea about how to fill in the 00645 // EPG data. Let's fix their bugs as good as we can: 00646 00647 // Some channels put the ShortText in quotes and use either the ShortText 00648 // or the Description field, depending on how long the string is: 00649 // 00650 // Title 00651 // "ShortText". Description 00652 // 00653 if ((shortText == NULL) != (description == NULL)) { 00654 char *p = shortText ? shortText : description; 00655 if (*p == '"') { 00656 const char *delim = "\"."; 00657 char *e = strstr(p + 1, delim); 00658 if (e) { 00659 *e = 0; 00660 char *s = strdup(p + 1); 00661 char *d = strdup(e + strlen(delim)); 00662 free(shortText); 00663 free(description); 00664 shortText = s; 00665 description = d; 00666 EpgBugFixStat(1, ChannelID()); 00667 } 00668 } 00669 } 00670 00671 // Some channels put the Description into the ShortText (preceded 00672 // by a blank) if there is no actual ShortText and the Description 00673 // is short enough: 00674 // 00675 // Title 00676 // Description 00677 // 00678 if (shortText && !description) { 00679 if (*shortText == ' ') { 00680 memmove(shortText, shortText + 1, strlen(shortText)); 00681 description = shortText; 00682 shortText = NULL; 00683 EpgBugFixStat(2, ChannelID()); 00684 } 00685 } 00686 00687 // Sometimes they repeat the Title in the ShortText: 00688 // 00689 // Title 00690 // Title 00691 // 00692 if (shortText && strcmp(title, shortText) == 0) { 00693 free(shortText); 00694 shortText = NULL; 00695 EpgBugFixStat(3, ChannelID()); 00696 } 00697 00698 // Some channels put the ShortText between double quotes, which is nothing 00699 // but annoying (some even put a '.' after the closing '"'): 00700 // 00701 // Title 00702 // "ShortText"[.] 00703 // 00704 if (shortText && *shortText == '"') { 00705 int l = strlen(shortText); 00706 if (l > 2 && (shortText[l - 1] == '"' || (shortText[l - 1] == '.' && shortText[l - 2] == '"'))) { 00707 memmove(shortText, shortText + 1, l); 00708 char *p = strrchr(shortText, '"'); 00709 if (p) 00710 *p = 0; 00711 EpgBugFixStat(4, ChannelID()); 00712 } 00713 } 00714 00715 if (Setup.EPGBugfixLevel <= 1) 00716 goto Final; 00717 00718 // Some channels apparently try to do some formatting in the texts, 00719 // which is a bad idea because they have no way of knowing the width 00720 // of the window that will actually display the text. 00721 // Remove excess whitespace: 00722 title = compactspace(title); 00723 shortText = compactspace(shortText); 00724 description = compactspace(description); 00725 00726 #define MAX_USEFUL_EPISODE_LENGTH 40 00727 // Some channels put a whole lot of information in the ShortText and leave 00728 // the Description totally empty. So if the ShortText length exceeds 00729 // MAX_USEFUL_EPISODE_LENGTH, let's put this into the Description 00730 // instead: 00731 if (!isempty(shortText) && isempty(description)) { 00732 if (strlen(shortText) > MAX_USEFUL_EPISODE_LENGTH) { 00733 free(description); 00734 description = shortText; 00735 shortText = NULL; 00736 EpgBugFixStat(6, ChannelID()); 00737 } 00738 } 00739 00740 // Some channels put the same information into ShortText and Description. 00741 // In that case we delete one of them: 00742 if (shortText && description && strcmp(shortText, description) == 0) { 00743 if (strlen(shortText) > MAX_USEFUL_EPISODE_LENGTH) { 00744 free(shortText); 00745 shortText = NULL; 00746 } 00747 else { 00748 free(description); 00749 description = NULL; 00750 } 00751 EpgBugFixStat(7, ChannelID()); 00752 } 00753 00754 // Some channels use the ` ("backtick") character, where a ' (single quote) 00755 // would be normally used. Actually, "backticks" in normal text don't make 00756 // much sense, so let's replace them: 00757 strreplace(title, '`', '\''); 00758 strreplace(shortText, '`', '\''); 00759 strreplace(description, '`', '\''); 00760 00761 if (Setup.EPGBugfixLevel <= 2) 00762 goto Final; 00763 00764 // The stream components have a "description" field which some channels 00765 // apparently have no idea of how to set correctly: 00766 if (components) { 00767 for (int i = 0; i < components->NumComponents(); i++) { 00768 tComponent *p = components->Component(i); 00769 switch (p->stream) { 00770 case 0x01: { // video 00771 if (p->description) { 00772 if (strcasecmp(p->description, "Video") == 0 || 00773 strcasecmp(p->description, "Bildformat") == 0) { 00774 // Yes, we know it's video - that's what the 'stream' code 00775 // is for! But _which_ video is it? 00776 free(p->description); 00777 p->description = NULL; 00778 EpgBugFixStat(8, ChannelID()); 00779 } 00780 } 00781 if (!p->description) { 00782 switch (p->type) { 00783 case 0x01: 00784 case 0x05: p->description = strdup("4:3"); break; 00785 case 0x02: 00786 case 0x03: 00787 case 0x06: 00788 case 0x07: p->description = strdup("16:9"); break; 00789 case 0x04: 00790 case 0x08: p->description = strdup(">16:9"); break; 00791 case 0x09: 00792 case 0x0D: p->description = strdup("HD 4:3"); break; 00793 case 0x0A: 00794 case 0x0B: 00795 case 0x0E: 00796 case 0x0F: p->description = strdup("HD 16:9"); break; 00797 case 0x0C: 00798 case 0x10: p->description = strdup("HD >16:9"); break; 00799 default: ; 00800 } 00801 EpgBugFixStat(9, ChannelID()); 00802 } 00803 } 00804 break; 00805 case 0x02: { // audio 00806 if (p->description) { 00807 if (strcasecmp(p->description, "Audio") == 0) { 00808 // Yes, we know it's audio - that's what the 'stream' code 00809 // is for! But _which_ audio is it? 00810 free(p->description); 00811 p->description = NULL; 00812 EpgBugFixStat(10, ChannelID()); 00813 } 00814 } 00815 if (!p->description) { 00816 switch (p->type) { 00817 case 0x05: p->description = strdup("Dolby Digital"); break; 00818 default: ; // all others will just display the language 00819 } 00820 EpgBugFixStat(11, ChannelID()); 00821 } 00822 } 00823 break; 00824 default: ; 00825 } 00826 } 00827 } 00828 00829 Final: 00830 00831 // VDR can't usefully handle newline characters in the title, shortText or component description of EPG 00832 // data, so let's always convert them to blanks (independent of the setting of EPGBugfixLevel): 00833 strreplace(title, '\n', ' '); 00834 strreplace(shortText, '\n', ' '); 00835 if (components) { 00836 for (int i = 0; i < components->NumComponents(); i++) { 00837 tComponent *p = components->Component(i); 00838 if (p->description) 00839 strreplace(p->description, '\n', ' '); 00840 } 00841 } 00842 /* TODO adapt to UTF-8 00843 // Same for control characters: 00844 strreplace(title, '\x86', ' '); 00845 strreplace(title, '\x87', ' '); 00846 strreplace(shortText, '\x86', ' '); 00847 strreplace(shortText, '\x87', ' '); 00848 strreplace(description, '\x86', ' '); 00849 strreplace(description, '\x87', ' '); 00850 XXX*/ 00851 } 00852 00853 // --- cSchedule ------------------------------------------------------------- 00854 00855 cSchedule::cSchedule(tChannelID ChannelID) 00856 { 00857 channelID = ChannelID; 00858 hasRunning = false; 00859 modified = 0; 00860 presentSeen = 0; 00861 } 00862 00863 cEvent *cSchedule::AddEvent(cEvent *Event) 00864 { 00865 events.Add(Event); 00866 Event->schedule = this; 00867 HashEvent(Event); 00868 return Event; 00869 } 00870 00871 void cSchedule::DelEvent(cEvent *Event) 00872 { 00873 if (Event->schedule == this) { 00874 if (hasRunning && Event->IsRunning()) 00875 ClrRunningStatus(); 00876 UnhashEvent(Event); 00877 events.Del(Event); 00878 } 00879 } 00880 00881 void cSchedule::HashEvent(cEvent *Event) 00882 { 00883 eventsHashID.Add(Event, Event->EventID()); 00884 if (Event->StartTime() > 0) // 'StartTime < 0' is apparently used with NVOD channels 00885 eventsHashStartTime.Add(Event, Event->StartTime()); 00886 } 00887 00888 void cSchedule::UnhashEvent(cEvent *Event) 00889 { 00890 eventsHashID.Del(Event, Event->EventID()); 00891 if (Event->StartTime() > 0) // 'StartTime < 0' is apparently used with NVOD channels 00892 eventsHashStartTime.Del(Event, Event->StartTime()); 00893 } 00894 00895 const cEvent *cSchedule::GetPresentEvent(void) const 00896 { 00897 const cEvent *pe = NULL; 00898 time_t now = time(NULL); 00899 for (cEvent *p = events.First(); p; p = events.Next(p)) { 00900 if (p->StartTime() <= now) 00901 pe = p; 00902 else if (p->StartTime() > now + 3600) 00903 break; 00904 if (p->SeenWithin(RUNNINGSTATUSTIMEOUT) && p->RunningStatus() >= SI::RunningStatusPausing) 00905 return p; 00906 } 00907 return pe; 00908 } 00909 00910 const cEvent *cSchedule::GetFollowingEvent(void) const 00911 { 00912 const cEvent *p = GetPresentEvent(); 00913 if (p) 00914 p = events.Next(p); 00915 else { 00916 time_t now = time(NULL); 00917 for (p = events.First(); p; p = events.Next(p)) { 00918 if (p->StartTime() >= now) 00919 break; 00920 } 00921 } 00922 return p; 00923 } 00924 00925 const cEvent *cSchedule::GetEvent(tEventID EventID, time_t StartTime) const 00926 { 00927 // Returns the event info with the given StartTime or, if no actual StartTime 00928 // is given, the one with the given EventID. 00929 if (StartTime > 0) // 'StartTime < 0' is apparently used with NVOD channels 00930 return eventsHashStartTime.Get(StartTime); 00931 else 00932 return eventsHashID.Get(EventID); 00933 } 00934 00935 const cEvent *cSchedule::GetEventAround(time_t Time) const 00936 { 00937 const cEvent *pe = NULL; 00938 time_t delta = INT_MAX; 00939 for (cEvent *p = events.First(); p; p = events.Next(p)) { 00940 time_t dt = Time - p->StartTime(); 00941 if (dt >= 0 && dt < delta && p->EndTime() >= Time) { 00942 delta = dt; 00943 pe = p; 00944 } 00945 } 00946 return pe; 00947 } 00948 00949 void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Channel) 00950 { 00951 hasRunning = false; 00952 for (cEvent *p = events.First(); p; p = events.Next(p)) { 00953 if (p == Event) { 00954 if (p->RunningStatus() > SI::RunningStatusNotRunning || RunningStatus > SI::RunningStatusNotRunning) { 00955 p->SetRunningStatus(RunningStatus, Channel); 00956 break; 00957 } 00958 } 00959 else if (RunningStatus >= SI::RunningStatusPausing && p->StartTime() < Event->StartTime()) 00960 p->SetRunningStatus(SI::RunningStatusNotRunning); 00961 if (p->RunningStatus() >= SI::RunningStatusPausing) 00962 hasRunning = true; 00963 } 00964 } 00965 00966 void cSchedule::ClrRunningStatus(cChannel *Channel) 00967 { 00968 if (hasRunning) { 00969 for (cEvent *p = events.First(); p; p = events.Next(p)) { 00970 if (p->RunningStatus() >= SI::RunningStatusPausing) { 00971 p->SetRunningStatus(SI::RunningStatusNotRunning, Channel); 00972 hasRunning = false; 00973 break; 00974 } 00975 } 00976 } 00977 } 00978 00979 void cSchedule::ResetVersions(void) 00980 { 00981 for (cEvent *p = events.First(); p; p = events.Next(p)) 00982 p->SetVersion(0xFF); 00983 } 00984 00985 void cSchedule::Sort(void) 00986 { 00987 events.Sort(); 00988 // Make sure there are no RunningStatusUndefined before the currently running event: 00989 if (hasRunning) { 00990 for (cEvent *p = events.First(); p; p = events.Next(p)) { 00991 if (p->RunningStatus() >= SI::RunningStatusPausing) 00992 break; 00993 p->SetRunningStatus(SI::RunningStatusNotRunning); 00994 } 00995 } 00996 } 00997 00998 void cSchedule::DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version) 00999 { 01000 if (SegmentStart > 0 && SegmentEnd > 0) { 01001 for (cEvent *p = events.First(); p; p = events.Next(p)) { 01002 if (p->EndTime() > SegmentStart) { 01003 if (p->StartTime() < SegmentEnd) { 01004 // The event overlaps with the given time segment. 01005 if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) { 01006 // The segment overwrites all events from tables with higher ids, and 01007 // within the same table id all events must have the same version. 01008 // We can't delete the event right here because a timer might have 01009 // a pointer to it, so let's set its id and start time to 0 to have it 01010 // "phased out": 01011 if (hasRunning && p->IsRunning()) 01012 ClrRunningStatus(); 01013 UnhashEvent(p); 01014 p->eventID = 0; 01015 p->startTime = 0; 01016 } 01017 } 01018 else 01019 break; 01020 } 01021 } 01022 } 01023 } 01024 01025 void cSchedule::Cleanup(void) 01026 { 01027 Cleanup(time(NULL)); 01028 } 01029 01030 void cSchedule::Cleanup(time_t Time) 01031 { 01032 cEvent *Event; 01033 while ((Event = events.First()) != NULL) { 01034 if (!Event->HasTimer() && Event->EndTime() + Setup.EPGLinger * 60 + 3600 < Time) // adding one hour for safety 01035 DelEvent(Event); 01036 else 01037 break; 01038 } 01039 } 01040 01041 void cSchedule::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) const 01042 { 01043 cChannel *channel = Channels.GetByChannelID(channelID, true); 01044 if (channel) { 01045 fprintf(f, "%sC %s %s\n", Prefix, *channel->GetChannelID().ToString(), channel->Name()); 01046 const cEvent *p; 01047 switch (DumpMode) { 01048 case dmAll: { 01049 for (p = events.First(); p; p = events.Next(p)) 01050 p->Dump(f, Prefix); 01051 } 01052 break; 01053 case dmPresent: { 01054 if ((p = GetPresentEvent()) != NULL) 01055 p->Dump(f, Prefix); 01056 } 01057 break; 01058 case dmFollowing: { 01059 if ((p = GetFollowingEvent()) != NULL) 01060 p->Dump(f, Prefix); 01061 } 01062 break; 01063 case dmAtTime: { 01064 if ((p = GetEventAround(AtTime)) != NULL) 01065 p->Dump(f, Prefix); 01066 } 01067 break; 01068 default: esyslog("ERROR: unknown DumpMode %d (%s %d)", DumpMode, __FUNCTION__, __LINE__); 01069 } 01070 fprintf(f, "%sc\n", Prefix); 01071 } 01072 } 01073 01074 bool cSchedule::Read(FILE *f, cSchedules *Schedules) 01075 { 01076 if (Schedules) { 01077 cReadLine ReadLine; 01078 char *s; 01079 while ((s = ReadLine.Read(f)) != NULL) { 01080 if (*s == 'C') { 01081 s = skipspace(s + 1); 01082 char *p = strchr(s, ' '); 01083 if (p) 01084 *p = 0; // strips optional channel name 01085 if (*s) { 01086 tChannelID channelID = tChannelID::FromString(s); 01087 if (channelID.Valid()) { 01088 cSchedule *p = Schedules->AddSchedule(channelID); 01089 if (p) { 01090 if (!cEvent::Read(f, p)) 01091 return false; 01092 p->Sort(); 01093 Schedules->SetModified(p); 01094 } 01095 } 01096 else { 01097 esyslog("ERROR: invalid channel ID: %s", s); 01098 return false; 01099 } 01100 } 01101 } 01102 else { 01103 esyslog("ERROR: unexpected tag while reading EPG data: %s", s); 01104 return false; 01105 } 01106 } 01107 return true; 01108 } 01109 return false; 01110 } 01111 01112 // --- cSchedulesLock -------------------------------------------------------- 01113 01114 cSchedulesLock::cSchedulesLock(bool WriteLock, int TimeoutMs) 01115 { 01116 locked = cSchedules::schedules.rwlock.Lock(WriteLock, TimeoutMs); 01117 } 01118 01119 cSchedulesLock::~cSchedulesLock() 01120 { 01121 if (locked) 01122 cSchedules::schedules.rwlock.Unlock(); 01123 } 01124 01125 // --- cSchedules ------------------------------------------------------------ 01126 01127 cSchedules cSchedules::schedules; 01128 const char *cSchedules::epgDataFileName = NULL; 01129 time_t cSchedules::lastCleanup = time(NULL); 01130 time_t cSchedules::lastDump = time(NULL); 01131 time_t cSchedules::modified = 0; 01132 01133 const cSchedules *cSchedules::Schedules(cSchedulesLock &SchedulesLock) 01134 { 01135 return SchedulesLock.Locked() ? &schedules : NULL; 01136 } 01137 01138 void cSchedules::SetEpgDataFileName(const char *FileName) 01139 { 01140 delete epgDataFileName; 01141 epgDataFileName = FileName ? strdup(FileName) : NULL; 01142 } 01143 01144 void cSchedules::SetModified(cSchedule *Schedule) 01145 { 01146 Schedule->SetModified(); 01147 modified = time(NULL); 01148 } 01149 01150 void cSchedules::Cleanup(bool Force) 01151 { 01152 if (Force) 01153 lastDump = 0; 01154 time_t now = time(NULL); 01155 struct tm tm_r; 01156 struct tm *ptm = localtime_r(&now, &tm_r); 01157 if (now - lastCleanup > 3600) { 01158 isyslog("cleaning up schedules data"); 01159 cSchedulesLock SchedulesLock(true, 1000); 01160 cSchedules *s = (cSchedules *)Schedules(SchedulesLock); 01161 if (s) { 01162 for (cSchedule *p = s->First(); p; p = s->Next(p)) 01163 p->Cleanup(now); 01164 } 01165 lastCleanup = now; 01166 if (ptm->tm_hour == 5) 01167 ReportEpgBugFixStats(true); 01168 } 01169 if (epgDataFileName && now - lastDump > 600) { 01170 cSafeFile f(epgDataFileName); 01171 if (f.Open()) { 01172 Dump(f); 01173 f.Close(); 01174 } 01175 else 01176 LOG_ERROR; 01177 lastDump = now; 01178 } 01179 } 01180 01181 void cSchedules::ResetVersions(void) 01182 { 01183 cSchedulesLock SchedulesLock(true); 01184 cSchedules *s = (cSchedules *)Schedules(SchedulesLock); 01185 if (s) { 01186 for (cSchedule *Schedule = s->First(); Schedule; Schedule = s->Next(Schedule)) 01187 Schedule->ResetVersions(); 01188 } 01189 } 01190 01191 bool cSchedules::ClearAll(void) 01192 { 01193 cSchedulesLock SchedulesLock(true, 1000); 01194 cSchedules *s = (cSchedules *)Schedules(SchedulesLock); 01195 if (s) { 01196 for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) 01197 Timer->SetEvent(NULL); 01198 for (cSchedule *Schedule = s->First(); Schedule; Schedule = s->Next(Schedule)) 01199 Schedule->Cleanup(INT_MAX); 01200 return true; 01201 } 01202 return false; 01203 } 01204 01205 bool cSchedules::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) 01206 { 01207 cSchedulesLock SchedulesLock; 01208 cSchedules *s = (cSchedules *)Schedules(SchedulesLock); 01209 if (s) { 01210 for (cSchedule *p = s->First(); p; p = s->Next(p)) 01211 p->Dump(f, Prefix, DumpMode, AtTime); 01212 return true; 01213 } 01214 return false; 01215 } 01216 01217 bool cSchedules::Read(FILE *f) 01218 { 01219 cSchedulesLock SchedulesLock(true, 1000); 01220 cSchedules *s = (cSchedules *)Schedules(SchedulesLock); 01221 if (s) { 01222 bool OwnFile = f == NULL; 01223 if (OwnFile) { 01224 if (epgDataFileName && access(epgDataFileName, R_OK) == 0) { 01225 dsyslog("reading EPG data from %s", epgDataFileName); 01226 if ((f = fopen(epgDataFileName, "r")) == NULL) { 01227 LOG_ERROR; 01228 return false; 01229 } 01230 } 01231 else 01232 return false; 01233 } 01234 bool result = cSchedule::Read(f, s); 01235 if (OwnFile) 01236 fclose(f); 01237 if (result) { 01238 // Initialize the channels' schedule pointers, so that the first WhatsOn menu will come up faster: 01239 for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) 01240 s->GetSchedule(Channel); 01241 } 01242 return result; 01243 } 01244 return false; 01245 } 01246 01247 cSchedule *cSchedules::AddSchedule(tChannelID ChannelID) 01248 { 01249 ChannelID.ClrRid(); 01250 cSchedule *p = (cSchedule *)GetSchedule(ChannelID); 01251 if (!p) { 01252 p = new cSchedule(ChannelID); 01253 Add(p); 01254 cChannel *channel = Channels.GetByChannelID(ChannelID); 01255 if (channel) 01256 channel->schedule = p; 01257 } 01258 return p; 01259 } 01260 01261 const cSchedule *cSchedules::GetSchedule(tChannelID ChannelID) const 01262 { 01263 ChannelID.ClrRid(); 01264 for (cSchedule *p = First(); p; p = Next(p)) { 01265 if (p->ChannelID() == ChannelID) 01266 return p; 01267 } 01268 return NULL; 01269 } 01270 01271 const cSchedule *cSchedules::GetSchedule(const cChannel *Channel, bool AddIfMissing) const 01272 { 01273 // This is not very beautiful, but it dramatically speeds up the 01274 // "What's on now/next?" menus. 01275 static cSchedule DummySchedule(tChannelID::InvalidID); 01276 if (!Channel->schedule) 01277 Channel->schedule = GetSchedule(Channel->GetChannelID()); 01278 if (!Channel->schedule) 01279 Channel->schedule = &DummySchedule; 01280 if (Channel->schedule == &DummySchedule && AddIfMissing) { 01281 cSchedule *Schedule = new cSchedule(Channel->GetChannelID()); 01282 ((cSchedules *)this)->Add(Schedule); 01283 Channel->schedule = Schedule; 01284 } 01285 return Channel->schedule != &DummySchedule? Channel->schedule : NULL; 01286 } 01287 01288 // --- cEpgDataReader -------------------------------------------------------- 01289 01290 cEpgDataReader::cEpgDataReader(void) 01291 :cThread("epg data reader") 01292 { 01293 } 01294 01295 void cEpgDataReader::Action(void) 01296 { 01297 cSchedules::Read(); 01298 } 01299 01300 // --- cEpgHandler ----------------------------------------------------------- 01301 01302 cEpgHandler::cEpgHandler(void) 01303 { 01304 EpgHandlers.Add(this); 01305 } 01306 01307 cEpgHandler::~cEpgHandler() 01308 { 01309 EpgHandlers.Del(this, false); 01310 } 01311 01312 // --- cEpgHandlers ---------------------------------------------------------- 01313 01314 cEpgHandlers EpgHandlers; 01315 01316 bool cEpgHandlers::IgnoreChannel(const cChannel *Channel) 01317 { 01318 for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { 01319 if (eh->IgnoreChannel(Channel)) 01320 return true; 01321 } 01322 return false; 01323 } 01324 01325 bool cEpgHandlers::HandleEitEvent(cSchedule *Schedule, const SI::EIT::Event *EitEvent, uchar TableID, uchar Version) 01326 { 01327 for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { 01328 if (eh->HandleEitEvent(Schedule, EitEvent, TableID, Version)) 01329 return true; 01330 } 01331 return false; 01332 } 01333 01334 void cEpgHandlers::SetEventID(cEvent *Event, tEventID EventID) 01335 { 01336 for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { 01337 if (eh->SetEventID(Event, EventID)) 01338 return; 01339 } 01340 Event->SetEventID(EventID); 01341 } 01342 01343 void cEpgHandlers::SetTitle(cEvent *Event, const char *Title) 01344 { 01345 for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { 01346 if (eh->SetTitle(Event, Title)) 01347 return; 01348 } 01349 Event->SetTitle(Title); 01350 } 01351 01352 void cEpgHandlers::SetShortText(cEvent *Event, const char *ShortText) 01353 { 01354 for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { 01355 if (eh->SetShortText(Event, ShortText)) 01356 return; 01357 } 01358 Event->SetShortText(ShortText); 01359 } 01360 01361 void cEpgHandlers::SetDescription(cEvent *Event, const char *Description) 01362 { 01363 for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { 01364 if (eh->SetDescription(Event, Description)) 01365 return; 01366 } 01367 Event->SetDescription(Description); 01368 } 01369 01370 void cEpgHandlers::SetContents(cEvent *Event, uchar *Contents) 01371 { 01372 for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { 01373 if (eh->SetContents(Event, Contents)) 01374 return; 01375 } 01376 Event->SetContents(Contents); 01377 } 01378 01379 void cEpgHandlers::SetParentalRating(cEvent *Event, int ParentalRating) 01380 { 01381 for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { 01382 if (eh->SetParentalRating(Event, ParentalRating)) 01383 return; 01384 } 01385 Event->SetParentalRating(ParentalRating); 01386 } 01387 01388 void cEpgHandlers::SetStartTime(cEvent *Event, time_t StartTime) 01389 { 01390 for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { 01391 if (eh->SetStartTime(Event, StartTime)) 01392 return; 01393 } 01394 Event->SetStartTime(StartTime); 01395 } 01396 01397 void cEpgHandlers::SetDuration(cEvent *Event, int Duration) 01398 { 01399 for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { 01400 if (eh->SetDuration(Event, Duration)) 01401 return; 01402 } 01403 Event->SetDuration(Duration); 01404 } 01405 01406 void cEpgHandlers::SetVps(cEvent *Event, time_t Vps) 01407 { 01408 for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { 01409 if (eh->SetVps(Event, Vps)) 01410 return; 01411 } 01412 Event->SetVps(Vps); 01413 } 01414 01415 void cEpgHandlers::FixEpgBugs(cEvent *Event) 01416 { 01417 for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { 01418 if (eh->FixEpgBugs(Event)) 01419 return; 01420 } 01421 Event->FixEpgBugs(); 01422 } 01423 01424 void cEpgHandlers::HandleEvent(cEvent *Event) 01425 { 01426 for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { 01427 if (eh->HandleEvent(Event)) 01428 break; 01429 } 01430 } 01431 01432 void cEpgHandlers::SortSchedule(cSchedule *Schedule) 01433 { 01434 for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { 01435 if (eh->SortSchedule(Schedule)) 01436 return; 01437 } 01438 Schedule->Sort(); 01439 } 01440 01441 void cEpgHandlers::DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version) 01442 { 01443 for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { 01444 if (eh->DropOutdated(Schedule, SegmentStart, SegmentEnd, TableID, Version)) 01445 return; 01446 } 01447 Schedule->DropOutdated(SegmentStart, SegmentEnd, TableID, Version); 01448 }