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.
- Official release.
2015-04-29: Version 2.3.1
2015-09-01: Version 2.3.1
- The new function cOsd::MaxPixmapSize() can be called to determine the maximum size
a cPixmap may have on the current OSD. The 'osddemo' example has been modified
@ -8671,3 +8671,114 @@ Video Disk Recorder Revision History
this VDR is connected to via SVDRP.
- The new class cSVDRPCommand can be used to execute an SVDRP command on one of
the servers this VDR is connected to, and retrieve the result.
- The cTimer class now has a new member named 'remote', which holds the name of the
remote server this timer will record on. If this is NULL, it is a local timer.
- Timers from other VDRs that are connected to this VDR via SVDRP are now
automatically fetched and stored in the global Timers list. In order for this
to work, all of the channels used by timers on the remote VDR must also be
defined on the local VDR (however, not necessarily in the same sequence).
Automatic channel syncing will be implemented later.
- The main menu of the LCARS skin now displays a small rectangle on the left side
of a timer if this is a remote timer. The color of that rectangle changes if
the timer is currently recording on the remote VDR.
- Accessing the global Timers list now has to be protected by proper locking,
because SVDRP commands are now executed in a separate thread.
The introduction of this locking mechanism required the following changes:
+ The new classes cStateLock and cStateKey are used to implement locking
with quick detection of state changes.
+ cConfig::cConfig() now has a parameter that indicates whether this list
requires locking.
+ The global lists of Timers, Channels, Schedules and Recordings are no longer
static variables. They are now pointers that need to be retrieved through
a call to cTimers::GetTimersRead/Write(), cChannels::GetChannelsRead/Write(),
cSchedules::GetSchedulesRead/Write() and cRecordings::GetRecordingsRead/Write(),
respectively.
+ References from/to link channels are now removed in cChannels::Del() rather
than cChannel::~cChannel(), to make sure the caller holds a proper lock.
+ cChannel::HasTimer() has been removed. This information is now retrieved
via cSchedule::HasTimer().
+ Several member functions of cChannel, cTimer, cMarks and cRecording have
been made 'const', and some of them are now available as both 'const' and
'non-const' versions.
+ The cChannel::Set...() functions are now 'bool' and return true if they have
actually changed any of the channels's members.
+ cChannels::SetModified() has been renamed to cChannels::SetModifiedByUser().
+ cChannels::Modified() has been renamed to cChannels::ModifiedByUser(), and
now has a 'State' parameter that allows the caller to see whether a channel
has been modified since the last call to this function with the same State
variable.
+ The macros CHANNELSMOD_NONE/_AUTO/_USER have been removed.
+ cMarks now requires locking via cStateKey.
+ cSortedTimers now requires a pointer to the list of timers.
+ cEvent::HasTimer() no longer scans the list of timers to check whether an event
is referenced by a timer, but rather keeps score of how many timers reference
it. This was necessary in order to avoid having to lock the list of timers from
within a cEvent.
+ The new class cListGarbageCollector is used to temporary store any objects deleted
from cLists that require locking. This allows pointers to such objects to be
dereferenced even if the objects are no longer part of the list.
+ cListBase::Contains() can be used to check whether a particular object is still
contained in that list.
+ Outdated events are no longer "phased out", but rather deleted right away and thus
taken care of by the new "garbage collector" of the list.
+ Deleted cRecording objects are no longer kept in a list of "vanished" recordings,
but are rather taken care of by the new "garbage collector" of the list.
+ cSchedules::ClearAll() has been removed. The functionality is now implemented
directly in cSVDRPServer::CmdCLRE().
+ tEventID has been changed to u_int16_t in order to make room for the new member
numTimers in cEvent.
+ cSchedule now has a member Modified(), which can be used with a State variable
to quickly determine whether this schedule has been modified since the last call
to this function with the same State variable.
+ cSchedulesLock has been removed. Locking the list of schedules is now done via
the cList's new locking mechanism.
+ The 'OnlyRunningStatus' parameters in cEpgHandler::BeginSegmentTransfer() and
cEpgHandler::EndSegmentTransfer() are now obsolete. They are still present in
the interface for backward compatibility, but may be removed in a future version.
Their value is always 'false'.
+ The constant tcMod is no longer used in cStatus::TimerChange(). The definition is
still there for backward compatibility.
Plugins that access the global lists of Timers, Channels, Recordings or Schedules
will need to be adapted as follows:
+ Instead of directly accessing the global variables Timers, Channels or Recordings,
they need to set up a cStateKey variable and call the proper getter function,
as in
cStateKey StateKey;
if (const cTimers *Timers = cTimers::GetTimersRead(StateKey)) {
// access the timers
StateKey.Remove();
}
and
cStateKey StateKey;
if (cTimers *Timers = cTimers::GetTimersWrite(StateKey)) {
// access the timers
StateKey.Remove();
}
See timers.h, thread.h and tools.h for details on this new locking mechanism.
+ There are convenience macros for easily accessing these lists without having
to explicitly set up a cStateKey and calling its Remove() function. These macros
have the form LOCK_*_READ/WRITE (with '*' being TIMERS, CHANNELS, SCHEDULES or
RECORDINGS). Simply put such a macro before the point where you need to access
the respective list, and there will be a pointer named Timers, Channels, Schedules
or Recordings, respectively, which is valid until the end of the current block.
+ If a plugin needs to access several of the global lists in parallel, locking must
always be done in the sequence Timers, Channels, Recordings, Schedules. This is
necessary to make sure that different threads that need to lock several lists at
the same time don't end up in a deadlock.
+ Some pointer variables may need to be made 'const'. The compiler will tell you
about these.
- cSectionSyncer has been improved to better handle missed sections.
- Added a missing initialization of 'seen' in cChannel's copy constructor.
- Background modifications of channels, timers and events are now displayed immediately
in the corresponding menus.
- cEIT now checks the version of the tables before doing any processing, which saves
a lot of locking and processing.
- If a timer is newly created with the Red button in the Schedule menu, and the timer
is presented to the user in the "Edit timer" menu because it will start immediately,
it now *must* be confirmed with "Ok" to set the timer. Otherwise the timer will not
be created.
- Recordings and deleted recordings are now scanned in a single thread.
- The new SVDRP command POLL is used by automatically established peer-to-peer
connections to trigger fetching remote timers.
- You can now set DumpSVDRPDataTransfer in svdrp.c to true to have all SVDRP
communication printed to the console for debugging.

View File

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

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: channels.h 4.1 2015/03/13 11:20:50 kls Exp $
* $Id: channels.h 4.2 2015/08/17 09:39:48 kls Exp $
*/
#ifndef __CHANNELS_H
@ -28,10 +28,6 @@
#define CHANNELMOD_LANGS 0x40
#define CHANNELMOD_RETUNE (CHANNELMOD_PIDS | CHANNELMOD_CA | CHANNELMOD_TRANSP)
#define CHANNELSMOD_NONE 0
#define CHANNELSMOD_AUTO 1
#define CHANNELSMOD_USER 2
#define MAXAPIDS 32 // audio
#define MAXDPIDS 16 // dolby (AC3 + DTS)
#define MAXSPIDS 32 // subtitles
@ -86,6 +82,7 @@ class cLinkChannels : public cList<cLinkChannel> {
};
class cSchedule;
class cChannels;
class cChannel : public cListObject {
friend class cSchedules;
@ -128,7 +125,7 @@ private:
mutable cString nameSource;
mutable cString shortNameSource;
cString parameters;
int modification;
mutable int modification;
time_t seen; // When this channel was last seen in the SDT of its transponder
mutable const cSchedule *schedule;
cLinkChannels *linkChannels;
@ -188,66 +185,84 @@ public:
bool IsTerr(void) const { return cSource::IsTerr(source); }
bool IsSourceType(char Source) const { return cSource::IsType(source, Source); }
tChannelID GetChannelID(void) const { return tChannelID(source, nid, (nid || tid) ? tid : Transponder(), sid, rid); }
bool HasTimer(void) const;
int Modification(int Mask = CHANNELMOD_ALL);
time_t Seen(void) { return seen; }
int Modification(int Mask = CHANNELMOD_ALL) const;
time_t Seen(void) const { return seen; }
void CopyTransponderData(const cChannel *Channel);
bool SetTransponderData(int Source, int Frequency, int Srate, const char *Parameters, bool Quiet = false);
void SetSource(int Source);
void SetId(int Nid, int Tid, int Sid, int Rid = 0);
void SetLcn(int Lcn);
void SetName(const char *Name, const char *ShortName, const char *Provider);
void SetPortalName(const char *PortalName);
void SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid);
void SetCaIds(const int *CaIds); // list must be zero-terminated
void SetCaDescriptors(int Level);
void SetLinkChannels(cLinkChannels *LinkChannels);
bool SetSource(int Source);
bool SetId(cChannels *Channels, int Nid, int Tid, int Sid, int Rid = 0);
bool SetLcn(int Lcn);
bool SetName(const char *Name, const char *ShortName, const char *Provider);
bool SetPortalName(const char *PortalName);
bool SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid);
bool SetCaIds(const int *CaIds); // list must be zero-terminated
bool SetCaDescriptors(int Level);
bool SetLinkChannels(cLinkChannels *LinkChannels);
void SetRefChannel(cChannel *RefChannel);
void SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds);
bool SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds);
void SetSeen(void);
void DelLinkChannel(cChannel *LinkChannel);
};
class cChannels : public cRwLock, public cConfig<cChannel> {
class cChannels : public cConfig<cChannel> {
private:
int maxNumber;
int maxChannelNameLength;
int maxShortChannelNameLength;
int modified;
int beingEdited;
static cChannels channels;
static int maxNumber;
static int maxChannelNameLength;
static int maxShortChannelNameLength;
int modifiedByUser;
cHash<cChannel> channelsHashSid;
void DeleteDuplicateChannels(void);
public:
cChannels(void);
bool Load(const char *FileName, bool AllowComments = false, bool MustExist = false);
static const cChannels *GetChannelsRead(cStateKey &StateKey, int TimeoutMs = 0);
///< Gets the list of channels for read access.
///< See cTimers::GetTimersRead() for details.
static cChannels *GetChannelsWrite(cStateKey &StateKey, int TimeoutMs = 0);
///< Gets the list of channels for write access.
///< See cTimers::GetTimersWrite() for details.
static bool Load(const char *FileName, bool AllowComments = false, bool MustExist = false);
void HashChannel(cChannel *Channel);
void UnhashChannel(cChannel *Channel);
int GetNextGroup(int Idx); // Get next channel group
int GetPrevGroup(int Idx); // Get previous channel group
int GetNextNormal(int Idx); // Get next normal channel (not group)
int GetPrevNormal(int Idx); // Get previous normal channel (not group)
void ReNumber(void); // Recalculate 'number' based on channel type
cChannel *GetByNumber(int Number, int SkipGap = 0);
cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID);
cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false);
cChannel *GetByTransponderID(tChannelID ChannelID);
int BeingEdited(void) { return beingEdited; }
void IncBeingEdited(void) { beingEdited++; }
void DecBeingEdited(void) { beingEdited--; }
bool HasUniqueChannelID(cChannel *NewChannel, cChannel *OldChannel = NULL);
bool SwitchTo(int Number);
int MaxNumber(void) { return maxNumber; }
int MaxChannelNameLength(void);
int MaxShortChannelNameLength(void);
void SetModified(bool ByUser = false);
int Modified(void);
///< Returns 0 if no channels have been modified, 1 if an automatic
///< modification has been made, and 2 if the user has made a modification.
///< Calling this function resets the 'modified' flag to 0.
int GetNextGroup(int Idx) const; ///< Get next channel group
int GetPrevGroup(int Idx) const; ///< Get previous channel group
int GetNextNormal(int Idx) const; ///< Get next normal channel (not group)
int GetPrevNormal(int Idx) const; ///< Get previous normal channel (not group)
void ReNumber(void); ///< Recalculate 'number' based on channel type
void Del(cChannel *Channel); ///< Delete the given Channel from the list
const cChannel *GetByNumber(int Number, int SkipGap = 0) const;
cChannel *GetByNumber(int Number, int SkipGap = 0) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByNumber(Number, SkipGap)); }
const cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID) const;
cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByServiceID(Source, Transponder, ServiceID)); }
const cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false) const;
cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByChannelID(ChannelID, TryWithoutRid, TryWithoutPolarization)); }
const cChannel *GetByTransponderID(tChannelID ChannelID) const;
cChannel *GetByTransponderID(tChannelID ChannelID) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByTransponderID(ChannelID)); }
bool HasUniqueChannelID(const cChannel *NewChannel, const cChannel *OldChannel = NULL) const;
bool SwitchTo(int Number) const;
static int MaxNumber(void) { return maxNumber; }
static int MaxChannelNameLength(void);
static int MaxShortChannelNameLength(void);
void SetModifiedByUser(void);
bool ModifiedByUser(int &State) const;
///< Returns true if the channels have been modified by the user since the last call
///< to this function with the same State variable. State must be initialized with 0
///< and will be set to the current value of the list's internal state variable upon
///< return from this function.
cChannel *NewChannel(const cChannel *Transponder, const char *Name, const char *ShortName, const char *Provider, int Nid, int Tid, int Sid, int Rid = 0);
void MarkObsoleteChannels(int Source, int Nid, int Tid);
bool MarkObsoleteChannels(int Source, int Nid, int Tid);
};
extern cChannels Channels;
// Provide lock controlled access to the list:
DEF_LIST_LOCK(Channels);
// These macros provide a convenient way of locking the global channels list
// and making sure the lock is released as soon as the current scope is left
// (note that these macros wait forever to obtain the lock!):
#define LOCK_CHANNELS_READ USE_LIST_LOCK_READ(Channels)
#define LOCK_CHANNELS_WRITE USE_LIST_LOCK_WRITE(Channels)
cString ChannelString(const cChannel *Channel, int Number);

