1
0
mirror of https://github.com/VDR4Arch/vdr.git synced 2023-10-10 13:36:52 +02:00

Implemented strict locking of global lists

This commit is contained in:
Klaus Schmidinger 2015-09-01 11:14:27 +02:00
parent 8a7bc6a0bb
commit 3cd5294d8a
41 changed files with 3512 additions and 2402 deletions

113
HISTORY
View File

@ -8596,7 +8596,7 @@ Video Disk Recorder Revision History
- Bumped all version numbers to 2.2.0. - Bumped all version numbers to 2.2.0.
- Official release. - 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 - 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 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. this VDR is connected to via SVDRP.
- The new class cSVDRPCommand can be used to execute an SVDRP command on one of - 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 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.

View File

@ -4,15 +4,13 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 "channels.h"
#include <ctype.h> #include <ctype.h>
#include "device.h" #include "device.h"
#include "epg.h"
#include "libsi/si.h" #include "libsi/si.h"
#include "timers.h"
// IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d' // 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 // format characters in order to allow any number of blanks after a numeric
@ -79,27 +77,13 @@ cChannel::cChannel(const cChannel &Channel)
schedule = NULL; schedule = NULL;
linkChannels = NULL; linkChannels = NULL;
refChannel = NULL; refChannel = NULL;
seen = 0;
*this = Channel; *this = Channel;
} }
cChannel::~cChannel() cChannel::~cChannel()
{ {
delete linkChannels; delete linkChannels; // any links from other channels pointing to this one have been deleted in cChannels::Del()
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;
}
}
}
free(name); free(name);
free(shortName); free(shortName);
free(provider); free(provider);
@ -167,16 +151,7 @@ int cChannel::Transponder(void) const
return tf; return tf;
} }
bool cChannel::HasTimer(void) const int cChannel::Modification(int Mask) 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 Result = modification & Mask; int Result = modification & Mask;
modification = CHANNELMOD_NONE; modification = CHANNELMOD_NONE;
@ -223,53 +198,57 @@ bool cChannel::SetTransponderData(int Source, int Frequency, int Srate, const ch
if (Number() && !Quiet) { if (Number() && !Quiet) {
dsyslog("changing transponder data of channel %d (%s) from %s to %s", Number(), name, *OldTransponderData, *TransponderDataToString()); dsyslog("changing transponder data of channel %d (%s) from %s to %s", Number(), name, *OldTransponderData, *TransponderDataToString());
modification |= CHANNELMOD_TRANSP; 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 (source != Source) {
if (Number()) { if (Number()) {
dsyslog("changing source of channel %d (%s) from %s to %s", Number(), name, *cSource::ToString(source), *cSource::ToString(Source)); dsyslog("changing source of channel %d (%s) from %s to %s", Number(), name, *cSource::ToString(source), *cSource::ToString(Source));
modification |= CHANNELMOD_TRANSP; modification |= CHANNELMOD_TRANSP;
Channels.SetModified();
} }
source = Source; 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 (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); 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; modification |= CHANNELMOD_ID;
Channels.SetModified(); Channels->UnhashChannel(this);
Channels.UnhashChannel(this);
} }
nid = Nid; nid = Nid;
tid = Tid; tid = Tid;
sid = Sid; sid = Sid;
rid = Rid; rid = Rid;
if (Number()) if (Channels)
Channels.HashChannel(this); Channels->HashChannel(this);
schedule = NULL; schedule = NULL;
return true;
} }
return false;
} }
void cChannel::SetLcn(int Lcn) bool cChannel::SetLcn(int Lcn)
{ {
if (lcn != Lcn) { if (lcn != Lcn) {
if (Number()) if (Number())
dsyslog("changing lcn of channel %d (%s) from %d to %d\n", Number(), name, lcn, Lcn); dsyslog("changing lcn of channel %d (%s) from %d to %d\n", Number(), name, lcn, Lcn);
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)) { if (!isempty(Name)) {
bool nn = strcmp(name, Name) != 0; bool nn = strcmp(name, Name) != 0;
@ -279,7 +258,6 @@ void cChannel::SetName(const char *Name, const char *ShortName, const char *Prov
if (Number()) { if (Number()) {
dsyslog("changing name of channel %d from '%s,%s;%s' to '%s,%s;%s'", Number(), name, shortName, provider, Name, ShortName, Provider); dsyslog("changing name of channel %d from '%s,%s;%s' to '%s,%s;%s'", Number(), name, shortName, provider, Name, ShortName, Provider);
modification |= CHANNELMOD_NAME; modification |= CHANNELMOD_NAME;
Channels.SetModified();
} }
if (nn) { if (nn) {
name = strcpyrealloc(name, Name); name = strcpyrealloc(name, Name);
@ -291,20 +269,23 @@ void cChannel::SetName(const char *Name, const char *ShortName, const char *Prov
} }
if (np) if (np)
provider = strcpyrealloc(provider, Provider); 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 (!isempty(PortalName) && strcmp(portalName, PortalName) != 0) {
if (Number()) { if (Number()) {
dsyslog("changing portal name of channel %d (%s) from '%s' to '%s'", Number(), name, portalName, PortalName); dsyslog("changing portal name of channel %d (%s) from '%s' to '%s'", Number(), name, portalName, PortalName);
modification |= CHANNELMOD_NAME; modification |= CHANNELMOD_NAME;
Channels.SetModified();
} }
portalName = strcpyrealloc(portalName, PortalName); portalName = strcpyrealloc(portalName, PortalName);
return true;
} }
return false;
} }
#define STRDIFF 0x01 #define STRDIFF 0x01
@ -349,7 +330,7 @@ static int IntArrayToString(char *s, const int *a, int Base = 10, const char n[]
return q - s; 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; int mod = CHANNELMOD_NONE;
if (vpid != Vpid || ppid != Ppid || vtype != Vtype) 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; spids[MAXSPIDS] = 0;
tpid = Tpid; tpid = Tpid;
modification |= mod; modification |= mod;
if (Number()) return true;
Channels.SetModified();
} }
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) { 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]; subtitlingTypes[i] = SubtitlingTypes[i];
}
} }
if (CompositionPageIds) { 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]; compositionPageIds[i] = CompositionPageIds[i];
}
} }
if (AncillaryPageIds) { 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]; ancillaryPageIds[i] = AncillaryPageIds[i];
}
} }
return Modified;
} }
void cChannel::SetSeen(void) void cChannel::SetSeen(void)
@ -438,10 +427,26 @@ void cChannel::SetSeen(void)
seen = time(NULL); 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) 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)) { if (IntArraysDiffer(caids, CaIds)) {
char OldCaIdsBuf[MAXCAIDS * 5 + 10]; // 5: 4 digits plus delimiting ',', 10: paranoia char OldCaIdsBuf[MAXCAIDS * 5 + 10]; // 5: 4 digits plus delimiting ',', 10: paranoia
char NewCaIdsBuf[MAXCAIDS * 5 + 10]; char NewCaIdsBuf[MAXCAIDS * 5 + 10];
@ -455,24 +460,26 @@ void cChannel::SetCaIds(const int *CaIds)
break; break;
} }
modification |= CHANNELMOD_CA; modification |= CHANNELMOD_CA;
Channels.SetModified(); return true;
} }
return false;
} }
void cChannel::SetCaDescriptors(int Level) bool cChannel::SetCaDescriptors(int Level)
{ {
if (Level > 0) { if (Level > 0) {
modification |= CHANNELMOD_CA; modification |= CHANNELMOD_CA;
Channels.SetModified();
if (Number() && Level > 1) if (Number() && Level > 1)
dsyslog("changing ca descriptors of channel %d (%s)", Number(), name); 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) if (!linkChannels && !LinkChannels)
return; return false;
if (linkChannels && LinkChannels) { if (linkChannels && LinkChannels) {
cLinkChannel *lca = linkChannels->First(); cLinkChannel *lca = linkChannels->First();
cLinkChannel *lcb = LinkChannels->First(); cLinkChannel *lcb = LinkChannels->First();
@ -486,7 +493,7 @@ void cChannel::SetLinkChannels(cLinkChannels *LinkChannels)
} }
if (!lca && !lcb) { if (!lca && !lcb) {
delete LinkChannels; 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 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"); q += sprintf(q, " none");
if (Number()) if (Number())
dsyslog("%s", buffer); dsyslog("%s", buffer);
return true;
} }
void cChannel::SetRefChannel(cChannel *RefChannel) void cChannel::SetRefChannel(cChannel *RefChannel)
@ -819,40 +827,52 @@ public:
// --- cChannels ------------------------------------------------------------- // --- cChannels -------------------------------------------------------------
cChannels Channels; cChannels cChannels::channels;
int cChannels::maxNumber = 0;
int cChannels::maxChannelNameLength = 0;
int cChannels::maxShortChannelNameLength = 0;
cChannels::cChannels(void) cChannels::cChannels(void)
:cConfig<cChannel>("Channels")
{ {
maxNumber = 0; modifiedByUser = 0;
maxChannelNameLength = 0; }
maxShortChannelNameLength = 0;
modified = CHANNELSMOD_NONE; 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) void cChannels::DeleteDuplicateChannels(void)
{ {
cList<cChannelSorter> ChannelSorter; cList<cChannelSorter> ChannelSorter;
for (cChannel *channel = First(); channel; channel = Next(channel)) { for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
if (!channel->GroupSep()) if (!Channel->GroupSep())
ChannelSorter.Add(new cChannelSorter(channel)); ChannelSorter.Add(new cChannelSorter(Channel));
} }
ChannelSorter.Sort(); ChannelSorter.Sort();
cChannelSorter *cs = ChannelSorter.First(); cChannelSorter *cs = ChannelSorter.First();
while (cs) { while (cs) {
cChannelSorter *next = ChannelSorter.Next(cs); cChannelSorter *Next = ChannelSorter.Next(cs);
if (next && cs->channelID == next->channelID) { if (Next && cs->channelID == Next->channelID) {
dsyslog("deleting duplicate channel %s", *next->channel->ToText()); dsyslog("deleting duplicate channel %s", *Next->channel->ToText());
Del(next->channel); Del(Next->channel);
} }
cs = next; cs = Next;
} }
} }
bool cChannels::Load(const char *FileName, bool AllowComments, bool MustExist) bool cChannels::Load(const char *FileName, bool AllowComments, bool MustExist)
{ {
if (cConfig<cChannel>::Load(FileName, AllowComments, MustExist)) { LOCK_CHANNELS_WRITE;
DeleteDuplicateChannels(); if (channels.cConfig<cChannel>::Load(FileName, AllowComments, MustExist)) {
ReNumber(); channels.DeleteDuplicateChannels();
channels.ReNumber();
return true; return true;
} }
return false; return false;
@ -868,36 +888,36 @@ void cChannels::UnhashChannel(cChannel *Channel)
channelsHashSid.Del(Channel, Channel->Sid()); channelsHashSid.Del(Channel, Channel->Sid());
} }
int cChannels::GetNextGroup(int Idx) int cChannels::GetNextGroup(int Idx) const
{ {
cChannel *channel = Get(++Idx); const cChannel *Channel = Get(++Idx);
while (channel && !(channel->GroupSep() && *channel->Name())) while (Channel && !(Channel->GroupSep() && *Channel->Name()))
channel = Get(++Idx); Channel = Get(++Idx);
return channel ? Idx : -1; return Channel ? Idx : -1;
} }
int cChannels::GetPrevGroup(int Idx) int cChannels::GetPrevGroup(int Idx) const
{ {
cChannel *channel = Get(--Idx); const cChannel *Channel = Get(--Idx);
while (channel && !(channel->GroupSep() && *channel->Name())) while (Channel && !(Channel->GroupSep() && *Channel->Name()))
channel = Get(--Idx); Channel = Get(--Idx);
return channel ? Idx : -1; return Channel ? Idx : -1;
} }
int cChannels::GetNextNormal(int Idx) int cChannels::GetNextNormal(int Idx) const
{ {
cChannel *channel = Get(++Idx); const cChannel *Channel = Get(++Idx);
while (channel && channel->GroupSep()) while (Channel && Channel->GroupSep())
channel = Get(++Idx); Channel = Get(++Idx);
return channel ? Idx : -1; return Channel ? Idx : -1;
} }
int cChannels::GetPrevNormal(int Idx) int cChannels::GetPrevNormal(int Idx) const
{ {
cChannel *channel = Get(--Idx); const cChannel *Channel = Get(--Idx);
while (channel && channel->GroupSep()) while (Channel && Channel->GroupSep())
channel = Get(--Idx); Channel = Get(--Idx);
return channel ? Idx : -1; return Channel ? Idx : -1;
} }
void cChannels::ReNumber(void) void cChannels::ReNumber(void)
@ -905,110 +925,120 @@ void cChannels::ReNumber(void)
channelsHashSid.Clear(); channelsHashSid.Clear();
maxNumber = 0; maxNumber = 0;
int Number = 1; int Number = 1;
for (cChannel *channel = First(); channel; channel = Next(channel)) { for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
if (channel->GroupSep()) { if (Channel->GroupSep()) {
if (channel->Number() > Number) if (Channel->Number() > Number)
Number = channel->Number(); Number = Channel->Number();
} }
else { else {
HashChannel(channel); HashChannel(Channel);
maxNumber = Number; maxNumber = Number;
channel->SetNumber(Number++); Channel->SetNumber(Number++);
} }
} }
} }
cChannel *cChannels::GetByNumber(int Number, int SkipGap) void cChannels::Del(cChannel *Channel)
{ {
cChannel *previous = NULL; UnhashChannel(Channel);
for (cChannel *channel = First(); channel; channel = Next(channel)) { for (cChannel *ch = First(); ch; ch = Next(ch))
if (!channel->GroupSep()) { ch->DelLinkChannel(Channel);
if (channel->Number() == Number) cList<cChannel>::Del(Channel);
return channel; }
else if (SkipGap && channel->Number() > Number)
return SkipGap > 0 ? channel : previous; const cChannel *cChannels::GetByNumber(int Number, int SkipGap) const
previous = channel; {
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; 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); cList<cHashObject> *list = channelsHashSid.GetList(ServiceID);
if (list) { if (list) {
for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
cChannel *channel = (cChannel *)hobj->Object(); cChannel *Channel = (cChannel *)hobj->Object();
if (channel->Sid() == ServiceID && channel->Source() == Source && ISTRANSPONDER(channel->Transponder(), Transponder)) if (Channel->Sid() == ServiceID && Channel->Source() == Source && ISTRANSPONDER(Channel->Transponder(), Transponder))
return channel; return Channel;
} }
} }
return NULL; 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(); int sid = ChannelID.Sid();
cList<cHashObject> *list = channelsHashSid.GetList(sid); cList<cHashObject> *list = channelsHashSid.GetList(sid);
if (list) { if (list) {
for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
cChannel *channel = (cChannel *)hobj->Object(); cChannel *Channel = (cChannel *)hobj->Object();
if (channel->Sid() == sid && channel->GetChannelID() == ChannelID) if (Channel->Sid() == sid && Channel->GetChannelID() == ChannelID)
return channel; return Channel;
} }
if (TryWithoutRid) { if (TryWithoutRid) {
ChannelID.ClrRid(); ChannelID.ClrRid();
for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
cChannel *channel = (cChannel *)hobj->Object(); cChannel *Channel = (cChannel *)hobj->Object();
if (channel->Sid() == sid && channel->GetChannelID().ClrRid() == ChannelID) if (Channel->Sid() == sid && Channel->GetChannelID().ClrRid() == ChannelID)
return channel; return Channel;
} }
} }
if (TryWithoutPolarization) { if (TryWithoutPolarization) {
ChannelID.ClrPolarization(); ChannelID.ClrPolarization();
for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
cChannel *channel = (cChannel *)hobj->Object(); cChannel *Channel = (cChannel *)hobj->Object();
if (channel->Sid() == sid && channel->GetChannelID().ClrPolarization() == ChannelID) if (Channel->Sid() == sid && Channel->GetChannelID().ClrPolarization() == ChannelID)
return channel; return Channel;
} }
} }
} }
return NULL; return NULL;
} }
cChannel *cChannels::GetByTransponderID(tChannelID ChannelID)
const cChannel *cChannels::GetByTransponderID(tChannelID ChannelID) const
{ {
int source = ChannelID.Source(); int source = ChannelID.Source();
int nid = ChannelID.Nid(); int nid = ChannelID.Nid();
int tid = ChannelID.Tid(); int tid = ChannelID.Tid();
for (cChannel *channel = First(); channel; channel = Next(channel)) { for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
if (channel->Tid() == tid && channel->Nid() == nid && channel->Source() == source) if (Channel->Tid() == tid && Channel->Nid() == nid && Channel->Source() == source)
return channel; return Channel;
} }
return NULL; return NULL;
} }
bool cChannels::HasUniqueChannelID(cChannel *NewChannel, cChannel *OldChannel) bool cChannels::HasUniqueChannelID(const cChannel *NewChannel, const cChannel *OldChannel) const
{ {
tChannelID NewChannelID = NewChannel->GetChannelID(); tChannelID NewChannelID = NewChannel->GetChannelID();
for (cChannel *channel = First(); channel; channel = Next(channel)) { for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
if (!channel->GroupSep() && channel != OldChannel && channel->GetChannelID() == NewChannelID) if (!Channel->GroupSep() && Channel != OldChannel && Channel->GetChannelID() == NewChannelID)
return false; return false;
} }
return true; return true;
} }
bool cChannels::SwitchTo(int Number) bool cChannels::SwitchTo(int Number) const
{ {
cChannel *channel = GetByNumber(Number); const cChannel *Channel = GetByNumber(Number);
return channel && cDevice::PrimaryDevice()->SwitchChannel(channel, true); return Channel && cDevice::PrimaryDevice()->SwitchChannel(Channel, true);
} }
int cChannels::MaxChannelNameLength(void) int cChannels::MaxChannelNameLength(void)
{ {
if (!maxChannelNameLength) { if (!maxChannelNameLength) {
for (cChannel *channel = First(); channel; channel = Next(channel)) { LOCK_CHANNELS_READ;
if (!channel->GroupSep()) for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
maxChannelNameLength = max(Utf8StrLen(channel->Name()), maxChannelNameLength); if (!Channel->GroupSep())
maxChannelNameLength = max(Utf8StrLen(Channel->Name()), maxChannelNameLength);
} }
} }
return maxChannelNameLength; return maxChannelNameLength;
@ -1017,24 +1047,25 @@ int cChannels::MaxChannelNameLength(void)
int cChannels::MaxShortChannelNameLength(void) int cChannels::MaxShortChannelNameLength(void)
{ {
if (!maxShortChannelNameLength) { if (!maxShortChannelNameLength) {
for (cChannel *channel = First(); channel; channel = Next(channel)) { LOCK_CHANNELS_READ;
if (!channel->GroupSep()) for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
maxShortChannelNameLength = max(Utf8StrLen(channel->ShortName(true)), maxShortChannelNameLength); if (!Channel->GroupSep())
maxShortChannelNameLength = max(Utf8StrLen(Channel->ShortName(true)), maxShortChannelNameLength);
} }
} }
return maxShortChannelNameLength; return maxShortChannelNameLength;
} }
void cChannels::SetModified(bool ByUser) void cChannels::SetModifiedByUser(void)
{ {
modified = ByUser ? CHANNELSMOD_USER : !modified ? CHANNELSMOD_AUTO : modified; modifiedByUser++;
maxChannelNameLength = maxShortChannelNameLength = 0; maxChannelNameLength = maxShortChannelNameLength = 0;
} }
int cChannels::Modified(void) bool cChannels::ModifiedByUser(int &State) const
{ {
int Result = modified; int Result = State != modifiedByUser;
modified = CHANNELSMOD_NONE; State = modifiedByUser;
return Result; 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); 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; cChannel *NewChannel = new cChannel;
NewChannel->CopyTransponderData(Transponder); NewChannel->CopyTransponderData(Transponder);
NewChannel->SetId(Nid, Tid, Sid, Rid); NewChannel->SetId(this, Nid, Tid, Sid, Rid);
NewChannel->SetName(Name, ShortName, Provider); NewChannel->SetName(Name, ShortName, Provider);
NewChannel->SetSeen(); NewChannel->SetSeen();
Add(NewChannel); Add(NewChannel);
@ -1057,17 +1088,19 @@ cChannel *cChannels::NewChannel(const cChannel *Transponder, const char *Name, c
#define CHANNELMARKOBSOLETE "OBSOLETE" #define CHANNELMARKOBSOLETE "OBSOLETE"
#define CHANNELTIMEOBSOLETE 3600 // seconds to wait before declaring a channel obsolete (in case it has actually been seen before) #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)) { bool ChannelsModified = false;
if (time(NULL) - channel->Seen() > CHANNELTIMEOBSOLETE && channel->Source() == Source && channel->Nid() == Nid && channel->Tid() == Tid && channel->Rid() == 0) { 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; bool OldShowChannelNamesWithSource = Setup.ShowChannelNamesWithSource;
Setup.ShowChannelNamesWithSource = false; Setup.ShowChannelNamesWithSource = false;
if (!endswith(channel->Name(), CHANNELMARKOBSOLETE)) if (!endswith(Channel->Name(), CHANNELMARKOBSOLETE))
channel->SetName(cString::sprintf("%s %s", channel->Name(), CHANNELMARKOBSOLETE), channel->ShortName(), cString::sprintf("%s %s", CHANNELMARKOBSOLETE, channel->Provider())); ChannelsModified |= Channel->SetName(cString::sprintf("%s %s", Channel->Name(), CHANNELMARKOBSOLETE), Channel->ShortName(), cString::sprintf("%s %s", CHANNELMARKOBSOLETE, Channel->Provider()));
Setup.ShowChannelNamesWithSource = OldShowChannelNamesWithSource; Setup.ShowChannelNamesWithSource = OldShowChannelNamesWithSource;
} }
} }
return ChannelsModified;
} }
cString ChannelString(const cChannel *Channel, int Number) cString ChannelString(const cChannel *Channel, int Number)

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __CHANNELS_H
@ -28,10 +28,6 @@
#define CHANNELMOD_LANGS 0x40 #define CHANNELMOD_LANGS 0x40
#define CHANNELMOD_RETUNE (CHANNELMOD_PIDS | CHANNELMOD_CA | CHANNELMOD_TRANSP) #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 MAXAPIDS 32 // audio
#define MAXDPIDS 16 // dolby (AC3 + DTS) #define MAXDPIDS 16 // dolby (AC3 + DTS)
#define MAXSPIDS 32 // subtitles #define MAXSPIDS 32 // subtitles
@ -86,6 +82,7 @@ class cLinkChannels : public cList<cLinkChannel> {
}; };
class cSchedule; class cSchedule;
class cChannels;
class cChannel : public cListObject { class cChannel : public cListObject {
friend class cSchedules; friend class cSchedules;
@ -128,7 +125,7 @@ private:
mutable cString nameSource; mutable cString nameSource;
mutable cString shortNameSource; mutable cString shortNameSource;
cString parameters; cString parameters;
int modification; mutable int modification;
time_t seen; // When this channel was last seen in the SDT of its transponder time_t seen; // When this channel was last seen in the SDT of its transponder
mutable const cSchedule *schedule; mutable const cSchedule *schedule;
cLinkChannels *linkChannels; cLinkChannels *linkChannels;
@ -188,66 +185,84 @@ public:
bool IsTerr(void) const { return cSource::IsTerr(source); } bool IsTerr(void) const { return cSource::IsTerr(source); }
bool IsSourceType(char Source) const { return cSource::IsType(source, 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); } tChannelID GetChannelID(void) const { return tChannelID(source, nid, (nid || tid) ? tid : Transponder(), sid, rid); }
bool HasTimer(void) const; int Modification(int Mask = CHANNELMOD_ALL) const;
int Modification(int Mask = CHANNELMOD_ALL); time_t Seen(void) const { return seen; }
time_t Seen(void) { return seen; }
void CopyTransponderData(const cChannel *Channel); void CopyTransponderData(const cChannel *Channel);
bool SetTransponderData(int Source, int Frequency, int Srate, const char *Parameters, bool Quiet = false); bool SetTransponderData(int Source, int Frequency, int Srate, const char *Parameters, bool Quiet = false);
void SetSource(int Source); bool SetSource(int Source);
void SetId(int Nid, int Tid, int Sid, int Rid = 0); bool SetId(cChannels *Channels, int Nid, int Tid, int Sid, int Rid = 0);
void SetLcn(int Lcn); bool SetLcn(int Lcn);
void SetName(const char *Name, const char *ShortName, const char *Provider); bool SetName(const char *Name, const char *ShortName, const char *Provider);
void SetPortalName(const char *PortalName); bool 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); 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);
void SetCaIds(const int *CaIds); // list must be zero-terminated bool SetCaIds(const int *CaIds); // list must be zero-terminated
void SetCaDescriptors(int Level); bool SetCaDescriptors(int Level);
void SetLinkChannels(cLinkChannels *LinkChannels); bool SetLinkChannels(cLinkChannels *LinkChannels);
void SetRefChannel(cChannel *RefChannel); 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 SetSeen(void);
void DelLinkChannel(cChannel *LinkChannel);
}; };
class cChannels : public cRwLock, public cConfig<cChannel> { class cChannels : public cConfig<cChannel> {
private: private:
int maxNumber; static cChannels channels;
int maxChannelNameLength; static int maxNumber;
int maxShortChannelNameLength; static int maxChannelNameLength;
int modified; static int maxShortChannelNameLength;
int beingEdited; int modifiedByUser;
cHash<cChannel> channelsHashSid; cHash<cChannel> channelsHashSid;
void DeleteDuplicateChannels(void); void DeleteDuplicateChannels(void);
public: public:
cChannels(void); 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 HashChannel(cChannel *Channel);
void UnhashChannel(cChannel *Channel); void UnhashChannel(cChannel *Channel);
int GetNextGroup(int Idx); // Get next channel group int GetNextGroup(int Idx) const; ///< Get next channel group
int GetPrevGroup(int Idx); // Get previous channel group int GetPrevGroup(int Idx) const; ///< Get previous channel group
int GetNextNormal(int Idx); // Get next normal channel (not group) int GetNextNormal(int Idx) const; ///< Get next normal channel (not group)
int GetPrevNormal(int Idx); // Get previous 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 ReNumber(void); ///< Recalculate 'number' based on channel type
cChannel *GetByNumber(int Number, int SkipGap = 0); void Del(cChannel *Channel); ///< Delete the given Channel from the list
cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID); const cChannel *GetByNumber(int Number, int SkipGap = 0) const;
cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false); cChannel *GetByNumber(int Number, int SkipGap = 0) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByNumber(Number, SkipGap)); }
cChannel *GetByTransponderID(tChannelID ChannelID); const cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID) const;
int BeingEdited(void) { return beingEdited; } cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByServiceID(Source, Transponder, ServiceID)); }
void IncBeingEdited(void) { beingEdited++; } const cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false) const;
void DecBeingEdited(void) { beingEdited--; } cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByChannelID(ChannelID, TryWithoutRid, TryWithoutPolarization)); }
bool HasUniqueChannelID(cChannel *NewChannel, cChannel *OldChannel = NULL); const cChannel *GetByTransponderID(tChannelID ChannelID) const;
bool SwitchTo(int Number); cChannel *GetByTransponderID(tChannelID ChannelID) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByTransponderID(ChannelID)); }
int MaxNumber(void) { return maxNumber; } bool HasUniqueChannelID(const cChannel *NewChannel, const cChannel *OldChannel = NULL) const;
int MaxChannelNameLength(void); bool SwitchTo(int Number) const;
int MaxShortChannelNameLength(void); static int MaxNumber(void) { return maxNumber; }
void SetModified(bool ByUser = false); static int MaxChannelNameLength(void);
int Modified(void); static int MaxShortChannelNameLength(void);
///< Returns 0 if no channels have been modified, 1 if an automatic void SetModifiedByUser(void);
///< modification has been made, and 2 if the user has made a modification. bool ModifiedByUser(int &State) const;
///< Calling this function resets the 'modified' flag to 0. ///< 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); 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); cString ChannelString(const cChannel *Channel, int Number);

