mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-12-27 07:11:05 +01:00
Implemented strict locking of global lists
This commit is contained in:
298
vdr.c
298
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);
|
||||
|
||||
Reference in New Issue
Block a user