mirror of
				https://github.com/vdr-projects/vdr.git
				synced 2025-03-01 10:50:46 +00:00 
			
		
		
		
	Implemented strict locking of global lists
This commit is contained in:
		
							
								
								
									
										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); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user