1
0
mirror of https://github.com/VDR4Arch/vdr.git synced 2023-10-10 13:36:52 +02:00
vdr/eit.c
2003-10-12 11:06:56 +02:00

1484 lines
43 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.80 2003/10/12 11:05:42 kls Exp $
***************************************************************************/
#include "eit.h"
#include <ctype.h>
#include <fcntl.h>
#include <limits.h>
#include <linux/dvb/dmx.h>
#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(tChannelID channelid, unsigned short eventid)
{
pTitle = NULL;
pSubtitle = NULL;
pExtendedDescription = NULL;
bIsPresent = bIsFollowing = false;
lDuration = 0;
tTime = 0;
uTableID = 0;
uEventID = eventid;
channelID = channelid;
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::SetChannelID(tChannelID channelid)
{
channelID = channelid;
}
/** */
tChannelID cEventInfo::GetChannelID() const
{
return channelID;
}
/** */
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->GetChannelID(), 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 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;
}
}
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) {
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 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;
}
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 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, GetChannelID());
// 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, GetChannelID());
}
}
}
// 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, GetChannelID());
}
}
// Pro7 sometimes repeats the Title in the Subtitle:
//
// Title
// Title
//
if (pSubtitle && strcmp(pTitle, pSubtitle) == 0) {
free(pSubtitle);
pSubtitle = NULL;
EpgBugFixStat(3, GetChannelID());
}
// 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, GetChannelID());
}
}
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, GetChannelID());
}
}
p++;
}
}
#define MAX_USEFUL_SUBTITLE_LENGTH 40
// Some channels put a whole lot of information in the Subtitle and leave
// the Extended Description totally empty. So if the Subtitle length exceeds
// MAX_USEFUL_SUBTITLE_LENGTH, let's put this into the Extended Description
// instead:
if (!isempty(pSubtitle) && isempty(pExtendedDescription)) {
if (strlen(pSubtitle) > MAX_USEFUL_SUBTITLE_LENGTH) {
free(pExtendedDescription);
pExtendedDescription = pSubtitle;
pSubtitle = NULL;
EpgBugFixStat(6, GetChannelID());
}
}
// 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(tChannelID channelid)
{
pPresent = pFollowing = NULL;
channelID = channelid;
}
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::SetChannelID(tChannelID channelid)
{
channelID = channelid;
}
/** */
tChannelID cSchedule::GetChannelID() const
{
return channelID;
}
/** */
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.GetByChannelID(channelID, true);
if (channel)
{
fprintf(f, "%sC %s %s\n", Prefix, channel->GetChannelID().ToString(), 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') {
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 = (cSchedule *)Schedules->AddChannelID(channelID);
if (p) {
if (!cEventInfo::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;
}
// --- cSchedules ------------------------------------------------------------
cSchedules::cSchedules()
{
pCurrentSchedule = NULL;
}
cSchedules::~cSchedules()
{
}
/** */
const cSchedule *cSchedules::AddChannelID(tChannelID channelid)
{
channelid.ClrRid();
const cSchedule *p = GetSchedule(channelid);
if (!p) {
Add(new cSchedule(channelid));
p = GetSchedule(channelid);
}
return p;
}
/** */
const cSchedule *cSchedules::SetCurrentChannelID(tChannelID channelid)
{
channelid.ClrRid();
pCurrentSchedule = AddChannelID(channelid);
if (pCurrentSchedule)
currentChannelID = channelid;
return pCurrentSchedule;
}
/** */
const cSchedule * cSchedules::GetSchedule() const
{
return pCurrentSchedule;
}
/** */
const cSchedule * cSchedules::GetSchedule(tChannelID channelid) const
{
cSchedule *p;
channelid.ClrRid();
p = First();
while (p != NULL)
{
if (p->GetChannelID() == channelid)
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, int CurrentSource);
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, int CurrentSource)
{
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)) {
//XXX TODO use complete channel ID
cChannel *channel = Channels.GetByServiceID(CurrentSource, VdrProgramInfo->ServiceID);
tChannelID channelID = channel ? channel->GetChannelID() : tChannelID(CurrentSource, 0, 0, VdrProgramInfo->ServiceID);
channelID.ClrRid();
//XXX
pSchedule = (cSchedule *)schedules->GetSchedule(channelID);
if (!pSchedule) {
schedules->Add(new cSchedule(channelID));
pSchedule = (cSchedule *)schedules->GetSchedule(channelID);
if (!pSchedule)
break;
}
if (VdrProgramInfo->ReferenceServiceID) {
rSchedule = (cSchedule *)schedules->GetSchedule(tChannelID(CurrentSource, 0, 0, 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(channelID, 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;
}
// --- cCaDescriptor ---------------------------------------------------------
class cCaDescriptor : public cListObject {
friend class cSIProcessor;
private:
int source;
int transponder;
int serviceId;
int caSystem;
unsigned int providerId;
int caPid;
int length;
uchar *data;
public:
cCaDescriptor(int Source, int Transponder, int ServiceId, int CaSystem, unsigned int ProviderId, int CaPid, int Length, uchar *Data);
virtual ~cCaDescriptor();
int Length(void) const { return length; }
const uchar *Data(void) const { return data; }
};
cCaDescriptor::cCaDescriptor(int Source, int Transponder, int ServiceId, int CaSystem, unsigned int ProviderId, int CaPid, int Length, uchar *Data)
{
source = Source;
transponder = Transponder;
serviceId = ServiceId;
caSystem = CaSystem;
providerId = ProviderId;
caPid = CaPid;
length = Length + 6;
data = MALLOC(uchar, length);
data[0] = DESCR_CA;
data[1] = length - 2;
data[2] = (caSystem >> 8) & 0xFF;
data[3] = caSystem & 0xFF;
data[4] = ((CaPid >> 8) & 0x1F) | 0xE0;
data[5] = CaPid & 0xFF;
if (Length)
memcpy(&data[6], Data, Length);
//#define DEBUG_CA_DESCRIPTORS 1
#ifdef DEBUG_CA_DESCRIPTORS
char buffer[1024];
char *q = buffer;
q += sprintf(q, "CAM: %04X %5d %5d %04X %6X %04X -", source, transponder, serviceId, caSystem, providerId, caPid);
for (int i = 0; i < length; i++)
q += sprintf(q, " %02X", data[i]);
dsyslog(buffer);
#endif
}
cCaDescriptor::~cCaDescriptor()
{
free(data);
}
// --- cSIProcessor ----------------------------------------------------------
#define MAX_FILTERS 20
#define EPGDATAFILENAME "epg.data"
int cSIProcessor::numSIProcessors = 0;
cSchedules *cSIProcessor::schedules = NULL;
cMutex cSIProcessor::schedulesMutex;
cList<cCaDescriptor> cSIProcessor::caDescriptors;
cMutex cSIProcessor::caDescriptorsMutex;
const char *cSIProcessor::epgDataFileName = EPGDATAFILENAME;
time_t cSIProcessor::lastDump = time(NULL);
/** */
cSIProcessor::cSIProcessor(const char *FileName)
{
fileName = strdup(FileName);
masterSIProcessor = numSIProcessors == 0; // the first one becomes the 'master'
currentSource = 0;
currentTransponder = 0;
statusCount = 0;
pmtIndex = 0;
pmtPid = 0;
filters = NULL;
if (!numSIProcessors++) { // the first one creates them
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 them
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)
{
LOCK_THREAD;
statusCount++;
ShutDownFilters();
pmtIndex = 0;
pmtPid = 0;
if (On)
{
AddFilter(0x00, 0x00); // PAT
AddFilter(0x14, 0x70); // TDT
AddFilter(0x12, 0x4e, 0xfe); // event info, actual(0x4e)/other(0x4f) TS, present/following
AddFilter(0x12, 0x50, 0xfe); // event info, actual TS, schedule(0x50)/schedule for another 4 days(0x51)
AddFilter(0x12, 0x60, 0xfe); // event info, other TS, schedule(0x60)/schedule for another 4 days(0x61)
}
}
#define PMT_SCAN_TIMEOUT 10 // seconds
/** 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 lastPmtScan = time(NULL);
int oldStatusCount = 0;
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
Lock();
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++;
}
}
oldStatusCount = statusCount;
Unlock();
// wait until data becomes ready from the bitfilter
if (poll(pfd, NumUsedFilters, 1000) != 0)
{
for (int aa = 0; aa < NumUsedFilters; aa++)
{
if (pfd[aa].revents & POLLIN)
{
int a;
for (a = 0; a < MAX_FILTERS; a++) {
if (pfd[aa].fd == filters[a].handle)
break;
}
if (a >= MAX_FILTERS || !filters[a].inuse) // filter no longer available
continue;
// read section
unsigned char buf[4096]; // max. allowed size for any EIT section
int r = safe_read(filters[a].handle, buf, sizeof(buf));
if (r > 3) // minimum number of bytes necessary to get section length
{
int seclen = (((buf[1] & 0x0F) << 8) | (buf[2] & 0xFF)) + 3;
int pid = filters[a].pid;
if (seclen == r)
{
//dsyslog("Received pid 0x%04X with table ID 0x%02X and length of %4d\n", pid, buf[0], seclen);
cMutexLock MutexLock(&schedulesMutex); // since the xMem... stuff is not thread safe, we need to use a "global" mutex
LOCK_THREAD;
if (statusCount != oldStatusCount)
break;
switch (pid)
{
case 0x00:
if (buf[0] == 0x00)
{
if (pmtPid && time(NULL) - lastPmtScan > PMT_SCAN_TIMEOUT) {
DelFilter(pmtPid, 0x02);
pmtPid = 0;
pmtIndex++;
lastPmtScan = time(NULL);
}
if (!pmtPid) {
struct LIST *pat = siParsePAT(buf);
if (pat) {
int Index = 0;
for (struct Program *prg = (struct Program *)pat->Head; prg; prg = (struct Program *)xSucc(prg)) {
if (prg->ProgramID) {
if (Index++ == pmtIndex) {
pmtPid = prg->NetworkPID;
AddFilter(pmtPid, 0x02);
break;
}
}
}
if (!pmtPid)
pmtIndex = 0;
}
xMemFreeAll(NULL);
}
}
break;
case 0x14:
if (buf[0] == 0x70)
{
if (Setup.SetSystemTime && Setup.TimeTransponder && ISTRANSPONDER(currentTransponder, Setup.TimeTransponder))
{
cTDT ctdt((tdt_t *)buf);
ctdt.SetSystemTime();
}
}
break;
case 0x12:
if (buf[0] != 0x72)
{
cEIT ceit(buf, seclen, schedules);
ceit.ProcessEIT(buf, currentSource);
}
/*else
dsyslog("Received stuffing section in EIT\n");
*/
break;
default: {
if (pid == pmtPid && buf[0] == 0x02 && currentSource && currentTransponder) {
struct Pid *pi = siParsePMT(buf);
if (pi) {
struct Descriptor *d;
for (d = (struct Descriptor *)pi->Descriptors->Head; d; d = (struct Descriptor *)xSucc(d))
NewCaDescriptor(d, pi->ProgramID);
// Also scan the PidInfo list for descriptors - some broadcasts send them only here.
for (struct PidInfo *p = (struct PidInfo *)pi->InfoList->Head; p; p = (struct PidInfo *)xSucc(p)) {
for (d = (struct Descriptor *)p->Descriptors->Head; d; d = (struct Descriptor *)xSucc(d))
NewCaDescriptor(d, pi->ProgramID);
}
}
xMemFreeAll(NULL);
lastPmtScan = 0; // this triggers the next scan
}
}
break;
}
}
/*
else
dsyslog("read incomplete section - seclen = %d, r = %d", seclen, r);
*/
}
}
}
}
}
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(unsigned short pid, u_char tid, u_char mask)
{
dmx_sct_filter_params sctFilterParams;
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] = mask;
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 (pid=%d, tid=%02X)", pid, tid);
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::DelFilter(unsigned short pid, u_char tid)
{
for (int a = 0; a < MAX_FILTERS; a++)
{
if (filters[a].inuse && filters[a].pid == pid && filters[a].tid == tid)
{
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;
}
}
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 CurrentSource, int CurrentTransponder)
{
currentSource = CurrentSource;
currentTransponder = CurrentTransponder;
}
/** */
bool cSIProcessor::SetCurrentChannelID(tChannelID channelid)
{
cMutexLock MutexLock(&schedulesMutex);
return schedules ? schedules->SetCurrentChannelID(channelid) : false;
}
void cSIProcessor::TriggerDump(void)
{
cMutexLock MutexLock(&schedulesMutex);
lastDump = 0;
}
void cSIProcessor::NewCaDescriptor(struct Descriptor *d, int ServiceId)
{
if (DescriptorTag(d) == DESCR_CA) {
struct CaDescriptor *cd = (struct CaDescriptor *)d;
cMutexLock MutexLock(&caDescriptorsMutex);
for (cCaDescriptor *ca = caDescriptors.First(); ca; ca = caDescriptors.Next(ca)) {
if (ca->source == currentSource && ca->transponder == currentTransponder && ca->serviceId == ServiceId && ca->caSystem == cd->CA_type && ca->providerId == cd->ProviderID && ca->caPid == cd->CA_PID)
return;
}
caDescriptors.Add(new cCaDescriptor(currentSource, currentTransponder, ServiceId, cd->CA_type, cd->ProviderID, cd->CA_PID, cd->DataLength, cd->Data));
//XXX update???
}
}
int cSIProcessor::GetCaDescriptors(int Source, int Transponder, int ServiceId, const unsigned short *CaSystemIds, int BufSize, uchar *Data)
{
if (!CaSystemIds || !*CaSystemIds)
return 0;
if (BufSize > 0 && Data) {
cMutexLock MutexLock(&caDescriptorsMutex);
int length = 0;
for (cCaDescriptor *d = caDescriptors.First(); d; d = caDescriptors.Next(d)) {
if (d->source == Source && d->transponder == Transponder && d->serviceId == ServiceId) {
const unsigned short *caids = CaSystemIds;
do {
if (d->caSystem == *caids) {
if (length + d->Length() <= BufSize) {
memcpy(Data + length, d->Data(), d->Length());
length += d->Length();
}
else
return -1;
}
} while (*++caids);
}
}
return length;
}
return -1;
}