diff options
author | Klaus Schmidinger <kls (at) cadsoft (dot) de> | 2004-01-04 18:00:00 +0100 |
---|---|---|
committer | Klaus Schmidinger <kls (at) cadsoft (dot) de> | 2004-01-04 18:00:00 +0100 |
commit | 93a94b18b540fbcb9bcdaaea9abd26cdf23d6ee6 (patch) | |
tree | bede17e6cd329d36ec02bc53dfe567b95ec835a5 /epg.c | |
parent | c432905dd60630f906ac89f58592ad835a9063ef (diff) | |
download | vdr-patch-lnbsharing-93a94b18b540fbcb9bcdaaea9abd26cdf23d6ee6.tar.gz vdr-patch-lnbsharing-93a94b18b540fbcb9bcdaaea9abd26cdf23d6ee6.tar.bz2 |
Version 1.3.0vdr-1.3.0
- Changed thread handling to make it work with NPTL ("Native Posix Thread Library").
Thanks to Jon Burgess, Andreas Schultz, Werner Fink and Stefan Huelswitt.
- The cThread class now accepts a 'Description' parameter, which is used to log
the beginning and end of the thread, together with its process and thread id.
For descriptions that need additional parameters you can use the function
cThread::SetDescription(), which accepts 'printf()' like arguments.
Existing plugins that use threads should be changed to use this functionality
instead of explicit 'dsyslog()' calls inside their Action() function in order
to support logging the thread ids.
- Added "Slovak Link" and "Czech Link" to 'ca.conf' (thanks to Emil Petersky).
However, 'ca.conf' is now pretty much obsolete due to the automatic CA handling.
- Mutexes are now created with PTHREAD_MUTEX_ERRORCHECK_NP, which makes the
'lockingTid' stuff obsolete (thanks to Stefan Huelswitt).
- Changed font handling to allow language specific character sets.
- Adopted the small font character set from the "Elchi" patch (originally
provided by Alessio Sangalli).
- Greek language texts now use iso8859-7 character set (thanks to Dimitrios
Dimitrakos).
- Rearranged section data handling, so that the actual data handling can be done
separately, even from within plugins.
- The EPG data structures have been moved from eit.[hc] to epg.[hc] and have been
adapted to the general VDR coding style. Plugins that use these data structures
may need to change some function names (which should be obvious).
The name 'subtitle' has been changed to 'shortText' to avoid clashes with actual
subtitles that are part of a movie. The name 'extendedDescription' has been
shortened to 'description'.
- Replaced 'libdtv' with 'libsi' (thanks to Marcel Wiesweg), which is thread
safe and can be used by multiple section filters simultaneously.
- Added 'cRwLock' to 'thread.[hc]'. Note that all plugin Makefiles need to
define _GNU_SOURCE for this to work (see the example plugin Makefiles and
'newplugin').
- Fixed a problem with crc32 in SI handling on 64bit systems (thanks to Pedro
Miguel Sequeira de Justo Teixeira for reporting this one).
- Fixed an alignment problem in CAM access on 64bit systems (thanks to Pedro
Miguel Sequeira de Justo Teixeira for reporting this one).
- Added 'StreamType' setting to CAM communication, which is important for
Aston/SECA CAMs (thanks to Antonino Sergi).
- Now the CA descriptors are sent to the CAM in the 'program' or 'ES level'
sections, depending on where they are found in the PMT (thanks to Hans-Peter
Raschke for reporting this one). This should make SkyCrypt CAMs work.
- Now using the 'version number' of EPG events to avoid unnecessary work.
- Channel data is now automatically derived from the DVB data stream (inspired
by the 'autopid' patch from Andreas Schultz).
- The current channel is now automatically re-tuned if the PIDs or other settings
change. If a recording is going on on a channel that has a change in its
settings, the recording will be stopped and immediately restarted to use the
new channel settings.
- EPG events now use the complete channel ID with NID, TID and SID.
- Channel names in 'channels.conf' can now have a short form, as provided
by some tv stations (see man vdr(5)). Currently channels that provide short
names in addition to long ones are listed in the OSD as "short,long name",
as in "RTL,RTL Television". The short names will be used explicitly later.
- The Ca parameter in 'channels.conf' has been extended and now contains all the
CA system ids for the given channel. When switching to a channel VDR now tests
for a device that provides one of these CA system ids. The devices automatically
get their supported ids from the CI handler.
- The values in 'ca.conf' are currently without any real meaning. Whether or not
a channel with conditional access can be received is now determined automatically
by evaluating its CA descriptors and comparing them to the CA system ids
provided by the installed CAM. Only the special values 1-16 are used to assign
a channel to a particular device.
- Increased the maximum number of possible OSD colors to 256.
- Limited the line length in the EPG bugfix report, which appears to fix a buffer
overflow that caused a crash when cleaning up the EPG data (at 05:00 in the
morning).
Diffstat (limited to 'epg.c')
-rw-r--r-- | epg.c | 684 |
1 files changed, 684 insertions, 0 deletions
@@ -0,0 +1,684 @@ +/* + * 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.3 2004/01/04 14:07:46 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; + version = 0xFF; // actual version numbers are 0..31 + 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::SetEventID(u_int16_t EventID) +{ + eventID = EventID; +} + +void cEvent::SetTableID(uchar TableID) +{ + tableID = TableID; +} + +void cEvent::SetVersion(uchar Version) +{ + version = Version; +} + +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 (q - buffer > 80) { + q += snprintf(q, sizeof(buffer) - (q - buffer), "%s...", delim); + break; + } + } + } + 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) { + + // Some channels put too much information into the ShortText and leave 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 + } + } + } + + // Some channels 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()); + } + } + } + + // Some channels 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()); + } + } + + // Sometimes they repeat the Title in the ShortText: + // + // Title + // Title + // + if (shortText && strcmp(title, shortText) == 0) { + free(shortText); + shortText = NULL; + EpgBugFixStat(3, ChannelID()); + } + + // Some channels put 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; +} + |