mirror of
https://github.com/VDR4Arch/vdr.git
synced 2023-10-10 13:36:52 +02:00
Implemented strict locking of global lists
This commit is contained in:
parent
8a7bc6a0bb
commit
3cd5294d8a
113
HISTORY
113
HISTORY
@ -8596,7 +8596,7 @@ Video Disk Recorder Revision History
|
||||
- Bumped all version numbers to 2.2.0.
|
||||
- Official release.
|
||||
|
||||
2015-04-29: Version 2.3.1
|
||||
2015-09-01: Version 2.3.1
|
||||
|
||||
- The new function cOsd::MaxPixmapSize() can be called to determine the maximum size
|
||||
a cPixmap may have on the current OSD. The 'osddemo' example has been modified
|
||||
@ -8671,3 +8671,114 @@ Video Disk Recorder Revision History
|
||||
this VDR is connected to via SVDRP.
|
||||
- The new class cSVDRPCommand can be used to execute an SVDRP command on one of
|
||||
the servers this VDR is connected to, and retrieve the result.
|
||||
- The cTimer class now has a new member named 'remote', which holds the name of the
|
||||
remote server this timer will record on. If this is NULL, it is a local timer.
|
||||
- Timers from other VDRs that are connected to this VDR via SVDRP are now
|
||||
automatically fetched and stored in the global Timers list. In order for this
|
||||
to work, all of the channels used by timers on the remote VDR must also be
|
||||
defined on the local VDR (however, not necessarily in the same sequence).
|
||||
Automatic channel syncing will be implemented later.
|
||||
- The main menu of the LCARS skin now displays a small rectangle on the left side
|
||||
of a timer if this is a remote timer. The color of that rectangle changes if
|
||||
the timer is currently recording on the remote VDR.
|
||||
- Accessing the global Timers list now has to be protected by proper locking,
|
||||
because SVDRP commands are now executed in a separate thread.
|
||||
The introduction of this locking mechanism required the following changes:
|
||||
+ The new classes cStateLock and cStateKey are used to implement locking
|
||||
with quick detection of state changes.
|
||||
+ cConfig::cConfig() now has a parameter that indicates whether this list
|
||||
requires locking.
|
||||
+ The global lists of Timers, Channels, Schedules and Recordings are no longer
|
||||
static variables. They are now pointers that need to be retrieved through
|
||||
a call to cTimers::GetTimersRead/Write(), cChannels::GetChannelsRead/Write(),
|
||||
cSchedules::GetSchedulesRead/Write() and cRecordings::GetRecordingsRead/Write(),
|
||||
respectively.
|
||||
+ References from/to link channels are now removed in cChannels::Del() rather
|
||||
than cChannel::~cChannel(), to make sure the caller holds a proper lock.
|
||||
+ cChannel::HasTimer() has been removed. This information is now retrieved
|
||||
via cSchedule::HasTimer().
|
||||
+ Several member functions of cChannel, cTimer, cMarks and cRecording have
|
||||
been made 'const', and some of them are now available as both 'const' and
|
||||
'non-const' versions.
|
||||
+ The cChannel::Set...() functions are now 'bool' and return true if they have
|
||||
actually changed any of the channels's members.
|
||||
+ cChannels::SetModified() has been renamed to cChannels::SetModifiedByUser().
|
||||
+ cChannels::Modified() has been renamed to cChannels::ModifiedByUser(), and
|
||||
now has a 'State' parameter that allows the caller to see whether a channel
|
||||
has been modified since the last call to this function with the same State
|
||||
variable.
|
||||
+ The macros CHANNELSMOD_NONE/_AUTO/_USER have been removed.
|
||||
+ cMarks now requires locking via cStateKey.
|
||||
+ cSortedTimers now requires a pointer to the list of timers.
|
||||
+ cEvent::HasTimer() no longer scans the list of timers to check whether an event
|
||||
is referenced by a timer, but rather keeps score of how many timers reference
|
||||
it. This was necessary in order to avoid having to lock the list of timers from
|
||||
within a cEvent.
|
||||
+ The new class cListGarbageCollector is used to temporary store any objects deleted
|
||||
from cLists that require locking. This allows pointers to such objects to be
|
||||
dereferenced even if the objects are no longer part of the list.
|
||||
+ cListBase::Contains() can be used to check whether a particular object is still
|
||||
contained in that list.
|
||||
+ Outdated events are no longer "phased out", but rather deleted right away and thus
|
||||
taken care of by the new "garbage collector" of the list.
|
||||
+ Deleted cRecording objects are no longer kept in a list of "vanished" recordings,
|
||||
but are rather taken care of by the new "garbage collector" of the list.
|
||||
+ cSchedules::ClearAll() has been removed. The functionality is now implemented
|
||||
directly in cSVDRPServer::CmdCLRE().
|
||||
+ tEventID has been changed to u_int16_t in order to make room for the new member
|
||||
numTimers in cEvent.
|
||||
+ cSchedule now has a member Modified(), which can be used with a State variable
|
||||
to quickly determine whether this schedule has been modified since the last call
|
||||
to this function with the same State variable.
|
||||
+ cSchedulesLock has been removed. Locking the list of schedules is now done via
|
||||
the cList's new locking mechanism.
|
||||
+ The 'OnlyRunningStatus' parameters in cEpgHandler::BeginSegmentTransfer() and
|
||||
cEpgHandler::EndSegmentTransfer() are now obsolete. They are still present in
|
||||
the interface for backward compatibility, but may be removed in a future version.
|
||||
Their value is always 'false'.
|
||||
+ The constant tcMod is no longer used in cStatus::TimerChange(). The definition is
|
||||
still there for backward compatibility.
|
||||
Plugins that access the global lists of Timers, Channels, Recordings or Schedules
|
||||
will need to be adapted as follows:
|
||||
+ Instead of directly accessing the global variables Timers, Channels or Recordings,
|
||||
they need to set up a cStateKey variable and call the proper getter function,
|
||||
as in
|
||||
cStateKey StateKey;
|
||||
if (const cTimers *Timers = cTimers::GetTimersRead(StateKey)) {
|
||||
// access the timers
|
||||
StateKey.Remove();
|
||||
}
|
||||
and
|
||||
cStateKey StateKey;
|
||||
if (cTimers *Timers = cTimers::GetTimersWrite(StateKey)) {
|
||||
// access the timers
|
||||
StateKey.Remove();
|
||||
}
|
||||
See timers.h, thread.h and tools.h for details on this new locking mechanism.
|
||||
+ There are convenience macros for easily accessing these lists without having
|
||||
to explicitly set up a cStateKey and calling its Remove() function. These macros
|
||||
have the form LOCK_*_READ/WRITE (with '*' being TIMERS, CHANNELS, SCHEDULES or
|
||||
RECORDINGS). Simply put such a macro before the point where you need to access
|
||||
the respective list, and there will be a pointer named Timers, Channels, Schedules
|
||||
or Recordings, respectively, which is valid until the end of the current block.
|
||||
+ If a plugin needs to access several of the global lists in parallel, locking must
|
||||
always be done in the sequence Timers, Channels, Recordings, Schedules. This is
|
||||
necessary to make sure that different threads that need to lock several lists at
|
||||
the same time don't end up in a deadlock.
|
||||
+ Some pointer variables may need to be made 'const'. The compiler will tell you
|
||||
about these.
|
||||
- cSectionSyncer has been improved to better handle missed sections.
|
||||
- Added a missing initialization of 'seen' in cChannel's copy constructor.
|
||||
- Background modifications of channels, timers and events are now displayed immediately
|
||||
in the corresponding menus.
|
||||
- cEIT now checks the version of the tables before doing any processing, which saves
|
||||
a lot of locking and processing.
|
||||
- If a timer is newly created with the Red button in the Schedule menu, and the timer
|
||||
is presented to the user in the "Edit timer" menu because it will start immediately,
|
||||
it now *must* be confirmed with "Ok" to set the timer. Otherwise the timer will not
|
||||
be created.
|
||||
- Recordings and deleted recordings are now scanned in a single thread.
|
||||
- The new SVDRP command POLL is used by automatically established peer-to-peer
|
||||
connections to trigger fetching remote timers.
|
||||
- You can now set DumpSVDRPDataTransfer in svdrp.c to true to have all SVDRP
|
||||
communication printed to the console for debugging.
|
||||
|
335
channels.c
335
channels.c
@ -4,15 +4,13 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: channels.c 4.1 2015/03/13 11:34:28 kls Exp $
|
||||
* $Id: channels.c 4.2 2015/08/29 12:16:24 kls Exp $
|
||||
*/
|
||||
|
||||
#include "channels.h"
|
||||
#include <ctype.h>
|
||||
#include "device.h"
|
||||
#include "epg.h"
|
||||
#include "libsi/si.h"
|
||||
#include "timers.h"
|
||||
|
||||
// IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d'
|
||||
// format characters in order to allow any number of blanks after a numeric
|
||||
@ -79,27 +77,13 @@ cChannel::cChannel(const cChannel &Channel)
|
||||
schedule = NULL;
|
||||
linkChannels = NULL;
|
||||
refChannel = NULL;
|
||||
seen = 0;
|
||||
*this = Channel;
|
||||
}
|
||||
|
||||
cChannel::~cChannel()
|
||||
{
|
||||
delete linkChannels;
|
||||
linkChannels = NULL; // more than one channel can link to this one, so we need the following loop
|
||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
||||
if (Channel->linkChannels) {
|
||||
for (cLinkChannel *lc = Channel->linkChannels->First(); lc; lc = Channel->linkChannels->Next(lc)) {
|
||||
if (lc->Channel() == this) {
|
||||
Channel->linkChannels->Del(lc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Channel->linkChannels->Count() == 0) {
|
||||
delete Channel->linkChannels;
|
||||
Channel->linkChannels = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
delete linkChannels; // any links from other channels pointing to this one have been deleted in cChannels::Del()
|
||||
free(name);
|
||||
free(shortName);
|
||||
free(provider);
|
||||
@ -167,16 +151,7 @@ int cChannel::Transponder(void) const
|
||||
return tf;
|
||||
}
|
||||
|
||||
bool cChannel::HasTimer(void) const
|
||||
{
|
||||
for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
|
||||
if (Timer->Channel() == this)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int cChannel::Modification(int Mask)
|
||||
int cChannel::Modification(int Mask) const
|
||||
{
|
||||
int Result = modification & Mask;
|
||||
modification = CHANNELMOD_NONE;
|
||||
@ -223,53 +198,57 @@ bool cChannel::SetTransponderData(int Source, int Frequency, int Srate, const ch
|
||||
if (Number() && !Quiet) {
|
||||
dsyslog("changing transponder data of channel %d (%s) from %s to %s", Number(), name, *OldTransponderData, *TransponderDataToString());
|
||||
modification |= CHANNELMOD_TRANSP;
|
||||
Channels.SetModified();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void cChannel::SetSource(int Source)
|
||||
bool cChannel::SetSource(int Source)
|
||||
{
|
||||
if (source != Source) {
|
||||
if (Number()) {
|
||||
dsyslog("changing source of channel %d (%s) from %s to %s", Number(), name, *cSource::ToString(source), *cSource::ToString(Source));
|
||||
modification |= CHANNELMOD_TRANSP;
|
||||
Channels.SetModified();
|
||||
}
|
||||
source = Source;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cChannel::SetId(int Nid, int Tid, int Sid, int Rid)
|
||||
bool cChannel::SetId(cChannels *Channels, int Nid, int Tid, int Sid, int Rid)
|
||||
{
|
||||
if (nid != Nid || tid != Tid || sid != Sid || rid != Rid) {
|
||||
if (Number()) {
|
||||
if (Channels && Number()) {
|
||||
dsyslog("changing id of channel %d (%s) from %d-%d-%d-%d to %d-%d-%d-%d", Number(), name, nid, tid, sid, rid, Nid, Tid, Sid, Rid);
|
||||
modification |= CHANNELMOD_ID;
|
||||
Channels.SetModified();
|
||||
Channels.UnhashChannel(this);
|
||||
Channels->UnhashChannel(this);
|
||||
}
|
||||
nid = Nid;
|
||||
tid = Tid;
|
||||
sid = Sid;
|
||||
rid = Rid;
|
||||
if (Number())
|
||||
Channels.HashChannel(this);
|
||||
if (Channels)
|
||||
Channels->HashChannel(this);
|
||||
schedule = NULL;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cChannel::SetLcn(int Lcn)
|
||||
bool cChannel::SetLcn(int Lcn)
|
||||
{
|
||||
if (lcn != Lcn) {
|
||||
if (Number())
|
||||
dsyslog("changing lcn of channel %d (%s) from %d to %d\n", Number(), name, lcn, Lcn);
|
||||
lcn = Lcn;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cChannel::SetName(const char *Name, const char *ShortName, const char *Provider)
|
||||
bool cChannel::SetName(const char *Name, const char *ShortName, const char *Provider)
|
||||
{
|
||||
if (!isempty(Name)) {
|
||||
bool nn = strcmp(name, Name) != 0;
|
||||
@ -279,7 +258,6 @@ void cChannel::SetName(const char *Name, const char *ShortName, const char *Prov
|
||||
if (Number()) {
|
||||
dsyslog("changing name of channel %d from '%s,%s;%s' to '%s,%s;%s'", Number(), name, shortName, provider, Name, ShortName, Provider);
|
||||
modification |= CHANNELMOD_NAME;
|
||||
Channels.SetModified();
|
||||
}
|
||||
if (nn) {
|
||||
name = strcpyrealloc(name, Name);
|
||||
@ -291,20 +269,23 @@ void cChannel::SetName(const char *Name, const char *ShortName, const char *Prov
|
||||
}
|
||||
if (np)
|
||||
provider = strcpyrealloc(provider, Provider);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cChannel::SetPortalName(const char *PortalName)
|
||||
bool cChannel::SetPortalName(const char *PortalName)
|
||||
{
|
||||
if (!isempty(PortalName) && strcmp(portalName, PortalName) != 0) {
|
||||
if (Number()) {
|
||||
dsyslog("changing portal name of channel %d (%s) from '%s' to '%s'", Number(), name, portalName, PortalName);
|
||||
modification |= CHANNELMOD_NAME;
|
||||
Channels.SetModified();
|
||||
}
|
||||
portalName = strcpyrealloc(portalName, PortalName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#define STRDIFF 0x01
|
||||
@ -349,7 +330,7 @@ static int IntArrayToString(char *s, const int *a, int Base = 10, const char n[]
|
||||
return q - s;
|
||||
}
|
||||
|
||||
void cChannel::SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid)
|
||||
bool cChannel::SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid)
|
||||
{
|
||||
int mod = CHANNELMOD_NONE;
|
||||
if (vpid != Vpid || ppid != Ppid || vtype != Vtype)
|
||||
@ -412,25 +393,33 @@ void cChannel::SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, c
|
||||
spids[MAXSPIDS] = 0;
|
||||
tpid = Tpid;
|
||||
modification |= mod;
|
||||
if (Number())
|
||||
Channels.SetModified();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cChannel::SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds)
|
||||
bool cChannel::SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds)
|
||||
{
|
||||
bool Modified = false;
|
||||
if (SubtitlingTypes) {
|
||||
for (int i = 0; i < MAXSPIDS; i++)
|
||||
for (int i = 0; i < MAXSPIDS; i++) {
|
||||
Modified = subtitlingTypes[i] != SubtitlingTypes[i];
|
||||
subtitlingTypes[i] = SubtitlingTypes[i];
|
||||
}
|
||||
}
|
||||
if (CompositionPageIds) {
|
||||
for (int i = 0; i < MAXSPIDS; i++)
|
||||
for (int i = 0; i < MAXSPIDS; i++) {
|
||||
Modified = compositionPageIds[i] != CompositionPageIds[i];
|
||||
compositionPageIds[i] = CompositionPageIds[i];
|
||||
}
|
||||
}
|
||||
if (AncillaryPageIds) {
|
||||
for (int i = 0; i < MAXSPIDS; i++)
|
||||
for (int i = 0; i < MAXSPIDS; i++) {
|
||||
Modified = ancillaryPageIds[i] != AncillaryPageIds[i];
|
||||
ancillaryPageIds[i] = AncillaryPageIds[i];
|
||||
}
|
||||
}
|
||||
return Modified;
|
||||
}
|
||||
|
||||
void cChannel::SetSeen(void)
|
||||
@ -438,10 +427,26 @@ void cChannel::SetSeen(void)
|
||||
seen = time(NULL);
|
||||
}
|
||||
|
||||
void cChannel::SetCaIds(const int *CaIds)
|
||||
void cChannel::DelLinkChannel(cChannel *LinkChannel)
|
||||
{
|
||||
if (linkChannels) {
|
||||
for (cLinkChannel *lc = linkChannels->First(); lc; lc = linkChannels->Next(lc)) {
|
||||
if (lc->Channel() == LinkChannel) {
|
||||
linkChannels->Del(lc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (linkChannels->Count() == 0) {
|
||||
delete linkChannels;
|
||||
linkChannels = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool cChannel::SetCaIds(const int *CaIds)
|
||||
{
|
||||
if (caids[0] && caids[0] <= CA_USER_MAX)
|
||||
return; // special values will not be overwritten
|
||||
return false; // special values will not be overwritten
|
||||
if (IntArraysDiffer(caids, CaIds)) {
|
||||
char OldCaIdsBuf[MAXCAIDS * 5 + 10]; // 5: 4 digits plus delimiting ',', 10: paranoia
|
||||
char NewCaIdsBuf[MAXCAIDS * 5 + 10];
|
||||
@ -455,24 +460,26 @@ void cChannel::SetCaIds(const int *CaIds)
|
||||
break;
|
||||
}
|
||||
modification |= CHANNELMOD_CA;
|
||||
Channels.SetModified();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cChannel::SetCaDescriptors(int Level)
|
||||
bool cChannel::SetCaDescriptors(int Level)
|
||||
{
|
||||
if (Level > 0) {
|
||||
modification |= CHANNELMOD_CA;
|
||||
Channels.SetModified();
|
||||
if (Number() && Level > 1)
|
||||
dsyslog("changing ca descriptors of channel %d (%s)", Number(), name);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cChannel::SetLinkChannels(cLinkChannels *LinkChannels)
|
||||
bool cChannel::SetLinkChannels(cLinkChannels *LinkChannels)
|
||||
{
|
||||
if (!linkChannels && !LinkChannels)
|
||||
return;
|
||||
return false;
|
||||
if (linkChannels && LinkChannels) {
|
||||
cLinkChannel *lca = linkChannels->First();
|
||||
cLinkChannel *lcb = LinkChannels->First();
|
||||
@ -486,7 +493,7 @@ void cChannel::SetLinkChannels(cLinkChannels *LinkChannels)
|
||||
}
|
||||
if (!lca && !lcb) {
|
||||
delete LinkChannels;
|
||||
return; // linkage has not changed
|
||||
return false; // linkage has not changed
|
||||
}
|
||||
}
|
||||
char buffer[((linkChannels ? linkChannels->Count() : 0) + (LinkChannels ? LinkChannels->Count() : 0)) * 6 + 256]; // 6: 5 digit channel number plus blank, 256: other texts (see below) plus reserve
|
||||
@ -514,6 +521,7 @@ void cChannel::SetLinkChannels(cLinkChannels *LinkChannels)
|
||||
q += sprintf(q, " none");
|
||||
if (Number())
|
||||
dsyslog("%s", buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
void cChannel::SetRefChannel(cChannel *RefChannel)
|
||||
@ -819,40 +827,52 @@ public:
|
||||
|
||||
// --- cChannels -------------------------------------------------------------
|
||||
|
||||
cChannels Channels;
|
||||
cChannels cChannels::channels;
|
||||
int cChannels::maxNumber = 0;
|
||||
int cChannels::maxChannelNameLength = 0;
|
||||
int cChannels::maxShortChannelNameLength = 0;
|
||||
|
||||
cChannels::cChannels(void)
|
||||
:cConfig<cChannel>("Channels")
|
||||
{
|
||||
maxNumber = 0;
|
||||
maxChannelNameLength = 0;
|
||||
maxShortChannelNameLength = 0;
|
||||
modified = CHANNELSMOD_NONE;
|
||||
modifiedByUser = 0;
|
||||
}
|
||||
|
||||
const cChannels *cChannels::GetChannelsRead(cStateKey &StateKey, int TimeoutMs)
|
||||
{
|
||||
return channels.Lock(StateKey, false, TimeoutMs) ? &channels : NULL;
|
||||
}
|
||||
|
||||
cChannels *cChannels::GetChannelsWrite(cStateKey &StateKey, int TimeoutMs)
|
||||
{
|
||||
return channels.Lock(StateKey, true, TimeoutMs) ? &channels : NULL;
|
||||
}
|
||||
|
||||
void cChannels::DeleteDuplicateChannels(void)
|
||||
{
|
||||
cList<cChannelSorter> ChannelSorter;
|
||||
for (cChannel *channel = First(); channel; channel = Next(channel)) {
|
||||
if (!channel->GroupSep())
|
||||
ChannelSorter.Add(new cChannelSorter(channel));
|
||||
for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
|
||||
if (!Channel->GroupSep())
|
||||
ChannelSorter.Add(new cChannelSorter(Channel));
|
||||
}
|
||||
ChannelSorter.Sort();
|
||||
cChannelSorter *cs = ChannelSorter.First();
|
||||
while (cs) {
|
||||
cChannelSorter *next = ChannelSorter.Next(cs);
|
||||
if (next && cs->channelID == next->channelID) {
|
||||
dsyslog("deleting duplicate channel %s", *next->channel->ToText());
|
||||
Del(next->channel);
|
||||
cChannelSorter *Next = ChannelSorter.Next(cs);
|
||||
if (Next && cs->channelID == Next->channelID) {
|
||||
dsyslog("deleting duplicate channel %s", *Next->channel->ToText());
|
||||
Del(Next->channel);
|
||||
}
|
||||
cs = next;
|
||||
cs = Next;
|
||||
}
|
||||
}
|
||||
|
||||
bool cChannels::Load(const char *FileName, bool AllowComments, bool MustExist)
|
||||
{
|
||||
if (cConfig<cChannel>::Load(FileName, AllowComments, MustExist)) {
|
||||
DeleteDuplicateChannels();
|
||||
ReNumber();
|
||||
LOCK_CHANNELS_WRITE;
|
||||
if (channels.cConfig<cChannel>::Load(FileName, AllowComments, MustExist)) {
|
||||
channels.DeleteDuplicateChannels();
|
||||
channels.ReNumber();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -868,36 +888,36 @@ void cChannels::UnhashChannel(cChannel *Channel)
|
||||
channelsHashSid.Del(Channel, Channel->Sid());
|
||||
}
|
||||
|
||||
int cChannels::GetNextGroup(int Idx)
|
||||
int cChannels::GetNextGroup(int Idx) const
|
||||
{
|
||||
cChannel *channel = Get(++Idx);
|
||||
while (channel && !(channel->GroupSep() && *channel->Name()))
|
||||
channel = Get(++Idx);
|
||||
return channel ? Idx : -1;
|
||||
const cChannel *Channel = Get(++Idx);
|
||||
while (Channel && !(Channel->GroupSep() && *Channel->Name()))
|
||||
Channel = Get(++Idx);
|
||||
return Channel ? Idx : -1;
|
||||
}
|
||||
|
||||
int cChannels::GetPrevGroup(int Idx)
|
||||
int cChannels::GetPrevGroup(int Idx) const
|
||||
{
|
||||
cChannel *channel = Get(--Idx);
|
||||
while (channel && !(channel->GroupSep() && *channel->Name()))
|
||||
channel = Get(--Idx);
|
||||
return channel ? Idx : -1;
|
||||
const cChannel *Channel = Get(--Idx);
|
||||
while (Channel && !(Channel->GroupSep() && *Channel->Name()))
|
||||
Channel = Get(--Idx);
|
||||
return Channel ? Idx : -1;
|
||||
}
|
||||
|
||||
int cChannels::GetNextNormal(int Idx)
|
||||
int cChannels::GetNextNormal(int Idx) const
|
||||
{
|
||||
cChannel *channel = Get(++Idx);
|
||||
while (channel && channel->GroupSep())
|
||||
channel = Get(++Idx);
|
||||
return channel ? Idx : -1;
|
||||
const cChannel *Channel = Get(++Idx);
|
||||
while (Channel && Channel->GroupSep())
|
||||
Channel = Get(++Idx);
|
||||
return Channel ? Idx : -1;
|
||||
}
|
||||
|
||||
int cChannels::GetPrevNormal(int Idx)
|
||||
int cChannels::GetPrevNormal(int Idx) const
|
||||
{
|
||||
cChannel *channel = Get(--Idx);
|
||||
while (channel && channel->GroupSep())
|
||||
channel = Get(--Idx);
|
||||
return channel ? Idx : -1;
|
||||
const cChannel *Channel = Get(--Idx);
|
||||
while (Channel && Channel->GroupSep())
|
||||
Channel = Get(--Idx);
|
||||
return Channel ? Idx : -1;
|
||||
}
|
||||
|
||||
void cChannels::ReNumber(void)
|
||||
@ -905,110 +925,120 @@ void cChannels::ReNumber(void)
|
||||
channelsHashSid.Clear();
|
||||
maxNumber = 0;
|
||||
int Number = 1;
|
||||
for (cChannel *channel = First(); channel; channel = Next(channel)) {
|
||||
if (channel->GroupSep()) {
|
||||
if (channel->Number() > Number)
|
||||
Number = channel->Number();
|
||||
for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
|
||||
if (Channel->GroupSep()) {
|
||||
if (Channel->Number() > Number)
|
||||
Number = Channel->Number();
|
||||
}
|
||||
else {
|
||||
HashChannel(channel);
|
||||
HashChannel(Channel);
|
||||
maxNumber = Number;
|
||||
channel->SetNumber(Number++);
|
||||
Channel->SetNumber(Number++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cChannel *cChannels::GetByNumber(int Number, int SkipGap)
|
||||
void cChannels::Del(cChannel *Channel)
|
||||
{
|
||||
cChannel *previous = NULL;
|
||||
for (cChannel *channel = First(); channel; channel = Next(channel)) {
|
||||
if (!channel->GroupSep()) {
|
||||
if (channel->Number() == Number)
|
||||
return channel;
|
||||
else if (SkipGap && channel->Number() > Number)
|
||||
return SkipGap > 0 ? channel : previous;
|
||||
previous = channel;
|
||||
UnhashChannel(Channel);
|
||||
for (cChannel *ch = First(); ch; ch = Next(ch))
|
||||
ch->DelLinkChannel(Channel);
|
||||
cList<cChannel>::Del(Channel);
|
||||
}
|
||||
|
||||
const cChannel *cChannels::GetByNumber(int Number, int SkipGap) const
|
||||
{
|
||||
const cChannel *Previous = NULL;
|
||||
for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
|
||||
if (!Channel->GroupSep()) {
|
||||
if (Channel->Number() == Number)
|
||||
return Channel;
|
||||
else if (SkipGap && Channel->Number() > Number)
|
||||
return SkipGap > 0 ? Channel : Previous;
|
||||
Previous = Channel;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cChannel *cChannels::GetByServiceID(int Source, int Transponder, unsigned short ServiceID)
|
||||
const cChannel *cChannels::GetByServiceID(int Source, int Transponder, unsigned short ServiceID) const
|
||||
{
|
||||
cList<cHashObject> *list = channelsHashSid.GetList(ServiceID);
|
||||
if (list) {
|
||||
for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
|
||||
cChannel *channel = (cChannel *)hobj->Object();
|
||||
if (channel->Sid() == ServiceID && channel->Source() == Source && ISTRANSPONDER(channel->Transponder(), Transponder))
|
||||
return channel;
|
||||
cChannel *Channel = (cChannel *)hobj->Object();
|
||||
if (Channel->Sid() == ServiceID && Channel->Source() == Source && ISTRANSPONDER(Channel->Transponder(), Transponder))
|
||||
return Channel;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cChannel *cChannels::GetByChannelID(tChannelID ChannelID, bool TryWithoutRid, bool TryWithoutPolarization)
|
||||
const cChannel *cChannels::GetByChannelID(tChannelID ChannelID, bool TryWithoutRid, bool TryWithoutPolarization) const
|
||||
{
|
||||
int sid = ChannelID.Sid();
|
||||
cList<cHashObject> *list = channelsHashSid.GetList(sid);
|
||||
if (list) {
|
||||
for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
|
||||
cChannel *channel = (cChannel *)hobj->Object();
|
||||
if (channel->Sid() == sid && channel->GetChannelID() == ChannelID)
|
||||
return channel;
|
||||
cChannel *Channel = (cChannel *)hobj->Object();
|
||||
if (Channel->Sid() == sid && Channel->GetChannelID() == ChannelID)
|
||||
return Channel;
|
||||
}
|
||||
if (TryWithoutRid) {
|
||||
ChannelID.ClrRid();
|
||||
for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
|
||||
cChannel *channel = (cChannel *)hobj->Object();
|
||||
if (channel->Sid() == sid && channel->GetChannelID().ClrRid() == ChannelID)
|
||||
return channel;
|
||||
cChannel *Channel = (cChannel *)hobj->Object();
|
||||
if (Channel->Sid() == sid && Channel->GetChannelID().ClrRid() == ChannelID)
|
||||
return Channel;
|
||||
}
|
||||
}
|
||||
if (TryWithoutPolarization) {
|
||||
ChannelID.ClrPolarization();
|
||||
for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
|
||||
cChannel *channel = (cChannel *)hobj->Object();
|
||||
if (channel->Sid() == sid && channel->GetChannelID().ClrPolarization() == ChannelID)
|
||||
return channel;
|
||||
cChannel *Channel = (cChannel *)hobj->Object();
|
||||
if (Channel->Sid() == sid && Channel->GetChannelID().ClrPolarization() == ChannelID)
|
||||
return Channel;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
cChannel *cChannels::GetByTransponderID(tChannelID ChannelID)
|
||||
|
||||
const cChannel *cChannels::GetByTransponderID(tChannelID ChannelID) const
|
||||
{
|
||||
int source = ChannelID.Source();
|
||||
int nid = ChannelID.Nid();
|
||||
int tid = ChannelID.Tid();
|
||||
for (cChannel *channel = First(); channel; channel = Next(channel)) {
|
||||
if (channel->Tid() == tid && channel->Nid() == nid && channel->Source() == source)
|
||||
return channel;
|
||||
for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
|
||||
if (Channel->Tid() == tid && Channel->Nid() == nid && Channel->Source() == source)
|
||||
return Channel;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool cChannels::HasUniqueChannelID(cChannel *NewChannel, cChannel *OldChannel)
|
||||
bool cChannels::HasUniqueChannelID(const cChannel *NewChannel, const cChannel *OldChannel) const
|
||||
{
|
||||
tChannelID NewChannelID = NewChannel->GetChannelID();
|
||||
for (cChannel *channel = First(); channel; channel = Next(channel)) {
|
||||
if (!channel->GroupSep() && channel != OldChannel && channel->GetChannelID() == NewChannelID)
|
||||
for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
|
||||
if (!Channel->GroupSep() && Channel != OldChannel && Channel->GetChannelID() == NewChannelID)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cChannels::SwitchTo(int Number)
|
||||
bool cChannels::SwitchTo(int Number) const
|
||||
{
|
||||
cChannel *channel = GetByNumber(Number);
|
||||
return channel && cDevice::PrimaryDevice()->SwitchChannel(channel, true);
|
||||
const cChannel *Channel = GetByNumber(Number);
|
||||
return Channel && cDevice::PrimaryDevice()->SwitchChannel(Channel, true);
|
||||
}
|
||||
|
||||
int cChannels::MaxChannelNameLength(void)
|
||||
{
|
||||
if (!maxChannelNameLength) {
|
||||
for (cChannel *channel = First(); channel; channel = Next(channel)) {
|
||||
if (!channel->GroupSep())
|
||||
maxChannelNameLength = max(Utf8StrLen(channel->Name()), maxChannelNameLength);
|
||||
LOCK_CHANNELS_READ;
|
||||
for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||
if (!Channel->GroupSep())
|
||||
maxChannelNameLength = max(Utf8StrLen(Channel->Name()), maxChannelNameLength);
|
||||
}
|
||||
}
|
||||
return maxChannelNameLength;
|
||||
@ -1017,24 +1047,25 @@ int cChannels::MaxChannelNameLength(void)
|
||||
int cChannels::MaxShortChannelNameLength(void)
|
||||
{
|
||||
if (!maxShortChannelNameLength) {
|
||||
for (cChannel *channel = First(); channel; channel = Next(channel)) {
|
||||
if (!channel->GroupSep())
|
||||
maxShortChannelNameLength = max(Utf8StrLen(channel->ShortName(true)), maxShortChannelNameLength);
|
||||
LOCK_CHANNELS_READ;
|
||||
for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||
if (!Channel->GroupSep())
|
||||
maxShortChannelNameLength = max(Utf8StrLen(Channel->ShortName(true)), maxShortChannelNameLength);
|
||||
}
|
||||
}
|
||||
return maxShortChannelNameLength;
|
||||
}
|
||||
|
||||
void cChannels::SetModified(bool ByUser)
|
||||
void cChannels::SetModifiedByUser(void)
|
||||
{
|
||||
modified = ByUser ? CHANNELSMOD_USER : !modified ? CHANNELSMOD_AUTO : modified;
|
||||
modifiedByUser++;
|
||||
maxChannelNameLength = maxShortChannelNameLength = 0;
|
||||
}
|
||||
|
||||
int cChannels::Modified(void)
|
||||
bool cChannels::ModifiedByUser(int &State) const
|
||||
{
|
||||
int Result = modified;
|
||||
modified = CHANNELSMOD_NONE;
|
||||
int Result = State != modifiedByUser;
|
||||
State = modifiedByUser;
|
||||
return Result;
|
||||
}
|
||||
|
||||
@ -1044,7 +1075,7 @@ cChannel *cChannels::NewChannel(const cChannel *Transponder, const char *Name, c
|
||||
dsyslog("creating new channel '%s,%s;%s' on %s transponder %d with id %d-%d-%d-%d", Name, ShortName, Provider, *cSource::ToString(Transponder->Source()), Transponder->Transponder(), Nid, Tid, Sid, Rid);
|
||||
cChannel *NewChannel = new cChannel;
|
||||
NewChannel->CopyTransponderData(Transponder);
|
||||
NewChannel->SetId(Nid, Tid, Sid, Rid);
|
||||
NewChannel->SetId(this, Nid, Tid, Sid, Rid);
|
||||
NewChannel->SetName(Name, ShortName, Provider);
|
||||
NewChannel->SetSeen();
|
||||
Add(NewChannel);
|
||||
@ -1057,17 +1088,19 @@ cChannel *cChannels::NewChannel(const cChannel *Transponder, const char *Name, c
|
||||
#define CHANNELMARKOBSOLETE "OBSOLETE"
|
||||
#define CHANNELTIMEOBSOLETE 3600 // seconds to wait before declaring a channel obsolete (in case it has actually been seen before)
|
||||
|
||||
void cChannels::MarkObsoleteChannels(int Source, int Nid, int Tid)
|
||||
bool cChannels::MarkObsoleteChannels(int Source, int Nid, int Tid)
|
||||
{
|
||||
for (cChannel *channel = First(); channel; channel = Next(channel)) {
|
||||
if (time(NULL) - channel->Seen() > CHANNELTIMEOBSOLETE && channel->Source() == Source && channel->Nid() == Nid && channel->Tid() == Tid && channel->Rid() == 0) {
|
||||
bool ChannelsModified = false;
|
||||
for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
|
||||
if (time(NULL) - Channel->Seen() > CHANNELTIMEOBSOLETE && Channel->Source() == Source && Channel->Nid() == Nid && Channel->Tid() == Tid && Channel->Rid() == 0) {
|
||||
bool OldShowChannelNamesWithSource = Setup.ShowChannelNamesWithSource;
|
||||
Setup.ShowChannelNamesWithSource = false;
|
||||
if (!endswith(channel->Name(), CHANNELMARKOBSOLETE))
|
||||
channel->SetName(cString::sprintf("%s %s", channel->Name(), CHANNELMARKOBSOLETE), channel->ShortName(), cString::sprintf("%s %s", CHANNELMARKOBSOLETE, channel->Provider()));
|
||||
if (!endswith(Channel->Name(), CHANNELMARKOBSOLETE))
|
||||
ChannelsModified |= Channel->SetName(cString::sprintf("%s %s", Channel->Name(), CHANNELMARKOBSOLETE), Channel->ShortName(), cString::sprintf("%s %s", CHANNELMARKOBSOLETE, Channel->Provider()));
|
||||
Setup.ShowChannelNamesWithSource = OldShowChannelNamesWithSource;
|
||||
}
|
||||
}
|
||||
return ChannelsModified;
|
||||
}
|
||||
|
||||
cString ChannelString(const cChannel *Channel, int Number)
|
||||
|
115
channels.h
115
channels.h
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: channels.h 4.1 2015/03/13 11:20:50 kls Exp $
|
||||
* $Id: channels.h 4.2 2015/08/17 09:39:48 kls Exp $
|
||||
*/
|
||||
|
||||
#ifndef __CHANNELS_H
|
||||
@ -28,10 +28,6 @@
|
||||
#define CHANNELMOD_LANGS 0x40
|
||||
#define CHANNELMOD_RETUNE (CHANNELMOD_PIDS | CHANNELMOD_CA | CHANNELMOD_TRANSP)
|
||||
|
||||
#define CHANNELSMOD_NONE 0
|
||||
#define CHANNELSMOD_AUTO 1
|
||||
#define CHANNELSMOD_USER 2
|
||||
|
||||
#define MAXAPIDS 32 // audio
|
||||
#define MAXDPIDS 16 // dolby (AC3 + DTS)
|
||||
#define MAXSPIDS 32 // subtitles
|
||||
@ -86,6 +82,7 @@ class cLinkChannels : public cList<cLinkChannel> {
|
||||
};
|
||||
|
||||
class cSchedule;
|
||||
class cChannels;
|
||||
|
||||
class cChannel : public cListObject {
|
||||
friend class cSchedules;
|
||||
@ -128,7 +125,7 @@ private:
|
||||
mutable cString nameSource;
|
||||
mutable cString shortNameSource;
|
||||
cString parameters;
|
||||
int modification;
|
||||
mutable int modification;
|
||||
time_t seen; // When this channel was last seen in the SDT of its transponder
|
||||
mutable const cSchedule *schedule;
|
||||
cLinkChannels *linkChannels;
|
||||
@ -188,66 +185,84 @@ public:
|
||||
bool IsTerr(void) const { return cSource::IsTerr(source); }
|
||||
bool IsSourceType(char Source) const { return cSource::IsType(source, Source); }
|
||||
tChannelID GetChannelID(void) const { return tChannelID(source, nid, (nid || tid) ? tid : Transponder(), sid, rid); }
|
||||
bool HasTimer(void) const;
|
||||
int Modification(int Mask = CHANNELMOD_ALL);
|
||||
time_t Seen(void) { return seen; }
|
||||
int Modification(int Mask = CHANNELMOD_ALL) const;
|
||||
time_t Seen(void) const { return seen; }
|
||||
void CopyTransponderData(const cChannel *Channel);
|
||||
bool SetTransponderData(int Source, int Frequency, int Srate, const char *Parameters, bool Quiet = false);
|
||||
void SetSource(int Source);
|
||||
void SetId(int Nid, int Tid, int Sid, int Rid = 0);
|
||||
void SetLcn(int Lcn);
|
||||
void SetName(const char *Name, const char *ShortName, const char *Provider);
|
||||
void SetPortalName(const char *PortalName);
|
||||
void SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid);
|
||||
void SetCaIds(const int *CaIds); // list must be zero-terminated
|
||||
void SetCaDescriptors(int Level);
|
||||
void SetLinkChannels(cLinkChannels *LinkChannels);
|
||||
bool SetSource(int Source);
|
||||
bool SetId(cChannels *Channels, int Nid, int Tid, int Sid, int Rid = 0);
|
||||
bool SetLcn(int Lcn);
|
||||
bool SetName(const char *Name, const char *ShortName, const char *Provider);
|
||||
bool SetPortalName(const char *PortalName);
|
||||
bool SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid);
|
||||
bool SetCaIds(const int *CaIds); // list must be zero-terminated
|
||||
bool SetCaDescriptors(int Level);
|
||||
bool SetLinkChannels(cLinkChannels *LinkChannels);
|
||||
void SetRefChannel(cChannel *RefChannel);
|
||||
void SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds);
|
||||
bool SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds);
|
||||
void SetSeen(void);
|
||||
void DelLinkChannel(cChannel *LinkChannel);
|
||||
};
|
||||
|
||||
class cChannels : public cRwLock, public cConfig<cChannel> {
|
||||
class cChannels : public cConfig<cChannel> {
|
||||
private:
|
||||
int maxNumber;
|
||||
int maxChannelNameLength;
|
||||
int maxShortChannelNameLength;
|
||||
int modified;
|
||||
int beingEdited;
|
||||
static cChannels channels;
|
||||
static int maxNumber;
|
||||
static int maxChannelNameLength;
|
||||
static int maxShortChannelNameLength;
|
||||
int modifiedByUser;
|
||||
cHash<cChannel> channelsHashSid;
|
||||
void DeleteDuplicateChannels(void);
|
||||
public:
|
||||
cChannels(void);
|
||||
bool Load(const char *FileName, bool AllowComments = false, bool MustExist = false);
|
||||
static const cChannels *GetChannelsRead(cStateKey &StateKey, int TimeoutMs = 0);
|
||||
///< Gets the list of channels for read access.
|
||||
///< See cTimers::GetTimersRead() for details.
|
||||
static cChannels *GetChannelsWrite(cStateKey &StateKey, int TimeoutMs = 0);
|
||||
///< Gets the list of channels for write access.
|
||||
///< See cTimers::GetTimersWrite() for details.
|
||||
static bool Load(const char *FileName, bool AllowComments = false, bool MustExist = false);
|
||||
void HashChannel(cChannel *Channel);
|
||||
void UnhashChannel(cChannel *Channel);
|
||||
int GetNextGroup(int Idx); // Get next channel group
|
||||
int GetPrevGroup(int Idx); // Get previous channel group
|
||||
int GetNextNormal(int Idx); // Get next normal channel (not group)
|
||||
int GetPrevNormal(int Idx); // Get previous normal channel (not group)
|
||||
void ReNumber(void); // Recalculate 'number' based on channel type
|
||||
cChannel *GetByNumber(int Number, int SkipGap = 0);
|
||||
cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID);
|
||||
cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false);
|
||||
cChannel *GetByTransponderID(tChannelID ChannelID);
|
||||
int BeingEdited(void) { return beingEdited; }
|
||||
void IncBeingEdited(void) { beingEdited++; }
|
||||
void DecBeingEdited(void) { beingEdited--; }
|
||||
bool HasUniqueChannelID(cChannel *NewChannel, cChannel *OldChannel = NULL);
|
||||
bool SwitchTo(int Number);
|
||||
int MaxNumber(void) { return maxNumber; }
|
||||
int MaxChannelNameLength(void);
|
||||
int MaxShortChannelNameLength(void);
|
||||
void SetModified(bool ByUser = false);
|
||||
int Modified(void);
|
||||
///< Returns 0 if no channels have been modified, 1 if an automatic
|
||||
///< modification has been made, and 2 if the user has made a modification.
|
||||
///< Calling this function resets the 'modified' flag to 0.
|
||||
int GetNextGroup(int Idx) const; ///< Get next channel group
|
||||
int GetPrevGroup(int Idx) const; ///< Get previous channel group
|
||||
int GetNextNormal(int Idx) const; ///< Get next normal channel (not group)
|
||||
int GetPrevNormal(int Idx) const; ///< Get previous normal channel (not group)
|
||||
void ReNumber(void); ///< Recalculate 'number' based on channel type
|
||||
void Del(cChannel *Channel); ///< Delete the given Channel from the list
|
||||
const cChannel *GetByNumber(int Number, int SkipGap = 0) const;
|
||||
cChannel *GetByNumber(int Number, int SkipGap = 0) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByNumber(Number, SkipGap)); }
|
||||
const cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID) const;
|
||||
cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByServiceID(Source, Transponder, ServiceID)); }
|
||||
const cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false) const;
|
||||
cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByChannelID(ChannelID, TryWithoutRid, TryWithoutPolarization)); }
|
||||
const cChannel *GetByTransponderID(tChannelID ChannelID) const;
|
||||
cChannel *GetByTransponderID(tChannelID ChannelID) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByTransponderID(ChannelID)); }
|
||||
bool HasUniqueChannelID(const cChannel *NewChannel, const cChannel *OldChannel = NULL) const;
|
||||
bool SwitchTo(int Number) const;
|
||||
static int MaxNumber(void) { return maxNumber; }
|
||||
static int MaxChannelNameLength(void);
|
||||
static int MaxShortChannelNameLength(void);
|
||||
void SetModifiedByUser(void);
|
||||
bool ModifiedByUser(int &State) const;
|
||||
///< Returns true if the channels have been modified by the user since the last call
|
||||
///< to this function with the same State variable. State must be initialized with 0
|
||||
///< and will be set to the current value of the list's internal state variable upon
|
||||
///< return from this function.
|
||||
cChannel *NewChannel(const cChannel *Transponder, const char *Name, const char *ShortName, const char *Provider, int Nid, int Tid, int Sid, int Rid = 0);
|
||||
void MarkObsoleteChannels(int Source, int Nid, int Tid);
|
||||
bool MarkObsoleteChannels(int Source, int Nid, int Tid);
|
||||
};
|
||||
|
||||
extern cChannels Channels;
|
||||
// Provide lock controlled access to the list:
|
||||
|
||||
DEF_LIST_LOCK(Channels);
|
||||
|
||||
// These macros provide a convenient way of locking the global channels list
|
||||
// and making sure the lock is released as soon as the current scope is left
|
||||
// (note that these macros wait forever to obtain the lock!):
|
||||
|
||||
#define LOCK_CHANNELS_READ USE_LIST_LOCK_READ(Channels)
|
||||
#define LOCK_CHANNELS_WRITE USE_LIST_LOCK_WRITE(Channels)
|
||||
|
||||
cString ChannelString(const cChannel *Channel, int Number);
|
||||
|
||||
|
5
ci.c
5
ci.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: ci.c 3.19 2015/02/02 14:04:10 kls Exp $
|
||||
* $Id: ci.c 4.1 2015/07/18 09:57:42 kls Exp $
|
||||
*/
|
||||
|
||||
#include "ci.h"
|
||||
@ -1921,7 +1921,8 @@ void cCamSlot::StartActivation(void)
|
||||
cMutexLock MutexLock(&mutex);
|
||||
if (!caActivationReceiver) {
|
||||
if (cDevice *d = Device()) {
|
||||
if (cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel())) {
|
||||
LOCK_CHANNELS_READ;
|
||||
if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel())) {
|
||||
caActivationReceiver = new cCaActivationReceiver(Channel, this);
|
||||
d->AttachReceiver(caActivationReceiver);
|
||||
dsyslog("CAM %d: activating on device %d with channel %d (%s)", SlotNumber(), d->DeviceNumber() + 1, Channel->Number(), Channel->Name());
|
||||
|
6
config.h
6
config.h
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: config.h 4.2 2015/04/18 13:13:15 kls Exp $
|
||||
* $Id: config.h 4.3 2015/08/09 09:17:46 kls Exp $
|
||||
*/
|
||||
|
||||
#ifndef __CONFIG_H
|
||||
@ -110,7 +110,7 @@ private:
|
||||
cList<T>::Clear();
|
||||
}
|
||||
public:
|
||||
cConfig(void) { fileName = NULL; }
|
||||
cConfig(const char *NeedsLocking = NULL): cList<T>(NeedsLocking) { fileName = NULL; }
|
||||
virtual ~cConfig() { free(fileName); }
|
||||
const char *FileName(void) { return fileName; }
|
||||
bool Load(const char *FileName = NULL, bool AllowComments = false, bool MustExist = false)
|
||||
@ -160,7 +160,7 @@ public:
|
||||
fprintf(stderr, "vdr: error while reading '%s'\n", fileName);
|
||||
return result;
|
||||
}
|
||||
bool Save(void)
|
||||
bool Save(void) const
|
||||
{
|
||||
bool result = true;
|
||||
T *l = (T *)this->First();
|
||||
|
9
cutter.c
9
cutter.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: cutter.c 4.1 2015/04/11 12:03:25 kls Exp $
|
||||
* $Id: cutter.c 4.2 2015/08/09 12:24:28 kls Exp $
|
||||
*/
|
||||
|
||||
#include "cutter.h"
|
||||
@ -634,7 +634,6 @@ void cCuttingThread::Action(void)
|
||||
}
|
||||
}
|
||||
}
|
||||
Recordings.TouchUpdate();
|
||||
}
|
||||
else
|
||||
esyslog("no editing marks found!");
|
||||
@ -678,7 +677,8 @@ bool cCutter::Start(void)
|
||||
cRecordingUserCommand::InvokeCommand(RUC_EDITINGRECORDING, editedVersionName, originalVersionName);
|
||||
if (cVideoDirectory::RemoveVideoFile(editedVersionName) && MakeDirs(editedVersionName, true)) {
|
||||
Recording.WriteInfo(editedVersionName);
|
||||
Recordings.AddByName(editedVersionName, false);
|
||||
LOCK_RECORDINGS_WRITE;
|
||||
Recordings->AddByName(editedVersionName, false);
|
||||
cuttingThread = new cCuttingThread(originalVersionName, editedVersionName);
|
||||
return true;
|
||||
}
|
||||
@ -703,7 +703,8 @@ void cCutter::Stop(void)
|
||||
if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), editedVersionName) == 0)
|
||||
cControl::Shutdown();
|
||||
cVideoDirectory::RemoveVideoFile(editedVersionName);
|
||||
Recordings.DelByName(editedVersionName);
|
||||
LOCK_RECORDINGS_WRITE;
|
||||
Recordings->DelByName(editedVersionName);
|
||||
}
|
||||
}
|
||||
|
||||
|
24
device.c
24
device.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: device.c 3.20 2015/01/30 12:11:30 kls Exp $
|
||||
* $Id: device.c 4.1 2015/08/29 12:41:08 kls Exp $
|
||||
*/
|
||||
|
||||
#include "device.h"
|
||||
@ -722,20 +722,21 @@ bool cDevice::SwitchChannel(int Direction)
|
||||
cControl::Shutdown(); // prevents old channel from being shown too long if GetDevice() takes longer
|
||||
int n = CurrentChannel() + Direction;
|
||||
int first = n;
|
||||
cChannel *channel;
|
||||
while ((channel = Channels.GetByNumber(n, Direction)) != NULL) {
|
||||
LOCK_CHANNELS_READ;
|
||||
const cChannel *Channel;
|
||||
while ((Channel = Channels->GetByNumber(n, Direction)) != NULL) {
|
||||
// try only channels which are currently available
|
||||
if (GetDevice(channel, LIVEPRIORITY, true, true))
|
||||
if (GetDevice(Channel, LIVEPRIORITY, true, true))
|
||||
break;
|
||||
n = channel->Number() + Direction;
|
||||
n = Channel->Number() + Direction;
|
||||
}
|
||||
if (channel) {
|
||||
if (Channel) {
|
||||
int d = n - first;
|
||||
if (abs(d) == 1)
|
||||
dsyslog("skipped channel %d", first);
|
||||
else if (d)
|
||||
dsyslog("skipped channels %d..%d", first, n - sgn(d));
|
||||
if (PrimaryDevice()->SwitchChannel(channel, true))
|
||||
if (PrimaryDevice()->SwitchChannel(Channel, true))
|
||||
result = true;
|
||||
}
|
||||
else if (n != first)
|
||||
@ -777,7 +778,6 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
|
||||
Result = scrNotAvailable;
|
||||
}
|
||||
else {
|
||||
Channels.Lock(false);
|
||||
// Stop section handling:
|
||||
if (sectionHandler) {
|
||||
sectionHandler->SetStatus(false);
|
||||
@ -790,7 +790,8 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
|
||||
if (SetChannelDevice(Channel, LiveView)) {
|
||||
// Start section handling:
|
||||
if (sectionHandler) {
|
||||
patFilter->Trigger(Channel->Sid());
|
||||
if (patFilter)
|
||||
patFilter->Trigger(Channel->Sid());
|
||||
sectionHandler->SetChannel(Channel);
|
||||
sectionHandler->SetStatus(true);
|
||||
}
|
||||
@ -800,7 +801,6 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
|
||||
}
|
||||
else
|
||||
Result = scrFailed;
|
||||
Channels.Unlock();
|
||||
}
|
||||
|
||||
if (Result == scrOk) {
|
||||
@ -829,8 +829,8 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
|
||||
void cDevice::ForceTransferMode(void)
|
||||
{
|
||||
if (!cTransferControl::ReceiverDevice()) {
|
||||
cChannel *Channel = Channels.GetByNumber(CurrentChannel());
|
||||
if (Channel)
|
||||
LOCK_CHANNELS_READ;
|
||||
if (const cChannel *Channel = Channels->GetByNumber(CurrentChannel()))
|
||||
SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode
|
||||
}
|
||||
}
|
||||
|
27
dvbplayer.c
27
dvbplayer.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: dvbplayer.c 3.6 2015/02/13 15:12:57 kls Exp $
|
||||
* $Id: dvbplayer.c 4.1 2015/08/06 13:09:19 kls Exp $
|
||||
*/
|
||||
|
||||
#include "dvbplayer.h"
|
||||
@ -210,7 +210,7 @@ private:
|
||||
cNonBlockingFileReader *nonBlockingFileReader;
|
||||
cRingBufferFrame *ringBuffer;
|
||||
cPtsIndex ptsIndex;
|
||||
cMarks *marks;
|
||||
const cMarks *marks;
|
||||
cFileName *fileName;
|
||||
cIndexFile *index;
|
||||
cUnbufferedFile *replayFile;
|
||||
@ -239,7 +239,7 @@ protected:
|
||||
public:
|
||||
cDvbPlayer(const char *FileName, bool PauseLive);
|
||||
virtual ~cDvbPlayer();
|
||||
void SetMarks(cMarks *Marks);
|
||||
void SetMarks(const cMarks *Marks);
|
||||
bool Active(void) { return cThread::Running(); }
|
||||
void Pause(void);
|
||||
void Play(void);
|
||||
@ -311,7 +311,7 @@ cDvbPlayer::~cDvbPlayer()
|
||||
// don't delete marks here, we don't own them!
|
||||
}
|
||||
|
||||
void cDvbPlayer::SetMarks(cMarks *Marks)
|
||||
void cDvbPlayer::SetMarks(const cMarks *Marks)
|
||||
{
|
||||
marks = Marks;
|
||||
}
|
||||
@ -383,10 +383,11 @@ bool cDvbPlayer::Save(void)
|
||||
int Index = ptsIndex.FindIndex(DeviceGetSTC());
|
||||
if (Index >= 0) {
|
||||
if (Setup.SkipEdited && marks) {
|
||||
marks->Lock();
|
||||
cStateKey StateKey;
|
||||
marks->Lock(StateKey);
|
||||
if (marks->First() && abs(Index - marks->First()->Position()) <= int(round(RESUMEBACKUP * framesPerSecond)))
|
||||
Index = 0; // when stopping within RESUMEBACKUP seconds of the first mark the recording shall still be considered unviewed
|
||||
marks->Unlock();
|
||||
StateKey.Remove();
|
||||
}
|
||||
Index -= int(round(RESUMEBACKUP * framesPerSecond));
|
||||
if (Index > 0)
|
||||
@ -419,7 +420,8 @@ void cDvbPlayer::Action(void)
|
||||
if (readIndex > 0)
|
||||
isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond));
|
||||
else if (Setup.SkipEdited && marks) {
|
||||
marks->Lock();
|
||||
cStateKey StateKey;
|
||||
marks->Lock(StateKey);
|
||||
if (marks->First() && index) {
|
||||
int Index = marks->First()->Position();
|
||||
uint16_t FileNumber;
|
||||
@ -429,7 +431,7 @@ void cDvbPlayer::Action(void)
|
||||
readIndex = Index;
|
||||
}
|
||||
}
|
||||
marks->Unlock();
|
||||
StateKey.Remove();
|
||||
}
|
||||
|
||||
nonBlockingFileReader = new cNonBlockingFileReader;
|
||||
@ -500,8 +502,9 @@ void cDvbPlayer::Action(void)
|
||||
if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length) && NextFile(FileNumber, FileOffset)) {
|
||||
readIndex++;
|
||||
if ((Setup.SkipEdited || Setup.PauseAtLastMark) && marks) {
|
||||
marks->Lock();
|
||||
cMark *m = marks->Get(readIndex);
|
||||
cStateKey StateKey;
|
||||
marks->Lock(StateKey);
|
||||
const cMark *m = marks->Get(readIndex);
|
||||
if (m && (m->Index() & 0x01) != 0) { // we're at an end mark
|
||||
m = marks->GetNextBegin(m);
|
||||
int Index = -1;
|
||||
@ -519,7 +522,7 @@ void cDvbPlayer::Action(void)
|
||||
CutIn = true;
|
||||
}
|
||||
}
|
||||
marks->Unlock();
|
||||
StateKey.Remove();
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -943,7 +946,7 @@ cDvbPlayerControl::~cDvbPlayerControl()
|
||||
Stop();
|
||||
}
|
||||
|
||||
void cDvbPlayerControl::SetMarks(cMarks *Marks)
|
||||
void cDvbPlayerControl::SetMarks(const cMarks *Marks)
|
||||
{
|
||||
if (player)
|
||||
player->SetMarks(Marks);
|
||||
|
@ -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
119
eit.c
@ -8,7 +8,7 @@
|
||||
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
||||
* Adapted to 'libsi' for VDR 1.3.0 by Marcel Wiesweg <marcel.wiesweg@gmx.de>.
|
||||
*
|
||||
* $Id: eit.c 3.6 2015/02/01 14:55:27 kls Exp $
|
||||
* $Id: eit.c 4.1 2015/08/23 10:43:36 kls Exp $
|
||||
*/
|
||||
|
||||
#include "eit.h"
|
||||
@ -24,31 +24,53 @@
|
||||
|
||||
class cEIT : public SI::EIT {
|
||||
public:
|
||||
cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bool OnlyRunningStatus = false);
|
||||
cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const u_char *Data);
|
||||
};
|
||||
|
||||
cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bool OnlyRunningStatus)
|
||||
cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const u_char *Data)
|
||||
:SI::EIT(Data, false)
|
||||
{
|
||||
if (!CheckCRCAndParse())
|
||||
return;
|
||||
int HashId = Tid * getServiceId();
|
||||
cSectionSyncerEntry *SectionSyncerEntry = SectionSyncerHash.Get(HashId);
|
||||
if (!SectionSyncerEntry) {
|
||||
SectionSyncerEntry = new cSectionSyncerEntry;
|
||||
SectionSyncerHash.Add(SectionSyncerEntry, HashId);
|
||||
}
|
||||
bool Process = SectionSyncerEntry->Sync(getVersionNumber(), getSectionNumber(), getLastSectionNumber());
|
||||
if (Tid != 0x4E && !Process) // we need to set the 'seen' tag to watch the running status of the present/following event
|
||||
return;
|
||||
|
||||
time_t Now = time(NULL);
|
||||
if (Now < VALID_TIME)
|
||||
return; // we need the current time for handling PDC descriptors
|
||||
|
||||
if (!Channels.Lock(false, 10))
|
||||
cStateKey ChannelsStateKey;
|
||||
cChannels *Channels = cChannels::GetChannelsWrite(ChannelsStateKey, 10);
|
||||
if (!Channels) {
|
||||
SectionSyncerEntry->Repeat(); // let's not miss any section of the EIT
|
||||
return;
|
||||
}
|
||||
tChannelID channelID(Source, getOriginalNetworkId(), getTransportStreamId(), getServiceId());
|
||||
cChannel *channel = Channels.GetByChannelID(channelID, true);
|
||||
if (!channel || EpgHandlers.IgnoreChannel(channel)) {
|
||||
Channels.Unlock();
|
||||
cChannel *Channel = Channels->GetByChannelID(channelID, true);
|
||||
if (!Channel || EpgHandlers.IgnoreChannel(Channel)) {
|
||||
ChannelsStateKey.Remove(false);
|
||||
return;
|
||||
}
|
||||
|
||||
EpgHandlers.BeginSegmentTransfer(channel, OnlyRunningStatus);
|
||||
bool handledExternally = EpgHandlers.HandledExternally(channel);
|
||||
cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(channel, true);
|
||||
cStateKey SchedulesStateKey;
|
||||
cSchedules *Schedules = cSchedules::GetSchedulesWrite(SchedulesStateKey, 10);
|
||||
if (!Schedules) {
|
||||
SectionSyncerEntry->Repeat(); // let's not miss any section of the EIT
|
||||
ChannelsStateKey.Remove(false);
|
||||
return;
|
||||
}
|
||||
|
||||
bool ChannelsModified = false;
|
||||
EpgHandlers.BeginSegmentTransfer(Channel);
|
||||
bool handledExternally = EpgHandlers.HandledExternally(Channel);
|
||||
cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(Channel, true);
|
||||
|
||||
bool Empty = true;
|
||||
bool Modified = false;
|
||||
@ -74,8 +96,6 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
|
||||
cEvent *rEvent = NULL;
|
||||
cEvent *pEvent = (cEvent *)pSchedule->GetEvent(SiEitEvent.getEventId(), StartTime);
|
||||
if (!pEvent || handledExternally) {
|
||||
if (OnlyRunningStatus)
|
||||
continue;
|
||||
if (handledExternally && !EpgHandlers.IsUpdate(SiEitEvent.getEventId(), StartTime, Tid, getVersionNumber()))
|
||||
continue;
|
||||
// If we don't have that event yet, we create a new one.
|
||||
@ -94,14 +114,6 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
|
||||
// The lower the table ID, the more "current" the information.
|
||||
if (Tid > TableID)
|
||||
continue;
|
||||
// If the new event comes from the same table and has the same version number
|
||||
// as the existing one, let's skip it to avoid unnecessary work.
|
||||
// Unfortunately some stations (like, e.g. "Premiere") broadcast their EPG data on several transponders (like
|
||||
// the actual Premiere transponder and the Sat.1/Pro7 transponder), but use different version numbers on
|
||||
// each of them :-( So if one DVB card is tuned to the Premiere transponder, while an other one is tuned
|
||||
// to the Sat.1/Pro7 transponder, events will keep toggling because of the bogus version numbers.
|
||||
else if (Tid == TableID && pEvent->Version() == getVersionNumber())
|
||||
continue;
|
||||
EpgHandlers.SetEventID(pEvent, SiEitEvent.getEventId()); // unfortunately some stations use different event ids for the same event in different tables :-(
|
||||
EpgHandlers.SetStartTime(pEvent, StartTime);
|
||||
EpgHandlers.SetDuration(pEvent, Duration);
|
||||
@ -110,11 +122,9 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
|
||||
pEvent->SetTableID(Tid);
|
||||
if (Tid == 0x4E) { // we trust only the present/following info on the actual TS
|
||||
if (SiEitEvent.getRunningStatus() >= SI::RunningStatusNotRunning)
|
||||
pSchedule->SetRunningStatus(pEvent, SiEitEvent.getRunningStatus(), channel);
|
||||
}
|
||||
if (OnlyRunningStatus) {
|
||||
pEvent->SetVersion(0xFF); // we have already changed the table id above, so set the version to an invalid value to make sure the next full run will be executed
|
||||
continue; // do this before setting the version, so that the full update can be done later
|
||||
pSchedule->SetRunningStatus(pEvent, SiEitEvent.getRunningStatus(), Channel);
|
||||
if (!Process)
|
||||
continue;
|
||||
}
|
||||
pEvent->SetVersion(getVersionNumber());
|
||||
|
||||
@ -206,7 +216,7 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
|
||||
break;
|
||||
case SI::TimeShiftedEventDescriptorTag: {
|
||||
SI::TimeShiftedEventDescriptor *tsed = (SI::TimeShiftedEventDescriptor *)d;
|
||||
cSchedule *rSchedule = (cSchedule *)Schedules->GetSchedule(tChannelID(Source, channel->Nid(), channel->Tid(), tsed->getReferenceServiceId()));
|
||||
cSchedule *rSchedule = (cSchedule *)Schedules->GetSchedule(tChannelID(Source, Channel->Nid(), Channel->Tid(), tsed->getReferenceServiceId()));
|
||||
if (!rSchedule)
|
||||
break;
|
||||
rEvent = (cEvent *)rSchedule->GetEvent(tsed->getReferenceEventId());
|
||||
@ -226,18 +236,18 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
|
||||
char linkName[ld->privateData.getLength() + 1];
|
||||
strn0cpy(linkName, (const char *)ld->privateData.getData(), sizeof(linkName));
|
||||
// TODO is there a standard way to determine the character set of this string?
|
||||
cChannel *link = Channels.GetByChannelID(linkID);
|
||||
if (link != channel) { // only link to other channels, not the same one
|
||||
//fprintf(stderr, "Linkage %s %4d %4d %5d %5d %5d %5d %02X '%s'\n", hit ? "*" : "", channel->Number(), link ? link->Number() : -1, SiEitEvent.getEventId(), ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId(), ld->getLinkageType(), linkName);//XXX
|
||||
cChannel *link = Channels->GetByChannelID(linkID);
|
||||
if (link != Channel) { // only link to other channels, not the same one
|
||||
if (link) {
|
||||
if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3)
|
||||
link->SetName(linkName, "", "");
|
||||
ChannelsModified |= link->SetName(linkName, "", "");
|
||||
}
|
||||
else if (Setup.UpdateChannels >= 4) {
|
||||
cChannel *transponder = channel;
|
||||
if (channel->Tid() != ld->getTransportStreamId())
|
||||
transponder = Channels.GetByTransponderID(linkID);
|
||||
link = Channels.NewChannel(transponder, linkName, "", "", ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId());
|
||||
cChannel *Transponder = Channel;
|
||||
if (Channel->Tid() != ld->getTransportStreamId())
|
||||
Transponder = Channels->GetByTransponderID(linkID);
|
||||
link = Channels->NewChannel(Transponder, linkName, "", "", ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId());
|
||||
ChannelsModified = true;
|
||||
//XXX patFilter->Trigger();
|
||||
}
|
||||
if (link) {
|
||||
@ -247,7 +257,7 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
|
||||
}
|
||||
}
|
||||
else
|
||||
channel->SetPortalName(linkName);
|
||||
ChannelsModified |= Channel->SetPortalName(linkName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -293,7 +303,7 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
|
||||
|
||||
EpgHandlers.FixEpgBugs(pEvent);
|
||||
if (LinkChannels)
|
||||
channel->SetLinkChannels(LinkChannels);
|
||||
ChannelsModified |= Channel->SetLinkChannels(LinkChannels);
|
||||
Modified = true;
|
||||
EpgHandlers.HandleEvent(pEvent);
|
||||
if (handledExternally)
|
||||
@ -302,16 +312,17 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
|
||||
if (Tid == 0x4E) {
|
||||
if (Empty && getSectionNumber() == 0)
|
||||
// ETR 211: an empty entry in section 0 of table 0x4E means there is currently no event running
|
||||
pSchedule->ClrRunningStatus(channel);
|
||||
pSchedule->ClrRunningStatus(Channel);
|
||||
pSchedule->SetPresentSeen();
|
||||
}
|
||||
if (Modified && !OnlyRunningStatus) {
|
||||
if (Modified) {
|
||||
EpgHandlers.SortSchedule(pSchedule);
|
||||
EpgHandlers.DropOutdated(pSchedule, SegmentStart, SegmentEnd, Tid, getVersionNumber());
|
||||
Schedules->SetModified(pSchedule);
|
||||
pSchedule->SetModified();
|
||||
}
|
||||
Channels.Unlock();
|
||||
EpgHandlers.EndSegmentTransfer(Modified, OnlyRunningStatus);
|
||||
SchedulesStateKey.Remove(Modified);
|
||||
ChannelsStateKey.Remove(ChannelsModified);
|
||||
EpgHandlers.EndSegmentTransfer(Modified);
|
||||
}
|
||||
|
||||
// --- cTDT ------------------------------------------------------------------
|
||||
@ -372,6 +383,13 @@ cEitFilter::cEitFilter(void)
|
||||
Set(0x14, 0x70); // TDT
|
||||
}
|
||||
|
||||
void cEitFilter::SetStatus(bool On)
|
||||
{
|
||||
cMutexLock MutexLock(&mutex);
|
||||
cFilter::SetStatus(On);
|
||||
sectionSyncerHash.Clear();
|
||||
}
|
||||
|
||||
void cEitFilter::SetDisableUntil(time_t Time)
|
||||
{
|
||||
disableUntil = Time;
|
||||
@ -379,6 +397,7 @@ void cEitFilter::SetDisableUntil(time_t Time)
|
||||
|
||||
void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
|
||||
{
|
||||
cMutexLock MutexLock(&mutex);
|
||||
if (disableUntil) {
|
||||
if (time(NULL) > disableUntil)
|
||||
disableUntil = 0;
|
||||
@ -387,22 +406,8 @@ void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
}
|
||||
switch (Pid) {
|
||||
case 0x12: {
|
||||
if (Tid >= 0x4E && Tid <= 0x6F) {
|
||||
cSchedulesLock SchedulesLock(true, 10);
|
||||
cSchedules *Schedules = (cSchedules *)cSchedules::Schedules(SchedulesLock);
|
||||
if (Schedules)
|
||||
cEIT EIT(Schedules, Source(), Tid, Data);
|
||||
else {
|
||||
// If we don't get a write lock, let's at least get a read lock, so
|
||||
// that we can set the running status and 'seen' timestamp (well, actually
|
||||
// with a read lock we shouldn't be doing that, but it's only integers that
|
||||
// get changed, so it should be ok)
|
||||
cSchedulesLock SchedulesLock;
|
||||
cSchedules *Schedules = (cSchedules *)cSchedules::Schedules(SchedulesLock);
|
||||
if (Schedules)
|
||||
cEIT EIT(Schedules, Source(), Tid, Data, true);
|
||||
}
|
||||
}
|
||||
if (Tid >= 0x4E && Tid <= 0x6F)
|
||||
cEIT EIT(sectionSyncerHash, Source(), Tid, Data);
|
||||
}
|
||||
break;
|
||||
case 0x14: {
|
||||
|
10
eit.h
10
eit.h
@ -4,21 +4,29 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: eit.h 2.1 2010/01/03 15:28:34 kls Exp $
|
||||
* $Id: eit.h 4.1 2015/07/25 11:03:53 kls Exp $
|
||||
*/
|
||||
|
||||
#ifndef __EIT_H
|
||||
#define __EIT_H
|
||||
|
||||
#include "filter.h"
|
||||
#include "tools.h"
|
||||
|
||||
class cSectionSyncerEntry : public cListObject, public cSectionSyncer {};
|
||||
|
||||
class cSectionSyncerHash : public cHash<cSectionSyncerEntry> {};
|
||||
|
||||
class cEitFilter : public cFilter {
|
||||
private:
|
||||
cMutex mutex;
|
||||
cSectionSyncerHash sectionSyncerHash;
|
||||
static time_t disableUntil;
|
||||
protected:
|
||||
virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
|
||||
public:
|
||||
cEitFilter(void);
|
||||
virtual void SetStatus(bool On);
|
||||
static void SetDisableUntil(time_t Time);
|
||||
};
|
||||
|
||||
|
18
eitscan.c
18
eitscan.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: eitscan.c 2.7 2012/04/07 14:39:28 kls Exp $
|
||||
* $Id: eitscan.c 4.1 2015/07/18 10:16:51 kls Exp $
|
||||
*/
|
||||
|
||||
#include "eitscan.h"
|
||||
@ -45,13 +45,13 @@ int cScanData::Compare(const cListObject &ListObject) const
|
||||
|
||||
class cScanList : public cList<cScanData> {
|
||||
public:
|
||||
void AddTransponders(cList<cChannel> *Channels);
|
||||
void AddTransponders(const cList<cChannel> *Channels);
|
||||
void AddTransponder(const cChannel *Channel);
|
||||
};
|
||||
|
||||
void cScanList::AddTransponders(cList<cChannel> *Channels)
|
||||
void cScanList::AddTransponders(const cList<cChannel> *Channels)
|
||||
{
|
||||
for (cChannel *ch = Channels->First(); ch; ch = Channels->Next(ch))
|
||||
for (const cChannel *ch = Channels->First(); ch; ch = Channels->Next(ch))
|
||||
AddTransponder(ch);
|
||||
Sort();
|
||||
}
|
||||
@ -118,7 +118,8 @@ void cEITScanner::ForceScan(void)
|
||||
void cEITScanner::Activity(void)
|
||||
{
|
||||
if (currentChannel) {
|
||||
Channels.SwitchTo(currentChannel);
|
||||
LOCK_CHANNELS_READ;
|
||||
Channels->SwitchTo(currentChannel);
|
||||
currentChannel = 0;
|
||||
}
|
||||
lastActivity = time(NULL);
|
||||
@ -129,7 +130,8 @@ void cEITScanner::Process(void)
|
||||
if (Setup.EPGScanTimeout || !lastActivity) { // !lastActivity means a scan was forced
|
||||
time_t now = time(NULL);
|
||||
if (now - lastScan > ScanTimeout && now - lastActivity > ActivityTimeout) {
|
||||
if (Channels.Lock(false, 10)) {
|
||||
cStateKey StateKey;
|
||||
if (const cChannels *Channels = cChannels::GetChannelsRead(StateKey, 10)) {
|
||||
if (!scanList) {
|
||||
scanList = new cScanList;
|
||||
if (transponderList) {
|
||||
@ -137,7 +139,7 @@ void cEITScanner::Process(void)
|
||||
delete transponderList;
|
||||
transponderList = NULL;
|
||||
}
|
||||
scanList->AddTransponders(&Channels);
|
||||
scanList->AddTransponders(Channels);
|
||||
}
|
||||
bool AnyDeviceSwitched = false;
|
||||
for (int i = 0; i < cDevice::NumDevices(); i++) {
|
||||
@ -177,7 +179,7 @@ void cEITScanner::Process(void)
|
||||
if (lastActivity == 0) // this was a triggered scan
|
||||
Activity();
|
||||
}
|
||||
Channels.Unlock();
|
||||
StateKey.Remove();
|
||||
}
|
||||
lastScan = time(NULL);
|
||||
}
|
||||
|
270
epg.c
270
epg.c
@ -7,7 +7,7 @@
|
||||
* Original version (as used in VDR before 1.3.0) written by
|
||||
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
||||
*
|
||||
* $Id: epg.c 3.3 2013/12/28 11:33:08 kls Exp $
|
||||
* $Id: epg.c 4.1 2015/08/23 10:39:59 kls Exp $
|
||||
*/
|
||||
|
||||
#include "epg.h"
|
||||
@ -15,7 +15,6 @@
|
||||
#include <limits.h>
|
||||
#include <time.h>
|
||||
#include "libsi/si.h"
|
||||
#include "timers.h"
|
||||
|
||||
#define RUNNINGSTATUSTIMEOUT 30 // seconds before the running status is considered unknown
|
||||
#define EPGDATAWRITEDELTA 600 // seconds between writing the epg.data file
|
||||
@ -111,9 +110,12 @@ tComponent *cComponents::GetComponent(int Index, uchar Stream, uchar Type)
|
||||
|
||||
// --- cEvent ----------------------------------------------------------------
|
||||
|
||||
cMutex cEvent::numTimersMutex;
|
||||
|
||||
cEvent::cEvent(tEventID EventID)
|
||||
{
|
||||
schedule = NULL;
|
||||
numTimers = 0;
|
||||
eventID = EventID;
|
||||
tableID = 0xFF; // actual table ids are 0x4E..0x60
|
||||
version = 0xFF; // actual version numbers are 0..31
|
||||
@ -170,9 +172,9 @@ void cEvent::SetVersion(uchar Version)
|
||||
version = Version;
|
||||
}
|
||||
|
||||
void cEvent::SetRunningStatus(int RunningStatus, cChannel *Channel)
|
||||
void cEvent::SetRunningStatus(int RunningStatus, const cChannel *Channel)
|
||||
{
|
||||
if (Channel && runningStatus != RunningStatus && (RunningStatus > SI::RunningStatusNotRunning || runningStatus > SI::RunningStatusUndefined) && Channel->HasTimer())
|
||||
if (Channel && runningStatus != RunningStatus && (RunningStatus > SI::RunningStatusNotRunning || runningStatus > SI::RunningStatusUndefined) && schedule && schedule->HasTimer())
|
||||
isyslog("channel %d (%s) event %s status %d", Channel->Number(), Channel->Name(), *ToDescr(), RunningStatus);
|
||||
runningStatus = RunningStatus;
|
||||
}
|
||||
@ -243,13 +245,22 @@ cString cEvent::ToDescr(void) const
|
||||
return cString::sprintf("%s %s-%s %s'%s'", *GetDateString(), *GetTimeString(), *GetEndTimeString(), vpsbuf, Title());
|
||||
}
|
||||
|
||||
bool cEvent::HasTimer(void) const
|
||||
void cEvent::IncNumTimers(void) const
|
||||
{
|
||||
for (cTimer *t = Timers.First(); t; t = Timers.Next(t)) {
|
||||
if (t->Event() == this)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
numTimersMutex.Lock();
|
||||
numTimers++;
|
||||
if (schedule)
|
||||
schedule->IncNumTimers();
|
||||
numTimersMutex.Unlock();
|
||||
}
|
||||
|
||||
void cEvent::DecNumTimers(void) const
|
||||
{
|
||||
numTimersMutex.Lock();
|
||||
numTimers--;
|
||||
if (schedule)
|
||||
schedule->DecNumTimers();
|
||||
numTimersMutex.Unlock();
|
||||
}
|
||||
|
||||
bool cEvent::IsRunning(bool OrAboutToStart) const
|
||||
@ -605,9 +616,9 @@ void ReportEpgBugFixStats(bool Force)
|
||||
bool PrintedStats = false;
|
||||
char *q = buffer;
|
||||
*buffer = 0;
|
||||
LOCK_CHANNELS_READ;
|
||||
for (int c = 0; c < p->n; c++) {
|
||||
cChannel *channel = Channels.GetByChannelID(p->channelIDs[c], true);
|
||||
if (channel) {
|
||||
if (const cChannel *Channel = Channels->GetByChannelID(p->channelIDs[c], true)) {
|
||||
if (!GotHits) {
|
||||
dsyslog("=====================");
|
||||
dsyslog("EPG bugfix statistics");
|
||||
@ -623,7 +634,7 @@ void ReportEpgBugFixStats(bool Force)
|
||||
q += snprintf(q, sizeof(buffer) - (q - buffer), "%-3d %-4d", i, p->hits);
|
||||
PrintedStats = true;
|
||||
}
|
||||
q += snprintf(q, sizeof(buffer) - (q - buffer), "%s%s", delim, channel->Name());
|
||||
q += snprintf(q, sizeof(buffer) - (q - buffer), "%s%s", delim, Channel->Name());
|
||||
delim = ", ";
|
||||
if (q - buffer > 80) {
|
||||
q += snprintf(q, sizeof(buffer) - (q - buffer), "%s...", delim);
|
||||
@ -878,14 +889,32 @@ Final:
|
||||
|
||||
// --- cSchedule -------------------------------------------------------------
|
||||
|
||||
cMutex cSchedule::numTimersMutex;
|
||||
|
||||
cSchedule::cSchedule(tChannelID ChannelID)
|
||||
{
|
||||
channelID = ChannelID;
|
||||
events.SetUseGarbageCollector();
|
||||
numTimers = 0;
|
||||
hasRunning = false;
|
||||
modified = 0;
|
||||
presentSeen = 0;
|
||||
}
|
||||
|
||||
void cSchedule::IncNumTimers(void) const
|
||||
{
|
||||
numTimersMutex.Lock();
|
||||
numTimers++;
|
||||
numTimersMutex.Unlock();
|
||||
}
|
||||
|
||||
void cSchedule::DecNumTimers(void) const
|
||||
{
|
||||
numTimersMutex.Lock();
|
||||
numTimers--;
|
||||
numTimersMutex.Unlock();
|
||||
}
|
||||
|
||||
cEvent *cSchedule::AddEvent(cEvent *Event)
|
||||
{
|
||||
events.Add(Event);
|
||||
@ -897,8 +926,6 @@ cEvent *cSchedule::AddEvent(cEvent *Event)
|
||||
void cSchedule::DelEvent(cEvent *Event)
|
||||
{
|
||||
if (Event->schedule == this) {
|
||||
if (hasRunning && Event->IsRunning())
|
||||
ClrRunningStatus();
|
||||
UnhashEvent(Event);
|
||||
events.Del(Event);
|
||||
}
|
||||
@ -922,7 +949,7 @@ const cEvent *cSchedule::GetPresentEvent(void) const
|
||||
{
|
||||
const cEvent *pe = NULL;
|
||||
time_t now = time(NULL);
|
||||
for (cEvent *p = events.First(); p; p = events.Next(p)) {
|
||||
for (const cEvent *p = events.First(); p; p = events.Next(p)) {
|
||||
if (p->StartTime() <= now)
|
||||
pe = p;
|
||||
else if (p->StartTime() > now + 3600)
|
||||
@ -962,7 +989,7 @@ const cEvent *cSchedule::GetEventAround(time_t Time) const
|
||||
{
|
||||
const cEvent *pe = NULL;
|
||||
time_t delta = INT_MAX;
|
||||
for (cEvent *p = events.First(); p; p = events.Next(p)) {
|
||||
for (const cEvent *p = events.First(); p; p = events.Next(p)) {
|
||||
time_t dt = Time - p->StartTime();
|
||||
if (dt >= 0 && dt < delta && p->EndTime() >= Time) {
|
||||
delta = dt;
|
||||
@ -972,7 +999,7 @@ const cEvent *cSchedule::GetEventAround(time_t Time) const
|
||||
return pe;
|
||||
}
|
||||
|
||||
void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Channel)
|
||||
void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, const cChannel *Channel)
|
||||
{
|
||||
hasRunning = false;
|
||||
for (cEvent *p = events.First(); p; p = events.Next(p)) {
|
||||
@ -987,6 +1014,7 @@ void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Cha
|
||||
if (p->RunningStatus() >= SI::RunningStatusPausing)
|
||||
hasRunning = true;
|
||||
}
|
||||
SetPresentSeen();
|
||||
}
|
||||
|
||||
void cSchedule::ClrRunningStatus(cChannel *Channel)
|
||||
@ -1019,32 +1047,29 @@ void cSchedule::Sort(void)
|
||||
p->SetRunningStatus(SI::RunningStatusNotRunning);
|
||||
}
|
||||
}
|
||||
SetModified();
|
||||
}
|
||||
|
||||
void cSchedule::DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
|
||||
{
|
||||
if (SegmentStart > 0 && SegmentEnd > 0) {
|
||||
for (cEvent *p = events.First(); p; p = events.Next(p)) {
|
||||
if (p->EndTime() > SegmentStart) {
|
||||
if (p->StartTime() < SegmentEnd) {
|
||||
// The event overlaps with the given time segment.
|
||||
if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) {
|
||||
// The segment overwrites all events from tables with higher ids, and
|
||||
// within the same table id all events must have the same version.
|
||||
// We can't delete the event right here because a timer might have
|
||||
// a pointer to it, so let's set its id and start time to 0 to have it
|
||||
// "phased out":
|
||||
if (hasRunning && p->IsRunning())
|
||||
ClrRunningStatus();
|
||||
UnhashEvent(p);
|
||||
p->eventID = 0;
|
||||
p->startTime = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
cEvent *p = events.First();
|
||||
while (p) {
|
||||
cEvent *n = events.Next(p);
|
||||
if (p->EndTime() > SegmentStart) {
|
||||
if (p->StartTime() < SegmentEnd) {
|
||||
// The event overlaps with the given time segment.
|
||||
if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) {
|
||||
// The segment overwrites all events from tables with higher ids, and
|
||||
// within the same table id all events must have the same version.
|
||||
DelEvent(p);
|
||||
}
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
p = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1066,9 +1091,9 @@ void cSchedule::Cleanup(time_t Time)
|
||||
|
||||
void cSchedule::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) const
|
||||
{
|
||||
cChannel *channel = Channels.GetByChannelID(channelID, true);
|
||||
if (channel) {
|
||||
fprintf(f, "%sC %s %s\n", Prefix, *channel->GetChannelID().ToString(), channel->Name());
|
||||
LOCK_CHANNELS_READ;
|
||||
if (const cChannel *Channel = Channels->GetByChannelID(channelID, true)) {
|
||||
fprintf(f, "%sC %s %s\n", Prefix, *Channel->GetChannelID().ToString(), Channel->Name());
|
||||
const cEvent *p;
|
||||
switch (DumpMode) {
|
||||
case dmAll: {
|
||||
@ -1111,12 +1136,10 @@ bool cSchedule::Read(FILE *f, cSchedules *Schedules)
|
||||
if (*s) {
|
||||
tChannelID channelID = tChannelID::FromString(s);
|
||||
if (channelID.Valid()) {
|
||||
cSchedule *p = Schedules->AddSchedule(channelID);
|
||||
if (p) {
|
||||
if (cSchedule *p = Schedules->AddSchedule(channelID)) {
|
||||
if (!cEvent::Read(f, p))
|
||||
return false;
|
||||
p->Sort();
|
||||
Schedules->SetModified(p);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -1164,12 +1187,12 @@ void cEpgDataWriter::Perform(void)
|
||||
{
|
||||
cMutexLock MutexLock(&mutex); // to make sure fore- and background calls don't cause parellel dumps!
|
||||
{
|
||||
cSchedulesLock SchedulesLock(true, 1000);
|
||||
cSchedules *s = (cSchedules *)cSchedules::Schedules(SchedulesLock);
|
||||
if (s) {
|
||||
cStateKey StateKey;
|
||||
if (cSchedules *Schedules = cSchedules::GetSchedulesWrite(StateKey, 1000)) {
|
||||
time_t now = time(NULL);
|
||||
for (cSchedule *p = s->First(); p; p = s->Next(p))
|
||||
for (cSchedule *p = Schedules->First(); p; p = Schedules->Next(p))
|
||||
p->Cleanup(now);
|
||||
StateKey.Remove();
|
||||
}
|
||||
}
|
||||
if (dump)
|
||||
@ -1178,29 +1201,25 @@ void cEpgDataWriter::Perform(void)
|
||||
|
||||
static cEpgDataWriter EpgDataWriter;
|
||||
|
||||
// --- cSchedulesLock --------------------------------------------------------
|
||||
|
||||
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
72
epg.h
@ -7,7 +7,7 @@
|
||||
* Original version (as used in VDR before 1.3.0) written by
|
||||
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
||||
*
|
||||
* $Id: epg.h 3.1 2013/08/23 10:50:05 kls Exp $
|
||||
* $Id: epg.h 4.1 2015/08/09 11:25:04 kls Exp $
|
||||
*/
|
||||
|
||||
#ifndef __EPG_H
|
||||
@ -66,13 +66,15 @@ public:
|
||||
|
||||
class cSchedule;
|
||||
|
||||
typedef u_int32_t tEventID;
|
||||
typedef u_int16_t tEventID;
|
||||
|
||||
class cEvent : public cListObject {
|
||||
friend class cSchedule;
|
||||
private:
|
||||
static cMutex numTimersMutex; // Protects numTimers, because it might be accessed from parallel read locks
|
||||
// The sequence of these parameters is optimized for minimal memory waste!
|
||||
cSchedule *schedule; // The Schedule this event belongs to
|
||||
mutable u_int16_t numTimers;// The number of timers that use this event
|
||||
tEventID eventID; // Event ID of this event
|
||||
uchar tableID; // Table ID this event came from
|
||||
uchar version; // Version number of section this event came from
|
||||
@ -109,7 +111,9 @@ public:
|
||||
time_t Vps(void) const { return vps; }
|
||||
time_t Seen(void) const { return seen; }
|
||||
bool SeenWithin(int Seconds) const { return time(NULL) - seen < Seconds; }
|
||||
bool HasTimer(void) const;
|
||||
void IncNumTimers(void) const;
|
||||
void DecNumTimers(void) const;
|
||||
bool HasTimer(void) const { return numTimers > 0; }
|
||||
bool IsRunning(bool OrAboutToStart = false) const;
|
||||
static const char *ContentToString(uchar Content);
|
||||
cString GetParentalRatingString(void) const;
|
||||
@ -120,7 +124,7 @@ public:
|
||||
void SetEventID(tEventID EventID);
|
||||
void SetTableID(uchar TableID);
|
||||
void SetVersion(uchar Version);
|
||||
void SetRunningStatus(int RunningStatus, cChannel *Channel = NULL);
|
||||
void SetRunningStatus(int RunningStatus, const cChannel *Channel = NULL);
|
||||
void SetTitle(const char *Title);
|
||||
void SetShortText(const char *ShortText);
|
||||
void SetDescription(const char *Description);
|
||||
@ -142,28 +146,33 @@ class cSchedules;
|
||||
|
||||
class cSchedule : public cListObject {
|
||||
private:
|
||||
static cMutex numTimersMutex; // Protects numTimers, because it might be accessed from parallel read locks
|
||||
tChannelID channelID;
|
||||
cList<cEvent> events;
|
||||
cHash<cEvent> eventsHashID;
|
||||
cHash<cEvent> eventsHashStartTime;
|
||||
mutable u_int16_t numTimers;// The number of timers that use this schedule
|
||||
bool hasRunning;
|
||||
time_t modified;
|
||||
int modified;
|
||||
time_t presentSeen;
|
||||
public:
|
||||
cSchedule(tChannelID ChannelID);
|
||||
tChannelID ChannelID(void) const { return channelID; }
|
||||
time_t Modified(void) const { return modified; }
|
||||
bool Modified(int &State) const { bool Result = State != modified; State = modified; return Result; }
|
||||
time_t PresentSeen(void) const { return presentSeen; }
|
||||
bool PresentSeenWithin(int Seconds) const { return time(NULL) - presentSeen < Seconds; }
|
||||
void SetModified(void) { modified = time(NULL); }
|
||||
void SetModified(void) { modified++; }
|
||||
void SetPresentSeen(void) { presentSeen = time(NULL); }
|
||||
void SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Channel = NULL);
|
||||
void SetRunningStatus(cEvent *Event, int RunningStatus, const cChannel *Channel = NULL);
|
||||
void ClrRunningStatus(cChannel *Channel = NULL);
|
||||
void ResetVersions(void);
|
||||
void Sort(void);
|
||||
void DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version);
|
||||
void Cleanup(time_t Time);
|
||||
void Cleanup(void);
|
||||
void IncNumTimers(void) const;
|
||||
void DecNumTimers(void) const;
|
||||
bool HasTimer(void) const { return numTimers > 0; }
|
||||
cEvent *AddEvent(cEvent *Event);
|
||||
void DelEvent(cEvent *Event);
|
||||
void HashEvent(cEvent *Event);
|
||||
@ -177,35 +186,23 @@ public:
|
||||
static bool Read(FILE *f, cSchedules *Schedules);
|
||||
};
|
||||
|
||||
class cSchedulesLock {
|
||||
private:
|
||||
bool locked;
|
||||
public:
|
||||
cSchedulesLock(bool WriteLock = false, int TimeoutMs = 0);
|
||||
~cSchedulesLock();
|
||||
bool Locked(void) { return locked; }
|
||||
};
|
||||
|
||||
class cSchedules : public cList<cSchedule> {
|
||||
friend class cSchedule;
|
||||
friend class cSchedulesLock;
|
||||
private:
|
||||
cRwLock rwlock;
|
||||
static cSchedules schedules;
|
||||
static char *epgDataFileName;
|
||||
static time_t lastDump;
|
||||
static time_t modified;
|
||||
public:
|
||||
cSchedules(void);
|
||||
static const cSchedules *GetSchedulesRead(cStateKey &StateKey, int TimeoutMs = 0);
|
||||
///< Gets the list of schedules for read access.
|
||||
///< See cTimers::GetTimersRead() for details.
|
||||
static cSchedules *GetSchedulesWrite(cStateKey &StateKey, int TimeoutMs = 0);
|
||||
///< Gets the list of schedules for write access.
|
||||
///< See cTimers::GetTimersWrite() for details.
|
||||
static void SetEpgDataFileName(const char *FileName);
|
||||
static const cSchedules *Schedules(cSchedulesLock &SchedulesLock);
|
||||
///< Caller must provide a cSchedulesLock which has to survive the entire
|
||||
///< time the returned cSchedules is accessed. Once the cSchedules is no
|
||||
///< longer used, the cSchedulesLock must be destroyed.
|
||||
static time_t Modified(void) { return modified; }
|
||||
static void SetModified(cSchedule *Schedule);
|
||||
static void Cleanup(bool Force = false);
|
||||
static void ResetVersions(void);
|
||||
static bool ClearAll(void);
|
||||
static bool Dump(FILE *f = NULL, const char *Prefix = "", eDumpMode DumpMode = dmAll, time_t AtTime = 0);
|
||||
static bool Read(FILE *f = NULL);
|
||||
cSchedule *AddSchedule(tChannelID ChannelID);
|
||||
@ -213,6 +210,17 @@ public:
|
||||
const cSchedule *GetSchedule(const cChannel *Channel, bool AddIfMissing = false) const;
|
||||
};
|
||||
|
||||
// Provide lock controlled access to the list:
|
||||
|
||||
DEF_LIST_LOCK(Schedules);
|
||||
|
||||
// These macros provide a convenient way of locking the global schedules list
|
||||
// and making sure the lock is released as soon as the current scope is left
|
||||
// (note that these macros wait forever to obtain the lock!):
|
||||
|
||||
#define LOCK_SCHEDULES_READ USE_LIST_LOCK_READ(Schedules);
|
||||
#define LOCK_SCHEDULES_WRITE USE_LIST_LOCK_WRITE(Schedules);
|
||||
|
||||
class cEpgDataReader : public cThread {
|
||||
public:
|
||||
cEpgDataReader(void);
|
||||
@ -273,12 +281,14 @@ public:
|
||||
virtual bool DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version) { return false; }
|
||||
///< Takes a look at all EPG events between SegmentStart and SegmentEnd and
|
||||
///< drops outdated events.
|
||||
virtual bool BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus) { return false; }
|
||||
virtual bool BeginSegmentTransfer(const cChannel *Channel, bool Dummy) { return false; } // TODO remove obsolete Dummy
|
||||
///< Called directly after IgnoreChannel() before any other handler method is called.
|
||||
///< Designed to give handlers the possibility to prepare a database transaction.
|
||||
virtual bool EndSegmentTransfer(bool Modified, bool OnlyRunningStatus) { return false; }
|
||||
///< Dummy is for backward compatibility and may be removed in a future version.
|
||||
virtual bool EndSegmentTransfer(bool Modified, bool Dummy) { return false; } // TODO remove obsolete Dummy
|
||||
///< Called after the segment data has been processed.
|
||||
///< At this point handlers should close/commit/rollback any pending database transactions.
|
||||
///< Dummy is for backward compatibility and may be removed in a future version.
|
||||
};
|
||||
|
||||
class cEpgHandlers : public cList<cEpgHandler> {
|
||||
@ -301,8 +311,8 @@ public:
|
||||
void HandleEvent(cEvent *Event);
|
||||
void SortSchedule(cSchedule *Schedule);
|
||||
void DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version);
|
||||
void BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus);
|
||||
void EndSegmentTransfer(bool Modified, bool OnlyRunningStatus);
|
||||
void BeginSegmentTransfer(const cChannel *Channel);
|
||||
void EndSegmentTransfer(bool Modified);
|
||||
};
|
||||
|
||||
extern cEpgHandlers EpgHandlers;
|
||||
|
41
filter.c
41
filter.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: filter.c 4.1 2015/03/17 15:04:39 kls Exp $
|
||||
* $Id: filter.c 4.2 2015/07/25 10:59:57 kls Exp $
|
||||
*/
|
||||
|
||||
#include "filter.h"
|
||||
@ -19,31 +19,38 @@ cSectionSyncer::cSectionSyncer(void)
|
||||
|
||||
void cSectionSyncer::Reset(void)
|
||||
{
|
||||
lastVersion = thisVersion = 0xFF;
|
||||
nextNumber = 0;
|
||||
currentVersion = -1;
|
||||
currentSection = -1;
|
||||
synced = false;
|
||||
complete = false;
|
||||
memset(sections, 0x00, sizeof(sections));
|
||||
}
|
||||
|
||||
void cSectionSyncer::Repeat(void)
|
||||
{
|
||||
lastVersion = 0xFF;
|
||||
nextNumber--;
|
||||
SetSectionFlag(currentSection, false);
|
||||
synced = false;
|
||||
complete = false;
|
||||
}
|
||||
|
||||
bool cSectionSyncer::Sync(uchar Version, int Number, int LastNumber)
|
||||
{
|
||||
if (Version != lastVersion) {
|
||||
if (Version != thisVersion) {
|
||||
thisVersion = Version;
|
||||
nextNumber = 0;
|
||||
}
|
||||
if (Number == nextNumber) {
|
||||
if (Number == LastNumber)
|
||||
lastVersion = Version;
|
||||
nextNumber++;
|
||||
return true;
|
||||
}
|
||||
if (Version != currentVersion) {
|
||||
Reset();
|
||||
currentVersion = Version;
|
||||
}
|
||||
return false;
|
||||
if (!synced) {
|
||||
if (Number != 0)
|
||||
return false;
|
||||
else
|
||||
synced = true;
|
||||
}
|
||||
currentSection = Number;
|
||||
bool Result = !GetSectionFlag(Number);
|
||||
SetSectionFlag(Number, true);
|
||||
if (Number == LastNumber)
|
||||
complete = true;
|
||||
return Result;
|
||||
}
|
||||
|
||||
// --- cFilterData -----------------------------------------------------------
|
||||
|
13
filter.h
13
filter.h
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: filter.h 4.1 2015/03/17 15:00:08 kls Exp $
|
||||
* $Id: filter.h 4.2 2015/07/25 10:03:44 kls Exp $
|
||||
*/
|
||||
|
||||
#ifndef __FILTER_H
|
||||
@ -15,13 +15,18 @@
|
||||
|
||||
class cSectionSyncer {
|
||||
private:
|
||||
int lastVersion;
|
||||
int thisVersion;
|
||||
int nextNumber;
|
||||
int currentVersion;
|
||||
int currentSection;
|
||||
bool synced;
|
||||
bool complete;
|
||||
uchar sections[32]; // holds 32 * 8 = 256 bits, as flags for the sections
|
||||
void SetSectionFlag(uchar Section, bool On) { if (On) sections[Section / 8] |= (1 << (Section % 8)); else sections[Section / 8] &= ~(1 << (Section % 8)); }
|
||||
bool GetSectionFlag(uchar Section) { return sections[Section / 8] & (1 << (Section % 8)); }
|
||||
public:
|
||||
cSectionSyncer(void);
|
||||
void Reset(void);
|
||||
void Repeat(void);
|
||||
bool Complete(void) { return complete; }
|
||||
bool Sync(uchar Version, int Number, int LastNumber);
|
||||
};
|
||||
|
||||
|
21
menu.h
21
menu.h
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: menu.h 3.8 2015/02/06 09:47:30 kls Exp $
|
||||
* $Id: menu.h 4.1 2015/08/31 13:34:12 kls Exp $
|
||||
*/
|
||||
|
||||
#ifndef __MENU_H
|
||||
@ -72,6 +72,7 @@ public:
|
||||
|
||||
class cMenuEditTimer : public cOsdMenu {
|
||||
private:
|
||||
static const cTimer *addedTimer;
|
||||
cTimer *timer;
|
||||
cTimer data;
|
||||
int channel;
|
||||
@ -86,13 +87,14 @@ public:
|
||||
cMenuEditTimer(cTimer *Timer, bool New = false);
|
||||
virtual ~cMenuEditTimer();
|
||||
virtual eOSState ProcessKey(eKeys Key);
|
||||
static const cTimer *AddedTimer(void);
|
||||
};
|
||||
|
||||
class cMenuEvent : public cOsdMenu {
|
||||
private:
|
||||
const cEvent *event;
|
||||
public:
|
||||
cMenuEvent(const cEvent *Event, bool CanSwitch = false, bool Buttons = false);
|
||||
cMenuEvent(const cTimers *Timers, const cChannels *Channels, const cEvent *Event, bool CanSwitch = false, bool Buttons = false);
|
||||
virtual void Display(void);
|
||||
virtual eOSState ProcessKey(eKeys Key);
|
||||
};
|
||||
@ -123,14 +125,14 @@ private:
|
||||
bool timeout;
|
||||
int osdState;
|
||||
const cPositioner *positioner;
|
||||
cChannel *channel;
|
||||
const cChannel *channel;
|
||||
const cEvent *lastPresent;
|
||||
const cEvent *lastFollowing;
|
||||
static cDisplayChannel *currentDisplayChannel;
|
||||
void DisplayChannel(void);
|
||||
void DisplayInfo(void);
|
||||
void Refresh(void);
|
||||
cChannel *NextAvailableChannel(cChannel *Channel, int Direction);
|
||||
const cChannel *NextAvailableChannel(const cChannel *Channel, int Direction);
|
||||
public:
|
||||
cDisplayChannel(int Number, bool Switched);
|
||||
cDisplayChannel(eKeys FirstKey);
|
||||
@ -205,7 +207,7 @@ class cMenuRecordings : public cOsdMenu {
|
||||
private:
|
||||
char *base;
|
||||
int level;
|
||||
int recordingsState;
|
||||
cStateKey recordingsStateKey;
|
||||
int helpKeys;
|
||||
const cRecordingFilter *filter;
|
||||
static cString path;
|
||||
@ -239,7 +241,7 @@ private:
|
||||
char *fileName;
|
||||
bool GetEvent(void);
|
||||
public:
|
||||
cRecordControl(cDevice *Device, cTimer *Timer = NULL, bool Pause = false);
|
||||
cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer = NULL, bool Pause = false);
|
||||
virtual ~cRecordControl();
|
||||
bool Process(time_t t);
|
||||
cDevice *Device(void) { return device; }
|
||||
@ -254,7 +256,8 @@ private:
|
||||
static cRecordControl *RecordControls[];
|
||||
static int state;
|
||||
public:
|
||||
static bool Start(cTimer *Timer = NULL, bool Pause = false);
|
||||
static bool Start(cTimers *Timers, cTimer *Timer, bool Pause = false);
|
||||
static bool Start(bool Pause = false);
|
||||
static void Stop(const char *InstantId);
|
||||
static bool PauseLiveVideo(void);
|
||||
static const char *GetInstantId(const char *LastInstantId);
|
||||
@ -262,8 +265,8 @@ public:
|
||||
static cRecordControl *GetRecordControl(const cTimer *Timer);
|
||||
///< Returns the cRecordControl for the given Timer.
|
||||
///< If there is no cRecordControl for Timer, NULL is returned.
|
||||
static void Process(time_t t);
|
||||
static void ChannelDataModified(cChannel *Channel);
|
||||
static bool Process(cTimers *Timers, time_t t);
|
||||
static void ChannelDataModified(const cChannel *Channel);
|
||||
static bool Active(void);
|
||||
static void Shutdown(void);
|
||||
static void ChangeState(void) { state++; }
|
||||
|
46
menuitems.c
46
menuitems.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: menuitems.c 3.3 2015/02/09 11:53:10 kls Exp $
|
||||
* $Id: menuitems.c 4.1 2015/07/18 10:38:31 kls Exp $
|
||||
*/
|
||||
|
||||
#include "menuitems.h"
|
||||
@ -777,7 +777,7 @@ void cMenuEditStraItem::Set(void)
|
||||
// --- cMenuEditChanItem -----------------------------------------------------
|
||||
|
||||
cMenuEditChanItem::cMenuEditChanItem(const char *Name, int *Value, const char *NoneString)
|
||||
:cMenuEditIntItem(Name, Value, NoneString ? 0 : 1, Channels.MaxNumber())
|
||||
:cMenuEditIntItem(Name, Value, NoneString ? 0 : 1, cChannels::MaxNumber())
|
||||
{
|
||||
channelID = NULL;
|
||||
noneString = NoneString;
|
||||
@ -786,12 +786,13 @@ cMenuEditChanItem::cMenuEditChanItem(const char *Name, int *Value, const char *N
|
||||
}
|
||||
|
||||
cMenuEditChanItem::cMenuEditChanItem(const char *Name, cString *ChannelID, const char *NoneString)
|
||||
:cMenuEditIntItem(Name, &dummyValue, NoneString ? 0 : 1, Channels.MaxNumber())
|
||||
:cMenuEditIntItem(Name, &dummyValue, NoneString ? 0 : 1, cChannels::MaxNumber())
|
||||
{
|
||||
channelID = ChannelID;
|
||||
noneString = NoneString;
|
||||
cChannel *channel = Channels.GetByChannelID(tChannelID::FromString(*ChannelID));
|
||||
dummyValue = channel ? channel->Number() : 0;
|
||||
LOCK_CHANNELS_READ;
|
||||
const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(*ChannelID));
|
||||
dummyValue = Channel ? Channel->Number() : 0;
|
||||
Set();
|
||||
}
|
||||
|
||||
@ -799,11 +800,12 @@ void cMenuEditChanItem::Set(void)
|
||||
{
|
||||
if (*value > 0) {
|
||||
char buf[255];
|
||||
cChannel *channel = Channels.GetByNumber(*value);
|
||||
snprintf(buf, sizeof(buf), "%d %s", *value, channel ? channel->Name() : "");
|
||||
LOCK_CHANNELS_READ;
|
||||
const cChannel *Channel = Channels->GetByNumber(*value);
|
||||
snprintf(buf, sizeof(buf), "%d %s", *value, Channel ? Channel->Name() : "");
|
||||
SetValue(buf);
|
||||
if (channelID)
|
||||
*channelID = channel ? channel->GetChannelID().ToString() : "";
|
||||
*channelID = Channel ? Channel->GetChannelID().ToString() : "";
|
||||
}
|
||||
else if (noneString) {
|
||||
SetValue(noneString);
|
||||
@ -822,13 +824,14 @@ eOSState cMenuEditChanItem::ProcessKey(eKeys Key)
|
||||
case kRight|k_Repeat:
|
||||
case kRight:
|
||||
{
|
||||
cChannel *channel = Channels.GetByNumber(*value + delta, delta);
|
||||
if (channel)
|
||||
*value = channel->Number();
|
||||
LOCK_CHANNELS_READ
|
||||
const cChannel *Channel = Channels->GetByNumber(*value + delta, delta);
|
||||
if (Channel)
|
||||
*value = Channel->Number();
|
||||
else if (delta < 0 && noneString)
|
||||
*value = 0;
|
||||
if (channelID)
|
||||
*channelID = channel ? channel->GetChannelID().ToString() : "";
|
||||
*channelID = Channel ? Channel->GetChannelID().ToString() : "";
|
||||
Set();
|
||||
}
|
||||
break;
|
||||
@ -845,13 +848,14 @@ cMenuEditTranItem::cMenuEditTranItem(const char *Name, int *Value, int *Source)
|
||||
number = 0;
|
||||
source = Source;
|
||||
transponder = Value;
|
||||
cChannel *channel = Channels.First();
|
||||
while (channel) {
|
||||
if (!channel->GroupSep() && *source == channel->Source() && ISTRANSPONDER(channel->Transponder(), *Value)) {
|
||||
number = channel->Number();
|
||||
LOCK_CHANNELS_READ;
|
||||
const cChannel *Channel = Channels->First();
|
||||
while (Channel) {
|
||||
if (!Channel->GroupSep() && *source == Channel->Source() && ISTRANSPONDER(Channel->Transponder(), *Value)) {
|
||||
number = Channel->Number();
|
||||
break;
|
||||
}
|
||||
channel = (cChannel *)channel->Next();
|
||||
Channel = Channels->Next(Channel);
|
||||
}
|
||||
Set();
|
||||
}
|
||||
@ -859,10 +863,10 @@ cMenuEditTranItem::cMenuEditTranItem(const char *Name, int *Value, int *Source)
|
||||
eOSState cMenuEditTranItem::ProcessKey(eKeys Key)
|
||||
{
|
||||
eOSState state = cMenuEditChanItem::ProcessKey(Key);
|
||||
cChannel *channel = Channels.GetByNumber(number);
|
||||
if (channel) {
|
||||
*source = channel->Source();
|
||||
*transponder = channel->Transponder();
|
||||
LOCK_CHANNELS_READ
|
||||
if (const cChannel *Channel = Channels->GetByNumber(number)) {
|
||||
*source = Channel->Source();
|
||||
*transponder = Channel->Transponder();
|
||||
}
|
||||
else {
|
||||
*source = 0;
|
||||
|
43
nit.c
43
nit.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: nit.c 4.2 2015/03/17 15:10:09 kls Exp $
|
||||
* $Id: nit.c 4.3 2015/07/26 09:24:36 kls Exp $
|
||||
*/
|
||||
|
||||
#include "nit.h"
|
||||
@ -61,10 +61,13 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
}
|
||||
dbgnit("NIT: %02X %2d %2d %2d %s %d %d '%s'\n", Tid, nit.getVersionNumber(), nit.getSectionNumber(), nit.getLastSectionNumber(), *cSource::ToString(Source()), nit.getNetworkId(), Transponder(), NetworkName);
|
||||
}
|
||||
if (!Channels.Lock(true, 10)) {
|
||||
cStateKey StateKey;
|
||||
cChannels *Channels = cChannels::GetChannelsWrite(StateKey, 10);
|
||||
if (!Channels) {
|
||||
sectionSyncer.Repeat(); // let's not miss any section of the NIT
|
||||
return;
|
||||
}
|
||||
bool ChannelsModified = false;
|
||||
SI::NIT::TransportStream ts;
|
||||
for (SI::Loop::Iterator it; nit.transportStreamLoop.getNext(ts, it); ) {
|
||||
SI::Descriptor *d;
|
||||
@ -115,7 +118,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
if (Setup.UpdateChannels >= 5) {
|
||||
bool found = false;
|
||||
bool forceTransponderUpdate = false;
|
||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
||||
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
||||
int transponder = Channel->Transponder();
|
||||
found = true;
|
||||
@ -128,7 +131,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
}
|
||||
}
|
||||
if (ISTRANSPONDER(cChannel::Transponder(Frequency, dtp.Polarization()), Transponder())) // only modify channels if we're actually receiving this transponder
|
||||
Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('S'));
|
||||
ChannelsModified |= Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('S'));
|
||||
else if (Channel->Srate() != SymbolRate || strcmp(Channel->Parameters(), dtp.ToString('S')))
|
||||
forceTransponderUpdate = true; // get us receiving this transponder
|
||||
}
|
||||
@ -136,7 +139,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
if (!found || forceTransponderUpdate) {
|
||||
for (int n = 0; n < NumFrequencies; n++) {
|
||||
cChannel *Channel = new cChannel;
|
||||
Channel->SetId(ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
|
||||
Channel->SetId(NULL, ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
|
||||
if (Channel->SetTransponderData(Source, Frequencies[n], SymbolRate, dtp.ToString('S')))
|
||||
EITScanner.AddTransponder(Channel);
|
||||
else
|
||||
@ -150,13 +153,13 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
break;
|
||||
case SI::S2SatelliteDeliverySystemDescriptorTag: {
|
||||
if (Setup.UpdateChannels >= 5) {
|
||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
||||
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||
if (!Channel->GroupSep() && cSource::IsSat(Channel->Source()) && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
||||
SI::S2SatelliteDeliverySystemDescriptor *sd = (SI::S2SatelliteDeliverySystemDescriptor *)d;
|
||||
cDvbTransponderParameters dtp(Channel->Parameters());
|
||||
dtp.SetSystem(DVB_SYSTEM_2);
|
||||
dtp.SetStreamId(sd->getInputStreamIdentifier());
|
||||
Channel->SetTransponderData(Channel->Source(), Channel->Frequency(), Channel->Srate(), dtp.ToString('S'));
|
||||
ChannelsModified |= Channel->SetTransponderData(Channel->Source(), Channel->Frequency(), Channel->Srate(), dtp.ToString('S'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -178,7 +181,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
if (Setup.UpdateChannels >= 5) {
|
||||
bool found = false;
|
||||
bool forceTransponderUpdate = false;
|
||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
||||
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
||||
int transponder = Channel->Transponder();
|
||||
found = true;
|
||||
@ -191,7 +194,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
}
|
||||
}
|
||||
if (ISTRANSPONDER(Frequency / 1000, Transponder())) // only modify channels if we're actually receiving this transponder
|
||||
Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('C'));
|
||||
ChannelsModified |= Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('C'));
|
||||
else if (Channel->Srate() != SymbolRate || strcmp(Channel->Parameters(), dtp.ToString('C')))
|
||||
forceTransponderUpdate = true; // get us receiving this transponder
|
||||
}
|
||||
@ -199,7 +202,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
if (!found || forceTransponderUpdate) {
|
||||
for (int n = 0; n < NumFrequencies; n++) {
|
||||
cChannel *Channel = new cChannel;
|
||||
Channel->SetId(ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
|
||||
Channel->SetId(NULL, ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
|
||||
if (Channel->SetTransponderData(Source, Frequencies[n], SymbolRate, dtp.ToString('C')))
|
||||
EITScanner.AddTransponder(Channel);
|
||||
else
|
||||
@ -234,7 +237,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
if (Setup.UpdateChannels >= 5) {
|
||||
bool found = false;
|
||||
bool forceTransponderUpdate = false;
|
||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
||||
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
||||
int transponder = Channel->Transponder();
|
||||
found = true;
|
||||
@ -247,7 +250,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
}
|
||||
}
|
||||
if (ISTRANSPONDER(Frequency / 1000000, Transponder())) // only modify channels if we're actually receiving this transponder
|
||||
Channel->SetTransponderData(Source, Frequency, 0, dtp.ToString('T'));
|
||||
ChannelsModified |= Channel->SetTransponderData(Source, Frequency, 0, dtp.ToString('T'));
|
||||
else if (strcmp(Channel->Parameters(), dtp.ToString('T')))
|
||||
forceTransponderUpdate = true; // get us receiving this transponder
|
||||
}
|
||||
@ -255,7 +258,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
if (!found || forceTransponderUpdate) {
|
||||
for (int n = 0; n < NumFrequencies; n++) {
|
||||
cChannel *Channel = new cChannel;
|
||||
Channel->SetId(ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
|
||||
Channel->SetId(NULL, ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
|
||||
if (Channel->SetTransponderData(Source, Frequencies[n], 0, dtp.ToString('T')))
|
||||
EITScanner.AddTransponder(Channel);
|
||||
else
|
||||
@ -272,7 +275,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
switch (sd->getExtensionDescriptorTag()) {
|
||||
case SI::T2DeliverySystemDescriptorTag: {
|
||||
if (Setup.UpdateChannels >= 5) {
|
||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
||||
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||
int Source = cSource::FromData(cSource::stTerr);
|
||||
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
||||
SI::T2DeliverySystemDescriptor *td = (SI::T2DeliverySystemDescriptor *)d;
|
||||
@ -292,7 +295,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
dtp.SetTransmission(T2TransmissionModes[td->getTransmissionMode()]);
|
||||
//TODO add parsing of frequencies
|
||||
}
|
||||
Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('T'));
|
||||
ChannelsModified |= Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('T'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -310,9 +313,9 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
int lcn = LogicalChannel.getLogicalChannelNumber();
|
||||
int sid = LogicalChannel.getServiceId();
|
||||
if (LogicalChannel.getVisibleServiceFlag()) {
|
||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
||||
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||
if (!Channel->GroupSep() && Channel->Sid() == sid && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
||||
Channel->SetLcn(lcn);
|
||||
ChannelsModified |= Channel->SetLcn(lcn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -328,9 +331,9 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
int lcn = HdSimulcastLogicalChannel.getLogicalChannelNumber();
|
||||
int sid = HdSimulcastLogicalChannel.getServiceId();
|
||||
if (HdSimulcastLogicalChannel.getVisibleServiceFlag()) {
|
||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
||||
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||
if (!Channel->GroupSep() && Channel->Sid() == sid && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
||||
Channel->SetLcn(lcn);
|
||||
ChannelsModified |= Channel->SetLcn(lcn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -343,5 +346,5 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
delete d;
|
||||
}
|
||||
}
|
||||
Channels.Unlock();
|
||||
StateKey.Remove(ChannelsModified);
|
||||
}
|
||||
|
23
pat.c
23
pat.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: pat.c 3.5 2015/01/04 13:14:01 kls Exp $
|
||||
* $Id: pat.c 4.1 2015/08/17 08:46:55 kls Exp $
|
||||
*/
|
||||
|
||||
#include "pat.h"
|
||||
@ -99,8 +99,8 @@ cCaDescriptors::cCaDescriptors(int Source, int Transponder, int ServiceId, int P
|
||||
|
||||
bool cCaDescriptors::operator== (const cCaDescriptors &arg) const
|
||||
{
|
||||
cCaDescriptor *ca1 = caDescriptors.First();
|
||||
cCaDescriptor *ca2 = arg.caDescriptors.First();
|
||||
const cCaDescriptor *ca1 = caDescriptors.First();
|
||||
const cCaDescriptor *ca2 = arg.caDescriptors.First();
|
||||
while (ca1 && ca2) {
|
||||
if (!(*ca1 == *ca2))
|
||||
return false;
|
||||
@ -396,11 +396,14 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
SwitchToNextPmtPid();
|
||||
return;
|
||||
}
|
||||
if (!Channels.Lock(true, 10))
|
||||
cStateKey StateKey;
|
||||
cChannels *Channels = cChannels::GetChannelsWrite(StateKey, 10);
|
||||
if (!Channels)
|
||||
return;
|
||||
bool ChannelsModified = false;
|
||||
PmtVersionChanged(Pid, pmt.getTableIdExtension(), pmt.getVersionNumber(), true);
|
||||
SwitchToNextPmtPid();
|
||||
cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), pmt.getServiceId());
|
||||
cChannel *Channel = Channels->GetByServiceID(Source(), Transponder(), pmt.getServiceId());
|
||||
if (Channel) {
|
||||
SI::CaDescriptor *d;
|
||||
cCaDescriptors *CaDescriptors = new cCaDescriptors(Channel->Source(), Channel->Transponder(), Channel->Sid(), Pid);
|
||||
@ -629,13 +632,13 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
}
|
||||
}
|
||||
if (Setup.UpdateChannels >= 2) {
|
||||
Channel->SetPids(Vpid, Ppid, Vtype, Apids, Atypes, ALangs, Dpids, Dtypes, DLangs, Spids, SLangs, Tpid);
|
||||
Channel->SetCaIds(CaDescriptors->CaIds());
|
||||
Channel->SetSubtitlingDescriptors(SubtitlingTypes, CompositionPageIds, AncillaryPageIds);
|
||||
ChannelsModified |= Channel->SetPids(Vpid, Ppid, Vtype, Apids, Atypes, ALangs, Dpids, Dtypes, DLangs, Spids, SLangs, Tpid);
|
||||
ChannelsModified |= Channel->SetCaIds(CaDescriptors->CaIds());
|
||||
ChannelsModified |= Channel->SetSubtitlingDescriptors(SubtitlingTypes, CompositionPageIds, AncillaryPageIds);
|
||||
}
|
||||
Channel->SetCaDescriptors(CaDescriptorHandler.AddCaDescriptors(CaDescriptors));
|
||||
ChannelsModified |= Channel->SetCaDescriptors(CaDescriptorHandler.AddCaDescriptors(CaDescriptors));
|
||||
}
|
||||
Channels.Unlock();
|
||||
StateKey.Remove(ChannelsModified);
|
||||
}
|
||||
if (timer.TimedOut()) {
|
||||
if (pmtIndex >= 0)
|
||||
|
@ -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;
|
||||
|
457
recording.c
457
recording.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: recording.c 4.2 2015/04/18 13:14:55 kls Exp $
|
||||
* $Id: recording.c 4.3 2015/08/29 14:42:53 kls Exp $
|
||||
*/
|
||||
|
||||
#include "recording.h"
|
||||
@ -76,9 +76,6 @@ int DirectoryNameMax = NAME_MAX;
|
||||
bool DirectoryEncoding = false;
|
||||
int InstanceId = 0;
|
||||
|
||||
cRecordings DeletedRecordings(true);
|
||||
static cRecordings VanishedRecordings;
|
||||
|
||||
// --- cRemoveDeletedRecordingsThread ----------------------------------------
|
||||
|
||||
class cRemoveDeletedRecordingsThread : public cThread {
|
||||
@ -100,8 +97,8 @@ void cRemoveDeletedRecordingsThread::Action(void)
|
||||
if (LockFile.Lock()) {
|
||||
time_t StartTime = time(NULL);
|
||||
bool deleted = false;
|
||||
cThreadLock DeletedRecordingsLock(&DeletedRecordings);
|
||||
for (cRecording *r = DeletedRecordings.First(); r; ) {
|
||||
LOCK_DELETEDRECORDINGS_WRITE;
|
||||
for (cRecording *r = DeletedRecordings->First(); r; ) {
|
||||
if (cIoThrottle::Engaged())
|
||||
return;
|
||||
if (time(NULL) - StartTime > MAXREMOVETIME)
|
||||
@ -109,14 +106,14 @@ void cRemoveDeletedRecordingsThread::Action(void)
|
||||
if (cRemote::HasKeys())
|
||||
return; // react immediately on user input
|
||||
if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) {
|
||||
cRecording *next = DeletedRecordings.Next(r);
|
||||
cRecording *next = DeletedRecordings->Next(r);
|
||||
r->Remove();
|
||||
DeletedRecordings.Del(r);
|
||||
DeletedRecordings->Del(r);
|
||||
r = next;
|
||||
deleted = true;
|
||||
continue;
|
||||
}
|
||||
r = DeletedRecordings.Next(r);
|
||||
r = DeletedRecordings->Next(r);
|
||||
}
|
||||
if (deleted) {
|
||||
const char *IgnoreFiles[] = { SORTMODEFILE, NULL };
|
||||
@ -134,8 +131,8 @@ void RemoveDeletedRecordings(void)
|
||||
static time_t LastRemoveCheck = 0;
|
||||
if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) {
|
||||
if (!RemoveDeletedRecordingsThread.Active()) {
|
||||
cThreadLock DeletedRecordingsLock(&DeletedRecordings);
|
||||
for (cRecording *r = DeletedRecordings.First(); r; r = DeletedRecordings.Next(r)) {
|
||||
LOCK_DELETEDRECORDINGS_READ;
|
||||
for (const cRecording *r = DeletedRecordings->First(); r; r = DeletedRecordings->Next(r)) {
|
||||
if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) {
|
||||
RemoveDeletedRecordingsThread.Start();
|
||||
break;
|
||||
@ -163,37 +160,43 @@ void AssertFreeDiskSpace(int Priority, bool Force)
|
||||
return;
|
||||
// Remove the oldest file that has been "deleted":
|
||||
isyslog("low disk space while recording, trying to remove a deleted recording...");
|
||||
cThreadLock DeletedRecordingsLock(&DeletedRecordings);
|
||||
if (DeletedRecordings.Count()) {
|
||||
cRecording *r = DeletedRecordings.First();
|
||||
cRecording *r0 = NULL;
|
||||
while (r) {
|
||||
if (r->IsOnVideoDirectoryFileSystem()) { // only remove recordings that will actually increase the free video disk space
|
||||
if (!r0 || r->Start() < r0->Start())
|
||||
r0 = r;
|
||||
}
|
||||
r = DeletedRecordings.Next(r);
|
||||
}
|
||||
if (r0) {
|
||||
if (r0->Remove())
|
||||
LastFreeDiskCheck += REMOVELATENCY / Factor;
|
||||
DeletedRecordings.Del(r0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
int NumDeletedRecordings = 0;
|
||||
{
|
||||
LOCK_DELETEDRECORDINGS_WRITE;
|
||||
NumDeletedRecordings = DeletedRecordings->Count();
|
||||
if (NumDeletedRecordings) {
|
||||
cRecording *r = DeletedRecordings->First();
|
||||
cRecording *r0 = NULL;
|
||||
while (r) {
|
||||
if (r->IsOnVideoDirectoryFileSystem()) { // only remove recordings that will actually increase the free video disk space
|
||||
if (!r0 || r->Start() < r0->Start())
|
||||
r0 = r;
|
||||
}
|
||||
r = DeletedRecordings->Next(r);
|
||||
}
|
||||
if (r0) {
|
||||
if (r0->Remove())
|
||||
LastFreeDiskCheck += REMOVELATENCY / Factor;
|
||||
DeletedRecordings->Del(r0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (NumDeletedRecordings == 0) {
|
||||
// DeletedRecordings was empty, so to be absolutely sure there are no
|
||||
// deleted recordings we need to double check:
|
||||
DeletedRecordings.Update(true);
|
||||
if (DeletedRecordings.Count())
|
||||
cRecordings::Update(true);
|
||||
LOCK_DELETEDRECORDINGS_READ;
|
||||
if (DeletedRecordings->Count())
|
||||
return; // the next call will actually remove it
|
||||
}
|
||||
// No "deleted" files to remove, so let's see if we can delete a recording:
|
||||
if (Priority > 0) {
|
||||
isyslog("...no deleted recording found, trying to delete an old recording...");
|
||||
cThreadLock RecordingsLock(&Recordings);
|
||||
if (Recordings.Count()) {
|
||||
cRecording *r = Recordings.First();
|
||||
LOCK_RECORDINGS_WRITE;
|
||||
Recordings->SetExplicitModify();
|
||||
if (Recordings->Count()) {
|
||||
cRecording *r = Recordings->First();
|
||||
cRecording *r0 = NULL;
|
||||
while (r) {
|
||||
if (r->IsOnVideoDirectoryFileSystem()) { // only delete recordings that will actually increase the free video disk space
|
||||
@ -209,10 +212,11 @@ void AssertFreeDiskSpace(int Priority, bool Force)
|
||||
}
|
||||
}
|
||||
}
|
||||
r = Recordings.Next(r);
|
||||
r = Recordings->Next(r);
|
||||
}
|
||||
if (r0 && r0->Delete()) {
|
||||
Recordings.Del(r0);
|
||||
Recordings->Del(r0);
|
||||
Recordings->SetModified();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -227,14 +231,6 @@ void AssertFreeDiskSpace(int Priority, bool Force)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Clear vanished recordings ---------------------------------------------
|
||||
|
||||
void ClearVanishedRecordings(void)
|
||||
{
|
||||
cThreadLock RecordingsLock(&Recordings); // yes, it *is* Recordings!
|
||||
VanishedRecordings.Clear();
|
||||
}
|
||||
|
||||
// --- cResumeFile -----------------------------------------------------------
|
||||
|
||||
cResumeFile::cResumeFile(const char *FileName, bool IsPesRecording)
|
||||
@ -309,7 +305,8 @@ bool cResumeFile::Save(int Index)
|
||||
if (safe_write(f, &Index, sizeof(Index)) < 0)
|
||||
LOG_ERROR_STR(fileName);
|
||||
close(f);
|
||||
Recordings.ResetResume(fileName);
|
||||
LOCK_RECORDINGS_WRITE;
|
||||
Recordings->ResetResume(fileName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -318,7 +315,8 @@ bool cResumeFile::Save(int Index)
|
||||
if (f) {
|
||||
fprintf(f, "I %d\n", Index);
|
||||
fclose(f);
|
||||
Recordings.ResetResume(fileName);
|
||||
LOCK_RECORDINGS_WRITE;
|
||||
Recordings->ResetResume(fileName);
|
||||
}
|
||||
else
|
||||
LOG_ERROR_STR(fileName);
|
||||
@ -331,8 +329,10 @@ bool cResumeFile::Save(int Index)
|
||||
void cResumeFile::Delete(void)
|
||||
{
|
||||
if (fileName) {
|
||||
if (remove(fileName) == 0)
|
||||
Recordings.ResetResume(fileName);
|
||||
if (remove(fileName) == 0) {
|
||||
LOCK_RECORDINGS_WRITE;
|
||||
Recordings->ResetResume(fileName);
|
||||
}
|
||||
else if (errno != ENOENT)
|
||||
LOG_ERROR_STR(fileName);
|
||||
}
|
||||
@ -787,10 +787,8 @@ cRecording::cRecording(cTimer *Timer, const cEvent *Event)
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (Timer->IsSingleEvent()) {
|
||||
if (Timer->IsSingleEvent())
|
||||
Timer->SetFile(name); // this was an instant recording, so let's set the actual data
|
||||
Timers.SetModified();
|
||||
}
|
||||
}
|
||||
else if (Timer->IsSingleEvent() || !Setup.UseSubtitle)
|
||||
name = strdup(Timer->File());
|
||||
@ -1017,7 +1015,7 @@ int cRecording::Compare(const cListObject &ListObject) const
|
||||
return strcasecmp(SortName(), r->SortName());
|
||||
}
|
||||
|
||||
bool cRecording::IsInPath(const char *Path)
|
||||
bool cRecording::IsInPath(const char *Path) const
|
||||
{
|
||||
if (isempty(Path))
|
||||
return true;
|
||||
@ -1154,20 +1152,14 @@ bool cRecording::IsOnVideoDirectoryFileSystem(void) const
|
||||
return isOnVideoDirectoryFileSystem;
|
||||
}
|
||||
|
||||
bool cRecording::HasMarks(void)
|
||||
bool cRecording::HasMarks(void) const
|
||||
{
|
||||
return access(cMarks::MarksFileName(this), F_OK) == 0;
|
||||
}
|
||||
|
||||
bool cRecording::DeleteMarks(void)
|
||||
{
|
||||
if (remove(cMarks::MarksFileName(this)) < 0) {
|
||||
if (errno != ENOENT) {
|
||||
LOG_ERROR_STR(fileName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return cMarks::DeleteMarksFile(this);
|
||||
}
|
||||
|
||||
void cRecording::ReadInfo(void)
|
||||
@ -1219,8 +1211,6 @@ bool cRecording::ChangePriorityLifetime(int NewPriority, int NewLifetime)
|
||||
if (!WriteInfo())
|
||||
return false;
|
||||
}
|
||||
Recordings.ChangeState();
|
||||
Recordings.TouchUpdate();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -1245,8 +1235,6 @@ bool cRecording::ChangeName(const char *NewName)
|
||||
}
|
||||
isOnVideoDirectoryFileSystem = -1; // it might have been moved to a different file system
|
||||
ClearSortName();
|
||||
Recordings.ChangeState();
|
||||
Recordings.TouchUpdate();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -1360,59 +1348,53 @@ int cRecording::FileSizeMB(void) const
|
||||
return fileSizeMB;
|
||||
}
|
||||
|
||||
// --- cRecordings -----------------------------------------------------------
|
||||
// --- cVideoDirectoryScannerThread ------------------------------------------
|
||||
|
||||
cRecordings Recordings;
|
||||
class cVideoDirectoryScannerThread : public cThread {
|
||||
private:
|
||||
cRecordings *recordings;
|
||||
cRecordings *deletedRecordings;
|
||||
bool initial;
|
||||
void ScanVideoDir(const char *DirName, int LinkLevel = 0, int DirLevel = 0);
|
||||
protected:
|
||||
virtual void Action(void);
|
||||
public:
|
||||
cVideoDirectoryScannerThread(cRecordings *Recordings, cRecordings *DeletedRecordings);
|
||||
~cVideoDirectoryScannerThread();
|
||||
};
|
||||
|
||||
char *cRecordings::updateFileName = NULL;
|
||||
|
||||
cRecordings::cRecordings(bool Deleted)
|
||||
cVideoDirectoryScannerThread::cVideoDirectoryScannerThread(cRecordings *Recordings, cRecordings *DeletedRecordings)
|
||||
:cThread("video directory scanner", true)
|
||||
{
|
||||
deleted = Deleted;
|
||||
recordings = Recordings;
|
||||
deletedRecordings = DeletedRecordings;
|
||||
initial = true;
|
||||
lastUpdate = 0;
|
||||
state = 0;
|
||||
}
|
||||
|
||||
cRecordings::~cRecordings()
|
||||
cVideoDirectoryScannerThread::~cVideoDirectoryScannerThread()
|
||||
{
|
||||
Cancel(3);
|
||||
}
|
||||
|
||||
void cRecordings::Action(void)
|
||||
void cVideoDirectoryScannerThread::Action(void)
|
||||
{
|
||||
Refresh();
|
||||
cStateKey StateKey;
|
||||
recordings->Lock(StateKey);
|
||||
initial = recordings->Count() == 0; // no name checking if the list is initially empty
|
||||
StateKey.Remove();
|
||||
deletedRecordings->Lock(StateKey, true);
|
||||
deletedRecordings->Clear();
|
||||
StateKey.Remove();
|
||||
ScanVideoDir(cVideoDirectory::Name());
|
||||
}
|
||||
|
||||
const char *cRecordings::UpdateFileName(void)
|
||||
void cVideoDirectoryScannerThread::ScanVideoDir(const char *DirName, int LinkLevel, int DirLevel)
|
||||
{
|
||||
if (!updateFileName)
|
||||
updateFileName = strdup(AddDirectory(cVideoDirectory::Name(), ".update"));
|
||||
return updateFileName;
|
||||
}
|
||||
|
||||
void cRecordings::Refresh(bool Foreground)
|
||||
{
|
||||
lastUpdate = time(NULL); // doing this first to make sure we don't miss anything
|
||||
initial = Count() == 0; // no name checking if the list is initially empty
|
||||
if (deleted) {
|
||||
Lock();
|
||||
Clear();
|
||||
ChangeState();
|
||||
Unlock();
|
||||
}
|
||||
ScanVideoDir(cVideoDirectory::Name(), Foreground);
|
||||
}
|
||||
|
||||
bool cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLevel, int DirLevel)
|
||||
{
|
||||
bool DoChangeState = false;
|
||||
// Find any new recordings:
|
||||
cReadDir d(DirName);
|
||||
struct dirent *e;
|
||||
while ((Foreground || Running()) && (e = d.Next()) != NULL) {
|
||||
if (!Foreground && cIoThrottle::Engaged())
|
||||
while (Running() && (e = d.Next()) != NULL) {
|
||||
if (cIoThrottle::Engaged())
|
||||
cCondWait::SleepMs(100);
|
||||
cString buffer = AddDirectory(DirName, e->d_name);
|
||||
struct stat st;
|
||||
@ -1428,57 +1410,73 @@ bool cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLev
|
||||
continue;
|
||||
}
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
if (endswith(buffer, deleted ? DELEXT : RECEXT)) {
|
||||
if (deleted || initial || !GetByName(buffer)) {
|
||||
cRecordings *Recordings = NULL;
|
||||
if (endswith(buffer, RECEXT))
|
||||
Recordings = recordings;
|
||||
else if (endswith(buffer, DELEXT))
|
||||
Recordings = deletedRecordings;
|
||||
if (Recordings) {
|
||||
cStateKey StateKey;
|
||||
Recordings->Lock(StateKey, true);
|
||||
if (Recordings == deletedRecordings || initial || !Recordings->GetByName(buffer)) {
|
||||
cRecording *r = new cRecording(buffer);
|
||||
if (r->Name()) {
|
||||
r->NumFrames(); // initializes the numFrames member
|
||||
r->FileSizeMB(); // initializes the fileSizeMB member
|
||||
r->IsOnVideoDirectoryFileSystem(); // initializes the isOnVideoDirectoryFileSystem member
|
||||
if (deleted)
|
||||
r->deleted = time(NULL);
|
||||
Lock();
|
||||
Add(r);
|
||||
if (initial)
|
||||
ChangeState();
|
||||
else
|
||||
DoChangeState = true;
|
||||
Unlock();
|
||||
if (Recordings == deletedRecordings)
|
||||
r->SetDeleted();
|
||||
Recordings->Add(r);
|
||||
}
|
||||
else
|
||||
delete r;
|
||||
}
|
||||
StateKey.Remove();
|
||||
}
|
||||
else
|
||||
DoChangeState |= ScanVideoDir(buffer, Foreground, LinkLevel + Link, DirLevel + 1);
|
||||
ScanVideoDir(buffer, LinkLevel + Link, DirLevel + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle any vanished recordings:
|
||||
if (!deleted && !initial && DirLevel == 0) {
|
||||
for (cRecording *recording = First(); recording; ) {
|
||||
cRecording *r = recording;
|
||||
recording = Next(recording);
|
||||
if (access(r->FileName(), F_OK) != 0) {
|
||||
Lock();
|
||||
Del(r, false);
|
||||
VanishedRecordings.Add(r);
|
||||
DoChangeState = true;
|
||||
Unlock();
|
||||
}
|
||||
if (!initial && DirLevel == 0) {
|
||||
cStateKey StateKey;
|
||||
recordings->Lock(StateKey, true);
|
||||
for (cRecording *Recording = recordings->First(); Recording; ) {
|
||||
cRecording *r = Recording;
|
||||
Recording = recordings->Next(Recording);
|
||||
if (access(r->FileName(), F_OK) != 0)
|
||||
recordings->Del(r);
|
||||
}
|
||||
StateKey.Remove();
|
||||
}
|
||||
if (DoChangeState && DirLevel == 0)
|
||||
ChangeState();
|
||||
return DoChangeState;
|
||||
}
|
||||
|
||||
bool cRecordings::StateChanged(int &State)
|
||||
// --- cRecordings -----------------------------------------------------------
|
||||
|
||||
cRecordings cRecordings::recordings;
|
||||
cRecordings cRecordings::deletedRecordings(true);
|
||||
char *cRecordings::updateFileName = NULL;
|
||||
cVideoDirectoryScannerThread *cRecordings::videoDirectoryScannerThread = NULL;
|
||||
time_t cRecordings::lastUpdate = 0;
|
||||
|
||||
cRecordings::cRecordings(bool Deleted)
|
||||
:cList<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"));
|
||||
|
105
recording.h
105
recording.h
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: recording.h 4.2 2015/04/28 09:26:02 kls Exp $
|
||||
* $Id: recording.h 4.3 2015/08/29 14:12:14 kls Exp $
|
||||
*/
|
||||
|
||||
#ifndef __RECORDING_H
|
||||
@ -41,7 +41,6 @@ enum eRecordingUsage {
|
||||
};
|
||||
|
||||
void RemoveDeletedRecordings(void);
|
||||
void ClearVanishedRecordings(void);
|
||||
void AssertFreeDiskSpace(int Priority = 0, bool Force = false);
|
||||
///< The special Priority value -1 means that we shall get rid of any
|
||||
///< deleted recordings faster than normal (because we're cutting).
|
||||
@ -129,8 +128,9 @@ public:
|
||||
int Priority(void) const { return priority; }
|
||||
int Lifetime(void) const { return lifetime; }
|
||||
time_t Deleted(void) const { return deleted; }
|
||||
void SetDeleted(void) { deleted = time(NULL); }
|
||||
virtual int Compare(const cListObject &ListObject) const;
|
||||
bool IsInPath(const char *Path);
|
||||
bool IsInPath(const char *Path) const;
|
||||
///< Returns true if this recording is stored anywhere under the given Path.
|
||||
///< If Path is NULL or an empty string, the entire video directory is checked.
|
||||
cString Folder(void) const;
|
||||
@ -140,7 +140,7 @@ public:
|
||||
///< Returns the base name of this recording (without the
|
||||
///< video directory and folder). For use in menus etc.
|
||||
const char *Name(void) const { return name; }
|
||||
///< Returns the full name of the recording (without the video directory.
|
||||
///< Returns the full name of the recording (without the video directory).
|
||||
///< For use in menus etc.
|
||||
const char *FileName(void) const;
|
||||
///< Returns the full path name to the recording directory, including the
|
||||
@ -166,7 +166,7 @@ public:
|
||||
bool IsEdited(void) const;
|
||||
bool IsPesRecording(void) const { return isPesRecording; }
|
||||
bool IsOnVideoDirectoryFileSystem(void) const;
|
||||
bool HasMarks(void);
|
||||
bool HasMarks(void) const;
|
||||
///< Returns true if this recording has any editing marks.
|
||||
bool DeleteMarks(void);
|
||||
///< Deletes the editing marks from this recording (if any).
|
||||
@ -216,49 +216,54 @@ public:
|
||||
///< as in time-shift).
|
||||
};
|
||||
|
||||
class cRecordings : public cList<cRecording>, public cThread {
|
||||
class cVideoDirectoryScannerThread;
|
||||
|
||||
class cRecordings : public cList<cRecording> {
|
||||
private:
|
||||
static cRecordings recordings;
|
||||
static cRecordings deletedRecordings;
|
||||
static char *updateFileName;
|
||||
bool deleted;
|
||||
bool initial;
|
||||
time_t lastUpdate;
|
||||
int state;
|
||||
const char *UpdateFileName(void);
|
||||
void Refresh(bool Foreground = false);
|
||||
bool ScanVideoDir(const char *DirName, bool Foreground = false, int LinkLevel = 0, int DirLevel = 0);
|
||||
protected:
|
||||
virtual void Action(void);
|
||||
static time_t lastUpdate;
|
||||
static cVideoDirectoryScannerThread *videoDirectoryScannerThread;
|
||||
static const char *UpdateFileName(void);
|
||||
public:
|
||||
cRecordings(bool Deleted = false);
|
||||
virtual ~cRecordings();
|
||||
bool Load(void) { return Update(true); }
|
||||
///< Loads the current list of recordings and returns true if there
|
||||
///< is anything in it (for compatibility with older plugins - use
|
||||
///< Update(true) instead).
|
||||
bool Update(bool Wait = false);
|
||||
static const cRecordings *GetRecordingsRead(cStateKey &StateKey, int TimeoutMs = 0) { return recordings.Lock(StateKey, false, TimeoutMs) ? &recordings : NULL; }
|
||||
///< Gets the list of recordings for read access.
|
||||
///< See cTimers::GetTimersRead() for details.
|
||||
static cRecordings *GetRecordingsWrite(cStateKey &StateKey, int TimeoutMs = 0) { return recordings.Lock(StateKey, true, TimeoutMs) ? &recordings : NULL; }
|
||||
///< Gets the list of recordings for write access.
|
||||
///< See cTimers::GetTimersWrite() for details.
|
||||
static const cRecordings *GetDeletedRecordingsRead(cStateKey &StateKey, int TimeoutMs = 0) { return deletedRecordings.Lock(StateKey, false, TimeoutMs) ? &deletedRecordings : NULL; }
|
||||
///< Gets the list of deleted recordings for read access.
|
||||
///< See cTimers::GetTimersRead() for details.
|
||||
static cRecordings *GetDeletedRecordingsWrite(cStateKey &StateKey, int TimeoutMs = 0) { return deletedRecordings.Lock(StateKey, true, TimeoutMs) ? &deletedRecordings : NULL; }
|
||||
///< Gets the list of deleted recordings for write access.
|
||||
///< See cTimers::GetTimersWrite() for details.
|
||||
static void Update(bool Wait = false);
|
||||
///< Triggers an update of the list of recordings, which will run
|
||||
///< as a separate thread if Wait is false. If Wait is true, the
|
||||
///< function returns only after the update has completed.
|
||||
///< Returns true if Wait is true and there is anything in the list
|
||||
///< of recordings, false otherwise.
|
||||
void TouchUpdate(void);
|
||||
static void TouchUpdate(void);
|
||||
///< Touches the '.update' file in the video directory, so that other
|
||||
///< instances of VDR that access the same video directory can be triggered
|
||||
///< to update their recordings list.
|
||||
bool NeedsUpdate(void);
|
||||
void ChangeState(void) { state++; }
|
||||
bool StateChanged(int &State);
|
||||
///< This function is 'const', because it doesn't actually modify the list
|
||||
///< of recordings.
|
||||
static bool NeedsUpdate(void);
|
||||
void ResetResume(const char *ResumeFileName = NULL);
|
||||
void ClearSortNames(void);
|
||||
cRecording *GetByName(const char *FileName);
|
||||
const cRecording *GetByName(const char *FileName) const;
|
||||
cRecording *GetByName(const char *FileName) { return const_cast<cRecording *>(static_cast<const cRecordings *>(this)->GetByName(FileName)); }
|
||||
void AddByName(const char *FileName, bool TriggerUpdate = true);
|
||||
void DelByName(const char *FileName);
|
||||
void UpdateByName(const char *FileName);
|
||||
int TotalFileSizeMB(void);
|
||||
double MBperMinute(void);
|
||||
int TotalFileSizeMB(void) const;
|
||||
double MBperMinute(void) const;
|
||||
///< Returns the average data rate (in MB/min) of all recordings, or -1 if
|
||||
///< this value is unknown.
|
||||
int PathIsInUse(const char *Path);
|
||||
int PathIsInUse(const char *Path) const;
|
||||
///< Checks whether any recording in the given Path is currently in use and therefore
|
||||
///< the whole Path shall not be tampered with. Returns 0 (ruNone) if no recording
|
||||
///< is in use.
|
||||
@ -266,7 +271,7 @@ public:
|
||||
///< If several recordings in the Path are currently in use, the return value will
|
||||
///< be the combination of all individual recordings' flags.
|
||||
///< If Path is NULL or an empty string, the entire video directory is checked.
|
||||
int GetNumRecordingsInPath(const char *Path);
|
||||
int GetNumRecordingsInPath(const char *Path) const;
|
||||
///< Returns the total number of recordings in the given Path, including all
|
||||
///< sub-folders of Path.
|
||||
///< If Path is NULL or an empty string, the entire video directory is checked.
|
||||
@ -281,10 +286,19 @@ public:
|
||||
///< if all recordings have been successfully added to the RecordingsHandler.
|
||||
};
|
||||
|
||||
/// Any access to Recordings that loops through the list of recordings
|
||||
/// needs to hold a thread lock on this object!
|
||||
extern cRecordings Recordings;
|
||||
extern cRecordings DeletedRecordings;
|
||||
// Provide lock controlled access to the list:
|
||||
|
||||
DEF_LIST_LOCK(Recordings);
|
||||
DEF_LIST_LOCK2(Recordings, DeletedRecordings);
|
||||
|
||||
// These macros provide a convenient way of locking the global recordings list
|
||||
// and making sure the lock is released as soon as the current scope is left
|
||||
// (note that these macros wait forever to obtain the lock!):
|
||||
|
||||
#define LOCK_RECORDINGS_READ USE_LIST_LOCK_READ(Recordings)
|
||||
#define LOCK_RECORDINGS_WRITE USE_LIST_LOCK_WRITE(Recordings)
|
||||
#define LOCK_DELETEDRECORDINGS_READ USE_LIST_LOCK_READ2(Recordings, DeletedRecordings)
|
||||
#define LOCK_DELETEDRECORDINGS_WRITE USE_LIST_LOCK_WRITE2(Recordings, DeletedRecordings)
|
||||
|
||||
class cRecordingsHandlerEntry;
|
||||
|
||||
@ -350,7 +364,7 @@ public:
|
||||
bool Save(FILE *f);
|
||||
};
|
||||
|
||||
class cMarks : public cConfig<cMark>, public cMutex {
|
||||
class cMarks : public cConfig<cMark> {
|
||||
private:
|
||||
cString recordingFileName;
|
||||
cString fileName;
|
||||
@ -360,9 +374,11 @@ private:
|
||||
time_t lastFileTime;
|
||||
time_t lastChange;
|
||||
public:
|
||||
cMarks(void): cConfig<cMark>("Marks") {};
|
||||
static cString MarksFileName(const cRecording *Recording);
|
||||
///< Returns the marks file name for the given Recording (regardless whether such
|
||||
///< a file actually exists).
|
||||
static bool DeleteMarksFile(const cRecording *Recording);
|
||||
bool Load(const char *RecordingFileName, double FramesPerSecond = DEFAULTFRAMESPERSECOND, bool IsPesRecording = false);
|
||||
bool Update(void);
|
||||
bool Save(void);
|
||||
@ -374,22 +390,27 @@ public:
|
||||
///< calls to Del(), or any of the functions that return a "cMark *", in case
|
||||
///< an other thread might modifiy the list while the returned pointer is
|
||||
///< considered valid.
|
||||
cMark *Get(int Position);
|
||||
cMark *GetPrev(int Position);
|
||||
cMark *GetNext(int Position);
|
||||
cMark *GetNextBegin(cMark *EndMark = NULL);
|
||||
const cMark *Get(int Position) const;
|
||||
const cMark *GetPrev(int Position) const;
|
||||
const cMark *GetNext(int Position) const;
|
||||
const cMark *GetNextBegin(const cMark *EndMark = NULL) const;
|
||||
///< Returns the next "begin" mark after EndMark, skipping any marks at the
|
||||
///< same position as EndMark. If EndMark is NULL, the first actual "begin"
|
||||
///< will be returned (if any).
|
||||
cMark *GetNextEnd(cMark *BeginMark);
|
||||
const cMark *GetNextEnd(const cMark *BeginMark) const;
|
||||
///< Returns the next "end" mark after BeginMark, skipping any marks at the
|
||||
///< same position as BeginMark.
|
||||
int GetNumSequences(void);
|
||||
int GetNumSequences(void) const;
|
||||
///< Returns the actual number of sequences to be cut from the recording.
|
||||
///< If there is only one actual "begin" mark, and it is positioned at index
|
||||
///< 0 (the beginning of the recording), and there is no "end" mark, the
|
||||
///< return value is 0, which means that the result is the same as the original
|
||||
///< recording.
|
||||
cMark *Get(int Position) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->Get(Position)); }
|
||||
cMark *GetPrev(int Position) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetPrev(Position)); }
|
||||
cMark *GetNext(int Position) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetNext(Position)); }
|
||||
cMark *GetNextBegin(const cMark *EndMark = NULL) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetNextBegin(EndMark)); }
|
||||
cMark *GetNextEnd(const cMark *BeginMark) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetNextEnd(BeginMark)); }
|
||||
};
|
||||
|
||||
#define RUC_BEFORERECORDING "before"
|
||||
|
52
sdt.c
52
sdt.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: sdt.c 4.4 2015/03/17 15:09:54 kls Exp $
|
||||
* $Id: sdt.c 4.5 2015/08/02 11:33:23 kls Exp $
|
||||
*/
|
||||
|
||||
#include "sdt.h"
|
||||
@ -52,18 +52,21 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
return;
|
||||
if (!sectionSyncer.Sync(sdt.getVersionNumber(), sdt.getSectionNumber(), sdt.getLastSectionNumber()))
|
||||
return;
|
||||
if (!Channels.Lock(true, 10)) {
|
||||
cStateKey StateKey;
|
||||
cChannels *Channels = cChannels::GetChannelsWrite(StateKey, 10);
|
||||
if (!Channels) {
|
||||
sectionSyncer.Repeat(); // let's not miss any section of the SDT
|
||||
return;
|
||||
}
|
||||
dbgsdt("SDT: %2d %2d %2d %s %d\n", sdt.getVersionNumber(), sdt.getSectionNumber(), sdt.getLastSectionNumber(), *cSource::ToString(source), Transponder());
|
||||
bool ChannelsModified = false;
|
||||
SI::SDT::Service SiSdtService;
|
||||
for (SI::Loop::Iterator it; sdt.serviceLoop.getNext(SiSdtService, it); ) {
|
||||
cChannel *channel = Channels.GetByChannelID(tChannelID(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId()));
|
||||
if (!channel)
|
||||
channel = Channels.GetByChannelID(tChannelID(source, 0, Transponder(), SiSdtService.getServiceId()));
|
||||
if (channel)
|
||||
channel->SetSeen();
|
||||
cChannel *Channel = Channels->GetByChannelID(tChannelID(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId()));
|
||||
if (!Channel)
|
||||
Channel = Channels->GetByChannelID(tChannelID(source, 0, Transponder(), SiSdtService.getServiceId()));
|
||||
if (Channel)
|
||||
Channel->SetSeen();
|
||||
|
||||
cLinkChannels *LinkChannels = NULL;
|
||||
SI::Descriptor *d;
|
||||
@ -101,20 +104,21 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
}
|
||||
sd->providerName.getText(ProviderNameBuf, sizeof(ProviderNameBuf));
|
||||
char *pp = compactspace(ProviderNameBuf);
|
||||
if (channel) {
|
||||
channel->SetId(sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId());
|
||||
if (Channel) {
|
||||
ChannelsModified |= Channel->SetId(Channels, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId());
|
||||
if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3)
|
||||
channel->SetName(pn, ps, pp);
|
||||
ChannelsModified |= Channel->SetName(pn, ps, pp);
|
||||
// Using SiSdtService.getFreeCaMode() is no good, because some
|
||||
// tv stations set this flag even for non-encrypted channels :-(
|
||||
// The special value 0xFFFF was supposed to mean "unknown encryption"
|
||||
// and would have been overwritten with real CA values later:
|
||||
// channel->SetCa(SiSdtService.getFreeCaMode() ? 0xFFFF : 0);
|
||||
// Channel->SetCa(SiSdtService.getFreeCaMode() ? 0xFFFF : 0);
|
||||
}
|
||||
else if (*pn && Setup.UpdateChannels >= 4) {
|
||||
dbgsdt(" %5d %5d %5d %s/%s %d %s\n", sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId(), *cSource::ToString(Channel()->Source()), *cSource::ToString(source), Channel()->Transponder(), pn);
|
||||
channel = Channels.NewChannel(Channel(), pn, ps, pp, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId());
|
||||
channel->SetSource(source); // in case this comes from a satellite with a slightly different position
|
||||
dbgsdt(" %5d %5d %5d %s/%s %d %s\n", sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId(), *cSource::ToString(this->Channel()->Source()), *cSource::ToString(source), this->Channel()->Transponder(), pn);
|
||||
Channel = Channels->NewChannel(this->Channel(), pn, ps, pp, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId());
|
||||
Channel->SetSource(source); // in case this comes from a satellite with a slightly different position
|
||||
ChannelsModified = true;
|
||||
patFilter->Trigger(SiSdtService.getServiceId());
|
||||
}
|
||||
}
|
||||
@ -127,9 +131,9 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
/*
|
||||
case SI::CaIdentifierDescriptorTag: {
|
||||
SI::CaIdentifierDescriptor *cid = (SI::CaIdentifierDescriptor *)d;
|
||||
if (channel) {
|
||||
if (Channel) {
|
||||
for (SI::Loop::Iterator it; cid->identifiers.hasNext(it); )
|
||||
channel->SetCa(cid->identifiers.getNext(it));
|
||||
Channel->SetCa(Channels, cid->identifiers.getNext(it));
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -138,15 +142,17 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
SI::NVODReferenceDescriptor *nrd = (SI::NVODReferenceDescriptor *)d;
|
||||
SI::NVODReferenceDescriptor::Service Service;
|
||||
for (SI::Loop::Iterator it; nrd->serviceLoop.getNext(Service, it); ) {
|
||||
cChannel *link = Channels.GetByChannelID(tChannelID(source, Service.getOriginalNetworkId(), Service.getTransportStream(), Service.getServiceId()));
|
||||
cChannel *link = Channels->GetByChannelID(tChannelID(source, Service.getOriginalNetworkId(), Service.getTransportStream(), Service.getServiceId()));
|
||||
if (!link && Setup.UpdateChannels >= 4) {
|
||||
link = Channels.NewChannel(Channel(), "NVOD", "", "", Service.getOriginalNetworkId(), Service.getTransportStream(), Service.getServiceId());
|
||||
link = Channels->NewChannel(this->Channel(), "NVOD", "", "", Service.getOriginalNetworkId(), Service.getTransportStream(), Service.getServiceId());
|
||||
patFilter->Trigger(Service.getServiceId());
|
||||
ChannelsModified = true;
|
||||
}
|
||||
if (link) {
|
||||
if (!LinkChannels)
|
||||
LinkChannels = new cLinkChannels;
|
||||
LinkChannels->Add(new cLinkChannel(link));
|
||||
ChannelsModified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -156,18 +162,18 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
||||
delete d;
|
||||
}
|
||||
if (LinkChannels) {
|
||||
if (channel)
|
||||
channel->SetLinkChannels(LinkChannels);
|
||||
if (Channel)
|
||||
ChannelsModified |= Channel->SetLinkChannels(LinkChannels);
|
||||
else
|
||||
delete LinkChannels;
|
||||
}
|
||||
}
|
||||
if (sdt.getSectionNumber() == sdt.getLastSectionNumber()) {
|
||||
if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3) {
|
||||
Channels.MarkObsoleteChannels(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId());
|
||||
ChannelsModified |= Channels->MarkObsoleteChannels(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId());
|
||||
if (source != Source())
|
||||
Channels.MarkObsoleteChannels(Source(), sdt.getOriginalNetworkId(), sdt.getTransportStreamId());
|
||||
ChannelsModified |= Channels->MarkObsoleteChannels(Source(), sdt.getOriginalNetworkId(), sdt.getTransportStreamId());
|
||||
}
|
||||
}
|
||||
Channels.Unlock();
|
||||
StateKey.Remove(ChannelsModified);
|
||||
}
|
||||
|
29
shutdown.c
29
shutdown.c
@ -6,7 +6,7 @@
|
||||
*
|
||||
* Original version written by Udo Richter <udo_richter@gmx.de>.
|
||||
*
|
||||
* $Id: shutdown.c 3.1 2013/10/02 09:02:01 kls Exp $
|
||||
* $Id: shutdown.c 4.1 2015/07/18 11:29:26 kls Exp $
|
||||
*/
|
||||
|
||||
#include "shutdown.h"
|
||||
@ -172,9 +172,10 @@ bool cShutdownHandler::ConfirmShutdown(bool Interactive)
|
||||
return false;
|
||||
}
|
||||
|
||||
cTimer *timer = Timers.GetNextActiveTimer();
|
||||
time_t Next = timer ? timer->StartTime() : 0;
|
||||
time_t Delta = timer ? Next - time(NULL) : 0;
|
||||
LOCK_TIMERS_READ;
|
||||
const cTimer *Timer = Timers->GetNextActiveTimer();
|
||||
time_t Next = Timer ? Timer->StartTime() : 0;
|
||||
time_t Delta = Timer ? Next - time(NULL) : 0;
|
||||
|
||||
if (cRecordControls::Active() || (Next && Delta <= 0)) {
|
||||
// VPS recordings in timer end margin may cause Delta <= 0
|
||||
@ -215,9 +216,10 @@ bool cShutdownHandler::ConfirmRestart(bool Interactive)
|
||||
return false;
|
||||
}
|
||||
|
||||
cTimer *timer = Timers.GetNextActiveTimer();
|
||||
time_t Next = timer ? timer->StartTime() : 0;
|
||||
time_t Delta = timer ? Next - time(NULL) : 0;
|
||||
LOCK_TIMERS_READ;
|
||||
const cTimer *Timer = Timers->GetNextActiveTimer();
|
||||
time_t Next = Timer ? Timer->StartTime() : 0;
|
||||
time_t Delta = Timer ? Next - time(NULL) : 0;
|
||||
|
||||
if (cRecordControls::Active() || (Next && Delta <= 0)) {
|
||||
// VPS recordings in timer end margin may cause Delta <= 0
|
||||
@ -233,15 +235,16 @@ bool cShutdownHandler::ConfirmRestart(bool Interactive)
|
||||
|
||||
bool cShutdownHandler::DoShutdown(bool Force)
|
||||
{
|
||||
LOCK_TIMERS_READ;
|
||||
time_t Now = time(NULL);
|
||||
cTimer *timer = Timers.GetNextActiveTimer();
|
||||
const cTimer *Timer = Timers->GetNextActiveTimer();
|
||||
cPlugin *Plugin = cPluginManager::GetNextWakeupPlugin();
|
||||
|
||||
time_t Next = timer ? timer->StartTime() : 0;
|
||||
time_t Next = Timer ? Timer->StartTime() : 0;
|
||||
time_t NextPlugin = Plugin ? Plugin->WakeupTime() : 0;
|
||||
if (NextPlugin && (!Next || Next > NextPlugin)) {
|
||||
Next = NextPlugin;
|
||||
timer = NULL;
|
||||
Timer = NULL;
|
||||
}
|
||||
time_t Delta = Next ? Next - Now : 0;
|
||||
|
||||
@ -250,13 +253,13 @@ bool cShutdownHandler::DoShutdown(bool Force)
|
||||
return false;
|
||||
Delta = Setup.MinEventTimeout * 60;
|
||||
Next = Now + Delta;
|
||||
timer = NULL;
|
||||
Timer = NULL;
|
||||
dsyslog("reboot at %s", *TimeToString(Next));
|
||||
}
|
||||
|
||||
if (Next && timer) {
|
||||
if (Next && Timer) {
|
||||
dsyslog("next timer event at %s", *TimeToString(Next));
|
||||
CallShutdownCommand(Next, timer->Channel()->Number(), timer->File(), Force);
|
||||
CallShutdownCommand(Next, Timer->Channel()->Number(), Timer->File(), Force);
|
||||
}
|
||||
else if (Next && Plugin) {
|
||||
CallShutdownCommand(Next, 0, Plugin->Name(), Force);
|
||||
|
67
skinlcars.c
67
skinlcars.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: skinlcars.c 3.8 2014/06/12 08:48:15 kls Exp $
|
||||
* $Id: skinlcars.c 4.1 2015/09/01 10:07:07 kls Exp $
|
||||
*/
|
||||
|
||||
// "Star Trek: The Next Generation"(R) is a registered trademark of Paramount Pictures,
|
||||
@ -710,7 +710,7 @@ private:
|
||||
int lastDiskUsageState;
|
||||
bool lastDiskAlert;
|
||||
double lastSystemLoad;
|
||||
int lastTimersState;
|
||||
cStateKey timersStateKey;
|
||||
time_t lastSignalDisplay;
|
||||
int lastLiveIndicatorY;
|
||||
bool lastLiveIndicatorTransferring;
|
||||
@ -775,7 +775,6 @@ cSkinLCARSDisplayMenu::cSkinLCARSDisplayMenu(void)
|
||||
lastEvent = NULL;
|
||||
lastRecording = NULL;
|
||||
lastSeen = -1;
|
||||
lastTimersState = -1;
|
||||
lastSignalDisplay = 0;
|
||||
lastLiveIndicatorY = -1;
|
||||
lastLiveIndicatorTransferring = false;
|
||||
@ -970,7 +969,7 @@ void cSkinLCARSDisplayMenu::SetMenuCategory(eMenuCategory MenuCategory)
|
||||
xi01 = xm03;
|
||||
xi02 = xm04;
|
||||
xi03 = xm05;
|
||||
lastTimersState = -1;
|
||||
timersStateKey.Reset();
|
||||
DrawMainFrameLower();
|
||||
DrawMainBracket();
|
||||
DrawStatusElbows();
|
||||
@ -1237,19 +1236,22 @@ void cSkinLCARSDisplayMenu::DrawTimer(const cTimer *Timer, int y, bool MultiRec)
|
||||
}
|
||||
if (Event)
|
||||
osd->DrawText(xs00 + d, y + lineHeight - tinyFont->Height(), Event->Title(), ColorFg, ColorBg, tinyFont, xs03 - xs00 - 2 * d);
|
||||
// The remote timer indicator:
|
||||
if (Timer->Remote())
|
||||
osd->DrawRectangle(xs00 - (lineHeight - Gap) / 2, y, xs00 - Gap - 1, y + lineHeight - 1, Timer->Recording() ? Theme.Color(clrMenuTimerRecording) : ColorBg);
|
||||
// The timer recording indicator:
|
||||
if (Timer->Recording())
|
||||
else if (Timer->Recording())
|
||||
osd->DrawRectangle(xs03 + Gap, y - (MultiRec ? Gap : 0), xs04 - Gap / 2 - 1, y + lineHeight - 1, Theme.Color(clrMenuTimerRecording));
|
||||
}
|
||||
|
||||
void cSkinLCARSDisplayMenu::DrawTimers(void)
|
||||
{
|
||||
if (Timers.Modified(lastTimersState)) {
|
||||
if (const cTimers *Timers = cTimers::GetTimersRead(timersStateKey)) {
|
||||
deviceRecording.Clear();
|
||||
const cFont *font = cFont::GetFont(fontOsd);
|
||||
osd->DrawRectangle(xs00, ys04, xs04 - 1, ys05 - 1, Theme.Color(clrBackground));
|
||||
osd->DrawRectangle(xs00 - (lineHeight - Gap) / 2, ys04, xs04 - 1, ys05 - 1, Theme.Color(clrBackground));
|
||||
osd->DrawRectangle(xs07, ys04, xs13 - 1, ys05 - 1, Theme.Color(clrBackground));
|
||||
cSortedTimers SortedTimers;
|
||||
cSortedTimers SortedTimers(Timers);
|
||||
cVector<int> FreeDeviceSlots;
|
||||
int NumDevices = 0;
|
||||
int y = ys04;
|
||||
@ -1262,7 +1264,14 @@ void cSkinLCARSDisplayMenu::DrawTimers(void)
|
||||
break;
|
||||
if (const cTimer *Timer = SortedTimers[i]) {
|
||||
if (Timer->Recording()) {
|
||||
if (cRecordControl *RecordControl = cRecordControls::GetRecordControl(Timer)) {
|
||||
if (Timer->Remote()) {
|
||||
if (!Device && Timer->HasFlags(tfActive)) {
|
||||
DrawTimer(Timer, y, false);
|
||||
FreeDeviceSlots.Append(y);
|
||||
y += lineHeight + Gap;
|
||||
}
|
||||
}
|
||||
else if (cRecordControl *RecordControl = cRecordControls::GetRecordControl(Timer)) {
|
||||
if (!Device || Device == RecordControl->Device()) {
|
||||
DrawTimer(Timer, y, NumTimers > 0);
|
||||
NumTimers++;
|
||||
@ -1313,7 +1322,7 @@ void cSkinLCARSDisplayMenu::DrawTimers(void)
|
||||
}
|
||||
// Total number of active timers:
|
||||
int NumTimers = 0;
|
||||
for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
|
||||
for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
|
||||
if (Timer->HasFlags(tfActive))
|
||||
NumTimers++;
|
||||
}
|
||||
@ -1321,6 +1330,7 @@ void cSkinLCARSDisplayMenu::DrawTimers(void)
|
||||
osd->DrawText(xs08, ys00, itoa(NumDevices), Theme.Color(clrMenuFrameFg), frameColor, font, xs09 - xs08, ys01 - ys00, taBottom | taRight | taBorder);
|
||||
lastSignalDisplay = 0;
|
||||
initial = true; // forces redrawing of devices
|
||||
timersStateKey.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1423,25 +1433,23 @@ void cSkinLCARSDisplayMenu::DrawLive(const cChannel *Channel)
|
||||
DrawSeen(0, 0);
|
||||
}
|
||||
// The current programme:
|
||||
cSchedulesLock SchedulesLock;
|
||||
if (const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock)) {
|
||||
if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) {
|
||||
const cEvent *Event = Schedule->GetPresentEvent();
|
||||
if (initial || Event != lastEvent) {
|
||||
DrawInfo(Event, true);
|
||||
lastEvent = Event;
|
||||
lastSeen = -1;
|
||||
}
|
||||
int Current = 0;
|
||||
int Total = 0;
|
||||
if (Event) {
|
||||
time_t t = time(NULL);
|
||||
if (t > Event->StartTime())
|
||||
Current = t - Event->StartTime();
|
||||
Total = Event->Duration();
|
||||
}
|
||||
DrawSeen(Current, Total);
|
||||
LOCK_SCHEDULES_READ;
|
||||
if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) {
|
||||
const cEvent *Event = Schedule->GetPresentEvent();
|
||||
if (initial || Event != lastEvent) {
|
||||
DrawInfo(Event, true);
|
||||
lastEvent = Event;
|
||||
lastSeen = -1;
|
||||
}
|
||||
int Current = 0;
|
||||
int Total = 0;
|
||||
if (Event) {
|
||||
time_t t = time(NULL);
|
||||
if (t > Event->StartTime())
|
||||
Current = t - Event->StartTime();
|
||||
Total = Event->Duration();
|
||||
}
|
||||
DrawSeen(Current, Total);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1731,7 +1739,8 @@ void cSkinLCARSDisplayMenu::Flush(void)
|
||||
if (MenuCategory() == mcMain) {
|
||||
cDevice *Device = cDevice::PrimaryDevice();
|
||||
if (!Device->Replaying() || Device->Transferring()) {
|
||||
const cChannel *Channel = Channels.GetByNumber(cDevice::PrimaryDevice()->CurrentChannel());
|
||||
LOCK_CHANNELS_READ;
|
||||
const cChannel *Channel = Channels->GetByNumber(cDevice::PrimaryDevice()->CurrentChannel());
|
||||
DrawLive(Channel);
|
||||
}
|
||||
else if (cControl *Control = cControl::Control(true))
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
9
status.h
9
status.h
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: status.h 3.1 2014/01/25 10:47:39 kls Exp $
|
||||
* $Id: status.h 4.1 2015/08/02 10:34:44 kls Exp $
|
||||
*/
|
||||
|
||||
#ifndef __STATUS_H
|
||||
@ -15,7 +15,7 @@
|
||||
#include "player.h"
|
||||
#include "tools.h"
|
||||
|
||||
enum eTimerChange { tcMod, tcAdd, tcDel };
|
||||
enum eTimerChange { tcMod, tcAdd, tcDel }; // tcMod is obsolete and no longer used!
|
||||
|
||||
class cTimer;
|
||||
|
||||
@ -29,10 +29,7 @@ protected:
|
||||
// require a retune.
|
||||
virtual void TimerChange(const cTimer *Timer, eTimerChange Change) {}
|
||||
// Indicates a change in the timer settings.
|
||||
// If Change is tcAdd or tcDel, Timer points to the timer that has
|
||||
// been added or will be deleted, respectively. In case of tcMod,
|
||||
// Timer is NULL; this indicates that some timer has been changed.
|
||||
// Note that tcAdd and tcDel are always also followed by a tcMod.
|
||||
// Timer points to the timer that has been added or will be deleted, respectively.
|
||||
virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber, bool LiveView) {}
|
||||
// Indicates a channel switch on the given DVB device.
|
||||
// If ChannelNumber is 0, this is before the channel is being switched,
|
||||
|
21
svdrp.h
21
svdrp.h
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: svdrp.h 4.2 2015/05/22 13:44:43 kls Exp $
|
||||
* $Id: svdrp.h 4.3 2015/09/01 10:34:09 kls Exp $
|
||||
*/
|
||||
|
||||
#ifndef __SVDRP_H
|
||||
@ -40,20 +40,35 @@ public:
|
||||
///< to the command. The response strings are exactly as received,
|
||||
///< with the leading three digit reply code and possible continuation
|
||||
///< line indicator ('-') in place.
|
||||
const char *Response(int Index) { return (Index > 0 && Index < response.Size()) ? response[Index] : NULL; }
|
||||
const char *Response(int Index) { return (Index >= 0 && Index < response.Size()) ? response[Index] : NULL; }
|
||||
///< This is a convenience function for accessing the response strings.
|
||||
///< Returns the string at the given Index, or NULL if Index is out
|
||||
///< of range.
|
||||
int Code(const char *s) { return s ? atoi(s) : 0; }
|
||||
///< Returns the value of the three digit reply code of the given
|
||||
///< response string.
|
||||
const char *Value(const char *s) { return s && s[0] && s[1] && s[2] && s[3] ? s + 4 : NULL; }
|
||||
///< Returns the actual value of the given response string, skipping
|
||||
///< the three digit reply code and possible continuation line indicator.
|
||||
};
|
||||
|
||||
enum eSvdrpFetchFlags {
|
||||
sffNone = 0b0000,
|
||||
sffTimers = 0b0001,
|
||||
};
|
||||
|
||||
const char *SVDRPHostName(void);
|
||||
void SetSVDRPGrabImageDir(const char *GrabImageDir);
|
||||
void StartSVDRPHandler(int TcpPort, int UdpPort);
|
||||
void StopSVDRPHandler(void);
|
||||
void SendSVDRPDiscover(const char *Address = NULL);
|
||||
bool GetSVDRPServerNames(cStringList *ServerNames);
|
||||
bool GetSVDRPServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlag = sffNone);
|
||||
///< Gets a list of all available VDRs this VDR is connected to via SVDRP,
|
||||
///< and stores it in the given ServerNames list. The list is cleared
|
||||
///< before getting the server names.
|
||||
///< If FetchFlag is given, only the server names for which the local
|
||||
///< client has this flag set will be returned, and the client's flag
|
||||
///< will be cleared.
|
||||
///< Returns true if the resulting list is not empty.
|
||||
|
||||
#endif //__SVDRP_H
|
||||
|
138
thread.c
138
thread.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: thread.c 3.2 2013/12/29 17:21:53 kls Exp $
|
||||
* $Id: thread.c 4.1 2015/08/29 14:43:03 kls Exp $
|
||||
*/
|
||||
|
||||
#include "thread.h"
|
||||
@ -21,6 +21,16 @@
|
||||
#include <unistd.h>
|
||||
#include "tools.h"
|
||||
|
||||
#define ABORT { dsyslog("ABORT!"); abort(); } // use debugger to trace back the problem
|
||||
|
||||
//#define DEBUG_LOCKING // uncomment this line to activate debug output for locking
|
||||
|
||||
#ifdef DEBUG_LOCKING
|
||||
#define dbglocking(a...) fprintf(stderr, a)
|
||||
#else
|
||||
#define dbglocking(a...)
|
||||
#endif
|
||||
|
||||
static bool GetAbsTime(struct timespec *Abstime, int MillisecondsFromNow)
|
||||
{
|
||||
struct timeval now;
|
||||
@ -403,6 +413,132 @@ bool cThreadLock::Lock(cThread *Thread)
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- cStateLock ------------------------------------------------------------
|
||||
|
||||
cStateLock::cStateLock(const char *Name)
|
||||
:rwLock(true)
|
||||
{
|
||||
name = Name;
|
||||
threadId = 0;
|
||||
state = 0;
|
||||
explicitModify = false;
|
||||
}
|
||||
|
||||
bool cStateLock::Lock(cStateKey &StateKey, bool Write, int TimeoutMs)
|
||||
{
|
||||
dbglocking("%5d %-10s %10p lock state = %d/%d write = %d timeout = %d\n", cThread::ThreadId(), name, &StateKey, state, StateKey.state, Write, TimeoutMs);
|
||||
StateKey.timedOut = false;
|
||||
if (StateKey.stateLock) {
|
||||
esyslog("ERROR: StateKey already in use in call to cStateLock::Lock() (tid=%d, lock=%s)", StateKey.stateLock->threadId, name);
|
||||
ABORT;
|
||||
return false;
|
||||
}
|
||||
if (rwLock.Lock(Write, TimeoutMs)) {
|
||||
StateKey.stateLock = this;
|
||||
if (Write) {
|
||||
dbglocking("%5d %-10s %10p locked write\n", cThread::ThreadId(), name, &StateKey);
|
||||
threadId = cThread::ThreadId();
|
||||
StateKey.write = true;
|
||||
return true;
|
||||
}
|
||||
else if (state != StateKey.state) {
|
||||
dbglocking("%5d %-10s %10p locked read\n", cThread::ThreadId(), name, &StateKey);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
dbglocking("%5d %-10s %10p state unchanged\n", cThread::ThreadId(), name, &StateKey);
|
||||
StateKey.stateLock = NULL;
|
||||
rwLock.Unlock();
|
||||
}
|
||||
}
|
||||
else if (TimeoutMs) {
|
||||
dbglocking("%5d %-10s %10p timeout\n", cThread::ThreadId(), name, &StateKey);
|
||||
StateKey.timedOut = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cStateLock::Unlock(cStateKey &StateKey, bool IncState)
|
||||
{
|
||||
dbglocking("%5d %-10s %10p unlock state = %d/%d inc = %d\n", cThread::ThreadId(), name, &StateKey, state, StateKey.state, IncState);
|
||||
if (StateKey.stateLock != this) {
|
||||
esyslog("ERROR: cStateLock::Unlock() called with an unused key (tid=%d, lock=%s)", threadId, name);
|
||||
ABORT;
|
||||
return;
|
||||
}
|
||||
if (StateKey.write && threadId != cThread::ThreadId()) {
|
||||
esyslog("ERROR: cStateLock::Unlock() called without holding a lock (tid=%d, lock=%s)", threadId, name);
|
||||
ABORT;
|
||||
return;
|
||||
}
|
||||
if (StateKey.write && IncState && !explicitModify)
|
||||
state++;
|
||||
StateKey.state = state;
|
||||
StateKey.write = false;
|
||||
threadId = 0;
|
||||
explicitModify = false;
|
||||
rwLock.Unlock();
|
||||
}
|
||||
|
||||
void cStateLock::IncState(void)
|
||||
{
|
||||
if (threadId != cThread::ThreadId()) {
|
||||
esyslog("ERROR: cStateLock::IncState() called without holding a lock (tid=%d, lock=%s)", threadId, name);
|
||||
ABORT;
|
||||
}
|
||||
else
|
||||
state++;
|
||||
}
|
||||
|
||||
// --- cStateKey -------------------------------------------------------------
|
||||
|
||||
cStateKey::cStateKey(bool IgnoreFirst)
|
||||
{
|
||||
stateLock = NULL;
|
||||
write = false;
|
||||
state = 0;
|
||||
if (!IgnoreFirst)
|
||||
Reset();
|
||||
}
|
||||
|
||||
cStateKey::~cStateKey()
|
||||
{
|
||||
if (stateLock) {
|
||||
esyslog("ERROR: cStateKey::~cStateKey() called without releasing the lock first (tid=%d, lock=%s, key=%p)", stateLock->threadId, stateLock->name, this);
|
||||
ABORT;
|
||||
}
|
||||
}
|
||||
|
||||
void cStateKey::Reset(void)
|
||||
{
|
||||
state = -1; // lock and key are initialized differently, to make the first check return true
|
||||
}
|
||||
|
||||
void cStateKey::Remove(bool IncState)
|
||||
{
|
||||
if (stateLock) {
|
||||
stateLock->Unlock(*this, IncState);
|
||||
stateLock = NULL;
|
||||
}
|
||||
else {
|
||||
esyslog("ERROR: cStateKey::Remove() called without holding a lock (key=%p)", this);
|
||||
ABORT;
|
||||
}
|
||||
}
|
||||
|
||||
bool cStateKey::StateChanged(void)
|
||||
{
|
||||
if (!stateLock) {
|
||||
esyslog("ERROR: cStateKey::StateChanged() called without holding a lock (tid=%d, key=%p)", cThread::ThreadId(), this);
|
||||
ABORT;
|
||||
}
|
||||
else if (write)
|
||||
return state != stateLock->state;
|
||||
else
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- cIoThrottle -----------------------------------------------------------
|
||||
|
||||
cMutex cIoThrottle::mutex;
|
||||
|
90
thread.h
90
thread.h
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: thread.h 3.2 2015/01/14 11:39:55 kls Exp $
|
||||
* $Id: thread.h 4.1 2015/08/17 13:06:24 kls Exp $
|
||||
*/
|
||||
|
||||
#ifndef __THREAD_H
|
||||
@ -164,6 +164,94 @@ public:
|
||||
|
||||
#define LOCK_THREAD cThreadLock ThreadLock(this)
|
||||
|
||||
class cStateKey;
|
||||
|
||||
class cStateLock {
|
||||
friend class cStateKey;
|
||||
private:
|
||||
const char *name;
|
||||
tThreadId threadId;
|
||||
cRwLock rwLock;
|
||||
int state;
|
||||
bool explicitModify;
|
||||
void Unlock(cStateKey &StateKey, bool IncState = true);
|
||||
///< Releases a lock that has been obtained by a previous call to Lock()
|
||||
///< with the given StateKey. If this was a write-lock, and IncState is true,
|
||||
///< the state of the lock will be incremented. In any case, the (new) state
|
||||
///< of the lock will be copied to the StateKey's state.
|
||||
public:
|
||||
cStateLock(const char *Name = NULL);
|
||||
bool Lock(cStateKey &StateKey, bool Write = false, int TimeoutMs = 0);
|
||||
///< Tries to get a lock and returns true if successful.
|
||||
///< If TimoutMs is not 0, it waits for the given number of milliseconds
|
||||
///< and returns false if no lock has been obtained within that time.
|
||||
///< Otherwise it waits indefinitely for the lock. The given StateKey
|
||||
///< will store which lock it has been used with, and will use that
|
||||
///< information when its Remove() function is called.
|
||||
///< There are two possible locks, one only for read access, and one
|
||||
///< for reading and writing:
|
||||
///<
|
||||
///< If Write is false (i.e. a read-lock is requested), the lock's state
|
||||
///< is compared to the given StateKey's state, and true is returned if
|
||||
///< they differ.
|
||||
///< If true is returned, the read-lock is still in place and the
|
||||
///< protected data structures can be safely accessed (in read-only mode!).
|
||||
///< Once the necessary operations have been performed, the lock must
|
||||
///< be released by a call to the StateKey's Remove() function.
|
||||
///< If false is returned, the state has not changed since the last check,
|
||||
///< and the read-lock has been released. In that case the protected
|
||||
///< data structures have not changed since the last call, so no action
|
||||
///< is required. Note that if TimeoutMs is used with read-locks, Lock()
|
||||
///< might return false even if the states of lock and key differ, just
|
||||
///< because it was unable to obtain the lock within the given time.
|
||||
///< You can call cStateKey::TimedOut() to detect this.
|
||||
///<
|
||||
///< If Write is true (i.e. a write-lock is requested), the states of the
|
||||
///< lock and the given StateKey don't matter, it will always try to obtain
|
||||
///< a write lock.
|
||||
void SetExplicitModify(void) { explicitModify = true; }
|
||||
///< If you have obtained a write lock on this lock, and you don't want its
|
||||
///< state to be automatically incremented when the lock is released, a call to
|
||||
///< this function will disable this, and you can explicitly call IncState()
|
||||
///< to increment the state.
|
||||
void IncState(void);
|
||||
///< Increments the state of this lock.
|
||||
};
|
||||
|
||||
class cStateKey {
|
||||
friend class cStateLock;
|
||||
private:
|
||||
cStateLock *stateLock;
|
||||
bool write;
|
||||
int state;
|
||||
bool timedOut;
|
||||
public:
|
||||
cStateKey(bool IgnoreFirst = false);
|
||||
///< Sets up a new state key. If IgnoreFirst is true, the first use
|
||||
///< of this key with a lock will not return true if the lock's state
|
||||
///< hasn't explicitly changed.
|
||||
~cStateKey();
|
||||
void Reset(void);
|
||||
///< Resets the state of this key, so that the next call to a lock's
|
||||
///< Lock() function with this key will return true, even if the
|
||||
///< lock's state hasn't changed.
|
||||
void Remove(bool IncState = true);
|
||||
///< Removes this key from the lock it was previously used with.
|
||||
///< If this key was used to obtain a write lock, the state of the lock will
|
||||
///< be incremented and copied to this key. You can set IncState to false
|
||||
///< to prevent this.
|
||||
bool StateChanged(void);
|
||||
///< Returns true if this key is used for obtaining a write lock, and the
|
||||
///< lock's state differs from that of the key. When used with a read lock,
|
||||
///< it always returns true, because otherwise the lock wouldn't have been
|
||||
///< obtained in the first place.
|
||||
bool InLock(void) { return stateLock; }
|
||||
///< Returns true if this key is currently in a lock.
|
||||
bool TimedOut(void) const { return timedOut; }
|
||||
///< Returns true if the last lock attempt this key was used with failed due
|
||||
///< to a timeout.
|
||||
};
|
||||
|
||||
class cIoThrottle {
|
||||
private:
|
||||
static cMutex mutex;
|
||||
|
326
timers.c
326
timers.c
@ -4,18 +4,18 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: timers.c 3.1 2013/12/28 11:33:08 kls Exp $
|
||||
* $Id: timers.c 4.1 2015/08/31 10:45:13 kls Exp $
|
||||
*/
|
||||
|
||||
#include "timers.h"
|
||||
#include <ctype.h>
|
||||
#include "channels.h"
|
||||
#include "device.h"
|
||||
#include "i18n.h"
|
||||
#include "libsi/si.h"
|
||||
#include "recording.h"
|
||||
#include "remote.h"
|
||||
#include "status.h"
|
||||
#include "svdrp.h"
|
||||
|
||||
// IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d'
|
||||
// format characters in order to allow any number of blanks after a numeric
|
||||
@ -23,19 +23,21 @@
|
||||
|
||||
// --- cTimer ----------------------------------------------------------------
|
||||
|
||||
cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
|
||||
cTimer::cTimer(bool Instant, bool Pause, const cChannel *Channel)
|
||||
{
|
||||
startTime = stopTime = 0;
|
||||
lastSetEvent = 0;
|
||||
scheduleState = -1;
|
||||
deferred = 0;
|
||||
recording = pending = inVpsMargin = false;
|
||||
pending = inVpsMargin = false;
|
||||
flags = tfNone;
|
||||
*file = 0;
|
||||
aux = NULL;
|
||||
remote = NULL;
|
||||
event = NULL;
|
||||
if (Instant)
|
||||
SetFlags(tfActive | tfInstant);
|
||||
channel = Channel ? Channel : Channels.GetByNumber(cDevice::CurrentChannel());
|
||||
LOCK_CHANNELS_READ;
|
||||
channel = Channel ? Channel : Channels->GetByNumber(cDevice::CurrentChannel());
|
||||
time_t t = time(NULL);
|
||||
struct tm tm_r;
|
||||
struct tm *now = localtime_r(&t, &tm_r);
|
||||
@ -44,27 +46,25 @@ cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
|
||||
start = now->tm_hour * 100 + now->tm_min;
|
||||
stop = 0;
|
||||
if (!Setup.InstantRecordTime && channel && (Instant || Pause)) {
|
||||
cSchedulesLock SchedulesLock;
|
||||
if (const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock)) {
|
||||
if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) {
|
||||
if (const cEvent *Event = Schedule->GetPresentEvent()) {
|
||||
time_t tstart = Event->StartTime();
|
||||
time_t tstop = Event->EndTime();
|
||||
if (Event->Vps() && Setup.UseVps) {
|
||||
SetFlags(tfVps);
|
||||
tstart = Event->Vps();
|
||||
}
|
||||
else {
|
||||
tstop += Setup.MarginStop * 60;
|
||||
tstart -= Setup.MarginStart * 60;
|
||||
}
|
||||
day = SetTime(tstart, 0);
|
||||
struct tm *time = localtime_r(&tstart, &tm_r);
|
||||
start = time->tm_hour * 100 + time->tm_min;
|
||||
time = localtime_r(&tstop, &tm_r);
|
||||
stop = time->tm_hour * 100 + time->tm_min;
|
||||
SetEvent(Event);
|
||||
LOCK_SCHEDULES_READ;
|
||||
if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) {
|
||||
if (const cEvent *Event = Schedule->GetPresentEvent()) {
|
||||
time_t tstart = Event->StartTime();
|
||||
time_t tstop = Event->EndTime();
|
||||
if (Event->Vps() && Setup.UseVps) {
|
||||
SetFlags(tfVps);
|
||||
tstart = Event->Vps();
|
||||
}
|
||||
else {
|
||||
tstop += Setup.MarginStop * 60;
|
||||
tstart -= Setup.MarginStart * 60;
|
||||
}
|
||||
day = SetTime(tstart, 0);
|
||||
struct tm *time = localtime_r(&tstart, &tm_r);
|
||||
start = time->tm_hour * 100 + time->tm_min;
|
||||
time = localtime_r(&tstop, &tm_r);
|
||||
stop = time->tm_hour * 100 + time->tm_min;
|
||||
SetEvent(Event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -83,16 +83,18 @@ cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
|
||||
cTimer::cTimer(const cEvent *Event)
|
||||
{
|
||||
startTime = stopTime = 0;
|
||||
lastSetEvent = 0;
|
||||
scheduleState = -1;
|
||||
deferred = 0;
|
||||
recording = pending = inVpsMargin = false;
|
||||
pending = inVpsMargin = false;
|
||||
flags = tfActive;
|
||||
*file = 0;
|
||||
aux = NULL;
|
||||
remote = NULL;
|
||||
event = NULL;
|
||||
if (Event->Vps() && Setup.UseVps)
|
||||
SetFlags(tfVps);
|
||||
channel = Channels.GetByChannelID(Event->ChannelID(), true);
|
||||
LOCK_CHANNELS_READ;
|
||||
channel = Channels->GetByChannelID(Event->ChannelID(), true);
|
||||
time_t tstart = (flags & tfVps) ? Event->Vps() : Event->StartTime();
|
||||
time_t tstop = tstart + Event->Duration();
|
||||
if (!(HasFlags(tfVps))) {
|
||||
@ -120,6 +122,7 @@ cTimer::cTimer(const cTimer &Timer)
|
||||
{
|
||||
channel = NULL;
|
||||
aux = NULL;
|
||||
remote = NULL;
|
||||
event = NULL;
|
||||
flags = tfNone;
|
||||
*this = Timer;
|
||||
@ -127,7 +130,10 @@ cTimer::cTimer(const cTimer &Timer)
|
||||
|
||||
cTimer::~cTimer()
|
||||
{
|
||||
if (event)
|
||||
event->DecNumTimers();
|
||||
free(aux);
|
||||
free(remote);
|
||||
}
|
||||
|
||||
cTimer& cTimer::operator= (const cTimer &Timer)
|
||||
@ -136,9 +142,8 @@ cTimer& cTimer::operator= (const cTimer &Timer)
|
||||
uint OldFlags = flags & tfRecording;
|
||||
startTime = Timer.startTime;
|
||||
stopTime = Timer.stopTime;
|
||||
lastSetEvent = 0;
|
||||
deferred = 0;
|
||||
recording = Timer.recording;
|
||||
scheduleState = -1;
|
||||
deferred = 0;
|
||||
pending = Timer.pending;
|
||||
inVpsMargin = Timer.inVpsMargin;
|
||||
flags = Timer.flags | OldFlags;
|
||||
@ -152,7 +157,13 @@ cTimer& cTimer::operator= (const cTimer &Timer)
|
||||
strncpy(file, Timer.file, sizeof(file));
|
||||
free(aux);
|
||||
aux = Timer.aux ? strdup(Timer.aux) : NULL;
|
||||
event = NULL;
|
||||
free(remote);
|
||||
remote = Timer.remote ? strdup(Timer.remote) : NULL;
|
||||
if (event)
|
||||
event->DecNumTimers();
|
||||
event = Timer.event;
|
||||
if (event)
|
||||
event->IncNumTimers();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@ -178,7 +189,7 @@ cString cTimer::ToText(bool UseChannelID) const
|
||||
|
||||
cString cTimer::ToDescr(void) const
|
||||
{
|
||||
return cString::sprintf("%d (%d %04d-%04d %s'%s')", Index() + 1, Channel()->Number(), start, stop, HasFlags(tfVps) ? "VPS " : "", file);
|
||||
return cString::sprintf("%d%s%s (%d %04d-%04d %s'%s')", Index() + 1, remote ? "@" : "", remote ? remote : "", Channel()->Number(), start, stop, HasFlags(tfVps) ? "VPS " : "", file);
|
||||
}
|
||||
|
||||
int cTimer::TimeToInt(int t)
|
||||
@ -313,7 +324,6 @@ bool cTimer::Parse(const char *s)
|
||||
}
|
||||
bool result = false;
|
||||
if (8 <= sscanf(s, "%u :%m[^:]:%m[^:]:%d :%d :%d :%d :%m[^:\n]:%m[^\n]", &flags, &channelbuffer, &daybuffer, &start, &stop, &priority, &lifetime, &filebuffer, &aux)) {
|
||||
ClrFlags(tfRecording);
|
||||
if (aux && !*skipspace(aux)) {
|
||||
free(aux);
|
||||
aux = NULL;
|
||||
@ -322,10 +332,11 @@ bool cTimer::Parse(const char *s)
|
||||
result = ParseDay(daybuffer, day, weekdays);
|
||||
Utf8Strn0Cpy(file, filebuffer, sizeof(file));
|
||||
strreplace(file, '|', ':');
|
||||
LOCK_CHANNELS_READ;
|
||||
if (isnumber(channelbuffer))
|
||||
channel = Channels.GetByNumber(atoi(channelbuffer));
|
||||
channel = Channels->GetByNumber(atoi(channelbuffer));
|
||||
else
|
||||
channel = Channels.GetByChannelID(tChannelID::FromString(channelbuffer), true, true);
|
||||
channel = Channels->GetByChannelID(tChannelID::FromString(channelbuffer), true, true);
|
||||
if (!channel) {
|
||||
esyslog("ERROR: channel %s not defined", channelbuffer);
|
||||
result = false;
|
||||
@ -340,7 +351,9 @@ bool cTimer::Parse(const char *s)
|
||||
|
||||
bool cTimer::Save(FILE *f)
|
||||
{
|
||||
return fprintf(f, "%s", *ToText(true)) > 0;
|
||||
if (!Remote())
|
||||
return fprintf(f, "%s", *ToText(true)) > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cTimer::IsSingleEvent(void) const
|
||||
@ -441,10 +454,8 @@ bool cTimer::Matches(time_t t, bool Directly, int Margin) const
|
||||
startTime = event->StartTime();
|
||||
stopTime = event->EndTime();
|
||||
if (!Margin) { // this is an actual check
|
||||
if (event->Schedule()->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) { // VPS control can only work with up-to-date events...
|
||||
if (event->StartTime() > 0) // checks for "phased out" events
|
||||
return event->IsRunning(true);
|
||||
}
|
||||
if (event->Schedule()->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) // VPS control can only work with up-to-date events...
|
||||
return event->IsRunning(true);
|
||||
return startTime <= t && t < stopTime; // ...otherwise we fall back to normal timer handling
|
||||
}
|
||||
}
|
||||
@ -511,27 +522,13 @@ time_t cTimer::StopTime(void) const
|
||||
#define EPGLIMITBEFORE (1 * 3600) // Time in seconds before a timer's start time and
|
||||
#define EPGLIMITAFTER (1 * 3600) // after its stop time within which EPG events will be taken into consideration.
|
||||
|
||||
void cTimer::SetEventFromSchedule(const cSchedules *Schedules)
|
||||
bool cTimer::SetEventFromSchedule(const cSchedules *Schedules)
|
||||
{
|
||||
cSchedulesLock SchedulesLock;
|
||||
if (!Schedules) {
|
||||
lastSetEvent = 0; // forces setting the event, even if the schedule hasn't been modified
|
||||
if (!(Schedules = cSchedules::Schedules(SchedulesLock)))
|
||||
return;
|
||||
}
|
||||
const cSchedule *Schedule = Schedules->GetSchedule(Channel());
|
||||
if (Schedule && Schedule->Events()->First()) {
|
||||
time_t now = time(NULL);
|
||||
if (!lastSetEvent || Schedule->Modified() >= lastSetEvent) {
|
||||
lastSetEvent = now;
|
||||
if (Schedule->Modified(scheduleState)) {
|
||||
const cEvent *Event = NULL;
|
||||
if (HasFlags(tfVps) && Schedule->Events()->First()->Vps()) {
|
||||
if (event && event->StartTime() > 0) { // checks for "phased out" events
|
||||
if (Recording())
|
||||
return; // let the recording end first
|
||||
if (now <= event->EndTime() || Matches(0, true))
|
||||
return; // stay with the old event until the timer has completely expired
|
||||
}
|
||||
// VPS timers only match if their start time exactly matches the event's VPS time:
|
||||
for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
|
||||
if (e->StartTime() && e->RunningStatus() != SI::RunningStatusNotRunning) { // skip outdated events
|
||||
@ -566,30 +563,39 @@ void cTimer::SetEventFromSchedule(const cSchedules *Schedules)
|
||||
}
|
||||
}
|
||||
}
|
||||
SetEvent(Event);
|
||||
return SetEvent(Event);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cTimer::SetEvent(const cEvent *Event)
|
||||
bool cTimer::SetEvent(const cEvent *Event)
|
||||
{
|
||||
if (event != Event) { //XXX TODO check event data, too???
|
||||
if (Event)
|
||||
if (event != Event) {
|
||||
if (event)
|
||||
event->DecNumTimers();
|
||||
if (Event) {
|
||||
isyslog("timer %s set to event %s", *ToDescr(), *Event->ToDescr());
|
||||
else
|
||||
Event->IncNumTimers();
|
||||
Event->Schedule()->Modified(scheduleState); // to get the current state
|
||||
}
|
||||
else {
|
||||
isyslog("timer %s set to no event", *ToDescr());
|
||||
scheduleState = -1;
|
||||
}
|
||||
event = Event;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cTimer::SetRecording(bool Recording)
|
||||
{
|
||||
recording = Recording;
|
||||
if (recording)
|
||||
if (Recording)
|
||||
SetFlags(tfRecording);
|
||||
else
|
||||
ClrFlags(tfRecording);
|
||||
isyslog("timer %s %s", *ToDescr(), recording ? "start" : "stop");
|
||||
isyslog("timer %s %s", *ToDescr(), Recording ? "start" : "stop");
|
||||
}
|
||||
|
||||
void cTimer::SetPending(bool Pending)
|
||||
@ -637,7 +643,13 @@ void cTimer::SetLifetime(int Lifetime)
|
||||
void cTimer::SetAux(const char *Aux)
|
||||
{
|
||||
free(aux);
|
||||
aux = strdup(Aux);
|
||||
aux = Aux ? strdup(Aux) : NULL;
|
||||
}
|
||||
|
||||
void cTimer::SetRemote(const char *Remote)
|
||||
{
|
||||
free(remote);
|
||||
remote = Remote ? strdup(Remote) : NULL;
|
||||
}
|
||||
|
||||
void cTimer::SetDeferred(int Seconds)
|
||||
@ -691,20 +703,33 @@ void cTimer::OnOff(void)
|
||||
|
||||
// --- cTimers ---------------------------------------------------------------
|
||||
|
||||
cTimers Timers;
|
||||
cTimers cTimers::timers;
|
||||
|
||||
cTimers::cTimers(void)
|
||||
:cConfig<cTimer>("Timers")
|
||||
{
|
||||
state = 0;
|
||||
beingEdited = 0;;
|
||||
lastSetEvents = 0;
|
||||
lastDeleteExpired = 0;
|
||||
}
|
||||
|
||||
bool cTimers::Load(const char *FileName)
|
||||
{
|
||||
LOCK_TIMERS_WRITE;
|
||||
Timers->SetExplicitModify();
|
||||
if (timers.cConfig<cTimer>::Load(FileName)) {
|
||||
for (cTimer *ti = timers.First(); ti; ti = timers.Next(ti)) {
|
||||
ti->ClrFlags(tfRecording);
|
||||
Timers->SetModified();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
cTimer *cTimers::GetTimer(cTimer *Timer)
|
||||
{
|
||||
for (cTimer *ti = First(); ti; ti = Next(ti)) {
|
||||
if (ti->Channel() == Timer->Channel() &&
|
||||
if (!ti->Remote() &&
|
||||
ti->Channel() == Timer->Channel() &&
|
||||
(ti->WeekDays() && ti->WeekDays() == Timer->WeekDays() || !ti->WeekDays() && ti->Day() == Timer->Day()) &&
|
||||
ti->Start() == Timer->Start() &&
|
||||
ti->Stop() == Timer->Stop())
|
||||
@ -713,12 +738,12 @@ cTimer *cTimers::GetTimer(cTimer *Timer)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cTimer *cTimers::GetMatch(time_t t)
|
||||
const cTimer *cTimers::GetMatch(time_t t) const
|
||||
{
|
||||
static int LastPending = -1;
|
||||
cTimer *t0 = NULL;
|
||||
for (cTimer *ti = First(); ti; ti = Next(ti)) {
|
||||
if (!ti->Recording() && ti->Matches(t)) {
|
||||
const cTimer *t0 = NULL;
|
||||
for (const cTimer *ti = First(); ti; ti = Next(ti)) {
|
||||
if (!ti->Remote() && !ti->Recording() && ti->Matches(t)) {
|
||||
if (ti->Pending()) {
|
||||
if (ti->Index() > LastPending) {
|
||||
LastPending = ti->Index();
|
||||
@ -736,11 +761,11 @@ cTimer *cTimers::GetMatch(time_t t)
|
||||
return t0;
|
||||
}
|
||||
|
||||
cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match)
|
||||
const cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match) const
|
||||
{
|
||||
cTimer *t = NULL;
|
||||
const cTimer *t = NULL;
|
||||
eTimerMatch m = tmNone;
|
||||
for (cTimer *ti = First(); ti; ti = Next(ti)) {
|
||||
for (const cTimer *ti = First(); ti; ti = Next(ti)) {
|
||||
eTimerMatch tm = ti->Matches(Event);
|
||||
if (tm > m) {
|
||||
t = ti;
|
||||
@ -754,21 +779,27 @@ cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match)
|
||||
return t;
|
||||
}
|
||||
|
||||
cTimer *cTimers::GetNextActiveTimer(void)
|
||||
const cTimer *cTimers::GetNextActiveTimer(void) const
|
||||
{
|
||||
cTimer *t0 = NULL;
|
||||
for (cTimer *ti = First(); ti; ti = Next(ti)) {
|
||||
ti->Matches();
|
||||
if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
|
||||
t0 = ti;
|
||||
const cTimer *t0 = NULL;
|
||||
for (const cTimer *ti = First(); ti; ti = Next(ti)) {
|
||||
if (!ti->Remote()) {
|
||||
ti->Matches();
|
||||
if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
|
||||
t0 = ti;
|
||||
}
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
void cTimers::SetModified(void)
|
||||
const cTimers *cTimers::GetTimersRead(cStateKey &StateKey, int TimeoutMs)
|
||||
{
|
||||
cStatus::MsgTimerChange(NULL, tcMod);
|
||||
state++;
|
||||
return timers.Lock(StateKey, false, TimeoutMs) ? &timers : NULL;
|
||||
}
|
||||
|
||||
cTimers *cTimers::GetTimersWrite(cStateKey &StateKey, int TimeoutMs)
|
||||
{
|
||||
return timers.Lock(StateKey, true, TimeoutMs) ? &timers : NULL;
|
||||
}
|
||||
|
||||
void cTimers::Add(cTimer *Timer, cTimer *After)
|
||||
@ -789,46 +820,113 @@ void cTimers::Del(cTimer *Timer, bool DeleteObject)
|
||||
cConfig<cTimer>::Del(Timer, DeleteObject);
|
||||
}
|
||||
|
||||
bool cTimers::Modified(int &State)
|
||||
const cTimer *cTimers::UsesChannel(const cChannel *Channel) const
|
||||
{
|
||||
bool Result = state != State;
|
||||
State = state;
|
||||
return Result;
|
||||
for (const cTimer *Timer = First(); Timer; Timer = Next(Timer)) {
|
||||
if (Timer->Channel() == Channel)
|
||||
return Timer;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void cTimers::SetEvents(void)
|
||||
bool cTimers::SetEvents(const cSchedules *Schedules)
|
||||
{
|
||||
if (time(NULL) - lastSetEvents < 5)
|
||||
return;
|
||||
cSchedulesLock SchedulesLock(false, 100);
|
||||
const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
|
||||
if (Schedules) {
|
||||
if (!lastSetEvents || Schedules->Modified() >= lastSetEvents) {
|
||||
for (cTimer *ti = First(); ti; ti = Next(ti)) {
|
||||
if (cRemote::HasKeys())
|
||||
return; // react immediately on user input
|
||||
ti->SetEventFromSchedule(Schedules);
|
||||
}
|
||||
}
|
||||
}
|
||||
lastSetEvents = time(NULL);
|
||||
bool TimersModified = false;
|
||||
for (cTimer *ti = First(); ti; ti = Next(ti))
|
||||
TimersModified |= ti->SetEventFromSchedule(Schedules);
|
||||
return TimersModified;
|
||||
}
|
||||
|
||||
void cTimers::DeleteExpired(void)
|
||||
bool cTimers::DeleteExpired(void)
|
||||
{
|
||||
if (time(NULL) - lastDeleteExpired < 30)
|
||||
return;
|
||||
return false;
|
||||
bool TimersModified = false;
|
||||
cTimer *ti = First();
|
||||
while (ti) {
|
||||
cTimer *next = Next(ti);
|
||||
if (ti->Expired()) {
|
||||
if (!ti->Remote() && ti->Expired()) {
|
||||
isyslog("deleting timer %s", *ti->ToDescr());
|
||||
Del(ti);
|
||||
SetModified();
|
||||
TimersModified = true;
|
||||
}
|
||||
ti = next;
|
||||
}
|
||||
lastDeleteExpired = time(NULL);
|
||||
return TimersModified;
|
||||
}
|
||||
|
||||
bool cTimers::GetRemoteTimers(const char *ServerName)
|
||||
{
|
||||
bool Result = false;
|
||||
if (ServerName) {
|
||||
Result = DelRemoteTimers(ServerName);
|
||||
cSVDRPCommand Cmd(ServerName, "LSTT ID");
|
||||
if (Cmd.Execute()) {
|
||||
const char *s;
|
||||
for (int i = 0; s = Cmd.Response(i); i++) {
|
||||
int Code = Cmd.Code(s);
|
||||
if (Code == 250) {
|
||||
if (const char *v = Cmd.Value(s)) {
|
||||
while (*v && *v != ' ')
|
||||
v++; // skip number
|
||||
cTimer *Timer = new cTimer;
|
||||
if (Timer->Parse(v)) {
|
||||
Timer->SetRemote(ServerName);
|
||||
Add(Timer);
|
||||
Result = true;
|
||||
}
|
||||
else {
|
||||
esyslog("ERROR: %s: error in timer settings: %s", ServerName, v);
|
||||
delete Timer;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Code != 550)
|
||||
esyslog("ERROR: %s: %s", ServerName, s);
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
else {
|
||||
cStringList ServerNames;
|
||||
if (GetSVDRPServerNames(&ServerNames, sffTimers)) {
|
||||
for (int i = 0; i < ServerNames.Size(); i++)
|
||||
Result |= GetRemoteTimers(ServerNames[i]);
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool cTimers::DelRemoteTimers(const char *ServerName)
|
||||
{
|
||||
bool Deleted = false;
|
||||
cTimer *Timer = First();
|
||||
while (Timer) {
|
||||
cTimer *t = Next(Timer);
|
||||
if (Timer->Remote() && (!ServerName || strcmp(Timer->Remote(), ServerName) == 0)) {
|
||||
Del(Timer);
|
||||
Deleted = true;
|
||||
}
|
||||
Timer = t;
|
||||
}
|
||||
return Deleted;
|
||||
}
|
||||
|
||||
void cTimers::TriggerRemoteTimerPoll(const char *ServerName)
|
||||
{
|
||||
if (ServerName) {
|
||||
cSVDRPCommand Cmd(ServerName, cString::sprintf("POLL %s TIMERS", SVDRPHostName()));
|
||||
if (!Cmd.Execute())
|
||||
esyslog("ERROR: can't send 'POLL %s TIMERS' to '%s'", SVDRPHostName(), ServerName);
|
||||
}
|
||||
else {
|
||||
cStringList ServerNames;
|
||||
if (GetSVDRPServerNames(&ServerNames)) {
|
||||
for (int i = 0; i < ServerNames.Size(); i++)
|
||||
TriggerRemoteTimerPoll(ServerNames[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- cSortedTimers ---------------------------------------------------------
|
||||
@ -838,10 +936,10 @@ static int CompareTimers(const void *a, const void *b)
|
||||
return (*(const cTimer **)a)->Compare(**(const cTimer **)b);
|
||||
}
|
||||
|
||||
cSortedTimers::cSortedTimers(void)
|
||||
:cVector<const cTimer *>(Timers.Count())
|
||||
cSortedTimers::cSortedTimers(const cTimers *Timers)
|
||||
:cVector<const cTimer *>(Timers->Count())
|
||||
{
|
||||
for (const cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer))
|
||||
for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer))
|
||||
Append(Timer);
|
||||
Sort(CompareTimers);
|
||||
}
|
||||
|
119
timers.h
119
timers.h
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: timers.h 2.7 2013/03/11 10:35:53 kls Exp $
|
||||
* $Id: timers.h 4.1 2015/08/31 10:42:53 kls Exp $
|
||||
*/
|
||||
|
||||
#ifndef __TIMERS_H
|
||||
@ -28,11 +28,11 @@ class cTimer : public cListObject {
|
||||
friend class cMenuEditTimer;
|
||||
private:
|
||||
mutable time_t startTime, stopTime;
|
||||
time_t lastSetEvent;
|
||||
int scheduleState;
|
||||
mutable time_t deferred; ///< Matches(time_t, ...) will return false if the current time is before this value
|
||||
bool recording, pending, inVpsMargin;
|
||||
bool pending, inVpsMargin;
|
||||
uint flags;
|
||||
cChannel *channel;
|
||||
const cChannel *channel;
|
||||
mutable time_t day; ///< midnight of the day this timer shall hit, or of the first day it shall hit in case of a repeating timer
|
||||
int weekdays; ///< bitmask, lowest bits: SSFTWTM (the 'M' is the LSB)
|
||||
int start;
|
||||
@ -41,15 +41,16 @@ private:
|
||||
int lifetime;
|
||||
mutable char file[NAME_MAX * 2 + 1]; // *2 to be able to hold 'title' and 'episode', which can each be up to 255 characters long
|
||||
char *aux;
|
||||
char *remote;
|
||||
const cEvent *event;
|
||||
public:
|
||||
cTimer(bool Instant = false, bool Pause = false, cChannel *Channel = NULL);
|
||||
cTimer(bool Instant = false, bool Pause = false, const cChannel *Channel = NULL);
|
||||
cTimer(const cEvent *Event);
|
||||
cTimer(const cTimer &Timer);
|
||||
virtual ~cTimer();
|
||||
cTimer& operator= (const cTimer &Timer);
|
||||
virtual int Compare(const cListObject &ListObject) const;
|
||||
bool Recording(void) const { return recording; }
|
||||
bool Recording(void) const { return HasFlags(tfRecording); }
|
||||
bool Pending(void) const { return pending; }
|
||||
bool InVpsMargin(void) const { return inVpsMargin; }
|
||||
uint Flags(void) const { return flags; }
|
||||
@ -63,6 +64,7 @@ public:
|
||||
const char *File(void) const { return file; }
|
||||
time_t FirstDay(void) const { return weekdays ? day : 0; }
|
||||
const char *Aux(void) const { return aux; }
|
||||
const char *Remote(void) const { return remote; }
|
||||
time_t Deferred(void) const { return deferred; }
|
||||
cString ToText(bool UseChannelID = false) const;
|
||||
cString ToDescr(void) const;
|
||||
@ -81,8 +83,8 @@ public:
|
||||
bool Expired(void) const;
|
||||
time_t StartTime(void) const;
|
||||
time_t StopTime(void) const;
|
||||
void SetEventFromSchedule(const cSchedules *Schedules = NULL);
|
||||
void SetEvent(const cEvent *Event);
|
||||
bool SetEventFromSchedule(const cSchedules *Schedules);
|
||||
bool SetEvent(const cEvent *Event);
|
||||
void SetRecording(bool Recording);
|
||||
void SetPending(bool Pending);
|
||||
void SetInVpsMargin(bool InVpsMargin);
|
||||
@ -93,6 +95,7 @@ public:
|
||||
void SetPriority(int Priority);
|
||||
void SetLifetime(int Lifetime);
|
||||
void SetAux(const char *Aux);
|
||||
void SetRemote(const char *Remote);
|
||||
void SetDeferred(int Seconds);
|
||||
void SetFlags(uint Flags);
|
||||
void ClrFlags(uint Flags);
|
||||
@ -108,36 +111,100 @@ public:
|
||||
|
||||
class cTimers : public cConfig<cTimer> {
|
||||
private:
|
||||
int state;
|
||||
int beingEdited;
|
||||
time_t lastSetEvents;
|
||||
static cTimers timers;
|
||||
time_t lastDeleteExpired;
|
||||
public:
|
||||
cTimers(void);
|
||||
static const cTimers *GetTimersRead(cStateKey &StateKey, int TimeoutMs = 0);
|
||||
///< Gets the list of timers for read access. If TimeoutMs is given,
|
||||
///< it will wait that long to get a write lock before giving up.
|
||||
///< Otherwise it will wait indefinitely. If no read lock can be
|
||||
///< obtained within the given timeout, NULL will be returned.
|
||||
///< The list is locked and a pointer to it is returned if the state
|
||||
///< of the list is different than the state of the given StateKey.
|
||||
///< If both states are equal, the list of timers has not been modified
|
||||
///< since the last call with the same StateKey, and NULL will be
|
||||
///< returned (and the list is not locked). After the returned list of
|
||||
///< timers is no longer needed, the StateKey's Remove() function must
|
||||
///< be called to release the list. The time between calling
|
||||
///< cTimers::GetTimersRead() and StateKey.Remove() should be as short
|
||||
///< as possible. After calling StateKey.Remove() the list returned from
|
||||
///< this call must not be accessed any more. If you need to access the
|
||||
///< timers again later, a new call to GetTimersRead() must be made.
|
||||
///< A typical code sequence would look like this:
|
||||
///< cStateKey StateKey;
|
||||
///< if (const cTimers *Timers = cTimers::GetTimersRead(StateKey)) {
|
||||
///< // access the timers
|
||||
///< StateKey.Remove();
|
||||
///< }
|
||||
static cTimers *GetTimersWrite(cStateKey &StateKey, int TimeoutMs = 0);
|
||||
///< Gets the list of timers for write access. If TimeoutMs is given,
|
||||
///< it will wait that long to get a write lock before giving up.
|
||||
///< Otherwise it will wait indefinitely. If no write lock can be
|
||||
///< obtained within the given timeout, NULL will be returned.
|
||||
///< If a write lock can be obtained, the list of timers will be
|
||||
///< returned, regardless of the state values of the timers or the
|
||||
///< given StateKey. After the returned list of timers is no longer
|
||||
///< needed, the StateKey's Remove() function must be called to release
|
||||
///< the list. The time between calling cTimers::GetTimersWrite() and
|
||||
///< StateKey.Remove() should be as short as possible. After calling
|
||||
///< StateKey.Remove() the list returned from this call must not be
|
||||
///< accessed any more. If you need to access the timers again later,
|
||||
///< a new call to GetTimersWrite() must be made. The call
|
||||
///< to StateKey.Remove() will increment the state of the list of
|
||||
///< timers and will copy the new state value to the StateKey. You can
|
||||
///< suppress this by using 'false' as the parameter to the call, in
|
||||
///< which case the state values are left untouched.
|
||||
///< A typical code sequence would look like this:
|
||||
///< cStateKey StateKey;
|
||||
///< if (cTimers *Timers = cTimers::GetTimersWrite(StateKey)) {
|
||||
///< // access the timers
|
||||
///< StateKey.Remove();
|
||||
///< }
|
||||
static bool Load(const char *FileName);
|
||||
cTimer *GetTimer(cTimer *Timer);
|
||||
cTimer *GetMatch(time_t t);
|
||||
cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL);
|
||||
cTimer *GetNextActiveTimer(void);
|
||||
int BeingEdited(void) { return beingEdited; }
|
||||
void IncBeingEdited(void) { beingEdited++; }
|
||||
void DecBeingEdited(void) { if (!--beingEdited) lastSetEvents = 0; }
|
||||
void SetModified(void);
|
||||
bool Modified(int &State);
|
||||
///< Returns true if any of the timers have been modified, which
|
||||
///< is detected by State being different than the internal state.
|
||||
///< Upon return the internal state will be stored in State.
|
||||
void SetEvents(void);
|
||||
void DeleteExpired(void);
|
||||
const cTimer *GetMatch(time_t t) const;
|
||||
cTimer *GetMatch(time_t t) { return const_cast<cTimer *>(static_cast<const cTimers *>(this)->GetMatch(t)); };
|
||||
const cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL) const;
|
||||
cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL) { return const_cast<cTimer *>(static_cast<const cTimers *>(this)->GetMatch(Event, Match)); }
|
||||
const cTimer *GetNextActiveTimer(void) const;
|
||||
const cTimer *UsesChannel(const cChannel *Channel) const;
|
||||
bool SetEvents(const cSchedules *Schedules);
|
||||
bool DeleteExpired(void);
|
||||
void Add(cTimer *Timer, cTimer *After = NULL);
|
||||
void Ins(cTimer *Timer, cTimer *Before = NULL);
|
||||
void Del(cTimer *Timer, bool DeleteObject = true);
|
||||
bool GetRemoteTimers(const char *ServerName = NULL);
|
||||
///< Gets the timers from the given remote machine and adds them to this
|
||||
///< list. If no ServerName is given, all timers from all known remote
|
||||
///< machines will be fetched. This function calls DelRemoteTimers() with
|
||||
///< the given ServerName first.
|
||||
///< Returns true if any remote timers have been added or deleted
|
||||
bool DelRemoteTimers(const char *ServerName = NULL);
|
||||
///< Deletes all timers of the given remote machine from this list (leaves
|
||||
///< them untouched on the remote machine). If no ServerName is given, the
|
||||
///< timers of all remote machines will be deleted from the list.
|
||||
///< Returns true if any remote timers have been deleted.
|
||||
void TriggerRemoteTimerPoll(const char *ServerName = NULL);
|
||||
///< Sends an SVDRP POLL command to the given remote machine.
|
||||
///< If no ServerName is given, the POLL command will be sent to all
|
||||
///< known remote machines.
|
||||
};
|
||||
|
||||
extern cTimers Timers;
|
||||
// Provide lock controlled access to the list:
|
||||
|
||||
DEF_LIST_LOCK(Timers);
|
||||
|
||||
// These macros provide a convenient way of locking the global timers list
|
||||
// and making sure the lock is released as soon as the current scope is left
|
||||
// (note that these macros wait forever to obtain the lock!):
|
||||
|
||||
#define LOCK_TIMERS_READ USE_LIST_LOCK_READ(Timers)
|
||||
#define LOCK_TIMERS_WRITE USE_LIST_LOCK_WRITE(Timers)
|
||||
|
||||
class cSortedTimers : public cVector<const cTimer *> {
|
||||
public:
|
||||
cSortedTimers(void);
|
||||
cSortedTimers(const cTimers *Timers);
|
||||
};
|
||||
|
||||
#endif //__TIMERS_H
|
||||
|
90
tools.c
90
tools.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: tools.c 4.1 2015/05/11 14:15:15 kls Exp $
|
||||
* $Id: tools.c 4.2 2015/08/29 12:11:20 kls Exp $
|
||||
*/
|
||||
|
||||
#include "tools.h"
|
||||
@ -2044,12 +2044,58 @@ int cListObject::Index(void) const
|
||||
return i;
|
||||
}
|
||||
|
||||
// --- cListGarbageCollector -------------------------------------------------
|
||||
|
||||
#define LIST_GARBAGE_COLLECTOR_TIMEOUT 5 // seconds
|
||||
|
||||
cListGarbageCollector ListGarbageCollector;
|
||||
|
||||
cListGarbageCollector::cListGarbageCollector(void)
|
||||
{
|
||||
objects = NULL;
|
||||
lastPut = 0;
|
||||
}
|
||||
|
||||
cListGarbageCollector::~cListGarbageCollector()
|
||||
{
|
||||
if (objects)
|
||||
esyslog("ERROR: ListGarbageCollector destroyed without prior Purge()!");
|
||||
}
|
||||
|
||||
void cListGarbageCollector::Put(cListObject *Object)
|
||||
{
|
||||
mutex.Lock();
|
||||
Object->next = objects;
|
||||
objects = Object;
|
||||
lastPut = time(NULL);
|
||||
mutex.Unlock();
|
||||
}
|
||||
|
||||
void cListGarbageCollector::Purge(bool Force)
|
||||
{
|
||||
mutex.Lock();
|
||||
if (objects && (time(NULL) - lastPut > LIST_GARBAGE_COLLECTOR_TIMEOUT || Force)) {
|
||||
// We make sure that any object stays in the garbage collector for at least
|
||||
// LIST_GARBAGE_COLLECTOR_TIMEOUT seconds, to give objects that have pointers
|
||||
// to them a chance to drop these references before the object is finally
|
||||
// deleted.
|
||||
while (cListObject *Object = objects) {
|
||||
objects = Object->next;
|
||||
delete Object;
|
||||
}
|
||||
}
|
||||
mutex.Unlock();
|
||||
}
|
||||
|
||||
// --- cListBase -------------------------------------------------------------
|
||||
|
||||
cListBase::cListBase(void)
|
||||
cListBase::cListBase(const char *NeedsLocking)
|
||||
:stateLock(NeedsLocking)
|
||||
{
|
||||
objects = lastObject = NULL;
|
||||
count = 0;
|
||||
needsLocking = NeedsLocking;
|
||||
useGarbageCollector = needsLocking;
|
||||
}
|
||||
|
||||
cListBase::~cListBase()
|
||||
@ -2057,6 +2103,15 @@ cListBase::~cListBase()
|
||||
Clear();
|
||||
}
|
||||
|
||||
bool cListBase::Lock(cStateKey &StateKey, bool Write, int TimeoutMs) const
|
||||
{
|
||||
if (needsLocking)
|
||||
return stateLock.Lock(StateKey, Write, TimeoutMs);
|
||||
else
|
||||
esyslog("ERROR: cListBase::Lock() called for a list that doesn't require locking");
|
||||
return false;
|
||||
}
|
||||
|
||||
void cListBase::Add(cListObject *Object, cListObject *After)
|
||||
{
|
||||
if (After && After != lastObject) {
|
||||
@ -2096,8 +2151,12 @@ void cListBase::Del(cListObject *Object, bool DeleteObject)
|
||||
if (Object == lastObject)
|
||||
lastObject = Object->Prev();
|
||||
Object->Unlink();
|
||||
if (DeleteObject)
|
||||
delete Object;
|
||||
if (DeleteObject) {
|
||||
if (useGarbageCollector)
|
||||
ListGarbageCollector.Put(Object);
|
||||
else
|
||||
delete Object;
|
||||
}
|
||||
count--;
|
||||
}
|
||||
|
||||
@ -2141,11 +2200,30 @@ void cListBase::Clear(void)
|
||||
count = 0;
|
||||
}
|
||||
|
||||
cListObject *cListBase::Get(int Index) const
|
||||
bool cListBase::Contains(const cListObject *Object) const
|
||||
{
|
||||
for (const cListObject *o = objects; o; o = o->Next()) {
|
||||
if (o == Object)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cListBase::SetExplicitModify(void)
|
||||
{
|
||||
stateLock.SetExplicitModify();
|
||||
}
|
||||
|
||||
void cListBase::SetModified(void)
|
||||
{
|
||||
stateLock.IncState();
|
||||
}
|
||||
|
||||
const cListObject *cListBase::Get(int Index) const
|
||||
{
|
||||
if (Index < 0)
|
||||
return NULL;
|
||||
cListObject *object = objects;
|
||||
const cListObject *object = objects;
|
||||
while (object && Index-- > 0)
|
||||
object = object->Next();
|
||||
return object;
|
||||
|
137
tools.h
137
tools.h
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: tools.h 4.1 2015/05/21 14:37:00 kls Exp $
|
||||
* $Id: tools.h 4.2 2015/08/29 11:45:51 kls Exp $
|
||||
*/
|
||||
|
||||
#ifndef __TOOLS_H
|
||||
@ -26,6 +26,7 @@
|
||||
#include <syslog.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include "thread.h"
|
||||
|
||||
typedef unsigned char uchar;
|
||||
|
||||
@ -462,6 +463,7 @@ public:
|
||||
};
|
||||
|
||||
class cListObject {
|
||||
friend class cListGarbageCollector;
|
||||
private:
|
||||
cListObject *prev, *next;
|
||||
public:
|
||||
@ -478,33 +480,152 @@ public:
|
||||
cListObject *Next(void) const { return next; }
|
||||
};
|
||||
|
||||
class cListGarbageCollector {
|
||||
private:
|
||||
cMutex mutex;
|
||||
cListObject *objects;
|
||||
time_t lastPut;
|
||||
public:
|
||||
cListGarbageCollector(void);
|
||||
~cListGarbageCollector();
|
||||
void Put(cListObject *Object);
|
||||
void Purge(bool Force = false);
|
||||
};
|
||||
|
||||
extern cListGarbageCollector ListGarbageCollector;
|
||||
|
||||
class cListBase {
|
||||
protected:
|
||||
cListObject *objects, *lastObject;
|
||||
cListBase(void);
|
||||
int count;
|
||||
mutable cStateLock stateLock;
|
||||
const char *needsLocking;
|
||||
bool useGarbageCollector;
|
||||
cListBase(const char *NeedsLocking = NULL);
|
||||
public:
|
||||
virtual ~cListBase();
|
||||
bool Lock(cStateKey &StateKey, bool Write = false, int TimeoutMs = 0) const;
|
||||
///< Tries to get a lock on this list and returns true if successful.
|
||||
///< By default a read lock is requested. Set Write to true to obtain
|
||||
///< a write lock. If TimeoutMs is not zero, it waits for the given
|
||||
///< number of milliseconds before giving up.
|
||||
///< If you need to lock more than one list at the same time, make sure
|
||||
///< you set TimeoutMs to a suitable value in all of the calls to
|
||||
///< Lock(), and be prepared to handle situations where you do not get all
|
||||
///< of the requested locks. In such cases you should release all the locks
|
||||
///< you have obtained so far and try again. StateKey.TimedOut() tells you
|
||||
///< whether the lock attempt failed due to a timeout or because the state
|
||||
///< of the lock hasn't changed since the previous locking attempt.
|
||||
///< To implicitly avoid deadlocks when locking more than one of the global
|
||||
///< lists of VDR at the same time, make sure you always lock Timers, Channels,
|
||||
///< Recordings and Schedules in this sequence.
|
||||
///< You may keep pointers to objects in this list, even after releasing
|
||||
///< the lock. However, you may only access such objects if you are
|
||||
///< holding a proper lock again. If an object has been deleted from the list
|
||||
///< while you did not hold a lock (for instance by an other thread), the
|
||||
///< object will still be there, but no longer within this list (it is then
|
||||
///< stored in the ListGarbageCollector). That way even if you access the object
|
||||
///< after it has been deleted, you won't cause a segfault. You can call the
|
||||
///< Contains() function to check whether an object you are holding a pointer
|
||||
///< to is still in the list. Note that the garbage collector is purged when
|
||||
///< the usual housekeeping is done.
|
||||
void SetUseGarbageCollector(void) { useGarbageCollector = true; }
|
||||
void SetExplicitModify(void);
|
||||
///< If you have obtained a write lock on this list, and you don't want it to
|
||||
///< be automatically marked as modified when the lock is released, a call to
|
||||
///< this function will disable this, and you can explicitly call SetModified()
|
||||
///< to have the list marked as modified.
|
||||
void SetModified(void);
|
||||
///< Unconditionally marks this list as modified.
|
||||
void Add(cListObject *Object, cListObject *After = NULL);
|
||||
void Ins(cListObject *Object, cListObject *Before = NULL);
|
||||
void Del(cListObject *Object, bool DeleteObject = true);
|
||||
virtual void Move(int From, int To);
|
||||
void Move(cListObject *From, cListObject *To);
|
||||
virtual void Clear(void);
|
||||
cListObject *Get(int Index) const;
|
||||
bool Contains(const cListObject *Object) const;
|
||||
///< If a pointer to an object contained in this list has been obtained while
|
||||
///< holding a lock, and that lock has been released, but the pointer is kept for
|
||||
///< later use (after obtaining a new lock), Contains() can be called with that
|
||||
///< pointer to make sure the object it points to is still part of this list
|
||||
///< (it may have been deleted or otherwise removed from the list after the lock
|
||||
///< during which the pointer was initially retrieved has been released).
|
||||
const cListObject *Get(int Index) const;
|
||||
cListObject *Get(int Index) { return const_cast<cListObject *>(static_cast<const cListBase *>(this)->Get(Index)); }
|
||||
int Count(void) const { return count; }
|
||||
void Sort(void);
|
||||
};
|
||||
|
||||
template<class T> class cList : public cListBase {
|
||||
public:
|
||||
T *Get(int Index) const { return (T *)cListBase::Get(Index); }
|
||||
T *First(void) const { return (T *)objects; }
|
||||
T *Last(void) const { return (T *)lastObject; }
|
||||
T *Prev(const T *object) const { return (T *)object->cListObject::Prev(); } // need to call cListObject's members to
|
||||
T *Next(const T *object) const { return (T *)object->cListObject::Next(); } // avoid ambiguities in case of a "list of lists"
|
||||
cList(const char *NeedsLocking = NULL): cListBase(NeedsLocking) {}
|
||||
///< Sets up a new cList of the given type T. If NeedsLocking is given, the list
|
||||
///< and any of its elements may only be accessed if the caller holds a lock
|
||||
///< obtained by a call to Lock() (see cListBase::Lock() for details).
|
||||
///< NeedsLocking is used as both a boolean flag to enable locking, and as
|
||||
///< a name to identify this list in debug output. It must be a static string
|
||||
///< and should be no longer than 10 characters. The string will not be copied!
|
||||
const T *Get(int Index) const { return (T *)cListBase::Get(Index); }
|
||||
///< Returns the list element at the given Index, or NULL if no such element
|
||||
///< exists.
|
||||
const T *First(void) const { return (T *)objects; }
|
||||
///< Returns the first element in this list, or NULL if the list is empty.
|
||||
const T *Last(void) const { return (T *)lastObject; }
|
||||
///< Returns the last element in this list, or NULL if the list is empty.
|
||||
const T *Prev(const T *Object) const { return (T *)Object->cListObject::Prev(); } // need to call cListObject's members to
|
||||
///< Returns the element immediately before Object in this list, or NULL
|
||||
///< if Object is the first element in the list. Object must not be NULL!
|
||||
const T *Next(const T *Object) const { return (T *)Object->cListObject::Next(); } // avoid ambiguities in case of a "list of lists"
|
||||
///< Returns the element immediately following Object in this list, or NULL
|
||||
///< if Object is the last element in the list. Object must not be NULL!
|
||||
T *Get(int Index) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Get(Index)); }
|
||||
///< Non-const version of Get().
|
||||
T *First(void) { return const_cast<T *>(static_cast<const cList<T> *>(this)->First()); }
|
||||
///< Non-const version of First().
|
||||
T *Last(void) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Last()); }
|
||||
///< Non-const version of Last().
|
||||
T *Prev(const T *Object) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Prev(Object)); }
|
||||
///< Non-const version of Prev().
|
||||
T *Next(const T *Object) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Next(Object)); }
|
||||
///< Non-const version of Next().
|
||||
};
|
||||
|
||||
// The DEF_LIST_LOCK macro defines a convenience class that can be used to obtain
|
||||
// a lock on a cList and make sure the lock is released when the current scope
|
||||
// is left:
|
||||
|
||||
#define DEF_LIST_LOCK2(Class, Name) \
|
||||
class c##Name##Lock { \
|
||||
private: \
|
||||
cStateKey stateKey; \
|
||||
const c##Class *list; \
|
||||
public: \
|
||||
c##Name##Lock(bool Write = false) \
|
||||
{ \
|
||||
if (Write) \
|
||||
list = c##Class::Get##Name##Write(stateKey); \
|
||||
else \
|
||||
list = c##Class::Get##Name##Read(stateKey); \
|
||||
} \
|
||||
~c##Name##Lock() { stateKey.Remove(); } \
|
||||
const c##Class *Name(void) const { return list; } \
|
||||
c##Class *Name(void) { return const_cast<c##Class *>(list); } \
|
||||
}
|
||||
#define DEF_LIST_LOCK(Class) DEF_LIST_LOCK2(Class, Class)
|
||||
|
||||
// The USE_LIST_LOCK macro sets up a local variable of a class defined by
|
||||
// a suitable DEF_LIST_LOCK, and also a pointer to the provided list:
|
||||
|
||||
#define USE_LIST_LOCK_READ2(Class, Name) \
|
||||
c##Name##Lock Name##Lock(false); \
|
||||
const c##Class *Name __attribute__((unused)) = Name##Lock.Name();
|
||||
#define USE_LIST_LOCK_READ(Class) USE_LIST_LOCK_READ2(Class, Class)
|
||||
|
||||
#define USE_LIST_LOCK_WRITE2(Class, Name) \
|
||||
c##Name##Lock Name##Lock(true); \
|
||||
c##Class *Name __attribute__((unused)) = Name##Lock.Name();
|
||||
#define USE_LIST_LOCK_WRITE(Class) USE_LIST_LOCK_WRITE2(Class, Class)
|
||||
|
||||
template<class T> class cVector {
|
||||
///< cVector may only be used for *simple* types, like int or pointers - not for class objects that allocate additional memory!
|
||||
private:
|
||||
|
298
vdr.c
298
vdr.c
@ -22,7 +22,7 @@
|
||||
*
|
||||
* The project's page is at http://www.tvdr.de
|
||||
*
|
||||
* $Id: vdr.c 4.4 2015/05/21 13:58:33 kls Exp $
|
||||
* $Id: vdr.c 4.5 2015/09/01 10:33:04 kls Exp $
|
||||
*/
|
||||
|
||||
#include <getopt.h>
|
||||
@ -748,8 +748,8 @@ int main(int argc, char *argv[])
|
||||
Sources.Load(AddDirectory(ConfigDirectory, "sources.conf"), true, true);
|
||||
Diseqcs.Load(AddDirectory(ConfigDirectory, "diseqc.conf"), true, Setup.DiSEqC);
|
||||
Scrs.Load(AddDirectory(ConfigDirectory, "scr.conf"), true);
|
||||
Channels.Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true);
|
||||
Timers.Load(AddDirectory(ConfigDirectory, "timers.conf"));
|
||||
cChannels::Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true);
|
||||
cTimers::Load(AddDirectory(ConfigDirectory, "timers.conf"));
|
||||
Commands.Load(AddDirectory(ConfigDirectory, "commands.conf"));
|
||||
RecordingCommands.Load(AddDirectory(ConfigDirectory, "reccmds.conf"));
|
||||
SVDRPhosts.Load(AddDirectory(ConfigDirectory, "svdrphosts.conf"), true);
|
||||
@ -765,8 +765,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
// Recordings:
|
||||
|
||||
Recordings.Update();
|
||||
DeletedRecordings.Update();
|
||||
cRecordings::Update();
|
||||
|
||||
// EPG data:
|
||||
|
||||
@ -879,16 +878,20 @@ int main(int argc, char *argv[])
|
||||
if (!CamSlots.WaitForAllCamSlotsReady(DEVICEREADYTIMEOUT))
|
||||
dsyslog("not all CAM slots ready after %d seconds", DEVICEREADYTIMEOUT);
|
||||
if (*Setup.InitialChannel) {
|
||||
LOCK_CHANNELS_READ;
|
||||
if (isnumber(Setup.InitialChannel)) { // for compatibility with old setup.conf files
|
||||
if (cChannel *Channel = Channels.GetByNumber(atoi(Setup.InitialChannel)))
|
||||
if (const cChannel *Channel = Channels->GetByNumber(atoi(Setup.InitialChannel)))
|
||||
Setup.InitialChannel = Channel->GetChannelID().ToString();
|
||||
}
|
||||
if (cChannel *Channel = Channels.GetByChannelID(tChannelID::FromString(Setup.InitialChannel)))
|
||||
if (const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(Setup.InitialChannel)))
|
||||
Setup.CurrentChannel = Channel->Number();
|
||||
}
|
||||
if (Setup.InitialVolume >= 0)
|
||||
Setup.CurrentVolume = Setup.InitialVolume;
|
||||
Channels.SwitchTo(Setup.CurrentChannel);
|
||||
{
|
||||
LOCK_CHANNELS_READ;
|
||||
Channels->SwitchTo(Setup.CurrentChannel);
|
||||
}
|
||||
if (MuteAudio)
|
||||
cDevice::PrimaryDevice()->ToggleMute();
|
||||
else
|
||||
@ -936,13 +939,14 @@ int main(int argc, char *argv[])
|
||||
static time_t lastTime = 0;
|
||||
if (!cDevice::PrimaryDevice()->HasProgramme()) {
|
||||
if (!CamMenuActive() && Now - lastTime > MINCHANNELWAIT) { // !CamMenuActive() to avoid interfering with the CAM if a CAM menu is open
|
||||
cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel());
|
||||
LOCK_CHANNELS_READ;
|
||||
const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel());
|
||||
if (Channel && (Channel->Vpid() || Channel->Apid(0) || Channel->Dpid(0))) {
|
||||
if (cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(Channel->Number())) // try to switch to the original channel...
|
||||
if (cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels->SwitchTo(Channel->Number())) // try to switch to the original channel...
|
||||
;
|
||||
else if (LastTimerChannel > 0) {
|
||||
Channel = Channels.GetByNumber(LastTimerChannel);
|
||||
if (Channel && cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(LastTimerChannel)) // ...or the one used by the last timer
|
||||
Channel = Channels->GetByNumber(LastTimerChannel);
|
||||
if (Channel && cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels->SwitchTo(LastTimerChannel)) // ...or the one used by the last timer
|
||||
;
|
||||
}
|
||||
}
|
||||
@ -970,40 +974,59 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
// Handle channel and timer modifications:
|
||||
if (!Channels.BeingEdited() && !Timers.BeingEdited()) {
|
||||
int modified = Channels.Modified();
|
||||
static time_t ChannelSaveTimeout = 0;
|
||||
static int TimerState = 0;
|
||||
// Channels and timers need to be stored in a consistent manner,
|
||||
// therefore if one of them is changed, we save both.
|
||||
if (modified == CHANNELSMOD_USER || Timers.Modified(TimerState))
|
||||
ChannelSaveTimeout = 1; // triggers an immediate save
|
||||
else if (modified && !ChannelSaveTimeout)
|
||||
ChannelSaveTimeout = Now + CHANNELSAVEDELTA;
|
||||
bool timeout = ChannelSaveTimeout == 1 || ChannelSaveTimeout && Now > ChannelSaveTimeout && !cRecordControls::Active();
|
||||
if ((modified || timeout) && Channels.Lock(false, 100)) {
|
||||
if (timeout) {
|
||||
Channels.Save();
|
||||
Timers.Save();
|
||||
ChannelSaveTimeout = 0;
|
||||
{
|
||||
// Channels and timers need to be stored in a consistent manner,
|
||||
// therefore if one of them is changed, we save both.
|
||||
static time_t ChannelSaveTimeout = 0;
|
||||
static cStateKey TimersStateKey(true);
|
||||
static cStateKey ChannelsStateKey(true);
|
||||
static int ChannelsModifiedByUser = 0;
|
||||
const cTimers *Timers = cTimers::GetTimersRead(TimersStateKey);
|
||||
const cChannels *Channels = cChannels::GetChannelsRead(ChannelsStateKey);
|
||||
if (ChannelSaveTimeout != 1) {
|
||||
if (Channels) {
|
||||
if (Channels->ModifiedByUser(ChannelsModifiedByUser))
|
||||
ChannelSaveTimeout = 1; // triggers an immediate save
|
||||
else if (!ChannelSaveTimeout)
|
||||
ChannelSaveTimeout = Now + CHANNELSAVEDELTA;
|
||||
}
|
||||
if (Timers)
|
||||
ChannelSaveTimeout = 1; // triggers an immediate save
|
||||
}
|
||||
if (ChannelSaveTimeout && Now > ChannelSaveTimeout && !cRecordControls::Active())
|
||||
ChannelSaveTimeout = 1; // triggers an immediate save
|
||||
if (Timers && Channels) {
|
||||
Channels->Save();
|
||||
Timers->Save();
|
||||
ChannelSaveTimeout = 0;
|
||||
}
|
||||
if (Channels) {
|
||||
for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||
if (Channel->Modification(CHANNELMOD_RETUNE)) {
|
||||
cRecordControls::ChannelDataModified(Channel);
|
||||
if (Channel->Number() == cDevice::CurrentChannel() && cDevice::PrimaryDevice()->HasDecoder()) {
|
||||
if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) {
|
||||
if (cDevice::ActualDevice()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
|
||||
isyslog("retuning due to modification of channel %d (%s)", Channel->Number(), Channel->Name());
|
||||
Channels->SwitchTo(Channel->Number());
|
||||
}
|
||||
}
|
||||
}
|
||||
cStatus::MsgChannelChange(Channel);
|
||||
}
|
||||
}
|
||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
||||
if (Channel->Modification(CHANNELMOD_RETUNE)) {
|
||||
cRecordControls::ChannelDataModified(Channel);
|
||||
if (Channel->Number() == cDevice::CurrentChannel() && cDevice::PrimaryDevice()->HasDecoder()) {
|
||||
if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) {
|
||||
if (cDevice::ActualDevice()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
|
||||
isyslog("retuning due to modification of channel %d (%s)", Channel->Number(), Channel->Name());
|
||||
Channels.SwitchTo(Channel->Number());
|
||||
}
|
||||
}
|
||||
}
|
||||
cStatus::MsgChannelChange(Channel);
|
||||
}
|
||||
}
|
||||
Channels.Unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
// State keys are removed in reverse order!
|
||||
if (Channels)
|
||||
ChannelsStateKey.Remove();
|
||||
if (Timers)
|
||||
TimersStateKey.Remove();
|
||||
if (ChannelSaveTimeout == 1) {
|
||||
// Only one of them was modified, so we reset the state keys to handle them both in the next turn:
|
||||
ChannelsStateKey.Reset();
|
||||
TimersStateKey.Reset();
|
||||
}
|
||||
}
|
||||
// Channel display:
|
||||
if (!EITScanner.Active() && cDevice::CurrentChannel() != LastChannel) {
|
||||
if (!Menu)
|
||||
@ -1013,80 +1036,109 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
if (Now - LastChannelChanged >= Setup.ZapTimeout && LastChannel != PreviousChannel[PreviousChannelIndex])
|
||||
PreviousChannel[PreviousChannelIndex ^= 1] = LastChannel;
|
||||
// Timers and Recordings:
|
||||
if (!Timers.BeingEdited()) {
|
||||
// Assign events to timers:
|
||||
Timers.SetEvents();
|
||||
// Must do all following calls with the exact same time!
|
||||
// Process ongoing recordings:
|
||||
cRecordControls::Process(Now);
|
||||
// Start new recordings:
|
||||
cTimer *Timer = Timers.GetMatch(Now);
|
||||
if (Timer) {
|
||||
if (!cRecordControls::Start(Timer))
|
||||
Timer->SetPending(true);
|
||||
else
|
||||
LastTimerChannel = Timer->Channel()->Number();
|
||||
}
|
||||
// Make sure timers "see" their channel early enough:
|
||||
static time_t LastTimerCheck = 0;
|
||||
if (Now - LastTimerCheck > TIMERCHECKDELTA) { // don't do this too often
|
||||
InhibitEpgScan = false;
|
||||
for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
|
||||
bool InVpsMargin = false;
|
||||
bool NeedsTransponder = false;
|
||||
if (Timer->HasFlags(tfActive) && !Timer->Recording()) {
|
||||
if (Timer->HasFlags(tfVps)) {
|
||||
if (Timer->Matches(Now, true, Setup.VpsMargin)) {
|
||||
InVpsMargin = true;
|
||||
Timer->SetInVpsMargin(InVpsMargin);
|
||||
}
|
||||
else if (Timer->Event()) {
|
||||
InVpsMargin = Timer->Event()->StartTime() <= Now && Now < Timer->Event()->EndTime();
|
||||
NeedsTransponder = Timer->Event()->StartTime() - Now < VPSLOOKAHEADTIME * 3600 && !Timer->Event()->SeenWithin(VPSUPTODATETIME);
|
||||
}
|
||||
else {
|
||||
cSchedulesLock SchedulesLock;
|
||||
const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
|
||||
if (Schedules) {
|
||||
const cSchedule *Schedule = Schedules->GetSchedule(Timer->Channel());
|
||||
InVpsMargin = !Schedule; // we must make sure we have the schedule
|
||||
NeedsTransponder = Schedule && !Schedule->PresentSeenWithin(VPSUPTODATETIME);
|
||||
}
|
||||
}
|
||||
InhibitEpgScan |= InVpsMargin | NeedsTransponder;
|
||||
}
|
||||
else
|
||||
NeedsTransponder = Timer->Matches(Now, true, TIMERLOOKAHEADTIME);
|
||||
}
|
||||
if (NeedsTransponder || InVpsMargin) {
|
||||
// Find a device that provides the required transponder:
|
||||
cDevice *Device = cDevice::GetDeviceForTransponder(Timer->Channel(), MINPRIORITY);
|
||||
if (!Device && InVpsMargin)
|
||||
Device = cDevice::GetDeviceForTransponder(Timer->Channel(), LIVEPRIORITY);
|
||||
// Switch the device to the transponder:
|
||||
if (Device) {
|
||||
bool HadProgramme = cDevice::PrimaryDevice()->HasProgramme();
|
||||
if (!Device->IsTunedToTransponder(Timer->Channel())) {
|
||||
if (Device == cDevice::ActualDevice() && !Device->IsPrimaryDevice())
|
||||
cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode
|
||||
dsyslog("switching device %d to channel %d (%s)", Device->DeviceNumber() + 1, Timer->Channel()->Number(), Timer->Channel()->Name());
|
||||
if (Device->SwitchChannel(Timer->Channel(), false))
|
||||
Device->SetOccupied(TIMERDEVICETIMEOUT);
|
||||
}
|
||||
if (cDevice::PrimaryDevice()->HasDecoder() && HadProgramme && !cDevice::PrimaryDevice()->HasProgramme())
|
||||
Skins.QueueMessage(mtInfo, tr("Upcoming recording!")); // the previous SwitchChannel() has switched away the current live channel
|
||||
}
|
||||
}
|
||||
}
|
||||
LastTimerCheck = Now;
|
||||
}
|
||||
// Delete expired timers:
|
||||
Timers.DeleteExpired();
|
||||
}
|
||||
if (!Menu && Recordings.NeedsUpdate()) {
|
||||
Recordings.Update();
|
||||
DeletedRecordings.Update();
|
||||
{
|
||||
// Timers and Recordings:
|
||||
bool TimersModified = false;
|
||||
bool TriggerRemoteTimerPoll = false;
|
||||
static cStateKey TimersStateKey(true);
|
||||
if (cTimers::GetTimersRead(TimersStateKey)) {
|
||||
TriggerRemoteTimerPoll = true;
|
||||
TimersStateKey.Remove();
|
||||
}
|
||||
cTimers *Timers = cTimers::GetTimersWrite(TimersStateKey);
|
||||
// Get remote timers:
|
||||
TimersModified |= Timers->GetRemoteTimers();
|
||||
// Assign events to timers:
|
||||
static cStateKey SchedulesStateKey;
|
||||
if (const cSchedules *Schedules = cSchedules::GetSchedulesRead(SchedulesStateKey))
|
||||
TimersModified |= Timers->SetEvents(Schedules);
|
||||
// Must do all following calls with the exact same time!
|
||||
// Process ongoing recordings:
|
||||
if (cRecordControls::Process(Timers, Now)) {
|
||||
TimersModified = true;
|
||||
TriggerRemoteTimerPoll = true;
|
||||
}
|
||||
// Must keep the lock on the schedules until after processing the record
|
||||
// controls, in order to avoid short interrupts in case the current event
|
||||
// is replaced by a new one (which some broadcasters do, instead of just
|
||||
// modifying the current event's data):
|
||||
if (SchedulesStateKey.InLock())
|
||||
SchedulesStateKey.Remove();
|
||||
// Start new recordings:
|
||||
if (cTimer *Timer = Timers->GetMatch(Now)) {
|
||||
if (!cRecordControls::Start(Timers, Timer))
|
||||
Timer->SetPending(true);
|
||||
else
|
||||
LastTimerChannel = Timer->Channel()->Number();
|
||||
TimersModified = true;
|
||||
TriggerRemoteTimerPoll = true;
|
||||
}
|
||||
// Make sure timers "see" their channel early enough:
|
||||
static time_t LastTimerCheck = 0;
|
||||
if (Now - LastTimerCheck > TIMERCHECKDELTA) { // don't do this too often
|
||||
InhibitEpgScan = false;
|
||||
for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
|
||||
if (Timer->Remote())
|
||||
continue;
|
||||
bool InVpsMargin = false;
|
||||
bool NeedsTransponder = false;
|
||||
if (Timer->HasFlags(tfActive) && !Timer->Recording()) {
|
||||
if (Timer->HasFlags(tfVps)) {
|
||||
if (Timer->Matches(Now, true, Setup.VpsMargin)) {
|
||||
InVpsMargin = true;
|
||||
Timer->SetInVpsMargin(InVpsMargin);
|
||||
}
|
||||
else if (Timer->Event()) {
|
||||
InVpsMargin = Timer->Event()->StartTime() <= Now && Now < Timer->Event()->EndTime();
|
||||
NeedsTransponder = Timer->Event()->StartTime() - Now < VPSLOOKAHEADTIME * 3600 && !Timer->Event()->SeenWithin(VPSUPTODATETIME);
|
||||
}
|
||||
else {
|
||||
LOCK_SCHEDULES_READ;
|
||||
const cSchedule *Schedule = Schedules->GetSchedule(Timer->Channel());
|
||||
InVpsMargin = !Schedule; // we must make sure we have the schedule
|
||||
NeedsTransponder = Schedule && !Schedule->PresentSeenWithin(VPSUPTODATETIME);
|
||||
}
|
||||
InhibitEpgScan |= InVpsMargin | NeedsTransponder;
|
||||
}
|
||||
else
|
||||
NeedsTransponder = Timer->Matches(Now, true, TIMERLOOKAHEADTIME);
|
||||
}
|
||||
if (NeedsTransponder || InVpsMargin) {
|
||||
// Find a device that provides the required transponder:
|
||||
cDevice *Device = cDevice::GetDeviceForTransponder(Timer->Channel(), MINPRIORITY);
|
||||
if (!Device && InVpsMargin)
|
||||
Device = cDevice::GetDeviceForTransponder(Timer->Channel(), LIVEPRIORITY);
|
||||
// Switch the device to the transponder:
|
||||
if (Device) {
|
||||
bool HadProgramme = cDevice::PrimaryDevice()->HasProgramme();
|
||||
if (!Device->IsTunedToTransponder(Timer->Channel())) {
|
||||
if (Device == cDevice::ActualDevice() && !Device->IsPrimaryDevice())
|
||||
cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode
|
||||
dsyslog("switching device %d to channel %d (%s)", Device->DeviceNumber() + 1, Timer->Channel()->Number(), Timer->Channel()->Name());
|
||||
if (Device->SwitchChannel(Timer->Channel(), false))
|
||||
Device->SetOccupied(TIMERDEVICETIMEOUT);
|
||||
}
|
||||
if (cDevice::PrimaryDevice()->HasDecoder() && HadProgramme && !cDevice::PrimaryDevice()->HasProgramme())
|
||||
Skins.QueueMessage(mtInfo, tr("Upcoming recording!")); // the previous SwitchChannel() has switched away the current live channel
|
||||
}
|
||||
}
|
||||
}
|
||||
LastTimerCheck = Now;
|
||||
}
|
||||
// Delete expired timers:
|
||||
if (Timers->DeleteExpired()) {
|
||||
TimersModified = true;
|
||||
TriggerRemoteTimerPoll = true;
|
||||
}
|
||||
// Trigger remote timer polls:
|
||||
if (TriggerRemoteTimerPoll)
|
||||
Timers->TriggerRemoteTimerPoll();
|
||||
TimersStateKey.Remove(TimersModified);
|
||||
}
|
||||
// Recordings:
|
||||
if (!Menu) {
|
||||
if (cRecordings::NeedsUpdate())
|
||||
cRecordings::Update();
|
||||
}
|
||||
// CAM control:
|
||||
if (!Menu && !cOsd::IsOpen())
|
||||
@ -1359,7 +1411,8 @@ int main(int argc, char *argv[])
|
||||
case k0: {
|
||||
if (PreviousChannel[PreviousChannelIndex ^ 1] == LastChannel || LastChannel != PreviousChannel[0] && LastChannel != PreviousChannel[1])
|
||||
PreviousChannelIndex ^= 1;
|
||||
Channels.SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]);
|
||||
LOCK_CHANNELS_READ;
|
||||
Channels->SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]);
|
||||
break;
|
||||
}
|
||||
// Direct Channel Select:
|
||||
@ -1447,7 +1500,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
// Disk housekeeping:
|
||||
RemoveDeletedRecordings();
|
||||
ClearVanishedRecordings();
|
||||
ListGarbageCollector.Purge();
|
||||
cSchedules::Cleanup();
|
||||
// Plugins housekeeping:
|
||||
PluginManager.Housekeeping();
|
||||
@ -1492,8 +1545,9 @@ Exit:
|
||||
cPositioner::DestroyPositioner();
|
||||
cVideoDirectory::Destroy();
|
||||
EpgHandlers.Clear();
|
||||
PluginManager.Shutdown(true);
|
||||
cSchedules::Cleanup(true);
|
||||
ListGarbageCollector.Purge(true);
|
||||
PluginManager.Shutdown(true);
|
||||
ReportEpgBugFixStats(true);
|
||||
if (WatchdogTimeout > 0)
|
||||
dsyslog("max. latency time %d seconds", MaxLatencyTime);
|
||||
|
17
videodir.c
17
videodir.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: videodir.c 3.4 2013/10/11 09:38:07 kls Exp $
|
||||
* $Id: videodir.c 4.1 2015/08/11 13:39:59 kls Exp $
|
||||
*/
|
||||
|
||||
#include "videodir.h"
|
||||
@ -19,24 +19,31 @@
|
||||
#include "recording.h"
|
||||
#include "tools.h"
|
||||
|
||||
cMutex cVideoDirectory::mutex;
|
||||
cString cVideoDirectory::name;
|
||||
cVideoDirectory *cVideoDirectory::current = NULL;
|
||||
|
||||
cVideoDirectory::cVideoDirectory(void)
|
||||
{
|
||||
mutex.Lock();
|
||||
delete current;
|
||||
current = this;
|
||||
mutex.Unlock();
|
||||
}
|
||||
|
||||
cVideoDirectory::~cVideoDirectory()
|
||||
{
|
||||
mutex.Lock();
|
||||
current = NULL;
|
||||
mutex.Unlock();
|
||||
}
|
||||
|
||||
cVideoDirectory *cVideoDirectory::Current(void)
|
||||
{
|
||||
mutex.Lock();
|
||||
if (!current)
|
||||
current = new cVideoDirectory;
|
||||
new cVideoDirectory;
|
||||
mutex.Unlock();
|
||||
return current;
|
||||
}
|
||||
|
||||
@ -141,7 +148,8 @@ int cVideoDirectory::VideoDiskSpace(int *FreeMB, int *UsedMB)
|
||||
{
|
||||
int used = 0;
|
||||
int free = Current()->FreeMB(&used);
|
||||
int deleted = DeletedRecordings.TotalFileSizeMB();
|
||||
LOCK_DELETEDRECORDINGS_READ;
|
||||
int deleted = DeletedRecordings->TotalFileSizeMB();
|
||||
if (deleted > used)
|
||||
deleted = used; // let's not get beyond 100%
|
||||
free += deleted;
|
||||
@ -202,7 +210,8 @@ bool cVideoDiskUsage::HasChanged(int &State)
|
||||
if (FreeMB != freeMB) {
|
||||
usedPercent = UsedPercent;
|
||||
freeMB = FreeMB;
|
||||
double MBperMinute = Recordings.MBperMinute();
|
||||
LOCK_RECORDINGS_READ;
|
||||
double MBperMinute = Recordings->MBperMinute();
|
||||
if (MBperMinute <= 0)
|
||||
MBperMinute = MB_PER_MINUTE;
|
||||
freeMinutes = int(double(FreeMB) / MBperMinute);
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user