Implemented strict locking of global lists

This commit is contained in:
Klaus Schmidinger
2015-09-01 11:14:27 +02:00
parent 8a7bc6a0bb
commit 3cd5294d8a
41 changed files with 3512 additions and 2402 deletions

326
timers.c
View File

@@ -4,18 +4,18 @@
* 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 $
* $Id: timers.c 4.1 2015/08/31 10:45:13 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"
#include "svdrp.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
@@ -23,19 +23,21 @@
// --- cTimer ----------------------------------------------------------------
cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
cTimer::cTimer(bool Instant, bool Pause, const cChannel *Channel)
{
startTime = stopTime = 0;
lastSetEvent = 0;
scheduleState = -1;
deferred = 0;
recording = pending = inVpsMargin = false;
pending = inVpsMargin = false;
flags = tfNone;
*file = 0;
aux = NULL;
remote = NULL;
event = NULL;
if (Instant)
SetFlags(tfActive | tfInstant);
channel = Channel ? Channel : Channels.GetByNumber(cDevice::CurrentChannel());
LOCK_CHANNELS_READ;
channel = Channel ? Channel : Channels->GetByNumber(cDevice::CurrentChannel());
time_t t = time(NULL);
struct tm tm_r;
struct tm *now = localtime_r(&t, &tm_r);
@@ -44,27 +46,25 @@ cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
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);
LOCK_SCHEDULES_READ;
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);
}
}
}
@@ -83,16 +83,18 @@ cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
cTimer::cTimer(const cEvent *Event)
{
startTime = stopTime = 0;
lastSetEvent = 0;
scheduleState = -1;
deferred = 0;
recording = pending = inVpsMargin = false;
pending = inVpsMargin = false;
flags = tfActive;
*file = 0;
aux = NULL;
remote = NULL;
event = NULL;
if (Event->Vps() && Setup.UseVps)
SetFlags(tfVps);
channel = Channels.GetByChannelID(Event->ChannelID(), true);
LOCK_CHANNELS_READ;
channel = Channels->GetByChannelID(Event->ChannelID(), true);
time_t tstart = (flags & tfVps) ? Event->Vps() : Event->StartTime();
time_t tstop = tstart + Event->Duration();
if (!(HasFlags(tfVps))) {
@@ -120,6 +122,7 @@ cTimer::cTimer(const cTimer &Timer)
{
channel = NULL;
aux = NULL;
remote = NULL;
event = NULL;
flags = tfNone;
*this = Timer;
@@ -127,7 +130,10 @@ cTimer::cTimer(const cTimer &Timer)
cTimer::~cTimer()
{
if (event)
event->DecNumTimers();
free(aux);
free(remote);
}
cTimer& cTimer::operator= (const cTimer &Timer)
@@ -136,9 +142,8 @@ cTimer& cTimer::operator= (const cTimer &Timer)
uint OldFlags = flags & tfRecording;
startTime = Timer.startTime;
stopTime = Timer.stopTime;
lastSetEvent = 0;
deferred = 0;
recording = Timer.recording;
scheduleState = -1;
deferred = 0;
pending = Timer.pending;
inVpsMargin = Timer.inVpsMargin;
flags = Timer.flags | OldFlags;
@@ -152,7 +157,13 @@ cTimer& cTimer::operator= (const cTimer &Timer)
strncpy(file, Timer.file, sizeof(file));
free(aux);
aux = Timer.aux ? strdup(Timer.aux) : NULL;
event = NULL;
free(remote);
remote = Timer.remote ? strdup(Timer.remote) : NULL;
if (event)
event->DecNumTimers();
event = Timer.event;
if (event)
event->IncNumTimers();
}
return *this;
}
@@ -178,7 +189,7 @@ cString cTimer::ToText(bool UseChannelID) const
cString cTimer::ToDescr(void) const
{
return cString::sprintf("%d (%d %04d-%04d %s'%s')", Index() + 1, Channel()->Number(), start, stop, HasFlags(tfVps) ? "VPS " : "", file);
return cString::sprintf("%d%s%s (%d %04d-%04d %s'%s')", Index() + 1, remote ? "@" : "", remote ? remote : "", Channel()->Number(), start, stop, HasFlags(tfVps) ? "VPS " : "", file);
}
int cTimer::TimeToInt(int t)
@@ -313,7 +324,6 @@ bool cTimer::Parse(const char *s)
}
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;
@@ -322,10 +332,11 @@ bool cTimer::Parse(const char *s)
result = ParseDay(daybuffer, day, weekdays);
Utf8Strn0Cpy(file, filebuffer, sizeof(file));
strreplace(file, '|', ':');
LOCK_CHANNELS_READ;
if (isnumber(channelbuffer))
channel = Channels.GetByNumber(atoi(channelbuffer));
channel = Channels->GetByNumber(atoi(channelbuffer));
else
channel = Channels.GetByChannelID(tChannelID::FromString(channelbuffer), true, true);
channel = Channels->GetByChannelID(tChannelID::FromString(channelbuffer), true, true);
if (!channel) {
esyslog("ERROR: channel %s not defined", channelbuffer);
result = false;
@@ -340,7 +351,9 @@ bool cTimer::Parse(const char *s)
bool cTimer::Save(FILE *f)
{
return fprintf(f, "%s", *ToText(true)) > 0;
if (!Remote())
return fprintf(f, "%s", *ToText(true)) > 0;
return true;
}
bool cTimer::IsSingleEvent(void) const
@@ -441,10 +454,8 @@ bool cTimer::Matches(time_t t, bool Directly, int Margin) const
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);
}
if (event->Schedule()->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) // VPS control can only work with up-to-date events...
return event->IsRunning(true);
return startTime <= t && t < stopTime; // ...otherwise we fall back to normal timer handling
}
}
@@ -511,27 +522,13 @@ time_t cTimer::StopTime(void) const
#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)
bool 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;
if (Schedule->Modified(scheduleState)) {
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
@@ -566,30 +563,39 @@ void cTimer::SetEventFromSchedule(const cSchedules *Schedules)
}
}
}
SetEvent(Event);
return SetEvent(Event);
}
}
return false;
}
void cTimer::SetEvent(const cEvent *Event)
bool cTimer::SetEvent(const cEvent *Event)
{
if (event != Event) { //XXX TODO check event data, too???
if (Event)
if (event != Event) {
if (event)
event->DecNumTimers();
if (Event) {
isyslog("timer %s set to event %s", *ToDescr(), *Event->ToDescr());
else
Event->IncNumTimers();
Event->Schedule()->Modified(scheduleState); // to get the current state
}
else {
isyslog("timer %s set to no event", *ToDescr());
scheduleState = -1;
}
event = Event;
return true;
}
return false;
}
void cTimer::SetRecording(bool Recording)
{
recording = Recording;
if (recording)
if (Recording)
SetFlags(tfRecording);
else
ClrFlags(tfRecording);
isyslog("timer %s %s", *ToDescr(), recording ? "start" : "stop");
isyslog("timer %s %s", *ToDescr(), Recording ? "start" : "stop");
}
void cTimer::SetPending(bool Pending)
@@ -637,7 +643,13 @@ void cTimer::SetLifetime(int Lifetime)
void cTimer::SetAux(const char *Aux)
{
free(aux);
aux = strdup(Aux);
aux = Aux ? strdup(Aux) : NULL;
}
void cTimer::SetRemote(const char *Remote)
{
free(remote);
remote = Remote ? strdup(Remote) : NULL;
}
void cTimer::SetDeferred(int Seconds)
@@ -691,20 +703,33 @@ void cTimer::OnOff(void)
// --- cTimers ---------------------------------------------------------------
cTimers Timers;
cTimers cTimers::timers;
cTimers::cTimers(void)
:cConfig<cTimer>("Timers")
{
state = 0;
beingEdited = 0;;
lastSetEvents = 0;
lastDeleteExpired = 0;
}
bool cTimers::Load(const char *FileName)
{
LOCK_TIMERS_WRITE;
Timers->SetExplicitModify();
if (timers.cConfig<cTimer>::Load(FileName)) {
for (cTimer *ti = timers.First(); ti; ti = timers.Next(ti)) {
ti->ClrFlags(tfRecording);
Timers->SetModified();
}
return true;
}
return false;
}
cTimer *cTimers::GetTimer(cTimer *Timer)
{
for (cTimer *ti = First(); ti; ti = Next(ti)) {
if (ti->Channel() == Timer->Channel() &&
if (!ti->Remote() &&
ti->Channel() == Timer->Channel() &&
(ti->WeekDays() && ti->WeekDays() == Timer->WeekDays() || !ti->WeekDays() && ti->Day() == Timer->Day()) &&
ti->Start() == Timer->Start() &&
ti->Stop() == Timer->Stop())
@@ -713,12 +738,12 @@ cTimer *cTimers::GetTimer(cTimer *Timer)
return NULL;
}
cTimer *cTimers::GetMatch(time_t t)
const cTimer *cTimers::GetMatch(time_t t) const
{
static int LastPending = -1;
cTimer *t0 = NULL;
for (cTimer *ti = First(); ti; ti = Next(ti)) {
if (!ti->Recording() && ti->Matches(t)) {
const cTimer *t0 = NULL;
for (const cTimer *ti = First(); ti; ti = Next(ti)) {
if (!ti->Remote() && !ti->Recording() && ti->Matches(t)) {
if (ti->Pending()) {
if (ti->Index() > LastPending) {
LastPending = ti->Index();
@@ -736,11 +761,11 @@ cTimer *cTimers::GetMatch(time_t t)
return t0;
}
cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match)
const cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match) const
{
cTimer *t = NULL;
const cTimer *t = NULL;
eTimerMatch m = tmNone;
for (cTimer *ti = First(); ti; ti = Next(ti)) {
for (const cTimer *ti = First(); ti; ti = Next(ti)) {
eTimerMatch tm = ti->Matches(Event);
if (tm > m) {
t = ti;
@@ -754,21 +779,27 @@ cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match)
return t;
}
cTimer *cTimers::GetNextActiveTimer(void)
const cTimer *cTimers::GetNextActiveTimer(void) const
{
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;
const cTimer *t0 = NULL;
for (const cTimer *ti = First(); ti; ti = Next(ti)) {
if (!ti->Remote()) {
ti->Matches();
if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
t0 = ti;
}
}
return t0;
}
void cTimers::SetModified(void)
const cTimers *cTimers::GetTimersRead(cStateKey &StateKey, int TimeoutMs)
{
cStatus::MsgTimerChange(NULL, tcMod);
state++;
return timers.Lock(StateKey, false, TimeoutMs) ? &timers : NULL;
}
cTimers *cTimers::GetTimersWrite(cStateKey &StateKey, int TimeoutMs)
{
return timers.Lock(StateKey, true, TimeoutMs) ? &timers : NULL;
}
void cTimers::Add(cTimer *Timer, cTimer *After)
@@ -789,46 +820,113 @@ void cTimers::Del(cTimer *Timer, bool DeleteObject)
cConfig<cTimer>::Del(Timer, DeleteObject);
}
bool cTimers::Modified(int &State)
const cTimer *cTimers::UsesChannel(const cChannel *Channel) const
{
bool Result = state != State;
State = state;
return Result;
for (const cTimer *Timer = First(); Timer; Timer = Next(Timer)) {
if (Timer->Channel() == Channel)
return Timer;
}
return NULL;
}
void cTimers::SetEvents(void)
bool cTimers::SetEvents(const cSchedules *Schedules)
{
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);
bool TimersModified = false;
for (cTimer *ti = First(); ti; ti = Next(ti))
TimersModified |= ti->SetEventFromSchedule(Schedules);
return TimersModified;
}
void cTimers::DeleteExpired(void)
bool cTimers::DeleteExpired(void)
{
if (time(NULL) - lastDeleteExpired < 30)
return;
return false;
bool TimersModified = false;
cTimer *ti = First();
while (ti) {
cTimer *next = Next(ti);
if (ti->Expired()) {
if (!ti->Remote() && ti->Expired()) {
isyslog("deleting timer %s", *ti->ToDescr());
Del(ti);
SetModified();
TimersModified = true;
}
ti = next;
}
lastDeleteExpired = time(NULL);
return TimersModified;
}
bool cTimers::GetRemoteTimers(const char *ServerName)
{
bool Result = false;
if (ServerName) {
Result = DelRemoteTimers(ServerName);
cSVDRPCommand Cmd(ServerName, "LSTT ID");
if (Cmd.Execute()) {
const char *s;
for (int i = 0; s = Cmd.Response(i); i++) {
int Code = Cmd.Code(s);
if (Code == 250) {
if (const char *v = Cmd.Value(s)) {
while (*v && *v != ' ')
v++; // skip number
cTimer *Timer = new cTimer;
if (Timer->Parse(v)) {
Timer->SetRemote(ServerName);
Add(Timer);
Result = true;
}
else {
esyslog("ERROR: %s: error in timer settings: %s", ServerName, v);
delete Timer;
}
}
}
else if (Code != 550)
esyslog("ERROR: %s: %s", ServerName, s);
}
return Result;
}
}
else {
cStringList ServerNames;
if (GetSVDRPServerNames(&ServerNames, sffTimers)) {
for (int i = 0; i < ServerNames.Size(); i++)
Result |= GetRemoteTimers(ServerNames[i]);
}
}
return Result;
}
bool cTimers::DelRemoteTimers(const char *ServerName)
{
bool Deleted = false;
cTimer *Timer = First();
while (Timer) {
cTimer *t = Next(Timer);
if (Timer->Remote() && (!ServerName || strcmp(Timer->Remote(), ServerName) == 0)) {
Del(Timer);
Deleted = true;
}
Timer = t;
}
return Deleted;
}
void cTimers::TriggerRemoteTimerPoll(const char *ServerName)
{
if (ServerName) {
cSVDRPCommand Cmd(ServerName, cString::sprintf("POLL %s TIMERS", SVDRPHostName()));
if (!Cmd.Execute())
esyslog("ERROR: can't send 'POLL %s TIMERS' to '%s'", SVDRPHostName(), ServerName);
}
else {
cStringList ServerNames;
if (GetSVDRPServerNames(&ServerNames)) {
for (int i = 0; i < ServerNames.Size(); i++)
TriggerRemoteTimerPoll(ServerNames[i]);
}
}
}
// --- cSortedTimers ---------------------------------------------------------
@@ -838,10 +936,10 @@ 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())
cSortedTimers::cSortedTimers(const cTimers *Timers)
:cVector<const cTimer *>(Timers->Count())
{
for (const cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer))
for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer))
Append(Timer);
Sort(CompareTimers);
}