vdr/timers.c
Klaus Schmidinger 716c03a47e Version 2.1.3
VDR developer version 2.1.3 is now available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-2.1.3.tar.bz2

A 'diff' against the previous version is available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-2.1.2-2.1.3.diff

MD5 checksums:

054f80e0045aa6fad118e9285b52f4f2  vdr-2.1.3.tar.bz2
3c5ab05d5c4d0b984b34e84190e80949  vdr-2.1.2-2.1.3.diff

WARNING:
========

This is a *developer* version. Even though *I* use it in my productive
environment, I strongly recommend that you only use it under controlled
conditions and for testing and debugging.

Originally I intended to release this version only after the new DiSEqC
configuration dialog was finished. But in the meantime quite a few other things
have come up, so I decided to postpone that dialog and first release what has
piled up so far.

From the HISTORY file:
- Changed the return value of cPositioner::HorizonLongitude() to 0 in case the
  latitude of the antenna location is beyond +/-81 degrees.
- Updated the Finnish OSD texts (thanks to Rolf Ahrenberg).
- Fixed some compiler warnings with gcc-4.6.3 (thanks to Rolf Ahrenberg).
- Changed the name of the SVDRP command RENR to MOVR (suggested by Rolf Ahrenberg).
- When cutting a recording it is now checked whether there is already an edited
  version of this recording (with the same name, but starting with '%'), and the
  user is prompted for confirmation to overwrite it (suggested by Rolf Ahrenberg).
- Revoked "Added maximum signal strength value for TechniSat SkyStar 2 DVB-S rev 2.3P"
  because it broke things for the "TechniSat AirStar 2" DVB-T card.
- The LIRC remote control now connects to the socket even if it doesn't yet exist when
  VDR is started (thanks to Lars Hanisch).
- Changed the absolute latitude limit for visible satellites to 81.2 degrees.
- Added code for parsing LCN and AVC descriptors to libsi (thanks to Rolf Ahrenberg).
- In the "Select folder" menu pressing Ok now selects the folder, even if this is a
  folder that contains sub folders (marked with "..."). To open such a folder you
  can press the Red key.
- Fixed a possible access to uninitialized data in cEIT::cEIT() (reported by Dominik
  Strasser).
- The new menu category mcRecordingEdit is now used to mark menus that edit recording
  properties (suggested by Stefan Braun).
- Changes in the teletext PID no longer cause retuning (and thus interrupting a
  recording).
- Removed '_' from the FileNameChars and CharMap translations in uk_UA.po.
- Updated the Italian OSD texts (thanks to Diego Pierotto).
- Fixed a missing initialization in the c'tor of cSkinLCARSDisplayChannel (thanks to
  Marko Mäkelä).
- Simplified some conditional expressions in skinlcars.c and skinsttng.c (suggested
  by Marko Mäkelä).
- Fixed uninitialized item area coordinates in cSkinLCARSDisplayMenu (reported by
  Marko Mäkelä).
- Fixed a possible crash if the recordings list is updated externally while the
  Recordings menu is open (reported by Lars Hanisch).
- Added a missing closing ')' in the help and man page entry of the --vfat option
  (reported by Lars Hanisch).
- Fixed setting the name of the video directory to avoid a crash when using --genindex,
  and also to use the correct directory with --edit (the latter reported by Marko
  Mäkelä).
- The Recordings menu can now be called with a cRecordingFilter, which allows the
  caller to have it display only a certain subset of the recordings (thanks to Lars
  Hanisch).
- Added handling UTF-8 'umlaut' characters to cKbdRemote (thanks to Lars Hanisch).
- Made it clear that the Data parameter in cDevice::StillPicture() may point to a
  series of packets, not just a single one (thanks to Thomas Reufer).
- cDevice::TrickSpeed() now has an additional parameter named Forward, which indicates
  the direction in which replay is being done (suggested by Thomas Reufer). This
  information may be necessary for some output devices in order to properly implement
  trick modes. Authors of plugins that implement output devices will need to add this
  parameter to their derived cDevice class, regardless of whether they will make use
  of it or not.
