summaryrefslogtreecommitdiff
path: root/vdr.c
diff options
context:
space:
mode:
authorKlaus Schmidinger <vdr@tvdr.de>2015-09-01 11:14:27 +0200
committerKlaus Schmidinger <vdr@tvdr.de>2015-09-01 11:14:27 +0200
commit3cd5294d8a337ee5cd2ec894c9fbe04ad3a7690d (patch)
treeda57ce74189de9bfb27e1a747063c37cd62de501 /vdr.c
parent8a7bc6a0bbf60cae8b6391a630880aad5cba3363 (diff)
downloadvdr-3cd5294d8a337ee5cd2ec894c9fbe04ad3a7690d.tar.gz
vdr-3cd5294d8a337ee5cd2ec894c9fbe04ad3a7690d.tar.bz2
Implemented strict locking of global lists
Diffstat (limited to 'vdr.c')
-rw-r--r--vdr.c298
1 files changed, 176 insertions, 122 deletions
diff --git a/vdr.c b/vdr.c
index b4d3f91b..a09ec086 100644
--- a/vdr.c
+++ b/vdr.c
@@ -22,7 +22,7 @@
*
* The project's page is at http://www.tvdr.de
*
- * $Id: vdr.c 4.4 2015/05/21 13:58:33 kls Exp $
+ * $Id: vdr.c 4.5 2015/09/01 10:33:04 kls Exp $
*/
#include <getopt.h>
@@ -748,8 +748,8 @@ int main(int argc, char *argv[])
Sources.Load(AddDirectory(ConfigDirectory, "sources.conf"), true, true);
Diseqcs.Load(AddDirectory(ConfigDirectory, "diseqc.conf"), true, Setup.DiSEqC);
Scrs.Load(AddDirectory(ConfigDirectory, "scr.conf"), true);
- Channels.Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true);
- Timers.Load(AddDirectory(ConfigDirectory, "timers.conf"));
+ cChannels::Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true);
+ cTimers::Load(AddDirectory(ConfigDirectory, "timers.conf"));
Commands.Load(AddDirectory(ConfigDirectory, "commands.conf"));
RecordingCommands.Load(AddDirectory(ConfigDirectory, "reccmds.conf"));
SVDRPhosts.Load(AddDirectory(ConfigDirectory, "svdrphosts.conf"), true);
@@ -765,8 +765,7 @@ int main(int argc, char *argv[])
// Recordings:
- Recordings.Update();
- DeletedRecordings.Update();
+ cRecordings::Update();
// EPG data:
@@ -879,16 +878,20 @@ int main(int argc, char *argv[])
if (!CamSlots.WaitForAllCamSlotsReady(DEVICEREADYTIMEOUT))
dsyslog("not all CAM slots ready after %d seconds", DEVICEREADYTIMEOUT);
if (*Setup.InitialChannel) {
+ LOCK_CHANNELS_READ;
if (isnumber(Setup.InitialChannel)) { // for compatibility with old setup.conf files
- if (cChannel *Channel = Channels.GetByNumber(atoi(Setup.InitialChannel)))
+ if (const cChannel *Channel = Channels->GetByNumber(atoi(Setup.InitialChannel)))
Setup.InitialChannel = Channel->GetChannelID().ToString();
}
- if (cChannel *Channel = Channels.GetByChannelID(tChannelID::FromString(Setup.InitialChannel)))
+ if (const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(Setup.InitialChannel)))
Setup.CurrentChannel = Channel->Number();
}
if (Setup.InitialVolume >= 0)
Setup.CurrentVolume = Setup.InitialVolume;
- Channels.SwitchTo(Setup.CurrentChannel);
+ {
+ LOCK_CHANNELS_READ;
+ Channels->SwitchTo(Setup.CurrentChannel);
+ }
if (MuteAudio)
cDevice::PrimaryDevice()->ToggleMute();
else
@@ -936,13 +939,14 @@ int main(int argc, char *argv[])
static time_t lastTime = 0;
if (!cDevice::PrimaryDevice()->HasProgramme()) {
if (!CamMenuActive() && Now - lastTime > MINCHANNELWAIT) { // !CamMenuActive() to avoid interfering with the CAM if a CAM menu is open
- cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel());
+ LOCK_CHANNELS_READ;
+ const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel());
if (Channel && (Channel->Vpid() || Channel->Apid(0) || Channel->Dpid(0))) {
- if (cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(Channel->Number())) // try to switch to the original channel...
+ if (cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels->SwitchTo(Channel->Number())) // try to switch to the original channel...
;
else if (LastTimerChannel > 0) {
- Channel = Channels.GetByNumber(LastTimerChannel);
- if (Channel && cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(LastTimerChannel)) // ...or the one used by the last timer
+ Channel = Channels->GetByNumber(LastTimerChannel);
+ if (Channel && cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels->SwitchTo(LastTimerChannel)) // ...or the one used by the last timer
;
}
}
@@ -970,40 +974,59 @@ int main(int argc, char *argv[])
}
}
// Handle channel and timer modifications:
- if (!Channels.BeingEdited() && !Timers.BeingEdited()) {
- int modified = Channels.Modified();
- static time_t ChannelSaveTimeout = 0;
- static int TimerState = 0;
- // Channels and timers need to be stored in a consistent manner,
- // therefore if one of them is changed, we save both.
- if (modified == CHANNELSMOD_USER || Timers.Modified(TimerState))
- ChannelSaveTimeout = 1; // triggers an immediate save
- else if (modified && !ChannelSaveTimeout)
- ChannelSaveTimeout = Now + CHANNELSAVEDELTA;
- bool timeout = ChannelSaveTimeout == 1 || ChannelSaveTimeout && Now > ChannelSaveTimeout && !cRecordControls::Active();
- if ((modified || timeout) && Channels.Lock(false, 100)) {
- if (timeout) {
- Channels.Save();
- Timers.Save();
- ChannelSaveTimeout = 0;
+ {
+ // Channels and timers need to be stored in a consistent manner,
+ // therefore if one of them is changed, we save both.
+ static time_t ChannelSaveTimeout = 0;
+ static cStateKey TimersStateKey(true);
+ static cStateKey ChannelsStateKey(true);
+ static int ChannelsModifiedByUser = 0;
+ const cTimers *Timers = cTimers::GetTimersRead(TimersStateKey);
+ const cChannels *Channels = cChannels::GetChannelsRead(ChannelsStateKey);
+ if (ChannelSaveTimeout != 1) {
+ if (Channels) {
+ if (Channels->ModifiedByUser(ChannelsModifiedByUser))
+ ChannelSaveTimeout = 1; // triggers an immediate save
+ else if (!ChannelSaveTimeout)
+ ChannelSaveTimeout = Now + CHANNELSAVEDELTA;
+ }
+ if (Timers)
+ ChannelSaveTimeout = 1; // triggers an immediate save
+ }
+ if (ChannelSaveTimeout && Now > ChannelSaveTimeout && !cRecordControls::Active())
+ ChannelSaveTimeout = 1; // triggers an immediate save
+ if (Timers && Channels) {
+ Channels->Save();
+ Timers->Save();
+ ChannelSaveTimeout = 0;
+ }
+ if (Channels) {
+ for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
+ if (Channel->Modification(CHANNELMOD_RETUNE)) {
+ cRecordControls::ChannelDataModified(Channel);
+ if (Channel->Number() == cDevice::CurrentChannel() && cDevice::PrimaryDevice()->HasDecoder()) {
+ if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) {
+ if (cDevice::ActualDevice()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
+ isyslog("retuning due to modification of channel %d (%s)", Channel->Number(), Channel->Name());
+ Channels->SwitchTo(Channel->Number());
+ }
+ }
+ }
+ cStatus::MsgChannelChange(Channel);
+ }
}
- for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
- if (Channel->Modification(CHANNELMOD_RETUNE)) {
- cRecordControls::ChannelDataModified(Channel);
- if (Channel->Number() == cDevice::CurrentChannel() && cDevice::PrimaryDevice()->HasDecoder()) {
- if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) {
- if (cDevice::ActualDevice()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
- isyslog("retuning due to modification of channel %d (%s)", Channel->Number(), Channel->Name());
- Channels.SwitchTo(Channel->Number());
- }
- }
- }
- cStatus::MsgChannelChange(Channel);
- }
- }
- Channels.Unlock();
- }
- }
+ }
+ // State keys are removed in reverse order!
+ if (Channels)
+ ChannelsStateKey.Remove();
+ if (Timers)
+ TimersStateKey.Remove();
+ if (ChannelSaveTimeout == 1) {
+ // Only one of them was modified, so we reset the state keys to handle them both in the next turn:
+ ChannelsStateKey.Reset();
+ TimersStateKey.Reset();
+ }
+ }
// Channel display:
if (!EITScanner.Active() && cDevice::CurrentChannel() != LastChannel) {
if (!Menu)
@@ -1013,80 +1036,109 @@ int main(int argc, char *argv[])
}
if (Now - LastChannelChanged >= Setup.ZapTimeout && LastChannel != PreviousChannel[PreviousChannelIndex])
PreviousChannel[PreviousChannelIndex ^= 1] = LastChannel;
- // Timers and Recordings:
- if (!Timers.BeingEdited()) {
- // Assign events to timers:
- Timers.SetEvents();
- // Must do all following calls with the exact same time!
- // Process ongoing recordings:
- cRecordControls::Process(Now);
- // Start new recordings:
- cTimer *Timer = Timers.GetMatch(Now);
- if (Timer) {
- if (!cRecordControls::Start(Timer))
- Timer->SetPending(true);
- else
- LastTimerChannel = Timer->Channel()->Number();
- }
- // Make sure timers "see" their channel early enough:
- static time_t LastTimerCheck = 0;
- if (Now - LastTimerCheck > TIMERCHECKDELTA) { // don't do this too often
- InhibitEpgScan = false;
- for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
- bool InVpsMargin = false;
- bool NeedsTransponder = false;
- if (Timer->HasFlags(tfActive) && !Timer->Recording()) {
- if (Timer->HasFlags(tfVps)) {
- if (Timer->Matches(Now, true, Setup.VpsMargin)) {
- InVpsMargin = true;
- Timer->SetInVpsMargin(InVpsMargin);
- }
- else if (Timer->Event()) {
- InVpsMargin = Timer->Event()->StartTime() <= Now && Now < Timer->Event()->EndTime();
- NeedsTransponder = Timer->Event()->StartTime() - Now < VPSLOOKAHEADTIME * 3600 && !Timer->Event()->SeenWithin(VPSUPTODATETIME);
- }
- else {
- cSchedulesLock SchedulesLock;
- const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
- if (Schedules) {
- const cSchedule *Schedule = Schedules->GetSchedule(Timer->Channel());
- InVpsMargin = !Schedule; // we must make sure we have the schedule
- NeedsTransponder = Schedule && !Schedule->PresentSeenWithin(VPSUPTODATETIME);
- }
- }
- InhibitEpgScan |= InVpsMargin | NeedsTransponder;
- }
- else
- NeedsTransponder = Timer->Matches(Now, true, TIMERLOOKAHEADTIME);
- }
- if (NeedsTransponder || InVpsMargin) {
- // Find a device that provides the required transponder:
- cDevice *Device = cDevice::GetDeviceForTransponder(Timer->Channel(), MINPRIORITY);
- if (!Device && InVpsMargin)
- Device = cDevice::GetDeviceForTransponder(Timer->Channel(), LIVEPRIORITY);
- // Switch the device to the transponder:
- if (Device) {
- bool HadProgramme = cDevice::PrimaryDevice()->HasProgramme();
- if (!Device->IsTunedToTransponder(Timer->Channel())) {
- if (Device == cDevice::ActualDevice() && !Device->IsPrimaryDevice())
- cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode
- dsyslog("switching device %d to channel %d (%s)", Device->DeviceNumber() + 1, Timer->Channel()->Number(), Timer->Channel()->Name());
- if (Device->SwitchChannel(Timer->Channel(), false))
- Device->SetOccupied(TIMERDEVICETIMEOUT);
- }
- if (cDevice::PrimaryDevice()->HasDecoder() && HadProgramme && !cDevice::PrimaryDevice()->HasProgramme())
- Skins.QueueMessage(mtInfo, tr("Upcoming recording!")); // the previous SwitchChannel() has switched away the current live channel
- }
- }
- }
- LastTimerCheck = Now;
- }
- // Delete expired timers:
- Timers.DeleteExpired();
- }
- if (!Menu && Recordings.NeedsUpdate()) {
- Recordings.Update();
- DeletedRecordings.Update();
+ {
+ // Timers and Recordings:
+ bool TimersModified = false;
+ bool TriggerRemoteTimerPoll = false;
+ static cStateKey TimersStateKey(true);
+ if (cTimers::GetTimersRead(TimersStateKey)) {
+ TriggerRemoteTimerPoll = true;
+ TimersStateKey.Remove();
+ }
+ cTimers *Timers = cTimers::GetTimersWrite(TimersStateKey);
+ // Get remote timers:
+ TimersModified |= Timers->GetRemoteTimers();
+ // Assign events to timers:
+ static cStateKey SchedulesStateKey;
+ if (const cSchedules *Schedules = cSchedules::GetSchedulesRead(SchedulesStateKey))
+ TimersModified |= Timers->SetEvents(Schedules);
+ // Must do all following calls with the exact same time!
+ // Process ongoing recordings:
+ if (cRecordControls::Process(Timers, Now)) {
+ TimersModified = true;
+ TriggerRemoteTimerPoll = true;
+ }
+ // Must keep the lock on the schedules until after processing the record
+ // controls, in order to avoid short interrupts in case the current event
+ // is replaced by a new one (which some broadcasters do, instead of just
+ // modifying the current event's data):
+ if (SchedulesStateKey.InLock())
+ SchedulesStateKey.Remove();
+ // Start new recordings:
+ if (cTimer *Timer = Timers->GetMatch(Now)) {
+ if (!cRecordControls::Start(Timers, Timer))
+ Timer->SetPending(true);
+ else
+ LastTimerChannel = Timer->Channel()->Number();
+ TimersModified = true;
+ TriggerRemoteTimerPoll = true;
+ }
+ // Make sure timers "see" their channel early enough:
+ static time_t LastTimerCheck = 0;
+ if (Now - LastTimerCheck > TIMERCHECKDELTA) { // don't do this too often
+ InhibitEpgScan = false;
+ for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
+ if (Timer->Remote())
+ continue;
+ bool InVpsMargin = false;
+ bool NeedsTransponder = false;
+ if (Timer->HasFlags(tfActive) && !Timer->Recording()) {
+ if (Timer->HasFlags(tfVps)) {
+ if (Timer->Matches(Now, true, Setup.VpsMargin)) {
+ InVpsMargin = true;
+ Timer->SetInVpsMargin(InVpsMargin);
+ }
+ else if (Timer->Event()) {
+ InVpsMargin = Timer->Event()->StartTime() <= Now && Now < Timer->Event()->EndTime();
+ NeedsTransponder = Timer->Event()->StartTime() - Now < VPSLOOKAHEADTIME * 3600 && !Timer->Event()->SeenWithin(VPSUPTODATETIME);
+ }
+ else {
+ LOCK_SCHEDULES_READ;
+ const cSchedule *Schedule = Schedules->GetSchedule(Timer->Channel());
+ InVpsMargin = !Schedule; // we must make sure we have the schedule
+ NeedsTransponder = Schedule && !Schedule->PresentSeenWithin(VPSUPTODATETIME);
+ }
+ InhibitEpgScan |= InVpsMargin | NeedsTransponder;
+ }
+ else
+ NeedsTransponder = Timer->Matches(Now, true, TIMERLOOKAHEADTIME);
+ }
+ if (NeedsTransponder || InVpsMargin) {
+ // Find a device that provides the required transponder:
+ cDevice *Device = cDevice::GetDeviceForTransponder(Timer->Channel(), MINPRIORITY);
+ if (!Device && InVpsMargin)
+ Device = cDevice::GetDeviceForTransponder(Timer->Channel(), LIVEPRIORITY);
+ // Switch the device to the transponder:
+ if (Device) {
+ bool HadProgramme = cDevice::PrimaryDevice()->HasProgramme();
+ if (!Device->IsTunedToTransponder(Timer->Channel())) {
+ if (Device == cDevice::ActualDevice() && !Device->IsPrimaryDevice())
+ cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode
+ dsyslog("switching device %d to channel %d (%s)", Device->DeviceNumber() + 1, Timer->Channel()->Number(), Timer->Channel()->Name());
+ if (Device->SwitchChannel(Timer->Channel(), false))
+ Device->SetOccupied(TIMERDEVICETIMEOUT);
+ }
+ if (cDevice::PrimaryDevice()->HasDecoder() && HadProgramme && !cDevice::PrimaryDevice()->HasProgramme())
+ Skins.QueueMessage(mtInfo, tr("Upcoming recording!")); // the previous SwitchChannel() has switched away the current live channel
+ }
+ }
+ }
+ LastTimerCheck = Now;
+ }
+ // Delete expired timers:
+ if (Timers->DeleteExpired()) {
+ TimersModified = true;
+ TriggerRemoteTimerPoll = true;
+ }
+ // Trigger remote timer polls:
+ if (TriggerRemoteTimerPoll)
+ Timers->TriggerRemoteTimerPoll();
+ TimersStateKey.Remove(TimersModified);
+ }
+ // Recordings:
+ if (!Menu) {
+ if (cRecordings::NeedsUpdate())
+ cRecordings::Update();
}
// CAM control:
if (!Menu && !cOsd::IsOpen())
@@ -1359,7 +1411,8 @@ int main(int argc, char *argv[])
case k0: {
if (PreviousChannel[PreviousChannelIndex ^ 1] == LastChannel || LastChannel != PreviousChannel[0] && LastChannel != PreviousChannel[1])
PreviousChannelIndex ^= 1;
- Channels.SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]);
+ LOCK_CHANNELS_READ;
+ Channels->SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]);
break;
}
// Direct Channel Select:
@@ -1447,7 +1500,7 @@ int main(int argc, char *argv[])
// Disk housekeeping:
RemoveDeletedRecordings();
- ClearVanishedRecordings();
+ ListGarbageCollector.Purge();
cSchedules::Cleanup();
// Plugins housekeeping:
PluginManager.Housekeeping();
@@ -1492,8 +1545,9 @@ Exit:
cPositioner::DestroyPositioner();
cVideoDirectory::Destroy();
EpgHandlers.Clear();
- PluginManager.Shutdown(true);
cSchedules::Cleanup(true);
+ ListGarbageCollector.Purge(true);
+ PluginManager.Shutdown(true);
ReportEpgBugFixStats(true);
if (WatchdogTimeout > 0)
dsyslog("max. latency time %d seconds", MaxLatencyTime);