summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--HISTORY113
-rw-r--r--channels.c335
-rw-r--r--channels.h115
-rw-r--r--ci.c5
-rw-r--r--config.h6
-rw-r--r--cutter.c9
-rw-r--r--device.c24
-rw-r--r--dvbplayer.c27
-rw-r--r--dvbplayer.h4
-rw-r--r--eit.c119
-rw-r--r--eit.h10
-rw-r--r--eitscan.c18
-rw-r--r--epg.c268
-rw-r--r--epg.h72
-rw-r--r--filter.c41
-rw-r--r--filter.h13
-rw-r--r--menu.c1288
-rw-r--r--menu.h21
-rw-r--r--menuitems.c46
-rw-r--r--nit.c43
-rw-r--r--pat.c23
-rw-r--r--recorder.c5
-rw-r--r--recording.c457
-rw-r--r--recording.h105
-rw-r--r--sdt.c52
-rw-r--r--shutdown.c29
-rw-r--r--skinlcars.c67
-rw-r--r--sourceparams.c4
-rw-r--r--sourceparams.h4
-rw-r--r--status.h9
-rw-r--r--svdrp.c1335
-rw-r--r--svdrp.h21
-rw-r--r--thread.c138
-rw-r--r--thread.h90
-rw-r--r--timers.c326
-rw-r--r--timers.h119
-rw-r--r--tools.c90
-rw-r--r--tools.h137
-rw-r--r--vdr.c298
-rw-r--r--videodir.c17
-rw-r--r--videodir.h3
41 files changed, 3508 insertions, 2398 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 <ctype.h>
#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<cChannel>("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<cChannelSorter> 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<cChannel>::Load(FileName, AllowComments, MustExist)) {
- DeleteDuplicateChannels();
- ReNumber();
+ LOCK_CHANNELS_WRITE;
+ if (channels.cConfig<cChannel>::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<cChannel>::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<cHashObject> *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<cHashObject> *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<cLinkChannel> {
};
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<cChannel> {
+class cChannels : public cConfig<cChannel> {
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<cChannel> 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<cChannel *>(static_cast<const cChannels *>(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<cChannel *>(static_cast<const cChannels *>(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<cChannel *>(static_cast<const cChannels *>(this)->GetByChannelID(ChannelID, TryWithoutRid, TryWithoutPolarization)); }
+ const cChannel *GetByTransponderID(tChannelID ChannelID) const;
+ cChannel *GetByTransponderID(tChannelID ChannelID) { return const_cast<cChannel *>(static_cast<const cChannels *>(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<T>::Clear();
}
public:
- cConfig(void) { fileName = NULL; }
+ cConfig(const char *NeedsLocking = NULL): cList<T>(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 <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
* Adapted to 'libsi' for VDR 1.3.0 by Marcel Wiesweg <marcel.wiesweg@gmx.de>.
*
- * $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<cSectionSyncerEntry> {};
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<cScanData> {
public:
- void AddTransponders(cList<cChannel> *Channels);
+ void AddTransponders(const cList<cChannel> *Channels);
void AddTransponder(const cChannel *Channel);
};
-void cScanList::AddTransponders(cList<cChannel> *Channels)
+void cScanList::AddTransponders(const cList<cChannel> *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 <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
*
- * $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 <limits.h>
#include <time.h>
#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 --------------------------------------------------------
+// --- cSchedules ------------------------------------------------------------
-cSchedulesLock::cSchedulesLock(bool WriteLock, int TimeoutMs)
+cSchedules cSchedules::schedules;
+char *cSchedules::epgDataFileName = NULL;
+time_t cSchedules::lastDump = time(NULL);
+
+cSchedules::cSchedules(void)
+:cList<cSchedule>("Schedules")
{
- locked = cSchedules::schedules.rwlock.Lock(WriteLock, TimeoutMs);
}
-cSchedulesLock::~cSchedulesLock()
+const cSchedules *cSchedules::GetSchedulesRead(cStateKey &StateKey, int TimeoutMs)
{
- if (locked)
- cSchedules::schedules.rwlock.Unlock();
+ return schedules.Lock(StateKey, false, TimeoutMs) ? &schedules : NULL;
}
-// --- 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::GetSchedulesWrite(cStateKey &StateKey, int TimeoutMs)
{
- return SchedulesLock.Locked() ? &schedules : NULL;
+ 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 <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
*
- * $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<cEvent> events;
cHash<cEvent> eventsHashID;
cHash<cEvent> 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<cSchedule> {
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<cEpgHandler> {
@@ -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<cMenuEditChannel *>(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<cNestedItem> *List, char *Path)
+static void AddRecordingFolders(const cRecordings *Recordings, cList<cNestedItem> *List, char *Path)
{
if (Path) {
char *p = strchr(Path, FOLDERDELIMCHARSUBST);
@@ -782,13 +815,12 @@ static void AddRecordingFolders(cList<cNestedItem> *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<cNestedItem> *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<cMenuRecordingItem *>(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<cMenuRecordingItem *>(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;
-
-char *cRecordings::updateFileName = NULL;
+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();
+ };
-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();
-}
-
-const char *cRecordings::UpdateFileName(void)
-{
- if (!updateFileName)
- updateFileName = strdup(AddDirectory(cVideoDirectory::Name(), ".update"));
- return updateFileName;
+ 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());
}
-void cRecordings::Refresh(bool Foreground)
+void cVideoDirectoryScannerThread::ScanVideoDir(const char *DirName, int LinkLevel, int DirLevel)
{
- 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<cRecording>(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<cMark>::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<cMark>::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<cRecording>, public cThread {
+class cVideoDirectoryScannerThread;
+
+class cRecordings : public cList<cRecording> {
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<cRecording *>(static_cast<const cRecordings *>(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<cMark>, public cMutex {
+class cMarks : public cConfig<cMark> {
private:
cString recordingFileName;
cString fileName;
@@ -360,9 +374,11 @@ private:
time_t lastFileTime;
time_t lastChange;
public:
+ cMarks(void): cConfig<cMark>("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<cMark *>(static_cast<const cMarks *>(this)->Get(Position)); }
+ cMark *GetPrev(int Position) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetPrev(Position)); }
+ cMark *GetNext(int Position) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetNext(Position)); }
+ cMark *GetNextBegin(const cMark *EndMark = NULL) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetNextBegin(EndMark)); }
+ cMark *GetNextEnd(const cMark *BeginMark) { return const_cast<cMark *>(static_cast<const cMarks *>(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 <udo_richter@gmx.de>.
*
- * $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<int> 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<cSourceParam> {
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<cSVDRPClient *> 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 <message>\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 <settings>\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;
- }
- }
+ 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))
+ AtTime = strtol(p, NULL, 10);
else {
- Reply(501, "Missing time");
+ Reply(501, "Invalid time");
return;
}
}
- else if (!Schedule) {
- 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);
+ else {
+ Reply(501, "Missing time");
+ return;
+ }
+ }
+ 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(501, "Unknown option: \"%s\"", p);
+ Reply(550, "Channel \"%s\" not defined", 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(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);
+ 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 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);
+ else {
+ cVector<const cTimer *> 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;
+ }
}
- else
- Reply(550, "No timers defined");
+ 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<cSVDRPClient *> 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 <unistd.h>
#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 <ctype.h>
-#include "channels.h"
#include "device.h"
#include "i18n.h"
#include "libsi/si.h"
#include "recording.h"
#include "remote.h"
#include "status.h"
+#include "svdrp.h"
// IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d'
// format characters in order to allow any number of blanks after a numeric
@@ -23,19 +23,21 @@
// --- cTimer ----------------------------------------------------------------
-cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
+cTimer::cTimer(bool Instant, bool Pause, const cChannel *Channel)
{
startTime = stopTime = 0;
- lastSetEvent = 0;
+ scheduleState = -1;
deferred = 0;
- recording = pending = inVpsMargin = false;
+ pending = inVpsMargin = false;
flags = tfNone;
*file = 0;
aux = NULL;
+ remote = NULL;
event = NULL;
if (Instant)
SetFlags(tfActive | tfInstant);
- channel = Channel ? Channel : Channels.GetByNumber(cDevice::CurrentChannel());
+ LOCK_CHANNELS_READ;
+ channel = Channel ? Channel : Channels->GetByNumber(cDevice::CurrentChannel());
time_t t = time(NULL);
struct tm tm_r;
struct tm *now = localtime_r(&t, &tm_r);
@@ -44,27 +46,25 @@ cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
start = now->tm_hour * 100 + now->tm_min;
stop = 0;
if (!Setup.InstantRecordTime && channel && (Instant || Pause)) {
- cSchedulesLock SchedulesLock;
- if (const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock)) {
- if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) {
- if (const cEvent *Event = Schedule->GetPresentEvent()) {
- time_t tstart = Event->StartTime();
- time_t tstop = Event->EndTime();
- if (Event->Vps() && Setup.UseVps) {
- SetFlags(tfVps);
- tstart = Event->Vps();
- }
- else {
- tstop += Setup.MarginStop * 60;
- tstart -= Setup.MarginStart * 60;
- }
- day = SetTime(tstart, 0);
- struct tm *time = localtime_r(&tstart, &tm_r);
- start = time->tm_hour * 100 + time->tm_min;
- time = localtime_r(&tstop, &tm_r);
- stop = time->tm_hour * 100 + time->tm_min;
- SetEvent(Event);
+ LOCK_SCHEDULES_READ;
+ if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) {
+ if (const cEvent *Event = Schedule->GetPresentEvent()) {
+ time_t tstart = Event->StartTime();
+ time_t tstop = Event->EndTime();
+ if (Event->Vps() && Setup.UseVps) {
+ SetFlags(tfVps);
+ tstart = Event->Vps();
}
+ else {
+ tstop += Setup.MarginStop * 60;
+ tstart -= Setup.MarginStart * 60;
+ }
+ day = SetTime(tstart, 0);
+ struct tm *time = localtime_r(&tstart, &tm_r);
+ start = time->tm_hour * 100 + time->tm_min;
+ time = localtime_r(&tstop, &tm_r);
+ stop = time->tm_hour * 100 + time->tm_min;
+ SetEvent(Event);
}
}
}
@@ -83,16 +83,18 @@ cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
cTimer::cTimer(const cEvent *Event)
{
startTime = stopTime = 0;
- lastSetEvent = 0;
+ scheduleState = -1;
deferred = 0;
- recording = pending = inVpsMargin = false;
+ pending = inVpsMargin = false;
flags = tfActive;
*file = 0;
aux = NULL;
+ remote = NULL;
event = NULL;
if (Event->Vps() && Setup.UseVps)
SetFlags(tfVps);
- channel = Channels.GetByChannelID(Event->ChannelID(), true);
+ LOCK_CHANNELS_READ;
+ channel = Channels->GetByChannelID(Event->ChannelID(), true);
time_t tstart = (flags & tfVps) ? Event->Vps() : Event->StartTime();
time_t tstop = tstart + Event->Duration();
if (!(HasFlags(tfVps))) {
@@ -120,6 +122,7 @@ cTimer::cTimer(const cTimer &Timer)
{
channel = NULL;
aux = NULL;
+ remote = NULL;
event = NULL;
flags = tfNone;
*this = Timer;
@@ -127,7 +130,10 @@ cTimer::cTimer(const cTimer &Timer)
cTimer::~cTimer()
{
+ if (event)
+ event->DecNumTimers();
free(aux);
+ free(remote);
}
cTimer& cTimer::operator= (const cTimer &Timer)
@@ -136,9 +142,8 @@ cTimer& cTimer::operator= (const cTimer &Timer)
uint OldFlags = flags & tfRecording;
startTime = Timer.startTime;
stopTime = Timer.stopTime;
- lastSetEvent = 0;
- deferred = 0;
- recording = Timer.recording;
+ scheduleState = -1;
+ deferred = 0;
pending = Timer.pending;
inVpsMargin = Timer.inVpsMargin;
flags = Timer.flags | OldFlags;
@@ -152,7 +157,13 @@ cTimer& cTimer::operator= (const cTimer &Timer)
strncpy(file, Timer.file, sizeof(file));
free(aux);
aux = Timer.aux ? strdup(Timer.aux) : NULL;
- event = NULL;
+ free(remote);
+ remote = Timer.remote ? strdup(Timer.remote) : NULL;
+ if (event)
+ event->DecNumTimers();
+ event = Timer.event;
+ if (event)
+ event->IncNumTimers();
}
return *this;
}
@@ -178,7 +189,7 @@ cString cTimer::ToText(bool UseChannelID) const
cString cTimer::ToDescr(void) const
{
- return cString::sprintf("%d (%d %04d-%04d %s'%s')", Index() + 1, Channel()->Number(), start, stop, HasFlags(tfVps) ? "VPS " : "", file);
+ return cString::sprintf("%d%s%s (%d %04d-%04d %s'%s')", Index() + 1, remote ? "@" : "", remote ? remote : "", Channel()->Number(), start, stop, HasFlags(tfVps) ? "VPS " : "", file);
}
int cTimer::TimeToInt(int t)
@@ -313,7 +324,6 @@ bool cTimer::Parse(const char *s)
}
bool result = false;
if (8 <= sscanf(s, "%u :%m[^:]:%m[^:]:%d :%d :%d :%d :%m[^:\n]:%m[^\n]", &flags, &channelbuffer, &daybuffer, &start, &stop, &priority, &lifetime, &filebuffer, &aux)) {
- ClrFlags(tfRecording);
if (aux && !*skipspace(aux)) {
free(aux);
aux = NULL;
@@ -322,10 +332,11 @@ bool cTimer::Parse(const char *s)
result = ParseDay(daybuffer, day, weekdays);
Utf8Strn0Cpy(file, filebuffer, sizeof(file));
strreplace(file, '|', ':');
+ LOCK_CHANNELS_READ;
if (isnumber(channelbuffer))
- channel = Channels.GetByNumber(atoi(channelbuffer));
+ channel = Channels->GetByNumber(atoi(channelbuffer));
else
- channel = Channels.GetByChannelID(tChannelID::FromString(channelbuffer), true, true);
+ channel = Channels->GetByChannelID(tChannelID::FromString(channelbuffer), true, true);
if (!channel) {
esyslog("ERROR: channel %s not defined", channelbuffer);
result = false;
@@ -340,7 +351,9 @@ bool cTimer::Parse(const char *s)
bool cTimer::Save(FILE *f)
{
- return fprintf(f, "%s", *ToText(true)) > 0;
+ if (!Remote())
+ return fprintf(f, "%s", *ToText(true)) > 0;
+ return true;
}
bool cTimer::IsSingleEvent(void) const
@@ -441,10 +454,8 @@ bool cTimer::Matches(time_t t, bool Directly, int Margin) const
startTime = event->StartTime();
stopTime = event->EndTime();
if (!Margin) { // this is an actual check
- if (event->Schedule()->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) { // VPS control can only work with up-to-date events...
- if (event->StartTime() > 0) // checks for "phased out" events
- return event->IsRunning(true);
- }
+ if (event->Schedule()->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) // VPS control can only work with up-to-date events...
+ return event->IsRunning(true);
return startTime <= t && t < stopTime; // ...otherwise we fall back to normal timer handling
}
}
@@ -511,27 +522,13 @@ time_t cTimer::StopTime(void) const
#define EPGLIMITBEFORE (1 * 3600) // Time in seconds before a timer's start time and
#define EPGLIMITAFTER (1 * 3600) // after its stop time within which EPG events will be taken into consideration.
-void cTimer::SetEventFromSchedule(const cSchedules *Schedules)
+bool cTimer::SetEventFromSchedule(const cSchedules *Schedules)
{
- cSchedulesLock SchedulesLock;
- if (!Schedules) {
- lastSetEvent = 0; // forces setting the event, even if the schedule hasn't been modified
- if (!(Schedules = cSchedules::Schedules(SchedulesLock)))
- return;
- }
const cSchedule *Schedule = Schedules->GetSchedule(Channel());
if (Schedule && Schedule->Events()->First()) {
- time_t now = time(NULL);
- if (!lastSetEvent || Schedule->Modified() >= lastSetEvent) {
- lastSetEvent = now;
+ if (Schedule->Modified(scheduleState)) {
const cEvent *Event = NULL;
if (HasFlags(tfVps) && Schedule->Events()->First()->Vps()) {
- if (event && event->StartTime() > 0) { // checks for "phased out" events
- if (Recording())
- return; // let the recording end first
- if (now <= event->EndTime() || Matches(0, true))
- return; // stay with the old event until the timer has completely expired
- }
// VPS timers only match if their start time exactly matches the event's VPS time:
for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
if (e->StartTime() && e->RunningStatus() != SI::RunningStatusNotRunning) { // skip outdated events
@@ -566,30 +563,39 @@ void cTimer::SetEventFromSchedule(const cSchedules *Schedules)
}
}
}
- SetEvent(Event);
+ return SetEvent(Event);
}
}
+ return false;
}
-void cTimer::SetEvent(const cEvent *Event)
+bool cTimer::SetEvent(const cEvent *Event)
{
- if (event != Event) { //XXX TODO check event data, too???
- if (Event)
+ if (event != Event) {
+ if (event)
+ event->DecNumTimers();
+ if (Event) {
isyslog("timer %s set to event %s", *ToDescr(), *Event->ToDescr());
- else
+ Event->IncNumTimers();
+ Event->Schedule()->Modified(scheduleState); // to get the current state
+ }
+ else {
isyslog("timer %s set to no event", *ToDescr());
+ scheduleState = -1;
+ }
event = Event;
+ return true;
}
+ return false;
}
void cTimer::SetRecording(bool Recording)
{
- recording = Recording;
- if (recording)
+ if (Recording)
SetFlags(tfRecording);
else
ClrFlags(tfRecording);
- isyslog("timer %s %s", *ToDescr(), recording ? "start" : "stop");
+ isyslog("timer %s %s", *ToDescr(), Recording ? "start" : "stop");
}
void cTimer::SetPending(bool Pending)
@@ -637,7 +643,13 @@ void cTimer::SetLifetime(int Lifetime)
void cTimer::SetAux(const char *Aux)
{
free(aux);
- aux = strdup(Aux);
+ aux = Aux ? strdup(Aux) : NULL;
+}
+
+void cTimer::SetRemote(const char *Remote)
+{
+ free(remote);
+ remote = Remote ? strdup(Remote) : NULL;
}
void cTimer::SetDeferred(int Seconds)
@@ -691,20 +703,33 @@ void cTimer::OnOff(void)
// --- cTimers ---------------------------------------------------------------
-cTimers Timers;
+cTimers cTimers::timers;
cTimers::cTimers(void)
+:cConfig<cTimer>("Timers")
{
- state = 0;
- beingEdited = 0;;
- lastSetEvents = 0;
lastDeleteExpired = 0;
}
+bool cTimers::Load(const char *FileName)
+{
+ LOCK_TIMERS_WRITE;
+ Timers->SetExplicitModify();
+ if (timers.cConfig<cTimer>::Load(FileName)) {
+ for (cTimer *ti = timers.First(); ti; ti = timers.Next(ti)) {
+ ti->ClrFlags(tfRecording);
+ Timers->SetModified();
+ }
+ return true;
+ }
+ return false;
+}
+
cTimer *cTimers::GetTimer(cTimer *Timer)
{
for (cTimer *ti = First(); ti; ti = Next(ti)) {
- if (ti->Channel() == Timer->Channel() &&
+ if (!ti->Remote() &&
+ ti->Channel() == Timer->Channel() &&
(ti->WeekDays() && ti->WeekDays() == Timer->WeekDays() || !ti->WeekDays() && ti->Day() == Timer->Day()) &&
ti->Start() == Timer->Start() &&
ti->Stop() == Timer->Stop())
@@ -713,12 +738,12 @@ cTimer *cTimers::GetTimer(cTimer *Timer)
return NULL;
}
-cTimer *cTimers::GetMatch(time_t t)
+const cTimer *cTimers::GetMatch(time_t t) const
{
static int LastPending = -1;
- cTimer *t0 = NULL;
- for (cTimer *ti = First(); ti; ti = Next(ti)) {
- if (!ti->Recording() && ti->Matches(t)) {
+ const cTimer *t0 = NULL;
+ for (const cTimer *ti = First(); ti; ti = Next(ti)) {
+ if (!ti->Remote() && !ti->Recording() && ti->Matches(t)) {
if (ti->Pending()) {
if (ti->Index() > LastPending) {
LastPending = ti->Index();
@@ -736,11 +761,11 @@ cTimer *cTimers::GetMatch(time_t t)
return t0;
}
-cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match)
+const cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match) const
{
- cTimer *t = NULL;
+ const cTimer *t = NULL;
eTimerMatch m = tmNone;
- for (cTimer *ti = First(); ti; ti = Next(ti)) {
+ for (const cTimer *ti = First(); ti; ti = Next(ti)) {
eTimerMatch tm = ti->Matches(Event);
if (tm > m) {
t = ti;
@@ -754,21 +779,27 @@ cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match)
return t;
}
-cTimer *cTimers::GetNextActiveTimer(void)
+const cTimer *cTimers::GetNextActiveTimer(void) const
{
- cTimer *t0 = NULL;
- for (cTimer *ti = First(); ti; ti = Next(ti)) {
- ti->Matches();
- if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
- t0 = ti;
+ const cTimer *t0 = NULL;
+ for (const cTimer *ti = First(); ti; ti = Next(ti)) {
+ if (!ti->Remote()) {
+ ti->Matches();
+ if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
+ t0 = ti;
+ }
}
return t0;
}
-void cTimers::SetModified(void)
+const cTimers *cTimers::GetTimersRead(cStateKey &StateKey, int TimeoutMs)
{
- cStatus::MsgTimerChange(NULL, tcMod);
- state++;
+ return timers.Lock(StateKey, false, TimeoutMs) ? &timers : NULL;
+}
+
+cTimers *cTimers::GetTimersWrite(cStateKey &StateKey, int TimeoutMs)
+{
+ return timers.Lock(StateKey, true, TimeoutMs) ? &timers : NULL;
}
void cTimers::Add(cTimer *Timer, cTimer *After)
@@ -789,46 +820,113 @@ void cTimers::Del(cTimer *Timer, bool DeleteObject)
cConfig<cTimer>::Del(Timer, DeleteObject);
}
-bool cTimers::Modified(int &State)
+const cTimer *cTimers::UsesChannel(const cChannel *Channel) const
{
- bool Result = state != State;
- State = state;
- return Result;
+ for (const cTimer *Timer = First(); Timer; Timer = Next(Timer)) {
+ if (Timer->Channel() == Channel)
+ return Timer;
+ }
+ return NULL;
}
-void cTimers::SetEvents(void)
+bool cTimers::SetEvents(const cSchedules *Schedules)
{
- if (time(NULL) - lastSetEvents < 5)
- return;
- cSchedulesLock SchedulesLock(false, 100);
- const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
- if (Schedules) {
- if (!lastSetEvents || Schedules->Modified() >= lastSetEvents) {
- for (cTimer *ti = First(); ti; ti = Next(ti)) {
- if (cRemote::HasKeys())
- return; // react immediately on user input
- ti->SetEventFromSchedule(Schedules);
- }
- }
- }
- lastSetEvents = time(NULL);
+ bool TimersModified = false;
+ for (cTimer *ti = First(); ti; ti = Next(ti))
+ TimersModified |= ti->SetEventFromSchedule(Schedules);
+ return TimersModified;
}
-void cTimers::DeleteExpired(void)
+bool cTimers::DeleteExpired(void)
{
if (time(NULL) - lastDeleteExpired < 30)
- return;
+ return false;
+ bool TimersModified = false;
cTimer *ti = First();
while (ti) {
cTimer *next = Next(ti);
- if (ti->Expired()) {
+ if (!ti->Remote() && ti->Expired()) {
isyslog("deleting timer %s", *ti->ToDescr());
Del(ti);
- SetModified();
+ TimersModified = true;
}
ti = next;
}
lastDeleteExpired = time(NULL);
+ return TimersModified;
+}
+
+bool cTimers::GetRemoteTimers(const char *ServerName)
+{
+ bool Result = false;
+ if (ServerName) {
+ Result = DelRemoteTimers(ServerName);
+ cSVDRPCommand Cmd(ServerName, "LSTT ID");
+ if (Cmd.Execute()) {
+ const char *s;
+ for (int i = 0; s = Cmd.Response(i); i++) {
+ int Code = Cmd.Code(s);
+ if (Code == 250) {
+ if (const char *v = Cmd.Value(s)) {
+ while (*v && *v != ' ')
+ v++; // skip number
+ cTimer *Timer = new cTimer;
+ if (Timer->Parse(v)) {
+ Timer->SetRemote(ServerName);
+ Add(Timer);
+ Result = true;
+ }
+ else {
+ esyslog("ERROR: %s: error in timer settings: %s", ServerName, v);
+ delete Timer;
+ }
+ }
+ }
+ else if (Code != 550)
+ esyslog("ERROR: %s: %s", ServerName, s);
+ }
+ return Result;
+ }
+ }
+ else {
+ cStringList ServerNames;
+ if (GetSVDRPServerNames(&ServerNames, sffTimers)) {
+ for (int i = 0; i < ServerNames.Size(); i++)
+ Result |= GetRemoteTimers(ServerNames[i]);
+ }
+ }
+ return Result;
+}
+
+bool cTimers::DelRemoteTimers(const char *ServerName)
+{
+ bool Deleted = false;
+ cTimer *Timer = First();
+ while (Timer) {
+ cTimer *t = Next(Timer);
+ if (Timer->Remote() && (!ServerName || strcmp(Timer->Remote(), ServerName) == 0)) {
+ Del(Timer);
+ Deleted = true;
+ }
+ Timer = t;
+ }
+ return Deleted;
+}
+
+void cTimers::TriggerRemoteTimerPoll(const char *ServerName)
+{
+ if (ServerName) {
+ cSVDRPCommand Cmd(ServerName, cString::sprintf("POLL %s TIMERS", SVDRPHostName()));
+ if (!Cmd.Execute())
+ esyslog("ERROR: can't send 'POLL %s TIMERS' to '%s'", SVDRPHostName(), ServerName);
+ }
+ else {
+ cStringList ServerNames;
+ if (GetSVDRPServerNames(&ServerNames)) {
+ for (int i = 0; i < ServerNames.Size(); i++)
+ TriggerRemoteTimerPoll(ServerNames[i]);
+ }
+ }
}
// --- cSortedTimers ---------------------------------------------------------
@@ -838,10 +936,10 @@ static int CompareTimers(const void *a, const void *b)
return (*(const cTimer **)a)->Compare(**(const cTimer **)b);
}
-cSortedTimers::cSortedTimers(void)
-:cVector<const cTimer *>(Timers.Count())
+cSortedTimers::cSortedTimers(const cTimers *Timers)
+:cVector<const cTimer *>(Timers->Count())
{
- for (const cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer))
+ for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer))
Append(Timer);
Sort(CompareTimers);
}
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<cTimer> {
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<cTimer *>(static_cast<const cTimers *>(this)->GetMatch(t)); };
+ const cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL) const;
+ cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL) { return const_cast<cTimer *>(static_cast<const cTimers *>(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<const cTimer *> {
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 <syslog.h>
#include <sys/stat.h>
#include <sys/types.h>
+#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<cListObject *>(static_cast<const cListBase *>(this)->Get(Index)); }
int Count(void) const { return count; }
void Sort(void);
};
template<class T> 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<T *>(static_cast<const cList<T> *>(this)->Get(Index)); }
+ ///< Non-const version of Get().
+ T *First(void) { return const_cast<T *>(static_cast<const cList<T> *>(this)->First()); }
+ ///< Non-const version of First().
+ T *Last(void) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Last()); }
+ ///< Non-const version of Last().
+ T *Prev(const T *Object) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Prev(Object)); }
+ ///< Non-const version of Prev().
+ T *Next(const T *Object) { return const_cast<T *>(static_cast<const cList<T> *>(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<c##Class *>(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 T> 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 <getopt.h>
@@ -748,8 +748,8 @@ int main(int argc, char *argv[])
Sources.Load(AddDirectory(ConfigDirectory, "sources.conf"), true, true);
Diseqcs.Load(AddDirectory(ConfigDirectory, "diseqc.conf"), true, Setup.DiSEqC);
Scrs.Load(AddDirectory(ConfigDirectory, "scr.conf"), true);
- Channels.Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true);
- Timers.Load(AddDirectory(ConfigDirectory, "timers.conf"));
+ cChannels::Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true);
+ cTimers::Load(AddDirectory(ConfigDirectory, "timers.conf"));
Commands.Load(AddDirectory(ConfigDirectory, "commands.conf"));
RecordingCommands.Load(AddDirectory(ConfigDirectory, "reccmds.conf"));
SVDRPhosts.Load(AddDirectory(ConfigDirectory, "svdrphosts.conf"), true);
@@ -765,8 +765,7 @@ int main(int argc, char *argv[])
// Recordings:
- Recordings.Update();
- DeletedRecordings.Update();
+ cRecordings::Update();
// EPG data:
@@ -879,16 +878,20 @@ int main(int argc, char *argv[])
if (!CamSlots.WaitForAllCamSlotsReady(DEVICEREADYTIMEOUT))
dsyslog("not all CAM slots ready after %d seconds", DEVICEREADYTIMEOUT);
if (*Setup.InitialChannel) {
+ LOCK_CHANNELS_READ;
if (isnumber(Setup.InitialChannel)) { // for compatibility with old setup.conf files
- if (cChannel *Channel = Channels.GetByNumber(atoi(Setup.InitialChannel)))
+ if (const cChannel *Channel = Channels->GetByNumber(atoi(Setup.InitialChannel)))
Setup.InitialChannel = Channel->GetChannelID().ToString();
}
- if (cChannel *Channel = Channels.GetByChannelID(tChannelID::FromString(Setup.InitialChannel)))
+ if (const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(Setup.InitialChannel)))
Setup.CurrentChannel = Channel->Number();
}
if (Setup.InitialVolume >= 0)
Setup.CurrentVolume = Setup.InitialVolume;
- Channels.SwitchTo(Setup.CurrentChannel);
+ {
+ LOCK_CHANNELS_READ;
+ Channels->SwitchTo(Setup.CurrentChannel);
+ }
if (MuteAudio)
cDevice::PrimaryDevice()->ToggleMute();
else
@@ -936,13 +939,14 @@ int main(int argc, char *argv[])
static time_t lastTime = 0;
if (!cDevice::PrimaryDevice()->HasProgramme()) {
if (!CamMenuActive() && Now - lastTime > MINCHANNELWAIT) { // !CamMenuActive() to avoid interfering with the CAM if a CAM menu is open
- cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel());
+ LOCK_CHANNELS_READ;
+ const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel());
if (Channel && (Channel->Vpid() || Channel->Apid(0) || Channel->Dpid(0))) {
- if (cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(Channel->Number())) // try to switch to the original channel...
+ if (cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels->SwitchTo(Channel->Number())) // try to switch to the original channel...
;
else if (LastTimerChannel > 0) {
- Channel = Channels.GetByNumber(LastTimerChannel);
- if (Channel && cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(LastTimerChannel)) // ...or the one used by the last timer
+ Channel = Channels->GetByNumber(LastTimerChannel);
+ if (Channel && cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels->SwitchTo(LastTimerChannel)) // ...or the one used by the last timer
;
}
}
@@ -970,40 +974,59 @@ int main(int argc, char *argv[])
}
}
// Handle channel and timer modifications:
- if (!Channels.BeingEdited() && !Timers.BeingEdited()) {
- int modified = Channels.Modified();
- static time_t ChannelSaveTimeout = 0;
- static int TimerState = 0;
- // Channels and timers need to be stored in a consistent manner,
- // therefore if one of them is changed, we save both.
- if (modified == CHANNELSMOD_USER || Timers.Modified(TimerState))
- ChannelSaveTimeout = 1; // triggers an immediate save
- else if (modified && !ChannelSaveTimeout)
- ChannelSaveTimeout = Now + CHANNELSAVEDELTA;
- bool timeout = ChannelSaveTimeout == 1 || ChannelSaveTimeout && Now > ChannelSaveTimeout && !cRecordControls::Active();
- if ((modified || timeout) && Channels.Lock(false, 100)) {
- if (timeout) {
- Channels.Save();
- Timers.Save();
- ChannelSaveTimeout = 0;
+ {
+ // Channels and timers need to be stored in a consistent manner,
+ // therefore if one of them is changed, we save both.
+ static time_t ChannelSaveTimeout = 0;
+ static cStateKey TimersStateKey(true);
+ static cStateKey ChannelsStateKey(true);
+ static int ChannelsModifiedByUser = 0;
+ const cTimers *Timers = cTimers::GetTimersRead(TimersStateKey);
+ const cChannels *Channels = cChannels::GetChannelsRead(ChannelsStateKey);
+ if (ChannelSaveTimeout != 1) {
+ if (Channels) {
+ if (Channels->ModifiedByUser(ChannelsModifiedByUser))
+ ChannelSaveTimeout = 1; // triggers an immediate save
+ else if (!ChannelSaveTimeout)
+ ChannelSaveTimeout = Now + CHANNELSAVEDELTA;
+ }
+ if (Timers)
+ ChannelSaveTimeout = 1; // triggers an immediate save
+ }
+ if (ChannelSaveTimeout && Now > ChannelSaveTimeout && !cRecordControls::Active())
+ ChannelSaveTimeout = 1; // triggers an immediate save
+ if (Timers && Channels) {
+ Channels->Save();
+ Timers->Save();
+ ChannelSaveTimeout = 0;
+ }
+ if (Channels) {
+ for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
+ if (Channel->Modification(CHANNELMOD_RETUNE)) {
+ cRecordControls::ChannelDataModified(Channel);
+ if (Channel->Number() == cDevice::CurrentChannel() && cDevice::PrimaryDevice()->HasDecoder()) {
+ if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) {
+ if (cDevice::ActualDevice()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
+ isyslog("retuning due to modification of channel %d (%s)", Channel->Number(), Channel->Name());
+ Channels->SwitchTo(Channel->Number());
+ }
+ }
+ }
+ cStatus::MsgChannelChange(Channel);
+ }
}
- for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
- if (Channel->Modification(CHANNELMOD_RETUNE)) {
- cRecordControls::ChannelDataModified(Channel);
- if (Channel->Number() == cDevice::CurrentChannel() && cDevice::PrimaryDevice()->HasDecoder()) {
- if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) {
- if (cDevice::ActualDevice()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
- isyslog("retuning due to modification of channel %d (%s)", Channel->Number(), Channel->Name());
- Channels.SwitchTo(Channel->Number());
- }
- }
- }
- cStatus::MsgChannelChange(Channel);
- }
- }
- Channels.Unlock();
- }
- }
+ }
+ // State keys are removed in reverse order!
+ if (Channels)
+ ChannelsStateKey.Remove();
+ if (Timers)
+ TimersStateKey.Remove();
+ if (ChannelSaveTimeout == 1) {
+ // Only one of them was modified, so we reset the state keys to handle them both in the next turn:
+ ChannelsStateKey.Reset();
+ TimersStateKey.Reset();
+ }
+ }
// Channel display:
if (!EITScanner.Active() && cDevice::CurrentChannel() != LastChannel) {
if (!Menu)
@@ -1013,80 +1036,109 @@ int main(int argc, char *argv[])
}
if (Now - LastChannelChanged >= Setup.ZapTimeout && LastChannel != PreviousChannel[PreviousChannelIndex])
PreviousChannel[PreviousChannelIndex ^= 1] = LastChannel;
- // Timers and Recordings:
- if (!Timers.BeingEdited()) {
- // Assign events to timers:
- Timers.SetEvents();
- // Must do all following calls with the exact same time!
- // Process ongoing recordings:
- cRecordControls::Process(Now);
- // Start new recordings:
- cTimer *Timer = Timers.GetMatch(Now);
- if (Timer) {
- if (!cRecordControls::Start(Timer))
- Timer->SetPending(true);
- else
- LastTimerChannel = Timer->Channel()->Number();
- }
- // Make sure timers "see" their channel early enough:
- static time_t LastTimerCheck = 0;
- if (Now - LastTimerCheck > TIMERCHECKDELTA) { // don't do this too often
- InhibitEpgScan = false;
- for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
- bool InVpsMargin = false;
- bool NeedsTransponder = false;
- if (Timer->HasFlags(tfActive) && !Timer->Recording()) {
- if (Timer->HasFlags(tfVps)) {
- if (Timer->Matches(Now, true, Setup.VpsMargin)) {
- InVpsMargin = true;
- Timer->SetInVpsMargin(InVpsMargin);
- }
- else if (Timer->Event()) {
- InVpsMargin = Timer->Event()->StartTime() <= Now && Now < Timer->Event()->EndTime();
- NeedsTransponder = Timer->Event()->StartTime() - Now < VPSLOOKAHEADTIME * 3600 && !Timer->Event()->SeenWithin(VPSUPTODATETIME);
- }
- else {
- cSchedulesLock SchedulesLock;
- const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
- if (Schedules) {
- const cSchedule *Schedule = Schedules->GetSchedule(Timer->Channel());
- InVpsMargin = !Schedule; // we must make sure we have the schedule
- NeedsTransponder = Schedule && !Schedule->PresentSeenWithin(VPSUPTODATETIME);
- }
- }
- InhibitEpgScan |= InVpsMargin | NeedsTransponder;
- }
- else
- NeedsTransponder = Timer->Matches(Now, true, TIMERLOOKAHEADTIME);
- }
- if (NeedsTransponder || InVpsMargin) {
- // Find a device that provides the required transponder:
- cDevice *Device = cDevice::GetDeviceForTransponder(Timer->Channel(), MINPRIORITY);
- if (!Device && InVpsMargin)
- Device = cDevice::GetDeviceForTransponder(Timer->Channel(), LIVEPRIORITY);
- // Switch the device to the transponder:
- if (Device) {
- bool HadProgramme = cDevice::PrimaryDevice()->HasProgramme();
- if (!Device->IsTunedToTransponder(Timer->Channel())) {
- if (Device == cDevice::ActualDevice() && !Device->IsPrimaryDevice())
- cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode
- dsyslog("switching device %d to channel %d (%s)", Device->DeviceNumber() + 1, Timer->Channel()->Number(), Timer->Channel()->Name());
- if (Device->SwitchChannel(Timer->Channel(), false))
- Device->SetOccupied(TIMERDEVICETIMEOUT);
- }
- if (cDevice::PrimaryDevice()->HasDecoder() && HadProgramme && !cDevice::PrimaryDevice()->HasProgramme())
- Skins.QueueMessage(mtInfo, tr("Upcoming recording!")); // the previous SwitchChannel() has switched away the current live channel
- }
- }
- }
- LastTimerCheck = Now;
- }
- // Delete expired timers:
- Timers.DeleteExpired();
- }
- if (!Menu && Recordings.NeedsUpdate()) {
- Recordings.Update();
- DeletedRecordings.Update();
+ {
+ // Timers and Recordings:
+ bool TimersModified = false;
+ bool TriggerRemoteTimerPoll = false;
+ static cStateKey TimersStateKey(true);
+ if (cTimers::GetTimersRead(TimersStateKey)) {
+ TriggerRemoteTimerPoll = true;
+ TimersStateKey.Remove();
+ }
+ cTimers *Timers = cTimers::GetTimersWrite(TimersStateKey);
+ // Get remote timers:
+ TimersModified |= Timers->GetRemoteTimers();
+ // Assign events to timers:
+ static cStateKey SchedulesStateKey;
+ if (const cSchedules *Schedules = cSchedules::GetSchedulesRead(SchedulesStateKey))
+ TimersModified |= Timers->SetEvents(Schedules);
+ // Must do all following calls with the exact same time!
+ // Process ongoing recordings:
+ if (cRecordControls::Process(Timers, Now)) {
+ TimersModified = true;
+ TriggerRemoteTimerPoll = true;
+ }
+ // Must keep the lock on the schedules until after processing the record
+ // controls, in order to avoid short interrupts in case the current event
+ // is replaced by a new one (which some broadcasters do, instead of just
+ // modifying the current event's data):
+ if (SchedulesStateKey.InLock())
+ SchedulesStateKey.Remove();
+ // Start new recordings:
+ if (cTimer *Timer = Timers->GetMatch(Now)) {
+ if (!cRecordControls::Start(Timers, Timer))
+ Timer->SetPending(true);
+ else
+ LastTimerChannel = Timer->Channel()->Number();
+ TimersModified = true;
+ TriggerRemoteTimerPoll = true;
+ }
+ // Make sure timers "see" their channel early enough:
+ static time_t LastTimerCheck = 0;
+ if (Now - LastTimerCheck > TIMERCHECKDELTA) { // don't do this too often
+ InhibitEpgScan = false;
+ for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
+ if (Timer->Remote())
+ continue;
+ bool InVpsMargin = false;
+ bool NeedsTransponder = false;
+ if (Timer->HasFlags(tfActive) && !Timer->Recording()) {
+ if (Timer->HasFlags(tfVps)) {
+ if (Timer->Matches(Now, true, Setup.VpsMargin)) {
+ InVpsMargin = true;
+ Timer->SetInVpsMargin(InVpsMargin);
+ }
+ else if (Timer->Event()) {
+ InVpsMargin = Timer->Event()->StartTime() <= Now && Now < Timer->Event()->EndTime();
+ NeedsTransponder = Timer->Event()->StartTime() - Now < VPSLOOKAHEADTIME * 3600 && !Timer->Event()->SeenWithin(VPSUPTODATETIME);
+ }
+ else {
+ LOCK_SCHEDULES_READ;
+ const cSchedule *Schedule = Schedules->GetSchedule(Timer->Channel());
+ InVpsMargin = !Schedule; // we must make sure we have the schedule
+ NeedsTransponder = Schedule && !Schedule->PresentSeenWithin(VPSUPTODATETIME);
+ }
+ InhibitEpgScan |= InVpsMargin | NeedsTransponder;
+ }
+ else
+ NeedsTransponder = Timer->Matches(Now, true, TIMERLOOKAHEADTIME);
+ }
+ if (NeedsTransponder || InVpsMargin) {
+ // Find a device that provides the required transponder:
+ cDevice *Device = cDevice::GetDeviceForTransponder(Timer->Channel(), MINPRIORITY);
+ if (!Device && InVpsMargin)
+ Device = cDevice::GetDeviceForTransponder(Timer->Channel(), LIVEPRIORITY);
+ // Switch the device to the transponder:
+ if (Device) {
+ bool HadProgramme = cDevice::PrimaryDevice()->HasProgramme();
+ if (!Device->IsTunedToTransponder(Timer->Channel())) {
+ if (Device == cDevice::ActualDevice() && !Device->IsPrimaryDevice())
+ cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode
+ dsyslog("switching device %d to channel %d (%s)", Device->DeviceNumber() + 1, Timer->Channel()->Number(), Timer->Channel()->Name());
+ if (Device->SwitchChannel(Timer->Channel(), false))
+ Device->SetOccupied(TIMERDEVICETIMEOUT);
+ }
+ if (cDevice::PrimaryDevice()->HasDecoder() && HadProgramme && !cDevice::PrimaryDevice()->HasProgramme())
+ Skins.QueueMessage(mtInfo, tr("Upcoming recording!")); // the previous SwitchChannel() has switched away the current live channel
+ }
+ }
+ }
+ LastTimerCheck = Now;
+ }
+ // Delete expired timers:
+ if (Timers->DeleteExpired()) {
+ TimersModified = true;
+ TriggerRemoteTimerPoll = true;
+ }
+ // Trigger remote timer polls:
+ if (TriggerRemoteTimerPoll)
+ Timers->TriggerRemoteTimerPoll();
+ TimersStateKey.Remove(TimersModified);
+ }
+ // Recordings:
+ if (!Menu) {
+ if (cRecordings::NeedsUpdate())
+ cRecordings::Update();
}
// CAM control:
if (!Menu && !cOsd::IsOpen())
@@ -1359,7 +1411,8 @@ int main(int argc, char *argv[])
case k0: {
if (PreviousChannel[PreviousChannelIndex ^ 1] == LastChannel || LastChannel != PreviousChannel[0] && LastChannel != PreviousChannel[1])
PreviousChannelIndex ^= 1;
- Channels.SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]);
+ LOCK_CHANNELS_READ;
+ Channels->SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]);
break;
}
// Direct Channel Select:
@@ -1447,7 +1500,7 @@ int main(int argc, char *argv[])
// Disk housekeeping:
RemoveDeletedRecordings();
- ClearVanishedRecordings();
+ ListGarbageCollector.Purge();
cSchedules::Cleanup();
// Plugins housekeeping:
PluginManager.Housekeeping();
@@ -1492,8 +1545,9 @@ Exit:
cPositioner::DestroyPositioner();
cVideoDirectory::Destroy();
EpgHandlers.Clear();
- PluginManager.Shutdown(true);
cSchedules::Cleanup(true);
+ ListGarbageCollector.Purge(true);
+ PluginManager.Shutdown(true);
ReportEpgBugFixStats(true);
if (WatchdogTimeout > 0)
dsyslog("max. latency time %d seconds", MaxLatencyTime);
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);