- Added a note to ePlayMode in device.h that VDR itself always uses pmAudioVideo when
  replaying a recording (suggested by Thomas Reufer).
- Fixed some spellings in positioner.h and Doxyfile (thanks to Ville Skyttä).
- Changed '%a' to the POSIX compliant '%m' in all scanf() calls (thanks to Ville
  Skyttä).
- The new function cCamSlot::Decrypt() can be used by derived classes to implement a
  CAM slot that can be freely assigned to any device, without being directly inserted
  into the full TS data stream in hardware. A derived class that implements Decrypt()
  will also need to set the new parameter ReceiveCaPids in the call to the cCamSlot
  base class constructor to true, in order to receive the CA pid TS packets that
  contain data necessary for decrypting.
- Many member functions of cCamSlot have been made virtual to allow for easier
  implementation of derived classes.
- cTSBuffer now provides the number of available bytes in its Get() function.
- cDvbDevice::GetTSPacket() now calls CamSlot()->Decrypt() in order to allow CAM slots
  that can be freely assigned to any device access to the TS data stream.
- Added a check to avoid a possible NULL pointer dereference in cCiSession::SendData()
  (reported by Ville Skyttä).
- Deleted a superfluous assignment in cPipe::Open() (reported by Ville Skyttä).
- The script given to VDR with the '-r' option is now also called after the recording
  process has actually started (thanks to Christian Kaiser).
- Avoiding unnecessary pkg-config warnings in plugin Makefiles (thanks to Ville Skyttä).
  Plugin authors may want to apply the following change to their Makefile:
  -PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell pkg-config --variable=$(1) vdr || pkg-config --variable=$(1) ../../../vdr.pc))
  +PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell PKG_CONFIG_PATH="$$PKG_CONFIG_PATH:../../.." pkg-config --variable=$(1) vdr))
- Eliminated MAXDVBDEVICES (suggested by Oliver Endriss).
- Channels that are no longer contained in the current SDT of a transponder are now
  marked with the keyword OBSOLETE in their name and provider fields. That way you can
  identify obsolete channels when you switch to them, and you can get the complete
  overview of all obsolete channels by sorting the Channels list by provider (by
  pressing the 0 key twice). Automatic deletion of obsolete channels may follow later.
2014-01-06 18:37:32 +01:00

848 lines
23 KiB
C

