diff options
author | Klaus Schmidinger <vdr@tvdr.de> | 2015-09-01 11:14:27 +0200 |
---|---|---|
committer | Klaus Schmidinger <vdr@tvdr.de> | 2015-09-01 11:14:27 +0200 |
commit | 3cd5294d8a337ee5cd2ec894c9fbe04ad3a7690d (patch) | |
tree | da57ce74189de9bfb27e1a747063c37cd62de501 /vdr.c | |
parent | 8a7bc6a0bbf60cae8b6391a630880aad5cba3363 (diff) | |
download | vdr-3cd5294d8a337ee5cd2ec894c9fbe04ad3a7690d.tar.gz vdr-3cd5294d8a337ee5cd2ec894c9fbe04ad3a7690d.tar.bz2 |
Implemented strict locking of global lists
Diffstat (limited to 'vdr.c')
-rw-r--r-- | vdr.c | 298 |
1 files changed, 176 insertions, 122 deletions
@@ -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); |