5
ci.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: ci.c 3.19 2015/02/02 14:04:10 kls Exp $
* $Id: ci.c 4.1 2015/07/18 09:57:42 kls Exp $
*/
#include "ci.h"
@ -1921,7 +1921,8 @@ void cCamSlot::StartActivation(void)
cMutexLock MutexLock(&mutex);
if (!caActivationReceiver) {
if (cDevice *d = Device()) {
if (cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel())) {
LOCK_CHANNELS_READ;
if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel())) {
caActivationReceiver = new cCaActivationReceiver(Channel, this);
d->AttachReceiver(caActivationReceiver);
dsyslog("CAM %d: activating on device %d with channel %d (%s)", SlotNumber(), d->DeviceNumber() + 1, Channel->Number(), Channel->Name());

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: config.h 4.2 2015/04/18 13:13:15 kls Exp $
* $Id: config.h 4.3 2015/08/09 09:17:46 kls Exp $
*/
#ifndef __CONFIG_H
@ -110,7 +110,7 @@ private:
cList<T>::Clear();
}
public:
cConfig(void) { fileName = NULL; }
cConfig(const char *NeedsLocking = NULL): cList<T>(NeedsLocking) { fileName = NULL; }
virtual ~cConfig() { free(fileName); }
const char *FileName(void) { return fileName; }
bool Load(const char *FileName = NULL, bool AllowComments = false, bool MustExist = false)
@ -160,7 +160,7 @@ public:
fprintf(stderr, "vdr: error while reading '%s'\n", fileName);
return result;
}
bool Save(void)
bool Save(void) const
{
bool result = true;
T *l = (T *)this->First();

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: cutter.c 4.1 2015/04/11 12:03:25 kls Exp $
* $Id: cutter.c 4.2 2015/08/09 12:24:28 kls Exp $
*/
#include "cutter.h"
@ -634,7 +634,6 @@ void cCuttingThread::Action(void)
}
}
}
Recordings.TouchUpdate();
}
else
esyslog("no editing marks found!");
@ -678,7 +677,8 @@ bool cCutter::Start(void)
cRecordingUserCommand::InvokeCommand(RUC_EDITINGRECORDING, editedVersionName, originalVersionName);
if (cVideoDirectory::RemoveVideoFile(editedVersionName) && MakeDirs(editedVersionName, true)) {
Recording.WriteInfo(editedVersionName);
Recordings.AddByName(editedVersionName, false);
LOCK_RECORDINGS_WRITE;
Recordings->AddByName(editedVersionName, false);
cuttingThread = new cCuttingThread(originalVersionName, editedVersionName);
return true;
}
@ -703,7 +703,8 @@ void cCutter::Stop(void)
if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), editedVersionName) == 0)
cControl::Shutdown();
cVideoDirectory::RemoveVideoFile(editedVersionName);
Recordings.DelByName(editedVersionName);
LOCK_RECORDINGS_WRITE;
Recordings->DelByName(editedVersionName);
}
}

View File

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

View File

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

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: dvbplayer.h 3.2 2015/02/06 12:27:39 kls Exp $
* $Id: dvbplayer.h 4.1 2015/08/02 13:01:44 kls Exp $
*/
#ifndef __DVBPLAYER_H
@ -26,7 +26,7 @@ public:
// file of the recording is long enough to allow the player to display
// the first frame in still picture mode.
virtual ~cDvbPlayerControl();
void SetMarks(cMarks *Marks);
void SetMarks(const cMarks *Marks);
bool Active(void);
void Stop(void);
// Stops the current replay session (if any).

119
eit.c
View File