/*
* timers.c: Timer handling
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: timers.c 3.1 2013/12/28 11:33:08 kls Exp $
*/
#include "timers.h"
#include <ctype.h>
#include "channels.h"
#include "device.h"
#include "i18n.h"
#include "libsi/si.h"
#include "recording.h"
#include "remote.h"
#include "status.h"
// IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d'
// format characters in order to allow any number of blanks after a numeric
// value!
// --- cTimer ----------------------------------------------------------------
cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
{
startTime = stopTime = 0;
lastSetEvent = 0;
deferred = 0;
recording = pending = inVpsMargin = false;
flags = tfNone;
*file = 0;
aux = NULL;
event = NULL;
if (Instant)
SetFlags(tfActive | tfInstant);
channel = Channel ? Channel : Channels.GetByNumber(cDevice::CurrentChannel());
time_t t = time(NULL);
struct tm tm_r;
struct tm *now = localtime_r(&t, &tm_r);
day = SetTime(t, 0);
weekdays = 0;
start = now->tm_hour * 100 + now->tm_min;
stop = 0;
if (!Setup.InstantRecordTime && channel && (Instant || Pause)) {
cSchedulesLock SchedulesLock;
if (const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock)) {
if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) {
if (const cEvent *Event = Schedule->GetPresentEvent()) {
time_t tstart = Event->StartTime();
time_t tstop = Event->EndTime();
if (Event->Vps() && Setup.UseVps) {
SetFlags(tfVps);
tstart = Event->Vps();
}
else {
tstop += Setup.MarginStop * 60;
tstart -= Setup.MarginStart * 60;
}
day = SetTime(tstart, 0);
struct tm *time = localtime_r(&tstart, &tm_r);
start = time->tm_hour * 100 + time->tm_min;
time = localtime_r(&tstop, &tm_r);
stop = time->tm_hour * 100 + time->tm_min;
SetEvent(Event);
}
}
}
}
if (!stop) {
stop = now->tm_hour * 60 + now->tm_min + (Setup.InstantRecordTime ? Setup.InstantRecordTime : DEFINSTRECTIME);
stop = (stop / 60) * 100 + (stop % 60);
}
if (stop >= 2400)
stop -= 2400;
priority = Pause ? Setup.PausePriority : Setup.DefaultPriority;
lifetime = Pause ? Setup.PauseLifetime : Setup.DefaultLifetime;
if (Instant && channel)
snprintf(file, sizeof(file), "%s%s", Setup.MarkInstantRecord ? "@" : "", *Setup.NameInstantRecord ? Setup.NameInstantRecord : channel->Name());
}
cTimer::cTimer(const cEvent *Event)
{
startTime = stopTime = 0;
lastSetEvent = 0;
deferred = 0;
recording = pending = inVpsMargin = false;
flags = tfActive;
*file = 0;
aux = NULL;
event = NULL;
if (Event->Vps() && Setup.UseVps)
SetFlags(tfVps);
channel = Channels.GetByChannelID(Event->ChannelID(), true);
time_t tstart = (flags & tfVps) ? Event->Vps() : Event->StartTime();
time_t tstop = tstart + Event->Duration();
if (!(HasFlags(tfVps))) {
tstop += Setup.MarginStop * 60;
tstart -= Setup.MarginStart * 60;
}
struct tm tm_r;
struct tm *time = localtime_r(&tstart, &tm_r);
day = SetTime(tstart, 0);
weekdays = 0;
start = time->tm_hour * 100 + time->tm_min;
time = localtime_r(&tstop, &tm_r);
stop = time->tm_hour * 100 + time->tm_min;
if (stop >= 2400)
stop -= 2400;
priority = Setup.DefaultPriority;
lifetime = Setup.DefaultLifetime;
const char *Title = Event->Title();
if (!isempty(Title))
Utf8Strn0Cpy(file, Event->Title(), sizeof(file));
SetEvent(Event);
}
cTimer::cTimer(const cTimer &Timer)
{
channel = NULL;
aux = NULL;
event = NULL;
flags = tfNone;
*this = Timer;
}
cTimer::~cTimer()
{
free(aux);
}
cTimer& cTimer::operator= (const cTimer &Timer)
{
if (&Timer != this) {
uint OldFlags = flags & tfRecording;
startTime = Timer.startTime;
stopTime = Timer.stopTime;
lastSetEvent = 0;
deferred = 0;
recording = Timer.recording;
pending = Timer.pending;
inVpsMargin = Timer.inVpsMargin;
flags = Timer.flags | OldFlags;
channel = Timer.channel;
day = Timer.day;
weekdays = Timer.weekdays;
start = Timer.start;
stop = Timer.stop;
priority = Timer.priority;
lifetime = Timer.lifetime;
strncpy(file, Timer.file, sizeof(file));
free(aux);
aux = Timer.aux ? strdup(Timer.aux) : NULL;
event = NULL;
}
return *this;
}
int cTimer::Compare(const cListObject &ListObject) const
{
const cTimer *ti = (const cTimer *)&ListObject;
time_t t1 = StartTime();
time_t t2 = ti->StartTime();
int r = t1 - t2;
if (r == 0)
r = ti->priority - priority;
return r;
}
cString cTimer::ToText(bool UseChannelID) const
{
strreplace(file, ':', '|');
cString buffer = cString::sprintf("%u:%s:%s:%04d:%04d:%d:%d:%s:%s\n", flags, UseChannelID ? *Channel()->GetChannelID().ToString() : *itoa(Channel()->Number()), *PrintDay(day, weekdays, true), start, stop, priority, lifetime, file, aux ? aux : "");
strreplace(file, '|', ':');
return buffer;
}
cString cTimer::ToDescr(void) const
{
return cString::sprintf("%d (%d %04d-%04d %s'%s')", Index() + 1, Channel()->Number(), start, stop, HasFlags(tfVps) ? "VPS " : "", file);
}
int cTimer::TimeToInt(int t)
{
return (t / 100 * 60 + t % 100) * 60;
}
bool cTimer::ParseDay(const char *s, time_t &Day, int &WeekDays)
{
// possible formats are:
// 19
// 2005-03-19
// MTWTFSS
// MTWTFSS@19
// MTWTFSS@2005-03-19
Day = 0;
WeekDays = 0;
s = skipspace(s);
if (!*s)
return false;
const char *a = strchr(s, '@');
const char *d = a ? a + 1 : isdigit(*s) ? s : NULL;
if (d) {
if (strlen(d) == 10) {
struct tm tm_r;
if (3 == sscanf(d, "%d-%d-%d", &tm_r.tm_year, &tm_r.tm_mon, &tm_r.tm_mday)) {
tm_r.tm_year -= 1900;
tm_r.tm_mon--;
tm_r.tm_hour = tm_r.tm_min = tm_r.tm_sec = 0;
tm_r.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
Day = mktime(&tm_r);
}
else
return false;
}
else {
// handle "day of month" for compatibility with older versions:
char *tail = NULL;
int day = strtol(d, &tail, 10);
if (tail && *tail || day < 1 || day > 31)
return false;
time_t t = time(NULL);
int DaysToCheck = 61; // 61 to handle months with 31/30/31
for (int i = -1; i <= DaysToCheck; i++) {
time_t t0 = IncDay(t, i);
if (GetMDay(t0) == day) {
Day = SetTime(t0, 0);
break;
}
}
}
}
if (a || !isdigit(*s)) {
if ((a && a - s == 7) || strlen(s) == 7) {
for (const char *p = s + 6; p >= s; p--) {
WeekDays <<= 1;
WeekDays |= (*p != '-');
}
}
else
return false;
}
return true;
}
cString cTimer::PrintDay(time_t Day, int WeekDays, bool SingleByteChars)
{
#define DAYBUFFERSIZE 64
char buffer[DAYBUFFERSIZE];
char *b = buffer;
if (WeekDays) {
// TRANSLATORS: the first character of each weekday, beginning with monday
const char *w = trNOOP("MTWTFSS");
if (!SingleByteChars)
w = tr(w);
while (*w) {
int sl = Utf8CharLen(w);
if (WeekDays & 1) {
for (int i = 0; i < sl; i++)
b[i] = w[i];
b += sl;
}
else
*b++ = '-';
WeekDays >>= 1;
w += sl;
}
if (Day)
*b++ = '@';
}
if (Day) {
struct tm tm_r;
localtime_r(&Day, &tm_r);
b += strftime(b, DAYBUFFERSIZE - (b - buffer), "%Y-%m-%d", &tm_r);
}
*b = 0;
return buffer;
}
cString cTimer::PrintFirstDay(void) const
{
if (weekdays) {
cString s = PrintDay(day, weekdays, true);
if (strlen(s) == 18)
return *s + 8;
}
return ""; // not NULL, so the caller can always use the result
}
bool cTimer::Parse(const char *s)
{
char *channelbuffer = NULL;
char *daybuffer = NULL;
char *filebuffer = NULL;
free(aux);
aux = NULL;
//XXX Apparently sscanf() doesn't work correctly if the last %m argument
//XXX results in an empty string (this first occurred when the EIT gathering
//XXX was put into a separate thread - don't know why this happens...
//XXX As a cure we copy the original string and add a blank.
//XXX If anybody can shed some light on why sscanf() failes here, I'd love
//XXX to hear about that!
char *s2 = NULL;
int l2 = strlen(s);
while (l2 > 0 && isspace(s[l2 - 1]))
l2--;
if (s[l2 - 1] == ':') {
s2 = MALLOC(char, l2 + 3);
strcat(strn0cpy(s2, s, l2 + 1), " \n");
s = s2;
}
bool result = false;
if (8 <= sscanf(s, "%u :%m[^:]:%m[^:]:%d :%d :%d :%d :%m[^:\n]:%m[^\n]", &flags, &channelbuffer, &daybuffer, &start, &stop, &priority, &lifetime, &filebuffer, &aux)) {
ClrFlags(tfRecording);
if (aux && !*skipspace(aux)) {
free(aux);
aux = NULL;
}
//TODO add more plausibility checks
result = ParseDay(daybuffer, day, weekdays);
Utf8Strn0Cpy(file, filebuffer, sizeof(file));
strreplace(file, '|', ':');
if (isnumber(channelbuffer))
channel = Channels.GetByNumber(atoi(channelbuffer));
else
channel = Channels.GetByChannelID(tChannelID::FromString(channelbuffer), true, true);
if (!channel) {
esyslog("ERROR: channel %s not defined", channelbuffer);
result = false;
}
}
free(channelbuffer);
free(daybuffer);
free(filebuffer);
free(s2);
return result;
}
bool cTimer::Save(FILE *f)
{
return fprintf(f, "%s", *ToText(true)) > 0;
}
bool cTimer::IsSingleEvent(void) const
{
return !weekdays;
}
int cTimer::GetMDay(time_t t)
{
struct tm tm_r;
return localtime_r(&t, &tm_r)->tm_mday;
}
int cTimer::GetWDay(time_t t)
{
struct tm tm_r;
int weekday = localtime_r(&t, &tm_r)->tm_wday;
return weekday == 0 ? 6 : weekday - 1; // we start with Monday==0!
}
bool cTimer::DayMatches(time_t t) const
{
return IsSingleEvent() ? SetTime(t, 0) == day : (weekdays & (1 << GetWDay(t))) != 0;
}
time_t cTimer::IncDay(time_t t, int Days)
{
struct tm tm_r;
tm tm = *localtime_r(&t, &tm_r);
tm.tm_mday += Days; // now tm_mday may be out of its valid range
int h = tm.tm_hour; // save original hour to compensate for DST change
tm.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
t = mktime(&tm); // normalize all values
tm.tm_hour = h; // compensate for DST change
return mktime(&tm); // calculate final result
}
time_t cTimer::SetTime(time_t t, int SecondsFromMidnight)
{
struct tm tm_r;
tm tm = *localtime_r(&t, &tm_r);
tm.tm_hour = SecondsFromMidnight / 3600;
tm.tm_min = (SecondsFromMidnight % 3600) / 60;
tm.tm_sec = SecondsFromMidnight % 60;
tm.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
return mktime(&tm);
}
void cTimer::SetFile(const char *File)
{
if (!isempty(File))
Utf8Strn0Cpy(file, File, sizeof(file));
}
#define EITPRESENTFOLLOWINGRATE 10 // max. seconds between two occurrences of the "EIT present/following table for the actual multiplex" (2s by the standard, using some more for safety)
bool cTimer::Matches(time_t t, bool Directly, int Margin) const
{
startTime = stopTime = 0;
if (t == 0)
t = time(NULL);
int begin = TimeToInt(start); // seconds from midnight
int length = TimeToInt(stop) - begin;
if (length < 0)
length += SECSINDAY;
if (IsSingleEvent()) {
startTime = SetTime(day, begin);
stopTime = startTime + length;
}
else {
for (int i = -1; i <= 7; i++) {
time_t t0 = IncDay(day ? max(day, t) : t, i);
if (DayMatches(t0)) {
time_t a = SetTime(t0, begin);
time_t b = a + length;
if ((!day || a >= day) && t < b) {
startTime = a;
stopTime = b;
break;
}
}
}
if (!startTime)
startTime = IncDay(t, 7); // just to have something that's more than a week in the future
else if (!Directly && (t > startTime || t > day + SECSINDAY + 3600)) // +3600 in case of DST change
day = 0;
}
if (t < deferred)
return false;
deferred = 0;
if (HasFlags(tfActive)) {
if (HasFlags(tfVps) && event && event->Vps()) {
if (Margin || !Directly) {
startTime = event->StartTime();
stopTime = event->EndTime();
if (!Margin) { // this is an actual check
if (event->Schedule()->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) { // VPS control can only work with up-to-date events...
if (event->StartTime() > 0) // checks for "phased out" events
return event->IsRunning(true);
}
return startTime <= t && t < stopTime; // ...otherwise we fall back to normal timer handling
}
}
}
return startTime <= t + Margin && t < stopTime; // must stop *before* stopTime to allow adjacent timers
}
return false;
}
#define FULLMATCH 1000
eTimerMatch cTimer::Matches(const cEvent *Event, int *Overlap) const
{
// Overlap is the percentage of the Event's duration that is covered by
// this timer (based on FULLMATCH for finer granularity than just 100).
// To make sure a VPS timer can be distinguished from a plain 100% overlap,
// it gets an additional 100 added, and a VPS event that is actually running
// gets 200 added to the FULLMATCH.
if (HasFlags(tfActive) && channel->GetChannelID() == Event->ChannelID()) {
bool UseVps = HasFlags(tfVps) && Event->Vps();
Matches(UseVps ? Event->Vps() : Event->StartTime(), true);
int overlap = 0;
if (UseVps)
overlap = (startTime == Event->Vps()) ? FULLMATCH + (Event->IsRunning() ? 200 : 100) : 0;
if (!overlap) {
if (startTime <= Event->StartTime() && Event->EndTime() <= stopTime)
overlap = FULLMATCH;
else if (stopTime <= Event->StartTime() || Event->EndTime() <= startTime)
overlap = 0;
else
overlap = (min(stopTime, Event->EndTime()) - max(startTime, Event->StartTime())) * FULLMATCH / max(Event->Duration(), 1);
}
startTime = stopTime = 0;
if (Overlap)
*Overlap = overlap;
if (UseVps)
return overlap > FULLMATCH ? tmFull : tmNone;
return overlap >= FULLMATCH ? tmFull : overlap > 0 ? tmPartial : tmNone;
}
return tmNone;
}
#define EXPIRELATENCY 60 // seconds (just in case there's a short glitch in the VPS signal)
bool cTimer::Expired(void) const
{
return IsSingleEvent() && !Recording() && StopTime() + EXPIRELATENCY <= time(NULL) && (!HasFlags(tfVps) || !event || !event->Vps());
}
time_t cTimer::StartTime(void) const
{
if (!startTime)
Matches();
return startTime;
}
time_t cTimer::StopTime(void) const
{
if (!stopTime)
Matches();
return stopTime;
}
#define EPGLIMITBEFORE (1 * 3600) // Time in seconds before a timer's start time and
#define EPGLIMITAFTER (1 * 3600) // after its stop time within which EPG events will be taken into consideration.
void cTimer::SetEventFromSchedule(const cSchedules *Schedules)
{
cSchedulesLock SchedulesLock;
if (!Schedules) {
lastSetEvent = 0; // forces setting the event, even if the schedule hasn't been modified
if (!(Schedules = cSchedules::Schedules(SchedulesLock)))
return;
}
const cSchedule *Schedule = Schedules->GetSchedule(Channel());
if (Schedule && Schedule->Events()->First()) {
time_t now = time(NULL);
if (!lastSetEvent || Schedule->Modified() >= lastSetEvent) {
lastSetEvent = now;
const cEvent *Event = NULL;
if (HasFlags(tfVps) && Schedule->Events()->First()->Vps()) {
if (event && event->StartTime() > 0) { // checks for "phased out" events
if (Recording())
return; // let the recording end first
if (now <= event->EndTime() || Matches(0, true))
return; // stay with the old event until the timer has completely expired
}
// VPS timers only match if their start time exactly matches the event's VPS time:
for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
if (e->StartTime() && e->RunningStatus() != SI::RunningStatusNotRunning) { // skip outdated events
int overlap = 0;
Matches(e, &overlap);
if (overlap > FULLMATCH) {
Event = e;
break; // take the first matching event
}
}
}
}
else {
// Normal timers match the event they have the most overlap with:
int Overlap = 0;
// Set up the time frame within which to check events:
Matches(0, true);
time_t TimeFrameBegin = StartTime() - EPGLIMITBEFORE;
time_t TimeFrameEnd = StopTime() + EPGLIMITAFTER;
for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
if (e->EndTime() < TimeFrameBegin)
continue; // skip events way before the timer starts
if (e->StartTime() > TimeFrameEnd)
break; // the rest is way after the timer ends
int overlap = 0;
Matches(e, &overlap);
if (overlap && overlap >= Overlap) {
if (Event && overlap == Overlap && e->Duration() <= Event->Duration())
continue; // if overlap is the same, we take the longer event
Overlap = overlap;
Event = e;
}
}
}
SetEvent(Event);
}
}
}
void cTimer::SetEvent(const cEvent *Event)
{
if (event != Event) { //XXX TODO check event data, too???
if (Event)
isyslog("timer %s set to event %s", *ToDescr(), *Event->ToDescr());
else
isyslog("timer %s set to no event", *ToDescr());
event = Event;
}
}
void cTimer::SetRecording(bool Recording)
{
recording = Recording;
if (recording)
SetFlags(tfRecording);
else
ClrFlags(tfRecording);
isyslog("timer %s %s", *ToDescr(), recording ? "start" : "stop");
}
void cTimer::SetPending(bool Pending)
{
pending = Pending;
}
void cTimer::SetInVpsMargin(bool InVpsMargin)
{
if (InVpsMargin && !inVpsMargin)
isyslog("timer %s entered VPS margin", *ToDescr());
inVpsMargin = InVpsMargin;
}
void cTimer::SetDay(time_t Day)
{
day = Day;
}
void cTimer::SetWeekDays(int WeekDays)
{
weekdays = WeekDays;
}
void cTimer::SetStart(int Start)
{
start = Start;
}
void cTimer::SetStop(int Stop)
{
stop = Stop;
}
void cTimer::SetPriority(int Priority)
{
priority = Priority;
}
void cTimer::SetLifetime(int Lifetime)
{
lifetime = Lifetime;
}
void cTimer::SetAux(const char *Aux)
{
free(aux);
aux = strdup(Aux);
}
void cTimer::SetDeferred(int Seconds)
{
deferred = time(NULL) + Seconds;
isyslog("timer %s deferred for %d seconds", *ToDescr(), Seconds);
}
void cTimer::SetFlags(uint Flags)
{
flags |= Flags;
}
void cTimer::ClrFlags(uint Flags)
{
flags &= ~Flags;
}
void cTimer::InvFlags(uint Flags)
{
flags ^= Flags;
}
bool cTimer::HasFlags(uint Flags) const
{
return (flags & Flags) == Flags;
}
void cTimer::Skip(void)
{
day = IncDay(SetTime(StartTime(), 0), 1);
startTime = 0;
SetEvent(NULL);
}
void cTimer::OnOff(void)
{
if (IsSingleEvent())
InvFlags(tfActive);
else if (day) {
day = 0;
ClrFlags(tfActive);
}
else if (HasFlags(tfActive))
Skip();
else
SetFlags(tfActive);
SetEvent(NULL);
Matches(); // refresh start and end time
}
// --- cTimers ---------------------------------------------------------------
cTimers Timers;
cTimers::cTimers(void)
{
state = 0;
beingEdited = 0;;
lastSetEvents = 0;
lastDeleteExpired = 0;
}
cTimer *cTimers::GetTimer(cTimer *Timer)
{
for (cTimer *ti = First(); ti; ti = Next(ti)) {
if (ti->Channel() == Timer->Channel() &&
(ti->WeekDays() && ti->WeekDays() == Timer->WeekDays() || !ti->WeekDays() && ti->Day() == Timer->Day()) &&
ti->Start() == Timer->Start() &&
ti->Stop() == Timer->Stop())
return ti;
}
return NULL;
}
cTimer *cTimers::GetMatch(time_t t)
{
static int LastPending = -1;
cTimer *t0 = NULL;
for (cTimer *ti = First(); ti; ti = Next(ti)) {
if (!ti->Recording() && ti->Matches(t)) {
if (ti->Pending()) {
if (ti->Index() > LastPending) {
LastPending = ti->Index();
return ti;
}
else
continue;
}
if (!t0 || ti->Priority() > t0->Priority())
t0 = ti;
}
}
if (!t0)
LastPending = -1;
return t0;
}
cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match)
{
cTimer *t = NULL;
eTimerMatch m = tmNone;
for (cTimer *ti = First(); ti; ti = Next(ti)) {
eTimerMatch tm = ti->Matches(Event);
if (tm > m) {
t = ti;
m = tm;
if (m == tmFull)
break;
}
}
if (Match)
*Match = m;
return t;
}
cTimer *cTimers::GetNextActiveTimer(void)
{
cTimer *t0 = NULL;
for (cTimer *ti = First(); ti; ti = Next(ti)) {
ti->Matches();
if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
t0 = ti;
}
return t0;
}
void cTimers::SetModified(void)
{
cStatus::MsgTimerChange(NULL, tcMod);
state++;
}
void cTimers::Add(cTimer *Timer, cTimer *After)
{
cConfig<cTimer>::Add(Timer, After);
cStatus::MsgTimerChange(Timer, tcAdd);
}
void cTimers::Ins(cTimer *Timer, cTimer *Before)
{
cConfig<cTimer>::Ins(Timer, Before);
cStatus::MsgTimerChange(Timer, tcAdd);
}
void cTimers::Del(cTimer *Timer, bool DeleteObject)
{
cStatus::MsgTimerChange(Timer, tcDel);
cConfig<cTimer>::Del(Timer, DeleteObject);
}
bool cTimers::Modified(int &State)
{
bool Result = state != State;
State = state;
return Result;
}
void cTimers::SetEvents(void)
{
if (time(NULL) - lastSetEvents < 5)
return;
cSchedulesLock SchedulesLock(false, 100);
const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
if (Schedules) {
if (!lastSetEvents || Schedules->Modified() >= lastSetEvents) {
for (cTimer *ti = First(); ti; ti = Next(ti)) {
if (cRemote::HasKeys())
return; // react immediately on user input
ti->SetEventFromSchedule(Schedules);
}
}
}
lastSetEvents = time(NULL);
}
void cTimers::DeleteExpired(void)
{
if (time(NULL) - lastDeleteExpired < 30)
return;
cTimer *ti = First();
while (ti) {
cTimer *next = Next(ti);
if (ti->Expired()) {
isyslog("deleting timer %s", *ti->ToDescr());
Del(ti);
SetModified();
}
ti = next;
}
lastDeleteExpired = time(NULL);
}
// --- cSortedTimers ---------------------------------------------------------
static int CompareTimers(const void *a, const void *b)
{
return (*(const cTimer **)a)->Compare(**(const cTimer **)b);
}
cSortedTimers::cSortedTimers(void)
:cVector<const cTimer *>(Timers.Count())
{
for (const cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer))
Append(Timer);
Sort(CompareTimers);
}