5
ci.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "ci.h"
@ -1921,7 +1921,8 @@ void cCamSlot::StartActivation(void)
cMutexLock MutexLock(&mutex); cMutexLock MutexLock(&mutex);
if (!caActivationReceiver) { if (!caActivationReceiver) {
if (cDevice *d = Device()) { 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); caActivationReceiver = new cCaActivationReceiver(Channel, this);
d->AttachReceiver(caActivationReceiver); d->AttachReceiver(caActivationReceiver);
dsyslog("CAM %d: activating on device %d with channel %d (%s)", SlotNumber(), d->DeviceNumber() + 1, Channel->Number(), Channel->Name()); dsyslog("CAM %d: activating on device %d with channel %d (%s)", SlotNumber(), d->DeviceNumber() + 1, Channel->Number(), Channel->Name());

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __CONFIG_H
@ -110,7 +110,7 @@ private:
cList<T>::Clear(); cList<T>::Clear();
} }
public: public:
cConfig(void) { fileName = NULL; } cConfig(const char *NeedsLocking = NULL): cList<T>(NeedsLocking) { fileName = NULL; }
virtual ~cConfig() { free(fileName); } virtual ~cConfig() { free(fileName); }
const char *FileName(void) { return fileName; } const char *FileName(void) { return fileName; }
bool Load(const char *FileName = NULL, bool AllowComments = false, bool MustExist = false) 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); fprintf(stderr, "vdr: error while reading '%s'\n", fileName);
return result; return result;
} }
bool Save(void) bool Save(void) const
{ {
bool result = true; bool result = true;
T *l = (T *)this->First(); T *l = (T *)this->First();

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "cutter.h"
@ -634,7 +634,6 @@ void cCuttingThread::Action(void)
} }
} }
} }
Recordings.TouchUpdate();
} }
else else
esyslog("no editing marks found!"); esyslog("no editing marks found!");
@ -678,7 +677,8 @@ bool cCutter::Start(void)
cRecordingUserCommand::InvokeCommand(RUC_EDITINGRECORDING, editedVersionName, originalVersionName); cRecordingUserCommand::InvokeCommand(RUC_EDITINGRECORDING, editedVersionName, originalVersionName);
if (cVideoDirectory::RemoveVideoFile(editedVersionName) && MakeDirs(editedVersionName, true)) { if (cVideoDirectory::RemoveVideoFile(editedVersionName) && MakeDirs(editedVersionName, true)) {
Recording.WriteInfo(editedVersionName); Recording.WriteInfo(editedVersionName);
Recordings.AddByName(editedVersionName, false); LOCK_RECORDINGS_WRITE;
Recordings->AddByName(editedVersionName, false);
cuttingThread = new cCuttingThread(originalVersionName, editedVersionName); cuttingThread = new cCuttingThread(originalVersionName, editedVersionName);
return true; return true;
} }
@ -703,7 +703,8 @@ void cCutter::Stop(void)
if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), editedVersionName) == 0) if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), editedVersionName) == 0)
cControl::Shutdown(); cControl::Shutdown();
cVideoDirectory::RemoveVideoFile(editedVersionName); cVideoDirectory::RemoveVideoFile(editedVersionName);
Recordings.DelByName(editedVersionName); LOCK_RECORDINGS_WRITE;
Recordings->DelByName(editedVersionName);
} }
} }

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #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 cControl::Shutdown(); // prevents old channel from being shown too long if GetDevice() takes longer
int n = CurrentChannel() + Direction; int n = CurrentChannel() + Direction;
int first = n; int first = n;
cChannel *channel; LOCK_CHANNELS_READ;
while ((channel = Channels.GetByNumber(n, Direction)) != NULL) { const cChannel *Channel;
while ((Channel = Channels->GetByNumber(n, Direction)) != NULL) {
// try only channels which are currently available // try only channels which are currently available
if (GetDevice(channel, LIVEPRIORITY, true, true)) if (GetDevice(Channel, LIVEPRIORITY, true, true))
break; break;
n = channel->Number() + Direction; n = Channel->Number() + Direction;
} }
if (channel) { if (Channel) {
int d = n - first; int d = n - first;
if (abs(d) == 1) if (abs(d) == 1)
dsyslog("skipped channel %d", first); dsyslog("skipped channel %d", first);
else if (d) else if (d)
dsyslog("skipped channels %d..%d", first, n - sgn(d)); dsyslog("skipped channels %d..%d", first, n - sgn(d));
if (PrimaryDevice()->SwitchChannel(channel, true)) if (PrimaryDevice()->SwitchChannel(Channel, true))
result = true; result = true;
} }
else if (n != first) else if (n != first)
@ -777,7 +778,6 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
Result = scrNotAvailable; Result = scrNotAvailable;
} }
else { else {
Channels.Lock(false);
// Stop section handling: // Stop section handling:
if (sectionHandler) { if (sectionHandler) {
sectionHandler->SetStatus(false); sectionHandler->SetStatus(false);
@ -790,7 +790,8 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
if (SetChannelDevice(Channel, LiveView)) { if (SetChannelDevice(Channel, LiveView)) {
// Start section handling: // Start section handling:
if (sectionHandler) { if (sectionHandler) {
patFilter->Trigger(Channel->Sid()); if (patFilter)
patFilter->Trigger(Channel->Sid());
sectionHandler->SetChannel(Channel); sectionHandler->SetChannel(Channel);
sectionHandler->SetStatus(true); sectionHandler->SetStatus(true);
} }
@ -800,7 +801,6 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
} }
else else
Result = scrFailed; Result = scrFailed;
Channels.Unlock();
} }
if (Result == scrOk) { if (Result == scrOk) {
@ -829,8 +829,8 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
void cDevice::ForceTransferMode(void) void cDevice::ForceTransferMode(void)
{ {
if (!cTransferControl::ReceiverDevice()) { if (!cTransferControl::ReceiverDevice()) {
cChannel *Channel = Channels.GetByNumber(CurrentChannel()); LOCK_CHANNELS_READ;
if (Channel) if (const cChannel *Channel = Channels->GetByNumber(CurrentChannel()))
SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode
} }
} }

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "dvbplayer.h"
@ -210,7 +210,7 @@ private:
cNonBlockingFileReader *nonBlockingFileReader; cNonBlockingFileReader *nonBlockingFileReader;
cRingBufferFrame *ringBuffer; cRingBufferFrame *ringBuffer;
cPtsIndex ptsIndex; cPtsIndex ptsIndex;
cMarks *marks; const cMarks *marks;
cFileName *fileName; cFileName *fileName;
cIndexFile *index; cIndexFile *index;
cUnbufferedFile *replayFile; cUnbufferedFile *replayFile;
@ -239,7 +239,7 @@ protected:
public: public:
cDvbPlayer(const char *FileName, bool PauseLive); cDvbPlayer(const char *FileName, bool PauseLive);
virtual ~cDvbPlayer(); virtual ~cDvbPlayer();
void SetMarks(cMarks *Marks); void SetMarks(const cMarks *Marks);
bool Active(void) { return cThread::Running(); } bool Active(void) { return cThread::Running(); }
void Pause(void); void Pause(void);
void Play(void); void Play(void);
@ -311,7 +311,7 @@ cDvbPlayer::~cDvbPlayer()
// don't delete marks here, we don't own them! // don't delete marks here, we don't own them!
} }
void cDvbPlayer::SetMarks(cMarks *Marks) void cDvbPlayer::SetMarks(const cMarks *Marks)
{ {
marks = Marks; marks = Marks;
} }
@ -383,10 +383,11 @@ bool cDvbPlayer::Save(void)
int Index = ptsIndex.FindIndex(DeviceGetSTC()); int Index = ptsIndex.FindIndex(DeviceGetSTC());
if (Index >= 0) { if (Index >= 0) {
if (Setup.SkipEdited && marks) { if (Setup.SkipEdited && marks) {
marks->Lock(); cStateKey StateKey;
marks->Lock(StateKey);
if (marks->First() && abs(Index - marks->First()->Position()) <= int(round(RESUMEBACKUP * framesPerSecond))) 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 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)); Index -= int(round(RESUMEBACKUP * framesPerSecond));
if (Index > 0) if (Index > 0)
@ -419,7 +420,8 @@ void cDvbPlayer::Action(void)
if (readIndex > 0) if (readIndex > 0)
isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond)); isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond));
else if (Setup.SkipEdited && marks) { else if (Setup.SkipEdited && marks) {
marks->Lock(); cStateKey StateKey;
marks->Lock(StateKey);
if (marks->First() && index) { if (marks->First() && index) {
int Index = marks->First()->Position(); int Index = marks->First()->Position();
uint16_t FileNumber; uint16_t FileNumber;
@ -429,7 +431,7 @@ void cDvbPlayer::Action(void)
readIndex = Index; readIndex = Index;
} }
} }
marks->Unlock(); StateKey.Remove();
} }
nonBlockingFileReader = new cNonBlockingFileReader; nonBlockingFileReader = new cNonBlockingFileReader;
@ -500,8 +502,9 @@ void cDvbPlayer::Action(void)
if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length) && NextFile(FileNumber, FileOffset)) { if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length) && NextFile(FileNumber, FileOffset)) {
readIndex++; readIndex++;
if ((Setup.SkipEdited || Setup.PauseAtLastMark) && marks) { if ((Setup.SkipEdited || Setup.PauseAtLastMark) && marks) {
marks->Lock(); cStateKey StateKey;
cMark *m = marks->Get(readIndex); marks->Lock(StateKey);
const cMark *m = marks->Get(readIndex);
if (m && (m->Index() & 0x01) != 0) { // we're at an end mark if (m && (m->Index() & 0x01) != 0) { // we're at an end mark
m = marks->GetNextBegin(m); m = marks->GetNextBegin(m);
int Index = -1; int Index = -1;
@ -519,7 +522,7 @@ void cDvbPlayer::Action(void)
CutIn = true; CutIn = true;
} }
} }
marks->Unlock(); StateKey.Remove();
} }
} }
else else
@ -943,7 +946,7 @@ cDvbPlayerControl::~cDvbPlayerControl()
Stop(); Stop();
} }
void cDvbPlayerControl::SetMarks(cMarks *Marks) void cDvbPlayerControl::SetMarks(const cMarks *Marks)
{ {
if (player) if (player)
player->SetMarks(Marks); player->SetMarks(Marks);

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __DVBPLAYER_H
@ -26,7 +26,7 @@ public:
// file of the recording is long enough to allow the player to display // file of the recording is long enough to allow the player to display
// the first frame in still picture mode. // the first frame in still picture mode.
virtual ~cDvbPlayerControl(); virtual ~cDvbPlayerControl();
void SetMarks(cMarks *Marks); void SetMarks(const cMarks *Marks);
bool Active(void); bool Active(void);
void Stop(void); void Stop(void);
// Stops the current replay session (if any). // Stops the current replay session (if any).

119
eit.c
View File

@ -8,7 +8,7 @@
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>. * 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>. * 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" #include "eit.h"
@ -24,31 +24,53 @@
class cEIT : public SI::EIT { class cEIT : public SI::EIT {
public: 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) :SI::EIT(Data, false)
{ {
if (!CheckCRCAndParse()) if (!CheckCRCAndParse())
return; 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); time_t Now = time(NULL);
if (Now < VALID_TIME) if (Now < VALID_TIME)
return; // we need the current time for handling PDC descriptors 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; return;
}
tChannelID channelID(Source, getOriginalNetworkId(), getTransportStreamId(), getServiceId()); tChannelID channelID(Source, getOriginalNetworkId(), getTransportStreamId(), getServiceId());
cChannel *channel = Channels.GetByChannelID(channelID, true); cChannel *Channel = Channels->GetByChannelID(channelID, true);
if (!channel || EpgHandlers.IgnoreChannel(channel)) { if (!Channel || EpgHandlers.IgnoreChannel(Channel)) {
Channels.Unlock(); ChannelsStateKey.Remove(false);
return; return;
} }
EpgHandlers.BeginSegmentTransfer(channel, OnlyRunningStatus); cStateKey SchedulesStateKey;
bool handledExternally = EpgHandlers.HandledExternally(channel); cSchedules *Schedules = cSchedules::GetSchedulesWrite(SchedulesStateKey, 10);
cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(channel, true); 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 Empty = true;
bool Modified = false; 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 *rEvent = NULL;
cEvent *pEvent = (cEvent *)pSchedule->GetEvent(SiEitEvent.getEventId(), StartTime); cEvent *pEvent = (cEvent *)pSchedule->GetEvent(SiEitEvent.getEventId(), StartTime);
if (!pEvent || handledExternally) { if (!pEvent || handledExternally) {
if (OnlyRunningStatus)
continue;
if (handledExternally && !EpgHandlers.IsUpdate(SiEitEvent.getEventId(), StartTime, Tid, getVersionNumber())) if (handledExternally && !EpgHandlers.IsUpdate(SiEitEvent.getEventId(), StartTime, Tid, getVersionNumber()))
continue; continue;
// If we don't have that event yet, we create a new one. // 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. // The lower the table ID, the more "current" the information.
if (Tid > TableID) if (Tid > TableID)
continue; 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.SetEventID(pEvent, SiEitEvent.getEventId()); // unfortunately some stations use different event ids for the same event in different tables :-(
EpgHandlers.SetStartTime(pEvent, StartTime); EpgHandlers.SetStartTime(pEvent, StartTime);
EpgHandlers.SetDuration(pEvent, Duration); 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); pEvent->SetTableID(Tid);
if (Tid == 0x4E) { // we trust only the present/following info on the actual TS if (Tid == 0x4E) { // we trust only the present/following info on the actual TS
if (SiEitEvent.getRunningStatus() >= SI::RunningStatusNotRunning) if (SiEitEvent.getRunningStatus() >= SI::RunningStatusNotRunning)
pSchedule->SetRunningStatus(pEvent, SiEitEvent.getRunningStatus(), channel); pSchedule->SetRunningStatus(pEvent, SiEitEvent.getRunningStatus(), Channel);
} if (!Process)
if (OnlyRunningStatus) { continue;
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
} }
pEvent->SetVersion(getVersionNumber()); pEvent->SetVersion(getVersionNumber());
@ -206,7 +216,7 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
break; break;
case SI::TimeShiftedEventDescriptorTag: { case SI::TimeShiftedEventDescriptorTag: {
SI::TimeShiftedEventDescriptor *tsed = (SI::TimeShiftedEventDescriptor *)d; 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) if (!rSchedule)
break; break;
rEvent = (cEvent *)rSchedule->GetEvent(tsed->getReferenceEventId()); 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]; char linkName[ld->privateData.getLength() + 1];
strn0cpy(linkName, (const char *)ld->privateData.getData(), sizeof(linkName)); strn0cpy(linkName, (const char *)ld->privateData.getData(), sizeof(linkName));
// TODO is there a standard way to determine the character set of this string? // TODO is there a standard way to determine the character set of this string?
cChannel *link = Channels.GetByChannelID(linkID); cChannel *link = Channels->GetByChannelID(linkID);
if (link != channel) { // only link to other channels, not the same one 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
if (link) { if (link) {
if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3) if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3)
link->SetName(linkName, "", ""); ChannelsModified |= link->SetName(linkName, "", "");
} }
else if (Setup.UpdateChannels >= 4) { else if (Setup.UpdateChannels >= 4) {
cChannel *transponder = channel; cChannel *Transponder = Channel;
if (channel->Tid() != ld->getTransportStreamId()) if (Channel->Tid() != ld->getTransportStreamId())
transponder = Channels.GetByTransponderID(linkID); Transponder = Channels->GetByTransponderID(linkID);
link = Channels.NewChannel(transponder, linkName, "", "", ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId()); link = Channels->NewChannel(Transponder, linkName, "", "", ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId());
ChannelsModified = true;
//XXX patFilter->Trigger(); //XXX patFilter->Trigger();
} }
if (link) { if (link) {
@ -247,7 +257,7 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
} }
} }
else 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); EpgHandlers.FixEpgBugs(pEvent);
if (LinkChannels) if (LinkChannels)
channel->SetLinkChannels(LinkChannels); ChannelsModified |= Channel->SetLinkChannels(LinkChannels);
Modified = true; Modified = true;
EpgHandlers.HandleEvent(pEvent); EpgHandlers.HandleEvent(pEvent);
if (handledExternally) if (handledExternally)
@ -302,16 +312,17 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
if (Tid == 0x4E) { if (Tid == 0x4E) {
if (Empty && getSectionNumber() == 0) if (Empty && getSectionNumber() == 0)
// ETR 211: an empty entry in section 0 of table 0x4E means there is currently no event running // 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(); pSchedule->SetPresentSeen();
} }
if (Modified && !OnlyRunningStatus) { if (Modified) {
EpgHandlers.SortSchedule(pSchedule); EpgHandlers.SortSchedule(pSchedule);
EpgHandlers.DropOutdated(pSchedule, SegmentStart, SegmentEnd, Tid, getVersionNumber()); EpgHandlers.DropOutdated(pSchedule, SegmentStart, SegmentEnd, Tid, getVersionNumber());
Schedules->SetModified(pSchedule); pSchedule->SetModified();
} }
Channels.Unlock(); SchedulesStateKey.Remove(Modified);
EpgHandlers.EndSegmentTransfer(Modified, OnlyRunningStatus); ChannelsStateKey.Remove(ChannelsModified);
EpgHandlers.EndSegmentTransfer(Modified);
} }
// --- cTDT ------------------------------------------------------------------ // --- cTDT ------------------------------------------------------------------
@ -372,6 +383,13 @@ cEitFilter::cEitFilter(void)
Set(0x14, 0x70); // TDT Set(0x14, 0x70); // TDT
} }
void cEitFilter::SetStatus(bool On)
{
cMutexLock MutexLock(&mutex);
cFilter::SetStatus(On);
sectionSyncerHash.Clear();
}
void cEitFilter::SetDisableUntil(time_t Time) void cEitFilter::SetDisableUntil(time_t Time)
{ {
disableUntil = 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) void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
{ {
cMutexLock MutexLock(&mutex);
if (disableUntil) { if (disableUntil) {
if (time(NULL) > disableUntil) if (time(NULL) > disableUntil)
disableUntil = 0; disableUntil = 0;
@ -387,22 +406,8 @@ void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
} }
switch (Pid) { switch (Pid) {
case 0x12: { case 0x12: {
if (Tid >= 0x4E && Tid <= 0x6F) { if (Tid >= 0x4E && Tid <= 0x6F)
cSchedulesLock SchedulesLock(true, 10); cEIT EIT(sectionSyncerHash, Source(), Tid, Data);
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);
}
}
} }
break; break;
case 0x14: { case 0x14: {

10
eit.h
View File

@ -4,21 +4,29 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __EIT_H
#define __EIT_H #define __EIT_H
#include "filter.h" #include "filter.h"
#include "tools.h"
class cSectionSyncerEntry : public cListObject, public cSectionSyncer {};
class cSectionSyncerHash : public cHash<cSectionSyncerEntry> {};
class cEitFilter : public cFilter { class cEitFilter : public cFilter {
private: private:
cMutex mutex;
cSectionSyncerHash sectionSyncerHash;
static time_t disableUntil; static time_t disableUntil;
protected: protected:
virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length); virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
public: public:
cEitFilter(void); cEitFilter(void);
virtual void SetStatus(bool On);
static void SetDisableUntil(time_t Time); static void SetDisableUntil(time_t Time);
}; };

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "eitscan.h"
@ -45,13 +45,13 @@ int cScanData::Compare(const cListObject &ListObject) const
class cScanList : public cList<cScanData> { class cScanList : public cList<cScanData> {
public: public:
void AddTransponders(cList<cChannel> *Channels); void AddTransponders(const cList<cChannel> *Channels);
void AddTransponder(const cChannel *Channel); 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); AddTransponder(ch);
Sort(); Sort();
} }
@ -118,7 +118,8 @@ void cEITScanner::ForceScan(void)
void cEITScanner::Activity(void) void cEITScanner::Activity(void)
{ {
if (currentChannel) { if (currentChannel) {
Channels.SwitchTo(currentChannel); LOCK_CHANNELS_READ;
Channels->SwitchTo(currentChannel);
currentChannel = 0; currentChannel = 0;
} }
lastActivity = time(NULL); lastActivity = time(NULL);
@ -129,7 +130,8 @@ void cEITScanner::Process(void)
if (Setup.EPGScanTimeout || !lastActivity) { // !lastActivity means a scan was forced if (Setup.EPGScanTimeout || !lastActivity) { // !lastActivity means a scan was forced
time_t now = time(NULL); time_t now = time(NULL);
if (now - lastScan > ScanTimeout && now - lastActivity > ActivityTimeout) { if (now - lastScan > ScanTimeout && now - lastActivity > ActivityTimeout) {
if (Channels.Lock(false, 10)) { cStateKey StateKey;
if (const cChannels *Channels = cChannels::GetChannelsRead(StateKey, 10)) {
if (!scanList) { if (!scanList) {
scanList = new cScanList; scanList = new cScanList;
if (transponderList) { if (transponderList) {
@ -137,7 +139,7 @@ void cEITScanner::Process(void)
delete transponderList; delete transponderList;
transponderList = NULL; transponderList = NULL;
} }
scanList->AddTransponders(&Channels); scanList->AddTransponders(Channels);
} }
bool AnyDeviceSwitched = false; bool AnyDeviceSwitched = false;
for (int i = 0; i < cDevice::NumDevices(); i++) { for (int i = 0; i < cDevice::NumDevices(); i++) {
@ -177,7 +179,7 @@ void cEITScanner::Process(void)
if (lastActivity == 0) // this was a triggered scan if (lastActivity == 0) // this was a triggered scan
Activity(); Activity();
} }
Channels.Unlock(); StateKey.Remove();
} }
lastScan = time(NULL); lastScan = time(NULL);
} }

270
epg.c
View File

@ -7,7 +7,7 @@
* Original version (as used in VDR before 1.3.0) written by * Original version (as used in VDR before 1.3.0) written by
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>. * 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" #include "epg.h"
@ -15,7 +15,6 @@
#include <limits.h> #include <limits.h>
#include <time.h> #include <time.h>
#include "libsi/si.h" #include "libsi/si.h"
#include "timers.h"
#define RUNNINGSTATUSTIMEOUT 30 // seconds before the running status is considered unknown #define RUNNINGSTATUSTIMEOUT 30 // seconds before the running status is considered unknown
#define EPGDATAWRITEDELTA 600 // seconds between writing the epg.data file #define EPGDATAWRITEDELTA 600 // seconds between writing the epg.data file
@ -111,9 +110,12 @@ tComponent *cComponents::GetComponent(int Index, uchar Stream, uchar Type)
// --- cEvent ---------------------------------------------------------------- // --- cEvent ----------------------------------------------------------------
cMutex cEvent::numTimersMutex;
cEvent::cEvent(tEventID EventID) cEvent::cEvent(tEventID EventID)
{ {
schedule = NULL; schedule = NULL;
numTimers = 0;
eventID = EventID; eventID = EventID;
tableID = 0xFF; // actual table ids are 0x4E..0x60 tableID = 0xFF; // actual table ids are 0x4E..0x60
version = 0xFF; // actual version numbers are 0..31 version = 0xFF; // actual version numbers are 0..31
@ -170,9 +172,9 @@ void cEvent::SetVersion(uchar Version)
version = 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); isyslog("channel %d (%s) event %s status %d", Channel->Number(), Channel->Name(), *ToDescr(), RunningStatus);
runningStatus = 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()); 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)) { numTimersMutex.Lock();
if (t->Event() == this) numTimers++;
return true; if (schedule)
} schedule->IncNumTimers();
return false; numTimersMutex.Unlock();
}
void cEvent::DecNumTimers(void) const
{
numTimersMutex.Lock();
numTimers--;
if (schedule)
schedule->DecNumTimers();
numTimersMutex.Unlock();
} }
bool cEvent::IsRunning(bool OrAboutToStart) const bool cEvent::IsRunning(bool OrAboutToStart) const
@ -605,9 +616,9 @@ void ReportEpgBugFixStats(bool Force)
bool PrintedStats = false; bool PrintedStats = false;
char *q = buffer; char *q = buffer;
*buffer = 0; *buffer = 0;
LOCK_CHANNELS_READ;
for (int c = 0; c < p->n; c++) { for (int c = 0; c < p->n; c++) {
cChannel *channel = Channels.GetByChannelID(p->channelIDs[c], true); if (const cChannel *Channel = Channels->GetByChannelID(p->channelIDs[c], true)) {
if (channel) {
if (!GotHits) { if (!GotHits) {
dsyslog("====================="); dsyslog("=====================");
dsyslog("EPG bugfix statistics"); dsyslog("EPG bugfix statistics");
@ -623,7 +634,7 @@ void ReportEpgBugFixStats(bool Force)
q += snprintf(q, sizeof(buffer) - (q - buffer), "%-3d %-4d", i, p->hits); q += snprintf(q, sizeof(buffer) - (q - buffer), "%-3d %-4d", i, p->hits);
PrintedStats = true; 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 = ", "; delim = ", ";
if (q - buffer > 80) { if (q - buffer > 80) {
q += snprintf(q, sizeof(buffer) - (q - buffer), "%s...", delim); q += snprintf(q, sizeof(buffer) - (q - buffer), "%s...", delim);
@ -878,14 +889,32 @@ Final:
// --- cSchedule ------------------------------------------------------------- // --- cSchedule -------------------------------------------------------------
cMutex cSchedule::numTimersMutex;
cSchedule::cSchedule(tChannelID ChannelID) cSchedule::cSchedule(tChannelID ChannelID)
{ {
channelID = ChannelID; channelID = ChannelID;
events.SetUseGarbageCollector();
numTimers = 0;
hasRunning = false; hasRunning = false;
modified = 0; modified = 0;
presentSeen = 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) cEvent *cSchedule::AddEvent(cEvent *Event)
{ {
events.Add(Event); events.Add(Event);
@ -897,8 +926,6 @@ cEvent *cSchedule::AddEvent(cEvent *Event)
void cSchedule::DelEvent(cEvent *Event) void cSchedule::DelEvent(cEvent *Event)
{ {
if (Event->schedule == this) { if (Event->schedule == this) {
if (hasRunning && Event->IsRunning())
ClrRunningStatus();
UnhashEvent(Event); UnhashEvent(Event);
events.Del(Event); events.Del(Event);
} }
@ -922,7 +949,7 @@ const cEvent *cSchedule::GetPresentEvent(void) const
{ {
const cEvent *pe = NULL; const cEvent *pe = NULL;
time_t now = time(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) if (p->StartTime() <= now)
pe = p; pe = p;
else if (p->StartTime() > now + 3600) else if (p->StartTime() > now + 3600)
@ -962,7 +989,7 @@ const cEvent *cSchedule::GetEventAround(time_t Time) const
{ {
const cEvent *pe = NULL; const cEvent *pe = NULL;
time_t delta = INT_MAX; 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(); time_t dt = Time - p->StartTime();
if (dt >= 0 && dt < delta && p->EndTime() >= Time) { if (dt >= 0 && dt < delta && p->EndTime() >= Time) {
delta = dt; delta = dt;
@ -972,7 +999,7 @@ const cEvent *cSchedule::GetEventAround(time_t Time) const
return pe; return pe;
} }
void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Channel) void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, const cChannel *Channel)
{ {
hasRunning = false; hasRunning = false;
for (cEvent *p = events.First(); p; p = events.Next(p)) { 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) if (p->RunningStatus() >= SI::RunningStatusPausing)
hasRunning = true; hasRunning = true;
} }
SetPresentSeen();
} }
void cSchedule::ClrRunningStatus(cChannel *Channel) void cSchedule::ClrRunningStatus(cChannel *Channel)
@ -1019,32 +1047,29 @@ void cSchedule::Sort(void)
p->SetRunningStatus(SI::RunningStatusNotRunning); p->SetRunningStatus(SI::RunningStatusNotRunning);
} }
} }
SetModified();
} }
void cSchedule::DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version) void cSchedule::DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
{ {
if (SegmentStart > 0 && SegmentEnd > 0) { if (SegmentStart > 0 && SegmentEnd > 0) {
for (cEvent *p = events.First(); p; p = events.Next(p)) { cEvent *p = events.First();
if (p->EndTime() > SegmentStart) { while (p) {
if (p->StartTime() < SegmentEnd) { cEvent *n = events.Next(p);
// The event overlaps with the given time segment. if (p->EndTime() > SegmentStart) {
if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) { if (p->StartTime() < SegmentEnd) {
// The segment overwrites all events from tables with higher ids, and // The event overlaps with the given time segment.
// within the same table id all events must have the same version. if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) {
// We can't delete the event right here because a timer might have // The segment overwrites all events from tables with higher ids, and
// a pointer to it, so let's set its id and start time to 0 to have it // within the same table id all events must have the same version.
// "phased out": DelEvent(p);
if (hasRunning && p->IsRunning()) }
ClrRunningStatus(); }
UnhashEvent(p); else
p->eventID = 0; break;
p->startTime = 0; }
} p = n;
} }
else
break;
}
}
} }
} }
@ -1066,9 +1091,9 @@ void cSchedule::Cleanup(time_t Time)
void cSchedule::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) const void cSchedule::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) const
{ {
cChannel *channel = Channels.GetByChannelID(channelID, true); LOCK_CHANNELS_READ;
if (channel) { if (const cChannel *Channel = Channels->GetByChannelID(channelID, true)) {
fprintf(f, "%sC %s %s\n", Prefix, *channel->GetChannelID().ToString(), channel->Name()); fprintf(f, "%sC %s %s\n", Prefix, *Channel->GetChannelID().ToString(), Channel->Name());
const cEvent *p; const cEvent *p;
switch (DumpMode) { switch (DumpMode) {
case dmAll: { case dmAll: {
@ -1111,12 +1136,10 @@ bool cSchedule::Read(FILE *f, cSchedules *Schedules)
if (*s) { if (*s) {
tChannelID channelID = tChannelID::FromString(s); tChannelID channelID = tChannelID::FromString(s);
if (channelID.Valid()) { if (channelID.Valid()) {
cSchedule *p = Schedules->AddSchedule(channelID); if (cSchedule *p = Schedules->AddSchedule(channelID)) {
if (p) {
if (!cEvent::Read(f, p)) if (!cEvent::Read(f, p))
return false; return false;
p->Sort(); p->Sort();
Schedules->SetModified(p);
} }
} }
else { else {
@ -1164,12 +1187,12 @@ void cEpgDataWriter::Perform(void)
{ {
cMutexLock MutexLock(&mutex); // to make sure fore- and background calls don't cause parellel dumps! cMutexLock MutexLock(&mutex); // to make sure fore- and background calls don't cause parellel dumps!
{ {
cSchedulesLock SchedulesLock(true, 1000); cStateKey StateKey;
cSchedules *s = (cSchedules *)cSchedules::Schedules(SchedulesLock); if (cSchedules *Schedules = cSchedules::GetSchedulesWrite(StateKey, 1000)) {
if (s) {
time_t now = time(NULL); 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); p->Cleanup(now);
StateKey.Remove();
} }
} }
if (dump) if (dump)
@ -1178,29 +1201,25 @@ void cEpgDataWriter::Perform(void)
static cEpgDataWriter EpgDataWriter; static cEpgDataWriter EpgDataWriter;
// --- cSchedulesLock --------------------------------------------------------
cSchedulesLock::cSchedulesLock(bool WriteLock, int TimeoutMs)
{
locked = cSchedules::schedules.rwlock.Lock(WriteLock, TimeoutMs);
}
cSchedulesLock::~cSchedulesLock()
{
if (locked)
cSchedules::schedules.rwlock.Unlock();
}
// --- cSchedules ------------------------------------------------------------ // --- cSchedules ------------------------------------------------------------
cSchedules cSchedules::schedules; cSchedules cSchedules::schedules;
char *cSchedules::epgDataFileName = NULL; char *cSchedules::epgDataFileName = NULL;
time_t cSchedules::lastDump = time(NULL); time_t cSchedules::lastDump = time(NULL);
time_t cSchedules::modified = 0;
const cSchedules *cSchedules::Schedules(cSchedulesLock &SchedulesLock) cSchedules::cSchedules(void)
:cList<cSchedule>("Schedules")
{ {
return SchedulesLock.Locked() ? &schedules : NULL; }
const cSchedules *cSchedules::GetSchedulesRead(cStateKey &StateKey, int TimeoutMs)
{
return schedules.Lock(StateKey, false, TimeoutMs) ? &schedules : NULL;
}
cSchedules *cSchedules::GetSchedulesWrite(cStateKey &StateKey, int TimeoutMs)
{
return schedules.Lock(StateKey, true, TimeoutMs) ? &schedules : NULL;
} }
void cSchedules::SetEpgDataFileName(const char *FileName) void cSchedules::SetEpgDataFileName(const char *FileName)
@ -1210,12 +1229,6 @@ void cSchedules::SetEpgDataFileName(const char *FileName)
EpgDataWriter.SetDump(epgDataFileName != NULL); EpgDataWriter.SetDump(epgDataFileName != NULL);
} }
void cSchedules::SetModified(cSchedule *Schedule)
{
Schedule->SetModified();
modified = time(NULL);
}
void cSchedules::Cleanup(bool Force) void cSchedules::Cleanup(bool Force)
{ {
if (Force) if (Force)
@ -1232,83 +1245,59 @@ void cSchedules::Cleanup(bool Force)
void cSchedules::ResetVersions(void) void cSchedules::ResetVersions(void)
{ {
cSchedulesLock SchedulesLock(true); LOCK_SCHEDULES_WRITE;
cSchedules *s = (cSchedules *)Schedules(SchedulesLock); for (cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->Next(Schedule))
if (s) { Schedule->ResetVersions();
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;
} }
bool cSchedules::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) bool cSchedules::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime)
{ {
cSchedulesLock SchedulesLock; cSafeFile *sf = NULL;
cSchedules *s = (cSchedules *)Schedules(SchedulesLock); if (!f) {
if (s) { sf = new cSafeFile(epgDataFileName);
cSafeFile *sf = NULL; if (sf->Open())
if (!f) { f = *sf;
sf = new cSafeFile(epgDataFileName); else {
if (sf->Open()) LOG_ERROR;
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();
delete sf; 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) bool cSchedules::Read(FILE *f)
{ {
cSchedulesLock SchedulesLock(true, 1000); bool OwnFile = f == NULL;
cSchedules *s = (cSchedules *)Schedules(SchedulesLock); if (OwnFile) {
if (s) { if (epgDataFileName && access(epgDataFileName, R_OK) == 0) {
bool OwnFile = f == NULL; dsyslog("reading EPG data from %s", epgDataFileName);
if (OwnFile) { if ((f = fopen(epgDataFileName, "r")) == NULL) {
if (epgDataFileName && access(epgDataFileName, R_OK) == 0) { LOG_ERROR;
dsyslog("reading EPG data from %s", epgDataFileName);
if ((f = fopen(epgDataFileName, "r")) == NULL) {
LOG_ERROR;
return false;
}
}
else
return false; return false;
}
} }
bool result = cSchedule::Read(f, s); else
if (OwnFile) return false;
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;
} }
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) cSchedule *cSchedules::AddSchedule(tChannelID ChannelID)
@ -1318,9 +1307,6 @@ cSchedule *cSchedules::AddSchedule(tChannelID ChannelID)
if (!p) { if (!p) {
p = new cSchedule(ChannelID); p = new cSchedule(ChannelID);
Add(p); Add(p);
cChannel *channel = Channels.GetByChannelID(ChannelID);
if (channel)
channel->schedule = p;
} }
return p; return p;
} }
@ -1328,7 +1314,7 @@ cSchedule *cSchedules::AddSchedule(tChannelID ChannelID)
const cSchedule *cSchedules::GetSchedule(tChannelID ChannelID) const const cSchedule *cSchedules::GetSchedule(tChannelID ChannelID) const
{ {
ChannelID.ClrRid(); ChannelID.ClrRid();
for (cSchedule *p = First(); p; p = Next(p)) { for (const cSchedule *p = First(); p; p = Next(p)) {
if (p->ChannelID() == ChannelID) if (p->ChannelID() == ChannelID)
return p; return p;
} }
@ -1541,18 +1527,18 @@ void cEpgHandlers::DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t
Schedule->DropOutdated(SegmentStart, SegmentEnd, TableID, Version); 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)) { for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
if (eh->BeginSegmentTransfer(Channel, OnlyRunningStatus)) if (eh->BeginSegmentTransfer(Channel, false))
return; return;
} }
} }
void cEpgHandlers::EndSegmentTransfer(bool Modified, bool OnlyRunningStatus) void cEpgHandlers::EndSegmentTransfer(bool Modified)
{ {
for (cEpgHandler *eh = First(); eh; eh = Next(eh)) { for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
if (eh->EndSegmentTransfer(Modified, OnlyRunningStatus)) if (eh->EndSegmentTransfer(Modified, false))
return; return;
} }
} }

72
epg.h
View File

@ -7,7 +7,7 @@
* Original version (as used in VDR before 1.3.0) written by * Original version (as used in VDR before 1.3.0) written by
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>. * 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 #ifndef __EPG_H
@ -66,13 +66,15 @@ public:
class cSchedule; class cSchedule;
typedef u_int32_t tEventID; typedef u_int16_t tEventID;
class cEvent : public cListObject { class cEvent : public cListObject {
friend class cSchedule; friend class cSchedule;
private: 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! // The sequence of these parameters is optimized for minimal memory waste!
cSchedule *schedule; // The Schedule this event belongs to 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 tEventID eventID; // Event ID of this event
uchar tableID; // Table ID this event came from uchar tableID; // Table ID this event came from
uchar version; // Version number of section 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 Vps(void) const { return vps; }
time_t Seen(void) const { return seen; } time_t Seen(void) const { return seen; }
bool SeenWithin(int Seconds) const { return time(NULL) - seen < Seconds; } 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; bool IsRunning(bool OrAboutToStart = false) const;
static const char *ContentToString(uchar Content); static const char *ContentToString(uchar Content);
cString GetParentalRatingString(void) const; cString GetParentalRatingString(void) const;
@ -120,7 +124,7 @@ public:
void SetEventID(tEventID EventID); void SetEventID(tEventID EventID);
void SetTableID(uchar TableID); void SetTableID(uchar TableID);
void SetVersion(uchar Version); 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 SetTitle(const char *Title);
void SetShortText(const char *ShortText); void SetShortText(const char *ShortText);
void SetDescription(const char *Description); void SetDescription(const char *Description);
@ -142,28 +146,33 @@ class cSchedules;
class cSchedule : public cListObject { class cSchedule : public cListObject {
private: private:
static cMutex numTimersMutex; // Protects numTimers, because it might be accessed from parallel read locks
tChannelID channelID; tChannelID channelID;
cList<cEvent> events; cList<cEvent> events;
cHash<cEvent> eventsHashID; cHash<cEvent> eventsHashID;
cHash<cEvent> eventsHashStartTime; cHash<cEvent> eventsHashStartTime;
mutable u_int16_t numTimers;// The number of timers that use this schedule
bool hasRunning; bool hasRunning;
time_t modified; int modified;
time_t presentSeen; time_t presentSeen;
public: public:
cSchedule(tChannelID ChannelID); cSchedule(tChannelID ChannelID);
tChannelID ChannelID(void) const { return 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; } time_t PresentSeen(void) const { return presentSeen; }
bool PresentSeenWithin(int Seconds) const { return time(NULL) - presentSeen < Seconds; } 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 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 ClrRunningStatus(cChannel *Channel = NULL);
void ResetVersions(void); void ResetVersions(void);
void Sort(void); void Sort(void);
void DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version); void DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version);
void Cleanup(time_t Time); void Cleanup(time_t Time);
void Cleanup(void); void Cleanup(void);
void IncNumTimers(void) const;
void DecNumTimers(void) const;
bool HasTimer(void) const { return numTimers > 0; }
cEvent *AddEvent(cEvent *Event); cEvent *AddEvent(cEvent *Event);
void DelEvent(cEvent *Event); void DelEvent(cEvent *Event);
void HashEvent(cEvent *Event); void HashEvent(cEvent *Event);
@ -177,35 +186,23 @@ public:
static bool Read(FILE *f, cSchedules *Schedules); 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> { class cSchedules : public cList<cSchedule> {
friend class cSchedule; friend class cSchedule;
friend class cSchedulesLock;
private: private:
cRwLock rwlock;
static cSchedules schedules; static cSchedules schedules;
static char *epgDataFileName; static char *epgDataFileName;
static time_t lastDump; static time_t lastDump;
static time_t modified;
public: 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 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 Cleanup(bool Force = false);
static void ResetVersions(void); 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 Dump(FILE *f = NULL, const char *Prefix = "", eDumpMode DumpMode = dmAll, time_t AtTime = 0);
static bool Read(FILE *f = NULL); static bool Read(FILE *f = NULL);
cSchedule *AddSchedule(tChannelID ChannelID); cSchedule *AddSchedule(tChannelID ChannelID);
@ -213,6 +210,17 @@ public:
const cSchedule *GetSchedule(const cChannel *Channel, bool AddIfMissing = false) const; 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 { class cEpgDataReader : public cThread {
public: public:
cEpgDataReader(void); cEpgDataReader(void);
@ -273,12 +281,14 @@ public:
virtual bool DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version) { return false; } 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 ///< Takes a look at all EPG events between SegmentStart and SegmentEnd and
///< drops outdated events. ///< 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. ///< Called directly after IgnoreChannel() before any other handler method is called.
///< Designed to give handlers the possibility to prepare a database transaction. ///< 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. ///< Called after the segment data has been processed.
///< At this point handlers should close/commit/rollback any pending database transactions. ///< 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> { class cEpgHandlers : public cList<cEpgHandler> {
@ -301,8 +311,8 @@ public:
void HandleEvent(cEvent *Event); void HandleEvent(cEvent *Event);
void SortSchedule(cSchedule *Schedule); void SortSchedule(cSchedule *Schedule);
void DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version); void DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version);
void BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus); void BeginSegmentTransfer(const cChannel *Channel);
void EndSegmentTransfer(bool Modified, bool OnlyRunningStatus); void EndSegmentTransfer(bool Modified);
}; };
extern cEpgHandlers EpgHandlers; extern cEpgHandlers EpgHandlers;

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "filter.h"
@ -19,31 +19,38 @@ cSectionSyncer::cSectionSyncer(void)
void cSectionSyncer::Reset(void) void cSectionSyncer::Reset(void)
{ {
lastVersion = thisVersion = 0xFF; currentVersion = -1;
nextNumber = 0; currentSection = -1;
synced = false;
complete = false;
memset(sections, 0x00, sizeof(sections));
} }
void cSectionSyncer::Repeat(void) void cSectionSyncer::Repeat(void)
{ {
lastVersion = 0xFF; SetSectionFlag(currentSection, false);
nextNumber--; synced = false;
complete = false;
} }
bool cSectionSyncer::Sync(uchar Version, int Number, int LastNumber) bool cSectionSyncer::Sync(uchar Version, int Number, int LastNumber)
{ {
if (Version != lastVersion) { if (Version != currentVersion) {
if (Version != thisVersion) { Reset();
thisVersion = Version; currentVersion = Version;
nextNumber = 0;
}
if (Number == nextNumber) {
if (Number == LastNumber)
lastVersion = Version;
nextNumber++;
return true;
}
} }
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 ----------------------------------------------------------- // --- cFilterData -----------------------------------------------------------

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __FILTER_H
@ -15,13 +15,18 @@
class cSectionSyncer { class cSectionSyncer {
private: private:
int lastVersion; int currentVersion;
int thisVersion; int currentSection;
int nextNumber; 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: public:
cSectionSyncer(void); cSectionSyncer(void);
void Reset(void); void Reset(void);
void Repeat(void); void Repeat(void);
bool Complete(void) { return complete; }
bool Sync(uchar Version, int Number, int LastNumber); bool Sync(uchar Version, int Number, int LastNumber);
}; };

1288
menu.c

File diff suppressed because it is too large Load Diff

21
menu.h
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __MENU_H
@ -72,6 +72,7 @@ public:
class cMenuEditTimer : public cOsdMenu { class cMenuEditTimer : public cOsdMenu {
private: private:
static const cTimer *addedTimer;
cTimer *timer; cTimer *timer;
cTimer data; cTimer data;
int channel; int channel;
@ -86,13 +87,14 @@ public:
cMenuEditTimer(cTimer *Timer, bool New = false); cMenuEditTimer(cTimer *Timer, bool New = false);
virtual ~cMenuEditTimer(); virtual ~cMenuEditTimer();
virtual eOSState ProcessKey(eKeys Key); virtual eOSState ProcessKey(eKeys Key);
static const cTimer *AddedTimer(void);
}; };
class cMenuEvent : public cOsdMenu { class cMenuEvent : public cOsdMenu {
private: private:
const cEvent *event; const cEvent *event;
public: 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 void Display(void);
virtual eOSState ProcessKey(eKeys Key); virtual eOSState ProcessKey(eKeys Key);
}; };
@ -123,14 +125,14 @@ private:
bool timeout; bool timeout;
int osdState; int osdState;
const cPositioner *positioner; const cPositioner *positioner;
cChannel *channel; const cChannel *channel;
const cEvent *lastPresent; const cEvent *lastPresent;
const cEvent *lastFollowing; const cEvent *lastFollowing;
static cDisplayChannel *currentDisplayChannel; static cDisplayChannel *currentDisplayChannel;
void DisplayChannel(void); void DisplayChannel(void);
void DisplayInfo(void); void DisplayInfo(void);
void Refresh(void); void Refresh(void);
cChannel *NextAvailableChannel(cChannel *Channel, int Direction); const cChannel *NextAvailableChannel(const cChannel *Channel, int Direction);
public: public:
cDisplayChannel(int Number, bool Switched); cDisplayChannel(int Number, bool Switched);
cDisplayChannel(eKeys FirstKey); cDisplayChannel(eKeys FirstKey);
@ -205,7 +207,7 @@ class cMenuRecordings : public cOsdMenu {
private: private:
char *base; char *base;
int level; int level;
int recordingsState; cStateKey recordingsStateKey;
int helpKeys; int helpKeys;
const cRecordingFilter *filter; const cRecordingFilter *filter;
static cString path; static cString path;
@ -239,7 +241,7 @@ private:
char *fileName; char *fileName;
bool GetEvent(void); bool GetEvent(void);
public: public:
cRecordControl(cDevice *Device, cTimer *Timer = NULL, bool Pause = false); cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer = NULL, bool Pause = false);
virtual ~cRecordControl(); virtual ~cRecordControl();
bool Process(time_t t); bool Process(time_t t);
cDevice *Device(void) { return device; } cDevice *Device(void) { return device; }
@ -254,7 +256,8 @@ private:
static cRecordControl *RecordControls[]; static cRecordControl *RecordControls[];
static int state; static int state;
public: 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 void Stop(const char *InstantId);
static bool PauseLiveVideo(void); static bool PauseLiveVideo(void);
static const char *GetInstantId(const char *LastInstantId); static const char *GetInstantId(const char *LastInstantId);
@ -262,8 +265,8 @@ public:
static cRecordControl *GetRecordControl(const cTimer *Timer); static cRecordControl *GetRecordControl(const cTimer *Timer);
///< Returns the cRecordControl for the given Timer. ///< Returns the cRecordControl for the given Timer.
///< If there is no cRecordControl for Timer, NULL is returned. ///< If there is no cRecordControl for Timer, NULL is returned.
static void Process(time_t t); static bool Process(cTimers *Timers, time_t t);
static void ChannelDataModified(cChannel *Channel); static void ChannelDataModified(const cChannel *Channel);
static bool Active(void); static bool Active(void);
static void Shutdown(void); static void Shutdown(void);
static void ChangeState(void) { state++; } static void ChangeState(void) { state++; }

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "menuitems.h"
@ -777,7 +777,7 @@ void cMenuEditStraItem::Set(void)
// --- cMenuEditChanItem ----------------------------------------------------- // --- cMenuEditChanItem -----------------------------------------------------
cMenuEditChanItem::cMenuEditChanItem(const char *Name, int *Value, const char *NoneString) 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; channelID = NULL;
noneString = NoneString; 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) 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; channelID = ChannelID;
noneString = NoneString; noneString = NoneString;
cChannel *channel = Channels.GetByChannelID(tChannelID::FromString(*ChannelID)); LOCK_CHANNELS_READ;
dummyValue = channel ? channel->Number() : 0; const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(*ChannelID));
dummyValue = Channel ? Channel->Number() : 0;
Set(); Set();
} }
@ -799,11 +800,12 @@ void cMenuEditChanItem::Set(void)
{ {
if (*value > 0) { if (*value > 0) {
char buf[255]; char buf[255];
cChannel *channel = Channels.GetByNumber(*value); LOCK_CHANNELS_READ;
snprintf(buf, sizeof(buf), "%d %s", *value, channel ? channel->Name() : ""); const cChannel *Channel = Channels->GetByNumber(*value);
snprintf(buf, sizeof(buf), "%d %s", *value, Channel ? Channel->Name() : "");
SetValue(buf); SetValue(buf);
if (channelID) if (channelID)
*channelID = channel ? channel->GetChannelID().ToString() : ""; *channelID = Channel ? Channel->GetChannelID().ToString() : "";
} }
else if (noneString) { else if (noneString) {
SetValue(noneString); SetValue(noneString);
@ -822,13 +824,14 @@ eOSState cMenuEditChanItem::ProcessKey(eKeys Key)
case kRight|k_Repeat: case kRight|k_Repeat:
case kRight: case kRight:
{ {
cChannel *channel = Channels.GetByNumber(*value + delta, delta); LOCK_CHANNELS_READ
if (channel) const cChannel *Channel = Channels->GetByNumber(*value + delta, delta);
*value = channel->Number(); if (Channel)
*value = Channel->Number();
else if (delta < 0 && noneString) else if (delta < 0 && noneString)
*value = 0; *value = 0;
if (channelID) if (channelID)
*channelID = channel ? channel->GetChannelID().ToString() : ""; *channelID = Channel ? Channel->GetChannelID().ToString() : "";
Set(); Set();
} }
break; break;
@ -845,13 +848,14 @@ cMenuEditTranItem::cMenuEditTranItem(const char *Name, int *Value, int *Source)
number = 0; number = 0;
source = Source; source = Source;
transponder = Value; transponder = Value;
cChannel *channel = Channels.First(); LOCK_CHANNELS_READ;
while (channel) { const cChannel *Channel = Channels->First();
if (!channel->GroupSep() && *source == channel->Source() && ISTRANSPONDER(channel->Transponder(), *Value)) { while (Channel) {
number = channel->Number(); if (!Channel->GroupSep() && *source == Channel->Source() && ISTRANSPONDER(Channel->Transponder(), *Value)) {
number = Channel->Number();
break; break;
} }
channel = (cChannel *)channel->Next(); Channel = Channels->Next(Channel);
} }
Set(); Set();
} }
@ -859,10 +863,10 @@ cMenuEditTranItem::cMenuEditTranItem(const char *Name, int *Value, int *Source)
eOSState cMenuEditTranItem::ProcessKey(eKeys Key) eOSState cMenuEditTranItem::ProcessKey(eKeys Key)
{ {
eOSState state = cMenuEditChanItem::ProcessKey(Key); eOSState state = cMenuEditChanItem::ProcessKey(Key);
cChannel *channel = Channels.GetByNumber(number); LOCK_CHANNELS_READ
if (channel) { if (const cChannel *Channel = Channels->GetByNumber(number)) {
*source = channel->Source(); *source = Channel->Source();
*transponder = channel->Transponder(); *transponder = Channel->Transponder();
} }
else { else {
*source = 0; *source = 0;

43
nit.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #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); 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 sectionSyncer.Repeat(); // let's not miss any section of the NIT
return; return;
} }
bool ChannelsModified = false;
SI::NIT::TransportStream ts; SI::NIT::TransportStream ts;
for (SI::Loop::Iterator it; nit.transportStreamLoop.getNext(ts, it); ) { for (SI::Loop::Iterator it; nit.transportStreamLoop.getNext(ts, it); ) {
SI::Descriptor *d; 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) { if (Setup.UpdateChannels >= 5) {
bool found = false; bool found = false;
bool forceTransponderUpdate = 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()) { if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
int transponder = Channel->Transponder(); int transponder = Channel->Transponder();
found = true; 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 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'))) else if (Channel->Srate() != SymbolRate || strcmp(Channel->Parameters(), dtp.ToString('S')))
forceTransponderUpdate = true; // get us receiving this transponder 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) { if (!found || forceTransponderUpdate) {
for (int n = 0; n < NumFrequencies; n++) { for (int n = 0; n < NumFrequencies; n++) {
cChannel *Channel = new cChannel; 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'))) if (Channel->SetTransponderData(Source, Frequencies[n], SymbolRate, dtp.ToString('S')))
EITScanner.AddTransponder(Channel); EITScanner.AddTransponder(Channel);
else else
@ -150,13 +153,13 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
break; break;
case SI::S2SatelliteDeliverySystemDescriptorTag: { case SI::S2SatelliteDeliverySystemDescriptorTag: {
if (Setup.UpdateChannels >= 5) { 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()) { if (!Channel->GroupSep() && cSource::IsSat(Channel->Source()) && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
SI::S2SatelliteDeliverySystemDescriptor *sd = (SI::S2SatelliteDeliverySystemDescriptor *)d; SI::S2SatelliteDeliverySystemDescriptor *sd = (SI::S2SatelliteDeliverySystemDescriptor *)d;
cDvbTransponderParameters dtp(Channel->Parameters()); cDvbTransponderParameters dtp(Channel->Parameters());
dtp.SetSystem(DVB_SYSTEM_2); dtp.SetSystem(DVB_SYSTEM_2);
dtp.SetStreamId(sd->getInputStreamIdentifier()); 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; break;
} }
} }
@ -178,7 +181,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
if (Setup.UpdateChannels >= 5) { if (Setup.UpdateChannels >= 5) {
bool found = false; bool found = false;
bool forceTransponderUpdate = 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()) { if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
int transponder = Channel->Transponder(); int transponder = Channel->Transponder();
found = true; 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 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'))) else if (Channel->Srate() != SymbolRate || strcmp(Channel->Parameters(), dtp.ToString('C')))
forceTransponderUpdate = true; // get us receiving this transponder 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) { if (!found || forceTransponderUpdate) {
for (int n = 0; n < NumFrequencies; n++) { for (int n = 0; n < NumFrequencies; n++) {
cChannel *Channel = new cChannel; 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'))) if (Channel->SetTransponderData(Source, Frequencies[n], SymbolRate, dtp.ToString('C')))
EITScanner.AddTransponder(Channel); EITScanner.AddTransponder(Channel);
else else
@ -234,7 +237,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
if (Setup.UpdateChannels >= 5) { if (Setup.UpdateChannels >= 5) {
bool found = false; bool found = false;
bool forceTransponderUpdate = 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()) { if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
int transponder = Channel->Transponder(); int transponder = Channel->Transponder();
found = true; 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 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'))) else if (strcmp(Channel->Parameters(), dtp.ToString('T')))
forceTransponderUpdate = true; // get us receiving this transponder 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) { if (!found || forceTransponderUpdate) {
for (int n = 0; n < NumFrequencies; n++) { for (int n = 0; n < NumFrequencies; n++) {
cChannel *Channel = new cChannel; 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'))) if (Channel->SetTransponderData(Source, Frequencies[n], 0, dtp.ToString('T')))
EITScanner.AddTransponder(Channel); EITScanner.AddTransponder(Channel);
else else
@ -272,7 +275,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
switch (sd->getExtensionDescriptorTag()) { switch (sd->getExtensionDescriptorTag()) {
case SI::T2DeliverySystemDescriptorTag: { case SI::T2DeliverySystemDescriptorTag: {
if (Setup.UpdateChannels >= 5) { 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); int Source = cSource::FromData(cSource::stTerr);
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) { if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
SI::T2DeliverySystemDescriptor *td = (SI::T2DeliverySystemDescriptor *)d; 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()]); dtp.SetTransmission(T2TransmissionModes[td->getTransmissionMode()]);
//TODO add parsing of frequencies //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 lcn = LogicalChannel.getLogicalChannelNumber();
int sid = LogicalChannel.getServiceId(); int sid = LogicalChannel.getServiceId();
if (LogicalChannel.getVisibleServiceFlag()) { 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()) { if (!Channel->GroupSep() && Channel->Sid() == sid && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
Channel->SetLcn(lcn); ChannelsModified |= Channel->SetLcn(lcn);
break; 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 lcn = HdSimulcastLogicalChannel.getLogicalChannelNumber();
int sid = HdSimulcastLogicalChannel.getServiceId(); int sid = HdSimulcastLogicalChannel.getServiceId();
if (HdSimulcastLogicalChannel.getVisibleServiceFlag()) { 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()) { if (!Channel->GroupSep() && Channel->Sid() == sid && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
Channel->SetLcn(lcn); ChannelsModified |= Channel->SetLcn(lcn);
break; break;
} }
} }
@ -343,5 +346,5 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
delete d; delete d;
} }
} }
Channels.Unlock(); StateKey.Remove(ChannelsModified);
} }

23
pat.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "pat.h"
@ -99,8 +99,8 @@ cCaDescriptors::cCaDescriptors(int Source, int Transponder, int ServiceId, int P
bool cCaDescriptors::operator== (const cCaDescriptors &arg) const bool cCaDescriptors::operator== (const cCaDescriptors &arg) const
{ {
cCaDescriptor *ca1 = caDescriptors.First(); const cCaDescriptor *ca1 = caDescriptors.First();
cCaDescriptor *ca2 = arg.caDescriptors.First(); const cCaDescriptor *ca2 = arg.caDescriptors.First();
while (ca1 && ca2) { while (ca1 && ca2) {
if (!(*ca1 == *ca2)) if (!(*ca1 == *ca2))
return false; return false;
@ -396,11 +396,14 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
SwitchToNextPmtPid(); SwitchToNextPmtPid();
return; return;
} }
if (!Channels.Lock(true, 10)) cStateKey StateKey;
cChannels *Channels = cChannels::GetChannelsWrite(StateKey, 10);
if (!Channels)
return; return;
bool ChannelsModified = false;
PmtVersionChanged(Pid, pmt.getTableIdExtension(), pmt.getVersionNumber(), true); PmtVersionChanged(Pid, pmt.getTableIdExtension(), pmt.getVersionNumber(), true);
SwitchToNextPmtPid(); SwitchToNextPmtPid();
cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), pmt.getServiceId()); cChannel *Channel = Channels->GetByServiceID(Source(), Transponder(), pmt.getServiceId());
if (Channel) { if (Channel) {
SI::CaDescriptor *d; SI::CaDescriptor *d;
cCaDescriptors *CaDescriptors = new cCaDescriptors(Channel->Source(), Channel->Transponder(), Channel->Sid(), Pid); 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) { if (Setup.UpdateChannels >= 2) {
Channel->SetPids(Vpid, Ppid, Vtype, Apids, Atypes, ALangs, Dpids, Dtypes, DLangs, Spids, SLangs, Tpid); ChannelsModified |= Channel->SetPids(Vpid, Ppid, Vtype, Apids, Atypes, ALangs, Dpids, Dtypes, DLangs, Spids, SLangs, Tpid);
Channel->SetCaIds(CaDescriptors->CaIds()); ChannelsModified |= Channel->SetCaIds(CaDescriptors->CaIds());
Channel->SetSubtitlingDescriptors(SubtitlingTypes, CompositionPageIds, AncillaryPageIds); 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 (timer.TimedOut()) {
if (pmtIndex >= 0) if (pmtIndex >= 0)

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "recorder.h"
@ -135,7 +135,8 @@ void cRecorder::Action(void)
if (frameDetector->FramesPerSecond() > 0 && DoubleEqual(RecordingInfo.FramesPerSecond(), DEFAULTFRAMESPERSECOND) && !DoubleEqual(RecordingInfo.FramesPerSecond(), frameDetector->FramesPerSecond())) { if (frameDetector->FramesPerSecond() > 0 && DoubleEqual(RecordingInfo.FramesPerSecond(), DEFAULTFRAMESPERSECOND) && !DoubleEqual(RecordingInfo.FramesPerSecond(), frameDetector->FramesPerSecond())) {
RecordingInfo.SetFramesPerSecond(frameDetector->FramesPerSecond()); RecordingInfo.SetFramesPerSecond(frameDetector->FramesPerSecond());
RecordingInfo.Write(); RecordingInfo.Write();
Recordings.UpdateByName(recordingName); LOCK_RECORDINGS_WRITE;
Recordings->UpdateByName(recordingName);
} }
} }
InfoWritten = true; InfoWritten = true;

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "recording.h"
@ -76,9 +76,6 @@ int DirectoryNameMax = NAME_MAX;
bool DirectoryEncoding = false; bool DirectoryEncoding = false;
int InstanceId = 0; int InstanceId = 0;
cRecordings DeletedRecordings(true);
static cRecordings VanishedRecordings;
// --- cRemoveDeletedRecordingsThread ---------------------------------------- // --- cRemoveDeletedRecordingsThread ----------------------------------------
class cRemoveDeletedRecordingsThread : public cThread { class cRemoveDeletedRecordingsThread : public cThread {
@ -100,8 +97,8 @@ void cRemoveDeletedRecordingsThread::Action(void)
if (LockFile.Lock()) { if (LockFile.Lock()) {
time_t StartTime = time(NULL); time_t StartTime = time(NULL);
bool deleted = false; bool deleted = false;
cThreadLock DeletedRecordingsLock(&DeletedRecordings); LOCK_DELETEDRECORDINGS_WRITE;
for (cRecording *r = DeletedRecordings.First(); r; ) { for (cRecording *r = DeletedRecordings->First(); r; ) {
if (cIoThrottle::Engaged()) if (cIoThrottle::Engaged())
return; return;
if (time(NULL) - StartTime > MAXREMOVETIME) if (time(NULL) - StartTime > MAXREMOVETIME)
@ -109,14 +106,14 @@ void cRemoveDeletedRecordingsThread::Action(void)
if (cRemote::HasKeys()) if (cRemote::HasKeys())
return; // react immediately on user input return; // react immediately on user input
if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) { if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) {
cRecording *next = DeletedRecordings.Next(r); cRecording *next = DeletedRecordings->Next(r);
r->Remove(); r->Remove();
DeletedRecordings.Del(r); DeletedRecordings->Del(r);
r = next; r = next;
deleted = true; deleted = true;
continue; continue;
} }
r = DeletedRecordings.Next(r); r = DeletedRecordings->Next(r);
} }
if (deleted) { if (deleted) {
const char *IgnoreFiles[] = { SORTMODEFILE, NULL }; const char *IgnoreFiles[] = { SORTMODEFILE, NULL };
@ -134,8 +131,8 @@ void RemoveDeletedRecordings(void)
static time_t LastRemoveCheck = 0; static time_t LastRemoveCheck = 0;
if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) { if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) {
if (!RemoveDeletedRecordingsThread.Active()) { if (!RemoveDeletedRecordingsThread.Active()) {
cThreadLock DeletedRecordingsLock(&DeletedRecordings); LOCK_DELETEDRECORDINGS_READ;
for (cRecording *r = DeletedRecordings.First(); r; r = DeletedRecordings.Next(r)) { for (const cRecording *r = DeletedRecordings->First(); r; r = DeletedRecordings->Next(r)) {
if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) { if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) {
RemoveDeletedRecordingsThread.Start(); RemoveDeletedRecordingsThread.Start();
break; break;
@ -163,37 +160,43 @@ void AssertFreeDiskSpace(int Priority, bool Force)
return; return;
// Remove the oldest file that has been "deleted": // Remove the oldest file that has been "deleted":
isyslog("low disk space while recording, trying to remove a deleted recording..."); isyslog("low disk space while recording, trying to remove a deleted recording...");
cThreadLock DeletedRecordingsLock(&DeletedRecordings); int NumDeletedRecordings = 0;
if (DeletedRecordings.Count()) { {
cRecording *r = DeletedRecordings.First(); LOCK_DELETEDRECORDINGS_WRITE;
cRecording *r0 = NULL; NumDeletedRecordings = DeletedRecordings->Count();
while (r) { if (NumDeletedRecordings) {
if (r->IsOnVideoDirectoryFileSystem()) { // only remove recordings that will actually increase the free video disk space cRecording *r = DeletedRecordings->First();
if (!r0 || r->Start() < r0->Start()) cRecording *r0 = NULL;
r0 = r; while (r) {
} if (r->IsOnVideoDirectoryFileSystem()) { // only remove recordings that will actually increase the free video disk space
r = DeletedRecordings.Next(r); if (!r0 || r->Start() < r0->Start())
} r0 = r;
if (r0) { }
if (r0->Remove()) r = DeletedRecordings->Next(r);
LastFreeDiskCheck += REMOVELATENCY / Factor; }
DeletedRecordings.Del(r0); if (r0) {
return; if (r0->Remove())
} LastFreeDiskCheck += REMOVELATENCY / Factor;
} DeletedRecordings->Del(r0);
else { return;
}
}
}
if (NumDeletedRecordings == 0) {
// DeletedRecordings was empty, so to be absolutely sure there are no // DeletedRecordings was empty, so to be absolutely sure there are no
// deleted recordings we need to double check: // deleted recordings we need to double check:
DeletedRecordings.Update(true); cRecordings::Update(true);
if (DeletedRecordings.Count()) LOCK_DELETEDRECORDINGS_READ;
if (DeletedRecordings->Count())
return; // the next call will actually remove it return; // the next call will actually remove it
} }
// No "deleted" files to remove, so let's see if we can delete a recording: // No "deleted" files to remove, so let's see if we can delete a recording:
if (Priority > 0) { if (Priority > 0) {
isyslog("...no deleted recording found, trying to delete an old recording..."); isyslog("...no deleted recording found, trying to delete an old recording...");
cThreadLock RecordingsLock(&Recordings); LOCK_RECORDINGS_WRITE;
if (Recordings.Count()) { Recordings->SetExplicitModify();
cRecording *r = Recordings.First(); if (Recordings->Count()) {
cRecording *r = Recordings->First();
cRecording *r0 = NULL; cRecording *r0 = NULL;
while (r) { while (r) {
if (r->IsOnVideoDirectoryFileSystem()) { // only delete recordings that will actually increase the free video disk space 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()) { if (r0 && r0->Delete()) {
Recordings.Del(r0); Recordings->Del(r0);
Recordings->SetModified();
return; 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::cResumeFile(const char *FileName, bool IsPesRecording) cResumeFile::cResumeFile(const char *FileName, bool IsPesRecording)
@ -309,7 +305,8 @@ bool cResumeFile::Save(int Index)
if (safe_write(f, &Index, sizeof(Index)) < 0) if (safe_write(f, &Index, sizeof(Index)) < 0)
LOG_ERROR_STR(fileName); LOG_ERROR_STR(fileName);
close(f); close(f);
Recordings.ResetResume(fileName); LOCK_RECORDINGS_WRITE;
Recordings->ResetResume(fileName);
return true; return true;
} }
} }
@ -318,7 +315,8 @@ bool cResumeFile::Save(int Index)
if (f) { if (f) {
fprintf(f, "I %d\n", Index); fprintf(f, "I %d\n", Index);
fclose(f); fclose(f);
Recordings.ResetResume(fileName); LOCK_RECORDINGS_WRITE;
Recordings->ResetResume(fileName);
} }
else else
LOG_ERROR_STR(fileName); LOG_ERROR_STR(fileName);
@ -331,8 +329,10 @@ bool cResumeFile::Save(int Index)
void cResumeFile::Delete(void) void cResumeFile::Delete(void)
{ {
if (fileName) { if (fileName) {
if (remove(fileName) == 0) if (remove(fileName) == 0) {
Recordings.ResetResume(fileName); LOCK_RECORDINGS_WRITE;
Recordings->ResetResume(fileName);
}
else if (errno != ENOENT) else if (errno != ENOENT)
LOG_ERROR_STR(fileName); LOG_ERROR_STR(fileName);
} }
@ -787,10 +787,8 @@ cRecording::cRecording(cTimer *Timer, const cEvent *Event)
else else
break; break;
} }
if (Timer->IsSingleEvent()) { if (Timer->IsSingleEvent())
Timer->SetFile(name); // this was an instant recording, so let's set the actual data Timer->SetFile(name); // this was an instant recording, so let's set the actual data
Timers.SetModified();
}
} }
else if (Timer->IsSingleEvent() || !Setup.UseSubtitle) else if (Timer->IsSingleEvent() || !Setup.UseSubtitle)
name = strdup(Timer->File()); name = strdup(Timer->File());
@ -1017,7 +1015,7 @@ int cRecording::Compare(const cListObject &ListObject) const
return strcasecmp(SortName(), r->SortName()); return strcasecmp(SortName(), r->SortName());
} }
bool cRecording::IsInPath(const char *Path) bool cRecording::IsInPath(const char *Path) const
{ {
if (isempty(Path)) if (isempty(Path))
return true; return true;
@ -1154,20 +1152,14 @@ bool cRecording::IsOnVideoDirectoryFileSystem(void) const
return isOnVideoDirectoryFileSystem; return isOnVideoDirectoryFileSystem;
} }
bool cRecording::HasMarks(void) bool cRecording::HasMarks(void) const
{ {
return access(cMarks::MarksFileName(this), F_OK) == 0; return access(cMarks::MarksFileName(this), F_OK) == 0;
} }
bool cRecording::DeleteMarks(void) bool cRecording::DeleteMarks(void)
{ {
if (remove(cMarks::MarksFileName(this)) < 0) { return cMarks::DeleteMarksFile(this);
if (errno != ENOENT) {
LOG_ERROR_STR(fileName);
return false;
}
}
return true;
} }
void cRecording::ReadInfo(void) void cRecording::ReadInfo(void)
@ -1219,8 +1211,6 @@ bool cRecording::ChangePriorityLifetime(int NewPriority, int NewLifetime)
if (!WriteInfo()) if (!WriteInfo())
return false; return false;
} }
Recordings.ChangeState();
Recordings.TouchUpdate();
} }
return true; return true;
} }
@ -1245,8 +1235,6 @@ bool cRecording::ChangeName(const char *NewName)
} }
isOnVideoDirectoryFileSystem = -1; // it might have been moved to a different file system isOnVideoDirectoryFileSystem = -1; // it might have been moved to a different file system
ClearSortName(); ClearSortName();
Recordings.ChangeState();
Recordings.TouchUpdate();
} }
return true; return true;
} }
@ -1360,59 +1348,53 @@ int cRecording::FileSizeMB(void) const
return fileSizeMB; return fileSizeMB;
} }
// --- cRecordings ----------------------------------------------------------- // --- cVideoDirectoryScannerThread ------------------------------------------
cRecordings Recordings; class cVideoDirectoryScannerThread : public cThread {
private:
cRecordings *recordings;
cRecordings *deletedRecordings;
bool initial;
void ScanVideoDir(const char *DirName, int LinkLevel = 0, int DirLevel = 0);
protected:
virtual void Action(void);
public:
cVideoDirectoryScannerThread(cRecordings *Recordings, cRecordings *DeletedRecordings);
~cVideoDirectoryScannerThread();
};
char *cRecordings::updateFileName = NULL; cVideoDirectoryScannerThread::cVideoDirectoryScannerThread(cRecordings *Recordings, cRecordings *DeletedRecordings)
cRecordings::cRecordings(bool Deleted)
:cThread("video directory scanner", true) :cThread("video directory scanner", true)
{ {
deleted = Deleted; recordings = Recordings;
deletedRecordings = DeletedRecordings;
initial = true; initial = true;
lastUpdate = 0;
state = 0;
} }
cRecordings::~cRecordings() cVideoDirectoryScannerThread::~cVideoDirectoryScannerThread()
{ {
Cancel(3); Cancel(3);
} }
void cRecordings::Action(void) void cVideoDirectoryScannerThread::Action(void)
{ {
Refresh(); cStateKey StateKey;
recordings->Lock(StateKey);
initial = recordings->Count() == 0; // no name checking if the list is initially empty
StateKey.Remove();
deletedRecordings->Lock(StateKey, true);
deletedRecordings->Clear();
StateKey.Remove();
ScanVideoDir(cVideoDirectory::Name());
} }
const char *cRecordings::UpdateFileName(void) void cVideoDirectoryScannerThread::ScanVideoDir(const char *DirName, int LinkLevel, int DirLevel)
{ {
if (!updateFileName)
updateFileName = strdup(AddDirectory(cVideoDirectory::Name(), ".update"));
return updateFileName;
}
void cRecordings::Refresh(bool Foreground)
{
lastUpdate = time(NULL); // doing this first to make sure we don't miss anything
initial = Count() == 0; // no name checking if the list is initially empty
if (deleted) {
Lock();
Clear();
ChangeState();
Unlock();
}
ScanVideoDir(cVideoDirectory::Name(), Foreground);
}
bool cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLevel, int DirLevel)
{
bool DoChangeState = false;
// Find any new recordings: // Find any new recordings:
cReadDir d(DirName); cReadDir d(DirName);
struct dirent *e; struct dirent *e;
while ((Foreground || Running()) && (e = d.Next()) != NULL) { while (Running() && (e = d.Next()) != NULL) {
if (!Foreground && cIoThrottle::Engaged()) if (cIoThrottle::Engaged())
cCondWait::SleepMs(100); cCondWait::SleepMs(100);
cString buffer = AddDirectory(DirName, e->d_name); cString buffer = AddDirectory(DirName, e->d_name);
struct stat st; struct stat st;
@ -1428,57 +1410,73 @@ bool cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLev
continue; continue;
} }
if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) {
if (endswith(buffer, deleted ? DELEXT : RECEXT)) { cRecordings *Recordings = NULL;
if (deleted || initial || !GetByName(buffer)) { 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); cRecording *r = new cRecording(buffer);
if (r->Name()) { if (r->Name()) {
r->NumFrames(); // initializes the numFrames member r->NumFrames(); // initializes the numFrames member
r->FileSizeMB(); // initializes the fileSizeMB member r->FileSizeMB(); // initializes the fileSizeMB member
r->IsOnVideoDirectoryFileSystem(); // initializes the isOnVideoDirectoryFileSystem member r->IsOnVideoDirectoryFileSystem(); // initializes the isOnVideoDirectoryFileSystem member
if (deleted) if (Recordings == deletedRecordings)
r->deleted = time(NULL); r->SetDeleted();
Lock(); Recordings->Add(r);
Add(r);
if (initial)
ChangeState();
else
DoChangeState = true;
Unlock();
} }
else else
delete r; delete r;
} }
StateKey.Remove();
} }
else else
DoChangeState |= ScanVideoDir(buffer, Foreground, LinkLevel + Link, DirLevel + 1); ScanVideoDir(buffer, LinkLevel + Link, DirLevel + 1);
} }
} }
} }
// Handle any vanished recordings: // Handle any vanished recordings:
if (!deleted && !initial && DirLevel == 0) { if (!initial && DirLevel == 0) {
for (cRecording *recording = First(); recording; ) { cStateKey StateKey;
cRecording *r = recording; recordings->Lock(StateKey, true);
recording = Next(recording); for (cRecording *Recording = recordings->First(); Recording; ) {
if (access(r->FileName(), F_OK) != 0) { cRecording *r = Recording;
Lock(); Recording = recordings->Next(Recording);
Del(r, false); if (access(r->FileName(), F_OK) != 0)
VanishedRecordings.Add(r); recordings->Del(r);
DoChangeState = true;
Unlock();
}
} }
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; cRecordings::~cRecordings()
return Result; {
// 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) void cRecordings::TouchUpdate(void)
@ -1497,24 +1495,24 @@ bool cRecordings::NeedsUpdate(void)
return lastUpdate < lastModified; 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) { if (Wait) {
Refresh(true); while (videoDirectoryScannerThread->Active())
return Count() > 0; cCondWait::SleepMs(100);
} }
else
Start();
return false;
} }
cRecording *cRecordings::GetByName(const char *FileName) const cRecording *cRecordings::GetByName(const char *FileName) const
{ {
if (FileName) { if (FileName) {
LOCK_THREAD; for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
for (cRecording *recording = First(); recording; recording = Next(recording)) { if (strcmp(Recording->FileName(), FileName) == 0)
if (strcmp(recording->FileName(), FileName) == 0) return Recording;
return recording;
} }
} }
return NULL; return NULL;
@ -1522,12 +1520,8 @@ cRecording *cRecordings::GetByName(const char *FileName)
void cRecordings::AddByName(const char *FileName, bool TriggerUpdate) void cRecordings::AddByName(const char *FileName, bool TriggerUpdate)
{ {
LOCK_THREAD; if (!GetByName(FileName)) {
cRecording *recording = GetByName(FileName); Add(new cRecording(FileName));
if (!recording) {
recording = new cRecording(FileName);
Add(recording);
ChangeState();
if (TriggerUpdate) if (TriggerUpdate)
TouchUpdate(); TouchUpdate();
} }
@ -1535,58 +1529,52 @@ void cRecordings::AddByName(const char *FileName, bool TriggerUpdate)
void cRecordings::DelByName(const char *FileName) void cRecordings::DelByName(const char *FileName)
{ {
LOCK_THREAD; cRecording *Recording = GetByName(FileName);
cRecording *recording = GetByName(FileName);
cRecording *dummy = NULL; cRecording *dummy = NULL;
if (!recording) if (!Recording)
recording = dummy = new cRecording(FileName); // allows us to use a FileName that is not in the Recordings list Recording = dummy = new cRecording(FileName); // allows us to use a FileName that is not in the Recordings list
cThreadLock DeletedRecordingsLock(&DeletedRecordings); LOCK_DELETEDRECORDINGS_WRITE;
if (!dummy) if (!dummy)
Del(recording, false); Del(Recording, false);
char *ext = strrchr(recording->fileName, '.'); char *ext = strrchr(Recording->fileName, '.');
if (ext) { if (ext) {
strncpy(ext, DELEXT, strlen(ext)); strncpy(ext, DELEXT, strlen(ext));
if (access(recording->FileName(), F_OK) == 0) { if (access(Recording->FileName(), F_OK) == 0) {
recording->deleted = time(NULL); Recording->SetDeleted();
DeletedRecordings.Add(recording); DeletedRecordings->Add(Recording);
recording = NULL; // to prevent it from being deleted below Recording = NULL; // to prevent it from being deleted below
} }
} }
delete recording; delete Recording;
ChangeState();
TouchUpdate(); TouchUpdate();
} }
void cRecordings::UpdateByName(const char *FileName) void cRecordings::UpdateByName(const char *FileName)
{ {
LOCK_THREAD; if (cRecording *Recording = GetByName(FileName))
cRecording *recording = GetByName(FileName); Recording->ReadInfo();
if (recording)
recording->ReadInfo();
} }
int cRecordings::TotalFileSizeMB(void) int cRecordings::TotalFileSizeMB(void) const
{ {
int size = 0; int size = 0;
LOCK_THREAD; for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
for (cRecording *recording = First(); recording; recording = Next(recording)) { int FileSizeMB = Recording->FileSizeMB();
int FileSizeMB = recording->FileSizeMB(); if (FileSizeMB > 0 && Recording->IsOnVideoDirectoryFileSystem())
if (FileSizeMB > 0 && recording->IsOnVideoDirectoryFileSystem())
size += FileSizeMB; size += FileSizeMB;
} }
return size; return size;
} }
double cRecordings::MBperMinute(void) double cRecordings::MBperMinute(void) const
{ {
int size = 0; int size = 0;
int length = 0; int length = 0;
LOCK_THREAD; for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
for (cRecording *recording = First(); recording; recording = Next(recording)) { if (Recording->IsOnVideoDirectoryFileSystem()) {
if (recording->IsOnVideoDirectoryFileSystem()) { int FileSizeMB = Recording->FileSizeMB();
int FileSizeMB = recording->FileSizeMB();
if (FileSizeMB > 0) { if (FileSizeMB > 0) {
int LengthInSeconds = recording->LengthInSeconds(); int LengthInSeconds = Recording->LengthInSeconds();
if (LengthInSeconds > 0) { if (LengthInSeconds > 0) {
if (LengthInSeconds / FileSizeMB < LIMIT_SECS_PER_MB_RADIO) { // don't count radio recordings if (LengthInSeconds / FileSizeMB < LIMIT_SECS_PER_MB_RADIO) { // don't count radio recordings
size += FileSizeMB; size += FileSizeMB;
@ -1599,23 +1587,21 @@ double cRecordings::MBperMinute(void)
return (size && length) ? double(size) * 60 / length : -1; 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; int Use = ruNone;
for (cRecording *recording = First(); recording; recording = Next(recording)) { for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
if (recording->IsInPath(Path)) if (Recording->IsInPath(Path))
Use |= recording->IsInUse(); Use |= Recording->IsInUse();
} }
return Use; return Use;
} }
int cRecordings::GetNumRecordingsInPath(const char *Path) int cRecordings::GetNumRecordingsInPath(const char *Path) const
{ {
LOCK_THREAD;
int n = 0; int n = 0;
for (cRecording *recording = First(); recording; recording = Next(recording)) { for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
if (recording->IsInPath(Path)) if (Recording->IsInPath(Path))
n++; n++;
} }
return n; return n;
@ -1624,36 +1610,35 @@ int cRecordings::GetNumRecordingsInPath(const char *Path)
bool cRecordings::MoveRecordings(const char *OldPath, const char *NewPath) bool cRecordings::MoveRecordings(const char *OldPath, const char *NewPath)
{ {
if (OldPath && NewPath && strcmp(OldPath, NewPath)) { if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
LOCK_THREAD;
dsyslog("moving '%s' to '%s'", OldPath, NewPath); dsyslog("moving '%s' to '%s'", OldPath, NewPath);
for (cRecording *recording = First(); recording; recording = Next(recording)) { bool Moved = false;
if (recording->IsInPath(OldPath)) { for (cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
const char *p = recording->Name() + strlen(OldPath); if (Recording->IsInPath(OldPath)) {
const char *p = Recording->Name() + strlen(OldPath);
cString NewName = cString::sprintf("%s%s", NewPath, p); cString NewName = cString::sprintf("%s%s", NewPath, p);
if (!recording->ChangeName(NewName)) if (!Recording->ChangeName(NewName))
return false; return false;
ChangeState(); Moved = true;
} }
} }
if (Moved)
TouchUpdate();
} }
return true; return true;
} }
void cRecordings::ResetResume(const char *ResumeFileName) void cRecordings::ResetResume(const char *ResumeFileName)
{ {
LOCK_THREAD; for (cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
for (cRecording *recording = First(); recording; recording = Next(recording)) { if (!ResumeFileName || strncmp(ResumeFileName, Recording->FileName(), strlen(Recording->FileName())) == 0)
if (!ResumeFileName || strncmp(ResumeFileName, recording->FileName(), strlen(recording->FileName())) == 0) Recording->ResetResume();
recording->ResetResume();
} }
ChangeState();
} }
void cRecordings::ClearSortNames(void) void cRecordings::ClearSortNames(void)
{ {
LOCK_THREAD; for (cRecording *Recording = First(); Recording; Recording = Next(Recording))
for (cRecording *recording = First(); recording; recording = Next(recording)) Recording->ClearSortName();
recording->ClearSortName();
} }
// --- cDirCopier ------------------------------------------------------------ // --- cDirCopier ------------------------------------------------------------
@ -1812,8 +1797,9 @@ void cDirCopier::Stop(void)
Cancel(3); Cancel(3);
if (error) { if (error) {
cVideoDirectory::RemoveVideoFile(dirNameDst); cVideoDirectory::RemoveVideoFile(dirNameDst);
Recordings.AddByName(dirNameSrc); LOCK_RECORDINGS_WRITE;
Recordings.DelByName(dirNameDst); Recordings->AddByName(dirNameSrc);
Recordings->DelByName(dirNameDst);
} }
} }
@ -1893,17 +1879,19 @@ bool cRecordingsHandlerEntry::Active(bool &Error)
copier->Start(); copier->Start();
} }
ClearPending(); ClearPending();
Recordings.ChangeState(); LOCK_RECORDINGS_WRITE; // to trigger a state change
return true; return true;
} }
// Clean up: // Clean up:
if (CopierFinishedOk && (Usage() & ruMove) != 0) { if (CopierFinishedOk && (Usage() & ruMove) != 0) {
cRecording Recording(FileNameSrc()); cRecording Recording(FileNameSrc());
if (Recording.Delete()) if (Recording.Delete()) {
Recordings.DelByName(Recording.FileName()); LOCK_RECORDINGS_WRITE;
Recordings->DelByName(Recording.FileName());
}
} }
Recordings.ChangeState(); LOCK_RECORDINGS_WRITE; // to trigger a state change
Recordings.TouchUpdate(); Recordings->TouchUpdate();
return false; return false;
} }
@ -1947,7 +1935,7 @@ bool cRecordingsHandler::Add(int Usage, const char *FileNameSrc, const char *Fil
operations.Add(new cRecordingsHandlerEntry(Usage, FileNameSrc, FileNameDst)); operations.Add(new cRecordingsHandlerEntry(Usage, FileNameSrc, FileNameDst));
finished = false; finished = false;
Active(); // start it right away if possible Active(); // start it right away if possible
Recordings.ChangeState(); LOCK_RECORDINGS_WRITE; // to trigger a state change
return true; return true;
} }
else else
@ -1969,7 +1957,7 @@ void cRecordingsHandler::Del(const char *FileName)
cMutexLock MutexLock(&mutex); cMutexLock MutexLock(&mutex);
if (cRecordingsHandlerEntry *r = Get(FileName)) { if (cRecordingsHandlerEntry *r = Get(FileName)) {
operations.Del(r); operations.Del(r);
Recordings.ChangeState(); LOCK_RECORDINGS_WRITE; // to trigger a state change
} }
} }
@ -1977,7 +1965,7 @@ void cRecordingsHandler::DelAll(void)
{ {
cMutexLock MutexLock(&mutex); cMutexLock MutexLock(&mutex);
operations.Clear(); operations.Clear();
Recordings.ChangeState(); LOCK_RECORDINGS_WRITE; // to trigger a state change
} }
int cRecordingsHandler::GetUsage(const char *FileName) 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); 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) bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool IsPesRecording)
{ {
cMutexLock MutexLock(this);
recordingFileName = RecordingFileName; recordingFileName = RecordingFileName;
fileName = AddDirectory(RecordingFileName, IsPesRecording ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX); fileName = AddDirectory(RecordingFileName, IsPesRecording ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX);
framesPerSecond = FramesPerSecond; framesPerSecond = FramesPerSecond;
@ -2074,7 +2072,6 @@ bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool Is
bool cMarks::Update(void) bool cMarks::Update(void)
{ {
cMutexLock MutexLock(this);
time_t t = time(NULL); time_t t = time(NULL);
if (t > nextUpdate && *fileName) { if (t > nextUpdate && *fileName) {
time_t LastModified = LastModifiedTime(fileName); time_t LastModified = LastModifiedTime(fileName);
@ -2106,7 +2103,6 @@ bool cMarks::Update(void)
bool cMarks::Save(void) bool cMarks::Save(void)
{ {
cMutexLock MutexLock(this);
if (cConfig<cMark>::Save()) { if (cConfig<cMark>::Save()) {
lastFileTime = LastModifiedTime(fileName); lastFileTime = LastModifiedTime(fileName);
return true; return true;
@ -2116,7 +2112,6 @@ bool cMarks::Save(void)
void cMarks::Align(void) void cMarks::Align(void)
{ {
cMutexLock MutexLock(this);
cIndexFile IndexFile(recordingFileName, false, isPesRecording); cIndexFile IndexFile(recordingFileName, false, isPesRecording);
for (cMark *m = First(); m; m = Next(m)) { for (cMark *m = First(); m; m = Next(m)) {
int p = IndexFile.GetClosestIFrame(m->Position()); int p = IndexFile.GetClosestIFrame(m->Position());
@ -2129,7 +2124,6 @@ void cMarks::Align(void)
void cMarks::Sort(void) void cMarks::Sort(void)
{ {
cMutexLock MutexLock(this);
for (cMark *m1 = First(); m1; m1 = Next(m1)) { for (cMark *m1 = First(); m1; m1 = Next(m1)) {
for (cMark *m2 = Next(m1); m2; m2 = Next(m2)) { for (cMark *m2 = Next(m1); m2; m2 = Next(m2)) {
if (m2->Position() < m1->Position()) { if (m2->Position() < m1->Position()) {
@ -2142,43 +2136,42 @@ void cMarks::Sort(void)
void cMarks::Add(int Position) void cMarks::Add(int Position)
{ {
cMutexLock MutexLock(this);
cConfig<cMark>::Add(new cMark(Position, NULL, framesPerSecond)); cConfig<cMark>::Add(new cMark(Position, NULL, framesPerSecond));
Sort(); 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) if (mi->Position() == Position)
return mi; return mi;
} }
return NULL; 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) if (mi->Position() < Position)
return mi; return mi;
} }
return NULL; 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) if (mi->Position() > Position)
return mi; return mi;
} }
return NULL; 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()) { 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->Position() == NextMark->Position()) { // skip Begin/End at the same position
if (!(BeginMark = Next(NextMark))) if (!(BeginMark = Next(NextMark)))
break; break;
@ -2190,13 +2183,13 @@ cMark *cMarks::GetNextBegin(cMark *EndMark)
return BeginMark; return BeginMark;
} }
cMark *cMarks::GetNextEnd(cMark *BeginMark) const cMark *cMarks::GetNextEnd(const cMark *BeginMark) const
{ {
if (!BeginMark) if (!BeginMark)
return NULL; return NULL;
cMark *EndMark = Next(BeginMark); const cMark *EndMark = Next(BeginMark);
if (EndMark && BeginMark && BeginMark->Position() == EndMark->Position()) { 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->Position() == NextMark->Position()) { // skip End/Begin at the same position
if (!(EndMark = Next(NextMark))) if (!(EndMark = Next(NextMark)))
break; break;
@ -2208,12 +2201,11 @@ cMark *cMarks::GetNextEnd(cMark *BeginMark)
return EndMark; return EndMark;
} }
int cMarks::GetNumSequences(void) int cMarks::GetNumSequences(void) const
{ {
cMutexLock MutexLock(this);
int NumSequences = 0; int NumSequences = 0;
if (cMark *BeginMark = GetNextBegin()) { if (const cMark *BeginMark = GetNextBegin()) {
while (cMark *EndMark = GetNextEnd(BeginMark)) { while (const cMark *EndMark = GetNextEnd(BeginMark)) {
NumSequences++; NumSequences++;
BeginMark = GetNextBegin(EndMark); BeginMark = GetNextBegin(EndMark);
} }
@ -2403,7 +2395,8 @@ void cIndexFileGenerator::Action(void)
if (FrameDetector.FramesPerSecond() > 0 && !DoubleEqual(RecordingInfo.FramesPerSecond(), FrameDetector.FramesPerSecond())) { if (FrameDetector.FramesPerSecond() > 0 && !DoubleEqual(RecordingInfo.FramesPerSecond(), FrameDetector.FramesPerSecond())) {
RecordingInfo.SetFramesPerSecond(FrameDetector.FramesPerSecond()); RecordingInfo.SetFramesPerSecond(FrameDetector.FramesPerSecond());
RecordingInfo.Write(); RecordingInfo.Write();
Recordings.UpdateByName(recordingName); LOCK_RECORDINGS_WRITE;
Recordings->UpdateByName(recordingName);
} }
} }
Skins.QueueMessage(mtInfo, tr("Index file regeneration complete")); Skins.QueueMessage(mtInfo, tr("Index file regeneration complete"));

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __RECORDING_H
@ -41,7 +41,6 @@ enum eRecordingUsage {
}; };
void RemoveDeletedRecordings(void); void RemoveDeletedRecordings(void);
void ClearVanishedRecordings(void);
void AssertFreeDiskSpace(int Priority = 0, bool Force = false); void AssertFreeDiskSpace(int Priority = 0, bool Force = false);
///< The special Priority value -1 means that we shall get rid of any ///< The special Priority value -1 means that we shall get rid of any
///< deleted recordings faster than normal (because we're cutting). ///< deleted recordings faster than normal (because we're cutting).
@ -129,8 +128,9 @@ public:
int Priority(void) const { return priority; } int Priority(void) const { return priority; }
int Lifetime(void) const { return lifetime; } int Lifetime(void) const { return lifetime; }
time_t Deleted(void) const { return deleted; } time_t Deleted(void) const { return deleted; }
void SetDeleted(void) { deleted = time(NULL); }
virtual int Compare(const cListObject &ListObject) const; 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. ///< 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. ///< If Path is NULL or an empty string, the entire video directory is checked.
cString Folder(void) const; cString Folder(void) const;
@ -140,7 +140,7 @@ public:
///< Returns the base name of this recording (without the ///< Returns the base name of this recording (without the
///< video directory and folder). For use in menus etc. ///< video directory and folder). For use in menus etc.
const char *Name(void) const { return name; } 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. ///< For use in menus etc.
const char *FileName(void) const; const char *FileName(void) const;
///< Returns the full path name to the recording directory, including the ///< Returns the full path name to the recording directory, including the
@ -166,7 +166,7 @@ public:
bool IsEdited(void) const; bool IsEdited(void) const;
bool IsPesRecording(void) const { return isPesRecording; } bool IsPesRecording(void) const { return isPesRecording; }
bool IsOnVideoDirectoryFileSystem(void) const; bool IsOnVideoDirectoryFileSystem(void) const;
bool HasMarks(void); bool HasMarks(void) const;
///< Returns true if this recording has any editing marks. ///< Returns true if this recording has any editing marks.
bool DeleteMarks(void); bool DeleteMarks(void);
///< Deletes the editing marks from this recording (if any). ///< Deletes the editing marks from this recording (if any).
@ -216,49 +216,54 @@ public:
///< as in time-shift). ///< as in time-shift).
}; };
class cRecordings : public cList<cRecording>, public cThread { class cVideoDirectoryScannerThread;
class cRecordings : public cList<cRecording> {
private: private:
static cRecordings recordings;
static cRecordings deletedRecordings;
static char *updateFileName; static char *updateFileName;
bool deleted; static time_t lastUpdate;
bool initial; static cVideoDirectoryScannerThread *videoDirectoryScannerThread;
time_t lastUpdate; static const char *UpdateFileName(void);
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);
public: public:
cRecordings(bool Deleted = false); cRecordings(bool Deleted = false);
virtual ~cRecordings(); virtual ~cRecordings();
bool Load(void) { return Update(true); } static const cRecordings *GetRecordingsRead(cStateKey &StateKey, int TimeoutMs = 0) { return recordings.Lock(StateKey, false, TimeoutMs) ? &recordings : NULL; }
///< Loads the current list of recordings and returns true if there ///< Gets the list of recordings for read access.
///< is anything in it (for compatibility with older plugins - use ///< See cTimers::GetTimersRead() for details.
///< Update(true) instead). static cRecordings *GetRecordingsWrite(cStateKey &StateKey, int TimeoutMs = 0) { return recordings.Lock(StateKey, true, TimeoutMs) ? &recordings : NULL; }
bool Update(bool Wait = false); ///< 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 ///< Triggers an update of the list of recordings, which will run
///< as a separate thread if Wait is false. If Wait is true, the ///< as a separate thread if Wait is false. If Wait is true, the
///< function returns only after the update has completed. ///< function returns only after the update has completed.
///< Returns true if Wait is true and there is anything in the list static void TouchUpdate(void);
///< of recordings, false otherwise.
void TouchUpdate(void);
///< Touches the '.update' file in the video directory, so that other ///< Touches the '.update' file in the video directory, so that other
///< instances of VDR that access the same video directory can be triggered ///< instances of VDR that access the same video directory can be triggered
///< to update their recordings list. ///< to update their recordings list.
bool NeedsUpdate(void); ///< This function is 'const', because it doesn't actually modify the list
void ChangeState(void) { state++; } ///< of recordings.
bool StateChanged(int &State); static bool NeedsUpdate(void);
void ResetResume(const char *ResumeFileName = NULL); void ResetResume(const char *ResumeFileName = NULL);
void ClearSortNames(void); 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 AddByName(const char *FileName, bool TriggerUpdate = true);
void DelByName(const char *FileName); void DelByName(const char *FileName);
void UpdateByName(const char *FileName); void UpdateByName(const char *FileName);
int TotalFileSizeMB(void); int TotalFileSizeMB(void) const;
double MBperMinute(void); double MBperMinute(void) const;
///< Returns the average data rate (in MB/min) of all recordings, or -1 if ///< Returns the average data rate (in MB/min) of all recordings, or -1 if
///< this value is unknown. ///< 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 ///< 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 ///< the whole Path shall not be tampered with. Returns 0 (ruNone) if no recording
///< is in use. ///< is in use.
@ -266,7 +271,7 @@ public:
///< If several recordings in the Path are currently in use, the return value will ///< If several recordings in the Path are currently in use, the return value will
///< be the combination of all individual recordings' flags. ///< be the combination of all individual recordings' flags.
///< If Path is NULL or an empty string, the entire video directory is checked. ///< 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 ///< Returns the total number of recordings in the given Path, including all
///< sub-folders of Path. ///< sub-folders of Path.
///< If Path is NULL or an empty string, the entire video directory is checked. ///< 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. ///< if all recordings have been successfully added to the RecordingsHandler.
}; };
/// Any access to Recordings that loops through the list of recordings // Provide lock controlled access to the list:
/// needs to hold a thread lock on this object!
extern cRecordings Recordings; DEF_LIST_LOCK(Recordings);
extern cRecordings DeletedRecordings; 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; class cRecordingsHandlerEntry;
@ -350,7 +364,7 @@ public:
bool Save(FILE *f); bool Save(FILE *f);
}; };
class cMarks : public cConfig<cMark>, public cMutex { class cMarks : public cConfig<cMark> {
private: private:
cString recordingFileName; cString recordingFileName;
cString fileName; cString fileName;
@ -360,9 +374,11 @@ private:
time_t lastFileTime; time_t lastFileTime;
time_t lastChange; time_t lastChange;
public: public:
cMarks(void): cConfig<cMark>("Marks") {};
static cString MarksFileName(const cRecording *Recording); static cString MarksFileName(const cRecording *Recording);
///< Returns the marks file name for the given Recording (regardless whether such ///< Returns the marks file name for the given Recording (regardless whether such
///< a file actually exists). ///< a file actually exists).
static bool DeleteMarksFile(const cRecording *Recording);
bool Load(const char *RecordingFileName, double FramesPerSecond = DEFAULTFRAMESPERSECOND, bool IsPesRecording = false); bool Load(const char *RecordingFileName, double FramesPerSecond = DEFAULTFRAMESPERSECOND, bool IsPesRecording = false);
bool Update(void); bool Update(void);
bool Save(void); bool Save(void);
@ -374,22 +390,27 @@ public:
///< calls to Del(), or any of the functions that return a "cMark *", in case ///< 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 ///< an other thread might modifiy the list while the returned pointer is
///< considered valid. ///< considered valid.
cMark *Get(int Position); const cMark *Get(int Position) const;
cMark *GetPrev(int Position); const cMark *GetPrev(int Position) const;
cMark *GetNext(int Position); const cMark *GetNext(int Position) const;
cMark *GetNextBegin(cMark *EndMark = NULL); const cMark *GetNextBegin(const cMark *EndMark = NULL) const;
///< Returns the next "begin" mark after EndMark, skipping any marks at the ///< Returns the next "begin" mark after EndMark, skipping any marks at the
///< same position as EndMark. If EndMark is NULL, the first actual "begin" ///< same position as EndMark. If EndMark is NULL, the first actual "begin"
///< will be returned (if any). ///< 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 ///< Returns the next "end" mark after BeginMark, skipping any marks at the
///< same position as BeginMark. ///< same position as BeginMark.
int GetNumSequences(void); int GetNumSequences(void) const;
///< Returns the actual number of sequences to be cut from the recording. ///< 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 ///< 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 ///< 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 ///< return value is 0, which means that the result is the same as the original
///< recording. ///< 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" #define RUC_BEFORERECORDING "before"

52
sdt.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "sdt.h"
@ -52,18 +52,21 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
return; return;
if (!sectionSyncer.Sync(sdt.getVersionNumber(), sdt.getSectionNumber(), sdt.getLastSectionNumber())) if (!sectionSyncer.Sync(sdt.getVersionNumber(), sdt.getSectionNumber(), sdt.getLastSectionNumber()))
return; 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 sectionSyncer.Repeat(); // let's not miss any section of the SDT
return; return;
} }
dbgsdt("SDT: %2d %2d %2d %s %d\n", sdt.getVersionNumber(), sdt.getSectionNumber(), sdt.getLastSectionNumber(), *cSource::ToString(source), Transponder()); 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; SI::SDT::Service SiSdtService;
for (SI::Loop::Iterator it; sdt.serviceLoop.getNext(SiSdtService, it); ) { for (SI::Loop::Iterator it; sdt.serviceLoop.getNext(SiSdtService, it); ) {
cChannel *channel = Channels.GetByChannelID(tChannelID(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId())); cChannel *Channel = Channels->GetByChannelID(tChannelID(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId()));
if (!channel) if (!Channel)
channel = Channels.GetByChannelID(tChannelID(source, 0, Transponder(), SiSdtService.getServiceId())); Channel = Channels->GetByChannelID(tChannelID(source, 0, Transponder(), SiSdtService.getServiceId()));
if (channel) if (Channel)
channel->SetSeen(); Channel->SetSeen();
cLinkChannels *LinkChannels = NULL; cLinkChannels *LinkChannels = NULL;
SI::Descriptor *d; 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)); sd->providerName.getText(ProviderNameBuf, sizeof(ProviderNameBuf));
char *pp = compactspace(ProviderNameBuf); char *pp = compactspace(ProviderNameBuf);
if (channel) { if (Channel) {
channel->SetId(sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId()); ChannelsModified |= Channel->SetId(Channels, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId());
if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3) 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 // Using SiSdtService.getFreeCaMode() is no good, because some
// tv stations set this flag even for non-encrypted channels :-( // tv stations set this flag even for non-encrypted channels :-(
// The special value 0xFFFF was supposed to mean "unknown encryption" // The special value 0xFFFF was supposed to mean "unknown encryption"
// and would have been overwritten with real CA values later: // 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) { 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); 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(Channel(), pn, ps, pp, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId()); 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 Channel->SetSource(source); // in case this comes from a satellite with a slightly different position
ChannelsModified = true;
patFilter->Trigger(SiSdtService.getServiceId()); 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: { case SI::CaIdentifierDescriptorTag: {
SI::CaIdentifierDescriptor *cid = (SI::CaIdentifierDescriptor *)d; SI::CaIdentifierDescriptor *cid = (SI::CaIdentifierDescriptor *)d;
if (channel) { if (Channel) {
for (SI::Loop::Iterator it; cid->identifiers.hasNext(it); ) for (SI::Loop::Iterator it; cid->identifiers.hasNext(it); )
channel->SetCa(cid->identifiers.getNext(it)); Channel->SetCa(Channels, cid->identifiers.getNext(it));
} }
} }
break; 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 *nrd = (SI::NVODReferenceDescriptor *)d;
SI::NVODReferenceDescriptor::Service Service; SI::NVODReferenceDescriptor::Service Service;
for (SI::Loop::Iterator it; nrd->serviceLoop.getNext(Service, it); ) { 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) { 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()); patFilter->Trigger(Service.getServiceId());
ChannelsModified = true;
} }
if (link) { if (link) {
if (!LinkChannels) if (!LinkChannels)
LinkChannels = new cLinkChannels; LinkChannels = new cLinkChannels;
LinkChannels->Add(new cLinkChannel(link)); 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; delete d;
} }
if (LinkChannels) { if (LinkChannels) {
if (channel) if (Channel)
channel->SetLinkChannels(LinkChannels); ChannelsModified |= Channel->SetLinkChannels(LinkChannels);
else else
delete LinkChannels; delete LinkChannels;
} }
} }
if (sdt.getSectionNumber() == sdt.getLastSectionNumber()) { if (sdt.getSectionNumber() == sdt.getLastSectionNumber()) {
if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3) { 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()) if (source != Source())
Channels.MarkObsoleteChannels(Source(), sdt.getOriginalNetworkId(), sdt.getTransportStreamId()); ChannelsModified |= Channels->MarkObsoleteChannels(Source(), sdt.getOriginalNetworkId(), sdt.getTransportStreamId());
} }
} }
Channels.Unlock(); StateKey.Remove(ChannelsModified);
} }

View File

@ -6,7 +6,7 @@
* *
* Original version written by Udo Richter <udo_richter@gmx.de>. * 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" #include "shutdown.h"
@ -172,9 +172,10 @@ bool cShutdownHandler::ConfirmShutdown(bool Interactive)
return false; return false;
} }
cTimer *timer = Timers.GetNextActiveTimer(); LOCK_TIMERS_READ;
time_t Next = timer ? timer->StartTime() : 0; const cTimer *Timer = Timers->GetNextActiveTimer();
time_t Delta = timer ? Next - time(NULL) : 0; time_t Next = Timer ? Timer->StartTime() : 0;
time_t Delta = Timer ? Next - time(NULL) : 0;
if (cRecordControls::Active() || (Next && Delta <= 0)) { if (cRecordControls::Active() || (Next && Delta <= 0)) {
// VPS recordings in timer end margin may cause Delta <= 0 // VPS recordings in timer end margin may cause Delta <= 0
@ -215,9 +216,10 @@ bool cShutdownHandler::ConfirmRestart(bool Interactive)
return false; return false;
} }
cTimer *timer = Timers.GetNextActiveTimer(); LOCK_TIMERS_READ;
time_t Next = timer ? timer->StartTime() : 0; const cTimer *Timer = Timers->GetNextActiveTimer();
time_t Delta = timer ? Next - time(NULL) : 0; time_t Next = Timer ? Timer->StartTime() : 0;
time_t Delta = Timer ? Next - time(NULL) : 0;
if (cRecordControls::Active() || (Next && Delta <= 0)) { if (cRecordControls::Active() || (Next && Delta <= 0)) {
// VPS recordings in timer end margin may cause 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) bool cShutdownHandler::DoShutdown(bool Force)
{ {
LOCK_TIMERS_READ;
time_t Now = time(NULL); time_t Now = time(NULL);
cTimer *timer = Timers.GetNextActiveTimer(); const cTimer *Timer = Timers->GetNextActiveTimer();
cPlugin *Plugin = cPluginManager::GetNextWakeupPlugin(); 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; time_t NextPlugin = Plugin ? Plugin->WakeupTime() : 0;
if (NextPlugin && (!Next || Next > NextPlugin)) { if (NextPlugin && (!Next || Next > NextPlugin)) {
Next = NextPlugin; Next = NextPlugin;
timer = NULL; Timer = NULL;
} }
time_t Delta = Next ? Next - Now : 0; time_t Delta = Next ? Next - Now : 0;
@ -250,13 +253,13 @@ bool cShutdownHandler::DoShutdown(bool Force)
return false; return false;
Delta = Setup.MinEventTimeout * 60; Delta = Setup.MinEventTimeout * 60;
Next = Now + Delta; Next = Now + Delta;
timer = NULL; Timer = NULL;
dsyslog("reboot at %s", *TimeToString(Next)); dsyslog("reboot at %s", *TimeToString(Next));
} }
if (Next && timer) { if (Next && Timer) {
dsyslog("next timer event at %s", *TimeToString(Next)); 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) { else if (Next && Plugin) {
CallShutdownCommand(Next, 0, Plugin->Name(), Force); CallShutdownCommand(Next, 0, Plugin->Name(), Force);

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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, // "Star Trek: The Next Generation"(R) is a registered trademark of Paramount Pictures,
@ -710,7 +710,7 @@ private:
int lastDiskUsageState; int lastDiskUsageState;
bool lastDiskAlert; bool lastDiskAlert;
double lastSystemLoad; double lastSystemLoad;
int lastTimersState; cStateKey timersStateKey;
time_t lastSignalDisplay; time_t lastSignalDisplay;
int lastLiveIndicatorY; int lastLiveIndicatorY;
bool lastLiveIndicatorTransferring; bool lastLiveIndicatorTransferring;
@ -775,7 +775,6 @@ cSkinLCARSDisplayMenu::cSkinLCARSDisplayMenu(void)
lastEvent = NULL; lastEvent = NULL;
lastRecording = NULL; lastRecording = NULL;
lastSeen = -1; lastSeen = -1;
lastTimersState = -1;
lastSignalDisplay = 0; lastSignalDisplay = 0;
lastLiveIndicatorY = -1; lastLiveIndicatorY = -1;
lastLiveIndicatorTransferring = false; lastLiveIndicatorTransferring = false;
@ -970,7 +969,7 @@ void cSkinLCARSDisplayMenu::SetMenuCategory(eMenuCategory MenuCategory)
xi01 = xm03; xi01 = xm03;
xi02 = xm04; xi02 = xm04;
xi03 = xm05; xi03 = xm05;
lastTimersState = -1; timersStateKey.Reset();
DrawMainFrameLower(); DrawMainFrameLower();
DrawMainBracket(); DrawMainBracket();
DrawStatusElbows(); DrawStatusElbows();
@ -1237,19 +1236,22 @@ void cSkinLCARSDisplayMenu::DrawTimer(const cTimer *Timer, int y, bool MultiRec)
} }
if (Event) if (Event)
osd->DrawText(xs00 + d, y + lineHeight - tinyFont->Height(), Event->Title(), ColorFg, ColorBg, tinyFont, xs03 - xs00 - 2 * d); 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: // 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)); osd->DrawRectangle(xs03 + Gap, y - (MultiRec ? Gap : 0), xs04 - Gap / 2 - 1, y + lineHeight - 1, Theme.Color(clrMenuTimerRecording));
} }
void cSkinLCARSDisplayMenu::DrawTimers(void) void cSkinLCARSDisplayMenu::DrawTimers(void)
{ {
if (Timers.Modified(lastTimersState)) { if (const cTimers *Timers = cTimers::GetTimersRead(timersStateKey)) {
deviceRecording.Clear(); deviceRecording.Clear();
const cFont *font = cFont::GetFont(fontOsd); 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)); osd->DrawRectangle(xs07, ys04, xs13 - 1, ys05 - 1, Theme.Color(clrBackground));
cSortedTimers SortedTimers; cSortedTimers SortedTimers(Timers);
cVector<int> FreeDeviceSlots; cVector<int> FreeDeviceSlots;
int NumDevices = 0; int NumDevices = 0;
int y = ys04; int y = ys04;
@ -1262,7 +1264,14 @@ void cSkinLCARSDisplayMenu::DrawTimers(void)
break; break;
if (const cTimer *Timer = SortedTimers[i]) { if (const cTimer *Timer = SortedTimers[i]) {
if (Timer->Recording()) { 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()) { if (!Device || Device == RecordControl->Device()) {
DrawTimer(Timer, y, NumTimers > 0); DrawTimer(Timer, y, NumTimers > 0);
NumTimers++; NumTimers++;
@ -1313,7 +1322,7 @@ void cSkinLCARSDisplayMenu::DrawTimers(void)
} }
// Total number of active timers: // Total number of active timers:
int NumTimers = 0; 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)) if (Timer->HasFlags(tfActive))
NumTimers++; 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); osd->DrawText(xs08, ys00, itoa(NumDevices), Theme.Color(clrMenuFrameFg), frameColor, font, xs09 - xs08, ys01 - ys00, taBottom | taRight | taBorder);
lastSignalDisplay = 0; lastSignalDisplay = 0;
initial = true; // forces redrawing of devices initial = true; // forces redrawing of devices
timersStateKey.Remove();
} }
} }
@ -1423,25 +1433,23 @@ void cSkinLCARSDisplayMenu::DrawLive(const cChannel *Channel)
DrawSeen(0, 0); DrawSeen(0, 0);
} }
// The current programme: // The current programme:
cSchedulesLock SchedulesLock; LOCK_SCHEDULES_READ;
if (const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock)) { if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) {
if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) { const cEvent *Event = Schedule->GetPresentEvent();
const cEvent *Event = Schedule->GetPresentEvent(); if (initial || Event != lastEvent) {
if (initial || Event != lastEvent) { DrawInfo(Event, true);
DrawInfo(Event, true); lastEvent = Event;
lastEvent = Event; lastSeen = -1;
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);
} }
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) { if (MenuCategory() == mcMain) {
cDevice *Device = cDevice::PrimaryDevice(); cDevice *Device = cDevice::PrimaryDevice();
if (!Device->Replaying() || Device->Transferring()) { 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); DrawLive(Channel);
} }
else if (cControl *Control = cControl::Control(true)) else if (cControl *Control = cControl::Control(true))

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "sourceparams.h"
@ -33,7 +33,7 @@ cSourceParam::cSourceParam(char Source, const char *Description)
cSourceParams SourceParams; cSourceParams SourceParams;
cSourceParam *cSourceParams::Get(char Source) const cSourceParam *cSourceParams::Get(char Source)
{ {
for (cSourceParam *sp = First(); sp; sp = Next(sp)) { for (cSourceParam *sp = First(); sp; sp = Next(sp)) {
if (sp->Source() == Source) if (sp->Source() == Source)

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __SOURCEPARAMS_H
@ -45,7 +45,7 @@ public:
class cSourceParams : public cList<cSourceParam> { class cSourceParams : public cList<cSourceParam> {
public: public:
cSourceParam *Get(char Source) const; cSourceParam *Get(char Source);
}; };
extern cSourceParams SourceParams; extern cSourceParams SourceParams;

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __STATUS_H
@ -15,7 +15,7 @@
#include "player.h" #include "player.h"
#include "tools.h" #include "tools.h"
enum eTimerChange { tcMod, tcAdd, tcDel }; enum eTimerChange { tcMod, tcAdd, tcDel }; // tcMod is obsolete and no longer used!
class cTimer; class cTimer;
@ -29,10 +29,7 @@ protected:
// require a retune. // require a retune.
virtual void TimerChange(const cTimer *Timer, eTimerChange Change) {} virtual void TimerChange(const cTimer *Timer, eTimerChange Change) {}
// Indicates a change in the timer settings. // Indicates a change in the timer settings.
// If Change is tcAdd or tcDel, Timer points to the timer that has // Timer points to the timer that has been added or will be deleted, respectively.
// 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.
virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber, bool LiveView) {} virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber, bool LiveView) {}
// Indicates a channel switch on the given DVB device. // Indicates a channel switch on the given DVB device.
// If ChannelNumber is 0, this is before the channel is being switched, // If ChannelNumber is 0, this is before the channel is being switched,

1341
svdrp.c

File diff suppressed because it is too large Load Diff

21
svdrp.h
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __SVDRP_H
@ -40,20 +40,35 @@ public:
///< to the command. The response strings are exactly as received, ///< to the command. The response strings are exactly as received,
///< with the leading three digit reply code and possible continuation ///< with the leading three digit reply code and possible continuation
///< line indicator ('-') in place. ///< 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. ///< This is a convenience function for accessing the response strings.
///< Returns the string at the given Index, or NULL if Index is out ///< Returns the string at the given Index, or NULL if Index is out
///< of range. ///< 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 SetSVDRPGrabImageDir(const char *GrabImageDir);
void StartSVDRPHandler(int TcpPort, int UdpPort); void StartSVDRPHandler(int TcpPort, int UdpPort);
void StopSVDRPHandler(void); void StopSVDRPHandler(void);
void SendSVDRPDiscover(const char *Address = NULL); 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, ///< 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 ///< and stores it in the given ServerNames list. The list is cleared
///< before getting the server names. ///< 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. ///< Returns true if the resulting list is not empty.
#endif //__SVDRP_H #endif //__SVDRP_H

138
thread.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "thread.h"
@ -21,6 +21,16 @@
#include <unistd.h> #include <unistd.h>
#include "tools.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) static bool GetAbsTime(struct timespec *Abstime, int MillisecondsFromNow)
{ {
struct timeval now; struct timeval now;
@ -403,6 +413,132 @@ bool cThreadLock::Lock(cThread *Thread)
return false; 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 ----------------------------------------------------------- // --- cIoThrottle -----------------------------------------------------------
cMutex cIoThrottle::mutex; cMutex cIoThrottle::mutex;

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __THREAD_H
@ -164,6 +164,94 @@ public:
#define LOCK_THREAD cThreadLock ThreadLock(this) #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 { class cIoThrottle {
private: private:
static cMutex mutex; static cMutex mutex;

326
timers.c
View File

@ -4,18 +4,18 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 "timers.h"
#include <ctype.h> #include <ctype.h>
#include "channels.h"
#include "device.h" #include "device.h"
#include "i18n.h" #include "i18n.h"
#include "libsi/si.h" #include "libsi/si.h"
#include "recording.h" #include "recording.h"
#include "remote.h" #include "remote.h"
#include "status.h" #include "status.h"
#include "svdrp.h"
// IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d' // 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 // format characters in order to allow any number of blanks after a numeric
@ -23,19 +23,21 @@
// --- cTimer ---------------------------------------------------------------- // --- cTimer ----------------------------------------------------------------
cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel) cTimer::cTimer(bool Instant, bool Pause, const cChannel *Channel)
{ {
startTime = stopTime = 0; startTime = stopTime = 0;
lastSetEvent = 0; scheduleState = -1;
deferred = 0; deferred = 0;
recording = pending = inVpsMargin = false; pending = inVpsMargin = false;
flags = tfNone; flags = tfNone;
*file = 0; *file = 0;
aux = NULL; aux = NULL;
remote = NULL;
event = NULL; event = NULL;
if (Instant) if (Instant)
SetFlags(tfActive | tfInstant); 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); time_t t = time(NULL);
struct tm tm_r; struct tm tm_r;
struct tm *now = localtime_r(&t, &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; start = now->tm_hour * 100 + now->tm_min;
stop = 0; stop = 0;
if (!Setup.InstantRecordTime && channel && (Instant || Pause)) { if (!Setup.InstantRecordTime && channel && (Instant || Pause)) {
cSchedulesLock SchedulesLock; LOCK_SCHEDULES_READ;
if (const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock)) { if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) {
if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) { if (const cEvent *Event = Schedule->GetPresentEvent()) {
if (const cEvent *Event = Schedule->GetPresentEvent()) { time_t tstart = Event->StartTime();
time_t tstart = Event->StartTime(); time_t tstop = Event->EndTime();
time_t tstop = Event->EndTime(); if (Event->Vps() && Setup.UseVps) {
if (Event->Vps() && Setup.UseVps) { SetFlags(tfVps);
SetFlags(tfVps); tstart = Event->Vps();
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);
} }
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) cTimer::cTimer(const cEvent *Event)
{ {
startTime = stopTime = 0; startTime = stopTime = 0;
lastSetEvent = 0; scheduleState = -1;
deferred = 0; deferred = 0;
recording = pending = inVpsMargin = false; pending = inVpsMargin = false;
flags = tfActive; flags = tfActive;
*file = 0; *file = 0;
aux = NULL; aux = NULL;
remote = NULL;
event = NULL; event = NULL;
if (Event->Vps() && Setup.UseVps) if (Event->Vps() && Setup.UseVps)
SetFlags(tfVps); 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 tstart = (flags & tfVps) ? Event->Vps() : Event->StartTime();
time_t tstop = tstart + Event->Duration(); time_t tstop = tstart + Event->Duration();
if (!(HasFlags(tfVps))) { if (!(HasFlags(tfVps))) {
@ -120,6 +122,7 @@ cTimer::cTimer(const cTimer &Timer)
{ {
channel = NULL; channel = NULL;
aux = NULL; aux = NULL;
remote = NULL;
event = NULL; event = NULL;
flags = tfNone; flags = tfNone;
*this = Timer; *this = Timer;
@ -127,7 +130,10 @@ cTimer::cTimer(const cTimer &Timer)
cTimer::~cTimer() cTimer::~cTimer()
{ {
if (event)
event->DecNumTimers();
free(aux); free(aux);
free(remote);
} }
cTimer& cTimer::operator= (const cTimer &Timer) cTimer& cTimer::operator= (const cTimer &Timer)
@ -136,9 +142,8 @@ cTimer& cTimer::operator= (const cTimer &Timer)
uint OldFlags = flags & tfRecording; uint OldFlags = flags & tfRecording;
startTime = Timer.startTime; startTime = Timer.startTime;
stopTime = Timer.stopTime; stopTime = Timer.stopTime;
lastSetEvent = 0; scheduleState = -1;
deferred = 0; deferred = 0;
recording = Timer.recording;
pending = Timer.pending; pending = Timer.pending;
inVpsMargin = Timer.inVpsMargin; inVpsMargin = Timer.inVpsMargin;
flags = Timer.flags | OldFlags; flags = Timer.flags | OldFlags;
@ -152,7 +157,13 @@ cTimer& cTimer::operator= (const cTimer &Timer)
strncpy(file, Timer.file, sizeof(file)); strncpy(file, Timer.file, sizeof(file));
free(aux); free(aux);
aux = Timer.aux ? strdup(Timer.aux) : NULL; 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; return *this;
} }
@ -178,7 +189,7 @@ cString cTimer::ToText(bool UseChannelID) const
cString cTimer::ToDescr(void) 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) int cTimer::TimeToInt(int t)
@ -313,7 +324,6 @@ bool cTimer::Parse(const char *s)
} }
bool result = false; 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)) { 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)) { if (aux && !*skipspace(aux)) {
free(aux); free(aux);
aux = NULL; aux = NULL;
@ -322,10 +332,11 @@ bool cTimer::Parse(const char *s)
result = ParseDay(daybuffer, day, weekdays); result = ParseDay(daybuffer, day, weekdays);
Utf8Strn0Cpy(file, filebuffer, sizeof(file)); Utf8Strn0Cpy(file, filebuffer, sizeof(file));
strreplace(file, '|', ':'); strreplace(file, '|', ':');
LOCK_CHANNELS_READ;
if (isnumber(channelbuffer)) if (isnumber(channelbuffer))
channel = Channels.GetByNumber(atoi(channelbuffer)); channel = Channels->GetByNumber(atoi(channelbuffer));
else else
channel = Channels.GetByChannelID(tChannelID::FromString(channelbuffer), true, true); channel = Channels->GetByChannelID(tChannelID::FromString(channelbuffer), true, true);
if (!channel) { if (!channel) {
esyslog("ERROR: channel %s not defined", channelbuffer); esyslog("ERROR: channel %s not defined", channelbuffer);
result = false; result = false;
@ -340,7 +351,9 @@ bool cTimer::Parse(const char *s)
bool cTimer::Save(FILE *f) 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 bool cTimer::IsSingleEvent(void) const
@ -441,10 +454,8 @@ bool cTimer::Matches(time_t t, bool Directly, int Margin) const
startTime = event->StartTime(); startTime = event->StartTime();
stopTime = event->EndTime(); stopTime = event->EndTime();
if (!Margin) { // this is an actual check if (!Margin) { // this is an actual check
if (event->Schedule()->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) { // VPS control can only work with up-to-date events... 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);
return event->IsRunning(true);
}
return startTime <= t && t < stopTime; // ...otherwise we fall back to normal timer handling 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 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. #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()); const cSchedule *Schedule = Schedules->GetSchedule(Channel());
if (Schedule && Schedule->Events()->First()) { if (Schedule && Schedule->Events()->First()) {
time_t now = time(NULL); if (Schedule->Modified(scheduleState)) {
if (!lastSetEvent || Schedule->Modified() >= lastSetEvent) {
lastSetEvent = now;
const cEvent *Event = NULL; const cEvent *Event = NULL;
if (HasFlags(tfVps) && Schedule->Events()->First()->Vps()) { 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: // 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)) { for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
if (e->StartTime() && e->RunningStatus() != SI::RunningStatusNotRunning) { // skip outdated events 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 != Event) {
if (Event) if (event)
event->DecNumTimers();
if (Event) {
isyslog("timer %s set to event %s", *ToDescr(), *Event->ToDescr()); 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()); isyslog("timer %s set to no event", *ToDescr());
scheduleState = -1;
}
event = Event; event = Event;
return true;
} }
return false;
} }
void cTimer::SetRecording(bool Recording) void cTimer::SetRecording(bool Recording)
{ {
recording = Recording; if (Recording)
if (recording)
SetFlags(tfRecording); SetFlags(tfRecording);
else else
ClrFlags(tfRecording); ClrFlags(tfRecording);
isyslog("timer %s %s", *ToDescr(), recording ? "start" : "stop"); isyslog("timer %s %s", *ToDescr(), Recording ? "start" : "stop");
} }
void cTimer::SetPending(bool Pending) void cTimer::SetPending(bool Pending)
@ -637,7 +643,13 @@ void cTimer::SetLifetime(int Lifetime)
void cTimer::SetAux(const char *Aux) void cTimer::SetAux(const char *Aux)
{ {
free(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) void cTimer::SetDeferred(int Seconds)
@ -691,20 +703,33 @@ void cTimer::OnOff(void)
// --- cTimers --------------------------------------------------------------- // --- cTimers ---------------------------------------------------------------
cTimers Timers; cTimers cTimers::timers;
cTimers::cTimers(void) cTimers::cTimers(void)
:cConfig<cTimer>("Timers")
{ {
state = 0;
beingEdited = 0;;
lastSetEvents = 0;
lastDeleteExpired = 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) cTimer *cTimers::GetTimer(cTimer *Timer)
{ {
for (cTimer *ti = First(); ti; ti = Next(ti)) { 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->WeekDays() && ti->WeekDays() == Timer->WeekDays() || !ti->WeekDays() && ti->Day() == Timer->Day()) &&
ti->Start() == Timer->Start() && ti->Start() == Timer->Start() &&
ti->Stop() == Timer->Stop()) ti->Stop() == Timer->Stop())
@ -713,12 +738,12 @@ cTimer *cTimers::GetTimer(cTimer *Timer)
return NULL; return NULL;
} }
cTimer *cTimers::GetMatch(time_t t) const cTimer *cTimers::GetMatch(time_t t) const
{ {
static int LastPending = -1; static int LastPending = -1;
cTimer *t0 = NULL; const cTimer *t0 = NULL;
for (cTimer *ti = First(); ti; ti = Next(ti)) { for (const cTimer *ti = First(); ti; ti = Next(ti)) {
if (!ti->Recording() && ti->Matches(t)) { if (!ti->Remote() && !ti->Recording() && ti->Matches(t)) {
if (ti->Pending()) { if (ti->Pending()) {
if (ti->Index() > LastPending) { if (ti->Index() > LastPending) {
LastPending = ti->Index(); LastPending = ti->Index();
@ -736,11 +761,11 @@ cTimer *cTimers::GetMatch(time_t t)
return t0; 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; 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); eTimerMatch tm = ti->Matches(Event);
if (tm > m) { if (tm > m) {
t = ti; t = ti;
@ -754,21 +779,27 @@ cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match)
return t; return t;
} }
cTimer *cTimers::GetNextActiveTimer(void) const cTimer *cTimers::GetNextActiveTimer(void) const
{ {
cTimer *t0 = NULL; const cTimer *t0 = NULL;
for (cTimer *ti = First(); ti; ti = Next(ti)) { for (const cTimer *ti = First(); ti; ti = Next(ti)) {
ti->Matches(); if (!ti->Remote()) {
if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0)) ti->Matches();
t0 = ti; if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
t0 = ti;
}
} }
return t0; return t0;
} }
void cTimers::SetModified(void) const cTimers *cTimers::GetTimersRead(cStateKey &StateKey, int TimeoutMs)
{ {
cStatus::MsgTimerChange(NULL, tcMod); return timers.Lock(StateKey, false, TimeoutMs) ? &timers : NULL;
state++; }
cTimers *cTimers::GetTimersWrite(cStateKey &StateKey, int TimeoutMs)
{
return timers.Lock(StateKey, true, TimeoutMs) ? &timers : NULL;
} }
void cTimers::Add(cTimer *Timer, cTimer *After) void cTimers::Add(cTimer *Timer, cTimer *After)
@ -789,46 +820,113 @@ void cTimers::Del(cTimer *Timer, bool DeleteObject)
cConfig<cTimer>::Del(Timer, DeleteObject); cConfig<cTimer>::Del(Timer, DeleteObject);
} }
bool cTimers::Modified(int &State) const cTimer *cTimers::UsesChannel(const cChannel *Channel) const
{ {
bool Result = state != State; for (const cTimer *Timer = First(); Timer; Timer = Next(Timer)) {
State = state; if (Timer->Channel() == Channel)
return Result; return Timer;
}
return NULL;
} }
void cTimers::SetEvents(void) bool cTimers::SetEvents(const cSchedules *Schedules)
{ {
if (time(NULL) - lastSetEvents < 5) bool TimersModified = false;
return; for (cTimer *ti = First(); ti; ti = Next(ti))
cSchedulesLock SchedulesLock(false, 100); TimersModified |= ti->SetEventFromSchedule(Schedules);
const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock); return TimersModified;
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);
} }
void cTimers::DeleteExpired(void) bool cTimers::DeleteExpired(void)
{ {
if (time(NULL) - lastDeleteExpired < 30) if (time(NULL) - lastDeleteExpired < 30)
return; return false;
bool TimersModified = false;
cTimer *ti = First(); cTimer *ti = First();
while (ti) { while (ti) {
cTimer *next = Next(ti); cTimer *next = Next(ti);
if (ti->Expired()) { if (!ti->Remote() && ti->Expired()) {
isyslog("deleting timer %s", *ti->ToDescr()); isyslog("deleting timer %s", *ti->ToDescr());
Del(ti); Del(ti);
SetModified(); TimersModified = true;
} }
ti = next; ti = next;
} }
lastDeleteExpired = time(NULL); 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 --------------------------------------------------------- // --- cSortedTimers ---------------------------------------------------------
@ -838,10 +936,10 @@ static int CompareTimers(const void *a, const void *b)
return (*(const cTimer **)a)->Compare(**(const cTimer **)b); return (*(const cTimer **)a)->Compare(**(const cTimer **)b);
} }
cSortedTimers::cSortedTimers(void) cSortedTimers::cSortedTimers(const cTimers *Timers)
:cVector<const cTimer *>(Timers.Count()) :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); Append(Timer);
Sort(CompareTimers); Sort(CompareTimers);
} }

119
timers.h
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __TIMERS_H
@ -28,11 +28,11 @@ class cTimer : public cListObject {
friend class cMenuEditTimer; friend class cMenuEditTimer;
private: private:
mutable time_t startTime, stopTime; 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 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; 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 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 weekdays; ///< bitmask, lowest bits: SSFTWTM (the 'M' is the LSB)
int start; int start;
@ -41,15 +41,16 @@ private:
int lifetime; 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 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 *aux;
char *remote;
const cEvent *event; const cEvent *event;
public: 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 cEvent *Event);
cTimer(const cTimer &Timer); cTimer(const cTimer &Timer);
virtual ~cTimer(); virtual ~cTimer();
cTimer& operator= (const cTimer &Timer); cTimer& operator= (const cTimer &Timer);
virtual int Compare(const cListObject &ListObject) const; 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 Pending(void) const { return pending; }
bool InVpsMargin(void) const { return inVpsMargin; } bool InVpsMargin(void) const { return inVpsMargin; }
uint Flags(void) const { return flags; } uint Flags(void) const { return flags; }
@ -63,6 +64,7 @@ public:
const char *File(void) const { return file; } const char *File(void) const { return file; }
time_t FirstDay(void) const { return weekdays ? day : 0; } time_t FirstDay(void) const { return weekdays ? day : 0; }
const char *Aux(void) const { return aux; } const char *Aux(void) const { return aux; }
const char *Remote(void) const { return remote; }
time_t Deferred(void) const { return deferred; } time_t Deferred(void) const { return deferred; }
cString ToText(bool UseChannelID = false) const; cString ToText(bool UseChannelID = false) const;
cString ToDescr(void) const; cString ToDescr(void) const;
@ -81,8 +83,8 @@ public:
bool Expired(void) const; bool Expired(void) const;
time_t StartTime(void) const; time_t StartTime(void) const;
time_t StopTime(void) const; time_t StopTime(void) const;
void SetEventFromSchedule(const cSchedules *Schedules = NULL); bool SetEventFromSchedule(const cSchedules *Schedules);
void SetEvent(const cEvent *Event); bool SetEvent(const cEvent *Event);
void SetRecording(bool Recording); void SetRecording(bool Recording);
void SetPending(bool Pending); void SetPending(bool Pending);
void SetInVpsMargin(bool InVpsMargin); void SetInVpsMargin(bool InVpsMargin);
@ -93,6 +95,7 @@ public:
void SetPriority(int Priority); void SetPriority(int Priority);
void SetLifetime(int Lifetime); void SetLifetime(int Lifetime);
void SetAux(const char *Aux); void SetAux(const char *Aux);
void SetRemote(const char *Remote);
void SetDeferred(int Seconds); void SetDeferred(int Seconds);
void SetFlags(uint Flags); void SetFlags(uint Flags);
void ClrFlags(uint Flags); void ClrFlags(uint Flags);
@ -108,36 +111,100 @@ public:
class cTimers : public cConfig<cTimer> { class cTimers : public cConfig<cTimer> {
private: private:
int state; static cTimers timers;
int beingEdited;
time_t lastSetEvents;
time_t lastDeleteExpired; time_t lastDeleteExpired;
public: public:
cTimers(void); 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 *GetTimer(cTimer *Timer);
cTimer *GetMatch(time_t t); const cTimer *GetMatch(time_t t) const;
cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL); cTimer *GetMatch(time_t t) { return const_cast<cTimer *>(static_cast<const cTimers *>(this)->GetMatch(t)); };
cTimer *GetNextActiveTimer(void); const cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL) const;
int BeingEdited(void) { return beingEdited; } cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL) { return const_cast<cTimer *>(static_cast<const cTimers *>(this)->GetMatch(Event, Match)); }
void IncBeingEdited(void) { beingEdited++; } const cTimer *GetNextActiveTimer(void) const;
void DecBeingEdited(void) { if (!--beingEdited) lastSetEvents = 0; } const cTimer *UsesChannel(const cChannel *Channel) const;
void SetModified(void); bool SetEvents(const cSchedules *Schedules);
bool Modified(int &State); bool DeleteExpired(void);
///< 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);
void Add(cTimer *Timer, cTimer *After = NULL); void Add(cTimer *Timer, cTimer *After = NULL);
void Ins(cTimer *Timer, cTimer *Before = NULL); void Ins(cTimer *Timer, cTimer *Before = NULL);
void Del(cTimer *Timer, bool DeleteObject = true); 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 *> { class cSortedTimers : public cVector<const cTimer *> {
public: public:
cSortedTimers(void); cSortedTimers(const cTimers *Timers);
}; };
#endif //__TIMERS_H #endif //__TIMERS_H

90
tools.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "tools.h"
@ -2044,12 +2044,58 @@ int cListObject::Index(void) const
return i; 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::cListBase(void) cListBase::cListBase(const char *NeedsLocking)
:stateLock(NeedsLocking)
{ {
objects = lastObject = NULL; objects = lastObject = NULL;
count = 0; count = 0;
needsLocking = NeedsLocking;
useGarbageCollector = needsLocking;
} }
cListBase::~cListBase() cListBase::~cListBase()
@ -2057,6 +2103,15 @@ cListBase::~cListBase()
Clear(); 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) void cListBase::Add(cListObject *Object, cListObject *After)
{ {
if (After && After != lastObject) { if (After && After != lastObject) {
@ -2096,8 +2151,12 @@ void cListBase::Del(cListObject *Object, bool DeleteObject)
if (Object == lastObject) if (Object == lastObject)
lastObject = Object->Prev(); lastObject = Object->Prev();
Object->Unlink(); Object->Unlink();
if (DeleteObject) if (DeleteObject) {
delete Object; if (useGarbageCollector)
ListGarbageCollector.Put(Object);
else
delete Object;
}
count--; count--;
} }
@ -2141,11 +2200,30 @@ void cListBase::Clear(void)
count = 0; 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) if (Index < 0)
return NULL; return NULL;
cListObject *object = objects; const cListObject *object = objects;
while (object && Index-- > 0) while (object && Index-- > 0)
object = object->Next(); object = object->Next();
return object; return object;

137
tools.h
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __TOOLS_H
@ -26,6 +26,7 @@
#include <syslog.h> #include <syslog.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include "thread.h"
typedef unsigned char uchar; typedef unsigned char uchar;
@ -462,6 +463,7 @@ public:
}; };
class cListObject { class cListObject {
friend class cListGarbageCollector;
private: private:
cListObject *prev, *next; cListObject *prev, *next;
public: public:
@ -478,33 +480,152 @@ public:
cListObject *Next(void) const { return next; } 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 { class cListBase {
protected: protected:
cListObject *objects, *lastObject; cListObject *objects, *lastObject;
cListBase(void);
int count; int count;
mutable cStateLock stateLock;
const char *needsLocking;
bool useGarbageCollector;
cListBase(const char *NeedsLocking = NULL);
public: public:
virtual ~cListBase(); 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 Add(cListObject *Object, cListObject *After = NULL);
void Ins(cListObject *Object, cListObject *Before = NULL); void Ins(cListObject *Object, cListObject *Before = NULL);
void Del(cListObject *Object, bool DeleteObject = true); void Del(cListObject *Object, bool DeleteObject = true);
virtual void Move(int From, int To); virtual void Move(int From, int To);
void Move(cListObject *From, cListObject *To); void Move(cListObject *From, cListObject *To);
virtual void Clear(void); 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; } int Count(void) const { return count; }
void Sort(void); void Sort(void);
}; };
template<class T> class cList : public cListBase { template<class T> class cList : public cListBase {
public: public:
T *Get(int Index) const { return (T *)cListBase::Get(Index); } cList(const char *NeedsLocking = NULL): cListBase(NeedsLocking) {}
T *First(void) const { return (T *)objects; } ///< Sets up a new cList of the given type T. If NeedsLocking is given, the list
T *Last(void) const { return (T *)lastObject; } ///< and any of its elements may only be accessed if the caller holds a lock
T *Prev(const T *object) const { return (T *)object->cListObject::Prev(); } // need to call cListObject's members to ///< obtained by a call to Lock() (see cListBase::Lock() for details).
T *Next(const T *object) const { return (T *)object->cListObject::Next(); } // avoid ambiguities in case of a "list of lists" ///< 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 { 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! ///< cVector may only be used for *simple* types, like int or pointers - not for class objects that allocate additional memory!
private: private:

298
vdr.c
View File

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

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "videodir.h"
@ -19,24 +19,31 @@
#include "recording.h" #include "recording.h"
#include "tools.h" #include "tools.h"
cMutex cVideoDirectory::mutex;
cString cVideoDirectory::name; cString cVideoDirectory::name;
cVideoDirectory *cVideoDirectory::current = NULL; cVideoDirectory *cVideoDirectory::current = NULL;
cVideoDirectory::cVideoDirectory(void) cVideoDirectory::cVideoDirectory(void)
{ {
mutex.Lock();
delete current; delete current;
current = this; current = this;
mutex.Unlock();
} }
cVideoDirectory::~cVideoDirectory() cVideoDirectory::~cVideoDirectory()
{ {
mutex.Lock();
current = NULL; current = NULL;
mutex.Unlock();
} }
cVideoDirectory *cVideoDirectory::Current(void) cVideoDirectory *cVideoDirectory::Current(void)
{ {
mutex.Lock();
if (!current) if (!current)
current = new cVideoDirectory; new cVideoDirectory;
mutex.Unlock();
return current; return current;
} }
@ -141,7 +148,8 @@ int cVideoDirectory::VideoDiskSpace(int *FreeMB, int *UsedMB)
{ {
int used = 0; int used = 0;
int free = Current()->FreeMB(&used); int free = Current()->FreeMB(&used);
int deleted = DeletedRecordings.TotalFileSizeMB(); LOCK_DELETEDRECORDINGS_READ;
int deleted = DeletedRecordings->TotalFileSizeMB();
if (deleted > used) if (deleted > used)
deleted = used; // let's not get beyond 100% deleted = used; // let's not get beyond 100%
free += deleted; free += deleted;
@ -202,7 +210,8 @@ bool cVideoDiskUsage::HasChanged(int &State)
if (FreeMB != freeMB) { if (FreeMB != freeMB) {
usedPercent = UsedPercent; usedPercent = UsedPercent;
freeMB = FreeMB; freeMB = FreeMB;
double MBperMinute = Recordings.MBperMinute(); LOCK_RECORDINGS_READ;
double MBperMinute = Recordings->MBperMinute();
if (MBperMinute <= 0) if (MBperMinute <= 0)
MBperMinute = MB_PER_MINUTE; MBperMinute = MB_PER_MINUTE;
freeMinutes = int(double(FreeMB) / MBperMinute); freeMinutes = int(double(FreeMB) / MBperMinute);

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __VIDEODIR_H
@ -15,6 +15,7 @@
class cVideoDirectory { class cVideoDirectory {
private: private:
static cMutex mutex;
static cString name; static cString name;
static cVideoDirectory *current; static cVideoDirectory *current;
static cVideoDirectory *Current(void); static cVideoDirectory *Current(void);