1
0
mirror of https://github.com/VDR4Arch/vdr.git synced 2023-10-10 13:36:52 +02:00
vdr/eit.c
2002-10-11 13:23:44 +02:00

1270 lines
35 KiB
C

/***************************************************************************
eit.c - description
-------------------
begin : Fri Aug 25 2000
copyright : (C) 2000 by Robert Schneider
email : Robert.Schneider@web.de
2001-08-15: Adapted to 'libdtv' by Rolf Hakenes <hakenes@hippomi.de>
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* $Id: eit.c 1.56 2002/10/11 13:14:57 kls Exp $
***************************************************************************/
#include "eit.h"
#include <ctype.h>
#include <fcntl.h>
#include <limits.h>
#ifdef NEWSTRUCT
#include <linux/dvb/dmx.h>
#else
#include <ost/dmx.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "channels.h"
#include "config.h"
#include "libdtv/libdtv.h"
#include "videodir.h"
// --- cMJD ------------------------------------------------------------------
class cMJD {
public:
cMJD();
cMJD(u_char date_hi, u_char date_lo);
cMJD(u_char date_hi, u_char date_lo, u_char timehr, u_char timemi, u_char timese);
~cMJD();
/** */
void ConvertToTime();
/** */
bool SetSystemTime();
/** */
time_t GetTime_t();
protected: // Protected attributes
/** */
time_t mjdtime;
protected: // Protected attributes
/** */
u_char time_second;
protected: // Protected attributes
/** */
u_char time_minute;
protected: // Protected attributes
/** */
u_char time_hour;
protected: // Protected attributes
/** */
u_short mjd;
};
cMJD::cMJD()
{
}
cMJD::cMJD(u_char date_hi, u_char date_lo)
{
mjd = date_hi << 8 | date_lo;
time_hour = time_minute = time_second = 0;
ConvertToTime();
}
cMJD::cMJD(u_char date_hi, u_char date_lo, u_char timehr, u_char timemi, u_char timese)
{
mjd = date_hi << 8 | date_lo;
time_hour = timehr;
time_minute = timemi;
time_second = timese;
ConvertToTime();
}
cMJD::~cMJD()
{
}
/** */
void cMJD::ConvertToTime()
{
struct tm t;
t.tm_sec = time_second;
t.tm_min = time_minute;
t.tm_hour = time_hour;
int k;
t.tm_year = (int) ((mjd - 15078.2) / 365.25);
t.tm_mon = (int) ((mjd - 14956.1 - (int)(t.tm_year * 365.25)) / 30.6001);
t.tm_mday = (int) (mjd - 14956 - (int)(t.tm_year * 365.25) - (int)(t.tm_mon * 30.6001));
k = (t.tm_mon == 14 || t.tm_mon == 15) ? 1 : 0;
t.tm_year = t.tm_year + k;
t.tm_mon = t.tm_mon - 1 - k * 12;
t.tm_mon--;
t.tm_isdst = -1;
t.tm_gmtoff = 0;
mjdtime = timegm(&t);
//isyslog("Time parsed = %s\n", ctime(&mjdtime));
}
/** */
bool cMJD::SetSystemTime()
{
struct tm *ptm;
time_t loctim;
struct tm tm_r;
ptm = localtime_r(&mjdtime, &tm_r);
loctim = time(NULL);
if (abs(mjdtime - loctim) > 2)
{
isyslog("System Time = %s (%ld)\n", ctime(&loctim), loctim);
isyslog("Local Time = %s (%ld)\n", ctime(&mjdtime), mjdtime);
if (stime(&mjdtime) < 0)
esyslog("ERROR while setting system time: %m");
return true;
}
return false;
}
/** */
time_t cMJD::GetTime_t()
{
return mjdtime;
}
// --- cTDT ------------------------------------------------------------------
class cTDT {
public:
cTDT(tdt_t *ptdt);
~cTDT();
/** */
bool SetSystemTime();
protected: // Protected attributes
/** */
tdt_t tdt;
/** */
cMJD mjd; // kls 2001-03-02: made this a member instead of a pointer (it wasn't deleted in the destructor!)
};
#define BCD2DEC(b) (((b >> 4) & 0x0F) * 10 + (b & 0x0F))
cTDT::cTDT(tdt_t *ptdt)
:tdt(*ptdt)
,mjd(tdt.utc_mjd_hi, tdt.utc_mjd_lo, BCD2DEC(tdt.utc_time_h), BCD2DEC(tdt.utc_time_m), BCD2DEC(tdt.utc_time_s))
{
}
cTDT::~cTDT()
{
}
/** */
bool cTDT::SetSystemTime()
{
return mjd.SetSystemTime();
}
// --- cEventInfo ------------------------------------------------------------
cEventInfo::cEventInfo(unsigned short serviceid, unsigned short eventid)
{
pTitle = NULL;
pSubtitle = NULL;
pExtendedDescription = NULL;
bIsPresent = bIsFollowing = false;
lDuration = 0;
tTime = 0;
uTableID = 0;
uEventID = eventid;
uServiceID = serviceid;
nChannelNumber = 0;
}
cEventInfo::~cEventInfo()
{
free(pTitle);
free(pSubtitle);
free(pExtendedDescription);
}
/** */
const char * cEventInfo::GetTitle() const
{
return pTitle;
}
/** */
const char * cEventInfo::GetSubtitle() const
{
return pSubtitle;
}
/** */
const char * cEventInfo::GetExtendedDescription() const
{
return pExtendedDescription;
}
/** */
bool cEventInfo::IsPresent() const
{
return bIsPresent;
}
/** */
void cEventInfo::SetPresent(bool pres)
{
bIsPresent = pres;
}
/** */
bool cEventInfo::IsFollowing() const
{
return bIsFollowing;
}
void cEventInfo::SetTableID(unsigned char tableid)
{
uTableID = tableid;
}
/** */
void cEventInfo::SetFollowing(bool foll)
{
bIsFollowing = foll;
}
/** */
const char * cEventInfo::GetDate() const
{
static char szDate[25];
struct tm tm_r;
strftime(szDate, sizeof(szDate), "%d.%m.%Y", localtime_r(&tTime, &tm_r));
return szDate;
}
const unsigned char cEventInfo::GetTableID(void) const
{
return uTableID;
}
/** */
const char * cEventInfo::GetTimeString() const
{
static char szTime[25];
struct tm tm_r;
strftime(szTime, sizeof(szTime), "%R", localtime_r(&tTime, &tm_r));
return szTime;
}
/** */
const char * cEventInfo::GetEndTimeString() const
{
static char szEndTime[25];
time_t tEndTime = tTime + lDuration;
struct tm tm_r;
strftime(szEndTime, sizeof(szEndTime), "%R", localtime_r(&tEndTime, &tm_r));
return szEndTime;
}
/** */
time_t cEventInfo::GetTime() const
{
return tTime;
}
/** */
long cEventInfo::GetDuration() const
{
return lDuration;
}
/** */
unsigned short cEventInfo::GetEventID() const
{
return uEventID;
}
/** */
void cEventInfo::SetTitle(const char *string)
{
pTitle = strcpyrealloc(pTitle, string);
}
/** */
void cEventInfo::SetSubtitle(const char *string)
{
pSubtitle = strcpyrealloc(pSubtitle, string);
}
/** */
void cEventInfo::SetExtendedDescription(const char *string)
{
pExtendedDescription = strcpyrealloc(pExtendedDescription, string);
}
/** */
void cEventInfo::SetTime(time_t t)
{
tTime = t;
}
/** */
void cEventInfo::SetDuration(long l)
{
lDuration = l;
}
/** */
void cEventInfo::SetEventID(unsigned short evid)
{
uEventID = evid;
}
/** */
void cEventInfo::SetServiceID(unsigned short servid)
{
uServiceID = servid;
}
/** */
unsigned short cEventInfo::GetServiceID() const
{
return uServiceID;
}
/** */
void cEventInfo::Dump(FILE *f, const char *Prefix) const
{
if (tTime + lDuration >= time(NULL)) {
fprintf(f, "%sE %u %ld %ld %X\n", Prefix, uEventID, tTime, lDuration, uTableID);
if (!isempty(pTitle))
fprintf(f, "%sT %s\n", Prefix, pTitle);
if (!isempty(pSubtitle))
fprintf(f, "%sS %s\n", Prefix, pSubtitle);
if (!isempty(pExtendedDescription))
fprintf(f, "%sD %s\n", Prefix, pExtendedDescription);
fprintf(f, "%se\n", Prefix);
}
}
bool cEventInfo::Read(FILE *f, cSchedule *Schedule)
{
if (Schedule) {
cEventInfo *pEvent = NULL;
char *s;
while ((s = readline(f)) != NULL) {
char *t = skipspace(s + 1);
switch (*s) {
case 'E': if (!pEvent) {
unsigned int uEventID;
time_t tTime;
long lDuration;
unsigned int uTableID = 0;
int n = sscanf(t, "%u %ld %ld %X", &uEventID, &tTime, &lDuration, &uTableID);
if (n == 3 || n == 4) {
pEvent = (cEventInfo *)Schedule->GetEvent(uEventID, tTime);
if (!pEvent)
pEvent = Schedule->AddEvent(new cEventInfo(Schedule->GetServiceID(), uEventID));
if (pEvent) {
pEvent->SetTableID(uTableID);
pEvent->SetTime(tTime);
pEvent->SetDuration(lDuration);
}
}
}
break;
case 'T': if (pEvent)
pEvent->SetTitle(t);
break;
case 'S': if (pEvent)
pEvent->SetSubtitle(t);
break;
case 'D': if (pEvent)
pEvent->SetExtendedDescription(t);
break;
case 'e': pEvent = 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 6
#define MAXEPGBUGFIXCHANS 50
struct tEpgBugFixStats {
int hits;
int n;
unsigned short serviceIDs[MAXEPGBUGFIXCHANS];
tEpgBugFixStats(void) { hits = n = 0; }
};
tEpgBugFixStats EpgBugFixStats[MAXEPGBUGFIXSTATS];
static void EpgBugFixStat(int Number, unsigned int ServiceID)
{
if (0 <= Number && Number < MAXEPGBUGFIXSTATS) {
tEpgBugFixStats *p = &EpgBugFixStats[Number];
p->hits++;
int i = 0;
for (; i < p->n; i++) {
if (p->serviceIDs[i] == ServiceID)
break;
}
if (i == p->n && p->n < MAXEPGBUGFIXCHANS)
p->serviceIDs[p->n++] = ServiceID;
}
}
static void ReportEpgBugFixStats(bool Reset = false)
{
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) {
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 cEventInfo::FixEpgBugs()");
dsyslog("IN VDR/eit.c TO LEARN WHAT'S WRONG WITH YOUR DATA, AND FIX IT!");
dsyslog("=====================");
dsyslog("Fix\tHits\tChannels");
GotHits = true;
}
char *q = buffer;
q += snprintf(q, sizeof(buffer) - (q - buffer), "%d\t%d", i, p->hits);
for (int c = 0; c < p->n; c++) {
cChannel *channel = Channels.GetByServiceID(p->serviceIDs[c]);
if (channel) {
q += snprintf(q, sizeof(buffer) - (q - buffer), "%s%s", delim, channel->Name());
delim = ", ";
}
}
dsyslog("%s", buffer);
}
if (Reset)
p->hits = p->n = 0;
}
if (GotHits)
dsyslog("=====================");
}
}
void cEventInfo::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(pTitle, '\n', ' ');
strreplace(pSubtitle, '\n', ' ');
strreplace(pExtendedDescription, '\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 (pTitle) {
// VOX puts too much information into the Subtitle and leaves the Extended
// Description empty:
//
// Title
// (NAT, Year Min')[ ["Subtitle". ]Extended Description]
//
if (pSubtitle && !pExtendedDescription) {
if (*pSubtitle == '(') {
char *e = strchr(pSubtitle + 1, ')');
if (e) {
if (*(e + 1)) {
if (*++e == ' ')
if (*(e + 1) == '"')
e++;
}
else
e = NULL;
char *s = e ? strdup(e) : NULL;
free(pSubtitle);
pSubtitle = s;
EpgBugFixStat(0, GetServiceID());
// now the fixes #1 and #2 below will handle the rest
}
}
}
// VOX and VIVA put the Subtitle in quotes and use either the Subtitle
// or the Extended Description field, depending on how long the string is:
//
// Title
// "Subtitle". Extended Description
//
if ((pSubtitle == NULL) != (pExtendedDescription == NULL)) {
char *p = pSubtitle ? pSubtitle : pExtendedDescription;
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(pSubtitle);
free(pExtendedDescription);
pSubtitle = s;
pExtendedDescription = d;
EpgBugFixStat(1, GetServiceID());
}
}
}
// VOX and VIVA put the Extended Description into the Subtitle (preceeded
// by a blank) if there is no actual Subtitle and the Extended Description
// is short enough:
//
// Title
// Extended Description
//
if (pSubtitle && !pExtendedDescription) {
if (*pSubtitle == ' ') {
memmove(pSubtitle, pSubtitle + 1, strlen(pSubtitle));
pExtendedDescription = pSubtitle;
pSubtitle = NULL;
EpgBugFixStat(2, GetServiceID());
}
}
// Pro7 sometimes repeats the Title in the Subtitle:
//
// Title
// Title
//
if (pSubtitle && strcmp(pTitle, pSubtitle) == 0) {
free(pSubtitle);
pSubtitle = NULL;
EpgBugFixStat(3, GetServiceID());
}
// ZDF.info puts the Subtitle between double quotes, which is nothing
// but annoying (some even put a '.' after the closing '"'):
//
// Title
// "Subtitle"[.]
//
if (pSubtitle && *pSubtitle == '"') {
int l = strlen(pSubtitle);
if (l > 2 && (pSubtitle[l - 1] == '"' || (pSubtitle[l - 1] == '.' && pSubtitle[l - 2] == '"'))) {
memmove(pSubtitle, pSubtitle + 1, l);
char *p = strrchr(pSubtitle, '"');
if (p)
*p = 0;
EpgBugFixStat(4, GetServiceID());
}
}
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:
pTitle = compactspace(pTitle);
pSubtitle = compactspace(pSubtitle);
pExtendedDescription = compactspace(pExtendedDescription);
// Remove superfluous hyphens:
if (pExtendedDescription) {
char *p = pExtendedDescription;
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, GetServiceID());
}
}
p++;
}
}
// 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(pTitle, '`', '\'');
strreplace(pSubtitle, '`', '\'');
strreplace(pExtendedDescription, '`', '\'');
}
}
// --- cSchedule -------------------------------------------------------------
cSchedule::cSchedule(unsigned short servid)
{
pPresent = pFollowing = NULL;
uServiceID = servid;
}
cSchedule::~cSchedule()
{
}
cEventInfo *cSchedule::AddEvent(cEventInfo *EventInfo)
{
Events.Add(EventInfo);
return EventInfo;
}
const cEventInfo *cSchedule::GetPresentEvent(void) const
{
return GetEventAround(time(NULL));
}
const cEventInfo *cSchedule::GetFollowingEvent(void) const
{
const cEventInfo *pe = NULL;
time_t now = time(NULL);
time_t delta = INT_MAX;
for (cEventInfo *p = Events.First(); p; p = Events.Next(p)) {
time_t dt = p->GetTime() - now;
if (dt > 0 && dt < delta) {
delta = dt;
pe = p;
}
}
return pe;
}
void cSchedule::SetServiceID(unsigned short servid)
{
uServiceID = servid;
}
/** */
unsigned short cSchedule::GetServiceID() const
{
return uServiceID;
}
/** */
const cEventInfo * cSchedule::GetEvent(unsigned short uEventID, time_t tTime) const
{
// Returns either the event info with the given uEventID or, if that one can't
// be found, the one with the given tTime (or NULL if neither can be found)
cEventInfo *pe = Events.First();
cEventInfo *pt = NULL;
while (pe != NULL)
{
if (pe->GetEventID() == uEventID)
return pe;
if (tTime > 0 && pe->GetTime() == tTime) // 'tTime < 0' is apparently used with NVOD channels
pt = pe;
pe = Events.Next(pe);
}
return pt;
}
const cEventInfo *cSchedule::GetEventAround(time_t Time) const
{
const cEventInfo *pe = NULL;
time_t delta = INT_MAX;
for (cEventInfo *p = Events.First(); p; p = Events.Next(p)) {
time_t dt = Time - p->GetTime();
if (dt >= 0 && dt < delta && p->GetTime() + p->GetDuration() >= Time) {
delta = dt;
pe = p;
}
}
return pe;
}
bool cSchedule::SetPresentEvent(cEventInfo *pEvent)
{
if (pPresent != NULL)
pPresent->SetPresent(false);
pPresent = pEvent;
pPresent->SetPresent(true);
return true;
}
/** */
bool cSchedule::SetFollowingEvent(cEventInfo *pEvent)
{
if (pFollowing != NULL)
pFollowing->SetFollowing(false);
pFollowing = pEvent;
pFollowing->SetFollowing(true);
return true;
}
/** */
void cSchedule::Cleanup()
{
Cleanup(time(NULL));
}
/** */
void cSchedule::Cleanup(time_t tTime)
{
cEventInfo *pEvent;
for (int a = 0; true ; a++)
{
pEvent = Events.Get(a);
if (pEvent == NULL)
break;
if (pEvent->GetTime() + pEvent->GetDuration() + 3600 < tTime) // adding one hour for safety
{
Events.Del(pEvent);
a--;
}
}
}
/** */
void cSchedule::Dump(FILE *f, const char *Prefix) const
{
cChannel *channel = Channels.GetByServiceID(uServiceID);
if (channel)
{
fprintf(f, "%sC %u %s\n", Prefix, uServiceID, channel->Name());
for (cEventInfo *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') {
unsigned int uServiceID;
if (1 == sscanf(s + 1, "%u", &uServiceID)) {
cSchedule *p = (cSchedule *)Schedules->SetCurrentServiceID(uServiceID);
if (p) {
if (!cEventInfo::Read(f, p))
return false;
}
}
}
else {
esyslog("ERROR: unexpected tag while reading EPG data: %s", s);
return false;
}
}
return true;
}
return false;
}
// --- cSchedules ------------------------------------------------------------
cSchedules::cSchedules()
{
pCurrentSchedule = NULL;
uCurrentServiceID = 0;
}
cSchedules::~cSchedules()
{
}
/** */
const cSchedule *cSchedules::SetCurrentServiceID(unsigned short servid)
{
pCurrentSchedule = GetSchedule(servid);
if (pCurrentSchedule == NULL)
{
Add(new cSchedule(servid));
pCurrentSchedule = GetSchedule(servid);
if (pCurrentSchedule == NULL)
return NULL;
}
uCurrentServiceID = servid;
return pCurrentSchedule;
}
/** */
const cSchedule * cSchedules::GetSchedule() const
{
return pCurrentSchedule;
}
/** */
const cSchedule * cSchedules::GetSchedule(unsigned short servid) const
{
cSchedule *p;
p = First();
while (p != NULL)
{
if (p->GetServiceID() == servid)
return p;
p = Next(p);
}
return NULL;
}
/** */
void cSchedules::Cleanup()
{
cSchedule *p;
p = First();
while (p != NULL)
{
p->Cleanup(time(NULL));
p = Next(p);
}
}
/** */
void cSchedules::Dump(FILE *f, const char *Prefix) const
{
for (cSchedule *p = First(); p; p = Next(p))
p->Dump(f, Prefix);
}
/** */
bool cSchedules::Read(FILE *f)
{
cMutexLock MutexLock;
return cSchedule::Read(f, (cSchedules *)cSIProcessor::Schedules(MutexLock));
}
// --- cEIT ------------------------------------------------------------------
class cEIT {
private:
cSchedules *schedules;
public:
cEIT(unsigned char *buf, int length, cSchedules *Schedules);
~cEIT();
/** */
int ProcessEIT(unsigned char *buffer);
protected: // Protected methods
/** returns true if this EIT covers a
present/following information, false if it's
schedule information */
bool IsPresentFollowing();
protected: // Protected attributes
/** Table ID of this EIT struct */
u_char tid;
};
cEIT::cEIT(unsigned char * buf, int length, cSchedules *Schedules)
{
tid = buf[0];
schedules = Schedules;
}
cEIT::~cEIT()
{
}
/** */
int cEIT::ProcessEIT(unsigned char *buffer)
{
cEventInfo *pEvent, *rEvent = NULL;
cSchedule *pSchedule, *rSchedule = NULL;
struct LIST *VdrProgramInfos;
struct VdrProgramInfo *VdrProgramInfo;
if (!buffer)
return -1;
VdrProgramInfos = createVdrProgramInfos(buffer);
if (VdrProgramInfos) {
for (VdrProgramInfo = (struct VdrProgramInfo *) VdrProgramInfos->Head; VdrProgramInfo; VdrProgramInfo = (struct VdrProgramInfo *) xSucc (VdrProgramInfo)) {
// Drop events that belong to an "other TS" and are very long (some stations broadcast bogus data for "other" channels):
if (VdrProgramInfo->Duration >= 86400 && (tid == 0x4F || tid == 0x60 || tid == 0x61))
continue;
pSchedule = (cSchedule *)schedules->GetSchedule(VdrProgramInfo->ServiceID);
if (!pSchedule) {
schedules->Add(new cSchedule(VdrProgramInfo->ServiceID));
pSchedule = (cSchedule *)schedules->GetSchedule(VdrProgramInfo->ServiceID);
if (!pSchedule)
break;
}
if (VdrProgramInfo->ReferenceServiceID) {
rSchedule = (cSchedule *)schedules->GetSchedule(VdrProgramInfo->ReferenceServiceID);
if (!rSchedule)
break;
rEvent = (cEventInfo *)rSchedule->GetEvent((unsigned short)VdrProgramInfo->ReferenceEventID);
if (!rEvent)
break;
}
pEvent = (cEventInfo *)pSchedule->GetEvent((unsigned short)VdrProgramInfo->EventID, VdrProgramInfo->StartTime);
if (!pEvent) {
// If we don't have that event ID yet, we create a new one.
// Otherwise we copy the information into the existing event anyway, because the data might have changed.
pEvent = pSchedule->AddEvent(new cEventInfo(VdrProgramInfo->ServiceID, VdrProgramInfo->EventID));
if (!pEvent)
break;
pEvent->SetTableID(tid);
}
else {
// We have found an existing event, either through its event ID or its start time.
// If the existing event has a zero table ID it was defined externally and shall
// not be overwritten.
if (pEvent->GetTableID() == 0x00)
continue;
// If the new event comes from a table that belongs to an "other TS" and the existing
// one comes from an "actual TS" table, lets skip it.
if ((tid == 0x4F || tid == 0x60 || tid == 0x61) && (pEvent->GetTableID() == 0x4E || pEvent->GetTableID() == 0x50 || pEvent->GetTableID() == 0x51))
continue;
}
if (rEvent) {
pEvent->SetTitle(rEvent->GetTitle());
pEvent->SetSubtitle(rEvent->GetSubtitle());
pEvent->SetExtendedDescription(rEvent->GetExtendedDescription());
}
else {
pEvent->SetTableID(tid);
pEvent->SetTitle(VdrProgramInfo->ShortName);
pEvent->SetSubtitle(VdrProgramInfo->ShortText);
pEvent->SetExtendedDescription(VdrProgramInfo->ExtendedName);
//XXX kls 2001-09-22:
//XXX apparently this never occurred, so I have simpified ExtendedDescription handling
//XXX pEvent->AddExtendedDescription(VdrProgramInfo->ExtendedText);
}
pEvent->SetTime(VdrProgramInfo->StartTime);
pEvent->SetDuration(VdrProgramInfo->Duration);
pEvent->FixEpgBugs();
if (IsPresentFollowing()) {
if ((GetRunningStatus(VdrProgramInfo->Status) == RUNNING_STATUS_PAUSING) || (GetRunningStatus(VdrProgramInfo->Status) == RUNNING_STATUS_RUNNING))
pSchedule->SetPresentEvent(pEvent);
else if (GetRunningStatus(VdrProgramInfo->Status) == RUNNING_STATUS_AWAITING)
pSchedule->SetFollowingEvent(pEvent);
}
}
}
xMemFreeAll(NULL);
return 0;
}
/** returns true if this EIT covers a
present/following information, false if it's
schedule information */
bool cEIT::IsPresentFollowing()
{
if (tid == 0x4e || tid == 0x4f)
return true;
return false;
}
// --- cSIProcessor ----------------------------------------------------------
#define MAX_FILTERS 20
#define EPGDATAFILENAME "epg.data"
int cSIProcessor::numSIProcessors = 0;
cSchedules *cSIProcessor::schedules = NULL;
cMutex cSIProcessor::schedulesMutex;
const char *cSIProcessor::epgDataFileName = EPGDATAFILENAME;
/** */
cSIProcessor::cSIProcessor(const char *FileName)
{
fileName = strdup(FileName);
masterSIProcessor = numSIProcessors == 0; // the first one becomes the 'master'
filters = NULL;
if (!numSIProcessors++) // the first one creates it
schedules = new cSchedules;
filters = (SIP_FILTER *)calloc(MAX_FILTERS, sizeof(SIP_FILTER));
SetStatus(true);
Start();
}
cSIProcessor::~cSIProcessor()
{
if (masterSIProcessor)
ReportEpgBugFixStats();
active = false;
Cancel(3);
ShutDownFilters();
free(filters);
if (!--numSIProcessors) // the last one deletes it
delete schedules;
free(fileName);
}
const cSchedules *cSIProcessor::Schedules(cMutexLock &MutexLock)
{
if (MutexLock.Lock(&schedulesMutex))
return schedules;
return NULL;
}
bool cSIProcessor::Read(FILE *f)
{
bool OwnFile = f == NULL;
if (OwnFile) {
const char *FileName = GetEpgDataFileName();
if (access(FileName, R_OK) == 0) {
dsyslog("reading EPG data from %s", FileName);
if ((f = fopen(FileName, "r")) == NULL) {
LOG_ERROR;
return false;
}
}
else
return false;
}
bool result = cSchedules::Read(f);
if (OwnFile)
fclose(f);
return result;
}
void cSIProcessor::Clear(void)
{
cMutexLock MutexLock(&schedulesMutex);
delete schedules;
schedules = new cSchedules;
}
void cSIProcessor::SetEpgDataFileName(const char *FileName)
{
epgDataFileName = NULL;
if (FileName)
epgDataFileName = strdup(DirectoryOk(FileName) ? AddDirectory(FileName, EPGDATAFILENAME) : FileName);
}
const char *cSIProcessor::GetEpgDataFileName(void)
{
if (epgDataFileName)
return *epgDataFileName == '/' ? epgDataFileName : AddDirectory(VideoDirectory, epgDataFileName);
return NULL;
}
void cSIProcessor::SetStatus(bool On)
{
ShutDownFilters();
if (On)
{
AddFilter(0x14, 0x70); // TDT
AddFilter(0x14, 0x73); // TOT
AddFilter(0x12, 0x4e); // event info, actual TS, present/following
AddFilter(0x12, 0x4f); // event info, other TS, present/following
AddFilter(0x12, 0x50); // event info, actual TS, schedule
AddFilter(0x12, 0x60); // event info, other TS, schedule
AddFilter(0x12, 0x51); // event info, actual TS, schedule for another 4 days
AddFilter(0x12, 0x61); // event info, other TS, schedule for another 4 days
}
}
/** use the vbi device to parse all relevant SI
information and let the classes corresponding
to the tables write their information to the disk */
void cSIProcessor::Action()
{
dsyslog("EIT processing thread started (pid=%d)%s", getpid(), masterSIProcessor ? " - master" : "");
time_t lastCleanup = time(NULL);
time_t lastDump = time(NULL);
active = true;
while(active)
{
if (masterSIProcessor)
{
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)
{
cMutexLock MutexLock(&schedulesMutex);
isyslog("cleaning up schedules data");
schedules->Cleanup();
lastCleanup = now;
ReportEpgBugFixStats(true);
}
if (epgDataFileName && now - lastDump > 600)
{
cMutexLock MutexLock(&schedulesMutex);
cSafeFile f(GetEpgDataFileName());
if (f.Open()) {
schedules->Dump(f);
f.Close();
}
else
LOG_ERROR;
lastDump = now;
}
}
// set up pfd structures for all active filter
pollfd pfd[MAX_FILTERS];
int NumUsedFilters = 0;
for (int a = 0; a < MAX_FILTERS ; a++)
{
if (filters[a].inuse)
{
pfd[NumUsedFilters].fd = filters[a].handle;
pfd[NumUsedFilters].events = POLLIN;
NumUsedFilters++;
}
}
// wait until data becomes ready from the bitfilter
if (poll(pfd, NumUsedFilters, 1000) != 0)
{
for (int a = 0; a < NumUsedFilters ; a++)
{
if (pfd[a].revents & POLLIN)
{
/* read section */
unsigned char buf[4096+1]; // max. allowed size for any EIT section (+1 for safety ;-)
if (safe_read(filters[a].handle, buf, 3) == 3)
{
int seclen = ((buf[1] & 0x0F) << 8) | (buf[2] & 0xFF);
int pid = filters[a].pid;
int n = safe_read(filters[a].handle, buf + 3, seclen);
if (n == seclen)
{
seclen += 3;
//dsyslog("Received pid 0x%02x with table ID 0x%02x and length of %04d\n", pid, buf[0], seclen);
switch (pid)
{
case 0x14:
if (buf[0] == 0x70)
{
if (Setup.SetSystemTime && Setup.TimeTransponder && ISTRANSPONDER(currentTransponder, Setup.TimeTransponder))
{
cTDT ctdt((tdt_t *)buf);
ctdt.SetSystemTime();
}
}
/*XXX this comes pretty often:
else
dsyslog("Time packet was not 0x70 but 0x%02x\n", (int)buf[0]);
XXX*/
break;
case 0x12:
if (buf[0] != 0x72)
{
cMutexLock MutexLock(&schedulesMutex);
cEIT ceit(buf, seclen, schedules);
ceit.ProcessEIT(buf);
}
else
dsyslog("Received stuffing section in EIT\n");
break;
default:
break;
}
}
/*XXX this just fills up the log file - shouldn't we rather try to re-sync?
else
dsyslog("read incomplete section - seclen = %d, n = %d", seclen, n);
XXX*/
}
}
}
}
}
dsyslog("EIT processing thread ended (pid=%d)%s", getpid(), masterSIProcessor ? " - master" : "");
}
/** Add a filter with packet identifier pid and
table identifer tid */
bool cSIProcessor::AddFilter(u_char pid, u_char tid)
{
#ifdef NEWSTRUCT
dmx_sct_filter_params sctFilterParams;
#else
dmxSctFilterParams sctFilterParams;
#endif
memset(&sctFilterParams, 0, sizeof(sctFilterParams));
sctFilterParams.pid = pid;
sctFilterParams.timeout = 0;
sctFilterParams.flags = DMX_IMMEDIATE_START;
sctFilterParams.filter.filter[0] = tid;
sctFilterParams.filter.mask[0] = 0xFF;
for (int a = 0; a < MAX_FILTERS; a++)
{
if (!filters[a].inuse)
{
filters[a].pid = pid;
filters[a].tid = tid;
if ((filters[a].handle = open(fileName, O_RDWR | O_NONBLOCK)) >= 0)
{
if (ioctl(filters[a].handle, DMX_SET_FILTER, &sctFilterParams) >= 0)
filters[a].inuse = true;
else
{
esyslog("ERROR: can't set filter");
close(filters[a].handle);
return false;
}
// dsyslog("Registered filter handle %04x, pid = %02d, tid = %02d", filters[a].handle, filters[a].pid, filters[a].tid);
}
else
{
esyslog("ERROR: can't open filter handle");
return false;
}
return true;
}
}
esyslog("ERROR: too many filters");
return false;
}
/** */
bool cSIProcessor::ShutDownFilters(void)
{
for (int a = 0; a < MAX_FILTERS; a++)
{
if (filters[a].inuse)
{
close(filters[a].handle);
// dsyslog("Deregistered filter handle %04x, pid = %02d, tid = %02d", filters[a].handle, filters[a].pid, filters[a].tid);
filters[a].inuse = false;
}
}
return true; // there's no real 'boolean' to return here...
}
/** */
void cSIProcessor::SetCurrentTransponder(int CurrentTransponder)
{
currentTransponder = CurrentTransponder;
}
/** */
bool cSIProcessor::SetCurrentServiceID(unsigned short servid)
{
cMutexLock MutexLock(&schedulesMutex);
return schedules ? schedules->SetCurrentServiceID(servid) : false;
}