From 3cd5294d8a337ee5cd2ec894c9fbe04ad3a7690d Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Tue, 1 Sep 2015 11:14:27 +0200 Subject: [PATCH] Implemented strict locking of global lists --- HISTORY | 113 +++- channels.c | 335 ++++++------ channels.h | 115 +++-- ci.c | 5 +- config.h | 6 +- cutter.c | 9 +- device.c | 24 +- dvbplayer.c | 27 +- dvbplayer.h | 4 +- eit.c | 119 +++-- eit.h | 10 +- eitscan.c | 18 +- epg.c | 270 +++++----- epg.h | 72 +-- filter.c | 41 +- filter.h | 13 +- menu.c | 1288 ++++++++++++++++++++++++++-------------------- menu.h | 21 +- menuitems.c | 46 +- nit.c | 43 +- pat.c | 23 +- recorder.c | 5 +- recording.c | 457 ++++++++--------- recording.h | 105 ++-- sdt.c | 52 +- shutdown.c | 29 +- skinlcars.c | 67 +-- sourceparams.c | 4 +- sourceparams.h | 4 +- status.h | 9 +- svdrp.c | 1341 +++++++++++++++++++++++++----------------------- svdrp.h | 21 +- thread.c | 138 ++++- thread.h | 90 +++- timers.c | 326 ++++++++---- timers.h | 119 ++++- tools.c | 90 +++- tools.h | 137 ++++- vdr.c | 298 ++++++----- videodir.c | 17 +- videodir.h | 3 +- 41 files changed, 3512 insertions(+), 2402 deletions(-) diff --git a/HISTORY b/HISTORY index e65f99d7..cae45f3b 100644 --- a/HISTORY +++ b/HISTORY @@ -8596,7 +8596,7 @@ Video Disk Recorder Revision History - Bumped all version numbers to 2.2.0. - Official release. -2015-04-29: Version 2.3.1 +2015-09-01: Version 2.3.1 - The new function cOsd::MaxPixmapSize() can be called to determine the maximum size a cPixmap may have on the current OSD. The 'osddemo' example has been modified @@ -8671,3 +8671,114 @@ Video Disk Recorder Revision History this VDR is connected to via SVDRP. - The new class cSVDRPCommand can be used to execute an SVDRP command on one of the servers this VDR is connected to, and retrieve the result. +- The cTimer class now has a new member named 'remote', which holds the name of the + remote server this timer will record on. If this is NULL, it is a local timer. +- Timers from other VDRs that are connected to this VDR via SVDRP are now + automatically fetched and stored in the global Timers list. In order for this + to work, all of the channels used by timers on the remote VDR must also be + defined on the local VDR (however, not necessarily in the same sequence). + Automatic channel syncing will be implemented later. +- The main menu of the LCARS skin now displays a small rectangle on the left side + of a timer if this is a remote timer. The color of that rectangle changes if + the timer is currently recording on the remote VDR. +- Accessing the global Timers list now has to be protected by proper locking, + because SVDRP commands are now executed in a separate thread. + The introduction of this locking mechanism required the following changes: + + The new classes cStateLock and cStateKey are used to implement locking + with quick detection of state changes. + + cConfig::cConfig() now has a parameter that indicates whether this list + requires locking. + + The global lists of Timers, Channels, Schedules and Recordings are no longer + static variables. They are now pointers that need to be retrieved through + a call to cTimers::GetTimersRead/Write(), cChannels::GetChannelsRead/Write(), + cSchedules::GetSchedulesRead/Write() and cRecordings::GetRecordingsRead/Write(), + respectively. + + References from/to link channels are now removed in cChannels::Del() rather + than cChannel::~cChannel(), to make sure the caller holds a proper lock. + + cChannel::HasTimer() has been removed. This information is now retrieved + via cSchedule::HasTimer(). + + Several member functions of cChannel, cTimer, cMarks and cRecording have + been made 'const', and some of them are now available as both 'const' and + 'non-const' versions. + + The cChannel::Set...() functions are now 'bool' and return true if they have + actually changed any of the channels's members. + + cChannels::SetModified() has been renamed to cChannels::SetModifiedByUser(). + + cChannels::Modified() has been renamed to cChannels::ModifiedByUser(), and + now has a 'State' parameter that allows the caller to see whether a channel + has been modified since the last call to this function with the same State + variable. + + The macros CHANNELSMOD_NONE/_AUTO/_USER have been removed. + + cMarks now requires locking via cStateKey. + + cSortedTimers now requires a pointer to the list of timers. + + cEvent::HasTimer() no longer scans the list of timers to check whether an event + is referenced by a timer, but rather keeps score of how many timers reference + it. This was necessary in order to avoid having to lock the list of timers from + within a cEvent. + + The new class cListGarbageCollector is used to temporary store any objects deleted + from cLists that require locking. This allows pointers to such objects to be + dereferenced even if the objects are no longer part of the list. + + cListBase::Contains() can be used to check whether a particular object is still + contained in that list. + + Outdated events are no longer "phased out", but rather deleted right away and thus + taken care of by the new "garbage collector" of the list. + + Deleted cRecording objects are no longer kept in a list of "vanished" recordings, + but are rather taken care of by the new "garbage collector" of the list. + + cSchedules::ClearAll() has been removed. The functionality is now implemented + directly in cSVDRPServer::CmdCLRE(). + + tEventID has been changed to u_int16_t in order to make room for the new member + numTimers in cEvent. + + cSchedule now has a member Modified(), which can be used with a State variable + to quickly determine whether this schedule has been modified since the last call + to this function with the same State variable. + + cSchedulesLock has been removed. Locking the list of schedules is now done via + the cList's new locking mechanism. + + The 'OnlyRunningStatus' parameters in cEpgHandler::BeginSegmentTransfer() and + cEpgHandler::EndSegmentTransfer() are now obsolete. They are still present in + the interface for backward compatibility, but may be removed in a future version. + Their value is always 'false'. + + The constant tcMod is no longer used in cStatus::TimerChange(). The definition is + still there for backward compatibility. + Plugins that access the global lists of Timers, Channels, Recordings or Schedules + will need to be adapted as follows: + + Instead of directly accessing the global variables Timers, Channels or Recordings, + they need to set up a cStateKey variable and call the proper getter function, + as in + cStateKey StateKey; + if (const cTimers *Timers = cTimers::GetTimersRead(StateKey)) { + // access the timers + StateKey.Remove(); + } + and + cStateKey StateKey; + if (cTimers *Timers = cTimers::GetTimersWrite(StateKey)) { + // access the timers + StateKey.Remove(); + } + See timers.h, thread.h and tools.h for details on this new locking mechanism. + + There are convenience macros for easily accessing these lists without having + to explicitly set up a cStateKey and calling its Remove() function. These macros + have the form LOCK_*_READ/WRITE (with '*' being TIMERS, CHANNELS, SCHEDULES or + RECORDINGS). Simply put such a macro before the point where you need to access + the respective list, and there will be a pointer named Timers, Channels, Schedules + or Recordings, respectively, which is valid until the end of the current block. + + If a plugin needs to access several of the global lists in parallel, locking must + always be done in the sequence Timers, Channels, Recordings, Schedules. This is + necessary to make sure that different threads that need to lock several lists at + the same time don't end up in a deadlock. + + Some pointer variables may need to be made 'const'. The compiler will tell you + about these. +- cSectionSyncer has been improved to better handle missed sections. +- Added a missing initialization of 'seen' in cChannel's copy constructor. +- Background modifications of channels, timers and events are now displayed immediately + in the corresponding menus. +- cEIT now checks the version of the tables before doing any processing, which saves + a lot of locking and processing. +- If a timer is newly created with the Red button in the Schedule menu, and the timer + is presented to the user in the "Edit timer" menu because it will start immediately, + it now *must* be confirmed with "Ok" to set the timer. Otherwise the timer will not + be created. +- Recordings and deleted recordings are now scanned in a single thread. +- The new SVDRP command POLL is used by automatically established peer-to-peer + connections to trigger fetching remote timers. +- You can now set DumpSVDRPDataTransfer in svdrp.c to true to have all SVDRP + communication printed to the console for debugging. diff --git a/channels.c b/channels.c index f8b9e920..005f28c9 100644 --- a/channels.c +++ b/channels.c @@ -4,15 +4,13 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: channels.c 4.1 2015/03/13 11:34:28 kls Exp $ + * $Id: channels.c 4.2 2015/08/29 12:16:24 kls Exp $ */ #include "channels.h" #include #include "device.h" -#include "epg.h" #include "libsi/si.h" -#include "timers.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 @@ -79,27 +77,13 @@ cChannel::cChannel(const cChannel &Channel) schedule = NULL; linkChannels = NULL; refChannel = NULL; + seen = 0; *this = Channel; } cChannel::~cChannel() { - delete linkChannels; - linkChannels = NULL; // more than one channel can link to this one, so we need the following loop - for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) { - if (Channel->linkChannels) { - for (cLinkChannel *lc = Channel->linkChannels->First(); lc; lc = Channel->linkChannels->Next(lc)) { - if (lc->Channel() == this) { - Channel->linkChannels->Del(lc); - break; - } - } - if (Channel->linkChannels->Count() == 0) { - delete Channel->linkChannels; - Channel->linkChannels = NULL; - } - } - } + delete linkChannels; // any links from other channels pointing to this one have been deleted in cChannels::Del() free(name); free(shortName); free(provider); @@ -167,16 +151,7 @@ int cChannel::Transponder(void) const return tf; } -bool cChannel::HasTimer(void) const -{ - for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) { - if (Timer->Channel() == this) - return true; - } - return false; -} - -int cChannel::Modification(int Mask) +int cChannel::Modification(int Mask) const { int Result = modification & Mask; modification = CHANNELMOD_NONE; @@ -223,53 +198,57 @@ bool cChannel::SetTransponderData(int Source, int Frequency, int Srate, const ch if (Number() && !Quiet) { dsyslog("changing transponder data of channel %d (%s) from %s to %s", Number(), name, *OldTransponderData, *TransponderDataToString()); modification |= CHANNELMOD_TRANSP; - Channels.SetModified(); } + return true; } - return true; + return false; } -void cChannel::SetSource(int Source) +bool cChannel::SetSource(int Source) { if (source != Source) { if (Number()) { dsyslog("changing source of channel %d (%s) from %s to %s", Number(), name, *cSource::ToString(source), *cSource::ToString(Source)); modification |= CHANNELMOD_TRANSP; - Channels.SetModified(); } source = Source; + return true; } + return false; } -void cChannel::SetId(int Nid, int Tid, int Sid, int Rid) +bool cChannel::SetId(cChannels *Channels, int Nid, int Tid, int Sid, int Rid) { if (nid != Nid || tid != Tid || sid != Sid || rid != Rid) { - if (Number()) { + if (Channels && Number()) { dsyslog("changing id of channel %d (%s) from %d-%d-%d-%d to %d-%d-%d-%d", Number(), name, nid, tid, sid, rid, Nid, Tid, Sid, Rid); modification |= CHANNELMOD_ID; - Channels.SetModified(); - Channels.UnhashChannel(this); + Channels->UnhashChannel(this); } nid = Nid; tid = Tid; sid = Sid; rid = Rid; - if (Number()) - Channels.HashChannel(this); + if (Channels) + Channels->HashChannel(this); schedule = NULL; + return true; } + return false; } -void cChannel::SetLcn(int Lcn) +bool cChannel::SetLcn(int Lcn) { if (lcn != Lcn) { if (Number()) dsyslog("changing lcn of channel %d (%s) from %d to %d\n", Number(), name, lcn, Lcn); lcn = Lcn; + return true; } + return false; } -void cChannel::SetName(const char *Name, const char *ShortName, const char *Provider) +bool cChannel::SetName(const char *Name, const char *ShortName, const char *Provider) { if (!isempty(Name)) { bool nn = strcmp(name, Name) != 0; @@ -279,7 +258,6 @@ void cChannel::SetName(const char *Name, const char *ShortName, const char *Prov if (Number()) { dsyslog("changing name of channel %d from '%s,%s;%s' to '%s,%s;%s'", Number(), name, shortName, provider, Name, ShortName, Provider); modification |= CHANNELMOD_NAME; - Channels.SetModified(); } if (nn) { name = strcpyrealloc(name, Name); @@ -291,20 +269,23 @@ void cChannel::SetName(const char *Name, const char *ShortName, const char *Prov } if (np) provider = strcpyrealloc(provider, Provider); + return true; } } + return false; } -void cChannel::SetPortalName(const char *PortalName) +bool cChannel::SetPortalName(const char *PortalName) { if (!isempty(PortalName) && strcmp(portalName, PortalName) != 0) { if (Number()) { dsyslog("changing portal name of channel %d (%s) from '%s' to '%s'", Number(), name, portalName, PortalName); modification |= CHANNELMOD_NAME; - Channels.SetModified(); } portalName = strcpyrealloc(portalName, PortalName); + return true; } + return false; } #define STRDIFF 0x01 @@ -349,7 +330,7 @@ static int IntArrayToString(char *s, const int *a, int Base = 10, const char n[] return q - s; } -void cChannel::SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid) +bool cChannel::SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid) { int mod = CHANNELMOD_NONE; if (vpid != Vpid || ppid != Ppid || vtype != Vtype) @@ -412,25 +393,33 @@ void cChannel::SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, c spids[MAXSPIDS] = 0; tpid = Tpid; modification |= mod; - if (Number()) - Channels.SetModified(); + return true; } + return false; } -void cChannel::SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds) +bool cChannel::SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds) { + bool Modified = false; if (SubtitlingTypes) { - for (int i = 0; i < MAXSPIDS; i++) + for (int i = 0; i < MAXSPIDS; i++) { + Modified = subtitlingTypes[i] != SubtitlingTypes[i]; subtitlingTypes[i] = SubtitlingTypes[i]; + } } if (CompositionPageIds) { - for (int i = 0; i < MAXSPIDS; i++) + for (int i = 0; i < MAXSPIDS; i++) { + Modified = compositionPageIds[i] != CompositionPageIds[i]; compositionPageIds[i] = CompositionPageIds[i]; + } } if (AncillaryPageIds) { - for (int i = 0; i < MAXSPIDS; i++) + for (int i = 0; i < MAXSPIDS; i++) { + Modified = ancillaryPageIds[i] != AncillaryPageIds[i]; ancillaryPageIds[i] = AncillaryPageIds[i]; + } } + return Modified; } void cChannel::SetSeen(void) @@ -438,10 +427,26 @@ void cChannel::SetSeen(void) seen = time(NULL); } -void cChannel::SetCaIds(const int *CaIds) +void cChannel::DelLinkChannel(cChannel *LinkChannel) +{ + if (linkChannels) { + for (cLinkChannel *lc = linkChannels->First(); lc; lc = linkChannels->Next(lc)) { + if (lc->Channel() == LinkChannel) { + linkChannels->Del(lc); + break; + } + } + if (linkChannels->Count() == 0) { + delete linkChannels; + linkChannels = NULL; + } + } +} + +bool cChannel::SetCaIds(const int *CaIds) { if (caids[0] && caids[0] <= CA_USER_MAX) - return; // special values will not be overwritten + return false; // special values will not be overwritten if (IntArraysDiffer(caids, CaIds)) { char OldCaIdsBuf[MAXCAIDS * 5 + 10]; // 5: 4 digits plus delimiting ',', 10: paranoia char NewCaIdsBuf[MAXCAIDS * 5 + 10]; @@ -455,24 +460,26 @@ void cChannel::SetCaIds(const int *CaIds) break; } modification |= CHANNELMOD_CA; - Channels.SetModified(); + return true; } + return false; } -void cChannel::SetCaDescriptors(int Level) +bool cChannel::SetCaDescriptors(int Level) { if (Level > 0) { modification |= CHANNELMOD_CA; - Channels.SetModified(); if (Number() && Level > 1) dsyslog("changing ca descriptors of channel %d (%s)", Number(), name); + return true; } + return false; } -void cChannel::SetLinkChannels(cLinkChannels *LinkChannels) +bool cChannel::SetLinkChannels(cLinkChannels *LinkChannels) { if (!linkChannels && !LinkChannels) - return; + return false; if (linkChannels && LinkChannels) { cLinkChannel *lca = linkChannels->First(); cLinkChannel *lcb = LinkChannels->First(); @@ -486,7 +493,7 @@ void cChannel::SetLinkChannels(cLinkChannels *LinkChannels) } if (!lca && !lcb) { delete LinkChannels; - return; // linkage has not changed + return false; // linkage has not changed } } char buffer[((linkChannels ? linkChannels->Count() : 0) + (LinkChannels ? LinkChannels->Count() : 0)) * 6 + 256]; // 6: 5 digit channel number plus blank, 256: other texts (see below) plus reserve @@ -514,6 +521,7 @@ void cChannel::SetLinkChannels(cLinkChannels *LinkChannels) q += sprintf(q, " none"); if (Number()) dsyslog("%s", buffer); + return true; } void cChannel::SetRefChannel(cChannel *RefChannel) @@ -819,40 +827,52 @@ public: // --- cChannels ------------------------------------------------------------- -cChannels Channels; +cChannels cChannels::channels; +int cChannels::maxNumber = 0; +int cChannels::maxChannelNameLength = 0; +int cChannels::maxShortChannelNameLength = 0; cChannels::cChannels(void) +:cConfig("Channels") { - maxNumber = 0; - maxChannelNameLength = 0; - maxShortChannelNameLength = 0; - modified = CHANNELSMOD_NONE; + modifiedByUser = 0; +} + +const cChannels *cChannels::GetChannelsRead(cStateKey &StateKey, int TimeoutMs) +{ + return channels.Lock(StateKey, false, TimeoutMs) ? &channels : NULL; +} + +cChannels *cChannels::GetChannelsWrite(cStateKey &StateKey, int TimeoutMs) +{ + return channels.Lock(StateKey, true, TimeoutMs) ? &channels : NULL; } void cChannels::DeleteDuplicateChannels(void) { cList ChannelSorter; - for (cChannel *channel = First(); channel; channel = Next(channel)) { - if (!channel->GroupSep()) - ChannelSorter.Add(new cChannelSorter(channel)); + for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) { + if (!Channel->GroupSep()) + ChannelSorter.Add(new cChannelSorter(Channel)); } ChannelSorter.Sort(); cChannelSorter *cs = ChannelSorter.First(); while (cs) { - cChannelSorter *next = ChannelSorter.Next(cs); - if (next && cs->channelID == next->channelID) { - dsyslog("deleting duplicate channel %s", *next->channel->ToText()); - Del(next->channel); + cChannelSorter *Next = ChannelSorter.Next(cs); + if (Next && cs->channelID == Next->channelID) { + dsyslog("deleting duplicate channel %s", *Next->channel->ToText()); + Del(Next->channel); } - cs = next; + cs = Next; } } bool cChannels::Load(const char *FileName, bool AllowComments, bool MustExist) { - if (cConfig::Load(FileName, AllowComments, MustExist)) { - DeleteDuplicateChannels(); - ReNumber(); + LOCK_CHANNELS_WRITE; + if (channels.cConfig::Load(FileName, AllowComments, MustExist)) { + channels.DeleteDuplicateChannels(); + channels.ReNumber(); return true; } return false; @@ -868,36 +888,36 @@ void cChannels::UnhashChannel(cChannel *Channel) channelsHashSid.Del(Channel, Channel->Sid()); } -int cChannels::GetNextGroup(int Idx) +int cChannels::GetNextGroup(int Idx) const { - cChannel *channel = Get(++Idx); - while (channel && !(channel->GroupSep() && *channel->Name())) - channel = Get(++Idx); - return channel ? Idx : -1; + const cChannel *Channel = Get(++Idx); + while (Channel && !(Channel->GroupSep() && *Channel->Name())) + Channel = Get(++Idx); + return Channel ? Idx : -1; } -int cChannels::GetPrevGroup(int Idx) +int cChannels::GetPrevGroup(int Idx) const { - cChannel *channel = Get(--Idx); - while (channel && !(channel->GroupSep() && *channel->Name())) - channel = Get(--Idx); - return channel ? Idx : -1; + const cChannel *Channel = Get(--Idx); + while (Channel && !(Channel->GroupSep() && *Channel->Name())) + Channel = Get(--Idx); + return Channel ? Idx : -1; } -int cChannels::GetNextNormal(int Idx) +int cChannels::GetNextNormal(int Idx) const { - cChannel *channel = Get(++Idx); - while (channel && channel->GroupSep()) - channel = Get(++Idx); - return channel ? Idx : -1; + const cChannel *Channel = Get(++Idx); + while (Channel && Channel->GroupSep()) + Channel = Get(++Idx); + return Channel ? Idx : -1; } -int cChannels::GetPrevNormal(int Idx) +int cChannels::GetPrevNormal(int Idx) const { - cChannel *channel = Get(--Idx); - while (channel && channel->GroupSep()) - channel = Get(--Idx); - return channel ? Idx : -1; + const cChannel *Channel = Get(--Idx); + while (Channel && Channel->GroupSep()) + Channel = Get(--Idx); + return Channel ? Idx : -1; } void cChannels::ReNumber(void) @@ -905,110 +925,120 @@ void cChannels::ReNumber(void) channelsHashSid.Clear(); maxNumber = 0; int Number = 1; - for (cChannel *channel = First(); channel; channel = Next(channel)) { - if (channel->GroupSep()) { - if (channel->Number() > Number) - Number = channel->Number(); + for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) { + if (Channel->GroupSep()) { + if (Channel->Number() > Number) + Number = Channel->Number(); } else { - HashChannel(channel); + HashChannel(Channel); maxNumber = Number; - channel->SetNumber(Number++); + Channel->SetNumber(Number++); } } } -cChannel *cChannels::GetByNumber(int Number, int SkipGap) +void cChannels::Del(cChannel *Channel) { - cChannel *previous = NULL; - for (cChannel *channel = First(); channel; channel = Next(channel)) { - if (!channel->GroupSep()) { - if (channel->Number() == Number) - return channel; - else if (SkipGap && channel->Number() > Number) - return SkipGap > 0 ? channel : previous; - previous = channel; + UnhashChannel(Channel); + for (cChannel *ch = First(); ch; ch = Next(ch)) + ch->DelLinkChannel(Channel); + cList::Del(Channel); +} + +const cChannel *cChannels::GetByNumber(int Number, int SkipGap) const +{ + const cChannel *Previous = NULL; + for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) { + if (!Channel->GroupSep()) { + if (Channel->Number() == Number) + return Channel; + else if (SkipGap && Channel->Number() > Number) + return SkipGap > 0 ? Channel : Previous; + Previous = Channel; } } return NULL; } -cChannel *cChannels::GetByServiceID(int Source, int Transponder, unsigned short ServiceID) +const cChannel *cChannels::GetByServiceID(int Source, int Transponder, unsigned short ServiceID) const { cList *list = channelsHashSid.GetList(ServiceID); if (list) { for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { - cChannel *channel = (cChannel *)hobj->Object(); - if (channel->Sid() == ServiceID && channel->Source() == Source && ISTRANSPONDER(channel->Transponder(), Transponder)) - return channel; + cChannel *Channel = (cChannel *)hobj->Object(); + if (Channel->Sid() == ServiceID && Channel->Source() == Source && ISTRANSPONDER(Channel->Transponder(), Transponder)) + return Channel; } } return NULL; } -cChannel *cChannels::GetByChannelID(tChannelID ChannelID, bool TryWithoutRid, bool TryWithoutPolarization) +const cChannel *cChannels::GetByChannelID(tChannelID ChannelID, bool TryWithoutRid, bool TryWithoutPolarization) const { int sid = ChannelID.Sid(); cList *list = channelsHashSid.GetList(sid); if (list) { for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { - cChannel *channel = (cChannel *)hobj->Object(); - if (channel->Sid() == sid && channel->GetChannelID() == ChannelID) - return channel; + cChannel *Channel = (cChannel *)hobj->Object(); + if (Channel->Sid() == sid && Channel->GetChannelID() == ChannelID) + return Channel; } if (TryWithoutRid) { ChannelID.ClrRid(); for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { - cChannel *channel = (cChannel *)hobj->Object(); - if (channel->Sid() == sid && channel->GetChannelID().ClrRid() == ChannelID) - return channel; + cChannel *Channel = (cChannel *)hobj->Object(); + if (Channel->Sid() == sid && Channel->GetChannelID().ClrRid() == ChannelID) + return Channel; } } if (TryWithoutPolarization) { ChannelID.ClrPolarization(); for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { - cChannel *channel = (cChannel *)hobj->Object(); - if (channel->Sid() == sid && channel->GetChannelID().ClrPolarization() == ChannelID) - return channel; + cChannel *Channel = (cChannel *)hobj->Object(); + if (Channel->Sid() == sid && Channel->GetChannelID().ClrPolarization() == ChannelID) + return Channel; } } } return NULL; } -cChannel *cChannels::GetByTransponderID(tChannelID ChannelID) + +const cChannel *cChannels::GetByTransponderID(tChannelID ChannelID) const { int source = ChannelID.Source(); int nid = ChannelID.Nid(); int tid = ChannelID.Tid(); - for (cChannel *channel = First(); channel; channel = Next(channel)) { - if (channel->Tid() == tid && channel->Nid() == nid && channel->Source() == source) - return channel; + for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) { + if (Channel->Tid() == tid && Channel->Nid() == nid && Channel->Source() == source) + return Channel; } return NULL; } -bool cChannels::HasUniqueChannelID(cChannel *NewChannel, cChannel *OldChannel) +bool cChannels::HasUniqueChannelID(const cChannel *NewChannel, const cChannel *OldChannel) const { tChannelID NewChannelID = NewChannel->GetChannelID(); - for (cChannel *channel = First(); channel; channel = Next(channel)) { - if (!channel->GroupSep() && channel != OldChannel && channel->GetChannelID() == NewChannelID) + for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) { + if (!Channel->GroupSep() && Channel != OldChannel && Channel->GetChannelID() == NewChannelID) return false; } return true; } -bool cChannels::SwitchTo(int Number) +bool cChannels::SwitchTo(int Number) const { - cChannel *channel = GetByNumber(Number); - return channel && cDevice::PrimaryDevice()->SwitchChannel(channel, true); + const cChannel *Channel = GetByNumber(Number); + return Channel && cDevice::PrimaryDevice()->SwitchChannel(Channel, true); } int cChannels::MaxChannelNameLength(void) { if (!maxChannelNameLength) { - for (cChannel *channel = First(); channel; channel = Next(channel)) { - if (!channel->GroupSep()) - maxChannelNameLength = max(Utf8StrLen(channel->Name()), maxChannelNameLength); + LOCK_CHANNELS_READ; + for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { + if (!Channel->GroupSep()) + maxChannelNameLength = max(Utf8StrLen(Channel->Name()), maxChannelNameLength); } } return maxChannelNameLength; @@ -1017,24 +1047,25 @@ int cChannels::MaxChannelNameLength(void) int cChannels::MaxShortChannelNameLength(void) { if (!maxShortChannelNameLength) { - for (cChannel *channel = First(); channel; channel = Next(channel)) { - if (!channel->GroupSep()) - maxShortChannelNameLength = max(Utf8StrLen(channel->ShortName(true)), maxShortChannelNameLength); + LOCK_CHANNELS_READ; + for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { + if (!Channel->GroupSep()) + maxShortChannelNameLength = max(Utf8StrLen(Channel->ShortName(true)), maxShortChannelNameLength); } } return maxShortChannelNameLength; } -void cChannels::SetModified(bool ByUser) +void cChannels::SetModifiedByUser(void) { - modified = ByUser ? CHANNELSMOD_USER : !modified ? CHANNELSMOD_AUTO : modified; + modifiedByUser++; maxChannelNameLength = maxShortChannelNameLength = 0; } -int cChannels::Modified(void) +bool cChannels::ModifiedByUser(int &State) const { - int Result = modified; - modified = CHANNELSMOD_NONE; + int Result = State != modifiedByUser; + State = modifiedByUser; return Result; } @@ -1044,7 +1075,7 @@ cChannel *cChannels::NewChannel(const cChannel *Transponder, const char *Name, c dsyslog("creating new channel '%s,%s;%s' on %s transponder %d with id %d-%d-%d-%d", Name, ShortName, Provider, *cSource::ToString(Transponder->Source()), Transponder->Transponder(), Nid, Tid, Sid, Rid); cChannel *NewChannel = new cChannel; NewChannel->CopyTransponderData(Transponder); - NewChannel->SetId(Nid, Tid, Sid, Rid); + NewChannel->SetId(this, Nid, Tid, Sid, Rid); NewChannel->SetName(Name, ShortName, Provider); NewChannel->SetSeen(); Add(NewChannel); @@ -1057,17 +1088,19 @@ cChannel *cChannels::NewChannel(const cChannel *Transponder, const char *Name, c #define CHANNELMARKOBSOLETE "OBSOLETE" #define CHANNELTIMEOBSOLETE 3600 // seconds to wait before declaring a channel obsolete (in case it has actually been seen before) -void cChannels::MarkObsoleteChannels(int Source, int Nid, int Tid) +bool cChannels::MarkObsoleteChannels(int Source, int Nid, int Tid) { - for (cChannel *channel = First(); channel; channel = Next(channel)) { - if (time(NULL) - channel->Seen() > CHANNELTIMEOBSOLETE && channel->Source() == Source && channel->Nid() == Nid && channel->Tid() == Tid && channel->Rid() == 0) { + bool ChannelsModified = false; + for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) { + if (time(NULL) - Channel->Seen() > CHANNELTIMEOBSOLETE && Channel->Source() == Source && Channel->Nid() == Nid && Channel->Tid() == Tid && Channel->Rid() == 0) { bool OldShowChannelNamesWithSource = Setup.ShowChannelNamesWithSource; Setup.ShowChannelNamesWithSource = false; - if (!endswith(channel->Name(), CHANNELMARKOBSOLETE)) - channel->SetName(cString::sprintf("%s %s", channel->Name(), CHANNELMARKOBSOLETE), channel->ShortName(), cString::sprintf("%s %s", CHANNELMARKOBSOLETE, channel->Provider())); + if (!endswith(Channel->Name(), CHANNELMARKOBSOLETE)) + ChannelsModified |= Channel->SetName(cString::sprintf("%s %s", Channel->Name(), CHANNELMARKOBSOLETE), Channel->ShortName(), cString::sprintf("%s %s", CHANNELMARKOBSOLETE, Channel->Provider())); Setup.ShowChannelNamesWithSource = OldShowChannelNamesWithSource; } } + return ChannelsModified; } cString ChannelString(const cChannel *Channel, int Number) diff --git a/channels.h b/channels.h index 95e11952..478b3d64 100644 --- a/channels.h +++ b/channels.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: channels.h 4.1 2015/03/13 11:20:50 kls Exp $ + * $Id: channels.h 4.2 2015/08/17 09:39:48 kls Exp $ */ #ifndef __CHANNELS_H @@ -28,10 +28,6 @@ #define CHANNELMOD_LANGS 0x40 #define CHANNELMOD_RETUNE (CHANNELMOD_PIDS | CHANNELMOD_CA | CHANNELMOD_TRANSP) -#define CHANNELSMOD_NONE 0 -#define CHANNELSMOD_AUTO 1 -#define CHANNELSMOD_USER 2 - #define MAXAPIDS 32 // audio #define MAXDPIDS 16 // dolby (AC3 + DTS) #define MAXSPIDS 32 // subtitles @@ -86,6 +82,7 @@ class cLinkChannels : public cList { }; class cSchedule; +class cChannels; class cChannel : public cListObject { friend class cSchedules; @@ -128,7 +125,7 @@ private: mutable cString nameSource; mutable cString shortNameSource; cString parameters; - int modification; + mutable int modification; time_t seen; // When this channel was last seen in the SDT of its transponder mutable const cSchedule *schedule; cLinkChannels *linkChannels; @@ -188,66 +185,84 @@ public: bool IsTerr(void) const { return cSource::IsTerr(source); } bool IsSourceType(char Source) const { return cSource::IsType(source, Source); } tChannelID GetChannelID(void) const { return tChannelID(source, nid, (nid || tid) ? tid : Transponder(), sid, rid); } - bool HasTimer(void) const; - int Modification(int Mask = CHANNELMOD_ALL); - time_t Seen(void) { return seen; } + int Modification(int Mask = CHANNELMOD_ALL) const; + time_t Seen(void) const { return seen; } void CopyTransponderData(const cChannel *Channel); bool SetTransponderData(int Source, int Frequency, int Srate, const char *Parameters, bool Quiet = false); - void SetSource(int Source); - void SetId(int Nid, int Tid, int Sid, int Rid = 0); - void SetLcn(int Lcn); - void SetName(const char *Name, const char *ShortName, const char *Provider); - void SetPortalName(const char *PortalName); - void SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid); - void SetCaIds(const int *CaIds); // list must be zero-terminated - void SetCaDescriptors(int Level); - void SetLinkChannels(cLinkChannels *LinkChannels); + bool SetSource(int Source); + bool SetId(cChannels *Channels, int Nid, int Tid, int Sid, int Rid = 0); + bool SetLcn(int Lcn); + bool SetName(const char *Name, const char *ShortName, const char *Provider); + bool SetPortalName(const char *PortalName); + bool SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid); + bool SetCaIds(const int *CaIds); // list must be zero-terminated + bool SetCaDescriptors(int Level); + bool SetLinkChannels(cLinkChannels *LinkChannels); void SetRefChannel(cChannel *RefChannel); - void SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds); + bool SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds); void SetSeen(void); + void DelLinkChannel(cChannel *LinkChannel); }; -class cChannels : public cRwLock, public cConfig { +class cChannels : public cConfig { private: - int maxNumber; - int maxChannelNameLength; - int maxShortChannelNameLength; - int modified; - int beingEdited; + static cChannels channels; + static int maxNumber; + static int maxChannelNameLength; + static int maxShortChannelNameLength; + int modifiedByUser; cHash channelsHashSid; void DeleteDuplicateChannels(void); public: cChannels(void); - bool Load(const char *FileName, bool AllowComments = false, bool MustExist = false); + static const cChannels *GetChannelsRead(cStateKey &StateKey, int TimeoutMs = 0); + ///< Gets the list of channels for read access. + ///< See cTimers::GetTimersRead() for details. + static cChannels *GetChannelsWrite(cStateKey &StateKey, int TimeoutMs = 0); + ///< Gets the list of channels for write access. + ///< See cTimers::GetTimersWrite() for details. + static bool Load(const char *FileName, bool AllowComments = false, bool MustExist = false); void HashChannel(cChannel *Channel); void UnhashChannel(cChannel *Channel); - int GetNextGroup(int Idx); // Get next channel group - int GetPrevGroup(int Idx); // Get previous channel group - int GetNextNormal(int Idx); // Get next normal channel (not group) - int GetPrevNormal(int Idx); // Get previous normal channel (not group) - void ReNumber(void); // Recalculate 'number' based on channel type - cChannel *GetByNumber(int Number, int SkipGap = 0); - cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID); - cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false); - cChannel *GetByTransponderID(tChannelID ChannelID); - int BeingEdited(void) { return beingEdited; } - void IncBeingEdited(void) { beingEdited++; } - void DecBeingEdited(void) { beingEdited--; } - bool HasUniqueChannelID(cChannel *NewChannel, cChannel *OldChannel = NULL); - bool SwitchTo(int Number); - int MaxNumber(void) { return maxNumber; } - int MaxChannelNameLength(void); - int MaxShortChannelNameLength(void); - void SetModified(bool ByUser = false); - int Modified(void); - ///< Returns 0 if no channels have been modified, 1 if an automatic - ///< modification has been made, and 2 if the user has made a modification. - ///< Calling this function resets the 'modified' flag to 0. + int GetNextGroup(int Idx) const; ///< Get next channel group + int GetPrevGroup(int Idx) const; ///< Get previous channel group + int GetNextNormal(int Idx) const; ///< Get next normal channel (not group) + int GetPrevNormal(int Idx) const; ///< Get previous normal channel (not group) + void ReNumber(void); ///< Recalculate 'number' based on channel type + void Del(cChannel *Channel); ///< Delete the given Channel from the list + const cChannel *GetByNumber(int Number, int SkipGap = 0) const; + cChannel *GetByNumber(int Number, int SkipGap = 0) { return const_cast(static_cast(this)->GetByNumber(Number, SkipGap)); } + const cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID) const; + cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID) { return const_cast(static_cast(this)->GetByServiceID(Source, Transponder, ServiceID)); } + const cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false) const; + cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false) { return const_cast(static_cast(this)->GetByChannelID(ChannelID, TryWithoutRid, TryWithoutPolarization)); } + const cChannel *GetByTransponderID(tChannelID ChannelID) const; + cChannel *GetByTransponderID(tChannelID ChannelID) { return const_cast(static_cast(this)->GetByTransponderID(ChannelID)); } + bool HasUniqueChannelID(const cChannel *NewChannel, const cChannel *OldChannel = NULL) const; + bool SwitchTo(int Number) const; + static int MaxNumber(void) { return maxNumber; } + static int MaxChannelNameLength(void); + static int MaxShortChannelNameLength(void); + void SetModifiedByUser(void); + bool ModifiedByUser(int &State) const; + ///< Returns true if the channels have been modified by the user since the last call + ///< to this function with the same State variable. State must be initialized with 0 + ///< and will be set to the current value of the list's internal state variable upon + ///< return from this function. cChannel *NewChannel(const cChannel *Transponder, const char *Name, const char *ShortName, const char *Provider, int Nid, int Tid, int Sid, int Rid = 0); - void MarkObsoleteChannels(int Source, int Nid, int Tid); + bool MarkObsoleteChannels(int Source, int Nid, int Tid); }; -extern cChannels Channels; +// Provide lock controlled access to the list: + +DEF_LIST_LOCK(Channels); + +// These macros provide a convenient way of locking the global channels list +// and making sure the lock is released as soon as the current scope is left +// (note that these macros wait forever to obtain the lock!): + +#define LOCK_CHANNELS_READ USE_LIST_LOCK_READ(Channels) +#define LOCK_CHANNELS_WRITE USE_LIST_LOCK_WRITE(Channels) cString ChannelString(const cChannel *Channel, int Number); diff --git a/ci.c b/ci.c index b4a2d008..da572f02 100644 --- a/ci.c +++ b/ci.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: ci.c 3.19 2015/02/02 14:04:10 kls Exp $ + * $Id: ci.c 4.1 2015/07/18 09:57:42 kls Exp $ */ #include "ci.h" @@ -1921,7 +1921,8 @@ void cCamSlot::StartActivation(void) cMutexLock MutexLock(&mutex); if (!caActivationReceiver) { if (cDevice *d = Device()) { - if (cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel())) { + LOCK_CHANNELS_READ; + if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel())) { caActivationReceiver = new cCaActivationReceiver(Channel, this); d->AttachReceiver(caActivationReceiver); dsyslog("CAM %d: activating on device %d with channel %d (%s)", SlotNumber(), d->DeviceNumber() + 1, Channel->Number(), Channel->Name()); diff --git a/config.h b/config.h index 23c601b4..2978d7d6 100644 --- a/config.h +++ b/config.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.h 4.2 2015/04/18 13:13:15 kls Exp $ + * $Id: config.h 4.3 2015/08/09 09:17:46 kls Exp $ */ #ifndef __CONFIG_H @@ -110,7 +110,7 @@ private: cList::Clear(); } public: - cConfig(void) { fileName = NULL; } + cConfig(const char *NeedsLocking = NULL): cList(NeedsLocking) { fileName = NULL; } virtual ~cConfig() { free(fileName); } const char *FileName(void) { return fileName; } bool Load(const char *FileName = NULL, bool AllowComments = false, bool MustExist = false) @@ -160,7 +160,7 @@ public: fprintf(stderr, "vdr: error while reading '%s'\n", fileName); return result; } - bool Save(void) + bool Save(void) const { bool result = true; T *l = (T *)this->First(); diff --git a/cutter.c b/cutter.c index 55a3cd4d..587fddf0 100644 --- a/cutter.c +++ b/cutter.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: cutter.c 4.1 2015/04/11 12:03:25 kls Exp $ + * $Id: cutter.c 4.2 2015/08/09 12:24:28 kls Exp $ */ #include "cutter.h" @@ -634,7 +634,6 @@ void cCuttingThread::Action(void) } } } - Recordings.TouchUpdate(); } else esyslog("no editing marks found!"); @@ -678,7 +677,8 @@ bool cCutter::Start(void) cRecordingUserCommand::InvokeCommand(RUC_EDITINGRECORDING, editedVersionName, originalVersionName); if (cVideoDirectory::RemoveVideoFile(editedVersionName) && MakeDirs(editedVersionName, true)) { Recording.WriteInfo(editedVersionName); - Recordings.AddByName(editedVersionName, false); + LOCK_RECORDINGS_WRITE; + Recordings->AddByName(editedVersionName, false); cuttingThread = new cCuttingThread(originalVersionName, editedVersionName); return true; } @@ -703,7 +703,8 @@ void cCutter::Stop(void) if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), editedVersionName) == 0) cControl::Shutdown(); cVideoDirectory::RemoveVideoFile(editedVersionName); - Recordings.DelByName(editedVersionName); + LOCK_RECORDINGS_WRITE; + Recordings->DelByName(editedVersionName); } } diff --git a/device.c b/device.c index 14ab07d3..6ae9d5ac 100644 --- a/device.c +++ b/device.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: device.c 3.20 2015/01/30 12:11:30 kls Exp $ + * $Id: device.c 4.1 2015/08/29 12:41:08 kls Exp $ */ #include "device.h" @@ -722,20 +722,21 @@ bool cDevice::SwitchChannel(int Direction) cControl::Shutdown(); // prevents old channel from being shown too long if GetDevice() takes longer int n = CurrentChannel() + Direction; int first = n; - cChannel *channel; - while ((channel = Channels.GetByNumber(n, Direction)) != NULL) { + LOCK_CHANNELS_READ; + const cChannel *Channel; + while ((Channel = Channels->GetByNumber(n, Direction)) != NULL) { // try only channels which are currently available - if (GetDevice(channel, LIVEPRIORITY, true, true)) + if (GetDevice(Channel, LIVEPRIORITY, true, true)) break; - n = channel->Number() + Direction; + n = Channel->Number() + Direction; } - if (channel) { + if (Channel) { int d = n - first; if (abs(d) == 1) dsyslog("skipped channel %d", first); else if (d) dsyslog("skipped channels %d..%d", first, n - sgn(d)); - if (PrimaryDevice()->SwitchChannel(channel, true)) + if (PrimaryDevice()->SwitchChannel(Channel, true)) result = true; } else if (n != first) @@ -777,7 +778,6 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) Result = scrNotAvailable; } else { - Channels.Lock(false); // Stop section handling: if (sectionHandler) { sectionHandler->SetStatus(false); @@ -790,7 +790,8 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) if (SetChannelDevice(Channel, LiveView)) { // Start section handling: if (sectionHandler) { - patFilter->Trigger(Channel->Sid()); + if (patFilter) + patFilter->Trigger(Channel->Sid()); sectionHandler->SetChannel(Channel); sectionHandler->SetStatus(true); } @@ -800,7 +801,6 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) } else Result = scrFailed; - Channels.Unlock(); } if (Result == scrOk) { @@ -829,8 +829,8 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) void cDevice::ForceTransferMode(void) { if (!cTransferControl::ReceiverDevice()) { - cChannel *Channel = Channels.GetByNumber(CurrentChannel()); - if (Channel) + LOCK_CHANNELS_READ; + if (const cChannel *Channel = Channels->GetByNumber(CurrentChannel())) SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode } } diff --git a/dvbplayer.c b/dvbplayer.c index aae0507c..ca4007e3 100644 --- a/dvbplayer.c +++ b/dvbplayer.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbplayer.c 3.6 2015/02/13 15:12:57 kls Exp $ + * $Id: dvbplayer.c 4.1 2015/08/06 13:09:19 kls Exp $ */ #include "dvbplayer.h" @@ -210,7 +210,7 @@ private: cNonBlockingFileReader *nonBlockingFileReader; cRingBufferFrame *ringBuffer; cPtsIndex ptsIndex; - cMarks *marks; + const cMarks *marks; cFileName *fileName; cIndexFile *index; cUnbufferedFile *replayFile; @@ -239,7 +239,7 @@ protected: public: cDvbPlayer(const char *FileName, bool PauseLive); virtual ~cDvbPlayer(); - void SetMarks(cMarks *Marks); + void SetMarks(const cMarks *Marks); bool Active(void) { return cThread::Running(); } void Pause(void); void Play(void); @@ -311,7 +311,7 @@ cDvbPlayer::~cDvbPlayer() // don't delete marks here, we don't own them! } -void cDvbPlayer::SetMarks(cMarks *Marks) +void cDvbPlayer::SetMarks(const cMarks *Marks) { marks = Marks; } @@ -383,10 +383,11 @@ bool cDvbPlayer::Save(void) int Index = ptsIndex.FindIndex(DeviceGetSTC()); if (Index >= 0) { if (Setup.SkipEdited && marks) { - marks->Lock(); + cStateKey StateKey; + marks->Lock(StateKey); if (marks->First() && abs(Index - marks->First()->Position()) <= int(round(RESUMEBACKUP * framesPerSecond))) Index = 0; // when stopping within RESUMEBACKUP seconds of the first mark the recording shall still be considered unviewed - marks->Unlock(); + StateKey.Remove(); } Index -= int(round(RESUMEBACKUP * framesPerSecond)); if (Index > 0) @@ -419,7 +420,8 @@ void cDvbPlayer::Action(void) if (readIndex > 0) isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond)); else if (Setup.SkipEdited && marks) { - marks->Lock(); + cStateKey StateKey; + marks->Lock(StateKey); if (marks->First() && index) { int Index = marks->First()->Position(); uint16_t FileNumber; @@ -429,7 +431,7 @@ void cDvbPlayer::Action(void) readIndex = Index; } } - marks->Unlock(); + StateKey.Remove(); } nonBlockingFileReader = new cNonBlockingFileReader; @@ -500,8 +502,9 @@ void cDvbPlayer::Action(void) if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length) && NextFile(FileNumber, FileOffset)) { readIndex++; if ((Setup.SkipEdited || Setup.PauseAtLastMark) && marks) { - marks->Lock(); - cMark *m = marks->Get(readIndex); + cStateKey StateKey; + marks->Lock(StateKey); + const cMark *m = marks->Get(readIndex); if (m && (m->Index() & 0x01) != 0) { // we're at an end mark m = marks->GetNextBegin(m); int Index = -1; @@ -519,7 +522,7 @@ void cDvbPlayer::Action(void) CutIn = true; } } - marks->Unlock(); + StateKey.Remove(); } } else @@ -943,7 +946,7 @@ cDvbPlayerControl::~cDvbPlayerControl() Stop(); } -void cDvbPlayerControl::SetMarks(cMarks *Marks) +void cDvbPlayerControl::SetMarks(const cMarks *Marks) { if (player) player->SetMarks(Marks); diff --git a/dvbplayer.h b/dvbplayer.h index 7094454d..ef6f1fce 100644 --- a/dvbplayer.h +++ b/dvbplayer.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbplayer.h 3.2 2015/02/06 12:27:39 kls Exp $ + * $Id: dvbplayer.h 4.1 2015/08/02 13:01:44 kls Exp $ */ #ifndef __DVBPLAYER_H @@ -26,7 +26,7 @@ public: // file of the recording is long enough to allow the player to display // the first frame in still picture mode. virtual ~cDvbPlayerControl(); - void SetMarks(cMarks *Marks); + void SetMarks(const cMarks *Marks); bool Active(void); void Stop(void); // Stops the current replay session (if any). diff --git a/eit.c b/eit.c index 1f960bb2..b5c671d2 100644 --- a/eit.c +++ b/eit.c @@ -8,7 +8,7 @@ * Robert Schneider and Rolf Hakenes . * Adapted to 'libsi' for VDR 1.3.0 by Marcel Wiesweg . * - * $Id: eit.c 3.6 2015/02/01 14:55:27 kls Exp $ + * $Id: eit.c 4.1 2015/08/23 10:43:36 kls Exp $ */ #include "eit.h" @@ -24,31 +24,53 @@ class cEIT : public SI::EIT { public: - cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bool OnlyRunningStatus = false); + cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const u_char *Data); }; -cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bool OnlyRunningStatus) +cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const u_char *Data) :SI::EIT(Data, false) { if (!CheckCRCAndParse()) return; + int HashId = Tid * getServiceId(); + cSectionSyncerEntry *SectionSyncerEntry = SectionSyncerHash.Get(HashId); + if (!SectionSyncerEntry) { + SectionSyncerEntry = new cSectionSyncerEntry; + SectionSyncerHash.Add(SectionSyncerEntry, HashId); + } + bool Process = SectionSyncerEntry->Sync(getVersionNumber(), getSectionNumber(), getLastSectionNumber()); + if (Tid != 0x4E && !Process) // we need to set the 'seen' tag to watch the running status of the present/following event + return; time_t Now = time(NULL); if (Now < VALID_TIME) return; // we need the current time for handling PDC descriptors - if (!Channels.Lock(false, 10)) + cStateKey ChannelsStateKey; + cChannels *Channels = cChannels::GetChannelsWrite(ChannelsStateKey, 10); + if (!Channels) { + SectionSyncerEntry->Repeat(); // let's not miss any section of the EIT return; + } tChannelID channelID(Source, getOriginalNetworkId(), getTransportStreamId(), getServiceId()); - cChannel *channel = Channels.GetByChannelID(channelID, true); - if (!channel || EpgHandlers.IgnoreChannel(channel)) { - Channels.Unlock(); + cChannel *Channel = Channels->GetByChannelID(channelID, true); + if (!Channel || EpgHandlers.IgnoreChannel(Channel)) { + ChannelsStateKey.Remove(false); return; } - EpgHandlers.BeginSegmentTransfer(channel, OnlyRunningStatus); - bool handledExternally = EpgHandlers.HandledExternally(channel); - cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(channel, true); + cStateKey SchedulesStateKey; + cSchedules *Schedules = cSchedules::GetSchedulesWrite(SchedulesStateKey, 10); + if (!Schedules) { + SectionSyncerEntry->Repeat(); // let's not miss any section of the EIT + ChannelsStateKey.Remove(false); + return; + } + + bool ChannelsModified = false; + EpgHandlers.BeginSegmentTransfer(Channel); + bool handledExternally = EpgHandlers.HandledExternally(Channel); + cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(Channel, true); bool Empty = true; bool Modified = false; @@ -74,8 +96,6 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo cEvent *rEvent = NULL; cEvent *pEvent = (cEvent *)pSchedule->GetEvent(SiEitEvent.getEventId(), StartTime); if (!pEvent || handledExternally) { - if (OnlyRunningStatus) - continue; if (handledExternally && !EpgHandlers.IsUpdate(SiEitEvent.getEventId(), StartTime, Tid, getVersionNumber())) continue; // If we don't have that event yet, we create a new one. @@ -94,14 +114,6 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo // The lower the table ID, the more "current" the information. if (Tid > TableID) continue; - // If the new event comes from the same table and has the same version number - // as the existing one, let's skip it to avoid unnecessary work. - // Unfortunately some stations (like, e.g. "Premiere") broadcast their EPG data on several transponders (like - // the actual Premiere transponder and the Sat.1/Pro7 transponder), but use different version numbers on - // each of them :-( So if one DVB card is tuned to the Premiere transponder, while an other one is tuned - // to the Sat.1/Pro7 transponder, events will keep toggling because of the bogus version numbers. - else if (Tid == TableID && pEvent->Version() == getVersionNumber()) - continue; EpgHandlers.SetEventID(pEvent, SiEitEvent.getEventId()); // unfortunately some stations use different event ids for the same event in different tables :-( EpgHandlers.SetStartTime(pEvent, StartTime); EpgHandlers.SetDuration(pEvent, Duration); @@ -110,11 +122,9 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo pEvent->SetTableID(Tid); if (Tid == 0x4E) { // we trust only the present/following info on the actual TS if (SiEitEvent.getRunningStatus() >= SI::RunningStatusNotRunning) - pSchedule->SetRunningStatus(pEvent, SiEitEvent.getRunningStatus(), channel); - } - if (OnlyRunningStatus) { - pEvent->SetVersion(0xFF); // we have already changed the table id above, so set the version to an invalid value to make sure the next full run will be executed - continue; // do this before setting the version, so that the full update can be done later + pSchedule->SetRunningStatus(pEvent, SiEitEvent.getRunningStatus(), Channel); + if (!Process) + continue; } pEvent->SetVersion(getVersionNumber()); @@ -206,7 +216,7 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo break; case SI::TimeShiftedEventDescriptorTag: { SI::TimeShiftedEventDescriptor *tsed = (SI::TimeShiftedEventDescriptor *)d; - cSchedule *rSchedule = (cSchedule *)Schedules->GetSchedule(tChannelID(Source, channel->Nid(), channel->Tid(), tsed->getReferenceServiceId())); + cSchedule *rSchedule = (cSchedule *)Schedules->GetSchedule(tChannelID(Source, Channel->Nid(), Channel->Tid(), tsed->getReferenceServiceId())); if (!rSchedule) break; rEvent = (cEvent *)rSchedule->GetEvent(tsed->getReferenceEventId()); @@ -226,18 +236,18 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo char linkName[ld->privateData.getLength() + 1]; strn0cpy(linkName, (const char *)ld->privateData.getData(), sizeof(linkName)); // TODO is there a standard way to determine the character set of this string? - cChannel *link = Channels.GetByChannelID(linkID); - if (link != channel) { // only link to other channels, not the same one - //fprintf(stderr, "Linkage %s %4d %4d %5d %5d %5d %5d %02X '%s'\n", hit ? "*" : "", channel->Number(), link ? link->Number() : -1, SiEitEvent.getEventId(), ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId(), ld->getLinkageType(), linkName);//XXX + cChannel *link = Channels->GetByChannelID(linkID); + if (link != Channel) { // only link to other channels, not the same one if (link) { if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3) - link->SetName(linkName, "", ""); + ChannelsModified |= link->SetName(linkName, "", ""); } else if (Setup.UpdateChannels >= 4) { - cChannel *transponder = channel; - if (channel->Tid() != ld->getTransportStreamId()) - transponder = Channels.GetByTransponderID(linkID); - link = Channels.NewChannel(transponder, linkName, "", "", ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId()); + cChannel *Transponder = Channel; + if (Channel->Tid() != ld->getTransportStreamId()) + Transponder = Channels->GetByTransponderID(linkID); + link = Channels->NewChannel(Transponder, linkName, "", "", ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId()); + ChannelsModified = true; //XXX patFilter->Trigger(); } if (link) { @@ -247,7 +257,7 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo } } else - channel->SetPortalName(linkName); + ChannelsModified |= Channel->SetPortalName(linkName); } } } @@ -293,7 +303,7 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo EpgHandlers.FixEpgBugs(pEvent); if (LinkChannels) - channel->SetLinkChannels(LinkChannels); + ChannelsModified |= Channel->SetLinkChannels(LinkChannels); Modified = true; EpgHandlers.HandleEvent(pEvent); if (handledExternally) @@ -302,16 +312,17 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo if (Tid == 0x4E) { if (Empty && getSectionNumber() == 0) // ETR 211: an empty entry in section 0 of table 0x4E means there is currently no event running - pSchedule->ClrRunningStatus(channel); + pSchedule->ClrRunningStatus(Channel); pSchedule->SetPresentSeen(); } - if (Modified && !OnlyRunningStatus) { + if (Modified) { EpgHandlers.SortSchedule(pSchedule); EpgHandlers.DropOutdated(pSchedule, SegmentStart, SegmentEnd, Tid, getVersionNumber()); - Schedules->SetModified(pSchedule); + pSchedule->SetModified(); } - Channels.Unlock(); - EpgHandlers.EndSegmentTransfer(Modified, OnlyRunningStatus); + SchedulesStateKey.Remove(Modified); + ChannelsStateKey.Remove(ChannelsModified); + EpgHandlers.EndSegmentTransfer(Modified); } // --- cTDT ------------------------------------------------------------------ @@ -372,6 +383,13 @@ cEitFilter::cEitFilter(void) Set(0x14, 0x70); // TDT } +void cEitFilter::SetStatus(bool On) +{ + cMutexLock MutexLock(&mutex); + cFilter::SetStatus(On); + sectionSyncerHash.Clear(); +} + void cEitFilter::SetDisableUntil(time_t Time) { disableUntil = Time; @@ -379,6 +397,7 @@ void cEitFilter::SetDisableUntil(time_t Time) void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length) { + cMutexLock MutexLock(&mutex); if (disableUntil) { if (time(NULL) > disableUntil) disableUntil = 0; @@ -387,22 +406,8 @@ void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length } switch (Pid) { case 0x12: { - if (Tid >= 0x4E && Tid <= 0x6F) { - cSchedulesLock SchedulesLock(true, 10); - cSchedules *Schedules = (cSchedules *)cSchedules::Schedules(SchedulesLock); - if (Schedules) - cEIT EIT(Schedules, Source(), Tid, Data); - else { - // If we don't get a write lock, let's at least get a read lock, so - // that we can set the running status and 'seen' timestamp (well, actually - // with a read lock we shouldn't be doing that, but it's only integers that - // get changed, so it should be ok) - cSchedulesLock SchedulesLock; - cSchedules *Schedules = (cSchedules *)cSchedules::Schedules(SchedulesLock); - if (Schedules) - cEIT EIT(Schedules, Source(), Tid, Data, true); - } - } + if (Tid >= 0x4E && Tid <= 0x6F) + cEIT EIT(sectionSyncerHash, Source(), Tid, Data); } break; case 0x14: { diff --git a/eit.h b/eit.h index b5523728..51af6433 100644 --- a/eit.h +++ b/eit.h @@ -4,21 +4,29 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: eit.h 2.1 2010/01/03 15:28:34 kls Exp $ + * $Id: eit.h 4.1 2015/07/25 11:03:53 kls Exp $ */ #ifndef __EIT_H #define __EIT_H #include "filter.h" +#include "tools.h" + +class cSectionSyncerEntry : public cListObject, public cSectionSyncer {}; + +class cSectionSyncerHash : public cHash {}; class cEitFilter : public cFilter { private: + cMutex mutex; + cSectionSyncerHash sectionSyncerHash; static time_t disableUntil; protected: virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length); public: cEitFilter(void); + virtual void SetStatus(bool On); static void SetDisableUntil(time_t Time); }; diff --git a/eitscan.c b/eitscan.c index 027bf44c..c9b528aa 100644 --- a/eitscan.c +++ b/eitscan.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: eitscan.c 2.7 2012/04/07 14:39:28 kls Exp $ + * $Id: eitscan.c 4.1 2015/07/18 10:16:51 kls Exp $ */ #include "eitscan.h" @@ -45,13 +45,13 @@ int cScanData::Compare(const cListObject &ListObject) const class cScanList : public cList { public: - void AddTransponders(cList *Channels); + void AddTransponders(const cList *Channels); void AddTransponder(const cChannel *Channel); }; -void cScanList::AddTransponders(cList *Channels) +void cScanList::AddTransponders(const cList *Channels) { - for (cChannel *ch = Channels->First(); ch; ch = Channels->Next(ch)) + for (const cChannel *ch = Channels->First(); ch; ch = Channels->Next(ch)) AddTransponder(ch); Sort(); } @@ -118,7 +118,8 @@ void cEITScanner::ForceScan(void) void cEITScanner::Activity(void) { if (currentChannel) { - Channels.SwitchTo(currentChannel); + LOCK_CHANNELS_READ; + Channels->SwitchTo(currentChannel); currentChannel = 0; } lastActivity = time(NULL); @@ -129,7 +130,8 @@ void cEITScanner::Process(void) if (Setup.EPGScanTimeout || !lastActivity) { // !lastActivity means a scan was forced time_t now = time(NULL); if (now - lastScan > ScanTimeout && now - lastActivity > ActivityTimeout) { - if (Channels.Lock(false, 10)) { + cStateKey StateKey; + if (const cChannels *Channels = cChannels::GetChannelsRead(StateKey, 10)) { if (!scanList) { scanList = new cScanList; if (transponderList) { @@ -137,7 +139,7 @@ void cEITScanner::Process(void) delete transponderList; transponderList = NULL; } - scanList->AddTransponders(&Channels); + scanList->AddTransponders(Channels); } bool AnyDeviceSwitched = false; for (int i = 0; i < cDevice::NumDevices(); i++) { @@ -177,7 +179,7 @@ void cEITScanner::Process(void) if (lastActivity == 0) // this was a triggered scan Activity(); } - Channels.Unlock(); + StateKey.Remove(); } lastScan = time(NULL); } diff --git a/epg.c b/epg.c index 096b68cf..3dde2045 100644 --- a/epg.c +++ b/epg.c @@ -7,7 +7,7 @@ * Original version (as used in VDR before 1.3.0) written by * Robert Schneider and Rolf Hakenes . * - * $Id: epg.c 3.3 2013/12/28 11:33:08 kls Exp $ + * $Id: epg.c 4.1 2015/08/23 10:39:59 kls Exp $ */ #include "epg.h" @@ -15,7 +15,6 @@ #include #include #include "libsi/si.h" -#include "timers.h" #define RUNNINGSTATUSTIMEOUT 30 // seconds before the running status is considered unknown #define EPGDATAWRITEDELTA 600 // seconds between writing the epg.data file @@ -111,9 +110,12 @@ tComponent *cComponents::GetComponent(int Index, uchar Stream, uchar Type) // --- cEvent ---------------------------------------------------------------- +cMutex cEvent::numTimersMutex; + cEvent::cEvent(tEventID EventID) { schedule = NULL; + numTimers = 0; eventID = EventID; tableID = 0xFF; // actual table ids are 0x4E..0x60 version = 0xFF; // actual version numbers are 0..31 @@ -170,9 +172,9 @@ void cEvent::SetVersion(uchar Version) version = Version; } -void cEvent::SetRunningStatus(int RunningStatus, cChannel *Channel) +void cEvent::SetRunningStatus(int RunningStatus, const cChannel *Channel) { - if (Channel && runningStatus != RunningStatus && (RunningStatus > SI::RunningStatusNotRunning || runningStatus > SI::RunningStatusUndefined) && Channel->HasTimer()) + if (Channel && runningStatus != RunningStatus && (RunningStatus > SI::RunningStatusNotRunning || runningStatus > SI::RunningStatusUndefined) && schedule && schedule->HasTimer()) isyslog("channel %d (%s) event %s status %d", Channel->Number(), Channel->Name(), *ToDescr(), RunningStatus); runningStatus = RunningStatus; } @@ -243,13 +245,22 @@ cString cEvent::ToDescr(void) const return cString::sprintf("%s %s-%s %s'%s'", *GetDateString(), *GetTimeString(), *GetEndTimeString(), vpsbuf, Title()); } -bool cEvent::HasTimer(void) const +void cEvent::IncNumTimers(void) const { - for (cTimer *t = Timers.First(); t; t = Timers.Next(t)) { - if (t->Event() == this) - return true; - } - return false; + numTimersMutex.Lock(); + numTimers++; + if (schedule) + schedule->IncNumTimers(); + numTimersMutex.Unlock(); +} + +void cEvent::DecNumTimers(void) const +{ + numTimersMutex.Lock(); + numTimers--; + if (schedule) + schedule->DecNumTimers(); + numTimersMutex.Unlock(); } bool cEvent::IsRunning(bool OrAboutToStart) const @@ -605,9 +616,9 @@ void ReportEpgBugFixStats(bool Force) bool PrintedStats = false; char *q = buffer; *buffer = 0; + LOCK_CHANNELS_READ; for (int c = 0; c < p->n; c++) { - cChannel *channel = Channels.GetByChannelID(p->channelIDs[c], true); - if (channel) { + if (const cChannel *Channel = Channels->GetByChannelID(p->channelIDs[c], true)) { if (!GotHits) { dsyslog("====================="); dsyslog("EPG bugfix statistics"); @@ -623,7 +634,7 @@ void ReportEpgBugFixStats(bool Force) q += snprintf(q, sizeof(buffer) - (q - buffer), "%-3d %-4d", i, p->hits); PrintedStats = true; } - q += snprintf(q, sizeof(buffer) - (q - buffer), "%s%s", delim, channel->Name()); + q += snprintf(q, sizeof(buffer) - (q - buffer), "%s%s", delim, Channel->Name()); delim = ", "; if (q - buffer > 80) { q += snprintf(q, sizeof(buffer) - (q - buffer), "%s...", delim); @@ -878,14 +889,32 @@ Final: // --- cSchedule ------------------------------------------------------------- +cMutex cSchedule::numTimersMutex; + cSchedule::cSchedule(tChannelID ChannelID) { channelID = ChannelID; + events.SetUseGarbageCollector(); + numTimers = 0; hasRunning = false; modified = 0; presentSeen = 0; } +void cSchedule::IncNumTimers(void) const +{ + numTimersMutex.Lock(); + numTimers++; + numTimersMutex.Unlock(); +} + +void cSchedule::DecNumTimers(void) const +{ + numTimersMutex.Lock(); + numTimers--; + numTimersMutex.Unlock(); +} + cEvent *cSchedule::AddEvent(cEvent *Event) { events.Add(Event); @@ -897,8 +926,6 @@ cEvent *cSchedule::AddEvent(cEvent *Event) void cSchedule::DelEvent(cEvent *Event) { if (Event->schedule == this) { - if (hasRunning && Event->IsRunning()) - ClrRunningStatus(); UnhashEvent(Event); events.Del(Event); } @@ -922,7 +949,7 @@ const cEvent *cSchedule::GetPresentEvent(void) const { const cEvent *pe = NULL; time_t now = time(NULL); - for (cEvent *p = events.First(); p; p = events.Next(p)) { + for (const cEvent *p = events.First(); p; p = events.Next(p)) { if (p->StartTime() <= now) pe = p; else if (p->StartTime() > now + 3600) @@ -962,7 +989,7 @@ const cEvent *cSchedule::GetEventAround(time_t Time) const { const cEvent *pe = NULL; time_t delta = INT_MAX; - for (cEvent *p = events.First(); p; p = events.Next(p)) { + for (const cEvent *p = events.First(); p; p = events.Next(p)) { time_t dt = Time - p->StartTime(); if (dt >= 0 && dt < delta && p->EndTime() >= Time) { delta = dt; @@ -972,7 +999,7 @@ const cEvent *cSchedule::GetEventAround(time_t Time) const return pe; } -void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Channel) +void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, const cChannel *Channel) { hasRunning = false; for (cEvent *p = events.First(); p; p = events.Next(p)) { @@ -987,6 +1014,7 @@ void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Cha if (p->RunningStatus() >= SI::RunningStatusPausing) hasRunning = true; } + SetPresentSeen(); } void cSchedule::ClrRunningStatus(cChannel *Channel) @@ -1019,32 +1047,29 @@ void cSchedule::Sort(void) p->SetRunningStatus(SI::RunningStatusNotRunning); } } + SetModified(); } void cSchedule::DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version) { if (SegmentStart > 0 && SegmentEnd > 0) { - for (cEvent *p = events.First(); p; p = events.Next(p)) { - if (p->EndTime() > SegmentStart) { - if (p->StartTime() < SegmentEnd) { - // The event overlaps with the given time segment. - if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) { - // The segment overwrites all events from tables with higher ids, and - // within the same table id all events must have the same version. - // We can't delete the event right here because a timer might have - // a pointer to it, so let's set its id and start time to 0 to have it - // "phased out": - if (hasRunning && p->IsRunning()) - ClrRunningStatus(); - UnhashEvent(p); - p->eventID = 0; - p->startTime = 0; - } - } - else - break; - } - } + cEvent *p = events.First(); + while (p) { + cEvent *n = events.Next(p); + if (p->EndTime() > SegmentStart) { + if (p->StartTime() < SegmentEnd) { + // The event overlaps with the given time segment. + if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) { + // The segment overwrites all events from tables with higher ids, and + // within the same table id all events must have the same version. + DelEvent(p); + } + } + else + break; + } + p = n; + } } } @@ -1066,9 +1091,9 @@ void cSchedule::Cleanup(time_t Time) void cSchedule::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) const { - cChannel *channel = Channels.GetByChannelID(channelID, true); - if (channel) { - fprintf(f, "%sC %s %s\n", Prefix, *channel->GetChannelID().ToString(), channel->Name()); + LOCK_CHANNELS_READ; + if (const cChannel *Channel = Channels->GetByChannelID(channelID, true)) { + fprintf(f, "%sC %s %s\n", Prefix, *Channel->GetChannelID().ToString(), Channel->Name()); const cEvent *p; switch (DumpMode) { case dmAll: { @@ -1111,12 +1136,10 @@ bool cSchedule::Read(FILE *f, cSchedules *Schedules) if (*s) { tChannelID channelID = tChannelID::FromString(s); if (channelID.Valid()) { - cSchedule *p = Schedules->AddSchedule(channelID); - if (p) { + if (cSchedule *p = Schedules->AddSchedule(channelID)) { if (!cEvent::Read(f, p)) return false; p->Sort(); - Schedules->SetModified(p); } } else { @@ -1164,12 +1187,12 @@ void cEpgDataWriter::Perform(void) { cMutexLock MutexLock(&mutex); // to make sure fore- and background calls don't cause parellel dumps! { - cSchedulesLock SchedulesLock(true, 1000); - cSchedules *s = (cSchedules *)cSchedules::Schedules(SchedulesLock); - if (s) { + cStateKey StateKey; + if (cSchedules *Schedules = cSchedules::GetSchedulesWrite(StateKey, 1000)) { time_t now = time(NULL); - for (cSchedule *p = s->First(); p; p = s->Next(p)) + for (cSchedule *p = Schedules->First(); p; p = Schedules->Next(p)) p->Cleanup(now); + StateKey.Remove(); } } if (dump) @@ -1178,29 +1201,25 @@ void cEpgDataWriter::Perform(void) static cEpgDataWriter EpgDataWriter; -// --- cSchedulesLock -------------------------------------------------------- - -cSchedulesLock::cSchedulesLock(bool WriteLock, int TimeoutMs) -{ - locked = cSchedules::schedules.rwlock.Lock(WriteLock, TimeoutMs); -} - -cSchedulesLock::~cSchedulesLock() -{ - if (locked) - cSchedules::schedules.rwlock.Unlock(); -} - // --- cSchedules ------------------------------------------------------------ cSchedules cSchedules::schedules; char *cSchedules::epgDataFileName = NULL; time_t cSchedules::lastDump = time(NULL); -time_t cSchedules::modified = 0; -const cSchedules *cSchedules::Schedules(cSchedulesLock &SchedulesLock) +cSchedules::cSchedules(void) +:cList("Schedules") { - return SchedulesLock.Locked() ? &schedules : NULL; +} + +const cSchedules *cSchedules::GetSchedulesRead(cStateKey &StateKey, int TimeoutMs) +{ + return schedules.Lock(StateKey, false, TimeoutMs) ? &schedules : NULL; +} + +cSchedules *cSchedules::GetSchedulesWrite(cStateKey &StateKey, int TimeoutMs) +{ + return schedules.Lock(StateKey, true, TimeoutMs) ? &schedules : NULL; } void cSchedules::SetEpgDataFileName(const char *FileName) @@ -1210,12 +1229,6 @@ void cSchedules::SetEpgDataFileName(const char *FileName) EpgDataWriter.SetDump(epgDataFileName != NULL); } -void cSchedules::SetModified(cSchedule *Schedule) -{ - Schedule->SetModified(); - modified = time(NULL); -} - void cSchedules::Cleanup(bool Force) { if (Force) @@ -1232,83 +1245,59 @@ void cSchedules::Cleanup(bool Force) void cSchedules::ResetVersions(void) { - cSchedulesLock SchedulesLock(true); - cSchedules *s = (cSchedules *)Schedules(SchedulesLock); - if (s) { - for (cSchedule *Schedule = s->First(); Schedule; Schedule = s->Next(Schedule)) - Schedule->ResetVersions(); - } -} - -bool cSchedules::ClearAll(void) -{ - cSchedulesLock SchedulesLock(true, 1000); - cSchedules *s = (cSchedules *)Schedules(SchedulesLock); - if (s) { - for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) - Timer->SetEvent(NULL); - for (cSchedule *Schedule = s->First(); Schedule; Schedule = s->Next(Schedule)) - Schedule->Cleanup(INT_MAX); - return true; - } - return false; + LOCK_SCHEDULES_WRITE; + for (cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->Next(Schedule)) + Schedule->ResetVersions(); } bool cSchedules::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) { - cSchedulesLock SchedulesLock; - cSchedules *s = (cSchedules *)Schedules(SchedulesLock); - if (s) { - cSafeFile *sf = NULL; - if (!f) { - sf = new cSafeFile(epgDataFileName); - if (sf->Open()) - f = *sf; - else { - LOG_ERROR; - delete sf; - return false; - } - } - for (cSchedule *p = s->First(); p; p = s->Next(p)) - p->Dump(f, Prefix, DumpMode, AtTime); - if (sf) { - sf->Close(); + cSafeFile *sf = NULL; + if (!f) { + sf = new cSafeFile(epgDataFileName); + if (sf->Open()) + f = *sf; + else { + LOG_ERROR; delete sf; + return false; } - return true; } - return false; + LOCK_SCHEDULES_READ; + for (const cSchedule *p = Schedules->First(); p; p = Schedules->Next(p)) + p->Dump(f, Prefix, DumpMode, AtTime); + if (sf) { + sf->Close(); + delete sf; + } + return true; } bool cSchedules::Read(FILE *f) { - cSchedulesLock SchedulesLock(true, 1000); - cSchedules *s = (cSchedules *)Schedules(SchedulesLock); - if (s) { - bool OwnFile = f == NULL; - if (OwnFile) { - if (epgDataFileName && access(epgDataFileName, R_OK) == 0) { - dsyslog("reading EPG data from %s", epgDataFileName); - if ((f = fopen(epgDataFileName, "r")) == NULL) { - LOG_ERROR; - return false; - } - } - else + bool OwnFile = f == NULL; + if (OwnFile) { + if (epgDataFileName && access(epgDataFileName, R_OK) == 0) { + dsyslog("reading EPG data from %s", epgDataFileName); + if ((f = fopen(epgDataFileName, "r")) == NULL) { + LOG_ERROR; return false; + } } - bool result = cSchedule::Read(f, s); - if (OwnFile) - fclose(f); - if (result) { - // Initialize the channels' schedule pointers, so that the first WhatsOn menu will come up faster: - for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) - s->GetSchedule(Channel); - } - return result; + else + return false; } - return false; + LOCK_CHANNELS_WRITE; + LOCK_SCHEDULES_WRITE; + bool result = cSchedule::Read(f, Schedules); + if (OwnFile) + fclose(f); + if (result) { + // Initialize the channels' schedule pointers, so that the first WhatsOn menu will come up faster: + for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) + Schedules->GetSchedule(Channel); + } + return result; } cSchedule *cSchedules::AddSchedule(tChannelID ChannelID) @@ -1318,9 +1307,6 @@ cSchedule *cSchedules::AddSchedule(tChannelID ChannelID) if (!p) { p = new cSchedule(ChannelID); Add(p); - cChannel *channel = Channels.GetByChannelID(ChannelID); - if (channel) - channel->schedule = p; } return p; } @@ -1328,7 +1314,7 @@ cSchedule *cSchedules::AddSchedule(tChannelID ChannelID) const cSchedule *cSchedules::GetSchedule(tChannelID ChannelID) const { ChannelID.ClrRid(); - for (cSchedule *p = First(); p; p = Next(p)) { + for (const cSchedule *p = First(); p; p = Next(p)) { if (p->ChannelID() == ChannelID) return p; } @@ -1541,18 +1527,18 @@ void cEpgHandlers::DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t Schedule->DropOutdated(SegmentStart, SegmentEnd, TableID, Version); } -void cEpgHandlers::BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus) +void cEpgHandlers::BeginSegmentTransfer(const cChannel *Channel) { for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { - if (eh->BeginSegmentTransfer(Channel, OnlyRunningStatus)) + if (eh->BeginSegmentTransfer(Channel, false)) return; } } -void cEpgHandlers::EndSegmentTransfer(bool Modified, bool OnlyRunningStatus) +void cEpgHandlers::EndSegmentTransfer(bool Modified) { for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { - if (eh->EndSegmentTransfer(Modified, OnlyRunningStatus)) + if (eh->EndSegmentTransfer(Modified, false)) return; } } diff --git a/epg.h b/epg.h index 65fbcbf9..b6f7d68e 100644 --- a/epg.h +++ b/epg.h @@ -7,7 +7,7 @@ * Original version (as used in VDR before 1.3.0) written by * Robert Schneider and Rolf Hakenes . * - * $Id: epg.h 3.1 2013/08/23 10:50:05 kls Exp $ + * $Id: epg.h 4.1 2015/08/09 11:25:04 kls Exp $ */ #ifndef __EPG_H @@ -66,13 +66,15 @@ public: class cSchedule; -typedef u_int32_t tEventID; +typedef u_int16_t tEventID; class cEvent : public cListObject { friend class cSchedule; private: + static cMutex numTimersMutex; // Protects numTimers, because it might be accessed from parallel read locks // The sequence of these parameters is optimized for minimal memory waste! cSchedule *schedule; // The Schedule this event belongs to + mutable u_int16_t numTimers;// The number of timers that use this event tEventID eventID; // Event ID of this event uchar tableID; // Table ID this event came from uchar version; // Version number of section this event came from @@ -109,7 +111,9 @@ public: time_t Vps(void) const { return vps; } time_t Seen(void) const { return seen; } bool SeenWithin(int Seconds) const { return time(NULL) - seen < Seconds; } - bool HasTimer(void) const; + void IncNumTimers(void) const; + void DecNumTimers(void) const; + bool HasTimer(void) const { return numTimers > 0; } bool IsRunning(bool OrAboutToStart = false) const; static const char *ContentToString(uchar Content); cString GetParentalRatingString(void) const; @@ -120,7 +124,7 @@ public: void SetEventID(tEventID EventID); void SetTableID(uchar TableID); void SetVersion(uchar Version); - void SetRunningStatus(int RunningStatus, cChannel *Channel = NULL); + void SetRunningStatus(int RunningStatus, const cChannel *Channel = NULL); void SetTitle(const char *Title); void SetShortText(const char *ShortText); void SetDescription(const char *Description); @@ -142,28 +146,33 @@ class cSchedules; class cSchedule : public cListObject { private: + static cMutex numTimersMutex; // Protects numTimers, because it might be accessed from parallel read locks tChannelID channelID; cList events; cHash eventsHashID; cHash eventsHashStartTime; + mutable u_int16_t numTimers;// The number of timers that use this schedule bool hasRunning; - time_t modified; + int modified; time_t presentSeen; public: cSchedule(tChannelID ChannelID); tChannelID ChannelID(void) const { return channelID; } - time_t Modified(void) const { return modified; } + bool Modified(int &State) const { bool Result = State != modified; State = modified; return Result; } time_t PresentSeen(void) const { return presentSeen; } bool PresentSeenWithin(int Seconds) const { return time(NULL) - presentSeen < Seconds; } - void SetModified(void) { modified = time(NULL); } + void SetModified(void) { modified++; } void SetPresentSeen(void) { presentSeen = time(NULL); } - void SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Channel = NULL); + void SetRunningStatus(cEvent *Event, int RunningStatus, const cChannel *Channel = NULL); void ClrRunningStatus(cChannel *Channel = NULL); void ResetVersions(void); void Sort(void); void DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version); void Cleanup(time_t Time); void Cleanup(void); + void IncNumTimers(void) const; + void DecNumTimers(void) const; + bool HasTimer(void) const { return numTimers > 0; } cEvent *AddEvent(cEvent *Event); void DelEvent(cEvent *Event); void HashEvent(cEvent *Event); @@ -177,35 +186,23 @@ public: static bool Read(FILE *f, cSchedules *Schedules); }; -class cSchedulesLock { -private: - bool locked; -public: - cSchedulesLock(bool WriteLock = false, int TimeoutMs = 0); - ~cSchedulesLock(); - bool Locked(void) { return locked; } - }; - class cSchedules : public cList { friend class cSchedule; - friend class cSchedulesLock; private: - cRwLock rwlock; static cSchedules schedules; static char *epgDataFileName; static time_t lastDump; - static time_t modified; public: + cSchedules(void); + static const cSchedules *GetSchedulesRead(cStateKey &StateKey, int TimeoutMs = 0); + ///< Gets the list of schedules for read access. + ///< See cTimers::GetTimersRead() for details. + static cSchedules *GetSchedulesWrite(cStateKey &StateKey, int TimeoutMs = 0); + ///< Gets the list of schedules for write access. + ///< See cTimers::GetTimersWrite() for details. static void SetEpgDataFileName(const char *FileName); - static const cSchedules *Schedules(cSchedulesLock &SchedulesLock); - ///< Caller must provide a cSchedulesLock which has to survive the entire - ///< time the returned cSchedules is accessed. Once the cSchedules is no - ///< longer used, the cSchedulesLock must be destroyed. - static time_t Modified(void) { return modified; } - static void SetModified(cSchedule *Schedule); static void Cleanup(bool Force = false); static void ResetVersions(void); - static bool ClearAll(void); static bool Dump(FILE *f = NULL, const char *Prefix = "", eDumpMode DumpMode = dmAll, time_t AtTime = 0); static bool Read(FILE *f = NULL); cSchedule *AddSchedule(tChannelID ChannelID); @@ -213,6 +210,17 @@ public: const cSchedule *GetSchedule(const cChannel *Channel, bool AddIfMissing = false) const; }; +// Provide lock controlled access to the list: + +DEF_LIST_LOCK(Schedules); + +// These macros provide a convenient way of locking the global schedules list +// and making sure the lock is released as soon as the current scope is left +// (note that these macros wait forever to obtain the lock!): + +#define LOCK_SCHEDULES_READ USE_LIST_LOCK_READ(Schedules); +#define LOCK_SCHEDULES_WRITE USE_LIST_LOCK_WRITE(Schedules); + class cEpgDataReader : public cThread { public: cEpgDataReader(void); @@ -273,12 +281,14 @@ public: virtual bool DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version) { return false; } ///< Takes a look at all EPG events between SegmentStart and SegmentEnd and ///< drops outdated events. - virtual bool BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus) { return false; } + virtual bool BeginSegmentTransfer(const cChannel *Channel, bool Dummy) { return false; } // TODO remove obsolete Dummy ///< Called directly after IgnoreChannel() before any other handler method is called. ///< Designed to give handlers the possibility to prepare a database transaction. - virtual bool EndSegmentTransfer(bool Modified, bool OnlyRunningStatus) { return false; } + ///< Dummy is for backward compatibility and may be removed in a future version. + virtual bool EndSegmentTransfer(bool Modified, bool Dummy) { return false; } // TODO remove obsolete Dummy ///< Called after the segment data has been processed. ///< At this point handlers should close/commit/rollback any pending database transactions. + ///< Dummy is for backward compatibility and may be removed in a future version. }; class cEpgHandlers : public cList { @@ -301,8 +311,8 @@ public: void HandleEvent(cEvent *Event); void SortSchedule(cSchedule *Schedule); void DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version); - void BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus); - void EndSegmentTransfer(bool Modified, bool OnlyRunningStatus); + void BeginSegmentTransfer(const cChannel *Channel); + void EndSegmentTransfer(bool Modified); }; extern cEpgHandlers EpgHandlers; diff --git a/filter.c b/filter.c index 331983a8..aa91c1f1 100644 --- a/filter.c +++ b/filter.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: filter.c 4.1 2015/03/17 15:04:39 kls Exp $ + * $Id: filter.c 4.2 2015/07/25 10:59:57 kls Exp $ */ #include "filter.h" @@ -19,31 +19,38 @@ cSectionSyncer::cSectionSyncer(void) void cSectionSyncer::Reset(void) { - lastVersion = thisVersion = 0xFF; - nextNumber = 0; + currentVersion = -1; + currentSection = -1; + synced = false; + complete = false; + memset(sections, 0x00, sizeof(sections)); } void cSectionSyncer::Repeat(void) { - lastVersion = 0xFF; - nextNumber--; + SetSectionFlag(currentSection, false); + synced = false; + complete = false; } bool cSectionSyncer::Sync(uchar Version, int Number, int LastNumber) { - if (Version != lastVersion) { - if (Version != thisVersion) { - thisVersion = Version; - nextNumber = 0; - } - if (Number == nextNumber) { - if (Number == LastNumber) - lastVersion = Version; - nextNumber++; - return true; - } + if (Version != currentVersion) { + Reset(); + currentVersion = Version; } - return false; + if (!synced) { + if (Number != 0) + return false; + else + synced = true; + } + currentSection = Number; + bool Result = !GetSectionFlag(Number); + SetSectionFlag(Number, true); + if (Number == LastNumber) + complete = true; + return Result; } // --- cFilterData ----------------------------------------------------------- diff --git a/filter.h b/filter.h index 282a1e90..7cc3ec32 100644 --- a/filter.h +++ b/filter.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: filter.h 4.1 2015/03/17 15:00:08 kls Exp $ + * $Id: filter.h 4.2 2015/07/25 10:03:44 kls Exp $ */ #ifndef __FILTER_H @@ -15,13 +15,18 @@ class cSectionSyncer { private: - int lastVersion; - int thisVersion; - int nextNumber; + int currentVersion; + int currentSection; + bool synced; + bool complete; + uchar sections[32]; // holds 32 * 8 = 256 bits, as flags for the sections + void SetSectionFlag(uchar Section, bool On) { if (On) sections[Section / 8] |= (1 << (Section % 8)); else sections[Section / 8] &= ~(1 << (Section % 8)); } + bool GetSectionFlag(uchar Section) { return sections[Section / 8] & (1 << (Section % 8)); } public: cSectionSyncer(void); void Reset(void); void Repeat(void); + bool Complete(void) { return complete; } bool Sync(uchar Version, int Number, int LastNumber); }; diff --git a/menu.c b/menu.c index c5a25981..9a35bb70 100644 --- a/menu.c +++ b/menu.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.c 4.2 2015/04/18 13:20:41 kls Exp $ + * $Id: menu.c 4.3 2015/08/31 13:36:05 kls Exp $ */ #include "menu.h" @@ -48,8 +48,8 @@ #define NODISKSPACEDELTA 300 // seconds between "Not enough disk space to start recording!" messages #define MAXCHNAMWIDTH 16 // maximum number of characters of channels' short names shown in schedules menus -#define CHNUMWIDTH (numdigits(Channels.MaxNumber()) + 1) -#define CHNAMWIDTH (min(MAXCHNAMWIDTH, Channels.MaxShortChannelNameLength() + 1)) +#define CHNUMWIDTH (numdigits(cChannels::MaxNumber()) + 1) +#define CHNAMWIDTH (min(MAXCHNAMWIDTH, cChannels::MaxShortChannelNameLength() + 1)) // --- cMenuEditCaItem ------------------------------------------------------- @@ -159,20 +159,23 @@ eOSState cMenuEditSrcItem::ProcessKey(eKeys Key) class cMenuEditChannel : public cOsdMenu { private: + cStateKey *channelsStateKey; cChannel *channel; cChannel data; cSourceParam *sourceParam; char name[256]; void Setup(void); public: - cMenuEditChannel(cChannel *Channel, bool New = false); + cMenuEditChannel(cStateKey *ChannelsStateKey, cChannel *Channel, bool New = false); + cChannel *Channel(void) { return channel; } virtual eOSState ProcessKey(eKeys Key); }; -cMenuEditChannel::cMenuEditChannel(cChannel *Channel, bool New) +cMenuEditChannel::cMenuEditChannel(cStateKey *ChannelsStateKey, cChannel *Channel, bool New) :cOsdMenu(tr("Edit channel"), 16) { SetMenuCategory(mcChannelEdit); + channelsStateKey = ChannelsStateKey; channel = Channel; sourceParam = NULL; *name = 0; @@ -239,32 +242,37 @@ eOSState cMenuEditChannel::ProcessKey(eKeys Key) if (state == osUnknown) { if (Key == kOk) { + cChannels *Channels =cChannels::GetChannelsWrite(*channelsStateKey); + bool Modified = false; if (sourceParam) sourceParam->GetData(&data); - if (Channels.HasUniqueChannelID(&data, channel)) { + if (Channels->HasUniqueChannelID(&data, channel)) { data.name = strcpyrealloc(data.name, name); if (channel) { *channel = data; - isyslog("edited channel %d %s", channel->Number(), *data.ToText()); + isyslog("edited channel %d %s", channel->Number(), *channel->ToText()); state = osBack; } else { channel = new cChannel; *channel = data; - Channels.Add(channel); - Channels.ReNumber(); - isyslog("added channel %d %s", channel->Number(), *data.ToText()); + Channels->Add(channel); + Channels->ReNumber(); + isyslog("added channel %d %s", channel->Number(), *channel->ToText()); state = osUser1; } - Channels.SetModified(true); + Channels->SetModifiedByUser(); + Modified = true; } else { Skins.Message(mtError, tr("Channel settings are not unique!")); state = osContinue; } + channelsStateKey->Remove(Modified); } } if (Key != kNone && (data.source & cSource::st_Mask) != (oldSource & cSource::st_Mask)) { + LOCK_CHANNELS_WRITE; if (sourceParam) sourceParam->GetData(&data); Setup(); @@ -279,21 +287,21 @@ public: enum eChannelSortMode { csmNumber, csmName, csmProvider }; private: static eChannelSortMode sortMode; - cChannel *channel; + const cChannel *channel; public: - cMenuChannelItem(cChannel *Channel); + cMenuChannelItem(const cChannel *Channel); static void SetSortMode(eChannelSortMode SortMode) { sortMode = SortMode; } static void IncSortMode(void) { sortMode = eChannelSortMode((sortMode == csmProvider) ? csmNumber : sortMode + 1); } static eChannelSortMode SortMode(void) { return sortMode; } virtual int Compare(const cListObject &ListObject) const; virtual void Set(void); - cChannel *Channel(void) { return channel; } + const cChannel *Channel(void) { return channel; } virtual void SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable); }; cMenuChannelItem::eChannelSortMode cMenuChannelItem::sortMode = csmNumber; -cMenuChannelItem::cMenuChannelItem(cChannel *Channel) +cMenuChannelItem::cMenuChannelItem(const cChannel *Channel) { channel = Channel; if (channel->GroupSep()) @@ -340,11 +348,12 @@ void cMenuChannelItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, boo class cMenuChannels : public cOsdMenu { private: + cStateKey channelsStateKey; int number; cTimeMs numberTimer; - void Setup(void); + void Set(bool Force = false); cChannel *GetChannel(int Index); - void Propagate(void); + void Propagate(cChannels *Channels); protected: eOSState Number(eKeys Key); eOSState Switch(void); @@ -363,38 +372,41 @@ cMenuChannels::cMenuChannels(void) { SetMenuCategory(mcChannel); number = 0; - Setup(); - Channels.IncBeingEdited(); + Set(); } cMenuChannels::~cMenuChannels() { - Channels.DecBeingEdited(); } -void cMenuChannels::Setup(void) +void cMenuChannels::Set(bool Force) { - cChannel *currentChannel = GetChannel(Current()); - if (!currentChannel) - currentChannel = Channels.GetByNumber(cDevice::CurrentChannel()); - cMenuChannelItem *currentItem = NULL; - Clear(); - for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) { - if (!channel->GroupSep() || cMenuChannelItem::SortMode() == cMenuChannelItem::csmNumber && *channel->Name()) { - cMenuChannelItem *item = new cMenuChannelItem(channel); - Add(item); - if (channel == currentChannel) - currentItem = item; + if (Force) + channelsStateKey.Reset(); + if (const cChannels *Channels = cChannels::GetChannelsRead(channelsStateKey)) { + const cChannel *CurrentChannel = GetChannel(Current()); + if (!CurrentChannel) + CurrentChannel = Channels->GetByNumber(cDevice::CurrentChannel()); + cMenuChannelItem *CurrentItem = NULL; + Clear(); + for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { + if (!Channel->GroupSep() || cMenuChannelItem::SortMode() == cMenuChannelItem::csmNumber && *Channel->Name()) { + cMenuChannelItem *Item = new cMenuChannelItem(Channel); + Add(Item); + if (Channel == CurrentChannel) + CurrentItem = Item; + } } - } - SetMenuSortMode(cMenuChannelItem::SortMode() == cMenuChannelItem::csmName ? msmName : - cMenuChannelItem::SortMode() == cMenuChannelItem::csmProvider ? msmProvider : - msmNumber); - if (cMenuChannelItem::SortMode() != cMenuChannelItem::csmNumber) - Sort(); - SetCurrent(currentItem); - SetHelp(tr("Button$Edit"), tr("Button$New"), tr("Button$Delete"), tr("Button$Mark")); - Display(); + SetMenuSortMode(cMenuChannelItem::SortMode() == cMenuChannelItem::csmName ? msmName : + cMenuChannelItem::SortMode() == cMenuChannelItem::csmProvider ? msmProvider : + msmNumber); + if (cMenuChannelItem::SortMode() != cMenuChannelItem::csmNumber) + Sort(); + SetCurrent(CurrentItem); + SetHelp(tr("Button$Edit"), tr("Button$New"), tr("Button$Delete"), tr("Button$Mark")); + Display(); + channelsStateKey.Remove(); + } } cChannel *cMenuChannels::GetChannel(int Index) @@ -403,13 +415,13 @@ cChannel *cMenuChannels::GetChannel(int Index) return p ? (cChannel *)p->Channel() : NULL; } -void cMenuChannels::Propagate(void) +void cMenuChannels::Propagate(cChannels *Channels) { - Channels.ReNumber(); + Channels->ReNumber(); for (cMenuChannelItem *ci = (cMenuChannelItem *)First(); ci; ci = (cMenuChannelItem *)ci->Next()) ci->Set(); Display(); - Channels.SetModified(true); + Channels->SetModifiedByUser(); } eOSState cMenuChannels::Number(eKeys Key) @@ -420,9 +432,10 @@ eOSState cMenuChannels::Number(eKeys Key) number = 0; if (!number && Key == k0) { cMenuChannelItem::IncSortMode(); - Setup(); + Set(true); } else { + LOCK_CHANNELS_READ; number = number * 10 + Key - k0; for (cMenuChannelItem *ci = (cMenuChannelItem *)First(); ci; ci = (cMenuChannelItem *)ci->Next()) { if (!ci->Channel()->GroupSep() && ci->Channel()->Number() == number) { @@ -440,6 +453,7 @@ eOSState cMenuChannels::Switch(void) { if (HasSubMenu()) return osContinue; + LOCK_CHANNELS_READ; cChannel *ch = GetChannel(Current()); if (ch) return cDevice::PrimaryDevice()->SwitchChannel(ch, true) ? osEnd : osContinue; @@ -450,9 +464,10 @@ eOSState cMenuChannels::Edit(void) { if (HasSubMenu() || Count() == 0) return osContinue; + LOCK_CHANNELS_READ; cChannel *ch = GetChannel(Current()); if (ch) - return AddSubMenu(new cMenuEditChannel(ch)); + return AddSubMenu(new cMenuEditChannel(&channelsStateKey, ch)); return osContinue; } @@ -460,79 +475,97 @@ eOSState cMenuChannels::New(void) { if (HasSubMenu()) return osContinue; - return AddSubMenu(new cMenuEditChannel(GetChannel(Current()), true)); + LOCK_CHANNELS_READ; + return AddSubMenu(new cMenuEditChannel(&channelsStateKey, GetChannel(Current()), true)); } eOSState cMenuChannels::Delete(void) { if (!HasSubMenu() && Count() > 0) { - int CurrentChannelNr = cDevice::CurrentChannel(); - cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr); + LOCK_TIMERS_READ; // must lock timers before channels! + cChannels *Channels = cChannels::GetChannelsWrite(channelsStateKey); int Index = Current(); - cChannel *channel = GetChannel(Current()); - int DeletedChannel = channel->Number(); + cChannel *Channel = GetChannel(Current()); + if (!Channels->Contains(Channel)) { + channelsStateKey.Remove(false); + channelsStateKey.Reset(); // makes sure the menu is refreshed + return osContinue; + } + bool Deleted = false; + int CurrentChannelNr = cDevice::CurrentChannel(); + cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr); + int DeletedChannel = Channel->Number(); // Check if there is a timer using this channel: - if (channel->HasTimer()) { + if (Timers->UsesChannel(Channel)) { Skins.Message(mtError, tr("Channel is being used by a timer!")); return osContinue; } if (Interface->Confirm(tr("Delete channel?"))) { - if (CurrentChannel && channel == CurrentChannel) { - int n = Channels.GetNextNormal(CurrentChannel->Index()); + if (CurrentChannel && Channel == CurrentChannel) { + int n = Channels->GetNextNormal(CurrentChannel->Index()); if (n < 0) - n = Channels.GetPrevNormal(CurrentChannel->Index()); - CurrentChannel = Channels.Get(n); + n = Channels->GetPrevNormal(CurrentChannel->Index()); + CurrentChannel = Channels->Get(n); CurrentChannelNr = 0; // triggers channel switch below } - Channels.Del(channel); + Channels->Del(Channel); cOsdMenu::Del(Index); - Propagate(); - Channels.SetModified(true); + Propagate(Channels); + Channels->SetModifiedByUser(); isyslog("channel %d deleted", DeletedChannel); + Deleted = true; if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) - Channels.SwitchTo(CurrentChannel->Number()); + Channels->SwitchTo(CurrentChannel->Number()); else cDevice::SetCurrentChannel(CurrentChannel); } } + channelsStateKey.Remove(Deleted); } return osContinue; } void cMenuChannels::Move(int From, int To) { - int CurrentChannelNr = cDevice::CurrentChannel(); - cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr); - cChannel *FromChannel = GetChannel(From); - cChannel *ToChannel = GetChannel(To); - if (FromChannel && ToChannel) { - int FromNumber = FromChannel->Number(); - int ToNumber = ToChannel->Number(); - Channels.Move(FromChannel, ToChannel); - cOsdMenu::Move(From, To); - Propagate(); - Channels.SetModified(true); - isyslog("channel %d moved to %d", FromNumber, ToNumber); - if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { - if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) - Channels.SwitchTo(CurrentChannel->Number()); - else - cDevice::SetCurrentChannel(CurrentChannel); + if (cChannels *Channels = cChannels::GetChannelsWrite(channelsStateKey)) { + int CurrentChannelNr = cDevice::CurrentChannel(); + cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr); + cChannel *FromChannel = GetChannel(From); + cChannel *ToChannel = GetChannel(To); + if (FromChannel && ToChannel) { + int FromNumber = FromChannel->Number(); + int ToNumber = ToChannel->Number(); + Channels->Move(FromChannel, ToChannel); + cOsdMenu::Move(From, To); + Propagate(Channels); + Channels->SetModifiedByUser(); + isyslog("channel %d moved to %d", FromNumber, ToNumber); + if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { + if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) + Channels->SwitchTo(CurrentChannel->Number()); + else + cDevice::SetCurrentChannel(CurrentChannel); + } } + channelsStateKey.Remove(); } } eOSState cMenuChannels::ProcessKey(eKeys Key) { + if (!HasSubMenu()) + Set(); // react on any changes to the channels list eOSState state = cOsdMenu::ProcessKey(Key); switch (state) { case osUser1: { - cChannel *channel = Channels.Last(); - if (channel) { - Add(new cMenuChannelItem(channel), true); - return CloseSubMenu(); + if (cMenuEditChannel *MenuEditChannel = dynamic_cast(SubMenu())) { + if (cChannel *Channel = MenuEditChannel->Channel()) { + LOCK_CHANNELS_READ; + Add(new cMenuChannelItem(Channel), true); + return CloseSubMenu(); + } } } break; @@ -767,7 +800,7 @@ void cMenuFolder::SetHelpKeys(void) } #define FOLDERDELIMCHARSUBST 0x01 -static void AddRecordingFolders(cList *List, char *Path) +static void AddRecordingFolders(const cRecordings *Recordings, cList *List, char *Path) { if (Path) { char *p = strchr(Path, FOLDERDELIMCHARSUBST); @@ -782,13 +815,12 @@ static void AddRecordingFolders(cList *List, char *Path) List->Add(Folder = new cNestedItem(Path)); if (p) { Folder->SetSubItems(true); - AddRecordingFolders(Folder->SubItems(), p); + AddRecordingFolders(Recordings, Folder->SubItems(), p); } } else { - cThreadLock RecordingsLock(&Recordings); cStringList Dirs; - for (cRecording *Recording = Recordings.First(); Recording; Recording = Recordings.Next(Recording)) { + for (const cRecording *Recording = Recordings->First(); Recording; Recording = Recordings->Next(Recording)) { cString Folder = Recording->Folder(); strreplace((char *)*Folder, FOLDERDELIMCHAR, FOLDERDELIMCHARSUBST); // makes sure parent folders come before subfolders if (Dirs.Find(Folder) < 0) @@ -796,18 +828,21 @@ static void AddRecordingFolders(cList *List, char *Path) } Dirs.Sort(); for (int i = 0; i < Dirs.Size(); i++) { - char *s = Dirs[i]; - if (*s) - AddRecordingFolders(&Folders, s); + if (char *s = Dirs[i]) + AddRecordingFolders(Recordings, &Folders, s); } } } void cMenuFolder::Set(const char *CurrentFolder) { - static int RecordingsState = -1; - if (list == &Folders && Recordings.StateChanged(RecordingsState)) - AddRecordingFolders(&Folders, NULL); + static cStateKey RecordingsStateKey; + if (list == &Folders) { + if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(RecordingsStateKey)) { + AddRecordingFolders(Recordings, &Folders, NULL); + RecordingsStateKey.Remove(); + } + } firstFolder = NULL; Clear(); if (!isempty(dir)) { @@ -937,10 +972,13 @@ eOSState cMenuFolder::ProcessKey(eKeys Key) // --- cMenuEditTimer -------------------------------------------------------- +const cTimer *cMenuEditTimer::addedTimer = NULL; + cMenuEditTimer::cMenuEditTimer(cTimer *Timer, bool New) :cOsdMenu(tr("Edit timer"), 12) { SetMenuCategory(mcTimerEdit); + addedTimer = NULL; file = NULL; day = firstday = NULL; timer = Timer; @@ -962,14 +1000,19 @@ cMenuEditTimer::cMenuEditTimer(cTimer *Timer, bool New) SetFirstDayItem(); } SetHelpKeys(); - Timers.IncBeingEdited(); } cMenuEditTimer::~cMenuEditTimer() { if (timer && addIfConfirmed) delete timer; // apparently it wasn't confirmed - Timers.DecBeingEdited(); +} + +const cTimer *cMenuEditTimer::AddedTimer(void) +{ + const cTimer *Timer = addedTimer; + addedTimer = NULL; + return Timer; } void cMenuEditTimer::SetHelpKeys(void) @@ -1015,28 +1058,32 @@ eOSState cMenuEditTimer::ProcessKey(eKeys Key) if (state == osUnknown) { switch (Key) { - case kOk: { - cChannel *ch = Channels.GetByNumber(channel); - if (ch) - data.channel = ch; - else { - Skins.Message(mtError, tr("*** Invalid Channel ***")); - break; - } - if (!*data.file) - strcpy(data.file, data.Channel()->ShortName(true)); - if (timer) { - if (memcmp((void *)timer, &data, sizeof(data)) != 0) - *timer = data; - if (addIfConfirmed) - Timers.Add(timer); - timer->SetEventFromSchedule(); - timer->Matches(); - Timers.SetModified(); - isyslog("timer %s %s (%s)", *timer->ToDescr(), addIfConfirmed ? "added" : "modified", timer->HasFlags(tfActive) ? "active" : "inactive"); - addIfConfirmed = false; - } - } + case kOk: if (timer) { + LOCK_TIMERS_WRITE; + if (!addIfConfirmed && !Timers->Contains(timer)) { + Skins.Message(mtWarning, tr("Timer has been deleted!")); + break; + } + LOCK_CHANNELS_READ; + if (const cChannel *Channel = Channels->GetByNumber(channel)) + data.channel = Channel; + else { + Skins.Message(mtError, tr("*** Invalid Channel ***")); + break; + } + if (!*data.file) + strcpy(data.file, data.Channel()->ShortName(true)); + *timer = data; + if (addIfConfirmed) { + Timers->Add(timer); + addedTimer = timer; + } + isyslog("timer %s %s (%s)", *timer->ToDescr(), addIfConfirmed ? "added" : "modified", timer->HasFlags(tfActive) ? "active" : "inactive"); + LOCK_SCHEDULES_READ; + timer->SetEventFromSchedule(Schedules); + timer->Matches(); + addIfConfirmed = false; + } return osBack; case kRed: return AddSubMenu(new cMenuFolder(tr("Select folder"), &Folders, data.file)); case kGreen: if (day) { @@ -1063,16 +1110,16 @@ eOSState cMenuEditTimer::ProcessKey(eKeys Key) class cMenuTimerItem : public cOsdItem { private: - cTimer *timer; + const cTimer *timer; public: - cMenuTimerItem(cTimer *Timer); + cMenuTimerItem(const cTimer *Timer); virtual int Compare(const cListObject &ListObject) const; virtual void Set(void); - cTimer *Timer(void) { return timer; } + const cTimer *Timer(void) { return timer; } virtual void SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable); }; -cMenuTimerItem::cMenuTimerItem(cTimer *Timer) +cMenuTimerItem::cMenuTimerItem(const cTimer *Timer) { timer = Timer; Set(); @@ -1128,13 +1175,15 @@ void cMenuTimerItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool class cMenuTimers : public cOsdMenu { private: + cStateKey timersStateKey; int helpKeys; + void Set(void); eOSState Edit(void); eOSState New(void); eOSState Delete(void); eOSState OnOff(void); eOSState Info(void); - cTimer *CurrentTimer(void); + cTimer *GetTimer(void); void SetHelpKeys(void); public: cMenuTimers(void); @@ -1147,33 +1196,45 @@ cMenuTimers::cMenuTimers(void) { SetMenuCategory(mcTimer); helpKeys = -1; - for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) { - timer->SetEventFromSchedule(); // make sure the event is current - Add(new cMenuTimerItem(timer)); - } - Sort(); - SetCurrent(First()); - SetHelpKeys(); - Timers.IncBeingEdited(); + cMenuEditTimer::AddedTimer(); // to clear any leftovers + Set(); } cMenuTimers::~cMenuTimers() { - Timers.DecBeingEdited(); } -cTimer *cMenuTimers::CurrentTimer(void) +void cMenuTimers::Set(void) +{ + if (const cTimers *Timers = cTimers::GetTimersRead(timersStateKey)) { + const cTimer *CurrentTimer = GetTimer(); + cMenuTimerItem *CurrentItem = NULL; + Clear(); + for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) { + cMenuTimerItem *Item = new cMenuTimerItem(Timer); + Add(Item); + if (Timer == CurrentTimer) + CurrentItem = Item; + } + Sort(); + SetCurrent(CurrentItem ? CurrentItem : First()); + SetHelpKeys(); + Display(); + timersStateKey.Remove(); + } +} + +cTimer *cMenuTimers::GetTimer(void) { cMenuTimerItem *item = (cMenuTimerItem *)Get(Current()); - return item ? item->Timer() : NULL; + return item ? (cTimer *)item->Timer() : NULL; } void cMenuTimers::SetHelpKeys(void) { int NewHelpKeys = 0; - cTimer *timer = CurrentTimer(); - if (timer) { - if (timer->Event()) + if (const cTimer *Timer = GetTimer()) { + if (Timer->Event()) NewHelpKeys = 2; else NewHelpKeys = 1; @@ -1188,18 +1249,20 @@ eOSState cMenuTimers::OnOff(void) { if (HasSubMenu()) return osContinue; - cTimer *timer = CurrentTimer(); - if (timer) { - timer->OnOff(); - timer->SetEventFromSchedule(); + cTimers::GetTimersWrite(timersStateKey); + cTimer *Timer = GetTimer(); + if (Timer) { + Timer->OnOff(); + LOCK_SCHEDULES_READ; + Timer->SetEventFromSchedule(Schedules); RefreshCurrent(); DisplayCurrent(true); - if (timer->FirstDay()) - isyslog("timer %s first day set to %s", *timer->ToDescr(), *timer->PrintFirstDay()); + if (Timer->FirstDay()) + isyslog("timer %s first day set to %s", *Timer->ToDescr(), *Timer->PrintFirstDay()); else - isyslog("timer %s %sactivated", *timer->ToDescr(), timer->HasFlags(tfActive) ? "" : "de"); - Timers.SetModified(); + isyslog("timer %s %sactivated", *Timer->ToDescr(), Timer->HasFlags(tfActive) ? "" : "de"); } + timersStateKey.Remove(Timer != NULL); return osContinue; } @@ -1207,8 +1270,8 @@ eOSState cMenuTimers::Edit(void) { if (HasSubMenu() || Count() == 0) return osContinue; - isyslog("editing timer %s", *CurrentTimer()->ToDescr()); - return AddSubMenu(new cMenuEditTimer(CurrentTimer())); + isyslog("editing timer %s", *GetTimer()->ToDescr()); + return AddSubMenu(new cMenuEditTimer(GetTimer())); } eOSState cMenuTimers::New(void) @@ -1220,25 +1283,28 @@ eOSState cMenuTimers::New(void) eOSState cMenuTimers::Delete(void) { + cTimers *Timers = cTimers::GetTimersWrite(timersStateKey); // Check if this timer is active: - cTimer *ti = CurrentTimer(); - if (ti) { + cTimer *Timer = GetTimer(); + if (Timer) { if (Interface->Confirm(tr("Delete timer?"))) { - if (ti->Recording()) { + if (Timer->Recording()) { if (Interface->Confirm(tr("Timer still recording - really delete?"))) { - ti->Skip(); - cRecordControls::Process(time(NULL)); + Timer->Skip(); + cRecordControls::Process(Timers, time(NULL)); } else - return osContinue; + Timer = NULL; + } + if (Timer) { + isyslog("deleting timer %s", *Timer->ToDescr()); + Timers->Del(Timer); + cOsdMenu::Del(Current()); + Display(); } - isyslog("deleting timer %s", *ti->ToDescr()); - Timers.Del(ti); - cOsdMenu::Del(Current()); - Timers.SetModified(); - Display(); } } + timersStateKey.Remove(Timer != NULL); return osContinue; } @@ -1246,17 +1312,19 @@ eOSState cMenuTimers::Info(void) { if (HasSubMenu() || Count() == 0) return osContinue; - cTimer *ti = CurrentTimer(); - if (ti && ti->Event()) - return AddSubMenu(new cMenuEvent(ti->Event())); + LOCK_TIMERS_READ; + LOCK_CHANNELS_READ; + cTimer *Timer = GetTimer(); + if (Timer && Timer->Event()) + return AddSubMenu(new cMenuEvent(Timers, Channels, Timer->Event())); return osContinue; } eOSState cMenuTimers::ProcessKey(eKeys Key) { - int TimerNumber = HasSubMenu() ? Count() : -1; + if (!HasSubMenu()) + Set(); eOSState state = cOsdMenu::ProcessKey(Key); - if (state == osUnknown) { switch (Key) { case kOk: return Edit(); @@ -1269,9 +1337,10 @@ eOSState cMenuTimers::ProcessKey(eKeys Key) default: break; } } - if (TimerNumber >= 0 && !HasSubMenu() && Timers.Get(TimerNumber)) { - // a newly created timer was confirmed with Ok - Add(new cMenuTimerItem(Timers.Get(TimerNumber)), true); + if (const cTimer *Timer = cMenuEditTimer::AddedTimer()) { + // a newly created timer was confirmed with Ok and the proper item needs to be added: + LOCK_TIMERS_READ; + Add(new cMenuTimerItem(Timer), true); Display(); } if (Key != kNone) @@ -1281,19 +1350,19 @@ eOSState cMenuTimers::ProcessKey(eKeys Key) // --- cMenuEvent ------------------------------------------------------------ -cMenuEvent::cMenuEvent(const cEvent *Event, bool CanSwitch, bool Buttons) +cMenuEvent::cMenuEvent(const cTimers *Timers, const cChannels *Channels, const cEvent *Event, bool CanSwitch, bool Buttons) :cOsdMenu(tr("Event")) { SetMenuCategory(mcEvent); event = Event; if (event) { - cChannel *channel = Channels.GetByChannelID(event->ChannelID(), true); - if (channel) { - SetTitle(channel->Name()); - eTimerMatch TimerMatch = tmNone; - Timers.GetMatch(event, &TimerMatch); - if (Buttons) + if (const cChannel *Channel = Channels->GetByChannelID(event->ChannelID(), true)) { + SetTitle(Channel->Name()); + if (Buttons) { + eTimerMatch TimerMatch = tmNone; + Timers->GetMatch(event, &TimerMatch); SetHelp(TimerMatch == tmFull ? tr("Button$Timer") : tr("Button$Record"), NULL, NULL, CanSwitch ? tr("Button$Switch") : NULL); + } } } } @@ -1349,24 +1418,24 @@ public: const cChannel *channel; bool withDate; eTimerMatch timerMatch; - cMenuScheduleItem(const cEvent *Event, cChannel *Channel = NULL, bool WithDate = false); + cMenuScheduleItem(const cTimers *Timers, const cEvent *Event, const cChannel *Channel = NULL, bool WithDate = false); static void SetSortMode(eScheduleSortMode SortMode) { sortMode = SortMode; } static void IncSortMode(void) { sortMode = eScheduleSortMode((sortMode == ssmAllAll) ? ssmAllThis : sortMode + 1); } static eScheduleSortMode SortMode(void) { return sortMode; } virtual int Compare(const cListObject &ListObject) const; - bool Update(bool Force = false); + bool Update(const cTimers *Timers, bool Force = false); virtual void SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable); }; cMenuScheduleItem::eScheduleSortMode cMenuScheduleItem::sortMode = ssmAllThis; -cMenuScheduleItem::cMenuScheduleItem(const cEvent *Event, cChannel *Channel, bool WithDate) +cMenuScheduleItem::cMenuScheduleItem(const cTimers *Timers, const cEvent *Event, const cChannel *Channel, bool WithDate) { event = Event; channel = Channel; withDate = WithDate; timerMatch = tmNone; - Update(true); + Update(Timers, true); } int cMenuScheduleItem::Compare(const cListObject &ListObject) const @@ -1382,11 +1451,10 @@ int cMenuScheduleItem::Compare(const cListObject &ListObject) const static const char *TimerMatchChars = " tT"; -bool cMenuScheduleItem::Update(bool Force) +bool cMenuScheduleItem::Update(const cTimers *Timers, bool Force) { - bool result = false; eTimerMatch OldTimerMatch = timerMatch; - Timers.GetMatch(event, &timerMatch); + Timers->GetMatch(event, &timerMatch); if (Force || timerMatch != OldTimerMatch) { cString buffer; char t = TimerMatchChars[timerMatch]; @@ -1401,9 +1469,9 @@ bool cMenuScheduleItem::Update(bool Force) else buffer = cString::sprintf("%.*s\t%s\t%c%c%c\t%s", Utf8SymChars(eds, 6), *eds, *event->GetTimeString(), t, v, r, event->Title()); SetText(buffer); - result = true; + return true; } - return result; + return false; } void cMenuScheduleItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable) @@ -1419,7 +1487,7 @@ private: bool now; bool canSwitch; int helpKeys; - int timerState; + cStateKey timersStateKey; eOSState Record(void); eOSState Switch(void); static int currentChannel; @@ -1427,7 +1495,7 @@ private: bool Update(void); void SetHelpKeys(void); public: - cMenuWhatsOn(const cSchedules *Schedules, bool Now, int CurrentChannelNr); + cMenuWhatsOn(const cTimers *Timers, const cChannels *Channels, const cSchedules *Schedules, bool Now, int CurrentChannelNr); static int CurrentChannel(void) { return currentChannel; } static void SetCurrentChannel(int ChannelNr) { currentChannel = ChannelNr; } static const cEvent *ScheduleEvent(void); @@ -1437,22 +1505,18 @@ public: int cMenuWhatsOn::currentChannel = 0; const cEvent *cMenuWhatsOn::scheduleEvent = NULL; -cMenuWhatsOn::cMenuWhatsOn(const cSchedules *Schedules, bool Now, int CurrentChannelNr) +cMenuWhatsOn::cMenuWhatsOn(const cTimers *Timers, const cChannels *Channels, const cSchedules *Schedules, bool Now, int CurrentChannelNr) :cOsdMenu(Now ? tr("What's on now?") : tr("What's on next?"), CHNUMWIDTH, CHNAMWIDTH, 6, 4) { SetMenuCategory(Now ? mcScheduleNow : mcScheduleNext); now = Now; canSwitch = false; helpKeys = 0; - timerState = 0; - Timers.Modified(timerState); - for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) { + for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { if (!Channel->GroupSep()) { - const cSchedule *Schedule = Schedules->GetSchedule(Channel); - if (Schedule) { - const cEvent *Event = Now ? Schedule->GetPresentEvent() : Schedule->GetFollowingEvent(); - if (Event) - Add(new cMenuScheduleItem(Event, Channel), Channel->Number() == CurrentChannelNr); + if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) { + if (const cEvent *Event = Now ? Schedule->GetPresentEvent() : Schedule->GetFollowingEvent()) + Add(new cMenuScheduleItem(Timers, Event, Channel), Channel->Number() == CurrentChannelNr); } } } @@ -1464,11 +1528,12 @@ cMenuWhatsOn::cMenuWhatsOn(const cSchedules *Schedules, bool Now, int CurrentCha bool cMenuWhatsOn::Update(void) { bool result = false; - if (Timers.Modified(timerState)) { + if (const cTimers *Timers = cTimers::GetTimersRead(timersStateKey)) { for (cOsdItem *item = First(); item; item = Next(item)) { - if (((cMenuScheduleItem *)item)->Update()) + if (((cMenuScheduleItem *)item)->Update(Timers)) result = true; } + timersStateKey.Remove(); } return result; } @@ -1487,7 +1552,8 @@ void cMenuWhatsOn::SetHelpKeys(void) NewHelpKeys |= 0x04; // "Next" else NewHelpKeys |= 0x08; // "Now" - if (cChannel *Channel = Channels.GetByChannelID(item->event->ChannelID(), true)) { + LOCK_CHANNELS_READ; + if (const cChannel *Channel = Channels->GetByChannelID(item->event->ChannelID(), true)) { if (Channel->Number() != cDevice::CurrentChannel()) { NewHelpKeys |= 0x10; // "Switch" canSwitch = true; @@ -1512,8 +1578,13 @@ eOSState cMenuWhatsOn::Switch(void) { cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current()); if (item) { - cChannel *channel = Channels.GetByChannelID(item->event->ChannelID(), true); - if (channel && cDevice::PrimaryDevice()->SwitchChannel(channel, true)) + LOCK_CHANNELS_READ; + const cChannel *Channel = Channels->GetByChannelID(item->event->ChannelID(), true); + if (Channel) { + if (!cDevice::PrimaryDevice()->SwitchChannel(Channel, true)) + Channel = NULL; + } + if (Channel) return osEnd; } Skins.Message(mtError, tr("Can't switch channel!")); @@ -1522,33 +1593,32 @@ eOSState cMenuWhatsOn::Switch(void) eOSState cMenuWhatsOn::Record(void) { - cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current()); - if (item) { - if (item->timerMatch == tmFull) { - eTimerMatch tm = tmNone; - cTimer *timer = Timers.GetMatch(item->event, &tm); - if (timer) - return AddSubMenu(new cMenuEditTimer(timer)); - } - cTimer *timer = new cTimer(item->event); - cTimer *t = Timers.GetTimer(timer); - if (t) { - delete timer; - timer = t; - return AddSubMenu(new cMenuEditTimer(timer)); - } - else { - Timers.Add(timer); - Timers.SetModified(); - isyslog("timer %s added (active)", *timer->ToDescr()); - if (timer->Matches(0, false, NEWTIMERLIMIT)) - return AddSubMenu(new cMenuEditTimer(timer)); - if (HasSubMenu()) - CloseSubMenu(); - if (Update()) - Display(); - SetHelpKeys(); - } + if (cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current())) { + { + LOCK_TIMERS_WRITE; + LOCK_SCHEDULES_READ; + Timers->SetExplicitModify(); + if (item->timerMatch == tmFull) { + if (cTimer *Timer = Timers->GetMatch(item->event)) + return AddSubMenu(new cMenuEditTimer(Timer)); + } + cTimer *Timer = new cTimer(item->event); + if (cTimer *t = Timers->GetTimer(Timer)) { + delete Timer; + Timer = t; + return AddSubMenu(new cMenuEditTimer(Timer)); + } + if (Timer->Matches(0, false, NEWTIMERLIMIT)) + return AddSubMenu(new cMenuEditTimer(Timer, true)); + Timers->Add(Timer); + Timers->SetModified(); + isyslog("timer %s added (active)", *Timer->ToDescr()); + } + if (HasSubMenu()) + CloseSubMenu(); + if (Update()) + Display(); + SetHelpKeys(); } return osContinue; } @@ -1576,8 +1646,11 @@ eOSState cMenuWhatsOn::ProcessKey(eKeys Key) return Switch(); break; case kInfo: - case kOk: if (Count()) - return AddSubMenu(new cMenuEvent(((cMenuScheduleItem *)Get(Current()))->event, canSwitch, true)); + case kOk: if (Count()) { + LOCK_TIMERS_READ; + LOCK_CHANNELS_READ; + return AddSubMenu(new cMenuEvent(Timers, Channels, ((cMenuScheduleItem *)Get(Current()))->event, canSwitch, true)); + } break; default: break; } @@ -1595,19 +1668,20 @@ eOSState cMenuWhatsOn::ProcessKey(eKeys Key) class cMenuSchedule : public cOsdMenu { private: - cSchedulesLock schedulesLock; - const cSchedules *schedules; + cStateKey timersStateKey; + cStateKey schedulesStateKey; + int scheduleState; bool now, next; bool canSwitch; int helpKeys; - int timerState; + void Set(const cChannel *Channel = NULL, bool Force = false); eOSState Number(void); eOSState Record(void); eOSState Switch(void); - void PrepareScheduleAllThis(const cEvent *Event, const cChannel *Channel); - void PrepareScheduleThisThis(const cEvent *Event, const cChannel *Channel); - void PrepareScheduleThisAll(const cEvent *Event, const cChannel *Channel); - void PrepareScheduleAllAll(const cEvent *Event, const cChannel *Channel); + bool PrepareScheduleAllThis(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel); + bool PrepareScheduleThisThis(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel); + bool PrepareScheduleThisAll(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel); + bool PrepareScheduleAllAll(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel); bool Update(void); void SetHelpKeys(void); public: @@ -1620,19 +1694,13 @@ cMenuSchedule::cMenuSchedule(void) :cOsdMenu("") { SetMenuCategory(mcSchedule); + scheduleState = -1; now = next = false; canSwitch = false; helpKeys = 0; - timerState = 0; - Timers.Modified(timerState); cMenuScheduleItem::SetSortMode(cMenuScheduleItem::ssmAllThis); - cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel()); - if (channel) { - cMenuWhatsOn::SetCurrentChannel(channel->Number()); - schedules = cSchedules::Schedules(schedulesLock); - PrepareScheduleAllThis(NULL, channel); - SetHelpKeys(); - } + cMenuWhatsOn::SetCurrentChannel(cDevice::CurrentChannel()); + Set(NULL, true); } cMenuSchedule::~cMenuSchedule() @@ -1640,87 +1708,131 @@ cMenuSchedule::~cMenuSchedule() cMenuWhatsOn::ScheduleEvent(); // makes sure any posted data is cleared } -void cMenuSchedule::PrepareScheduleAllThis(const cEvent *Event, const cChannel *Channel) +void cMenuSchedule::Set(const cChannel *Channel, bool Force) { - Clear(); - SetCols(7, 6, 4); - SetTitle(cString::sprintf(tr("Schedule - %s"), Channel->Name())); - if (schedules && Channel) { - const cSchedule *Schedule = schedules->GetSchedule(Channel); - if (Schedule) { + if (Force) { + schedulesStateKey.Reset(); + scheduleState = -1; + } + LOCK_TIMERS_READ; + LOCK_CHANNELS_READ; + if (const cSchedules *Schedules = cSchedules::GetSchedulesRead(schedulesStateKey)) { + cMenuScheduleItem *CurrentItem = (cMenuScheduleItem *)Get(Current()); + const cEvent *Event = NULL; + if (!Channel) { + if (CurrentItem) { + Event = CurrentItem->event; + Channel = Channels->GetByChannelID(Event->ChannelID(), true); + } + else + Channel = Channels->GetByNumber(cDevice::CurrentChannel()); + } + bool Refresh = false; + switch (cMenuScheduleItem::SortMode()) { + case cMenuScheduleItem::ssmAllThis: Refresh = PrepareScheduleAllThis(Timers, Schedules, Event, Channel); break; + case cMenuScheduleItem::ssmThisThis: Refresh = PrepareScheduleThisThis(Timers, Schedules, Event, Channel); break; + case cMenuScheduleItem::ssmThisAll: Refresh = Force && PrepareScheduleThisAll(Timers, Schedules, Event, Channel); break; + case cMenuScheduleItem::ssmAllAll: Refresh = Force && PrepareScheduleAllAll(Timers, Schedules, Event, Channel); break; + default: esyslog("ERROR: unknown SortMode %d (%s %d)", cMenuScheduleItem::SortMode(), __FUNCTION__, __LINE__); + } + if (Refresh) { + CurrentItem = (cMenuScheduleItem *)Get(Current()); + Sort(); + SetCurrent(CurrentItem); + SetHelpKeys(); + Display(); + } + schedulesStateKey.Remove(); + } +} + +bool cMenuSchedule::PrepareScheduleAllThis(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel) +{ + if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) { + if (Schedule->Modified(scheduleState)) { + Clear(); + SetCols(7, 6, 4); + SetTitle(cString::sprintf(tr("Schedule - %s"), Channel->Name())); const cEvent *PresentEvent = Event ? Event : Schedule->GetPresentEvent(); time_t now = time(NULL) - Setup.EPGLinger * 60; for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) { if (ev->EndTime() > now || ev == PresentEvent) - Add(new cMenuScheduleItem(ev), ev == PresentEvent); + Add(new cMenuScheduleItem(Timers, ev), ev == PresentEvent); } + return true; } } + return false; } -void cMenuSchedule::PrepareScheduleThisThis(const cEvent *Event, const cChannel *Channel) +bool cMenuSchedule::PrepareScheduleThisThis(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel) { - Clear(); - SetCols(7, 6, 4); - SetTitle(cString::sprintf(tr("This event - %s"), Channel->Name())); - if (schedules && Channel && Event) { - const cSchedule *Schedule = schedules->GetSchedule(Channel); - if (Schedule) { - time_t now = time(NULL) - Setup.EPGLinger * 60; - for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) { - if ((ev->EndTime() > now || ev == Event) && !strcmp(ev->Title(), Event->Title())) - Add(new cMenuScheduleItem(ev), ev == Event); - } + if (Event) { + if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) { + if (Schedule->Modified(scheduleState)) { + Clear(); + SetCols(7, 6, 4); + SetTitle(cString::sprintf(tr("This event - %s"), Channel->Name())); + time_t now = time(NULL) - Setup.EPGLinger * 60; + for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) { + if ((ev->EndTime() > now || ev == Event) && !strcmp(ev->Title(), Event->Title())) + Add(new cMenuScheduleItem(Timers, ev), ev == Event); + } + return true; + } } } + return false; } -void cMenuSchedule::PrepareScheduleThisAll(const cEvent *Event, const cChannel *Channel) +bool cMenuSchedule::PrepareScheduleThisAll(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel) { Clear(); SetCols(CHNUMWIDTH, CHNAMWIDTH, 7, 6, 4); SetTitle(tr("This event - all channels")); - if (schedules && Event) { - for (cChannel *ch = Channels.First(); ch; ch = Channels.Next(ch)) { - const cSchedule *Schedule = schedules->GetSchedule(ch); - if (Schedule) { + if (Event) { + LOCK_CHANNELS_READ; + for (const cChannel *ch = Channels->First(); ch; ch = Channels->Next(ch)) { + if (const cSchedule *Schedule = Schedules->GetSchedule(ch)) { time_t now = time(NULL) - Setup.EPGLinger * 60; for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) { if ((ev->EndTime() > now || ev == Event) && !strcmp(ev->Title(), Event->Title())) - Add(new cMenuScheduleItem(ev, ch, true), ev == Event && ch == Channel); + Add(new cMenuScheduleItem(Timers, ev, ch, true), ev == Event && ch == Channel); } } } } + return true; } -void cMenuSchedule::PrepareScheduleAllAll(const cEvent *Event, const cChannel *Channel) +bool cMenuSchedule::PrepareScheduleAllAll(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel) { Clear(); SetCols(CHNUMWIDTH, CHNAMWIDTH, 7, 6, 4); SetTitle(tr("All events - all channels")); - if (schedules) { - for (cChannel *ch = Channels.First(); ch; ch = Channels.Next(ch)) { - const cSchedule *Schedule = schedules->GetSchedule(ch); - if (Schedule) { - time_t now = time(NULL) - Setup.EPGLinger * 60; - for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) { - if (ev->EndTime() > now || ev == Event) - Add(new cMenuScheduleItem(ev, ch, true), ev == Event && ch == Channel); - } - } + LOCK_CHANNELS_READ; + cStateKey StateKey; + for (const cChannel *ch = Channels->First(); ch; ch = Channels->Next(ch)) { + if (const cSchedule *Schedule = Schedules->GetSchedule(ch)) { + time_t now = time(NULL) - Setup.EPGLinger * 60; + for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) { + if (ev->EndTime() > now || ev == Event) + Add(new cMenuScheduleItem(Timers, ev, ch, true), ev == Event && ch == Channel); + } } - } + } + return true; } bool cMenuSchedule::Update(void) { bool result = false; - if (Timers.Modified(timerState)) { + if (const cTimers *Timers = cTimers::GetTimersRead(timersStateKey)) { for (cOsdItem *item = First(); item; item = Next(item)) { - if (((cMenuScheduleItem *)item)->Update()) + if (((cMenuScheduleItem *)item)->Update(Timers)) result = true; } + timersStateKey.Remove(); } return result; } @@ -1735,7 +1847,8 @@ void cMenuSchedule::SetHelpKeys(void) NewHelpKeys |= 0x02; // "Timer" else NewHelpKeys |= 0x01; // "Record" - if (cChannel *Channel = Channels.GetByChannelID(item->event->ChannelID(), true)) { + LOCK_CHANNELS_READ; + if (const cChannel *Channel = Channels->GetByChannelID(item->event->ChannelID(), true)) { if (Channel->Number() != cDevice::CurrentChannel()) { NewHelpKeys |= 0x10; // "Switch" canSwitch = true; @@ -1752,58 +1865,38 @@ void cMenuSchedule::SetHelpKeys(void) eOSState cMenuSchedule::Number(void) { cMenuScheduleItem::IncSortMode(); - cMenuScheduleItem *CurrentItem = (cMenuScheduleItem *)Get(Current()); - const cChannel *Channel = NULL; - const cEvent *Event = NULL; - if (CurrentItem) { - Event = CurrentItem->event; - Channel = Channels.GetByChannelID(Event->ChannelID(), true); - } - else - Channel = Channels.GetByNumber(cDevice::CurrentChannel()); - switch (cMenuScheduleItem::SortMode()) { - case cMenuScheduleItem::ssmAllThis: PrepareScheduleAllThis(Event, Channel); break; - case cMenuScheduleItem::ssmThisThis: PrepareScheduleThisThis(Event, Channel); break; - case cMenuScheduleItem::ssmThisAll: PrepareScheduleThisAll(Event, Channel); break; - case cMenuScheduleItem::ssmAllAll: PrepareScheduleAllAll(Event, Channel); break; - default: esyslog("ERROR: unknown SortMode %d (%s %d)", cMenuScheduleItem::SortMode(), __FUNCTION__, __LINE__); - } - CurrentItem = (cMenuScheduleItem *)Get(Current()); - Sort(); - SetCurrent(CurrentItem); - Display(); + Set(NULL, true); return osContinue; } eOSState cMenuSchedule::Record(void) { - cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current()); - if (item) { - if (item->timerMatch == tmFull) { - eTimerMatch tm = tmNone; - cTimer *timer = Timers.GetMatch(item->event, &tm); - if (timer) - return AddSubMenu(new cMenuEditTimer(timer)); - } - cTimer *timer = new cTimer(item->event); - cTimer *t = Timers.GetTimer(timer); - if (t) { - delete timer; - timer = t; - return AddSubMenu(new cMenuEditTimer(timer)); - } - else { - Timers.Add(timer); - Timers.SetModified(); - isyslog("timer %s added (active)", *timer->ToDescr()); - if (timer->Matches(0, false, NEWTIMERLIMIT)) - return AddSubMenu(new cMenuEditTimer(timer)); - if (HasSubMenu()) - CloseSubMenu(); - if (Update()) - Display(); - SetHelpKeys(); - } + if (cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current())) { + { + LOCK_TIMERS_WRITE; + LOCK_SCHEDULES_READ; + Timers->SetExplicitModify(); + if (item->timerMatch == tmFull) { + if (cTimer *Timer = Timers->GetMatch(item->event)) + return AddSubMenu(new cMenuEditTimer(Timer)); + } + cTimer *Timer = new cTimer(item->event); + if (cTimer *t = Timers->GetTimer(Timer)) { + delete Timer; + Timer = t; + return AddSubMenu(new cMenuEditTimer(Timer)); + } + if (Timer->Matches(0, false, NEWTIMERLIMIT)) + return AddSubMenu(new cMenuEditTimer(Timer, true)); + Timers->Add(Timer); + Timers->SetModified(); + isyslog("timer %s added (active)", *Timer->ToDescr()); + } + if (HasSubMenu()) + CloseSubMenu(); + if (Update()) + Display(); + SetHelpKeys(); } return osContinue; } @@ -1812,10 +1905,14 @@ eOSState cMenuSchedule::Switch(void) { cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current()); if (item) { - if (cChannel *Channel = Channels.GetByChannelID(item->event->ChannelID(), true)) { - if (Channels.SwitchTo(Channel->Number())) - return osEnd; + LOCK_CHANNELS_READ; + const cChannel *Channel = NULL; + if (Channel = Channels->GetByChannelID(item->event->ChannelID(), true)) { + if (!Channels->SwitchTo(Channel->Number())) + Channel = NULL; } + if (Channel) + return osEnd; } Skins.Message(mtError, tr("Can't switch channel!")); return osContinue; @@ -1823,6 +1920,8 @@ eOSState cMenuSchedule::Switch(void) eOSState cMenuSchedule::ProcessKey(eKeys Key) { + if (!HasSubMenu()) + Set(); // react on any changes to the schedules list bool HadSubMenu = HasSubMenu(); eOSState state = cOsdMenu::ProcessKey(Key); @@ -1831,43 +1930,50 @@ eOSState cMenuSchedule::ProcessKey(eKeys Key) case k0: return Number(); case kRecord: case kRed: return Record(); - case kGreen: if (schedules) { - if (!now && !next) { - int ChannelNr = 0; - if (Count()) { - cChannel *channel = Channels.GetByChannelID(((cMenuScheduleItem *)Get(Current()))->event->ChannelID(), true); - if (channel) - ChannelNr = channel->Number(); - } - now = true; - return AddSubMenu(new cMenuWhatsOn(schedules, true, ChannelNr)); - } - now = !now; - next = !next; - return AddSubMenu(new cMenuWhatsOn(schedules, now, cMenuWhatsOn::CurrentChannel())); - } - case kYellow: if (schedules) - return AddSubMenu(new cMenuWhatsOn(schedules, false, cMenuWhatsOn::CurrentChannel())); - break; + case kGreen: { + LOCK_TIMERS_READ; + LOCK_CHANNELS_READ; + LOCK_SCHEDULES_READ; + if (!now && !next) { + int ChannelNr = 0; + if (Count()) { + if (const cChannel *Channel = Channels->GetByChannelID(((cMenuScheduleItem *)Get(Current()))->event->ChannelID(), true)) + ChannelNr = Channel->Number(); + } + now = true; + return AddSubMenu(new cMenuWhatsOn(Timers, Channels, Schedules, now, ChannelNr)); + } + now = !now; + next = !next; + return AddSubMenu(new cMenuWhatsOn(Timers, Channels, Schedules, now, cMenuWhatsOn::CurrentChannel())); + } + case kYellow: { + LOCK_TIMERS_READ; + LOCK_CHANNELS_READ; + LOCK_SCHEDULES_READ; + return AddSubMenu(new cMenuWhatsOn(Timers, Channels, Schedules, false, cMenuWhatsOn::CurrentChannel())); + } case kBlue: if (canSwitch) return Switch(); break; case kInfo: - case kOk: if (Count()) - return AddSubMenu(new cMenuEvent(((cMenuScheduleItem *)Get(Current()))->event, canSwitch, true)); + case kOk: if (Count()) { + LOCK_TIMERS_READ; + LOCK_CHANNELS_READ; + LOCK_SCHEDULES_READ; + return AddSubMenu(new cMenuEvent(Timers, Channels, ((cMenuScheduleItem *)Get(Current()))->event, canSwitch, true)); + } break; default: break; } } else if (!HasSubMenu()) { now = next = false; - const cEvent *ei = cMenuWhatsOn::ScheduleEvent(); - if (ei) { - cChannel *channel = Channels.GetByChannelID(ei->ChannelID(), true); - if (channel) { + if (const cEvent *ei = cMenuWhatsOn::ScheduleEvent()) { + LOCK_CHANNELS_READ; + if (const cChannel *Channel = Channels->GetByChannelID(ei->ChannelID(), true)) { cMenuScheduleItem::SetSortMode(cMenuScheduleItem::ssmAllThis); - PrepareScheduleAllThis(NULL, channel); - Display(); + Set(Channel, true); } } else if (HadSubMenu && Update()) @@ -2219,7 +2325,10 @@ cMenuPathEdit::cMenuPathEdit(const char *Path) else s = path; strn0cpy(name, s, sizeof(name)); - pathIsInUse = Recordings.PathIsInUse(path); + { + LOCK_RECORDINGS_READ; + pathIsInUse = Recordings->PathIsInUse(path); + } cOsdItem *p; Add(p = folderItem = new cMenuEditStrItem(tr("Folder"), folder, sizeof(folder))); p->SetSelectable(!pathIsInUse); @@ -2258,14 +2367,17 @@ eOSState cMenuPathEdit::ApplyChanges(void) cString NewPath = *folder ? cString::sprintf("%s%c%s", folder, FOLDERDELIMCHAR, name) : name; NewPath.CompactChars(FOLDERDELIMCHAR); if (strcmp(NewPath, path)) { - int NumRecordings = Recordings.GetNumRecordingsInPath(path); + LOCK_RECORDINGS_WRITE; + Recordings->SetExplicitModify(); + int NumRecordings = Recordings->GetNumRecordingsInPath(path); if (NumRecordings > 1 && !Interface->Confirm(cString::sprintf(tr("Move entire folder containing %d recordings?"), NumRecordings))) return osContinue; - if (!Recordings.MoveRecordings(path, NewPath)) { + if (!Recordings->MoveRecordings(path, NewPath)) { Skins.Message(mtError, tr("Error while moving folder!")); return osContinue; } cMenuRecordings::SetPath(NewPath); // makes sure the Recordings menu will reposition to the new path + Recordings->SetModified(); return osUser1; } return osBack; @@ -2294,9 +2406,9 @@ eOSState cMenuPathEdit::ProcessKey(eKeys Key) class cMenuRecordingEdit : public cOsdMenu { private: - cRecording *recording; + const cRecording *recording; cString originalFileName; - int recordingsState; + cStateKey recordingsStateKey; char folder[PATH_MAX]; char name[NAME_MAX]; int priority; @@ -2319,17 +2431,16 @@ private: eOSState DeleteMarks(void); eOSState ApplyChanges(void); public: - cMenuRecordingEdit(cRecording *Recording); + cMenuRecordingEdit(const cRecording *Recording); virtual eOSState ProcessKey(eKeys Key); }; -cMenuRecordingEdit::cMenuRecordingEdit(cRecording *Recording) +cMenuRecordingEdit::cMenuRecordingEdit(const cRecording *Recording) :cOsdMenu(tr("Edit recording"), 12) { SetMenuCategory(mcRecordingEdit); recording = Recording; originalFileName = recording->FileName(); - Recordings.StateChanged(recordingsState); // just to get the current state strn0cpy(folder, recording->Folder(), sizeof(folder)); strn0cpy(name, recording->BaseName(), sizeof(name)); priority = recording->Priority(); @@ -2390,13 +2501,15 @@ void cMenuRecordingEdit::SetHelpKeys(void) bool cMenuRecordingEdit::RefreshRecording(void) { - if (Recordings.StateChanged(recordingsState)) { - if ((recording = Recordings.GetByName(originalFileName)) != NULL) + if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(recordingsStateKey)) { + if ((recording = Recordings->GetByName(originalFileName)) != NULL) Set(); else { + recordingsStateKey.Remove(); Skins.Message(mtWarning, tr("Recording vanished!")); return false; } + recordingsStateKey.Remove(); } return true; } @@ -2453,7 +2566,7 @@ eOSState cMenuRecordingEdit::RemoveName(void) eOSState cMenuRecordingEdit::DeleteMarks(void) { if (buttonDeleteMarks && Interface->Confirm(tr("Delete editing marks for this recording?"))) { - if (recording->DeleteMarks()) + if (cMarks::DeleteMarksFile(recording)) SetHelpKeys(); else Skins.Message(mtError, tr("Error while deleting editing marks!")); @@ -2463,10 +2576,18 @@ eOSState cMenuRecordingEdit::DeleteMarks(void) eOSState cMenuRecordingEdit::ApplyChanges(void) { + cStateKey StateKey; + cRecordings *Recordings = cRecordings::GetRecordingsWrite(StateKey); + cRecording *Recording = Recordings->GetByName(recording->FileName()); + if (!Recording) { + Skins.Message(mtWarning, tr("Recording vanished!")); + return osBack; + } bool Modified = false; if (priority != recording->Priority() || lifetime != recording->Lifetime()) { - if (!recording->ChangePriorityLifetime(priority, lifetime)) { + if (!Recording->ChangePriorityLifetime(priority, lifetime)) { Skins.Message(mtError, tr("Error while changing priority/lifetime!")); + StateKey.Remove(Modified); return osContinue; } Modified = true; @@ -2477,17 +2598,21 @@ eOSState cMenuRecordingEdit::ApplyChanges(void) } cString NewName = *folder ? cString::sprintf("%s%c%s", folder, FOLDERDELIMCHAR, name) : name; NewName.CompactChars(FOLDERDELIMCHAR); - if (strcmp(NewName, recording->Name())) { - if (!recording->ChangeName(NewName)) { + if (strcmp(NewName, Recording->Name())) { + if (!Recording->ChangeName(NewName)) { Skins.Message(mtError, tr("Error while changing folder/name!")); + StateKey.Remove(Modified); return osContinue; } Modified = true; } if (Modified) { - cMenuRecordings::SetRecording(recording->FileName()); // makes sure the Recordings menu will reposition to the renamed recording + cMenuRecordings::SetRecording(Recording->FileName()); // makes sure the Recordings menu will reposition to the renamed recording + Recordings->TouchUpdate(); + StateKey.Remove(Modified); return osUser1; } + StateKey.Remove(Modified); return osBack; } @@ -2517,24 +2642,23 @@ eOSState cMenuRecordingEdit::ProcessKey(eKeys Key) class cMenuRecording : public cOsdMenu { private: - cRecording *recording; + const cRecording *recording; cString originalFileName; - int recordingsState; + cStateKey recordingsStateKey; bool withButtons; bool RefreshRecording(void); public: - cMenuRecording(cRecording *Recording, bool WithButtons = false); + cMenuRecording(const cRecording *Recording, bool WithButtons = false); virtual void Display(void); virtual eOSState ProcessKey(eKeys Key); }; -cMenuRecording::cMenuRecording(cRecording *Recording, bool WithButtons) +cMenuRecording::cMenuRecording(const cRecording *Recording, bool WithButtons) :cOsdMenu(tr("Recording info")) { SetMenuCategory(mcRecordingInfo); recording = Recording; originalFileName = recording->FileName(); - Recordings.StateChanged(recordingsState); // just to get the current state withButtons = WithButtons; if (withButtons) SetHelp(tr("Button$Play"), tr("Button$Rewind"), NULL, tr("Button$Edit")); @@ -2542,13 +2666,15 @@ cMenuRecording::cMenuRecording(cRecording *Recording, bool WithButtons) bool cMenuRecording::RefreshRecording(void) { - if (Recordings.StateChanged(recordingsState)) { - if ((recording = Recordings.GetByName(originalFileName)) != NULL) + if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(recordingsStateKey)) { + if ((recording = Recordings->GetByName(originalFileName)) != NULL) Display(); else { + recordingsStateKey.Remove(); Skins.Message(mtWarning, tr("Recording vanished!")); return false; } + recordingsStateKey.Remove(); } return true; } @@ -2611,23 +2737,23 @@ eOSState cMenuRecording::ProcessKey(eKeys Key) class cMenuRecordingItem : public cOsdItem { private: - cRecording *recording; + const cRecording *recording; int level; char *name; int totalEntries, newEntries; public: - cMenuRecordingItem(cRecording *Recording, int Level); + cMenuRecordingItem(const cRecording *Recording, int Level); ~cMenuRecordingItem(); void IncrementCounter(bool New); const char *Name(void) { return name; } int Level(void) { return level; } - cRecording *Recording(void) { return recording; } + const cRecording *Recording(void) { return recording; } bool IsDirectory(void) { return name != NULL; } - void SetRecording(cRecording *Recording) { recording = Recording; } + void SetRecording(const cRecording *Recording) { recording = Recording; } virtual void SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable); }; -cMenuRecordingItem::cMenuRecordingItem(cRecording *Recording, int Level) +cMenuRecordingItem::cMenuRecordingItem(const cRecording *Recording, int Level) { recording = Recording; level = Level; @@ -2669,7 +2795,6 @@ cMenuRecordings::cMenuRecordings(const char *Base, int Level, bool OpenSubMenus, base = Base ? strdup(Base) : NULL; level = Setup.RecordingDirs ? Level : -1; filter = Filter; - Recordings.StateChanged(recordingsState); // just to get the current state helpKeys = -1; Display(); // this keeps the higher level menus from showing up briefly when pressing 'Back' during replay Set(); @@ -2717,50 +2842,54 @@ void cMenuRecordings::SetHelpKeys(void) void cMenuRecordings::Set(bool Refresh) { - const char *CurrentRecording = *fileName ? *fileName : cReplayControl::LastReplayed(); - cMenuRecordingItem *LastItem = NULL; - cThreadLock RecordingsLock(&Recordings); - if (Refresh) { - if (cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current())) - CurrentRecording = ri->Recording()->FileName(); - } - Clear(); - GetRecordingsSortMode(DirectoryName()); - Recordings.Sort(); - for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) { - if ((!filter || filter->Filter(recording)) && (!base || (strstr(recording->Name(), base) == recording->Name() && recording->Name()[strlen(base)] == FOLDERDELIMCHAR))) { - cMenuRecordingItem *Item = new cMenuRecordingItem(recording, level); - cMenuRecordingItem *LastDir = NULL; - if (Item->IsDirectory()) { - // Sorting may ignore non-alphanumeric characters, so we need to explicitly handle directories in case they only differ in such characters: - for (cMenuRecordingItem *p = LastItem; p; p = dynamic_cast(p->Prev())) { - if (p->Name() && strcmp(p->Name(), Item->Name()) == 0) { - LastDir = p; - break; + if (cRecordings::GetRecordingsRead(recordingsStateKey)) { + recordingsStateKey.Remove(); + const char *CurrentRecording = *fileName ? *fileName : cReplayControl::LastReplayed(); + cRecordings *Recordings = cRecordings::GetRecordingsWrite(recordingsStateKey); // write access is necessary for sorting! + cMenuRecordingItem *LastItem = NULL; + if (Refresh) { + if (cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current())) + CurrentRecording = ri->Recording()->FileName(); + } + Clear(); + GetRecordingsSortMode(DirectoryName()); + Recordings->Sort(); + for (const cRecording *Recording = Recordings->First(); Recording; Recording = Recordings->Next(Recording)) { + if ((!filter || filter->Filter(Recording)) && (!base || (strstr(Recording->Name(), base) == Recording->Name() && Recording->Name()[strlen(base)] == FOLDERDELIMCHAR))) { + cMenuRecordingItem *Item = new cMenuRecordingItem(Recording, level); + cMenuRecordingItem *LastDir = NULL; + if (Item->IsDirectory()) { + // Sorting may ignore non-alphanumeric characters, so we need to explicitly handle directories in case they only differ in such characters: + for (cMenuRecordingItem *p = LastItem; p; p = dynamic_cast(p->Prev())) { + if (p->Name() && strcmp(p->Name(), Item->Name()) == 0) { + LastDir = p; + break; + } } - } - } - if (*Item->Text() && !LastDir) { - Add(Item); - LastItem = Item; - if (Item->IsDirectory()) - LastDir = Item; - } - else - delete Item; - if (LastItem || LastDir) { - if (*path) { - if (strcmp(path, recording->Folder()) == 0) + } + if (*Item->Text() && !LastDir) { + Add(Item); + LastItem = Item; + if (Item->IsDirectory()) + LastDir = Item; + } + else + delete Item; + if (LastItem || LastDir) { + if (*path) { + if (strcmp(path, Recording->Folder()) == 0) + SetCurrent(LastDir ? LastDir : LastItem); + } + else if (CurrentRecording && strcmp(CurrentRecording, Recording->FileName()) == 0) SetCurrent(LastDir ? LastDir : LastItem); } - else if (CurrentRecording && strcmp(CurrentRecording, recording->FileName()) == 0) - SetCurrent(LastDir ? LastDir : LastItem); + if (LastDir) + LastDir->IncrementCounter(Recording->IsNew()); } - if (LastDir) - LastDir->IncrementCounter(recording->IsNew()); } - } - SetMenuSortMode(RecordingsSortMode == rsmName ? msmName : msmTime); + SetMenuSortMode(RecordingsSortMode == rsmName ? msmName : msmTime); + recordingsStateKey.Remove(false); // sorting doesn't count as a real modification + } if (Refresh) Display(); } @@ -2837,50 +2966,60 @@ eOSState cMenuRecordings::Delete(void) cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()); if (ri && !ri->IsDirectory()) { if (Interface->Confirm(tr("Delete recording?"))) { - cRecordControl *rc = cRecordControls::GetRecordControl(ri->Recording()->FileName()); - if (rc) { + if (cRecordControl *rc = cRecordControls::GetRecordControl(ri->Recording()->FileName())) { if (Interface->Confirm(tr("Timer still recording - really delete?"))) { - cTimer *timer = rc->Timer(); - if (timer) { - timer->Skip(); - cRecordControls::Process(time(NULL)); - if (timer->IsSingleEvent()) { - isyslog("deleting timer %s", *timer->ToDescr()); - Timers.Del(timer); + if (cTimer *Timer = rc->Timer()) { + LOCK_TIMERS_WRITE; + Timer->Skip(); + cRecordControls::Process(Timers, time(NULL)); + if (Timer->IsSingleEvent()) { + isyslog("deleting timer %s", *Timer->ToDescr()); + Timers->Del(Timer); } - Timers.SetModified(); } } else return osContinue; } - cRecording *recording = ri->Recording(); - cString FileName = recording->FileName(); + cRecordings *Recordings = cRecordings::GetRecordingsWrite(recordingsStateKey); + Recordings->SetExplicitModify(); + cRecording *Recording = Recordings->GetByName(ri->Recording()->FileName()); + if (!Recording) { + Skins.Message(mtWarning, tr("Recording vanished!")); + recordingsStateKey.Remove(); + return osContinue; + } + cString FileName = Recording->FileName(); if (RecordingsHandler.GetUsage(FileName)) { if (Interface->Confirm(tr("Recording is being edited - really delete?"))) { RecordingsHandler.Del(FileName); - recording = Recordings.GetByName(FileName); // RecordingsHandler.Del() might have deleted it if it was the edited version - // we continue with the code below even if recording is NULL, + Recording = Recordings->GetByName(FileName); // RecordingsHandler.Del() might have deleted it if it was the edited version + // we continue with the code below even if Recording is NULL, // in order to have the menu updated etc. } - else + else { + recordingsStateKey.Remove(); return osContinue; + } } if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), FileName) == 0) cControl::Shutdown(); - if (!recording || recording->Delete()) { + if (!Recording || Recording->Delete()) { cReplayControl::ClearLastReplayed(FileName); - Recordings.DelByName(FileName); + Recordings->DelByName(FileName); cOsdMenu::Del(Current()); SetHelpKeys(); cVideoDiskUsage::ForceCheck(); Display(); + Recordings->SetModified(); + recordingsStateKey.Remove(); if (!Count()) return osBack; return osUser2; } else Skins.Message(mtError, tr("Error while deleting recording!")); + recordingsStateKey.Remove(false); } } return osContinue; @@ -2925,6 +3064,8 @@ eOSState cMenuRecordings::Sort(void) eOSState cMenuRecordings::ProcessKey(eKeys Key) { + if (!HasSubMenu()) + Set(); // react on any changes to the recordings list bool HadSubMenu = HasSubMenu(); eOSState state = cOsdMenu::ProcessKey(Key); @@ -2940,9 +3081,6 @@ eOSState cMenuRecordings::ProcessKey(eKeys Key) case kBlue: return Info(); case k0: return Sort(); case k1...k9: return Commands(Key); - case kNone: if (Recordings.StateChanged(recordingsState)) - Set(true); - break; default: break; } } @@ -3136,8 +3274,10 @@ eOSState cMenuSetupOSD::ProcessKey(eKeys Key) ModifiedAppearance = true; if (strcmp(data.FontFix, Setup.FontFix) || !DoubleEqual(data.FontFixSizeP, Setup.FontFixSizeP)) ModifiedAppearance = true; - if (data.AlwaysSortFoldersFirst != Setup.AlwaysSortFoldersFirst || data.RecordingDirs != Setup.RecordingDirs) - Recordings.ClearSortNames(); + if (data.AlwaysSortFoldersFirst != Setup.AlwaysSortFoldersFirst || data.RecordingDirs != Setup.RecordingDirs) { + LOCK_RECORDINGS_WRITE; + Recordings->ClearSortNames(); + } } int oldSkinIndex = skinIndex; @@ -3614,7 +3754,8 @@ eOSState cMenuSetupCAM::Activate(void) CamSlot->CancelActivation(); else if (CamSlot->CanActivate()) { if (CamSlot->Priority() < LIVEPRIORITY) { // don't interrupt recordings - if (cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel())) { + LOCK_CHANNELS_READ; + if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel())) { for (int i = 0; i < cDevice::NumDevices(); i++) { if (cDevice *Device = cDevice::GetDevice(i)) { if (Device->ProvidesChannel(Channel)) { @@ -3745,8 +3886,10 @@ cMenuSetupReplay::cMenuSetupReplay(void) void cMenuSetupReplay::Store(void) { - if (Setup.ResumeID != data.ResumeID) - Recordings.ResetResume(); + if (Setup.ResumeID != data.ResumeID) { + LOCK_RECORDINGS_WRITE; + Recordings->ResetResume(); + } cMenuSetupBase::Store(); } @@ -4078,8 +4221,7 @@ eOSState cMenuMain::ProcessKey(eKeys Key) case osSetup: return AddSubMenu(new cMenuSetup); case osCommands: return AddSubMenu(new cMenuCommands(tr("Commands"), &Commands)); case osStopRecord: if (Interface->Confirm(tr("Stop recording?"))) { - cOsdItem *item = Get(Current()); - if (item) { + if (cOsdItem *item = Get(Current())) { cRecordControls::Stop(item->Text() + strlen(tr(STOP_RECORDING))); return osEnd; } @@ -4146,25 +4288,20 @@ static void SetTrackDescriptions(int LiveChannel) { cDevice::PrimaryDevice()->ClrAvailableTracks(true); const cComponents *Components = NULL; - cSchedulesLock SchedulesLock; if (LiveChannel) { - cChannel *Channel = Channels.GetByNumber(LiveChannel); - if (Channel) { - const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock); - if (Schedules) { - const cSchedule *Schedule = Schedules->GetSchedule(Channel); - if (Schedule) { - const cEvent *Present = Schedule->GetPresentEvent(); - if (Present) - Components = Present->Components(); - } + LOCK_CHANNELS_READ; + if (const cChannel *Channel = Channels->GetByNumber(LiveChannel)) { + LOCK_SCHEDULES_READ; + if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) { + const cEvent *Present = Schedule->GetPresentEvent(); + if (Present) + Components = Present->Components(); } } } else if (cReplayControl::NowReplaying()) { - cThreadLock RecordingsLock(&Recordings); - cRecording *Recording = Recordings.GetByName(cReplayControl::NowReplaying()); - if (Recording) + LOCK_RECORDINGS_READ; + if (const cRecording *Recording = Recordings->GetByName(cReplayControl::NowReplaying())) Components = Recording->Info()->Components(); } if (Components) { @@ -4204,7 +4341,9 @@ cDisplayChannel::cDisplayChannel(int Number, bool Switched) timeout = Switched || Setup.TimeoutRequChInfo; cOsdProvider::OsdSizeChanged(osdState); // just to get the current state positioner = NULL; - channel = Channels.GetByNumber(Number); + channel = NULL; + LOCK_CHANNELS_READ; + channel = Channels->GetByNumber(Number); lastPresent = lastFollowing = NULL; if (channel) { DisplayChannel(); @@ -4226,7 +4365,9 @@ cDisplayChannel::cDisplayChannel(eKeys FirstKey) withInfo = Setup.ShowInfoOnChSwitch; displayChannel = Skins.Current()->DisplayChannel(withInfo); positioner = NULL; - channel = Channels.GetByNumber(cDevice::CurrentChannel()); + channel = NULL; + LOCK_CHANNELS_READ; + channel = Channels->GetByNumber(cDevice::CurrentChannel()); ProcessKey(FirstKey); } @@ -4247,20 +4388,16 @@ void cDisplayChannel::DisplayChannel(void) void cDisplayChannel::DisplayInfo(void) { if (withInfo && channel) { - cSchedulesLock SchedulesLock; - const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock); - if (Schedules) { - const cSchedule *Schedule = Schedules->GetSchedule(channel); - if (Schedule) { - const cEvent *Present = Schedule->GetPresentEvent(); - const cEvent *Following = Schedule->GetFollowingEvent(); - if (Present != lastPresent || Following != lastFollowing) { - SetTrackDescriptions(channel->Number()); - displayChannel->SetEvents(Present, Following); - cStatus::MsgOsdProgramme(Present ? Present->StartTime() : 0, Present ? Present->Title() : NULL, Present ? Present->ShortText() : NULL, Following ? Following->StartTime() : 0, Following ? Following->Title() : NULL, Following ? Following->ShortText() : NULL); - lastPresent = Present; - lastFollowing = Following; - } + LOCK_SCHEDULES_READ; + if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) { + const cEvent *Present = Schedule->GetPresentEvent(); + const cEvent *Following = Schedule->GetFollowingEvent(); + if (Present != lastPresent || Following != lastFollowing) { + SetTrackDescriptions(channel->Number()); + displayChannel->SetEvents(Present, Following); + cStatus::MsgOsdProgramme(Present ? Present->StartTime() : 0, Present ? Present->Title() : NULL, Present ? Present->ShortText() : NULL, Following ? Following->StartTime() : 0, Following ? Following->Title() : NULL, Following ? Following->ShortText() : NULL); + lastPresent = Present; + lastFollowing = Following; } } } @@ -4272,13 +4409,14 @@ void cDisplayChannel::Refresh(void) displayChannel->SetEvents(NULL, NULL); } -cChannel *cDisplayChannel::NextAvailableChannel(cChannel *Channel, int Direction) +const cChannel *cDisplayChannel::NextAvailableChannel(const cChannel *Channel, int Direction) { if (Direction) { + LOCK_CHANNELS_READ; while (Channel) { - Channel = Direction > 0 ? Channels.Next(Channel) : Channels.Prev(Channel); + Channel = Direction > 0 ? Channels->Next(Channel) : Channels->Prev(Channel); if (!Channel && Setup.ChannelsWrap) - Channel = Direction > 0 ? Channels.First() : Channels.Last(); + Channel = Direction > 0 ? Channels->First() : Channels->Last(); if (Channel && !Channel->GroupSep() && cDevice::GetDevice(Channel, LIVEPRIORITY, true, true)) return Channel; } @@ -4292,7 +4430,7 @@ eOSState cDisplayChannel::ProcessKey(eKeys Key) delete displayChannel; displayChannel = Skins.Current()->DisplayChannel(withInfo); } - cChannel *NewChannel = NULL; + const cChannel *NewChannel = NULL; if (Key != kNone) lastTime.Set(); switch (int(Key)) { @@ -4305,18 +4443,19 @@ eOSState cDisplayChannel::ProcessKey(eKeys Key) case k1 ... k9: group = -1; if (number >= 0) { - if (number > Channels.MaxNumber()) + if (number > cChannels::MaxNumber()) number = Key - k0; else number = number * 10 + Key - k0; - channel = Channels.GetByNumber(number); + LOCK_CHANNELS_READ + channel = Channels->GetByNumber(number); Refresh(); withInfo = false; // Lets see if there can be any useful further input: int n = channel ? number * 10 : 0; int m = 10; - cChannel *ch = channel; - while (ch && (ch = Channels.Next(ch)) != NULL) { + const cChannel *ch = channel; + while (ch && (ch = Channels->Next(ch)) != NULL) { if (!ch->GroupSep()) { if (n <= ch->Number() && ch->Number() < n + m) { n = 0; @@ -4344,23 +4483,23 @@ eOSState cDisplayChannel::ProcessKey(eKeys Key) case kNext|k_Repeat: case kNext: case kPrev|k_Repeat: - case kPrev: + case kPrev: { withInfo = false; number = 0; + LOCK_CHANNELS_READ; if (group < 0) { - cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel()); - if (channel) - group = channel->Index(); + if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel())) + group = Channel->Index(); } if (group >= 0) { int SaveGroup = group; if (NORMALKEY(Key) == kRight || NORMALKEY(Key) == kNext) - group = Channels.GetNextGroup(group) ; + group = Channels->GetNextGroup(group) ; else - group = Channels.GetPrevGroup(group < 1 ? 1 : group); + group = Channels->GetPrevGroup(group < 1 ? 1 : group); if (group < 0) group = SaveGroup; - channel = Channels.Get(group); + channel = Channels->Get(group); if (channel) { Refresh(); if (!channel->GroupSep()) @@ -4368,6 +4507,7 @@ eOSState cDisplayChannel::ProcessKey(eKeys Key) } } break; + } case kUp|k_Repeat: case kUp: case kDown|k_Repeat: @@ -4377,9 +4517,8 @@ eOSState cDisplayChannel::ProcessKey(eKeys Key) case kChanDn|k_Repeat: case kChanDn: { eKeys k = NORMALKEY(Key); - cChannel *ch = NextAvailableChannel(channel, (k == kUp || k == kChanUp) ? 1 : -1); - if (ch) - channel = ch; + if (const cChannel *Channel = NextAvailableChannel(channel, (k == kUp || k == kChanUp) ? 1 : -1)) + channel = Channel; else if (channel && channel->Number() != cDevice::CurrentChannel()) Key = k; // immediately switches channel when hitting the beginning/end of the channel list with k_Repeat } @@ -4399,7 +4538,8 @@ eOSState cDisplayChannel::ProcessKey(eKeys Key) break; case kNone: if (number && Setup.ChannelEntryTimeout && int(lastTime.Elapsed()) > Setup.ChannelEntryTimeout) { - channel = Channels.GetByNumber(number); + LOCK_CHANNELS_READ; + channel = Channels->GetByNumber(number); if (channel) NewChannel = channel; withInfo = true; @@ -4411,9 +4551,10 @@ eOSState cDisplayChannel::ProcessKey(eKeys Key) //TODO //XXX case kGreen: return osEventNow; //XXX case kYellow: return osEventNext; - case kOk: + case kOk: { + LOCK_CHANNELS_READ; if (group >= 0) { - channel = Channels.Get(Channels.GetNextNormal(group)); + channel = Channels->Get(Channels->GetNextNormal(group)); if (channel) NewChannel = channel; withInfo = true; @@ -4421,15 +4562,17 @@ eOSState cDisplayChannel::ProcessKey(eKeys Key) Refresh(); } else if (number > 0) { - channel = Channels.GetByNumber(number); + channel = Channels->GetByNumber(number); if (channel) NewChannel = channel; withInfo = true; number = 0; Refresh(); } - else + else { return osEnd; + } + } break; default: if ((Key & (k_Repeat | k_Release)) == 0) { @@ -4438,16 +4581,17 @@ eOSState cDisplayChannel::ProcessKey(eKeys Key) } }; if (positioner || !timeout || lastTime.Elapsed() < (uint64_t)(Setup.ChannelInfoTime * 1000)) { + LOCK_CHANNELS_READ; if (Key == kNone && !number && group < 0 && !NewChannel && channel && channel->Number() != cDevice::CurrentChannel()) { // makes sure a channel switch through the SVDRP CHAN command is displayed - channel = Channels.GetByNumber(cDevice::CurrentChannel()); + channel = Channels->GetByNumber(cDevice::CurrentChannel()); Refresh(); lastTime.Set(); } DisplayInfo(); if (NewChannel) { SetTrackDescriptions(NewChannel->Number()); // to make them immediately visible in the channel display - Channels.SwitchTo(NewChannel->Number()); + Channels->SwitchTo(NewChannel->Number()); SetTrackDescriptions(NewChannel->Number()); // switching the channel has cleared them channel = NewChannel; } @@ -4758,14 +4902,13 @@ eOSState cDisplaySubtitleTracks::ProcessKey(eKeys Key) // --- cRecordControl -------------------------------------------------------- -cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause) +cRecordControl::cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause) { // Whatever happens here, the timers will be modified in some way... - Timers.SetModified(); - // We're going to manipulate an event here, so we need to prevent + Timers->SetModified(); + // We're going to work with an event here, so we need to prevent // others from modifying any EPG data: - cSchedulesLock SchedulesLock; - cSchedules::Schedules(SchedulesLock); + LOCK_SCHEDULES_READ; event = NULL; fileName = NULL; @@ -4775,7 +4918,7 @@ cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause) timer = Timer; if (!timer) { timer = new cTimer(true, Pause); - Timers.Add(timer); + Timers->Add(timer); instantId = cString::sprintf(cDevice::NumDevices() > 1 ? "%s - %d" : "%s", timer->Channel()->Name(), device->CardIndex() + 1); } timer->SetPending(true); @@ -4796,7 +4939,7 @@ cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause) timer->OnOff(); } else { - Timers.Del(timer); + Timers->Del(timer); if (!cReplayControl::LastReplayed()) // an instant recording, maybe from cRecordControls::PauseLiveVideo() cReplayControl::SetRecording(fileName); } @@ -4814,7 +4957,8 @@ cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause) cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true); if (!Timer && !cReplayControl::LastReplayed()) // an instant recording, maybe from cRecordControls::PauseLiveVideo() cReplayControl::SetRecording(fileName); - Recordings.AddByName(fileName); + LOCK_RECORDINGS_WRITE; + Recordings->AddByName(fileName); return; } else @@ -4823,7 +4967,7 @@ cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause) else timer->SetDeferred(DEFERTIMER); if (!Timer) { - Timers.Del(timer); + Timers->Del(timer); timer = NULL; } } @@ -4838,21 +4982,17 @@ cRecordControl::~cRecordControl() bool cRecordControl::GetEvent(void) { - const cChannel *channel = timer->Channel(); + const cChannel *Channel = timer->Channel(); time_t Time = timer->HasFlags(tfInstant) ? timer->StartTime() + INSTANT_REC_EPG_LOOKAHEAD : timer->StartTime() + (timer->StopTime() - timer->StartTime()) / 2; for (int seconds = 0; seconds <= MAXWAIT4EPGINFO; seconds++) { { - cSchedulesLock SchedulesLock; - const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock); - if (Schedules) { - const cSchedule *Schedule = Schedules->GetSchedule(channel); - if (Schedule) { - event = Schedule->GetEventAround(Time); - if (event) { - if (seconds > 0) - dsyslog("got EPG info after %d seconds", seconds); - return true; - } + LOCK_SCHEDULES_READ; + if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) { + event = Schedule->GetEventAround(Time); + if (event) { + if (seconds > 0) + dsyslog("got EPG info after %d seconds", seconds); + return true; } } } @@ -4873,7 +5013,6 @@ void cRecordControl::Stop(bool ExecuteUserCommand) cStatus::MsgRecording(device, NULL, fileName, false); if (ExecuteUserCommand) cRecordingUserCommand::InvokeCommand(RUC_AFTERRECORDING, fileName); - Timers.SetModified(); } } @@ -4893,7 +5032,7 @@ bool cRecordControl::Process(time_t t) cRecordControl *cRecordControls::RecordControls[MAXRECORDCONTROLS] = { NULL }; int cRecordControls::state = 0; -bool cRecordControls::Start(cTimer *Timer, bool Pause) +bool cRecordControls::Start(cTimers *Timers, cTimer *Timer, bool Pause) { static time_t LastNoDiskSpaceMessage = 0; int FreeMB = 0; @@ -4913,29 +5052,28 @@ bool cRecordControls::Start(cTimer *Timer, bool Pause) LastNoDiskSpaceMessage = 0; ChangeState(); + LOCK_CHANNELS_READ; int ch = Timer ? Timer->Channel()->Number() : cDevice::CurrentChannel(); - cChannel *channel = Channels.GetByNumber(ch); - - if (channel) { + if (const cChannel *Channel = Channels->GetByNumber(ch)) { int Priority = Timer ? Timer->Priority() : Pause ? Setup.PausePriority : Setup.DefaultPriority; - cDevice *device = cDevice::GetDevice(channel, Priority, false); + cDevice *device = cDevice::GetDevice(Channel, Priority, false); if (device) { - dsyslog("switching device %d to channel %d (%s)", device->DeviceNumber() + 1, channel->Number(), channel->Name()); - if (!device->SwitchChannel(channel, false)) { + dsyslog("switching device %d to channel %d (%s)", device->DeviceNumber() + 1, Channel->Number(), Channel->Name()); + if (!device->SwitchChannel(Channel, false)) { ShutdownHandler.RequestEmergencyExit(); return false; } if (!Timer || Timer->Matches()) { for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (!RecordControls[i]) { - RecordControls[i] = new cRecordControl(device, Timer, Pause); + RecordControls[i] = new cRecordControl(device, Timers, Timer, Pause); return RecordControls[i]->Process(time(NULL)); } } } } else if (!Timer || !Timer->Pending()) { - isyslog("no free DVB device to record channel %d (%s)!", ch, channel->Name()); + isyslog("no free DVB device to record channel %d (%s)!", ch, Channel->Name()); Skins.Message(mtError, tr("No free DVB device to record!")); } } @@ -4944,19 +5082,25 @@ bool cRecordControls::Start(cTimer *Timer, bool Pause) return false; } +bool cRecordControls::Start(bool Pause) +{ + LOCK_TIMERS_WRITE; + return Start(Timers, NULL, Pause); +} + void cRecordControls::Stop(const char *InstantId) { + LOCK_TIMERS_WRITE; ChangeState(); for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (RecordControls[i]) { const char *id = RecordControls[i]->InstantId(); if (id && strcmp(id, InstantId) == 0) { - cTimer *timer = RecordControls[i]->Timer(); + cTimer *Timer = RecordControls[i]->Timer(); RecordControls[i]->Stop(); - if (timer) { - isyslog("deleting timer %s", *timer->ToDescr()); - Timers.Del(timer); - Timers.SetModified(); + if (Timer) { + isyslog("deleting timer %s", *Timer->ToDescr()); + Timers->Del(Timer); } break; } @@ -4968,7 +5112,7 @@ bool cRecordControls::PauseLiveVideo(void) { Skins.Message(mtStatus, tr("Pausing live video...")); cReplayControl::SetRecording(NULL); // make sure the new cRecordControl will set cReplayControl::LastReplayed() - if (Start(NULL, true)) { + if (Start(true)) { cReplayControl *rc = new cReplayControl(true); cControl::Launch(rc); cControl::Attach(); @@ -5012,19 +5156,22 @@ cRecordControl *cRecordControls::GetRecordControl(const cTimer *Timer) return NULL; } -void cRecordControls::Process(time_t t) +bool cRecordControls::Process(cTimers *Timers, time_t t) { + bool Result = false; for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (RecordControls[i]) { if (!RecordControls[i]->Process(t)) { DELETENULL(RecordControls[i]); ChangeState(); + Result = true; } } } + return Result; } -void cRecordControls::ChannelDataModified(cChannel *Channel) +void cRecordControls::ChannelDataModified(const cChannel *Channel) { for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (RecordControls[i]) { @@ -5146,19 +5293,25 @@ void cReplayControl::Stop(void) if (rc && rc->InstantId()) { if (Active()) { if (Setup.DelTimeshiftRec == 2 || Interface->Confirm(tr("Delete timeshift recording?"))) { - cTimer *timer = rc->Timer(); - rc->Stop(false); // don't execute user command - if (timer) { - isyslog("deleting timer %s", *timer->ToDescr()); - Timers.Del(timer); - Timers.SetModified(); - } + { + LOCK_TIMERS_WRITE; + Timers->SetExplicitModify(); + cTimer *Timer = rc->Timer(); + rc->Stop(false); // don't execute user command + if (Timer) { + isyslog("deleting timer %s", *Timer->ToDescr()); + Timers->Del(Timer); + Timers->SetModified(); + } + } cDvbPlayerControl::Stop(); - cRecording *recording = Recordings.GetByName(fileName); - if (recording) { - if (recording->Delete()) { - Recordings.DelByName(fileName); + LOCK_RECORDINGS_WRITE; + Recordings->SetExplicitModify(); + if (cRecording *Recording = Recordings->GetByName(fileName)) { + if (Recording->Delete()) { + Recordings->DelByName(fileName); ClearLastReplayed(fileName); + Recordings->SetModified(); } else Skins.Message(mtError, tr("Error while deleting recording!")); @@ -5184,7 +5337,8 @@ const char *cReplayControl::NowReplaying(void) const char *cReplayControl::LastReplayed(void) { - if (!Recordings.GetByName(fileName)) + LOCK_RECORDINGS_READ; + if (!Recordings->GetByName(fileName)) fileName = NULL; return fileName; } @@ -5269,7 +5423,8 @@ bool cReplayControl::ShowProgress(bool Initial) } if (Initial) { if (*fileName) { - if (cRecording *Recording = Recordings.GetByName(fileName)) + LOCK_RECORDINGS_READ; + if (const cRecording *Recording = Recordings->GetByName(fileName)) displayReplay->SetRecording(Recording); } lastCurrent = lastTotal = -1; @@ -5392,15 +5547,12 @@ void cReplayControl::MarkToggle(void) int Current, Total; if (GetIndex(Current, Total, true)) { lastCurrent = -1; // triggers redisplay - if (cMark *m = marks.Get(Current)) { - marks.Lock(); + cStateKey StateKey; + marks.Lock(StateKey); + if (cMark *m = marks.Get(Current)) marks.Del(m); - marks.Unlock(); - } else { - marks.Lock(); marks.Add(Current); - marks.Unlock(); bool Play, Forward; int Speed; if (Setup.PauseOnMarkSet || GetReplayMode(Play, Forward, Speed) && !Play) { @@ -5408,6 +5560,7 @@ void cReplayControl::MarkToggle(void) displayFrames = true; } } + StateKey.Remove(); ShowTimed(2); marksModified = true; } @@ -5515,15 +5668,16 @@ void cReplayControl::EditTest(void) cOsdObject *cReplayControl::GetInfo(void) { - cRecording *Recording = Recordings.GetByName(cReplayControl::LastReplayed()); - if (Recording) + LOCK_RECORDINGS_READ; + if (const cRecording *Recording = Recordings->GetByName(cReplayControl::LastReplayed())) return new cMenuRecording(Recording, false); return NULL; } const cRecording *cReplayControl::GetRecording(void) { - if (const cRecording *Recording = Recordings.GetByName(LastReplayed())) + LOCK_RECORDINGS_READ; + if (const cRecording *Recording = Recordings->GetByName(LastReplayed())) return Recording; return NULL; } diff --git a/menu.h b/menu.h index fad27afa..d4855edc 100644 --- a/menu.h +++ b/menu.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.h 3.8 2015/02/06 09:47:30 kls Exp $ + * $Id: menu.h 4.1 2015/08/31 13:34:12 kls Exp $ */ #ifndef __MENU_H @@ -72,6 +72,7 @@ public: class cMenuEditTimer : public cOsdMenu { private: + static const cTimer *addedTimer; cTimer *timer; cTimer data; int channel; @@ -86,13 +87,14 @@ public: cMenuEditTimer(cTimer *Timer, bool New = false); virtual ~cMenuEditTimer(); virtual eOSState ProcessKey(eKeys Key); + static const cTimer *AddedTimer(void); }; class cMenuEvent : public cOsdMenu { private: const cEvent *event; public: - cMenuEvent(const cEvent *Event, bool CanSwitch = false, bool Buttons = false); + cMenuEvent(const cTimers *Timers, const cChannels *Channels, const cEvent *Event, bool CanSwitch = false, bool Buttons = false); virtual void Display(void); virtual eOSState ProcessKey(eKeys Key); }; @@ -123,14 +125,14 @@ private: bool timeout; int osdState; const cPositioner *positioner; - cChannel *channel; + const cChannel *channel; const cEvent *lastPresent; const cEvent *lastFollowing; static cDisplayChannel *currentDisplayChannel; void DisplayChannel(void); void DisplayInfo(void); void Refresh(void); - cChannel *NextAvailableChannel(cChannel *Channel, int Direction); + const cChannel *NextAvailableChannel(const cChannel *Channel, int Direction); public: cDisplayChannel(int Number, bool Switched); cDisplayChannel(eKeys FirstKey); @@ -205,7 +207,7 @@ class cMenuRecordings : public cOsdMenu { private: char *base; int level; - int recordingsState; + cStateKey recordingsStateKey; int helpKeys; const cRecordingFilter *filter; static cString path; @@ -239,7 +241,7 @@ private: char *fileName; bool GetEvent(void); public: - cRecordControl(cDevice *Device, cTimer *Timer = NULL, bool Pause = false); + cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer = NULL, bool Pause = false); virtual ~cRecordControl(); bool Process(time_t t); cDevice *Device(void) { return device; } @@ -254,7 +256,8 @@ private: static cRecordControl *RecordControls[]; static int state; public: - static bool Start(cTimer *Timer = NULL, bool Pause = false); + static bool Start(cTimers *Timers, cTimer *Timer, bool Pause = false); + static bool Start(bool Pause = false); static void Stop(const char *InstantId); static bool PauseLiveVideo(void); static const char *GetInstantId(const char *LastInstantId); @@ -262,8 +265,8 @@ public: static cRecordControl *GetRecordControl(const cTimer *Timer); ///< Returns the cRecordControl for the given Timer. ///< If there is no cRecordControl for Timer, NULL is returned. - static void Process(time_t t); - static void ChannelDataModified(cChannel *Channel); + static bool Process(cTimers *Timers, time_t t); + static void ChannelDataModified(const cChannel *Channel); static bool Active(void); static void Shutdown(void); static void ChangeState(void) { state++; } diff --git a/menuitems.c b/menuitems.c index d0a068d5..c07ad2a7 100644 --- a/menuitems.c +++ b/menuitems.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menuitems.c 3.3 2015/02/09 11:53:10 kls Exp $ + * $Id: menuitems.c 4.1 2015/07/18 10:38:31 kls Exp $ */ #include "menuitems.h" @@ -777,7 +777,7 @@ void cMenuEditStraItem::Set(void) // --- cMenuEditChanItem ----------------------------------------------------- cMenuEditChanItem::cMenuEditChanItem(const char *Name, int *Value, const char *NoneString) -:cMenuEditIntItem(Name, Value, NoneString ? 0 : 1, Channels.MaxNumber()) +:cMenuEditIntItem(Name, Value, NoneString ? 0 : 1, cChannels::MaxNumber()) { channelID = NULL; noneString = NoneString; @@ -786,12 +786,13 @@ cMenuEditChanItem::cMenuEditChanItem(const char *Name, int *Value, const char *N } cMenuEditChanItem::cMenuEditChanItem(const char *Name, cString *ChannelID, const char *NoneString) -:cMenuEditIntItem(Name, &dummyValue, NoneString ? 0 : 1, Channels.MaxNumber()) +:cMenuEditIntItem(Name, &dummyValue, NoneString ? 0 : 1, cChannels::MaxNumber()) { channelID = ChannelID; noneString = NoneString; - cChannel *channel = Channels.GetByChannelID(tChannelID::FromString(*ChannelID)); - dummyValue = channel ? channel->Number() : 0; + LOCK_CHANNELS_READ; + const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(*ChannelID)); + dummyValue = Channel ? Channel->Number() : 0; Set(); } @@ -799,11 +800,12 @@ void cMenuEditChanItem::Set(void) { if (*value > 0) { char buf[255]; - cChannel *channel = Channels.GetByNumber(*value); - snprintf(buf, sizeof(buf), "%d %s", *value, channel ? channel->Name() : ""); + LOCK_CHANNELS_READ; + const cChannel *Channel = Channels->GetByNumber(*value); + snprintf(buf, sizeof(buf), "%d %s", *value, Channel ? Channel->Name() : ""); SetValue(buf); if (channelID) - *channelID = channel ? channel->GetChannelID().ToString() : ""; + *channelID = Channel ? Channel->GetChannelID().ToString() : ""; } else if (noneString) { SetValue(noneString); @@ -822,13 +824,14 @@ eOSState cMenuEditChanItem::ProcessKey(eKeys Key) case kRight|k_Repeat: case kRight: { - cChannel *channel = Channels.GetByNumber(*value + delta, delta); - if (channel) - *value = channel->Number(); + LOCK_CHANNELS_READ + const cChannel *Channel = Channels->GetByNumber(*value + delta, delta); + if (Channel) + *value = Channel->Number(); else if (delta < 0 && noneString) *value = 0; if (channelID) - *channelID = channel ? channel->GetChannelID().ToString() : ""; + *channelID = Channel ? Channel->GetChannelID().ToString() : ""; Set(); } break; @@ -845,13 +848,14 @@ cMenuEditTranItem::cMenuEditTranItem(const char *Name, int *Value, int *Source) number = 0; source = Source; transponder = Value; - cChannel *channel = Channels.First(); - while (channel) { - if (!channel->GroupSep() && *source == channel->Source() && ISTRANSPONDER(channel->Transponder(), *Value)) { - number = channel->Number(); + LOCK_CHANNELS_READ; + const cChannel *Channel = Channels->First(); + while (Channel) { + if (!Channel->GroupSep() && *source == Channel->Source() && ISTRANSPONDER(Channel->Transponder(), *Value)) { + number = Channel->Number(); break; } - channel = (cChannel *)channel->Next(); + Channel = Channels->Next(Channel); } Set(); } @@ -859,10 +863,10 @@ cMenuEditTranItem::cMenuEditTranItem(const char *Name, int *Value, int *Source) eOSState cMenuEditTranItem::ProcessKey(eKeys Key) { eOSState state = cMenuEditChanItem::ProcessKey(Key); - cChannel *channel = Channels.GetByNumber(number); - if (channel) { - *source = channel->Source(); - *transponder = channel->Transponder(); + LOCK_CHANNELS_READ + if (const cChannel *Channel = Channels->GetByNumber(number)) { + *source = Channel->Source(); + *transponder = Channel->Transponder(); } else { *source = 0; diff --git a/nit.c b/nit.c index 15579453..864fdac2 100644 --- a/nit.c +++ b/nit.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: nit.c 4.2 2015/03/17 15:10:09 kls Exp $ + * $Id: nit.c 4.3 2015/07/26 09:24:36 kls Exp $ */ #include "nit.h" @@ -61,10 +61,13 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length } dbgnit("NIT: %02X %2d %2d %2d %s %d %d '%s'\n", Tid, nit.getVersionNumber(), nit.getSectionNumber(), nit.getLastSectionNumber(), *cSource::ToString(Source()), nit.getNetworkId(), Transponder(), NetworkName); } - if (!Channels.Lock(true, 10)) { + cStateKey StateKey; + cChannels *Channels = cChannels::GetChannelsWrite(StateKey, 10); + if (!Channels) { sectionSyncer.Repeat(); // let's not miss any section of the NIT return; } + bool ChannelsModified = false; SI::NIT::TransportStream ts; for (SI::Loop::Iterator it; nit.transportStreamLoop.getNext(ts, it); ) { SI::Descriptor *d; @@ -115,7 +118,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length if (Setup.UpdateChannels >= 5) { bool found = false; bool forceTransponderUpdate = false; - for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) { + for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) { int transponder = Channel->Transponder(); found = true; @@ -128,7 +131,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length } } if (ISTRANSPONDER(cChannel::Transponder(Frequency, dtp.Polarization()), Transponder())) // only modify channels if we're actually receiving this transponder - Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('S')); + ChannelsModified |= Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('S')); else if (Channel->Srate() != SymbolRate || strcmp(Channel->Parameters(), dtp.ToString('S'))) forceTransponderUpdate = true; // get us receiving this transponder } @@ -136,7 +139,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length if (!found || forceTransponderUpdate) { for (int n = 0; n < NumFrequencies; n++) { cChannel *Channel = new cChannel; - Channel->SetId(ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0); + Channel->SetId(NULL, ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0); if (Channel->SetTransponderData(Source, Frequencies[n], SymbolRate, dtp.ToString('S'))) EITScanner.AddTransponder(Channel); else @@ -150,13 +153,13 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length break; case SI::S2SatelliteDeliverySystemDescriptorTag: { if (Setup.UpdateChannels >= 5) { - for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) { + for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { if (!Channel->GroupSep() && cSource::IsSat(Channel->Source()) && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) { SI::S2SatelliteDeliverySystemDescriptor *sd = (SI::S2SatelliteDeliverySystemDescriptor *)d; cDvbTransponderParameters dtp(Channel->Parameters()); dtp.SetSystem(DVB_SYSTEM_2); dtp.SetStreamId(sd->getInputStreamIdentifier()); - Channel->SetTransponderData(Channel->Source(), Channel->Frequency(), Channel->Srate(), dtp.ToString('S')); + ChannelsModified |= Channel->SetTransponderData(Channel->Source(), Channel->Frequency(), Channel->Srate(), dtp.ToString('S')); break; } } @@ -178,7 +181,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length if (Setup.UpdateChannels >= 5) { bool found = false; bool forceTransponderUpdate = false; - for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) { + for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) { int transponder = Channel->Transponder(); found = true; @@ -191,7 +194,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length } } if (ISTRANSPONDER(Frequency / 1000, Transponder())) // only modify channels if we're actually receiving this transponder - Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('C')); + ChannelsModified |= Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('C')); else if (Channel->Srate() != SymbolRate || strcmp(Channel->Parameters(), dtp.ToString('C'))) forceTransponderUpdate = true; // get us receiving this transponder } @@ -199,7 +202,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length if (!found || forceTransponderUpdate) { for (int n = 0; n < NumFrequencies; n++) { cChannel *Channel = new cChannel; - Channel->SetId(ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0); + Channel->SetId(NULL, ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0); if (Channel->SetTransponderData(Source, Frequencies[n], SymbolRate, dtp.ToString('C'))) EITScanner.AddTransponder(Channel); else @@ -234,7 +237,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length if (Setup.UpdateChannels >= 5) { bool found = false; bool forceTransponderUpdate = false; - for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) { + for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) { int transponder = Channel->Transponder(); found = true; @@ -247,7 +250,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length } } if (ISTRANSPONDER(Frequency / 1000000, Transponder())) // only modify channels if we're actually receiving this transponder - Channel->SetTransponderData(Source, Frequency, 0, dtp.ToString('T')); + ChannelsModified |= Channel->SetTransponderData(Source, Frequency, 0, dtp.ToString('T')); else if (strcmp(Channel->Parameters(), dtp.ToString('T'))) forceTransponderUpdate = true; // get us receiving this transponder } @@ -255,7 +258,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length if (!found || forceTransponderUpdate) { for (int n = 0; n < NumFrequencies; n++) { cChannel *Channel = new cChannel; - Channel->SetId(ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0); + Channel->SetId(NULL, ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0); if (Channel->SetTransponderData(Source, Frequencies[n], 0, dtp.ToString('T'))) EITScanner.AddTransponder(Channel); else @@ -272,7 +275,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length switch (sd->getExtensionDescriptorTag()) { case SI::T2DeliverySystemDescriptorTag: { if (Setup.UpdateChannels >= 5) { - for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) { + for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { int Source = cSource::FromData(cSource::stTerr); if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) { SI::T2DeliverySystemDescriptor *td = (SI::T2DeliverySystemDescriptor *)d; @@ -292,7 +295,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length dtp.SetTransmission(T2TransmissionModes[td->getTransmissionMode()]); //TODO add parsing of frequencies } - Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('T')); + ChannelsModified |= Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('T')); } } } @@ -310,9 +313,9 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length int lcn = LogicalChannel.getLogicalChannelNumber(); int sid = LogicalChannel.getServiceId(); if (LogicalChannel.getVisibleServiceFlag()) { - for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) { + for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { if (!Channel->GroupSep() && Channel->Sid() == sid && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) { - Channel->SetLcn(lcn); + ChannelsModified |= Channel->SetLcn(lcn); break; } } @@ -328,9 +331,9 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length int lcn = HdSimulcastLogicalChannel.getLogicalChannelNumber(); int sid = HdSimulcastLogicalChannel.getServiceId(); if (HdSimulcastLogicalChannel.getVisibleServiceFlag()) { - for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) { + for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { if (!Channel->GroupSep() && Channel->Sid() == sid && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) { - Channel->SetLcn(lcn); + ChannelsModified |= Channel->SetLcn(lcn); break; } } @@ -343,5 +346,5 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length delete d; } } - Channels.Unlock(); + StateKey.Remove(ChannelsModified); } diff --git a/pat.c b/pat.c index 98d306eb..beb5609a 100644 --- a/pat.c +++ b/pat.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: pat.c 3.5 2015/01/04 13:14:01 kls Exp $ + * $Id: pat.c 4.1 2015/08/17 08:46:55 kls Exp $ */ #include "pat.h" @@ -99,8 +99,8 @@ cCaDescriptors::cCaDescriptors(int Source, int Transponder, int ServiceId, int P bool cCaDescriptors::operator== (const cCaDescriptors &arg) const { - cCaDescriptor *ca1 = caDescriptors.First(); - cCaDescriptor *ca2 = arg.caDescriptors.First(); + const cCaDescriptor *ca1 = caDescriptors.First(); + const cCaDescriptor *ca2 = arg.caDescriptors.First(); while (ca1 && ca2) { if (!(*ca1 == *ca2)) return false; @@ -396,11 +396,14 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length SwitchToNextPmtPid(); return; } - if (!Channels.Lock(true, 10)) + cStateKey StateKey; + cChannels *Channels = cChannels::GetChannelsWrite(StateKey, 10); + if (!Channels) return; + bool ChannelsModified = false; PmtVersionChanged(Pid, pmt.getTableIdExtension(), pmt.getVersionNumber(), true); SwitchToNextPmtPid(); - cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), pmt.getServiceId()); + cChannel *Channel = Channels->GetByServiceID(Source(), Transponder(), pmt.getServiceId()); if (Channel) { SI::CaDescriptor *d; cCaDescriptors *CaDescriptors = new cCaDescriptors(Channel->Source(), Channel->Transponder(), Channel->Sid(), Pid); @@ -629,13 +632,13 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length } } if (Setup.UpdateChannels >= 2) { - Channel->SetPids(Vpid, Ppid, Vtype, Apids, Atypes, ALangs, Dpids, Dtypes, DLangs, Spids, SLangs, Tpid); - Channel->SetCaIds(CaDescriptors->CaIds()); - Channel->SetSubtitlingDescriptors(SubtitlingTypes, CompositionPageIds, AncillaryPageIds); + ChannelsModified |= Channel->SetPids(Vpid, Ppid, Vtype, Apids, Atypes, ALangs, Dpids, Dtypes, DLangs, Spids, SLangs, Tpid); + ChannelsModified |= Channel->SetCaIds(CaDescriptors->CaIds()); + ChannelsModified |= Channel->SetSubtitlingDescriptors(SubtitlingTypes, CompositionPageIds, AncillaryPageIds); } - Channel->SetCaDescriptors(CaDescriptorHandler.AddCaDescriptors(CaDescriptors)); + ChannelsModified |= Channel->SetCaDescriptors(CaDescriptorHandler.AddCaDescriptors(CaDescriptors)); } - Channels.Unlock(); + StateKey.Remove(ChannelsModified); } if (timer.TimedOut()) { if (pmtIndex >= 0) diff --git a/recorder.c b/recorder.c index 3d6b8063..429c9375 100644 --- a/recorder.c +++ b/recorder.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recorder.c 3.3 2014/02/21 09:19:52 kls Exp $ + * $Id: recorder.c 4.1 2015/08/03 10:23:35 kls Exp $ */ #include "recorder.h" @@ -135,7 +135,8 @@ void cRecorder::Action(void) if (frameDetector->FramesPerSecond() > 0 && DoubleEqual(RecordingInfo.FramesPerSecond(), DEFAULTFRAMESPERSECOND) && !DoubleEqual(RecordingInfo.FramesPerSecond(), frameDetector->FramesPerSecond())) { RecordingInfo.SetFramesPerSecond(frameDetector->FramesPerSecond()); RecordingInfo.Write(); - Recordings.UpdateByName(recordingName); + LOCK_RECORDINGS_WRITE; + Recordings->UpdateByName(recordingName); } } InfoWritten = true; diff --git a/recording.c b/recording.c index a28bb444..3f5cec7c 100644 --- a/recording.c +++ b/recording.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recording.c 4.2 2015/04/18 13:14:55 kls Exp $ + * $Id: recording.c 4.3 2015/08/29 14:42:53 kls Exp $ */ #include "recording.h" @@ -76,9 +76,6 @@ int DirectoryNameMax = NAME_MAX; bool DirectoryEncoding = false; int InstanceId = 0; -cRecordings DeletedRecordings(true); -static cRecordings VanishedRecordings; - // --- cRemoveDeletedRecordingsThread ---------------------------------------- class cRemoveDeletedRecordingsThread : public cThread { @@ -100,8 +97,8 @@ void cRemoveDeletedRecordingsThread::Action(void) if (LockFile.Lock()) { time_t StartTime = time(NULL); bool deleted = false; - cThreadLock DeletedRecordingsLock(&DeletedRecordings); - for (cRecording *r = DeletedRecordings.First(); r; ) { + LOCK_DELETEDRECORDINGS_WRITE; + for (cRecording *r = DeletedRecordings->First(); r; ) { if (cIoThrottle::Engaged()) return; if (time(NULL) - StartTime > MAXREMOVETIME) @@ -109,14 +106,14 @@ void cRemoveDeletedRecordingsThread::Action(void) if (cRemote::HasKeys()) return; // react immediately on user input if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) { - cRecording *next = DeletedRecordings.Next(r); + cRecording *next = DeletedRecordings->Next(r); r->Remove(); - DeletedRecordings.Del(r); + DeletedRecordings->Del(r); r = next; deleted = true; continue; } - r = DeletedRecordings.Next(r); + r = DeletedRecordings->Next(r); } if (deleted) { const char *IgnoreFiles[] = { SORTMODEFILE, NULL }; @@ -134,8 +131,8 @@ void RemoveDeletedRecordings(void) static time_t LastRemoveCheck = 0; if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) { if (!RemoveDeletedRecordingsThread.Active()) { - cThreadLock DeletedRecordingsLock(&DeletedRecordings); - for (cRecording *r = DeletedRecordings.First(); r; r = DeletedRecordings.Next(r)) { + LOCK_DELETEDRECORDINGS_READ; + for (const cRecording *r = DeletedRecordings->First(); r; r = DeletedRecordings->Next(r)) { if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) { RemoveDeletedRecordingsThread.Start(); break; @@ -163,37 +160,43 @@ void AssertFreeDiskSpace(int Priority, bool Force) return; // Remove the oldest file that has been "deleted": isyslog("low disk space while recording, trying to remove a deleted recording..."); - cThreadLock DeletedRecordingsLock(&DeletedRecordings); - if (DeletedRecordings.Count()) { - cRecording *r = DeletedRecordings.First(); - cRecording *r0 = NULL; - while (r) { - if (r->IsOnVideoDirectoryFileSystem()) { // only remove recordings that will actually increase the free video disk space - if (!r0 || r->Start() < r0->Start()) - r0 = r; - } - r = DeletedRecordings.Next(r); - } - if (r0) { - if (r0->Remove()) - LastFreeDiskCheck += REMOVELATENCY / Factor; - DeletedRecordings.Del(r0); - return; - } - } - else { + int NumDeletedRecordings = 0; + { + LOCK_DELETEDRECORDINGS_WRITE; + NumDeletedRecordings = DeletedRecordings->Count(); + if (NumDeletedRecordings) { + cRecording *r = DeletedRecordings->First(); + cRecording *r0 = NULL; + while (r) { + if (r->IsOnVideoDirectoryFileSystem()) { // only remove recordings that will actually increase the free video disk space + if (!r0 || r->Start() < r0->Start()) + r0 = r; + } + r = DeletedRecordings->Next(r); + } + if (r0) { + if (r0->Remove()) + LastFreeDiskCheck += REMOVELATENCY / Factor; + DeletedRecordings->Del(r0); + return; + } + } + } + if (NumDeletedRecordings == 0) { // DeletedRecordings was empty, so to be absolutely sure there are no // deleted recordings we need to double check: - DeletedRecordings.Update(true); - if (DeletedRecordings.Count()) + cRecordings::Update(true); + LOCK_DELETEDRECORDINGS_READ; + if (DeletedRecordings->Count()) return; // the next call will actually remove it } // No "deleted" files to remove, so let's see if we can delete a recording: if (Priority > 0) { isyslog("...no deleted recording found, trying to delete an old recording..."); - cThreadLock RecordingsLock(&Recordings); - if (Recordings.Count()) { - cRecording *r = Recordings.First(); + LOCK_RECORDINGS_WRITE; + Recordings->SetExplicitModify(); + if (Recordings->Count()) { + cRecording *r = Recordings->First(); cRecording *r0 = NULL; while (r) { if (r->IsOnVideoDirectoryFileSystem()) { // only delete recordings that will actually increase the free video disk space @@ -209,10 +212,11 @@ void AssertFreeDiskSpace(int Priority, bool Force) } } } - r = Recordings.Next(r); + r = Recordings->Next(r); } if (r0 && r0->Delete()) { - Recordings.Del(r0); + Recordings->Del(r0); + Recordings->SetModified(); return; } } @@ -227,14 +231,6 @@ void AssertFreeDiskSpace(int Priority, bool Force) } } -// --- Clear vanished recordings --------------------------------------------- - -void ClearVanishedRecordings(void) -{ - cThreadLock RecordingsLock(&Recordings); // yes, it *is* Recordings! - VanishedRecordings.Clear(); -} - // --- cResumeFile ----------------------------------------------------------- cResumeFile::cResumeFile(const char *FileName, bool IsPesRecording) @@ -309,7 +305,8 @@ bool cResumeFile::Save(int Index) if (safe_write(f, &Index, sizeof(Index)) < 0) LOG_ERROR_STR(fileName); close(f); - Recordings.ResetResume(fileName); + LOCK_RECORDINGS_WRITE; + Recordings->ResetResume(fileName); return true; } } @@ -318,7 +315,8 @@ bool cResumeFile::Save(int Index) if (f) { fprintf(f, "I %d\n", Index); fclose(f); - Recordings.ResetResume(fileName); + LOCK_RECORDINGS_WRITE; + Recordings->ResetResume(fileName); } else LOG_ERROR_STR(fileName); @@ -331,8 +329,10 @@ bool cResumeFile::Save(int Index) void cResumeFile::Delete(void) { if (fileName) { - if (remove(fileName) == 0) - Recordings.ResetResume(fileName); + if (remove(fileName) == 0) { + LOCK_RECORDINGS_WRITE; + Recordings->ResetResume(fileName); + } else if (errno != ENOENT) LOG_ERROR_STR(fileName); } @@ -787,10 +787,8 @@ cRecording::cRecording(cTimer *Timer, const cEvent *Event) else break; } - if (Timer->IsSingleEvent()) { + if (Timer->IsSingleEvent()) Timer->SetFile(name); // this was an instant recording, so let's set the actual data - Timers.SetModified(); - } } else if (Timer->IsSingleEvent() || !Setup.UseSubtitle) name = strdup(Timer->File()); @@ -1017,7 +1015,7 @@ int cRecording::Compare(const cListObject &ListObject) const return strcasecmp(SortName(), r->SortName()); } -bool cRecording::IsInPath(const char *Path) +bool cRecording::IsInPath(const char *Path) const { if (isempty(Path)) return true; @@ -1154,20 +1152,14 @@ bool cRecording::IsOnVideoDirectoryFileSystem(void) const return isOnVideoDirectoryFileSystem; } -bool cRecording::HasMarks(void) +bool cRecording::HasMarks(void) const { return access(cMarks::MarksFileName(this), F_OK) == 0; } bool cRecording::DeleteMarks(void) { - if (remove(cMarks::MarksFileName(this)) < 0) { - if (errno != ENOENT) { - LOG_ERROR_STR(fileName); - return false; - } - } - return true; + return cMarks::DeleteMarksFile(this); } void cRecording::ReadInfo(void) @@ -1219,8 +1211,6 @@ bool cRecording::ChangePriorityLifetime(int NewPriority, int NewLifetime) if (!WriteInfo()) return false; } - Recordings.ChangeState(); - Recordings.TouchUpdate(); } return true; } @@ -1245,8 +1235,6 @@ bool cRecording::ChangeName(const char *NewName) } isOnVideoDirectoryFileSystem = -1; // it might have been moved to a different file system ClearSortName(); - Recordings.ChangeState(); - Recordings.TouchUpdate(); } return true; } @@ -1360,59 +1348,53 @@ int cRecording::FileSizeMB(void) const return fileSizeMB; } -// --- cRecordings ----------------------------------------------------------- +// --- cVideoDirectoryScannerThread ------------------------------------------ -cRecordings Recordings; +class cVideoDirectoryScannerThread : public cThread { +private: + cRecordings *recordings; + cRecordings *deletedRecordings; + bool initial; + void ScanVideoDir(const char *DirName, int LinkLevel = 0, int DirLevel = 0); +protected: + virtual void Action(void); +public: + cVideoDirectoryScannerThread(cRecordings *Recordings, cRecordings *DeletedRecordings); + ~cVideoDirectoryScannerThread(); + }; -char *cRecordings::updateFileName = NULL; - -cRecordings::cRecordings(bool Deleted) +cVideoDirectoryScannerThread::cVideoDirectoryScannerThread(cRecordings *Recordings, cRecordings *DeletedRecordings) :cThread("video directory scanner", true) { - deleted = Deleted; + recordings = Recordings; + deletedRecordings = DeletedRecordings; initial = true; - lastUpdate = 0; - state = 0; } -cRecordings::~cRecordings() +cVideoDirectoryScannerThread::~cVideoDirectoryScannerThread() { Cancel(3); } -void cRecordings::Action(void) +void cVideoDirectoryScannerThread::Action(void) { - Refresh(); + cStateKey StateKey; + recordings->Lock(StateKey); + initial = recordings->Count() == 0; // no name checking if the list is initially empty + StateKey.Remove(); + deletedRecordings->Lock(StateKey, true); + deletedRecordings->Clear(); + StateKey.Remove(); + ScanVideoDir(cVideoDirectory::Name()); } -const char *cRecordings::UpdateFileName(void) +void cVideoDirectoryScannerThread::ScanVideoDir(const char *DirName, int LinkLevel, int DirLevel) { - if (!updateFileName) - updateFileName = strdup(AddDirectory(cVideoDirectory::Name(), ".update")); - return updateFileName; -} - -void cRecordings::Refresh(bool Foreground) -{ - lastUpdate = time(NULL); // doing this first to make sure we don't miss anything - initial = Count() == 0; // no name checking if the list is initially empty - if (deleted) { - Lock(); - Clear(); - ChangeState(); - Unlock(); - } - ScanVideoDir(cVideoDirectory::Name(), Foreground); -} - -bool cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLevel, int DirLevel) -{ - bool DoChangeState = false; // Find any new recordings: cReadDir d(DirName); struct dirent *e; - while ((Foreground || Running()) && (e = d.Next()) != NULL) { - if (!Foreground && cIoThrottle::Engaged()) + while (Running() && (e = d.Next()) != NULL) { + if (cIoThrottle::Engaged()) cCondWait::SleepMs(100); cString buffer = AddDirectory(DirName, e->d_name); struct stat st; @@ -1428,57 +1410,73 @@ bool cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLev continue; } if (S_ISDIR(st.st_mode)) { - if (endswith(buffer, deleted ? DELEXT : RECEXT)) { - if (deleted || initial || !GetByName(buffer)) { + cRecordings *Recordings = NULL; + if (endswith(buffer, RECEXT)) + Recordings = recordings; + else if (endswith(buffer, DELEXT)) + Recordings = deletedRecordings; + if (Recordings) { + cStateKey StateKey; + Recordings->Lock(StateKey, true); + if (Recordings == deletedRecordings || initial || !Recordings->GetByName(buffer)) { cRecording *r = new cRecording(buffer); if (r->Name()) { r->NumFrames(); // initializes the numFrames member r->FileSizeMB(); // initializes the fileSizeMB member r->IsOnVideoDirectoryFileSystem(); // initializes the isOnVideoDirectoryFileSystem member - if (deleted) - r->deleted = time(NULL); - Lock(); - Add(r); - if (initial) - ChangeState(); - else - DoChangeState = true; - Unlock(); + if (Recordings == deletedRecordings) + r->SetDeleted(); + Recordings->Add(r); } else delete r; } + StateKey.Remove(); } else - DoChangeState |= ScanVideoDir(buffer, Foreground, LinkLevel + Link, DirLevel + 1); + ScanVideoDir(buffer, LinkLevel + Link, DirLevel + 1); } } } // Handle any vanished recordings: - if (!deleted && !initial && DirLevel == 0) { - for (cRecording *recording = First(); recording; ) { - cRecording *r = recording; - recording = Next(recording); - if (access(r->FileName(), F_OK) != 0) { - Lock(); - Del(r, false); - VanishedRecordings.Add(r); - DoChangeState = true; - Unlock(); - } + if (!initial && DirLevel == 0) { + cStateKey StateKey; + recordings->Lock(StateKey, true); + for (cRecording *Recording = recordings->First(); Recording; ) { + cRecording *r = Recording; + Recording = recordings->Next(Recording); + if (access(r->FileName(), F_OK) != 0) + recordings->Del(r); } + StateKey.Remove(); } - if (DoChangeState && DirLevel == 0) - ChangeState(); - return DoChangeState; } -bool cRecordings::StateChanged(int &State) +// --- cRecordings ----------------------------------------------------------- + +cRecordings cRecordings::recordings; +cRecordings cRecordings::deletedRecordings(true); +char *cRecordings::updateFileName = NULL; +cVideoDirectoryScannerThread *cRecordings::videoDirectoryScannerThread = NULL; +time_t cRecordings::lastUpdate = 0; + +cRecordings::cRecordings(bool Deleted) +:cList(Deleted ? "DelRecs" : "Recordings") { - int NewState = state; - bool Result = State != NewState; - State = state; - return Result; +} + +cRecordings::~cRecordings() +{ + // The first one to be destructed deletes it: + delete videoDirectoryScannerThread; + videoDirectoryScannerThread = NULL; +} + +const char *cRecordings::UpdateFileName(void) +{ + if (!updateFileName) + updateFileName = strdup(AddDirectory(cVideoDirectory::Name(), ".update")); + return updateFileName; } void cRecordings::TouchUpdate(void) @@ -1497,24 +1495,24 @@ bool cRecordings::NeedsUpdate(void) return lastUpdate < lastModified; } -bool cRecordings::Update(bool Wait) +void cRecordings::Update(bool Wait) { + if (!videoDirectoryScannerThread) + videoDirectoryScannerThread = new cVideoDirectoryScannerThread(&recordings, &deletedRecordings); + lastUpdate = time(NULL); // doing this first to make sure we don't miss anything + videoDirectoryScannerThread->Start(); if (Wait) { - Refresh(true); - return Count() > 0; + while (videoDirectoryScannerThread->Active()) + cCondWait::SleepMs(100); } - else - Start(); - return false; } -cRecording *cRecordings::GetByName(const char *FileName) +const cRecording *cRecordings::GetByName(const char *FileName) const { if (FileName) { - LOCK_THREAD; - for (cRecording *recording = First(); recording; recording = Next(recording)) { - if (strcmp(recording->FileName(), FileName) == 0) - return recording; + for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) { + if (strcmp(Recording->FileName(), FileName) == 0) + return Recording; } } return NULL; @@ -1522,12 +1520,8 @@ cRecording *cRecordings::GetByName(const char *FileName) void cRecordings::AddByName(const char *FileName, bool TriggerUpdate) { - LOCK_THREAD; - cRecording *recording = GetByName(FileName); - if (!recording) { - recording = new cRecording(FileName); - Add(recording); - ChangeState(); + if (!GetByName(FileName)) { + Add(new cRecording(FileName)); if (TriggerUpdate) TouchUpdate(); } @@ -1535,58 +1529,52 @@ void cRecordings::AddByName(const char *FileName, bool TriggerUpdate) void cRecordings::DelByName(const char *FileName) { - LOCK_THREAD; - cRecording *recording = GetByName(FileName); + cRecording *Recording = GetByName(FileName); cRecording *dummy = NULL; - if (!recording) - recording = dummy = new cRecording(FileName); // allows us to use a FileName that is not in the Recordings list - cThreadLock DeletedRecordingsLock(&DeletedRecordings); + if (!Recording) + Recording = dummy = new cRecording(FileName); // allows us to use a FileName that is not in the Recordings list + LOCK_DELETEDRECORDINGS_WRITE; if (!dummy) - Del(recording, false); - char *ext = strrchr(recording->fileName, '.'); + Del(Recording, false); + char *ext = strrchr(Recording->fileName, '.'); if (ext) { strncpy(ext, DELEXT, strlen(ext)); - if (access(recording->FileName(), F_OK) == 0) { - recording->deleted = time(NULL); - DeletedRecordings.Add(recording); - recording = NULL; // to prevent it from being deleted below + if (access(Recording->FileName(), F_OK) == 0) { + Recording->SetDeleted(); + DeletedRecordings->Add(Recording); + Recording = NULL; // to prevent it from being deleted below } } - delete recording; - ChangeState(); + delete Recording; TouchUpdate(); } void cRecordings::UpdateByName(const char *FileName) { - LOCK_THREAD; - cRecording *recording = GetByName(FileName); - if (recording) - recording->ReadInfo(); + if (cRecording *Recording = GetByName(FileName)) + Recording->ReadInfo(); } -int cRecordings::TotalFileSizeMB(void) +int cRecordings::TotalFileSizeMB(void) const { int size = 0; - LOCK_THREAD; - for (cRecording *recording = First(); recording; recording = Next(recording)) { - int FileSizeMB = recording->FileSizeMB(); - if (FileSizeMB > 0 && recording->IsOnVideoDirectoryFileSystem()) + for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) { + int FileSizeMB = Recording->FileSizeMB(); + if (FileSizeMB > 0 && Recording->IsOnVideoDirectoryFileSystem()) size += FileSizeMB; } return size; } -double cRecordings::MBperMinute(void) +double cRecordings::MBperMinute(void) const { int size = 0; int length = 0; - LOCK_THREAD; - for (cRecording *recording = First(); recording; recording = Next(recording)) { - if (recording->IsOnVideoDirectoryFileSystem()) { - int FileSizeMB = recording->FileSizeMB(); + for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) { + if (Recording->IsOnVideoDirectoryFileSystem()) { + int FileSizeMB = Recording->FileSizeMB(); if (FileSizeMB > 0) { - int LengthInSeconds = recording->LengthInSeconds(); + int LengthInSeconds = Recording->LengthInSeconds(); if (LengthInSeconds > 0) { if (LengthInSeconds / FileSizeMB < LIMIT_SECS_PER_MB_RADIO) { // don't count radio recordings size += FileSizeMB; @@ -1599,23 +1587,21 @@ double cRecordings::MBperMinute(void) return (size && length) ? double(size) * 60 / length : -1; } -int cRecordings::PathIsInUse(const char *Path) +int cRecordings::PathIsInUse(const char *Path) const { - LOCK_THREAD; int Use = ruNone; - for (cRecording *recording = First(); recording; recording = Next(recording)) { - if (recording->IsInPath(Path)) - Use |= recording->IsInUse(); + for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) { + if (Recording->IsInPath(Path)) + Use |= Recording->IsInUse(); } return Use; } -int cRecordings::GetNumRecordingsInPath(const char *Path) +int cRecordings::GetNumRecordingsInPath(const char *Path) const { - LOCK_THREAD; int n = 0; - for (cRecording *recording = First(); recording; recording = Next(recording)) { - if (recording->IsInPath(Path)) + for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) { + if (Recording->IsInPath(Path)) n++; } return n; @@ -1624,36 +1610,35 @@ int cRecordings::GetNumRecordingsInPath(const char *Path) bool cRecordings::MoveRecordings(const char *OldPath, const char *NewPath) { if (OldPath && NewPath && strcmp(OldPath, NewPath)) { - LOCK_THREAD; dsyslog("moving '%s' to '%s'", OldPath, NewPath); - for (cRecording *recording = First(); recording; recording = Next(recording)) { - if (recording->IsInPath(OldPath)) { - const char *p = recording->Name() + strlen(OldPath); + bool Moved = false; + for (cRecording *Recording = First(); Recording; Recording = Next(Recording)) { + if (Recording->IsInPath(OldPath)) { + const char *p = Recording->Name() + strlen(OldPath); cString NewName = cString::sprintf("%s%s", NewPath, p); - if (!recording->ChangeName(NewName)) + if (!Recording->ChangeName(NewName)) return false; - ChangeState(); + Moved = true; } } + if (Moved) + TouchUpdate(); } return true; } void cRecordings::ResetResume(const char *ResumeFileName) { - LOCK_THREAD; - for (cRecording *recording = First(); recording; recording = Next(recording)) { - if (!ResumeFileName || strncmp(ResumeFileName, recording->FileName(), strlen(recording->FileName())) == 0) - recording->ResetResume(); + for (cRecording *Recording = First(); Recording; Recording = Next(Recording)) { + if (!ResumeFileName || strncmp(ResumeFileName, Recording->FileName(), strlen(Recording->FileName())) == 0) + Recording->ResetResume(); } - ChangeState(); } void cRecordings::ClearSortNames(void) { - LOCK_THREAD; - for (cRecording *recording = First(); recording; recording = Next(recording)) - recording->ClearSortName(); + for (cRecording *Recording = First(); Recording; Recording = Next(Recording)) + Recording->ClearSortName(); } // --- cDirCopier ------------------------------------------------------------ @@ -1812,8 +1797,9 @@ void cDirCopier::Stop(void) Cancel(3); if (error) { cVideoDirectory::RemoveVideoFile(dirNameDst); - Recordings.AddByName(dirNameSrc); - Recordings.DelByName(dirNameDst); + LOCK_RECORDINGS_WRITE; + Recordings->AddByName(dirNameSrc); + Recordings->DelByName(dirNameDst); } } @@ -1893,17 +1879,19 @@ bool cRecordingsHandlerEntry::Active(bool &Error) copier->Start(); } ClearPending(); - Recordings.ChangeState(); + LOCK_RECORDINGS_WRITE; // to trigger a state change return true; } // Clean up: if (CopierFinishedOk && (Usage() & ruMove) != 0) { cRecording Recording(FileNameSrc()); - if (Recording.Delete()) - Recordings.DelByName(Recording.FileName()); + if (Recording.Delete()) { + LOCK_RECORDINGS_WRITE; + Recordings->DelByName(Recording.FileName()); + } } - Recordings.ChangeState(); - Recordings.TouchUpdate(); + LOCK_RECORDINGS_WRITE; // to trigger a state change + Recordings->TouchUpdate(); return false; } @@ -1947,7 +1935,7 @@ bool cRecordingsHandler::Add(int Usage, const char *FileNameSrc, const char *Fil operations.Add(new cRecordingsHandlerEntry(Usage, FileNameSrc, FileNameDst)); finished = false; Active(); // start it right away if possible - Recordings.ChangeState(); + LOCK_RECORDINGS_WRITE; // to trigger a state change return true; } else @@ -1969,7 +1957,7 @@ void cRecordingsHandler::Del(const char *FileName) cMutexLock MutexLock(&mutex); if (cRecordingsHandlerEntry *r = Get(FileName)) { operations.Del(r); - Recordings.ChangeState(); + LOCK_RECORDINGS_WRITE; // to trigger a state change } } @@ -1977,7 +1965,7 @@ void cRecordingsHandler::DelAll(void) { cMutexLock MutexLock(&mutex); operations.Clear(); - Recordings.ChangeState(); + LOCK_RECORDINGS_WRITE; // to trigger a state change } int cRecordingsHandler::GetUsage(const char *FileName) @@ -2059,9 +2047,19 @@ cString cMarks::MarksFileName(const cRecording *Recording) return AddDirectory(Recording->FileName(), Recording->IsPesRecording() ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX); } +bool cMarks::DeleteMarksFile(const cRecording *Recording) +{ + if (remove(cMarks::MarksFileName(Recording)) < 0) { + if (errno != ENOENT) { + LOG_ERROR_STR(Recording->FileName()); + return false; + } + } + return true; +} + bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool IsPesRecording) { - cMutexLock MutexLock(this); recordingFileName = RecordingFileName; fileName = AddDirectory(RecordingFileName, IsPesRecording ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX); framesPerSecond = FramesPerSecond; @@ -2074,7 +2072,6 @@ bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool Is bool cMarks::Update(void) { - cMutexLock MutexLock(this); time_t t = time(NULL); if (t > nextUpdate && *fileName) { time_t LastModified = LastModifiedTime(fileName); @@ -2106,7 +2103,6 @@ bool cMarks::Update(void) bool cMarks::Save(void) { - cMutexLock MutexLock(this); if (cConfig::Save()) { lastFileTime = LastModifiedTime(fileName); return true; @@ -2116,7 +2112,6 @@ bool cMarks::Save(void) void cMarks::Align(void) { - cMutexLock MutexLock(this); cIndexFile IndexFile(recordingFileName, false, isPesRecording); for (cMark *m = First(); m; m = Next(m)) { int p = IndexFile.GetClosestIFrame(m->Position()); @@ -2129,7 +2124,6 @@ void cMarks::Align(void) void cMarks::Sort(void) { - cMutexLock MutexLock(this); for (cMark *m1 = First(); m1; m1 = Next(m1)) { for (cMark *m2 = Next(m1); m2; m2 = Next(m2)) { if (m2->Position() < m1->Position()) { @@ -2142,43 +2136,42 @@ void cMarks::Sort(void) void cMarks::Add(int Position) { - cMutexLock MutexLock(this); cConfig::Add(new cMark(Position, NULL, framesPerSecond)); Sort(); } -cMark *cMarks::Get(int Position) +const cMark *cMarks::Get(int Position) const { - for (cMark *mi = First(); mi; mi = Next(mi)) { + for (const cMark *mi = First(); mi; mi = Next(mi)) { if (mi->Position() == Position) return mi; } return NULL; } -cMark *cMarks::GetPrev(int Position) +const cMark *cMarks::GetPrev(int Position) const { - for (cMark *mi = Last(); mi; mi = Prev(mi)) { + for (const cMark *mi = Last(); mi; mi = Prev(mi)) { if (mi->Position() < Position) return mi; } return NULL; } -cMark *cMarks::GetNext(int Position) +const cMark *cMarks::GetNext(int Position) const { - for (cMark *mi = First(); mi; mi = Next(mi)) { + for (const cMark *mi = First(); mi; mi = Next(mi)) { if (mi->Position() > Position) return mi; } return NULL; } -cMark *cMarks::GetNextBegin(cMark *EndMark) +const cMark *cMarks::GetNextBegin(const cMark *EndMark) const { - cMark *BeginMark = EndMark ? Next(EndMark) : First(); + const cMark *BeginMark = EndMark ? Next(EndMark) : First(); if (BeginMark && EndMark && BeginMark->Position() == EndMark->Position()) { - while (cMark *NextMark = Next(BeginMark)) { + while (const cMark *NextMark = Next(BeginMark)) { if (BeginMark->Position() == NextMark->Position()) { // skip Begin/End at the same position if (!(BeginMark = Next(NextMark))) break; @@ -2190,13 +2183,13 @@ cMark *cMarks::GetNextBegin(cMark *EndMark) return BeginMark; } -cMark *cMarks::GetNextEnd(cMark *BeginMark) +const cMark *cMarks::GetNextEnd(const cMark *BeginMark) const { if (!BeginMark) return NULL; - cMark *EndMark = Next(BeginMark); + const cMark *EndMark = Next(BeginMark); if (EndMark && BeginMark && BeginMark->Position() == EndMark->Position()) { - while (cMark *NextMark = Next(EndMark)) { + while (const cMark *NextMark = Next(EndMark)) { if (EndMark->Position() == NextMark->Position()) { // skip End/Begin at the same position if (!(EndMark = Next(NextMark))) break; @@ -2208,12 +2201,11 @@ cMark *cMarks::GetNextEnd(cMark *BeginMark) return EndMark; } -int cMarks::GetNumSequences(void) +int cMarks::GetNumSequences(void) const { - cMutexLock MutexLock(this); int NumSequences = 0; - if (cMark *BeginMark = GetNextBegin()) { - while (cMark *EndMark = GetNextEnd(BeginMark)) { + if (const cMark *BeginMark = GetNextBegin()) { + while (const cMark *EndMark = GetNextEnd(BeginMark)) { NumSequences++; BeginMark = GetNextBegin(EndMark); } @@ -2403,7 +2395,8 @@ void cIndexFileGenerator::Action(void) if (FrameDetector.FramesPerSecond() > 0 && !DoubleEqual(RecordingInfo.FramesPerSecond(), FrameDetector.FramesPerSecond())) { RecordingInfo.SetFramesPerSecond(FrameDetector.FramesPerSecond()); RecordingInfo.Write(); - Recordings.UpdateByName(recordingName); + LOCK_RECORDINGS_WRITE; + Recordings->UpdateByName(recordingName); } } Skins.QueueMessage(mtInfo, tr("Index file regeneration complete")); diff --git a/recording.h b/recording.h index dca5ee3d..cebd2ee0 100644 --- a/recording.h +++ b/recording.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recording.h 4.2 2015/04/28 09:26:02 kls Exp $ + * $Id: recording.h 4.3 2015/08/29 14:12:14 kls Exp $ */ #ifndef __RECORDING_H @@ -41,7 +41,6 @@ enum eRecordingUsage { }; void RemoveDeletedRecordings(void); -void ClearVanishedRecordings(void); void AssertFreeDiskSpace(int Priority = 0, bool Force = false); ///< The special Priority value -1 means that we shall get rid of any ///< deleted recordings faster than normal (because we're cutting). @@ -129,8 +128,9 @@ public: int Priority(void) const { return priority; } int Lifetime(void) const { return lifetime; } time_t Deleted(void) const { return deleted; } + void SetDeleted(void) { deleted = time(NULL); } virtual int Compare(const cListObject &ListObject) const; - bool IsInPath(const char *Path); + bool IsInPath(const char *Path) const; ///< Returns true if this recording is stored anywhere under the given Path. ///< If Path is NULL or an empty string, the entire video directory is checked. cString Folder(void) const; @@ -140,7 +140,7 @@ public: ///< Returns the base name of this recording (without the ///< video directory and folder). For use in menus etc. const char *Name(void) const { return name; } - ///< Returns the full name of the recording (without the video directory. + ///< Returns the full name of the recording (without the video directory). ///< For use in menus etc. const char *FileName(void) const; ///< Returns the full path name to the recording directory, including the @@ -166,7 +166,7 @@ public: bool IsEdited(void) const; bool IsPesRecording(void) const { return isPesRecording; } bool IsOnVideoDirectoryFileSystem(void) const; - bool HasMarks(void); + bool HasMarks(void) const; ///< Returns true if this recording has any editing marks. bool DeleteMarks(void); ///< Deletes the editing marks from this recording (if any). @@ -216,49 +216,54 @@ public: ///< as in time-shift). }; -class cRecordings : public cList, public cThread { +class cVideoDirectoryScannerThread; + +class cRecordings : public cList { private: + static cRecordings recordings; + static cRecordings deletedRecordings; static char *updateFileName; - bool deleted; - bool initial; - time_t lastUpdate; - int state; - const char *UpdateFileName(void); - void Refresh(bool Foreground = false); - bool ScanVideoDir(const char *DirName, bool Foreground = false, int LinkLevel = 0, int DirLevel = 0); -protected: - virtual void Action(void); + static time_t lastUpdate; + static cVideoDirectoryScannerThread *videoDirectoryScannerThread; + static const char *UpdateFileName(void); public: cRecordings(bool Deleted = false); virtual ~cRecordings(); - bool Load(void) { return Update(true); } - ///< Loads the current list of recordings and returns true if there - ///< is anything in it (for compatibility with older plugins - use - ///< Update(true) instead). - bool Update(bool Wait = false); + static const cRecordings *GetRecordingsRead(cStateKey &StateKey, int TimeoutMs = 0) { return recordings.Lock(StateKey, false, TimeoutMs) ? &recordings : NULL; } + ///< Gets the list of recordings for read access. + ///< See cTimers::GetTimersRead() for details. + static cRecordings *GetRecordingsWrite(cStateKey &StateKey, int TimeoutMs = 0) { return recordings.Lock(StateKey, true, TimeoutMs) ? &recordings : NULL; } + ///< Gets the list of recordings for write access. + ///< See cTimers::GetTimersWrite() for details. + static const cRecordings *GetDeletedRecordingsRead(cStateKey &StateKey, int TimeoutMs = 0) { return deletedRecordings.Lock(StateKey, false, TimeoutMs) ? &deletedRecordings : NULL; } + ///< Gets the list of deleted recordings for read access. + ///< See cTimers::GetTimersRead() for details. + static cRecordings *GetDeletedRecordingsWrite(cStateKey &StateKey, int TimeoutMs = 0) { return deletedRecordings.Lock(StateKey, true, TimeoutMs) ? &deletedRecordings : NULL; } + ///< Gets the list of deleted recordings for write access. + ///< See cTimers::GetTimersWrite() for details. + static void Update(bool Wait = false); ///< Triggers an update of the list of recordings, which will run ///< as a separate thread if Wait is false. If Wait is true, the ///< function returns only after the update has completed. - ///< Returns true if Wait is true and there is anything in the list - ///< of recordings, false otherwise. - void TouchUpdate(void); + static void TouchUpdate(void); ///< Touches the '.update' file in the video directory, so that other ///< instances of VDR that access the same video directory can be triggered ///< to update their recordings list. - bool NeedsUpdate(void); - void ChangeState(void) { state++; } - bool StateChanged(int &State); + ///< This function is 'const', because it doesn't actually modify the list + ///< of recordings. + static bool NeedsUpdate(void); void ResetResume(const char *ResumeFileName = NULL); void ClearSortNames(void); - cRecording *GetByName(const char *FileName); + const cRecording *GetByName(const char *FileName) const; + cRecording *GetByName(const char *FileName) { return const_cast(static_cast(this)->GetByName(FileName)); } void AddByName(const char *FileName, bool TriggerUpdate = true); void DelByName(const char *FileName); void UpdateByName(const char *FileName); - int TotalFileSizeMB(void); - double MBperMinute(void); + int TotalFileSizeMB(void) const; + double MBperMinute(void) const; ///< Returns the average data rate (in MB/min) of all recordings, or -1 if ///< this value is unknown. - int PathIsInUse(const char *Path); + int PathIsInUse(const char *Path) const; ///< Checks whether any recording in the given Path is currently in use and therefore ///< the whole Path shall not be tampered with. Returns 0 (ruNone) if no recording ///< is in use. @@ -266,7 +271,7 @@ public: ///< If several recordings in the Path are currently in use, the return value will ///< be the combination of all individual recordings' flags. ///< If Path is NULL or an empty string, the entire video directory is checked. - int GetNumRecordingsInPath(const char *Path); + int GetNumRecordingsInPath(const char *Path) const; ///< Returns the total number of recordings in the given Path, including all ///< sub-folders of Path. ///< If Path is NULL or an empty string, the entire video directory is checked. @@ -281,10 +286,19 @@ public: ///< if all recordings have been successfully added to the RecordingsHandler. }; -/// Any access to Recordings that loops through the list of recordings -/// needs to hold a thread lock on this object! -extern cRecordings Recordings; -extern cRecordings DeletedRecordings; +// Provide lock controlled access to the list: + +DEF_LIST_LOCK(Recordings); +DEF_LIST_LOCK2(Recordings, DeletedRecordings); + +// These macros provide a convenient way of locking the global recordings list +// and making sure the lock is released as soon as the current scope is left +// (note that these macros wait forever to obtain the lock!): + +#define LOCK_RECORDINGS_READ USE_LIST_LOCK_READ(Recordings) +#define LOCK_RECORDINGS_WRITE USE_LIST_LOCK_WRITE(Recordings) +#define LOCK_DELETEDRECORDINGS_READ USE_LIST_LOCK_READ2(Recordings, DeletedRecordings) +#define LOCK_DELETEDRECORDINGS_WRITE USE_LIST_LOCK_WRITE2(Recordings, DeletedRecordings) class cRecordingsHandlerEntry; @@ -350,7 +364,7 @@ public: bool Save(FILE *f); }; -class cMarks : public cConfig, public cMutex { +class cMarks : public cConfig { private: cString recordingFileName; cString fileName; @@ -360,9 +374,11 @@ private: time_t lastFileTime; time_t lastChange; public: + cMarks(void): cConfig("Marks") {}; static cString MarksFileName(const cRecording *Recording); ///< Returns the marks file name for the given Recording (regardless whether such ///< a file actually exists). + static bool DeleteMarksFile(const cRecording *Recording); bool Load(const char *RecordingFileName, double FramesPerSecond = DEFAULTFRAMESPERSECOND, bool IsPesRecording = false); bool Update(void); bool Save(void); @@ -374,22 +390,27 @@ public: ///< calls to Del(), or any of the functions that return a "cMark *", in case ///< an other thread might modifiy the list while the returned pointer is ///< considered valid. - cMark *Get(int Position); - cMark *GetPrev(int Position); - cMark *GetNext(int Position); - cMark *GetNextBegin(cMark *EndMark = NULL); + const cMark *Get(int Position) const; + const cMark *GetPrev(int Position) const; + const cMark *GetNext(int Position) const; + const cMark *GetNextBegin(const cMark *EndMark = NULL) const; ///< Returns the next "begin" mark after EndMark, skipping any marks at the ///< same position as EndMark. If EndMark is NULL, the first actual "begin" ///< will be returned (if any). - cMark *GetNextEnd(cMark *BeginMark); + const cMark *GetNextEnd(const cMark *BeginMark) const; ///< Returns the next "end" mark after BeginMark, skipping any marks at the ///< same position as BeginMark. - int GetNumSequences(void); + int GetNumSequences(void) const; ///< Returns the actual number of sequences to be cut from the recording. ///< If there is only one actual "begin" mark, and it is positioned at index ///< 0 (the beginning of the recording), and there is no "end" mark, the ///< return value is 0, which means that the result is the same as the original ///< recording. + cMark *Get(int Position) { return const_cast(static_cast(this)->Get(Position)); } + cMark *GetPrev(int Position) { return const_cast(static_cast(this)->GetPrev(Position)); } + cMark *GetNext(int Position) { return const_cast(static_cast(this)->GetNext(Position)); } + cMark *GetNextBegin(const cMark *EndMark = NULL) { return const_cast(static_cast(this)->GetNextBegin(EndMark)); } + cMark *GetNextEnd(const cMark *BeginMark) { return const_cast(static_cast(this)->GetNextEnd(BeginMark)); } }; #define RUC_BEFORERECORDING "before" diff --git a/sdt.c b/sdt.c index 782bec65..05cd931b 100644 --- a/sdt.c +++ b/sdt.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: sdt.c 4.4 2015/03/17 15:09:54 kls Exp $ + * $Id: sdt.c 4.5 2015/08/02 11:33:23 kls Exp $ */ #include "sdt.h" @@ -52,18 +52,21 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length return; if (!sectionSyncer.Sync(sdt.getVersionNumber(), sdt.getSectionNumber(), sdt.getLastSectionNumber())) return; - if (!Channels.Lock(true, 10)) { + cStateKey StateKey; + cChannels *Channels = cChannels::GetChannelsWrite(StateKey, 10); + if (!Channels) { sectionSyncer.Repeat(); // let's not miss any section of the SDT return; } dbgsdt("SDT: %2d %2d %2d %s %d\n", sdt.getVersionNumber(), sdt.getSectionNumber(), sdt.getLastSectionNumber(), *cSource::ToString(source), Transponder()); + bool ChannelsModified = false; SI::SDT::Service SiSdtService; for (SI::Loop::Iterator it; sdt.serviceLoop.getNext(SiSdtService, it); ) { - cChannel *channel = Channels.GetByChannelID(tChannelID(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId())); - if (!channel) - channel = Channels.GetByChannelID(tChannelID(source, 0, Transponder(), SiSdtService.getServiceId())); - if (channel) - channel->SetSeen(); + cChannel *Channel = Channels->GetByChannelID(tChannelID(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId())); + if (!Channel) + Channel = Channels->GetByChannelID(tChannelID(source, 0, Transponder(), SiSdtService.getServiceId())); + if (Channel) + Channel->SetSeen(); cLinkChannels *LinkChannels = NULL; SI::Descriptor *d; @@ -101,20 +104,21 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length } sd->providerName.getText(ProviderNameBuf, sizeof(ProviderNameBuf)); char *pp = compactspace(ProviderNameBuf); - if (channel) { - channel->SetId(sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId()); + if (Channel) { + ChannelsModified |= Channel->SetId(Channels, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId()); if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3) - channel->SetName(pn, ps, pp); + ChannelsModified |= Channel->SetName(pn, ps, pp); // Using SiSdtService.getFreeCaMode() is no good, because some // tv stations set this flag even for non-encrypted channels :-( // The special value 0xFFFF was supposed to mean "unknown encryption" // and would have been overwritten with real CA values later: - // channel->SetCa(SiSdtService.getFreeCaMode() ? 0xFFFF : 0); + // Channel->SetCa(SiSdtService.getFreeCaMode() ? 0xFFFF : 0); } else if (*pn && Setup.UpdateChannels >= 4) { - dbgsdt(" %5d %5d %5d %s/%s %d %s\n", sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId(), *cSource::ToString(Channel()->Source()), *cSource::ToString(source), Channel()->Transponder(), pn); - channel = Channels.NewChannel(Channel(), pn, ps, pp, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId()); - channel->SetSource(source); // in case this comes from a satellite with a slightly different position + dbgsdt(" %5d %5d %5d %s/%s %d %s\n", sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId(), *cSource::ToString(this->Channel()->Source()), *cSource::ToString(source), this->Channel()->Transponder(), pn); + Channel = Channels->NewChannel(this->Channel(), pn, ps, pp, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId()); + Channel->SetSource(source); // in case this comes from a satellite with a slightly different position + ChannelsModified = true; patFilter->Trigger(SiSdtService.getServiceId()); } } @@ -127,9 +131,9 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length /* case SI::CaIdentifierDescriptorTag: { SI::CaIdentifierDescriptor *cid = (SI::CaIdentifierDescriptor *)d; - if (channel) { + if (Channel) { for (SI::Loop::Iterator it; cid->identifiers.hasNext(it); ) - channel->SetCa(cid->identifiers.getNext(it)); + Channel->SetCa(Channels, cid->identifiers.getNext(it)); } } break; @@ -138,15 +142,17 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length SI::NVODReferenceDescriptor *nrd = (SI::NVODReferenceDescriptor *)d; SI::NVODReferenceDescriptor::Service Service; for (SI::Loop::Iterator it; nrd->serviceLoop.getNext(Service, it); ) { - cChannel *link = Channels.GetByChannelID(tChannelID(source, Service.getOriginalNetworkId(), Service.getTransportStream(), Service.getServiceId())); + cChannel *link = Channels->GetByChannelID(tChannelID(source, Service.getOriginalNetworkId(), Service.getTransportStream(), Service.getServiceId())); if (!link && Setup.UpdateChannels >= 4) { - link = Channels.NewChannel(Channel(), "NVOD", "", "", Service.getOriginalNetworkId(), Service.getTransportStream(), Service.getServiceId()); + link = Channels->NewChannel(this->Channel(), "NVOD", "", "", Service.getOriginalNetworkId(), Service.getTransportStream(), Service.getServiceId()); patFilter->Trigger(Service.getServiceId()); + ChannelsModified = true; } if (link) { if (!LinkChannels) LinkChannels = new cLinkChannels; LinkChannels->Add(new cLinkChannel(link)); + ChannelsModified = true; } } } @@ -156,18 +162,18 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length delete d; } if (LinkChannels) { - if (channel) - channel->SetLinkChannels(LinkChannels); + if (Channel) + ChannelsModified |= Channel->SetLinkChannels(LinkChannels); else delete LinkChannels; } } if (sdt.getSectionNumber() == sdt.getLastSectionNumber()) { if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3) { - Channels.MarkObsoleteChannels(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId()); + ChannelsModified |= Channels->MarkObsoleteChannels(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId()); if (source != Source()) - Channels.MarkObsoleteChannels(Source(), sdt.getOriginalNetworkId(), sdt.getTransportStreamId()); + ChannelsModified |= Channels->MarkObsoleteChannels(Source(), sdt.getOriginalNetworkId(), sdt.getTransportStreamId()); } } - Channels.Unlock(); + StateKey.Remove(ChannelsModified); } diff --git a/shutdown.c b/shutdown.c index 97d056e9..7b8ff804 100644 --- a/shutdown.c +++ b/shutdown.c @@ -6,7 +6,7 @@ * * Original version written by Udo Richter . * - * $Id: shutdown.c 3.1 2013/10/02 09:02:01 kls Exp $ + * $Id: shutdown.c 4.1 2015/07/18 11:29:26 kls Exp $ */ #include "shutdown.h" @@ -172,9 +172,10 @@ bool cShutdownHandler::ConfirmShutdown(bool Interactive) return false; } - cTimer *timer = Timers.GetNextActiveTimer(); - time_t Next = timer ? timer->StartTime() : 0; - time_t Delta = timer ? Next - time(NULL) : 0; + LOCK_TIMERS_READ; + const cTimer *Timer = Timers->GetNextActiveTimer(); + time_t Next = Timer ? Timer->StartTime() : 0; + time_t Delta = Timer ? Next - time(NULL) : 0; if (cRecordControls::Active() || (Next && Delta <= 0)) { // VPS recordings in timer end margin may cause Delta <= 0 @@ -215,9 +216,10 @@ bool cShutdownHandler::ConfirmRestart(bool Interactive) return false; } - cTimer *timer = Timers.GetNextActiveTimer(); - time_t Next = timer ? timer->StartTime() : 0; - time_t Delta = timer ? Next - time(NULL) : 0; + LOCK_TIMERS_READ; + const cTimer *Timer = Timers->GetNextActiveTimer(); + time_t Next = Timer ? Timer->StartTime() : 0; + time_t Delta = Timer ? Next - time(NULL) : 0; if (cRecordControls::Active() || (Next && Delta <= 0)) { // VPS recordings in timer end margin may cause Delta <= 0 @@ -233,15 +235,16 @@ bool cShutdownHandler::ConfirmRestart(bool Interactive) bool cShutdownHandler::DoShutdown(bool Force) { + LOCK_TIMERS_READ; time_t Now = time(NULL); - cTimer *timer = Timers.GetNextActiveTimer(); + const cTimer *Timer = Timers->GetNextActiveTimer(); cPlugin *Plugin = cPluginManager::GetNextWakeupPlugin(); - time_t Next = timer ? timer->StartTime() : 0; + time_t Next = Timer ? Timer->StartTime() : 0; time_t NextPlugin = Plugin ? Plugin->WakeupTime() : 0; if (NextPlugin && (!Next || Next > NextPlugin)) { Next = NextPlugin; - timer = NULL; + Timer = NULL; } time_t Delta = Next ? Next - Now : 0; @@ -250,13 +253,13 @@ bool cShutdownHandler::DoShutdown(bool Force) return false; Delta = Setup.MinEventTimeout * 60; Next = Now + Delta; - timer = NULL; + Timer = NULL; dsyslog("reboot at %s", *TimeToString(Next)); } - if (Next && timer) { + if (Next && Timer) { dsyslog("next timer event at %s", *TimeToString(Next)); - CallShutdownCommand(Next, timer->Channel()->Number(), timer->File(), Force); + CallShutdownCommand(Next, Timer->Channel()->Number(), Timer->File(), Force); } else if (Next && Plugin) { CallShutdownCommand(Next, 0, Plugin->Name(), Force); diff --git a/skinlcars.c b/skinlcars.c index a722b521..d84a753f 100644 --- a/skinlcars.c +++ b/skinlcars.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: skinlcars.c 3.8 2014/06/12 08:48:15 kls Exp $ + * $Id: skinlcars.c 4.1 2015/09/01 10:07:07 kls Exp $ */ // "Star Trek: The Next Generation"(R) is a registered trademark of Paramount Pictures, @@ -710,7 +710,7 @@ private: int lastDiskUsageState; bool lastDiskAlert; double lastSystemLoad; - int lastTimersState; + cStateKey timersStateKey; time_t lastSignalDisplay; int lastLiveIndicatorY; bool lastLiveIndicatorTransferring; @@ -775,7 +775,6 @@ cSkinLCARSDisplayMenu::cSkinLCARSDisplayMenu(void) lastEvent = NULL; lastRecording = NULL; lastSeen = -1; - lastTimersState = -1; lastSignalDisplay = 0; lastLiveIndicatorY = -1; lastLiveIndicatorTransferring = false; @@ -970,7 +969,7 @@ void cSkinLCARSDisplayMenu::SetMenuCategory(eMenuCategory MenuCategory) xi01 = xm03; xi02 = xm04; xi03 = xm05; - lastTimersState = -1; + timersStateKey.Reset(); DrawMainFrameLower(); DrawMainBracket(); DrawStatusElbows(); @@ -1237,19 +1236,22 @@ void cSkinLCARSDisplayMenu::DrawTimer(const cTimer *Timer, int y, bool MultiRec) } if (Event) osd->DrawText(xs00 + d, y + lineHeight - tinyFont->Height(), Event->Title(), ColorFg, ColorBg, tinyFont, xs03 - xs00 - 2 * d); + // The remote timer indicator: + if (Timer->Remote()) + osd->DrawRectangle(xs00 - (lineHeight - Gap) / 2, y, xs00 - Gap - 1, y + lineHeight - 1, Timer->Recording() ? Theme.Color(clrMenuTimerRecording) : ColorBg); // The timer recording indicator: - if (Timer->Recording()) + else if (Timer->Recording()) osd->DrawRectangle(xs03 + Gap, y - (MultiRec ? Gap : 0), xs04 - Gap / 2 - 1, y + lineHeight - 1, Theme.Color(clrMenuTimerRecording)); } void cSkinLCARSDisplayMenu::DrawTimers(void) { - if (Timers.Modified(lastTimersState)) { + if (const cTimers *Timers = cTimers::GetTimersRead(timersStateKey)) { deviceRecording.Clear(); const cFont *font = cFont::GetFont(fontOsd); - osd->DrawRectangle(xs00, ys04, xs04 - 1, ys05 - 1, Theme.Color(clrBackground)); + osd->DrawRectangle(xs00 - (lineHeight - Gap) / 2, ys04, xs04 - 1, ys05 - 1, Theme.Color(clrBackground)); osd->DrawRectangle(xs07, ys04, xs13 - 1, ys05 - 1, Theme.Color(clrBackground)); - cSortedTimers SortedTimers; + cSortedTimers SortedTimers(Timers); cVector FreeDeviceSlots; int NumDevices = 0; int y = ys04; @@ -1262,7 +1264,14 @@ void cSkinLCARSDisplayMenu::DrawTimers(void) break; if (const cTimer *Timer = SortedTimers[i]) { if (Timer->Recording()) { - if (cRecordControl *RecordControl = cRecordControls::GetRecordControl(Timer)) { + if (Timer->Remote()) { + if (!Device && Timer->HasFlags(tfActive)) { + DrawTimer(Timer, y, false); + FreeDeviceSlots.Append(y); + y += lineHeight + Gap; + } + } + else if (cRecordControl *RecordControl = cRecordControls::GetRecordControl(Timer)) { if (!Device || Device == RecordControl->Device()) { DrawTimer(Timer, y, NumTimers > 0); NumTimers++; @@ -1313,7 +1322,7 @@ void cSkinLCARSDisplayMenu::DrawTimers(void) } // Total number of active timers: int NumTimers = 0; - for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) { + for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) { if (Timer->HasFlags(tfActive)) NumTimers++; } @@ -1321,6 +1330,7 @@ void cSkinLCARSDisplayMenu::DrawTimers(void) osd->DrawText(xs08, ys00, itoa(NumDevices), Theme.Color(clrMenuFrameFg), frameColor, font, xs09 - xs08, ys01 - ys00, taBottom | taRight | taBorder); lastSignalDisplay = 0; initial = true; // forces redrawing of devices + timersStateKey.Remove(); } } @@ -1423,25 +1433,23 @@ void cSkinLCARSDisplayMenu::DrawLive(const cChannel *Channel) DrawSeen(0, 0); } // The current programme: - cSchedulesLock SchedulesLock; - if (const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock)) { - if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) { - const cEvent *Event = Schedule->GetPresentEvent(); - if (initial || Event != lastEvent) { - DrawInfo(Event, true); - lastEvent = Event; - lastSeen = -1; - } - int Current = 0; - int Total = 0; - if (Event) { - time_t t = time(NULL); - if (t > Event->StartTime()) - Current = t - Event->StartTime(); - Total = Event->Duration(); - } - DrawSeen(Current, Total); + LOCK_SCHEDULES_READ; + if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) { + const cEvent *Event = Schedule->GetPresentEvent(); + if (initial || Event != lastEvent) { + DrawInfo(Event, true); + lastEvent = Event; + lastSeen = -1; } + int Current = 0; + int Total = 0; + if (Event) { + time_t t = time(NULL); + if (t > Event->StartTime()) + Current = t - Event->StartTime(); + Total = Event->Duration(); + } + DrawSeen(Current, Total); } } @@ -1731,7 +1739,8 @@ void cSkinLCARSDisplayMenu::Flush(void) if (MenuCategory() == mcMain) { cDevice *Device = cDevice::PrimaryDevice(); if (!Device->Replaying() || Device->Transferring()) { - const cChannel *Channel = Channels.GetByNumber(cDevice::PrimaryDevice()->CurrentChannel()); + LOCK_CHANNELS_READ; + const cChannel *Channel = Channels->GetByNumber(cDevice::PrimaryDevice()->CurrentChannel()); DrawLive(Channel); } else if (cControl *Control = cControl::Control(true)) diff --git a/sourceparams.c b/sourceparams.c index 3eec406a..6e92bbef 100644 --- a/sourceparams.c +++ b/sourceparams.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: sourceparams.c 3.1 2014/03/09 12:03:09 kls Exp $ + * $Id: sourceparams.c 4.1 2015/08/02 11:56:39 kls Exp $ */ #include "sourceparams.h" @@ -33,7 +33,7 @@ cSourceParam::cSourceParam(char Source, const char *Description) cSourceParams SourceParams; -cSourceParam *cSourceParams::Get(char Source) const +cSourceParam *cSourceParams::Get(char Source) { for (cSourceParam *sp = First(); sp; sp = Next(sp)) { if (sp->Source() == Source) diff --git a/sourceparams.h b/sourceparams.h index be04b567..1e0a6abc 100644 --- a/sourceparams.h +++ b/sourceparams.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: sourceparams.h 1.1 2010/02/28 11:58:03 kls Exp $ + * $Id: sourceparams.h 4.1 2015/08/02 11:56:25 kls Exp $ */ #ifndef __SOURCEPARAMS_H @@ -45,7 +45,7 @@ public: class cSourceParams : public cList { public: - cSourceParam *Get(char Source) const; + cSourceParam *Get(char Source); }; extern cSourceParams SourceParams; diff --git a/status.h b/status.h index 222280ac..6d1b9df5 100644 --- a/status.h +++ b/status.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: status.h 3.1 2014/01/25 10:47:39 kls Exp $ + * $Id: status.h 4.1 2015/08/02 10:34:44 kls Exp $ */ #ifndef __STATUS_H @@ -15,7 +15,7 @@ #include "player.h" #include "tools.h" -enum eTimerChange { tcMod, tcAdd, tcDel }; +enum eTimerChange { tcMod, tcAdd, tcDel }; // tcMod is obsolete and no longer used! class cTimer; @@ -29,10 +29,7 @@ protected: // require a retune. virtual void TimerChange(const cTimer *Timer, eTimerChange Change) {} // Indicates a change in the timer settings. - // If Change is tcAdd or tcDel, Timer points to the timer that has - // been added or will be deleted, respectively. In case of tcMod, - // Timer is NULL; this indicates that some timer has been changed. - // Note that tcAdd and tcDel are always also followed by a tcMod. + // Timer points to the timer that has been added or will be deleted, respectively. virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber, bool LiveView) {} // Indicates a channel switch on the given DVB device. // If ChannelNumber is 0, this is before the channel is being switched, diff --git a/svdrp.c b/svdrp.c index d621363d..8d7fce7d 100644 --- a/svdrp.c +++ b/svdrp.c @@ -10,7 +10,7 @@ * and interact with the Video Disk Recorder - or write a full featured * graphical interface that sits on top of an SVDRP connection. * - * $Id: svdrp.c 4.2 2015/05/22 11:01:33 kls Exp $ + * $Id: svdrp.c 4.3 2015/09/01 10:34:34 kls Exp $ */ #include "svdrp.h" @@ -45,7 +45,7 @@ static bool DumpSVDRPDataTransfer = false; #define dbgsvdrp(a...) if (DumpSVDRPDataTransfer) fprintf(stderr, a) -static const char *HostName(void) +const char *SVDRPHostName(void) { static char buffer[HOST_NAME_MAX] = ""; if (!*buffer) { @@ -243,7 +243,7 @@ bool cSocket::Connect(const char *Address) LOG_ERROR; return false; } - isyslog("SVDRP > %s:%d connection established", Address, port); + isyslog("SVDRP > %s:%d server connection established", Address, port); return true; } return false; @@ -298,7 +298,7 @@ int cSocket::Accept(void) NewSock = -1; } lastIpAddress.Set((sockaddr *)&Addr); - isyslog("SVDRP < %s connection %s", lastIpAddress.Connection(), Accepted ? "accepted" : "DENIED"); + isyslog("SVDRP < %s client connection %s", lastIpAddress.Connection(), Accepted ? "accepted" : "DENIED"); } else if (FATALERRNO) LOG_ERROR; @@ -336,6 +336,321 @@ cString cSocket::Discover(void) return NULL; } +// --- cSVDRPClient ---------------------------------------------------------- + +class cSVDRPClient { +private: + cIpAddress ipAddress; + cSocket socket; + cString serverName; + int timeout; + cTimeMs pingTime; + cFile file; + int fetchFlags; + void Close(void); +public: + cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout); + ~cSVDRPClient(); + const char *ServerName(void) const { return serverName; } + const char *Connection(void) const { return ipAddress.Connection(); } + bool HasAddress(const char *Address, int Port) const; + bool Send(const char *Command); + bool Process(cStringList *Response = NULL); + bool Execute(const char *Command, cStringList *Response = NULL); + void SetFetchFlag(eSvdrpFetchFlags Flag); + bool HasFetchFlag(eSvdrpFetchFlags Flag); + }; + +static cPoller SVDRPClientPoller; + +cSVDRPClient::cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout) +:ipAddress(Address, Port) +,socket(Port, true) +{ + serverName = ServerName; + timeout = Timeout * 1000 * 9 / 10; // ping after 90% of timeout + pingTime.Set(timeout); + fetchFlags = sffTimers; + if (socket.Connect(Address)) { + if (file.Open(socket.Socket())) { + SVDRPClientPoller.Add(file, false); + dsyslog("SVDRP > %s client created for '%s'", ipAddress.Connection(), *serverName); + SendSVDRPDiscover(Address); + return; + } + } + esyslog("SVDRP > %s ERROR: failed to create client for '%s'", ipAddress.Connection(), *serverName); +} + +cSVDRPClient::~cSVDRPClient() +{ + Close(); + dsyslog("SVDRP > %s client destroyed for '%s'", ipAddress.Connection(), *serverName); +} + +void cSVDRPClient::Close(void) +{ + if (file.IsOpen()) { + SVDRPClientPoller.Del(file, false); + file.Close(); + socket.Close(); + } +} + +bool cSVDRPClient::HasAddress(const char *Address, int Port) const +{ + return strcmp(ipAddress.Address(), Address) == 0 && ipAddress.Port() == Port; +} + +bool cSVDRPClient::Send(const char *Command) +{ + pingTime.Set(timeout); + dbgsvdrp("> %s: %s\n", *serverName, Command); + if (safe_write(file, Command, strlen(Command) + 1) < 0) { + LOG_ERROR; + return false; + } + return true; +} + +bool cSVDRPClient::Process(cStringList *Response) +{ + if (file.IsOpen()) { + char input[BUFSIZ]; + int numChars = 0; +#define SVDRPResonseTimeout 5000 // ms + cTimeMs Timeout(SVDRPResonseTimeout); + for (;;) { + if (file.Ready(false)) { + unsigned char c; + int r = safe_read(file, &c, 1); + if (r > 0) { + if (c == '\n' || c == 0x00) { + // strip trailing whitespace: + while (numChars > 0 && strchr(" \t\r\n", input[numChars - 1])) + input[--numChars] = 0; + // make sure the string is terminated: + input[numChars] = 0; + dbgsvdrp("< %s: %s\n", *serverName, input); + if (Response) { + Response->Append(strdup(input)); + if (numChars >= 4 && input[3] != '-') // no more lines will follow + break; + } + else { + switch (atoi(input)) { + case 220: if (numChars > 4) { + char *n = input + 4; + if (char *t = strchr(n, ' ')) { + *t = 0; + if (strcmp(n, serverName) != 0) { + serverName = n; + dsyslog("SVDRP < %s remote server name is '%s'", ipAddress.Connection(), *serverName); + } + } + } + break; + case 221: dsyslog("SVDRP < %s remote server closed connection to '%s'", ipAddress.Connection(), *serverName); + Close(); + break; + } + } + numChars = 0; + } + else { + if (numChars >= int(sizeof(input))) { + esyslog("SVDRP < %s ERROR: out of memory", ipAddress.Connection()); + Close(); + break; + } + input[numChars++] = c; + input[numChars] = 0; + } + Timeout.Set(SVDRPResonseTimeout); + } + else if (r <= 0) { + isyslog("SVDRP < %s lost connection to remote server '%s'", ipAddress.Connection(), *serverName); + Close(); + } + } + else if (!Response) + break; + else if (Timeout.TimedOut()) { + esyslog("SVDRP < %s timeout while waiting for response from '%s'", ipAddress.Connection(), *serverName); + return false; + } + } + if (pingTime.TimedOut()) + Execute("PING"); + } + return file.IsOpen(); +} + +bool cSVDRPClient::Execute(const char *Command, cStringList *Response) +{ + if (Response) + Response->Clear(); + return Send(Command) && Process(Response); +} + +void cSVDRPClient::SetFetchFlag(eSvdrpFetchFlags Flags) +{ + fetchFlags |= Flags; +} + +bool cSVDRPClient::HasFetchFlag(eSvdrpFetchFlags Flag) +{ + bool Result = (fetchFlags & Flag); + fetchFlags &= ~Flag; + return Result; +} + +// --- cSVDRPClientHandler --------------------------------------------------- + +class cSVDRPClientHandler : public cThread { +private: + cMutex mutex; + cSocket udpSocket; + int tcpPort; + cVector clientConnections; + void HandleClientConnection(void); + void ProcessConnections(void); + cSVDRPClient *GetClientForServer(const char *ServerName); +protected: + virtual void Action(void); +public: + cSVDRPClientHandler(int UdpPort, int TcpPort); + virtual ~cSVDRPClientHandler(); + void SendDiscover(const char *Address = NULL); + bool Execute(const char *ServerName, const char *Command, cStringList *Response); + bool GetServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlags = sffNone); + bool TriggerFetchingTimers(const char *ServerName); + }; + +static cSVDRPClientHandler *SVDRPClientHandler = NULL; + +cSVDRPClientHandler::cSVDRPClientHandler(int UdpPort, int TcpPort) +:cThread("SVDRP client handler", true) +,udpSocket(UdpPort, false) +{ + tcpPort = TcpPort; +} + +cSVDRPClientHandler::~cSVDRPClientHandler() +{ + Cancel(3); + for (int i = 0; i < clientConnections.Size(); i++) + delete clientConnections[i]; +} + +cSVDRPClient *cSVDRPClientHandler::GetClientForServer(const char *ServerName) +{ + for (int i = 0; i < clientConnections.Size(); i++) { + if (strcmp(clientConnections[i]->ServerName(), ServerName) == 0) + return clientConnections[i]; + } + return NULL; +} + +void cSVDRPClientHandler::SendDiscover(const char *Address) +{ + cMutexLock MutexLock(&mutex); + cString Dgram = cString::sprintf("SVDRP:discover name:%s port:%d vdrversion:%d apiversion:%d timeout:%d", SVDRPHostName(), tcpPort, VDRVERSNUM, APIVERSNUM, Setup.SVDRPTimeout); + udpSocket.SendDgram(Dgram, udpSocket.Port(), Address); +} + +void cSVDRPClientHandler::ProcessConnections(void) +{ + cMutexLock MutexLock(&mutex); + for (int i = 0; i < clientConnections.Size(); i++) { + if (!clientConnections[i]->Process()) { + delete clientConnections[i]; + clientConnections.Remove(i); + i--; + } + } +} + +void cSVDRPClientHandler::HandleClientConnection(void) +{ + cString NewDiscover = udpSocket.Discover(); + if (*NewDiscover) { + cString p = strgetval(NewDiscover, "port", ':'); + if (*p) { + int Port = atoi(p); + for (int i = 0; i < clientConnections.Size(); i++) { + if (clientConnections[i]->HasAddress(udpSocket.LastIpAddress()->Address(), Port)) { + dsyslog("SVDRP < %s connection to '%s' confirmed", clientConnections[i]->Connection(), clientConnections[i]->ServerName()); + return; + } + } + cString ServerName = strgetval(NewDiscover, "name", ':'); + if (*ServerName) { + cString t = strgetval(NewDiscover, "timeout", ':'); + if (*t) { + int Timeout = atoi(t); + if (Timeout > 10) // don't let it get too small + clientConnections.Append(new cSVDRPClient(udpSocket.LastIpAddress()->Address(), Port, ServerName, Timeout)); + else + esyslog("SVDRP < %s ERROR: invalid timeout (%d)", udpSocket.LastIpAddress()->Connection(), Timeout); + } + else + esyslog("SVDRP < %s ERROR: missing timeout", udpSocket.LastIpAddress()->Connection()); + } + else + esyslog("SVDRP < %s ERROR: missing server name", udpSocket.LastIpAddress()->Connection()); + } + else + esyslog("SVDRP < %s ERROR: missing port number", udpSocket.LastIpAddress()->Connection()); + } +} + +void cSVDRPClientHandler::Action(void) +{ + if (udpSocket.Listen()) { + SVDRPClientPoller.Add(udpSocket.Socket(), false); + SendDiscover(); + while (Running()) { + SVDRPClientPoller.Poll(1000); + cMutexLock MutexLock(&mutex); + HandleClientConnection(); + ProcessConnections(); + } + SVDRPClientPoller.Del(udpSocket.Socket(), false); + udpSocket.Close(); + } +} + +bool cSVDRPClientHandler::Execute(const char *ServerName, const char *Command, cStringList *Response) +{ + cMutexLock MutexLock(&mutex); + if (cSVDRPClient *Client = GetClientForServer(ServerName)) + return Client->Execute(Command, Response); + return false; +} + +bool cSVDRPClientHandler::GetServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlag) +{ + cMutexLock MutexLock(&mutex); + ServerNames->Clear(); + for (int i = 0; i < clientConnections.Size(); i++) { + cSVDRPClient *Client = clientConnections[i]; + if (FetchFlag == sffNone || Client->HasFetchFlag(FetchFlag)) + ServerNames->Append(strdup(Client->ServerName())); + } + return ServerNames->Size() > 0; +} + +bool cSVDRPClientHandler::TriggerFetchingTimers(const char *ServerName) +{ + cMutexLock MutexLock(&mutex); + if (cSVDRPClient *Client = GetClientForServer(ServerName)) { + Client->SetFetchFlag(sffTimers); + return true; + } + return false; +} + // --- cPUTEhandler ---------------------------------------------------------- class cPUTEhandler { @@ -465,7 +780,8 @@ const char *HelpPages[] = { " List timers. Without option, all timers are listed. Otherwise\n" " only the given timer is listed. If the keyword 'id' is given, the\n" " channels will be listed with their unique channel ids instead of\n" - " their numbers.", + " their numbers. This command lists only the timers that are defined\n" + " locally on this VDR, not any remote timers from other VDRs.", "MESG \n" " Displays the given message on the OSD. The message will be queued\n" " and displayed whenever this is suitable.\n", @@ -523,6 +839,10 @@ const char *HelpPages[] = { " If 'help' is followed by a command, the detailed help for that command is\n" " given. The keyword 'main' initiates a call to the main menu function of the\n" " given plugin.\n", + "POLL timers\n" + " Used by peer-to-peer connections between VDRs to inform other machines\n" + " about changes to timers. The receiving VDR shall use LSTT to query the\n" + " remote machine's timers and update its list of timers accordingly.\n", "PUTE [ file ]\n" " Put data into the EPG list. The data entered has to strictly follow the\n" " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n" @@ -542,7 +862,7 @@ const char *HelpPages[] = { "UPDT \n" " Updates a timer. Settings must be in the same format as returned\n" " by the LSTT command. If a timer with the same channel, day, start\n" - " and stop time does not yet exists, it will be created.", + " and stop time does not yet exist, it will be created.", "UPDR\n" " Initiates a re-read of the recordings directory, which is the SVDRP\n" " equivalent to 'touch .update'.", @@ -617,7 +937,6 @@ private: int socket; cString connection; cFile file; - cRecordings recordings; cPUTEhandler *PUTEhandler; int numChars; int length; @@ -651,6 +970,7 @@ private: void CmdPING(const char *Option); void CmdPLAY(const char *Option); void CmdPLUG(const char *Option); + void CmdPOLL(const char *Option); void CmdPUTE(const char *Option); void CmdREMO(const char *Option); void CmdSCAN(const char *Option); @@ -667,7 +987,6 @@ public: }; static cPoller SVDRPServerPoller; -static cPoller SVDRPClientPoller; cSVDRPServer::cSVDRPServer(int Socket, const char *Connection) { @@ -680,7 +999,7 @@ cSVDRPServer::cSVDRPServer(int Socket, const char *Connection) lastActivity = time(NULL); if (file.Open(socket)) { time_t now = time(NULL); - Reply(220, "%s SVDRP VideoDiskRecorder %s; %s; %s", HostName(), VDRVERSION, *TimeToString(now), cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8"); + Reply(220, "%s SVDRP VideoDiskRecorder %s; %s; %s", SVDRPHostName(), VDRVERSION, *TimeToString(now), cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8"); SVDRPServerPoller.Add(file, false); } dsyslog("SVDRP < %s server created", *connection); @@ -697,7 +1016,7 @@ void cSVDRPServer::Close(bool SendReply, bool Timeout) { if (file.IsOpen()) { if (SendReply) { - Reply(221, "%s closing connection%s", HostName(), Timeout ? " (timeout)" : ""); + Reply(221, "%s closing connection%s", SVDRPHostName(), Timeout ? " (timeout)" : ""); } isyslog("SVDRP < %s connection closed", *connection); SVDRPServerPoller.Del(file, false); @@ -775,12 +1094,13 @@ void cSVDRPServer::PrintHelpTopics(const char **hp) void cSVDRPServer::CmdCHAN(const char *Option) { + LOCK_CHANNELS_READ; if (*Option) { int n = -1; int d = 0; if (isnumber(Option)) { int o = strtol(Option, NULL, 10); - if (o >= 1 && o <= Channels.MaxNumber()) + if (o >= 1 && o <= cChannels::MaxNumber()) n = o; } else if (strcmp(Option, "-") == 0) { @@ -792,35 +1112,31 @@ void cSVDRPServer::CmdCHAN(const char *Option) } else if (strcmp(Option, "+") == 0) { n = cDevice::CurrentChannel(); - if (n < Channels.MaxNumber()) { + if (n < cChannels::MaxNumber()) { n++; d = 1; } } + else if (const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(Option))) + n = Channel->Number(); else { - cChannel *channel = Channels.GetByChannelID(tChannelID::FromString(Option)); - if (channel) - n = channel->Number(); - else { - for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) { - if (!channel->GroupSep()) { - if (strcasecmp(channel->Name(), Option) == 0) { - n = channel->Number(); - break; - } + for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { + if (!Channel->GroupSep()) { + if (strcasecmp(Channel->Name(), Option) == 0) { + n = Channel->Number(); + break; } } - } + } } if (n < 0) { Reply(501, "Undefined channel \"%s\"", Option); return; } if (!d) { - cChannel *channel = Channels.GetByNumber(n); - if (channel) { - if (!cDevice::PrimaryDevice()->SwitchChannel(channel, true)) { - Reply(554, "Error switching to channel \"%d\"", channel->Number()); + if (const cChannel *Channel = Channels->GetByNumber(n)) { + if (!cDevice::PrimaryDevice()->SwitchChannel(Channel, true)) { + Reply(554, "Error switching to channel \"%d\"", Channel->Number()); return; } } @@ -832,9 +1148,8 @@ void cSVDRPServer::CmdCHAN(const char *Option) else cDevice::SwitchChannel(d); } - cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel()); - if (channel) - Reply(250, "%d %s", channel->Number(), channel->Name()); + if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel())) + Reply(250, "%d %s", Channel->Number(), Channel->Name()); else Reply(550, "Unable to find channel \"%d\"", cDevice::CurrentChannel()); } @@ -842,16 +1157,18 @@ void cSVDRPServer::CmdCHAN(const char *Option) void cSVDRPServer::CmdCLRE(const char *Option) { if (*Option) { + LOCK_TIMERS_WRITE; + LOCK_CHANNELS_READ; tChannelID ChannelID = tChannelID::InvalidID; if (isnumber(Option)) { int o = strtol(Option, NULL, 10); - if (o >= 1 && o <= Channels.MaxNumber()) - ChannelID = Channels.GetByNumber(o)->GetChannelID(); + if (o >= 1 && o <= cChannels::MaxNumber()) + ChannelID = Channels->GetByNumber(o)->GetChannelID(); } else { ChannelID = tChannelID::FromString(Option); if (ChannelID == tChannelID::InvalidID) { - for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) { + for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { if (!Channel->GroupSep()) { if (strcasecmp(Channel->Name(), Option) == 0) { ChannelID = Channel->GetChannelID(); @@ -862,45 +1179,41 @@ void cSVDRPServer::CmdCLRE(const char *Option) } } if (!(ChannelID == tChannelID::InvalidID)) { - cSchedulesLock SchedulesLock(true, 1000); - cSchedules *s = (cSchedules *)cSchedules::Schedules(SchedulesLock); - if (s) { - cSchedule *Schedule = NULL; - ChannelID.ClrRid(); - for (cSchedule *p = s->First(); p; p = s->Next(p)) { - if (p->ChannelID() == ChannelID) { - Schedule = p; - break; - } + LOCK_SCHEDULES_WRITE; + cSchedule *Schedule = NULL; + ChannelID.ClrRid(); + for (cSchedule *p = Schedules->First(); p; p = Schedules->Next(p)) { + if (p->ChannelID() == ChannelID) { + Schedule = p; + break; } - if (Schedule) { - for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) { - if (ChannelID == Timer->Channel()->GetChannelID().ClrRid()) - Timer->SetEvent(NULL); - } - Schedule->Cleanup(INT_MAX); - cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME); - Reply(250, "EPG data of channel \"%s\" cleared", Option); - } - else { - Reply(550, "No EPG data found for channel \"%s\"", Option); - return; - } + } + if (Schedule) { + for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) { + if (ChannelID == Timer->Channel()->GetChannelID().ClrRid()) + Timer->SetEvent(NULL); + } + Schedule->Cleanup(INT_MAX); + cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME); + Reply(250, "EPG data of channel \"%s\" cleared", Option); + } + else { + Reply(550, "No EPG data found for channel \"%s\"", Option); + return; } - else - Reply(451, "Can't get EPG data"); } else Reply(501, "Undefined channel \"%s\"", Option); } else { + LOCK_TIMERS_WRITE; + LOCK_SCHEDULES_WRITE; + for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) + Timer->SetEvent(NULL); // processing all timers here (local *and* remote) + for (cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->Next(Schedule)) + Schedule->Cleanup(INT_MAX); cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME); - if (cSchedules::ClearAll()) { - Reply(250, "EPG data cleared"); - cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME); - } - else - Reply(451, "Error while clearing EPG data"); + Reply(250, "EPG data cleared"); } } @@ -908,41 +1221,38 @@ void cSVDRPServer::CmdDELC(const char *Option) { if (*Option) { if (isnumber(Option)) { - if (!Channels.BeingEdited()) { - cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10)); - if (channel) { - for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) { - if (timer->Channel() == channel) { - Reply(550, "Channel \"%s\" is in use by timer %d", Option, timer->Index() + 1); - return; - } - } - int CurrentChannelNr = cDevice::CurrentChannel(); - cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr); - if (CurrentChannel && channel == CurrentChannel) { - int n = Channels.GetNextNormal(CurrentChannel->Index()); - if (n < 0) - n = Channels.GetPrevNormal(CurrentChannel->Index()); - CurrentChannel = Channels.Get(n); - CurrentChannelNr = 0; // triggers channel switch below - } - Channels.Del(channel); - Channels.ReNumber(); - Channels.SetModified(true); - isyslog("SVDRP < %s channel %s deleted", *connection, Option); - if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { - if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) - Channels.SwitchTo(CurrentChannel->Number()); - else - cDevice::SetCurrentChannel(CurrentChannel); - } - Reply(250, "Channel \"%s\" deleted", Option); + LOCK_TIMERS_READ; + LOCK_CHANNELS_WRITE; + Channels->SetExplicitModify(); + if (cChannel *Channel = Channels->GetByNumber(strtol(Option, NULL, 10))) { + if (const cTimer *Timer = Timers->UsesChannel(Channel)) { + Reply(550, "Channel \"%s\" is in use by timer %d", Option, Timer->Index() + 1); + return; } - else - Reply(501, "Channel \"%s\" not defined", Option); + int CurrentChannelNr = cDevice::CurrentChannel(); + cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr); + if (CurrentChannel && Channel == CurrentChannel) { + int n = Channels->GetNextNormal(CurrentChannel->Index()); + if (n < 0) + n = Channels->GetPrevNormal(CurrentChannel->Index()); + CurrentChannel = Channels->Get(n); + CurrentChannelNr = 0; // triggers channel switch below + } + Channels->Del(Channel); + Channels->ReNumber(); + Channels->SetModifiedByUser(); + Channels->SetModified(); + isyslog("SVDRP < %s channel %s deleted", *connection, Option); + if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { + if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) + Channels->SwitchTo(CurrentChannel->Number()); + else + cDevice::SetCurrentChannel(CurrentChannel); + } + Reply(250, "Channel \"%s\" deleted", Option); } else - Reply(550, "Channels are being edited - try again later"); + Reply(501, "Channel \"%s\" not defined", Option); } else Reply(501, "Error in channel number \"%s\"", Option); @@ -971,21 +1281,23 @@ void cSVDRPServer::CmdDELR(const char *Option) { if (*Option) { if (isnumber(Option)) { - cRecording *recording = recordings.Get(strtol(Option, NULL, 10) - 1); - if (recording) { - if (int RecordingInUse = recording->IsInUse()) - Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, recording)); + LOCK_RECORDINGS_WRITE; + Recordings->SetExplicitModify(); + if (cRecording *Recording = Recordings->Get(strtol(Option, NULL, 10) - 1)) { + if (int RecordingInUse = Recording->IsInUse()) + Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording)); else { - if (recording->Delete()) { + if (Recording->Delete()) { + Recordings->DelByName(Recording->FileName()); + Recordings->SetModified(); Reply(250, "Recording \"%s\" deleted", Option); - Recordings.DelByName(recording->FileName()); } else Reply(554, "Error while deleting recording!"); } } else - Reply(550, "Recording \"%s\" not found%s", Option, recordings.Count() ? "" : " (use LSTR before deleting)"); + Reply(550, "Recording \"%s\" not found", Option); } else Reply(501, "Error in recording number \"%s\"", Option); @@ -998,23 +1310,21 @@ void cSVDRPServer::CmdDELT(const char *Option) { if (*Option) { if (isnumber(Option)) { - if (!Timers.BeingEdited()) { - cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1); - if (timer) { - if (!timer->Recording()) { - isyslog("SVDRP < %s deleting timer %s", *connection, *timer->ToDescr()); - Timers.Del(timer); - Timers.SetModified(); - Reply(250, "Timer \"%s\" deleted", Option); - } - else - Reply(550, "Timer \"%s\" is recording", Option); + LOCK_TIMERS_WRITE; + Timers->SetExplicitModify(); + cTimer *Timer = Timers->Get(strtol(Option, NULL, 10) - 1); + if (Timer && !Timer->Remote()) { + if (!Timer->Recording()) { + isyslog("SVDRP < %s deleting timer %s", *connection, *Timer->ToDescr()); + Timers->Del(Timer); + Timers->SetModified(); + Reply(250, "Timer \"%s\" deleted", Option); } else - Reply(501, "Timer \"%s\" not defined", Option); + Reply(550, "Timer \"%s\" is recording", Option); } else - Reply(550, "Timers are being edited - try again later"); + Reply(501, "Timer \"%s\" not defined", Option); } else Reply(501, "Error in timer number \"%s\"", Option); @@ -1027,12 +1337,12 @@ void cSVDRPServer::CmdEDIT(const char *Option) { if (*Option) { if (isnumber(Option)) { - cRecording *recording = recordings.Get(strtol(Option, NULL, 10) - 1); - if (recording) { + LOCK_RECORDINGS_READ; + if (const cRecording *Recording = Recordings->Get(strtol(Option, NULL, 10) - 1)) { cMarks Marks; - if (Marks.Load(recording->FileName(), recording->FramesPerSecond(), recording->IsPesRecording()) && Marks.Count()) { - if (RecordingsHandler.Add(ruCut, recording->FileName())) - Reply(250, "Editing recording \"%s\" [%s]", Option, recording->Title()); + if (Marks.Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.Count()) { + if (RecordingsHandler.Add(ruCut, Recording->FileName())) + Reply(250, "Editing recording \"%s\" [%s]", Option, Recording->Title()); else Reply(554, "Can't start editing process"); } @@ -1040,7 +1350,7 @@ void cSVDRPServer::CmdEDIT(const char *Option) Reply(554, "No editing marks defined"); } else - Reply(550, "Recording \"%s\" not found%s", Option, recordings.Count() ? "" : " (use LSTR before editing)"); + Reply(550, "Recording \"%s\" not found", Option); } else Reply(501, "Error in recording number \"%s\"", Option); @@ -1255,40 +1565,40 @@ void cSVDRPServer::CmdHITK(const char *Option) void cSVDRPServer::CmdLSTC(const char *Option) { + LOCK_CHANNELS_READ; bool WithGroupSeps = strcasecmp(Option, ":groups") == 0; if (*Option && !WithGroupSeps) { if (isnumber(Option)) { - cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10)); - if (channel) - Reply(250, "%d %s", channel->Number(), *channel->ToText()); + if (const cChannel *Channel = Channels->GetByNumber(strtol(Option, NULL, 10))) + Reply(250, "%d %s", Channel->Number(), *Channel->ToText()); else Reply(501, "Channel \"%s\" not defined", Option); } else { - cChannel *next = Channels.GetByChannelID(tChannelID::FromString(Option)); - if (!next) { - for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) { - if (!channel->GroupSep()) { - if (strcasestr(channel->Name(), Option)) { - if (next) - Reply(-250, "%d %s", next->Number(), *next->ToText()); - next = channel; + const cChannel *Next = Channels->GetByChannelID(tChannelID::FromString(Option)); + if (!Next) { + for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { + if (!Channel->GroupSep()) { + if (strcasestr(Channel->Name(), Option)) { + if (Next) + Reply(-250, "%d %s", Next->Number(), *Next->ToText()); + Next = Channel; } } } } - if (next) - Reply(250, "%d %s", next->Number(), *next->ToText()); + if (Next) + Reply(250, "%d %s", Next->Number(), *Next->ToText()); else Reply(501, "Channel \"%s\" not defined", Option); } } - else if (Channels.MaxNumber() >= 1) { - for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) { + else if (cChannels::MaxNumber() >= 1) { + for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { if (WithGroupSeps) - Reply(channel->Next() ? -250: 250, "%d %s", channel->GroupSep() ? 0 : channel->Number(), *channel->ToText()); - else if (!channel->GroupSep()) - Reply(channel->Number() < Channels.MaxNumber() ? -250 : 250, "%d %s", channel->Number(), *channel->ToText()); + Reply(Channel->Next() ? -250: 250, "%d %s", Channel->GroupSep() ? 0 : Channel->Number(), *Channel->ToText()); + else if (!Channel->GroupSep()) + Reply(Channel->Number() < cChannels::MaxNumber() ? -250 : 250, "%d %s", Channel->Number(), *Channel->ToText()); } } else @@ -1297,92 +1607,88 @@ void cSVDRPServer::CmdLSTC(const char *Option) void cSVDRPServer::CmdLSTE(const char *Option) { - cSchedulesLock SchedulesLock; - const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock); - if (Schedules) { - const cSchedule* Schedule = NULL; - eDumpMode DumpMode = dmAll; - time_t AtTime = 0; - if (*Option) { - char buf[strlen(Option) + 1]; - strcpy(buf, Option); - const char *delim = " \t"; - char *strtok_next; - char *p = strtok_r(buf, delim, &strtok_next); - while (p && DumpMode == dmAll) { - if (strcasecmp(p, "NOW") == 0) - DumpMode = dmPresent; - else if (strcasecmp(p, "NEXT") == 0) - DumpMode = dmFollowing; - else if (strcasecmp(p, "AT") == 0) { - DumpMode = dmAtTime; - if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) { - if (isnumber(p)) - AtTime = strtol(p, NULL, 10); - else { - Reply(501, "Invalid time"); - return; - } - } - else { - Reply(501, "Missing time"); - return; - } - } - else if (!Schedule) { - cChannel* Channel = NULL; + LOCK_SCHEDULES_READ; + const cSchedule* Schedule = NULL; + eDumpMode DumpMode = dmAll; + time_t AtTime = 0; + if (*Option) { + char buf[strlen(Option) + 1]; + strcpy(buf, Option); + const char *delim = " \t"; + char *strtok_next; + char *p = strtok_r(buf, delim, &strtok_next); + while (p && DumpMode == dmAll) { + if (strcasecmp(p, "NOW") == 0) + DumpMode = dmPresent; + else if (strcasecmp(p, "NEXT") == 0) + DumpMode = dmFollowing; + else if (strcasecmp(p, "AT") == 0) { + DumpMode = dmAtTime; + if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) { if (isnumber(p)) - Channel = Channels.GetByNumber(strtol(Option, NULL, 10)); - else - Channel = Channels.GetByChannelID(tChannelID::FromString(Option)); - if (Channel) { - Schedule = Schedules->GetSchedule(Channel); - if (!Schedule) { - Reply(550, "No schedule found"); - return; - } - } + AtTime = strtol(p, NULL, 10); else { - Reply(550, "Channel \"%s\" not defined", p); + Reply(501, "Invalid time"); return; } } else { - Reply(501, "Unknown option: \"%s\"", p); + Reply(501, "Missing time"); return; } - p = strtok_r(NULL, delim, &strtok_next); } - } - int fd = dup(file); - if (fd) { - FILE *f = fdopen(fd, "w"); - if (f) { - if (Schedule) - Schedule->Dump(f, "215-", DumpMode, AtTime); - else - Schedules->Dump(f, "215-", DumpMode, AtTime); - fflush(f); - Reply(215, "End of EPG data"); - fclose(f); - } - else { - Reply(451, "Can't open file connection"); - close(fd); + else if (!Schedule) { + LOCK_CHANNELS_READ; + const cChannel* Channel = NULL; + if (isnumber(p)) + Channel = Channels->GetByNumber(strtol(Option, NULL, 10)); + else + Channel = Channels->GetByChannelID(tChannelID::FromString(Option)); + if (Channel) { + Schedule = Schedules->GetSchedule(Channel); + if (!Schedule) { + Reply(550, "No schedule found"); + return; + } + } + else { + Reply(550, "Channel \"%s\" not defined", p); + return; + } + } + else { + Reply(501, "Unknown option: \"%s\"", p); + return; + } + p = strtok_r(NULL, delim, &strtok_next); } + } + int fd = dup(file); + if (fd) { + FILE *f = fdopen(fd, "w"); + if (f) { + if (Schedule) + Schedule->Dump(f, "215-", DumpMode, AtTime); + else + Schedules->Dump(f, "215-", DumpMode, AtTime); + fflush(f); + Reply(215, "End of EPG data"); + fclose(f); + } + else { + Reply(451, "Can't open file connection"); + close(fd); } - else - Reply(451, "Can't dup stream descriptor"); } else - Reply(451, "Can't get EPG data"); + Reply(451, "Can't dup stream descriptor"); } void cSVDRPServer::CmdLSTR(const char *Option) { int Number = 0; bool Path = false; - recordings.Update(true); + LOCK_RECORDINGS_READ; if (*Option) { char buf[strlen(Option) + 1]; strcpy(buf, Option); @@ -1407,14 +1713,13 @@ void cSVDRPServer::CmdLSTR(const char *Option) p = strtok_r(NULL, delim, &strtok_next); } if (Number) { - cRecording *recording = recordings.Get(strtol(Option, NULL, 10) - 1); - if (recording) { + if (const cRecording *Recording = Recordings->Get(strtol(Option, NULL, 10) - 1)) { FILE *f = fdopen(file, "w"); if (f) { if (Path) - Reply(250, "%s", recording->FileName()); + Reply(250, "%s", Recording->FileName()); else { - recording->Info()->Write(f, "215-"); + Recording->Info()->Write(f, "215-"); fflush(f); Reply(215, "End of recording information"); } @@ -1427,11 +1732,11 @@ void cSVDRPServer::CmdLSTR(const char *Option) Reply(550, "Recording \"%s\" not found", Option); } } - else if (recordings.Count()) { - cRecording *recording = recordings.First(); - while (recording) { - Reply(recording == recordings.Last() ? 250 : -250, "%d %s", recording->Index() + 1, recording->Title(' ', true)); - recording = recordings.Next(recording); + else if (Recordings->Count()) { + const cRecording *Recording = Recordings->First(); + while (Recording) { + Reply(Recording == Recordings->Last() ? 250 : -250, "%d %s", Recording->Index() + 1, Recording->Title(' ', true)); + Recording = Recordings->Next(Recording); } } else @@ -1460,24 +1765,34 @@ void cSVDRPServer::CmdLSTT(const char *Option) p = strtok_r(NULL, delim, &strtok_next); } } + LOCK_TIMERS_READ; if (Number) { - cTimer *timer = Timers.Get(Number - 1); - if (timer) - Reply(250, "%d %s", timer->Index() + 1, *timer->ToText(Id)); - else - Reply(501, "Timer \"%s\" not defined", Option); - } - else if (Timers.Count()) { - for (int i = 0; i < Timers.Count(); i++) { - cTimer *timer = Timers.Get(i); - if (timer) - Reply(i < Timers.Count() - 1 ? -250 : 250, "%d %s", timer->Index() + 1, *timer->ToText(Id)); - else - Reply(501, "Timer \"%d\" not found", i + 1); + for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) { + if (!Timer->Remote()) { + if (Timer->Index() + 1 == Number) { + Reply(250, "%d %s", Timer->Index() + 1, *Timer->ToText(Id)); + return; + } + } } + Reply(501, "Timer \"%s\" not defined", Option); + return; } - else - Reply(550, "No timers defined"); + else { + cVector LocalTimers; + for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) { + if (!Timer->Remote()) + LocalTimers.Append(Timer); + } + if (LocalTimers.Size()) { + for (int i = 0; i < LocalTimers.Size(); i++) { + const cTimer *Timer = LocalTimers[i]; + Reply(i < LocalTimers.Size() - 1 ? -250 : 250, "%d %s", Timer->Index() + 1, *Timer->ToText(Id)); + } + return; + } + } + Reply(550, "No timers defined"); } void cSVDRPServer::CmdMESG(const char *Option) @@ -1498,29 +1813,27 @@ void cSVDRPServer::CmdMODC(const char *Option) int n = strtol(Option, &tail, 10); if (tail && tail != Option) { tail = skipspace(tail); - if (!Channels.BeingEdited()) { - cChannel *channel = Channels.GetByNumber(n); - if (channel) { - cChannel ch; - if (ch.Parse(tail)) { - if (Channels.HasUniqueChannelID(&ch, channel)) { - *channel = ch; - Channels.ReNumber(); - Channels.SetModified(true); - isyslog("SVDRP < %s modifed channel %d %s", *connection, channel->Number(), *channel->ToText()); - Reply(250, "%d %s", channel->Number(), *channel->ToText()); - } - else - Reply(501, "Channel settings are not unique"); + LOCK_CHANNELS_WRITE; + Channels->SetExplicitModify(); + if (cChannel *Channel = Channels->GetByNumber(n)) { + cChannel ch; + if (ch.Parse(tail)) { + if (Channels->HasUniqueChannelID(&ch, Channel)) { + *Channel = ch; + Channels->ReNumber(); + Channels->SetModifiedByUser(); + Channels->SetModified(); + isyslog("SVDRP < %s modifed channel %d %s", *connection, Channel->Number(), *Channel->ToText()); + Reply(250, "%d %s", Channel->Number(), *Channel->ToText()); } else - Reply(501, "Error in channel settings"); + Reply(501, "Channel settings are not unique"); } else - Reply(501, "Channel \"%d\" not defined", n); + Reply(501, "Error in channel settings"); } else - Reply(550, "Channels are being edited - try again later"); + Reply(501, "Channel \"%d\" not defined", n); } else Reply(501, "Error in channel number"); @@ -1536,28 +1849,25 @@ void cSVDRPServer::CmdMODT(const char *Option) int n = strtol(Option, &tail, 10); if (tail && tail != Option) { tail = skipspace(tail); - if (!Timers.BeingEdited()) { - cTimer *timer = Timers.Get(n - 1); - if (timer) { - cTimer t = *timer; - if (strcasecmp(tail, "ON") == 0) - t.SetFlags(tfActive); - else if (strcasecmp(tail, "OFF") == 0) - t.ClrFlags(tfActive); - else if (!t.Parse(tail)) { - Reply(501, "Error in timer settings"); - return; - } - *timer = t; - Timers.SetModified(); - isyslog("SVDRP < %s timer %s modified (%s)", *connection, *timer->ToDescr(), timer->HasFlags(tfActive) ? "active" : "inactive"); - Reply(250, "%d %s", timer->Index() + 1, *timer->ToText()); + LOCK_TIMERS_WRITE; + Timers->SetExplicitModify(); + if (cTimer *Timer = Timers->Get(n - 1)) { + cTimer t = *Timer; + if (strcasecmp(tail, "ON") == 0) + t.SetFlags(tfActive); + else if (strcasecmp(tail, "OFF") == 0) + t.ClrFlags(tfActive); + else if (!t.Parse(tail)) { + Reply(501, "Error in timer settings"); + return; } - else - Reply(501, "Timer \"%d\" not defined", n); + *Timer = t; + Timers->SetModified(); + isyslog("SVDRP < %s timer %s modified (%s)", *connection, *Timer->ToDescr(), Timer->HasFlags(tfActive) ? "active" : "inactive"); + Reply(250, "%d %s", Timer->Index() + 1, *Timer->ToText()); } else - Reply(550, "Timers are being edited - try again later"); + Reply(501, "Timer \"%d\" not defined", n); } else Reply(501, "Error in timer number"); @@ -1569,51 +1879,51 @@ void cSVDRPServer::CmdMODT(const char *Option) void cSVDRPServer::CmdMOVC(const char *Option) { if (*Option) { - if (!Channels.BeingEdited() && !Timers.BeingEdited()) { - char *tail; - int From = strtol(Option, &tail, 10); + char *tail; + int From = strtol(Option, &tail, 10); + if (tail && tail != Option) { + tail = skipspace(tail); if (tail && tail != Option) { - tail = skipspace(tail); - if (tail && tail != Option) { - int To = strtol(tail, NULL, 10); - int CurrentChannelNr = cDevice::CurrentChannel(); - cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr); - cChannel *FromChannel = Channels.GetByNumber(From); - if (FromChannel) { - cChannel *ToChannel = Channels.GetByNumber(To); - if (ToChannel) { - int FromNumber = FromChannel->Number(); - int ToNumber = ToChannel->Number(); - if (FromNumber != ToNumber) { - Channels.Move(FromChannel, ToChannel); - Channels.ReNumber(); - Channels.SetModified(true); - if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { - if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) - Channels.SwitchTo(CurrentChannel->Number()); - else - cDevice::SetCurrentChannel(CurrentChannel); - } - isyslog("SVDRP < %s channel %d moved to %d", *connection, FromNumber, ToNumber); - Reply(250,"Channel \"%d\" moved to \"%d\"", From, To); + LOCK_TIMERS_READ; // necessary to keep timers and channels in sync! + LOCK_CHANNELS_WRITE; + Channels->SetExplicitModify(); + int To = strtol(tail, NULL, 10); + int CurrentChannelNr = cDevice::CurrentChannel(); + const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr); + cChannel *FromChannel = Channels->GetByNumber(From); + if (FromChannel) { + cChannel *ToChannel = Channels->GetByNumber(To); + if (ToChannel) { + int FromNumber = FromChannel->Number(); + int ToNumber = ToChannel->Number(); + if (FromNumber != ToNumber) { + Channels->Move(FromChannel, ToChannel); + Channels->ReNumber(); + Channels->SetModifiedByUser(); + Channels->SetModified(); + if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { + if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) + Channels->SwitchTo(CurrentChannel->Number()); + else + cDevice::SetCurrentChannel(CurrentChannel); } - else - Reply(501, "Can't move channel to same position"); + isyslog("SVDRP < %s channel %d moved to %d", *connection, FromNumber, ToNumber); + Reply(250,"Channel \"%d\" moved to \"%d\"", From, To); } else - Reply(501, "Channel \"%d\" not defined", To); + Reply(501, "Can't move channel to same position"); } else - Reply(501, "Channel \"%d\" not defined", From); + Reply(501, "Channel \"%d\" not defined", To); } else - Reply(501, "Error in channel number"); + Reply(501, "Channel \"%d\" not defined", From); } else Reply(501, "Error in channel number"); } else - Reply(550, "Channels or timers are being edited - try again later"); + Reply(501, "Error in channel number"); } else Reply(501, "Missing channel number"); @@ -1630,17 +1940,21 @@ void cSVDRPServer::CmdMOVR(const char *Option) char c = *option; *option = 0; if (isnumber(num)) { - cRecording *recording = recordings.Get(strtol(num, NULL, 10) - 1); - if (recording) { - if (int RecordingInUse = recording->IsInUse()) - Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, recording)); + LOCK_RECORDINGS_WRITE; + Recordings->SetExplicitModify(); + if (cRecording *Recording = Recordings->Get(strtol(num, NULL, 10) - 1)) { + if (int RecordingInUse = Recording->IsInUse()) + Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording)); else { if (c) option = skipspace(++option); if (*option) { - cString oldName = recording->Name(); - if ((recording = Recordings.GetByName(recording->FileName())) != NULL && recording->ChangeName(option)) - Reply(250, "Recording \"%s\" moved to \"%s\"", *oldName, recording->Name()); + cString oldName = Recording->Name(); + if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) { + Recordings->SetModified(); + Recordings->TouchUpdate(); + Reply(250, "Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name()); + } else Reply(554, "Error while moving recording \"%s\" to \"%s\"!", *oldName, option); } @@ -1649,7 +1963,7 @@ void cSVDRPServer::CmdMOVR(const char *Option) } } else - Reply(550, "Recording \"%s\" not found%s", num, recordings.Count() ? "" : " (use LSTR before moving)"); + Reply(550, "Recording \"%s\" not found", num); } else Reply(501, "Error in recording number \"%s\"", num); @@ -1664,12 +1978,15 @@ void cSVDRPServer::CmdNEWC(const char *Option) if (*Option) { cChannel ch; if (ch.Parse(Option)) { - if (Channels.HasUniqueChannelID(&ch)) { + LOCK_CHANNELS_WRITE; + Channels->SetExplicitModify(); + if (Channels->HasUniqueChannelID(&ch)) { cChannel *channel = new cChannel; *channel = ch; - Channels.Add(channel); - Channels.ReNumber(); - Channels.SetModified(true); + Channels->Add(channel); + Channels->ReNumber(); + Channels->SetModifiedByUser(); + Channels->SetModified(); isyslog("SVDRP < %s new channel %d %s", *connection, channel->Number(), *channel->ToText()); Reply(250, "%d %s", channel->Number(), *channel->ToText()); } @@ -1686,17 +2003,17 @@ void cSVDRPServer::CmdNEWC(const char *Option) void cSVDRPServer::CmdNEWT(const char *Option) { if (*Option) { - cTimer *timer = new cTimer; - if (timer->Parse(Option)) { - Timers.Add(timer); - Timers.SetModified(); - isyslog("SVDRP < %s timer %s added", *connection, *timer->ToDescr()); - Reply(250, "%d %s", timer->Index() + 1, *timer->ToText()); + cTimer *Timer = new cTimer; + if (Timer->Parse(Option)) { + LOCK_TIMERS_WRITE; + Timers->Add(Timer); + isyslog("SVDRP < %s timer %s added", *connection, *Timer->ToDescr()); + Reply(250, "%d %s", Timer->Index() + 1, *Timer->ToText()); return; } else Reply(501, "Error in timer settings"); - delete timer; + delete Timer; } else Reply(501, "Missing timer settings"); @@ -1704,8 +2021,8 @@ void cSVDRPServer::CmdNEWT(const char *Option) void cSVDRPServer::CmdNEXT(const char *Option) { - cTimer *t = Timers.GetNextActiveTimer(); - if (t) { + LOCK_TIMERS_READ; + if (const cTimer *t = Timers->GetNextActiveTimer()) { time_t Start = t->StartTime(); int Number = t->Index() + 1; if (!*Option) @@ -1723,7 +2040,7 @@ void cSVDRPServer::CmdNEXT(const char *Option) void cSVDRPServer::CmdPING(const char *Option) { - Reply(250, "%s is alive", HostName()); + Reply(250, "%s is alive", SVDRPHostName()); } void cSVDRPServer::CmdPLAY(const char *Option) @@ -1737,8 +2054,8 @@ void cSVDRPServer::CmdPLAY(const char *Option) char c = *option; *option = 0; if (isnumber(num)) { - cRecording *recording = recordings.Get(strtol(num, NULL, 10) - 1); - if (recording) { + LOCK_RECORDINGS_READ; + if (const cRecording *Recording = Recordings->Get(strtol(num, NULL, 10) - 1)) { if (c) option = skipspace(++option); cReplayControl::SetRecording(NULL); @@ -1746,20 +2063,20 @@ void cSVDRPServer::CmdPLAY(const char *Option) if (*option) { int pos = 0; if (strcasecmp(option, "BEGIN") != 0) - pos = HMSFToIndex(option, recording->FramesPerSecond()); - cResumeFile resume(recording->FileName(), recording->IsPesRecording()); + pos = HMSFToIndex(option, Recording->FramesPerSecond()); + cResumeFile Resume(Recording->FileName(), Recording->IsPesRecording()); if (pos <= 0) - resume.Delete(); + Resume.Delete(); else - resume.Save(pos); + Resume.Save(pos); } - cReplayControl::SetRecording(recording->FileName()); + cReplayControl::SetRecording(Recording->FileName()); cControl::Launch(new cReplayControl); cControl::Attach(); - Reply(250, "Playing recording \"%s\" [%s]", num, recording->Title()); + Reply(250, "Playing recording \"%s\" [%s]", num, Recording->Title()); } else - Reply(550, "Recording \"%s\" not found%s", num, recordings.Count() ? "" : " (use LSTR before playing)"); + Reply(550, "Recording \"%s\" not found", num); } else Reply(501, "Error in recording number \"%s\"", num); @@ -1840,6 +2157,36 @@ void cSVDRPServer::CmdPLUG(const char *Option) } } +void cSVDRPServer::CmdPOLL(const char *Option) +{ + if (*Option) { + char buf[strlen(Option) + 1]; + char *p = strcpy(buf, Option); + const char *delim = " \t"; + char *strtok_next; + char *RemoteName = strtok_r(p, delim, &strtok_next); + char *ListName = strtok_r(NULL, delim, &strtok_next); + if (SVDRPClientHandler) { + if (ListName) { + if (strcasecmp(ListName, "timers") == 0) { + if (SVDRPClientHandler->TriggerFetchingTimers(RemoteName)) + Reply(250, "OK"); + else + Reply(501, "No connection to \"%s\"", RemoteName); + } + else + Reply(501, "Unknown list name: \"%s\"", ListName); + } + else + Reply(501, "Missing list name"); + } + else + Reply(501, "No SVDRP client connections"); + } + else + Reply(501, "Missing parameters"); +} + void cSVDRPServer::CmdPUTE(const char *Option) { if (*Option) { @@ -1907,30 +2254,25 @@ void cSVDRPServer::CmdSTAT(const char *Option) void cSVDRPServer::CmdUPDT(const char *Option) { if (*Option) { - cTimer *timer = new cTimer; - if (timer->Parse(Option)) { - if (!Timers.BeingEdited()) { - cTimer *t = Timers.GetTimer(timer); - if (t) { - t->Parse(Option); - delete timer; - timer = t; - isyslog("SVDRP < %s timer %s updated", *connection, *timer->ToDescr()); - } - else { - Timers.Add(timer); - isyslog("SVDRP < %s timer %s added", *connection, *timer->ToDescr()); - } - Timers.SetModified(); - Reply(250, "%d %s", timer->Index() + 1, *timer->ToText()); - return; + cTimer *Timer = new cTimer; + if (Timer->Parse(Option)) { + LOCK_TIMERS_WRITE; + if (cTimer *t = Timers->GetTimer(Timer)) { + t->Parse(Option); + delete Timer; + Timer = t; + isyslog("SVDRP < %s timer %s updated", *connection, *Timer->ToDescr()); } - else - Reply(550, "Timers are being edited - try again later"); + else { + Timers->Add(Timer); + isyslog("SVDRP < %s timer %s added", *connection, *Timer->ToDescr()); + } + Reply(250, "%d %s", Timer->Index() + 1, *Timer->ToText()); + return; } else Reply(501, "Error in timer settings"); - delete timer; + delete Timer; } else Reply(501, "Missing timer settings"); @@ -1938,7 +2280,8 @@ void cSVDRPServer::CmdUPDT(const char *Option) void cSVDRPServer::CmdUPDR(const char *Option) { - Recordings.Update(false); + LOCK_RECORDINGS_WRITE; + Recordings->Update(false); Reply(250, "Re-read of recordings directory triggered"); } @@ -2010,6 +2353,7 @@ void cSVDRPServer::Execute(char *Cmd) else if (CMD("PING")) CmdPING(s); else if (CMD("PLAY")) CmdPLAY(s); else if (CMD("PLUG")) CmdPLUG(s); + else if (CMD("POLL")) CmdPOLL(s); else if (CMD("PUTE")) CmdPUTE(s); else if (CMD("REMO")) CmdREMO(s); else if (CMD("SCAN")) CmdSCAN(s); @@ -2034,6 +2378,7 @@ bool cSVDRPServer::Process(void) cmdLine[--numChars] = 0; // make sure the string is terminated: cmdLine[numChars] = 0; + dbgsvdrp("< %s: %s\n", *connection, cmdLine); // showtime! Execute(cmdLine); numChars = 0; @@ -2091,162 +2436,6 @@ void SetSVDRPGrabImageDir(const char *GrabImageDir) grabImageDir = GrabImageDir; } -// --- cSVDRPClient ---------------------------------------------------------- - -class cSVDRPClient { -private: - cIpAddress ipAddress; - cSocket socket; - cString serverName; - int timeout; - cTimeMs pingTime; - cFile file; - void Close(void); -public: - cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout); - ~cSVDRPClient(); - const char *ServerName(void) const { return serverName; } - const char *Connection(void) const { return ipAddress.Connection(); } - bool HasAddress(const char *Address, int Port) const; - bool Send(const char *Command); - bool Ping(void); - bool Process(cStringList *Response = NULL); - bool Execute(const char *Command, cStringList *Response); - }; - -cSVDRPClient::cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout) -:ipAddress(Address, Port) -,socket(Port, true) -{ - serverName = ServerName; - timeout = Timeout * 1000 * 9 / 10; // ping after 90% of timeout - pingTime.Set(timeout); - if (socket.Connect(Address)) { - if (file.Open(socket.Socket())) - SVDRPClientPoller.Add(file, false); - } - dsyslog("SVDRP > %s client created for '%s'", ipAddress.Connection(), *serverName); - SendSVDRPDiscover(Address); -} - -cSVDRPClient::~cSVDRPClient() -{ - Close(); - dsyslog("SVDRP > %s client destroyed for '%s'", ipAddress.Connection(), *serverName); -} - -void cSVDRPClient::Close(void) -{ - if (file.IsOpen()) { - SVDRPClientPoller.Del(file, false); - file.Close(); - socket.Close(); - } -} - -bool cSVDRPClient::HasAddress(const char *Address, int Port) const -{ - return strcmp(ipAddress.Address(), Address) == 0 && ipAddress.Port() == Port; -} - -bool cSVDRPClient::Send(const char *Command) -{ - pingTime.Set(timeout); - dbgsvdrp("> %s: %s\n", *serverName, Command); - if (safe_write(file, Command, strlen(Command) + 1) < 0) { - LOG_ERROR; - return false; - } - return true; -} - -bool cSVDRPClient::Ping(void) -{ - cSVDRPCommand Cmd(serverName, "PING"); - if (Cmd.Execute()) - return Cmd.Response(0) && atoi(Cmd.Response(0)) == 250; - return false; -} - -bool cSVDRPClient::Process(cStringList *Response) -{ - if (file.IsOpen()) { - char input[BUFSIZ]; - int numChars = 0; -#define SVDRPResonseTimeout 5000 // ms - cTimeMs Timeout(SVDRPResonseTimeout); - for (;;) { - if (file.Ready(false)) { - unsigned char c; - int r = safe_read(file, &c, 1); - if (r > 0) { - if (c == '\n' || c == 0x00) { - // strip trailing whitespace: - while (numChars > 0 && strchr(" \t\r\n", input[numChars - 1])) - input[--numChars] = 0; - // make sure the string is terminated: - input[numChars] = 0; - dbgsvdrp("< %s: %s\n", *serverName, input); - if (Response) { - Response->Append(strdup(input)); - if (input[3] != '-') // no more lines will follow - break; - } - else { - switch (atoi(input)) { - case 220: if (numChars > 4) { - char *n = input + 4; - if (char *t = strchr(n, ' ')) { - *t = 0; - if (strcmp(n, serverName) != 0) { - serverName = n; - dsyslog("SVDRP < %s remote server name is '%s'", ipAddress.Connection(), *serverName); - } - } - } - break; - case 221: dsyslog("SVDRP < %s remote server closed connection to '%s'", ipAddress.Connection(), *serverName); - Close(); - break; - } - } - numChars = 0; - } - else { - if (numChars >= int(sizeof(input))) { - esyslog("SVDRP < %s ERROR: out of memory", ipAddress.Connection()); - Close(); - break; - } - input[numChars++] = c; - input[numChars] = 0; - } - Timeout.Set(SVDRPResonseTimeout); - } - else if (r <= 0) { - isyslog("SVDRP < %s lost connection to remote server '%s'", ipAddress.Connection(), *serverName); - Close(); - } - } - else if (!Response) - break; - else if (Timeout.TimedOut()) { - esyslog("SVDRP < %s timeout while waiting for response from '%s'", ipAddress.Connection(), *serverName); - return false; - } - } - if (pingTime.TimedOut()) - Ping(); - } - return file.IsOpen(); -} - -bool cSVDRPClient::Execute(const char *Command, cStringList *Response) -{ - Response->Clear(); - return Send(Command) && Process(Response); -} - // --- cSVDRPServerHandler --------------------------------------------------- class cSVDRPServerHandler : public cThread { @@ -2265,6 +2454,8 @@ public: void WaitUntilReady(void); }; +static cSVDRPServerHandler *SVDRPServerHandler = NULL; + cSVDRPServerHandler::cSVDRPServerHandler(int TcpPort) :cThread("SVDRP server handler", true) ,tcpSocket(TcpPort, true) @@ -2321,141 +2512,9 @@ void cSVDRPServerHandler::Action(void) } } -// --- cSVDRPClientHandler --------------------------------------------------- - -class cSVDRPClientHandler : public cThread { -private: - cMutex mutex; - cSocket udpSocket; - int tcpPort; - cVector clientConnections; - void HandleClientConnection(void); - void ProcessConnections(void); - cSVDRPClient *GetClientForServer(const char *ServerName); -protected: - virtual void Action(void); -public: - cSVDRPClientHandler(int UdpPort, int TcpPort); - virtual ~cSVDRPClientHandler(); - void SendDiscover(const char *Address = NULL); - bool Execute(const char *ServerName, const char *Command, cStringList *Response); - bool GetServerNames(cStringList *ServerNames); - }; - -cSVDRPClientHandler::cSVDRPClientHandler(int UdpPort, int TcpPort) -:cThread("SVDRP client handler", true) -,udpSocket(UdpPort, false) -{ - tcpPort = TcpPort; -} - -cSVDRPClientHandler::~cSVDRPClientHandler() -{ - Cancel(3); - for (int i = 0; i < clientConnections.Size(); i++) - delete clientConnections[i]; -} - -cSVDRPClient *cSVDRPClientHandler::GetClientForServer(const char *ServerName) -{ - for (int i = 0; i < clientConnections.Size(); i++) { - if (strcmp(clientConnections[i]->ServerName(), ServerName) == 0) - return clientConnections[i]; - } - return NULL; -} - -void cSVDRPClientHandler::SendDiscover(const char *Address) -{ - cMutexLock MutexLock(&mutex); - cString Dgram = cString::sprintf("SVDRP:discover name:%s port:%d vdrversion:%d apiversion:%d timeout:%d", HostName(), tcpPort, VDRVERSNUM, APIVERSNUM, Setup.SVDRPTimeout); - udpSocket.SendDgram(Dgram, udpSocket.Port(), Address); -} - -void cSVDRPClientHandler::ProcessConnections(void) -{ - cMutexLock MutexLock(&mutex); - for (int i = 0; i < clientConnections.Size(); i++) { - if (!clientConnections[i]->Process()) { - delete clientConnections[i]; - clientConnections.Remove(i); - i--; - } - } -} - -void cSVDRPClientHandler::HandleClientConnection(void) -{ - cString NewDiscover = udpSocket.Discover(); - if (*NewDiscover) { - cString p = strgetval(NewDiscover, "port", ':'); - if (*p) { - int Port = atoi(p); - for (int i = 0; i < clientConnections.Size(); i++) { - if (clientConnections[i]->HasAddress(udpSocket.LastIpAddress()->Address(), Port)) { - dsyslog("SVDRP < %s connection to '%s' confirmed", clientConnections[i]->Connection(), clientConnections[i]->ServerName()); - return; - } - } - cString ServerName = strgetval(NewDiscover, "name", ':'); - if (*ServerName) { - cString t = strgetval(NewDiscover, "timeout", ':'); - if (*t) { - int Timeout = atoi(t); - if (Timeout > 10) // don't let it get too small - clientConnections.Append(new cSVDRPClient(udpSocket.LastIpAddress()->Address(), Port, ServerName, Timeout)); - else - esyslog("SVDRP < %s ERROR: invalid timeout (%d)", udpSocket.LastIpAddress()->Connection(), Timeout); - } - else - esyslog("SVDRP < %s ERROR: missing timeout", udpSocket.LastIpAddress()->Connection()); - } - else - esyslog("SVDRP < %s ERROR: missing server name", udpSocket.LastIpAddress()->Connection()); - } - else - esyslog("SVDRP < %s ERROR: missing port number", udpSocket.LastIpAddress()->Connection()); - } -} - -void cSVDRPClientHandler::Action(void) -{ - if (udpSocket.Listen()) { - SVDRPClientPoller.Add(udpSocket.Socket(), false); - SendDiscover(); - while (Running()) { - SVDRPClientPoller.Poll(1000); - cMutexLock MutexLock(&mutex); - HandleClientConnection(); - ProcessConnections(); - } - SVDRPClientPoller.Del(udpSocket.Socket(), false); - udpSocket.Close(); - } -} - -bool cSVDRPClientHandler::Execute(const char *ServerName, const char *Command, cStringList *Response) -{ - cMutexLock MutexLock(&mutex); - if (cSVDRPClient *Client = GetClientForServer(ServerName)) - return Client->Execute(Command, Response); - return false; -} - -bool cSVDRPClientHandler::GetServerNames(cStringList *ServerNames) -{ - cMutexLock MutexLock(&mutex); - ServerNames->Clear(); - for (int i = 0; i < clientConnections.Size(); i++) - ServerNames->Append(strdup(clientConnections[i]->ServerName())); - return ServerNames->Size() > 0; -} - // --- SVDRP Handler --------------------------------------------------------- static cMutex SVDRPHandlerMutex; -static cSVDRPServerHandler *SVDRPServerHandler = NULL; -static cSVDRPClientHandler *SVDRPClientHandler = NULL; void StartSVDRPHandler(int TcpPort, int UdpPort) { @@ -2487,11 +2546,11 @@ void SendSVDRPDiscover(const char *Address) SVDRPClientHandler->SendDiscover(Address); } -bool GetSVDRPServerNames(cStringList *ServerNames) +bool GetSVDRPServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlag) { cMutexLock MutexLock(&SVDRPHandlerMutex); if (SVDRPClientHandler) - return SVDRPClientHandler->GetServerNames(ServerNames); + return SVDRPClientHandler->GetServerNames(ServerNames, FetchFlag); return false; } diff --git a/svdrp.h b/svdrp.h index 86ee0ab5..fd9d5fb5 100644 --- a/svdrp.h +++ b/svdrp.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: svdrp.h 4.2 2015/05/22 13:44:43 kls Exp $ + * $Id: svdrp.h 4.3 2015/09/01 10:34:09 kls Exp $ */ #ifndef __SVDRP_H @@ -40,20 +40,35 @@ public: ///< to the command. The response strings are exactly as received, ///< with the leading three digit reply code and possible continuation ///< line indicator ('-') in place. - const char *Response(int Index) { return (Index > 0 && Index < response.Size()) ? response[Index] : NULL; } + const char *Response(int Index) { return (Index >= 0 && Index < response.Size()) ? response[Index] : NULL; } ///< This is a convenience function for accessing the response strings. ///< Returns the string at the given Index, or NULL if Index is out ///< of range. + int Code(const char *s) { return s ? atoi(s) : 0; } + ///< Returns the value of the three digit reply code of the given + ///< response string. + const char *Value(const char *s) { return s && s[0] && s[1] && s[2] && s[3] ? s + 4 : NULL; } + ///< Returns the actual value of the given response string, skipping + ///< the three digit reply code and possible continuation line indicator. }; +enum eSvdrpFetchFlags { + sffNone = 0b0000, + sffTimers = 0b0001, + }; + +const char *SVDRPHostName(void); void SetSVDRPGrabImageDir(const char *GrabImageDir); void StartSVDRPHandler(int TcpPort, int UdpPort); void StopSVDRPHandler(void); void SendSVDRPDiscover(const char *Address = NULL); -bool GetSVDRPServerNames(cStringList *ServerNames); +bool GetSVDRPServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlag = sffNone); ///< Gets a list of all available VDRs this VDR is connected to via SVDRP, ///< and stores it in the given ServerNames list. The list is cleared ///< before getting the server names. + ///< If FetchFlag is given, only the server names for which the local + ///< client has this flag set will be returned, and the client's flag + ///< will be cleared. ///< Returns true if the resulting list is not empty. #endif //__SVDRP_H diff --git a/thread.c b/thread.c index 4245f32e..993d16dd 100644 --- a/thread.c +++ b/thread.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: thread.c 3.2 2013/12/29 17:21:53 kls Exp $ + * $Id: thread.c 4.1 2015/08/29 14:43:03 kls Exp $ */ #include "thread.h" @@ -21,6 +21,16 @@ #include #include "tools.h" +#define ABORT { dsyslog("ABORT!"); abort(); } // use debugger to trace back the problem + +//#define DEBUG_LOCKING // uncomment this line to activate debug output for locking + +#ifdef DEBUG_LOCKING +#define dbglocking(a...) fprintf(stderr, a) +#else +#define dbglocking(a...) +#endif + static bool GetAbsTime(struct timespec *Abstime, int MillisecondsFromNow) { struct timeval now; @@ -403,6 +413,132 @@ bool cThreadLock::Lock(cThread *Thread) return false; } +// --- cStateLock ------------------------------------------------------------ + +cStateLock::cStateLock(const char *Name) +:rwLock(true) +{ + name = Name; + threadId = 0; + state = 0; + explicitModify = false; +} + +bool cStateLock::Lock(cStateKey &StateKey, bool Write, int TimeoutMs) +{ + dbglocking("%5d %-10s %10p lock state = %d/%d write = %d timeout = %d\n", cThread::ThreadId(), name, &StateKey, state, StateKey.state, Write, TimeoutMs); + StateKey.timedOut = false; + if (StateKey.stateLock) { + esyslog("ERROR: StateKey already in use in call to cStateLock::Lock() (tid=%d, lock=%s)", StateKey.stateLock->threadId, name); + ABORT; + return false; + } + if (rwLock.Lock(Write, TimeoutMs)) { + StateKey.stateLock = this; + if (Write) { + dbglocking("%5d %-10s %10p locked write\n", cThread::ThreadId(), name, &StateKey); + threadId = cThread::ThreadId(); + StateKey.write = true; + return true; + } + else if (state != StateKey.state) { + dbglocking("%5d %-10s %10p locked read\n", cThread::ThreadId(), name, &StateKey); + return true; + } + else { + dbglocking("%5d %-10s %10p state unchanged\n", cThread::ThreadId(), name, &StateKey); + StateKey.stateLock = NULL; + rwLock.Unlock(); + } + } + else if (TimeoutMs) { + dbglocking("%5d %-10s %10p timeout\n", cThread::ThreadId(), name, &StateKey); + StateKey.timedOut = true; + } + return false; +} + +void cStateLock::Unlock(cStateKey &StateKey, bool IncState) +{ + dbglocking("%5d %-10s %10p unlock state = %d/%d inc = %d\n", cThread::ThreadId(), name, &StateKey, state, StateKey.state, IncState); + if (StateKey.stateLock != this) { + esyslog("ERROR: cStateLock::Unlock() called with an unused key (tid=%d, lock=%s)", threadId, name); + ABORT; + return; + } + if (StateKey.write && threadId != cThread::ThreadId()) { + esyslog("ERROR: cStateLock::Unlock() called without holding a lock (tid=%d, lock=%s)", threadId, name); + ABORT; + return; + } + if (StateKey.write && IncState && !explicitModify) + state++; + StateKey.state = state; + StateKey.write = false; + threadId = 0; + explicitModify = false; + rwLock.Unlock(); +} + +void cStateLock::IncState(void) +{ + if (threadId != cThread::ThreadId()) { + esyslog("ERROR: cStateLock::IncState() called without holding a lock (tid=%d, lock=%s)", threadId, name); + ABORT; + } + else + state++; +} + +// --- cStateKey ------------------------------------------------------------- + +cStateKey::cStateKey(bool IgnoreFirst) +{ + stateLock = NULL; + write = false; + state = 0; + if (!IgnoreFirst) + Reset(); +} + +cStateKey::~cStateKey() +{ + if (stateLock) { + esyslog("ERROR: cStateKey::~cStateKey() called without releasing the lock first (tid=%d, lock=%s, key=%p)", stateLock->threadId, stateLock->name, this); + ABORT; + } +} + +void cStateKey::Reset(void) +{ + state = -1; // lock and key are initialized differently, to make the first check return true +} + +void cStateKey::Remove(bool IncState) +{ + if (stateLock) { + stateLock->Unlock(*this, IncState); + stateLock = NULL; + } + else { + esyslog("ERROR: cStateKey::Remove() called without holding a lock (key=%p)", this); + ABORT; + } +} + +bool cStateKey::StateChanged(void) +{ + if (!stateLock) { + esyslog("ERROR: cStateKey::StateChanged() called without holding a lock (tid=%d, key=%p)", cThread::ThreadId(), this); + ABORT; + } + else if (write) + return state != stateLock->state; + else + return true; + return false; +} + // --- cIoThrottle ----------------------------------------------------------- cMutex cIoThrottle::mutex; diff --git a/thread.h b/thread.h index d2d8ee2a..b5a07c79 100644 --- a/thread.h +++ b/thread.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: thread.h 3.2 2015/01/14 11:39:55 kls Exp $ + * $Id: thread.h 4.1 2015/08/17 13:06:24 kls Exp $ */ #ifndef __THREAD_H @@ -164,6 +164,94 @@ public: #define LOCK_THREAD cThreadLock ThreadLock(this) +class cStateKey; + +class cStateLock { + friend class cStateKey; +private: + const char *name; + tThreadId threadId; + cRwLock rwLock; + int state; + bool explicitModify; + void Unlock(cStateKey &StateKey, bool IncState = true); + ///< Releases a lock that has been obtained by a previous call to Lock() + ///< with the given StateKey. If this was a write-lock, and IncState is true, + ///< the state of the lock will be incremented. In any case, the (new) state + ///< of the lock will be copied to the StateKey's state. +public: + cStateLock(const char *Name = NULL); + bool Lock(cStateKey &StateKey, bool Write = false, int TimeoutMs = 0); + ///< Tries to get a lock and returns true if successful. + ///< If TimoutMs is not 0, it waits for the given number of milliseconds + ///< and returns false if no lock has been obtained within that time. + ///< Otherwise it waits indefinitely for the lock. The given StateKey + ///< will store which lock it has been used with, and will use that + ///< information when its Remove() function is called. + ///< There are two possible locks, one only for read access, and one + ///< for reading and writing: + ///< + ///< If Write is false (i.e. a read-lock is requested), the lock's state + ///< is compared to the given StateKey's state, and true is returned if + ///< they differ. + ///< If true is returned, the read-lock is still in place and the + ///< protected data structures can be safely accessed (in read-only mode!). + ///< Once the necessary operations have been performed, the lock must + ///< be released by a call to the StateKey's Remove() function. + ///< If false is returned, the state has not changed since the last check, + ///< and the read-lock has been released. In that case the protected + ///< data structures have not changed since the last call, so no action + ///< is required. Note that if TimeoutMs is used with read-locks, Lock() + ///< might return false even if the states of lock and key differ, just + ///< because it was unable to obtain the lock within the given time. + ///< You can call cStateKey::TimedOut() to detect this. + ///< + ///< If Write is true (i.e. a write-lock is requested), the states of the + ///< lock and the given StateKey don't matter, it will always try to obtain + ///< a write lock. + void SetExplicitModify(void) { explicitModify = true; } + ///< If you have obtained a write lock on this lock, and you don't want its + ///< state to be automatically incremented when the lock is released, a call to + ///< this function will disable this, and you can explicitly call IncState() + ///< to increment the state. + void IncState(void); + ///< Increments the state of this lock. + }; + +class cStateKey { + friend class cStateLock; +private: + cStateLock *stateLock; + bool write; + int state; + bool timedOut; +public: + cStateKey(bool IgnoreFirst = false); + ///< Sets up a new state key. If IgnoreFirst is true, the first use + ///< of this key with a lock will not return true if the lock's state + ///< hasn't explicitly changed. + ~cStateKey(); + void Reset(void); + ///< Resets the state of this key, so that the next call to a lock's + ///< Lock() function with this key will return true, even if the + ///< lock's state hasn't changed. + void Remove(bool IncState = true); + ///< Removes this key from the lock it was previously used with. + ///< If this key was used to obtain a write lock, the state of the lock will + ///< be incremented and copied to this key. You can set IncState to false + ///< to prevent this. + bool StateChanged(void); + ///< Returns true if this key is used for obtaining a write lock, and the + ///< lock's state differs from that of the key. When used with a read lock, + ///< it always returns true, because otherwise the lock wouldn't have been + ///< obtained in the first place. + bool InLock(void) { return stateLock; } + ///< Returns true if this key is currently in a lock. + bool TimedOut(void) const { return timedOut; } + ///< Returns true if the last lock attempt this key was used with failed due + ///< to a timeout. + }; + class cIoThrottle { private: static cMutex mutex; diff --git a/timers.c b/timers.c index 3eb80689..e124a1d5 100644 --- a/timers.c +++ b/timers.c @@ -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 -#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("Timers") { - state = 0; - beingEdited = 0;; - lastSetEvents = 0; lastDeleteExpired = 0; } +bool cTimers::Load(const char *FileName) +{ + LOCK_TIMERS_WRITE; + Timers->SetExplicitModify(); + if (timers.cConfig::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::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(Timers.Count()) +cSortedTimers::cSortedTimers(const cTimers *Timers) +:cVector(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); } diff --git a/timers.h b/timers.h index 8910c106..88a06624 100644 --- a/timers.h +++ b/timers.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: timers.h 2.7 2013/03/11 10:35:53 kls Exp $ + * $Id: timers.h 4.1 2015/08/31 10:42:53 kls Exp $ */ #ifndef __TIMERS_H @@ -28,11 +28,11 @@ class cTimer : public cListObject { friend class cMenuEditTimer; private: mutable time_t startTime, stopTime; - time_t lastSetEvent; + int scheduleState; mutable time_t deferred; ///< Matches(time_t, ...) will return false if the current time is before this value - bool recording, pending, inVpsMargin; + bool pending, inVpsMargin; uint flags; - cChannel *channel; + const cChannel *channel; mutable time_t day; ///< midnight of the day this timer shall hit, or of the first day it shall hit in case of a repeating timer int weekdays; ///< bitmask, lowest bits: SSFTWTM (the 'M' is the LSB) int start; @@ -41,15 +41,16 @@ private: int lifetime; mutable char file[NAME_MAX * 2 + 1]; // *2 to be able to hold 'title' and 'episode', which can each be up to 255 characters long char *aux; + char *remote; const cEvent *event; public: - cTimer(bool Instant = false, bool Pause = false, cChannel *Channel = NULL); + cTimer(bool Instant = false, bool Pause = false, const cChannel *Channel = NULL); cTimer(const cEvent *Event); cTimer(const cTimer &Timer); virtual ~cTimer(); cTimer& operator= (const cTimer &Timer); virtual int Compare(const cListObject &ListObject) const; - bool Recording(void) const { return recording; } + bool Recording(void) const { return HasFlags(tfRecording); } bool Pending(void) const { return pending; } bool InVpsMargin(void) const { return inVpsMargin; } uint Flags(void) const { return flags; } @@ -63,6 +64,7 @@ public: const char *File(void) const { return file; } time_t FirstDay(void) const { return weekdays ? day : 0; } const char *Aux(void) const { return aux; } + const char *Remote(void) const { return remote; } time_t Deferred(void) const { return deferred; } cString ToText(bool UseChannelID = false) const; cString ToDescr(void) const; @@ -81,8 +83,8 @@ public: bool Expired(void) const; time_t StartTime(void) const; time_t StopTime(void) const; - void SetEventFromSchedule(const cSchedules *Schedules = NULL); - void SetEvent(const cEvent *Event); + bool SetEventFromSchedule(const cSchedules *Schedules); + bool SetEvent(const cEvent *Event); void SetRecording(bool Recording); void SetPending(bool Pending); void SetInVpsMargin(bool InVpsMargin); @@ -93,6 +95,7 @@ public: void SetPriority(int Priority); void SetLifetime(int Lifetime); void SetAux(const char *Aux); + void SetRemote(const char *Remote); void SetDeferred(int Seconds); void SetFlags(uint Flags); void ClrFlags(uint Flags); @@ -108,36 +111,100 @@ public: class cTimers : public cConfig { private: - int state; - int beingEdited; - time_t lastSetEvents; + static cTimers timers; time_t lastDeleteExpired; public: cTimers(void); + static const cTimers *GetTimersRead(cStateKey &StateKey, int TimeoutMs = 0); + ///< Gets the list of timers for read access. If TimeoutMs is given, + ///< it will wait that long to get a write lock before giving up. + ///< Otherwise it will wait indefinitely. If no read lock can be + ///< obtained within the given timeout, NULL will be returned. + ///< The list is locked and a pointer to it is returned if the state + ///< of the list is different than the state of the given StateKey. + ///< If both states are equal, the list of timers has not been modified + ///< since the last call with the same StateKey, and NULL will be + ///< returned (and the list is not locked). After the returned list of + ///< timers is no longer needed, the StateKey's Remove() function must + ///< be called to release the list. The time between calling + ///< cTimers::GetTimersRead() and StateKey.Remove() should be as short + ///< as possible. After calling StateKey.Remove() the list returned from + ///< this call must not be accessed any more. If you need to access the + ///< timers again later, a new call to GetTimersRead() must be made. + ///< A typical code sequence would look like this: + ///< cStateKey StateKey; + ///< if (const cTimers *Timers = cTimers::GetTimersRead(StateKey)) { + ///< // access the timers + ///< StateKey.Remove(); + ///< } + static cTimers *GetTimersWrite(cStateKey &StateKey, int TimeoutMs = 0); + ///< Gets the list of timers for write access. If TimeoutMs is given, + ///< it will wait that long to get a write lock before giving up. + ///< Otherwise it will wait indefinitely. If no write lock can be + ///< obtained within the given timeout, NULL will be returned. + ///< If a write lock can be obtained, the list of timers will be + ///< returned, regardless of the state values of the timers or the + ///< given StateKey. After the returned list of timers is no longer + ///< needed, the StateKey's Remove() function must be called to release + ///< the list. The time between calling cTimers::GetTimersWrite() and + ///< StateKey.Remove() should be as short as possible. After calling + ///< StateKey.Remove() the list returned from this call must not be + ///< accessed any more. If you need to access the timers again later, + ///< a new call to GetTimersWrite() must be made. The call + ///< to StateKey.Remove() will increment the state of the list of + ///< timers and will copy the new state value to the StateKey. You can + ///< suppress this by using 'false' as the parameter to the call, in + ///< which case the state values are left untouched. + ///< A typical code sequence would look like this: + ///< cStateKey StateKey; + ///< if (cTimers *Timers = cTimers::GetTimersWrite(StateKey)) { + ///< // access the timers + ///< StateKey.Remove(); + ///< } + static bool Load(const char *FileName); cTimer *GetTimer(cTimer *Timer); - cTimer *GetMatch(time_t t); - cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL); - cTimer *GetNextActiveTimer(void); - int BeingEdited(void) { return beingEdited; } - void IncBeingEdited(void) { beingEdited++; } - void DecBeingEdited(void) { if (!--beingEdited) lastSetEvents = 0; } - void SetModified(void); - bool Modified(int &State); - ///< Returns true if any of the timers have been modified, which - ///< is detected by State being different than the internal state. - ///< Upon return the internal state will be stored in State. - void SetEvents(void); - void DeleteExpired(void); + const cTimer *GetMatch(time_t t) const; + cTimer *GetMatch(time_t t) { return const_cast(static_cast(this)->GetMatch(t)); }; + const cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL) const; + cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL) { return const_cast(static_cast(this)->GetMatch(Event, Match)); } + const cTimer *GetNextActiveTimer(void) const; + const cTimer *UsesChannel(const cChannel *Channel) const; + bool SetEvents(const cSchedules *Schedules); + bool DeleteExpired(void); void Add(cTimer *Timer, cTimer *After = NULL); void Ins(cTimer *Timer, cTimer *Before = NULL); void Del(cTimer *Timer, bool DeleteObject = true); + bool GetRemoteTimers(const char *ServerName = NULL); + ///< Gets the timers from the given remote machine and adds them to this + ///< list. If no ServerName is given, all timers from all known remote + ///< machines will be fetched. This function calls DelRemoteTimers() with + ///< the given ServerName first. + ///< Returns true if any remote timers have been added or deleted + bool DelRemoteTimers(const char *ServerName = NULL); + ///< Deletes all timers of the given remote machine from this list (leaves + ///< them untouched on the remote machine). If no ServerName is given, the + ///< timers of all remote machines will be deleted from the list. + ///< Returns true if any remote timers have been deleted. + void TriggerRemoteTimerPoll(const char *ServerName = NULL); + ///< Sends an SVDRP POLL command to the given remote machine. + ///< If no ServerName is given, the POLL command will be sent to all + ///< known remote machines. }; -extern cTimers Timers; +// Provide lock controlled access to the list: + +DEF_LIST_LOCK(Timers); + +// These macros provide a convenient way of locking the global timers list +// and making sure the lock is released as soon as the current scope is left +// (note that these macros wait forever to obtain the lock!): + +#define LOCK_TIMERS_READ USE_LIST_LOCK_READ(Timers) +#define LOCK_TIMERS_WRITE USE_LIST_LOCK_WRITE(Timers) class cSortedTimers : public cVector { public: - cSortedTimers(void); + cSortedTimers(const cTimers *Timers); }; #endif //__TIMERS_H diff --git a/tools.c b/tools.c index d8e4ac7d..005e4027 100644 --- a/tools.c +++ b/tools.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.c 4.1 2015/05/11 14:15:15 kls Exp $ + * $Id: tools.c 4.2 2015/08/29 12:11:20 kls Exp $ */ #include "tools.h" @@ -2044,12 +2044,58 @@ int cListObject::Index(void) const return i; } +// --- cListGarbageCollector ------------------------------------------------- + +#define LIST_GARBAGE_COLLECTOR_TIMEOUT 5 // seconds + +cListGarbageCollector ListGarbageCollector; + +cListGarbageCollector::cListGarbageCollector(void) +{ + objects = NULL; + lastPut = 0; +} + +cListGarbageCollector::~cListGarbageCollector() +{ + if (objects) + esyslog("ERROR: ListGarbageCollector destroyed without prior Purge()!"); +} + +void cListGarbageCollector::Put(cListObject *Object) +{ + mutex.Lock(); + Object->next = objects; + objects = Object; + lastPut = time(NULL); + mutex.Unlock(); +} + +void cListGarbageCollector::Purge(bool Force) +{ + mutex.Lock(); + if (objects && (time(NULL) - lastPut > LIST_GARBAGE_COLLECTOR_TIMEOUT || Force)) { + // We make sure that any object stays in the garbage collector for at least + // LIST_GARBAGE_COLLECTOR_TIMEOUT seconds, to give objects that have pointers + // to them a chance to drop these references before the object is finally + // deleted. + while (cListObject *Object = objects) { + objects = Object->next; + delete Object; + } + } + mutex.Unlock(); +} + // --- cListBase ------------------------------------------------------------- -cListBase::cListBase(void) +cListBase::cListBase(const char *NeedsLocking) +:stateLock(NeedsLocking) { objects = lastObject = NULL; count = 0; + needsLocking = NeedsLocking; + useGarbageCollector = needsLocking; } cListBase::~cListBase() @@ -2057,6 +2103,15 @@ cListBase::~cListBase() Clear(); } +bool cListBase::Lock(cStateKey &StateKey, bool Write, int TimeoutMs) const +{ + if (needsLocking) + return stateLock.Lock(StateKey, Write, TimeoutMs); + else + esyslog("ERROR: cListBase::Lock() called for a list that doesn't require locking"); + return false; +} + void cListBase::Add(cListObject *Object, cListObject *After) { if (After && After != lastObject) { @@ -2096,8 +2151,12 @@ void cListBase::Del(cListObject *Object, bool DeleteObject) if (Object == lastObject) lastObject = Object->Prev(); Object->Unlink(); - if (DeleteObject) - delete Object; + if (DeleteObject) { + if (useGarbageCollector) + ListGarbageCollector.Put(Object); + else + delete Object; + } count--; } @@ -2141,11 +2200,30 @@ void cListBase::Clear(void) count = 0; } -cListObject *cListBase::Get(int Index) const +bool cListBase::Contains(const cListObject *Object) const +{ + for (const cListObject *o = objects; o; o = o->Next()) { + if (o == Object) + return true; + } + return false; +} + +void cListBase::SetExplicitModify(void) +{ + stateLock.SetExplicitModify(); +} + +void cListBase::SetModified(void) +{ + stateLock.IncState(); +} + +const cListObject *cListBase::Get(int Index) const { if (Index < 0) return NULL; - cListObject *object = objects; + const cListObject *object = objects; while (object && Index-- > 0) object = object->Next(); return object; diff --git a/tools.h b/tools.h index 8f56620c..a4de535b 100644 --- a/tools.h +++ b/tools.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.h 4.1 2015/05/21 14:37:00 kls Exp $ + * $Id: tools.h 4.2 2015/08/29 11:45:51 kls Exp $ */ #ifndef __TOOLS_H @@ -26,6 +26,7 @@ #include #include #include +#include "thread.h" typedef unsigned char uchar; @@ -462,6 +463,7 @@ public: }; class cListObject { + friend class cListGarbageCollector; private: cListObject *prev, *next; public: @@ -478,33 +480,152 @@ public: cListObject *Next(void) const { return next; } }; +class cListGarbageCollector { +private: + cMutex mutex; + cListObject *objects; + time_t lastPut; +public: + cListGarbageCollector(void); + ~cListGarbageCollector(); + void Put(cListObject *Object); + void Purge(bool Force = false); + }; + +extern cListGarbageCollector ListGarbageCollector; + class cListBase { protected: cListObject *objects, *lastObject; - cListBase(void); int count; + mutable cStateLock stateLock; + const char *needsLocking; + bool useGarbageCollector; + cListBase(const char *NeedsLocking = NULL); public: virtual ~cListBase(); + bool Lock(cStateKey &StateKey, bool Write = false, int TimeoutMs = 0) const; + ///< Tries to get a lock on this list and returns true if successful. + ///< By default a read lock is requested. Set Write to true to obtain + ///< a write lock. If TimeoutMs is not zero, it waits for the given + ///< number of milliseconds before giving up. + ///< If you need to lock more than one list at the same time, make sure + ///< you set TimeoutMs to a suitable value in all of the calls to + ///< Lock(), and be prepared to handle situations where you do not get all + ///< of the requested locks. In such cases you should release all the locks + ///< you have obtained so far and try again. StateKey.TimedOut() tells you + ///< whether the lock attempt failed due to a timeout or because the state + ///< of the lock hasn't changed since the previous locking attempt. + ///< To implicitly avoid deadlocks when locking more than one of the global + ///< lists of VDR at the same time, make sure you always lock Timers, Channels, + ///< Recordings and Schedules in this sequence. + ///< You may keep pointers to objects in this list, even after releasing + ///< the lock. However, you may only access such objects if you are + ///< holding a proper lock again. If an object has been deleted from the list + ///< while you did not hold a lock (for instance by an other thread), the + ///< object will still be there, but no longer within this list (it is then + ///< stored in the ListGarbageCollector). That way even if you access the object + ///< after it has been deleted, you won't cause a segfault. You can call the + ///< Contains() function to check whether an object you are holding a pointer + ///< to is still in the list. Note that the garbage collector is purged when + ///< the usual housekeeping is done. + void SetUseGarbageCollector(void) { useGarbageCollector = true; } + void SetExplicitModify(void); + ///< If you have obtained a write lock on this list, and you don't want it to + ///< be automatically marked as modified when the lock is released, a call to + ///< this function will disable this, and you can explicitly call SetModified() + ///< to have the list marked as modified. + void SetModified(void); + ///< Unconditionally marks this list as modified. void Add(cListObject *Object, cListObject *After = NULL); void Ins(cListObject *Object, cListObject *Before = NULL); void Del(cListObject *Object, bool DeleteObject = true); virtual void Move(int From, int To); void Move(cListObject *From, cListObject *To); virtual void Clear(void); - cListObject *Get(int Index) const; + bool Contains(const cListObject *Object) const; + ///< If a pointer to an object contained in this list has been obtained while + ///< holding a lock, and that lock has been released, but the pointer is kept for + ///< later use (after obtaining a new lock), Contains() can be called with that + ///< pointer to make sure the object it points to is still part of this list + ///< (it may have been deleted or otherwise removed from the list after the lock + ///< during which the pointer was initially retrieved has been released). + const cListObject *Get(int Index) const; + cListObject *Get(int Index) { return const_cast(static_cast(this)->Get(Index)); } int Count(void) const { return count; } void Sort(void); }; template class cList : public cListBase { public: - T *Get(int Index) const { return (T *)cListBase::Get(Index); } - T *First(void) const { return (T *)objects; } - T *Last(void) const { return (T *)lastObject; } - T *Prev(const T *object) const { return (T *)object->cListObject::Prev(); } // need to call cListObject's members to - T *Next(const T *object) const { return (T *)object->cListObject::Next(); } // avoid ambiguities in case of a "list of lists" + cList(const char *NeedsLocking = NULL): cListBase(NeedsLocking) {} + ///< Sets up a new cList of the given type T. If NeedsLocking is given, the list + ///< and any of its elements may only be accessed if the caller holds a lock + ///< obtained by a call to Lock() (see cListBase::Lock() for details). + ///< NeedsLocking is used as both a boolean flag to enable locking, and as + ///< a name to identify this list in debug output. It must be a static string + ///< and should be no longer than 10 characters. The string will not be copied! + const T *Get(int Index) const { return (T *)cListBase::Get(Index); } + ///< Returns the list element at the given Index, or NULL if no such element + ///< exists. + const T *First(void) const { return (T *)objects; } + ///< Returns the first element in this list, or NULL if the list is empty. + const T *Last(void) const { return (T *)lastObject; } + ///< Returns the last element in this list, or NULL if the list is empty. + const T *Prev(const T *Object) const { return (T *)Object->cListObject::Prev(); } // need to call cListObject's members to + ///< Returns the element immediately before Object in this list, or NULL + ///< if Object is the first element in the list. Object must not be NULL! + const T *Next(const T *Object) const { return (T *)Object->cListObject::Next(); } // avoid ambiguities in case of a "list of lists" + ///< Returns the element immediately following Object in this list, or NULL + ///< if Object is the last element in the list. Object must not be NULL! + T *Get(int Index) { return const_cast(static_cast *>(this)->Get(Index)); } + ///< Non-const version of Get(). + T *First(void) { return const_cast(static_cast *>(this)->First()); } + ///< Non-const version of First(). + T *Last(void) { return const_cast(static_cast *>(this)->Last()); } + ///< Non-const version of Last(). + T *Prev(const T *Object) { return const_cast(static_cast *>(this)->Prev(Object)); } + ///< Non-const version of Prev(). + T *Next(const T *Object) { return const_cast(static_cast *>(this)->Next(Object)); } + ///< Non-const version of Next(). }; +// The DEF_LIST_LOCK macro defines a convenience class that can be used to obtain +// a lock on a cList and make sure the lock is released when the current scope +// is left: + +#define DEF_LIST_LOCK2(Class, Name) \ +class c##Name##Lock { \ +private: \ + cStateKey stateKey; \ + const c##Class *list; \ +public: \ + c##Name##Lock(bool Write = false) \ + { \ + if (Write) \ + list = c##Class::Get##Name##Write(stateKey); \ + else \ + list = c##Class::Get##Name##Read(stateKey); \ + } \ + ~c##Name##Lock() { stateKey.Remove(); } \ + const c##Class *Name(void) const { return list; } \ + c##Class *Name(void) { return const_cast(list); } \ + } +#define DEF_LIST_LOCK(Class) DEF_LIST_LOCK2(Class, Class) + +// The USE_LIST_LOCK macro sets up a local variable of a class defined by +// a suitable DEF_LIST_LOCK, and also a pointer to the provided list: + +#define USE_LIST_LOCK_READ2(Class, Name) \ +c##Name##Lock Name##Lock(false); \ +const c##Class *Name __attribute__((unused)) = Name##Lock.Name(); +#define USE_LIST_LOCK_READ(Class) USE_LIST_LOCK_READ2(Class, Class) + +#define USE_LIST_LOCK_WRITE2(Class, Name) \ +c##Name##Lock Name##Lock(true); \ +c##Class *Name __attribute__((unused)) = Name##Lock.Name(); +#define USE_LIST_LOCK_WRITE(Class) USE_LIST_LOCK_WRITE2(Class, Class) + template class cVector { ///< cVector may only be used for *simple* types, like int or pointers - not for class objects that allocate additional memory! private: 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 @@ -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); diff --git a/videodir.c b/videodir.c index 932f8ce3..b2257fa6 100644 --- a/videodir.c +++ b/videodir.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: videodir.c 3.4 2013/10/11 09:38:07 kls Exp $ + * $Id: videodir.c 4.1 2015/08/11 13:39:59 kls Exp $ */ #include "videodir.h" @@ -19,24 +19,31 @@ #include "recording.h" #include "tools.h" +cMutex cVideoDirectory::mutex; cString cVideoDirectory::name; cVideoDirectory *cVideoDirectory::current = NULL; cVideoDirectory::cVideoDirectory(void) { + mutex.Lock(); delete current; current = this; + mutex.Unlock(); } cVideoDirectory::~cVideoDirectory() { + mutex.Lock(); current = NULL; + mutex.Unlock(); } cVideoDirectory *cVideoDirectory::Current(void) { + mutex.Lock(); if (!current) - current = new cVideoDirectory; + new cVideoDirectory; + mutex.Unlock(); return current; } @@ -141,7 +148,8 @@ int cVideoDirectory::VideoDiskSpace(int *FreeMB, int *UsedMB) { int used = 0; int free = Current()->FreeMB(&used); - int deleted = DeletedRecordings.TotalFileSizeMB(); + LOCK_DELETEDRECORDINGS_READ; + int deleted = DeletedRecordings->TotalFileSizeMB(); if (deleted > used) deleted = used; // let's not get beyond 100% free += deleted; @@ -202,7 +210,8 @@ bool cVideoDiskUsage::HasChanged(int &State) if (FreeMB != freeMB) { usedPercent = UsedPercent; freeMB = FreeMB; - double MBperMinute = Recordings.MBperMinute(); + LOCK_RECORDINGS_READ; + double MBperMinute = Recordings->MBperMinute(); if (MBperMinute <= 0) MBperMinute = MB_PER_MINUTE; freeMinutes = int(double(FreeMB) / MBperMinute); diff --git a/videodir.h b/videodir.h index f520d77b..385c822e 100644 --- a/videodir.h +++ b/videodir.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: videodir.h 3.2 2013/10/11 09:37:48 kls Exp $ + * $Id: videodir.h 4.1 2015/08/10 13:21:29 kls Exp $ */ #ifndef __VIDEODIR_H @@ -15,6 +15,7 @@ class cVideoDirectory { private: + static cMutex mutex; static cString name; static cVideoDirectory *current; static cVideoDirectory *Current(void);