@ -8,7 +8,7 @@
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
* Adapted to 'libsi' for VDR 1.3.0 by Marcel Wiesweg <marcel.wiesweg@gmx.de>.
*
* $Id: eit.c 3.6 2015/02/01 14:55:27 kls Exp $
* $Id: eit.c 4.1 2015/08/23 10:43:36 kls Exp $
*/
#include "eit.h"
@ -24,31 +24,53 @@
class cEIT : public SI::EIT {
public:
cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bool OnlyRunningStatus = false);
cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const u_char *Data);
};
cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bool OnlyRunningStatus)
cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const u_char *Data)
:SI::EIT(Data, false)
{
if (!CheckCRCAndParse())
return;
int HashId = Tid * getServiceId();
cSectionSyncerEntry *SectionSyncerEntry = SectionSyncerHash.Get(HashId);
if (!SectionSyncerEntry) {
SectionSyncerEntry = new cSectionSyncerEntry;
SectionSyncerHash.Add(SectionSyncerEntry, HashId);
}
bool Process = SectionSyncerEntry->Sync(getVersionNumber(), getSectionNumber(), getLastSectionNumber());
if (Tid != 0x4E && !Process) // we need to set the 'seen' tag to watch the running status of the present/following event
return;
time_t Now = time(NULL);
if (Now < VALID_TIME)
return; // we need the current time for handling PDC descriptors
if (!Channels.Lock(false, 10))
cStateKey ChannelsStateKey;
cChannels *Channels = cChannels::GetChannelsWrite(ChannelsStateKey, 10);
if (!Channels) {
SectionSyncerEntry->Repeat(); // let's not miss any section of the EIT
return;
}
tChannelID channelID(Source, getOriginalNetworkId(), getTransportStreamId(), getServiceId());
cChannel *channel = Channels.GetByChannelID(channelID, true);
if (!channel || EpgHandlers.IgnoreChannel(channel)) {
Channels.Unlock();
cChannel *Channel = Channels->GetByChannelID(channelID, true);
if (!Channel || EpgHandlers.IgnoreChannel(Channel)) {
ChannelsStateKey.Remove(false);
return;
}
EpgHandlers.BeginSegmentTransfer(channel, OnlyRunningStatus);
bool handledExternally = EpgHandlers.HandledExternally(channel);
cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(channel, true);
cStateKey SchedulesStateKey;
cSchedules *Schedules = cSchedules::GetSchedulesWrite(SchedulesStateKey, 10);
if (!Schedules) {
SectionSyncerEntry->Repeat(); // let's not miss any section of the EIT
ChannelsStateKey.Remove(false);
return;
}
bool ChannelsModified = false;
EpgHandlers.BeginSegmentTransfer(Channel);
bool handledExternally = EpgHandlers.HandledExternally(Channel);
cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(Channel, true);
bool Empty = true;
bool Modified = false;
@ -74,8 +96,6 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
cEvent *rEvent = NULL;
cEvent *pEvent = (cEvent *)pSchedule->GetEvent(SiEitEvent.getEventId(), StartTime);
if (!pEvent || handledExternally) {
if (OnlyRunningStatus)
continue;
if (handledExternally && !EpgHandlers.IsUpdate(SiEitEvent.getEventId(), StartTime, Tid, getVersionNumber()))
continue;
// If we don't have that event yet, we create a new one.
@ -94,14 +114,6 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
// The lower the table ID, the more "current" the information.
if (Tid > TableID)
continue;
// If the new event comes from the same table and has the same version number
// as the existing one, let's skip it to avoid unnecessary work.
// Unfortunately some stations (like, e.g. "Premiere") broadcast their EPG data on several transponders (like
// the actual Premiere transponder and the Sat.1/Pro7 transponder), but use different version numbers on
// each of them :-( So if one DVB card is tuned to the Premiere transponder, while an other one is tuned
// to the Sat.1/Pro7 transponder, events will keep toggling because of the bogus version numbers.
else if (Tid == TableID && pEvent->Version() == getVersionNumber())
continue;
EpgHandlers.SetEventID(pEvent, SiEitEvent.getEventId()); // unfortunately some stations use different event ids for the same event in different tables :-(
EpgHandlers.SetStartTime(pEvent, StartTime);
EpgHandlers.SetDuration(pEvent, Duration);
@ -110,11 +122,9 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
pEvent->SetTableID(Tid);
if (Tid == 0x4E) { // we trust only the present/following info on the actual TS
if (SiEitEvent.getRunningStatus() >= SI::RunningStatusNotRunning)
pSchedule->SetRunningStatus(pEvent, SiEitEvent.getRunningStatus(), channel);
}
if (OnlyRunningStatus) {
pEvent->SetVersion(0xFF); // we have already changed the table id above, so set the version to an invalid value to make sure the next full run will be executed
continue; // do this before setting the version, so that the full update can be done later
pSchedule->SetRunningStatus(pEvent, SiEitEvent.getRunningStatus(), Channel);
if (!Process)
continue;
}
pEvent->SetVersion(getVersionNumber());
@ -206,7 +216,7 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
break;
case SI::TimeShiftedEventDescriptorTag: {
SI::TimeShiftedEventDescriptor *tsed = (SI::TimeShiftedEventDescriptor *)d;
cSchedule *rSchedule = (cSchedule *)Schedules->GetSchedule(tChannelID(Source, channel->Nid(), channel->Tid(), tsed->getReferenceServiceId()));
cSchedule *rSchedule = (cSchedule *)Schedules->GetSchedule(tChannelID(Source, Channel->Nid(), Channel->Tid(), tsed->getReferenceServiceId()));
if (!rSchedule)
break;
rEvent = (cEvent *)rSchedule->GetEvent(tsed->getReferenceEventId());
@ -226,18 +236,18 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
char linkName[ld->privateData.getLength() + 1];
strn0cpy(linkName, (const char *)ld->privateData.getData(), sizeof(linkName));
// TODO is there a standard way to determine the character set of this string?
cChannel *link = Channels.GetByChannelID(linkID);
if (link != channel) { // only link to other channels, not the same one
//fprintf(stderr, "Linkage %s %4d %4d %5d %5d %5d %5d %02X '%s'\n", hit ? "*" : "", channel->Number(), link ? link->Number() : -1, SiEitEvent.getEventId(), ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId(), ld->getLinkageType(), linkName);//XXX
cChannel *link = Channels->GetByChannelID(linkID);
if (link != Channel) { // only link to other channels, not the same one
if (link) {
if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3)
link->SetName(linkName, "", "");
ChannelsModified |= link->SetName(linkName, "", "");
}
else if (Setup.UpdateChannels >= 4) {
cChannel *transponder = channel;
if (channel->Tid() != ld->getTransportStreamId())
transponder = Channels.GetByTransponderID(linkID);
link = Channels.NewChannel(transponder, linkName, "", "", ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId());
cChannel *Transponder = Channel;
if (Channel->Tid() != ld->getTransportStreamId())
Transponder = Channels->GetByTransponderID(linkID);
link = Channels->NewChannel(Transponder, linkName, "", "", ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId());
ChannelsModified = true;
//XXX patFilter->Trigger();
}
if (link) {
@ -247,7 +257,7 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
}
}
else
channel->SetPortalName(linkName);
ChannelsModified |= Channel->SetPortalName(linkName);
}
}
}
@ -293,7 +303,7 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
EpgHandlers.FixEpgBugs(pEvent);
if (LinkChannels)
channel->SetLinkChannels(LinkChannels);
ChannelsModified |= Channel->SetLinkChannels(LinkChannels);
Modified = true;
EpgHandlers.HandleEvent(pEvent);
if (handledExternally)
@ -302,16 +312,17 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
if (Tid == 0x4E) {
if (Empty && getSectionNumber() == 0)
// ETR 211: an empty entry in section 0 of table 0x4E means there is currently no event running
pSchedule->ClrRunningStatus(channel);
pSchedule->ClrRunningStatus(Channel);
pSchedule->SetPresentSeen();
}
if (Modified && !OnlyRunningStatus) {
if (Modified) {
EpgHandlers.SortSchedule(pSchedule);
EpgHandlers.DropOutdated(pSchedule, SegmentStart, SegmentEnd, Tid, getVersionNumber());
Schedules->SetModified(pSchedule);
pSchedule->SetModified();
}
Channels.Unlock();
EpgHandlers.EndSegmentTransfer(Modified, OnlyRunningStatus);
SchedulesStateKey.Remove(Modified);
ChannelsStateKey.Remove(ChannelsModified);
EpgHandlers.EndSegmentTransfer(Modified);
}
// --- cTDT ------------------------------------------------------------------
@ -372,6 +383,13 @@ cEitFilter::cEitFilter(void)
Set(0x14, 0x70); // TDT
}
void cEitFilter::SetStatus(bool On)
{
cMutexLock MutexLock(&mutex);
cFilter::SetStatus(On);
sectionSyncerHash.Clear();
}
void cEitFilter::SetDisableUntil(time_t Time)
{
disableUntil = Time;
@ -379,6 +397,7 @@ void cEitFilter::SetDisableUntil(time_t Time)
void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
{
cMutexLock MutexLock(&mutex);
if (disableUntil) {
if (time(NULL) > disableUntil)
disableUntil = 0;
@ -387,22 +406,8 @@ void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
}
switch (Pid) {
case 0x12: {
if (Tid >= 0x4E && Tid <= 0x6F) {
cSchedulesLock SchedulesLock(true, 10);
cSchedules *Schedules = (cSchedules *)cSchedules::Schedules(SchedulesLock);
if (Schedules)
cEIT EIT(Schedules, Source(), Tid, Data);
else {
// If we don't get a write lock, let's at least get a read lock, so
// that we can set the running status and 'seen' timestamp (well, actually
// with a read lock we shouldn't be doing that, but it's only integers that
// get changed, so it should be ok)
cSchedulesLock SchedulesLock;
cSchedules *Schedules = (cSchedules *)cSchedules::Schedules(SchedulesLock);
if (Schedules)
cEIT EIT(Schedules, Source(), Tid, Data, true);
}
}
if (Tid >= 0x4E && Tid <= 0x6F)
cEIT EIT(sectionSyncerHash, Source(), Tid, Data);
}
break;
case 0x14: {

10
eit.h
View File

@ -4,21 +4,29 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: eit.h 2.1 2010/01/03 15:28:34 kls Exp $
* $Id: eit.h 4.1 2015/07/25 11:03:53 kls Exp $
*/
#ifndef __EIT_H
#define __EIT_H
#include "filter.h"
#include "tools.h"
class cSectionSyncerEntry : public cListObject, public cSectionSyncer {};
class cSectionSyncerHash : public cHash<cSectionSyncerEntry> {};
class cEitFilter : public cFilter {
private:
cMutex mutex;
cSectionSyncerHash sectionSyncerHash;
static time_t disableUntil;
protected:
virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
public:
cEitFilter(void);
virtual void SetStatus(bool On);
static void SetDisableUntil(time_t Time);
};

View File

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

270
epg.c
View File

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

72
epg.h
View File

@ -7,7 +7,7 @@
* Original version (as used in VDR before 1.3.0) written by
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
*
* $Id: epg.h 3.1 2013/08/23 10:50:05 kls Exp $
* $Id: epg.h 4.1 2015/08/09 11:25:04 kls Exp $
*/
#ifndef __EPG_H
@ -66,13 +66,15 @@ public:
class cSchedule;
typedef u_int32_t tEventID;
typedef u_int16_t tEventID;
class cEvent : public cListObject {
friend class cSchedule;
private:
static cMutex numTimersMutex; // Protects numTimers, because it might be accessed from parallel read locks
// The sequence of these parameters is optimized for minimal memory waste!
cSchedule *schedule; // The Schedule this event belongs to
mutable u_int16_t numTimers;// The number of timers that use this event
tEventID eventID; // Event ID of this event
uchar tableID; // Table ID this event came from
uchar version; // Version number of section this event came from
@ -109,7 +111,9 @@ public:
time_t Vps(void) const { return vps; }
time_t Seen(void) const { return seen; }
bool SeenWithin(int Seconds) const { return time(NULL) - seen < Seconds; }
bool HasTimer(void) const;
void IncNumTimers(void) const;
void DecNumTimers(void) const;
bool HasTimer(void) const { return numTimers > 0; }
bool IsRunning(bool OrAboutToStart = false) const;
static const char *ContentToString(uchar Content);
cString GetParentalRatingString(void) const;
@ -120,7 +124,7 @@ public:
void SetEventID(tEventID EventID);
void SetTableID(uchar TableID);
void SetVersion(uchar Version);
void SetRunningStatus(int RunningStatus, cChannel *Channel = NULL);
void SetRunningStatus(int RunningStatus, const cChannel *Channel = NULL);
void SetTitle(const char *Title);
void SetShortText(const char *ShortText);
void SetDescription(const char *Description);
@ -142,28 +146,33 @@ class cSchedules;
class cSchedule : public cListObject {
private:
static cMutex numTimersMutex; // Protects numTimers, because it might be accessed from parallel read locks
tChannelID channelID;
cList<cEvent> events;
cHash<cEvent> eventsHashID;
cHash<cEvent> eventsHashStartTime;
mutable u_int16_t numTimers;// The number of timers that use this schedule
bool hasRunning;
time_t modified;
int modified;
time_t presentSeen;
public:
cSchedule(tChannelID ChannelID);
tChannelID ChannelID(void) const { return channelID; }
time_t Modified(void) const { return modified; }
bool Modified(int &State) const { bool Result = State != modified; State = modified; return Result; }
time_t PresentSeen(void) const { return presentSeen; }
bool PresentSeenWithin(int Seconds) const { return time(NULL) - presentSeen < Seconds; }
void SetModified(void) { modified = time(NULL); }
void SetModified(void) { modified++; }
void SetPresentSeen(void) { presentSeen = time(NULL); }
void SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Channel = NULL);
void SetRunningStatus(cEvent *Event, int RunningStatus, const cChannel *Channel = NULL);
void ClrRunningStatus(cChannel *Channel = NULL);
void ResetVersions(void);
void Sort(void);
void DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version);
void Cleanup(time_t Time);
void Cleanup(void);
void IncNumTimers(void) const;
void DecNumTimers(void) const;
bool HasTimer(void) const { return numTimers > 0; }
cEvent *AddEvent(cEvent *Event);
void DelEvent(cEvent *Event);
void HashEvent(cEvent *Event);
@ -177,35 +186,23 @@ public:
static bool Read(FILE *f, cSchedules *Schedules);
};
class cSchedulesLock {
private:
bool locked;
public:
cSchedulesLock(bool WriteLock = false, int TimeoutMs = 0);
~cSchedulesLock();
bool Locked(void) { return locked; }
};
class cSchedules : public cList<cSchedule> {
friend class cSchedule;
friend class cSchedulesLock;
private:
cRwLock rwlock;
static cSchedules schedules;
static char *epgDataFileName;
static time_t lastDump;
static time_t modified;
public:
cSchedules(void);
static const cSchedules *GetSchedulesRead(cStateKey &StateKey, int TimeoutMs = 0);
///< Gets the list of schedules for read access.
///< See cTimers::GetTimersRead() for details.
static cSchedules *GetSchedulesWrite(cStateKey &StateKey, int TimeoutMs = 0);
///< Gets the list of schedules for write access.
///< See cTimers::GetTimersWrite() for details.
static void SetEpgDataFileName(const char *FileName);
static const cSchedules *Schedules(cSchedulesLock &SchedulesLock);
///< Caller must provide a cSchedulesLock which has to survive the entire
///< time the returned cSchedules is accessed. Once the cSchedules is no
///< longer used, the cSchedulesLock must be destroyed.
static time_t Modified(void) { return modified; }
static void SetModified(cSchedule *Schedule);
static void Cleanup(bool Force = false);
static void ResetVersions(void);
static bool ClearAll(void);
static bool Dump(FILE *f = NULL, const char *Prefix = "", eDumpMode DumpMode = dmAll, time_t AtTime = 0);
static bool Read(FILE *f = NULL);
cSchedule *AddSchedule(tChannelID ChannelID);
@ -213,6 +210,17 @@ public:
const cSchedule *GetSchedule(const cChannel *Channel, bool AddIfMissing = false) const;
};
// Provide lock controlled access to the list:
DEF_LIST_LOCK(Schedules);
// These macros provide a convenient way of locking the global schedules list
// and making sure the lock is released as soon as the current scope is left
// (note that these macros wait forever to obtain the lock!):
#define LOCK_SCHEDULES_READ USE_LIST_LOCK_READ(Schedules);
#define LOCK_SCHEDULES_WRITE USE_LIST_LOCK_WRITE(Schedules);
class cEpgDataReader : public cThread {
public:
cEpgDataReader(void);
@ -273,12 +281,14 @@ public:
virtual bool DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version) { return false; }
///< Takes a look at all EPG events between SegmentStart and SegmentEnd and
///< drops outdated events.
virtual bool BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus) { return false; }
virtual bool BeginSegmentTransfer(const cChannel *Channel, bool Dummy) { return false; } // TODO remove obsolete Dummy
///< Called directly after IgnoreChannel() before any other handler method is called.
///< Designed to give handlers the possibility to prepare a database transaction.
virtual bool EndSegmentTransfer(bool Modified, bool OnlyRunningStatus) { return false; }
///< Dummy is for backward compatibility and may be removed in a future version.
virtual bool EndSegmentTransfer(bool Modified, bool Dummy) { return false; } // TODO remove obsolete Dummy
///< Called after the segment data has been processed.
///< At this point handlers should close/commit/rollback any pending database transactions.
///< Dummy is for backward compatibility and may be removed in a future version.
};
class cEpgHandlers : public cList<cEpgHandler> {
@ -301,8 +311,8 @@ public:
void HandleEvent(cEvent *Event);
void SortSchedule(cSchedule *Schedule);
void DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version);
void BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus);
void EndSegmentTransfer(bool Modified, bool OnlyRunningStatus);
void BeginSegmentTransfer(const cChannel *Channel);
void EndSegmentTransfer(bool Modified);
};
extern cEpgHandlers EpgHandlers;

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: filter.c 4.1 2015/03/17 15:04:39 kls Exp $
* $Id: filter.c 4.2 2015/07/25 10:59:57 kls Exp $
*/
#include "filter.h"
@ -19,31 +19,38 @@ cSectionSyncer::cSectionSyncer(void)
void cSectionSyncer::Reset(void)
{
lastVersion = thisVersion = 0xFF;
nextNumber = 0;
currentVersion = -1;
currentSection = -1;
synced = false;
complete = false;
memset(sections, 0x00, sizeof(sections));
}
void cSectionSyncer::Repeat(void)
{
lastVersion = 0xFF;
nextNumber--;
SetSectionFlag(currentSection, false);
synced = false;
complete = false;
}
bool cSectionSyncer::Sync(uchar Version, int Number, int LastNumber)
{
if (Version != lastVersion) {
if (Version != thisVersion) {
thisVersion = Version;
nextNumber = 0;
}
if (Number == nextNumber) {
if (Number == LastNumber)
lastVersion = Version;
nextNumber++;
return true;
}
if (Version != currentVersion) {
Reset();
currentVersion = Version;
}
return false;
if (!synced) {
if (Number != 0)
return false;
else
synced = true;
}
currentSection = Number;
bool Result = !GetSectionFlag(Number);
SetSectionFlag(Number, true);
if (Number == LastNumber)
complete = true;
return Result;
}
// --- cFilterData -----------------------------------------------------------

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: filter.h 4.1 2015/03/17 15:00:08 kls Exp $
* $Id: filter.h 4.2 2015/07/25 10:03:44 kls Exp $
*/
#ifndef __FILTER_H
@ -15,13 +15,18 @@
class cSectionSyncer {
private:
int lastVersion;
int thisVersion;
int nextNumber;
int currentVersion;
int currentSection;
bool synced;
bool complete;
uchar sections[32]; // holds 32 * 8 = 256 bits, as flags for the sections
void SetSectionFlag(uchar Section, bool On) { if (On) sections[Section / 8] |= (1 << (Section % 8)); else sections[Section / 8] &= ~(1 << (Section % 8)); }
bool GetSectionFlag(uchar Section) { return sections[Section / 8] & (1 << (Section % 8)); }
public:
cSectionSyncer(void);
void Reset(void);
void Repeat(void);
bool Complete(void) { return complete; }
bool Sync(uchar Version, int Number, int LastNumber);
};

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

View File

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

43
nit.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: nit.c 4.2 2015/03/17 15:10:09 kls Exp $
* $Id: nit.c 4.3 2015/07/26 09:24:36 kls Exp $
*/
#include "nit.h"
@ -61,10 +61,13 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
}
dbgnit("NIT: %02X %2d %2d %2d %s %d %d '%s'\n", Tid, nit.getVersionNumber(), nit.getSectionNumber(), nit.getLastSectionNumber(), *cSource::ToString(Source()), nit.getNetworkId(), Transponder(), NetworkName);
}
if (!Channels.Lock(true, 10)) {
cStateKey StateKey;
cChannels *Channels = cChannels::GetChannelsWrite(StateKey, 10);
if (!Channels) {
sectionSyncer.Repeat(); // let's not miss any section of the NIT
return;
}
bool ChannelsModified = false;
SI::NIT::TransportStream ts;
for (SI::Loop::Iterator it; nit.transportStreamLoop.getNext(ts, it); ) {
SI::Descriptor *d;
@ -115,7 +118,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
if (Setup.UpdateChannels >= 5) {
bool found = false;
bool forceTransponderUpdate = false;
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
int transponder = Channel->Transponder();
found = true;
@ -128,7 +131,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
}
}
if (ISTRANSPONDER(cChannel::Transponder(Frequency, dtp.Polarization()), Transponder())) // only modify channels if we're actually receiving this transponder
Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('S'));
ChannelsModified |= Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('S'));
else if (Channel->Srate() != SymbolRate || strcmp(Channel->Parameters(), dtp.ToString('S')))
forceTransponderUpdate = true; // get us receiving this transponder
}
@ -136,7 +139,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
if (!found || forceTransponderUpdate) {
for (int n = 0; n < NumFrequencies; n++) {
cChannel *Channel = new cChannel;
Channel->SetId(ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
Channel->SetId(NULL, ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
if (Channel->SetTransponderData(Source, Frequencies[n], SymbolRate, dtp.ToString('S')))
EITScanner.AddTransponder(Channel);
else
@ -150,13 +153,13 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
break;
case SI::S2SatelliteDeliverySystemDescriptorTag: {
if (Setup.UpdateChannels >= 5) {
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
if (!Channel->GroupSep() && cSource::IsSat(Channel->Source()) && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
SI::S2SatelliteDeliverySystemDescriptor *sd = (SI::S2SatelliteDeliverySystemDescriptor *)d;
cDvbTransponderParameters dtp(Channel->Parameters());
dtp.SetSystem(DVB_SYSTEM_2);
dtp.SetStreamId(sd->getInputStreamIdentifier());
Channel->SetTransponderData(Channel->Source(), Channel->Frequency(), Channel->Srate(), dtp.ToString('S'));
ChannelsModified |= Channel->SetTransponderData(Channel->Source(), Channel->Frequency(), Channel->Srate(), dtp.ToString('S'));
break;
}
}
@ -178,7 +181,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
if (Setup.UpdateChannels >= 5) {
bool found = false;
bool forceTransponderUpdate = false;
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
int transponder = Channel->Transponder();
found = true;
@ -191,7 +194,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
}
}
if (ISTRANSPONDER(Frequency / 1000, Transponder())) // only modify channels if we're actually receiving this transponder
Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('C'));
ChannelsModified |= Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('C'));
else if (Channel->Srate() != SymbolRate || strcmp(Channel->Parameters(), dtp.ToString('C')))
forceTransponderUpdate = true; // get us receiving this transponder
}
@ -199,7 +202,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
if (!found || forceTransponderUpdate) {
for (int n = 0; n < NumFrequencies; n++) {
cChannel *Channel = new cChannel;
Channel->SetId(ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
Channel->SetId(NULL, ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
if (Channel->SetTransponderData(Source, Frequencies[n], SymbolRate, dtp.ToString('C')))
EITScanner.AddTransponder(Channel);
else
@ -234,7 +237,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
if (Setup.UpdateChannels >= 5) {
bool found = false;
bool forceTransponderUpdate = false;
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
int transponder = Channel->Transponder();
found = true;
@ -247,7 +250,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
}
}
if (ISTRANSPONDER(Frequency / 1000000, Transponder())) // only modify channels if we're actually receiving this transponder
Channel->SetTransponderData(Source, Frequency, 0, dtp.ToString('T'));
ChannelsModified |= Channel->SetTransponderData(Source, Frequency, 0, dtp.ToString('T'));
else if (strcmp(Channel->Parameters(), dtp.ToString('T')))
forceTransponderUpdate = true; // get us receiving this transponder
}
@ -255,7 +258,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
if (!found || forceTransponderUpdate) {
for (int n = 0; n < NumFrequencies; n++) {
cChannel *Channel = new cChannel;
Channel->SetId(ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
Channel->SetId(NULL, ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
if (Channel->SetTransponderData(Source, Frequencies[n], 0, dtp.ToString('T')))
EITScanner.AddTransponder(Channel);
else
@ -272,7 +275,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
switch (sd->getExtensionDescriptorTag()) {
case SI::T2DeliverySystemDescriptorTag: {
if (Setup.UpdateChannels >= 5) {
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
int Source = cSource::FromData(cSource::stTerr);
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
SI::T2DeliverySystemDescriptor *td = (SI::T2DeliverySystemDescriptor *)d;
@ -292,7 +295,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
dtp.SetTransmission(T2TransmissionModes[td->getTransmissionMode()]);
//TODO add parsing of frequencies
}
Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('T'));
ChannelsModified |= Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('T'));
}
}
}
@ -310,9 +313,9 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
int lcn = LogicalChannel.getLogicalChannelNumber();
int sid = LogicalChannel.getServiceId();
if (LogicalChannel.getVisibleServiceFlag()) {
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
if (!Channel->GroupSep() && Channel->Sid() == sid && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
Channel->SetLcn(lcn);
ChannelsModified |= Channel->SetLcn(lcn);
break;
}
}
@ -328,9 +331,9 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
int lcn = HdSimulcastLogicalChannel.getLogicalChannelNumber();
int sid = HdSimulcastLogicalChannel.getServiceId();
if (HdSimulcastLogicalChannel.getVisibleServiceFlag()) {
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
if (!Channel->GroupSep() && Channel->Sid() == sid && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
Channel->SetLcn(lcn);
ChannelsModified |= Channel->SetLcn(lcn);
break;
}
}
@ -343,5 +346,5 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
delete d;
}
}
Channels.Unlock();
StateKey.Remove(ChannelsModified);
}

23
pat.c
View File

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

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: recorder.c 3.3 2014/02/21 09:19:52 kls Exp $
* $Id: recorder.c 4.1 2015/08/03 10:23:35 kls Exp $
*/
#include "recorder.h"
@ -135,7 +135,8 @@ void cRecorder::Action(void)
if (frameDetector->FramesPerSecond() > 0 && DoubleEqual(RecordingInfo.FramesPerSecond(), DEFAULTFRAMESPERSECOND) && !DoubleEqual(RecordingInfo.FramesPerSecond(), frameDetector->FramesPerSecond())) {
RecordingInfo.SetFramesPerSecond(frameDetector->FramesPerSecond());
RecordingInfo.Write();
Recordings.UpdateByName(recordingName);
LOCK_RECORDINGS_WRITE;
Recordings->UpdateByName(recordingName);
}
}
InfoWritten = true;

