vdr/epg.c

980 lines
29 KiB
C

/*
* 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.24 2005/01/02 11:25:25 kls Exp $
*/
#include "epg.h"
#include "libsi/si.h"
#include "timers.h"
#include <ctype.h>
#include <time.h>
// --- tComponent ------------------------------------------------------------
cString tComponent::ToString(void)
{
char buffer[256];
snprintf(buffer, sizeof(buffer), "%X %02X %-3s %s", stream, type, language, description ? description : "");
return buffer;
}
bool tComponent::FromString(const char *s)
{
unsigned int Stream, Type;
int n = sscanf(s, "%X %02X %3c %a[^\n]", &Stream, &Type, language, &description);
if (n != 4)
description = NULL;
else if (isempty(description)) {
free(description);
description = NULL;
}
stream = Stream;
type = Type;
return n >= 3;
}
// --- cComponents -----------------------------------------------------------
cComponents::cComponents(int NumComponents)
{
numComponents = NumComponents;
components = MALLOC(tComponent, numComponents);
memset(components, 0, sizeof(tComponent) * numComponents);
}
cComponents::~cComponents(void)
{
for (int i = 0; i < numComponents; i++)
free(components[i].description);
free(components);
}
bool cComponents::SetComponent(int Index, const char *s)
{
if (Index < numComponents)
return components[Index].FromString(s);
return false;
}
bool cComponents::SetComponent(int Index, uchar Stream, uchar Type, const char *Language, const char *Description)
{
if (Index < numComponents) {
tComponent *p = &components[Index];
p->stream = Stream;
p->type = Type;
strn0cpy(p->language, Language, sizeof(p->language));
p->description = strcpyrealloc(p->description, !isempty(Description) ? Description : NULL);
return true;
}
return false;
}
// --- cEvent ----------------------------------------------------------------
cEvent::cEvent(tChannelID ChannelID, u_int16_t EventID)
{
channelID = ChannelID;
eventID = EventID;
tableID = 0;
version = 0xFF; // actual version numbers are 0..31
runningStatus = 0;
title = NULL;
shortText = NULL;
description = NULL;
components = NULL;
startTime = 0;
duration = 0;
vps = 0;
SetSeen();
}
cEvent::~cEvent()
{
free(title);
free(shortText);
free(description);
delete components;
}
int cEvent::Compare(const cListObject &ListObject) const
{
cEvent *e = (cEvent *)&ListObject;
return startTime - e->startTime;
}
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::SetRunningStatus(int RunningStatus, cChannel *Channel)
{
if (Channel && runningStatus != RunningStatus && (RunningStatus > SI::RunningStatusNotRunning || runningStatus > SI::RunningStatusUndefined))
if (Channel->Number() <= 30)//XXX maybe log only those that have timers???
isyslog("channel %d (%s) event %s '%s' status %d", Channel->Number(), Channel->Name(), *GetTimeString(), Title(), RunningStatus);
runningStatus = RunningStatus;
}
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::SetComponents(cComponents *Components)
{
delete components;
components = Components;
}
void cEvent::SetStartTime(time_t StartTime)
{
startTime = StartTime;
}
void cEvent::SetDuration(int Duration)
{
duration = Duration;
}
void cEvent::SetVps(time_t Vps)
{
vps = Vps;
}
void cEvent::SetSeen(void)
{
seen = time(NULL);
}
bool cEvent::HasTimer(void) const
{
for (cTimer *t = Timers.First(); t; t = Timers.Next(t)) {
if (t->Event() == this)
return true;
}
return false;
}
bool cEvent::IsRunning(bool OrAboutToStart) const
{
return runningStatus >= (OrAboutToStart ? SI::RunningStatusStartsInAFewSeconds : SI::RunningStatusPausing);
}
cString cEvent::GetDateString(void) const
{
char buf[32];
struct tm tm_r;
tm *tm = localtime_r(&startTime, &tm_r);
char *p = stpcpy(buf, WeekDayName(tm->tm_wday));
*p++ = ' ';
strftime(p, sizeof(buf) - (p - buf), "%d.%m.%Y", tm);
return buf;
}
cString cEvent::GetTimeString(void) const
{
char buf[25];
struct tm tm_r;
strftime(buf, sizeof(buf), "%R", localtime_r(&startTime, &tm_r));
return buf;
}
cString cEvent::GetEndTimeString(void) const
{
char buf[25];
time_t EndTime = startTime + duration;
struct tm tm_r;
strftime(buf, sizeof(buf), "%R", localtime_r(&EndTime, &tm_r));
return buf;
}
cString cEvent::GetVpsString(void) const
{
char buf[25];
struct tm tm_r;
strftime(buf, sizeof(buf), "%d.%m %R", localtime_r(&vps, &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)) {
strreplace(description, '\n', '|');
fprintf(f, "%sD %s\n", Prefix, description);
strreplace(description, '|', '\n');
}
if (components) {
for (int i = 0; i < components->NumComponents(); i++) {
tComponent *p = components->Component(i);
fprintf(f, "%sX %s\n", Prefix, *p->ToString());
}
}
if (vps)
fprintf(f, "%sV %ld\n", Prefix, vps);
fprintf(f, "%se\n", Prefix);
}
}
bool cEvent::Read(FILE *f, cSchedule *Schedule)
{
if (Schedule) {
cEvent *Event = NULL;
int NumComponents = 0;
char *ComponentStrings[MAXCOMPONENTS];
char *s;
cReadLine ReadLine;
while ((s = ReadLine.Read(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);
}
}
NumComponents = 0;
}
break;
case 'T': if (Event)
Event->SetTitle(t);
break;
case 'S': if (Event)
Event->SetShortText(t);
break;
case 'D': if (Event) {
strreplace(t, '|', '\n');
Event->SetDescription(t);
}
break;
case 'X': if (Event) {
if (NumComponents < MAXCOMPONENTS)
ComponentStrings[NumComponents++] = strdup(t);
else
dsyslog("more than %d component descriptors!", MAXCOMPONENTS);
}
break;
case 'V': if (Event)
Event->SetVps(atoi(t));
break;
case 'e': if (Event && NumComponents > 0) {
cComponents *Components = new cComponents(NumComponents);
for (int i = 0; i < NumComponents; i++) {
if (!Components->SetComponent(i, ComponentStrings[i]))
esyslog("ERROR: faulty component string in EPG data: '%s'", ComponentStrings[i]);
free(ComponentStrings[i]);
}
Event->SetComponents(Components);
}
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 12
#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 title and shortText of EPG
// data, so let's always convert them to blanks (independent of the setting of EPGBugfixLevel):
strreplace(title, '\n', ' ');
strreplace(shortText, '\n', ' ');
// Same for control characters:
strreplace(title, '\x86', ' ');
strreplace(title, '\x87', ' ');
strreplace(shortText, '\x86', ' ');
strreplace(shortText, '\x87', ' ');
strreplace(description, '\x86', ' ');
strreplace(description, '\x87', ' ');
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 put the same information into ShortText and Description.
// In that case we delete one of them:
if (shortText && description && strcmp(shortText, description) == 0) {
if (strlen(shortText) > MAX_USEFUL_EPISODE_LENGTH) {
free(shortText);
shortText = NULL;
}
else {
free(description);
description = NULL;
}
EpgBugFixStat(7, 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, '`', '\'');
if (Setup.EPGBugfixLevel <= 2)
return;
// The stream components have a "description" field which some channels
// apparently have no idea of how to set correctly:
if (components) {
for (int i = 0; i < components->NumComponents(); i++) {
tComponent *p = components->Component(i);
switch (p->stream) {
case 0x01: { // video
if (p->description) {
if (strcasecmp(p->description, "Video") == 0 ||
strcasecmp(p->description, "Bildformat") == 0) {
// Yes, we know it's video - that's what the 'stream' code
// is for! But _which_ video is it?
free(p->description);
p->description = NULL;
EpgBugFixStat(8, ChannelID());
}
}
if (!p->description) {
switch (p->type) {
case 0x01:
case 0x05: p->description = strdup("4:3"); break;
case 0x02:
case 0x03:
case 0x06:
case 0x07: p->description = strdup("16:9"); break;
case 0x04:
case 0x08: p->description = strdup(">16:9"); break;
case 0x09:
case 0x0D: p->description = strdup("HD 4:3"); break;
case 0x0A:
case 0x0B:
case 0x0E:
case 0x0F: p->description = strdup("HD 16:9"); break;
case 0x0C:
case 0x10: p->description = strdup("HD >16:9"); break;
}
EpgBugFixStat(9, ChannelID());
}
}
break;
case 0x02: { // audio
if (p->description) {
if (strcasecmp(p->description, "Audio") == 0) {
// Yes, we know it's audio - that's what the 'stream' code
// is for! But _which_ audio is it?
free(p->description);
p->description = NULL;
EpgBugFixStat(10, ChannelID());
}
}
if (!p->description) {
switch (p->type) {
case 0x05: p->description = strdup("Dolby Digital"); break;
// all others will just display the language
}
EpgBugFixStat(11, ChannelID());
}
}
break;
}
}
}
}
}
// --- cSchedule -------------------------------------------------------------
cSchedule::cSchedule(tChannelID ChannelID)
{
channelID = ChannelID;
hasRunning = false;;
modified = 0;
}
cEvent *cSchedule::AddEvent(cEvent *Event)
{
events.Add(Event);
return Event;
}
const cEvent *cSchedule::GetPresentEvent(bool CheckRunningStatus) const
{
const cEvent *pe = NULL;
time_t now = time(NULL);
for (cEvent *p = events.First(); p; p = events.Next(p)) {
if (p->StartTime() <= now && now < p->EndTime()) {
pe = p;
if (!CheckRunningStatus)
break;
}
if (CheckRunningStatus && time(NULL) - p->Seen() < 30 && p->RunningStatus() >= SI::RunningStatusPausing)
return p;
}
return pe;
}
const cEvent *cSchedule::GetFollowingEvent(bool CheckRunningStatus) const
{
const cEvent *p = GetPresentEvent(CheckRunningStatus);
if (p)
p = events.Next(p);
return p;
}
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->EndTime() >= Time) {
delta = dt;
pe = p;
}
}
return pe;
}
void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Channel)
{
for (cEvent *p = events.First(); p; p = events.Next(p)) {
if (p == Event)
p->SetRunningStatus(RunningStatus, Channel);
else if (RunningStatus >= SI::RunningStatusPausing && p->RunningStatus() > SI::RunningStatusNotRunning)
p->SetRunningStatus(SI::RunningStatusNotRunning);
}
if (RunningStatus >= SI::RunningStatusPausing)
hasRunning = true;
}
void cSchedule::ClrRunningStatus(cChannel *Channel)
{
if (hasRunning) {
for (cEvent *p = events.First(); p; p = events.Next(p)) {
if (p->RunningStatus() >= SI::RunningStatusPausing) {
p->SetRunningStatus(SI::RunningStatusNotRunning, Channel);
hasRunning = false;
break;
}
}
}
}
void cSchedule::ResetVersions(void)
{
for (cEvent *p = events.First(); p; p = events.Next(p))
p->SetVersion(0xFF);
}
void cSchedule::Sort(void)
{
events.Sort();
}
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->HasTimer() && Event->EndTime() + Setup.EPGLinger * 60 + 3600 < Time) { // adding one hour for safety
events.Del(Event);
a--;
}
}
}
void cSchedule::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) const
{
cChannel *channel = Channels.GetByChannelID(channelID, true);
if (channel) {
fprintf(f, "%sC %s %s\n", Prefix, *channel->GetChannelID().ToString(), channel->Name());
const cEvent *p;
switch (DumpMode) {
case dmAll: {
for (p = events.First(); p; p = events.Next(p))
p->Dump(f, Prefix);
}
break;
case dmPresent: {
if ((p = GetPresentEvent()) != NULL)
p->Dump(f, Prefix);
}
break;
case dmFollowing: {
if ((p = GetFollowingEvent()) != NULL)
p->Dump(f, Prefix);
}
break;
case dmAtTime: {
if ((p = GetEventAround(AtTime)) != NULL)
p->Dump(f, Prefix);
}
break;
}
fprintf(f, "%sc\n", Prefix);
}
}
bool cSchedule::Read(FILE *f, cSchedules *Schedules)
{
if (Schedules) {
cReadLine ReadLine;
char *s;
while ((s = ReadLine.Read(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;
p->Sort();
Schedules->SetModified(p);
}
}
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);
time_t cSchedules::modified = 0;
const cSchedules *cSchedules::Schedules(cSchedulesLock &SchedulesLock)
{
return SchedulesLock.Locked() ? &schedules : NULL;
}
void cSchedules::SetEpgDataFileName(const char *FileName)
{
delete epgDataFileName;
epgDataFileName = FileName ? strdup(FileName) : NULL;
}
void cSchedules::SetModified(cSchedule *Schedule)
{
Schedule->SetModified();
modified = time(NULL);
}
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;
}
}
void cSchedules::ResetVersions(void)
{
cSchedulesLock SchedulesLock(true);
cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
if (s) {
for (cSchedule *Schedule = s->First(); Schedule; Schedule = s->Next(Schedule))
Schedule->ResetVersions();
}
}
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, eDumpMode DumpMode, time_t AtTime)
{
cSchedulesLock SchedulesLock;
cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
if (s) {
for (cSchedule *p = s->First(); p; p = s->Next(p))
p->Dump(f, Prefix, DumpMode, AtTime);
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;
}