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.
|
- Bumped all version numbers to 2.2.0.
|
||||||
- Official release.
|
- Official release.
|
||||||
|
|
||||||
2015-04-29: Version 2.3.1
|
2015-09-01: Version 2.3.1
|
||||||
|
|
||||||
- The new function cOsd::MaxPixmapSize() can be called to determine the maximum size
|
- The new function cOsd::MaxPixmapSize() can be called to determine the maximum size
|
||||||
a cPixmap may have on the current OSD. The 'osddemo' example has been modified
|
a cPixmap may have on the current OSD. The 'osddemo' example has been modified
|
||||||
@ -8671,3 +8671,114 @@ Video Disk Recorder Revision History
|
|||||||
this VDR is connected to via SVDRP.
|
this VDR is connected to via SVDRP.
|
||||||
- The new class cSVDRPCommand can be used to execute an SVDRP command on one of
|
- The new class cSVDRPCommand can be used to execute an SVDRP command on one of
|
||||||
the servers this VDR is connected to, and retrieve the result.
|
the servers this VDR is connected to, and retrieve the result.
|
||||||
|
- The cTimer class now has a new member named 'remote', which holds the name of the
|
||||||
|
remote server this timer will record on. If this is NULL, it is a local timer.
|
||||||
|
- Timers from other VDRs that are connected to this VDR via SVDRP are now
|
||||||
|
automatically fetched and stored in the global Timers list. In order for this
|
||||||
|
to work, all of the channels used by timers on the remote VDR must also be
|
||||||
|
defined on the local VDR (however, not necessarily in the same sequence).
|
||||||
|
Automatic channel syncing will be implemented later.
|
||||||
|
- The main menu of the LCARS skin now displays a small rectangle on the left side
|
||||||
|
of a timer if this is a remote timer. The color of that rectangle changes if
|
||||||
|
the timer is currently recording on the remote VDR.
|
||||||
|
- Accessing the global Timers list now has to be protected by proper locking,
|
||||||
|
because SVDRP commands are now executed in a separate thread.
|
||||||
|
The introduction of this locking mechanism required the following changes:
|
||||||
|
+ The new classes cStateLock and cStateKey are used to implement locking
|
||||||
|
with quick detection of state changes.
|
||||||
|
+ cConfig::cConfig() now has a parameter that indicates whether this list
|
||||||
|
requires locking.
|
||||||
|
+ The global lists of Timers, Channels, Schedules and Recordings are no longer
|
||||||
|
static variables. They are now pointers that need to be retrieved through
|
||||||
|
a call to cTimers::GetTimersRead/Write(), cChannels::GetChannelsRead/Write(),
|
||||||
|
cSchedules::GetSchedulesRead/Write() and cRecordings::GetRecordingsRead/Write(),
|
||||||
|
respectively.
|
||||||
|
+ References from/to link channels are now removed in cChannels::Del() rather
|
||||||
|
than cChannel::~cChannel(), to make sure the caller holds a proper lock.
|
||||||
|
+ cChannel::HasTimer() has been removed. This information is now retrieved
|
||||||
|
via cSchedule::HasTimer().
|
||||||
|
+ Several member functions of cChannel, cTimer, cMarks and cRecording have
|
||||||
|
been made 'const', and some of them are now available as both 'const' and
|
||||||
|
'non-const' versions.
|
||||||
|
+ The cChannel::Set...() functions are now 'bool' and return true if they have
|
||||||
|
actually changed any of the channels's members.
|
||||||
|
+ cChannels::SetModified() has been renamed to cChannels::SetModifiedByUser().
|
||||||
|
+ cChannels::Modified() has been renamed to cChannels::ModifiedByUser(), and
|
||||||
|
now has a 'State' parameter that allows the caller to see whether a channel
|
||||||
|
has been modified since the last call to this function with the same State
|
||||||
|
variable.
|
||||||
|
+ The macros CHANNELSMOD_NONE/_AUTO/_USER have been removed.
|
||||||
|
+ cMarks now requires locking via cStateKey.
|
||||||
|
+ cSortedTimers now requires a pointer to the list of timers.
|
||||||
|
+ cEvent::HasTimer() no longer scans the list of timers to check whether an event
|
||||||
|
is referenced by a timer, but rather keeps score of how many timers reference
|
||||||
|
it. This was necessary in order to avoid having to lock the list of timers from
|
||||||
|
within a cEvent.
|
||||||
|
+ The new class cListGarbageCollector is used to temporary store any objects deleted
|
||||||
|
from cLists that require locking. This allows pointers to such objects to be
|
||||||
|
dereferenced even if the objects are no longer part of the list.
|
||||||
|
+ cListBase::Contains() can be used to check whether a particular object is still
|
||||||
|
contained in that list.
|
||||||
|
+ Outdated events are no longer "phased out", but rather deleted right away and thus
|
||||||
|
taken care of by the new "garbage collector" of the list.
|
||||||
|
+ Deleted cRecording objects are no longer kept in a list of "vanished" recordings,
|
||||||
|
but are rather taken care of by the new "garbage collector" of the list.
|
||||||
|
+ cSchedules::ClearAll() has been removed. The functionality is now implemented
|
||||||
|
directly in cSVDRPServer::CmdCLRE().
|
||||||
|
+ tEventID has been changed to u_int16_t in order to make room for the new member
|
||||||
|
numTimers in cEvent.
|
||||||
|
+ cSchedule now has a member Modified(), which can be used with a State variable
|
||||||
|
to quickly determine whether this schedule has been modified since the last call
|
||||||
|
to this function with the same State variable.
|
||||||
|
+ cSchedulesLock has been removed. Locking the list of schedules is now done via
|
||||||
|
the cList's new locking mechanism.
|
||||||
|
+ The 'OnlyRunningStatus' parameters in cEpgHandler::BeginSegmentTransfer() and
|
||||||
|
cEpgHandler::EndSegmentTransfer() are now obsolete. They are still present in
|
||||||
|
the interface for backward compatibility, but may be removed in a future version.
|
||||||
|
Their value is always 'false'.
|
||||||
|
+ The constant tcMod is no longer used in cStatus::TimerChange(). The definition is
|
||||||
|
still there for backward compatibility.
|
||||||
|
Plugins that access the global lists of Timers, Channels, Recordings or Schedules
|
||||||
|
will need to be adapted as follows:
|
||||||
|
+ Instead of directly accessing the global variables Timers, Channels or Recordings,
|
||||||
|
they need to set up a cStateKey variable and call the proper getter function,
|
||||||
|
as in
|
||||||
|
cStateKey StateKey;
|
||||||
|
if (const cTimers *Timers = cTimers::GetTimersRead(StateKey)) {
|
||||||
|
// access the timers
|
||||||
|
StateKey.Remove();
|
||||||
|
}
|
||||||
|
and
|
||||||
|
cStateKey StateKey;
|
||||||
|
if (cTimers *Timers = cTimers::GetTimersWrite(StateKey)) {
|
||||||
|
// access the timers
|
||||||
|
StateKey.Remove();
|
||||||
|
}
|
||||||
|
See timers.h, thread.h and tools.h for details on this new locking mechanism.
|
||||||
|
+ There are convenience macros for easily accessing these lists without having
|
||||||
|
to explicitly set up a cStateKey and calling its Remove() function. These macros
|
||||||
|
have the form LOCK_*_READ/WRITE (with '*' being TIMERS, CHANNELS, SCHEDULES or
|
||||||
|
RECORDINGS). Simply put such a macro before the point where you need to access
|
||||||
|
the respective list, and there will be a pointer named Timers, Channels, Schedules
|
||||||
|
or Recordings, respectively, which is valid until the end of the current block.
|
||||||
|
+ If a plugin needs to access several of the global lists in parallel, locking must
|
||||||
|
always be done in the sequence Timers, Channels, Recordings, Schedules. This is
|
||||||
|
necessary to make sure that different threads that need to lock several lists at
|
||||||
|
the same time don't end up in a deadlock.
|
||||||
|
+ Some pointer variables may need to be made 'const'. The compiler will tell you
|
||||||
|
about these.
|
||||||
|
- cSectionSyncer has been improved to better handle missed sections.
|
||||||
|
- Added a missing initialization of 'seen' in cChannel's copy constructor.
|
||||||
|
- Background modifications of channels, timers and events are now displayed immediately
|
||||||
|
in the corresponding menus.
|
||||||
|
- cEIT now checks the version of the tables before doing any processing, which saves
|
||||||
|
a lot of locking and processing.
|
||||||
|
- If a timer is newly created with the Red button in the Schedule menu, and the timer
|
||||||
|
is presented to the user in the "Edit timer" menu because it will start immediately,
|
||||||
|
it now *must* be confirmed with "Ok" to set the timer. Otherwise the timer will not
|
||||||
|
be created.
|
||||||
|
- Recordings and deleted recordings are now scanned in a single thread.
|
||||||
|
- The new SVDRP command POLL is used by automatically established peer-to-peer
|
||||||
|
connections to trigger fetching remote timers.
|
||||||
|
- You can now set DumpSVDRPDataTransfer in svdrp.c to true to have all SVDRP
|
||||||
|
communication printed to the console for debugging.
|
||||||
|
335
channels.c
335
channels.c
@ -4,15 +4,13 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: channels.c 4.1 2015/03/13 11:34:28 kls Exp $
|
* $Id: channels.c 4.2 2015/08/29 12:16:24 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "channels.h"
|
#include "channels.h"
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
#include "epg.h"
|
|
||||||
#include "libsi/si.h"
|
#include "libsi/si.h"
|
||||||
#include "timers.h"
|
|
||||||
|
|
||||||
// IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d'
|
// IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d'
|
||||||
// format characters in order to allow any number of blanks after a numeric
|
// format characters in order to allow any number of blanks after a numeric
|
||||||
@ -79,27 +77,13 @@ cChannel::cChannel(const cChannel &Channel)
|
|||||||
schedule = NULL;
|
schedule = NULL;
|
||||||
linkChannels = NULL;
|
linkChannels = NULL;
|
||||||
refChannel = NULL;
|
refChannel = NULL;
|
||||||
|
seen = 0;
|
||||||
*this = Channel;
|
*this = Channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
cChannel::~cChannel()
|
cChannel::~cChannel()
|
||||||
{
|
{
|
||||||
delete linkChannels;
|
delete linkChannels; // any links from other channels pointing to this one have been deleted in cChannels::Del()
|
||||||
linkChannels = NULL; // more than one channel can link to this one, so we need the following loop
|
|
||||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
|
||||||
if (Channel->linkChannels) {
|
|
||||||
for (cLinkChannel *lc = Channel->linkChannels->First(); lc; lc = Channel->linkChannels->Next(lc)) {
|
|
||||||
if (lc->Channel() == this) {
|
|
||||||
Channel->linkChannels->Del(lc);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Channel->linkChannels->Count() == 0) {
|
|
||||||
delete Channel->linkChannels;
|
|
||||||
Channel->linkChannels = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(name);
|
free(name);
|
||||||
free(shortName);
|
free(shortName);
|
||||||
free(provider);
|
free(provider);
|
||||||
@ -167,16 +151,7 @@ int cChannel::Transponder(void) const
|
|||||||
return tf;
|
return tf;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cChannel::HasTimer(void) const
|
int cChannel::Modification(int Mask) const
|
||||||
{
|
|
||||||
for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
|
|
||||||
if (Timer->Channel() == this)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cChannel::Modification(int Mask)
|
|
||||||
{
|
{
|
||||||
int Result = modification & Mask;
|
int Result = modification & Mask;
|
||||||
modification = CHANNELMOD_NONE;
|
modification = CHANNELMOD_NONE;
|
||||||
@ -223,53 +198,57 @@ bool cChannel::SetTransponderData(int Source, int Frequency, int Srate, const ch
|
|||||||
if (Number() && !Quiet) {
|
if (Number() && !Quiet) {
|
||||||
dsyslog("changing transponder data of channel %d (%s) from %s to %s", Number(), name, *OldTransponderData, *TransponderDataToString());
|
dsyslog("changing transponder data of channel %d (%s) from %s to %s", Number(), name, *OldTransponderData, *TransponderDataToString());
|
||||||
modification |= CHANNELMOD_TRANSP;
|
modification |= CHANNELMOD_TRANSP;
|
||||||
Channels.SetModified();
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cChannel::SetSource(int Source)
|
bool cChannel::SetSource(int Source)
|
||||||
{
|
{
|
||||||
if (source != Source) {
|
if (source != Source) {
|
||||||
if (Number()) {
|
if (Number()) {
|
||||||
dsyslog("changing source of channel %d (%s) from %s to %s", Number(), name, *cSource::ToString(source), *cSource::ToString(Source));
|
dsyslog("changing source of channel %d (%s) from %s to %s", Number(), name, *cSource::ToString(source), *cSource::ToString(Source));
|
||||||
modification |= CHANNELMOD_TRANSP;
|
modification |= CHANNELMOD_TRANSP;
|
||||||
Channels.SetModified();
|
|
||||||
}
|
}
|
||||||
source = Source;
|
source = Source;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cChannel::SetId(int Nid, int Tid, int Sid, int Rid)
|
bool cChannel::SetId(cChannels *Channels, int Nid, int Tid, int Sid, int Rid)
|
||||||
{
|
{
|
||||||
if (nid != Nid || tid != Tid || sid != Sid || rid != Rid) {
|
if (nid != Nid || tid != Tid || sid != Sid || rid != Rid) {
|
||||||
if (Number()) {
|
if (Channels && Number()) {
|
||||||
dsyslog("changing id of channel %d (%s) from %d-%d-%d-%d to %d-%d-%d-%d", Number(), name, nid, tid, sid, rid, Nid, Tid, Sid, Rid);
|
dsyslog("changing id of channel %d (%s) from %d-%d-%d-%d to %d-%d-%d-%d", Number(), name, nid, tid, sid, rid, Nid, Tid, Sid, Rid);
|
||||||
modification |= CHANNELMOD_ID;
|
modification |= CHANNELMOD_ID;
|
||||||
Channels.SetModified();
|
Channels->UnhashChannel(this);
|
||||||
Channels.UnhashChannel(this);
|
|
||||||
}
|
}
|
||||||
nid = Nid;
|
nid = Nid;
|
||||||
tid = Tid;
|
tid = Tid;
|
||||||
sid = Sid;
|
sid = Sid;
|
||||||
rid = Rid;
|
rid = Rid;
|
||||||
if (Number())
|
if (Channels)
|
||||||
Channels.HashChannel(this);
|
Channels->HashChannel(this);
|
||||||
schedule = NULL;
|
schedule = NULL;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cChannel::SetLcn(int Lcn)
|
bool cChannel::SetLcn(int Lcn)
|
||||||
{
|
{
|
||||||
if (lcn != Lcn) {
|
if (lcn != Lcn) {
|
||||||
if (Number())
|
if (Number())
|
||||||
dsyslog("changing lcn of channel %d (%s) from %d to %d\n", Number(), name, lcn, Lcn);
|
dsyslog("changing lcn of channel %d (%s) from %d to %d\n", Number(), name, lcn, Lcn);
|
||||||
lcn = Lcn;
|
lcn = Lcn;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cChannel::SetName(const char *Name, const char *ShortName, const char *Provider)
|
bool cChannel::SetName(const char *Name, const char *ShortName, const char *Provider)
|
||||||
{
|
{
|
||||||
if (!isempty(Name)) {
|
if (!isempty(Name)) {
|
||||||
bool nn = strcmp(name, Name) != 0;
|
bool nn = strcmp(name, Name) != 0;
|
||||||
@ -279,7 +258,6 @@ void cChannel::SetName(const char *Name, const char *ShortName, const char *Prov
|
|||||||
if (Number()) {
|
if (Number()) {
|
||||||
dsyslog("changing name of channel %d from '%s,%s;%s' to '%s,%s;%s'", Number(), name, shortName, provider, Name, ShortName, Provider);
|
dsyslog("changing name of channel %d from '%s,%s;%s' to '%s,%s;%s'", Number(), name, shortName, provider, Name, ShortName, Provider);
|
||||||
modification |= CHANNELMOD_NAME;
|
modification |= CHANNELMOD_NAME;
|
||||||
Channels.SetModified();
|
|
||||||
}
|
}
|
||||||
if (nn) {
|
if (nn) {
|
||||||
name = strcpyrealloc(name, Name);
|
name = strcpyrealloc(name, Name);
|
||||||
@ -291,20 +269,23 @@ void cChannel::SetName(const char *Name, const char *ShortName, const char *Prov
|
|||||||
}
|
}
|
||||||
if (np)
|
if (np)
|
||||||
provider = strcpyrealloc(provider, Provider);
|
provider = strcpyrealloc(provider, Provider);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cChannel::SetPortalName(const char *PortalName)
|
bool cChannel::SetPortalName(const char *PortalName)
|
||||||
{
|
{
|
||||||
if (!isempty(PortalName) && strcmp(portalName, PortalName) != 0) {
|
if (!isempty(PortalName) && strcmp(portalName, PortalName) != 0) {
|
||||||
if (Number()) {
|
if (Number()) {
|
||||||
dsyslog("changing portal name of channel %d (%s) from '%s' to '%s'", Number(), name, portalName, PortalName);
|
dsyslog("changing portal name of channel %d (%s) from '%s' to '%s'", Number(), name, portalName, PortalName);
|
||||||
modification |= CHANNELMOD_NAME;
|
modification |= CHANNELMOD_NAME;
|
||||||
Channels.SetModified();
|
|
||||||
}
|
}
|
||||||
portalName = strcpyrealloc(portalName, PortalName);
|
portalName = strcpyrealloc(portalName, PortalName);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define STRDIFF 0x01
|
#define STRDIFF 0x01
|
||||||
@ -349,7 +330,7 @@ static int IntArrayToString(char *s, const int *a, int Base = 10, const char n[]
|
|||||||
return q - s;
|
return q - s;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cChannel::SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid)
|
bool cChannel::SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid)
|
||||||
{
|
{
|
||||||
int mod = CHANNELMOD_NONE;
|
int mod = CHANNELMOD_NONE;
|
||||||
if (vpid != Vpid || ppid != Ppid || vtype != Vtype)
|
if (vpid != Vpid || ppid != Ppid || vtype != Vtype)
|
||||||
@ -412,25 +393,33 @@ void cChannel::SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, c
|
|||||||
spids[MAXSPIDS] = 0;
|
spids[MAXSPIDS] = 0;
|
||||||
tpid = Tpid;
|
tpid = Tpid;
|
||||||
modification |= mod;
|
modification |= mod;
|
||||||
if (Number())
|
return true;
|
||||||
Channels.SetModified();
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cChannel::SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds)
|
bool cChannel::SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds)
|
||||||
{
|
{
|
||||||
|
bool Modified = false;
|
||||||
if (SubtitlingTypes) {
|
if (SubtitlingTypes) {
|
||||||
for (int i = 0; i < MAXSPIDS; i++)
|
for (int i = 0; i < MAXSPIDS; i++) {
|
||||||
|
Modified = subtitlingTypes[i] != SubtitlingTypes[i];
|
||||||
subtitlingTypes[i] = SubtitlingTypes[i];
|
subtitlingTypes[i] = SubtitlingTypes[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (CompositionPageIds) {
|
if (CompositionPageIds) {
|
||||||
for (int i = 0; i < MAXSPIDS; i++)
|
for (int i = 0; i < MAXSPIDS; i++) {
|
||||||
|
Modified = compositionPageIds[i] != CompositionPageIds[i];
|
||||||
compositionPageIds[i] = CompositionPageIds[i];
|
compositionPageIds[i] = CompositionPageIds[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (AncillaryPageIds) {
|
if (AncillaryPageIds) {
|
||||||
for (int i = 0; i < MAXSPIDS; i++)
|
for (int i = 0; i < MAXSPIDS; i++) {
|
||||||
|
Modified = ancillaryPageIds[i] != AncillaryPageIds[i];
|
||||||
ancillaryPageIds[i] = AncillaryPageIds[i];
|
ancillaryPageIds[i] = AncillaryPageIds[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return Modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cChannel::SetSeen(void)
|
void cChannel::SetSeen(void)
|
||||||
@ -438,10 +427,26 @@ void cChannel::SetSeen(void)
|
|||||||
seen = time(NULL);
|
seen = time(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cChannel::SetCaIds(const int *CaIds)
|
void cChannel::DelLinkChannel(cChannel *LinkChannel)
|
||||||
|
{
|
||||||
|
if (linkChannels) {
|
||||||
|
for (cLinkChannel *lc = linkChannels->First(); lc; lc = linkChannels->Next(lc)) {
|
||||||
|
if (lc->Channel() == LinkChannel) {
|
||||||
|
linkChannels->Del(lc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (linkChannels->Count() == 0) {
|
||||||
|
delete linkChannels;
|
||||||
|
linkChannels = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cChannel::SetCaIds(const int *CaIds)
|
||||||
{
|
{
|
||||||
if (caids[0] && caids[0] <= CA_USER_MAX)
|
if (caids[0] && caids[0] <= CA_USER_MAX)
|
||||||
return; // special values will not be overwritten
|
return false; // special values will not be overwritten
|
||||||
if (IntArraysDiffer(caids, CaIds)) {
|
if (IntArraysDiffer(caids, CaIds)) {
|
||||||
char OldCaIdsBuf[MAXCAIDS * 5 + 10]; // 5: 4 digits plus delimiting ',', 10: paranoia
|
char OldCaIdsBuf[MAXCAIDS * 5 + 10]; // 5: 4 digits plus delimiting ',', 10: paranoia
|
||||||
char NewCaIdsBuf[MAXCAIDS * 5 + 10];
|
char NewCaIdsBuf[MAXCAIDS * 5 + 10];
|
||||||
@ -455,24 +460,26 @@ void cChannel::SetCaIds(const int *CaIds)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
modification |= CHANNELMOD_CA;
|
modification |= CHANNELMOD_CA;
|
||||||
Channels.SetModified();
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cChannel::SetCaDescriptors(int Level)
|
bool cChannel::SetCaDescriptors(int Level)
|
||||||
{
|
{
|
||||||
if (Level > 0) {
|
if (Level > 0) {
|
||||||
modification |= CHANNELMOD_CA;
|
modification |= CHANNELMOD_CA;
|
||||||
Channels.SetModified();
|
|
||||||
if (Number() && Level > 1)
|
if (Number() && Level > 1)
|
||||||
dsyslog("changing ca descriptors of channel %d (%s)", Number(), name);
|
dsyslog("changing ca descriptors of channel %d (%s)", Number(), name);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cChannel::SetLinkChannels(cLinkChannels *LinkChannels)
|
bool cChannel::SetLinkChannels(cLinkChannels *LinkChannels)
|
||||||
{
|
{
|
||||||
if (!linkChannels && !LinkChannels)
|
if (!linkChannels && !LinkChannels)
|
||||||
return;
|
return false;
|
||||||
if (linkChannels && LinkChannels) {
|
if (linkChannels && LinkChannels) {
|
||||||
cLinkChannel *lca = linkChannels->First();
|
cLinkChannel *lca = linkChannels->First();
|
||||||
cLinkChannel *lcb = LinkChannels->First();
|
cLinkChannel *lcb = LinkChannels->First();
|
||||||
@ -486,7 +493,7 @@ void cChannel::SetLinkChannels(cLinkChannels *LinkChannels)
|
|||||||
}
|
}
|
||||||
if (!lca && !lcb) {
|
if (!lca && !lcb) {
|
||||||
delete LinkChannels;
|
delete LinkChannels;
|
||||||
return; // linkage has not changed
|
return false; // linkage has not changed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
char buffer[((linkChannels ? linkChannels->Count() : 0) + (LinkChannels ? LinkChannels->Count() : 0)) * 6 + 256]; // 6: 5 digit channel number plus blank, 256: other texts (see below) plus reserve
|
char buffer[((linkChannels ? linkChannels->Count() : 0) + (LinkChannels ? LinkChannels->Count() : 0)) * 6 + 256]; // 6: 5 digit channel number plus blank, 256: other texts (see below) plus reserve
|
||||||
@ -514,6 +521,7 @@ void cChannel::SetLinkChannels(cLinkChannels *LinkChannels)
|
|||||||
q += sprintf(q, " none");
|
q += sprintf(q, " none");
|
||||||
if (Number())
|
if (Number())
|
||||||
dsyslog("%s", buffer);
|
dsyslog("%s", buffer);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cChannel::SetRefChannel(cChannel *RefChannel)
|
void cChannel::SetRefChannel(cChannel *RefChannel)
|
||||||
@ -819,40 +827,52 @@ public:
|
|||||||
|
|
||||||
// --- cChannels -------------------------------------------------------------
|
// --- cChannels -------------------------------------------------------------
|
||||||
|
|
||||||
cChannels Channels;
|
cChannels cChannels::channels;
|
||||||
|
int cChannels::maxNumber = 0;
|
||||||
|
int cChannels::maxChannelNameLength = 0;
|
||||||
|
int cChannels::maxShortChannelNameLength = 0;
|
||||||
|
|
||||||
cChannels::cChannels(void)
|
cChannels::cChannels(void)
|
||||||
|
:cConfig<cChannel>("Channels")
|
||||||
{
|
{
|
||||||
maxNumber = 0;
|
modifiedByUser = 0;
|
||||||
maxChannelNameLength = 0;
|
}
|
||||||
maxShortChannelNameLength = 0;
|
|
||||||
modified = CHANNELSMOD_NONE;
|
const cChannels *cChannels::GetChannelsRead(cStateKey &StateKey, int TimeoutMs)
|
||||||
|
{
|
||||||
|
return channels.Lock(StateKey, false, TimeoutMs) ? &channels : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
cChannels *cChannels::GetChannelsWrite(cStateKey &StateKey, int TimeoutMs)
|
||||||
|
{
|
||||||
|
return channels.Lock(StateKey, true, TimeoutMs) ? &channels : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cChannels::DeleteDuplicateChannels(void)
|
void cChannels::DeleteDuplicateChannels(void)
|
||||||
{
|
{
|
||||||
cList<cChannelSorter> ChannelSorter;
|
cList<cChannelSorter> ChannelSorter;
|
||||||
for (cChannel *channel = First(); channel; channel = Next(channel)) {
|
for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
|
||||||
if (!channel->GroupSep())
|
if (!Channel->GroupSep())
|
||||||
ChannelSorter.Add(new cChannelSorter(channel));
|
ChannelSorter.Add(new cChannelSorter(Channel));
|
||||||
}
|
}
|
||||||
ChannelSorter.Sort();
|
ChannelSorter.Sort();
|
||||||
cChannelSorter *cs = ChannelSorter.First();
|
cChannelSorter *cs = ChannelSorter.First();
|
||||||
while (cs) {
|
while (cs) {
|
||||||
cChannelSorter *next = ChannelSorter.Next(cs);
|
cChannelSorter *Next = ChannelSorter.Next(cs);
|
||||||
if (next && cs->channelID == next->channelID) {
|
if (Next && cs->channelID == Next->channelID) {
|
||||||
dsyslog("deleting duplicate channel %s", *next->channel->ToText());
|
dsyslog("deleting duplicate channel %s", *Next->channel->ToText());
|
||||||
Del(next->channel);
|
Del(Next->channel);
|
||||||
}
|
}
|
||||||
cs = next;
|
cs = Next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cChannels::Load(const char *FileName, bool AllowComments, bool MustExist)
|
bool cChannels::Load(const char *FileName, bool AllowComments, bool MustExist)
|
||||||
{
|
{
|
||||||
if (cConfig<cChannel>::Load(FileName, AllowComments, MustExist)) {
|
LOCK_CHANNELS_WRITE;
|
||||||
DeleteDuplicateChannels();
|
if (channels.cConfig<cChannel>::Load(FileName, AllowComments, MustExist)) {
|
||||||
ReNumber();
|
channels.DeleteDuplicateChannels();
|
||||||
|
channels.ReNumber();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -868,36 +888,36 @@ void cChannels::UnhashChannel(cChannel *Channel)
|
|||||||
channelsHashSid.Del(Channel, Channel->Sid());
|
channelsHashSid.Del(Channel, Channel->Sid());
|
||||||
}
|
}
|
||||||
|
|
||||||
int cChannels::GetNextGroup(int Idx)
|
int cChannels::GetNextGroup(int Idx) const
|
||||||
{
|
{
|
||||||
cChannel *channel = Get(++Idx);
|
const cChannel *Channel = Get(++Idx);
|
||||||
while (channel && !(channel->GroupSep() && *channel->Name()))
|
while (Channel && !(Channel->GroupSep() && *Channel->Name()))
|
||||||
channel = Get(++Idx);
|
Channel = Get(++Idx);
|
||||||
return channel ? Idx : -1;
|
return Channel ? Idx : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cChannels::GetPrevGroup(int Idx)
|
int cChannels::GetPrevGroup(int Idx) const
|
||||||
{
|
{
|
||||||
cChannel *channel = Get(--Idx);
|
const cChannel *Channel = Get(--Idx);
|
||||||
while (channel && !(channel->GroupSep() && *channel->Name()))
|
while (Channel && !(Channel->GroupSep() && *Channel->Name()))
|
||||||
channel = Get(--Idx);
|
Channel = Get(--Idx);
|
||||||
return channel ? Idx : -1;
|
return Channel ? Idx : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cChannels::GetNextNormal(int Idx)
|
int cChannels::GetNextNormal(int Idx) const
|
||||||
{
|
{
|
||||||
cChannel *channel = Get(++Idx);
|
const cChannel *Channel = Get(++Idx);
|
||||||
while (channel && channel->GroupSep())
|
while (Channel && Channel->GroupSep())
|
||||||
channel = Get(++Idx);
|
Channel = Get(++Idx);
|
||||||
return channel ? Idx : -1;
|
return Channel ? Idx : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cChannels::GetPrevNormal(int Idx)
|
int cChannels::GetPrevNormal(int Idx) const
|
||||||
{
|
{
|
||||||
cChannel *channel = Get(--Idx);
|
const cChannel *Channel = Get(--Idx);
|
||||||
while (channel && channel->GroupSep())
|
while (Channel && Channel->GroupSep())
|
||||||
channel = Get(--Idx);
|
Channel = Get(--Idx);
|
||||||
return channel ? Idx : -1;
|
return Channel ? Idx : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cChannels::ReNumber(void)
|
void cChannels::ReNumber(void)
|
||||||
@ -905,110 +925,120 @@ void cChannels::ReNumber(void)
|
|||||||
channelsHashSid.Clear();
|
channelsHashSid.Clear();
|
||||||
maxNumber = 0;
|
maxNumber = 0;
|
||||||
int Number = 1;
|
int Number = 1;
|
||||||
for (cChannel *channel = First(); channel; channel = Next(channel)) {
|
for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
|
||||||
if (channel->GroupSep()) {
|
if (Channel->GroupSep()) {
|
||||||
if (channel->Number() > Number)
|
if (Channel->Number() > Number)
|
||||||
Number = channel->Number();
|
Number = Channel->Number();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
HashChannel(channel);
|
HashChannel(Channel);
|
||||||
maxNumber = Number;
|
maxNumber = Number;
|
||||||
channel->SetNumber(Number++);
|
Channel->SetNumber(Number++);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cChannel *cChannels::GetByNumber(int Number, int SkipGap)
|
void cChannels::Del(cChannel *Channel)
|
||||||
{
|
{
|
||||||
cChannel *previous = NULL;
|
UnhashChannel(Channel);
|
||||||
for (cChannel *channel = First(); channel; channel = Next(channel)) {
|
for (cChannel *ch = First(); ch; ch = Next(ch))
|
||||||
if (!channel->GroupSep()) {
|
ch->DelLinkChannel(Channel);
|
||||||
if (channel->Number() == Number)
|
cList<cChannel>::Del(Channel);
|
||||||
return channel;
|
}
|
||||||
else if (SkipGap && channel->Number() > Number)
|
|
||||||
return SkipGap > 0 ? channel : previous;
|
const cChannel *cChannels::GetByNumber(int Number, int SkipGap) const
|
||||||
previous = channel;
|
{
|
||||||
|
const cChannel *Previous = NULL;
|
||||||
|
for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
|
||||||
|
if (!Channel->GroupSep()) {
|
||||||
|
if (Channel->Number() == Number)
|
||||||
|
return Channel;
|
||||||
|
else if (SkipGap && Channel->Number() > Number)
|
||||||
|
return SkipGap > 0 ? Channel : Previous;
|
||||||
|
Previous = Channel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
cChannel *cChannels::GetByServiceID(int Source, int Transponder, unsigned short ServiceID)
|
const cChannel *cChannels::GetByServiceID(int Source, int Transponder, unsigned short ServiceID) const
|
||||||
{
|
{
|
||||||
cList<cHashObject> *list = channelsHashSid.GetList(ServiceID);
|
cList<cHashObject> *list = channelsHashSid.GetList(ServiceID);
|
||||||
if (list) {
|
if (list) {
|
||||||
for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
|
for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
|
||||||
cChannel *channel = (cChannel *)hobj->Object();
|
cChannel *Channel = (cChannel *)hobj->Object();
|
||||||
if (channel->Sid() == ServiceID && channel->Source() == Source && ISTRANSPONDER(channel->Transponder(), Transponder))
|
if (Channel->Sid() == ServiceID && Channel->Source() == Source && ISTRANSPONDER(Channel->Transponder(), Transponder))
|
||||||
return channel;
|
return Channel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
cChannel *cChannels::GetByChannelID(tChannelID ChannelID, bool TryWithoutRid, bool TryWithoutPolarization)
|
const cChannel *cChannels::GetByChannelID(tChannelID ChannelID, bool TryWithoutRid, bool TryWithoutPolarization) const
|
||||||
{
|
{
|
||||||
int sid = ChannelID.Sid();
|
int sid = ChannelID.Sid();
|
||||||
cList<cHashObject> *list = channelsHashSid.GetList(sid);
|
cList<cHashObject> *list = channelsHashSid.GetList(sid);
|
||||||
if (list) {
|
if (list) {
|
||||||
for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
|
for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
|
||||||
cChannel *channel = (cChannel *)hobj->Object();
|
cChannel *Channel = (cChannel *)hobj->Object();
|
||||||
if (channel->Sid() == sid && channel->GetChannelID() == ChannelID)
|
if (Channel->Sid() == sid && Channel->GetChannelID() == ChannelID)
|
||||||
return channel;
|
return Channel;
|
||||||
}
|
}
|
||||||
if (TryWithoutRid) {
|
if (TryWithoutRid) {
|
||||||
ChannelID.ClrRid();
|
ChannelID.ClrRid();
|
||||||
for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
|
for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
|
||||||
cChannel *channel = (cChannel *)hobj->Object();
|
cChannel *Channel = (cChannel *)hobj->Object();
|
||||||
if (channel->Sid() == sid && channel->GetChannelID().ClrRid() == ChannelID)
|
if (Channel->Sid() == sid && Channel->GetChannelID().ClrRid() == ChannelID)
|
||||||
return channel;
|
return Channel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (TryWithoutPolarization) {
|
if (TryWithoutPolarization) {
|
||||||
ChannelID.ClrPolarization();
|
ChannelID.ClrPolarization();
|
||||||
for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
|
for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
|
||||||
cChannel *channel = (cChannel *)hobj->Object();
|
cChannel *Channel = (cChannel *)hobj->Object();
|
||||||
if (channel->Sid() == sid && channel->GetChannelID().ClrPolarization() == ChannelID)
|
if (Channel->Sid() == sid && Channel->GetChannelID().ClrPolarization() == ChannelID)
|
||||||
return channel;
|
return Channel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
cChannel *cChannels::GetByTransponderID(tChannelID ChannelID)
|
|
||||||
|
const cChannel *cChannels::GetByTransponderID(tChannelID ChannelID) const
|
||||||
{
|
{
|
||||||
int source = ChannelID.Source();
|
int source = ChannelID.Source();
|
||||||
int nid = ChannelID.Nid();
|
int nid = ChannelID.Nid();
|
||||||
int tid = ChannelID.Tid();
|
int tid = ChannelID.Tid();
|
||||||
for (cChannel *channel = First(); channel; channel = Next(channel)) {
|
for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
|
||||||
if (channel->Tid() == tid && channel->Nid() == nid && channel->Source() == source)
|
if (Channel->Tid() == tid && Channel->Nid() == nid && Channel->Source() == source)
|
||||||
return channel;
|
return Channel;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cChannels::HasUniqueChannelID(cChannel *NewChannel, cChannel *OldChannel)
|
bool cChannels::HasUniqueChannelID(const cChannel *NewChannel, const cChannel *OldChannel) const
|
||||||
{
|
{
|
||||||
tChannelID NewChannelID = NewChannel->GetChannelID();
|
tChannelID NewChannelID = NewChannel->GetChannelID();
|
||||||
for (cChannel *channel = First(); channel; channel = Next(channel)) {
|
for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
|
||||||
if (!channel->GroupSep() && channel != OldChannel && channel->GetChannelID() == NewChannelID)
|
if (!Channel->GroupSep() && Channel != OldChannel && Channel->GetChannelID() == NewChannelID)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cChannels::SwitchTo(int Number)
|
bool cChannels::SwitchTo(int Number) const
|
||||||
{
|
{
|
||||||
cChannel *channel = GetByNumber(Number);
|
const cChannel *Channel = GetByNumber(Number);
|
||||||
return channel && cDevice::PrimaryDevice()->SwitchChannel(channel, true);
|
return Channel && cDevice::PrimaryDevice()->SwitchChannel(Channel, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
int cChannels::MaxChannelNameLength(void)
|
int cChannels::MaxChannelNameLength(void)
|
||||||
{
|
{
|
||||||
if (!maxChannelNameLength) {
|
if (!maxChannelNameLength) {
|
||||||
for (cChannel *channel = First(); channel; channel = Next(channel)) {
|
LOCK_CHANNELS_READ;
|
||||||
if (!channel->GroupSep())
|
for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||||
maxChannelNameLength = max(Utf8StrLen(channel->Name()), maxChannelNameLength);
|
if (!Channel->GroupSep())
|
||||||
|
maxChannelNameLength = max(Utf8StrLen(Channel->Name()), maxChannelNameLength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return maxChannelNameLength;
|
return maxChannelNameLength;
|
||||||
@ -1017,24 +1047,25 @@ int cChannels::MaxChannelNameLength(void)
|
|||||||
int cChannels::MaxShortChannelNameLength(void)
|
int cChannels::MaxShortChannelNameLength(void)
|
||||||
{
|
{
|
||||||
if (!maxShortChannelNameLength) {
|
if (!maxShortChannelNameLength) {
|
||||||
for (cChannel *channel = First(); channel; channel = Next(channel)) {
|
LOCK_CHANNELS_READ;
|
||||||
if (!channel->GroupSep())
|
for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||||
maxShortChannelNameLength = max(Utf8StrLen(channel->ShortName(true)), maxShortChannelNameLength);
|
if (!Channel->GroupSep())
|
||||||
|
maxShortChannelNameLength = max(Utf8StrLen(Channel->ShortName(true)), maxShortChannelNameLength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return maxShortChannelNameLength;
|
return maxShortChannelNameLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cChannels::SetModified(bool ByUser)
|
void cChannels::SetModifiedByUser(void)
|
||||||
{
|
{
|
||||||
modified = ByUser ? CHANNELSMOD_USER : !modified ? CHANNELSMOD_AUTO : modified;
|
modifiedByUser++;
|
||||||
maxChannelNameLength = maxShortChannelNameLength = 0;
|
maxChannelNameLength = maxShortChannelNameLength = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cChannels::Modified(void)
|
bool cChannels::ModifiedByUser(int &State) const
|
||||||
{
|
{
|
||||||
int Result = modified;
|
int Result = State != modifiedByUser;
|
||||||
modified = CHANNELSMOD_NONE;
|
State = modifiedByUser;
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1044,7 +1075,7 @@ cChannel *cChannels::NewChannel(const cChannel *Transponder, const char *Name, c
|
|||||||
dsyslog("creating new channel '%s,%s;%s' on %s transponder %d with id %d-%d-%d-%d", Name, ShortName, Provider, *cSource::ToString(Transponder->Source()), Transponder->Transponder(), Nid, Tid, Sid, Rid);
|
dsyslog("creating new channel '%s,%s;%s' on %s transponder %d with id %d-%d-%d-%d", Name, ShortName, Provider, *cSource::ToString(Transponder->Source()), Transponder->Transponder(), Nid, Tid, Sid, Rid);
|
||||||
cChannel *NewChannel = new cChannel;
|
cChannel *NewChannel = new cChannel;
|
||||||
NewChannel->CopyTransponderData(Transponder);
|
NewChannel->CopyTransponderData(Transponder);
|
||||||
NewChannel->SetId(Nid, Tid, Sid, Rid);
|
NewChannel->SetId(this, Nid, Tid, Sid, Rid);
|
||||||
NewChannel->SetName(Name, ShortName, Provider);
|
NewChannel->SetName(Name, ShortName, Provider);
|
||||||
NewChannel->SetSeen();
|
NewChannel->SetSeen();
|
||||||
Add(NewChannel);
|
Add(NewChannel);
|
||||||
@ -1057,17 +1088,19 @@ cChannel *cChannels::NewChannel(const cChannel *Transponder, const char *Name, c
|
|||||||
#define CHANNELMARKOBSOLETE "OBSOLETE"
|
#define CHANNELMARKOBSOLETE "OBSOLETE"
|
||||||
#define CHANNELTIMEOBSOLETE 3600 // seconds to wait before declaring a channel obsolete (in case it has actually been seen before)
|
#define CHANNELTIMEOBSOLETE 3600 // seconds to wait before declaring a channel obsolete (in case it has actually been seen before)
|
||||||
|
|
||||||
void cChannels::MarkObsoleteChannels(int Source, int Nid, int Tid)
|
bool cChannels::MarkObsoleteChannels(int Source, int Nid, int Tid)
|
||||||
{
|
{
|
||||||
for (cChannel *channel = First(); channel; channel = Next(channel)) {
|
bool ChannelsModified = false;
|
||||||
if (time(NULL) - channel->Seen() > CHANNELTIMEOBSOLETE && channel->Source() == Source && channel->Nid() == Nid && channel->Tid() == Tid && channel->Rid() == 0) {
|
for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
|
||||||
|
if (time(NULL) - Channel->Seen() > CHANNELTIMEOBSOLETE && Channel->Source() == Source && Channel->Nid() == Nid && Channel->Tid() == Tid && Channel->Rid() == 0) {
|
||||||
bool OldShowChannelNamesWithSource = Setup.ShowChannelNamesWithSource;
|
bool OldShowChannelNamesWithSource = Setup.ShowChannelNamesWithSource;
|
||||||
Setup.ShowChannelNamesWithSource = false;
|
Setup.ShowChannelNamesWithSource = false;
|
||||||
if (!endswith(channel->Name(), CHANNELMARKOBSOLETE))
|
if (!endswith(Channel->Name(), CHANNELMARKOBSOLETE))
|
||||||
channel->SetName(cString::sprintf("%s %s", channel->Name(), CHANNELMARKOBSOLETE), channel->ShortName(), cString::sprintf("%s %s", CHANNELMARKOBSOLETE, channel->Provider()));
|
ChannelsModified |= Channel->SetName(cString::sprintf("%s %s", Channel->Name(), CHANNELMARKOBSOLETE), Channel->ShortName(), cString::sprintf("%s %s", CHANNELMARKOBSOLETE, Channel->Provider()));
|
||||||
Setup.ShowChannelNamesWithSource = OldShowChannelNamesWithSource;
|
Setup.ShowChannelNamesWithSource = OldShowChannelNamesWithSource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return ChannelsModified;
|
||||||
}
|
}
|
||||||
|
|
||||||
cString ChannelString(const cChannel *Channel, int Number)
|
cString ChannelString(const cChannel *Channel, int Number)
|
||||||
|
115
channels.h
115
channels.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: channels.h 4.1 2015/03/13 11:20:50 kls Exp $
|
* $Id: channels.h 4.2 2015/08/17 09:39:48 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __CHANNELS_H
|
#ifndef __CHANNELS_H
|
||||||
@ -28,10 +28,6 @@
|
|||||||
#define CHANNELMOD_LANGS 0x40
|
#define CHANNELMOD_LANGS 0x40
|
||||||
#define CHANNELMOD_RETUNE (CHANNELMOD_PIDS | CHANNELMOD_CA | CHANNELMOD_TRANSP)
|
#define CHANNELMOD_RETUNE (CHANNELMOD_PIDS | CHANNELMOD_CA | CHANNELMOD_TRANSP)
|
||||||
|
|
||||||
#define CHANNELSMOD_NONE 0
|
|
||||||
#define CHANNELSMOD_AUTO 1
|
|
||||||
#define CHANNELSMOD_USER 2
|
|
||||||
|
|
||||||
#define MAXAPIDS 32 // audio
|
#define MAXAPIDS 32 // audio
|
||||||
#define MAXDPIDS 16 // dolby (AC3 + DTS)
|
#define MAXDPIDS 16 // dolby (AC3 + DTS)
|
||||||
#define MAXSPIDS 32 // subtitles
|
#define MAXSPIDS 32 // subtitles
|
||||||
@ -86,6 +82,7 @@ class cLinkChannels : public cList<cLinkChannel> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class cSchedule;
|
class cSchedule;
|
||||||
|
class cChannels;
|
||||||
|
|
||||||
class cChannel : public cListObject {
|
class cChannel : public cListObject {
|
||||||
friend class cSchedules;
|
friend class cSchedules;
|
||||||
@ -128,7 +125,7 @@ private:
|
|||||||
mutable cString nameSource;
|
mutable cString nameSource;
|
||||||
mutable cString shortNameSource;
|
mutable cString shortNameSource;
|
||||||
cString parameters;
|
cString parameters;
|
||||||
int modification;
|
mutable int modification;
|
||||||
time_t seen; // When this channel was last seen in the SDT of its transponder
|
time_t seen; // When this channel was last seen in the SDT of its transponder
|
||||||
mutable const cSchedule *schedule;
|
mutable const cSchedule *schedule;
|
||||||
cLinkChannels *linkChannels;
|
cLinkChannels *linkChannels;
|
||||||
@ -188,66 +185,84 @@ public:
|
|||||||
bool IsTerr(void) const { return cSource::IsTerr(source); }
|
bool IsTerr(void) const { return cSource::IsTerr(source); }
|
||||||
bool IsSourceType(char Source) const { return cSource::IsType(source, Source); }
|
bool IsSourceType(char Source) const { return cSource::IsType(source, Source); }
|
||||||
tChannelID GetChannelID(void) const { return tChannelID(source, nid, (nid || tid) ? tid : Transponder(), sid, rid); }
|
tChannelID GetChannelID(void) const { return tChannelID(source, nid, (nid || tid) ? tid : Transponder(), sid, rid); }
|
||||||
bool HasTimer(void) const;
|
int Modification(int Mask = CHANNELMOD_ALL) const;
|
||||||
int Modification(int Mask = CHANNELMOD_ALL);
|
time_t Seen(void) const { return seen; }
|
||||||
time_t Seen(void) { return seen; }
|
|
||||||
void CopyTransponderData(const cChannel *Channel);
|
void CopyTransponderData(const cChannel *Channel);
|
||||||
bool SetTransponderData(int Source, int Frequency, int Srate, const char *Parameters, bool Quiet = false);
|
bool SetTransponderData(int Source, int Frequency, int Srate, const char *Parameters, bool Quiet = false);
|
||||||
void SetSource(int Source);
|
bool SetSource(int Source);
|
||||||
void SetId(int Nid, int Tid, int Sid, int Rid = 0);
|
bool SetId(cChannels *Channels, int Nid, int Tid, int Sid, int Rid = 0);
|
||||||
void SetLcn(int Lcn);
|
bool SetLcn(int Lcn);
|
||||||
void SetName(const char *Name, const char *ShortName, const char *Provider);
|
bool SetName(const char *Name, const char *ShortName, const char *Provider);
|
||||||
void SetPortalName(const char *PortalName);
|
bool SetPortalName(const char *PortalName);
|
||||||
void SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid);
|
bool SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid);
|
||||||
void SetCaIds(const int *CaIds); // list must be zero-terminated
|
bool SetCaIds(const int *CaIds); // list must be zero-terminated
|
||||||
void SetCaDescriptors(int Level);
|
bool SetCaDescriptors(int Level);
|
||||||
void SetLinkChannels(cLinkChannels *LinkChannels);
|
bool SetLinkChannels(cLinkChannels *LinkChannels);
|
||||||
void SetRefChannel(cChannel *RefChannel);
|
void SetRefChannel(cChannel *RefChannel);
|
||||||
void SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds);
|
bool SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds);
|
||||||
void SetSeen(void);
|
void SetSeen(void);
|
||||||
|
void DelLinkChannel(cChannel *LinkChannel);
|
||||||
};
|
};
|
||||||
|
|
||||||
class cChannels : public cRwLock, public cConfig<cChannel> {
|
class cChannels : public cConfig<cChannel> {
|
||||||
private:
|
private:
|
||||||
int maxNumber;
|
static cChannels channels;
|
||||||
int maxChannelNameLength;
|
static int maxNumber;
|
||||||
int maxShortChannelNameLength;
|
static int maxChannelNameLength;
|
||||||
int modified;
|
static int maxShortChannelNameLength;
|
||||||
int beingEdited;
|
int modifiedByUser;
|
||||||
cHash<cChannel> channelsHashSid;
|
cHash<cChannel> channelsHashSid;
|
||||||
void DeleteDuplicateChannels(void);
|
void DeleteDuplicateChannels(void);
|
||||||
public:
|
public:
|
||||||
cChannels(void);
|
cChannels(void);
|
||||||
bool Load(const char *FileName, bool AllowComments = false, bool MustExist = false);
|
static const cChannels *GetChannelsRead(cStateKey &StateKey, int TimeoutMs = 0);
|
||||||
|
///< Gets the list of channels for read access.
|
||||||
|
///< See cTimers::GetTimersRead() for details.
|
||||||
|
static cChannels *GetChannelsWrite(cStateKey &StateKey, int TimeoutMs = 0);
|
||||||
|
///< Gets the list of channels for write access.
|
||||||
|
///< See cTimers::GetTimersWrite() for details.
|
||||||
|
static bool Load(const char *FileName, bool AllowComments = false, bool MustExist = false);
|
||||||
void HashChannel(cChannel *Channel);
|
void HashChannel(cChannel *Channel);
|
||||||
void UnhashChannel(cChannel *Channel);
|
void UnhashChannel(cChannel *Channel);
|
||||||
int GetNextGroup(int Idx); // Get next channel group
|
int GetNextGroup(int Idx) const; ///< Get next channel group
|
||||||
int GetPrevGroup(int Idx); // Get previous channel group
|
int GetPrevGroup(int Idx) const; ///< Get previous channel group
|
||||||
int GetNextNormal(int Idx); // Get next normal channel (not group)
|
int GetNextNormal(int Idx) const; ///< Get next normal channel (not group)
|
||||||
int GetPrevNormal(int Idx); // Get previous normal channel (not group)
|
int GetPrevNormal(int Idx) const; ///< Get previous normal channel (not group)
|
||||||
void ReNumber(void); // Recalculate 'number' based on channel type
|
void ReNumber(void); ///< Recalculate 'number' based on channel type
|
||||||
cChannel *GetByNumber(int Number, int SkipGap = 0);
|
void Del(cChannel *Channel); ///< Delete the given Channel from the list
|
||||||
cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID);
|
const cChannel *GetByNumber(int Number, int SkipGap = 0) const;
|
||||||
cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false);
|
cChannel *GetByNumber(int Number, int SkipGap = 0) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByNumber(Number, SkipGap)); }
|
||||||
cChannel *GetByTransponderID(tChannelID ChannelID);
|
const cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID) const;
|
||||||
int BeingEdited(void) { return beingEdited; }
|
cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByServiceID(Source, Transponder, ServiceID)); }
|
||||||
void IncBeingEdited(void) { beingEdited++; }
|
const cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false) const;
|
||||||
void DecBeingEdited(void) { beingEdited--; }
|
cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByChannelID(ChannelID, TryWithoutRid, TryWithoutPolarization)); }
|
||||||
bool HasUniqueChannelID(cChannel *NewChannel, cChannel *OldChannel = NULL);
|
const cChannel *GetByTransponderID(tChannelID ChannelID) const;
|
||||||
bool SwitchTo(int Number);
|
cChannel *GetByTransponderID(tChannelID ChannelID) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByTransponderID(ChannelID)); }
|
||||||
int MaxNumber(void) { return maxNumber; }
|
bool HasUniqueChannelID(const cChannel *NewChannel, const cChannel *OldChannel = NULL) const;
|
||||||
int MaxChannelNameLength(void);
|
bool SwitchTo(int Number) const;
|
||||||
int MaxShortChannelNameLength(void);
|
static int MaxNumber(void) { return maxNumber; }
|
||||||
void SetModified(bool ByUser = false);
|
static int MaxChannelNameLength(void);
|
||||||
int Modified(void);
|
static int MaxShortChannelNameLength(void);
|
||||||
///< Returns 0 if no channels have been modified, 1 if an automatic
|
void SetModifiedByUser(void);
|
||||||
///< modification has been made, and 2 if the user has made a modification.
|
bool ModifiedByUser(int &State) const;
|
||||||
///< Calling this function resets the 'modified' flag to 0.
|
///< Returns true if the channels have been modified by the user since the last call
|
||||||
|
///< to this function with the same State variable. State must be initialized with 0
|
||||||
|
///< and will be set to the current value of the list's internal state variable upon
|
||||||
|
///< return from this function.
|
||||||
cChannel *NewChannel(const cChannel *Transponder, const char *Name, const char *ShortName, const char *Provider, int Nid, int Tid, int Sid, int Rid = 0);
|
cChannel *NewChannel(const cChannel *Transponder, const char *Name, const char *ShortName, const char *Provider, int Nid, int Tid, int Sid, int Rid = 0);
|
||||||
void MarkObsoleteChannels(int Source, int Nid, int Tid);
|
bool MarkObsoleteChannels(int Source, int Nid, int Tid);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern cChannels Channels;
|
// Provide lock controlled access to the list:
|
||||||
|
|
||||||
|
DEF_LIST_LOCK(Channels);
|
||||||
|
|
||||||
|
// These macros provide a convenient way of locking the global channels list
|
||||||
|
// and making sure the lock is released as soon as the current scope is left
|
||||||
|
// (note that these macros wait forever to obtain the lock!):
|
||||||
|
|
||||||
|
#define LOCK_CHANNELS_READ USE_LIST_LOCK_READ(Channels)
|
||||||
|
#define LOCK_CHANNELS_WRITE USE_LIST_LOCK_WRITE(Channels)
|
||||||
|
|
||||||
cString ChannelString(const cChannel *Channel, int Number);
|
cString ChannelString(const cChannel *Channel, int Number);
|
||||||
|
|
||||||
|
5
ci.c
5
ci.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: ci.c 3.19 2015/02/02 14:04:10 kls Exp $
|
* $Id: ci.c 4.1 2015/07/18 09:57:42 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ci.h"
|
#include "ci.h"
|
||||||
@ -1921,7 +1921,8 @@ void cCamSlot::StartActivation(void)
|
|||||||
cMutexLock MutexLock(&mutex);
|
cMutexLock MutexLock(&mutex);
|
||||||
if (!caActivationReceiver) {
|
if (!caActivationReceiver) {
|
||||||
if (cDevice *d = Device()) {
|
if (cDevice *d = Device()) {
|
||||||
if (cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel())) {
|
LOCK_CHANNELS_READ;
|
||||||
|
if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel())) {
|
||||||
caActivationReceiver = new cCaActivationReceiver(Channel, this);
|
caActivationReceiver = new cCaActivationReceiver(Channel, this);
|
||||||
d->AttachReceiver(caActivationReceiver);
|
d->AttachReceiver(caActivationReceiver);
|
||||||
dsyslog("CAM %d: activating on device %d with channel %d (%s)", SlotNumber(), d->DeviceNumber() + 1, Channel->Number(), Channel->Name());
|
dsyslog("CAM %d: activating on device %d with channel %d (%s)", SlotNumber(), d->DeviceNumber() + 1, Channel->Number(), Channel->Name());
|
||||||
|
6
config.h
6
config.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: config.h 4.2 2015/04/18 13:13:15 kls Exp $
|
* $Id: config.h 4.3 2015/08/09 09:17:46 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __CONFIG_H
|
#ifndef __CONFIG_H
|
||||||
@ -110,7 +110,7 @@ private:
|
|||||||
cList<T>::Clear();
|
cList<T>::Clear();
|
||||||
}
|
}
|
||||||
public:
|
public:
|
||||||
cConfig(void) { fileName = NULL; }
|
cConfig(const char *NeedsLocking = NULL): cList<T>(NeedsLocking) { fileName = NULL; }
|
||||||
virtual ~cConfig() { free(fileName); }
|
virtual ~cConfig() { free(fileName); }
|
||||||
const char *FileName(void) { return fileName; }
|
const char *FileName(void) { return fileName; }
|
||||||
bool Load(const char *FileName = NULL, bool AllowComments = false, bool MustExist = false)
|
bool Load(const char *FileName = NULL, bool AllowComments = false, bool MustExist = false)
|
||||||
@ -160,7 +160,7 @@ public:
|
|||||||
fprintf(stderr, "vdr: error while reading '%s'\n", fileName);
|
fprintf(stderr, "vdr: error while reading '%s'\n", fileName);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
bool Save(void)
|
bool Save(void) const
|
||||||
{
|
{
|
||||||
bool result = true;
|
bool result = true;
|
||||||
T *l = (T *)this->First();
|
T *l = (T *)this->First();
|
||||||
|
9
cutter.c
9
cutter.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: cutter.c 4.1 2015/04/11 12:03:25 kls Exp $
|
* $Id: cutter.c 4.2 2015/08/09 12:24:28 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "cutter.h"
|
#include "cutter.h"
|
||||||
@ -634,7 +634,6 @@ void cCuttingThread::Action(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Recordings.TouchUpdate();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
esyslog("no editing marks found!");
|
esyslog("no editing marks found!");
|
||||||
@ -678,7 +677,8 @@ bool cCutter::Start(void)
|
|||||||
cRecordingUserCommand::InvokeCommand(RUC_EDITINGRECORDING, editedVersionName, originalVersionName);
|
cRecordingUserCommand::InvokeCommand(RUC_EDITINGRECORDING, editedVersionName, originalVersionName);
|
||||||
if (cVideoDirectory::RemoveVideoFile(editedVersionName) && MakeDirs(editedVersionName, true)) {
|
if (cVideoDirectory::RemoveVideoFile(editedVersionName) && MakeDirs(editedVersionName, true)) {
|
||||||
Recording.WriteInfo(editedVersionName);
|
Recording.WriteInfo(editedVersionName);
|
||||||
Recordings.AddByName(editedVersionName, false);
|
LOCK_RECORDINGS_WRITE;
|
||||||
|
Recordings->AddByName(editedVersionName, false);
|
||||||
cuttingThread = new cCuttingThread(originalVersionName, editedVersionName);
|
cuttingThread = new cCuttingThread(originalVersionName, editedVersionName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -703,7 +703,8 @@ void cCutter::Stop(void)
|
|||||||
if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), editedVersionName) == 0)
|
if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), editedVersionName) == 0)
|
||||||
cControl::Shutdown();
|
cControl::Shutdown();
|
||||||
cVideoDirectory::RemoveVideoFile(editedVersionName);
|
cVideoDirectory::RemoveVideoFile(editedVersionName);
|
||||||
Recordings.DelByName(editedVersionName);
|
LOCK_RECORDINGS_WRITE;
|
||||||
|
Recordings->DelByName(editedVersionName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
24
device.c
24
device.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: device.c 3.20 2015/01/30 12:11:30 kls Exp $
|
* $Id: device.c 4.1 2015/08/29 12:41:08 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
@ -722,20 +722,21 @@ bool cDevice::SwitchChannel(int Direction)
|
|||||||
cControl::Shutdown(); // prevents old channel from being shown too long if GetDevice() takes longer
|
cControl::Shutdown(); // prevents old channel from being shown too long if GetDevice() takes longer
|
||||||
int n = CurrentChannel() + Direction;
|
int n = CurrentChannel() + Direction;
|
||||||
int first = n;
|
int first = n;
|
||||||
cChannel *channel;
|
LOCK_CHANNELS_READ;
|
||||||
while ((channel = Channels.GetByNumber(n, Direction)) != NULL) {
|
const cChannel *Channel;
|
||||||
|
while ((Channel = Channels->GetByNumber(n, Direction)) != NULL) {
|
||||||
// try only channels which are currently available
|
// try only channels which are currently available
|
||||||
if (GetDevice(channel, LIVEPRIORITY, true, true))
|
if (GetDevice(Channel, LIVEPRIORITY, true, true))
|
||||||
break;
|
break;
|
||||||
n = channel->Number() + Direction;
|
n = Channel->Number() + Direction;
|
||||||
}
|
}
|
||||||
if (channel) {
|
if (Channel) {
|
||||||
int d = n - first;
|
int d = n - first;
|
||||||
if (abs(d) == 1)
|
if (abs(d) == 1)
|
||||||
dsyslog("skipped channel %d", first);
|
dsyslog("skipped channel %d", first);
|
||||||
else if (d)
|
else if (d)
|
||||||
dsyslog("skipped channels %d..%d", first, n - sgn(d));
|
dsyslog("skipped channels %d..%d", first, n - sgn(d));
|
||||||
if (PrimaryDevice()->SwitchChannel(channel, true))
|
if (PrimaryDevice()->SwitchChannel(Channel, true))
|
||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
else if (n != first)
|
else if (n != first)
|
||||||
@ -777,7 +778,6 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
|
|||||||
Result = scrNotAvailable;
|
Result = scrNotAvailable;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Channels.Lock(false);
|
|
||||||
// Stop section handling:
|
// Stop section handling:
|
||||||
if (sectionHandler) {
|
if (sectionHandler) {
|
||||||
sectionHandler->SetStatus(false);
|
sectionHandler->SetStatus(false);
|
||||||
@ -790,7 +790,8 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
|
|||||||
if (SetChannelDevice(Channel, LiveView)) {
|
if (SetChannelDevice(Channel, LiveView)) {
|
||||||
// Start section handling:
|
// Start section handling:
|
||||||
if (sectionHandler) {
|
if (sectionHandler) {
|
||||||
patFilter->Trigger(Channel->Sid());
|
if (patFilter)
|
||||||
|
patFilter->Trigger(Channel->Sid());
|
||||||
sectionHandler->SetChannel(Channel);
|
sectionHandler->SetChannel(Channel);
|
||||||
sectionHandler->SetStatus(true);
|
sectionHandler->SetStatus(true);
|
||||||
}
|
}
|
||||||
@ -800,7 +801,6 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
Result = scrFailed;
|
Result = scrFailed;
|
||||||
Channels.Unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Result == scrOk) {
|
if (Result == scrOk) {
|
||||||
@ -829,8 +829,8 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
|
|||||||
void cDevice::ForceTransferMode(void)
|
void cDevice::ForceTransferMode(void)
|
||||||
{
|
{
|
||||||
if (!cTransferControl::ReceiverDevice()) {
|
if (!cTransferControl::ReceiverDevice()) {
|
||||||
cChannel *Channel = Channels.GetByNumber(CurrentChannel());
|
LOCK_CHANNELS_READ;
|
||||||
if (Channel)
|
if (const cChannel *Channel = Channels->GetByNumber(CurrentChannel()))
|
||||||
SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode
|
SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
27
dvbplayer.c
27
dvbplayer.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: dvbplayer.c 3.6 2015/02/13 15:12:57 kls Exp $
|
* $Id: dvbplayer.c 4.1 2015/08/06 13:09:19 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "dvbplayer.h"
|
#include "dvbplayer.h"
|
||||||
@ -210,7 +210,7 @@ private:
|
|||||||
cNonBlockingFileReader *nonBlockingFileReader;
|
cNonBlockingFileReader *nonBlockingFileReader;
|
||||||
cRingBufferFrame *ringBuffer;
|
cRingBufferFrame *ringBuffer;
|
||||||
cPtsIndex ptsIndex;
|
cPtsIndex ptsIndex;
|
||||||
cMarks *marks;
|
const cMarks *marks;
|
||||||
cFileName *fileName;
|
cFileName *fileName;
|
||||||
cIndexFile *index;
|
cIndexFile *index;
|
||||||
cUnbufferedFile *replayFile;
|
cUnbufferedFile *replayFile;
|
||||||
@ -239,7 +239,7 @@ protected:
|
|||||||
public:
|
public:
|
||||||
cDvbPlayer(const char *FileName, bool PauseLive);
|
cDvbPlayer(const char *FileName, bool PauseLive);
|
||||||
virtual ~cDvbPlayer();
|
virtual ~cDvbPlayer();
|
||||||
void SetMarks(cMarks *Marks);
|
void SetMarks(const cMarks *Marks);
|
||||||
bool Active(void) { return cThread::Running(); }
|
bool Active(void) { return cThread::Running(); }
|
||||||
void Pause(void);
|
void Pause(void);
|
||||||
void Play(void);
|
void Play(void);
|
||||||
@ -311,7 +311,7 @@ cDvbPlayer::~cDvbPlayer()
|
|||||||
// don't delete marks here, we don't own them!
|
// don't delete marks here, we don't own them!
|
||||||
}
|
}
|
||||||
|
|
||||||
void cDvbPlayer::SetMarks(cMarks *Marks)
|
void cDvbPlayer::SetMarks(const cMarks *Marks)
|
||||||
{
|
{
|
||||||
marks = Marks;
|
marks = Marks;
|
||||||
}
|
}
|
||||||
@ -383,10 +383,11 @@ bool cDvbPlayer::Save(void)
|
|||||||
int Index = ptsIndex.FindIndex(DeviceGetSTC());
|
int Index = ptsIndex.FindIndex(DeviceGetSTC());
|
||||||
if (Index >= 0) {
|
if (Index >= 0) {
|
||||||
if (Setup.SkipEdited && marks) {
|
if (Setup.SkipEdited && marks) {
|
||||||
marks->Lock();
|
cStateKey StateKey;
|
||||||
|
marks->Lock(StateKey);
|
||||||
if (marks->First() && abs(Index - marks->First()->Position()) <= int(round(RESUMEBACKUP * framesPerSecond)))
|
if (marks->First() && abs(Index - marks->First()->Position()) <= int(round(RESUMEBACKUP * framesPerSecond)))
|
||||||
Index = 0; // when stopping within RESUMEBACKUP seconds of the first mark the recording shall still be considered unviewed
|
Index = 0; // when stopping within RESUMEBACKUP seconds of the first mark the recording shall still be considered unviewed
|
||||||
marks->Unlock();
|
StateKey.Remove();
|
||||||
}
|
}
|
||||||
Index -= int(round(RESUMEBACKUP * framesPerSecond));
|
Index -= int(round(RESUMEBACKUP * framesPerSecond));
|
||||||
if (Index > 0)
|
if (Index > 0)
|
||||||
@ -419,7 +420,8 @@ void cDvbPlayer::Action(void)
|
|||||||
if (readIndex > 0)
|
if (readIndex > 0)
|
||||||
isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond));
|
isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond));
|
||||||
else if (Setup.SkipEdited && marks) {
|
else if (Setup.SkipEdited && marks) {
|
||||||
marks->Lock();
|
cStateKey StateKey;
|
||||||
|
marks->Lock(StateKey);
|
||||||
if (marks->First() && index) {
|
if (marks->First() && index) {
|
||||||
int Index = marks->First()->Position();
|
int Index = marks->First()->Position();
|
||||||
uint16_t FileNumber;
|
uint16_t FileNumber;
|
||||||
@ -429,7 +431,7 @@ void cDvbPlayer::Action(void)
|
|||||||
readIndex = Index;
|
readIndex = Index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
marks->Unlock();
|
StateKey.Remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
nonBlockingFileReader = new cNonBlockingFileReader;
|
nonBlockingFileReader = new cNonBlockingFileReader;
|
||||||
@ -500,8 +502,9 @@ void cDvbPlayer::Action(void)
|
|||||||
if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length) && NextFile(FileNumber, FileOffset)) {
|
if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length) && NextFile(FileNumber, FileOffset)) {
|
||||||
readIndex++;
|
readIndex++;
|
||||||
if ((Setup.SkipEdited || Setup.PauseAtLastMark) && marks) {
|
if ((Setup.SkipEdited || Setup.PauseAtLastMark) && marks) {
|
||||||
marks->Lock();
|
cStateKey StateKey;
|
||||||
cMark *m = marks->Get(readIndex);
|
marks->Lock(StateKey);
|
||||||
|
const cMark *m = marks->Get(readIndex);
|
||||||
if (m && (m->Index() & 0x01) != 0) { // we're at an end mark
|
if (m && (m->Index() & 0x01) != 0) { // we're at an end mark
|
||||||
m = marks->GetNextBegin(m);
|
m = marks->GetNextBegin(m);
|
||||||
int Index = -1;
|
int Index = -1;
|
||||||
@ -519,7 +522,7 @@ void cDvbPlayer::Action(void)
|
|||||||
CutIn = true;
|
CutIn = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
marks->Unlock();
|
StateKey.Remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -943,7 +946,7 @@ cDvbPlayerControl::~cDvbPlayerControl()
|
|||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void cDvbPlayerControl::SetMarks(cMarks *Marks)
|
void cDvbPlayerControl::SetMarks(const cMarks *Marks)
|
||||||
{
|
{
|
||||||
if (player)
|
if (player)
|
||||||
player->SetMarks(Marks);
|
player->SetMarks(Marks);
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: dvbplayer.h 3.2 2015/02/06 12:27:39 kls Exp $
|
* $Id: dvbplayer.h 4.1 2015/08/02 13:01:44 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __DVBPLAYER_H
|
#ifndef __DVBPLAYER_H
|
||||||
@ -26,7 +26,7 @@ public:
|
|||||||
// file of the recording is long enough to allow the player to display
|
// file of the recording is long enough to allow the player to display
|
||||||
// the first frame in still picture mode.
|
// the first frame in still picture mode.
|
||||||
virtual ~cDvbPlayerControl();
|
virtual ~cDvbPlayerControl();
|
||||||
void SetMarks(cMarks *Marks);
|
void SetMarks(const cMarks *Marks);
|
||||||
bool Active(void);
|
bool Active(void);
|
||||||
void Stop(void);
|
void Stop(void);
|
||||||
// Stops the current replay session (if any).
|
// Stops the current replay session (if any).
|
||||||
|
119
eit.c
119
eit.c
@ -8,7 +8,7 @@
|
|||||||
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
||||||
* Adapted to 'libsi' for VDR 1.3.0 by Marcel Wiesweg <marcel.wiesweg@gmx.de>.
|
* Adapted to 'libsi' for VDR 1.3.0 by Marcel Wiesweg <marcel.wiesweg@gmx.de>.
|
||||||
*
|
*
|
||||||
* $Id: eit.c 3.6 2015/02/01 14:55:27 kls Exp $
|
* $Id: eit.c 4.1 2015/08/23 10:43:36 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "eit.h"
|
#include "eit.h"
|
||||||
@ -24,31 +24,53 @@
|
|||||||
|
|
||||||
class cEIT : public SI::EIT {
|
class cEIT : public SI::EIT {
|
||||||
public:
|
public:
|
||||||
cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bool OnlyRunningStatus = false);
|
cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const u_char *Data);
|
||||||
};
|
};
|
||||||
|
|
||||||
cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bool OnlyRunningStatus)
|
cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const u_char *Data)
|
||||||
:SI::EIT(Data, false)
|
:SI::EIT(Data, false)
|
||||||
{
|
{
|
||||||
if (!CheckCRCAndParse())
|
if (!CheckCRCAndParse())
|
||||||
return;
|
return;
|
||||||
|
int HashId = Tid * getServiceId();
|
||||||
|
cSectionSyncerEntry *SectionSyncerEntry = SectionSyncerHash.Get(HashId);
|
||||||
|
if (!SectionSyncerEntry) {
|
||||||
|
SectionSyncerEntry = new cSectionSyncerEntry;
|
||||||
|
SectionSyncerHash.Add(SectionSyncerEntry, HashId);
|
||||||
|
}
|
||||||
|
bool Process = SectionSyncerEntry->Sync(getVersionNumber(), getSectionNumber(), getLastSectionNumber());
|
||||||
|
if (Tid != 0x4E && !Process) // we need to set the 'seen' tag to watch the running status of the present/following event
|
||||||
|
return;
|
||||||
|
|
||||||
time_t Now = time(NULL);
|
time_t Now = time(NULL);
|
||||||
if (Now < VALID_TIME)
|
if (Now < VALID_TIME)
|
||||||
return; // we need the current time for handling PDC descriptors
|
return; // we need the current time for handling PDC descriptors
|
||||||
|
|
||||||
if (!Channels.Lock(false, 10))
|
cStateKey ChannelsStateKey;
|
||||||
|
cChannels *Channels = cChannels::GetChannelsWrite(ChannelsStateKey, 10);
|
||||||
|
if (!Channels) {
|
||||||
|
SectionSyncerEntry->Repeat(); // let's not miss any section of the EIT
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
tChannelID channelID(Source, getOriginalNetworkId(), getTransportStreamId(), getServiceId());
|
tChannelID channelID(Source, getOriginalNetworkId(), getTransportStreamId(), getServiceId());
|
||||||
cChannel *channel = Channels.GetByChannelID(channelID, true);
|
cChannel *Channel = Channels->GetByChannelID(channelID, true);
|
||||||
if (!channel || EpgHandlers.IgnoreChannel(channel)) {
|
if (!Channel || EpgHandlers.IgnoreChannel(Channel)) {
|
||||||
Channels.Unlock();
|
ChannelsStateKey.Remove(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EpgHandlers.BeginSegmentTransfer(channel, OnlyRunningStatus);
|
cStateKey SchedulesStateKey;
|
||||||
bool handledExternally = EpgHandlers.HandledExternally(channel);
|
cSchedules *Schedules = cSchedules::GetSchedulesWrite(SchedulesStateKey, 10);
|
||||||
cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(channel, true);
|
if (!Schedules) {
|
||||||
|
SectionSyncerEntry->Repeat(); // let's not miss any section of the EIT
|
||||||
|
ChannelsStateKey.Remove(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChannelsModified = false;
|
||||||
|
EpgHandlers.BeginSegmentTransfer(Channel);
|
||||||
|
bool handledExternally = EpgHandlers.HandledExternally(Channel);
|
||||||
|
cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(Channel, true);
|
||||||
|
|
||||||
bool Empty = true;
|
bool Empty = true;
|
||||||
bool Modified = false;
|
bool Modified = false;
|
||||||
@ -74,8 +96,6 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
|
|||||||
cEvent *rEvent = NULL;
|
cEvent *rEvent = NULL;
|
||||||
cEvent *pEvent = (cEvent *)pSchedule->GetEvent(SiEitEvent.getEventId(), StartTime);
|
cEvent *pEvent = (cEvent *)pSchedule->GetEvent(SiEitEvent.getEventId(), StartTime);
|
||||||
if (!pEvent || handledExternally) {
|
if (!pEvent || handledExternally) {
|
||||||
if (OnlyRunningStatus)
|
|
||||||
continue;
|
|
||||||
if (handledExternally && !EpgHandlers.IsUpdate(SiEitEvent.getEventId(), StartTime, Tid, getVersionNumber()))
|
if (handledExternally && !EpgHandlers.IsUpdate(SiEitEvent.getEventId(), StartTime, Tid, getVersionNumber()))
|
||||||
continue;
|
continue;
|
||||||
// If we don't have that event yet, we create a new one.
|
// If we don't have that event yet, we create a new one.
|
||||||
@ -94,14 +114,6 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
|
|||||||
// The lower the table ID, the more "current" the information.
|
// The lower the table ID, the more "current" the information.
|
||||||
if (Tid > TableID)
|
if (Tid > TableID)
|
||||||
continue;
|
continue;
|
||||||
// If the new event comes from the same table and has the same version number
|
|
||||||
// as the existing one, let's skip it to avoid unnecessary work.
|
|
||||||
// Unfortunately some stations (like, e.g. "Premiere") broadcast their EPG data on several transponders (like
|
|
||||||
// the actual Premiere transponder and the Sat.1/Pro7 transponder), but use different version numbers on
|
|
||||||
// each of them :-( So if one DVB card is tuned to the Premiere transponder, while an other one is tuned
|
|
||||||
// to the Sat.1/Pro7 transponder, events will keep toggling because of the bogus version numbers.
|
|
||||||
else if (Tid == TableID && pEvent->Version() == getVersionNumber())
|
|
||||||
continue;
|
|
||||||
EpgHandlers.SetEventID(pEvent, SiEitEvent.getEventId()); // unfortunately some stations use different event ids for the same event in different tables :-(
|
EpgHandlers.SetEventID(pEvent, SiEitEvent.getEventId()); // unfortunately some stations use different event ids for the same event in different tables :-(
|
||||||
EpgHandlers.SetStartTime(pEvent, StartTime);
|
EpgHandlers.SetStartTime(pEvent, StartTime);
|
||||||
EpgHandlers.SetDuration(pEvent, Duration);
|
EpgHandlers.SetDuration(pEvent, Duration);
|
||||||
@ -110,11 +122,9 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
|
|||||||
pEvent->SetTableID(Tid);
|
pEvent->SetTableID(Tid);
|
||||||
if (Tid == 0x4E) { // we trust only the present/following info on the actual TS
|
if (Tid == 0x4E) { // we trust only the present/following info on the actual TS
|
||||||
if (SiEitEvent.getRunningStatus() >= SI::RunningStatusNotRunning)
|
if (SiEitEvent.getRunningStatus() >= SI::RunningStatusNotRunning)
|
||||||
pSchedule->SetRunningStatus(pEvent, SiEitEvent.getRunningStatus(), channel);
|
pSchedule->SetRunningStatus(pEvent, SiEitEvent.getRunningStatus(), Channel);
|
||||||
}
|
if (!Process)
|
||||||
if (OnlyRunningStatus) {
|
continue;
|
||||||
pEvent->SetVersion(0xFF); // we have already changed the table id above, so set the version to an invalid value to make sure the next full run will be executed
|
|
||||||
continue; // do this before setting the version, so that the full update can be done later
|
|
||||||
}
|
}
|
||||||
pEvent->SetVersion(getVersionNumber());
|
pEvent->SetVersion(getVersionNumber());
|
||||||
|
|
||||||
@ -206,7 +216,7 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
|
|||||||
break;
|
break;
|
||||||
case SI::TimeShiftedEventDescriptorTag: {
|
case SI::TimeShiftedEventDescriptorTag: {
|
||||||
SI::TimeShiftedEventDescriptor *tsed = (SI::TimeShiftedEventDescriptor *)d;
|
SI::TimeShiftedEventDescriptor *tsed = (SI::TimeShiftedEventDescriptor *)d;
|
||||||
cSchedule *rSchedule = (cSchedule *)Schedules->GetSchedule(tChannelID(Source, channel->Nid(), channel->Tid(), tsed->getReferenceServiceId()));
|
cSchedule *rSchedule = (cSchedule *)Schedules->GetSchedule(tChannelID(Source, Channel->Nid(), Channel->Tid(), tsed->getReferenceServiceId()));
|
||||||
if (!rSchedule)
|
if (!rSchedule)
|
||||||
break;
|
break;
|
||||||
rEvent = (cEvent *)rSchedule->GetEvent(tsed->getReferenceEventId());
|
rEvent = (cEvent *)rSchedule->GetEvent(tsed->getReferenceEventId());
|
||||||
@ -226,18 +236,18 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
|
|||||||
char linkName[ld->privateData.getLength() + 1];
|
char linkName[ld->privateData.getLength() + 1];
|
||||||
strn0cpy(linkName, (const char *)ld->privateData.getData(), sizeof(linkName));
|
strn0cpy(linkName, (const char *)ld->privateData.getData(), sizeof(linkName));
|
||||||
// TODO is there a standard way to determine the character set of this string?
|
// TODO is there a standard way to determine the character set of this string?
|
||||||
cChannel *link = Channels.GetByChannelID(linkID);
|
cChannel *link = Channels->GetByChannelID(linkID);
|
||||||
if (link != channel) { // only link to other channels, not the same one
|
if (link != Channel) { // only link to other channels, not the same one
|
||||||
//fprintf(stderr, "Linkage %s %4d %4d %5d %5d %5d %5d %02X '%s'\n", hit ? "*" : "", channel->Number(), link ? link->Number() : -1, SiEitEvent.getEventId(), ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId(), ld->getLinkageType(), linkName);//XXX
|
|
||||||
if (link) {
|
if (link) {
|
||||||
if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3)
|
if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3)
|
||||||
link->SetName(linkName, "", "");
|
ChannelsModified |= link->SetName(linkName, "", "");
|
||||||
}
|
}
|
||||||
else if (Setup.UpdateChannels >= 4) {
|
else if (Setup.UpdateChannels >= 4) {
|
||||||
cChannel *transponder = channel;
|
cChannel *Transponder = Channel;
|
||||||
if (channel->Tid() != ld->getTransportStreamId())
|
if (Channel->Tid() != ld->getTransportStreamId())
|
||||||
transponder = Channels.GetByTransponderID(linkID);
|
Transponder = Channels->GetByTransponderID(linkID);
|
||||||
link = Channels.NewChannel(transponder, linkName, "", "", ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId());
|
link = Channels->NewChannel(Transponder, linkName, "", "", ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId());
|
||||||
|
ChannelsModified = true;
|
||||||
//XXX patFilter->Trigger();
|
//XXX patFilter->Trigger();
|
||||||
}
|
}
|
||||||
if (link) {
|
if (link) {
|
||||||
@ -247,7 +257,7 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
channel->SetPortalName(linkName);
|
ChannelsModified |= Channel->SetPortalName(linkName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -293,7 +303,7 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
|
|||||||
|
|
||||||
EpgHandlers.FixEpgBugs(pEvent);
|
EpgHandlers.FixEpgBugs(pEvent);
|
||||||
if (LinkChannels)
|
if (LinkChannels)
|
||||||
channel->SetLinkChannels(LinkChannels);
|
ChannelsModified |= Channel->SetLinkChannels(LinkChannels);
|
||||||
Modified = true;
|
Modified = true;
|
||||||
EpgHandlers.HandleEvent(pEvent);
|
EpgHandlers.HandleEvent(pEvent);
|
||||||
if (handledExternally)
|
if (handledExternally)
|
||||||
@ -302,16 +312,17 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo
|
|||||||
if (Tid == 0x4E) {
|
if (Tid == 0x4E) {
|
||||||
if (Empty && getSectionNumber() == 0)
|
if (Empty && getSectionNumber() == 0)
|
||||||
// ETR 211: an empty entry in section 0 of table 0x4E means there is currently no event running
|
// ETR 211: an empty entry in section 0 of table 0x4E means there is currently no event running
|
||||||
pSchedule->ClrRunningStatus(channel);
|
pSchedule->ClrRunningStatus(Channel);
|
||||||
pSchedule->SetPresentSeen();
|
pSchedule->SetPresentSeen();
|
||||||
}
|
}
|
||||||
if (Modified && !OnlyRunningStatus) {
|
if (Modified) {
|
||||||
EpgHandlers.SortSchedule(pSchedule);
|
EpgHandlers.SortSchedule(pSchedule);
|
||||||
EpgHandlers.DropOutdated(pSchedule, SegmentStart, SegmentEnd, Tid, getVersionNumber());
|
EpgHandlers.DropOutdated(pSchedule, SegmentStart, SegmentEnd, Tid, getVersionNumber());
|
||||||
Schedules->SetModified(pSchedule);
|
pSchedule->SetModified();
|
||||||
}
|
}
|
||||||
Channels.Unlock();
|
SchedulesStateKey.Remove(Modified);
|
||||||
EpgHandlers.EndSegmentTransfer(Modified, OnlyRunningStatus);
|
ChannelsStateKey.Remove(ChannelsModified);
|
||||||
|
EpgHandlers.EndSegmentTransfer(Modified);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- cTDT ------------------------------------------------------------------
|
// --- cTDT ------------------------------------------------------------------
|
||||||
@ -372,6 +383,13 @@ cEitFilter::cEitFilter(void)
|
|||||||
Set(0x14, 0x70); // TDT
|
Set(0x14, 0x70); // TDT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cEitFilter::SetStatus(bool On)
|
||||||
|
{
|
||||||
|
cMutexLock MutexLock(&mutex);
|
||||||
|
cFilter::SetStatus(On);
|
||||||
|
sectionSyncerHash.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
void cEitFilter::SetDisableUntil(time_t Time)
|
void cEitFilter::SetDisableUntil(time_t Time)
|
||||||
{
|
{
|
||||||
disableUntil = Time;
|
disableUntil = Time;
|
||||||
@ -379,6 +397,7 @@ void cEitFilter::SetDisableUntil(time_t Time)
|
|||||||
|
|
||||||
void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
|
void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
|
||||||
{
|
{
|
||||||
|
cMutexLock MutexLock(&mutex);
|
||||||
if (disableUntil) {
|
if (disableUntil) {
|
||||||
if (time(NULL) > disableUntil)
|
if (time(NULL) > disableUntil)
|
||||||
disableUntil = 0;
|
disableUntil = 0;
|
||||||
@ -387,22 +406,8 @@ void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
}
|
}
|
||||||
switch (Pid) {
|
switch (Pid) {
|
||||||
case 0x12: {
|
case 0x12: {
|
||||||
if (Tid >= 0x4E && Tid <= 0x6F) {
|
if (Tid >= 0x4E && Tid <= 0x6F)
|
||||||
cSchedulesLock SchedulesLock(true, 10);
|
cEIT EIT(sectionSyncerHash, Source(), Tid, Data);
|
||||||
cSchedules *Schedules = (cSchedules *)cSchedules::Schedules(SchedulesLock);
|
|
||||||
if (Schedules)
|
|
||||||
cEIT EIT(Schedules, Source(), Tid, Data);
|
|
||||||
else {
|
|
||||||
// If we don't get a write lock, let's at least get a read lock, so
|
|
||||||
// that we can set the running status and 'seen' timestamp (well, actually
|
|
||||||
// with a read lock we shouldn't be doing that, but it's only integers that
|
|
||||||
// get changed, so it should be ok)
|
|
||||||
cSchedulesLock SchedulesLock;
|
|
||||||
cSchedules *Schedules = (cSchedules *)cSchedules::Schedules(SchedulesLock);
|
|
||||||
if (Schedules)
|
|
||||||
cEIT EIT(Schedules, Source(), Tid, Data, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x14: {
|
case 0x14: {
|
||||||
|
10
eit.h
10
eit.h
@ -4,21 +4,29 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: eit.h 2.1 2010/01/03 15:28:34 kls Exp $
|
* $Id: eit.h 4.1 2015/07/25 11:03:53 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __EIT_H
|
#ifndef __EIT_H
|
||||||
#define __EIT_H
|
#define __EIT_H
|
||||||
|
|
||||||
#include "filter.h"
|
#include "filter.h"
|
||||||
|
#include "tools.h"
|
||||||
|
|
||||||
|
class cSectionSyncerEntry : public cListObject, public cSectionSyncer {};
|
||||||
|
|
||||||
|
class cSectionSyncerHash : public cHash<cSectionSyncerEntry> {};
|
||||||
|
|
||||||
class cEitFilter : public cFilter {
|
class cEitFilter : public cFilter {
|
||||||
private:
|
private:
|
||||||
|
cMutex mutex;
|
||||||
|
cSectionSyncerHash sectionSyncerHash;
|
||||||
static time_t disableUntil;
|
static time_t disableUntil;
|
||||||
protected:
|
protected:
|
||||||
virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
|
virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
|
||||||
public:
|
public:
|
||||||
cEitFilter(void);
|
cEitFilter(void);
|
||||||
|
virtual void SetStatus(bool On);
|
||||||
static void SetDisableUntil(time_t Time);
|
static void SetDisableUntil(time_t Time);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
18
eitscan.c
18
eitscan.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: eitscan.c 2.7 2012/04/07 14:39:28 kls Exp $
|
* $Id: eitscan.c 4.1 2015/07/18 10:16:51 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "eitscan.h"
|
#include "eitscan.h"
|
||||||
@ -45,13 +45,13 @@ int cScanData::Compare(const cListObject &ListObject) const
|
|||||||
|
|
||||||
class cScanList : public cList<cScanData> {
|
class cScanList : public cList<cScanData> {
|
||||||
public:
|
public:
|
||||||
void AddTransponders(cList<cChannel> *Channels);
|
void AddTransponders(const cList<cChannel> *Channels);
|
||||||
void AddTransponder(const cChannel *Channel);
|
void AddTransponder(const cChannel *Channel);
|
||||||
};
|
};
|
||||||
|
|
||||||
void cScanList::AddTransponders(cList<cChannel> *Channels)
|
void cScanList::AddTransponders(const cList<cChannel> *Channels)
|
||||||
{
|
{
|
||||||
for (cChannel *ch = Channels->First(); ch; ch = Channels->Next(ch))
|
for (const cChannel *ch = Channels->First(); ch; ch = Channels->Next(ch))
|
||||||
AddTransponder(ch);
|
AddTransponder(ch);
|
||||||
Sort();
|
Sort();
|
||||||
}
|
}
|
||||||
@ -118,7 +118,8 @@ void cEITScanner::ForceScan(void)
|
|||||||
void cEITScanner::Activity(void)
|
void cEITScanner::Activity(void)
|
||||||
{
|
{
|
||||||
if (currentChannel) {
|
if (currentChannel) {
|
||||||
Channels.SwitchTo(currentChannel);
|
LOCK_CHANNELS_READ;
|
||||||
|
Channels->SwitchTo(currentChannel);
|
||||||
currentChannel = 0;
|
currentChannel = 0;
|
||||||
}
|
}
|
||||||
lastActivity = time(NULL);
|
lastActivity = time(NULL);
|
||||||
@ -129,7 +130,8 @@ void cEITScanner::Process(void)
|
|||||||
if (Setup.EPGScanTimeout || !lastActivity) { // !lastActivity means a scan was forced
|
if (Setup.EPGScanTimeout || !lastActivity) { // !lastActivity means a scan was forced
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
if (now - lastScan > ScanTimeout && now - lastActivity > ActivityTimeout) {
|
if (now - lastScan > ScanTimeout && now - lastActivity > ActivityTimeout) {
|
||||||
if (Channels.Lock(false, 10)) {
|
cStateKey StateKey;
|
||||||
|
if (const cChannels *Channels = cChannels::GetChannelsRead(StateKey, 10)) {
|
||||||
if (!scanList) {
|
if (!scanList) {
|
||||||
scanList = new cScanList;
|
scanList = new cScanList;
|
||||||
if (transponderList) {
|
if (transponderList) {
|
||||||
@ -137,7 +139,7 @@ void cEITScanner::Process(void)
|
|||||||
delete transponderList;
|
delete transponderList;
|
||||||
transponderList = NULL;
|
transponderList = NULL;
|
||||||
}
|
}
|
||||||
scanList->AddTransponders(&Channels);
|
scanList->AddTransponders(Channels);
|
||||||
}
|
}
|
||||||
bool AnyDeviceSwitched = false;
|
bool AnyDeviceSwitched = false;
|
||||||
for (int i = 0; i < cDevice::NumDevices(); i++) {
|
for (int i = 0; i < cDevice::NumDevices(); i++) {
|
||||||
@ -177,7 +179,7 @@ void cEITScanner::Process(void)
|
|||||||
if (lastActivity == 0) // this was a triggered scan
|
if (lastActivity == 0) // this was a triggered scan
|
||||||
Activity();
|
Activity();
|
||||||
}
|
}
|
||||||
Channels.Unlock();
|
StateKey.Remove();
|
||||||
}
|
}
|
||||||
lastScan = time(NULL);
|
lastScan = time(NULL);
|
||||||
}
|
}
|
||||||
|
270
epg.c
270
epg.c
@ -7,7 +7,7 @@
|
|||||||
* Original version (as used in VDR before 1.3.0) written by
|
* Original version (as used in VDR before 1.3.0) written by
|
||||||
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
||||||
*
|
*
|
||||||
* $Id: epg.c 3.3 2013/12/28 11:33:08 kls Exp $
|
* $Id: epg.c 4.1 2015/08/23 10:39:59 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "epg.h"
|
#include "epg.h"
|
||||||
@ -15,7 +15,6 @@
|
|||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include "libsi/si.h"
|
#include "libsi/si.h"
|
||||||
#include "timers.h"
|
|
||||||
|
|
||||||
#define RUNNINGSTATUSTIMEOUT 30 // seconds before the running status is considered unknown
|
#define RUNNINGSTATUSTIMEOUT 30 // seconds before the running status is considered unknown
|
||||||
#define EPGDATAWRITEDELTA 600 // seconds between writing the epg.data file
|
#define EPGDATAWRITEDELTA 600 // seconds between writing the epg.data file
|
||||||
@ -111,9 +110,12 @@ tComponent *cComponents::GetComponent(int Index, uchar Stream, uchar Type)
|
|||||||
|
|
||||||
// --- cEvent ----------------------------------------------------------------
|
// --- cEvent ----------------------------------------------------------------
|
||||||
|
|
||||||
|
cMutex cEvent::numTimersMutex;
|
||||||
|
|
||||||
cEvent::cEvent(tEventID EventID)
|
cEvent::cEvent(tEventID EventID)
|
||||||
{
|
{
|
||||||
schedule = NULL;
|
schedule = NULL;
|
||||||
|
numTimers = 0;
|
||||||
eventID = EventID;
|
eventID = EventID;
|
||||||
tableID = 0xFF; // actual table ids are 0x4E..0x60
|
tableID = 0xFF; // actual table ids are 0x4E..0x60
|
||||||
version = 0xFF; // actual version numbers are 0..31
|
version = 0xFF; // actual version numbers are 0..31
|
||||||
@ -170,9 +172,9 @@ void cEvent::SetVersion(uchar Version)
|
|||||||
version = Version;
|
version = Version;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cEvent::SetRunningStatus(int RunningStatus, cChannel *Channel)
|
void cEvent::SetRunningStatus(int RunningStatus, const cChannel *Channel)
|
||||||
{
|
{
|
||||||
if (Channel && runningStatus != RunningStatus && (RunningStatus > SI::RunningStatusNotRunning || runningStatus > SI::RunningStatusUndefined) && Channel->HasTimer())
|
if (Channel && runningStatus != RunningStatus && (RunningStatus > SI::RunningStatusNotRunning || runningStatus > SI::RunningStatusUndefined) && schedule && schedule->HasTimer())
|
||||||
isyslog("channel %d (%s) event %s status %d", Channel->Number(), Channel->Name(), *ToDescr(), RunningStatus);
|
isyslog("channel %d (%s) event %s status %d", Channel->Number(), Channel->Name(), *ToDescr(), RunningStatus);
|
||||||
runningStatus = RunningStatus;
|
runningStatus = RunningStatus;
|
||||||
}
|
}
|
||||||
@ -243,13 +245,22 @@ cString cEvent::ToDescr(void) const
|
|||||||
return cString::sprintf("%s %s-%s %s'%s'", *GetDateString(), *GetTimeString(), *GetEndTimeString(), vpsbuf, Title());
|
return cString::sprintf("%s %s-%s %s'%s'", *GetDateString(), *GetTimeString(), *GetEndTimeString(), vpsbuf, Title());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cEvent::HasTimer(void) const
|
void cEvent::IncNumTimers(void) const
|
||||||
{
|
{
|
||||||
for (cTimer *t = Timers.First(); t; t = Timers.Next(t)) {
|
numTimersMutex.Lock();
|
||||||
if (t->Event() == this)
|
numTimers++;
|
||||||
return true;
|
if (schedule)
|
||||||
}
|
schedule->IncNumTimers();
|
||||||
return false;
|
numTimersMutex.Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void cEvent::DecNumTimers(void) const
|
||||||
|
{
|
||||||
|
numTimersMutex.Lock();
|
||||||
|
numTimers--;
|
||||||
|
if (schedule)
|
||||||
|
schedule->DecNumTimers();
|
||||||
|
numTimersMutex.Unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cEvent::IsRunning(bool OrAboutToStart) const
|
bool cEvent::IsRunning(bool OrAboutToStart) const
|
||||||
@ -605,9 +616,9 @@ void ReportEpgBugFixStats(bool Force)
|
|||||||
bool PrintedStats = false;
|
bool PrintedStats = false;
|
||||||
char *q = buffer;
|
char *q = buffer;
|
||||||
*buffer = 0;
|
*buffer = 0;
|
||||||
|
LOCK_CHANNELS_READ;
|
||||||
for (int c = 0; c < p->n; c++) {
|
for (int c = 0; c < p->n; c++) {
|
||||||
cChannel *channel = Channels.GetByChannelID(p->channelIDs[c], true);
|
if (const cChannel *Channel = Channels->GetByChannelID(p->channelIDs[c], true)) {
|
||||||
if (channel) {
|
|
||||||
if (!GotHits) {
|
if (!GotHits) {
|
||||||
dsyslog("=====================");
|
dsyslog("=====================");
|
||||||
dsyslog("EPG bugfix statistics");
|
dsyslog("EPG bugfix statistics");
|
||||||
@ -623,7 +634,7 @@ void ReportEpgBugFixStats(bool Force)
|
|||||||
q += snprintf(q, sizeof(buffer) - (q - buffer), "%-3d %-4d", i, p->hits);
|
q += snprintf(q, sizeof(buffer) - (q - buffer), "%-3d %-4d", i, p->hits);
|
||||||
PrintedStats = true;
|
PrintedStats = true;
|
||||||
}
|
}
|
||||||
q += snprintf(q, sizeof(buffer) - (q - buffer), "%s%s", delim, channel->Name());
|
q += snprintf(q, sizeof(buffer) - (q - buffer), "%s%s", delim, Channel->Name());
|
||||||
delim = ", ";
|
delim = ", ";
|
||||||
if (q - buffer > 80) {
|
if (q - buffer > 80) {
|
||||||
q += snprintf(q, sizeof(buffer) - (q - buffer), "%s...", delim);
|
q += snprintf(q, sizeof(buffer) - (q - buffer), "%s...", delim);
|
||||||
@ -878,14 +889,32 @@ Final:
|
|||||||
|
|
||||||
// --- cSchedule -------------------------------------------------------------
|
// --- cSchedule -------------------------------------------------------------
|
||||||
|
|
||||||
|
cMutex cSchedule::numTimersMutex;
|
||||||
|
|
||||||
cSchedule::cSchedule(tChannelID ChannelID)
|
cSchedule::cSchedule(tChannelID ChannelID)
|
||||||
{
|
{
|
||||||
channelID = ChannelID;
|
channelID = ChannelID;
|
||||||
|
events.SetUseGarbageCollector();
|
||||||
|
numTimers = 0;
|
||||||
hasRunning = false;
|
hasRunning = false;
|
||||||
modified = 0;
|
modified = 0;
|
||||||
presentSeen = 0;
|
presentSeen = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cSchedule::IncNumTimers(void) const
|
||||||
|
{
|
||||||
|
numTimersMutex.Lock();
|
||||||
|
numTimers++;
|
||||||
|
numTimersMutex.Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void cSchedule::DecNumTimers(void) const
|
||||||
|
{
|
||||||
|
numTimersMutex.Lock();
|
||||||
|
numTimers--;
|
||||||
|
numTimersMutex.Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
cEvent *cSchedule::AddEvent(cEvent *Event)
|
cEvent *cSchedule::AddEvent(cEvent *Event)
|
||||||
{
|
{
|
||||||
events.Add(Event);
|
events.Add(Event);
|
||||||
@ -897,8 +926,6 @@ cEvent *cSchedule::AddEvent(cEvent *Event)
|
|||||||
void cSchedule::DelEvent(cEvent *Event)
|
void cSchedule::DelEvent(cEvent *Event)
|
||||||
{
|
{
|
||||||
if (Event->schedule == this) {
|
if (Event->schedule == this) {
|
||||||
if (hasRunning && Event->IsRunning())
|
|
||||||
ClrRunningStatus();
|
|
||||||
UnhashEvent(Event);
|
UnhashEvent(Event);
|
||||||
events.Del(Event);
|
events.Del(Event);
|
||||||
}
|
}
|
||||||
@ -922,7 +949,7 @@ const cEvent *cSchedule::GetPresentEvent(void) const
|
|||||||
{
|
{
|
||||||
const cEvent *pe = NULL;
|
const cEvent *pe = NULL;
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
for (cEvent *p = events.First(); p; p = events.Next(p)) {
|
for (const cEvent *p = events.First(); p; p = events.Next(p)) {
|
||||||
if (p->StartTime() <= now)
|
if (p->StartTime() <= now)
|
||||||
pe = p;
|
pe = p;
|
||||||
else if (p->StartTime() > now + 3600)
|
else if (p->StartTime() > now + 3600)
|
||||||
@ -962,7 +989,7 @@ const cEvent *cSchedule::GetEventAround(time_t Time) const
|
|||||||
{
|
{
|
||||||
const cEvent *pe = NULL;
|
const cEvent *pe = NULL;
|
||||||
time_t delta = INT_MAX;
|
time_t delta = INT_MAX;
|
||||||
for (cEvent *p = events.First(); p; p = events.Next(p)) {
|
for (const cEvent *p = events.First(); p; p = events.Next(p)) {
|
||||||
time_t dt = Time - p->StartTime();
|
time_t dt = Time - p->StartTime();
|
||||||
if (dt >= 0 && dt < delta && p->EndTime() >= Time) {
|
if (dt >= 0 && dt < delta && p->EndTime() >= Time) {
|
||||||
delta = dt;
|
delta = dt;
|
||||||
@ -972,7 +999,7 @@ const cEvent *cSchedule::GetEventAround(time_t Time) const
|
|||||||
return pe;
|
return pe;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Channel)
|
void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, const cChannel *Channel)
|
||||||
{
|
{
|
||||||
hasRunning = false;
|
hasRunning = false;
|
||||||
for (cEvent *p = events.First(); p; p = events.Next(p)) {
|
for (cEvent *p = events.First(); p; p = events.Next(p)) {
|
||||||
@ -987,6 +1014,7 @@ void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Cha
|
|||||||
if (p->RunningStatus() >= SI::RunningStatusPausing)
|
if (p->RunningStatus() >= SI::RunningStatusPausing)
|
||||||
hasRunning = true;
|
hasRunning = true;
|
||||||
}
|
}
|
||||||
|
SetPresentSeen();
|
||||||
}
|
}
|
||||||
|
|
||||||
void cSchedule::ClrRunningStatus(cChannel *Channel)
|
void cSchedule::ClrRunningStatus(cChannel *Channel)
|
||||||
@ -1019,32 +1047,29 @@ void cSchedule::Sort(void)
|
|||||||
p->SetRunningStatus(SI::RunningStatusNotRunning);
|
p->SetRunningStatus(SI::RunningStatusNotRunning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SetModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
void cSchedule::DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
|
void cSchedule::DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
|
||||||
{
|
{
|
||||||
if (SegmentStart > 0 && SegmentEnd > 0) {
|
if (SegmentStart > 0 && SegmentEnd > 0) {
|
||||||
for (cEvent *p = events.First(); p; p = events.Next(p)) {
|
cEvent *p = events.First();
|
||||||
if (p->EndTime() > SegmentStart) {
|
while (p) {
|
||||||
if (p->StartTime() < SegmentEnd) {
|
cEvent *n = events.Next(p);
|
||||||
// The event overlaps with the given time segment.
|
if (p->EndTime() > SegmentStart) {
|
||||||
if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) {
|
if (p->StartTime() < SegmentEnd) {
|
||||||
// The segment overwrites all events from tables with higher ids, and
|
// The event overlaps with the given time segment.
|
||||||
// within the same table id all events must have the same version.
|
if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) {
|
||||||
// We can't delete the event right here because a timer might have
|
// The segment overwrites all events from tables with higher ids, and
|
||||||
// a pointer to it, so let's set its id and start time to 0 to have it
|
// within the same table id all events must have the same version.
|
||||||
// "phased out":
|
DelEvent(p);
|
||||||
if (hasRunning && p->IsRunning())
|
}
|
||||||
ClrRunningStatus();
|
}
|
||||||
UnhashEvent(p);
|
else
|
||||||
p->eventID = 0;
|
break;
|
||||||
p->startTime = 0;
|
}
|
||||||
}
|
p = n;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1066,9 +1091,9 @@ void cSchedule::Cleanup(time_t Time)
|
|||||||
|
|
||||||
void cSchedule::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) const
|
void cSchedule::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) const
|
||||||
{
|
{
|
||||||
cChannel *channel = Channels.GetByChannelID(channelID, true);
|
LOCK_CHANNELS_READ;
|
||||||
if (channel) {
|
if (const cChannel *Channel = Channels->GetByChannelID(channelID, true)) {
|
||||||
fprintf(f, "%sC %s %s\n", Prefix, *channel->GetChannelID().ToString(), channel->Name());
|
fprintf(f, "%sC %s %s\n", Prefix, *Channel->GetChannelID().ToString(), Channel->Name());
|
||||||
const cEvent *p;
|
const cEvent *p;
|
||||||
switch (DumpMode) {
|
switch (DumpMode) {
|
||||||
case dmAll: {
|
case dmAll: {
|
||||||
@ -1111,12 +1136,10 @@ bool cSchedule::Read(FILE *f, cSchedules *Schedules)
|
|||||||
if (*s) {
|
if (*s) {
|
||||||
tChannelID channelID = tChannelID::FromString(s);
|
tChannelID channelID = tChannelID::FromString(s);
|
||||||
if (channelID.Valid()) {
|
if (channelID.Valid()) {
|
||||||
cSchedule *p = Schedules->AddSchedule(channelID);
|
if (cSchedule *p = Schedules->AddSchedule(channelID)) {
|
||||||
if (p) {
|
|
||||||
if (!cEvent::Read(f, p))
|
if (!cEvent::Read(f, p))
|
||||||
return false;
|
return false;
|
||||||
p->Sort();
|
p->Sort();
|
||||||
Schedules->SetModified(p);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -1164,12 +1187,12 @@ void cEpgDataWriter::Perform(void)
|
|||||||
{
|
{
|
||||||
cMutexLock MutexLock(&mutex); // to make sure fore- and background calls don't cause parellel dumps!
|
cMutexLock MutexLock(&mutex); // to make sure fore- and background calls don't cause parellel dumps!
|
||||||
{
|
{
|
||||||
cSchedulesLock SchedulesLock(true, 1000);
|
cStateKey StateKey;
|
||||||
cSchedules *s = (cSchedules *)cSchedules::Schedules(SchedulesLock);
|
if (cSchedules *Schedules = cSchedules::GetSchedulesWrite(StateKey, 1000)) {
|
||||||
if (s) {
|
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
for (cSchedule *p = s->First(); p; p = s->Next(p))
|
for (cSchedule *p = Schedules->First(); p; p = Schedules->Next(p))
|
||||||
p->Cleanup(now);
|
p->Cleanup(now);
|
||||||
|
StateKey.Remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dump)
|
if (dump)
|
||||||
@ -1178,29 +1201,25 @@ void cEpgDataWriter::Perform(void)
|
|||||||
|
|
||||||
static cEpgDataWriter EpgDataWriter;
|
static cEpgDataWriter EpgDataWriter;
|
||||||
|
|
||||||
// --- cSchedulesLock --------------------------------------------------------
|
|
||||||
|
|
||||||
cSchedulesLock::cSchedulesLock(bool WriteLock, int TimeoutMs)
|
|
||||||
{
|
|
||||||
locked = cSchedules::schedules.rwlock.Lock(WriteLock, TimeoutMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
cSchedulesLock::~cSchedulesLock()
|
|
||||||
{
|
|
||||||
if (locked)
|
|
||||||
cSchedules::schedules.rwlock.Unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- cSchedules ------------------------------------------------------------
|
// --- cSchedules ------------------------------------------------------------
|
||||||
|
|
||||||
cSchedules cSchedules::schedules;
|
cSchedules cSchedules::schedules;
|
||||||
char *cSchedules::epgDataFileName = NULL;
|
char *cSchedules::epgDataFileName = NULL;
|
||||||
time_t cSchedules::lastDump = time(NULL);
|
time_t cSchedules::lastDump = time(NULL);
|
||||||
time_t cSchedules::modified = 0;
|
|
||||||
|
|
||||||
const cSchedules *cSchedules::Schedules(cSchedulesLock &SchedulesLock)
|
cSchedules::cSchedules(void)
|
||||||
|
:cList<cSchedule>("Schedules")
|
||||||
{
|
{
|
||||||
return SchedulesLock.Locked() ? &schedules : NULL;
|
}
|
||||||
|
|
||||||
|
const cSchedules *cSchedules::GetSchedulesRead(cStateKey &StateKey, int TimeoutMs)
|
||||||
|
{
|
||||||
|
return schedules.Lock(StateKey, false, TimeoutMs) ? &schedules : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
cSchedules *cSchedules::GetSchedulesWrite(cStateKey &StateKey, int TimeoutMs)
|
||||||
|
{
|
||||||
|
return schedules.Lock(StateKey, true, TimeoutMs) ? &schedules : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cSchedules::SetEpgDataFileName(const char *FileName)
|
void cSchedules::SetEpgDataFileName(const char *FileName)
|
||||||
@ -1210,12 +1229,6 @@ void cSchedules::SetEpgDataFileName(const char *FileName)
|
|||||||
EpgDataWriter.SetDump(epgDataFileName != NULL);
|
EpgDataWriter.SetDump(epgDataFileName != NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cSchedules::SetModified(cSchedule *Schedule)
|
|
||||||
{
|
|
||||||
Schedule->SetModified();
|
|
||||||
modified = time(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cSchedules::Cleanup(bool Force)
|
void cSchedules::Cleanup(bool Force)
|
||||||
{
|
{
|
||||||
if (Force)
|
if (Force)
|
||||||
@ -1232,83 +1245,59 @@ void cSchedules::Cleanup(bool Force)
|
|||||||
|
|
||||||
void cSchedules::ResetVersions(void)
|
void cSchedules::ResetVersions(void)
|
||||||
{
|
{
|
||||||
cSchedulesLock SchedulesLock(true);
|
LOCK_SCHEDULES_WRITE;
|
||||||
cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
|
for (cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->Next(Schedule))
|
||||||
if (s) {
|
Schedule->ResetVersions();
|
||||||
for (cSchedule *Schedule = s->First(); Schedule; Schedule = s->Next(Schedule))
|
|
||||||
Schedule->ResetVersions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool cSchedules::ClearAll(void)
|
|
||||||
{
|
|
||||||
cSchedulesLock SchedulesLock(true, 1000);
|
|
||||||
cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
|
|
||||||
if (s) {
|
|
||||||
for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer))
|
|
||||||
Timer->SetEvent(NULL);
|
|
||||||
for (cSchedule *Schedule = s->First(); Schedule; Schedule = s->Next(Schedule))
|
|
||||||
Schedule->Cleanup(INT_MAX);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cSchedules::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime)
|
bool cSchedules::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime)
|
||||||
{
|
{
|
||||||
cSchedulesLock SchedulesLock;
|
cSafeFile *sf = NULL;
|
||||||
cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
|
if (!f) {
|
||||||
if (s) {
|
sf = new cSafeFile(epgDataFileName);
|
||||||
cSafeFile *sf = NULL;
|
if (sf->Open())
|
||||||
if (!f) {
|
f = *sf;
|
||||||
sf = new cSafeFile(epgDataFileName);
|
else {
|
||||||
if (sf->Open())
|
LOG_ERROR;
|
||||||
f = *sf;
|
|
||||||
else {
|
|
||||||
LOG_ERROR;
|
|
||||||
delete sf;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (cSchedule *p = s->First(); p; p = s->Next(p))
|
|
||||||
p->Dump(f, Prefix, DumpMode, AtTime);
|
|
||||||
if (sf) {
|
|
||||||
sf->Close();
|
|
||||||
delete sf;
|
delete sf;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
LOCK_SCHEDULES_READ;
|
||||||
|
for (const cSchedule *p = Schedules->First(); p; p = Schedules->Next(p))
|
||||||
|
p->Dump(f, Prefix, DumpMode, AtTime);
|
||||||
|
if (sf) {
|
||||||
|
sf->Close();
|
||||||
|
delete sf;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cSchedules::Read(FILE *f)
|
bool cSchedules::Read(FILE *f)
|
||||||
{
|
{
|
||||||
cSchedulesLock SchedulesLock(true, 1000);
|
bool OwnFile = f == NULL;
|
||||||
cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
|
if (OwnFile) {
|
||||||
if (s) {
|
if (epgDataFileName && access(epgDataFileName, R_OK) == 0) {
|
||||||
bool OwnFile = f == NULL;
|
dsyslog("reading EPG data from %s", epgDataFileName);
|
||||||
if (OwnFile) {
|
if ((f = fopen(epgDataFileName, "r")) == NULL) {
|
||||||
if (epgDataFileName && access(epgDataFileName, R_OK) == 0) {
|
LOG_ERROR;
|
||||||
dsyslog("reading EPG data from %s", epgDataFileName);
|
|
||||||
if ((f = fopen(epgDataFileName, "r")) == NULL) {
|
|
||||||
LOG_ERROR;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bool result = cSchedule::Read(f, s);
|
else
|
||||||
if (OwnFile)
|
return false;
|
||||||
fclose(f);
|
|
||||||
if (result) {
|
|
||||||
// Initialize the channels' schedule pointers, so that the first WhatsOn menu will come up faster:
|
|
||||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel))
|
|
||||||
s->GetSchedule(Channel);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
return false;
|
LOCK_CHANNELS_WRITE;
|
||||||
|
LOCK_SCHEDULES_WRITE;
|
||||||
|
bool result = cSchedule::Read(f, Schedules);
|
||||||
|
if (OwnFile)
|
||||||
|
fclose(f);
|
||||||
|
if (result) {
|
||||||
|
// Initialize the channels' schedule pointers, so that the first WhatsOn menu will come up faster:
|
||||||
|
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel))
|
||||||
|
Schedules->GetSchedule(Channel);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
cSchedule *cSchedules::AddSchedule(tChannelID ChannelID)
|
cSchedule *cSchedules::AddSchedule(tChannelID ChannelID)
|
||||||
@ -1318,9 +1307,6 @@ cSchedule *cSchedules::AddSchedule(tChannelID ChannelID)
|
|||||||
if (!p) {
|
if (!p) {
|
||||||
p = new cSchedule(ChannelID);
|
p = new cSchedule(ChannelID);
|
||||||
Add(p);
|
Add(p);
|
||||||
cChannel *channel = Channels.GetByChannelID(ChannelID);
|
|
||||||
if (channel)
|
|
||||||
channel->schedule = p;
|
|
||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
@ -1328,7 +1314,7 @@ cSchedule *cSchedules::AddSchedule(tChannelID ChannelID)
|
|||||||
const cSchedule *cSchedules::GetSchedule(tChannelID ChannelID) const
|
const cSchedule *cSchedules::GetSchedule(tChannelID ChannelID) const
|
||||||
{
|
{
|
||||||
ChannelID.ClrRid();
|
ChannelID.ClrRid();
|
||||||
for (cSchedule *p = First(); p; p = Next(p)) {
|
for (const cSchedule *p = First(); p; p = Next(p)) {
|
||||||
if (p->ChannelID() == ChannelID)
|
if (p->ChannelID() == ChannelID)
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
@ -1541,18 +1527,18 @@ void cEpgHandlers::DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t
|
|||||||
Schedule->DropOutdated(SegmentStart, SegmentEnd, TableID, Version);
|
Schedule->DropOutdated(SegmentStart, SegmentEnd, TableID, Version);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cEpgHandlers::BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus)
|
void cEpgHandlers::BeginSegmentTransfer(const cChannel *Channel)
|
||||||
{
|
{
|
||||||
for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
|
for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
|
||||||
if (eh->BeginSegmentTransfer(Channel, OnlyRunningStatus))
|
if (eh->BeginSegmentTransfer(Channel, false))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cEpgHandlers::EndSegmentTransfer(bool Modified, bool OnlyRunningStatus)
|
void cEpgHandlers::EndSegmentTransfer(bool Modified)
|
||||||
{
|
{
|
||||||
for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
|
for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
|
||||||
if (eh->EndSegmentTransfer(Modified, OnlyRunningStatus))
|
if (eh->EndSegmentTransfer(Modified, false))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
72
epg.h
72
epg.h
@ -7,7 +7,7 @@
|
|||||||
* Original version (as used in VDR before 1.3.0) written by
|
* Original version (as used in VDR before 1.3.0) written by
|
||||||
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
||||||
*
|
*
|
||||||
* $Id: epg.h 3.1 2013/08/23 10:50:05 kls Exp $
|
* $Id: epg.h 4.1 2015/08/09 11:25:04 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __EPG_H
|
#ifndef __EPG_H
|
||||||
@ -66,13 +66,15 @@ public:
|
|||||||
|
|
||||||
class cSchedule;
|
class cSchedule;
|
||||||
|
|
||||||
typedef u_int32_t tEventID;
|
typedef u_int16_t tEventID;
|
||||||
|
|
||||||
class cEvent : public cListObject {
|
class cEvent : public cListObject {
|
||||||
friend class cSchedule;
|
friend class cSchedule;
|
||||||
private:
|
private:
|
||||||
|
static cMutex numTimersMutex; // Protects numTimers, because it might be accessed from parallel read locks
|
||||||
// The sequence of these parameters is optimized for minimal memory waste!
|
// The sequence of these parameters is optimized for minimal memory waste!
|
||||||
cSchedule *schedule; // The Schedule this event belongs to
|
cSchedule *schedule; // The Schedule this event belongs to
|
||||||
|
mutable u_int16_t numTimers;// The number of timers that use this event
|
||||||
tEventID eventID; // Event ID of this event
|
tEventID eventID; // Event ID of this event
|
||||||
uchar tableID; // Table ID this event came from
|
uchar tableID; // Table ID this event came from
|
||||||
uchar version; // Version number of section this event came from
|
uchar version; // Version number of section this event came from
|
||||||
@ -109,7 +111,9 @@ public:
|
|||||||
time_t Vps(void) const { return vps; }
|
time_t Vps(void) const { return vps; }
|
||||||
time_t Seen(void) const { return seen; }
|
time_t Seen(void) const { return seen; }
|
||||||
bool SeenWithin(int Seconds) const { return time(NULL) - seen < Seconds; }
|
bool SeenWithin(int Seconds) const { return time(NULL) - seen < Seconds; }
|
||||||
bool HasTimer(void) const;
|
void IncNumTimers(void) const;
|
||||||
|
void DecNumTimers(void) const;
|
||||||
|
bool HasTimer(void) const { return numTimers > 0; }
|
||||||
bool IsRunning(bool OrAboutToStart = false) const;
|
bool IsRunning(bool OrAboutToStart = false) const;
|
||||||
static const char *ContentToString(uchar Content);
|
static const char *ContentToString(uchar Content);
|
||||||
cString GetParentalRatingString(void) const;
|
cString GetParentalRatingString(void) const;
|
||||||
@ -120,7 +124,7 @@ public:
|
|||||||
void SetEventID(tEventID EventID);
|
void SetEventID(tEventID EventID);
|
||||||
void SetTableID(uchar TableID);
|
void SetTableID(uchar TableID);
|
||||||
void SetVersion(uchar Version);
|
void SetVersion(uchar Version);
|
||||||
void SetRunningStatus(int RunningStatus, cChannel *Channel = NULL);
|
void SetRunningStatus(int RunningStatus, const cChannel *Channel = NULL);
|
||||||
void SetTitle(const char *Title);
|
void SetTitle(const char *Title);
|
||||||
void SetShortText(const char *ShortText);
|
void SetShortText(const char *ShortText);
|
||||||
void SetDescription(const char *Description);
|
void SetDescription(const char *Description);
|
||||||
@ -142,28 +146,33 @@ class cSchedules;
|
|||||||
|
|
||||||
class cSchedule : public cListObject {
|
class cSchedule : public cListObject {
|
||||||
private:
|
private:
|
||||||
|
static cMutex numTimersMutex; // Protects numTimers, because it might be accessed from parallel read locks
|
||||||
tChannelID channelID;
|
tChannelID channelID;
|
||||||
cList<cEvent> events;
|
cList<cEvent> events;
|
||||||
cHash<cEvent> eventsHashID;
|
cHash<cEvent> eventsHashID;
|
||||||
cHash<cEvent> eventsHashStartTime;
|
cHash<cEvent> eventsHashStartTime;
|
||||||
|
mutable u_int16_t numTimers;// The number of timers that use this schedule
|
||||||
bool hasRunning;
|
bool hasRunning;
|
||||||
time_t modified;
|
int modified;
|
||||||
time_t presentSeen;
|
time_t presentSeen;
|
||||||
public:
|
public:
|
||||||
cSchedule(tChannelID ChannelID);
|
cSchedule(tChannelID ChannelID);
|
||||||
tChannelID ChannelID(void) const { return channelID; }
|
tChannelID ChannelID(void) const { return channelID; }
|
||||||
time_t Modified(void) const { return modified; }
|
bool Modified(int &State) const { bool Result = State != modified; State = modified; return Result; }
|
||||||
time_t PresentSeen(void) const { return presentSeen; }
|
time_t PresentSeen(void) const { return presentSeen; }
|
||||||
bool PresentSeenWithin(int Seconds) const { return time(NULL) - presentSeen < Seconds; }
|
bool PresentSeenWithin(int Seconds) const { return time(NULL) - presentSeen < Seconds; }
|
||||||
void SetModified(void) { modified = time(NULL); }
|
void SetModified(void) { modified++; }
|
||||||
void SetPresentSeen(void) { presentSeen = time(NULL); }
|
void SetPresentSeen(void) { presentSeen = time(NULL); }
|
||||||
void SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Channel = NULL);
|
void SetRunningStatus(cEvent *Event, int RunningStatus, const cChannel *Channel = NULL);
|
||||||
void ClrRunningStatus(cChannel *Channel = NULL);
|
void ClrRunningStatus(cChannel *Channel = NULL);
|
||||||
void ResetVersions(void);
|
void ResetVersions(void);
|
||||||
void Sort(void);
|
void Sort(void);
|
||||||
void DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version);
|
void DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version);
|
||||||
void Cleanup(time_t Time);
|
void Cleanup(time_t Time);
|
||||||
void Cleanup(void);
|
void Cleanup(void);
|
||||||
|
void IncNumTimers(void) const;
|
||||||
|
void DecNumTimers(void) const;
|
||||||
|
bool HasTimer(void) const { return numTimers > 0; }
|
||||||
cEvent *AddEvent(cEvent *Event);
|
cEvent *AddEvent(cEvent *Event);
|
||||||
void DelEvent(cEvent *Event);
|
void DelEvent(cEvent *Event);
|
||||||
void HashEvent(cEvent *Event);
|
void HashEvent(cEvent *Event);
|
||||||
@ -177,35 +186,23 @@ public:
|
|||||||
static bool Read(FILE *f, cSchedules *Schedules);
|
static bool Read(FILE *f, cSchedules *Schedules);
|
||||||
};
|
};
|
||||||
|
|
||||||
class cSchedulesLock {
|
|
||||||
private:
|
|
||||||
bool locked;
|
|
||||||
public:
|
|
||||||
cSchedulesLock(bool WriteLock = false, int TimeoutMs = 0);
|
|
||||||
~cSchedulesLock();
|
|
||||||
bool Locked(void) { return locked; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class cSchedules : public cList<cSchedule> {
|
class cSchedules : public cList<cSchedule> {
|
||||||
friend class cSchedule;
|
friend class cSchedule;
|
||||||
friend class cSchedulesLock;
|
|
||||||
private:
|
private:
|
||||||
cRwLock rwlock;
|
|
||||||
static cSchedules schedules;
|
static cSchedules schedules;
|
||||||
static char *epgDataFileName;
|
static char *epgDataFileName;
|
||||||
static time_t lastDump;
|
static time_t lastDump;
|
||||||
static time_t modified;
|
|
||||||
public:
|
public:
|
||||||
|
cSchedules(void);
|
||||||
|
static const cSchedules *GetSchedulesRead(cStateKey &StateKey, int TimeoutMs = 0);
|
||||||
|
///< Gets the list of schedules for read access.
|
||||||
|
///< See cTimers::GetTimersRead() for details.
|
||||||
|
static cSchedules *GetSchedulesWrite(cStateKey &StateKey, int TimeoutMs = 0);
|
||||||
|
///< Gets the list of schedules for write access.
|
||||||
|
///< See cTimers::GetTimersWrite() for details.
|
||||||
static void SetEpgDataFileName(const char *FileName);
|
static void SetEpgDataFileName(const char *FileName);
|
||||||
static const cSchedules *Schedules(cSchedulesLock &SchedulesLock);
|
|
||||||
///< Caller must provide a cSchedulesLock which has to survive the entire
|
|
||||||
///< time the returned cSchedules is accessed. Once the cSchedules is no
|
|
||||||
///< longer used, the cSchedulesLock must be destroyed.
|
|
||||||
static time_t Modified(void) { return modified; }
|
|
||||||
static void SetModified(cSchedule *Schedule);
|
|
||||||
static void Cleanup(bool Force = false);
|
static void Cleanup(bool Force = false);
|
||||||
static void ResetVersions(void);
|
static void ResetVersions(void);
|
||||||
static bool ClearAll(void);
|
|
||||||
static bool Dump(FILE *f = NULL, const char *Prefix = "", eDumpMode DumpMode = dmAll, time_t AtTime = 0);
|
static bool Dump(FILE *f = NULL, const char *Prefix = "", eDumpMode DumpMode = dmAll, time_t AtTime = 0);
|
||||||
static bool Read(FILE *f = NULL);
|
static bool Read(FILE *f = NULL);
|
||||||
cSchedule *AddSchedule(tChannelID ChannelID);
|
cSchedule *AddSchedule(tChannelID ChannelID);
|
||||||
@ -213,6 +210,17 @@ public:
|
|||||||
const cSchedule *GetSchedule(const cChannel *Channel, bool AddIfMissing = false) const;
|
const cSchedule *GetSchedule(const cChannel *Channel, bool AddIfMissing = false) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Provide lock controlled access to the list:
|
||||||
|
|
||||||
|
DEF_LIST_LOCK(Schedules);
|
||||||
|
|
||||||
|
// These macros provide a convenient way of locking the global schedules list
|
||||||
|
// and making sure the lock is released as soon as the current scope is left
|
||||||
|
// (note that these macros wait forever to obtain the lock!):
|
||||||
|
|
||||||
|
#define LOCK_SCHEDULES_READ USE_LIST_LOCK_READ(Schedules);
|
||||||
|
#define LOCK_SCHEDULES_WRITE USE_LIST_LOCK_WRITE(Schedules);
|
||||||
|
|
||||||
class cEpgDataReader : public cThread {
|
class cEpgDataReader : public cThread {
|
||||||
public:
|
public:
|
||||||
cEpgDataReader(void);
|
cEpgDataReader(void);
|
||||||
@ -273,12 +281,14 @@ public:
|
|||||||
virtual bool DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version) { return false; }
|
virtual bool DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version) { return false; }
|
||||||
///< Takes a look at all EPG events between SegmentStart and SegmentEnd and
|
///< Takes a look at all EPG events between SegmentStart and SegmentEnd and
|
||||||
///< drops outdated events.
|
///< drops outdated events.
|
||||||
virtual bool BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus) { return false; }
|
virtual bool BeginSegmentTransfer(const cChannel *Channel, bool Dummy) { return false; } // TODO remove obsolete Dummy
|
||||||
///< Called directly after IgnoreChannel() before any other handler method is called.
|
///< Called directly after IgnoreChannel() before any other handler method is called.
|
||||||
///< Designed to give handlers the possibility to prepare a database transaction.
|
///< Designed to give handlers the possibility to prepare a database transaction.
|
||||||
virtual bool EndSegmentTransfer(bool Modified, bool OnlyRunningStatus) { return false; }
|
///< Dummy is for backward compatibility and may be removed in a future version.
|
||||||
|
virtual bool EndSegmentTransfer(bool Modified, bool Dummy) { return false; } // TODO remove obsolete Dummy
|
||||||
///< Called after the segment data has been processed.
|
///< Called after the segment data has been processed.
|
||||||
///< At this point handlers should close/commit/rollback any pending database transactions.
|
///< At this point handlers should close/commit/rollback any pending database transactions.
|
||||||
|
///< Dummy is for backward compatibility and may be removed in a future version.
|
||||||
};
|
};
|
||||||
|
|
||||||
class cEpgHandlers : public cList<cEpgHandler> {
|
class cEpgHandlers : public cList<cEpgHandler> {
|
||||||
@ -301,8 +311,8 @@ public:
|
|||||||
void HandleEvent(cEvent *Event);
|
void HandleEvent(cEvent *Event);
|
||||||
void SortSchedule(cSchedule *Schedule);
|
void SortSchedule(cSchedule *Schedule);
|
||||||
void DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version);
|
void DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version);
|
||||||
void BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus);
|
void BeginSegmentTransfer(const cChannel *Channel);
|
||||||
void EndSegmentTransfer(bool Modified, bool OnlyRunningStatus);
|
void EndSegmentTransfer(bool Modified);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern cEpgHandlers EpgHandlers;
|
extern cEpgHandlers EpgHandlers;
|
||||||
|
41
filter.c
41
filter.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: filter.c 4.1 2015/03/17 15:04:39 kls Exp $
|
* $Id: filter.c 4.2 2015/07/25 10:59:57 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "filter.h"
|
#include "filter.h"
|
||||||
@ -19,31 +19,38 @@ cSectionSyncer::cSectionSyncer(void)
|
|||||||
|
|
||||||
void cSectionSyncer::Reset(void)
|
void cSectionSyncer::Reset(void)
|
||||||
{
|
{
|
||||||
lastVersion = thisVersion = 0xFF;
|
currentVersion = -1;
|
||||||
nextNumber = 0;
|
currentSection = -1;
|
||||||
|
synced = false;
|
||||||
|
complete = false;
|
||||||
|
memset(sections, 0x00, sizeof(sections));
|
||||||
}
|
}
|
||||||
|
|
||||||
void cSectionSyncer::Repeat(void)
|
void cSectionSyncer::Repeat(void)
|
||||||
{
|
{
|
||||||
lastVersion = 0xFF;
|
SetSectionFlag(currentSection, false);
|
||||||
nextNumber--;
|
synced = false;
|
||||||
|
complete = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cSectionSyncer::Sync(uchar Version, int Number, int LastNumber)
|
bool cSectionSyncer::Sync(uchar Version, int Number, int LastNumber)
|
||||||
{
|
{
|
||||||
if (Version != lastVersion) {
|
if (Version != currentVersion) {
|
||||||
if (Version != thisVersion) {
|
Reset();
|
||||||
thisVersion = Version;
|
currentVersion = Version;
|
||||||
nextNumber = 0;
|
|
||||||
}
|
|
||||||
if (Number == nextNumber) {
|
|
||||||
if (Number == LastNumber)
|
|
||||||
lastVersion = Version;
|
|
||||||
nextNumber++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
if (!synced) {
|
||||||
|
if (Number != 0)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
synced = true;
|
||||||
|
}
|
||||||
|
currentSection = Number;
|
||||||
|
bool Result = !GetSectionFlag(Number);
|
||||||
|
SetSectionFlag(Number, true);
|
||||||
|
if (Number == LastNumber)
|
||||||
|
complete = true;
|
||||||
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- cFilterData -----------------------------------------------------------
|
// --- cFilterData -----------------------------------------------------------
|
||||||
|
13
filter.h
13
filter.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: filter.h 4.1 2015/03/17 15:00:08 kls Exp $
|
* $Id: filter.h 4.2 2015/07/25 10:03:44 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __FILTER_H
|
#ifndef __FILTER_H
|
||||||
@ -15,13 +15,18 @@
|
|||||||
|
|
||||||
class cSectionSyncer {
|
class cSectionSyncer {
|
||||||
private:
|
private:
|
||||||
int lastVersion;
|
int currentVersion;
|
||||||
int thisVersion;
|
int currentSection;
|
||||||
int nextNumber;
|
bool synced;
|
||||||
|
bool complete;
|
||||||
|
uchar sections[32]; // holds 32 * 8 = 256 bits, as flags for the sections
|
||||||
|
void SetSectionFlag(uchar Section, bool On) { if (On) sections[Section / 8] |= (1 << (Section % 8)); else sections[Section / 8] &= ~(1 << (Section % 8)); }
|
||||||
|
bool GetSectionFlag(uchar Section) { return sections[Section / 8] & (1 << (Section % 8)); }
|
||||||
public:
|
public:
|
||||||
cSectionSyncer(void);
|
cSectionSyncer(void);
|
||||||
void Reset(void);
|
void Reset(void);
|
||||||
void Repeat(void);
|
void Repeat(void);
|
||||||
|
bool Complete(void) { return complete; }
|
||||||
bool Sync(uchar Version, int Number, int LastNumber);
|
bool Sync(uchar Version, int Number, int LastNumber);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
21
menu.h
21
menu.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: menu.h 3.8 2015/02/06 09:47:30 kls Exp $
|
* $Id: menu.h 4.1 2015/08/31 13:34:12 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __MENU_H
|
#ifndef __MENU_H
|
||||||
@ -72,6 +72,7 @@ public:
|
|||||||
|
|
||||||
class cMenuEditTimer : public cOsdMenu {
|
class cMenuEditTimer : public cOsdMenu {
|
||||||
private:
|
private:
|
||||||
|
static const cTimer *addedTimer;
|
||||||
cTimer *timer;
|
cTimer *timer;
|
||||||
cTimer data;
|
cTimer data;
|
||||||
int channel;
|
int channel;
|
||||||
@ -86,13 +87,14 @@ public:
|
|||||||
cMenuEditTimer(cTimer *Timer, bool New = false);
|
cMenuEditTimer(cTimer *Timer, bool New = false);
|
||||||
virtual ~cMenuEditTimer();
|
virtual ~cMenuEditTimer();
|
||||||
virtual eOSState ProcessKey(eKeys Key);
|
virtual eOSState ProcessKey(eKeys Key);
|
||||||
|
static const cTimer *AddedTimer(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
class cMenuEvent : public cOsdMenu {
|
class cMenuEvent : public cOsdMenu {
|
||||||
private:
|
private:
|
||||||
const cEvent *event;
|
const cEvent *event;
|
||||||
public:
|
public:
|
||||||
cMenuEvent(const cEvent *Event, bool CanSwitch = false, bool Buttons = false);
|
cMenuEvent(const cTimers *Timers, const cChannels *Channels, const cEvent *Event, bool CanSwitch = false, bool Buttons = false);
|
||||||
virtual void Display(void);
|
virtual void Display(void);
|
||||||
virtual eOSState ProcessKey(eKeys Key);
|
virtual eOSState ProcessKey(eKeys Key);
|
||||||
};
|
};
|
||||||
@ -123,14 +125,14 @@ private:
|
|||||||
bool timeout;
|
bool timeout;
|
||||||
int osdState;
|
int osdState;
|
||||||
const cPositioner *positioner;
|
const cPositioner *positioner;
|
||||||
cChannel *channel;
|
const cChannel *channel;
|
||||||
const cEvent *lastPresent;
|
const cEvent *lastPresent;
|
||||||
const cEvent *lastFollowing;
|
const cEvent *lastFollowing;
|
||||||
static cDisplayChannel *currentDisplayChannel;
|
static cDisplayChannel *currentDisplayChannel;
|
||||||
void DisplayChannel(void);
|
void DisplayChannel(void);
|
||||||
void DisplayInfo(void);
|
void DisplayInfo(void);
|
||||||
void Refresh(void);
|
void Refresh(void);
|
||||||
cChannel *NextAvailableChannel(cChannel *Channel, int Direction);
|
const cChannel *NextAvailableChannel(const cChannel *Channel, int Direction);
|
||||||
public:
|
public:
|
||||||
cDisplayChannel(int Number, bool Switched);
|
cDisplayChannel(int Number, bool Switched);
|
||||||
cDisplayChannel(eKeys FirstKey);
|
cDisplayChannel(eKeys FirstKey);
|
||||||
@ -205,7 +207,7 @@ class cMenuRecordings : public cOsdMenu {
|
|||||||
private:
|
private:
|
||||||
char *base;
|
char *base;
|
||||||
int level;
|
int level;
|
||||||
int recordingsState;
|
cStateKey recordingsStateKey;
|
||||||
int helpKeys;
|
int helpKeys;
|
||||||
const cRecordingFilter *filter;
|
const cRecordingFilter *filter;
|
||||||
static cString path;
|
static cString path;
|
||||||
@ -239,7 +241,7 @@ private:
|
|||||||
char *fileName;
|
char *fileName;
|
||||||
bool GetEvent(void);
|
bool GetEvent(void);
|
||||||
public:
|
public:
|
||||||
cRecordControl(cDevice *Device, cTimer *Timer = NULL, bool Pause = false);
|
cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer = NULL, bool Pause = false);
|
||||||
virtual ~cRecordControl();
|
virtual ~cRecordControl();
|
||||||
bool Process(time_t t);
|
bool Process(time_t t);
|
||||||
cDevice *Device(void) { return device; }
|
cDevice *Device(void) { return device; }
|
||||||
@ -254,7 +256,8 @@ private:
|
|||||||
static cRecordControl *RecordControls[];
|
static cRecordControl *RecordControls[];
|
||||||
static int state;
|
static int state;
|
||||||
public:
|
public:
|
||||||
static bool Start(cTimer *Timer = NULL, bool Pause = false);
|
static bool Start(cTimers *Timers, cTimer *Timer, bool Pause = false);
|
||||||
|
static bool Start(bool Pause = false);
|
||||||
static void Stop(const char *InstantId);
|
static void Stop(const char *InstantId);
|
||||||
static bool PauseLiveVideo(void);
|
static bool PauseLiveVideo(void);
|
||||||
static const char *GetInstantId(const char *LastInstantId);
|
static const char *GetInstantId(const char *LastInstantId);
|
||||||
@ -262,8 +265,8 @@ public:
|
|||||||
static cRecordControl *GetRecordControl(const cTimer *Timer);
|
static cRecordControl *GetRecordControl(const cTimer *Timer);
|
||||||
///< Returns the cRecordControl for the given Timer.
|
///< Returns the cRecordControl for the given Timer.
|
||||||
///< If there is no cRecordControl for Timer, NULL is returned.
|
///< If there is no cRecordControl for Timer, NULL is returned.
|
||||||
static void Process(time_t t);
|
static bool Process(cTimers *Timers, time_t t);
|
||||||
static void ChannelDataModified(cChannel *Channel);
|
static void ChannelDataModified(const cChannel *Channel);
|
||||||
static bool Active(void);
|
static bool Active(void);
|
||||||
static void Shutdown(void);
|
static void Shutdown(void);
|
||||||
static void ChangeState(void) { state++; }
|
static void ChangeState(void) { state++; }
|
||||||
|
46
menuitems.c
46
menuitems.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: menuitems.c 3.3 2015/02/09 11:53:10 kls Exp $
|
* $Id: menuitems.c 4.1 2015/07/18 10:38:31 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "menuitems.h"
|
#include "menuitems.h"
|
||||||
@ -777,7 +777,7 @@ void cMenuEditStraItem::Set(void)
|
|||||||
// --- cMenuEditChanItem -----------------------------------------------------
|
// --- cMenuEditChanItem -----------------------------------------------------
|
||||||
|
|
||||||
cMenuEditChanItem::cMenuEditChanItem(const char *Name, int *Value, const char *NoneString)
|
cMenuEditChanItem::cMenuEditChanItem(const char *Name, int *Value, const char *NoneString)
|
||||||
:cMenuEditIntItem(Name, Value, NoneString ? 0 : 1, Channels.MaxNumber())
|
:cMenuEditIntItem(Name, Value, NoneString ? 0 : 1, cChannels::MaxNumber())
|
||||||
{
|
{
|
||||||
channelID = NULL;
|
channelID = NULL;
|
||||||
noneString = NoneString;
|
noneString = NoneString;
|
||||||
@ -786,12 +786,13 @@ cMenuEditChanItem::cMenuEditChanItem(const char *Name, int *Value, const char *N
|
|||||||
}
|
}
|
||||||
|
|
||||||
cMenuEditChanItem::cMenuEditChanItem(const char *Name, cString *ChannelID, const char *NoneString)
|
cMenuEditChanItem::cMenuEditChanItem(const char *Name, cString *ChannelID, const char *NoneString)
|
||||||
:cMenuEditIntItem(Name, &dummyValue, NoneString ? 0 : 1, Channels.MaxNumber())
|
:cMenuEditIntItem(Name, &dummyValue, NoneString ? 0 : 1, cChannels::MaxNumber())
|
||||||
{
|
{
|
||||||
channelID = ChannelID;
|
channelID = ChannelID;
|
||||||
noneString = NoneString;
|
noneString = NoneString;
|
||||||
cChannel *channel = Channels.GetByChannelID(tChannelID::FromString(*ChannelID));
|
LOCK_CHANNELS_READ;
|
||||||
dummyValue = channel ? channel->Number() : 0;
|
const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(*ChannelID));
|
||||||
|
dummyValue = Channel ? Channel->Number() : 0;
|
||||||
Set();
|
Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -799,11 +800,12 @@ void cMenuEditChanItem::Set(void)
|
|||||||
{
|
{
|
||||||
if (*value > 0) {
|
if (*value > 0) {
|
||||||
char buf[255];
|
char buf[255];
|
||||||
cChannel *channel = Channels.GetByNumber(*value);
|
LOCK_CHANNELS_READ;
|
||||||
snprintf(buf, sizeof(buf), "%d %s", *value, channel ? channel->Name() : "");
|
const cChannel *Channel = Channels->GetByNumber(*value);
|
||||||
|
snprintf(buf, sizeof(buf), "%d %s", *value, Channel ? Channel->Name() : "");
|
||||||
SetValue(buf);
|
SetValue(buf);
|
||||||
if (channelID)
|
if (channelID)
|
||||||
*channelID = channel ? channel->GetChannelID().ToString() : "";
|
*channelID = Channel ? Channel->GetChannelID().ToString() : "";
|
||||||
}
|
}
|
||||||
else if (noneString) {
|
else if (noneString) {
|
||||||
SetValue(noneString);
|
SetValue(noneString);
|
||||||
@ -822,13 +824,14 @@ eOSState cMenuEditChanItem::ProcessKey(eKeys Key)
|
|||||||
case kRight|k_Repeat:
|
case kRight|k_Repeat:
|
||||||
case kRight:
|
case kRight:
|
||||||
{
|
{
|
||||||
cChannel *channel = Channels.GetByNumber(*value + delta, delta);
|
LOCK_CHANNELS_READ
|
||||||
if (channel)
|
const cChannel *Channel = Channels->GetByNumber(*value + delta, delta);
|
||||||
*value = channel->Number();
|
if (Channel)
|
||||||
|
*value = Channel->Number();
|
||||||
else if (delta < 0 && noneString)
|
else if (delta < 0 && noneString)
|
||||||
*value = 0;
|
*value = 0;
|
||||||
if (channelID)
|
if (channelID)
|
||||||
*channelID = channel ? channel->GetChannelID().ToString() : "";
|
*channelID = Channel ? Channel->GetChannelID().ToString() : "";
|
||||||
Set();
|
Set();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -845,13 +848,14 @@ cMenuEditTranItem::cMenuEditTranItem(const char *Name, int *Value, int *Source)
|
|||||||
number = 0;
|
number = 0;
|
||||||
source = Source;
|
source = Source;
|
||||||
transponder = Value;
|
transponder = Value;
|
||||||
cChannel *channel = Channels.First();
|
LOCK_CHANNELS_READ;
|
||||||
while (channel) {
|
const cChannel *Channel = Channels->First();
|
||||||
if (!channel->GroupSep() && *source == channel->Source() && ISTRANSPONDER(channel->Transponder(), *Value)) {
|
while (Channel) {
|
||||||
number = channel->Number();
|
if (!Channel->GroupSep() && *source == Channel->Source() && ISTRANSPONDER(Channel->Transponder(), *Value)) {
|
||||||
|
number = Channel->Number();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
channel = (cChannel *)channel->Next();
|
Channel = Channels->Next(Channel);
|
||||||
}
|
}
|
||||||
Set();
|
Set();
|
||||||
}
|
}
|
||||||
@ -859,10 +863,10 @@ cMenuEditTranItem::cMenuEditTranItem(const char *Name, int *Value, int *Source)
|
|||||||
eOSState cMenuEditTranItem::ProcessKey(eKeys Key)
|
eOSState cMenuEditTranItem::ProcessKey(eKeys Key)
|
||||||
{
|
{
|
||||||
eOSState state = cMenuEditChanItem::ProcessKey(Key);
|
eOSState state = cMenuEditChanItem::ProcessKey(Key);
|
||||||
cChannel *channel = Channels.GetByNumber(number);
|
LOCK_CHANNELS_READ
|
||||||
if (channel) {
|
if (const cChannel *Channel = Channels->GetByNumber(number)) {
|
||||||
*source = channel->Source();
|
*source = Channel->Source();
|
||||||
*transponder = channel->Transponder();
|
*transponder = Channel->Transponder();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
*source = 0;
|
*source = 0;
|
||||||
|
43
nit.c
43
nit.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: nit.c 4.2 2015/03/17 15:10:09 kls Exp $
|
* $Id: nit.c 4.3 2015/07/26 09:24:36 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "nit.h"
|
#include "nit.h"
|
||||||
@ -61,10 +61,13 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
}
|
}
|
||||||
dbgnit("NIT: %02X %2d %2d %2d %s %d %d '%s'\n", Tid, nit.getVersionNumber(), nit.getSectionNumber(), nit.getLastSectionNumber(), *cSource::ToString(Source()), nit.getNetworkId(), Transponder(), NetworkName);
|
dbgnit("NIT: %02X %2d %2d %2d %s %d %d '%s'\n", Tid, nit.getVersionNumber(), nit.getSectionNumber(), nit.getLastSectionNumber(), *cSource::ToString(Source()), nit.getNetworkId(), Transponder(), NetworkName);
|
||||||
}
|
}
|
||||||
if (!Channels.Lock(true, 10)) {
|
cStateKey StateKey;
|
||||||
|
cChannels *Channels = cChannels::GetChannelsWrite(StateKey, 10);
|
||||||
|
if (!Channels) {
|
||||||
sectionSyncer.Repeat(); // let's not miss any section of the NIT
|
sectionSyncer.Repeat(); // let's not miss any section of the NIT
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
bool ChannelsModified = false;
|
||||||
SI::NIT::TransportStream ts;
|
SI::NIT::TransportStream ts;
|
||||||
for (SI::Loop::Iterator it; nit.transportStreamLoop.getNext(ts, it); ) {
|
for (SI::Loop::Iterator it; nit.transportStreamLoop.getNext(ts, it); ) {
|
||||||
SI::Descriptor *d;
|
SI::Descriptor *d;
|
||||||
@ -115,7 +118,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
if (Setup.UpdateChannels >= 5) {
|
if (Setup.UpdateChannels >= 5) {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
bool forceTransponderUpdate = false;
|
bool forceTransponderUpdate = false;
|
||||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||||
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
||||||
int transponder = Channel->Transponder();
|
int transponder = Channel->Transponder();
|
||||||
found = true;
|
found = true;
|
||||||
@ -128,7 +131,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ISTRANSPONDER(cChannel::Transponder(Frequency, dtp.Polarization()), Transponder())) // only modify channels if we're actually receiving this transponder
|
if (ISTRANSPONDER(cChannel::Transponder(Frequency, dtp.Polarization()), Transponder())) // only modify channels if we're actually receiving this transponder
|
||||||
Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('S'));
|
ChannelsModified |= Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('S'));
|
||||||
else if (Channel->Srate() != SymbolRate || strcmp(Channel->Parameters(), dtp.ToString('S')))
|
else if (Channel->Srate() != SymbolRate || strcmp(Channel->Parameters(), dtp.ToString('S')))
|
||||||
forceTransponderUpdate = true; // get us receiving this transponder
|
forceTransponderUpdate = true; // get us receiving this transponder
|
||||||
}
|
}
|
||||||
@ -136,7 +139,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
if (!found || forceTransponderUpdate) {
|
if (!found || forceTransponderUpdate) {
|
||||||
for (int n = 0; n < NumFrequencies; n++) {
|
for (int n = 0; n < NumFrequencies; n++) {
|
||||||
cChannel *Channel = new cChannel;
|
cChannel *Channel = new cChannel;
|
||||||
Channel->SetId(ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
|
Channel->SetId(NULL, ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
|
||||||
if (Channel->SetTransponderData(Source, Frequencies[n], SymbolRate, dtp.ToString('S')))
|
if (Channel->SetTransponderData(Source, Frequencies[n], SymbolRate, dtp.ToString('S')))
|
||||||
EITScanner.AddTransponder(Channel);
|
EITScanner.AddTransponder(Channel);
|
||||||
else
|
else
|
||||||
@ -150,13 +153,13 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
break;
|
break;
|
||||||
case SI::S2SatelliteDeliverySystemDescriptorTag: {
|
case SI::S2SatelliteDeliverySystemDescriptorTag: {
|
||||||
if (Setup.UpdateChannels >= 5) {
|
if (Setup.UpdateChannels >= 5) {
|
||||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||||
if (!Channel->GroupSep() && cSource::IsSat(Channel->Source()) && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
if (!Channel->GroupSep() && cSource::IsSat(Channel->Source()) && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
||||||
SI::S2SatelliteDeliverySystemDescriptor *sd = (SI::S2SatelliteDeliverySystemDescriptor *)d;
|
SI::S2SatelliteDeliverySystemDescriptor *sd = (SI::S2SatelliteDeliverySystemDescriptor *)d;
|
||||||
cDvbTransponderParameters dtp(Channel->Parameters());
|
cDvbTransponderParameters dtp(Channel->Parameters());
|
||||||
dtp.SetSystem(DVB_SYSTEM_2);
|
dtp.SetSystem(DVB_SYSTEM_2);
|
||||||
dtp.SetStreamId(sd->getInputStreamIdentifier());
|
dtp.SetStreamId(sd->getInputStreamIdentifier());
|
||||||
Channel->SetTransponderData(Channel->Source(), Channel->Frequency(), Channel->Srate(), dtp.ToString('S'));
|
ChannelsModified |= Channel->SetTransponderData(Channel->Source(), Channel->Frequency(), Channel->Srate(), dtp.ToString('S'));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,7 +181,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
if (Setup.UpdateChannels >= 5) {
|
if (Setup.UpdateChannels >= 5) {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
bool forceTransponderUpdate = false;
|
bool forceTransponderUpdate = false;
|
||||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||||
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
||||||
int transponder = Channel->Transponder();
|
int transponder = Channel->Transponder();
|
||||||
found = true;
|
found = true;
|
||||||
@ -191,7 +194,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ISTRANSPONDER(Frequency / 1000, Transponder())) // only modify channels if we're actually receiving this transponder
|
if (ISTRANSPONDER(Frequency / 1000, Transponder())) // only modify channels if we're actually receiving this transponder
|
||||||
Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('C'));
|
ChannelsModified |= Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('C'));
|
||||||
else if (Channel->Srate() != SymbolRate || strcmp(Channel->Parameters(), dtp.ToString('C')))
|
else if (Channel->Srate() != SymbolRate || strcmp(Channel->Parameters(), dtp.ToString('C')))
|
||||||
forceTransponderUpdate = true; // get us receiving this transponder
|
forceTransponderUpdate = true; // get us receiving this transponder
|
||||||
}
|
}
|
||||||
@ -199,7 +202,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
if (!found || forceTransponderUpdate) {
|
if (!found || forceTransponderUpdate) {
|
||||||
for (int n = 0; n < NumFrequencies; n++) {
|
for (int n = 0; n < NumFrequencies; n++) {
|
||||||
cChannel *Channel = new cChannel;
|
cChannel *Channel = new cChannel;
|
||||||
Channel->SetId(ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
|
Channel->SetId(NULL, ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
|
||||||
if (Channel->SetTransponderData(Source, Frequencies[n], SymbolRate, dtp.ToString('C')))
|
if (Channel->SetTransponderData(Source, Frequencies[n], SymbolRate, dtp.ToString('C')))
|
||||||
EITScanner.AddTransponder(Channel);
|
EITScanner.AddTransponder(Channel);
|
||||||
else
|
else
|
||||||
@ -234,7 +237,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
if (Setup.UpdateChannels >= 5) {
|
if (Setup.UpdateChannels >= 5) {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
bool forceTransponderUpdate = false;
|
bool forceTransponderUpdate = false;
|
||||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||||
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
||||||
int transponder = Channel->Transponder();
|
int transponder = Channel->Transponder();
|
||||||
found = true;
|
found = true;
|
||||||
@ -247,7 +250,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ISTRANSPONDER(Frequency / 1000000, Transponder())) // only modify channels if we're actually receiving this transponder
|
if (ISTRANSPONDER(Frequency / 1000000, Transponder())) // only modify channels if we're actually receiving this transponder
|
||||||
Channel->SetTransponderData(Source, Frequency, 0, dtp.ToString('T'));
|
ChannelsModified |= Channel->SetTransponderData(Source, Frequency, 0, dtp.ToString('T'));
|
||||||
else if (strcmp(Channel->Parameters(), dtp.ToString('T')))
|
else if (strcmp(Channel->Parameters(), dtp.ToString('T')))
|
||||||
forceTransponderUpdate = true; // get us receiving this transponder
|
forceTransponderUpdate = true; // get us receiving this transponder
|
||||||
}
|
}
|
||||||
@ -255,7 +258,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
if (!found || forceTransponderUpdate) {
|
if (!found || forceTransponderUpdate) {
|
||||||
for (int n = 0; n < NumFrequencies; n++) {
|
for (int n = 0; n < NumFrequencies; n++) {
|
||||||
cChannel *Channel = new cChannel;
|
cChannel *Channel = new cChannel;
|
||||||
Channel->SetId(ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
|
Channel->SetId(NULL, ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
|
||||||
if (Channel->SetTransponderData(Source, Frequencies[n], 0, dtp.ToString('T')))
|
if (Channel->SetTransponderData(Source, Frequencies[n], 0, dtp.ToString('T')))
|
||||||
EITScanner.AddTransponder(Channel);
|
EITScanner.AddTransponder(Channel);
|
||||||
else
|
else
|
||||||
@ -272,7 +275,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
switch (sd->getExtensionDescriptorTag()) {
|
switch (sd->getExtensionDescriptorTag()) {
|
||||||
case SI::T2DeliverySystemDescriptorTag: {
|
case SI::T2DeliverySystemDescriptorTag: {
|
||||||
if (Setup.UpdateChannels >= 5) {
|
if (Setup.UpdateChannels >= 5) {
|
||||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||||
int Source = cSource::FromData(cSource::stTerr);
|
int Source = cSource::FromData(cSource::stTerr);
|
||||||
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
||||||
SI::T2DeliverySystemDescriptor *td = (SI::T2DeliverySystemDescriptor *)d;
|
SI::T2DeliverySystemDescriptor *td = (SI::T2DeliverySystemDescriptor *)d;
|
||||||
@ -292,7 +295,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
dtp.SetTransmission(T2TransmissionModes[td->getTransmissionMode()]);
|
dtp.SetTransmission(T2TransmissionModes[td->getTransmissionMode()]);
|
||||||
//TODO add parsing of frequencies
|
//TODO add parsing of frequencies
|
||||||
}
|
}
|
||||||
Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('T'));
|
ChannelsModified |= Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('T'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,9 +313,9 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
int lcn = LogicalChannel.getLogicalChannelNumber();
|
int lcn = LogicalChannel.getLogicalChannelNumber();
|
||||||
int sid = LogicalChannel.getServiceId();
|
int sid = LogicalChannel.getServiceId();
|
||||||
if (LogicalChannel.getVisibleServiceFlag()) {
|
if (LogicalChannel.getVisibleServiceFlag()) {
|
||||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||||
if (!Channel->GroupSep() && Channel->Sid() == sid && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
if (!Channel->GroupSep() && Channel->Sid() == sid && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
||||||
Channel->SetLcn(lcn);
|
ChannelsModified |= Channel->SetLcn(lcn);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -328,9 +331,9 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
int lcn = HdSimulcastLogicalChannel.getLogicalChannelNumber();
|
int lcn = HdSimulcastLogicalChannel.getLogicalChannelNumber();
|
||||||
int sid = HdSimulcastLogicalChannel.getServiceId();
|
int sid = HdSimulcastLogicalChannel.getServiceId();
|
||||||
if (HdSimulcastLogicalChannel.getVisibleServiceFlag()) {
|
if (HdSimulcastLogicalChannel.getVisibleServiceFlag()) {
|
||||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||||
if (!Channel->GroupSep() && Channel->Sid() == sid && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
if (!Channel->GroupSep() && Channel->Sid() == sid && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
|
||||||
Channel->SetLcn(lcn);
|
ChannelsModified |= Channel->SetLcn(lcn);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -343,5 +346,5 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
delete d;
|
delete d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Channels.Unlock();
|
StateKey.Remove(ChannelsModified);
|
||||||
}
|
}
|
||||||
|
23
pat.c
23
pat.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: pat.c 3.5 2015/01/04 13:14:01 kls Exp $
|
* $Id: pat.c 4.1 2015/08/17 08:46:55 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "pat.h"
|
#include "pat.h"
|
||||||
@ -99,8 +99,8 @@ cCaDescriptors::cCaDescriptors(int Source, int Transponder, int ServiceId, int P
|
|||||||
|
|
||||||
bool cCaDescriptors::operator== (const cCaDescriptors &arg) const
|
bool cCaDescriptors::operator== (const cCaDescriptors &arg) const
|
||||||
{
|
{
|
||||||
cCaDescriptor *ca1 = caDescriptors.First();
|
const cCaDescriptor *ca1 = caDescriptors.First();
|
||||||
cCaDescriptor *ca2 = arg.caDescriptors.First();
|
const cCaDescriptor *ca2 = arg.caDescriptors.First();
|
||||||
while (ca1 && ca2) {
|
while (ca1 && ca2) {
|
||||||
if (!(*ca1 == *ca2))
|
if (!(*ca1 == *ca2))
|
||||||
return false;
|
return false;
|
||||||
@ -396,11 +396,14 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
SwitchToNextPmtPid();
|
SwitchToNextPmtPid();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!Channels.Lock(true, 10))
|
cStateKey StateKey;
|
||||||
|
cChannels *Channels = cChannels::GetChannelsWrite(StateKey, 10);
|
||||||
|
if (!Channels)
|
||||||
return;
|
return;
|
||||||
|
bool ChannelsModified = false;
|
||||||
PmtVersionChanged(Pid, pmt.getTableIdExtension(), pmt.getVersionNumber(), true);
|
PmtVersionChanged(Pid, pmt.getTableIdExtension(), pmt.getVersionNumber(), true);
|
||||||
SwitchToNextPmtPid();
|
SwitchToNextPmtPid();
|
||||||
cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), pmt.getServiceId());
|
cChannel *Channel = Channels->GetByServiceID(Source(), Transponder(), pmt.getServiceId());
|
||||||
if (Channel) {
|
if (Channel) {
|
||||||
SI::CaDescriptor *d;
|
SI::CaDescriptor *d;
|
||||||
cCaDescriptors *CaDescriptors = new cCaDescriptors(Channel->Source(), Channel->Transponder(), Channel->Sid(), Pid);
|
cCaDescriptors *CaDescriptors = new cCaDescriptors(Channel->Source(), Channel->Transponder(), Channel->Sid(), Pid);
|
||||||
@ -629,13 +632,13 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Setup.UpdateChannels >= 2) {
|
if (Setup.UpdateChannels >= 2) {
|
||||||
Channel->SetPids(Vpid, Ppid, Vtype, Apids, Atypes, ALangs, Dpids, Dtypes, DLangs, Spids, SLangs, Tpid);
|
ChannelsModified |= Channel->SetPids(Vpid, Ppid, Vtype, Apids, Atypes, ALangs, Dpids, Dtypes, DLangs, Spids, SLangs, Tpid);
|
||||||
Channel->SetCaIds(CaDescriptors->CaIds());
|
ChannelsModified |= Channel->SetCaIds(CaDescriptors->CaIds());
|
||||||
Channel->SetSubtitlingDescriptors(SubtitlingTypes, CompositionPageIds, AncillaryPageIds);
|
ChannelsModified |= Channel->SetSubtitlingDescriptors(SubtitlingTypes, CompositionPageIds, AncillaryPageIds);
|
||||||
}
|
}
|
||||||
Channel->SetCaDescriptors(CaDescriptorHandler.AddCaDescriptors(CaDescriptors));
|
ChannelsModified |= Channel->SetCaDescriptors(CaDescriptorHandler.AddCaDescriptors(CaDescriptors));
|
||||||
}
|
}
|
||||||
Channels.Unlock();
|
StateKey.Remove(ChannelsModified);
|
||||||
}
|
}
|
||||||
if (timer.TimedOut()) {
|
if (timer.TimedOut()) {
|
||||||
if (pmtIndex >= 0)
|
if (pmtIndex >= 0)
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: recorder.c 3.3 2014/02/21 09:19:52 kls Exp $
|
* $Id: recorder.c 4.1 2015/08/03 10:23:35 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "recorder.h"
|
#include "recorder.h"
|
||||||
@ -135,7 +135,8 @@ void cRecorder::Action(void)
|
|||||||
if (frameDetector->FramesPerSecond() > 0 && DoubleEqual(RecordingInfo.FramesPerSecond(), DEFAULTFRAMESPERSECOND) && !DoubleEqual(RecordingInfo.FramesPerSecond(), frameDetector->FramesPerSecond())) {
|
if (frameDetector->FramesPerSecond() > 0 && DoubleEqual(RecordingInfo.FramesPerSecond(), DEFAULTFRAMESPERSECOND) && !DoubleEqual(RecordingInfo.FramesPerSecond(), frameDetector->FramesPerSecond())) {
|
||||||
RecordingInfo.SetFramesPerSecond(frameDetector->FramesPerSecond());
|
RecordingInfo.SetFramesPerSecond(frameDetector->FramesPerSecond());
|
||||||
RecordingInfo.Write();
|
RecordingInfo.Write();
|
||||||
Recordings.UpdateByName(recordingName);
|
LOCK_RECORDINGS_WRITE;
|
||||||
|
Recordings->UpdateByName(recordingName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InfoWritten = true;
|
InfoWritten = true;
|
||||||
|
457
recording.c
457
recording.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: recording.c 4.2 2015/04/18 13:14:55 kls Exp $
|
* $Id: recording.c 4.3 2015/08/29 14:42:53 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "recording.h"
|
#include "recording.h"
|
||||||
@ -76,9 +76,6 @@ int DirectoryNameMax = NAME_MAX;
|
|||||||
bool DirectoryEncoding = false;
|
bool DirectoryEncoding = false;
|
||||||
int InstanceId = 0;
|
int InstanceId = 0;
|
||||||
|
|
||||||
cRecordings DeletedRecordings(true);
|
|
||||||
static cRecordings VanishedRecordings;
|
|
||||||
|
|
||||||
// --- cRemoveDeletedRecordingsThread ----------------------------------------
|
// --- cRemoveDeletedRecordingsThread ----------------------------------------
|
||||||
|
|
||||||
class cRemoveDeletedRecordingsThread : public cThread {
|
class cRemoveDeletedRecordingsThread : public cThread {
|
||||||
@ -100,8 +97,8 @@ void cRemoveDeletedRecordingsThread::Action(void)
|
|||||||
if (LockFile.Lock()) {
|
if (LockFile.Lock()) {
|
||||||
time_t StartTime = time(NULL);
|
time_t StartTime = time(NULL);
|
||||||
bool deleted = false;
|
bool deleted = false;
|
||||||
cThreadLock DeletedRecordingsLock(&DeletedRecordings);
|
LOCK_DELETEDRECORDINGS_WRITE;
|
||||||
for (cRecording *r = DeletedRecordings.First(); r; ) {
|
for (cRecording *r = DeletedRecordings->First(); r; ) {
|
||||||
if (cIoThrottle::Engaged())
|
if (cIoThrottle::Engaged())
|
||||||
return;
|
return;
|
||||||
if (time(NULL) - StartTime > MAXREMOVETIME)
|
if (time(NULL) - StartTime > MAXREMOVETIME)
|
||||||
@ -109,14 +106,14 @@ void cRemoveDeletedRecordingsThread::Action(void)
|
|||||||
if (cRemote::HasKeys())
|
if (cRemote::HasKeys())
|
||||||
return; // react immediately on user input
|
return; // react immediately on user input
|
||||||
if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) {
|
if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) {
|
||||||
cRecording *next = DeletedRecordings.Next(r);
|
cRecording *next = DeletedRecordings->Next(r);
|
||||||
r->Remove();
|
r->Remove();
|
||||||
DeletedRecordings.Del(r);
|
DeletedRecordings->Del(r);
|
||||||
r = next;
|
r = next;
|
||||||
deleted = true;
|
deleted = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
r = DeletedRecordings.Next(r);
|
r = DeletedRecordings->Next(r);
|
||||||
}
|
}
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
const char *IgnoreFiles[] = { SORTMODEFILE, NULL };
|
const char *IgnoreFiles[] = { SORTMODEFILE, NULL };
|
||||||
@ -134,8 +131,8 @@ void RemoveDeletedRecordings(void)
|
|||||||
static time_t LastRemoveCheck = 0;
|
static time_t LastRemoveCheck = 0;
|
||||||
if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) {
|
if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) {
|
||||||
if (!RemoveDeletedRecordingsThread.Active()) {
|
if (!RemoveDeletedRecordingsThread.Active()) {
|
||||||
cThreadLock DeletedRecordingsLock(&DeletedRecordings);
|
LOCK_DELETEDRECORDINGS_READ;
|
||||||
for (cRecording *r = DeletedRecordings.First(); r; r = DeletedRecordings.Next(r)) {
|
for (const cRecording *r = DeletedRecordings->First(); r; r = DeletedRecordings->Next(r)) {
|
||||||
if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) {
|
if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) {
|
||||||
RemoveDeletedRecordingsThread.Start();
|
RemoveDeletedRecordingsThread.Start();
|
||||||
break;
|
break;
|
||||||
@ -163,37 +160,43 @@ void AssertFreeDiskSpace(int Priority, bool Force)
|
|||||||
return;
|
return;
|
||||||
// Remove the oldest file that has been "deleted":
|
// Remove the oldest file that has been "deleted":
|
||||||
isyslog("low disk space while recording, trying to remove a deleted recording...");
|
isyslog("low disk space while recording, trying to remove a deleted recording...");
|
||||||
cThreadLock DeletedRecordingsLock(&DeletedRecordings);
|
int NumDeletedRecordings = 0;
|
||||||
if (DeletedRecordings.Count()) {
|
{
|
||||||
cRecording *r = DeletedRecordings.First();
|
LOCK_DELETEDRECORDINGS_WRITE;
|
||||||
cRecording *r0 = NULL;
|
NumDeletedRecordings = DeletedRecordings->Count();
|
||||||
while (r) {
|
if (NumDeletedRecordings) {
|
||||||
if (r->IsOnVideoDirectoryFileSystem()) { // only remove recordings that will actually increase the free video disk space
|
cRecording *r = DeletedRecordings->First();
|
||||||
if (!r0 || r->Start() < r0->Start())
|
cRecording *r0 = NULL;
|
||||||
r0 = r;
|
while (r) {
|
||||||
}
|
if (r->IsOnVideoDirectoryFileSystem()) { // only remove recordings that will actually increase the free video disk space
|
||||||
r = DeletedRecordings.Next(r);
|
if (!r0 || r->Start() < r0->Start())
|
||||||
}
|
r0 = r;
|
||||||
if (r0) {
|
}
|
||||||
if (r0->Remove())
|
r = DeletedRecordings->Next(r);
|
||||||
LastFreeDiskCheck += REMOVELATENCY / Factor;
|
}
|
||||||
DeletedRecordings.Del(r0);
|
if (r0) {
|
||||||
return;
|
if (r0->Remove())
|
||||||
}
|
LastFreeDiskCheck += REMOVELATENCY / Factor;
|
||||||
}
|
DeletedRecordings->Del(r0);
|
||||||
else {
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (NumDeletedRecordings == 0) {
|
||||||
// DeletedRecordings was empty, so to be absolutely sure there are no
|
// DeletedRecordings was empty, so to be absolutely sure there are no
|
||||||
// deleted recordings we need to double check:
|
// deleted recordings we need to double check:
|
||||||
DeletedRecordings.Update(true);
|
cRecordings::Update(true);
|
||||||
if (DeletedRecordings.Count())
|
LOCK_DELETEDRECORDINGS_READ;
|
||||||
|
if (DeletedRecordings->Count())
|
||||||
return; // the next call will actually remove it
|
return; // the next call will actually remove it
|
||||||
}
|
}
|
||||||
// No "deleted" files to remove, so let's see if we can delete a recording:
|
// No "deleted" files to remove, so let's see if we can delete a recording:
|
||||||
if (Priority > 0) {
|
if (Priority > 0) {
|
||||||
isyslog("...no deleted recording found, trying to delete an old recording...");
|
isyslog("...no deleted recording found, trying to delete an old recording...");
|
||||||
cThreadLock RecordingsLock(&Recordings);
|
LOCK_RECORDINGS_WRITE;
|
||||||
if (Recordings.Count()) {
|
Recordings->SetExplicitModify();
|
||||||
cRecording *r = Recordings.First();
|
if (Recordings->Count()) {
|
||||||
|
cRecording *r = Recordings->First();
|
||||||
cRecording *r0 = NULL;
|
cRecording *r0 = NULL;
|
||||||
while (r) {
|
while (r) {
|
||||||
if (r->IsOnVideoDirectoryFileSystem()) { // only delete recordings that will actually increase the free video disk space
|
if (r->IsOnVideoDirectoryFileSystem()) { // only delete recordings that will actually increase the free video disk space
|
||||||
@ -209,10 +212,11 @@ void AssertFreeDiskSpace(int Priority, bool Force)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r = Recordings.Next(r);
|
r = Recordings->Next(r);
|
||||||
}
|
}
|
||||||
if (r0 && r0->Delete()) {
|
if (r0 && r0->Delete()) {
|
||||||
Recordings.Del(r0);
|
Recordings->Del(r0);
|
||||||
|
Recordings->SetModified();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,14 +231,6 @@ void AssertFreeDiskSpace(int Priority, bool Force)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Clear vanished recordings ---------------------------------------------
|
|
||||||
|
|
||||||
void ClearVanishedRecordings(void)
|
|
||||||
{
|
|
||||||
cThreadLock RecordingsLock(&Recordings); // yes, it *is* Recordings!
|
|
||||||
VanishedRecordings.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- cResumeFile -----------------------------------------------------------
|
// --- cResumeFile -----------------------------------------------------------
|
||||||
|
|
||||||
cResumeFile::cResumeFile(const char *FileName, bool IsPesRecording)
|
cResumeFile::cResumeFile(const char *FileName, bool IsPesRecording)
|
||||||
@ -309,7 +305,8 @@ bool cResumeFile::Save(int Index)
|
|||||||
if (safe_write(f, &Index, sizeof(Index)) < 0)
|
if (safe_write(f, &Index, sizeof(Index)) < 0)
|
||||||
LOG_ERROR_STR(fileName);
|
LOG_ERROR_STR(fileName);
|
||||||
close(f);
|
close(f);
|
||||||
Recordings.ResetResume(fileName);
|
LOCK_RECORDINGS_WRITE;
|
||||||
|
Recordings->ResetResume(fileName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -318,7 +315,8 @@ bool cResumeFile::Save(int Index)
|
|||||||
if (f) {
|
if (f) {
|
||||||
fprintf(f, "I %d\n", Index);
|
fprintf(f, "I %d\n", Index);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
Recordings.ResetResume(fileName);
|
LOCK_RECORDINGS_WRITE;
|
||||||
|
Recordings->ResetResume(fileName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
LOG_ERROR_STR(fileName);
|
LOG_ERROR_STR(fileName);
|
||||||
@ -331,8 +329,10 @@ bool cResumeFile::Save(int Index)
|
|||||||
void cResumeFile::Delete(void)
|
void cResumeFile::Delete(void)
|
||||||
{
|
{
|
||||||
if (fileName) {
|
if (fileName) {
|
||||||
if (remove(fileName) == 0)
|
if (remove(fileName) == 0) {
|
||||||
Recordings.ResetResume(fileName);
|
LOCK_RECORDINGS_WRITE;
|
||||||
|
Recordings->ResetResume(fileName);
|
||||||
|
}
|
||||||
else if (errno != ENOENT)
|
else if (errno != ENOENT)
|
||||||
LOG_ERROR_STR(fileName);
|
LOG_ERROR_STR(fileName);
|
||||||
}
|
}
|
||||||
@ -787,10 +787,8 @@ cRecording::cRecording(cTimer *Timer, const cEvent *Event)
|
|||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (Timer->IsSingleEvent()) {
|
if (Timer->IsSingleEvent())
|
||||||
Timer->SetFile(name); // this was an instant recording, so let's set the actual data
|
Timer->SetFile(name); // this was an instant recording, so let's set the actual data
|
||||||
Timers.SetModified();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (Timer->IsSingleEvent() || !Setup.UseSubtitle)
|
else if (Timer->IsSingleEvent() || !Setup.UseSubtitle)
|
||||||
name = strdup(Timer->File());
|
name = strdup(Timer->File());
|
||||||
@ -1017,7 +1015,7 @@ int cRecording::Compare(const cListObject &ListObject) const
|
|||||||
return strcasecmp(SortName(), r->SortName());
|
return strcasecmp(SortName(), r->SortName());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cRecording::IsInPath(const char *Path)
|
bool cRecording::IsInPath(const char *Path) const
|
||||||
{
|
{
|
||||||
if (isempty(Path))
|
if (isempty(Path))
|
||||||
return true;
|
return true;
|
||||||
@ -1154,20 +1152,14 @@ bool cRecording::IsOnVideoDirectoryFileSystem(void) const
|
|||||||
return isOnVideoDirectoryFileSystem;
|
return isOnVideoDirectoryFileSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cRecording::HasMarks(void)
|
bool cRecording::HasMarks(void) const
|
||||||
{
|
{
|
||||||
return access(cMarks::MarksFileName(this), F_OK) == 0;
|
return access(cMarks::MarksFileName(this), F_OK) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cRecording::DeleteMarks(void)
|
bool cRecording::DeleteMarks(void)
|
||||||
{
|
{
|
||||||
if (remove(cMarks::MarksFileName(this)) < 0) {
|
return cMarks::DeleteMarksFile(this);
|
||||||
if (errno != ENOENT) {
|
|
||||||
LOG_ERROR_STR(fileName);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void cRecording::ReadInfo(void)
|
void cRecording::ReadInfo(void)
|
||||||
@ -1219,8 +1211,6 @@ bool cRecording::ChangePriorityLifetime(int NewPriority, int NewLifetime)
|
|||||||
if (!WriteInfo())
|
if (!WriteInfo())
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Recordings.ChangeState();
|
|
||||||
Recordings.TouchUpdate();
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1245,8 +1235,6 @@ bool cRecording::ChangeName(const char *NewName)
|
|||||||
}
|
}
|
||||||
isOnVideoDirectoryFileSystem = -1; // it might have been moved to a different file system
|
isOnVideoDirectoryFileSystem = -1; // it might have been moved to a different file system
|
||||||
ClearSortName();
|
ClearSortName();
|
||||||
Recordings.ChangeState();
|
|
||||||
Recordings.TouchUpdate();
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1360,59 +1348,53 @@ int cRecording::FileSizeMB(void) const
|
|||||||
return fileSizeMB;
|
return fileSizeMB;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- cRecordings -----------------------------------------------------------
|
// --- cVideoDirectoryScannerThread ------------------------------------------
|
||||||
|
|
||||||
cRecordings Recordings;
|
class cVideoDirectoryScannerThread : public cThread {
|
||||||
|
private:
|
||||||
|
cRecordings *recordings;
|
||||||
|
cRecordings *deletedRecordings;
|
||||||
|
bool initial;
|
||||||
|
void ScanVideoDir(const char *DirName, int LinkLevel = 0, int DirLevel = 0);
|
||||||
|
protected:
|
||||||
|
virtual void Action(void);
|
||||||
|
public:
|
||||||
|
cVideoDirectoryScannerThread(cRecordings *Recordings, cRecordings *DeletedRecordings);
|
||||||
|
~cVideoDirectoryScannerThread();
|
||||||
|
};
|
||||||
|
|
||||||
char *cRecordings::updateFileName = NULL;
|
cVideoDirectoryScannerThread::cVideoDirectoryScannerThread(cRecordings *Recordings, cRecordings *DeletedRecordings)
|
||||||
|
|
||||||
cRecordings::cRecordings(bool Deleted)
|
|
||||||
:cThread("video directory scanner", true)
|
:cThread("video directory scanner", true)
|
||||||
{
|
{
|
||||||
deleted = Deleted;
|
recordings = Recordings;
|
||||||
|
deletedRecordings = DeletedRecordings;
|
||||||
initial = true;
|
initial = true;
|
||||||
lastUpdate = 0;
|
|
||||||
state = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cRecordings::~cRecordings()
|
cVideoDirectoryScannerThread::~cVideoDirectoryScannerThread()
|
||||||
{
|
{
|
||||||
Cancel(3);
|
Cancel(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cRecordings::Action(void)
|
void cVideoDirectoryScannerThread::Action(void)
|
||||||
{
|
{
|
||||||
Refresh();
|
cStateKey StateKey;
|
||||||
|
recordings->Lock(StateKey);
|
||||||
|
initial = recordings->Count() == 0; // no name checking if the list is initially empty
|
||||||
|
StateKey.Remove();
|
||||||
|
deletedRecordings->Lock(StateKey, true);
|
||||||
|
deletedRecordings->Clear();
|
||||||
|
StateKey.Remove();
|
||||||
|
ScanVideoDir(cVideoDirectory::Name());
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *cRecordings::UpdateFileName(void)
|
void cVideoDirectoryScannerThread::ScanVideoDir(const char *DirName, int LinkLevel, int DirLevel)
|
||||||
{
|
{
|
||||||
if (!updateFileName)
|
|
||||||
updateFileName = strdup(AddDirectory(cVideoDirectory::Name(), ".update"));
|
|
||||||
return updateFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
void cRecordings::Refresh(bool Foreground)
|
|
||||||
{
|
|
||||||
lastUpdate = time(NULL); // doing this first to make sure we don't miss anything
|
|
||||||
initial = Count() == 0; // no name checking if the list is initially empty
|
|
||||||
if (deleted) {
|
|
||||||
Lock();
|
|
||||||
Clear();
|
|
||||||
ChangeState();
|
|
||||||
Unlock();
|
|
||||||
}
|
|
||||||
ScanVideoDir(cVideoDirectory::Name(), Foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLevel, int DirLevel)
|
|
||||||
{
|
|
||||||
bool DoChangeState = false;
|
|
||||||
// Find any new recordings:
|
// Find any new recordings:
|
||||||
cReadDir d(DirName);
|
cReadDir d(DirName);
|
||||||
struct dirent *e;
|
struct dirent *e;
|
||||||
while ((Foreground || Running()) && (e = d.Next()) != NULL) {
|
while (Running() && (e = d.Next()) != NULL) {
|
||||||
if (!Foreground && cIoThrottle::Engaged())
|
if (cIoThrottle::Engaged())
|
||||||
cCondWait::SleepMs(100);
|
cCondWait::SleepMs(100);
|
||||||
cString buffer = AddDirectory(DirName, e->d_name);
|
cString buffer = AddDirectory(DirName, e->d_name);
|
||||||
struct stat st;
|
struct stat st;
|
||||||
@ -1428,57 +1410,73 @@ bool cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLev
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (S_ISDIR(st.st_mode)) {
|
if (S_ISDIR(st.st_mode)) {
|
||||||
if (endswith(buffer, deleted ? DELEXT : RECEXT)) {
|
cRecordings *Recordings = NULL;
|
||||||
if (deleted || initial || !GetByName(buffer)) {
|
if (endswith(buffer, RECEXT))
|
||||||
|
Recordings = recordings;
|
||||||
|
else if (endswith(buffer, DELEXT))
|
||||||
|
Recordings = deletedRecordings;
|
||||||
|
if (Recordings) {
|
||||||
|
cStateKey StateKey;
|
||||||
|
Recordings->Lock(StateKey, true);
|
||||||
|
if (Recordings == deletedRecordings || initial || !Recordings->GetByName(buffer)) {
|
||||||
cRecording *r = new cRecording(buffer);
|
cRecording *r = new cRecording(buffer);
|
||||||
if (r->Name()) {
|
if (r->Name()) {
|
||||||
r->NumFrames(); // initializes the numFrames member
|
r->NumFrames(); // initializes the numFrames member
|
||||||
r->FileSizeMB(); // initializes the fileSizeMB member
|
r->FileSizeMB(); // initializes the fileSizeMB member
|
||||||
r->IsOnVideoDirectoryFileSystem(); // initializes the isOnVideoDirectoryFileSystem member
|
r->IsOnVideoDirectoryFileSystem(); // initializes the isOnVideoDirectoryFileSystem member
|
||||||
if (deleted)
|
if (Recordings == deletedRecordings)
|
||||||
r->deleted = time(NULL);
|
r->SetDeleted();
|
||||||
Lock();
|
Recordings->Add(r);
|
||||||
Add(r);
|
|
||||||
if (initial)
|
|
||||||
ChangeState();
|
|
||||||
else
|
|
||||||
DoChangeState = true;
|
|
||||||
Unlock();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
delete r;
|
delete r;
|
||||||
}
|
}
|
||||||
|
StateKey.Remove();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
DoChangeState |= ScanVideoDir(buffer, Foreground, LinkLevel + Link, DirLevel + 1);
|
ScanVideoDir(buffer, LinkLevel + Link, DirLevel + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Handle any vanished recordings:
|
// Handle any vanished recordings:
|
||||||
if (!deleted && !initial && DirLevel == 0) {
|
if (!initial && DirLevel == 0) {
|
||||||
for (cRecording *recording = First(); recording; ) {
|
cStateKey StateKey;
|
||||||
cRecording *r = recording;
|
recordings->Lock(StateKey, true);
|
||||||
recording = Next(recording);
|
for (cRecording *Recording = recordings->First(); Recording; ) {
|
||||||
if (access(r->FileName(), F_OK) != 0) {
|
cRecording *r = Recording;
|
||||||
Lock();
|
Recording = recordings->Next(Recording);
|
||||||
Del(r, false);
|
if (access(r->FileName(), F_OK) != 0)
|
||||||
VanishedRecordings.Add(r);
|
recordings->Del(r);
|
||||||
DoChangeState = true;
|
|
||||||
Unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
StateKey.Remove();
|
||||||
}
|
}
|
||||||
if (DoChangeState && DirLevel == 0)
|
|
||||||
ChangeState();
|
|
||||||
return DoChangeState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cRecordings::StateChanged(int &State)
|
// --- cRecordings -----------------------------------------------------------
|
||||||
|
|
||||||
|
cRecordings cRecordings::recordings;
|
||||||
|
cRecordings cRecordings::deletedRecordings(true);
|
||||||
|
char *cRecordings::updateFileName = NULL;
|
||||||
|
cVideoDirectoryScannerThread *cRecordings::videoDirectoryScannerThread = NULL;
|
||||||
|
time_t cRecordings::lastUpdate = 0;
|
||||||
|
|
||||||
|
cRecordings::cRecordings(bool Deleted)
|
||||||
|
:cList<cRecording>(Deleted ? "DelRecs" : "Recordings")
|
||||||
{
|
{
|
||||||
int NewState = state;
|
}
|
||||||
bool Result = State != NewState;
|
|
||||||
State = state;
|
cRecordings::~cRecordings()
|
||||||
return Result;
|
{
|
||||||
|
// The first one to be destructed deletes it:
|
||||||
|
delete videoDirectoryScannerThread;
|
||||||
|
videoDirectoryScannerThread = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *cRecordings::UpdateFileName(void)
|
||||||
|
{
|
||||||
|
if (!updateFileName)
|
||||||
|
updateFileName = strdup(AddDirectory(cVideoDirectory::Name(), ".update"));
|
||||||
|
return updateFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cRecordings::TouchUpdate(void)
|
void cRecordings::TouchUpdate(void)
|
||||||
@ -1497,24 +1495,24 @@ bool cRecordings::NeedsUpdate(void)
|
|||||||
return lastUpdate < lastModified;
|
return lastUpdate < lastModified;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cRecordings::Update(bool Wait)
|
void cRecordings::Update(bool Wait)
|
||||||
{
|
{
|
||||||
|
if (!videoDirectoryScannerThread)
|
||||||
|
videoDirectoryScannerThread = new cVideoDirectoryScannerThread(&recordings, &deletedRecordings);
|
||||||
|
lastUpdate = time(NULL); // doing this first to make sure we don't miss anything
|
||||||
|
videoDirectoryScannerThread->Start();
|
||||||
if (Wait) {
|
if (Wait) {
|
||||||
Refresh(true);
|
while (videoDirectoryScannerThread->Active())
|
||||||
return Count() > 0;
|
cCondWait::SleepMs(100);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
Start();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cRecording *cRecordings::GetByName(const char *FileName)
|
const cRecording *cRecordings::GetByName(const char *FileName) const
|
||||||
{
|
{
|
||||||
if (FileName) {
|
if (FileName) {
|
||||||
LOCK_THREAD;
|
for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
|
||||||
for (cRecording *recording = First(); recording; recording = Next(recording)) {
|
if (strcmp(Recording->FileName(), FileName) == 0)
|
||||||
if (strcmp(recording->FileName(), FileName) == 0)
|
return Recording;
|
||||||
return recording;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -1522,12 +1520,8 @@ cRecording *cRecordings::GetByName(const char *FileName)
|
|||||||
|
|
||||||
void cRecordings::AddByName(const char *FileName, bool TriggerUpdate)
|
void cRecordings::AddByName(const char *FileName, bool TriggerUpdate)
|
||||||
{
|
{
|
||||||
LOCK_THREAD;
|
if (!GetByName(FileName)) {
|
||||||
cRecording *recording = GetByName(FileName);
|
Add(new cRecording(FileName));
|
||||||
if (!recording) {
|
|
||||||
recording = new cRecording(FileName);
|
|
||||||
Add(recording);
|
|
||||||
ChangeState();
|
|
||||||
if (TriggerUpdate)
|
if (TriggerUpdate)
|
||||||
TouchUpdate();
|
TouchUpdate();
|
||||||
}
|
}
|
||||||
@ -1535,58 +1529,52 @@ void cRecordings::AddByName(const char *FileName, bool TriggerUpdate)
|
|||||||
|
|
||||||
void cRecordings::DelByName(const char *FileName)
|
void cRecordings::DelByName(const char *FileName)
|
||||||
{
|
{
|
||||||
LOCK_THREAD;
|
cRecording *Recording = GetByName(FileName);
|
||||||
cRecording *recording = GetByName(FileName);
|
|
||||||
cRecording *dummy = NULL;
|
cRecording *dummy = NULL;
|
||||||
if (!recording)
|
if (!Recording)
|
||||||
recording = dummy = new cRecording(FileName); // allows us to use a FileName that is not in the Recordings list
|
Recording = dummy = new cRecording(FileName); // allows us to use a FileName that is not in the Recordings list
|
||||||
cThreadLock DeletedRecordingsLock(&DeletedRecordings);
|
LOCK_DELETEDRECORDINGS_WRITE;
|
||||||
if (!dummy)
|
if (!dummy)
|
||||||
Del(recording, false);
|
Del(Recording, false);
|
||||||
char *ext = strrchr(recording->fileName, '.');
|
char *ext = strrchr(Recording->fileName, '.');
|
||||||
if (ext) {
|
if (ext) {
|
||||||
strncpy(ext, DELEXT, strlen(ext));
|
strncpy(ext, DELEXT, strlen(ext));
|
||||||
if (access(recording->FileName(), F_OK) == 0) {
|
if (access(Recording->FileName(), F_OK) == 0) {
|
||||||
recording->deleted = time(NULL);
|
Recording->SetDeleted();
|
||||||
DeletedRecordings.Add(recording);
|
DeletedRecordings->Add(Recording);
|
||||||
recording = NULL; // to prevent it from being deleted below
|
Recording = NULL; // to prevent it from being deleted below
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete recording;
|
delete Recording;
|
||||||
ChangeState();
|
|
||||||
TouchUpdate();
|
TouchUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void cRecordings::UpdateByName(const char *FileName)
|
void cRecordings::UpdateByName(const char *FileName)
|
||||||
{
|
{
|
||||||
LOCK_THREAD;
|
if (cRecording *Recording = GetByName(FileName))
|
||||||
cRecording *recording = GetByName(FileName);
|
Recording->ReadInfo();
|
||||||
if (recording)
|
|
||||||
recording->ReadInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int cRecordings::TotalFileSizeMB(void)
|
int cRecordings::TotalFileSizeMB(void) const
|
||||||
{
|
{
|
||||||
int size = 0;
|
int size = 0;
|
||||||
LOCK_THREAD;
|
for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
|
||||||
for (cRecording *recording = First(); recording; recording = Next(recording)) {
|
int FileSizeMB = Recording->FileSizeMB();
|
||||||
int FileSizeMB = recording->FileSizeMB();
|
if (FileSizeMB > 0 && Recording->IsOnVideoDirectoryFileSystem())
|
||||||
if (FileSizeMB > 0 && recording->IsOnVideoDirectoryFileSystem())
|
|
||||||
size += FileSizeMB;
|
size += FileSizeMB;
|
||||||
}
|
}
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
double cRecordings::MBperMinute(void)
|
double cRecordings::MBperMinute(void) const
|
||||||
{
|
{
|
||||||
int size = 0;
|
int size = 0;
|
||||||
int length = 0;
|
int length = 0;
|
||||||
LOCK_THREAD;
|
for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
|
||||||
for (cRecording *recording = First(); recording; recording = Next(recording)) {
|
if (Recording->IsOnVideoDirectoryFileSystem()) {
|
||||||
if (recording->IsOnVideoDirectoryFileSystem()) {
|
int FileSizeMB = Recording->FileSizeMB();
|
||||||
int FileSizeMB = recording->FileSizeMB();
|
|
||||||
if (FileSizeMB > 0) {
|
if (FileSizeMB > 0) {
|
||||||
int LengthInSeconds = recording->LengthInSeconds();
|
int LengthInSeconds = Recording->LengthInSeconds();
|
||||||
if (LengthInSeconds > 0) {
|
if (LengthInSeconds > 0) {
|
||||||
if (LengthInSeconds / FileSizeMB < LIMIT_SECS_PER_MB_RADIO) { // don't count radio recordings
|
if (LengthInSeconds / FileSizeMB < LIMIT_SECS_PER_MB_RADIO) { // don't count radio recordings
|
||||||
size += FileSizeMB;
|
size += FileSizeMB;
|
||||||
@ -1599,23 +1587,21 @@ double cRecordings::MBperMinute(void)
|
|||||||
return (size && length) ? double(size) * 60 / length : -1;
|
return (size && length) ? double(size) * 60 / length : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cRecordings::PathIsInUse(const char *Path)
|
int cRecordings::PathIsInUse(const char *Path) const
|
||||||
{
|
{
|
||||||
LOCK_THREAD;
|
|
||||||
int Use = ruNone;
|
int Use = ruNone;
|
||||||
for (cRecording *recording = First(); recording; recording = Next(recording)) {
|
for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
|
||||||
if (recording->IsInPath(Path))
|
if (Recording->IsInPath(Path))
|
||||||
Use |= recording->IsInUse();
|
Use |= Recording->IsInUse();
|
||||||
}
|
}
|
||||||
return Use;
|
return Use;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cRecordings::GetNumRecordingsInPath(const char *Path)
|
int cRecordings::GetNumRecordingsInPath(const char *Path) const
|
||||||
{
|
{
|
||||||
LOCK_THREAD;
|
|
||||||
int n = 0;
|
int n = 0;
|
||||||
for (cRecording *recording = First(); recording; recording = Next(recording)) {
|
for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
|
||||||
if (recording->IsInPath(Path))
|
if (Recording->IsInPath(Path))
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
@ -1624,36 +1610,35 @@ int cRecordings::GetNumRecordingsInPath(const char *Path)
|
|||||||
bool cRecordings::MoveRecordings(const char *OldPath, const char *NewPath)
|
bool cRecordings::MoveRecordings(const char *OldPath, const char *NewPath)
|
||||||
{
|
{
|
||||||
if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
|
if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
|
||||||
LOCK_THREAD;
|
|
||||||
dsyslog("moving '%s' to '%s'", OldPath, NewPath);
|
dsyslog("moving '%s' to '%s'", OldPath, NewPath);
|
||||||
for (cRecording *recording = First(); recording; recording = Next(recording)) {
|
bool Moved = false;
|
||||||
if (recording->IsInPath(OldPath)) {
|
for (cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
|
||||||
const char *p = recording->Name() + strlen(OldPath);
|
if (Recording->IsInPath(OldPath)) {
|
||||||
|
const char *p = Recording->Name() + strlen(OldPath);
|
||||||
cString NewName = cString::sprintf("%s%s", NewPath, p);
|
cString NewName = cString::sprintf("%s%s", NewPath, p);
|
||||||
if (!recording->ChangeName(NewName))
|
if (!Recording->ChangeName(NewName))
|
||||||
return false;
|
return false;
|
||||||
ChangeState();
|
Moved = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Moved)
|
||||||
|
TouchUpdate();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cRecordings::ResetResume(const char *ResumeFileName)
|
void cRecordings::ResetResume(const char *ResumeFileName)
|
||||||
{
|
{
|
||||||
LOCK_THREAD;
|
for (cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
|
||||||
for (cRecording *recording = First(); recording; recording = Next(recording)) {
|
if (!ResumeFileName || strncmp(ResumeFileName, Recording->FileName(), strlen(Recording->FileName())) == 0)
|
||||||
if (!ResumeFileName || strncmp(ResumeFileName, recording->FileName(), strlen(recording->FileName())) == 0)
|
Recording->ResetResume();
|
||||||
recording->ResetResume();
|
|
||||||
}
|
}
|
||||||
ChangeState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void cRecordings::ClearSortNames(void)
|
void cRecordings::ClearSortNames(void)
|
||||||
{
|
{
|
||||||
LOCK_THREAD;
|
for (cRecording *Recording = First(); Recording; Recording = Next(Recording))
|
||||||
for (cRecording *recording = First(); recording; recording = Next(recording))
|
Recording->ClearSortName();
|
||||||
recording->ClearSortName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- cDirCopier ------------------------------------------------------------
|
// --- cDirCopier ------------------------------------------------------------
|
||||||
@ -1812,8 +1797,9 @@ void cDirCopier::Stop(void)
|
|||||||
Cancel(3);
|
Cancel(3);
|
||||||
if (error) {
|
if (error) {
|
||||||
cVideoDirectory::RemoveVideoFile(dirNameDst);
|
cVideoDirectory::RemoveVideoFile(dirNameDst);
|
||||||
Recordings.AddByName(dirNameSrc);
|
LOCK_RECORDINGS_WRITE;
|
||||||
Recordings.DelByName(dirNameDst);
|
Recordings->AddByName(dirNameSrc);
|
||||||
|
Recordings->DelByName(dirNameDst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1893,17 +1879,19 @@ bool cRecordingsHandlerEntry::Active(bool &Error)
|
|||||||
copier->Start();
|
copier->Start();
|
||||||
}
|
}
|
||||||
ClearPending();
|
ClearPending();
|
||||||
Recordings.ChangeState();
|
LOCK_RECORDINGS_WRITE; // to trigger a state change
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Clean up:
|
// Clean up:
|
||||||
if (CopierFinishedOk && (Usage() & ruMove) != 0) {
|
if (CopierFinishedOk && (Usage() & ruMove) != 0) {
|
||||||
cRecording Recording(FileNameSrc());
|
cRecording Recording(FileNameSrc());
|
||||||
if (Recording.Delete())
|
if (Recording.Delete()) {
|
||||||
Recordings.DelByName(Recording.FileName());
|
LOCK_RECORDINGS_WRITE;
|
||||||
|
Recordings->DelByName(Recording.FileName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Recordings.ChangeState();
|
LOCK_RECORDINGS_WRITE; // to trigger a state change
|
||||||
Recordings.TouchUpdate();
|
Recordings->TouchUpdate();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1947,7 +1935,7 @@ bool cRecordingsHandler::Add(int Usage, const char *FileNameSrc, const char *Fil
|
|||||||
operations.Add(new cRecordingsHandlerEntry(Usage, FileNameSrc, FileNameDst));
|
operations.Add(new cRecordingsHandlerEntry(Usage, FileNameSrc, FileNameDst));
|
||||||
finished = false;
|
finished = false;
|
||||||
Active(); // start it right away if possible
|
Active(); // start it right away if possible
|
||||||
Recordings.ChangeState();
|
LOCK_RECORDINGS_WRITE; // to trigger a state change
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -1969,7 +1957,7 @@ void cRecordingsHandler::Del(const char *FileName)
|
|||||||
cMutexLock MutexLock(&mutex);
|
cMutexLock MutexLock(&mutex);
|
||||||
if (cRecordingsHandlerEntry *r = Get(FileName)) {
|
if (cRecordingsHandlerEntry *r = Get(FileName)) {
|
||||||
operations.Del(r);
|
operations.Del(r);
|
||||||
Recordings.ChangeState();
|
LOCK_RECORDINGS_WRITE; // to trigger a state change
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1977,7 +1965,7 @@ void cRecordingsHandler::DelAll(void)
|
|||||||
{
|
{
|
||||||
cMutexLock MutexLock(&mutex);
|
cMutexLock MutexLock(&mutex);
|
||||||
operations.Clear();
|
operations.Clear();
|
||||||
Recordings.ChangeState();
|
LOCK_RECORDINGS_WRITE; // to trigger a state change
|
||||||
}
|
}
|
||||||
|
|
||||||
int cRecordingsHandler::GetUsage(const char *FileName)
|
int cRecordingsHandler::GetUsage(const char *FileName)
|
||||||
@ -2059,9 +2047,19 @@ cString cMarks::MarksFileName(const cRecording *Recording)
|
|||||||
return AddDirectory(Recording->FileName(), Recording->IsPesRecording() ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX);
|
return AddDirectory(Recording->FileName(), Recording->IsPesRecording() ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool cMarks::DeleteMarksFile(const cRecording *Recording)
|
||||||
|
{
|
||||||
|
if (remove(cMarks::MarksFileName(Recording)) < 0) {
|
||||||
|
if (errno != ENOENT) {
|
||||||
|
LOG_ERROR_STR(Recording->FileName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool IsPesRecording)
|
bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool IsPesRecording)
|
||||||
{
|
{
|
||||||
cMutexLock MutexLock(this);
|
|
||||||
recordingFileName = RecordingFileName;
|
recordingFileName = RecordingFileName;
|
||||||
fileName = AddDirectory(RecordingFileName, IsPesRecording ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX);
|
fileName = AddDirectory(RecordingFileName, IsPesRecording ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX);
|
||||||
framesPerSecond = FramesPerSecond;
|
framesPerSecond = FramesPerSecond;
|
||||||
@ -2074,7 +2072,6 @@ bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool Is
|
|||||||
|
|
||||||
bool cMarks::Update(void)
|
bool cMarks::Update(void)
|
||||||
{
|
{
|
||||||
cMutexLock MutexLock(this);
|
|
||||||
time_t t = time(NULL);
|
time_t t = time(NULL);
|
||||||
if (t > nextUpdate && *fileName) {
|
if (t > nextUpdate && *fileName) {
|
||||||
time_t LastModified = LastModifiedTime(fileName);
|
time_t LastModified = LastModifiedTime(fileName);
|
||||||
@ -2106,7 +2103,6 @@ bool cMarks::Update(void)
|
|||||||
|
|
||||||
bool cMarks::Save(void)
|
bool cMarks::Save(void)
|
||||||
{
|
{
|
||||||
cMutexLock MutexLock(this);
|
|
||||||
if (cConfig<cMark>::Save()) {
|
if (cConfig<cMark>::Save()) {
|
||||||
lastFileTime = LastModifiedTime(fileName);
|
lastFileTime = LastModifiedTime(fileName);
|
||||||
return true;
|
return true;
|
||||||
@ -2116,7 +2112,6 @@ bool cMarks::Save(void)
|
|||||||
|
|
||||||
void cMarks::Align(void)
|
void cMarks::Align(void)
|
||||||
{
|
{
|
||||||
cMutexLock MutexLock(this);
|
|
||||||
cIndexFile IndexFile(recordingFileName, false, isPesRecording);
|
cIndexFile IndexFile(recordingFileName, false, isPesRecording);
|
||||||
for (cMark *m = First(); m; m = Next(m)) {
|
for (cMark *m = First(); m; m = Next(m)) {
|
||||||
int p = IndexFile.GetClosestIFrame(m->Position());
|
int p = IndexFile.GetClosestIFrame(m->Position());
|
||||||
@ -2129,7 +2124,6 @@ void cMarks::Align(void)
|
|||||||
|
|
||||||
void cMarks::Sort(void)
|
void cMarks::Sort(void)
|
||||||
{
|
{
|
||||||
cMutexLock MutexLock(this);
|
|
||||||
for (cMark *m1 = First(); m1; m1 = Next(m1)) {
|
for (cMark *m1 = First(); m1; m1 = Next(m1)) {
|
||||||
for (cMark *m2 = Next(m1); m2; m2 = Next(m2)) {
|
for (cMark *m2 = Next(m1); m2; m2 = Next(m2)) {
|
||||||
if (m2->Position() < m1->Position()) {
|
if (m2->Position() < m1->Position()) {
|
||||||
@ -2142,43 +2136,42 @@ void cMarks::Sort(void)
|
|||||||
|
|
||||||
void cMarks::Add(int Position)
|
void cMarks::Add(int Position)
|
||||||
{
|
{
|
||||||
cMutexLock MutexLock(this);
|
|
||||||
cConfig<cMark>::Add(new cMark(Position, NULL, framesPerSecond));
|
cConfig<cMark>::Add(new cMark(Position, NULL, framesPerSecond));
|
||||||
Sort();
|
Sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
cMark *cMarks::Get(int Position)
|
const cMark *cMarks::Get(int Position) const
|
||||||
{
|
{
|
||||||
for (cMark *mi = First(); mi; mi = Next(mi)) {
|
for (const cMark *mi = First(); mi; mi = Next(mi)) {
|
||||||
if (mi->Position() == Position)
|
if (mi->Position() == Position)
|
||||||
return mi;
|
return mi;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
cMark *cMarks::GetPrev(int Position)
|
const cMark *cMarks::GetPrev(int Position) const
|
||||||
{
|
{
|
||||||
for (cMark *mi = Last(); mi; mi = Prev(mi)) {
|
for (const cMark *mi = Last(); mi; mi = Prev(mi)) {
|
||||||
if (mi->Position() < Position)
|
if (mi->Position() < Position)
|
||||||
return mi;
|
return mi;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
cMark *cMarks::GetNext(int Position)
|
const cMark *cMarks::GetNext(int Position) const
|
||||||
{
|
{
|
||||||
for (cMark *mi = First(); mi; mi = Next(mi)) {
|
for (const cMark *mi = First(); mi; mi = Next(mi)) {
|
||||||
if (mi->Position() > Position)
|
if (mi->Position() > Position)
|
||||||
return mi;
|
return mi;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
cMark *cMarks::GetNextBegin(cMark *EndMark)
|
const cMark *cMarks::GetNextBegin(const cMark *EndMark) const
|
||||||
{
|
{
|
||||||
cMark *BeginMark = EndMark ? Next(EndMark) : First();
|
const cMark *BeginMark = EndMark ? Next(EndMark) : First();
|
||||||
if (BeginMark && EndMark && BeginMark->Position() == EndMark->Position()) {
|
if (BeginMark && EndMark && BeginMark->Position() == EndMark->Position()) {
|
||||||
while (cMark *NextMark = Next(BeginMark)) {
|
while (const cMark *NextMark = Next(BeginMark)) {
|
||||||
if (BeginMark->Position() == NextMark->Position()) { // skip Begin/End at the same position
|
if (BeginMark->Position() == NextMark->Position()) { // skip Begin/End at the same position
|
||||||
if (!(BeginMark = Next(NextMark)))
|
if (!(BeginMark = Next(NextMark)))
|
||||||
break;
|
break;
|
||||||
@ -2190,13 +2183,13 @@ cMark *cMarks::GetNextBegin(cMark *EndMark)
|
|||||||
return BeginMark;
|
return BeginMark;
|
||||||
}
|
}
|
||||||
|
|
||||||
cMark *cMarks::GetNextEnd(cMark *BeginMark)
|
const cMark *cMarks::GetNextEnd(const cMark *BeginMark) const
|
||||||
{
|
{
|
||||||
if (!BeginMark)
|
if (!BeginMark)
|
||||||
return NULL;
|
return NULL;
|
||||||
cMark *EndMark = Next(BeginMark);
|
const cMark *EndMark = Next(BeginMark);
|
||||||
if (EndMark && BeginMark && BeginMark->Position() == EndMark->Position()) {
|
if (EndMark && BeginMark && BeginMark->Position() == EndMark->Position()) {
|
||||||
while (cMark *NextMark = Next(EndMark)) {
|
while (const cMark *NextMark = Next(EndMark)) {
|
||||||
if (EndMark->Position() == NextMark->Position()) { // skip End/Begin at the same position
|
if (EndMark->Position() == NextMark->Position()) { // skip End/Begin at the same position
|
||||||
if (!(EndMark = Next(NextMark)))
|
if (!(EndMark = Next(NextMark)))
|
||||||
break;
|
break;
|
||||||
@ -2208,12 +2201,11 @@ cMark *cMarks::GetNextEnd(cMark *BeginMark)
|
|||||||
return EndMark;
|
return EndMark;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cMarks::GetNumSequences(void)
|
int cMarks::GetNumSequences(void) const
|
||||||
{
|
{
|
||||||
cMutexLock MutexLock(this);
|
|
||||||
int NumSequences = 0;
|
int NumSequences = 0;
|
||||||
if (cMark *BeginMark = GetNextBegin()) {
|
if (const cMark *BeginMark = GetNextBegin()) {
|
||||||
while (cMark *EndMark = GetNextEnd(BeginMark)) {
|
while (const cMark *EndMark = GetNextEnd(BeginMark)) {
|
||||||
NumSequences++;
|
NumSequences++;
|
||||||
BeginMark = GetNextBegin(EndMark);
|
BeginMark = GetNextBegin(EndMark);
|
||||||
}
|
}
|
||||||
@ -2403,7 +2395,8 @@ void cIndexFileGenerator::Action(void)
|
|||||||
if (FrameDetector.FramesPerSecond() > 0 && !DoubleEqual(RecordingInfo.FramesPerSecond(), FrameDetector.FramesPerSecond())) {
|
if (FrameDetector.FramesPerSecond() > 0 && !DoubleEqual(RecordingInfo.FramesPerSecond(), FrameDetector.FramesPerSecond())) {
|
||||||
RecordingInfo.SetFramesPerSecond(FrameDetector.FramesPerSecond());
|
RecordingInfo.SetFramesPerSecond(FrameDetector.FramesPerSecond());
|
||||||
RecordingInfo.Write();
|
RecordingInfo.Write();
|
||||||
Recordings.UpdateByName(recordingName);
|
LOCK_RECORDINGS_WRITE;
|
||||||
|
Recordings->UpdateByName(recordingName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Skins.QueueMessage(mtInfo, tr("Index file regeneration complete"));
|
Skins.QueueMessage(mtInfo, tr("Index file regeneration complete"));
|
||||||
|
105
recording.h
105
recording.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: recording.h 4.2 2015/04/28 09:26:02 kls Exp $
|
* $Id: recording.h 4.3 2015/08/29 14:12:14 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __RECORDING_H
|
#ifndef __RECORDING_H
|
||||||
@ -41,7 +41,6 @@ enum eRecordingUsage {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void RemoveDeletedRecordings(void);
|
void RemoveDeletedRecordings(void);
|
||||||
void ClearVanishedRecordings(void);
|
|
||||||
void AssertFreeDiskSpace(int Priority = 0, bool Force = false);
|
void AssertFreeDiskSpace(int Priority = 0, bool Force = false);
|
||||||
///< The special Priority value -1 means that we shall get rid of any
|
///< The special Priority value -1 means that we shall get rid of any
|
||||||
///< deleted recordings faster than normal (because we're cutting).
|
///< deleted recordings faster than normal (because we're cutting).
|
||||||
@ -129,8 +128,9 @@ public:
|
|||||||
int Priority(void) const { return priority; }
|
int Priority(void) const { return priority; }
|
||||||
int Lifetime(void) const { return lifetime; }
|
int Lifetime(void) const { return lifetime; }
|
||||||
time_t Deleted(void) const { return deleted; }
|
time_t Deleted(void) const { return deleted; }
|
||||||
|
void SetDeleted(void) { deleted = time(NULL); }
|
||||||
virtual int Compare(const cListObject &ListObject) const;
|
virtual int Compare(const cListObject &ListObject) const;
|
||||||
bool IsInPath(const char *Path);
|
bool IsInPath(const char *Path) const;
|
||||||
///< Returns true if this recording is stored anywhere under the given Path.
|
///< Returns true if this recording is stored anywhere under the given Path.
|
||||||
///< If Path is NULL or an empty string, the entire video directory is checked.
|
///< If Path is NULL or an empty string, the entire video directory is checked.
|
||||||
cString Folder(void) const;
|
cString Folder(void) const;
|
||||||
@ -140,7 +140,7 @@ public:
|
|||||||
///< Returns the base name of this recording (without the
|
///< Returns the base name of this recording (without the
|
||||||
///< video directory and folder). For use in menus etc.
|
///< video directory and folder). For use in menus etc.
|
||||||
const char *Name(void) const { return name; }
|
const char *Name(void) const { return name; }
|
||||||
///< Returns the full name of the recording (without the video directory.
|
///< Returns the full name of the recording (without the video directory).
|
||||||
///< For use in menus etc.
|
///< For use in menus etc.
|
||||||
const char *FileName(void) const;
|
const char *FileName(void) const;
|
||||||
///< Returns the full path name to the recording directory, including the
|
///< Returns the full path name to the recording directory, including the
|
||||||
@ -166,7 +166,7 @@ public:
|
|||||||
bool IsEdited(void) const;
|
bool IsEdited(void) const;
|
||||||
bool IsPesRecording(void) const { return isPesRecording; }
|
bool IsPesRecording(void) const { return isPesRecording; }
|
||||||
bool IsOnVideoDirectoryFileSystem(void) const;
|
bool IsOnVideoDirectoryFileSystem(void) const;
|
||||||
bool HasMarks(void);
|
bool HasMarks(void) const;
|
||||||
///< Returns true if this recording has any editing marks.
|
///< Returns true if this recording has any editing marks.
|
||||||
bool DeleteMarks(void);
|
bool DeleteMarks(void);
|
||||||
///< Deletes the editing marks from this recording (if any).
|
///< Deletes the editing marks from this recording (if any).
|
||||||
@ -216,49 +216,54 @@ public:
|
|||||||
///< as in time-shift).
|
///< as in time-shift).
|
||||||
};
|
};
|
||||||
|
|
||||||
class cRecordings : public cList<cRecording>, public cThread {
|
class cVideoDirectoryScannerThread;
|
||||||
|
|
||||||
|
class cRecordings : public cList<cRecording> {
|
||||||
private:
|
private:
|
||||||
|
static cRecordings recordings;
|
||||||
|
static cRecordings deletedRecordings;
|
||||||
static char *updateFileName;
|
static char *updateFileName;
|
||||||
bool deleted;
|
static time_t lastUpdate;
|
||||||
bool initial;
|
static cVideoDirectoryScannerThread *videoDirectoryScannerThread;
|
||||||
time_t lastUpdate;
|
static const char *UpdateFileName(void);
|
||||||
int state;
|
|
||||||
const char *UpdateFileName(void);
|
|
||||||
void Refresh(bool Foreground = false);
|
|
||||||
bool ScanVideoDir(const char *DirName, bool Foreground = false, int LinkLevel = 0, int DirLevel = 0);
|
|
||||||
protected:
|
|
||||||
virtual void Action(void);
|
|
||||||
public:
|
public:
|
||||||
cRecordings(bool Deleted = false);
|
cRecordings(bool Deleted = false);
|
||||||
virtual ~cRecordings();
|
virtual ~cRecordings();
|
||||||
bool Load(void) { return Update(true); }
|
static const cRecordings *GetRecordingsRead(cStateKey &StateKey, int TimeoutMs = 0) { return recordings.Lock(StateKey, false, TimeoutMs) ? &recordings : NULL; }
|
||||||
///< Loads the current list of recordings and returns true if there
|
///< Gets the list of recordings for read access.
|
||||||
///< is anything in it (for compatibility with older plugins - use
|
///< See cTimers::GetTimersRead() for details.
|
||||||
///< Update(true) instead).
|
static cRecordings *GetRecordingsWrite(cStateKey &StateKey, int TimeoutMs = 0) { return recordings.Lock(StateKey, true, TimeoutMs) ? &recordings : NULL; }
|
||||||
bool Update(bool Wait = false);
|
///< Gets the list of recordings for write access.
|
||||||
|
///< See cTimers::GetTimersWrite() for details.
|
||||||
|
static const cRecordings *GetDeletedRecordingsRead(cStateKey &StateKey, int TimeoutMs = 0) { return deletedRecordings.Lock(StateKey, false, TimeoutMs) ? &deletedRecordings : NULL; }
|
||||||
|
///< Gets the list of deleted recordings for read access.
|
||||||
|
///< See cTimers::GetTimersRead() for details.
|
||||||
|
static cRecordings *GetDeletedRecordingsWrite(cStateKey &StateKey, int TimeoutMs = 0) { return deletedRecordings.Lock(StateKey, true, TimeoutMs) ? &deletedRecordings : NULL; }
|
||||||
|
///< Gets the list of deleted recordings for write access.
|
||||||
|
///< See cTimers::GetTimersWrite() for details.
|
||||||
|
static void Update(bool Wait = false);
|
||||||
///< Triggers an update of the list of recordings, which will run
|
///< Triggers an update of the list of recordings, which will run
|
||||||
///< as a separate thread if Wait is false. If Wait is true, the
|
///< as a separate thread if Wait is false. If Wait is true, the
|
||||||
///< function returns only after the update has completed.
|
///< function returns only after the update has completed.
|
||||||
///< Returns true if Wait is true and there is anything in the list
|
static void TouchUpdate(void);
|
||||||
///< of recordings, false otherwise.
|
|
||||||
void TouchUpdate(void);
|
|
||||||
///< Touches the '.update' file in the video directory, so that other
|
///< Touches the '.update' file in the video directory, so that other
|
||||||
///< instances of VDR that access the same video directory can be triggered
|
///< instances of VDR that access the same video directory can be triggered
|
||||||
///< to update their recordings list.
|
///< to update their recordings list.
|
||||||
bool NeedsUpdate(void);
|
///< This function is 'const', because it doesn't actually modify the list
|
||||||
void ChangeState(void) { state++; }
|
///< of recordings.
|
||||||
bool StateChanged(int &State);
|
static bool NeedsUpdate(void);
|
||||||
void ResetResume(const char *ResumeFileName = NULL);
|
void ResetResume(const char *ResumeFileName = NULL);
|
||||||
void ClearSortNames(void);
|
void ClearSortNames(void);
|
||||||
cRecording *GetByName(const char *FileName);
|
const cRecording *GetByName(const char *FileName) const;
|
||||||
|
cRecording *GetByName(const char *FileName) { return const_cast<cRecording *>(static_cast<const cRecordings *>(this)->GetByName(FileName)); }
|
||||||
void AddByName(const char *FileName, bool TriggerUpdate = true);
|
void AddByName(const char *FileName, bool TriggerUpdate = true);
|
||||||
void DelByName(const char *FileName);
|
void DelByName(const char *FileName);
|
||||||
void UpdateByName(const char *FileName);
|
void UpdateByName(const char *FileName);
|
||||||
int TotalFileSizeMB(void);
|
int TotalFileSizeMB(void) const;
|
||||||
double MBperMinute(void);
|
double MBperMinute(void) const;
|
||||||
///< Returns the average data rate (in MB/min) of all recordings, or -1 if
|
///< Returns the average data rate (in MB/min) of all recordings, or -1 if
|
||||||
///< this value is unknown.
|
///< this value is unknown.
|
||||||
int PathIsInUse(const char *Path);
|
int PathIsInUse(const char *Path) const;
|
||||||
///< Checks whether any recording in the given Path is currently in use and therefore
|
///< Checks whether any recording in the given Path is currently in use and therefore
|
||||||
///< the whole Path shall not be tampered with. Returns 0 (ruNone) if no recording
|
///< the whole Path shall not be tampered with. Returns 0 (ruNone) if no recording
|
||||||
///< is in use.
|
///< is in use.
|
||||||
@ -266,7 +271,7 @@ public:
|
|||||||
///< If several recordings in the Path are currently in use, the return value will
|
///< If several recordings in the Path are currently in use, the return value will
|
||||||
///< be the combination of all individual recordings' flags.
|
///< be the combination of all individual recordings' flags.
|
||||||
///< If Path is NULL or an empty string, the entire video directory is checked.
|
///< If Path is NULL or an empty string, the entire video directory is checked.
|
||||||
int GetNumRecordingsInPath(const char *Path);
|
int GetNumRecordingsInPath(const char *Path) const;
|
||||||
///< Returns the total number of recordings in the given Path, including all
|
///< Returns the total number of recordings in the given Path, including all
|
||||||
///< sub-folders of Path.
|
///< sub-folders of Path.
|
||||||
///< If Path is NULL or an empty string, the entire video directory is checked.
|
///< If Path is NULL or an empty string, the entire video directory is checked.
|
||||||
@ -281,10 +286,19 @@ public:
|
|||||||
///< if all recordings have been successfully added to the RecordingsHandler.
|
///< if all recordings have been successfully added to the RecordingsHandler.
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Any access to Recordings that loops through the list of recordings
|
// Provide lock controlled access to the list:
|
||||||
/// needs to hold a thread lock on this object!
|
|
||||||
extern cRecordings Recordings;
|
DEF_LIST_LOCK(Recordings);
|
||||||
extern cRecordings DeletedRecordings;
|
DEF_LIST_LOCK2(Recordings, DeletedRecordings);
|
||||||
|
|
||||||
|
// These macros provide a convenient way of locking the global recordings list
|
||||||
|
// and making sure the lock is released as soon as the current scope is left
|
||||||
|
// (note that these macros wait forever to obtain the lock!):
|
||||||
|
|
||||||
|
#define LOCK_RECORDINGS_READ USE_LIST_LOCK_READ(Recordings)
|
||||||
|
#define LOCK_RECORDINGS_WRITE USE_LIST_LOCK_WRITE(Recordings)
|
||||||
|
#define LOCK_DELETEDRECORDINGS_READ USE_LIST_LOCK_READ2(Recordings, DeletedRecordings)
|
||||||
|
#define LOCK_DELETEDRECORDINGS_WRITE USE_LIST_LOCK_WRITE2(Recordings, DeletedRecordings)
|
||||||
|
|
||||||
class cRecordingsHandlerEntry;
|
class cRecordingsHandlerEntry;
|
||||||
|
|
||||||
@ -350,7 +364,7 @@ public:
|
|||||||
bool Save(FILE *f);
|
bool Save(FILE *f);
|
||||||
};
|
};
|
||||||
|
|
||||||
class cMarks : public cConfig<cMark>, public cMutex {
|
class cMarks : public cConfig<cMark> {
|
||||||
private:
|
private:
|
||||||
cString recordingFileName;
|
cString recordingFileName;
|
||||||
cString fileName;
|
cString fileName;
|
||||||
@ -360,9 +374,11 @@ private:
|
|||||||
time_t lastFileTime;
|
time_t lastFileTime;
|
||||||
time_t lastChange;
|
time_t lastChange;
|
||||||
public:
|
public:
|
||||||
|
cMarks(void): cConfig<cMark>("Marks") {};
|
||||||
static cString MarksFileName(const cRecording *Recording);
|
static cString MarksFileName(const cRecording *Recording);
|
||||||
///< Returns the marks file name for the given Recording (regardless whether such
|
///< Returns the marks file name for the given Recording (regardless whether such
|
||||||
///< a file actually exists).
|
///< a file actually exists).
|
||||||
|
static bool DeleteMarksFile(const cRecording *Recording);
|
||||||
bool Load(const char *RecordingFileName, double FramesPerSecond = DEFAULTFRAMESPERSECOND, bool IsPesRecording = false);
|
bool Load(const char *RecordingFileName, double FramesPerSecond = DEFAULTFRAMESPERSECOND, bool IsPesRecording = false);
|
||||||
bool Update(void);
|
bool Update(void);
|
||||||
bool Save(void);
|
bool Save(void);
|
||||||
@ -374,22 +390,27 @@ public:
|
|||||||
///< calls to Del(), or any of the functions that return a "cMark *", in case
|
///< calls to Del(), or any of the functions that return a "cMark *", in case
|
||||||
///< an other thread might modifiy the list while the returned pointer is
|
///< an other thread might modifiy the list while the returned pointer is
|
||||||
///< considered valid.
|
///< considered valid.
|
||||||
cMark *Get(int Position);
|
const cMark *Get(int Position) const;
|
||||||
cMark *GetPrev(int Position);
|
const cMark *GetPrev(int Position) const;
|
||||||
cMark *GetNext(int Position);
|
const cMark *GetNext(int Position) const;
|
||||||
cMark *GetNextBegin(cMark *EndMark = NULL);
|
const cMark *GetNextBegin(const cMark *EndMark = NULL) const;
|
||||||
///< Returns the next "begin" mark after EndMark, skipping any marks at the
|
///< Returns the next "begin" mark after EndMark, skipping any marks at the
|
||||||
///< same position as EndMark. If EndMark is NULL, the first actual "begin"
|
///< same position as EndMark. If EndMark is NULL, the first actual "begin"
|
||||||
///< will be returned (if any).
|
///< will be returned (if any).
|
||||||
cMark *GetNextEnd(cMark *BeginMark);
|
const cMark *GetNextEnd(const cMark *BeginMark) const;
|
||||||
///< Returns the next "end" mark after BeginMark, skipping any marks at the
|
///< Returns the next "end" mark after BeginMark, skipping any marks at the
|
||||||
///< same position as BeginMark.
|
///< same position as BeginMark.
|
||||||
int GetNumSequences(void);
|
int GetNumSequences(void) const;
|
||||||
///< Returns the actual number of sequences to be cut from the recording.
|
///< Returns the actual number of sequences to be cut from the recording.
|
||||||
///< If there is only one actual "begin" mark, and it is positioned at index
|
///< If there is only one actual "begin" mark, and it is positioned at index
|
||||||
///< 0 (the beginning of the recording), and there is no "end" mark, the
|
///< 0 (the beginning of the recording), and there is no "end" mark, the
|
||||||
///< return value is 0, which means that the result is the same as the original
|
///< return value is 0, which means that the result is the same as the original
|
||||||
///< recording.
|
///< recording.
|
||||||
|
cMark *Get(int Position) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->Get(Position)); }
|
||||||
|
cMark *GetPrev(int Position) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetPrev(Position)); }
|
||||||
|
cMark *GetNext(int Position) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetNext(Position)); }
|
||||||
|
cMark *GetNextBegin(const cMark *EndMark = NULL) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetNextBegin(EndMark)); }
|
||||||
|
cMark *GetNextEnd(const cMark *BeginMark) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetNextEnd(BeginMark)); }
|
||||||
};
|
};
|
||||||
|
|
||||||
#define RUC_BEFORERECORDING "before"
|
#define RUC_BEFORERECORDING "before"
|
||||||
|
52
sdt.c
52
sdt.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: sdt.c 4.4 2015/03/17 15:09:54 kls Exp $
|
* $Id: sdt.c 4.5 2015/08/02 11:33:23 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "sdt.h"
|
#include "sdt.h"
|
||||||
@ -52,18 +52,21 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
return;
|
return;
|
||||||
if (!sectionSyncer.Sync(sdt.getVersionNumber(), sdt.getSectionNumber(), sdt.getLastSectionNumber()))
|
if (!sectionSyncer.Sync(sdt.getVersionNumber(), sdt.getSectionNumber(), sdt.getLastSectionNumber()))
|
||||||
return;
|
return;
|
||||||
if (!Channels.Lock(true, 10)) {
|
cStateKey StateKey;
|
||||||
|
cChannels *Channels = cChannels::GetChannelsWrite(StateKey, 10);
|
||||||
|
if (!Channels) {
|
||||||
sectionSyncer.Repeat(); // let's not miss any section of the SDT
|
sectionSyncer.Repeat(); // let's not miss any section of the SDT
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dbgsdt("SDT: %2d %2d %2d %s %d\n", sdt.getVersionNumber(), sdt.getSectionNumber(), sdt.getLastSectionNumber(), *cSource::ToString(source), Transponder());
|
dbgsdt("SDT: %2d %2d %2d %s %d\n", sdt.getVersionNumber(), sdt.getSectionNumber(), sdt.getLastSectionNumber(), *cSource::ToString(source), Transponder());
|
||||||
|
bool ChannelsModified = false;
|
||||||
SI::SDT::Service SiSdtService;
|
SI::SDT::Service SiSdtService;
|
||||||
for (SI::Loop::Iterator it; sdt.serviceLoop.getNext(SiSdtService, it); ) {
|
for (SI::Loop::Iterator it; sdt.serviceLoop.getNext(SiSdtService, it); ) {
|
||||||
cChannel *channel = Channels.GetByChannelID(tChannelID(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId()));
|
cChannel *Channel = Channels->GetByChannelID(tChannelID(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId()));
|
||||||
if (!channel)
|
if (!Channel)
|
||||||
channel = Channels.GetByChannelID(tChannelID(source, 0, Transponder(), SiSdtService.getServiceId()));
|
Channel = Channels->GetByChannelID(tChannelID(source, 0, Transponder(), SiSdtService.getServiceId()));
|
||||||
if (channel)
|
if (Channel)
|
||||||
channel->SetSeen();
|
Channel->SetSeen();
|
||||||
|
|
||||||
cLinkChannels *LinkChannels = NULL;
|
cLinkChannels *LinkChannels = NULL;
|
||||||
SI::Descriptor *d;
|
SI::Descriptor *d;
|
||||||
@ -101,20 +104,21 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
}
|
}
|
||||||
sd->providerName.getText(ProviderNameBuf, sizeof(ProviderNameBuf));
|
sd->providerName.getText(ProviderNameBuf, sizeof(ProviderNameBuf));
|
||||||
char *pp = compactspace(ProviderNameBuf);
|
char *pp = compactspace(ProviderNameBuf);
|
||||||
if (channel) {
|
if (Channel) {
|
||||||
channel->SetId(sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId());
|
ChannelsModified |= Channel->SetId(Channels, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId());
|
||||||
if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3)
|
if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3)
|
||||||
channel->SetName(pn, ps, pp);
|
ChannelsModified |= Channel->SetName(pn, ps, pp);
|
||||||
// Using SiSdtService.getFreeCaMode() is no good, because some
|
// Using SiSdtService.getFreeCaMode() is no good, because some
|
||||||
// tv stations set this flag even for non-encrypted channels :-(
|
// tv stations set this flag even for non-encrypted channels :-(
|
||||||
// The special value 0xFFFF was supposed to mean "unknown encryption"
|
// The special value 0xFFFF was supposed to mean "unknown encryption"
|
||||||
// and would have been overwritten with real CA values later:
|
// and would have been overwritten with real CA values later:
|
||||||
// channel->SetCa(SiSdtService.getFreeCaMode() ? 0xFFFF : 0);
|
// Channel->SetCa(SiSdtService.getFreeCaMode() ? 0xFFFF : 0);
|
||||||
}
|
}
|
||||||
else if (*pn && Setup.UpdateChannels >= 4) {
|
else if (*pn && Setup.UpdateChannels >= 4) {
|
||||||
dbgsdt(" %5d %5d %5d %s/%s %d %s\n", sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId(), *cSource::ToString(Channel()->Source()), *cSource::ToString(source), Channel()->Transponder(), pn);
|
dbgsdt(" %5d %5d %5d %s/%s %d %s\n", sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId(), *cSource::ToString(this->Channel()->Source()), *cSource::ToString(source), this->Channel()->Transponder(), pn);
|
||||||
channel = Channels.NewChannel(Channel(), pn, ps, pp, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId());
|
Channel = Channels->NewChannel(this->Channel(), pn, ps, pp, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId());
|
||||||
channel->SetSource(source); // in case this comes from a satellite with a slightly different position
|
Channel->SetSource(source); // in case this comes from a satellite with a slightly different position
|
||||||
|
ChannelsModified = true;
|
||||||
patFilter->Trigger(SiSdtService.getServiceId());
|
patFilter->Trigger(SiSdtService.getServiceId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,9 +131,9 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
/*
|
/*
|
||||||
case SI::CaIdentifierDescriptorTag: {
|
case SI::CaIdentifierDescriptorTag: {
|
||||||
SI::CaIdentifierDescriptor *cid = (SI::CaIdentifierDescriptor *)d;
|
SI::CaIdentifierDescriptor *cid = (SI::CaIdentifierDescriptor *)d;
|
||||||
if (channel) {
|
if (Channel) {
|
||||||
for (SI::Loop::Iterator it; cid->identifiers.hasNext(it); )
|
for (SI::Loop::Iterator it; cid->identifiers.hasNext(it); )
|
||||||
channel->SetCa(cid->identifiers.getNext(it));
|
Channel->SetCa(Channels, cid->identifiers.getNext(it));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -138,15 +142,17 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
SI::NVODReferenceDescriptor *nrd = (SI::NVODReferenceDescriptor *)d;
|
SI::NVODReferenceDescriptor *nrd = (SI::NVODReferenceDescriptor *)d;
|
||||||
SI::NVODReferenceDescriptor::Service Service;
|
SI::NVODReferenceDescriptor::Service Service;
|
||||||
for (SI::Loop::Iterator it; nrd->serviceLoop.getNext(Service, it); ) {
|
for (SI::Loop::Iterator it; nrd->serviceLoop.getNext(Service, it); ) {
|
||||||
cChannel *link = Channels.GetByChannelID(tChannelID(source, Service.getOriginalNetworkId(), Service.getTransportStream(), Service.getServiceId()));
|
cChannel *link = Channels->GetByChannelID(tChannelID(source, Service.getOriginalNetworkId(), Service.getTransportStream(), Service.getServiceId()));
|
||||||
if (!link && Setup.UpdateChannels >= 4) {
|
if (!link && Setup.UpdateChannels >= 4) {
|
||||||
link = Channels.NewChannel(Channel(), "NVOD", "", "", Service.getOriginalNetworkId(), Service.getTransportStream(), Service.getServiceId());
|
link = Channels->NewChannel(this->Channel(), "NVOD", "", "", Service.getOriginalNetworkId(), Service.getTransportStream(), Service.getServiceId());
|
||||||
patFilter->Trigger(Service.getServiceId());
|
patFilter->Trigger(Service.getServiceId());
|
||||||
|
ChannelsModified = true;
|
||||||
}
|
}
|
||||||
if (link) {
|
if (link) {
|
||||||
if (!LinkChannels)
|
if (!LinkChannels)
|
||||||
LinkChannels = new cLinkChannels;
|
LinkChannels = new cLinkChannels;
|
||||||
LinkChannels->Add(new cLinkChannel(link));
|
LinkChannels->Add(new cLinkChannel(link));
|
||||||
|
ChannelsModified = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,18 +162,18 @@ void cSdtFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
|
|||||||
delete d;
|
delete d;
|
||||||
}
|
}
|
||||||
if (LinkChannels) {
|
if (LinkChannels) {
|
||||||
if (channel)
|
if (Channel)
|
||||||
channel->SetLinkChannels(LinkChannels);
|
ChannelsModified |= Channel->SetLinkChannels(LinkChannels);
|
||||||
else
|
else
|
||||||
delete LinkChannels;
|
delete LinkChannels;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sdt.getSectionNumber() == sdt.getLastSectionNumber()) {
|
if (sdt.getSectionNumber() == sdt.getLastSectionNumber()) {
|
||||||
if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3) {
|
if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3) {
|
||||||
Channels.MarkObsoleteChannels(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId());
|
ChannelsModified |= Channels->MarkObsoleteChannels(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId());
|
||||||
if (source != Source())
|
if (source != Source())
|
||||||
Channels.MarkObsoleteChannels(Source(), sdt.getOriginalNetworkId(), sdt.getTransportStreamId());
|
ChannelsModified |= Channels->MarkObsoleteChannels(Source(), sdt.getOriginalNetworkId(), sdt.getTransportStreamId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Channels.Unlock();
|
StateKey.Remove(ChannelsModified);
|
||||||
}
|
}
|
||||||
|
29
shutdown.c
29
shutdown.c
@ -6,7 +6,7 @@
|
|||||||
*
|
*
|
||||||
* Original version written by Udo Richter <udo_richter@gmx.de>.
|
* Original version written by Udo Richter <udo_richter@gmx.de>.
|
||||||
*
|
*
|
||||||
* $Id: shutdown.c 3.1 2013/10/02 09:02:01 kls Exp $
|
* $Id: shutdown.c 4.1 2015/07/18 11:29:26 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "shutdown.h"
|
#include "shutdown.h"
|
||||||
@ -172,9 +172,10 @@ bool cShutdownHandler::ConfirmShutdown(bool Interactive)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
cTimer *timer = Timers.GetNextActiveTimer();
|
LOCK_TIMERS_READ;
|
||||||
time_t Next = timer ? timer->StartTime() : 0;
|
const cTimer *Timer = Timers->GetNextActiveTimer();
|
||||||
time_t Delta = timer ? Next - time(NULL) : 0;
|
time_t Next = Timer ? Timer->StartTime() : 0;
|
||||||
|
time_t Delta = Timer ? Next - time(NULL) : 0;
|
||||||
|
|
||||||
if (cRecordControls::Active() || (Next && Delta <= 0)) {
|
if (cRecordControls::Active() || (Next && Delta <= 0)) {
|
||||||
// VPS recordings in timer end margin may cause Delta <= 0
|
// VPS recordings in timer end margin may cause Delta <= 0
|
||||||
@ -215,9 +216,10 @@ bool cShutdownHandler::ConfirmRestart(bool Interactive)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
cTimer *timer = Timers.GetNextActiveTimer();
|
LOCK_TIMERS_READ;
|
||||||
time_t Next = timer ? timer->StartTime() : 0;
|
const cTimer *Timer = Timers->GetNextActiveTimer();
|
||||||
time_t Delta = timer ? Next - time(NULL) : 0;
|
time_t Next = Timer ? Timer->StartTime() : 0;
|
||||||
|
time_t Delta = Timer ? Next - time(NULL) : 0;
|
||||||
|
|
||||||
if (cRecordControls::Active() || (Next && Delta <= 0)) {
|
if (cRecordControls::Active() || (Next && Delta <= 0)) {
|
||||||
// VPS recordings in timer end margin may cause Delta <= 0
|
// VPS recordings in timer end margin may cause Delta <= 0
|
||||||
@ -233,15 +235,16 @@ bool cShutdownHandler::ConfirmRestart(bool Interactive)
|
|||||||
|
|
||||||
bool cShutdownHandler::DoShutdown(bool Force)
|
bool cShutdownHandler::DoShutdown(bool Force)
|
||||||
{
|
{
|
||||||
|
LOCK_TIMERS_READ;
|
||||||
time_t Now = time(NULL);
|
time_t Now = time(NULL);
|
||||||
cTimer *timer = Timers.GetNextActiveTimer();
|
const cTimer *Timer = Timers->GetNextActiveTimer();
|
||||||
cPlugin *Plugin = cPluginManager::GetNextWakeupPlugin();
|
cPlugin *Plugin = cPluginManager::GetNextWakeupPlugin();
|
||||||
|
|
||||||
time_t Next = timer ? timer->StartTime() : 0;
|
time_t Next = Timer ? Timer->StartTime() : 0;
|
||||||
time_t NextPlugin = Plugin ? Plugin->WakeupTime() : 0;
|
time_t NextPlugin = Plugin ? Plugin->WakeupTime() : 0;
|
||||||
if (NextPlugin && (!Next || Next > NextPlugin)) {
|
if (NextPlugin && (!Next || Next > NextPlugin)) {
|
||||||
Next = NextPlugin;
|
Next = NextPlugin;
|
||||||
timer = NULL;
|
Timer = NULL;
|
||||||
}
|
}
|
||||||
time_t Delta = Next ? Next - Now : 0;
|
time_t Delta = Next ? Next - Now : 0;
|
||||||
|
|
||||||
@ -250,13 +253,13 @@ bool cShutdownHandler::DoShutdown(bool Force)
|
|||||||
return false;
|
return false;
|
||||||
Delta = Setup.MinEventTimeout * 60;
|
Delta = Setup.MinEventTimeout * 60;
|
||||||
Next = Now + Delta;
|
Next = Now + Delta;
|
||||||
timer = NULL;
|
Timer = NULL;
|
||||||
dsyslog("reboot at %s", *TimeToString(Next));
|
dsyslog("reboot at %s", *TimeToString(Next));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Next && timer) {
|
if (Next && Timer) {
|
||||||
dsyslog("next timer event at %s", *TimeToString(Next));
|
dsyslog("next timer event at %s", *TimeToString(Next));
|
||||||
CallShutdownCommand(Next, timer->Channel()->Number(), timer->File(), Force);
|
CallShutdownCommand(Next, Timer->Channel()->Number(), Timer->File(), Force);
|
||||||
}
|
}
|
||||||
else if (Next && Plugin) {
|
else if (Next && Plugin) {
|
||||||
CallShutdownCommand(Next, 0, Plugin->Name(), Force);
|
CallShutdownCommand(Next, 0, Plugin->Name(), Force);
|
||||||
|
67
skinlcars.c
67
skinlcars.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: skinlcars.c 3.8 2014/06/12 08:48:15 kls Exp $
|
* $Id: skinlcars.c 4.1 2015/09/01 10:07:07 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// "Star Trek: The Next Generation"(R) is a registered trademark of Paramount Pictures,
|
// "Star Trek: The Next Generation"(R) is a registered trademark of Paramount Pictures,
|
||||||
@ -710,7 +710,7 @@ private:
|
|||||||
int lastDiskUsageState;
|
int lastDiskUsageState;
|
||||||
bool lastDiskAlert;
|
bool lastDiskAlert;
|
||||||
double lastSystemLoad;
|
double lastSystemLoad;
|
||||||
int lastTimersState;
|
cStateKey timersStateKey;
|
||||||
time_t lastSignalDisplay;
|
time_t lastSignalDisplay;
|
||||||
int lastLiveIndicatorY;
|
int lastLiveIndicatorY;
|
||||||
bool lastLiveIndicatorTransferring;
|
bool lastLiveIndicatorTransferring;
|
||||||
@ -775,7 +775,6 @@ cSkinLCARSDisplayMenu::cSkinLCARSDisplayMenu(void)
|
|||||||
lastEvent = NULL;
|
lastEvent = NULL;
|
||||||
lastRecording = NULL;
|
lastRecording = NULL;
|
||||||
lastSeen = -1;
|
lastSeen = -1;
|
||||||
lastTimersState = -1;
|
|
||||||
lastSignalDisplay = 0;
|
lastSignalDisplay = 0;
|
||||||
lastLiveIndicatorY = -1;
|
lastLiveIndicatorY = -1;
|
||||||
lastLiveIndicatorTransferring = false;
|
lastLiveIndicatorTransferring = false;
|
||||||
@ -970,7 +969,7 @@ void cSkinLCARSDisplayMenu::SetMenuCategory(eMenuCategory MenuCategory)
|
|||||||
xi01 = xm03;
|
xi01 = xm03;
|
||||||
xi02 = xm04;
|
xi02 = xm04;
|
||||||
xi03 = xm05;
|
xi03 = xm05;
|
||||||
lastTimersState = -1;
|
timersStateKey.Reset();
|
||||||
DrawMainFrameLower();
|
DrawMainFrameLower();
|
||||||
DrawMainBracket();
|
DrawMainBracket();
|
||||||
DrawStatusElbows();
|
DrawStatusElbows();
|
||||||
@ -1237,19 +1236,22 @@ void cSkinLCARSDisplayMenu::DrawTimer(const cTimer *Timer, int y, bool MultiRec)
|
|||||||
}
|
}
|
||||||
if (Event)
|
if (Event)
|
||||||
osd->DrawText(xs00 + d, y + lineHeight - tinyFont->Height(), Event->Title(), ColorFg, ColorBg, tinyFont, xs03 - xs00 - 2 * d);
|
osd->DrawText(xs00 + d, y + lineHeight - tinyFont->Height(), Event->Title(), ColorFg, ColorBg, tinyFont, xs03 - xs00 - 2 * d);
|
||||||
|
// The remote timer indicator:
|
||||||
|
if (Timer->Remote())
|
||||||
|
osd->DrawRectangle(xs00 - (lineHeight - Gap) / 2, y, xs00 - Gap - 1, y + lineHeight - 1, Timer->Recording() ? Theme.Color(clrMenuTimerRecording) : ColorBg);
|
||||||
// The timer recording indicator:
|
// The timer recording indicator:
|
||||||
if (Timer->Recording())
|
else if (Timer->Recording())
|
||||||
osd->DrawRectangle(xs03 + Gap, y - (MultiRec ? Gap : 0), xs04 - Gap / 2 - 1, y + lineHeight - 1, Theme.Color(clrMenuTimerRecording));
|
osd->DrawRectangle(xs03 + Gap, y - (MultiRec ? Gap : 0), xs04 - Gap / 2 - 1, y + lineHeight - 1, Theme.Color(clrMenuTimerRecording));
|
||||||
}
|
}
|
||||||
|
|
||||||
void cSkinLCARSDisplayMenu::DrawTimers(void)
|
void cSkinLCARSDisplayMenu::DrawTimers(void)
|
||||||
{
|
{
|
||||||
if (Timers.Modified(lastTimersState)) {
|
if (const cTimers *Timers = cTimers::GetTimersRead(timersStateKey)) {
|
||||||
deviceRecording.Clear();
|
deviceRecording.Clear();
|
||||||
const cFont *font = cFont::GetFont(fontOsd);
|
const cFont *font = cFont::GetFont(fontOsd);
|
||||||
osd->DrawRectangle(xs00, ys04, xs04 - 1, ys05 - 1, Theme.Color(clrBackground));
|
osd->DrawRectangle(xs00 - (lineHeight - Gap) / 2, ys04, xs04 - 1, ys05 - 1, Theme.Color(clrBackground));
|
||||||
osd->DrawRectangle(xs07, ys04, xs13 - 1, ys05 - 1, Theme.Color(clrBackground));
|
osd->DrawRectangle(xs07, ys04, xs13 - 1, ys05 - 1, Theme.Color(clrBackground));
|
||||||
cSortedTimers SortedTimers;
|
cSortedTimers SortedTimers(Timers);
|
||||||
cVector<int> FreeDeviceSlots;
|
cVector<int> FreeDeviceSlots;
|
||||||
int NumDevices = 0;
|
int NumDevices = 0;
|
||||||
int y = ys04;
|
int y = ys04;
|
||||||
@ -1262,7 +1264,14 @@ void cSkinLCARSDisplayMenu::DrawTimers(void)
|
|||||||
break;
|
break;
|
||||||
if (const cTimer *Timer = SortedTimers[i]) {
|
if (const cTimer *Timer = SortedTimers[i]) {
|
||||||
if (Timer->Recording()) {
|
if (Timer->Recording()) {
|
||||||
if (cRecordControl *RecordControl = cRecordControls::GetRecordControl(Timer)) {
|
if (Timer->Remote()) {
|
||||||
|
if (!Device && Timer->HasFlags(tfActive)) {
|
||||||
|
DrawTimer(Timer, y, false);
|
||||||
|
FreeDeviceSlots.Append(y);
|
||||||
|
y += lineHeight + Gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (cRecordControl *RecordControl = cRecordControls::GetRecordControl(Timer)) {
|
||||||
if (!Device || Device == RecordControl->Device()) {
|
if (!Device || Device == RecordControl->Device()) {
|
||||||
DrawTimer(Timer, y, NumTimers > 0);
|
DrawTimer(Timer, y, NumTimers > 0);
|
||||||
NumTimers++;
|
NumTimers++;
|
||||||
@ -1313,7 +1322,7 @@ void cSkinLCARSDisplayMenu::DrawTimers(void)
|
|||||||
}
|
}
|
||||||
// Total number of active timers:
|
// Total number of active timers:
|
||||||
int NumTimers = 0;
|
int NumTimers = 0;
|
||||||
for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
|
for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
|
||||||
if (Timer->HasFlags(tfActive))
|
if (Timer->HasFlags(tfActive))
|
||||||
NumTimers++;
|
NumTimers++;
|
||||||
}
|
}
|
||||||
@ -1321,6 +1330,7 @@ void cSkinLCARSDisplayMenu::DrawTimers(void)
|
|||||||
osd->DrawText(xs08, ys00, itoa(NumDevices), Theme.Color(clrMenuFrameFg), frameColor, font, xs09 - xs08, ys01 - ys00, taBottom | taRight | taBorder);
|
osd->DrawText(xs08, ys00, itoa(NumDevices), Theme.Color(clrMenuFrameFg), frameColor, font, xs09 - xs08, ys01 - ys00, taBottom | taRight | taBorder);
|
||||||
lastSignalDisplay = 0;
|
lastSignalDisplay = 0;
|
||||||
initial = true; // forces redrawing of devices
|
initial = true; // forces redrawing of devices
|
||||||
|
timersStateKey.Remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1423,25 +1433,23 @@ void cSkinLCARSDisplayMenu::DrawLive(const cChannel *Channel)
|
|||||||
DrawSeen(0, 0);
|
DrawSeen(0, 0);
|
||||||
}
|
}
|
||||||
// The current programme:
|
// The current programme:
|
||||||
cSchedulesLock SchedulesLock;
|
LOCK_SCHEDULES_READ;
|
||||||
if (const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock)) {
|
if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) {
|
||||||
if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) {
|
const cEvent *Event = Schedule->GetPresentEvent();
|
||||||
const cEvent *Event = Schedule->GetPresentEvent();
|
if (initial || Event != lastEvent) {
|
||||||
if (initial || Event != lastEvent) {
|
DrawInfo(Event, true);
|
||||||
DrawInfo(Event, true);
|
lastEvent = Event;
|
||||||
lastEvent = Event;
|
lastSeen = -1;
|
||||||
lastSeen = -1;
|
|
||||||
}
|
|
||||||
int Current = 0;
|
|
||||||
int Total = 0;
|
|
||||||
if (Event) {
|
|
||||||
time_t t = time(NULL);
|
|
||||||
if (t > Event->StartTime())
|
|
||||||
Current = t - Event->StartTime();
|
|
||||||
Total = Event->Duration();
|
|
||||||
}
|
|
||||||
DrawSeen(Current, Total);
|
|
||||||
}
|
}
|
||||||
|
int Current = 0;
|
||||||
|
int Total = 0;
|
||||||
|
if (Event) {
|
||||||
|
time_t t = time(NULL);
|
||||||
|
if (t > Event->StartTime())
|
||||||
|
Current = t - Event->StartTime();
|
||||||
|
Total = Event->Duration();
|
||||||
|
}
|
||||||
|
DrawSeen(Current, Total);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1731,7 +1739,8 @@ void cSkinLCARSDisplayMenu::Flush(void)
|
|||||||
if (MenuCategory() == mcMain) {
|
if (MenuCategory() == mcMain) {
|
||||||
cDevice *Device = cDevice::PrimaryDevice();
|
cDevice *Device = cDevice::PrimaryDevice();
|
||||||
if (!Device->Replaying() || Device->Transferring()) {
|
if (!Device->Replaying() || Device->Transferring()) {
|
||||||
const cChannel *Channel = Channels.GetByNumber(cDevice::PrimaryDevice()->CurrentChannel());
|
LOCK_CHANNELS_READ;
|
||||||
|
const cChannel *Channel = Channels->GetByNumber(cDevice::PrimaryDevice()->CurrentChannel());
|
||||||
DrawLive(Channel);
|
DrawLive(Channel);
|
||||||
}
|
}
|
||||||
else if (cControl *Control = cControl::Control(true))
|
else if (cControl *Control = cControl::Control(true))
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: sourceparams.c 3.1 2014/03/09 12:03:09 kls Exp $
|
* $Id: sourceparams.c 4.1 2015/08/02 11:56:39 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "sourceparams.h"
|
#include "sourceparams.h"
|
||||||
@ -33,7 +33,7 @@ cSourceParam::cSourceParam(char Source, const char *Description)
|
|||||||
|
|
||||||
cSourceParams SourceParams;
|
cSourceParams SourceParams;
|
||||||
|
|
||||||
cSourceParam *cSourceParams::Get(char Source) const
|
cSourceParam *cSourceParams::Get(char Source)
|
||||||
{
|
{
|
||||||
for (cSourceParam *sp = First(); sp; sp = Next(sp)) {
|
for (cSourceParam *sp = First(); sp; sp = Next(sp)) {
|
||||||
if (sp->Source() == Source)
|
if (sp->Source() == Source)
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: sourceparams.h 1.1 2010/02/28 11:58:03 kls Exp $
|
* $Id: sourceparams.h 4.1 2015/08/02 11:56:25 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __SOURCEPARAMS_H
|
#ifndef __SOURCEPARAMS_H
|
||||||
@ -45,7 +45,7 @@ public:
|
|||||||
|
|
||||||
class cSourceParams : public cList<cSourceParam> {
|
class cSourceParams : public cList<cSourceParam> {
|
||||||
public:
|
public:
|
||||||
cSourceParam *Get(char Source) const;
|
cSourceParam *Get(char Source);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern cSourceParams SourceParams;
|
extern cSourceParams SourceParams;
|
||||||
|
9
status.h
9
status.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: status.h 3.1 2014/01/25 10:47:39 kls Exp $
|
* $Id: status.h 4.1 2015/08/02 10:34:44 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __STATUS_H
|
#ifndef __STATUS_H
|
||||||
@ -15,7 +15,7 @@
|
|||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
|
|
||||||
enum eTimerChange { tcMod, tcAdd, tcDel };
|
enum eTimerChange { tcMod, tcAdd, tcDel }; // tcMod is obsolete and no longer used!
|
||||||
|
|
||||||
class cTimer;
|
class cTimer;
|
||||||
|
|
||||||
@ -29,10 +29,7 @@ protected:
|
|||||||
// require a retune.
|
// require a retune.
|
||||||
virtual void TimerChange(const cTimer *Timer, eTimerChange Change) {}
|
virtual void TimerChange(const cTimer *Timer, eTimerChange Change) {}
|
||||||
// Indicates a change in the timer settings.
|
// Indicates a change in the timer settings.
|
||||||
// If Change is tcAdd or tcDel, Timer points to the timer that has
|
// Timer points to the timer that has been added or will be deleted, respectively.
|
||||||
// been added or will be deleted, respectively. In case of tcMod,
|
|
||||||
// Timer is NULL; this indicates that some timer has been changed.
|
|
||||||
// Note that tcAdd and tcDel are always also followed by a tcMod.
|
|
||||||
virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber, bool LiveView) {}
|
virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber, bool LiveView) {}
|
||||||
// Indicates a channel switch on the given DVB device.
|
// Indicates a channel switch on the given DVB device.
|
||||||
// If ChannelNumber is 0, this is before the channel is being switched,
|
// If ChannelNumber is 0, this is before the channel is being switched,
|
||||||
|
21
svdrp.h
21
svdrp.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: svdrp.h 4.2 2015/05/22 13:44:43 kls Exp $
|
* $Id: svdrp.h 4.3 2015/09/01 10:34:09 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __SVDRP_H
|
#ifndef __SVDRP_H
|
||||||
@ -40,20 +40,35 @@ public:
|
|||||||
///< to the command. The response strings are exactly as received,
|
///< to the command. The response strings are exactly as received,
|
||||||
///< with the leading three digit reply code and possible continuation
|
///< with the leading three digit reply code and possible continuation
|
||||||
///< line indicator ('-') in place.
|
///< line indicator ('-') in place.
|
||||||
const char *Response(int Index) { return (Index > 0 && Index < response.Size()) ? response[Index] : NULL; }
|
const char *Response(int Index) { return (Index >= 0 && Index < response.Size()) ? response[Index] : NULL; }
|
||||||
///< This is a convenience function for accessing the response strings.
|
///< This is a convenience function for accessing the response strings.
|
||||||
///< Returns the string at the given Index, or NULL if Index is out
|
///< Returns the string at the given Index, or NULL if Index is out
|
||||||
///< of range.
|
///< of range.
|
||||||
|
int Code(const char *s) { return s ? atoi(s) : 0; }
|
||||||
|
///< Returns the value of the three digit reply code of the given
|
||||||
|
///< response string.
|
||||||
|
const char *Value(const char *s) { return s && s[0] && s[1] && s[2] && s[3] ? s + 4 : NULL; }
|
||||||
|
///< Returns the actual value of the given response string, skipping
|
||||||
|
///< the three digit reply code and possible continuation line indicator.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum eSvdrpFetchFlags {
|
||||||
|
sffNone = 0b0000,
|
||||||
|
sffTimers = 0b0001,
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *SVDRPHostName(void);
|
||||||
void SetSVDRPGrabImageDir(const char *GrabImageDir);
|
void SetSVDRPGrabImageDir(const char *GrabImageDir);
|
||||||
void StartSVDRPHandler(int TcpPort, int UdpPort);
|
void StartSVDRPHandler(int TcpPort, int UdpPort);
|
||||||
void StopSVDRPHandler(void);
|
void StopSVDRPHandler(void);
|
||||||
void SendSVDRPDiscover(const char *Address = NULL);
|
void SendSVDRPDiscover(const char *Address = NULL);
|
||||||
bool GetSVDRPServerNames(cStringList *ServerNames);
|
bool GetSVDRPServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlag = sffNone);
|
||||||
///< Gets a list of all available VDRs this VDR is connected to via SVDRP,
|
///< Gets a list of all available VDRs this VDR is connected to via SVDRP,
|
||||||
///< and stores it in the given ServerNames list. The list is cleared
|
///< and stores it in the given ServerNames list. The list is cleared
|
||||||
///< before getting the server names.
|
///< before getting the server names.
|
||||||
|
///< If FetchFlag is given, only the server names for which the local
|
||||||
|
///< client has this flag set will be returned, and the client's flag
|
||||||
|
///< will be cleared.
|
||||||
///< Returns true if the resulting list is not empty.
|
///< Returns true if the resulting list is not empty.
|
||||||
|
|
||||||
#endif //__SVDRP_H
|
#endif //__SVDRP_H
|
||||||
|
138
thread.c
138
thread.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: thread.c 3.2 2013/12/29 17:21:53 kls Exp $
|
* $Id: thread.c 4.1 2015/08/29 14:43:03 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
@ -21,6 +21,16 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
|
|
||||||
|
#define ABORT { dsyslog("ABORT!"); abort(); } // use debugger to trace back the problem
|
||||||
|
|
||||||
|
//#define DEBUG_LOCKING // uncomment this line to activate debug output for locking
|
||||||
|
|
||||||
|
#ifdef DEBUG_LOCKING
|
||||||
|
#define dbglocking(a...) fprintf(stderr, a)
|
||||||
|
#else
|
||||||
|
#define dbglocking(a...)
|
||||||
|
#endif
|
||||||
|
|
||||||
static bool GetAbsTime(struct timespec *Abstime, int MillisecondsFromNow)
|
static bool GetAbsTime(struct timespec *Abstime, int MillisecondsFromNow)
|
||||||
{
|
{
|
||||||
struct timeval now;
|
struct timeval now;
|
||||||
@ -403,6 +413,132 @@ bool cThreadLock::Lock(cThread *Thread)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- cStateLock ------------------------------------------------------------
|
||||||
|
|
||||||
|
cStateLock::cStateLock(const char *Name)
|
||||||
|
:rwLock(true)
|
||||||
|
{
|
||||||
|
name = Name;
|
||||||
|
threadId = 0;
|
||||||
|
state = 0;
|
||||||
|
explicitModify = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cStateLock::Lock(cStateKey &StateKey, bool Write, int TimeoutMs)
|
||||||
|
{
|
||||||
|
dbglocking("%5d %-10s %10p lock state = %d/%d write = %d timeout = %d\n", cThread::ThreadId(), name, &StateKey, state, StateKey.state, Write, TimeoutMs);
|
||||||
|
StateKey.timedOut = false;
|
||||||
|
if (StateKey.stateLock) {
|
||||||
|
esyslog("ERROR: StateKey already in use in call to cStateLock::Lock() (tid=%d, lock=%s)", StateKey.stateLock->threadId, name);
|
||||||
|
ABORT;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (rwLock.Lock(Write, TimeoutMs)) {
|
||||||
|
StateKey.stateLock = this;
|
||||||
|
if (Write) {
|
||||||
|
dbglocking("%5d %-10s %10p locked write\n", cThread::ThreadId(), name, &StateKey);
|
||||||
|
threadId = cThread::ThreadId();
|
||||||
|
StateKey.write = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (state != StateKey.state) {
|
||||||
|
dbglocking("%5d %-10s %10p locked read\n", cThread::ThreadId(), name, &StateKey);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dbglocking("%5d %-10s %10p state unchanged\n", cThread::ThreadId(), name, &StateKey);
|
||||||
|
StateKey.stateLock = NULL;
|
||||||
|
rwLock.Unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (TimeoutMs) {
|
||||||
|
dbglocking("%5d %-10s %10p timeout\n", cThread::ThreadId(), name, &StateKey);
|
||||||
|
StateKey.timedOut = true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cStateLock::Unlock(cStateKey &StateKey, bool IncState)
|
||||||
|
{
|
||||||
|
dbglocking("%5d %-10s %10p unlock state = %d/%d inc = %d\n", cThread::ThreadId(), name, &StateKey, state, StateKey.state, IncState);
|
||||||
|
if (StateKey.stateLock != this) {
|
||||||
|
esyslog("ERROR: cStateLock::Unlock() called with an unused key (tid=%d, lock=%s)", threadId, name);
|
||||||
|
ABORT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (StateKey.write && threadId != cThread::ThreadId()) {
|
||||||
|
esyslog("ERROR: cStateLock::Unlock() called without holding a lock (tid=%d, lock=%s)", threadId, name);
|
||||||
|
ABORT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (StateKey.write && IncState && !explicitModify)
|
||||||
|
state++;
|
||||||
|
StateKey.state = state;
|
||||||
|
StateKey.write = false;
|
||||||
|
threadId = 0;
|
||||||
|
explicitModify = false;
|
||||||
|
rwLock.Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void cStateLock::IncState(void)
|
||||||
|
{
|
||||||
|
if (threadId != cThread::ThreadId()) {
|
||||||
|
esyslog("ERROR: cStateLock::IncState() called without holding a lock (tid=%d, lock=%s)", threadId, name);
|
||||||
|
ABORT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
state++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- cStateKey -------------------------------------------------------------
|
||||||
|
|
||||||
|
cStateKey::cStateKey(bool IgnoreFirst)
|
||||||
|
{
|
||||||
|
stateLock = NULL;
|
||||||
|
write = false;
|
||||||
|
state = 0;
|
||||||
|
if (!IgnoreFirst)
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
cStateKey::~cStateKey()
|
||||||
|
{
|
||||||
|
if (stateLock) {
|
||||||
|
esyslog("ERROR: cStateKey::~cStateKey() called without releasing the lock first (tid=%d, lock=%s, key=%p)", stateLock->threadId, stateLock->name, this);
|
||||||
|
ABORT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cStateKey::Reset(void)
|
||||||
|
{
|
||||||
|
state = -1; // lock and key are initialized differently, to make the first check return true
|
||||||
|
}
|
||||||
|
|
||||||
|
void cStateKey::Remove(bool IncState)
|
||||||
|
{
|
||||||
|
if (stateLock) {
|
||||||
|
stateLock->Unlock(*this, IncState);
|
||||||
|
stateLock = NULL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
esyslog("ERROR: cStateKey::Remove() called without holding a lock (key=%p)", this);
|
||||||
|
ABORT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cStateKey::StateChanged(void)
|
||||||
|
{
|
||||||
|
if (!stateLock) {
|
||||||
|
esyslog("ERROR: cStateKey::StateChanged() called without holding a lock (tid=%d, key=%p)", cThread::ThreadId(), this);
|
||||||
|
ABORT;
|
||||||
|
}
|
||||||
|
else if (write)
|
||||||
|
return state != stateLock->state;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// --- cIoThrottle -----------------------------------------------------------
|
// --- cIoThrottle -----------------------------------------------------------
|
||||||
|
|
||||||
cMutex cIoThrottle::mutex;
|
cMutex cIoThrottle::mutex;
|
||||||
|
90
thread.h
90
thread.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: thread.h 3.2 2015/01/14 11:39:55 kls Exp $
|
* $Id: thread.h 4.1 2015/08/17 13:06:24 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __THREAD_H
|
#ifndef __THREAD_H
|
||||||
@ -164,6 +164,94 @@ public:
|
|||||||
|
|
||||||
#define LOCK_THREAD cThreadLock ThreadLock(this)
|
#define LOCK_THREAD cThreadLock ThreadLock(this)
|
||||||
|
|
||||||
|
class cStateKey;
|
||||||
|
|
||||||
|
class cStateLock {
|
||||||
|
friend class cStateKey;
|
||||||
|
private:
|
||||||
|
const char *name;
|
||||||
|
tThreadId threadId;
|
||||||
|
cRwLock rwLock;
|
||||||
|
int state;
|
||||||
|
bool explicitModify;
|
||||||
|
void Unlock(cStateKey &StateKey, bool IncState = true);
|
||||||
|
///< Releases a lock that has been obtained by a previous call to Lock()
|
||||||
|
///< with the given StateKey. If this was a write-lock, and IncState is true,
|
||||||
|
///< the state of the lock will be incremented. In any case, the (new) state
|
||||||
|
///< of the lock will be copied to the StateKey's state.
|
||||||
|
public:
|
||||||
|
cStateLock(const char *Name = NULL);
|
||||||
|
bool Lock(cStateKey &StateKey, bool Write = false, int TimeoutMs = 0);
|
||||||
|
///< Tries to get a lock and returns true if successful.
|
||||||
|
///< If TimoutMs is not 0, it waits for the given number of milliseconds
|
||||||
|
///< and returns false if no lock has been obtained within that time.
|
||||||
|
///< Otherwise it waits indefinitely for the lock. The given StateKey
|
||||||
|
///< will store which lock it has been used with, and will use that
|
||||||
|
///< information when its Remove() function is called.
|
||||||
|
///< There are two possible locks, one only for read access, and one
|
||||||
|
///< for reading and writing:
|
||||||
|
///<
|
||||||
|
///< If Write is false (i.e. a read-lock is requested), the lock's state
|
||||||
|
///< is compared to the given StateKey's state, and true is returned if
|
||||||
|
///< they differ.
|
||||||
|
///< If true is returned, the read-lock is still in place and the
|
||||||
|
///< protected data structures can be safely accessed (in read-only mode!).
|
||||||
|
///< Once the necessary operations have been performed, the lock must
|
||||||
|
///< be released by a call to the StateKey's Remove() function.
|
||||||
|
///< If false is returned, the state has not changed since the last check,
|
||||||
|
///< and the read-lock has been released. In that case the protected
|
||||||
|
///< data structures have not changed since the last call, so no action
|
||||||
|
///< is required. Note that if TimeoutMs is used with read-locks, Lock()
|
||||||
|
///< might return false even if the states of lock and key differ, just
|
||||||
|
///< because it was unable to obtain the lock within the given time.
|
||||||
|
///< You can call cStateKey::TimedOut() to detect this.
|
||||||
|
///<
|
||||||
|
///< If Write is true (i.e. a write-lock is requested), the states of the
|
||||||
|
///< lock and the given StateKey don't matter, it will always try to obtain
|
||||||
|
///< a write lock.
|
||||||
|
void SetExplicitModify(void) { explicitModify = true; }
|
||||||
|
///< If you have obtained a write lock on this lock, and you don't want its
|
||||||
|
///< state to be automatically incremented when the lock is released, a call to
|
||||||
|
///< this function will disable this, and you can explicitly call IncState()
|
||||||
|
///< to increment the state.
|
||||||
|
void IncState(void);
|
||||||
|
///< Increments the state of this lock.
|
||||||
|
};
|
||||||
|
|
||||||
|
class cStateKey {
|
||||||
|
friend class cStateLock;
|
||||||
|
private:
|
||||||
|
cStateLock *stateLock;
|
||||||
|
bool write;
|
||||||
|
int state;
|
||||||
|
bool timedOut;
|
||||||
|
public:
|
||||||
|
cStateKey(bool IgnoreFirst = false);
|
||||||
|
///< Sets up a new state key. If IgnoreFirst is true, the first use
|
||||||
|
///< of this key with a lock will not return true if the lock's state
|
||||||
|
///< hasn't explicitly changed.
|
||||||
|
~cStateKey();
|
||||||
|
void Reset(void);
|
||||||
|
///< Resets the state of this key, so that the next call to a lock's
|
||||||
|
///< Lock() function with this key will return true, even if the
|
||||||
|
///< lock's state hasn't changed.
|
||||||
|
void Remove(bool IncState = true);
|
||||||
|
///< Removes this key from the lock it was previously used with.
|
||||||
|
///< If this key was used to obtain a write lock, the state of the lock will
|
||||||
|
///< be incremented and copied to this key. You can set IncState to false
|
||||||
|
///< to prevent this.
|
||||||
|
bool StateChanged(void);
|
||||||
|
///< Returns true if this key is used for obtaining a write lock, and the
|
||||||
|
///< lock's state differs from that of the key. When used with a read lock,
|
||||||
|
///< it always returns true, because otherwise the lock wouldn't have been
|
||||||
|
///< obtained in the first place.
|
||||||
|
bool InLock(void) { return stateLock; }
|
||||||
|
///< Returns true if this key is currently in a lock.
|
||||||
|
bool TimedOut(void) const { return timedOut; }
|
||||||
|
///< Returns true if the last lock attempt this key was used with failed due
|
||||||
|
///< to a timeout.
|
||||||
|
};
|
||||||
|
|
||||||
class cIoThrottle {
|
class cIoThrottle {
|
||||||
private:
|
private:
|
||||||
static cMutex mutex;
|
static cMutex mutex;
|
||||||
|
326
timers.c
326
timers.c
@ -4,18 +4,18 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: timers.c 3.1 2013/12/28 11:33:08 kls Exp $
|
* $Id: timers.c 4.1 2015/08/31 10:45:13 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "timers.h"
|
#include "timers.h"
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include "channels.h"
|
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
#include "i18n.h"
|
#include "i18n.h"
|
||||||
#include "libsi/si.h"
|
#include "libsi/si.h"
|
||||||
#include "recording.h"
|
#include "recording.h"
|
||||||
#include "remote.h"
|
#include "remote.h"
|
||||||
#include "status.h"
|
#include "status.h"
|
||||||
|
#include "svdrp.h"
|
||||||
|
|
||||||
// IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d'
|
// IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d'
|
||||||
// format characters in order to allow any number of blanks after a numeric
|
// format characters in order to allow any number of blanks after a numeric
|
||||||
@ -23,19 +23,21 @@
|
|||||||
|
|
||||||
// --- cTimer ----------------------------------------------------------------
|
// --- cTimer ----------------------------------------------------------------
|
||||||
|
|
||||||
cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
|
cTimer::cTimer(bool Instant, bool Pause, const cChannel *Channel)
|
||||||
{
|
{
|
||||||
startTime = stopTime = 0;
|
startTime = stopTime = 0;
|
||||||
lastSetEvent = 0;
|
scheduleState = -1;
|
||||||
deferred = 0;
|
deferred = 0;
|
||||||
recording = pending = inVpsMargin = false;
|
pending = inVpsMargin = false;
|
||||||
flags = tfNone;
|
flags = tfNone;
|
||||||
*file = 0;
|
*file = 0;
|
||||||
aux = NULL;
|
aux = NULL;
|
||||||
|
remote = NULL;
|
||||||
event = NULL;
|
event = NULL;
|
||||||
if (Instant)
|
if (Instant)
|
||||||
SetFlags(tfActive | tfInstant);
|
SetFlags(tfActive | tfInstant);
|
||||||
channel = Channel ? Channel : Channels.GetByNumber(cDevice::CurrentChannel());
|
LOCK_CHANNELS_READ;
|
||||||
|
channel = Channel ? Channel : Channels->GetByNumber(cDevice::CurrentChannel());
|
||||||
time_t t = time(NULL);
|
time_t t = time(NULL);
|
||||||
struct tm tm_r;
|
struct tm tm_r;
|
||||||
struct tm *now = localtime_r(&t, &tm_r);
|
struct tm *now = localtime_r(&t, &tm_r);
|
||||||
@ -44,27 +46,25 @@ cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
|
|||||||
start = now->tm_hour * 100 + now->tm_min;
|
start = now->tm_hour * 100 + now->tm_min;
|
||||||
stop = 0;
|
stop = 0;
|
||||||
if (!Setup.InstantRecordTime && channel && (Instant || Pause)) {
|
if (!Setup.InstantRecordTime && channel && (Instant || Pause)) {
|
||||||
cSchedulesLock SchedulesLock;
|
LOCK_SCHEDULES_READ;
|
||||||
if (const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock)) {
|
if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) {
|
||||||
if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) {
|
if (const cEvent *Event = Schedule->GetPresentEvent()) {
|
||||||
if (const cEvent *Event = Schedule->GetPresentEvent()) {
|
time_t tstart = Event->StartTime();
|
||||||
time_t tstart = Event->StartTime();
|
time_t tstop = Event->EndTime();
|
||||||
time_t tstop = Event->EndTime();
|
if (Event->Vps() && Setup.UseVps) {
|
||||||
if (Event->Vps() && Setup.UseVps) {
|
SetFlags(tfVps);
|
||||||
SetFlags(tfVps);
|
tstart = Event->Vps();
|
||||||
tstart = Event->Vps();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tstop += Setup.MarginStop * 60;
|
|
||||||
tstart -= Setup.MarginStart * 60;
|
|
||||||
}
|
|
||||||
day = SetTime(tstart, 0);
|
|
||||||
struct tm *time = localtime_r(&tstart, &tm_r);
|
|
||||||
start = time->tm_hour * 100 + time->tm_min;
|
|
||||||
time = localtime_r(&tstop, &tm_r);
|
|
||||||
stop = time->tm_hour * 100 + time->tm_min;
|
|
||||||
SetEvent(Event);
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
tstop += Setup.MarginStop * 60;
|
||||||
|
tstart -= Setup.MarginStart * 60;
|
||||||
|
}
|
||||||
|
day = SetTime(tstart, 0);
|
||||||
|
struct tm *time = localtime_r(&tstart, &tm_r);
|
||||||
|
start = time->tm_hour * 100 + time->tm_min;
|
||||||
|
time = localtime_r(&tstop, &tm_r);
|
||||||
|
stop = time->tm_hour * 100 + time->tm_min;
|
||||||
|
SetEvent(Event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,16 +83,18 @@ cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
|
|||||||
cTimer::cTimer(const cEvent *Event)
|
cTimer::cTimer(const cEvent *Event)
|
||||||
{
|
{
|
||||||
startTime = stopTime = 0;
|
startTime = stopTime = 0;
|
||||||
lastSetEvent = 0;
|
scheduleState = -1;
|
||||||
deferred = 0;
|
deferred = 0;
|
||||||
recording = pending = inVpsMargin = false;
|
pending = inVpsMargin = false;
|
||||||
flags = tfActive;
|
flags = tfActive;
|
||||||
*file = 0;
|
*file = 0;
|
||||||
aux = NULL;
|
aux = NULL;
|
||||||
|
remote = NULL;
|
||||||
event = NULL;
|
event = NULL;
|
||||||
if (Event->Vps() && Setup.UseVps)
|
if (Event->Vps() && Setup.UseVps)
|
||||||
SetFlags(tfVps);
|
SetFlags(tfVps);
|
||||||
channel = Channels.GetByChannelID(Event->ChannelID(), true);
|
LOCK_CHANNELS_READ;
|
||||||
|
channel = Channels->GetByChannelID(Event->ChannelID(), true);
|
||||||
time_t tstart = (flags & tfVps) ? Event->Vps() : Event->StartTime();
|
time_t tstart = (flags & tfVps) ? Event->Vps() : Event->StartTime();
|
||||||
time_t tstop = tstart + Event->Duration();
|
time_t tstop = tstart + Event->Duration();
|
||||||
if (!(HasFlags(tfVps))) {
|
if (!(HasFlags(tfVps))) {
|
||||||
@ -120,6 +122,7 @@ cTimer::cTimer(const cTimer &Timer)
|
|||||||
{
|
{
|
||||||
channel = NULL;
|
channel = NULL;
|
||||||
aux = NULL;
|
aux = NULL;
|
||||||
|
remote = NULL;
|
||||||
event = NULL;
|
event = NULL;
|
||||||
flags = tfNone;
|
flags = tfNone;
|
||||||
*this = Timer;
|
*this = Timer;
|
||||||
@ -127,7 +130,10 @@ cTimer::cTimer(const cTimer &Timer)
|
|||||||
|
|
||||||
cTimer::~cTimer()
|
cTimer::~cTimer()
|
||||||
{
|
{
|
||||||
|
if (event)
|
||||||
|
event->DecNumTimers();
|
||||||
free(aux);
|
free(aux);
|
||||||
|
free(remote);
|
||||||
}
|
}
|
||||||
|
|
||||||
cTimer& cTimer::operator= (const cTimer &Timer)
|
cTimer& cTimer::operator= (const cTimer &Timer)
|
||||||
@ -136,9 +142,8 @@ cTimer& cTimer::operator= (const cTimer &Timer)
|
|||||||
uint OldFlags = flags & tfRecording;
|
uint OldFlags = flags & tfRecording;
|
||||||
startTime = Timer.startTime;
|
startTime = Timer.startTime;
|
||||||
stopTime = Timer.stopTime;
|
stopTime = Timer.stopTime;
|
||||||
lastSetEvent = 0;
|
scheduleState = -1;
|
||||||
deferred = 0;
|
deferred = 0;
|
||||||
recording = Timer.recording;
|
|
||||||
pending = Timer.pending;
|
pending = Timer.pending;
|
||||||
inVpsMargin = Timer.inVpsMargin;
|
inVpsMargin = Timer.inVpsMargin;
|
||||||
flags = Timer.flags | OldFlags;
|
flags = Timer.flags | OldFlags;
|
||||||
@ -152,7 +157,13 @@ cTimer& cTimer::operator= (const cTimer &Timer)
|
|||||||
strncpy(file, Timer.file, sizeof(file));
|
strncpy(file, Timer.file, sizeof(file));
|
||||||
free(aux);
|
free(aux);
|
||||||
aux = Timer.aux ? strdup(Timer.aux) : NULL;
|
aux = Timer.aux ? strdup(Timer.aux) : NULL;
|
||||||
event = NULL;
|
free(remote);
|
||||||
|
remote = Timer.remote ? strdup(Timer.remote) : NULL;
|
||||||
|
if (event)
|
||||||
|
event->DecNumTimers();
|
||||||
|
event = Timer.event;
|
||||||
|
if (event)
|
||||||
|
event->IncNumTimers();
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@ -178,7 +189,7 @@ cString cTimer::ToText(bool UseChannelID) const
|
|||||||
|
|
||||||
cString cTimer::ToDescr(void) const
|
cString cTimer::ToDescr(void) const
|
||||||
{
|
{
|
||||||
return cString::sprintf("%d (%d %04d-%04d %s'%s')", Index() + 1, Channel()->Number(), start, stop, HasFlags(tfVps) ? "VPS " : "", file);
|
return cString::sprintf("%d%s%s (%d %04d-%04d %s'%s')", Index() + 1, remote ? "@" : "", remote ? remote : "", Channel()->Number(), start, stop, HasFlags(tfVps) ? "VPS " : "", file);
|
||||||
}
|
}
|
||||||
|
|
||||||
int cTimer::TimeToInt(int t)
|
int cTimer::TimeToInt(int t)
|
||||||
@ -313,7 +324,6 @@ bool cTimer::Parse(const char *s)
|
|||||||
}
|
}
|
||||||
bool result = false;
|
bool result = false;
|
||||||
if (8 <= sscanf(s, "%u :%m[^:]:%m[^:]:%d :%d :%d :%d :%m[^:\n]:%m[^\n]", &flags, &channelbuffer, &daybuffer, &start, &stop, &priority, &lifetime, &filebuffer, &aux)) {
|
if (8 <= sscanf(s, "%u :%m[^:]:%m[^:]:%d :%d :%d :%d :%m[^:\n]:%m[^\n]", &flags, &channelbuffer, &daybuffer, &start, &stop, &priority, &lifetime, &filebuffer, &aux)) {
|
||||||
ClrFlags(tfRecording);
|
|
||||||
if (aux && !*skipspace(aux)) {
|
if (aux && !*skipspace(aux)) {
|
||||||
free(aux);
|
free(aux);
|
||||||
aux = NULL;
|
aux = NULL;
|
||||||
@ -322,10 +332,11 @@ bool cTimer::Parse(const char *s)
|
|||||||
result = ParseDay(daybuffer, day, weekdays);
|
result = ParseDay(daybuffer, day, weekdays);
|
||||||
Utf8Strn0Cpy(file, filebuffer, sizeof(file));
|
Utf8Strn0Cpy(file, filebuffer, sizeof(file));
|
||||||
strreplace(file, '|', ':');
|
strreplace(file, '|', ':');
|
||||||
|
LOCK_CHANNELS_READ;
|
||||||
if (isnumber(channelbuffer))
|
if (isnumber(channelbuffer))
|
||||||
channel = Channels.GetByNumber(atoi(channelbuffer));
|
channel = Channels->GetByNumber(atoi(channelbuffer));
|
||||||
else
|
else
|
||||||
channel = Channels.GetByChannelID(tChannelID::FromString(channelbuffer), true, true);
|
channel = Channels->GetByChannelID(tChannelID::FromString(channelbuffer), true, true);
|
||||||
if (!channel) {
|
if (!channel) {
|
||||||
esyslog("ERROR: channel %s not defined", channelbuffer);
|
esyslog("ERROR: channel %s not defined", channelbuffer);
|
||||||
result = false;
|
result = false;
|
||||||
@ -340,7 +351,9 @@ bool cTimer::Parse(const char *s)
|
|||||||
|
|
||||||
bool cTimer::Save(FILE *f)
|
bool cTimer::Save(FILE *f)
|
||||||
{
|
{
|
||||||
return fprintf(f, "%s", *ToText(true)) > 0;
|
if (!Remote())
|
||||||
|
return fprintf(f, "%s", *ToText(true)) > 0;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cTimer::IsSingleEvent(void) const
|
bool cTimer::IsSingleEvent(void) const
|
||||||
@ -441,10 +454,8 @@ bool cTimer::Matches(time_t t, bool Directly, int Margin) const
|
|||||||
startTime = event->StartTime();
|
startTime = event->StartTime();
|
||||||
stopTime = event->EndTime();
|
stopTime = event->EndTime();
|
||||||
if (!Margin) { // this is an actual check
|
if (!Margin) { // this is an actual check
|
||||||
if (event->Schedule()->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) { // VPS control can only work with up-to-date events...
|
if (event->Schedule()->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) // VPS control can only work with up-to-date events...
|
||||||
if (event->StartTime() > 0) // checks for "phased out" events
|
return event->IsRunning(true);
|
||||||
return event->IsRunning(true);
|
|
||||||
}
|
|
||||||
return startTime <= t && t < stopTime; // ...otherwise we fall back to normal timer handling
|
return startTime <= t && t < stopTime; // ...otherwise we fall back to normal timer handling
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -511,27 +522,13 @@ time_t cTimer::StopTime(void) const
|
|||||||
#define EPGLIMITBEFORE (1 * 3600) // Time in seconds before a timer's start time and
|
#define EPGLIMITBEFORE (1 * 3600) // Time in seconds before a timer's start time and
|
||||||
#define EPGLIMITAFTER (1 * 3600) // after its stop time within which EPG events will be taken into consideration.
|
#define EPGLIMITAFTER (1 * 3600) // after its stop time within which EPG events will be taken into consideration.
|
||||||
|
|
||||||
void cTimer::SetEventFromSchedule(const cSchedules *Schedules)
|
bool cTimer::SetEventFromSchedule(const cSchedules *Schedules)
|
||||||
{
|
{
|
||||||
cSchedulesLock SchedulesLock;
|
|
||||||
if (!Schedules) {
|
|
||||||
lastSetEvent = 0; // forces setting the event, even if the schedule hasn't been modified
|
|
||||||
if (!(Schedules = cSchedules::Schedules(SchedulesLock)))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const cSchedule *Schedule = Schedules->GetSchedule(Channel());
|
const cSchedule *Schedule = Schedules->GetSchedule(Channel());
|
||||||
if (Schedule && Schedule->Events()->First()) {
|
if (Schedule && Schedule->Events()->First()) {
|
||||||
time_t now = time(NULL);
|
if (Schedule->Modified(scheduleState)) {
|
||||||
if (!lastSetEvent || Schedule->Modified() >= lastSetEvent) {
|
|
||||||
lastSetEvent = now;
|
|
||||||
const cEvent *Event = NULL;
|
const cEvent *Event = NULL;
|
||||||
if (HasFlags(tfVps) && Schedule->Events()->First()->Vps()) {
|
if (HasFlags(tfVps) && Schedule->Events()->First()->Vps()) {
|
||||||
if (event && event->StartTime() > 0) { // checks for "phased out" events
|
|
||||||
if (Recording())
|
|
||||||
return; // let the recording end first
|
|
||||||
if (now <= event->EndTime() || Matches(0, true))
|
|
||||||
return; // stay with the old event until the timer has completely expired
|
|
||||||
}
|
|
||||||
// VPS timers only match if their start time exactly matches the event's VPS time:
|
// VPS timers only match if their start time exactly matches the event's VPS time:
|
||||||
for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
|
for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
|
||||||
if (e->StartTime() && e->RunningStatus() != SI::RunningStatusNotRunning) { // skip outdated events
|
if (e->StartTime() && e->RunningStatus() != SI::RunningStatusNotRunning) { // skip outdated events
|
||||||
@ -566,30 +563,39 @@ void cTimer::SetEventFromSchedule(const cSchedules *Schedules)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SetEvent(Event);
|
return SetEvent(Event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cTimer::SetEvent(const cEvent *Event)
|
bool cTimer::SetEvent(const cEvent *Event)
|
||||||
{
|
{
|
||||||
if (event != Event) { //XXX TODO check event data, too???
|
if (event != Event) {
|
||||||
if (Event)
|
if (event)
|
||||||
|
event->DecNumTimers();
|
||||||
|
if (Event) {
|
||||||
isyslog("timer %s set to event %s", *ToDescr(), *Event->ToDescr());
|
isyslog("timer %s set to event %s", *ToDescr(), *Event->ToDescr());
|
||||||
else
|
Event->IncNumTimers();
|
||||||
|
Event->Schedule()->Modified(scheduleState); // to get the current state
|
||||||
|
}
|
||||||
|
else {
|
||||||
isyslog("timer %s set to no event", *ToDescr());
|
isyslog("timer %s set to no event", *ToDescr());
|
||||||
|
scheduleState = -1;
|
||||||
|
}
|
||||||
event = Event;
|
event = Event;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cTimer::SetRecording(bool Recording)
|
void cTimer::SetRecording(bool Recording)
|
||||||
{
|
{
|
||||||
recording = Recording;
|
if (Recording)
|
||||||
if (recording)
|
|
||||||
SetFlags(tfRecording);
|
SetFlags(tfRecording);
|
||||||
else
|
else
|
||||||
ClrFlags(tfRecording);
|
ClrFlags(tfRecording);
|
||||||
isyslog("timer %s %s", *ToDescr(), recording ? "start" : "stop");
|
isyslog("timer %s %s", *ToDescr(), Recording ? "start" : "stop");
|
||||||
}
|
}
|
||||||
|
|
||||||
void cTimer::SetPending(bool Pending)
|
void cTimer::SetPending(bool Pending)
|
||||||
@ -637,7 +643,13 @@ void cTimer::SetLifetime(int Lifetime)
|
|||||||
void cTimer::SetAux(const char *Aux)
|
void cTimer::SetAux(const char *Aux)
|
||||||
{
|
{
|
||||||
free(aux);
|
free(aux);
|
||||||
aux = strdup(Aux);
|
aux = Aux ? strdup(Aux) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cTimer::SetRemote(const char *Remote)
|
||||||
|
{
|
||||||
|
free(remote);
|
||||||
|
remote = Remote ? strdup(Remote) : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cTimer::SetDeferred(int Seconds)
|
void cTimer::SetDeferred(int Seconds)
|
||||||
@ -691,20 +703,33 @@ void cTimer::OnOff(void)
|
|||||||
|
|
||||||
// --- cTimers ---------------------------------------------------------------
|
// --- cTimers ---------------------------------------------------------------
|
||||||
|
|
||||||
cTimers Timers;
|
cTimers cTimers::timers;
|
||||||
|
|
||||||
cTimers::cTimers(void)
|
cTimers::cTimers(void)
|
||||||
|
:cConfig<cTimer>("Timers")
|
||||||
{
|
{
|
||||||
state = 0;
|
|
||||||
beingEdited = 0;;
|
|
||||||
lastSetEvents = 0;
|
|
||||||
lastDeleteExpired = 0;
|
lastDeleteExpired = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool cTimers::Load(const char *FileName)
|
||||||
|
{
|
||||||
|
LOCK_TIMERS_WRITE;
|
||||||
|
Timers->SetExplicitModify();
|
||||||
|
if (timers.cConfig<cTimer>::Load(FileName)) {
|
||||||
|
for (cTimer *ti = timers.First(); ti; ti = timers.Next(ti)) {
|
||||||
|
ti->ClrFlags(tfRecording);
|
||||||
|
Timers->SetModified();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
cTimer *cTimers::GetTimer(cTimer *Timer)
|
cTimer *cTimers::GetTimer(cTimer *Timer)
|
||||||
{
|
{
|
||||||
for (cTimer *ti = First(); ti; ti = Next(ti)) {
|
for (cTimer *ti = First(); ti; ti = Next(ti)) {
|
||||||
if (ti->Channel() == Timer->Channel() &&
|
if (!ti->Remote() &&
|
||||||
|
ti->Channel() == Timer->Channel() &&
|
||||||
(ti->WeekDays() && ti->WeekDays() == Timer->WeekDays() || !ti->WeekDays() && ti->Day() == Timer->Day()) &&
|
(ti->WeekDays() && ti->WeekDays() == Timer->WeekDays() || !ti->WeekDays() && ti->Day() == Timer->Day()) &&
|
||||||
ti->Start() == Timer->Start() &&
|
ti->Start() == Timer->Start() &&
|
||||||
ti->Stop() == Timer->Stop())
|
ti->Stop() == Timer->Stop())
|
||||||
@ -713,12 +738,12 @@ cTimer *cTimers::GetTimer(cTimer *Timer)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
cTimer *cTimers::GetMatch(time_t t)
|
const cTimer *cTimers::GetMatch(time_t t) const
|
||||||
{
|
{
|
||||||
static int LastPending = -1;
|
static int LastPending = -1;
|
||||||
cTimer *t0 = NULL;
|
const cTimer *t0 = NULL;
|
||||||
for (cTimer *ti = First(); ti; ti = Next(ti)) {
|
for (const cTimer *ti = First(); ti; ti = Next(ti)) {
|
||||||
if (!ti->Recording() && ti->Matches(t)) {
|
if (!ti->Remote() && !ti->Recording() && ti->Matches(t)) {
|
||||||
if (ti->Pending()) {
|
if (ti->Pending()) {
|
||||||
if (ti->Index() > LastPending) {
|
if (ti->Index() > LastPending) {
|
||||||
LastPending = ti->Index();
|
LastPending = ti->Index();
|
||||||
@ -736,11 +761,11 @@ cTimer *cTimers::GetMatch(time_t t)
|
|||||||
return t0;
|
return t0;
|
||||||
}
|
}
|
||||||
|
|
||||||
cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match)
|
const cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match) const
|
||||||
{
|
{
|
||||||
cTimer *t = NULL;
|
const cTimer *t = NULL;
|
||||||
eTimerMatch m = tmNone;
|
eTimerMatch m = tmNone;
|
||||||
for (cTimer *ti = First(); ti; ti = Next(ti)) {
|
for (const cTimer *ti = First(); ti; ti = Next(ti)) {
|
||||||
eTimerMatch tm = ti->Matches(Event);
|
eTimerMatch tm = ti->Matches(Event);
|
||||||
if (tm > m) {
|
if (tm > m) {
|
||||||
t = ti;
|
t = ti;
|
||||||
@ -754,21 +779,27 @@ cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match)
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
cTimer *cTimers::GetNextActiveTimer(void)
|
const cTimer *cTimers::GetNextActiveTimer(void) const
|
||||||
{
|
{
|
||||||
cTimer *t0 = NULL;
|
const cTimer *t0 = NULL;
|
||||||
for (cTimer *ti = First(); ti; ti = Next(ti)) {
|
for (const cTimer *ti = First(); ti; ti = Next(ti)) {
|
||||||
ti->Matches();
|
if (!ti->Remote()) {
|
||||||
if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
|
ti->Matches();
|
||||||
t0 = ti;
|
if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
|
||||||
|
t0 = ti;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return t0;
|
return t0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cTimers::SetModified(void)
|
const cTimers *cTimers::GetTimersRead(cStateKey &StateKey, int TimeoutMs)
|
||||||
{
|
{
|
||||||
cStatus::MsgTimerChange(NULL, tcMod);
|
return timers.Lock(StateKey, false, TimeoutMs) ? &timers : NULL;
|
||||||
state++;
|
}
|
||||||
|
|
||||||
|
cTimers *cTimers::GetTimersWrite(cStateKey &StateKey, int TimeoutMs)
|
||||||
|
{
|
||||||
|
return timers.Lock(StateKey, true, TimeoutMs) ? &timers : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cTimers::Add(cTimer *Timer, cTimer *After)
|
void cTimers::Add(cTimer *Timer, cTimer *After)
|
||||||
@ -789,46 +820,113 @@ void cTimers::Del(cTimer *Timer, bool DeleteObject)
|
|||||||
cConfig<cTimer>::Del(Timer, DeleteObject);
|
cConfig<cTimer>::Del(Timer, DeleteObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cTimers::Modified(int &State)
|
const cTimer *cTimers::UsesChannel(const cChannel *Channel) const
|
||||||
{
|
{
|
||||||
bool Result = state != State;
|
for (const cTimer *Timer = First(); Timer; Timer = Next(Timer)) {
|
||||||
State = state;
|
if (Timer->Channel() == Channel)
|
||||||
return Result;
|
return Timer;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cTimers::SetEvents(void)
|
bool cTimers::SetEvents(const cSchedules *Schedules)
|
||||||
{
|
{
|
||||||
if (time(NULL) - lastSetEvents < 5)
|
bool TimersModified = false;
|
||||||
return;
|
for (cTimer *ti = First(); ti; ti = Next(ti))
|
||||||
cSchedulesLock SchedulesLock(false, 100);
|
TimersModified |= ti->SetEventFromSchedule(Schedules);
|
||||||
const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
|
return TimersModified;
|
||||||
if (Schedules) {
|
|
||||||
if (!lastSetEvents || Schedules->Modified() >= lastSetEvents) {
|
|
||||||
for (cTimer *ti = First(); ti; ti = Next(ti)) {
|
|
||||||
if (cRemote::HasKeys())
|
|
||||||
return; // react immediately on user input
|
|
||||||
ti->SetEventFromSchedule(Schedules);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastSetEvents = time(NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void cTimers::DeleteExpired(void)
|
bool cTimers::DeleteExpired(void)
|
||||||
{
|
{
|
||||||
if (time(NULL) - lastDeleteExpired < 30)
|
if (time(NULL) - lastDeleteExpired < 30)
|
||||||
return;
|
return false;
|
||||||
|
bool TimersModified = false;
|
||||||
cTimer *ti = First();
|
cTimer *ti = First();
|
||||||
while (ti) {
|
while (ti) {
|
||||||
cTimer *next = Next(ti);
|
cTimer *next = Next(ti);
|
||||||
if (ti->Expired()) {
|
if (!ti->Remote() && ti->Expired()) {
|
||||||
isyslog("deleting timer %s", *ti->ToDescr());
|
isyslog("deleting timer %s", *ti->ToDescr());
|
||||||
Del(ti);
|
Del(ti);
|
||||||
SetModified();
|
TimersModified = true;
|
||||||
}
|
}
|
||||||
ti = next;
|
ti = next;
|
||||||
}
|
}
|
||||||
lastDeleteExpired = time(NULL);
|
lastDeleteExpired = time(NULL);
|
||||||
|
return TimersModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cTimers::GetRemoteTimers(const char *ServerName)
|
||||||
|
{
|
||||||
|
bool Result = false;
|
||||||
|
if (ServerName) {
|
||||||
|
Result = DelRemoteTimers(ServerName);
|
||||||
|
cSVDRPCommand Cmd(ServerName, "LSTT ID");
|
||||||
|
if (Cmd.Execute()) {
|
||||||
|
const char *s;
|
||||||
|
for (int i = 0; s = Cmd.Response(i); i++) {
|
||||||
|
int Code = Cmd.Code(s);
|
||||||
|
if (Code == 250) {
|
||||||
|
if (const char *v = Cmd.Value(s)) {
|
||||||
|
while (*v && *v != ' ')
|
||||||
|
v++; // skip number
|
||||||
|
cTimer *Timer = new cTimer;
|
||||||
|
if (Timer->Parse(v)) {
|
||||||
|
Timer->SetRemote(ServerName);
|
||||||
|
Add(Timer);
|
||||||
|
Result = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
esyslog("ERROR: %s: error in timer settings: %s", ServerName, v);
|
||||||
|
delete Timer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Code != 550)
|
||||||
|
esyslog("ERROR: %s: %s", ServerName, s);
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cStringList ServerNames;
|
||||||
|
if (GetSVDRPServerNames(&ServerNames, sffTimers)) {
|
||||||
|
for (int i = 0; i < ServerNames.Size(); i++)
|
||||||
|
Result |= GetRemoteTimers(ServerNames[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cTimers::DelRemoteTimers(const char *ServerName)
|
||||||
|
{
|
||||||
|
bool Deleted = false;
|
||||||
|
cTimer *Timer = First();
|
||||||
|
while (Timer) {
|
||||||
|
cTimer *t = Next(Timer);
|
||||||
|
if (Timer->Remote() && (!ServerName || strcmp(Timer->Remote(), ServerName) == 0)) {
|
||||||
|
Del(Timer);
|
||||||
|
Deleted = true;
|
||||||
|
}
|
||||||
|
Timer = t;
|
||||||
|
}
|
||||||
|
return Deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cTimers::TriggerRemoteTimerPoll(const char *ServerName)
|
||||||
|
{
|
||||||
|
if (ServerName) {
|
||||||
|
cSVDRPCommand Cmd(ServerName, cString::sprintf("POLL %s TIMERS", SVDRPHostName()));
|
||||||
|
if (!Cmd.Execute())
|
||||||
|
esyslog("ERROR: can't send 'POLL %s TIMERS' to '%s'", SVDRPHostName(), ServerName);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cStringList ServerNames;
|
||||||
|
if (GetSVDRPServerNames(&ServerNames)) {
|
||||||
|
for (int i = 0; i < ServerNames.Size(); i++)
|
||||||
|
TriggerRemoteTimerPoll(ServerNames[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- cSortedTimers ---------------------------------------------------------
|
// --- cSortedTimers ---------------------------------------------------------
|
||||||
@ -838,10 +936,10 @@ static int CompareTimers(const void *a, const void *b)
|
|||||||
return (*(const cTimer **)a)->Compare(**(const cTimer **)b);
|
return (*(const cTimer **)a)->Compare(**(const cTimer **)b);
|
||||||
}
|
}
|
||||||
|
|
||||||
cSortedTimers::cSortedTimers(void)
|
cSortedTimers::cSortedTimers(const cTimers *Timers)
|
||||||
:cVector<const cTimer *>(Timers.Count())
|
:cVector<const cTimer *>(Timers->Count())
|
||||||
{
|
{
|
||||||
for (const cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer))
|
for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer))
|
||||||
Append(Timer);
|
Append(Timer);
|
||||||
Sort(CompareTimers);
|
Sort(CompareTimers);
|
||||||
}
|
}
|
||||||
|
119
timers.h
119
timers.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: timers.h 2.7 2013/03/11 10:35:53 kls Exp $
|
* $Id: timers.h 4.1 2015/08/31 10:42:53 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __TIMERS_H
|
#ifndef __TIMERS_H
|
||||||
@ -28,11 +28,11 @@ class cTimer : public cListObject {
|
|||||||
friend class cMenuEditTimer;
|
friend class cMenuEditTimer;
|
||||||
private:
|
private:
|
||||||
mutable time_t startTime, stopTime;
|
mutable time_t startTime, stopTime;
|
||||||
time_t lastSetEvent;
|
int scheduleState;
|
||||||
mutable time_t deferred; ///< Matches(time_t, ...) will return false if the current time is before this value
|
mutable time_t deferred; ///< Matches(time_t, ...) will return false if the current time is before this value
|
||||||
bool recording, pending, inVpsMargin;
|
bool pending, inVpsMargin;
|
||||||
uint flags;
|
uint flags;
|
||||||
cChannel *channel;
|
const cChannel *channel;
|
||||||
mutable time_t day; ///< midnight of the day this timer shall hit, or of the first day it shall hit in case of a repeating timer
|
mutable time_t day; ///< midnight of the day this timer shall hit, or of the first day it shall hit in case of a repeating timer
|
||||||
int weekdays; ///< bitmask, lowest bits: SSFTWTM (the 'M' is the LSB)
|
int weekdays; ///< bitmask, lowest bits: SSFTWTM (the 'M' is the LSB)
|
||||||
int start;
|
int start;
|
||||||
@ -41,15 +41,16 @@ private:
|
|||||||
int lifetime;
|
int lifetime;
|
||||||
mutable char file[NAME_MAX * 2 + 1]; // *2 to be able to hold 'title' and 'episode', which can each be up to 255 characters long
|
mutable char file[NAME_MAX * 2 + 1]; // *2 to be able to hold 'title' and 'episode', which can each be up to 255 characters long
|
||||||
char *aux;
|
char *aux;
|
||||||
|
char *remote;
|
||||||
const cEvent *event;
|
const cEvent *event;
|
||||||
public:
|
public:
|
||||||
cTimer(bool Instant = false, bool Pause = false, cChannel *Channel = NULL);
|
cTimer(bool Instant = false, bool Pause = false, const cChannel *Channel = NULL);
|
||||||
cTimer(const cEvent *Event);
|
cTimer(const cEvent *Event);
|
||||||
cTimer(const cTimer &Timer);
|
cTimer(const cTimer &Timer);
|
||||||
virtual ~cTimer();
|
virtual ~cTimer();
|
||||||
cTimer& operator= (const cTimer &Timer);
|
cTimer& operator= (const cTimer &Timer);
|
||||||
virtual int Compare(const cListObject &ListObject) const;
|
virtual int Compare(const cListObject &ListObject) const;
|
||||||
bool Recording(void) const { return recording; }
|
bool Recording(void) const { return HasFlags(tfRecording); }
|
||||||
bool Pending(void) const { return pending; }
|
bool Pending(void) const { return pending; }
|
||||||
bool InVpsMargin(void) const { return inVpsMargin; }
|
bool InVpsMargin(void) const { return inVpsMargin; }
|
||||||
uint Flags(void) const { return flags; }
|
uint Flags(void) const { return flags; }
|
||||||
@ -63,6 +64,7 @@ public:
|
|||||||
const char *File(void) const { return file; }
|
const char *File(void) const { return file; }
|
||||||
time_t FirstDay(void) const { return weekdays ? day : 0; }
|
time_t FirstDay(void) const { return weekdays ? day : 0; }
|
||||||
const char *Aux(void) const { return aux; }
|
const char *Aux(void) const { return aux; }
|
||||||
|
const char *Remote(void) const { return remote; }
|
||||||
time_t Deferred(void) const { return deferred; }
|
time_t Deferred(void) const { return deferred; }
|
||||||
cString ToText(bool UseChannelID = false) const;
|
cString ToText(bool UseChannelID = false) const;
|
||||||
cString ToDescr(void) const;
|
cString ToDescr(void) const;
|
||||||
@ -81,8 +83,8 @@ public:
|
|||||||
bool Expired(void) const;
|
bool Expired(void) const;
|
||||||
time_t StartTime(void) const;
|
time_t StartTime(void) const;
|
||||||
time_t StopTime(void) const;
|
time_t StopTime(void) const;
|
||||||
void SetEventFromSchedule(const cSchedules *Schedules = NULL);
|
bool SetEventFromSchedule(const cSchedules *Schedules);
|
||||||
void SetEvent(const cEvent *Event);
|
bool SetEvent(const cEvent *Event);
|
||||||
void SetRecording(bool Recording);
|
void SetRecording(bool Recording);
|
||||||
void SetPending(bool Pending);
|
void SetPending(bool Pending);
|
||||||
void SetInVpsMargin(bool InVpsMargin);
|
void SetInVpsMargin(bool InVpsMargin);
|
||||||
@ -93,6 +95,7 @@ public:
|
|||||||
void SetPriority(int Priority);
|
void SetPriority(int Priority);
|
||||||
void SetLifetime(int Lifetime);
|
void SetLifetime(int Lifetime);
|
||||||
void SetAux(const char *Aux);
|
void SetAux(const char *Aux);
|
||||||
|
void SetRemote(const char *Remote);
|
||||||
void SetDeferred(int Seconds);
|
void SetDeferred(int Seconds);
|
||||||
void SetFlags(uint Flags);
|
void SetFlags(uint Flags);
|
||||||
void ClrFlags(uint Flags);
|
void ClrFlags(uint Flags);
|
||||||
@ -108,36 +111,100 @@ public:
|
|||||||
|
|
||||||
class cTimers : public cConfig<cTimer> {
|
class cTimers : public cConfig<cTimer> {
|
||||||
private:
|
private:
|
||||||
int state;
|
static cTimers timers;
|
||||||
int beingEdited;
|
|
||||||
time_t lastSetEvents;
|
|
||||||
time_t lastDeleteExpired;
|
time_t lastDeleteExpired;
|
||||||
public:
|
public:
|
||||||
cTimers(void);
|
cTimers(void);
|
||||||
|
static const cTimers *GetTimersRead(cStateKey &StateKey, int TimeoutMs = 0);
|
||||||
|
///< Gets the list of timers for read access. If TimeoutMs is given,
|
||||||
|
///< it will wait that long to get a write lock before giving up.
|
||||||
|
///< Otherwise it will wait indefinitely. If no read lock can be
|
||||||
|
///< obtained within the given timeout, NULL will be returned.
|
||||||
|
///< The list is locked and a pointer to it is returned if the state
|
||||||
|
///< of the list is different than the state of the given StateKey.
|
||||||
|
///< If both states are equal, the list of timers has not been modified
|
||||||
|
///< since the last call with the same StateKey, and NULL will be
|
||||||
|
///< returned (and the list is not locked). After the returned list of
|
||||||
|
///< timers is no longer needed, the StateKey's Remove() function must
|
||||||
|
///< be called to release the list. The time between calling
|
||||||
|
///< cTimers::GetTimersRead() and StateKey.Remove() should be as short
|
||||||
|
///< as possible. After calling StateKey.Remove() the list returned from
|
||||||
|
///< this call must not be accessed any more. If you need to access the
|
||||||
|
///< timers again later, a new call to GetTimersRead() must be made.
|
||||||
|
///< A typical code sequence would look like this:
|
||||||
|
///< cStateKey StateKey;
|
||||||
|
///< if (const cTimers *Timers = cTimers::GetTimersRead(StateKey)) {
|
||||||
|
///< // access the timers
|
||||||
|
///< StateKey.Remove();
|
||||||
|
///< }
|
||||||
|
static cTimers *GetTimersWrite(cStateKey &StateKey, int TimeoutMs = 0);
|
||||||
|
///< Gets the list of timers for write access. If TimeoutMs is given,
|
||||||
|
///< it will wait that long to get a write lock before giving up.
|
||||||
|
///< Otherwise it will wait indefinitely. If no write lock can be
|
||||||
|
///< obtained within the given timeout, NULL will be returned.
|
||||||
|
///< If a write lock can be obtained, the list of timers will be
|
||||||
|
///< returned, regardless of the state values of the timers or the
|
||||||
|
///< given StateKey. After the returned list of timers is no longer
|
||||||
|
///< needed, the StateKey's Remove() function must be called to release
|
||||||
|
///< the list. The time between calling cTimers::GetTimersWrite() and
|
||||||
|
///< StateKey.Remove() should be as short as possible. After calling
|
||||||
|
///< StateKey.Remove() the list returned from this call must not be
|
||||||
|
///< accessed any more. If you need to access the timers again later,
|
||||||
|
///< a new call to GetTimersWrite() must be made. The call
|
||||||
|
///< to StateKey.Remove() will increment the state of the list of
|
||||||
|
///< timers and will copy the new state value to the StateKey. You can
|
||||||
|
///< suppress this by using 'false' as the parameter to the call, in
|
||||||
|
///< which case the state values are left untouched.
|
||||||
|
///< A typical code sequence would look like this:
|
||||||
|
///< cStateKey StateKey;
|
||||||
|
///< if (cTimers *Timers = cTimers::GetTimersWrite(StateKey)) {
|
||||||
|
///< // access the timers
|
||||||
|
///< StateKey.Remove();
|
||||||
|
///< }
|
||||||
|
static bool Load(const char *FileName);
|
||||||
cTimer *GetTimer(cTimer *Timer);
|
cTimer *GetTimer(cTimer *Timer);
|
||||||
cTimer *GetMatch(time_t t);
|
const cTimer *GetMatch(time_t t) const;
|
||||||
cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL);
|
cTimer *GetMatch(time_t t) { return const_cast<cTimer *>(static_cast<const cTimers *>(this)->GetMatch(t)); };
|
||||||
cTimer *GetNextActiveTimer(void);
|
const cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL) const;
|
||||||
int BeingEdited(void) { return beingEdited; }
|
cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL) { return const_cast<cTimer *>(static_cast<const cTimers *>(this)->GetMatch(Event, Match)); }
|
||||||
void IncBeingEdited(void) { beingEdited++; }
|
const cTimer *GetNextActiveTimer(void) const;
|
||||||
void DecBeingEdited(void) { if (!--beingEdited) lastSetEvents = 0; }
|
const cTimer *UsesChannel(const cChannel *Channel) const;
|
||||||
void SetModified(void);
|
bool SetEvents(const cSchedules *Schedules);
|
||||||
bool Modified(int &State);
|
bool DeleteExpired(void);
|
||||||
///< Returns true if any of the timers have been modified, which
|
|
||||||
///< is detected by State being different than the internal state.
|
|
||||||
///< Upon return the internal state will be stored in State.
|
|
||||||
void SetEvents(void);
|
|
||||||
void DeleteExpired(void);
|
|
||||||
void Add(cTimer *Timer, cTimer *After = NULL);
|
void Add(cTimer *Timer, cTimer *After = NULL);
|
||||||
void Ins(cTimer *Timer, cTimer *Before = NULL);
|
void Ins(cTimer *Timer, cTimer *Before = NULL);
|
||||||
void Del(cTimer *Timer, bool DeleteObject = true);
|
void Del(cTimer *Timer, bool DeleteObject = true);
|
||||||
|
bool GetRemoteTimers(const char *ServerName = NULL);
|
||||||
|
///< Gets the timers from the given remote machine and adds them to this
|
||||||
|
///< list. If no ServerName is given, all timers from all known remote
|
||||||
|
///< machines will be fetched. This function calls DelRemoteTimers() with
|
||||||
|
///< the given ServerName first.
|
||||||
|
///< Returns true if any remote timers have been added or deleted
|
||||||
|
bool DelRemoteTimers(const char *ServerName = NULL);
|
||||||
|
///< Deletes all timers of the given remote machine from this list (leaves
|
||||||
|
///< them untouched on the remote machine). If no ServerName is given, the
|
||||||
|
///< timers of all remote machines will be deleted from the list.
|
||||||
|
///< Returns true if any remote timers have been deleted.
|
||||||
|
void TriggerRemoteTimerPoll(const char *ServerName = NULL);
|
||||||
|
///< Sends an SVDRP POLL command to the given remote machine.
|
||||||
|
///< If no ServerName is given, the POLL command will be sent to all
|
||||||
|
///< known remote machines.
|
||||||
};
|
};
|
||||||
|
|
||||||
extern cTimers Timers;
|
// Provide lock controlled access to the list:
|
||||||
|
|
||||||
|
DEF_LIST_LOCK(Timers);
|
||||||
|
|
||||||
|
// These macros provide a convenient way of locking the global timers list
|
||||||
|
// and making sure the lock is released as soon as the current scope is left
|
||||||
|
// (note that these macros wait forever to obtain the lock!):
|
||||||
|
|
||||||
|
#define LOCK_TIMERS_READ USE_LIST_LOCK_READ(Timers)
|
||||||
|
#define LOCK_TIMERS_WRITE USE_LIST_LOCK_WRITE(Timers)
|
||||||
|
|
||||||
class cSortedTimers : public cVector<const cTimer *> {
|
class cSortedTimers : public cVector<const cTimer *> {
|
||||||
public:
|
public:
|
||||||
cSortedTimers(void);
|
cSortedTimers(const cTimers *Timers);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //__TIMERS_H
|
#endif //__TIMERS_H
|
||||||
|
90
tools.c
90
tools.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: tools.c 4.1 2015/05/11 14:15:15 kls Exp $
|
* $Id: tools.c 4.2 2015/08/29 12:11:20 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
@ -2044,12 +2044,58 @@ int cListObject::Index(void) const
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- cListGarbageCollector -------------------------------------------------
|
||||||
|
|
||||||
|
#define LIST_GARBAGE_COLLECTOR_TIMEOUT 5 // seconds
|
||||||
|
|
||||||
|
cListGarbageCollector ListGarbageCollector;
|
||||||
|
|
||||||
|
cListGarbageCollector::cListGarbageCollector(void)
|
||||||
|
{
|
||||||
|
objects = NULL;
|
||||||
|
lastPut = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cListGarbageCollector::~cListGarbageCollector()
|
||||||
|
{
|
||||||
|
if (objects)
|
||||||
|
esyslog("ERROR: ListGarbageCollector destroyed without prior Purge()!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void cListGarbageCollector::Put(cListObject *Object)
|
||||||
|
{
|
||||||
|
mutex.Lock();
|
||||||
|
Object->next = objects;
|
||||||
|
objects = Object;
|
||||||
|
lastPut = time(NULL);
|
||||||
|
mutex.Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void cListGarbageCollector::Purge(bool Force)
|
||||||
|
{
|
||||||
|
mutex.Lock();
|
||||||
|
if (objects && (time(NULL) - lastPut > LIST_GARBAGE_COLLECTOR_TIMEOUT || Force)) {
|
||||||
|
// We make sure that any object stays in the garbage collector for at least
|
||||||
|
// LIST_GARBAGE_COLLECTOR_TIMEOUT seconds, to give objects that have pointers
|
||||||
|
// to them a chance to drop these references before the object is finally
|
||||||
|
// deleted.
|
||||||
|
while (cListObject *Object = objects) {
|
||||||
|
objects = Object->next;
|
||||||
|
delete Object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex.Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
// --- cListBase -------------------------------------------------------------
|
// --- cListBase -------------------------------------------------------------
|
||||||
|
|
||||||
cListBase::cListBase(void)
|
cListBase::cListBase(const char *NeedsLocking)
|
||||||
|
:stateLock(NeedsLocking)
|
||||||
{
|
{
|
||||||
objects = lastObject = NULL;
|
objects = lastObject = NULL;
|
||||||
count = 0;
|
count = 0;
|
||||||
|
needsLocking = NeedsLocking;
|
||||||
|
useGarbageCollector = needsLocking;
|
||||||
}
|
}
|
||||||
|
|
||||||
cListBase::~cListBase()
|
cListBase::~cListBase()
|
||||||
@ -2057,6 +2103,15 @@ cListBase::~cListBase()
|
|||||||
Clear();
|
Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool cListBase::Lock(cStateKey &StateKey, bool Write, int TimeoutMs) const
|
||||||
|
{
|
||||||
|
if (needsLocking)
|
||||||
|
return stateLock.Lock(StateKey, Write, TimeoutMs);
|
||||||
|
else
|
||||||
|
esyslog("ERROR: cListBase::Lock() called for a list that doesn't require locking");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void cListBase::Add(cListObject *Object, cListObject *After)
|
void cListBase::Add(cListObject *Object, cListObject *After)
|
||||||
{
|
{
|
||||||
if (After && After != lastObject) {
|
if (After && After != lastObject) {
|
||||||
@ -2096,8 +2151,12 @@ void cListBase::Del(cListObject *Object, bool DeleteObject)
|
|||||||
if (Object == lastObject)
|
if (Object == lastObject)
|
||||||
lastObject = Object->Prev();
|
lastObject = Object->Prev();
|
||||||
Object->Unlink();
|
Object->Unlink();
|
||||||
if (DeleteObject)
|
if (DeleteObject) {
|
||||||
delete Object;
|
if (useGarbageCollector)
|
||||||
|
ListGarbageCollector.Put(Object);
|
||||||
|
else
|
||||||
|
delete Object;
|
||||||
|
}
|
||||||
count--;
|
count--;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2141,11 +2200,30 @@ void cListBase::Clear(void)
|
|||||||
count = 0;
|
count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
cListObject *cListBase::Get(int Index) const
|
bool cListBase::Contains(const cListObject *Object) const
|
||||||
|
{
|
||||||
|
for (const cListObject *o = objects; o; o = o->Next()) {
|
||||||
|
if (o == Object)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cListBase::SetExplicitModify(void)
|
||||||
|
{
|
||||||
|
stateLock.SetExplicitModify();
|
||||||
|
}
|
||||||
|
|
||||||
|
void cListBase::SetModified(void)
|
||||||
|
{
|
||||||
|
stateLock.IncState();
|
||||||
|
}
|
||||||
|
|
||||||
|
const cListObject *cListBase::Get(int Index) const
|
||||||
{
|
{
|
||||||
if (Index < 0)
|
if (Index < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
cListObject *object = objects;
|
const cListObject *object = objects;
|
||||||
while (object && Index-- > 0)
|
while (object && Index-- > 0)
|
||||||
object = object->Next();
|
object = object->Next();
|
||||||
return object;
|
return object;
|
||||||
|
137
tools.h
137
tools.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: tools.h 4.1 2015/05/21 14:37:00 kls Exp $
|
* $Id: tools.h 4.2 2015/08/29 11:45:51 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __TOOLS_H
|
#ifndef __TOOLS_H
|
||||||
@ -26,6 +26,7 @@
|
|||||||
#include <syslog.h>
|
#include <syslog.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include "thread.h"
|
||||||
|
|
||||||
typedef unsigned char uchar;
|
typedef unsigned char uchar;
|
||||||
|
|
||||||
@ -462,6 +463,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
class cListObject {
|
class cListObject {
|
||||||
|
friend class cListGarbageCollector;
|
||||||
private:
|
private:
|
||||||
cListObject *prev, *next;
|
cListObject *prev, *next;
|
||||||
public:
|
public:
|
||||||
@ -478,33 +480,152 @@ public:
|
|||||||
cListObject *Next(void) const { return next; }
|
cListObject *Next(void) const { return next; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class cListGarbageCollector {
|
||||||
|
private:
|
||||||
|
cMutex mutex;
|
||||||
|
cListObject *objects;
|
||||||
|
time_t lastPut;
|
||||||
|
public:
|
||||||
|
cListGarbageCollector(void);
|
||||||
|
~cListGarbageCollector();
|
||||||
|
void Put(cListObject *Object);
|
||||||
|
void Purge(bool Force = false);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern cListGarbageCollector ListGarbageCollector;
|
||||||
|
|
||||||
class cListBase {
|
class cListBase {
|
||||||
protected:
|
protected:
|
||||||
cListObject *objects, *lastObject;
|
cListObject *objects, *lastObject;
|
||||||
cListBase(void);
|
|
||||||
int count;
|
int count;
|
||||||
|
mutable cStateLock stateLock;
|
||||||
|
const char *needsLocking;
|
||||||
|
bool useGarbageCollector;
|
||||||
|
cListBase(const char *NeedsLocking = NULL);
|
||||||
public:
|
public:
|
||||||
virtual ~cListBase();
|
virtual ~cListBase();
|
||||||
|
bool Lock(cStateKey &StateKey, bool Write = false, int TimeoutMs = 0) const;
|
||||||
|
///< Tries to get a lock on this list and returns true if successful.
|
||||||
|
///< By default a read lock is requested. Set Write to true to obtain
|
||||||
|
///< a write lock. If TimeoutMs is not zero, it waits for the given
|
||||||
|
///< number of milliseconds before giving up.
|
||||||
|
///< If you need to lock more than one list at the same time, make sure
|
||||||
|
///< you set TimeoutMs to a suitable value in all of the calls to
|
||||||
|
///< Lock(), and be prepared to handle situations where you do not get all
|
||||||
|
///< of the requested locks. In such cases you should release all the locks
|
||||||
|
///< you have obtained so far and try again. StateKey.TimedOut() tells you
|
||||||
|
///< whether the lock attempt failed due to a timeout or because the state
|
||||||
|
///< of the lock hasn't changed since the previous locking attempt.
|
||||||
|
///< To implicitly avoid deadlocks when locking more than one of the global
|
||||||
|
///< lists of VDR at the same time, make sure you always lock Timers, Channels,
|
||||||
|
///< Recordings and Schedules in this sequence.
|
||||||
|
///< You may keep pointers to objects in this list, even after releasing
|
||||||
|
///< the lock. However, you may only access such objects if you are
|
||||||
|
///< holding a proper lock again. If an object has been deleted from the list
|
||||||
|
///< while you did not hold a lock (for instance by an other thread), the
|
||||||
|
///< object will still be there, but no longer within this list (it is then
|
||||||
|
///< stored in the ListGarbageCollector). That way even if you access the object
|
||||||
|
///< after it has been deleted, you won't cause a segfault. You can call the
|
||||||
|
///< Contains() function to check whether an object you are holding a pointer
|
||||||
|
///< to is still in the list. Note that the garbage collector is purged when
|
||||||
|
///< the usual housekeeping is done.
|
||||||
|
void SetUseGarbageCollector(void) { useGarbageCollector = true; }
|
||||||
|
void SetExplicitModify(void);
|
||||||
|
///< If you have obtained a write lock on this list, and you don't want it to
|
||||||
|
///< be automatically marked as modified when the lock is released, a call to
|
||||||
|
///< this function will disable this, and you can explicitly call SetModified()
|
||||||
|
///< to have the list marked as modified.
|
||||||
|
void SetModified(void);
|
||||||
|
///< Unconditionally marks this list as modified.
|
||||||
void Add(cListObject *Object, cListObject *After = NULL);
|
void Add(cListObject *Object, cListObject *After = NULL);
|
||||||
void Ins(cListObject *Object, cListObject *Before = NULL);
|
void Ins(cListObject *Object, cListObject *Before = NULL);
|
||||||
void Del(cListObject *Object, bool DeleteObject = true);
|
void Del(cListObject *Object, bool DeleteObject = true);
|
||||||
virtual void Move(int From, int To);
|
virtual void Move(int From, int To);
|
||||||
void Move(cListObject *From, cListObject *To);
|
void Move(cListObject *From, cListObject *To);
|
||||||
virtual void Clear(void);
|
virtual void Clear(void);
|
||||||
cListObject *Get(int Index) const;
|
bool Contains(const cListObject *Object) const;
|
||||||
|
///< If a pointer to an object contained in this list has been obtained while
|
||||||
|
///< holding a lock, and that lock has been released, but the pointer is kept for
|
||||||
|
///< later use (after obtaining a new lock), Contains() can be called with that
|
||||||
|
///< pointer to make sure the object it points to is still part of this list
|
||||||
|
///< (it may have been deleted or otherwise removed from the list after the lock
|
||||||
|
///< during which the pointer was initially retrieved has been released).
|
||||||
|
const cListObject *Get(int Index) const;
|
||||||
|
cListObject *Get(int Index) { return const_cast<cListObject *>(static_cast<const cListBase *>(this)->Get(Index)); }
|
||||||
int Count(void) const { return count; }
|
int Count(void) const { return count; }
|
||||||
void Sort(void);
|
void Sort(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class T> class cList : public cListBase {
|
template<class T> class cList : public cListBase {
|
||||||
public:
|
public:
|
||||||
T *Get(int Index) const { return (T *)cListBase::Get(Index); }
|
cList(const char *NeedsLocking = NULL): cListBase(NeedsLocking) {}
|
||||||
T *First(void) const { return (T *)objects; }
|
///< Sets up a new cList of the given type T. If NeedsLocking is given, the list
|
||||||
T *Last(void) const { return (T *)lastObject; }
|
///< and any of its elements may only be accessed if the caller holds a lock
|
||||||
T *Prev(const T *object) const { return (T *)object->cListObject::Prev(); } // need to call cListObject's members to
|
///< obtained by a call to Lock() (see cListBase::Lock() for details).
|
||||||
T *Next(const T *object) const { return (T *)object->cListObject::Next(); } // avoid ambiguities in case of a "list of lists"
|
///< NeedsLocking is used as both a boolean flag to enable locking, and as
|
||||||
|
///< a name to identify this list in debug output. It must be a static string
|
||||||
|
///< and should be no longer than 10 characters. The string will not be copied!
|
||||||
|
const T *Get(int Index) const { return (T *)cListBase::Get(Index); }
|
||||||
|
///< Returns the list element at the given Index, or NULL if no such element
|
||||||
|
///< exists.
|
||||||
|
const T *First(void) const { return (T *)objects; }
|
||||||
|
///< Returns the first element in this list, or NULL if the list is empty.
|
||||||
|
const T *Last(void) const { return (T *)lastObject; }
|
||||||
|
///< Returns the last element in this list, or NULL if the list is empty.
|
||||||
|
const T *Prev(const T *Object) const { return (T *)Object->cListObject::Prev(); } // need to call cListObject's members to
|
||||||
|
///< Returns the element immediately before Object in this list, or NULL
|
||||||
|
///< if Object is the first element in the list. Object must not be NULL!
|
||||||
|
const T *Next(const T *Object) const { return (T *)Object->cListObject::Next(); } // avoid ambiguities in case of a "list of lists"
|
||||||
|
///< Returns the element immediately following Object in this list, or NULL
|
||||||
|
///< if Object is the last element in the list. Object must not be NULL!
|
||||||
|
T *Get(int Index) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Get(Index)); }
|
||||||
|
///< Non-const version of Get().
|
||||||
|
T *First(void) { return const_cast<T *>(static_cast<const cList<T> *>(this)->First()); }
|
||||||
|
///< Non-const version of First().
|
||||||
|
T *Last(void) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Last()); }
|
||||||
|
///< Non-const version of Last().
|
||||||
|
T *Prev(const T *Object) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Prev(Object)); }
|
||||||
|
///< Non-const version of Prev().
|
||||||
|
T *Next(const T *Object) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Next(Object)); }
|
||||||
|
///< Non-const version of Next().
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The DEF_LIST_LOCK macro defines a convenience class that can be used to obtain
|
||||||
|
// a lock on a cList and make sure the lock is released when the current scope
|
||||||
|
// is left:
|
||||||
|
|
||||||
|
#define DEF_LIST_LOCK2(Class, Name) \
|
||||||
|
class c##Name##Lock { \
|
||||||
|
private: \
|
||||||
|
cStateKey stateKey; \
|
||||||
|
const c##Class *list; \
|
||||||
|
public: \
|
||||||
|
c##Name##Lock(bool Write = false) \
|
||||||
|
{ \
|
||||||
|
if (Write) \
|
||||||
|
list = c##Class::Get##Name##Write(stateKey); \
|
||||||
|
else \
|
||||||
|
list = c##Class::Get##Name##Read(stateKey); \
|
||||||
|
} \
|
||||||
|
~c##Name##Lock() { stateKey.Remove(); } \
|
||||||
|
const c##Class *Name(void) const { return list; } \
|
||||||
|
c##Class *Name(void) { return const_cast<c##Class *>(list); } \
|
||||||
|
}
|
||||||
|
#define DEF_LIST_LOCK(Class) DEF_LIST_LOCK2(Class, Class)
|
||||||
|
|
||||||
|
// The USE_LIST_LOCK macro sets up a local variable of a class defined by
|
||||||
|
// a suitable DEF_LIST_LOCK, and also a pointer to the provided list:
|
||||||
|
|
||||||
|
#define USE_LIST_LOCK_READ2(Class, Name) \
|
||||||
|
c##Name##Lock Name##Lock(false); \
|
||||||
|
const c##Class *Name __attribute__((unused)) = Name##Lock.Name();
|
||||||
|
#define USE_LIST_LOCK_READ(Class) USE_LIST_LOCK_READ2(Class, Class)
|
||||||
|
|
||||||
|
#define USE_LIST_LOCK_WRITE2(Class, Name) \
|
||||||
|
c##Name##Lock Name##Lock(true); \
|
||||||
|
c##Class *Name __attribute__((unused)) = Name##Lock.Name();
|
||||||
|
#define USE_LIST_LOCK_WRITE(Class) USE_LIST_LOCK_WRITE2(Class, Class)
|
||||||
|
|
||||||
template<class T> class cVector {
|
template<class T> class cVector {
|
||||||
///< cVector may only be used for *simple* types, like int or pointers - not for class objects that allocate additional memory!
|
///< cVector may only be used for *simple* types, like int or pointers - not for class objects that allocate additional memory!
|
||||||
private:
|
private:
|
||||||
|
298
vdr.c
298
vdr.c
@ -22,7 +22,7 @@
|
|||||||
*
|
*
|
||||||
* The project's page is at http://www.tvdr.de
|
* The project's page is at http://www.tvdr.de
|
||||||
*
|
*
|
||||||
* $Id: vdr.c 4.4 2015/05/21 13:58:33 kls Exp $
|
* $Id: vdr.c 4.5 2015/09/01 10:33:04 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
@ -748,8 +748,8 @@ int main(int argc, char *argv[])
|
|||||||
Sources.Load(AddDirectory(ConfigDirectory, "sources.conf"), true, true);
|
Sources.Load(AddDirectory(ConfigDirectory, "sources.conf"), true, true);
|
||||||
Diseqcs.Load(AddDirectory(ConfigDirectory, "diseqc.conf"), true, Setup.DiSEqC);
|
Diseqcs.Load(AddDirectory(ConfigDirectory, "diseqc.conf"), true, Setup.DiSEqC);
|
||||||
Scrs.Load(AddDirectory(ConfigDirectory, "scr.conf"), true);
|
Scrs.Load(AddDirectory(ConfigDirectory, "scr.conf"), true);
|
||||||
Channels.Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true);
|
cChannels::Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true);
|
||||||
Timers.Load(AddDirectory(ConfigDirectory, "timers.conf"));
|
cTimers::Load(AddDirectory(ConfigDirectory, "timers.conf"));
|
||||||
Commands.Load(AddDirectory(ConfigDirectory, "commands.conf"));
|
Commands.Load(AddDirectory(ConfigDirectory, "commands.conf"));
|
||||||
RecordingCommands.Load(AddDirectory(ConfigDirectory, "reccmds.conf"));
|
RecordingCommands.Load(AddDirectory(ConfigDirectory, "reccmds.conf"));
|
||||||
SVDRPhosts.Load(AddDirectory(ConfigDirectory, "svdrphosts.conf"), true);
|
SVDRPhosts.Load(AddDirectory(ConfigDirectory, "svdrphosts.conf"), true);
|
||||||
@ -765,8 +765,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
// Recordings:
|
// Recordings:
|
||||||
|
|
||||||
Recordings.Update();
|
cRecordings::Update();
|
||||||
DeletedRecordings.Update();
|
|
||||||
|
|
||||||
// EPG data:
|
// EPG data:
|
||||||
|
|
||||||
@ -879,16 +878,20 @@ int main(int argc, char *argv[])
|
|||||||
if (!CamSlots.WaitForAllCamSlotsReady(DEVICEREADYTIMEOUT))
|
if (!CamSlots.WaitForAllCamSlotsReady(DEVICEREADYTIMEOUT))
|
||||||
dsyslog("not all CAM slots ready after %d seconds", DEVICEREADYTIMEOUT);
|
dsyslog("not all CAM slots ready after %d seconds", DEVICEREADYTIMEOUT);
|
||||||
if (*Setup.InitialChannel) {
|
if (*Setup.InitialChannel) {
|
||||||
|
LOCK_CHANNELS_READ;
|
||||||
if (isnumber(Setup.InitialChannel)) { // for compatibility with old setup.conf files
|
if (isnumber(Setup.InitialChannel)) { // for compatibility with old setup.conf files
|
||||||
if (cChannel *Channel = Channels.GetByNumber(atoi(Setup.InitialChannel)))
|
if (const cChannel *Channel = Channels->GetByNumber(atoi(Setup.InitialChannel)))
|
||||||
Setup.InitialChannel = Channel->GetChannelID().ToString();
|
Setup.InitialChannel = Channel->GetChannelID().ToString();
|
||||||
}
|
}
|
||||||
if (cChannel *Channel = Channels.GetByChannelID(tChannelID::FromString(Setup.InitialChannel)))
|
if (const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(Setup.InitialChannel)))
|
||||||
Setup.CurrentChannel = Channel->Number();
|
Setup.CurrentChannel = Channel->Number();
|
||||||
}
|
}
|
||||||
if (Setup.InitialVolume >= 0)
|
if (Setup.InitialVolume >= 0)
|
||||||
Setup.CurrentVolume = Setup.InitialVolume;
|
Setup.CurrentVolume = Setup.InitialVolume;
|
||||||
Channels.SwitchTo(Setup.CurrentChannel);
|
{
|
||||||
|
LOCK_CHANNELS_READ;
|
||||||
|
Channels->SwitchTo(Setup.CurrentChannel);
|
||||||
|
}
|
||||||
if (MuteAudio)
|
if (MuteAudio)
|
||||||
cDevice::PrimaryDevice()->ToggleMute();
|
cDevice::PrimaryDevice()->ToggleMute();
|
||||||
else
|
else
|
||||||
@ -936,13 +939,14 @@ int main(int argc, char *argv[])
|
|||||||
static time_t lastTime = 0;
|
static time_t lastTime = 0;
|
||||||
if (!cDevice::PrimaryDevice()->HasProgramme()) {
|
if (!cDevice::PrimaryDevice()->HasProgramme()) {
|
||||||
if (!CamMenuActive() && Now - lastTime > MINCHANNELWAIT) { // !CamMenuActive() to avoid interfering with the CAM if a CAM menu is open
|
if (!CamMenuActive() && Now - lastTime > MINCHANNELWAIT) { // !CamMenuActive() to avoid interfering with the CAM if a CAM menu is open
|
||||||
cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel());
|
LOCK_CHANNELS_READ;
|
||||||
|
const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel());
|
||||||
if (Channel && (Channel->Vpid() || Channel->Apid(0) || Channel->Dpid(0))) {
|
if (Channel && (Channel->Vpid() || Channel->Apid(0) || Channel->Dpid(0))) {
|
||||||
if (cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(Channel->Number())) // try to switch to the original channel...
|
if (cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels->SwitchTo(Channel->Number())) // try to switch to the original channel...
|
||||||
;
|
;
|
||||||
else if (LastTimerChannel > 0) {
|
else if (LastTimerChannel > 0) {
|
||||||
Channel = Channels.GetByNumber(LastTimerChannel);
|
Channel = Channels->GetByNumber(LastTimerChannel);
|
||||||
if (Channel && cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(LastTimerChannel)) // ...or the one used by the last timer
|
if (Channel && cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels->SwitchTo(LastTimerChannel)) // ...or the one used by the last timer
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -970,40 +974,59 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Handle channel and timer modifications:
|
// Handle channel and timer modifications:
|
||||||
if (!Channels.BeingEdited() && !Timers.BeingEdited()) {
|
{
|
||||||
int modified = Channels.Modified();
|
// Channels and timers need to be stored in a consistent manner,
|
||||||
static time_t ChannelSaveTimeout = 0;
|
// therefore if one of them is changed, we save both.
|
||||||
static int TimerState = 0;
|
static time_t ChannelSaveTimeout = 0;
|
||||||
// Channels and timers need to be stored in a consistent manner,
|
static cStateKey TimersStateKey(true);
|
||||||
// therefore if one of them is changed, we save both.
|
static cStateKey ChannelsStateKey(true);
|
||||||
if (modified == CHANNELSMOD_USER || Timers.Modified(TimerState))
|
static int ChannelsModifiedByUser = 0;
|
||||||
ChannelSaveTimeout = 1; // triggers an immediate save
|
const cTimers *Timers = cTimers::GetTimersRead(TimersStateKey);
|
||||||
else if (modified && !ChannelSaveTimeout)
|
const cChannels *Channels = cChannels::GetChannelsRead(ChannelsStateKey);
|
||||||
ChannelSaveTimeout = Now + CHANNELSAVEDELTA;
|
if (ChannelSaveTimeout != 1) {
|
||||||
bool timeout = ChannelSaveTimeout == 1 || ChannelSaveTimeout && Now > ChannelSaveTimeout && !cRecordControls::Active();
|
if (Channels) {
|
||||||
if ((modified || timeout) && Channels.Lock(false, 100)) {
|
if (Channels->ModifiedByUser(ChannelsModifiedByUser))
|
||||||
if (timeout) {
|
ChannelSaveTimeout = 1; // triggers an immediate save
|
||||||
Channels.Save();
|
else if (!ChannelSaveTimeout)
|
||||||
Timers.Save();
|
ChannelSaveTimeout = Now + CHANNELSAVEDELTA;
|
||||||
ChannelSaveTimeout = 0;
|
}
|
||||||
|
if (Timers)
|
||||||
|
ChannelSaveTimeout = 1; // triggers an immediate save
|
||||||
|
}
|
||||||
|
if (ChannelSaveTimeout && Now > ChannelSaveTimeout && !cRecordControls::Active())
|
||||||
|
ChannelSaveTimeout = 1; // triggers an immediate save
|
||||||
|
if (Timers && Channels) {
|
||||||
|
Channels->Save();
|
||||||
|
Timers->Save();
|
||||||
|
ChannelSaveTimeout = 0;
|
||||||
|
}
|
||||||
|
if (Channels) {
|
||||||
|
for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
|
||||||
|
if (Channel->Modification(CHANNELMOD_RETUNE)) {
|
||||||
|
cRecordControls::ChannelDataModified(Channel);
|
||||||
|
if (Channel->Number() == cDevice::CurrentChannel() && cDevice::PrimaryDevice()->HasDecoder()) {
|
||||||
|
if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) {
|
||||||
|
if (cDevice::ActualDevice()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
|
||||||
|
isyslog("retuning due to modification of channel %d (%s)", Channel->Number(), Channel->Name());
|
||||||
|
Channels->SwitchTo(Channel->Number());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cStatus::MsgChannelChange(Channel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
|
}
|
||||||
if (Channel->Modification(CHANNELMOD_RETUNE)) {
|
// State keys are removed in reverse order!
|
||||||
cRecordControls::ChannelDataModified(Channel);
|
if (Channels)
|
||||||
if (Channel->Number() == cDevice::CurrentChannel() && cDevice::PrimaryDevice()->HasDecoder()) {
|
ChannelsStateKey.Remove();
|
||||||
if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) {
|
if (Timers)
|
||||||
if (cDevice::ActualDevice()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
|
TimersStateKey.Remove();
|
||||||
isyslog("retuning due to modification of channel %d (%s)", Channel->Number(), Channel->Name());
|
if (ChannelSaveTimeout == 1) {
|
||||||
Channels.SwitchTo(Channel->Number());
|
// Only one of them was modified, so we reset the state keys to handle them both in the next turn:
|
||||||
}
|
ChannelsStateKey.Reset();
|
||||||
}
|
TimersStateKey.Reset();
|
||||||
}
|
}
|
||||||
cStatus::MsgChannelChange(Channel);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
Channels.Unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Channel display:
|
// Channel display:
|
||||||
if (!EITScanner.Active() && cDevice::CurrentChannel() != LastChannel) {
|
if (!EITScanner.Active() && cDevice::CurrentChannel() != LastChannel) {
|
||||||
if (!Menu)
|
if (!Menu)
|
||||||
@ -1013,80 +1036,109 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
if (Now - LastChannelChanged >= Setup.ZapTimeout && LastChannel != PreviousChannel[PreviousChannelIndex])
|
if (Now - LastChannelChanged >= Setup.ZapTimeout && LastChannel != PreviousChannel[PreviousChannelIndex])
|
||||||
PreviousChannel[PreviousChannelIndex ^= 1] = LastChannel;
|
PreviousChannel[PreviousChannelIndex ^= 1] = LastChannel;
|
||||||
// Timers and Recordings:
|
{
|
||||||
if (!Timers.BeingEdited()) {
|
// Timers and Recordings:
|
||||||
// Assign events to timers:
|
bool TimersModified = false;
|
||||||
Timers.SetEvents();
|
bool TriggerRemoteTimerPoll = false;
|
||||||
// Must do all following calls with the exact same time!
|
static cStateKey TimersStateKey(true);
|
||||||
// Process ongoing recordings:
|
if (cTimers::GetTimersRead(TimersStateKey)) {
|
||||||
cRecordControls::Process(Now);
|
TriggerRemoteTimerPoll = true;
|
||||||
// Start new recordings:
|
TimersStateKey.Remove();
|
||||||
cTimer *Timer = Timers.GetMatch(Now);
|
}
|
||||||
if (Timer) {
|
cTimers *Timers = cTimers::GetTimersWrite(TimersStateKey);
|
||||||
if (!cRecordControls::Start(Timer))
|
// Get remote timers:
|
||||||
Timer->SetPending(true);
|
TimersModified |= Timers->GetRemoteTimers();
|
||||||
else
|
// Assign events to timers:
|
||||||
LastTimerChannel = Timer->Channel()->Number();
|
static cStateKey SchedulesStateKey;
|
||||||
}
|
if (const cSchedules *Schedules = cSchedules::GetSchedulesRead(SchedulesStateKey))
|
||||||
// Make sure timers "see" their channel early enough:
|
TimersModified |= Timers->SetEvents(Schedules);
|
||||||
static time_t LastTimerCheck = 0;
|
// Must do all following calls with the exact same time!
|
||||||
if (Now - LastTimerCheck > TIMERCHECKDELTA) { // don't do this too often
|
// Process ongoing recordings:
|
||||||
InhibitEpgScan = false;
|
if (cRecordControls::Process(Timers, Now)) {
|
||||||
for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
|
TimersModified = true;
|
||||||
bool InVpsMargin = false;
|
TriggerRemoteTimerPoll = true;
|
||||||
bool NeedsTransponder = false;
|
}
|
||||||
if (Timer->HasFlags(tfActive) && !Timer->Recording()) {
|
// Must keep the lock on the schedules until after processing the record
|
||||||
if (Timer->HasFlags(tfVps)) {
|
// controls, in order to avoid short interrupts in case the current event
|
||||||
if (Timer->Matches(Now, true, Setup.VpsMargin)) {
|
// is replaced by a new one (which some broadcasters do, instead of just
|
||||||
InVpsMargin = true;
|
// modifying the current event's data):
|
||||||
Timer->SetInVpsMargin(InVpsMargin);
|
if (SchedulesStateKey.InLock())
|
||||||
}
|
SchedulesStateKey.Remove();
|
||||||
else if (Timer->Event()) {
|
// Start new recordings:
|
||||||
InVpsMargin = Timer->Event()->StartTime() <= Now && Now < Timer->Event()->EndTime();
|
if (cTimer *Timer = Timers->GetMatch(Now)) {
|
||||||
NeedsTransponder = Timer->Event()->StartTime() - Now < VPSLOOKAHEADTIME * 3600 && !Timer->Event()->SeenWithin(VPSUPTODATETIME);
|
if (!cRecordControls::Start(Timers, Timer))
|
||||||
}
|
Timer->SetPending(true);
|
||||||
else {
|
else
|
||||||
cSchedulesLock SchedulesLock;
|
LastTimerChannel = Timer->Channel()->Number();
|
||||||
const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
|
TimersModified = true;
|
||||||
if (Schedules) {
|
TriggerRemoteTimerPoll = true;
|
||||||
const cSchedule *Schedule = Schedules->GetSchedule(Timer->Channel());
|
}
|
||||||
InVpsMargin = !Schedule; // we must make sure we have the schedule
|
// Make sure timers "see" their channel early enough:
|
||||||
NeedsTransponder = Schedule && !Schedule->PresentSeenWithin(VPSUPTODATETIME);
|
static time_t LastTimerCheck = 0;
|
||||||
}
|
if (Now - LastTimerCheck > TIMERCHECKDELTA) { // don't do this too often
|
||||||
}
|
InhibitEpgScan = false;
|
||||||
InhibitEpgScan |= InVpsMargin | NeedsTransponder;
|
for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
|
||||||
}
|
if (Timer->Remote())
|
||||||
else
|
continue;
|
||||||
NeedsTransponder = Timer->Matches(Now, true, TIMERLOOKAHEADTIME);
|
bool InVpsMargin = false;
|
||||||
}
|
bool NeedsTransponder = false;
|
||||||
if (NeedsTransponder || InVpsMargin) {
|
if (Timer->HasFlags(tfActive) && !Timer->Recording()) {
|
||||||
// Find a device that provides the required transponder:
|
if (Timer->HasFlags(tfVps)) {
|
||||||
cDevice *Device = cDevice::GetDeviceForTransponder(Timer->Channel(), MINPRIORITY);
|
if (Timer->Matches(Now, true, Setup.VpsMargin)) {
|
||||||
if (!Device && InVpsMargin)
|
InVpsMargin = true;
|
||||||
Device = cDevice::GetDeviceForTransponder(Timer->Channel(), LIVEPRIORITY);
|
Timer->SetInVpsMargin(InVpsMargin);
|
||||||
// Switch the device to the transponder:
|
}
|
||||||
if (Device) {
|
else if (Timer->Event()) {
|
||||||
bool HadProgramme = cDevice::PrimaryDevice()->HasProgramme();
|
InVpsMargin = Timer->Event()->StartTime() <= Now && Now < Timer->Event()->EndTime();
|
||||||
if (!Device->IsTunedToTransponder(Timer->Channel())) {
|
NeedsTransponder = Timer->Event()->StartTime() - Now < VPSLOOKAHEADTIME * 3600 && !Timer->Event()->SeenWithin(VPSUPTODATETIME);
|
||||||
if (Device == cDevice::ActualDevice() && !Device->IsPrimaryDevice())
|
}
|
||||||
cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode
|
else {
|
||||||
dsyslog("switching device %d to channel %d (%s)", Device->DeviceNumber() + 1, Timer->Channel()->Number(), Timer->Channel()->Name());
|
LOCK_SCHEDULES_READ;
|
||||||
if (Device->SwitchChannel(Timer->Channel(), false))
|
const cSchedule *Schedule = Schedules->GetSchedule(Timer->Channel());
|
||||||
Device->SetOccupied(TIMERDEVICETIMEOUT);
|
InVpsMargin = !Schedule; // we must make sure we have the schedule
|
||||||
}
|
NeedsTransponder = Schedule && !Schedule->PresentSeenWithin(VPSUPTODATETIME);
|
||||||
if (cDevice::PrimaryDevice()->HasDecoder() && HadProgramme && !cDevice::PrimaryDevice()->HasProgramme())
|
}
|
||||||
Skins.QueueMessage(mtInfo, tr("Upcoming recording!")); // the previous SwitchChannel() has switched away the current live channel
|
InhibitEpgScan |= InVpsMargin | NeedsTransponder;
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
}
|
NeedsTransponder = Timer->Matches(Now, true, TIMERLOOKAHEADTIME);
|
||||||
LastTimerCheck = Now;
|
}
|
||||||
}
|
if (NeedsTransponder || InVpsMargin) {
|
||||||
// Delete expired timers:
|
// Find a device that provides the required transponder:
|
||||||
Timers.DeleteExpired();
|
cDevice *Device = cDevice::GetDeviceForTransponder(Timer->Channel(), MINPRIORITY);
|
||||||
}
|
if (!Device && InVpsMargin)
|
||||||
if (!Menu && Recordings.NeedsUpdate()) {
|
Device = cDevice::GetDeviceForTransponder(Timer->Channel(), LIVEPRIORITY);
|
||||||
Recordings.Update();
|
// Switch the device to the transponder:
|
||||||
DeletedRecordings.Update();
|
if (Device) {
|
||||||
|
bool HadProgramme = cDevice::PrimaryDevice()->HasProgramme();
|
||||||
|
if (!Device->IsTunedToTransponder(Timer->Channel())) {
|
||||||
|
if (Device == cDevice::ActualDevice() && !Device->IsPrimaryDevice())
|
||||||
|
cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode
|
||||||
|
dsyslog("switching device %d to channel %d (%s)", Device->DeviceNumber() + 1, Timer->Channel()->Number(), Timer->Channel()->Name());
|
||||||
|
if (Device->SwitchChannel(Timer->Channel(), false))
|
||||||
|
Device->SetOccupied(TIMERDEVICETIMEOUT);
|
||||||
|
}
|
||||||
|
if (cDevice::PrimaryDevice()->HasDecoder() && HadProgramme && !cDevice::PrimaryDevice()->HasProgramme())
|
||||||
|
Skins.QueueMessage(mtInfo, tr("Upcoming recording!")); // the previous SwitchChannel() has switched away the current live channel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LastTimerCheck = Now;
|
||||||
|
}
|
||||||
|
// Delete expired timers:
|
||||||
|
if (Timers->DeleteExpired()) {
|
||||||
|
TimersModified = true;
|
||||||
|
TriggerRemoteTimerPoll = true;
|
||||||
|
}
|
||||||
|
// Trigger remote timer polls:
|
||||||
|
if (TriggerRemoteTimerPoll)
|
||||||
|
Timers->TriggerRemoteTimerPoll();
|
||||||
|
TimersStateKey.Remove(TimersModified);
|
||||||
|
}
|
||||||
|
// Recordings:
|
||||||
|
if (!Menu) {
|
||||||
|
if (cRecordings::NeedsUpdate())
|
||||||
|
cRecordings::Update();
|
||||||
}
|
}
|
||||||
// CAM control:
|
// CAM control:
|
||||||
if (!Menu && !cOsd::IsOpen())
|
if (!Menu && !cOsd::IsOpen())
|
||||||
@ -1359,7 +1411,8 @@ int main(int argc, char *argv[])
|
|||||||
case k0: {
|
case k0: {
|
||||||
if (PreviousChannel[PreviousChannelIndex ^ 1] == LastChannel || LastChannel != PreviousChannel[0] && LastChannel != PreviousChannel[1])
|
if (PreviousChannel[PreviousChannelIndex ^ 1] == LastChannel || LastChannel != PreviousChannel[0] && LastChannel != PreviousChannel[1])
|
||||||
PreviousChannelIndex ^= 1;
|
PreviousChannelIndex ^= 1;
|
||||||
Channels.SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]);
|
LOCK_CHANNELS_READ;
|
||||||
|
Channels->SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Direct Channel Select:
|
// Direct Channel Select:
|
||||||
@ -1447,7 +1500,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
// Disk housekeeping:
|
// Disk housekeeping:
|
||||||
RemoveDeletedRecordings();
|
RemoveDeletedRecordings();
|
||||||
ClearVanishedRecordings();
|
ListGarbageCollector.Purge();
|
||||||
cSchedules::Cleanup();
|
cSchedules::Cleanup();
|
||||||
// Plugins housekeeping:
|
// Plugins housekeeping:
|
||||||
PluginManager.Housekeeping();
|
PluginManager.Housekeeping();
|
||||||
@ -1492,8 +1545,9 @@ Exit:
|
|||||||
cPositioner::DestroyPositioner();
|
cPositioner::DestroyPositioner();
|
||||||
cVideoDirectory::Destroy();
|
cVideoDirectory::Destroy();
|
||||||
EpgHandlers.Clear();
|
EpgHandlers.Clear();
|
||||||
PluginManager.Shutdown(true);
|
|
||||||
cSchedules::Cleanup(true);
|
cSchedules::Cleanup(true);
|
||||||
|
ListGarbageCollector.Purge(true);
|
||||||
|
PluginManager.Shutdown(true);
|
||||||
ReportEpgBugFixStats(true);
|
ReportEpgBugFixStats(true);
|
||||||
if (WatchdogTimeout > 0)
|
if (WatchdogTimeout > 0)
|
||||||
dsyslog("max. latency time %d seconds", MaxLatencyTime);
|
dsyslog("max. latency time %d seconds", MaxLatencyTime);
|
||||||
|
17
videodir.c
17
videodir.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: videodir.c 3.4 2013/10/11 09:38:07 kls Exp $
|
* $Id: videodir.c 4.1 2015/08/11 13:39:59 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "videodir.h"
|
#include "videodir.h"
|
||||||
@ -19,24 +19,31 @@
|
|||||||
#include "recording.h"
|
#include "recording.h"
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
|
|
||||||
|
cMutex cVideoDirectory::mutex;
|
||||||
cString cVideoDirectory::name;
|
cString cVideoDirectory::name;
|
||||||
cVideoDirectory *cVideoDirectory::current = NULL;
|
cVideoDirectory *cVideoDirectory::current = NULL;
|
||||||
|
|
||||||
cVideoDirectory::cVideoDirectory(void)
|
cVideoDirectory::cVideoDirectory(void)
|
||||||
{
|
{
|
||||||
|
mutex.Lock();
|
||||||
delete current;
|
delete current;
|
||||||
current = this;
|
current = this;
|
||||||
|
mutex.Unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
cVideoDirectory::~cVideoDirectory()
|
cVideoDirectory::~cVideoDirectory()
|
||||||
{
|
{
|
||||||
|
mutex.Lock();
|
||||||
current = NULL;
|
current = NULL;
|
||||||
|
mutex.Unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
cVideoDirectory *cVideoDirectory::Current(void)
|
cVideoDirectory *cVideoDirectory::Current(void)
|
||||||
{
|
{
|
||||||
|
mutex.Lock();
|
||||||
if (!current)
|
if (!current)
|
||||||
current = new cVideoDirectory;
|
new cVideoDirectory;
|
||||||
|
mutex.Unlock();
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +148,8 @@ int cVideoDirectory::VideoDiskSpace(int *FreeMB, int *UsedMB)
|
|||||||
{
|
{
|
||||||
int used = 0;
|
int used = 0;
|
||||||
int free = Current()->FreeMB(&used);
|
int free = Current()->FreeMB(&used);
|
||||||
int deleted = DeletedRecordings.TotalFileSizeMB();
|
LOCK_DELETEDRECORDINGS_READ;
|
||||||
|
int deleted = DeletedRecordings->TotalFileSizeMB();
|
||||||
if (deleted > used)
|
if (deleted > used)
|
||||||
deleted = used; // let's not get beyond 100%
|
deleted = used; // let's not get beyond 100%
|
||||||
free += deleted;
|
free += deleted;
|
||||||
@ -202,7 +210,8 @@ bool cVideoDiskUsage::HasChanged(int &State)
|
|||||||
if (FreeMB != freeMB) {
|
if (FreeMB != freeMB) {
|
||||||
usedPercent = UsedPercent;
|
usedPercent = UsedPercent;
|
||||||
freeMB = FreeMB;
|
freeMB = FreeMB;
|
||||||
double MBperMinute = Recordings.MBperMinute();
|
LOCK_RECORDINGS_READ;
|
||||||
|
double MBperMinute = Recordings->MBperMinute();
|
||||||
if (MBperMinute <= 0)
|
if (MBperMinute <= 0)
|
||||||
MBperMinute = MB_PER_MINUTE;
|
MBperMinute = MB_PER_MINUTE;
|
||||||
freeMinutes = int(double(FreeMB) / MBperMinute);
|
freeMinutes = int(double(FreeMB) / MBperMinute);
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: videodir.h 3.2 2013/10/11 09:37:48 kls Exp $
|
* $Id: videodir.h 4.1 2015/08/10 13:21:29 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __VIDEODIR_H
|
#ifndef __VIDEODIR_H
|
||||||
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
class cVideoDirectory {
|
class cVideoDirectory {
|
||||||
private:
|
private:
|
||||||
|
static cMutex mutex;
|
||||||
static cString name;
|
static cString name;
|
||||||
static cVideoDirectory *current;
|
static cVideoDirectory *current;
|
||||||
static cVideoDirectory *Current(void);
|
static cVideoDirectory *Current(void);
|
||||||
|
Loading…
Reference in New Issue
Block a user