View File

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

View File

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

52
sdt.c
View File

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

View File

@ -6,7 +6,7 @@
*
* Original version written by Udo Richter <udo_richter@gmx.de>.
*
* $Id: shutdown.c 3.1 2013/10/02 09:02:01 kls Exp $
* $Id: shutdown.c 4.1 2015/07/18 11:29:26 kls Exp $
*/
#include "shutdown.h"
@ -172,9 +172,10 @@ bool cShutdownHandler::ConfirmShutdown(bool Interactive)
return false;
}
cTimer *timer = Timers.GetNextActiveTimer();
time_t Next = timer ? timer->StartTime() : 0;
time_t Delta = timer ? Next - time(NULL) : 0;
LOCK_TIMERS_READ;
const cTimer *Timer = Timers->GetNextActiveTimer();
time_t Next = Timer ? Timer->StartTime() : 0;
time_t Delta = Timer ? Next - time(NULL) : 0;
if (cRecordControls::Active() || (Next && Delta <= 0)) {
// VPS recordings in timer end margin may cause Delta <= 0
@ -215,9 +216,10 @@ bool cShutdownHandler::ConfirmRestart(bool Interactive)
return false;
}
cTimer *timer = Timers.GetNextActiveTimer();
time_t Next = timer ? timer->StartTime() : 0;
time_t Delta = timer ? Next - time(NULL) : 0;
LOCK_TIMERS_READ;
const cTimer *Timer = Timers->GetNextActiveTimer();
time_t Next = Timer ? Timer->StartTime() : 0;
time_t Delta = Timer ? Next - time(NULL) : 0;
if (cRecordControls::Active() || (Next && Delta <= 0)) {
// VPS recordings in timer end margin may cause Delta <= 0
@ -233,15 +235,16 @@ bool cShutdownHandler::ConfirmRestart(bool Interactive)
bool cShutdownHandler::DoShutdown(bool Force)
{
LOCK_TIMERS_READ;
time_t Now = time(NULL);
cTimer *timer = Timers.GetNextActiveTimer();
const cTimer *Timer = Timers->GetNextActiveTimer();
cPlugin *Plugin = cPluginManager::GetNextWakeupPlugin();
time_t Next = timer ? timer->StartTime() : 0;
time_t Next = Timer ? Timer->StartTime() : 0;
time_t NextPlugin = Plugin ? Plugin->WakeupTime() : 0;
if (NextPlugin && (!Next || Next > NextPlugin)) {
Next = NextPlugin;
timer = NULL;
Timer = NULL;
}
time_t Delta = Next ? Next - Now : 0;
@ -250,13 +253,13 @@ bool cShutdownHandler::DoShutdown(bool Force)
return false;
Delta = Setup.MinEventTimeout * 60;
Next = Now + Delta;
timer = NULL;
Timer = NULL;
dsyslog("reboot at %s", *TimeToString(Next));
}
if (Next && timer) {
if (Next && Timer) {
dsyslog("next timer event at %s", *TimeToString(Next));
CallShutdownCommand(Next, timer->Channel()->Number(), timer->File(), Force);
CallShutdownCommand(Next, Timer->Channel()->Number(), Timer->File(), Force);
}
else if (Next && Plugin) {
CallShutdownCommand(Next, 0, Plugin->Name(), Force);

View File

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

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: sourceparams.c 3.1 2014/03/09 12:03:09 kls Exp $
* $Id: sourceparams.c 4.1 2015/08/02 11:56:39 kls Exp $
*/
#include "sourceparams.h"
@ -33,7 +33,7 @@ cSourceParam::cSourceParam(char Source, const char *Description)
cSourceParams SourceParams;
cSourceParam *cSourceParams::Get(char Source) const
cSourceParam *cSourceParams::Get(char Source)
{
for (cSourceParam *sp = First(); sp; sp = Next(sp)) {
if (sp->Source() == Source)

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: sourceparams.h 1.1 2010/02/28 11:58:03 kls Exp $
* $Id: sourceparams.h 4.1 2015/08/02 11:56:25 kls Exp $
*/
#ifndef __SOURCEPARAMS_H
@ -45,7 +45,7 @@ public:
class cSourceParams : public cList<cSourceParam> {
public:
cSourceParam *Get(char Source) const;
cSourceParam *Get(char Source);
};
extern cSourceParams SourceParams;

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: status.h 3.1 2014/01/25 10:47:39 kls Exp $
* $Id: status.h 4.1 2015/08/02 10:34:44 kls Exp $
*/
#ifndef __STATUS_H
@ -15,7 +15,7 @@
#include "player.h"
#include "tools.h"
enum eTimerChange { tcMod, tcAdd, tcDel };
enum eTimerChange { tcMod, tcAdd, tcDel }; // tcMod is obsolete and no longer used!
class cTimer;
@ -29,10 +29,7 @@ protected:
// require a retune.
virtual void TimerChange(const cTimer *Timer, eTimerChange Change) {}
// Indicates a change in the timer settings.
// If Change is tcAdd or tcDel, Timer points to the timer that has
// been added or will be deleted, respectively. In case of tcMod,
// Timer is NULL; this indicates that some timer has been changed.
// Note that tcAdd and tcDel are always also followed by a tcMod.
// Timer points to the timer that has been added or will be deleted, respectively.
virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber, bool LiveView) {}
// Indicates a channel switch on the given DVB device.
// If ChannelNumber is 0, this is before the channel is being switched,

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
* how to reach the author.
*
* $Id: svdrp.h 4.2 2015/05/22 13:44:43 kls Exp $
* $Id: svdrp.h 4.3 2015/09/01 10:34:09 kls Exp $
*/
#ifndef __SVDRP_H
@ -40,20 +40,35 @@ public:
///< to the command. The response strings are exactly as received,
///< with the leading three digit reply code and possible continuation
///< line indicator ('-') in place.
const char *Response(int Index) { return (Index > 0 && Index < response.Size()) ? response[Index] : NULL; }
const char *Response(int Index) { return (Index >= 0 && Index < response.Size()) ? response[Index] : NULL; }
///< This is a convenience function for accessing the response strings.
///< Returns the string at the given Index, or NULL if Index is out
///< of range.
int Code(const char *s) { return s ? atoi(s) : 0; }
///< Returns the value of the three digit reply code of the given
///< response string.
const char *Value(const char *s) { return s && s[0] && s[1] && s[2] && s[3] ? s + 4 : NULL; }
///< Returns the actual value of the given response string, skipping
///< the three digit reply code and possible continuation line indicator.
};
enum eSvdrpFetchFlags {
sffNone = 0b0000,
sffTimers = 0b0001,
};
const char *SVDRPHostName(void);
void SetSVDRPGrabImageDir(const char *GrabImageDir);
void StartSVDRPHandler(int TcpPort, int UdpPort);
void StopSVDRPHandler(void);
void SendSVDRPDiscover(const char *Address = NULL);
bool GetSVDRPServerNames(cStringList *ServerNames);
bool GetSVDRPServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlag = sffNone);
///< Gets a list of all available VDRs this VDR is connected to via SVDRP,
///< and stores it in the given ServerNames list. The list is cleared
///< before getting the server names.
///< If FetchFlag is given, only the server names for which the local
///< client has this flag set will be returned, and the client's flag
///< will be cleared.
///< Returns true if the resulting list is not empty.
#endif //__SVDRP_H

138
thread.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: thread.c 3.2 2013/12/29 17:21:53 kls Exp $
* $Id: thread.c 4.1 2015/08/29 14:43:03 kls Exp $
*/
#include "thread.h"
@ -21,6 +21,16 @@
#include <unistd.h>
#include "tools.h"
#define ABORT { dsyslog("ABORT!"); abort(); } // use debugger to trace back the problem
//#define DEBUG_LOCKING // uncomment this line to activate debug output for locking
#ifdef DEBUG_LOCKING
#define dbglocking(a...) fprintf(stderr, a)
#else
#define dbglocking(a...)
#endif
static bool GetAbsTime(struct timespec *Abstime, int MillisecondsFromNow)
{
struct timeval now;
@ -403,6 +413,132 @@ bool cThreadLock::Lock(cThread *Thread)
return false;
}
// --- cStateLock ------------------------------------------------------------
cStateLock::cStateLock(const char *Name)
:rwLock(true)
{
name = Name;
threadId = 0;
state = 0;
explicitModify = false;
}
bool cStateLock::Lock(cStateKey &StateKey, bool Write, int TimeoutMs)
{
dbglocking("%5d %-10s %10p lock state = %d/%d write = %d timeout = %d\n", cThread::ThreadId(), name, &StateKey, state, StateKey.state, Write, TimeoutMs);
StateKey.timedOut = false;
if (StateKey.stateLock) {
esyslog("ERROR: StateKey already in use in call to cStateLock::Lock() (tid=%d, lock=%s)", StateKey.stateLock->threadId, name);
ABORT;
return false;
}
if (rwLock.Lock(Write, TimeoutMs)) {
StateKey.stateLock = this;
if (Write) {
dbglocking("%5d %-10s %10p locked write\n", cThread::ThreadId(), name, &StateKey);
threadId = cThread::ThreadId();
StateKey.write = true;
return true;
}
else if (state != StateKey.state) {
dbglocking("%5d %-10s %10p locked read\n", cThread::ThreadId(), name, &StateKey);
return true;
}
else {
dbglocking("%5d %-10s %10p state unchanged\n", cThread::ThreadId(), name, &StateKey);
StateKey.stateLock = NULL;
rwLock.Unlock();
}
}
else if (TimeoutMs) {
dbglocking("%5d %-10s %10p timeout\n", cThread::ThreadId(), name, &StateKey);
StateKey.timedOut = true;
}
return false;
}
void cStateLock::Unlock(cStateKey &StateKey, bool IncState)
{
dbglocking("%5d %-10s %10p unlock state = %d/%d inc = %d\n", cThread::ThreadId(), name, &StateKey, state, StateKey.state, IncState);
if (StateKey.stateLock != this) {
esyslog("ERROR: cStateLock::Unlock() called with an unused key (tid=%d, lock=%s)", threadId, name);
ABORT;
return;
}
if (StateKey.write && threadId != cThread::ThreadId()) {
esyslog("ERROR: cStateLock::Unlock() called without holding a lock (tid=%d, lock=%s)", threadId, name);
ABORT;
return;
}
if (StateKey.write && IncState && !explicitModify)
state++;
StateKey.state = state;
StateKey.write = false;
threadId = 0;
explicitModify = false;
rwLock.Unlock();
}
void cStateLock::IncState(void)
{
if (threadId != cThread::ThreadId()) {
esyslog("ERROR: cStateLock::IncState() called without holding a lock (tid=%d, lock=%s)", threadId, name);
ABORT;
}
else
state++;
}
// --- cStateKey -------------------------------------------------------------
cStateKey::cStateKey(bool IgnoreFirst)
{
stateLock = NULL;
write = false;
state = 0;
if (!IgnoreFirst)
Reset();
}
cStateKey::~cStateKey()
{
if (stateLock) {
esyslog("ERROR: cStateKey::~cStateKey() called without releasing the lock first (tid=%d, lock=%s, key=%p)", stateLock->threadId, stateLock->name, this);
ABORT;
}
}
void cStateKey::Reset(void)
{
state = -1; // lock and key are initialized differently, to make the first check return true
}
void cStateKey::Remove(bool IncState)
{
if (stateLock) {
stateLock->Unlock(*this, IncState);
stateLock = NULL;
}
else {
esyslog("ERROR: cStateKey::Remove() called without holding a lock (key=%p)", this);
ABORT;
}
}
bool cStateKey::StateChanged(void)
{
if (!stateLock) {
esyslog("ERROR: cStateKey::StateChanged() called without holding a lock (tid=%d, key=%p)", cThread::ThreadId(), this);
ABORT;
}
else if (write)
return state != stateLock->state;
else
return true;
return false;
}
// --- cIoThrottle -----------------------------------------------------------
cMutex cIoThrottle::mutex;

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: thread.h 3.2 2015/01/14 11:39:55 kls Exp $
* $Id: thread.h 4.1 2015/08/17 13:06:24 kls Exp $
*/
#ifndef __THREAD_H
@ -164,6 +164,94 @@ public:
#define LOCK_THREAD cThreadLock ThreadLock(this)
class cStateKey;
class cStateLock {
friend class cStateKey;
private:
const char *name;
tThreadId threadId;
cRwLock rwLock;
int state;
bool explicitModify;
void Unlock(cStateKey &StateKey, bool IncState = true);
///< Releases a lock that has been obtained by a previous call to Lock()
///< with the given StateKey. If this was a write-lock, and IncState is true,
///< the state of the lock will be incremented. In any case, the (new) state
///< of the lock will be copied to the StateKey's state.
public:
cStateLock(const char *Name = NULL);
bool Lock(cStateKey &StateKey, bool Write = false, int TimeoutMs = 0);
///< Tries to get a lock and returns true if successful.
///< If TimoutMs is not 0, it waits for the given number of milliseconds
///< and returns false if no lock has been obtained within that time.
///< Otherwise it waits indefinitely for the lock. The given StateKey
///< will store which lock it has been used with, and will use that
///< information when its Remove() function is called.
///< There are two possible locks, one only for read access, and one
///< for reading and writing:
///<
///< If Write is false (i.e. a read-lock is requested), the lock's state
///< is compared to the given StateKey's state, and true is returned if
///< they differ.
///< If true is returned, the read-lock is still in place and the
///< protected data structures can be safely accessed (in read-only mode!).
///< Once the necessary operations have been performed, the lock must
///< be released by a call to the StateKey's Remove() function.
///< If false is returned, the state has not changed since the last check,
///< and the read-lock has been released. In that case the protected
///< data structures have not changed since the last call, so no action
///< is required. Note that if TimeoutMs is used with read-locks, Lock()
///< might return false even if the states of lock and key differ, just
///< because it was unable to obtain the lock within the given time.
///< You can call cStateKey::TimedOut() to detect this.
///<
///< If Write is true (i.e. a write-lock is requested), the states of the
///< lock and the given StateKey don't matter, it will always try to obtain
///< a write lock.
void SetExplicitModify(void) { explicitModify = true; }
///< If you have obtained a write lock on this lock, and you don't want its
///< state to be automatically incremented when the lock is released, a call to
///< this function will disable this, and you can explicitly call IncState()
///< to increment the state.
void IncState(void);
///< Increments the state of this lock.
};
class cStateKey {
friend class cStateLock;
private:
cStateLock *stateLock;
bool write;
int state;
bool timedOut;
public:
cStateKey(bool IgnoreFirst = false);
///< Sets up a new state key. If IgnoreFirst is true, the first use
///< of this key with a lock will not return true if the lock's state
///< hasn't explicitly changed.
~cStateKey();
void Reset(void);
///< Resets the state of this key, so that the next call to a lock's
///< Lock() function with this key will return true, even if the
///< lock's state hasn't changed.
void Remove(bool IncState = true);
///< Removes this key from the lock it was previously used with.
///< If this key was used to obtain a write lock, the state of the lock will
///< be incremented and copied to this key. You can set IncState to false
///< to prevent this.
bool StateChanged(void);
///< Returns true if this key is used for obtaining a write lock, and the
///< lock's state differs from that of the key. When used with a read lock,
///< it always returns true, because otherwise the lock wouldn't have been
///< obtained in the first place.
bool InLock(void) { return stateLock; }
///< Returns true if this key is currently in a lock.
bool TimedOut(void) const { return timedOut; }
///< Returns true if the last lock attempt this key was used with failed due
///< to a timeout.
};
class cIoThrottle {
private:
static cMutex mutex;

326
timers.c
View File

@ -4,18 +4,18 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: timers.c 3.1 2013/12/28 11:33:08 kls Exp $
* $Id: timers.c 4.1 2015/08/31 10:45:13 kls Exp $
*/
#include "timers.h"
#include <ctype.h>
#include "channels.h"
#include "device.h"
#include "i18n.h"
#include "libsi/si.h"
#include "recording.h"
#include "remote.h"
#include "status.h"
#include "svdrp.h"
// IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d'
// format characters in order to allow any number of blanks after a numeric
@ -23,19 +23,21 @@
// --- cTimer ----------------------------------------------------------------
cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
cTimer::cTimer(bool Instant, bool Pause, const cChannel *Channel)
{
startTime = stopTime = 0;
lastSetEvent = 0;
scheduleState = -1;
deferred = 0;
recording = pending = inVpsMargin = false;
pending = inVpsMargin = false;
flags = tfNone;
*file = 0;
aux = NULL;
remote = NULL;
event = NULL;
if (Instant)
SetFlags(tfActive | tfInstant);
channel = Channel ? Channel : Channels.GetByNumber(cDevice::CurrentChannel());
LOCK_CHANNELS_READ;
channel = Channel ? Channel : Channels->GetByNumber(cDevice::CurrentChannel());
time_t t = time(NULL);
struct tm tm_r;
struct tm *now = localtime_r(&t, &tm_r);
@ -44,27 +46,25 @@ cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
start = now->tm_hour * 100 + now->tm_min;
stop = 0;
if (!Setup.InstantRecordTime && channel && (Instant || Pause)) {
cSchedulesLock SchedulesLock;
if (const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock)) {
if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) {
if (const cEvent *Event = Schedule->GetPresentEvent()) {
time_t tstart = Event->StartTime();
time_t tstop = Event->EndTime();
if (Event->Vps() && Setup.UseVps) {
SetFlags(tfVps);
tstart = Event->Vps();
}
else {
tstop += Setup.MarginStop * 60;
tstart -= Setup.MarginStart * 60;
}
day = SetTime(tstart, 0);
struct tm *time = localtime_r(&tstart, &tm_r);
start = time->tm_hour * 100 + time->tm_min;
time = localtime_r(&tstop, &tm_r);
stop = time->tm_hour * 100 + time->tm_min;
SetEvent(Event);
LOCK_SCHEDULES_READ;
if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) {
if (const cEvent *Event = Schedule->GetPresentEvent()) {
time_t tstart = Event->StartTime();
time_t tstop = Event->EndTime();
if (Event->Vps() && Setup.UseVps) {
SetFlags(tfVps);
tstart = Event->Vps();
}
else {
tstop += Setup.MarginStop * 60;
tstart -= Setup.MarginStart * 60;
}
day = SetTime(tstart, 0);
struct tm *time = localtime_r(&tstart, &tm_r);
start = time->tm_hour * 100 + time->tm_min;
time = localtime_r(&tstop, &tm_r);
stop = time->tm_hour * 100 + time->tm_min;
SetEvent(Event);
}
}
}
@ -83,16 +83,18 @@ cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
cTimer::cTimer(const cEvent *Event)
{
startTime = stopTime = 0;
lastSetEvent = 0;
scheduleState = -1;
deferred = 0;
recording = pending = inVpsMargin = false;
pending = inVpsMargin = false;
flags = tfActive;
*file = 0;
aux = NULL;
remote = NULL;
event = NULL;
if (Event->Vps() && Setup.UseVps)
SetFlags(tfVps);
channel = Channels.GetByChannelID(Event->ChannelID(), true);
LOCK_CHANNELS_READ;
channel = Channels->GetByChannelID(Event->ChannelID(), true);
time_t tstart = (flags & tfVps) ? Event->Vps() : Event->StartTime();
time_t tstop = tstart + Event->Duration();
if (!(HasFlags(tfVps))) {
@ -120,6 +122,7 @@ cTimer::cTimer(const cTimer &Timer)
{
channel = NULL;
aux = NULL;
remote = NULL;
event = NULL;
flags = tfNone;
*this = Timer;
@ -127,7 +130,10 @@ cTimer::cTimer(const cTimer &Timer)
cTimer::~cTimer()
{
if (event)
event->DecNumTimers();
free(aux);
free(remote);
}
cTimer& cTimer::operator= (const cTimer &Timer)
@ -136,9 +142,8 @@ cTimer& cTimer::operator= (const cTimer &Timer)
uint OldFlags = flags & tfRecording;
startTime = Timer.startTime;
stopTime = Timer.stopTime;
lastSetEvent = 0;
deferred = 0;
recording = Timer.recording;
scheduleState = -1;
deferred = 0;
pending = Timer.pending;
inVpsMargin = Timer.inVpsMargin;
flags = Timer.flags | OldFlags;
@ -152,7 +157,13 @@ cTimer& cTimer::operator= (const cTimer &Timer)
strncpy(file, Timer.file, sizeof(file));
free(aux);
aux = Timer.aux ? strdup(Timer.aux) : NULL;
event = NULL;
free(remote);
remote = Timer.remote ? strdup(Timer.remote) : NULL;
if (event)
event->DecNumTimers();
event = Timer.event;
if (event)
event->IncNumTimers();
}
return *this;
}
@ -178,7 +189,7 @@ cString cTimer::ToText(bool UseChannelID) const
cString cTimer::ToDescr(void) const
{
return cString::sprintf("%d (%d %04d-%04d %s'%s')", Index() + 1, Channel()->Number(), start, stop, HasFlags(tfVps) ? "VPS " : "", file);
return cString::sprintf("%d%s%s (%d %04d-%04d %s'%s')", Index() + 1, remote ? "@" : "", remote ? remote : "", Channel()->Number(), start, stop, HasFlags(tfVps) ? "VPS " : "", file);
}
int cTimer::TimeToInt(int t)
@ -313,7 +324,6 @@ bool cTimer::Parse(const char *s)
}
bool result = false;
if (8 <= sscanf(s, "%u :%m[^:]:%m[^:]:%d :%d :%d :%d :%m[^:\n]:%m[^\n]", &flags, &channelbuffer, &daybuffer, &start, &stop, &priority, &lifetime, &filebuffer, &aux)) {
ClrFlags(tfRecording);
if (aux && !*skipspace(aux)) {
free(aux);
aux = NULL;
@ -322,10 +332,11 @@ bool cTimer::Parse(const char *s)
result = ParseDay(daybuffer, day, weekdays);
Utf8Strn0Cpy(file, filebuffer, sizeof(file));
strreplace(file, '|', ':');
LOCK_CHANNELS_READ;
if (isnumber(channelbuffer))
channel = Channels.GetByNumber(atoi(channelbuffer));
channel = Channels->GetByNumber(atoi(channelbuffer));
else
channel = Channels.GetByChannelID(tChannelID::FromString(channelbuffer), true, true);
channel = Channels->GetByChannelID(tChannelID::FromString(channelbuffer), true, true);
if (!channel) {
esyslog("ERROR: channel %s not defined", channelbuffer);
result = false;
@ -340,7 +351,9 @@ bool cTimer::Parse(const char *s)
bool cTimer::Save(FILE *f)
{
return fprintf(f, "%s", *ToText(true)) > 0;
if (!Remote())
return fprintf(f, "%s", *ToText(true)) > 0;
return true;
}
bool cTimer::IsSingleEvent(void) const
@ -441,10 +454,8 @@ bool cTimer::Matches(time_t t, bool Directly, int Margin) const
startTime = event->StartTime();
stopTime = event->EndTime();
if (!Margin) { // this is an actual check
if (event->Schedule()->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) { // VPS control can only work with up-to-date events...
if (event->StartTime() > 0) // checks for "phased out" events
return event->IsRunning(true);
}
if (event->Schedule()->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) // VPS control can only work with up-to-date events...
return event->IsRunning(true);
return startTime <= t && t < stopTime; // ...otherwise we fall back to normal timer handling
}
}
@ -511,27 +522,13 @@ time_t cTimer::StopTime(void) const
#define EPGLIMITBEFORE (1 * 3600) // Time in seconds before a timer's start time and
#define EPGLIMITAFTER (1 * 3600) // after its stop time within which EPG events will be taken into consideration.
void cTimer::SetEventFromSchedule(const cSchedules *Schedules)
bool cTimer::SetEventFromSchedule(const cSchedules *Schedules)
{
cSchedulesLock SchedulesLock;
if (!Schedules) {
lastSetEvent = 0; // forces setting the event, even if the schedule hasn't been modified
if (!(Schedules = cSchedules::Schedules(SchedulesLock)))
return;
}
const cSchedule *Schedule = Schedules->GetSchedule(Channel());
if (Schedule && Schedule->Events()->First()) {
time_t now = time(NULL);
if (!lastSetEvent || Schedule->Modified() >= lastSetEvent) {
lastSetEvent = now;
if (Schedule->Modified(scheduleState)) {
const cEvent *Event = NULL;
if (HasFlags(tfVps) && Schedule->Events()->First()->Vps()) {
if (event && event->StartTime() > 0) { // checks for "phased out" events
if (Recording())
return; // let the recording end first
if (now <= event->EndTime() || Matches(0, true))
return; // stay with the old event until the timer has completely expired
}
// VPS timers only match if their start time exactly matches the event's VPS time:
for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
if (e->StartTime() && e->RunningStatus() != SI::RunningStatusNotRunning) { // skip outdated events
@ -566,30 +563,39 @@ void cTimer::SetEventFromSchedule(const cSchedules *Schedules)
}
}
}
SetEvent(Event);
return SetEvent(Event);
}
}
return false;
}
void cTimer::SetEvent(const cEvent *Event)
bool cTimer::SetEvent(const cEvent *Event)
{
if (event != Event) { //XXX TODO check event data, too???
if (Event)
if (event != Event) {
if (event)
event->DecNumTimers();
if (Event) {
isyslog("timer %s set to event %s", *ToDescr(), *Event->ToDescr());
else
Event->IncNumTimers();
Event->Schedule()->Modified(scheduleState); // to get the current state
}
else {
isyslog("timer %s set to no event", *ToDescr());
scheduleState = -1;
}
event = Event;
return true;
}
return false;
}
void cTimer::SetRecording(bool Recording)
{
recording = Recording;
if (recording)
if (Recording)
SetFlags(tfRecording);
else
ClrFlags(tfRecording);
isyslog("timer %s %s", *ToDescr(), recording ? "start" : "stop");
isyslog("timer %s %s", *ToDescr(), Recording ? "start" : "stop");
}
void cTimer::SetPending(bool Pending)
@ -637,7 +643,13 @@ void cTimer::SetLifetime(int Lifetime)
void cTimer::SetAux(const char *Aux)
{
free(aux);
aux = strdup(Aux);
aux = Aux ? strdup(Aux) : NULL;
}
void cTimer::SetRemote(const char *Remote)
{
free(remote);
remote = Remote ? strdup(Remote) : NULL;
}
void cTimer::SetDeferred(int Seconds)
@ -691,20 +703,33 @@ void cTimer::OnOff(void)
// --- cTimers ---------------------------------------------------------------
cTimers Timers;
cTimers cTimers::timers;
cTimers::cTimers(void)
:cConfig<cTimer>("Timers")
{
state = 0;
beingEdited = 0;;
lastSetEvents = 0;
lastDeleteExpired = 0;
}
bool cTimers::Load(const char *FileName)
{
LOCK_TIMERS_WRITE;
Timers->SetExplicitModify();
if (timers.cConfig<cTimer>::Load(FileName)) {
for (cTimer *ti = timers.First(); ti; ti = timers.Next(ti)) {
ti->ClrFlags(tfRecording);
Timers->SetModified();
}
return true;
}
return false;
}
cTimer *cTimers::GetTimer(cTimer *Timer)
{
for (cTimer *ti = First(); ti; ti = Next(ti)) {
if (ti->Channel() == Timer->Channel() &&
if (!ti->Remote() &&
ti->Channel() == Timer->Channel() &&
(ti->WeekDays() && ti->WeekDays() == Timer->WeekDays() || !ti->WeekDays() && ti->Day() == Timer->Day()) &&
ti->Start() == Timer->Start() &&
ti->Stop() == Timer->Stop())
@ -713,12 +738,12 @@ cTimer *cTimers::GetTimer(cTimer *Timer)
return NULL;
}
cTimer *cTimers::GetMatch(time_t t)
const cTimer *cTimers::GetMatch(time_t t) const
{
static int LastPending = -1;
cTimer *t0 = NULL;
for (cTimer *ti = First(); ti; ti = Next(ti)) {
if (!ti->Recording() && ti->Matches(t)) {
const cTimer *t0 = NULL;
for (const cTimer *ti = First(); ti; ti = Next(ti)) {
if (!ti->Remote() && !ti->Recording() && ti->Matches(t)) {
if (ti->Pending()) {
if (ti->Index() > LastPending) {
LastPending = ti->Index();
@ -736,11 +761,11 @@ cTimer *cTimers::GetMatch(time_t t)
return t0;
}
cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match)
const cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match) const
{
cTimer *t = NULL;
const cTimer *t = NULL;
eTimerMatch m = tmNone;
for (cTimer *ti = First(); ti; ti = Next(ti)) {
for (const cTimer *ti = First(); ti; ti = Next(ti)) {
eTimerMatch tm = ti->Matches(Event);
if (tm > m) {
t = ti;
@ -754,21 +779,27 @@ cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match)
return t;
}
cTimer *cTimers::GetNextActiveTimer(void)
const cTimer *cTimers::GetNextActiveTimer(void) const
{
cTimer *t0 = NULL;
for (cTimer *ti = First(); ti; ti = Next(ti)) {
ti->Matches();
if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
t0 = ti;
const cTimer *t0 = NULL;
for (const cTimer *ti = First(); ti; ti = Next(ti)) {
if (!ti->Remote()) {
ti->Matches();
if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
t0 = ti;
}
}
return t0;
}
void cTimers::SetModified(void)
const cTimers *cTimers::GetTimersRead(cStateKey &StateKey, int TimeoutMs)
{
cStatus::MsgTimerChange(NULL, tcMod);
state++;
return timers.Lock(StateKey, false, TimeoutMs) ? &timers : NULL;
}
cTimers *cTimers::GetTimersWrite(cStateKey &StateKey, int TimeoutMs)
{
return timers.Lock(StateKey, true, TimeoutMs) ? &timers : NULL;
}
void cTimers::Add(cTimer *Timer, cTimer *After)
@ -789,46 +820,113 @@ void cTimers::Del(cTimer *Timer, bool DeleteObject)
cConfig<cTimer>::Del(Timer, DeleteObject);
}
bool cTimers::Modified(int &State)
const cTimer *cTimers::UsesChannel(const cChannel *Channel) const
{
bool Result = state != State;
State = state;
return Result;
for (const cTimer *Timer = First(); Timer; Timer = Next(Timer)) {
if (Timer->Channel() == Channel)
return Timer;
}
return NULL;
}
void cTimers::SetEvents(void)
bool cTimers::SetEvents(const cSchedules *Schedules)
{
if (time(NULL) - lastSetEvents < 5)
return;
cSchedulesLock SchedulesLock(false, 100);
const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
if (Schedules) {
if (!lastSetEvents || Schedules->Modified() >= lastSetEvents) {
for (cTimer *ti = First(); ti; ti = Next(ti)) {
if (cRemote::HasKeys())
return; // react immediately on user input
ti->SetEventFromSchedule(Schedules);
}
}
}
lastSetEvents = time(NULL);
bool TimersModified = false;
for (cTimer *ti = First(); ti; ti = Next(ti))
TimersModified |= ti->SetEventFromSchedule(Schedules);
return TimersModified;
}
void cTimers::DeleteExpired(void)
bool cTimers::DeleteExpired(void)
{
if (time(NULL) - lastDeleteExpired < 30)
return;
return false;
bool TimersModified = false;
cTimer *ti = First();
while (ti) {
cTimer *next = Next(ti);
if (ti->Expired()) {
if (!ti->Remote() && ti->Expired()) {
isyslog("deleting timer %s", *ti->ToDescr());
Del(ti);
SetModified();
TimersModified = true;
}
ti = next;
}
lastDeleteExpired = time(NULL);
return TimersModified;
}
bool cTimers::GetRemoteTimers(const char *ServerName)
{
bool Result = false;
if (ServerName) {
Result = DelRemoteTimers(ServerName);
cSVDRPCommand Cmd(ServerName, "LSTT ID");
if (Cmd.Execute()) {
const char *s;
for (int i = 0; s = Cmd.Response(i); i++) {
int Code = Cmd.Code(s);
if (Code == 250) {
if (const char *v = Cmd.Value(s)) {
while (*v && *v != ' ')
v++; // skip number
cTimer *Timer = new cTimer;
if (Timer->Parse(v)) {
Timer->SetRemote(ServerName);
Add(Timer);
Result = true;
}
else {
esyslog("ERROR: %s: error in timer settings: %s", ServerName, v);
delete Timer;
}
}
}
else if (Code != 550)
esyslog("ERROR: %s: %s", ServerName, s);
}
return Result;
}
}
else {
cStringList ServerNames;
if (GetSVDRPServerNames(&ServerNames, sffTimers)) {
for (int i = 0; i < ServerNames.Size(); i++)
Result |= GetRemoteTimers(ServerNames[i]);
}
}
return Result;
}
bool cTimers::DelRemoteTimers(const char *ServerName)
{
bool Deleted = false;
cTimer *Timer = First();
while (Timer) {
cTimer *t = Next(Timer);
if (Timer->Remote() && (!ServerName || strcmp(Timer->Remote(), ServerName) == 0)) {
Del(Timer);
Deleted = true;
}
Timer = t;
}
return Deleted;
}
void cTimers::TriggerRemoteTimerPoll(const char *ServerName)
{
if (ServerName) {
cSVDRPCommand Cmd(ServerName, cString::sprintf("POLL %s TIMERS", SVDRPHostName()));
if (!Cmd.Execute())
esyslog("ERROR: can't send 'POLL %s TIMERS' to '%s'", SVDRPHostName(), ServerName);
}
else {
cStringList ServerNames;
if (GetSVDRPServerNames(&ServerNames)) {
for (int i = 0; i < ServerNames.Size(); i++)
TriggerRemoteTimerPoll(ServerNames[i]);
}
}
}
// --- cSortedTimers ---------------------------------------------------------
@ -838,10 +936,10 @@ static int CompareTimers(const void *a, const void *b)
return (*(const cTimer **)a)->Compare(**(const cTimer **)b);
}
cSortedTimers::cSortedTimers(void)
:cVector<const cTimer *>(Timers.Count())
cSortedTimers::cSortedTimers(const cTimers *Timers)
:cVector<const cTimer *>(Timers->Count())
{
for (const cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer))
for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer))
Append(Timer);
Sort(CompareTimers);
}

119
timers.h
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: timers.h 2.7 2013/03/11 10:35:53 kls Exp $
* $Id: timers.h 4.1 2015/08/31 10:42:53 kls Exp $
*/
#ifndef __TIMERS_H
@ -28,11 +28,11 @@ class cTimer : public cListObject {
friend class cMenuEditTimer;
private:
mutable time_t startTime, stopTime;
time_t lastSetEvent;
int scheduleState;
mutable time_t deferred; ///< Matches(time_t, ...) will return false if the current time is before this value
bool recording, pending, inVpsMargin;
bool pending, inVpsMargin;
uint flags;
cChannel *channel;
const cChannel *channel;
mutable time_t day; ///< midnight of the day this timer shall hit, or of the first day it shall hit in case of a repeating timer
int weekdays; ///< bitmask, lowest bits: SSFTWTM (the 'M' is the LSB)
int start;
@ -41,15 +41,16 @@ private:
int lifetime;
mutable char file[NAME_MAX * 2 + 1]; // *2 to be able to hold 'title' and 'episode', which can each be up to 255 characters long
char *aux;
char *remote;
const cEvent *event;
public:
cTimer(bool Instant = false, bool Pause = false, cChannel *Channel = NULL);
cTimer(bool Instant = false, bool Pause = false, const cChannel *Channel = NULL);
cTimer(const cEvent *Event);
cTimer(const cTimer &Timer);
virtual ~cTimer();
cTimer& operator= (const cTimer &Timer);
virtual int Compare(const cListObject &ListObject) const;
bool Recording(void) const { return recording; }
bool Recording(void) const { return HasFlags(tfRecording); }
bool Pending(void) const { return pending; }
bool InVpsMargin(void) const { return inVpsMargin; }
uint Flags(void) const { return flags; }
@ -63,6 +64,7 @@ public:
const char *File(void) const { return file; }
time_t FirstDay(void) const { return weekdays ? day : 0; }
const char *Aux(void) const { return aux; }
const char *Remote(void) const { return remote; }
time_t Deferred(void) const { return deferred; }
cString ToText(bool UseChannelID = false) const;
cString ToDescr(void) const;
@ -81,8 +83,8 @@ public:
bool Expired(void) const;
time_t StartTime(void) const;
time_t StopTime(void) const;
void SetEventFromSchedule(const cSchedules *Schedules = NULL);
void SetEvent(const cEvent *Event);
bool SetEventFromSchedule(const cSchedules *Schedules);
bool SetEvent(const cEvent *Event);
void SetRecording(bool Recording);
void SetPending(bool Pending);
void SetInVpsMargin(bool InVpsMargin);
@ -93,6 +95,7 @@ public:
void SetPriority(int Priority);
void SetLifetime(int Lifetime);
void SetAux(const char *Aux);
void SetRemote(const char *Remote);
void SetDeferred(int Seconds);
void SetFlags(uint Flags);
void ClrFlags(uint Flags);
@ -108,36 +111,100 @@ public:
class cTimers : public cConfig<cTimer> {
private:
int state;
int beingEdited;
time_t lastSetEvents;
static cTimers timers;
time_t lastDeleteExpired;
public:
cTimers(void);
static const cTimers *GetTimersRead(cStateKey &StateKey, int TimeoutMs = 0);
///< Gets the list of timers for read access. If TimeoutMs is given,
///< it will wait that long to get a write lock before giving up.
///< Otherwise it will wait indefinitely. If no read lock can be
///< obtained within the given timeout, NULL will be returned.
///< The list is locked and a pointer to it is returned if the state
///< of the list is different than the state of the given StateKey.
///< If both states are equal, the list of timers has not been modified
///< since the last call with the same StateKey, and NULL will be
///< returned (and the list is not locked). After the returned list of
///< timers is no longer needed, the StateKey's Remove() function must
///< be called to release the list. The time between calling
///< cTimers::GetTimersRead() and StateKey.Remove() should be as short
///< as possible. After calling StateKey.Remove() the list returned from
///< this call must not be accessed any more. If you need to access the
///< timers again later, a new call to GetTimersRead() must be made.
///< A typical code sequence would look like this:
///< cStateKey StateKey;
///< if (const cTimers *Timers = cTimers::GetTimersRead(StateKey)) {
///< // access the timers
///< StateKey.Remove();
///< }
static cTimers *GetTimersWrite(cStateKey &StateKey, int TimeoutMs = 0);
///< Gets the list of timers for write access. If TimeoutMs is given,
///< it will wait that long to get a write lock before giving up.
///< Otherwise it will wait indefinitely. If no write lock can be
///< obtained within the given timeout, NULL will be returned.
///< If a write lock can be obtained, the list of timers will be
///< returned, regardless of the state values of the timers or the
///< given StateKey. After the returned list of timers is no longer
///< needed, the StateKey's Remove() function must be called to release
///< the list. The time between calling cTimers::GetTimersWrite() and
///< StateKey.Remove() should be as short as possible. After calling
///< StateKey.Remove() the list returned from this call must not be
///< accessed any more. If you need to access the timers again later,
///< a new call to GetTimersWrite() must be made. The call
///< to StateKey.Remove() will increment the state of the list of
///< timers and will copy the new state value to the StateKey. You can
///< suppress this by using 'false' as the parameter to the call, in
///< which case the state values are left untouched.
///< A typical code sequence would look like this:
///< cStateKey StateKey;
///< if (cTimers *Timers = cTimers::GetTimersWrite(StateKey)) {
///< // access the timers
///< StateKey.Remove();
///< }
static bool Load(const char *FileName);
cTimer *GetTimer(cTimer *Timer);
cTimer *GetMatch(time_t t);
cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL);
cTimer *GetNextActiveTimer(void);
int BeingEdited(void) { return beingEdited; }
void IncBeingEdited(void) { beingEdited++; }
void DecBeingEdited(void) { if (!--beingEdited) lastSetEvents = 0; }
void SetModified(void);
bool Modified(int &State);
///< Returns true if any of the timers have been modified, which
///< is detected by State being different than the internal state.
///< Upon return the internal state will be stored in State.
void SetEvents(void);
void DeleteExpired(void);
const cTimer *GetMatch(time_t t) const;
cTimer *GetMatch(time_t t) { return const_cast<cTimer *>(static_cast<const cTimers *>(this)->GetMatch(t)); };
const cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL) const;
cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL) { return const_cast<cTimer *>(static_cast<const cTimers *>(this)->GetMatch(Event, Match)); }
const cTimer *GetNextActiveTimer(void) const;
const cTimer *UsesChannel(const cChannel *Channel) const;
bool SetEvents(const cSchedules *Schedules);
bool DeleteExpired(void);
void Add(cTimer *Timer, cTimer *After = NULL);
void Ins(cTimer *Timer, cTimer *Before = NULL);
void Del(cTimer *Timer, bool DeleteObject = true);
bool GetRemoteTimers(const char *ServerName = NULL);
///< Gets the timers from the given remote machine and adds them to this
///< list. If no ServerName is given, all timers from all known remote
///< machines will be fetched. This function calls DelRemoteTimers() with
///< the given ServerName first.
///< Returns true if any remote timers have been added or deleted
bool DelRemoteTimers(const char *ServerName = NULL);
///< Deletes all timers of the given remote machine from this list (leaves
///< them untouched on the remote machine). If no ServerName is given, the
///< timers of all remote machines will be deleted from the list.
///< Returns true if any remote timers have been deleted.
void TriggerRemoteTimerPoll(const char *ServerName = NULL);
///< Sends an SVDRP POLL command to the given remote machine.
///< If no ServerName is given, the POLL command will be sent to all
///< known remote machines.
};
extern cTimers Timers;
// Provide lock controlled access to the list:
DEF_LIST_LOCK(Timers);
// These macros provide a convenient way of locking the global timers list
// and making sure the lock is released as soon as the current scope is left
// (note that these macros wait forever to obtain the lock!):
#define LOCK_TIMERS_READ USE_LIST_LOCK_READ(Timers)
#define LOCK_TIMERS_WRITE USE_LIST_LOCK_WRITE(Timers)
class cSortedTimers : public cVector<const cTimer *> {
public:
cSortedTimers(void);
cSortedTimers(const cTimers *Timers);
};
#endif //__TIMERS_H

90
tools.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: tools.c 4.1 2015/05/11 14:15:15 kls Exp $
* $Id: tools.c 4.2 2015/08/29 12:11:20 kls Exp $
*/
#include "tools.h"
@ -2044,12 +2044,58 @@ int cListObject::Index(void) const
return i;
}
// --- cListGarbageCollector -------------------------------------------------
#define LIST_GARBAGE_COLLECTOR_TIMEOUT 5 // seconds
cListGarbageCollector ListGarbageCollector;
cListGarbageCollector::cListGarbageCollector(void)
{
objects = NULL;
lastPut = 0;
}
cListGarbageCollector::~cListGarbageCollector()
{
if (objects)
esyslog("ERROR: ListGarbageCollector destroyed without prior Purge()!");
}
void cListGarbageCollector::Put(cListObject *Object)
{
mutex.Lock();
Object->next = objects;
objects = Object;
lastPut = time(NULL);
mutex.Unlock();
}
void cListGarbageCollector::Purge(bool Force)
{
mutex.Lock();
if (objects && (time(NULL) - lastPut > LIST_GARBAGE_COLLECTOR_TIMEOUT || Force)) {
// We make sure that any object stays in the garbage collector for at least
// LIST_GARBAGE_COLLECTOR_TIMEOUT seconds, to give objects that have pointers
// to them a chance to drop these references before the object is finally
// deleted.
while (cListObject *Object = objects) {
objects = Object->next;
delete Object;
}
}
mutex.Unlock();
}
// --- cListBase -------------------------------------------------------------
cListBase::cListBase(void)
cListBase::cListBase(const char *NeedsLocking)
:stateLock(NeedsLocking)
{
objects = lastObject = NULL;
count = 0;
needsLocking = NeedsLocking;
useGarbageCollector = needsLocking;
}
cListBase::~cListBase()
@ -2057,6 +2103,15 @@ cListBase::~cListBase()
Clear();
}
bool cListBase::Lock(cStateKey &StateKey, bool Write, int TimeoutMs) const
{
if (needsLocking)
return stateLock.Lock(StateKey, Write, TimeoutMs);
else
esyslog("ERROR: cListBase::Lock() called for a list that doesn't require locking");
return false;
}
void cListBase::Add(cListObject *Object, cListObject *After)
{
if (After && After != lastObject) {
@ -2096,8 +2151,12 @@ void cListBase::Del(cListObject *Object, bool DeleteObject)
if (Object == lastObject)
lastObject = Object->Prev();
Object->Unlink();
if (DeleteObject)
delete Object;
if (DeleteObject) {
if (useGarbageCollector)
ListGarbageCollector.Put(Object);
else
delete Object;
}
count--;
}
@ -2141,11 +2200,30 @@ void cListBase::Clear(void)
count = 0;
}
cListObject *cListBase::Get(int Index) const
bool cListBase::Contains(const cListObject *Object) const
{
for (const cListObject *o = objects; o; o = o->Next()) {
if (o == Object)
return true;
}
return false;
}
void cListBase::SetExplicitModify(void)
{
stateLock.SetExplicitModify();
}
void cListBase::SetModified(void)
{
stateLock.IncState();
}
const cListObject *cListBase::Get(int Index) const
{
if (Index < 0)
return NULL;
cListObject *object = objects;
const cListObject *object = objects;
while (object && Index-- > 0)
object = object->Next();
return object;

137
tools.h
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: tools.h 4.1 2015/05/21 14:37:00 kls Exp $
* $Id: tools.h 4.2 2015/08/29 11:45:51 kls Exp $
*/
#ifndef __TOOLS_H
@ -26,6 +26,7 @@
#include <syslog.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "thread.h"
typedef unsigned char uchar;
@ -462,6 +463,7 @@ public:
};
class cListObject {
friend class cListGarbageCollector;
private:
cListObject *prev, *next;
public:
@ -478,33 +480,152 @@ public:
cListObject *Next(void) const { return next; }
};
class cListGarbageCollector {
private:
cMutex mutex;
cListObject *objects;
time_t lastPut;
public:
cListGarbageCollector(void);
~cListGarbageCollector();
void Put(cListObject *Object);
void Purge(bool Force = false);
};
extern cListGarbageCollector ListGarbageCollector;
class cListBase {
protected:
cListObject *objects, *lastObject;
cListBase(void);
int count;
mutable cStateLock stateLock;
const char *needsLocking;
bool useGarbageCollector;
cListBase(const char *NeedsLocking = NULL);
public:
virtual ~cListBase();
bool Lock(cStateKey &StateKey, bool Write = false, int TimeoutMs = 0) const;
///< Tries to get a lock on this list and returns true if successful.
///< By default a read lock is requested. Set Write to true to obtain
///< a write lock. If TimeoutMs is not zero, it waits for the given
///< number of milliseconds before giving up.
///< If you need to lock more than one list at the same time, make sure
///< you set TimeoutMs to a suitable value in all of the calls to
///< Lock(), and be prepared to handle situations where you do not get all
///< of the requested locks. In such cases you should release all the locks
///< you have obtained so far and try again. StateKey.TimedOut() tells you
///< whether the lock attempt failed due to a timeout or because the state
///< of the lock hasn't changed since the previous locking attempt.
///< To implicitly avoid deadlocks when locking more than one of the global
///< lists of VDR at the same time, make sure you always lock Timers, Channels,
///< Recordings and Schedules in this sequence.
///< You may keep pointers to objects in this list, even after releasing
///< the lock. However, you may only access such objects if you are
///< holding a proper lock again. If an object has been deleted from the list
///< while you did not hold a lock (for instance by an other thread), the
///< object will still be there, but no longer within this list (it is then
///< stored in the ListGarbageCollector). That way even if you access the object
///< after it has been deleted, you won't cause a segfault. You can call the
///< Contains() function to check whether an object you are holding a pointer
///< to is still in the list. Note that the garbage collector is purged when
///< the usual housekeeping is done.
void SetUseGarbageCollector(void) { useGarbageCollector = true; }
void SetExplicitModify(void);
///< If you have obtained a write lock on this list, and you don't want it to
///< be automatically marked as modified when the lock is released, a call to
///< this function will disable this, and you can explicitly call SetModified()
///< to have the list marked as modified.
void SetModified(void);
///< Unconditionally marks this list as modified.
void Add(cListObject *Object, cListObject *After = NULL);
void Ins(cListObject *Object, cListObject *Before = NULL);
void Del(cListObject *Object, bool DeleteObject = true);
virtual void Move(int From, int To);
void Move(cListObject *From, cListObject *To);
virtual void Clear(void);
cListObject *Get(int Index) const;
bool Contains(const cListObject *Object) const;
///< If a pointer to an object contained in this list has been obtained while
///< holding a lock, and that lock has been released, but the pointer is kept for
///< later use (after obtaining a new lock), Contains() can be called with that
///< pointer to make sure the object it points to is still part of this list
///< (it may have been deleted or otherwise removed from the list after the lock
///< during which the pointer was initially retrieved has been released).
const cListObject *Get(int Index) const;
cListObject *Get(int Index) { return const_cast<cListObject *>(static_cast<const cListBase *>(this)->Get(Index)); }
int Count(void) const { return count; }
void Sort(void);
};
template<class T> class cList : public cListBase {
public:
T *Get(int Index) const { return (T *)cListBase::Get(Index); }
T *First(void) const { return (T *)objects; }
T *Last(void) const { return (T *)lastObject; }
T *Prev(const T *object) const { return (T *)object->cListObject::Prev(); } // need to call cListObject's members to
T *Next(const T *object) const { return (T *)object->cListObject::Next(); } // avoid ambiguities in case of a "list of lists"
cList(const char *NeedsLocking = NULL): cListBase(NeedsLocking) {}
///< Sets up a new cList of the given type T. If NeedsLocking is given, the list
///< and any of its elements may only be accessed if the caller holds a lock
///< obtained by a call to Lock() (see cListBase::Lock() for details).
///< NeedsLocking is used as both a boolean flag to enable locking, and as
///< a name to identify this list in debug output. It must be a static string
///< and should be no longer than 10 characters. The string will not be copied!
const T *Get(int Index) const { return (T *)cListBase::Get(Index); }
///< Returns the list element at the given Index, or NULL if no such element
///< exists.
const T *First(void) const { return (T *)objects; }
///< Returns the first element in this list, or NULL if the list is empty.
const T *Last(void) const { return (T *)lastObject; }
///< Returns the last element in this list, or NULL if the list is empty.
const T *Prev(const T *Object) const { return (T *)Object->cListObject::Prev(); } // need to call cListObject's members to
///< Returns the element immediately before Object in this list, or NULL
///< if Object is the first element in the list. Object must not be NULL!
const T *Next(const T *Object) const { return (T *)Object->cListObject::Next(); } // avoid ambiguities in case of a "list of lists"
///< Returns the element immediately following Object in this list, or NULL
///< if Object is the last element in the list. Object must not be NULL!
T *Get(int Index) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Get(Index)); }
///< Non-const version of Get().
T *First(void) { return const_cast<T *>(static_cast<const cList<T> *>(this)->First()); }
///< Non-const version of First().
T *Last(void) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Last()); }
///< Non-const version of Last().
T *Prev(const T *Object) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Prev(Object)); }
///< Non-const version of Prev().
T *Next(const T *Object) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Next(Object)); }
///< Non-const version of Next().
};
// The DEF_LIST_LOCK macro defines a convenience class that can be used to obtain
// a lock on a cList and make sure the lock is released when the current scope
// is left:
#define DEF_LIST_LOCK2(Class, Name) \
class c##Name##Lock { \
private: \
cStateKey stateKey; \
const c##Class *list; \
public: \
c##Name##Lock(bool Write = false) \
{ \
if (Write) \
list = c##Class::Get##Name##Write(stateKey); \
else \
list = c##Class::Get##Name##Read(stateKey); \
} \
~c##Name##Lock() { stateKey.Remove(); } \
const c##Class *Name(void) const { return list; } \
c##Class *Name(void) { return const_cast<c##Class *>(list); } \
}
#define DEF_LIST_LOCK(Class) DEF_LIST_LOCK2(Class, Class)
// The USE_LIST_LOCK macro sets up a local variable of a class defined by
// a suitable DEF_LIST_LOCK, and also a pointer to the provided list:
#define USE_LIST_LOCK_READ2(Class, Name) \
c##Name##Lock Name##Lock(false); \
const c##Class *Name __attribute__((unused)) = Name##Lock.Name();
#define USE_LIST_LOCK_READ(Class) USE_LIST_LOCK_READ2(Class, Class)
#define USE_LIST_LOCK_WRITE2(Class, Name) \
c##Name##Lock Name##Lock(true); \
c##Class *Name __attribute__((unused)) = Name##Lock.Name();
#define USE_LIST_LOCK_WRITE(Class) USE_LIST_LOCK_WRITE2(Class, Class)
template<class T> class cVector {
///< cVector may only be used for *simple* types, like int or pointers - not for class objects that allocate additional memory!
private:

298
vdr.c
View File

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

View File

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

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: videodir.h 3.2 2013/10/11 09:37:48 kls Exp $
* $Id: videodir.h 4.1 2015/08/10 13:21:29 kls Exp $
*/
#ifndef __VIDEODIR_H
@ -15,6 +15,7 @@
class cVideoDirectory {
private:
static cMutex mutex;
static cString name;
static cVideoDirectory *current;
static cVideoDirectory *Current(void);