diff options
author | Klaus Schmidinger <vdr@tvdr.de> | 2003-12-22 13:29:24 +0100 |
---|---|---|
committer | Klaus Schmidinger <vdr@tvdr.de> | 2003-12-22 13:29:24 +0100 |
commit | 7ff59171e3f907a5584b72f0f8588ed65f22c0bd (patch) | |
tree | 801b1b65840c50a4f1d8abea806fa5c180051df1 /epg.c | |
parent | 84b99ea81095f421ec049dd6b5bd5f0f2fe679c1 (diff) | |
download | vdr-7ff59171e3f907a5584b72f0f8588ed65f22c0bd.tar.gz vdr-7ff59171e3f907a5584b72f0f8588ed65f22c0bd.tar.bz2 |
Changed section handling; replaced 'libdtv' with 'libsi'
Diffstat (limited to 'epg.c')
-rw-r--r-- | epg.c | 669 |
1 files changed, 669 insertions, 0 deletions
@@ -0,0 +1,669 @@ +/* + * epg.c: Electronic Program Guide + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * Original version (as used in VDR before 1.3.0) written by + * Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>. + * + * $Id: epg.c 1.1 2003/12/22 13:07:32 kls Exp $ + */ + +#include "epg.h" +#include <ctype.h> +#include <time.h> + +// --- cEvent ---------------------------------------------------------------- + +cEvent::cEvent(tChannelID ChannelID, u_int16_t EventID) +{ + channelID = ChannelID; + eventID = EventID; + tableID = 0; + isPresent = isFollowing = false; + title = NULL; + shortText = NULL; + description = NULL; + startTime = 0; + duration = 0; + channelNumber = 0; +} + +cEvent::~cEvent() +{ + free(title); + free(shortText); + free(description); +} + +void cEvent::SetTableID(uchar TableID) +{ + tableID = TableID; +} + +void cEvent::SetIsPresent(bool IsPresent) +{ + isPresent = IsPresent; +} + +void cEvent::SetIsFollowing(bool IsFollowing) +{ + isFollowing = IsFollowing; +} + +void cEvent::SetTitle(const char *Title) +{ + title = strcpyrealloc(title, Title); +} + +void cEvent::SetShortText(const char *ShortText) +{ + shortText = strcpyrealloc(shortText, ShortText); +} + +void cEvent::SetDescription(const char *Description) +{ + description = strcpyrealloc(description, Description); +} + +void cEvent::SetStartTime(time_t StartTime) +{ + startTime = StartTime; +} + +void cEvent::SetDuration(int Duration) +{ + duration = Duration; +} + +const char *cEvent::GetDateString(void) const +{ + static char buf[25]; + struct tm tm_r; + strftime(buf, sizeof(buf), "%d.%m.%Y", localtime_r(&startTime, &tm_r)); + return buf; +} + +const char *cEvent::GetTimeString(void) const +{ + static char buf[25]; + struct tm tm_r; + strftime(buf, sizeof(buf), "%R", localtime_r(&startTime, &tm_r)); + return buf; +} + +const char *cEvent::GetEndTimeString(void) const +{ + static char buf[25]; + time_t EndTime = startTime + duration; + struct tm tm_r; + strftime(buf, sizeof(buf), "%R", localtime_r(&EndTime, &tm_r)); + return buf; +} + +void cEvent::Dump(FILE *f, const char *Prefix) const +{ + if (startTime + duration >= time(NULL)) { + fprintf(f, "%sE %u %ld %d %X\n", Prefix, eventID, startTime, duration, tableID); + if (!isempty(title)) + fprintf(f, "%sT %s\n", Prefix, title); + if (!isempty(shortText)) + fprintf(f, "%sS %s\n", Prefix, shortText); + if (!isempty(description)) + fprintf(f, "%sD %s\n", Prefix, description); + fprintf(f, "%se\n", Prefix); + } +} + +bool cEvent::Read(FILE *f, cSchedule *Schedule) +{ + if (Schedule) { + cEvent *Event = NULL; + char *s; + while ((s = readline(f)) != NULL) { + char *t = skipspace(s + 1); + switch (*s) { + case 'E': if (!Event) { + unsigned int EventID; + time_t StartTime; + int Duration; + unsigned int TableID = 0; + int n = sscanf(t, "%u %ld %d %X", &EventID, &StartTime, &Duration, &TableID); + if (n == 3 || n == 4) { + Event = (cEvent *)Schedule->GetEvent(EventID, StartTime); + if (!Event) + Event = Schedule->AddEvent(new cEvent(Schedule->ChannelID(), EventID)); + if (Event) { + Event->SetTableID(TableID); + Event->SetStartTime(StartTime); + Event->SetDuration(Duration); + } + } + } + break; + case 'T': if (Event) + Event->SetTitle(t); + break; + case 'S': if (Event) + Event->SetShortText(t); + break; + case 'D': if (Event) + Event->SetDescription(t); + break; + case 'e': Event = NULL; + break; + case 'c': // to keep things simple we react on 'c' here + return true; + default: esyslog("ERROR: unexpected tag while reading EPG data: %s", s); + return false; + } + } + esyslog("ERROR: unexpected end of file while reading EPG data"); + } + return false; +} + +#define MAXEPGBUGFIXSTATS 7 +#define MAXEPGBUGFIXCHANS 100 +struct tEpgBugFixStats { + int hits; + int n; + tChannelID channelIDs[MAXEPGBUGFIXCHANS]; + tEpgBugFixStats(void) { hits = n = 0; } + }; + +tEpgBugFixStats EpgBugFixStats[MAXEPGBUGFIXSTATS]; + +static void EpgBugFixStat(int Number, tChannelID ChannelID) +{ + if (0 <= Number && Number < MAXEPGBUGFIXSTATS) { + tEpgBugFixStats *p = &EpgBugFixStats[Number]; + p->hits++; + int i = 0; + for (; i < p->n; i++) { + if (p->channelIDs[i] == ChannelID) + break; + } + if (i == p->n && p->n < MAXEPGBUGFIXCHANS) + p->channelIDs[p->n++] = ChannelID; + } +} + +void ReportEpgBugFixStats(bool Reset) +{ + if (Setup.EPGBugfixLevel > 0) { + bool GotHits = false; + char buffer[1024]; + for (int i = 0; i < MAXEPGBUGFIXSTATS; i++) { + const char *delim = "\t"; + tEpgBugFixStats *p = &EpgBugFixStats[i]; + if (p->hits) { + bool PrintedStats = false; + char *q = buffer; + *buffer = 0; + for (int c = 0; c < p->n; c++) { + cChannel *channel = Channels.GetByChannelID(p->channelIDs[c], true); + if (channel) { + if (!GotHits) { + dsyslog("====================="); + dsyslog("EPG bugfix statistics"); + dsyslog("====================="); + dsyslog("IF SOMEBODY WHO IS IN CHARGE OF THE EPG DATA FOR ONE OF THE LISTED"); + dsyslog("CHANNELS READS THIS: PLEASE TAKE A LOOK AT THE FUNCTION cEvent::FixEpgBugs()"); + dsyslog("IN VDR/epg.c TO LEARN WHAT'S WRONG WITH YOUR DATA, AND FIX IT!"); + dsyslog("====================="); + dsyslog("Fix\tHits\tChannels"); + GotHits = true; + } + if (!PrintedStats) { + q += snprintf(q, sizeof(buffer) - (q - buffer), "%d\t%d", i, p->hits); + PrintedStats = true; + } + q += snprintf(q, sizeof(buffer) - (q - buffer), "%s%s", delim, channel->Name()); + delim = ", "; + } + } + if (*buffer) + dsyslog("%s", buffer); + } + if (Reset) + p->hits = p->n = 0; + } + if (GotHits) + dsyslog("====================="); + } +} + +void cEvent::FixEpgBugs(void) +{ + // VDR can't usefully handle newline characters in the EPG data, so let's + // always convert them to blanks (independent of the setting of EPGBugfixLevel): + strreplace(title, '\n', ' '); + strreplace(shortText, '\n', ' '); + strreplace(description, '\n', ' '); + + if (Setup.EPGBugfixLevel == 0) + return; + + // Some TV stations apparently have their own idea about how to fill in the + // EPG data. Let's fix their bugs as good as we can: + if (title) { + + // VOX puts too much information into the ShortText and leaves the + // Description empty: + // + // Title + // (NAT, Year Min')[ ["ShortText". ]Description] + // + if (shortText && !description) { + if (*shortText == '(') { + char *e = strchr(shortText + 1, ')'); + if (e) { + if (*(e + 1)) { + if (*++e == ' ') + if (*(e + 1) == '"') + e++; + } + else + e = NULL; + char *s = e ? strdup(e) : NULL; + free(shortText); + shortText = s; + EpgBugFixStat(0, ChannelID()); + // now the fixes #1 and #2 below will handle the rest + } + } + } + + // VOX and VIVA put the ShortText in quotes and use either the ShortText + // or the Description field, depending on how long the string is: + // + // Title + // "ShortText". Description + // + if ((shortText == NULL) != (description == NULL)) { + char *p = shortText ? shortText : description; + if (*p == '"') { + const char *delim = "\"."; + char *e = strstr(p + 1, delim); + if (e) { + *e = 0; + char *s = strdup(p + 1); + char *d = strdup(e + strlen(delim)); + free(shortText); + free(description); + shortText = s; + description = d; + EpgBugFixStat(1, ChannelID()); + } + } + } + + // VOX and VIVA put the Description into the ShortText (preceeded + // by a blank) if there is no actual ShortText and the Description + // is short enough: + // + // Title + // Description + // + if (shortText && !description) { + if (*shortText == ' ') { + memmove(shortText, shortText + 1, strlen(shortText)); + description = shortText; + shortText = NULL; + EpgBugFixStat(2, ChannelID()); + } + } + + // Pro7 sometimes repeats the Title in the ShortText: + // + // Title + // Title + // + if (shortText && strcmp(title, shortText) == 0) { + free(shortText); + shortText = NULL; + EpgBugFixStat(3, ChannelID()); + } + + // ZDF.info puts the ShortText between double quotes, which is nothing + // but annoying (some even put a '.' after the closing '"'): + // + // Title + // "ShortText"[.] + // + if (shortText && *shortText == '"') { + int l = strlen(shortText); + if (l > 2 && (shortText[l - 1] == '"' || (shortText[l - 1] == '.' && shortText[l - 2] == '"'))) { + memmove(shortText, shortText + 1, l); + char *p = strrchr(shortText, '"'); + if (p) + *p = 0; + EpgBugFixStat(4, ChannelID()); + } + } + + if (Setup.EPGBugfixLevel <= 1) + return; + + // Some channels apparently try to do some formatting in the texts, + // which is a bad idea because they have no way of knowing the width + // of the window that will actually display the text. + // Remove excess whitespace: + title = compactspace(title); + shortText = compactspace(shortText); + description = compactspace(description); + // Remove superfluous hyphens: + if (description) { + char *p = description; + while (*p && *(p + 1) && *(p + 2)) { + if (*p == '-' && *(p + 1) == ' ' && *(p + 2) && islower(*(p - 1)) && islower(*(p + 2))) { + if (!startswith(p + 2, "und ")) { // special case in German, as in "Lach- und Sachgeschichten" + memmove(p, p + 2, strlen(p + 2) + 1); + EpgBugFixStat(5, ChannelID()); + } + } + p++; + } + } + +#define MAX_USEFUL_EPISODE_LENGTH 40 + // Some channels put a whole lot of information in the ShortText and leave + // the Description totally empty. So if the ShortText length exceeds + // MAX_USEFUL_EPISODE_LENGTH, let's put this into the Description + // instead: + if (!isempty(shortText) && isempty(description)) { + if (strlen(shortText) > MAX_USEFUL_EPISODE_LENGTH) { + free(description); + description = shortText; + shortText = NULL; + EpgBugFixStat(6, ChannelID()); + } + } + + // Some channels use the ` ("backtick") character, where a ' (single quote) + // would be normally used. Actually, "backticks" in normal text don't make + // much sense, so let's replace them: + strreplace(title, '`', '\''); + strreplace(shortText, '`', '\''); + strreplace(description, '`', '\''); + } +} + +// --- cSchedule ------------------------------------------------------------- + +cSchedule::cSchedule(tChannelID ChannelID) +{ + channelID = ChannelID; + present = following = NULL; +} + +cEvent *cSchedule::AddEvent(cEvent *Event) +{ + events.Add(Event); + return Event; +} + +const cEvent *cSchedule::GetPresentEvent(void) const +{ + return GetEventAround(time(NULL)); +} + +const cEvent *cSchedule::GetFollowingEvent(void) const +{ + const cEvent *pe = NULL; + time_t now = time(NULL); + time_t delta = INT_MAX; + for (cEvent *p = events.First(); p; p = events.Next(p)) { + time_t dt = p->StartTime() - now; + if (dt > 0 && dt < delta) { + delta = dt; + pe = p; + } + } + return pe; +} + +const cEvent *cSchedule::GetEvent(u_int16_t EventID, time_t StartTime) const +{ + // Returns either the event info with the given EventID or, if that one can't + // be found, the one with the given StartTime (or NULL if neither can be found) + cEvent *pt = NULL; + for (cEvent *pe = events.First(); pe; pe = events.Next(pe)) { + if (pe->EventID() == EventID) + return pe; + if (StartTime > 0 && pe->StartTime() == StartTime) // 'StartTime < 0' is apparently used with NVOD channels + pt = pe; + } + return pt; +} + +const cEvent *cSchedule::GetEventAround(time_t Time) const +{ + const cEvent *pe = NULL; + time_t delta = INT_MAX; + for (cEvent *p = events.First(); p; p = events.Next(p)) { + time_t dt = Time - p->StartTime(); + if (dt >= 0 && dt < delta && p->StartTime() + p->Duration() >= Time) { + delta = dt; + pe = p; + } + } + return pe; +} + +bool cSchedule::SetPresentEvent(cEvent *Event) +{ + if (present) + present->SetIsPresent(false); + present = Event; + present->SetIsPresent(true); + return true; +} + +bool cSchedule::SetFollowingEvent(cEvent *Event) +{ + if (following) + following->SetIsFollowing(false); + following = Event; + following->SetIsFollowing(true); + return true; +} + +void cSchedule::Cleanup(void) +{ + Cleanup(time(NULL)); +} + +void cSchedule::Cleanup(time_t Time) +{ + cEvent *Event; + for (int a = 0; true ; a++) { + Event = events.Get(a); + if (!Event) + break; + if (Event->StartTime() + Event->Duration() + 3600 < Time) { // adding one hour for safety + events.Del(Event); + a--; + } + } +} + +void cSchedule::Dump(FILE *f, const char *Prefix) const +{ + cChannel *channel = Channels.GetByChannelID(channelID, true); + if (channel) { + fprintf(f, "%sC %s %s\n", Prefix, channel->GetChannelID().ToString(), channel->Name()); + for (cEvent *p = events.First(); p; p = events.Next(p)) + p->Dump(f, Prefix); + fprintf(f, "%sc\n", Prefix); + } +} + +bool cSchedule::Read(FILE *f, cSchedules *Schedules) +{ + if (Schedules) { + char *s; + while ((s = readline(f)) != NULL) { + if (*s == 'C') { + s = skipspace(s + 1); + char *p = strchr(s, ' '); + if (p) + *p = 0; // strips optional channel name + if (*s) { + tChannelID channelID = tChannelID::FromString(s); + if (channelID.Valid()) { + cSchedule *p = Schedules->AddSchedule(channelID); + if (p) { + if (!cEvent::Read(f, p)) + return false; + } + } + else { + esyslog("ERROR: illegal channel ID: %s", s); + return false; + } + } + } + else { + esyslog("ERROR: unexpected tag while reading EPG data: %s", s); + return false; + } + } + return true; + } + return false; +} + +// --- cSchedulesLock -------------------------------------------------------- + +cSchedulesLock::cSchedulesLock(bool WriteLock, int TimeoutMs) +{ + locked = cSchedules::schedules.rwlock.Lock(WriteLock, TimeoutMs); +} + +cSchedulesLock::~cSchedulesLock() +{ + if (locked) + cSchedules::schedules.rwlock.Unlock(); +} + +// --- cSchedules ------------------------------------------------------------ + +cSchedules cSchedules::schedules; +const char *cSchedules::epgDataFileName = NULL; +time_t cSchedules::lastCleanup = time(NULL); +time_t cSchedules::lastDump = time(NULL); + +const cSchedules *cSchedules::Schedules(cSchedulesLock &SchedulesLock) +{ + return SchedulesLock.Locked() ? &schedules : NULL; +} + +void cSchedules::SetEpgDataFileName(const char *FileName) +{ + delete epgDataFileName; + if (FileName) + epgDataFileName = strdup(FileName); +} + +void cSchedules::Cleanup(bool Force) +{ + if (Force) + lastDump = 0; + time_t now = time(NULL); + struct tm tm_r; + struct tm *ptm = localtime_r(&now, &tm_r); + if (now - lastCleanup > 3600 && ptm->tm_hour == 5) { + isyslog("cleaning up schedules data"); + cSchedulesLock SchedulesLock(true, 1000); + cSchedules *s = (cSchedules *)Schedules(SchedulesLock); + if (s) { + for (cSchedule *p = s->First(); p; p = s->Next(p)) + p->Cleanup(now); + } + lastCleanup = now; + ReportEpgBugFixStats(true); + } + if (epgDataFileName && now - lastDump > 600) { + cSafeFile f(epgDataFileName); + if (f.Open()) { + Dump(f); + f.Close(); + } + else + LOG_ERROR; + lastDump = now; + } +} + +bool cSchedules::ClearAll(void) +{ + cSchedulesLock SchedulesLock(true, 1000); + cSchedules *s = (cSchedules *)Schedules(SchedulesLock); + if (s) { + s->Clear(); + return true; + } + return false; +} + +bool cSchedules::Dump(FILE *f, const char *Prefix) +{ + cSchedulesLock SchedulesLock; + cSchedules *s = (cSchedules *)Schedules(SchedulesLock); + if (s) { + for (cSchedule *p = s->First(); p; p = s->Next(p)) + p->Dump(f, Prefix); + return true; + } + return false; +} + +bool cSchedules::Read(FILE *f) +{ + cSchedulesLock SchedulesLock(true, 1000); + cSchedules *s = (cSchedules *)Schedules(SchedulesLock); + if (s) { + bool OwnFile = f == NULL; + if (OwnFile) { + if (epgDataFileName && access(epgDataFileName, R_OK) == 0) { + dsyslog("reading EPG data from %s", epgDataFileName); + if ((f = fopen(epgDataFileName, "r")) == NULL) { + LOG_ERROR; + return false; + } + } + else + return false; + } + bool result = cSchedule::Read(f, s); + if (OwnFile) + fclose(f); + return result; + } + return false; +} + +cSchedule *cSchedules::AddSchedule(tChannelID ChannelID) +{ + ChannelID.ClrRid(); + cSchedule *p = (cSchedule *)GetSchedule(ChannelID); + if (!p) { + p = new cSchedule(ChannelID); + Add(p); + } + return p; +} + +const cSchedule *cSchedules::GetSchedule(tChannelID ChannelID) const +{ + ChannelID.ClrRid(); + for (cSchedule *p = First(); p; p = Next(p)) { + if (p->ChannelID() == ChannelID) + return p; + } + return NULL; +} + |