diff -ruN vdr-2.2.0/CONTRIBUTORS vdr-2.3.1/CONTRIBUTORS
--- vdr-2.2.0/CONTRIBUTORS	2015-02-19 09:20:08.000000000 +0100
+++ vdr-2.3.1/CONTRIBUTORS	2015-09-11 12:00:22.000000000 +0200
@@ -714,6 +714,7 @@
  to detect the frame type
  for suggesting to ignore channels with an RID that is not 0 when checking for obsolete
  channels
+ for fixing a possible stack overflow in cListBase::Sort()
 
 Reinhard Walter Buchner <rw.buchner@freenet.de>
  for adding some satellites to 'sources.conf'
@@ -2842,6 +2843,7 @@
  for reporting a missing template for DVBDIR in Make.config.template
  for suggesting to add ARGSDIR to the ONEDIR section of Make.config.template
  for suggesting to change the naming of "binary skip mode" to "adaptive skip mode"
+ for suggesting to make the -u option also accept a numerical user id
 
 Marcel Unbehaun <frostworks@gmx.de>
  for adding cRecordingInfo::GetEvent()
@@ -2964,6 +2966,7 @@
  for reporting a possible crash in the OSD demo
  for adding support for systemd
  for suggesting to replace VDR_CHARSET_OVERRIDE with a command line option
+ for making the recorder skip empty adaptation field TS packets
 
 Stefan Huskamp <coca_cola1@gmx.de>
  for suggesting to make entering characters via the number keys
@@ -3003,6 +3006,8 @@
  for reporting a problem with tuning timeouts when using SCR with multiple tuners
  for fixing the German translation of "VDR will shut down in %s minutes"
  for adding support for "Satellite Channel Routing" (SCR) according to EN50607 ("JESS")
+ for suggesting to make the Setup/CAM menu display which device an individual CAM
+ is currently assigned to
 
 Gerald Dachs <vdr@dachsweb.de>
  for reporting a problem with checking for minimum line length of 21 characters in
@@ -3262,6 +3267,7 @@
  for suggesting to add the menu category mcRecordingEdit for marking menus that edit
  recording properties
  for suggesting to make cRecording::GetResume() public
+ for implementing the possibility for skins to display horizontal menus
 
 Jochen Dolze <vdr@dolze.de>
  for changing cThread::SetIOPriority() from "best effort class" to "idle class" in order
@@ -3311,6 +3317,11 @@
  skipping"
  for suggesting to change the return value of cOsd::RenderPixmaps() from cPixmapMemory
  to cPixmap
+ for adding detection of 24fps
+ for suggesting to add some comment to cPixmap about the relation between OSD,
+ ViewPort and DrawPort
+ for suggesting to reduce the priority of the "video directory scanner" thread
+ for making the 'newplugin' script create the 'po' subdirectory for translations
 
 Eike Sauer <EikeSauer@t-online.de>
  for reporting a problem with channels that need more than 5 TS packets for detecting
@@ -3345,6 +3356,9 @@
  for reporting a problem that led to a fix with EMM pids not being properly reset for
  CAMs that need to receive the TS
  for suggesting to add the channel name to log messages that reference a channel
+ for suggesting to provide a way of using no DVB devices at all
+ for suggesting that the -V and -h options should list the plugins in alphabetical order
+ for suggesting to implement the setup option "Recording/Record key handling"
 
 Stefan Schallenberg <infos@nafets.de>
  for adding the functions IndexOf(), InsertUnique(), AppendUnique() and RemoveElement()
@@ -3359,6 +3373,8 @@
  case replay was paused
  for reporting a problem with the system getting unresponsive when removing a huge
  number of files in the thread that removes deleted recordings
+ for suggesting to call the script that gets called for recordings also right before a
+ recording is edited
 
 Jasmin Jessich <jasmin@anw.at>
  for modifying the CAM API so that it is possible to implement CAMs that can be freely
@@ -3400,3 +3416,15 @@
 
 Gabriel Bonich <gbonich@gmail.com>
  for translating OSD texts to the Spanish language
+
+Daniel Ribeiro <drwyrm@gmail.com>
+ for reporting a problem with setting the source value of newly created channels, in
+ case the NIT is received from a different, but very close satellite position, and
+ for helping to debug this
+
+Janne P�nk�l� <epankala@gmail.com>
+ for reporting that some broadcasters use the character 0x0D in EPG texts
+
+Stefan P�schel <basic.master@gmx.de>
+ for coding the AFFcleaner, parts of which were used to make the recorder skip empty
+ adaptation field TS packets
diff -ruN vdr-2.2.0/Doxyfile.filter vdr-2.3.1/Doxyfile.filter
--- vdr-2.2.0/Doxyfile.filter	2013-02-17 11:54:05.000000000 +0100
+++ vdr-2.3.1/Doxyfile.filter	2013-02-17 11:54:05.000000000 +0100
@@ -9,7 +9,7 @@
 # See the main source file 'vdr.c' for copyright information and
 # how to reach the author.
 #
-# $Id: Doxyfile.filter 3.0 2013/02/17 10:54:05 kls Exp $
+# $Id: Doxyfile.filter 4.0 2013/02/17 10:54:05 kls Exp $
 
 $TAG = "///<";
 
diff -ruN vdr-2.2.0/HISTORY vdr-2.3.1/HISTORY
--- vdr-2.2.0/HISTORY	2015-02-19 10:17:46.000000000 +0100
+++ vdr-2.3.1/HISTORY	2015-09-14 16:11:00.000000000 +0200
@@ -8595,3 +8595,235 @@
 - Fixed leading/trailing/multiple blanks in the translation files.
 - Bumped all version numbers to 2.2.0.
 - Official release.
+
+2015-09-14: Version 2.3.1
+
+- The new function cOsd::MaxPixmapSize() can be called to determine the maximum size
+  a cPixmap may have on the current OSD. The 'osddemo' example has been modified
+  accordingly. Plugin authors may want to use this function in case they use pixmaps
+  that are larger than the full OSD size. The default implementation sets this limit
+  to 2048x2048 pixel.
+- The Setup/CAM menu now displays which device an individual CAM is currently
+  assigned to (suggested by Frank Neumann).
+- Added detection of 24fps (thanks to Thomas Reufer).
+- Added a note about the VDR User Counter and VDR's facebook page to the README file.
+- The dvbhddevice plugin is no longer part of the VDR source archive.
+  You can get the latest version of this plugin from the author's repository at
+  https://bitbucket.org/powARman/dvbhddevice.
+- The dvbsddevice and rcu plugins are no longer part of the VDR source archive.
+  You can get the latest versions of these plugins from ftp://ftp.tvdr.de/vdr/Plugins.
+- Added a section about Output Devices to the INSTALL file.
+- Fixed setting the source value of newly created channels, in case the NIT is
+  received from a different, but very close satellite position (reported by Daniel
+  Ribeiro). The code for handling different NITs has been removed from nit.c, because
+  according to the DVB standard table id 0x40 carries only the NIT of the actual
+  network.
+- Added some comment to cPixmap about the relation between OSD, ViewPort and DrawPort
+  (suggested by Thomas Reufer).
+- Improved syncing on sections when parsing the NIT and SDT.
+- Fixed scaling subtitles (their areas could sometimes extend outside the actual OSD).
+- Reduced the priority of the "video directory scanner" thread (suggested by Thomas
+  Reufer) and checking cIoThrottle::Engaged() when it is running.
+- The script that gets called for recordings is now also called right before a
+  recording is edited, with the first parameter being "editing" (suggested by
+  Dieter Ferdinand).
+- The new setup option "OSD/Default sort mode for recordings" can be used to define
+  how recordings shall be sorted by default (either by time or by name, with "by time"
+  being the default). If a particular sort mode has been selected for a folder by
+  pressing '0', the default no longer applies to that folder. Repeating timers no
+  longer write a ".sort" file into a recordings folder to have the recordings sorted
+  by time.
+- The command line option -D now accepts the value '-' (as in -D-), which prevents
+  VDR from using any DVB devices (suggested by Dietmar Spingler).
+- The -V and -h options now list the plugins in alphabetical order (suggested by
+  Dietmar Spingler).
+- Fixed a compiler warning in font.c.
+- Commented out the line
+  #define DEPRECATED_VIDEOSYSTEM
+  in device.h. If a plugin doesn't compile with this version of VDR, you can uncomment
+  this line as a quick workaround. In the long run the plugin will need to be adapted.
+- The function cOsd::GetBitmap() is now 'protected'. If a plugin doesn't compile with
+  this version of VDR, you can uncomment the line
+  //#define DEPRECATED_GETBITMAP
+  in osd.h as a quick workaround. In the long run the plugin will need to be adapted.
+- The -u option now also accepts a numerical user id (suggested by Derek Kelly).
+- The SVDRP port now accepts multiple concurrent connections. You can now keep an
+  SVDRP connection open as long as you wish, without preventing others from
+  connecting. Note, though, that SVDRP connections still get closed automatically
+  if there has been no activity for 300 seconds (configurable via
+  "Setup/Miscellaneous/SVDRP timeout (s)").
+- The SVDRP log messages have been unified and now always contain the IP and port
+  number of the remote host.
+- SVDRP connections are now handled in a separate "SVDRP server handler" thread,
+  which makes them more responsive. Note that there is only one thread that handles
+  all concurrent SVDRP connections. That way each SVDRP command is guaranteed to be
+  processed separately, without interfering with any other SVDRP commands that might
+  be issued at the same time. Plugins that implement SVDRP commands may need to take
+  care of proper locking if the commands access global data.
+- VDR now sends out a broadcast to port 6419/udp, which was assigned to 'svdrp-disc'
+  by the IANA. VDRs listening on that port will automatically initiate an SVDRP
+  connection to the broadcasting VDR, and in turn send out a broadcast to make
+  other VDRs connect to them. That way all VDRs within the local network will
+  have permanent "peer-to-peer" SVDRP connections between each other. The
+  configuration in the svdrphosts.conf file is taken into account when considering
+  whether or not to respond to an SVDRP discover broadcast.
+- The new SVDRP command PING is used by automatically established peer-to-peer
+  connections to keep them alive.
+- The new function GetSVDRPServerNames() can be used to get a list of all VDRs
+  this VDR is connected to via SVDRP.
+- The new function ExecSVDRPCommand() can be used to execute an SVDRP command on
+  one of the servers this VDR is connected to, and retrieve the result.
+  The helper functions SVDRPCode() and SVDRPValue() can be used to easily access
+  the codes and values returned by ExecSVDRPCommand().
+- 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.
+- Added a missing 'const' to cReceiver::Receive(), to protect the given Data from
+  being modified.
+- The SVDRP commands that deal with timers (DELT, LSTT, MODT, NEWT, NEXT and UPDT)
+  as well as any log messages that refer to timers, now use a unique id for each
+  timer, which remains valid as long as this instance of VDR is running. This means
+  that timers are no longer continuously numbered from 1 to N in LSTT. There may be
+  gaps in the sequence, in case timers have been deleted.
+- The Timers menu now displays the name of the remote VDR in front of the timer's
+  file name, if this is a remote timer.
+- The new options "Setup/Miscellaneous/SVDRP peering", ".../SVDRP host name" and
+  ".../SVDRP default host" can be used to configure automatic peering between VDRs
+  in the same network. Peering is disabled by default and can be enabled by setting
+  "SVDRP peering" to "yes".
+- The function cTimer::ToText() no longer returns a newline character at the end of
+  the string. The newline is now added by the caller as necessary. This was changed
+  because cTimer::ToText() is now also needed in a context where the terminating
+  newline can't be used. Consequently, cChannel::ToText() and cMark::ToText() have
+  been modified accordingly.
+- All timer related response strings from SVDRP commands now use the channel ID
+  instead of channel numbers.
+- The "Edit timer" menu now has a new parameter "Record on", which can be used to
+  select the VDR on which this timer shall record. Timers can be freely moved
+  between connected VDRs by simply selecting the desired machine in this field.
+- The SVDRP command DELT no longer checks whether the timer that shall be deleted
+  is currently recording.
+- The character 0x0D is now stripped from EPG texts (reported by Janne P�nk�l�).
+- The EPG scanner no longer moves the dish if there is a positioner.
+- The 'newplugin' script now creates the 'po' subdirectory for translations (thanks
+  to Thomas Reufer).
+- Skins can now implement cSkinDisplayMenu::MenuOrientation() to display horizontal
+  menus (thanks to Stefan Braun).
+- Fixed a possible stack overflow in cListBase::Sort() (thanks to Oliver Endriss).
+- Changed the description of the --chartab option in the INSTALL file to refer to
+  "DVB SI table strings" instead of "EPG data".
+- The width and height of the OSD are now limited to the actual maximum dimensions
+  of the output device, taking into account the top and left offset.
+- The new setup option "Recording/Record key handling" can be used to define
+  what happens if the Record key on the remote control is pressed during
+  live tv (suggested by Dietmar Spingler).
+- Empty adaptation field TS packets are now skipped when recording (thanks to
+  Christopher Reimer, based on the "AFFcleaner" by Stefan P�schel).
diff -ruN vdr-2.2.0/INSTALL vdr-2.3.1/INSTALL
--- vdr-2.2.0/INSTALL	2015-02-17 14:47:52.000000000 +0100
+++ vdr-2.3.1/INSTALL	2015-09-10 15:34:51.000000000 +0200
@@ -94,6 +94,24 @@
 
 See the man page vdr(1) for complete information about all command line options.
 
+Output devices
+--------------
+
+VDR by itself doesn't produce any audio or video output. In order to watch
+live tv or recordings, you will need to use a plugin that supports the actual
+hardware in your system, for instance:
+
+Plugin:           Device:
+
+dvbsddevice       Full-Featured SD DVB cards (Fujitsu-Siemens Design)
+                  (comes with the VDR source)
+dvbhddevice       Full-featured HD DVB cards (Technotrend TT S2-6400)
+                  https://bitbucket.org/powARman/dvbhddevice
+rpihddevice       Raspberry Pi
+                  http://projects.vdr-developer.org/git/vdr-plugin-rpihddevice.git
+
+See http://linuxtv.org/vdrwiki/index.php/Output_devices for more.
+
 Standard compliance
 -------------------
 
@@ -134,10 +152,10 @@
 additional memory allocation and copying, this feature is not compiled in
 by default, so that users that have no need for this don't get any overhead.
 
-Workaround for providers not encoding their EPG data correctly
---------------------------------------------------------------
+Workaround for providers not encoding their DVB SI table strings correctly
+--------------------------------------------------------------------------
 
-According to "ETSI EN 300 468" the default character set fo SI data is
+According to "ETSI EN 300 468" the default character set for SI data is
 ISO6937. But unfortunately some broadcasters actually use ISO-8859-9 or
 other encodings, but fail to correctly announce that.
 Users who want to set the default character set to something different can
@@ -250,19 +268,20 @@
 before and after a recording is performed, and after an editing process
 has finished or a recording has been deleted.
 
-The program will be called with two or three (in case of "edited") string
-parameters. The first parameter is one of
+The program will be called with two or three (in case of "editing" and "edited")
+string parameters. The first parameter is one of
 
   before      if this is *before* a recording starts
   started     if this is after a recording has *started*
   after       if this is *after* a recording has finished
+  editing     if this is before *editing* a recording
   edited      if this is after a recording has been *edited*
   deleted     if this is after a recording has been *deleted*
 
 and the second parameter contains the full name of the recording's
 directory (which may not yet exists at that moment in the "before" case).
-In the "edited" case it will be the name of the edited version (second
-parameter) and the name of the source version (third parameter).
+In the "editing" and "edited" case it will be the name of the edited version
+(second parameter) and the name of the source version (third parameter).
 In the "deleted" case the extension of the directory name is ".del"
 instead of ".rec".
 
@@ -286,6 +305,10 @@
      after)
             echo "After recording $2"
             ;;
+     editing)
+            echo "Editing recording $2"
+            echo "Source recording $3"
+            ;;
      edited)
             echo "Edited recording $2"
             echo "Source recording $3"
diff -ruN vdr-2.2.0/MANUAL vdr-2.3.1/MANUAL
--- vdr-2.2.0/MANUAL	2015-02-17 14:48:38.000000000 +0100
+++ vdr-2.3.1/MANUAL	2015-09-11 10:29:41.000000000 +0200
@@ -498,6 +498,9 @@
              the name of the recording.
   First day: The date of the first day when this timer shall start recording
              (only available for repeating timers).
+  Record on: The name of the remote VDR this timer shall record on (only available
+             if there are any remote VDRs connected to this VDR). If this field
+             is empty, the timer will record on the local VDR.
 
   A timer can also be programmed by pressing the "Red" key on the "Schedule",
   "Now", "Next" or "Event" menus.
@@ -654,6 +657,11 @@
                          plain recordings. Set this option to "no" if you want folders
                          to be interspersed with recordings when sorted alphabetically.
 
+  Default sort mode for recordings = by time
+                         Controls whether recordings are sorted by time or by name.
+                         If a particular sort mode has been selected for a folder by
+                         pressing '0', the default no longer applies to that folder.
+
   Number keys for characters = yes
                          Controls whether the number keys can be used to enter
                          characters in a text input field. You may want to set this
@@ -882,8 +890,13 @@
                          means that this recording will never be deleted
                          automatically.
 
-  Pause priority = 10    The Priority and Lifetime values used when pausing live
-  Pause lifetime = 1     video.
+  Record key handling = 2
+                         Defines what happens if the Record key on the remote control
+                         is pressed during live tv.
+                         0 = no instant recording
+                         1 = confirm instant recording
+                         2 = record instantly
+                         The default is 2.
 
   Pause key handling = 2 Defines what happens if the Pause key on the remote control
                          is pressed during live tv.
@@ -892,6 +905,9 @@
                          2 = pause live video
                          The default is 2.
 
+  Pause priority = 10    The Priority and Lifetime values used when pausing live
+  Pause lifetime = 1     video.
+
   Use episode name = yes Repeating timers use the EPG's 'Episode name' information
                          to create recording file names in a hierarchical structure
                          (for instance to gather all episodes of a series in a
@@ -1066,6 +1082,15 @@
                          connection after which the connection is automatically
                          closed. Default is 300, a value of 0 means no timeout.
 
+  SVDRP peering = no     Activates automatic connections between VDRs in the same
+                         network.
+
+  SVDRP host name        The name of this VDR, which is used when connecting VDRs
+                         via SVDRP. By default, the machine's host name is used.
+
+  SVDRP default host     The name of the VDR to be used by default when creating a
+                         new timer.
+
   Zap timeout = 3        The time (in seconds) until a channel counts as "previous"
                          for switching with '0'
 
diff -ruN vdr-2.2.0/Make.config.template vdr-2.3.1/Make.config.template
--- vdr-2.2.0/Make.config.template	2015-02-09 10:58:45.000000000 +0100
+++ vdr-2.3.1/Make.config.template	2015-02-09 10:58:45.000000000 +0100
@@ -6,7 +6,7 @@
 # See the main source file 'vdr.c' for copyright information and
 # how to reach the author.
 #
-# $Id: Make.config.template 3.4 2015/02/09 09:58:45 kls Exp $
+# $Id: Make.config.template 4.0 2015/02/09 09:58:45 kls Exp $
 
 ### The C compiler and options:
 
diff -ruN vdr-2.2.0/Make.global vdr-2.3.1/Make.global
--- vdr-2.2.0/Make.global	2012-12-29 13:03:01.000000000 +0100
+++ vdr-2.3.1/Make.global	2012-12-29 13:03:01.000000000 +0100
@@ -4,7 +4,7 @@
 # See the main source file 'vdr.c' for copyright information and
 # how to reach the author.
 #
-# $Id: Make.global 3.0 2012/12/29 12:03:01 kls Exp $
+# $Id: Make.global 4.0 2012/12/29 12:03:01 kls Exp $
 
 # This is just a dummy file for plugins that use old style (version 1.7.33
 # or earlier) Makefiles.
diff -ruN vdr-2.2.0/Makefile vdr-2.3.1/Makefile
--- vdr-2.2.0/Makefile	2015-02-09 13:28:24.000000000 +0100
+++ vdr-2.3.1/Makefile	2015-02-09 13:28:24.000000000 +0100
@@ -4,7 +4,7 @@
 # See the main source file 'vdr.c' for copyright information and
 # how to reach the author.
 #
-# $Id: Makefile 3.6 2015/02/09 12:28:24 kls Exp $
+# $Id: Makefile 4.0 2015/02/09 12:28:24 kls Exp $
 
 .DELETE_ON_ERROR:
 
diff -ruN vdr-2.2.0/PLUGINS/src/epgtableid0/Makefile vdr-2.3.1/PLUGINS/src/epgtableid0/Makefile
--- vdr-2.2.0/PLUGINS/src/epgtableid0/Makefile	2014-01-01 14:29:54.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/epgtableid0/Makefile	2014-01-01 14:29:54.000000000 +0100
@@ -1,7 +1,7 @@
 #
 # Makefile for a Video Disk Recorder plugin
 #
-# $Id: Makefile 3.1 2014/01/01 13:29:54 kls Exp $
+# $Id: Makefile 4.0 2014/01/01 13:29:54 kls Exp $
 
 # The official name of this plugin.
 # This name will be used in the '-P...' option of VDR to load the plugin.
diff -ruN vdr-2.2.0/PLUGINS/src/epgtableid0/epgtableid0.c vdr-2.3.1/PLUGINS/src/epgtableid0/epgtableid0.c
--- vdr-2.2.0/PLUGINS/src/epgtableid0/epgtableid0.c	2015-02-17 14:12:18.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/epgtableid0/epgtableid0.c	2015-02-17 14:12:18.000000000 +0100
@@ -3,7 +3,7 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: epgtableid0.c 3.2 2015/02/17 13:12:18 kls Exp $
+ * $Id: epgtableid0.c 4.0 2015/02/17 13:12:18 kls Exp $
  */
 
 #include <vdr/epg.h>
diff -ruN vdr-2.2.0/PLUGINS/src/hello/Makefile vdr-2.3.1/PLUGINS/src/hello/Makefile
--- vdr-2.2.0/PLUGINS/src/hello/Makefile	2014-01-01 14:29:54.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/hello/Makefile	2014-01-01 14:29:54.000000000 +0100
@@ -1,7 +1,7 @@
 #
 # Makefile for a Video Disk Recorder plugin
 #
-# $Id: Makefile 3.1 2014/01/01 13:29:54 kls Exp $
+# $Id: Makefile 4.0 2014/01/01 13:29:54 kls Exp $
 
 # The official name of this plugin.
 # This name will be used in the '-P...' option of VDR to load the plugin.
diff -ruN vdr-2.2.0/PLUGINS/src/hello/hello.c vdr-2.3.1/PLUGINS/src/hello/hello.c
--- vdr-2.2.0/PLUGINS/src/hello/hello.c	2015-02-17 14:12:26.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/hello/hello.c	2015-02-17 14:12:26.000000000 +0100
@@ -3,7 +3,7 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: hello.c 3.2 2015/02/17 13:12:26 kls Exp $
+ * $Id: hello.c 4.0 2015/02/17 13:12:26 kls Exp $
  */
 
 #include <getopt.h>
diff -ruN vdr-2.2.0/PLUGINS/src/osddemo/HISTORY vdr-2.3.1/PLUGINS/src/osddemo/HISTORY
--- vdr-2.2.0/PLUGINS/src/osddemo/HISTORY	2015-02-17 14:47:01.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/osddemo/HISTORY	2015-04-12 12:37:36.000000000 +0200
@@ -72,3 +72,10 @@
 2015-02-19: Version 2.2.0
 
 - Official release.
+
+2015-03-08: Version 2.3.1
+
+- Now using cOsd::MaxPixmapSize().
+- Fixed a vertical black line in the "TiledPixmaps" area on the rpihddevice OSD with
+  1280x800 pixel (thanks to Thomas Reufer).
+- Added a demo case for storing images (thanks to Thomas Reufer).
diff -ruN vdr-2.2.0/PLUGINS/src/osddemo/Makefile vdr-2.3.1/PLUGINS/src/osddemo/Makefile
--- vdr-2.2.0/PLUGINS/src/osddemo/Makefile	2014-01-01 14:29:54.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/osddemo/Makefile	2014-01-01 14:29:54.000000000 +0100
@@ -1,7 +1,7 @@
 #
 # Makefile for a Video Disk Recorder plugin
 #
-# $Id: Makefile 3.1 2014/01/01 13:29:54 kls Exp $
+# $Id: Makefile 4.0 2014/01/01 13:29:54 kls Exp $
 
 # The official name of this plugin.
 # This name will be used in the '-P...' option of VDR to load the plugin.
diff -ruN vdr-2.2.0/PLUGINS/src/osddemo/osddemo.c vdr-2.3.1/PLUGINS/src/osddemo/osddemo.c
--- vdr-2.2.0/PLUGINS/src/osddemo/osddemo.c	2015-02-17 14:12:36.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/osddemo/osddemo.c	2015-04-12 11:35:21.000000000 +0200
@@ -3,13 +3,13 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: osddemo.c 3.4 2015/02/17 13:12:36 kls Exp $
+ * $Id: osddemo.c 4.3 2015/04/12 09:35:21 kls Exp $
  */
 
 #include <vdr/osd.h>
 #include <vdr/plugin.h>
 
-static const char *VERSION        = "2.2.0";
+static const char *VERSION        = "2.3.1";
 static const char *DESCRIPTION    = "Demo of arbitrary OSD setup";
 static const char *MAINMENUENTRY  = "Osd Demo";
 
@@ -90,6 +90,52 @@
   Osd->Flush();
 }
 
+// --- DrawImages ------------------------------------------------------------
+
+struct tOsdImageRef {
+  int image;
+  cSize size;
+  };
+
+#define NUMOSDIMAGES 16
+#define NUMOSDIMAGEVARIANTS 8
+
+void DrawImages(cOsd *Osd)
+{
+  // Create images:
+  cImage *images[NUMOSDIMAGEVARIANTS];
+  for (int i = 0; i < NUMOSDIMAGEVARIANTS; i++) {
+      images[i] = new cImage(cSize(
+        i == 0 || i == 1 ? Osd->MaxPixmapSize().Width() + 1 : rand() % Osd->Width(),
+        i == 0 || i == 2 ? Osd->MaxPixmapSize().Height() + 1 : rand() % Osd->Height()));
+      for (int x = 0; x < images[i]->Width(); x++) {
+          for (int y = 0; y < images[i]->Height(); y++) {
+              images[i]->SetPixel(cPoint(x, y),
+                (!x || !y || x == images[i]->Width() - 1 || y == images[i]->Height() - 1) ? clrWhite :
+                (x > images[i]->Width() / 2 ?
+                  (y > images[i]->Height() / 2 ? clrBlue : clrGreen) :
+                  (y > images[i]->Height() / 2 ? clrRed : clrYellow)));
+              }
+          }
+      }
+  // Store images:
+  tOsdImageRef osdImages[NUMOSDIMAGES];
+  for (int i = 0; i < NUMOSDIMAGES; i++) {
+      osdImages[i].image = cOsdProvider::StoreImage(*images[i % NUMOSDIMAGEVARIANTS]);
+      osdImages[i].size.Set(images[i % NUMOSDIMAGEVARIANTS]->Size());
+      }
+  // Delete images:
+  for (int i = 0; i < NUMOSDIMAGEVARIANTS; i++)
+      delete images[i];
+  // Draw images:
+  for (int i = 0; i < NUMOSDIMAGES; i++)
+      Osd->DrawImage(cPoint(rand() % (Osd->Width() + osdImages[i].size.Width()), rand() % (Osd->Height() + osdImages[i].size.Height())).Shifted(-osdImages[i].size.Width(), -osdImages[i].size.Height()), osdImages[i].image);
+  // Drop image references:
+  for (int i = 0; i < NUMOSDIMAGES; i++)
+      cOsdProvider::DropImage(osdImages[i].image);
+  Osd->Flush();
+}
+
 // --- cLineGame -------------------------------------------------------------
 
 class cLineGame : public cOsdObject {
@@ -210,7 +256,7 @@
   if (Pixmap) {
      Pixmap->Clear();
      Pixmap->SetAlpha(0);
-     Pixmap->DrawText(cPoint(0, 0), s, ColorFg, ColorBg, Font);
+     Pixmap->DrawText(cPoint(0, 0), s, ColorFg, ColorBg, Font, w);
      }
   return Pixmap;
 }
@@ -427,16 +473,20 @@
                        const int Size = SmlFont->Width(Text) + 10;
                        const int NumDots = 12;
                        const int AnimFrames = NumDots;
+                       int Rows = min(osd->MaxPixmapSize().Height() / Size, AnimFrames);
+                       int Cols = (AnimFrames + Rows - 1) / Rows;
                        // Temporarily using pixmap layer 0 to have the text alpha blended:
-                       AnimPixmap = osd->CreatePixmap(0, cRect((osd->Width() - Size) / 2, StartLine, Size, Size), cRect(0, 0, Size, Size * AnimFrames));
+                       AnimPixmap = osd->CreatePixmap(0, cRect((osd->Width() - Size) / 2, StartLine, Size, Size), cRect(0, 0, Size * Cols, Size * Rows));
                        if (AnimPixmap) {
                           AnimPixmap->SetAlpha(0);
                           AnimPixmap->Clear();
                           const int Diameter = Size / 5;
-                          int xc = Size / 2 - Diameter / 2;
                           for (int Frame = 0; Frame < AnimFrames; Frame++) {
-                              AnimPixmap->DrawEllipse(cRect(0, Frame * Size, Size, Size), 0xDDFFFFFF);
-                              int yc = Frame * Size + Size / 2 - Diameter / 2;
+                              int x0 = Frame / Rows * Size;
+                              int y0 = Frame % Rows * Size;
+                              AnimPixmap->DrawEllipse(cRect(x0, y0, Size, Size), 0xDDFFFFFF);
+                              int xc = x0 + Size / 2 - Diameter / 2;
+                              int yc = y0 + Size / 2 - Diameter / 2;
                               int Color = 0xFF;
                               int Delta = Color / NumDots / 3;
                               for (int a = 0; a < NumDots; a++) {
@@ -446,7 +496,7 @@
                                   AnimPixmap->DrawEllipse(cRect(x, y, Diameter, Diameter), ArgbToColor(0xFF, Color, Color, Color));
                                   Color -= Delta;
                                   }
-                              AnimPixmap->DrawText(cPoint(0, Frame * Size), Text, clrBlack, clrTransparent, SmlFont, Size, Size, taCenter);
+                              AnimPixmap->DrawText(cPoint(x0, y0), Text, clrBlack, clrTransparent, SmlFont, Size, Size, taCenter);
                               }
                           AnimPixmap->SetLayer(3); // now setting the actual pixmap layer
                           FadeInPixmap = AnimPixmap;
@@ -536,6 +586,10 @@
                      SetArea();
                      DrawSlopes(osd);
                      break;
+       case k3:      Cancel(3);
+                     SetArea();
+                     DrawImages(osd);
+                     break;
        case kBack:
        case kOk:     return osEnd;
        default: return state;
diff -ruN vdr-2.2.0/PLUGINS/src/pictures/HISTORY vdr-2.3.1/PLUGINS/src/pictures/HISTORY
--- vdr-2.2.0/PLUGINS/src/pictures/HISTORY	2015-02-17 14:47:08.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/pictures/HISTORY	2015-07-23 12:15:35.000000000 +0200
@@ -99,3 +99,7 @@
 2015-02-19: Version 2.2.0
 
 - Official release.
+
+2015-07.23: Version 2.3.1
+
+- Added a missing 'const'.
diff -ruN vdr-2.2.0/PLUGINS/src/pictures/Makefile vdr-2.3.1/PLUGINS/src/pictures/Makefile
--- vdr-2.2.0/PLUGINS/src/pictures/Makefile	2014-01-01 14:29:54.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/pictures/Makefile	2014-01-01 14:29:54.000000000 +0100
@@ -1,7 +1,7 @@
 #
 # Makefile for a Video Disk Recorder plugin
 #
-# $Id: Makefile 3.1 2014/01/01 13:29:54 kls Exp $
+# $Id: Makefile 4.0 2014/01/01 13:29:54 kls Exp $
 
 # The official name of this plugin.
 # This name will be used in the '-P...' option of VDR to load the plugin.
diff -ruN vdr-2.2.0/PLUGINS/src/pictures/entry.c vdr-2.3.1/PLUGINS/src/pictures/entry.c
--- vdr-2.2.0/PLUGINS/src/pictures/entry.c	2012-02-17 15:00:28.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/pictures/entry.c	2012-02-17 15:00:28.000000000 +0100
@@ -3,7 +3,7 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: entry.c 3.0 2012/02/17 14:00:28 kls Exp $
+ * $Id: entry.c 4.0 2012/02/17 14:00:28 kls Exp $
  */
 
 #include "entry.h"
diff -ruN vdr-2.2.0/PLUGINS/src/pictures/entry.h vdr-2.3.1/PLUGINS/src/pictures/entry.h
--- vdr-2.2.0/PLUGINS/src/pictures/entry.h	2008-01-06 13:30:50.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/pictures/entry.h	2008-01-06 13:30:50.000000000 +0100
@@ -3,7 +3,7 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: entry.h 3.0 2008/01/06 12:30:50 kls Exp $
+ * $Id: entry.h 4.0 2008/01/06 12:30:50 kls Exp $
  */
 
 #ifndef _ENTRY_H
diff -ruN vdr-2.2.0/PLUGINS/src/pictures/menu.c vdr-2.3.1/PLUGINS/src/pictures/menu.c
--- vdr-2.2.0/PLUGINS/src/pictures/menu.c	2008-01-13 12:35:18.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/pictures/menu.c	2015-07-23 12:13:56.000000000 +0200
@@ -3,7 +3,7 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: menu.c 3.0 2008/01/13 11:35:18 kls Exp $
+ * $Id: menu.c 4.1 2015/07/23 10:13:56 kls Exp $
  */
 
 #include "menu.h"
@@ -85,7 +85,7 @@
   if (Item) {
      const cList<cPictureEntry> *l = pictureEntry->Entries();
      if (l) {
-        cPictureEntry *pe = l->Get(Current());
+        const cPictureEntry *pe = l->Get(Current());
         if (pe) {
            if (SlideShow) {
               cControl::Launch(new cPictureControl(pictures, pe, true));
diff -ruN vdr-2.2.0/PLUGINS/src/pictures/menu.h vdr-2.3.1/PLUGINS/src/pictures/menu.h
--- vdr-2.2.0/PLUGINS/src/pictures/menu.h	2008-01-12 12:22:52.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/pictures/menu.h	2008-01-12 12:22:52.000000000 +0100
@@ -3,7 +3,7 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: menu.h 3.0 2008/01/12 11:22:52 kls Exp $
+ * $Id: menu.h 4.0 2008/01/12 11:22:52 kls Exp $
  */
 
 #ifndef _MENU_H
diff -ruN vdr-2.2.0/PLUGINS/src/pictures/pic2mpg vdr-2.3.1/PLUGINS/src/pictures/pic2mpg
--- vdr-2.2.0/PLUGINS/src/pictures/pic2mpg	2013-07-01 10:33:38.000000000 +0200
+++ vdr-2.3.1/PLUGINS/src/pictures/pic2mpg	2013-07-01 10:33:38.000000000 +0200
@@ -7,7 +7,7 @@
 #
 # See the README file for copyright information and how to reach the author.
 #
-# $Id: pic2mpg 3.1 2013/07/01 08:33:38 kls Exp $
+# $Id: pic2mpg 4.0 2013/07/01 08:33:38 kls Exp $
 
 use File::Path;
 use File::Spec;
diff -ruN vdr-2.2.0/PLUGINS/src/pictures/pictures.c vdr-2.3.1/PLUGINS/src/pictures/pictures.c
--- vdr-2.2.0/PLUGINS/src/pictures/pictures.c	2015-02-17 14:12:50.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/pictures/pictures.c	2015-07-23 12:14:22.000000000 +0200
@@ -3,7 +3,7 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: pictures.c 3.2 2015/02/17 13:12:50 kls Exp $
+ * $Id: pictures.c 4.1 2015/07/23 10:14:22 kls Exp $
  */
 
 #include <getopt.h>
@@ -11,7 +11,7 @@
 #include "menu.h"
 #include "player.h"
 
-static const char *VERSION       = "2.2.0";
+static const char *VERSION       = "2.3.1";
 static const char *DESCRIPTION   = trNOOP("A simple picture viewer");
 static const char *MAINMENUENTRY = trNOOP("Pictures");
 
diff -ruN vdr-2.2.0/PLUGINS/src/pictures/player.c vdr-2.3.1/PLUGINS/src/pictures/player.c
--- vdr-2.2.0/PLUGINS/src/pictures/player.c	2014-02-08 13:48:12.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/pictures/player.c	2014-02-08 13:48:12.000000000 +0100
@@ -3,7 +3,7 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: player.c 3.1 2014/02/08 12:48:12 kls Exp $
+ * $Id: player.c 4.0 2014/02/08 12:48:12 kls Exp $
  */
 
 #include "player.h"
diff -ruN vdr-2.2.0/PLUGINS/src/pictures/player.h vdr-2.3.1/PLUGINS/src/pictures/player.h
--- vdr-2.2.0/PLUGINS/src/pictures/player.h	2012-04-28 13:56:01.000000000 +0200
+++ vdr-2.3.1/PLUGINS/src/pictures/player.h	2012-04-28 13:56:01.000000000 +0200
@@ -3,7 +3,7 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: player.h 3.0 2012/04/28 11:56:01 kls Exp $
+ * $Id: player.h 4.0 2012/04/28 11:56:01 kls Exp $
  */
 
 #ifndef _PLAYER_H
diff -ruN vdr-2.2.0/PLUGINS/src/servicedemo/Makefile vdr-2.3.1/PLUGINS/src/servicedemo/Makefile
--- vdr-2.2.0/PLUGINS/src/servicedemo/Makefile	2014-01-01 14:29:54.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/servicedemo/Makefile	2014-01-01 14:29:54.000000000 +0100
@@ -1,7 +1,7 @@
 #
 # Makefile for a Video Disk Recorder plugin
 #
-# $Id: Makefile 3.1 2014/01/01 13:29:54 kls Exp $
+# $Id: Makefile 4.0 2014/01/01 13:29:54 kls Exp $
 
 # The official name of this plugin.
 # This name will be used in the '-P...' option of VDR to load the plugin.
diff -ruN vdr-2.2.0/PLUGINS/src/servicedemo/svccli.c vdr-2.3.1/PLUGINS/src/servicedemo/svccli.c
--- vdr-2.2.0/PLUGINS/src/servicedemo/svccli.c	2015-02-17 14:13:05.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/servicedemo/svccli.c	2015-02-17 14:13:05.000000000 +0100
@@ -3,7 +3,7 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: svccli.c 3.2 2015/02/17 13:13:05 kls Exp $
+ * $Id: svccli.c 4.0 2015/02/17 13:13:05 kls Exp $
  */
 
 #include <stdlib.h>
diff -ruN vdr-2.2.0/PLUGINS/src/servicedemo/svcsvr.c vdr-2.3.1/PLUGINS/src/servicedemo/svcsvr.c
--- vdr-2.2.0/PLUGINS/src/servicedemo/svcsvr.c	2015-02-17 14:13:09.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/servicedemo/svcsvr.c	2015-02-17 14:13:09.000000000 +0100
@@ -3,7 +3,7 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: svcsvr.c 3.2 2015/02/17 13:13:09 kls Exp $
+ * $Id: svcsvr.c 4.0 2015/02/17 13:13:09 kls Exp $
  */
 
 #include <stdlib.h>
diff -ruN vdr-2.2.0/PLUGINS/src/skincurses/Makefile vdr-2.3.1/PLUGINS/src/skincurses/Makefile
--- vdr-2.2.0/PLUGINS/src/skincurses/Makefile	2014-01-01 14:29:54.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/skincurses/Makefile	2014-01-01 14:29:54.000000000 +0100
@@ -1,7 +1,7 @@
 #
 # Makefile for a Video Disk Recorder plugin
 #
-# $Id: Makefile 3.1 2014/01/01 13:29:54 kls Exp $
+# $Id: Makefile 4.0 2014/01/01 13:29:54 kls Exp $
 
 # The official name of this plugin.
 # This name will be used in the '-P...' option of VDR to load the plugin.
diff -ruN vdr-2.2.0/PLUGINS/src/skincurses/skincurses.c vdr-2.3.1/PLUGINS/src/skincurses/skincurses.c
--- vdr-2.2.0/PLUGINS/src/skincurses/skincurses.c	2015-02-17 14:13:17.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/skincurses/skincurses.c	2015-02-17 14:13:17.000000000 +0100
@@ -3,7 +3,7 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: skincurses.c 3.3 2015/02/17 13:13:17 kls Exp $
+ * $Id: skincurses.c 4.0 2015/02/17 13:13:17 kls Exp $
  */
 
 #include <ncurses.h>
diff -ruN vdr-2.2.0/PLUGINS/src/status/Makefile vdr-2.3.1/PLUGINS/src/status/Makefile
--- vdr-2.2.0/PLUGINS/src/status/Makefile	2014-01-01 14:29:54.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/status/Makefile	2014-01-01 14:29:54.000000000 +0100
@@ -1,7 +1,7 @@
 #
 # Makefile for a Video Disk Recorder plugin
 #
-# $Id: Makefile 3.1 2014/01/01 13:29:54 kls Exp $
+# $Id: Makefile 4.0 2014/01/01 13:29:54 kls Exp $
 
 # The official name of this plugin.
 # This name will be used in the '-P...' option of VDR to load the plugin.
diff -ruN vdr-2.2.0/PLUGINS/src/status/status.c vdr-2.3.1/PLUGINS/src/status/status.c
--- vdr-2.2.0/PLUGINS/src/status/status.c	2015-02-17 14:13:21.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/status/status.c	2015-02-17 14:13:21.000000000 +0100
@@ -3,7 +3,7 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: status.c 3.2 2015/02/17 13:13:21 kls Exp $
+ * $Id: status.c 4.0 2015/02/17 13:13:21 kls Exp $
  */
 
 #include <vdr/plugin.h>
diff -ruN vdr-2.2.0/PLUGINS/src/svdrpdemo/Makefile vdr-2.3.1/PLUGINS/src/svdrpdemo/Makefile
--- vdr-2.2.0/PLUGINS/src/svdrpdemo/Makefile	2014-01-01 14:29:54.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/svdrpdemo/Makefile	2014-01-01 14:29:54.000000000 +0100
@@ -1,7 +1,7 @@
 #
 # Makefile for a Video Disk Recorder plugin
 #
-# $Id: Makefile 3.1 2014/01/01 13:29:54 kls Exp $
+# $Id: Makefile 4.0 2014/01/01 13:29:54 kls Exp $
 
 # The official name of this plugin.
 # This name will be used in the '-P...' option of VDR to load the plugin.
diff -ruN vdr-2.2.0/PLUGINS/src/svdrpdemo/svdrpdemo.c vdr-2.3.1/PLUGINS/src/svdrpdemo/svdrpdemo.c
--- vdr-2.2.0/PLUGINS/src/svdrpdemo/svdrpdemo.c	2015-02-17 14:13:29.000000000 +0100
+++ vdr-2.3.1/PLUGINS/src/svdrpdemo/svdrpdemo.c	2015-02-17 14:13:29.000000000 +0100
@@ -3,7 +3,7 @@
  *
  * See the README file for copyright information and how to reach the author.
  *
- * $Id: svdrpdemo.c 3.2 2015/02/17 13:13:29 kls Exp $
+ * $Id: svdrpdemo.c 4.0 2015/02/17 13:13:29 kls Exp $
  */
 
 #include <vdr/plugin.h>
diff -ruN vdr-2.2.0/PLUGINS.html vdr-2.3.1/PLUGINS.html
--- vdr-2.2.0/PLUGINS.html	2015-02-17 16:11:36.000000000 +0100
+++ vdr-2.3.1/PLUGINS.html	2015-05-21 14:16:37.000000000 +0200
@@ -38,7 +38,7 @@
 <a href="http://www.tvdr.de">www.tvdr.de</a>
 </div>
 <div class="center">
-<modified>Important modifications introduced since version 2.0 are marked like this.</modified>
+<modified>Important modifications introduced since version 2.2 are marked like this.</modified>
 </div>
 <p>
 VDR provides an easy to use plugin interface that allows additional functionality
@@ -99,12 +99,12 @@
 <li><a href="#Skins">Skins</a>
 <li><a href="#Themes">Themes</a>
 <li><a href="#Devices">Devices</a>
-<li><modified><a href="#Positioners">Positioners</a></modified>
+<li><a href="#Positioners">Positioners</a>
 <li><a href="#Audio">Audio</a>
 <li><a href="#Remote Control">Remote Control</a>
 <li><a href="#Conditional Access">Conditional Access</a>
 <li><a href="#Electronic Program Guide">Electronic Program Guide</a>
-<li><modified><a href="#The video directory">The video directory</a></modified>
+<li><a href="#The video directory">The video directory</a>
 </ul>
 </ul>
 
@@ -1161,6 +1161,12 @@
 ('<tt>\n</tt>'). Each of these lines will be preceded with the <tt>ReplyCode</tt>
 when presenting them to the caller, and the continuation character ('<tt>-</tt>')
 will be set for all but the last one.
+<p>
+<modified>
+<b>The SVDRP functions are called from the separate "SVDRP server handler" thread.
+Therefore the plugin needs to take care of proper locking if it accesses any
+global data.</b>
+</modified>
 
 <hr><h2><a name="Loading plugins into VDR">Loading plugins into VDR</a></h2>
 
@@ -1877,7 +1883,7 @@
 virtual int64_t GetSTC(void);
 virtual bool IsPlayingVideo(void) const;
 virtual bool HasIBPTrickSpeed(void);
-virtual void TrickSpeed(int Speed<modified>, bool Forward</modified>);
+virtual void TrickSpeed(int Speed, bool Forward);
 virtual void Clear(void);
 virtual void Play(void);
 virtual void Freeze(void);
@@ -2026,7 +2032,6 @@
 
 and shall not delete this object. It will be automatically deleted when the program ends.
 
-<div class="modified">
 <hr><h2><a name="Positioners">Positioners</a></h2>
 
 <div class="blurb">Now you see me - now you don't!</div><p>
@@ -2065,7 +2070,6 @@
 Note that the object has to be created on the heap (using <tt>new</tt>),
 and you shall not delete it at any point (it will be deleted automatically
 when the program ends).
-</div modified>
 
 <hr><h2><a name="Audio">Audio</a></h2>
 
@@ -2301,7 +2305,6 @@
 <p>
 See <tt>VDR/epg.h</tt> for details.
 
-<div class="modified">
 <hr><h2><a name="The video directory">The video directory</a></h2>
 
 <div class="blurb">Bits and pieces...</div><p>
@@ -2335,7 +2338,6 @@
 Note that the object has to be created on the heap (using <tt>new</tt>),
 and you shall not delete it at any point (it will be deleted automatically
 when the program ends).
-</div modified>
 
 </body>
 </html>
diff -ruN vdr-2.2.0/README vdr-2.3.1/README
--- vdr-2.2.0/README	2013-03-11 14:24:12.000000000 +0100
+++ vdr-2.3.1/README	2015-03-12 11:24:42.000000000 +0100
@@ -32,6 +32,10 @@
 the ones in this system, but here we have the full source code
 and can modify the menus in whatever way desired.
 
+If you actually use VDR, please add yourself to the "VDR User Counter"
+at http://www.tvdr.de/counter.htm. You can also like VDR on facebook
+at https://www.facebook.com/VideoDiskRecorder.
+
 
 "VDR Video Disk Recorder" is a registered trademark of Klaus Schmidinger.
 "TVDR TV Done Right" is a registered trademark of Klaus Schmidinger.
diff -ruN vdr-2.2.0/args.c vdr-2.3.1/args.c
--- vdr-2.2.0/args.c	2014-04-14 14:02:38.000000000 +0200
+++ vdr-2.3.1/args.c	2014-04-14 14:02:38.000000000 +0200
@@ -6,7 +6,7 @@
  *
  * Original version written by Lars Hanisch <dvb@flensrocker.de>.
  *
- * $Id: args.c 1.1 2014/04/14 12:02:38 kls Exp $
+ * $Id: args.c 4.0 2014/04/14 12:02:38 kls Exp $
  */
 
 #include "args.h"
diff -ruN vdr-2.2.0/args.h vdr-2.3.1/args.h
--- vdr-2.2.0/args.h	2014-04-14 13:54:21.000000000 +0200
+++ vdr-2.3.1/args.h	2014-04-14 13:54:21.000000000 +0200
@@ -6,7 +6,7 @@
  *
  * Original version written by Lars Hanisch <dvb@flensrocker.de>.
  *
- * $Id: args.h 1.1 2014/04/14 11:54:21 kls Exp $
+ * $Id: args.h 4.0 2014/04/14 11:54:21 kls Exp $
  */
 
 #ifndef __ARGS_H
diff -ruN vdr-2.2.0/audio.c vdr-2.3.1/audio.c
--- vdr-2.2.0/audio.c	2010-05-16 15:30:11.000000000 +0200
+++ vdr-2.3.1/audio.c	2010-05-16 15:30:11.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: audio.c 3.0 2010/05/16 13:30:11 kls Exp $
+ * $Id: audio.c 4.0 2010/05/16 13:30:11 kls Exp $
  */
 
 #include "audio.h"
diff -ruN vdr-2.2.0/audio.h vdr-2.3.1/audio.h
--- vdr-2.2.0/audio.h	2008-07-06 13:39:21.000000000 +0200
+++ vdr-2.3.1/audio.h	2008-07-06 13:39:21.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: audio.h 3.0 2008/07/06 11:39:21 kls Exp $
+ * $Id: audio.h 4.0 2008/07/06 11:39:21 kls Exp $
  */
 
 #ifndef __AUDIO_H
diff -ruN vdr-2.2.0/channels.c vdr-2.3.1/channels.c
--- vdr-2.2.0/channels.c	2015-02-01 14:47:05.000000000 +0100
+++ vdr-2.3.1/channels.c	2015-09-09 12:21:22.000000000 +0200
@@ -4,15 +4,13 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: channels.c 3.8 2015/02/01 13:47:05 kls Exp $
+ * $Id: channels.c 4.3 2015/09/09 10:21:22 kls Exp $
  */
 
 #include "channels.h"
 #include <ctype.h>
 #include "device.h"
-#include "epg.h"
 #include "libsi/si.h"
-#include "timers.h"
 
 // IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d'
 // format characters in order to allow any number of blanks after a numeric
@@ -79,27 +77,13 @@
   schedule     = NULL;
   linkChannels = NULL;
   refChannel   = NULL;
+  seen         = 0;
   *this = Channel;
 }
 
 cChannel::~cChannel()
 {
-  delete linkChannels;
-  linkChannels = NULL; // more than one channel can link to this one, so we need the following loop
-  for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
-      if (Channel->linkChannels) {
-         for (cLinkChannel *lc = Channel->linkChannels->First(); lc; lc = Channel->linkChannels->Next(lc)) {
-             if (lc->Channel() == this) {
-                Channel->linkChannels->Del(lc);
-                break;
-                }
-             }
-         if (Channel->linkChannels->Count() == 0) {
-            delete Channel->linkChannels;
-            Channel->linkChannels = NULL;
-            }
-         }
-      }
+  delete linkChannels; // any links from other channels pointing to this one have been deleted in cChannels::Del()
   free(name);
   free(shortName);
   free(provider);
@@ -167,16 +151,7 @@
   return tf;
 }
 
-bool cChannel::HasTimer(void) const
-{
-  for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
-      if (Timer->Channel() == this)
-         return true;
-      }
-  return false;
-}
-
-int cChannel::Modification(int Mask)
+int cChannel::Modification(int Mask) const
 {
   int Result = modification & Mask;
   modification = CHANNELMOD_NONE;
@@ -223,41 +198,57 @@
      if (Number() && !Quiet) {
         dsyslog("changing transponder data of channel %d (%s) from %s to %s", Number(), name, *OldTransponderData, *TransponderDataToString());
         modification |= CHANNELMOD_TRANSP;
-        Channels.SetModified();
         }
+     return true;
      }
-  return true;
+  return false;
 }
 
-void cChannel::SetId(int Nid, int Tid, int Sid, int Rid)
+bool cChannel::SetSource(int Source)
 {
-  if (nid != Nid || tid != Tid || sid != Sid || rid != Rid) {
+  if (source != Source) {
      if (Number()) {
+        dsyslog("changing source of channel %d (%s) from %s to %s", Number(), name, *cSource::ToString(source), *cSource::ToString(Source));
+        modification |= CHANNELMOD_TRANSP;
+        }
+     source = Source;
+     return true;
+     }
+  return false;
+}
+
+bool cChannel::SetId(cChannels *Channels, int Nid, int Tid, int Sid, int Rid)
+{
+  if (nid != Nid || tid != Tid || sid != Sid || rid != Rid) {
+     if (Channels && Number()) {
         dsyslog("changing id of channel %d (%s) from %d-%d-%d-%d to %d-%d-%d-%d", Number(), name, nid, tid, sid, rid, Nid, Tid, Sid, Rid);
         modification |= CHANNELMOD_ID;
-        Channels.SetModified();
-        Channels.UnhashChannel(this);
+        Channels->UnhashChannel(this);
         }
      nid = Nid;
      tid = Tid;
      sid = Sid;
      rid = Rid;
-     if (Number())
-        Channels.HashChannel(this);
+     if (Channels)
+        Channels->HashChannel(this);
      schedule = NULL;
+     return true;
      }
+  return false;
 }
 
-void cChannel::SetLcn(int Lcn)
+bool cChannel::SetLcn(int Lcn)
 {
   if (lcn != Lcn) {
      if (Number())
         dsyslog("changing lcn of channel %d (%s) from %d to %d\n", Number(), name, lcn, Lcn);
      lcn = Lcn;
+     return true;
      }
+  return false;
 }
 
-void cChannel::SetName(const char *Name, const char *ShortName, const char *Provider)
+bool cChannel::SetName(const char *Name, const char *ShortName, const char *Provider)
 {
   if (!isempty(Name)) {
      bool nn = strcmp(name, Name) != 0;
@@ -267,7 +258,6 @@
         if (Number()) {
            dsyslog("changing name of channel %d from '%s,%s;%s' to '%s,%s;%s'", Number(), name, shortName, provider, Name, ShortName, Provider);
            modification |= CHANNELMOD_NAME;
-           Channels.SetModified();
            }
         if (nn) {
            name = strcpyrealloc(name, Name);
@@ -279,20 +269,23 @@
            }
         if (np)
            provider = strcpyrealloc(provider, Provider);
+        return true;
         }
      }
+  return false;
 }
 
-void cChannel::SetPortalName(const char *PortalName)
+bool cChannel::SetPortalName(const char *PortalName)
 {
   if (!isempty(PortalName) && strcmp(portalName, PortalName) != 0) {
      if (Number()) {
         dsyslog("changing portal name of channel %d (%s) from '%s' to '%s'", Number(), name, portalName, PortalName);
         modification |= CHANNELMOD_NAME;
-        Channels.SetModified();
         }
      portalName = strcpyrealloc(portalName, PortalName);
+     return true;
      }
+  return false;
 }
 
 #define STRDIFF 0x01
@@ -337,7 +330,7 @@
   return q - s;
 }
 
-void cChannel::SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid)
+bool cChannel::SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid)
 {
   int mod = CHANNELMOD_NONE;
   if (vpid != Vpid || ppid != Ppid || vtype != Vtype)
@@ -400,25 +393,33 @@
      spids[MAXSPIDS] = 0;
      tpid = Tpid;
      modification |= mod;
-     if (Number())
-        Channels.SetModified();
+     return true;
      }
+  return false;
 }
 
-void cChannel::SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds)
+bool cChannel::SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds)
 {
+  bool Modified = false;
   if (SubtitlingTypes) {
-     for (int i = 0; i < MAXSPIDS; i++)
+     for (int i = 0; i < MAXSPIDS; i++) {
+         Modified = subtitlingTypes[i] != SubtitlingTypes[i];
          subtitlingTypes[i] = SubtitlingTypes[i];
+         }
      }
   if (CompositionPageIds) {
-     for (int i = 0; i < MAXSPIDS; i++)
+     for (int i = 0; i < MAXSPIDS; i++) {
+         Modified = compositionPageIds[i] != CompositionPageIds[i];
          compositionPageIds[i] = CompositionPageIds[i];
+         }
      }
   if (AncillaryPageIds) {
-     for (int i = 0; i < MAXSPIDS; i++)
+     for (int i = 0; i < MAXSPIDS; i++) {
+         Modified = ancillaryPageIds[i] != AncillaryPageIds[i];
          ancillaryPageIds[i] = AncillaryPageIds[i];
+         }
      }
+  return Modified;
 }
 
 void cChannel::SetSeen(void)
@@ -426,10 +427,26 @@
   seen = time(NULL);
 }
 
-void cChannel::SetCaIds(const int *CaIds)
+void cChannel::DelLinkChannel(cChannel *LinkChannel)
+{
+  if (linkChannels) {
+     for (cLinkChannel *lc = linkChannels->First(); lc; lc = linkChannels->Next(lc)) {
+         if (lc->Channel() == LinkChannel) {
+            linkChannels->Del(lc);
+            break;
+            }
+         }
+     if (linkChannels->Count() == 0) {
+        delete linkChannels;
+        linkChannels = NULL;
+        }
+     }
+}
+
+bool cChannel::SetCaIds(const int *CaIds)
 {
   if (caids[0] && caids[0] <= CA_USER_MAX)
-     return; // special values will not be overwritten
+     return false; // special values will not be overwritten
   if (IntArraysDiffer(caids, CaIds)) {
      char OldCaIdsBuf[MAXCAIDS * 5 + 10]; // 5: 4 digits plus delimiting ',', 10: paranoia
      char NewCaIdsBuf[MAXCAIDS * 5 + 10];
@@ -443,24 +460,26 @@
             break;
          }
      modification |= CHANNELMOD_CA;
-     Channels.SetModified();
+     return true;
      }
+  return false;
 }
 
-void cChannel::SetCaDescriptors(int Level)
+bool cChannel::SetCaDescriptors(int Level)
 {
   if (Level > 0) {
      modification |= CHANNELMOD_CA;
-     Channels.SetModified();
      if (Number() && Level > 1)
         dsyslog("changing ca descriptors of channel %d (%s)", Number(), name);
+     return true;
      }
+  return false;
 }
 
-void cChannel::SetLinkChannels(cLinkChannels *LinkChannels)
+bool cChannel::SetLinkChannels(cLinkChannels *LinkChannels)
 {
   if (!linkChannels && !LinkChannels)
-     return;
+     return false;
   if (linkChannels && LinkChannels) {
      cLinkChannel *lca = linkChannels->First();
      cLinkChannel *lcb = LinkChannels->First();
@@ -474,7 +493,7 @@
            }
      if (!lca && !lcb) {
         delete LinkChannels;
-        return; // linkage has not changed
+        return false; // linkage has not changed
         }
      }
   char buffer[((linkChannels ? linkChannels->Count() : 0) + (LinkChannels ? LinkChannels->Count() : 0)) * 6 + 256]; // 6: 5 digit channel number plus blank, 256: other texts (see below) plus reserve
@@ -502,6 +521,7 @@
      q += sprintf(q, " none");
   if (Number())
      dsyslog("%s", buffer);
+  return true;
 }
 
 void cChannel::SetRefChannel(cChannel *RefChannel)
@@ -534,9 +554,9 @@
   cString buffer;
   if (Channel->groupSep) {
      if (Channel->number)
-        buffer = cString::sprintf(":@%d %s\n", Channel->number, FullName);
+        buffer = cString::sprintf(":@%d %s", Channel->number, FullName);
      else
-        buffer = cString::sprintf(":%s\n", FullName);
+        buffer = cString::sprintf(":%s", FullName);
      }
   else {
      char vpidbuf[32];
@@ -568,7 +588,7 @@
      q = caidbuf;
      q += IntArrayToString(q, Channel->caids, 16);
      *q = 0;
-     buffer = cString::sprintf("%s:%d:%s:%s:%d:%s:%s:%s:%s:%d:%d:%d:%d\n", FullName, Channel->frequency, *Channel->parameters, *cSource::ToString(Channel->source), Channel->srate, vpidbuf, apidbuf, tpidbuf, caidbuf, Channel->sid, Channel->nid, Channel->tid, Channel->rid);
+     buffer = cString::sprintf("%s:%d:%s:%s:%d:%s:%s:%s:%s:%d:%d:%d:%d", FullName, Channel->frequency, *Channel->parameters, *cSource::ToString(Channel->source), Channel->srate, vpidbuf, apidbuf, tpidbuf, caidbuf, Channel->sid, Channel->nid, Channel->tid, Channel->rid);
      }
   return buffer;
 }
@@ -786,7 +806,7 @@
 
 bool cChannel::Save(FILE *f)
 {
-  return fprintf(f, "%s", *ToText()) > 0;
+  return fprintf(f, "%s\n", *ToText()) > 0;
 }
 
 // --- cChannelSorter --------------------------------------------------------
@@ -807,40 +827,52 @@
 
 // --- cChannels -------------------------------------------------------------
 
-cChannels Channels;
+cChannels cChannels::channels;
+int cChannels::maxNumber = 0;
+int cChannels::maxChannelNameLength = 0;
+int cChannels::maxShortChannelNameLength = 0;
 
 cChannels::cChannels(void)
+:cConfig<cChannel>("Channels")
 {
-  maxNumber = 0;
-  maxChannelNameLength = 0;
-  maxShortChannelNameLength = 0;
-  modified = CHANNELSMOD_NONE;
+  modifiedByUser = 0;
+}
+
+const cChannels *cChannels::GetChannelsRead(cStateKey &StateKey, int TimeoutMs)
+{
+  return channels.Lock(StateKey, false, TimeoutMs) ? &channels : NULL;
+}
+
+cChannels *cChannels::GetChannelsWrite(cStateKey &StateKey, int TimeoutMs)
+{
+  return channels.Lock(StateKey, true, TimeoutMs) ? &channels : NULL;
 }
 
 void cChannels::DeleteDuplicateChannels(void)
 {
   cList<cChannelSorter> ChannelSorter;
-  for (cChannel *channel = First(); channel; channel = Next(channel)) {
-      if (!channel->GroupSep())
-         ChannelSorter.Add(new cChannelSorter(channel));
+  for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
+      if (!Channel->GroupSep())
+         ChannelSorter.Add(new cChannelSorter(Channel));
       }
   ChannelSorter.Sort();
   cChannelSorter *cs = ChannelSorter.First();
   while (cs) {
-        cChannelSorter *next = ChannelSorter.Next(cs);
-        if (next && cs->channelID == next->channelID) {
-           dsyslog("deleting duplicate channel %s", *next->channel->ToText());
-           Del(next->channel);
+        cChannelSorter *Next = ChannelSorter.Next(cs);
+        if (Next && cs->channelID == Next->channelID) {
+           dsyslog("deleting duplicate channel %s", *Next->channel->ToText());
+           Del(Next->channel);
            }
-        cs = next;
+        cs = Next;
         }
 }
 
 bool cChannels::Load(const char *FileName, bool AllowComments, bool MustExist)
 {
-  if (cConfig<cChannel>::Load(FileName, AllowComments, MustExist)) {
-     DeleteDuplicateChannels();
-     ReNumber();
+  LOCK_CHANNELS_WRITE;
+  if (channels.cConfig<cChannel>::Load(FileName, AllowComments, MustExist)) {
+     channels.DeleteDuplicateChannels();
+     channels.ReNumber();
      return true;
      }
   return false;
@@ -856,36 +888,36 @@
   channelsHashSid.Del(Channel, Channel->Sid());
 }
 
-int cChannels::GetNextGroup(int Idx)
+int cChannels::GetNextGroup(int Idx) const
 {
-  cChannel *channel = Get(++Idx);
-  while (channel && !(channel->GroupSep() && *channel->Name()))
-        channel = Get(++Idx);
-  return channel ? Idx : -1;
+  const cChannel *Channel = Get(++Idx);
+  while (Channel && !(Channel->GroupSep() && *Channel->Name()))
+        Channel = Get(++Idx);
+  return Channel ? Idx : -1;
 }
 
-int cChannels::GetPrevGroup(int Idx)
+int cChannels::GetPrevGroup(int Idx) const
 {
-  cChannel *channel = Get(--Idx);
-  while (channel && !(channel->GroupSep() && *channel->Name()))
-        channel = Get(--Idx);
-  return channel ? Idx : -1;
+  const cChannel *Channel = Get(--Idx);
+  while (Channel && !(Channel->GroupSep() && *Channel->Name()))
+        Channel = Get(--Idx);
+  return Channel ? Idx : -1;
 }
 
-int cChannels::GetNextNormal(int Idx)
+int cChannels::GetNextNormal(int Idx) const
 {
-  cChannel *channel = Get(++Idx);
-  while (channel && channel->GroupSep())
-        channel = Get(++Idx);
-  return channel ? Idx : -1;
+  const cChannel *Channel = Get(++Idx);
+  while (Channel && Channel->GroupSep())
+        Channel = Get(++Idx);
+  return Channel ? Idx : -1;
 }
 
-int cChannels::GetPrevNormal(int Idx)
+int cChannels::GetPrevNormal(int Idx) const
 {
-  cChannel *channel = Get(--Idx);
-  while (channel && channel->GroupSep())
-        channel = Get(--Idx);
-  return channel ? Idx : -1;
+  const cChannel *Channel = Get(--Idx);
+  while (Channel && Channel->GroupSep())
+        Channel = Get(--Idx);
+  return Channel ? Idx : -1;
 }
 
 void cChannels::ReNumber(void)
@@ -893,110 +925,120 @@
   channelsHashSid.Clear();
   maxNumber = 0;
   int Number = 1;
-  for (cChannel *channel = First(); channel; channel = Next(channel)) {
-      if (channel->GroupSep()) {
-         if (channel->Number() > Number)
-            Number = channel->Number();
+  for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
+      if (Channel->GroupSep()) {
+         if (Channel->Number() > Number)
+            Number = Channel->Number();
          }
       else {
-         HashChannel(channel);
+         HashChannel(Channel);
          maxNumber = Number;
-         channel->SetNumber(Number++);
+         Channel->SetNumber(Number++);
          }
       }
 }
 
-cChannel *cChannels::GetByNumber(int Number, int SkipGap)
+void cChannels::Del(cChannel *Channel)
 {
-  cChannel *previous = NULL;
-  for (cChannel *channel = First(); channel; channel = Next(channel)) {
-      if (!channel->GroupSep()) {
-         if (channel->Number() == Number)
-            return channel;
-         else if (SkipGap && channel->Number() > Number)
-            return SkipGap > 0 ? channel : previous;
-         previous = channel;
+  UnhashChannel(Channel);
+  for (cChannel *ch = First(); ch; ch = Next(ch))
+      ch->DelLinkChannel(Channel);
+  cList<cChannel>::Del(Channel);
+}
+
+const cChannel *cChannels::GetByNumber(int Number, int SkipGap) const
+{
+  const cChannel *Previous = NULL;
+  for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
+      if (!Channel->GroupSep()) {
+         if (Channel->Number() == Number)
+            return Channel;
+         else if (SkipGap && Channel->Number() > Number)
+            return SkipGap > 0 ? Channel : Previous;
+         Previous = Channel;
          }
       }
   return NULL;
 }
 
-cChannel *cChannels::GetByServiceID(int Source, int Transponder, unsigned short ServiceID)
+const cChannel *cChannels::GetByServiceID(int Source, int Transponder, unsigned short ServiceID) const
 {
   cList<cHashObject> *list = channelsHashSid.GetList(ServiceID);
   if (list) {
      for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
-         cChannel *channel = (cChannel *)hobj->Object();
-         if (channel->Sid() == ServiceID && channel->Source() == Source && ISTRANSPONDER(channel->Transponder(), Transponder))
-            return channel;
+         cChannel *Channel = (cChannel *)hobj->Object();
+         if (Channel->Sid() == ServiceID && Channel->Source() == Source && ISTRANSPONDER(Channel->Transponder(), Transponder))
+            return Channel;
          }
      }
   return NULL;
 }
 
-cChannel *cChannels::GetByChannelID(tChannelID ChannelID, bool TryWithoutRid, bool TryWithoutPolarization)
+const cChannel *cChannels::GetByChannelID(tChannelID ChannelID, bool TryWithoutRid, bool TryWithoutPolarization) const
 {
   int sid = ChannelID.Sid();
   cList<cHashObject> *list = channelsHashSid.GetList(sid);
   if (list) {
      for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
-         cChannel *channel = (cChannel *)hobj->Object();
-         if (channel->Sid() == sid && channel->GetChannelID() == ChannelID)
-            return channel;
+         cChannel *Channel = (cChannel *)hobj->Object();
+         if (Channel->Sid() == sid && Channel->GetChannelID() == ChannelID)
+            return Channel;
          }
      if (TryWithoutRid) {
         ChannelID.ClrRid();
         for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
-            cChannel *channel = (cChannel *)hobj->Object();
-            if (channel->Sid() == sid && channel->GetChannelID().ClrRid() == ChannelID)
-               return channel;
+            cChannel *Channel = (cChannel *)hobj->Object();
+            if (Channel->Sid() == sid && Channel->GetChannelID().ClrRid() == ChannelID)
+               return Channel;
             }
         }
      if (TryWithoutPolarization) {
         ChannelID.ClrPolarization();
         for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
-            cChannel *channel = (cChannel *)hobj->Object();
-            if (channel->Sid() == sid && channel->GetChannelID().ClrPolarization() == ChannelID)
-               return channel;
+            cChannel *Channel = (cChannel *)hobj->Object();
+            if (Channel->Sid() == sid && Channel->GetChannelID().ClrPolarization() == ChannelID)
+               return Channel;
             }
         }
      }
   return NULL;
 }
-cChannel *cChannels::GetByTransponderID(tChannelID ChannelID)
+
+const cChannel *cChannels::GetByTransponderID(tChannelID ChannelID) const
 {
   int source = ChannelID.Source();
   int nid = ChannelID.Nid();
   int tid = ChannelID.Tid();
-  for (cChannel *channel = First(); channel; channel = Next(channel)) {
-      if (channel->Tid() == tid && channel->Nid() == nid && channel->Source() == source)
-         return channel;
+  for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
+      if (Channel->Tid() == tid && Channel->Nid() == nid && Channel->Source() == source)
+         return Channel;
       }
   return NULL;
 }
 
-bool cChannels::HasUniqueChannelID(cChannel *NewChannel, cChannel *OldChannel)
+bool cChannels::HasUniqueChannelID(const cChannel *NewChannel, const cChannel *OldChannel) const
 {
   tChannelID NewChannelID = NewChannel->GetChannelID();
-  for (cChannel *channel = First(); channel; channel = Next(channel)) {
-      if (!channel->GroupSep() && channel != OldChannel && channel->GetChannelID() == NewChannelID)
+  for (const cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
+      if (!Channel->GroupSep() && Channel != OldChannel && Channel->GetChannelID() == NewChannelID)
          return false;
       }
   return true;
 }
 
-bool cChannels::SwitchTo(int Number)
+bool cChannels::SwitchTo(int Number) const
 {
-  cChannel *channel = GetByNumber(Number);
-  return channel && cDevice::PrimaryDevice()->SwitchChannel(channel, true);
+  const cChannel *Channel = GetByNumber(Number);
+  return Channel && cDevice::PrimaryDevice()->SwitchChannel(Channel, true);
 }
 
 int cChannels::MaxChannelNameLength(void)
 {
   if (!maxChannelNameLength) {
-     for (cChannel *channel = First(); channel; channel = Next(channel)) {
-         if (!channel->GroupSep())
-            maxChannelNameLength = max(Utf8StrLen(channel->Name()), maxChannelNameLength);
+     LOCK_CHANNELS_READ;
+     for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
+         if (!Channel->GroupSep())
+            maxChannelNameLength = max(Utf8StrLen(Channel->Name()), maxChannelNameLength);
          }
      }
   return maxChannelNameLength;
@@ -1005,24 +1047,25 @@
 int cChannels::MaxShortChannelNameLength(void)
 {
   if (!maxShortChannelNameLength) {
-     for (cChannel *channel = First(); channel; channel = Next(channel)) {
-         if (!channel->GroupSep())
-            maxShortChannelNameLength = max(Utf8StrLen(channel->ShortName(true)), maxShortChannelNameLength);
+     LOCK_CHANNELS_READ;
+     for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
+         if (!Channel->GroupSep())
+            maxShortChannelNameLength = max(Utf8StrLen(Channel->ShortName(true)), maxShortChannelNameLength);
          }
      }
   return maxShortChannelNameLength;
 }
 
-void cChannels::SetModified(bool ByUser)
+void cChannels::SetModifiedByUser(void)
 {
-  modified = ByUser ? CHANNELSMOD_USER : !modified ? CHANNELSMOD_AUTO : modified;
+  modifiedByUser++;
   maxChannelNameLength = maxShortChannelNameLength = 0;
 }
 
-int cChannels::Modified(void)
+bool cChannels::ModifiedByUser(int &State) const
 {
-  int Result = modified;
-  modified = CHANNELSMOD_NONE;
+  int Result = State != modifiedByUser;
+  State = modifiedByUser;
   return Result;
 }
 
@@ -1032,7 +1075,7 @@
      dsyslog("creating new channel '%s,%s;%s' on %s transponder %d with id %d-%d-%d-%d", Name, ShortName, Provider, *cSource::ToString(Transponder->Source()), Transponder->Transponder(), Nid, Tid, Sid, Rid);
      cChannel *NewChannel = new cChannel;
      NewChannel->CopyTransponderData(Transponder);
-     NewChannel->SetId(Nid, Tid, Sid, Rid);
+     NewChannel->SetId(this, Nid, Tid, Sid, Rid);
      NewChannel->SetName(Name, ShortName, Provider);
      NewChannel->SetSeen();
      Add(NewChannel);
@@ -1045,17 +1088,19 @@
 #define CHANNELMARKOBSOLETE "OBSOLETE"
 #define CHANNELTIMEOBSOLETE 3600 // seconds to wait before declaring a channel obsolete (in case it has actually been seen before)
 
-void cChannels::MarkObsoleteChannels(int Source, int Nid, int Tid)
+bool cChannels::MarkObsoleteChannels(int Source, int Nid, int Tid)
 {
-  for (cChannel *channel = First(); channel; channel = Next(channel)) {
-      if (time(NULL) - channel->Seen() > CHANNELTIMEOBSOLETE && channel->Source() == Source && channel->Nid() == Nid && channel->Tid() == Tid && channel->Rid() == 0) {
+  bool ChannelsModified = false;
+  for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) {
+      if (time(NULL) - Channel->Seen() > CHANNELTIMEOBSOLETE && Channel->Source() == Source && Channel->Nid() == Nid && Channel->Tid() == Tid && Channel->Rid() == 0) {
          bool OldShowChannelNamesWithSource = Setup.ShowChannelNamesWithSource;
          Setup.ShowChannelNamesWithSource = false;
-         if (!endswith(channel->Name(), CHANNELMARKOBSOLETE))
-            channel->SetName(cString::sprintf("%s %s", channel->Name(), CHANNELMARKOBSOLETE), channel->ShortName(), cString::sprintf("%s %s", CHANNELMARKOBSOLETE, channel->Provider()));
+         if (!endswith(Channel->Name(), CHANNELMARKOBSOLETE))
+            ChannelsModified |= Channel->SetName(cString::sprintf("%s %s", Channel->Name(), CHANNELMARKOBSOLETE), Channel->ShortName(), cString::sprintf("%s %s", CHANNELMARKOBSOLETE, Channel->Provider()));
          Setup.ShowChannelNamesWithSource = OldShowChannelNamesWithSource;
          }
       }
+  return ChannelsModified;
 }
 
 cString ChannelString(const cChannel *Channel, int Number)
diff -ruN vdr-2.2.0/channels.h vdr-2.3.1/channels.h
--- vdr-2.2.0/channels.h	2015-02-01 14:30:26.000000000 +0100
+++ vdr-2.3.1/channels.h	2015-08-17 11:39:48.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: channels.h 3.3 2015/02/01 13:30:26 kls Exp $
+ * $Id: channels.h 4.2 2015/08/17 09:39:48 kls Exp $
  */
 
 #ifndef __CHANNELS_H
@@ -28,10 +28,6 @@
 #define CHANNELMOD_LANGS    0x40
 #define CHANNELMOD_RETUNE   (CHANNELMOD_PIDS | CHANNELMOD_CA | CHANNELMOD_TRANSP)
 
-#define CHANNELSMOD_NONE    0
-#define CHANNELSMOD_AUTO    1
-#define CHANNELSMOD_USER    2
-
 #define MAXAPIDS 32 // audio
 #define MAXDPIDS 16 // dolby (AC3 + DTS)
 #define MAXSPIDS 32 // subtitles
@@ -86,6 +82,7 @@
   };
 
 class cSchedule;
+class cChannels;
 
 class cChannel : public cListObject {
   friend class cSchedules;
@@ -128,7 +125,7 @@
   mutable cString nameSource;
   mutable cString shortNameSource;
   cString parameters;
-  int modification;
+  mutable int modification;
   time_t seen; // When this channel was last seen in the SDT of its transponder
   mutable const cSchedule *schedule;
   cLinkChannels *linkChannels;
@@ -188,65 +185,84 @@
   bool IsTerr(void) const { return cSource::IsTerr(source); }
   bool IsSourceType(char Source) const { return cSource::IsType(source, Source); }
   tChannelID GetChannelID(void) const { return tChannelID(source, nid, (nid || tid) ? tid : Transponder(), sid, rid); }
-  bool HasTimer(void) const;
-  int Modification(int Mask = CHANNELMOD_ALL);
-  time_t Seen(void) { return seen; }
+  int Modification(int Mask = CHANNELMOD_ALL) const;
+  time_t Seen(void) const { return seen; }
   void CopyTransponderData(const cChannel *Channel);
   bool SetTransponderData(int Source, int Frequency, int Srate, const char *Parameters, bool Quiet = false);
-  void SetId(int Nid, int Tid, int Sid, int Rid = 0);
-  void SetLcn(int Lcn);
-  void SetName(const char *Name, const char *ShortName, const char *Provider);
-  void SetPortalName(const char *PortalName);
-  void SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid);
-  void SetCaIds(const int *CaIds); // list must be zero-terminated
-  void SetCaDescriptors(int Level);
-  void SetLinkChannels(cLinkChannels *LinkChannels);
+  bool SetSource(int Source);
+  bool SetId(cChannels *Channels, int Nid, int Tid, int Sid, int Rid = 0);
+  bool SetLcn(int Lcn);
+  bool SetName(const char *Name, const char *ShortName, const char *Provider);
+  bool SetPortalName(const char *PortalName);
+  bool SetPids(int Vpid, int Ppid, int Vtype, int *Apids, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid);
+  bool SetCaIds(const int *CaIds); // list must be zero-terminated
+  bool SetCaDescriptors(int Level);
+  bool SetLinkChannels(cLinkChannels *LinkChannels);
   void SetRefChannel(cChannel *RefChannel);
-  void SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds);
+  bool SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds);
   void SetSeen(void);
+  void DelLinkChannel(cChannel *LinkChannel);
   };
 
-class cChannels : public cRwLock, public cConfig<cChannel> {
+class cChannels : public cConfig<cChannel> {
 private:
-  int maxNumber;
-  int maxChannelNameLength;
-  int maxShortChannelNameLength;
-  int modified;
-  int beingEdited;
+  static cChannels channels;
+  static int maxNumber;
+  static int maxChannelNameLength;
+  static int maxShortChannelNameLength;
+  int modifiedByUser;
   cHash<cChannel> channelsHashSid;
   void DeleteDuplicateChannels(void);
 public:
   cChannels(void);
-  bool Load(const char *FileName, bool AllowComments = false, bool MustExist = false);
+  static const cChannels *GetChannelsRead(cStateKey &StateKey, int TimeoutMs = 0);
+      ///< Gets the list of channels for read access.
+      ///< See cTimers::GetTimersRead() for details.
+  static cChannels *GetChannelsWrite(cStateKey &StateKey, int TimeoutMs = 0);
+      ///< Gets the list of channels for write access.
+      ///< See cTimers::GetTimersWrite() for details.
+  static bool Load(const char *FileName, bool AllowComments = false, bool MustExist = false);
   void HashChannel(cChannel *Channel);
   void UnhashChannel(cChannel *Channel);
-  int GetNextGroup(int Idx);   // Get next channel group
-  int GetPrevGroup(int Idx);   // Get previous channel group
-  int GetNextNormal(int Idx);  // Get next normal channel (not group)
-  int GetPrevNormal(int Idx);  // Get previous normal channel (not group)
-  void ReNumber(void);         // Recalculate 'number' based on channel type
-  cChannel *GetByNumber(int Number, int SkipGap = 0);
-  cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID);
-  cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false);
-  cChannel *GetByTransponderID(tChannelID ChannelID);
-  int BeingEdited(void) { return beingEdited; }
-  void IncBeingEdited(void) { beingEdited++; }
-  void DecBeingEdited(void) { beingEdited--; }
-  bool HasUniqueChannelID(cChannel *NewChannel, cChannel *OldChannel = NULL);
-  bool SwitchTo(int Number);
-  int MaxNumber(void) { return maxNumber; }
-  int MaxChannelNameLength(void);
-  int MaxShortChannelNameLength(void);
-  void SetModified(bool ByUser = false);
-  int Modified(void);
-      ///< Returns 0 if no channels have been modified, 1 if an automatic
-      ///< modification has been made, and 2 if the user has made a modification.
-      ///< Calling this function resets the 'modified' flag to 0.
+  int GetNextGroup(int Idx) const;   ///< Get next channel group
+  int GetPrevGroup(int Idx) const;   ///< Get previous channel group
+  int GetNextNormal(int Idx) const;  ///< Get next normal channel (not group)
+  int GetPrevNormal(int Idx) const;  ///< Get previous normal channel (not group)
+  void ReNumber(void);               ///< Recalculate 'number' based on channel type
+  void Del(cChannel *Channel);       ///< Delete the given Channel from the list
+  const cChannel *GetByNumber(int Number, int SkipGap = 0) const;
+  cChannel *GetByNumber(int Number, int SkipGap = 0) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByNumber(Number, SkipGap)); }
+  const cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID) const;
+  cChannel *GetByServiceID(int Source, int Transponder, unsigned short ServiceID) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByServiceID(Source, Transponder, ServiceID)); }
+  const cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false) const;
+  cChannel *GetByChannelID(tChannelID ChannelID, bool TryWithoutRid = false, bool TryWithoutPolarization = false) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByChannelID(ChannelID, TryWithoutRid, TryWithoutPolarization)); }
+  const cChannel *GetByTransponderID(tChannelID ChannelID) const;
+  cChannel *GetByTransponderID(tChannelID ChannelID) { return const_cast<cChannel *>(static_cast<const cChannels *>(this)->GetByTransponderID(ChannelID)); }
+  bool HasUniqueChannelID(const cChannel *NewChannel, const cChannel *OldChannel = NULL) const;
+  bool SwitchTo(int Number) const;
+  static int MaxNumber(void) { return maxNumber; }
+  static int MaxChannelNameLength(void);
+  static int MaxShortChannelNameLength(void);
+  void SetModifiedByUser(void);
+  bool ModifiedByUser(int &State) const;
+      ///< Returns true if the channels have been modified by the user since the last call
+      ///< to this function with the same State variable. State must be initialized with 0
+      ///< and will be set to the current value of the list's internal state variable upon
+      ///< return from this function.
   cChannel *NewChannel(const cChannel *Transponder, const char *Name, const char *ShortName, const char *Provider, int Nid, int Tid, int Sid, int Rid = 0);
-  void MarkObsoleteChannels(int Source, int Nid, int Tid);
+  bool MarkObsoleteChannels(int Source, int Nid, int Tid);
   };
 
-extern cChannels Channels;
+// Provide lock controlled access to the list:
+
+DEF_LIST_LOCK(Channels);
+
+// These macros provide a convenient way of locking the global channels list
+// and making sure the lock is released as soon as the current scope is left
+// (note that these macros wait forever to obtain the lock!):
+
+#define LOCK_CHANNELS_READ  USE_LIST_LOCK_READ(Channels)
+#define LOCK_CHANNELS_WRITE USE_LIST_LOCK_WRITE(Channels)
 
 cString ChannelString(const cChannel *Channel, int Number);
 
diff -ruN vdr-2.2.0/ci.c vdr-2.3.1/ci.c
--- vdr-2.2.0/ci.c	2015-02-02 14:57:39.000000000 +0100
+++ vdr-2.3.1/ci.c	2015-09-05 13:45:19.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: ci.c 3.19 2015/02/02 13:57:39 kls Exp $
+ * $Id: ci.c 4.2 2015/09/05 11:45:19 kls Exp $
  */
 
 #include "ci.h"
@@ -125,8 +125,8 @@
 public:
   cCaPidReceiver(void);
   virtual ~cCaPidReceiver() { Detach(); }
-  virtual void Receive(uchar *Data, int Length);
-  bool HasCaPids(void) { return NumPids() - emmPids.Size() - 1 > 0; }
+  virtual void Receive(const uchar *Data, int Length);
+  bool HasCaPids(void) const { return NumPids() - emmPids.Size() - 1 > 0; }
   void Reset(void) { DelEmmPids(); catVersion = -1; }
   };
 
@@ -160,10 +160,10 @@
   catVersion = -1; // can be done independent of 'On'
 }
 
-void cCaPidReceiver::Receive(uchar *Data, int Length)
+void cCaPidReceiver::Receive(const uchar *Data, int Length)
 {
   if (TsPid(Data) == CATPID) {
-     uchar *p = NULL;
+     const uchar *p = NULL;
      if (TsPayloadStart(Data)) {
         if (Data[5] == SI::TableIdCAT) {
            length = (int(Data[6] & 0x03) << 8) | Data[7]; // section length
@@ -251,7 +251,7 @@
   time_t lastScrambledTime;
   int numTsPackets;
 protected:
-  virtual void Receive(uchar *Data, int Length);
+  virtual void Receive(const uchar *Data, int Length);
 public:
   cCaActivationReceiver(const cChannel *Channel, cCamSlot *CamSlot);
   virtual ~cCaActivationReceiver();
@@ -270,7 +270,7 @@
   Detach();
 }
 
-void cCaActivationReceiver::Receive(uchar *Data, int Length)
+void cCaActivationReceiver::Receive(const uchar *Data, int Length)
 {
   if (numTsPackets++ % TS_PACKET_FACTOR == 0) {
      time_t Now = time(NULL);
@@ -1921,7 +1921,8 @@
   cMutexLock MutexLock(&mutex);
   if (!caActivationReceiver) {
      if (cDevice *d = Device()) {
-        if (cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel())) {
+        LOCK_CHANNELS_READ;
+        if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel())) {
            caActivationReceiver = new cCaActivationReceiver(Channel, this);
            d->AttachReceiver(caActivationReceiver);
            dsyslog("CAM %d: activating on device %d with channel %d (%s)", SlotNumber(), d->DeviceNumber() + 1, Channel->Number(), Channel->Name());
diff -ruN vdr-2.2.0/ci.h vdr-2.3.1/ci.h
--- vdr-2.2.0/ci.h	2015-01-31 15:36:41.000000000 +0100
+++ vdr-2.3.1/ci.h	2015-01-31 15:36:41.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: ci.h 3.11 2015/01/31 14:36:41 kls Exp $
+ * $Id: ci.h 4.0 2015/01/31 14:36:41 kls Exp $
  */
 
 #ifndef __CI_H
diff -ruN vdr-2.2.0/config.c vdr-2.3.1/config.c
--- vdr-2.2.0/config.c	2015-02-10 13:24:13.000000000 +0100
+++ vdr-2.3.1/config.c	2015-09-13 13:09:44.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: config.c 3.10 2015/02/10 12:24:13 kls Exp $
+ * $Id: config.c 4.4 2015/09/13 11:09:44 kls Exp $
  */
 
 #include "config.h"
@@ -412,12 +412,16 @@
   EPGBugfixLevel = 3;
   EPGLinger = 0;
   SVDRPTimeout = 300;
+  SVDRPPeering = 0;
+  strn0cpy(SVDRPHostName, GetHostName(), sizeof(SVDRPHostName));
+  strcpy(SVDRPDefaultHost, "");
   ZapTimeout = 3;
   ChannelEntryTimeout = 1000;
   RcRepeatDelay = 300;
   RcRepeatDelta = 100;
   DefaultPriority = 50;
   DefaultLifetime = MAXLIFETIME;
+  RecordKeyHandling = 2;
   PauseKeyHandling = 2;
   PausePriority = 10;
   PauseLifetime = 1;
@@ -427,6 +431,7 @@
   RecordingDirs = 1;
   FoldersInTimerMenu = 1;
   AlwaysSortFoldersFirst = 1;
+  DefaultSortModeRec = rsmTime;
   NumberKeysForChars = 1;
   ColorKey0 = 0;
   ColorKey1 = 1;
@@ -634,12 +639,16 @@
   else if (!strcasecmp(Name, "EPGBugfixLevel"))      EPGBugfixLevel     = atoi(Value);
   else if (!strcasecmp(Name, "EPGLinger"))           EPGLinger          = atoi(Value);
   else if (!strcasecmp(Name, "SVDRPTimeout"))        SVDRPTimeout       = atoi(Value);
+  else if (!strcasecmp(Name, "SVDRPPeering"))        SVDRPPeering       = atoi(Value);
+  else if (!strcasecmp(Name, "SVDRPHostName"))       strn0cpy(SVDRPHostName, Value, sizeof(SVDRPHostName));
+  else if (!strcasecmp(Name, "SVDRPdefaultHost"))    strn0cpy(SVDRPDefaultHost, Value, sizeof(SVDRPDefaultHost));
   else if (!strcasecmp(Name, "ZapTimeout"))          ZapTimeout         = atoi(Value);
   else if (!strcasecmp(Name, "ChannelEntryTimeout")) ChannelEntryTimeout= atoi(Value);
   else if (!strcasecmp(Name, "RcRepeatDelay"))       RcRepeatDelay      = atoi(Value);
   else if (!strcasecmp(Name, "RcRepeatDelta"))       RcRepeatDelta      = atoi(Value);
   else if (!strcasecmp(Name, "DefaultPriority"))     DefaultPriority    = atoi(Value);
   else if (!strcasecmp(Name, "DefaultLifetime"))     DefaultLifetime    = atoi(Value);
+  else if (!strcasecmp(Name, "RecordKeyHandling"))   RecordKeyHandling  = atoi(Value);
   else if (!strcasecmp(Name, "PauseKeyHandling"))    PauseKeyHandling   = atoi(Value);
   else if (!strcasecmp(Name, "PausePriority"))       PausePriority      = atoi(Value);
   else if (!strcasecmp(Name, "PauseLifetime"))       PauseLifetime      = atoi(Value);
@@ -649,6 +658,7 @@
   else if (!strcasecmp(Name, "RecordingDirs"))       RecordingDirs      = atoi(Value);
   else if (!strcasecmp(Name, "FoldersInTimerMenu"))  FoldersInTimerMenu = atoi(Value);
   else if (!strcasecmp(Name, "AlwaysSortFoldersFirst")) AlwaysSortFoldersFirst = atoi(Value);
+  else if (!strcasecmp(Name, "DefaultSortModeRec"))  DefaultSortModeRec = atoi(Value);
   else if (!strcasecmp(Name, "NumberKeysForChars"))  NumberKeysForChars = atoi(Value);
   else if (!strcasecmp(Name, "ColorKey0"))           ColorKey0          = atoi(Value);
   else if (!strcasecmp(Name, "ColorKey1"))           ColorKey1          = atoi(Value);
@@ -760,12 +770,16 @@
   Store("EPGBugfixLevel",     EPGBugfixLevel);
   Store("EPGLinger",          EPGLinger);
   Store("SVDRPTimeout",       SVDRPTimeout);
+  Store("SVDRPPeering",       SVDRPPeering);
+  Store("SVDRPHostName",      SVDRPHostName);
+  Store("SVDRPDefaultHost",   SVDRPDefaultHost);
   Store("ZapTimeout",         ZapTimeout);
   Store("ChannelEntryTimeout",ChannelEntryTimeout);
   Store("RcRepeatDelay",      RcRepeatDelay);
   Store("RcRepeatDelta",      RcRepeatDelta);
   Store("DefaultPriority",    DefaultPriority);
   Store("DefaultLifetime",    DefaultLifetime);
+  Store("RecordKeyHandling",  RecordKeyHandling);
   Store("PauseKeyHandling",   PauseKeyHandling);
   Store("PausePriority",      PausePriority);
   Store("PauseLifetime",      PauseLifetime);
@@ -775,6 +789,7 @@
   Store("RecordingDirs",      RecordingDirs);
   Store("FoldersInTimerMenu", FoldersInTimerMenu);
   Store("AlwaysSortFoldersFirst", AlwaysSortFoldersFirst);
+  Store("DefaultSortModeRec", DefaultSortModeRec);
   Store("NumberKeysForChars", NumberKeysForChars);
   Store("ColorKey0",          ColorKey0);
   Store("ColorKey1",          ColorKey1);
diff -ruN vdr-2.2.0/config.h vdr-2.3.1/config.h
--- vdr-2.2.0/config.h	2015-02-13 16:39:08.000000000 +0100
+++ vdr-2.3.1/config.h	2015-09-11 10:07:34.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: config.h 3.21 2015/02/13 15:39:08 kls Exp $
+ * $Id: config.h 4.5 2015/09/11 08:07:34 kls Exp $
  */
 
 #ifndef __CONFIG_H
@@ -22,13 +22,13 @@
 
 // VDR's own version number:
 
-#define VDRVERSION  "2.2.0"
-#define VDRVERSNUM   20200  // Version * 10000 + Major * 100 + Minor
+#define VDRVERSION  "2.3.1"
+#define VDRVERSNUM   20301  // Version * 10000 + Major * 100 + Minor
 
 // The plugin API's version number:
 
-#define APIVERSION  "2.2.0"
-#define APIVERSNUM   20200  // Version * 10000 + Major * 100 + Minor
+#define APIVERSION  "2.3.1"
+#define APIVERSNUM   20301  // Version * 10000 + Major * 100 + Minor
 
 // When loading plugins, VDR searches them by their APIVERSION, which
 // may be smaller than VDRVERSION in case there have been no changes to
@@ -110,7 +110,7 @@
     cList<T>::Clear();
   }
 public:
-  cConfig(void) { fileName = NULL; }
+  cConfig(const char *NeedsLocking = NULL): cList<T>(NeedsLocking) { fileName = NULL; }
   virtual ~cConfig() { free(fileName); }
   const char *FileName(void) { return fileName; }
   bool Load(const char *FileName = NULL, bool AllowComments = false, bool MustExist = false)
@@ -160,7 +160,7 @@
        fprintf(stderr, "vdr: error while reading '%s'\n", fileName);
     return result;
   }
-  bool Save(void)
+  bool Save(void) const
   {
     bool result = true;
     T *l = (T *)this->First();
@@ -288,19 +288,24 @@
   int EPGBugfixLevel;
   int EPGLinger;
   int SVDRPTimeout;
+  int SVDRPPeering;
+  char SVDRPHostName[HOST_NAME_MAX];
+  char SVDRPDefaultHost[HOST_NAME_MAX];
   int ZapTimeout;
   int ChannelEntryTimeout;
   int RcRepeatDelay;
   int RcRepeatDelta;
   int DefaultPriority, DefaultLifetime;
-  int PausePriority, PauseLifetime;
+  int RecordKeyHandling;
   int PauseKeyHandling;
+  int PausePriority, PauseLifetime;
   int UseSubtitle;
   int UseVps;
   int VpsMargin;
   int RecordingDirs;
   int FoldersInTimerMenu;
   int AlwaysSortFoldersFirst;
+  int DefaultSortModeRec;
   int NumberKeysForChars;
   int ColorKey0, ColorKey1, ColorKey2, ColorKey3;
   int VideoDisplayFormat;
diff -ruN vdr-2.2.0/cutter.c vdr-2.3.1/cutter.c
--- vdr-2.2.0/cutter.c	2013-10-02 15:18:02.000000000 +0200
+++ vdr-2.3.1/cutter.c	2015-08-09 14:24:28.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: cutter.c 3.4 2013/10/02 13:18:02 kls Exp $
+ * $Id: cutter.c 4.2 2015/08/09 12:24:28 kls Exp $
  */
 
 #include "cutter.h"
@@ -634,7 +634,6 @@
                  }
               }
            }
-     Recordings.TouchUpdate();
      }
   else
      esyslog("no editing marks found!");
@@ -675,9 +674,11 @@
         editedVersionName = EditedFileName(originalVersionName);
         if (*editedVersionName) {
            if (strcmp(originalVersionName, editedVersionName) != 0) { // names must be different!
+              cRecordingUserCommand::InvokeCommand(RUC_EDITINGRECORDING, editedVersionName, originalVersionName);
               if (cVideoDirectory::RemoveVideoFile(editedVersionName) && MakeDirs(editedVersionName, true)) {
                  Recording.WriteInfo(editedVersionName);
-                 Recordings.AddByName(editedVersionName, false);
+                 LOCK_RECORDINGS_WRITE;
+                 Recordings->AddByName(editedVersionName, false);
                  cuttingThread = new cCuttingThread(originalVersionName, editedVersionName);
                  return true;
                  }
@@ -702,7 +703,8 @@
      if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), editedVersionName) == 0)
         cControl::Shutdown();
      cVideoDirectory::RemoveVideoFile(editedVersionName);
-     Recordings.DelByName(editedVersionName);
+     LOCK_RECORDINGS_WRITE;
+     Recordings->DelByName(editedVersionName);
      }
 }
 
diff -ruN vdr-2.2.0/cutter.h vdr-2.3.1/cutter.h
--- vdr-2.2.0/cutter.h	2013-10-05 13:34:55.000000000 +0200
+++ vdr-2.3.1/cutter.h	2013-10-05 13:34:55.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: cutter.h 3.1 2013/10/05 11:34:55 kls Exp $
+ * $Id: cutter.h 4.0 2013/10/05 11:34:55 kls Exp $
  */
 
 #ifndef __CUTTER_H
diff -ruN vdr-2.2.0/device.c vdr-2.3.1/device.c
--- vdr-2.2.0/device.c	2015-01-30 13:11:30.000000000 +0100
+++ vdr-2.3.1/device.c	2015-09-05 13:42:17.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: device.c 3.20 2015/01/30 12:11:30 kls Exp $
+ * $Id: device.c 4.2 2015/09/05 11:42:17 kls Exp $
  */
 
 #include "device.h"
@@ -24,7 +24,7 @@
 
 class cLiveSubtitle : public cReceiver {
 protected:
-  virtual void Receive(uchar *Data, int Length);
+  virtual void Receive(const uchar *Data, int Length);
 public:
   cLiveSubtitle(int SPid);
   virtual ~cLiveSubtitle();
@@ -40,7 +40,7 @@
   cReceiver::Detach();
 }
 
-void cLiveSubtitle::Receive(uchar *Data, int Length)
+void cLiveSubtitle::Receive(const uchar *Data, int Length)
 {
   if (cDevice::PrimaryDevice())
      cDevice::PrimaryDevice()->PlayTs(Data, Length);
@@ -722,20 +722,21 @@
      cControl::Shutdown(); // prevents old channel from being shown too long if GetDevice() takes longer
      int n = CurrentChannel() + Direction;
      int first = n;
-     cChannel *channel;
-     while ((channel = Channels.GetByNumber(n, Direction)) != NULL) {
+     LOCK_CHANNELS_READ;
+     const cChannel *Channel;
+     while ((Channel = Channels->GetByNumber(n, Direction)) != NULL) {
            // try only channels which are currently available
-           if (GetDevice(channel, LIVEPRIORITY, true, true))
+           if (GetDevice(Channel, LIVEPRIORITY, true, true))
               break;
-           n = channel->Number() + Direction;
+           n = Channel->Number() + Direction;
            }
-     if (channel) {
+     if (Channel) {
         int d = n - first;
         if (abs(d) == 1)
            dsyslog("skipped channel %d", first);
         else if (d)
            dsyslog("skipped channels %d..%d", first, n - sgn(d));
-        if (PrimaryDevice()->SwitchChannel(channel, true))
+        if (PrimaryDevice()->SwitchChannel(Channel, true))
            result = true;
         }
      else if (n != first)
@@ -777,7 +778,6 @@
         Result = scrNotAvailable;
      }
   else {
-     Channels.Lock(false);
      // Stop section handling:
      if (sectionHandler) {
         sectionHandler->SetStatus(false);
@@ -790,7 +790,8 @@
      if (SetChannelDevice(Channel, LiveView)) {
         // Start section handling:
         if (sectionHandler) {
-           patFilter->Trigger(Channel->Sid());
+           if (patFilter)
+              patFilter->Trigger(Channel->Sid());
            sectionHandler->SetChannel(Channel);
            sectionHandler->SetStatus(true);
            }
@@ -800,7 +801,6 @@
         }
      else
         Result = scrFailed;
-     Channels.Unlock();
      }
 
   if (Result == scrOk) {
@@ -829,8 +829,8 @@
 void cDevice::ForceTransferMode(void)
 {
   if (!cTransferControl::ReceiverDevice()) {
-     cChannel *Channel = Channels.GetByNumber(CurrentChannel());
-     if (Channel)
+     LOCK_CHANNELS_READ;
+     if (const cChannel *Channel = Channels->GetByNumber(CurrentChannel()))
         SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode
      }
 }
diff -ruN vdr-2.2.0/device.h vdr-2.3.1/device.h
--- vdr-2.2.0/device.h	2015-01-12 15:39:09.000000000 +0100
+++ vdr-2.3.1/device.h	2015-04-19 14:12:43.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: device.h 3.10 2015/01/12 14:39:09 kls Exp $
+ * $Id: device.h 4.1 2015/04/19 12:12:43 kls Exp $
  */
 
 #ifndef __DEVICE_H
@@ -55,7 +55,7 @@
                  // KNOWN TO YOUR PLAYER.
                };
 
-#define DEPRECATED_VIDEOSYSTEM
+//#define DEPRECATED_VIDEOSYSTEM
 #ifdef DEPRECATED_VIDEOSYSTEM
 enum eVideoSystem { vsPAL,
                     vsNTSC
diff -ruN vdr-2.2.0/diseqc.c vdr-2.3.1/diseqc.c
--- vdr-2.2.0/diseqc.c	2015-01-26 13:02:14.000000000 +0100
+++ vdr-2.3.1/diseqc.c	2015-01-26 13:02:14.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: diseqc.c 3.4 2015/01/26 12:02:14 kls Exp $
+ * $Id: diseqc.c 4.0 2015/01/26 12:02:14 kls Exp $
  */
 
 #include "diseqc.h"
diff -ruN vdr-2.2.0/diseqc.h vdr-2.3.1/diseqc.h
--- vdr-2.2.0/diseqc.h	2013-06-12 13:52:17.000000000 +0200
+++ vdr-2.3.1/diseqc.h	2013-06-12 13:52:17.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: diseqc.h 3.1 2013/06/12 11:52:17 kls Exp $
+ * $Id: diseqc.h 4.0 2013/06/12 11:52:17 kls Exp $
  */
 
 #ifndef __DISEQC_H
diff -ruN vdr-2.2.0/dvbci.c vdr-2.3.1/dvbci.c
--- vdr-2.2.0/dvbci.c	2015-01-14 12:13:49.000000000 +0100
+++ vdr-2.3.1/dvbci.c	2015-01-14 12:13:49.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: dvbci.c 3.1 2015/01/14 11:13:49 kls Exp $
+ * $Id: dvbci.c 4.0 2015/01/14 11:13:49 kls Exp $
  */
 
 #include "dvbci.h"
diff -ruN vdr-2.2.0/dvbci.h vdr-2.3.1/dvbci.h
--- vdr-2.2.0/dvbci.h	2006-11-26 12:19:42.000000000 +0100
+++ vdr-2.3.1/dvbci.h	2006-11-26 12:19:42.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: dvbci.h 3.0 2006/11/26 11:19:42 kls Exp $
+ * $Id: dvbci.h 4.0 2006/11/26 11:19:42 kls Exp $
  */
 
 #ifndef __DVBCI_H
diff -ruN vdr-2.2.0/dvbdevice.c vdr-2.3.1/dvbdevice.c
--- vdr-2.2.0/dvbdevice.c	2015-01-14 13:09:19.000000000 +0100
+++ vdr-2.3.1/dvbdevice.c	2015-04-18 18:19:28.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: dvbdevice.c 3.14 2015/01/14 12:09:19 kls Exp $
+ * $Id: dvbdevice.c 4.2 2015/04/18 16:19:28 kls Exp $
  */
 
 #include "dvbdevice.h"
@@ -1064,6 +1064,7 @@
 
 // --- cDvbDevice ------------------------------------------------------------
 
+bool cDvbDevice::useDvbDevices = true;
 int cDvbDevice::setTransferModeForDolbyDigital = 1;
 cMutex cDvbDevice::bondMutex;
 
@@ -1234,7 +1235,7 @@
             if (Exists(Adapter, Frontend)) {
                if (Found < MAXDEVICES) {
                   Found++;
-                  if (UseDevice(NextCardIndex())) {
+                  if (useDvbDevices && UseDevice(NextCardIndex())) {
                      if (Probe(Adapter, Frontend))
                         Used++;
                      }
@@ -1248,7 +1249,7 @@
   if (Found > 0) {
      isyslog("found %d DVB device%s", Found, Found > 1 ? "s" : "");
      if (Used != Found)
-        isyslog("using only %d DVB device%s", Used, Used > 1 ? "s" : "");
+        isyslog("using only %d DVB device%s", Used, Used != 1 ? "s" : "");
      }
   else
      isyslog("no DVB device found");
diff -ruN vdr-2.2.0/dvbdevice.h vdr-2.3.1/dvbdevice.h
--- vdr-2.2.0/dvbdevice.h	2014-03-16 11:38:31.000000000 +0100
+++ vdr-2.3.1/dvbdevice.h	2015-04-18 15:57:27.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: dvbdevice.h 3.5 2014/03/16 10:38:31 kls Exp $
+ * $Id: dvbdevice.h 4.1 2015/04/18 13:57:27 kls Exp $
  */
 
 #ifndef __DVBDEVICE_H
@@ -174,6 +174,7 @@
   static bool Probe(int Adapter, int Frontend);
          ///< Probes for existing DVB devices.
 public:
+  static bool useDvbDevices;
   static bool Initialize(void);
          ///< Initializes the DVB devices.
          ///< Must be called before accessing any DVB functions.
diff -ruN vdr-2.2.0/dvbplayer.c vdr-2.3.1/dvbplayer.c
--- vdr-2.2.0/dvbplayer.c	2015-02-13 16:12:57.000000000 +0100
+++ vdr-2.3.1/dvbplayer.c	2015-08-06 15:09:19.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: dvbplayer.c 3.6 2015/02/13 15:12:57 kls Exp $
+ * $Id: dvbplayer.c 4.1 2015/08/06 13:09:19 kls Exp $
  */
 
 #include "dvbplayer.h"
@@ -210,7 +210,7 @@
   cNonBlockingFileReader *nonBlockingFileReader;
   cRingBufferFrame *ringBuffer;
   cPtsIndex ptsIndex;
-  cMarks *marks;
+  const cMarks *marks;
   cFileName *fileName;
   cIndexFile *index;
   cUnbufferedFile *replayFile;
@@ -239,7 +239,7 @@
 public:
   cDvbPlayer(const char *FileName, bool PauseLive);
   virtual ~cDvbPlayer();
-  void SetMarks(cMarks *Marks);
+  void SetMarks(const cMarks *Marks);
   bool Active(void) { return cThread::Running(); }
   void Pause(void);
   void Play(void);
@@ -311,7 +311,7 @@
   // don't delete marks here, we don't own them!
 }
 
-void cDvbPlayer::SetMarks(cMarks *Marks)
+void cDvbPlayer::SetMarks(const cMarks *Marks)
 {
   marks = Marks;
 }
@@ -383,10 +383,11 @@
      int Index = ptsIndex.FindIndex(DeviceGetSTC());
      if (Index >= 0) {
         if (Setup.SkipEdited && marks) {
-           marks->Lock();
+           cStateKey StateKey;
+           marks->Lock(StateKey);
            if (marks->First() && abs(Index - marks->First()->Position()) <= int(round(RESUMEBACKUP * framesPerSecond)))
               Index = 0; // when stopping within RESUMEBACKUP seconds of the first mark the recording shall still be considered unviewed
-           marks->Unlock();
+           StateKey.Remove();
            }
         Index -= int(round(RESUMEBACKUP * framesPerSecond));
         if (Index > 0)
@@ -419,7 +420,8 @@
   if (readIndex > 0)
      isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond));
   else if (Setup.SkipEdited && marks) {
-     marks->Lock();
+     cStateKey StateKey;
+     marks->Lock(StateKey);
      if (marks->First() && index) {
         int Index = marks->First()->Position();
         uint16_t FileNumber;
@@ -429,7 +431,7 @@
            readIndex = Index;
            }
         }
-     marks->Unlock();
+     StateKey.Remove();
      }
 
   nonBlockingFileReader = new cNonBlockingFileReader;
@@ -500,8 +502,9 @@
                       if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length) && NextFile(FileNumber, FileOffset)) {
                          readIndex++;
                          if ((Setup.SkipEdited || Setup.PauseAtLastMark) && marks) {
-                            marks->Lock();
-                            cMark *m = marks->Get(readIndex);
+                            cStateKey StateKey;
+                            marks->Lock(StateKey);
+                            const cMark *m = marks->Get(readIndex);
                             if (m && (m->Index() & 0x01) != 0) { // we're at an end mark
                                m = marks->GetNextBegin(m);
                                int Index = -1;
@@ -519,7 +522,7 @@
                                   CutIn = true;
                                   }
                                }
-                            marks->Unlock();
+                            StateKey.Remove();
                             }
                          }
                       else
@@ -943,7 +946,7 @@
   Stop();
 }
 
-void cDvbPlayerControl::SetMarks(cMarks *Marks)
+void cDvbPlayerControl::SetMarks(const cMarks *Marks)
 {
   if (player)
      player->SetMarks(Marks);
diff -ruN vdr-2.2.0/dvbplayer.h vdr-2.3.1/dvbplayer.h
--- vdr-2.2.0/dvbplayer.h	2015-02-06 13:27:39.000000000 +0100
+++ vdr-2.3.1/dvbplayer.h	2015-08-02 15:01:44.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: dvbplayer.h 3.2 2015/02/06 12:27:39 kls Exp $
+ * $Id: dvbplayer.h 4.1 2015/08/02 13:01:44 kls Exp $
  */
 
 #ifndef __DVBPLAYER_H
@@ -26,7 +26,7 @@
        // file of the recording is long enough to allow the player to display
        // the first frame in still picture mode.
   virtual ~cDvbPlayerControl();
-  void SetMarks(cMarks *Marks);
+  void SetMarks(const cMarks *Marks);
   bool Active(void);
   void Stop(void);
        // Stops the current replay session (if any).
diff -ruN vdr-2.2.0/dvbspu.c vdr-2.3.1/dvbspu.c
--- vdr-2.2.0/dvbspu.c	2013-02-22 16:25:16.000000000 +0100
+++ vdr-2.3.1/dvbspu.c	2013-02-22 16:25:16.000000000 +0100
@@ -8,7 +8,7 @@
  *
  * parts of this file are derived from the OMS program.
  *
- * $Id: dvbspu.c 3.0 2013/02/22 15:25:16 kls Exp $
+ * $Id: dvbspu.c 4.0 2013/02/22 15:25:16 kls Exp $
  */
 
 #include "dvbspu.h"
diff -ruN vdr-2.2.0/dvbspu.h vdr-2.3.1/dvbspu.h
--- vdr-2.2.0/dvbspu.h	2014-02-08 13:27:34.000000000 +0100
+++ vdr-2.3.1/dvbspu.h	2014-02-08 13:27:34.000000000 +0100
@@ -8,7 +8,7 @@
  *
  * parts of this file are derived from the OMS program.
  *
- * $Id: dvbspu.h 3.1 2014/02/08 12:27:34 kls Exp $
+ * $Id: dvbspu.h 4.0 2014/02/08 12:27:34 kls Exp $
  */
 
 #ifndef __DVBSPU_H
diff -ruN vdr-2.2.0/dvbsubtitle.c vdr-2.3.1/dvbsubtitle.c
--- vdr-2.2.0/dvbsubtitle.c	2015-01-20 15:53:57.000000000 +0100
+++ vdr-2.3.1/dvbsubtitle.c	2015-03-25 13:37:08.000000000 +0100
@@ -7,7 +7,7 @@
  * Original author: Marco Schluessler <marco@lordzodiac.de>
  * With some input from the "subtitles plugin" by Pekka Virtanen <pekka.virtanen@sci.fi>
  *
- * $Id: dvbsubtitle.c 3.10 2015/01/20 14:53:57 kls Exp $
+ * $Id: dvbsubtitle.c 4.1 2015/03/25 12:37:08 kls Exp $
  */
 
 #include "dvbsubtitle.h"
@@ -1516,8 +1516,7 @@
      osdDeltaX = osdDeltaY = 0;
      }
   else {
-     osdFactorX = VideoAspect * OsdHeight / displayWidth;
-     osdFactorY = double(OsdHeight) / displayHeight;
+     osdFactorX = osdFactorY = min(double(OsdWidth) / displayWidth, double(OsdHeight) / displayHeight);
      osdDeltaX = (OsdWidth - displayWidth * osdFactorX) / 2;
      osdDeltaY = (OsdHeight - displayHeight * osdFactorY) / 2;
      }
diff -ruN vdr-2.2.0/dvbsubtitle.h vdr-2.3.1/dvbsubtitle.h
--- vdr-2.2.0/dvbsubtitle.h	2015-01-14 11:01:48.000000000 +0100
+++ vdr-2.3.1/dvbsubtitle.h	2015-04-28 11:25:57.000000000 +0200
@@ -6,7 +6,7 @@
  *
  * Original author: Marco Schluessler <marco@lordzodiac.de>
  *
- * $Id: dvbsubtitle.h 3.2 2015/01/14 10:01:48 kls Exp $
+ * $Id: dvbsubtitle.h 4.1 2015/04/28 09:25:57 kls Exp $
  */
 
 #ifndef __DVBSUBTITLE_H
@@ -48,7 +48,7 @@
 public:
   cDvbSubtitleConverter(void);
   virtual ~cDvbSubtitleConverter();
-  void Action(void);
+  virtual void Action(void);
   void Reset(void);
   void Freeze(bool Status) { frozen = Status; }
   int ConvertFragments(const uchar *Data, int Length); // for legacy PES recordings
diff -ruN vdr-2.2.0/eit.c vdr-2.3.1/eit.c
--- vdr-2.2.0/eit.c	2015-02-01 15:55:27.000000000 +0100
+++ vdr-2.3.1/eit.c	2015-08-23 12:43:36.000000000 +0200
@@ -8,7 +8,7 @@
  * Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
  * Adapted to 'libsi' for VDR 1.3.0 by Marcel Wiesweg <marcel.wiesweg@gmx.de>.
  *
- * $Id: eit.c 3.6 2015/02/01 14:55:27 kls Exp $
+ * $Id: eit.c 4.1 2015/08/23 10:43:36 kls Exp $
  */
 
 #include "eit.h"
@@ -24,31 +24,53 @@
 
 class cEIT : public SI::EIT {
 public:
-  cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bool OnlyRunningStatus = false);
+  cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const u_char *Data);
   };
 
-cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bool OnlyRunningStatus)
+cEIT::cEIT(cSectionSyncerHash &SectionSyncerHash, int Source, u_char Tid, const u_char *Data)
 :SI::EIT(Data, false)
 {
   if (!CheckCRCAndParse())
      return;
+  int HashId = Tid * getServiceId();
+  cSectionSyncerEntry *SectionSyncerEntry = SectionSyncerHash.Get(HashId);
+  if (!SectionSyncerEntry) {
+     SectionSyncerEntry = new cSectionSyncerEntry;
+     SectionSyncerHash.Add(SectionSyncerEntry, HashId);
+     }
+  bool Process = SectionSyncerEntry->Sync(getVersionNumber(), getSectionNumber(), getLastSectionNumber());
+  if (Tid != 0x4E && !Process) // we need to set the 'seen' tag to watch the running status of the present/following event
+     return;
 
   time_t Now = time(NULL);
   if (Now < VALID_TIME)
      return; // we need the current time for handling PDC descriptors
 
-  if (!Channels.Lock(false, 10))
+  cStateKey ChannelsStateKey;
+  cChannels *Channels = cChannels::GetChannelsWrite(ChannelsStateKey, 10);
+  if (!Channels) {
+     SectionSyncerEntry->Repeat(); // let's not miss any section of the EIT
      return;
+     }
   tChannelID channelID(Source, getOriginalNetworkId(), getTransportStreamId(), getServiceId());
-  cChannel *channel = Channels.GetByChannelID(channelID, true);
-  if (!channel || EpgHandlers.IgnoreChannel(channel)) {
-     Channels.Unlock();
+  cChannel *Channel = Channels->GetByChannelID(channelID, true);
+  if (!Channel || EpgHandlers.IgnoreChannel(Channel)) {
+     ChannelsStateKey.Remove(false);
      return;
      }
 
-  EpgHandlers.BeginSegmentTransfer(channel, OnlyRunningStatus);
-  bool handledExternally = EpgHandlers.HandledExternally(channel);
-  cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(channel, true);
+  cStateKey SchedulesStateKey;
+  cSchedules *Schedules = cSchedules::GetSchedulesWrite(SchedulesStateKey, 10);
+  if (!Schedules) {
+     SectionSyncerEntry->Repeat(); // let's not miss any section of the EIT
+     ChannelsStateKey.Remove(false);
+     return;
+     }
+
+  bool ChannelsModified = false;
+  EpgHandlers.BeginSegmentTransfer(Channel);
+  bool handledExternally = EpgHandlers.HandledExternally(Channel);
+  cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(Channel, true);
 
   bool Empty = true;
   bool Modified = false;
@@ -74,8 +96,6 @@
       cEvent *rEvent = NULL;
       cEvent *pEvent = (cEvent *)pSchedule->GetEvent(SiEitEvent.getEventId(), StartTime);
       if (!pEvent || handledExternally) {
-         if (OnlyRunningStatus)
-            continue;
          if (handledExternally && !EpgHandlers.IsUpdate(SiEitEvent.getEventId(), StartTime, Tid, getVersionNumber()))
             continue;
          // If we don't have that event yet, we create a new one.
@@ -94,14 +114,6 @@
          // The lower the table ID, the more "current" the information.
          if (Tid > TableID)
             continue;
-         // If the new event comes from the same table and has the same version number
-         // as the existing one, let's skip it to avoid unnecessary work.
-         // Unfortunately some stations (like, e.g. "Premiere") broadcast their EPG data on several transponders (like
-         // the actual Premiere transponder and the Sat.1/Pro7 transponder), but use different version numbers on
-         // each of them :-( So if one DVB card is tuned to the Premiere transponder, while an other one is tuned
-         // to the Sat.1/Pro7 transponder, events will keep toggling because of the bogus version numbers.
-         else if (Tid == TableID && pEvent->Version() == getVersionNumber())
-            continue;
          EpgHandlers.SetEventID(pEvent, SiEitEvent.getEventId()); // unfortunately some stations use different event ids for the same event in different tables :-(
          EpgHandlers.SetStartTime(pEvent, StartTime);
          EpgHandlers.SetDuration(pEvent, Duration);
@@ -110,11 +122,9 @@
          pEvent->SetTableID(Tid);
       if (Tid == 0x4E) { // we trust only the present/following info on the actual TS
          if (SiEitEvent.getRunningStatus() >= SI::RunningStatusNotRunning)
-            pSchedule->SetRunningStatus(pEvent, SiEitEvent.getRunningStatus(), channel);
-         }
-      if (OnlyRunningStatus) {
-         pEvent->SetVersion(0xFF); // we have already changed the table id above, so set the version to an invalid value to make sure the next full run will be executed
-         continue; // do this before setting the version, so that the full update can be done later
+            pSchedule->SetRunningStatus(pEvent, SiEitEvent.getRunningStatus(), Channel);
+         if (!Process)
+            continue;
          }
       pEvent->SetVersion(getVersionNumber());
 
@@ -206,7 +216,7 @@
                  break;
             case SI::TimeShiftedEventDescriptorTag: {
                  SI::TimeShiftedEventDescriptor *tsed = (SI::TimeShiftedEventDescriptor *)d;
-                 cSchedule *rSchedule = (cSchedule *)Schedules->GetSchedule(tChannelID(Source, channel->Nid(), channel->Tid(), tsed->getReferenceServiceId()));
+                 cSchedule *rSchedule = (cSchedule *)Schedules->GetSchedule(tChannelID(Source, Channel->Nid(), Channel->Tid(), tsed->getReferenceServiceId()));
                  if (!rSchedule)
                     break;
                  rEvent = (cEvent *)rSchedule->GetEvent(tsed->getReferenceEventId());
@@ -226,18 +236,18 @@
                        char linkName[ld->privateData.getLength() + 1];
                        strn0cpy(linkName, (const char *)ld->privateData.getData(), sizeof(linkName));
                        // TODO is there a standard way to determine the character set of this string?
-                       cChannel *link = Channels.GetByChannelID(linkID);
-                       if (link != channel) { // only link to other channels, not the same one
-                          //fprintf(stderr, "Linkage %s %4d %4d %5d %5d %5d %5d  %02X  '%s'\n", hit ? "*" : "", channel->Number(), link ? link->Number() : -1, SiEitEvent.getEventId(), ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId(), ld->getLinkageType(), linkName);//XXX
+                       cChannel *link = Channels->GetByChannelID(linkID);
+                       if (link != Channel) { // only link to other channels, not the same one
                           if (link) {
                              if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3)
-                                link->SetName(linkName, "", "");
+                                ChannelsModified |= link->SetName(linkName, "", "");
                              }
                           else if (Setup.UpdateChannels >= 4) {
-                             cChannel *transponder = channel;
-                             if (channel->Tid() != ld->getTransportStreamId())
-                                transponder = Channels.GetByTransponderID(linkID);
-                             link = Channels.NewChannel(transponder, linkName, "", "", ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId());
+                             cChannel *Transponder = Channel;
+                             if (Channel->Tid() != ld->getTransportStreamId())
+                                Transponder = Channels->GetByTransponderID(linkID);
+                             link = Channels->NewChannel(Transponder, linkName, "", "", ld->getOriginalNetworkId(), ld->getTransportStreamId(), ld->getServiceId());
+                             ChannelsModified = true;
                              //XXX patFilter->Trigger();
                              }
                           if (link) {
@@ -247,7 +257,7 @@
                              }
                           }
                        else
-                          channel->SetPortalName(linkName);
+                          ChannelsModified |= Channel->SetPortalName(linkName);
                        }
                     }
                  }
@@ -293,7 +303,7 @@
 
       EpgHandlers.FixEpgBugs(pEvent);
       if (LinkChannels)
-         channel->SetLinkChannels(LinkChannels);
+         ChannelsModified |= Channel->SetLinkChannels(LinkChannels);
       Modified = true;
       EpgHandlers.HandleEvent(pEvent);
       if (handledExternally)
@@ -302,16 +312,17 @@
   if (Tid == 0x4E) {
      if (Empty && getSectionNumber() == 0)
         // ETR 211: an empty entry in section 0 of table 0x4E means there is currently no event running
-        pSchedule->ClrRunningStatus(channel);
+        pSchedule->ClrRunningStatus(Channel);
      pSchedule->SetPresentSeen();
      }
-  if (Modified && !OnlyRunningStatus) {
+  if (Modified) {
      EpgHandlers.SortSchedule(pSchedule);
      EpgHandlers.DropOutdated(pSchedule, SegmentStart, SegmentEnd, Tid, getVersionNumber());
-     Schedules->SetModified(pSchedule);
+     pSchedule->SetModified();
      }
-  Channels.Unlock();
-  EpgHandlers.EndSegmentTransfer(Modified, OnlyRunningStatus);
+  SchedulesStateKey.Remove(Modified);
+  ChannelsStateKey.Remove(ChannelsModified);
+  EpgHandlers.EndSegmentTransfer(Modified);
 }
 
 // --- cTDT ------------------------------------------------------------------
@@ -372,6 +383,13 @@
   Set(0x14, 0x70);        // TDT
 }
 
+void cEitFilter::SetStatus(bool On)
+{
+  cMutexLock MutexLock(&mutex);
+  cFilter::SetStatus(On);
+  sectionSyncerHash.Clear();
+}
+
 void cEitFilter::SetDisableUntil(time_t Time)
 {
   disableUntil = Time;
@@ -379,6 +397,7 @@
 
 void cEitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
 {
+  cMutexLock MutexLock(&mutex);
   if (disableUntil) {
      if (time(NULL) > disableUntil)
         disableUntil = 0;
@@ -387,22 +406,8 @@
      }
   switch (Pid) {
     case 0x12: {
-         if (Tid >= 0x4E && Tid <= 0x6F) {
-            cSchedulesLock SchedulesLock(true, 10);
-            cSchedules *Schedules = (cSchedules *)cSchedules::Schedules(SchedulesLock);
-            if (Schedules)
-               cEIT EIT(Schedules, Source(), Tid, Data);
-            else {
-               // If we don't get a write lock, let's at least get a read lock, so
-               // that we can set the running status and 'seen' timestamp (well, actually
-               // with a read lock we shouldn't be doing that, but it's only integers that
-               // get changed, so it should be ok)
-               cSchedulesLock SchedulesLock;
-               cSchedules *Schedules = (cSchedules *)cSchedules::Schedules(SchedulesLock);
-               if (Schedules)
-                  cEIT EIT(Schedules, Source(), Tid, Data, true);
-               }
-            }
+         if (Tid >= 0x4E && Tid <= 0x6F)
+            cEIT EIT(sectionSyncerHash, Source(), Tid, Data);
          }
          break;
     case 0x14: {
diff -ruN vdr-2.2.0/eit.h vdr-2.3.1/eit.h
--- vdr-2.2.0/eit.h	2010-01-03 16:28:34.000000000 +0100
+++ vdr-2.3.1/eit.h	2015-07-25 13:03:53.000000000 +0200
@@ -4,21 +4,29 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: eit.h 3.0 2010/01/03 15:28:34 kls Exp $
+ * $Id: eit.h 4.1 2015/07/25 11:03:53 kls Exp $
  */
 
 #ifndef __EIT_H
 #define __EIT_H
 
 #include "filter.h"
+#include "tools.h"
+
+class cSectionSyncerEntry : public cListObject, public cSectionSyncer {};
+
+class cSectionSyncerHash : public cHash<cSectionSyncerEntry> {};
 
 class cEitFilter : public cFilter {
 private:
+  cMutex mutex;
+  cSectionSyncerHash sectionSyncerHash;
   static time_t disableUntil;
 protected:
   virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
 public:
   cEitFilter(void);
+  virtual void SetStatus(bool On);
   static void SetDisableUntil(time_t Time);
   };
 
diff -ruN vdr-2.2.0/eitscan.c vdr-2.3.1/eitscan.c
--- vdr-2.2.0/eitscan.c	2012-04-07 16:39:28.000000000 +0200
+++ vdr-2.3.1/eitscan.c	2015-09-10 13:05:03.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: eitscan.c 3.0 2012/04/07 14:39:28 kls Exp $
+ * $Id: eitscan.c 4.2 2015/09/10 11:05:03 kls Exp $
  */
 
 #include "eitscan.h"
@@ -45,13 +45,13 @@
 
 class cScanList : public cList<cScanData> {
 public:
-  void AddTransponders(cList<cChannel> *Channels);
+  void AddTransponders(const cList<cChannel> *Channels);
   void AddTransponder(const cChannel *Channel);
   };
 
-void cScanList::AddTransponders(cList<cChannel> *Channels)
+void cScanList::AddTransponders(const cList<cChannel> *Channels)
 {
-  for (cChannel *ch = Channels->First(); ch; ch = Channels->Next(ch))
+  for (const cChannel *ch = Channels->First(); ch; ch = Channels->Next(ch))
       AddTransponder(ch);
   Sort();
 }
@@ -118,7 +118,8 @@
 void cEITScanner::Activity(void)
 {
   if (currentChannel) {
-     Channels.SwitchTo(currentChannel);
+     LOCK_CHANNELS_READ;
+     Channels->SwitchTo(currentChannel);
      currentChannel = 0;
      }
   lastActivity = time(NULL);
@@ -129,7 +130,8 @@
   if (Setup.EPGScanTimeout || !lastActivity) { // !lastActivity means a scan was forced
      time_t now = time(NULL);
      if (now - lastScan > ScanTimeout && now - lastActivity > ActivityTimeout) {
-        if (Channels.Lock(false, 10)) {
+        cStateKey StateKey;
+        if (const cChannels *Channels = cChannels::GetChannelsRead(StateKey, 10)) {
            if (!scanList) {
               scanList = new cScanList;
               if (transponderList) {
@@ -137,7 +139,7 @@
                  delete transponderList;
                  transponderList = NULL;
                  }
-              scanList->AddTransponders(&Channels);
+              scanList->AddTransponders(Channels);
               }
            bool AnyDeviceSwitched = false;
            for (int i = 0; i < cDevice::NumDevices(); i++) {
@@ -149,6 +151,10 @@
                          if (!Channel->Ca() || Channel->Ca() == Device->DeviceNumber() + 1 || Channel->Ca() >= CA_ENCRYPTED_MIN) {
                             if (Device->ProvidesTransponder(Channel)) {
                                if (Device->Priority() < 0) {
+                                  if (const cPositioner *Positioner = Device->Positioner()) {
+                                     if (Positioner->LastLongitude() != cSource::Position(Channel->Source()))
+                                        continue;
+                                     }
                                   bool MaySwitchTransponder = Device->MaySwitchTransponder(Channel);
                                   if (MaySwitchTransponder || Device->ProvidesTransponderExclusively(Channel) && now - lastActivity > Setup.EPGScanTimeout * 3600) {
                                      if (!MaySwitchTransponder) {
@@ -177,7 +183,7 @@
               if (lastActivity == 0) // this was a triggered scan
                  Activity();
               }
-           Channels.Unlock();
+           StateKey.Remove();
            }
         lastScan = time(NULL);
         }
diff -ruN vdr-2.2.0/eitscan.h vdr-2.3.1/eitscan.h
--- vdr-2.2.0/eitscan.h	2012-03-07 14:54:16.000000000 +0100
+++ vdr-2.3.1/eitscan.h	2012-03-07 14:54:16.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: eitscan.h 3.0 2012/03/07 13:54:16 kls Exp $
+ * $Id: eitscan.h 4.0 2012/03/07 13:54:16 kls Exp $
  */
 
 #ifndef __EITSCAN_H
diff -ruN vdr-2.2.0/epg.c vdr-2.3.1/epg.c
--- vdr-2.2.0/epg.c	2013-12-28 12:33:08.000000000 +0100
+++ vdr-2.3.1/epg.c	2015-09-10 12:58:19.000000000 +0200
@@ -7,7 +7,7 @@
  * Original version (as used in VDR before 1.3.0) written by
  * Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
  *
- * $Id: epg.c 3.3 2013/12/28 11:33:08 kls Exp $
+ * $Id: epg.c 4.2 2015/09/10 10:58:19 kls Exp $
  */
 
 #include "epg.h"
@@ -15,7 +15,6 @@
 #include <limits.h>
 #include <time.h>
 #include "libsi/si.h"
-#include "timers.h"
 
 #define RUNNINGSTATUSTIMEOUT 30 // seconds before the running status is considered unknown
 #define EPGDATAWRITEDELTA   600 // seconds between writing the epg.data file
@@ -111,9 +110,12 @@
 
 // --- cEvent ----------------------------------------------------------------
 
+cMutex cEvent::numTimersMutex;
+
 cEvent::cEvent(tEventID EventID)
 {
   schedule = NULL;
+  numTimers = 0;
   eventID = EventID;
   tableID = 0xFF; // actual table ids are 0x4E..0x60
   version = 0xFF; // actual version numbers are 0..31
@@ -170,9 +172,9 @@
   version = Version;
 }
 
-void cEvent::SetRunningStatus(int RunningStatus, cChannel *Channel)
+void cEvent::SetRunningStatus(int RunningStatus, const cChannel *Channel)
 {
-  if (Channel && runningStatus != RunningStatus && (RunningStatus > SI::RunningStatusNotRunning || runningStatus > SI::RunningStatusUndefined) && Channel->HasTimer())
+  if (Channel && runningStatus != RunningStatus && (RunningStatus > SI::RunningStatusNotRunning || runningStatus > SI::RunningStatusUndefined) && schedule && schedule->HasTimer())
      isyslog("channel %d (%s) event %s status %d", Channel->Number(), Channel->Name(), *ToDescr(), RunningStatus);
   runningStatus = RunningStatus;
 }
@@ -243,13 +245,22 @@
   return cString::sprintf("%s %s-%s %s'%s'", *GetDateString(), *GetTimeString(), *GetEndTimeString(), vpsbuf, Title());
 }
 
-bool cEvent::HasTimer(void) const
+void cEvent::IncNumTimers(void) const
 {
-  for (cTimer *t = Timers.First(); t; t = Timers.Next(t)) {
-      if (t->Event() == this)
-         return true;
-      }
-  return false;
+  numTimersMutex.Lock();
+  numTimers++;
+  if (schedule)
+     schedule->IncNumTimers();
+  numTimersMutex.Unlock();
+}
+
+void cEvent::DecNumTimers(void) const
+{
+  numTimersMutex.Lock();
+  numTimers--;
+  if (schedule)
+     schedule->DecNumTimers();
+  numTimersMutex.Unlock();
 }
 
 bool cEvent::IsRunning(bool OrAboutToStart) const
@@ -605,9 +616,9 @@
             bool PrintedStats = false;
             char *q = buffer;
             *buffer = 0;
+            LOCK_CHANNELS_READ;
             for (int c = 0; c < p->n; c++) {
-                cChannel *channel = Channels.GetByChannelID(p->channelIDs[c], true);
-                if (channel) {
+                if (const cChannel *Channel = Channels->GetByChannelID(p->channelIDs[c], true)) {
                    if (!GotHits) {
                       dsyslog("=====================");
                       dsyslog("EPG bugfix statistics");
@@ -623,7 +634,7 @@
                       q += snprintf(q, sizeof(buffer) - (q - buffer), "%-3d %-4d", i, p->hits);
                       PrintedStats = true;
                       }
-                   q += snprintf(q, sizeof(buffer) - (q - buffer), "%s%s", delim, channel->Name());
+                   q += snprintf(q, sizeof(buffer) - (q - buffer), "%s%s", delim, Channel->Name());
                    delim = ", ";
                    if (q - buffer > 80) {
                       q += snprintf(q, sizeof(buffer) - (q - buffer), "%s...", delim);
@@ -650,7 +661,7 @@
            uchar *p = (uchar *)s;
            if (l == 2 && *p == 0xC2) // UTF-8 sequence
               p++;
-           if (*p == 0x86 || *p == 0x87) {
+           if (*p == 0x86 || *p == 0x87 || *p == 0x0D) {
               memmove(s, p + 1, len - l + 1); // we also copy the terminating 0!
               len -= l;
               l = 0;
@@ -878,14 +889,32 @@
 
 // --- cSchedule -------------------------------------------------------------
 
+cMutex cSchedule::numTimersMutex;
+
 cSchedule::cSchedule(tChannelID ChannelID)
 {
   channelID = ChannelID;
+  events.SetUseGarbageCollector();
+  numTimers = 0;
   hasRunning = false;
   modified = 0;
   presentSeen = 0;
 }
 
+void cSchedule::IncNumTimers(void) const
+{
+  numTimersMutex.Lock();
+  numTimers++;
+  numTimersMutex.Unlock();
+}
+
+void cSchedule::DecNumTimers(void) const
+{
+  numTimersMutex.Lock();
+  numTimers--;
+  numTimersMutex.Unlock();
+}
+
 cEvent *cSchedule::AddEvent(cEvent *Event)
 {
   events.Add(Event);
@@ -897,8 +926,6 @@
 void cSchedule::DelEvent(cEvent *Event)
 {
   if (Event->schedule == this) {
-     if (hasRunning && Event->IsRunning())
-        ClrRunningStatus();
      UnhashEvent(Event);
      events.Del(Event);
      }
@@ -922,7 +949,7 @@
 {
   const cEvent *pe = NULL;
   time_t now = time(NULL);
-  for (cEvent *p = events.First(); p; p = events.Next(p)) {
+  for (const cEvent *p = events.First(); p; p = events.Next(p)) {
       if (p->StartTime() <= now)
          pe = p;
       else if (p->StartTime() > now + 3600)
@@ -962,7 +989,7 @@
 {
   const cEvent *pe = NULL;
   time_t delta = INT_MAX;
-  for (cEvent *p = events.First(); p; p = events.Next(p)) {
+  for (const cEvent *p = events.First(); p; p = events.Next(p)) {
       time_t dt = Time - p->StartTime();
       if (dt >= 0 && dt < delta && p->EndTime() >= Time) {
          delta = dt;
@@ -972,7 +999,7 @@
   return pe;
 }
 
-void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Channel)
+void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, const cChannel *Channel)
 {
   hasRunning = false;
   for (cEvent *p = events.First(); p; p = events.Next(p)) {
@@ -987,6 +1014,7 @@
       if (p->RunningStatus() >= SI::RunningStatusPausing)
          hasRunning = true;
       }
+  SetPresentSeen();
 }
 
 void cSchedule::ClrRunningStatus(cChannel *Channel)
@@ -1019,32 +1047,29 @@
          p->SetRunningStatus(SI::RunningStatusNotRunning);
          }
      }
+  SetModified();
 }
 
 void cSchedule::DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version)
 {
   if (SegmentStart > 0 && SegmentEnd > 0) {
-     for (cEvent *p = events.First(); p; p = events.Next(p)) {
-         if (p->EndTime() > SegmentStart) {
-            if (p->StartTime() < SegmentEnd) {
-               // The event overlaps with the given time segment.
-               if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) {
-                  // The segment overwrites all events from tables with higher ids, and
-                  // within the same table id all events must have the same version.
-                  // We can't delete the event right here because a timer might have
-                  // a pointer to it, so let's set its id and start time to 0 to have it
-                  // "phased out":
-                  if (hasRunning && p->IsRunning())
-                     ClrRunningStatus();
-                  UnhashEvent(p);
-                  p->eventID = 0;
-                  p->startTime = 0;
-                  }
-               }
-            else
-               break;
-            }
-         }
+     cEvent *p = events.First();
+     while (p) {
+           cEvent *n = events.Next(p);
+           if (p->EndTime() > SegmentStart) {
+              if (p->StartTime() < SegmentEnd) {
+                 // The event overlaps with the given time segment.
+                 if (p->TableID() > TableID || p->TableID() == TableID && p->Version() != Version) {
+                    // The segment overwrites all events from tables with higher ids, and
+                    // within the same table id all events must have the same version.
+                    DelEvent(p);
+                    }
+                 }
+              else
+                 break;
+              }
+           p = n;
+           }
      }
 }
 
@@ -1066,9 +1091,9 @@
 
 void cSchedule::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime) const
 {
-  cChannel *channel = Channels.GetByChannelID(channelID, true);
-  if (channel) {
-     fprintf(f, "%sC %s %s\n", Prefix, *channel->GetChannelID().ToString(), channel->Name());
+  LOCK_CHANNELS_READ;
+  if (const cChannel *Channel = Channels->GetByChannelID(channelID, true)) {
+     fprintf(f, "%sC %s %s\n", Prefix, *Channel->GetChannelID().ToString(), Channel->Name());
      const cEvent *p;
      switch (DumpMode) {
        case dmAll: {
@@ -1111,12 +1136,10 @@
               if (*s) {
                  tChannelID channelID = tChannelID::FromString(s);
                  if (channelID.Valid()) {
-                    cSchedule *p = Schedules->AddSchedule(channelID);
-                    if (p) {
+                    if (cSchedule *p = Schedules->AddSchedule(channelID)) {
                        if (!cEvent::Read(f, p))
                           return false;
                        p->Sort();
-                       Schedules->SetModified(p);
                        }
                     }
                  else {
@@ -1164,12 +1187,12 @@
 {
   cMutexLock MutexLock(&mutex); // to make sure fore- and background calls don't cause parellel dumps!
   {
-    cSchedulesLock SchedulesLock(true, 1000);
-    cSchedules *s = (cSchedules *)cSchedules::Schedules(SchedulesLock);
-    if (s) {
+    cStateKey StateKey;
+    if (cSchedules *Schedules = cSchedules::GetSchedulesWrite(StateKey, 1000)) {
        time_t now = time(NULL);
-       for (cSchedule *p = s->First(); p; p = s->Next(p))
+       for (cSchedule *p = Schedules->First(); p; p = Schedules->Next(p))
            p->Cleanup(now);
+       StateKey.Remove();
        }
   }
   if (dump)
@@ -1178,29 +1201,25 @@
 
 static cEpgDataWriter EpgDataWriter;
 
-// --- cSchedulesLock --------------------------------------------------------
+// --- cSchedules ------------------------------------------------------------
+
+cSchedules cSchedules::schedules;
+char *cSchedules::epgDataFileName = NULL;
+time_t cSchedules::lastDump = time(NULL);
 
-cSchedulesLock::cSchedulesLock(bool WriteLock, int TimeoutMs)
+cSchedules::cSchedules(void)
+:cList<cSchedule>("Schedules")
 {
-  locked = cSchedules::schedules.rwlock.Lock(WriteLock, TimeoutMs);
 }
 
-cSchedulesLock::~cSchedulesLock()
+const cSchedules *cSchedules::GetSchedulesRead(cStateKey &StateKey, int TimeoutMs)
 {
-  if (locked)
-     cSchedules::schedules.rwlock.Unlock();
+  return schedules.Lock(StateKey, false, TimeoutMs) ? &schedules : NULL;
 }
 
-// --- cSchedules ------------------------------------------------------------
-
-cSchedules cSchedules::schedules;
-char *cSchedules::epgDataFileName = NULL;
-time_t cSchedules::lastDump = time(NULL);
-time_t cSchedules::modified = 0;
-
-const cSchedules *cSchedules::Schedules(cSchedulesLock &SchedulesLock)
+cSchedules *cSchedules::GetSchedulesWrite(cStateKey &StateKey, int TimeoutMs)
 {
-  return SchedulesLock.Locked() ? &schedules : NULL;
+  return schedules.Lock(StateKey, true, TimeoutMs) ? &schedules : NULL;
 }
 
 void cSchedules::SetEpgDataFileName(const char *FileName)
@@ -1210,12 +1229,6 @@
   EpgDataWriter.SetDump(epgDataFileName != NULL);
 }
 
-void cSchedules::SetModified(cSchedule *Schedule)
-{
-  Schedule->SetModified();
-  modified = time(NULL);
-}
-
 void cSchedules::Cleanup(bool Force)
 {
   if (Force)
@@ -1232,83 +1245,59 @@
 
 void cSchedules::ResetVersions(void)
 {
-  cSchedulesLock SchedulesLock(true);
-  cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
-  if (s) {
-     for (cSchedule *Schedule = s->First(); Schedule; Schedule = s->Next(Schedule))
-         Schedule->ResetVersions();
-     }
-}
-
-bool cSchedules::ClearAll(void)
-{
-  cSchedulesLock SchedulesLock(true, 1000);
-  cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
-  if (s) {
-     for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer))
-         Timer->SetEvent(NULL);
-     for (cSchedule *Schedule = s->First(); Schedule; Schedule = s->Next(Schedule))
-         Schedule->Cleanup(INT_MAX);
-     return true;
-     }
-  return false;
+  LOCK_SCHEDULES_WRITE;
+  for (cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->Next(Schedule))
+      Schedule->ResetVersions();
 }
 
 bool cSchedules::Dump(FILE *f, const char *Prefix, eDumpMode DumpMode, time_t AtTime)
 {
-  cSchedulesLock SchedulesLock;
-  cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
-  if (s) {
-     cSafeFile *sf = NULL;
-     if (!f) {
-        sf = new cSafeFile(epgDataFileName);
-        if (sf->Open())
-           f = *sf;
-        else {
-           LOG_ERROR;
-           delete sf;
-           return false;
-           }
-        }
-     for (cSchedule *p = s->First(); p; p = s->Next(p))
-         p->Dump(f, Prefix, DumpMode, AtTime);
-     if (sf) {
-        sf->Close();
+  cSafeFile *sf = NULL;
+  if (!f) {
+     sf = new cSafeFile(epgDataFileName);
+     if (sf->Open())
+        f = *sf;
+     else {
+        LOG_ERROR;
         delete sf;
+        return false;
         }
-     return true;
      }
-  return false;
+  LOCK_SCHEDULES_READ;
+  for (const cSchedule *p = Schedules->First(); p; p = Schedules->Next(p))
+      p->Dump(f, Prefix, DumpMode, AtTime);
+  if (sf) {
+     sf->Close();
+     delete sf;
+     }
+  return true;
 }
 
 bool cSchedules::Read(FILE *f)
 {
-  cSchedulesLock SchedulesLock(true, 1000);
-  cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
-  if (s) {
-     bool OwnFile = f == NULL;
-     if (OwnFile) {
-        if (epgDataFileName && access(epgDataFileName, R_OK) == 0) {
-           dsyslog("reading EPG data from %s", epgDataFileName);
-           if ((f = fopen(epgDataFileName, "r")) == NULL) {
-              LOG_ERROR;
-              return false;
-              }
-           }
-        else
+  bool OwnFile = f == NULL;
+  if (OwnFile) {
+     if (epgDataFileName && access(epgDataFileName, R_OK) == 0) {
+        dsyslog("reading EPG data from %s", epgDataFileName);
+        if ((f = fopen(epgDataFileName, "r")) == NULL) {
+           LOG_ERROR;
            return false;
+           }
         }
-     bool result = cSchedule::Read(f, s);
-     if (OwnFile)
-        fclose(f);
-     if (result) {
-        // Initialize the channels' schedule pointers, so that the first WhatsOn menu will come up faster:
-        for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel))
-            s->GetSchedule(Channel);
-        }
-     return result;
+     else
+        return false;
      }
-  return false;
+  LOCK_CHANNELS_WRITE;
+  LOCK_SCHEDULES_WRITE;
+  bool result = cSchedule::Read(f, Schedules);
+  if (OwnFile)
+     fclose(f);
+  if (result) {
+     // Initialize the channels' schedule pointers, so that the first WhatsOn menu will come up faster:
+     for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel))
+         Schedules->GetSchedule(Channel);
+     }
+  return result;
 }
 
 cSchedule *cSchedules::AddSchedule(tChannelID ChannelID)
@@ -1318,9 +1307,6 @@
   if (!p) {
      p = new cSchedule(ChannelID);
      Add(p);
-     cChannel *channel = Channels.GetByChannelID(ChannelID);
-     if (channel)
-        channel->schedule = p;
      }
   return p;
 }
@@ -1328,7 +1314,7 @@
 const cSchedule *cSchedules::GetSchedule(tChannelID ChannelID) const
 {
   ChannelID.ClrRid();
-  for (cSchedule *p = First(); p; p = Next(p)) {
+  for (const cSchedule *p = First(); p; p = Next(p)) {
       if (p->ChannelID() == ChannelID)
          return p;
       }
@@ -1541,18 +1527,18 @@
   Schedule->DropOutdated(SegmentStart, SegmentEnd, TableID, Version);
 }
 
-void cEpgHandlers::BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus)
+void cEpgHandlers::BeginSegmentTransfer(const cChannel *Channel)
 {
   for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
-      if (eh->BeginSegmentTransfer(Channel, OnlyRunningStatus))
+      if (eh->BeginSegmentTransfer(Channel, false))
          return;
       }
 }
 
-void cEpgHandlers::EndSegmentTransfer(bool Modified, bool OnlyRunningStatus)
+void cEpgHandlers::EndSegmentTransfer(bool Modified)
 {
   for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
-      if (eh->EndSegmentTransfer(Modified, OnlyRunningStatus))
+      if (eh->EndSegmentTransfer(Modified, false))
          return;
       }
 }
diff -ruN vdr-2.2.0/epg.h vdr-2.3.1/epg.h
--- vdr-2.2.0/epg.h	2013-08-23 12:50:05.000000000 +0200
+++ vdr-2.3.1/epg.h	2015-08-09 13:25:04.000000000 +0200
@@ -7,7 +7,7 @@
  * Original version (as used in VDR before 1.3.0) written by
  * Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
  *
- * $Id: epg.h 3.1 2013/08/23 10:50:05 kls Exp $
+ * $Id: epg.h 4.1 2015/08/09 11:25:04 kls Exp $
  */
 
 #ifndef __EPG_H
@@ -66,13 +66,15 @@
 
 class cSchedule;
 
-typedef u_int32_t tEventID;
+typedef u_int16_t tEventID;
 
 class cEvent : public cListObject {
   friend class cSchedule;
 private:
+  static cMutex numTimersMutex; // Protects numTimers, because it might be accessed from parallel read locks
   // The sequence of these parameters is optimized for minimal memory waste!
   cSchedule *schedule;     // The Schedule this event belongs to
+  mutable u_int16_t numTimers;// The number of timers that use this event
   tEventID eventID;        // Event ID of this event
   uchar tableID;           // Table ID this event came from
   uchar version;           // Version number of section this event came from
@@ -109,7 +111,9 @@
   time_t Vps(void) const { return vps; }
   time_t Seen(void) const { return seen; }
   bool SeenWithin(int Seconds) const { return time(NULL) - seen < Seconds; }
-  bool HasTimer(void) const;
+  void IncNumTimers(void) const;
+  void DecNumTimers(void) const;
+  bool HasTimer(void) const { return numTimers > 0; }
   bool IsRunning(bool OrAboutToStart = false) const;
   static const char *ContentToString(uchar Content);
   cString GetParentalRatingString(void) const;
@@ -120,7 +124,7 @@
   void SetEventID(tEventID EventID);
   void SetTableID(uchar TableID);
   void SetVersion(uchar Version);
-  void SetRunningStatus(int RunningStatus, cChannel *Channel = NULL);
+  void SetRunningStatus(int RunningStatus, const cChannel *Channel = NULL);
   void SetTitle(const char *Title);
   void SetShortText(const char *ShortText);
   void SetDescription(const char *Description);
@@ -142,28 +146,33 @@
 
 class cSchedule : public cListObject  {
 private:
+  static cMutex numTimersMutex; // Protects numTimers, because it might be accessed from parallel read locks
   tChannelID channelID;
   cList<cEvent> events;
   cHash<cEvent> eventsHashID;
   cHash<cEvent> eventsHashStartTime;
+  mutable u_int16_t numTimers;// The number of timers that use this schedule
   bool hasRunning;
-  time_t modified;
+  int modified;
   time_t presentSeen;
 public:
   cSchedule(tChannelID ChannelID);
   tChannelID ChannelID(void) const { return channelID; }
-  time_t Modified(void) const { return modified; }
+  bool Modified(int &State) const { bool Result = State != modified; State = modified; return Result; }
   time_t PresentSeen(void) const { return presentSeen; }
   bool PresentSeenWithin(int Seconds) const { return time(NULL) - presentSeen < Seconds; }
-  void SetModified(void) { modified = time(NULL); }
+  void SetModified(void) { modified++; }
   void SetPresentSeen(void) { presentSeen = time(NULL); }
-  void SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Channel = NULL);
+  void SetRunningStatus(cEvent *Event, int RunningStatus, const cChannel *Channel = NULL);
   void ClrRunningStatus(cChannel *Channel = NULL);
   void ResetVersions(void);
   void Sort(void);
   void DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version);
   void Cleanup(time_t Time);
   void Cleanup(void);
+  void IncNumTimers(void) const;
+  void DecNumTimers(void) const;
+  bool HasTimer(void) const { return numTimers > 0; }
   cEvent *AddEvent(cEvent *Event);
   void DelEvent(cEvent *Event);
   void HashEvent(cEvent *Event);
@@ -177,35 +186,23 @@
   static bool Read(FILE *f, cSchedules *Schedules);
   };
 
-class cSchedulesLock {
-private:
-  bool locked;
-public:
-  cSchedulesLock(bool WriteLock = false, int TimeoutMs = 0);
-  ~cSchedulesLock();
-  bool Locked(void) { return locked; }
-  };
-
 class cSchedules : public cList<cSchedule> {
   friend class cSchedule;
-  friend class cSchedulesLock;
 private:
-  cRwLock rwlock;
   static cSchedules schedules;
   static char *epgDataFileName;
   static time_t lastDump;
-  static time_t modified;
 public:
+  cSchedules(void);
+  static const cSchedules *GetSchedulesRead(cStateKey &StateKey, int TimeoutMs = 0);
+      ///< Gets the list of schedules for read access.
+      ///< See cTimers::GetTimersRead() for details.
+  static cSchedules *GetSchedulesWrite(cStateKey &StateKey, int TimeoutMs = 0);
+      ///< Gets the list of schedules for write access.
+      ///< See cTimers::GetTimersWrite() for details.
   static void SetEpgDataFileName(const char *FileName);
-  static const cSchedules *Schedules(cSchedulesLock &SchedulesLock);
-         ///< Caller must provide a cSchedulesLock which has to survive the entire
-         ///< time the returned cSchedules is accessed. Once the cSchedules is no
-         ///< longer used, the cSchedulesLock must be destroyed.
-  static time_t Modified(void) { return modified; }
-  static void SetModified(cSchedule *Schedule);
   static void Cleanup(bool Force = false);
   static void ResetVersions(void);
-  static bool ClearAll(void);
   static bool Dump(FILE *f = NULL, const char *Prefix = "", eDumpMode DumpMode = dmAll, time_t AtTime = 0);
   static bool Read(FILE *f = NULL);
   cSchedule *AddSchedule(tChannelID ChannelID);
@@ -213,6 +210,17 @@
   const cSchedule *GetSchedule(const cChannel *Channel, bool AddIfMissing = false) const;
   };
 
+// Provide lock controlled access to the list:
+
+DEF_LIST_LOCK(Schedules);
+
+// These macros provide a convenient way of locking the global schedules list
+// and making sure the lock is released as soon as the current scope is left
+// (note that these macros wait forever to obtain the lock!):
+
+#define LOCK_SCHEDULES_READ  USE_LIST_LOCK_READ(Schedules);
+#define LOCK_SCHEDULES_WRITE USE_LIST_LOCK_WRITE(Schedules);
+
 class cEpgDataReader : public cThread {
 public:
   cEpgDataReader(void);
@@ -273,12 +281,14 @@
   virtual bool DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version) { return false; }
           ///< Takes a look at all EPG events between SegmentStart and SegmentEnd and
           ///< drops outdated events.
-  virtual bool BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus) { return false; }
+  virtual bool BeginSegmentTransfer(const cChannel *Channel, bool Dummy) { return false; } // TODO remove obsolete Dummy
           ///< Called directly after IgnoreChannel() before any other handler method is called.
           ///< Designed to give handlers the possibility to prepare a database transaction.
-  virtual bool EndSegmentTransfer(bool Modified, bool OnlyRunningStatus) { return false; }
+          ///< Dummy is for backward compatibility and may be removed in a future version.
+  virtual bool EndSegmentTransfer(bool Modified, bool Dummy) { return false; } // TODO remove obsolete Dummy
           ///< Called after the segment data has been processed.
           ///< At this point handlers should close/commit/rollback any pending database transactions.
+          ///< Dummy is for backward compatibility and may be removed in a future version.
   };
 
 class cEpgHandlers : public cList<cEpgHandler> {
@@ -301,8 +311,8 @@
   void HandleEvent(cEvent *Event);
   void SortSchedule(cSchedule *Schedule);
   void DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version);
-  void BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus);
-  void EndSegmentTransfer(bool Modified, bool OnlyRunningStatus);
+  void BeginSegmentTransfer(const cChannel *Channel);
+  void EndSegmentTransfer(bool Modified);
   };
 
 extern cEpgHandlers EpgHandlers;
diff -ruN vdr-2.2.0/epg2html vdr-2.3.1/epg2html
--- vdr-2.2.0/epg2html	2013-03-04 14:02:20.000000000 +0100
+++ vdr-2.3.1/epg2html	2013-03-04 14:02:20.000000000 +0100
@@ -12,7 +12,7 @@
 # See the main source file 'vdr.c' for copyright information and
 # how to reach the author.
 #
-# $Id: epg2html 3.0 2013/03/04 13:02:20 kls Exp $
+# $Id: epg2html 4.0 2013/03/04 13:02:20 kls Exp $
 
 @Index = ();
 ($Charset = $ENV{LANG}) =~ s/^[^.]*\.?(.*)/$1/;
diff -ruN vdr-2.2.0/filter.c vdr-2.3.1/filter.c
--- vdr-2.2.0/filter.c	2004-01-11 14:31:34.000000000 +0100
+++ vdr-2.3.1/filter.c	2015-07-25 12:59:57.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: filter.c 3.0 2004/01/11 13:31:34 kls Exp $
+ * $Id: filter.c 4.2 2015/07/25 10:59:57 kls Exp $
  */
 
 #include "filter.h"
@@ -19,22 +19,38 @@
 
 void cSectionSyncer::Reset(void)
 {
-  lastVersion = 0xFF;
+  currentVersion = -1;
+  currentSection = -1;
   synced = false;
+  complete = false;
+  memset(sections, 0x00, sizeof(sections));
+}
+
+void cSectionSyncer::Repeat(void)
+{
+  SetSectionFlag(currentSection, false);
+  synced = false;
+  complete = false;
 }
 
 bool cSectionSyncer::Sync(uchar Version, int Number, int LastNumber)
 {
-  if (Version == lastVersion)
-     return false;
+  if (Version != currentVersion) {
+     Reset();
+     currentVersion = Version;
+     }
   if (!synced) {
      if (Number != 0)
-        return false; // sync on first section
-     synced = true;
+        return false;
+     else
+        synced = true;
      }
+  currentSection = Number;
+  bool Result = !GetSectionFlag(Number);
+  SetSectionFlag(Number, true);
   if (Number == LastNumber)
-     lastVersion = Version;
-  return synced;
+     complete = true;
+  return Result;
 }
 
 // --- cFilterData -----------------------------------------------------------
diff -ruN vdr-2.2.0/filter.h vdr-2.3.1/filter.h
--- vdr-2.2.0/filter.h	2004-01-11 14:31:59.000000000 +0100
+++ vdr-2.3.1/filter.h	2015-07-25 12:03:44.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: filter.h 3.0 2004/01/11 13:31:59 kls Exp $
+ * $Id: filter.h 4.2 2015/07/25 10:03:44 kls Exp $
  */
 
 #ifndef __FILTER_H
@@ -15,11 +15,18 @@
 
 class cSectionSyncer {
 private:
-  int lastVersion;
+  int currentVersion;
+  int currentSection;
   bool synced;
+  bool complete;
+  uchar sections[32]; // holds 32 * 8 = 256 bits, as flags for the sections
+  void SetSectionFlag(uchar Section, bool On) { if (On) sections[Section / 8] |= (1 << (Section % 8)); else sections[Section / 8] &= ~(1 << (Section % 8)); }
+  bool GetSectionFlag(uchar Section) { return sections[Section / 8] & (1 << (Section % 8)); }
 public:
   cSectionSyncer(void);
   void Reset(void);
+  void Repeat(void);
+  bool Complete(void) { return complete; }
   bool Sync(uchar Version, int Number, int LastNumber);
   };
 
diff -ruN vdr-2.2.0/font.c vdr-2.3.1/font.c
--- vdr-2.2.0/font.c	2014-01-07 13:19:45.000000000 +0100
+++ vdr-2.3.1/font.c	2015-04-19 13:13:45.000000000 +0200
@@ -6,7 +6,7 @@
  *
  * BiDi support by Osama Alrawab <alrawab@hotmail.com> @2008 Tripoli-Libya.
  *
- * $Id: font.c 3.2 2014/01/07 12:19:45 kls Exp $
+ * $Id: font.c 4.1 2015/04/19 11:13:45 kls Exp $
  */
 
 #include "font.h"
@@ -140,7 +140,7 @@
                if (!error) {
                   error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
                   if (!error) {
-                     if (face->glyph->bitmap.rows-face->glyph->bitmap_top > bottom)
+                     if (int(face->glyph->bitmap.rows-face->glyph->bitmap_top) > bottom)
                         bottom = face->glyph->bitmap.rows-face->glyph->bitmap_top;
                      }
                   else
diff -ruN vdr-2.2.0/font.h vdr-2.3.1/font.h
--- vdr-2.2.0/font.h	2014-01-07 13:11:55.000000000 +0100
+++ vdr-2.3.1/font.h	2014-01-07 13:11:55.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: font.h 3.1 2014/01/07 12:11:55 kls Exp $
+ * $Id: font.h 4.0 2014/01/07 12:11:55 kls Exp $
  */
 
 #ifndef __FONT_H
diff -ruN vdr-2.2.0/i18n.c vdr-2.3.1/i18n.c
--- vdr-2.2.0/i18n.c	2012-09-01 12:53:43.000000000 +0200
+++ vdr-2.3.1/i18n.c	2012-09-01 12:53:43.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: i18n.c 3.0 2012/09/01 10:53:43 kls Exp $
+ * $Id: i18n.c 4.0 2012/09/01 10:53:43 kls Exp $
  */
 
 /*
diff -ruN vdr-2.2.0/i18n.h vdr-2.3.1/i18n.h
--- vdr-2.2.0/i18n.h	2012-03-11 15:07:45.000000000 +0100
+++ vdr-2.3.1/i18n.h	2012-03-11 15:07:45.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: i18n.h 3.0 2012/03/11 14:07:45 kls Exp $
+ * $Id: i18n.h 4.0 2012/03/11 14:07:45 kls Exp $
  */
 
 #ifndef __I18N_H
diff -ruN vdr-2.2.0/interface.c vdr-2.3.1/interface.c
--- vdr-2.2.0/interface.c	2015-01-11 14:37:47.000000000 +0100
+++ vdr-2.3.1/interface.c	2015-04-28 13:16:06.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: interface.c 3.1 2015/01/11 13:37:47 kls Exp $
+ * $Id: interface.c 4.1 2015/04/28 11:16:06 kls Exp $
  */
 
 #include "interface.h"
@@ -19,27 +19,19 @@
 
 cInterface *Interface = NULL;
 
-cInterface::cInterface(int SVDRPport)
+cInterface::cInterface(void)
 {
   interrupted = false;
-  SVDRP = NULL;
-  if (SVDRPport)
-     SVDRP = new cSVDRP(SVDRPport);
 }
 
 cInterface::~cInterface()
 {
-  delete SVDRP;
 }
 
 eKeys cInterface::GetKey(bool Wait)
 {
   if (!cRemote::HasKeys())
      Skins.Flush();
-  if (SVDRP) {
-     if (SVDRP->Process())
-        Wait = false;
-     }
   if (!cRemote::IsLearning())
      return cRemote::Get(Wait ? 1000 : 10);
   else
diff -ruN vdr-2.2.0/interface.h vdr-2.3.1/interface.h
--- vdr-2.2.0/interface.h	2004-05-01 13:11:13.000000000 +0200
+++ vdr-2.3.1/interface.h	2015-04-28 13:15:11.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: interface.h 3.0 2004/05/01 11:11:13 kls Exp $
+ * $Id: interface.h 4.1 2015/04/28 11:15:11 kls Exp $
  */
 
 #ifndef __INTERFACE_H
@@ -13,17 +13,14 @@
 #include "config.h"
 #include "remote.h"
 #include "skins.h"
-#include "svdrp.h"
 
 class cInterface {
 private:
   bool interrupted;
-  cSVDRP *SVDRP;
   bool QueryKeys(cRemote *Remote, cSkinDisplayMenu *DisplayMenu);
 public:
-  cInterface(int SVDRPport = 0);
+  cInterface(void);
   ~cInterface();
-  bool HasSVDRPConnection(void) { return SVDRP && SVDRP->HasConnection(); }
   void Interrupt(void) { interrupted = true; }
   eKeys GetKey(bool Wait = true);
   eKeys Wait(int Seconds = 0, bool KeepChar = false);
diff -ruN vdr-2.2.0/keys.c vdr-2.3.1/keys.c
--- vdr-2.2.0/keys.c	2012-12-04 13:52:52.000000000 +0100
+++ vdr-2.3.1/keys.c	2012-12-04 13:52:52.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: keys.c 3.0 2012/12/04 12:52:52 kls Exp $
+ * $Id: keys.c 4.0 2012/12/04 12:52:52 kls Exp $
  */
 
 #include "keys.h"
diff -ruN vdr-2.2.0/keys.h vdr-2.3.1/keys.h
--- vdr-2.2.0/keys.h	2015-01-27 11:45:18.000000000 +0100
+++ vdr-2.3.1/keys.h	2015-01-27 11:45:18.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: keys.h 3.1 2015/01/27 10:45:18 kls Exp $
+ * $Id: keys.h 4.0 2015/01/27 10:45:18 kls Exp $
  */
 
 #ifndef __KEYS_H
diff -ruN vdr-2.2.0/libsi/Makefile vdr-2.3.1/libsi/Makefile
--- vdr-2.2.0/libsi/Makefile	2015-02-11 11:24:54.000000000 +0100
+++ vdr-2.3.1/libsi/Makefile	2015-02-11 11:24:54.000000000 +0100
@@ -1,7 +1,7 @@
 #
 # Makefile for a libsi
 #
-# $Id: Makefile 3.1 2015/02/11 10:24:54 kls Exp $
+# $Id: Makefile 4.0 2015/02/11 10:24:54 kls Exp $
 
 ### The archiver options:
 
diff -ruN vdr-2.2.0/libsi/descriptor.c vdr-2.3.1/libsi/descriptor.c
--- vdr-2.2.0/libsi/descriptor.c	2013-10-30 11:16:18.000000000 +0100
+++ vdr-2.3.1/libsi/descriptor.c	2013-10-30 11:16:18.000000000 +0100
@@ -6,7 +6,7 @@
  *   the Free Software Foundation; either version 2 of the License, or     *
  *   (at your option) any later version.                                   *
  *                                                                         *
- *   $Id: descriptor.c 3.1 2013/10/30 10:16:18 kls Exp $
+ *   $Id: descriptor.c 4.0 2013/10/30 10:16:18 kls Exp $
  *                                                                         *
  ***************************************************************************/
 
diff -ruN vdr-2.2.0/libsi/descriptor.h vdr-2.3.1/libsi/descriptor.h
--- vdr-2.2.0/libsi/descriptor.h	2014-02-08 13:44:17.000000000 +0100
+++ vdr-2.3.1/libsi/descriptor.h	2014-02-08 13:44:17.000000000 +0100
@@ -6,7 +6,7 @@
  *   the Free Software Foundation; either version 2 of the License, or     *
  *   (at your option) any later version.                                   *
  *                                                                         *
- *   $Id: descriptor.h 3.2 2014/02/08 12:44:17 kls Exp $
+ *   $Id: descriptor.h 4.0 2014/02/08 12:44:17 kls Exp $
  *                                                                         *
  ***************************************************************************/
 
diff -ruN vdr-2.2.0/libsi/gendescr vdr-2.3.1/libsi/gendescr
--- vdr-2.2.0/libsi/gendescr	2003-12-13 11:40:53.000000000 +0100
+++ vdr-2.3.1/libsi/gendescr	2003-12-13 11:40:53.000000000 +0100
@@ -1,6 +1,6 @@
 #!/usr/bin/perl
 
-# $Id: gendescr 3.0 2003/12/13 10:40:53 kls Exp $
+# $Id: gendescr 4.0 2003/12/13 10:40:53 kls Exp $
 
 print "Name (ohne ...Descriptor):";
 $name=<STDIN>;
diff -ruN vdr-2.2.0/libsi/headers.h vdr-2.3.1/libsi/headers.h
--- vdr-2.2.0/libsi/headers.h	2013-10-30 11:16:18.000000000 +0100
+++ vdr-2.3.1/libsi/headers.h	2013-10-30 11:16:18.000000000 +0100
@@ -10,7 +10,7 @@
  *   the Free Software Foundation; either version 2 of the License, or     *
  *   (at your option) any later version.                                   *
  *                                                                         *
- *   $Id: headers.h 3.1 2013/10/30 10:16:18 kls Exp $
+ *   $Id: headers.h 4.0 2013/10/30 10:16:18 kls Exp $
  *                                                                         *
  ***************************************************************************/
 
diff -ruN vdr-2.2.0/libsi/section.c vdr-2.3.1/libsi/section.c
--- vdr-2.2.0/libsi/section.c	2006-04-14 12:53:44.000000000 +0200
+++ vdr-2.3.1/libsi/section.c	2006-04-14 12:53:44.000000000 +0200
@@ -6,7 +6,7 @@
  *   the Free Software Foundation; either version 2 of the License, or     *
  *   (at your option) any later version.                                   *
  *                                                                         *
- *   $Id: section.c 3.0 2006/04/14 10:53:44 kls Exp $
+ *   $Id: section.c 4.0 2006/04/14 10:53:44 kls Exp $
  *                                                                         *
  ***************************************************************************/
 
diff -ruN vdr-2.2.0/libsi/section.h vdr-2.3.1/libsi/section.h
--- vdr-2.2.0/libsi/section.h	2012-02-26 14:58:26.000000000 +0100
+++ vdr-2.3.1/libsi/section.h	2012-02-26 14:58:26.000000000 +0100
@@ -6,7 +6,7 @@
  *   the Free Software Foundation; either version 2 of the License, or     *
  *   (at your option) any later version.                                   *
  *                                                                         *
- *   $Id: section.h 3.0 2012/02/26 13:58:26 kls Exp $
+ *   $Id: section.h 4.0 2012/02/26 13:58:26 kls Exp $
  *                                                                         *
  ***************************************************************************/
 
diff -ruN vdr-2.2.0/libsi/si.c vdr-2.3.1/libsi/si.c
--- vdr-2.2.0/libsi/si.c	2015-02-10 14:42:41.000000000 +0100
+++ vdr-2.3.1/libsi/si.c	2015-02-10 14:42:41.000000000 +0100
@@ -6,7 +6,7 @@
  *   the Free Software Foundation; either version 2 of the License, or     *
  *   (at your option) any later version.                                   *
  *                                                                         *
- *   $Id: si.c 3.3 2015/02/10 13:42:41 kls Exp $
+ *   $Id: si.c 4.0 2015/02/10 13:42:41 kls Exp $
  *                                                                         *
  ***************************************************************************/
 
diff -ruN vdr-2.2.0/libsi/si.h vdr-2.3.1/libsi/si.h
--- vdr-2.2.0/libsi/si.h	2015-02-10 14:54:28.000000000 +0100
+++ vdr-2.3.1/libsi/si.h	2015-02-10 14:54:28.000000000 +0100
@@ -6,7 +6,7 @@
  *   the Free Software Foundation; either version 2 of the License, or     *
  *   (at your option) any later version.                                   *
  *                                                                         *
- *   $Id: si.h 3.4 2015/02/10 13:54:28 kls Exp $
+ *   $Id: si.h 4.0 2015/02/10 13:54:28 kls Exp $
  *                                                                         *
  ***************************************************************************/
 
diff -ruN vdr-2.2.0/libsi/util.c vdr-2.3.1/libsi/util.c
--- vdr-2.2.0/libsi/util.c	2006-02-18 12:17:50.000000000 +0100
+++ vdr-2.3.1/libsi/util.c	2006-02-18 12:17:50.000000000 +0100
@@ -6,7 +6,7 @@
  *   the Free Software Foundation; either version 2 of the License, or     *
  *   (at your option) any later version.                                   *
  *                                                                         *
- *   $Id: util.c 3.0 2006/02/18 11:17:50 kls Exp $
+ *   $Id: util.c 4.0 2006/02/18 11:17:50 kls Exp $
  *                                                                         *
  ***************************************************************************/
 
diff -ruN vdr-2.2.0/libsi/util.h vdr-2.3.1/libsi/util.h
--- vdr-2.2.0/libsi/util.h	2012-02-26 14:58:26.000000000 +0100
+++ vdr-2.3.1/libsi/util.h	2012-02-26 14:58:26.000000000 +0100
@@ -6,7 +6,7 @@
  *   the Free Software Foundation; either version 2 of the License, or     *
  *   (at your option) any later version.                                   *
  *                                                                         *
- *   $Id: util.h 3.0 2012/02/26 13:58:26 kls Exp $
+ *   $Id: util.h 4.0 2012/02/26 13:58:26 kls Exp $
  *                                                                         *
  ***************************************************************************/
 
diff -ruN vdr-2.2.0/lirc.c vdr-2.3.1/lirc.c
--- vdr-2.2.0/lirc.c	2013-10-29 13:32:12.000000000 +0100
+++ vdr-2.3.1/lirc.c	2013-10-29 13:32:12.000000000 +0100
@@ -6,7 +6,7 @@
  *
  * LIRC support added by Carsten Koch <Carsten.Koch@icem.de>  2000-06-16.
  *
- * $Id: lirc.c 3.2 2013/10/29 12:32:12 kls Exp $
+ * $Id: lirc.c 4.0 2013/10/29 12:32:12 kls Exp $
  */
 
 #include "lirc.h"
diff -ruN vdr-2.2.0/lirc.h vdr-2.3.1/lirc.h
--- vdr-2.2.0/lirc.h	2006-01-27 17:00:19.000000000 +0100
+++ vdr-2.3.1/lirc.h	2006-01-27 17:00:19.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: lirc.h 3.0 2006/01/27 16:00:19 kls Exp $
+ * $Id: lirc.h 4.0 2006/01/27 16:00:19 kls Exp $
  */
 
 #ifndef __LIRC_H
diff -ruN vdr-2.2.0/menu.c vdr-2.3.1/menu.c
--- vdr-2.2.0/menu.c	2015-02-10 13:37:06.000000000 +0100
+++ vdr-2.3.1/menu.c	2015-09-14 15:22:49.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: menu.c 3.48 2015/02/10 12:37:06 kls Exp $
+ * $Id: menu.c 4.12 2015/09/14 13:22:49 kls Exp $
  */
 
 #include "menu.h"
@@ -27,6 +27,7 @@
 #include "sourceparams.h"
 #include "sources.h"
 #include "status.h"
+#include "svdrp.h"
 #include "themes.h"
 #include "timers.h"
 #include "transfer.h"
@@ -48,8 +49,8 @@
 #define NODISKSPACEDELTA  300 // seconds between "Not enough disk space to start recording!" messages
 #define MAXCHNAMWIDTH      16 // maximum number of characters of channels' short names shown in schedules menus
 
-#define CHNUMWIDTH  (numdigits(Channels.MaxNumber()) + 1)
-#define CHNAMWIDTH  (min(MAXCHNAMWIDTH, Channels.MaxShortChannelNameLength() + 1))
+#define CHNUMWIDTH  (numdigits(cChannels::MaxNumber()) + 1)
+#define CHNAMWIDTH  (min(MAXCHNAMWIDTH, cChannels::MaxShortChannelNameLength() + 1))
 
 // --- cMenuEditCaItem -------------------------------------------------------
 
@@ -159,20 +160,23 @@
 
 class cMenuEditChannel : public cOsdMenu {
 private:
+  cStateKey *channelsStateKey;
   cChannel *channel;
   cChannel data;
   cSourceParam *sourceParam;
   char name[256];
   void Setup(void);
 public:
-  cMenuEditChannel(cChannel *Channel, bool New = false);
+  cMenuEditChannel(cStateKey *ChannelsStateKey, cChannel *Channel, bool New = false);
+  cChannel *Channel(void) { return channel; }
   virtual eOSState ProcessKey(eKeys Key);
   };
 
-cMenuEditChannel::cMenuEditChannel(cChannel *Channel, bool New)
+cMenuEditChannel::cMenuEditChannel(cStateKey *ChannelsStateKey, cChannel *Channel, bool New)
 :cOsdMenu(tr("Edit channel"), 16)
 {
   SetMenuCategory(mcChannelEdit);
+  channelsStateKey = ChannelsStateKey;
   channel = Channel;
   sourceParam = NULL;
   *name = 0;
@@ -239,32 +243,37 @@
 
   if (state == osUnknown) {
      if (Key == kOk) {
+        cChannels *Channels =cChannels::GetChannelsWrite(*channelsStateKey);
+        bool Modified = false;
         if (sourceParam)
            sourceParam->GetData(&data);
-        if (Channels.HasUniqueChannelID(&data, channel)) {
+        if (Channels->HasUniqueChannelID(&data, channel)) {
            data.name = strcpyrealloc(data.name, name);
            if (channel) {
               *channel = data;
-              isyslog("edited channel %d %s", channel->Number(), *data.ToText());
+              isyslog("edited channel %d %s", channel->Number(), *channel->ToText());
               state = osBack;
               }
            else {
               channel = new cChannel;
               *channel = data;
-              Channels.Add(channel);
-              Channels.ReNumber();
-              isyslog("added channel %d %s", channel->Number(), *data.ToText());
+              Channels->Add(channel);
+              Channels->ReNumber();
+              isyslog("added channel %d %s", channel->Number(), *channel->ToText());
               state = osUser1;
               }
-           Channels.SetModified(true);
+           Channels->SetModifiedByUser();
+           Modified = true;
            }
         else {
            Skins.Message(mtError, tr("Channel settings are not unique!"));
            state = osContinue;
            }
+        channelsStateKey->Remove(Modified);
         }
      }
   if (Key != kNone && (data.source & cSource::st_Mask) != (oldSource & cSource::st_Mask)) {
+     LOCK_CHANNELS_WRITE;
      if (sourceParam)
         sourceParam->GetData(&data);
      Setup();
@@ -279,21 +288,21 @@
   enum eChannelSortMode { csmNumber, csmName, csmProvider };
 private:
   static eChannelSortMode sortMode;
-  cChannel *channel;
+  const cChannel *channel;
 public:
-  cMenuChannelItem(cChannel *Channel);
+  cMenuChannelItem(const cChannel *Channel);
   static void SetSortMode(eChannelSortMode SortMode) { sortMode = SortMode; }
   static void IncSortMode(void) { sortMode = eChannelSortMode((sortMode == csmProvider) ? csmNumber : sortMode + 1); }
   static eChannelSortMode SortMode(void) { return sortMode; }
   virtual int Compare(const cListObject &ListObject) const;
   virtual void Set(void);
-  cChannel *Channel(void) { return channel; }
+  const cChannel *Channel(void) { return channel; }
   virtual void SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable);
   };
 
 cMenuChannelItem::eChannelSortMode cMenuChannelItem::sortMode = csmNumber;
 
-cMenuChannelItem::cMenuChannelItem(cChannel *Channel)
+cMenuChannelItem::cMenuChannelItem(const cChannel *Channel)
 {
   channel = Channel;
   if (channel->GroupSep())
@@ -340,11 +349,12 @@
 
 class cMenuChannels : public cOsdMenu {
 private:
+  cStateKey channelsStateKey;
   int number;
   cTimeMs numberTimer;
-  void Setup(void);
+  void Set(bool Force = false);
   cChannel *GetChannel(int Index);
-  void Propagate(void);
+  void Propagate(cChannels *Channels);
 protected:
   eOSState Number(eKeys Key);
   eOSState Switch(void);
@@ -363,38 +373,41 @@
 {
   SetMenuCategory(mcChannel);
   number = 0;
-  Setup();
-  Channels.IncBeingEdited();
+  Set();
 }
 
 cMenuChannels::~cMenuChannels()
 {
-  Channels.DecBeingEdited();
 }
 
-void cMenuChannels::Setup(void)
+void cMenuChannels::Set(bool Force)
 {
-  cChannel *currentChannel = GetChannel(Current());
-  if (!currentChannel)
-     currentChannel = Channels.GetByNumber(cDevice::CurrentChannel());
-  cMenuChannelItem *currentItem = NULL;
-  Clear();
-  for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) {
-      if (!channel->GroupSep() || cMenuChannelItem::SortMode() == cMenuChannelItem::csmNumber && *channel->Name()) {
-         cMenuChannelItem *item = new cMenuChannelItem(channel);
-         Add(item);
-         if (channel == currentChannel)
-            currentItem = item;
+  if (Force)
+     channelsStateKey.Reset();
+  if (const cChannels *Channels = cChannels::GetChannelsRead(channelsStateKey)) {
+     const cChannel *CurrentChannel = GetChannel(Current());
+     if (!CurrentChannel)
+        CurrentChannel = Channels->GetByNumber(cDevice::CurrentChannel());
+     cMenuChannelItem *CurrentItem = NULL;
+     Clear();
+     for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
+         if (!Channel->GroupSep() || cMenuChannelItem::SortMode() == cMenuChannelItem::csmNumber && *Channel->Name()) {
+            cMenuChannelItem *Item = new cMenuChannelItem(Channel);
+            Add(Item);
+            if (Channel == CurrentChannel)
+               CurrentItem = Item;
+            }
          }
-      }
-  SetMenuSortMode(cMenuChannelItem::SortMode() == cMenuChannelItem::csmName ? msmName :
-                  cMenuChannelItem::SortMode() == cMenuChannelItem::csmProvider ? msmProvider :
-                  msmNumber);
-  if (cMenuChannelItem::SortMode() != cMenuChannelItem::csmNumber)
-     Sort();
-  SetCurrent(currentItem);
-  SetHelp(tr("Button$Edit"), tr("Button$New"), tr("Button$Delete"), tr("Button$Mark"));
-  Display();
+     SetMenuSortMode(cMenuChannelItem::SortMode() == cMenuChannelItem::csmName ? msmName :
+                     cMenuChannelItem::SortMode() == cMenuChannelItem::csmProvider ? msmProvider :
+                     msmNumber);
+     if (cMenuChannelItem::SortMode() != cMenuChannelItem::csmNumber)
+        Sort();
+     SetCurrent(CurrentItem);
+     SetHelp(tr("Button$Edit"), tr("Button$New"), tr("Button$Delete"), tr("Button$Mark"));
+     Display();
+     channelsStateKey.Remove();
+     }
 }
 
 cChannel *cMenuChannels::GetChannel(int Index)
@@ -403,13 +416,13 @@
   return p ? (cChannel *)p->Channel() : NULL;
 }
 
-void cMenuChannels::Propagate(void)
+void cMenuChannels::Propagate(cChannels *Channels)
 {
-  Channels.ReNumber();
+  Channels->ReNumber();
   for (cMenuChannelItem *ci = (cMenuChannelItem *)First(); ci; ci = (cMenuChannelItem *)ci->Next())
       ci->Set();
   Display();
-  Channels.SetModified(true);
+  Channels->SetModifiedByUser();
 }
 
 eOSState cMenuChannels::Number(eKeys Key)
@@ -420,9 +433,10 @@
      number = 0;
   if (!number && Key == k0) {
      cMenuChannelItem::IncSortMode();
-     Setup();
+     Set(true);
      }
   else {
+     LOCK_CHANNELS_READ;
      number = number * 10 + Key - k0;
      for (cMenuChannelItem *ci = (cMenuChannelItem *)First(); ci; ci = (cMenuChannelItem *)ci->Next()) {
          if (!ci->Channel()->GroupSep() && ci->Channel()->Number() == number) {
@@ -440,6 +454,7 @@
 {
   if (HasSubMenu())
      return osContinue;
+  LOCK_CHANNELS_READ;
   cChannel *ch = GetChannel(Current());
   if (ch)
      return cDevice::PrimaryDevice()->SwitchChannel(ch, true) ? osEnd : osContinue;
@@ -450,9 +465,10 @@
 {
   if (HasSubMenu() || Count() == 0)
      return osContinue;
+  LOCK_CHANNELS_READ;
   cChannel *ch = GetChannel(Current());
   if (ch)
-     return AddSubMenu(new cMenuEditChannel(ch));
+     return AddSubMenu(new cMenuEditChannel(&channelsStateKey, ch));
   return osContinue;
 }
 
@@ -460,79 +476,97 @@
 {
   if (HasSubMenu())
      return osContinue;
-  return AddSubMenu(new cMenuEditChannel(GetChannel(Current()), true));
+  LOCK_CHANNELS_READ;
+  return AddSubMenu(new cMenuEditChannel(&channelsStateKey, GetChannel(Current()), true));
 }
 
 eOSState cMenuChannels::Delete(void)
 {
   if (!HasSubMenu() && Count() > 0) {
-     int CurrentChannelNr = cDevice::CurrentChannel();
-     cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
+     LOCK_TIMERS_READ; // must lock timers before channels!
+     cChannels *Channels = cChannels::GetChannelsWrite(channelsStateKey);
      int Index = Current();
-     cChannel *channel = GetChannel(Current());
-     int DeletedChannel = channel->Number();
+     cChannel *Channel = GetChannel(Current());
+     if (!Channels->Contains(Channel)) {
+        channelsStateKey.Remove(false);
+        channelsStateKey.Reset(); // makes sure the menu is refreshed
+        return osContinue;
+        }
+     bool Deleted = false;
+     int CurrentChannelNr = cDevice::CurrentChannel();
+     cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
+     int DeletedChannel = Channel->Number();
      // Check if there is a timer using this channel:
-     if (channel->HasTimer()) {
+     if (Timers->UsesChannel(Channel)) {
         Skins.Message(mtError, tr("Channel is being used by a timer!"));
         return osContinue;
         }
      if (Interface->Confirm(tr("Delete channel?"))) {
-        if (CurrentChannel && channel == CurrentChannel) {
-           int n = Channels.GetNextNormal(CurrentChannel->Index());
+        if (CurrentChannel && Channel == CurrentChannel) {
+           int n = Channels->GetNextNormal(CurrentChannel->Index());
            if (n < 0)
-              n = Channels.GetPrevNormal(CurrentChannel->Index());
-           CurrentChannel = Channels.Get(n);
+              n = Channels->GetPrevNormal(CurrentChannel->Index());
+           CurrentChannel = Channels->Get(n);
            CurrentChannelNr = 0; // triggers channel switch below
            }
-        Channels.Del(channel);
+        Channels->Del(Channel);
         cOsdMenu::Del(Index);
-        Propagate();
-        Channels.SetModified(true);
+        Propagate(Channels);
+        Channels->SetModifiedByUser();
         isyslog("channel %d deleted", DeletedChannel);
+        Deleted = true;
         if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
            if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
-              Channels.SwitchTo(CurrentChannel->Number());
+              Channels->SwitchTo(CurrentChannel->Number());
            else
               cDevice::SetCurrentChannel(CurrentChannel);
            }
         }
+     channelsStateKey.Remove(Deleted);
      }
   return osContinue;
 }
 
 void cMenuChannels::Move(int From, int To)
 {
-  int CurrentChannelNr = cDevice::CurrentChannel();
-  cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
-  cChannel *FromChannel = GetChannel(From);
-  cChannel *ToChannel = GetChannel(To);
-  if (FromChannel && ToChannel) {
-     int FromNumber = FromChannel->Number();
-     int ToNumber = ToChannel->Number();
-     Channels.Move(FromChannel, ToChannel);
-     cOsdMenu::Move(From, To);
-     Propagate();
-     Channels.SetModified(true);
-     isyslog("channel %d moved to %d", FromNumber, ToNumber);
-     if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
-        if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
-           Channels.SwitchTo(CurrentChannel->Number());
-        else
-           cDevice::SetCurrentChannel(CurrentChannel);
+  if (cChannels *Channels = cChannels::GetChannelsWrite(channelsStateKey)) {
+     int CurrentChannelNr = cDevice::CurrentChannel();
+     cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
+     cChannel *FromChannel = GetChannel(From);
+     cChannel *ToChannel = GetChannel(To);
+     if (FromChannel && ToChannel) {
+        int FromNumber = FromChannel->Number();
+        int ToNumber = ToChannel->Number();
+        Channels->Move(FromChannel, ToChannel);
+        cOsdMenu::Move(From, To);
+        Propagate(Channels);
+        Channels->SetModifiedByUser();
+        isyslog("channel %d moved to %d", FromNumber, ToNumber);
+        if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
+           if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
+              Channels->SwitchTo(CurrentChannel->Number());
+           else
+              cDevice::SetCurrentChannel(CurrentChannel);
+           }
         }
+     channelsStateKey.Remove();
      }
 }
 
 eOSState cMenuChannels::ProcessKey(eKeys Key)
 {
+  if (!HasSubMenu())
+     Set(); // react on any changes to the channels list
   eOSState state = cOsdMenu::ProcessKey(Key);
 
   switch (state) {
     case osUser1: {
-         cChannel *channel = Channels.Last();
-         if (channel) {
-            Add(new cMenuChannelItem(channel), true);
-            return CloseSubMenu();
+         if (cMenuEditChannel *MenuEditChannel = dynamic_cast<cMenuEditChannel *>(SubMenu())) {
+            if (cChannel *Channel = MenuEditChannel->Channel()) {
+               LOCK_CHANNELS_READ;
+               Add(new cMenuChannelItem(Channel), true);
+               return CloseSubMenu();
+               }
             }
          }
          break;
@@ -767,7 +801,7 @@
 }
 
 #define FOLDERDELIMCHARSUBST 0x01
-static void AddRecordingFolders(cList<cNestedItem> *List, char *Path)
+static void AddRecordingFolders(const cRecordings *Recordings, cList<cNestedItem> *List, char *Path)
 {
   if (Path) {
      char *p = strchr(Path, FOLDERDELIMCHARSUBST);
@@ -782,13 +816,12 @@
         List->Add(Folder = new cNestedItem(Path));
      if (p) {
         Folder->SetSubItems(true);
-        AddRecordingFolders(Folder->SubItems(), p);
+        AddRecordingFolders(Recordings, Folder->SubItems(), p);
         }
      }
   else {
-     cThreadLock RecordingsLock(&Recordings);
      cStringList Dirs;
-     for (cRecording *Recording = Recordings.First(); Recording; Recording = Recordings.Next(Recording)) {
+     for (const cRecording *Recording = Recordings->First(); Recording; Recording = Recordings->Next(Recording)) {
          cString Folder = Recording->Folder();
          strreplace((char *)*Folder, FOLDERDELIMCHAR, FOLDERDELIMCHARSUBST); // makes sure parent folders come before subfolders
          if (Dirs.Find(Folder) < 0)
@@ -796,18 +829,21 @@
          }
      Dirs.Sort();
      for (int i = 0; i < Dirs.Size(); i++) {
-         char *s = Dirs[i];
-         if (*s)
-            AddRecordingFolders(&Folders, s);
+         if (char *s = Dirs[i])
+            AddRecordingFolders(Recordings, &Folders, s);
          }
      }
 }
 
 void cMenuFolder::Set(const char *CurrentFolder)
 {
-  static int RecordingsState = -1;
-  if (list == &Folders && Recordings.StateChanged(RecordingsState))
-     AddRecordingFolders(&Folders, NULL);
+  static cStateKey RecordingsStateKey;
+  if (list == &Folders) {
+     if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(RecordingsStateKey)) {
+        AddRecordingFolders(Recordings, &Folders, NULL);
+        RecordingsStateKey.Remove();
+        }
+     }
   firstFolder = NULL;
   Clear();
   if (!isempty(dir)) {
@@ -937,10 +973,13 @@
 
 // --- cMenuEditTimer --------------------------------------------------------
 
+const cTimer *cMenuEditTimer::addedTimer = NULL;
+
 cMenuEditTimer::cMenuEditTimer(cTimer *Timer, bool New)
 :cOsdMenu(tr("Edit timer"), 12)
 {
   SetMenuCategory(mcTimerEdit);
+  addedTimer = NULL;
   file = NULL;
   day = firstday = NULL;
   timer = Timer;
@@ -960,16 +999,30 @@
      Add(new cMenuEditIntItem( tr("Lifetime"),     &data.lifetime, 0, MAXLIFETIME));
      Add(file = new cMenuEditStrItem( tr("File"),   data.file, sizeof(data.file)));
      SetFirstDayItem();
+     if (data.remote)
+        strn0cpy(remote, data.remote, sizeof(remote));
+     else
+        *remote = 0;
+     if (GetSVDRPServerNames(&svdrpServerNames)) {
+        svdrpServerNames.Sort(true);
+        svdrpServerNames.Insert(strdup(""));
+        Add(new cMenuEditStrlItem(tr("Record on"), remote, sizeof(remote), &svdrpServerNames));
+        }
      }
   SetHelpKeys();
-  Timers.IncBeingEdited();
 }
 
 cMenuEditTimer::~cMenuEditTimer()
 {
   if (timer && addIfConfirmed)
      delete timer; // apparently it wasn't confirmed
-  Timers.DecBeingEdited();
+}
+
+const cTimer *cMenuEditTimer::AddedTimer(void)
+{
+  const cTimer *Timer = addedTimer;
+  addedTimer = NULL;
+  return Timer;
 }
 
 void cMenuEditTimer::SetHelpKeys(void)
@@ -1009,34 +1062,107 @@
   return CloseSubMenu();
 }
 
+static bool RemoteTimerError(const cTimer *Timer)
+{
+  Skins.Message(mtError, cString::sprintf("%s %d@%s!", tr("Error while accessing remote timer"), Timer->Id(), Timer->Remote()));
+  return false; // convenience return code
+}
+
+static bool HandleRemoteModifications(cTimer *NewTimer, cTimer *OldTimer = NULL)
+{
+  cStringList Response;
+  if (!OldTimer || OldTimer->Local() || !OldTimer->Id()) {
+     if (NewTimer->Local()) { // timer stays local, nothing to do
+        if (OldTimer && OldTimer->Id())
+           isyslog("modified timer %s", *NewTimer->ToDescr());
+        else
+           isyslog("added timer %s", *NewTimer->ToDescr());
+        }
+     else { // timer is new, or moved from local to remote
+        if (!ExecSVDRPCommand(NewTimer->Remote(), cString::sprintf("NEWT %s", *NewTimer->ToText(true)), &Response) || SVDRPCode(Response[0]) != 250)
+           return RemoteTimerError(NewTimer);
+        int RemoteId = atoi(SVDRPValue(Response[0]));
+        if (RemoteId <= 0)
+           return RemoteTimerError(NewTimer);
+        NewTimer->SetId(RemoteId);
+        if (OldTimer && OldTimer->Id()) {
+           if (OldTimer->Recording())
+              cRecordControls::Stop(OldTimer);
+           isyslog("moved timer %d to %s", OldTimer->Id(), *NewTimer->ToDescr());
+           }
+        else
+           isyslog("added timer %s", *NewTimer->ToDescr());
+        }
+     }
+  else if (NewTimer->Local()) { // timer is moved from remote to local
+     if (!ExecSVDRPCommand(OldTimer->Remote(), cString::sprintf("DELT %d", OldTimer->Id()), &Response) || SVDRPCode(Response[0]) != 250)
+        return RemoteTimerError(OldTimer);
+     NewTimer->SetId(cTimers::NewTimerId());
+     NewTimer->ClrFlags(tfRecording); // in case it was recording on the remote machine
+     isyslog("moved timer %d@%s to %s", OldTimer->Id(), OldTimer->Remote(), *NewTimer->ToDescr());
+     }
+  else if (strcmp(OldTimer->Remote(), NewTimer->Remote()) == 0) { // timer stays remote on same machine
+     if (!ExecSVDRPCommand(OldTimer->Remote(), cString::sprintf("MODT %d %s", OldTimer->Id(), *NewTimer->ToText(true)), &Response) || SVDRPCode(Response[0]) != 250)
+        return RemoteTimerError(NewTimer);
+     isyslog("modified timer %s", *NewTimer->ToDescr());
+     }
+  else { // timer is moved from one remote machine to an other
+     if (!ExecSVDRPCommand(NewTimer->Remote(), cString::sprintf("NEWT %s", *NewTimer->ToText(true)), &Response) || SVDRPCode(Response[0]) != 250)
+        return RemoteTimerError(NewTimer);
+     int RemoteId = atoi(SVDRPValue(Response[0]));
+     if (RemoteId <= 0)
+        return RemoteTimerError(NewTimer);
+     NewTimer->SetId(RemoteId);
+     if (!ExecSVDRPCommand(OldTimer->Remote(), cString::sprintf("DELT %d", OldTimer->Id()), &Response) || SVDRPCode(Response[0]) != 250)
+        return RemoteTimerError(OldTimer);
+     isyslog("moved timer %d@%s to %s", OldTimer->Id(), OldTimer->Remote(), *NewTimer->ToDescr());
+     }
+  return true;
+}
+
 eOSState cMenuEditTimer::ProcessKey(eKeys Key)
 {
   eOSState state = cOsdMenu::ProcessKey(Key);
 
   if (state == osUnknown) {
      switch (Key) {
-       case kOk:     {
-                       cChannel *ch = Channels.GetByNumber(channel);
-                       if (ch)
-                          data.channel = ch;
-                       else {
-                          Skins.Message(mtError, tr("*** Invalid Channel ***"));
-                          break;
-                          }
-                       if (!*data.file)
-                          strcpy(data.file, data.Channel()->ShortName(true));
-                       if (timer) {
-                          if (memcmp((void *)timer, &data, sizeof(data)) != 0)
-                             *timer = data;
-                          if (addIfConfirmed)
-                             Timers.Add(timer);
-                          timer->SetEventFromSchedule();
-                          timer->Matches();
-                          Timers.SetModified();
-                          isyslog("timer %s %s (%s)", *timer->ToDescr(), addIfConfirmed ? "added" : "modified", timer->HasFlags(tfActive) ? "active" : "inactive");
-                          addIfConfirmed = false;
-                          }
-                     }
+       case kOk:     if (timer) {
+                        LOCK_TIMERS_WRITE;
+                        if (!addIfConfirmed && !Timers->Contains(timer)) {
+                           Skins.Message(mtWarning, tr("Timer has been deleted!"));
+                           break;
+                           }
+                        LOCK_CHANNELS_READ;
+                        if (const cChannel *Channel = Channels->GetByNumber(channel))
+                           data.channel = Channel;
+                        else {
+                           Skins.Message(mtError, tr("*** Invalid Channel ***"));
+                           break;
+                           }
+                        if (!*data.file)
+                           strcpy(data.file, data.Channel()->ShortName(true));
+                        data.SetRemote(*remote ? remote : NULL);
+                        if (addIfConfirmed) {
+                           *timer = data;
+                           Timers->Add(timer);
+                           addedTimer = timer;
+                           if (!HandleRemoteModifications(timer)) {
+                              // must add the timer before HandleRemoteModifications to get proper log messages with timer ids
+                              Timers->Del(timer);
+                              addedTimer = NULL;
+                              return osContinue;
+                              }
+                           }
+                        else {
+                           if (!HandleRemoteModifications(&data, timer))
+                              return osContinue;
+                           *timer = data;
+                           }
+                        LOCK_SCHEDULES_READ;
+                        timer->SetEventFromSchedule(Schedules);
+                        timer->Matches();
+                        addIfConfirmed = false;
+                        }
                      return osBack;
        case kRed:    return AddSubMenu(new cMenuFolder(tr("Select folder"), &Folders, data.file));
        case kGreen:  if (day) {
@@ -1063,16 +1189,16 @@
 
 class cMenuTimerItem : public cOsdItem {
 private:
-  cTimer *timer;
+  const cTimer *timer;
 public:
-  cMenuTimerItem(cTimer *Timer);
+  cMenuTimerItem(const cTimer *Timer);
   virtual int Compare(const cListObject &ListObject) const;
   virtual void Set(void);
-  cTimer *Timer(void) { return timer; }
+  const cTimer *Timer(void) { return timer; }
   virtual void SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable);
   };
 
-cMenuTimerItem::cMenuTimerItem(cTimer *Timer)
+cMenuTimerItem::cMenuTimerItem(const cTimer *Timer)
 {
   timer = Timer;
   Set();
@@ -1105,7 +1231,7 @@
      File++;
   else
      File = timer->File();
-  SetText(cString::sprintf("%c\t%d\t%s%s%s\t%02d:%02d\t%02d:%02d\t%s",
+  SetText(cString::sprintf("%c\t%d\t%s%s%s\t%02d:%02d\t%02d:%02d\t%s%s",
                     !(timer->HasFlags(tfActive)) ? ' ' : timer->FirstDay() ? '!' : timer->Recording() ? '#' : '>',
                     timer->Channel()->Number(),
                     *name,
@@ -1115,6 +1241,7 @@
                     timer->Start() % 100,
                     timer->Stop() / 100,
                     timer->Stop() % 100,
+                    timer->Remote() ? *cString::sprintf("@%s: ", timer->Remote()) : "",
                     File));
 }
 
@@ -1128,13 +1255,15 @@
 
 class cMenuTimers : public cOsdMenu {
 private:
+  cStateKey timersStateKey;
   int helpKeys;
+  void Set(void);
   eOSState Edit(void);
   eOSState New(void);
   eOSState Delete(void);
   eOSState OnOff(void);
   eOSState Info(void);
-  cTimer *CurrentTimer(void);
+  cTimer *GetTimer(void);
   void SetHelpKeys(void);
 public:
   cMenuTimers(void);
@@ -1147,33 +1276,45 @@
 {
   SetMenuCategory(mcTimer);
   helpKeys = -1;
-  for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) {
-      timer->SetEventFromSchedule(); // make sure the event is current
-      Add(new cMenuTimerItem(timer));
-      }
-  Sort();
-  SetCurrent(First());
-  SetHelpKeys();
-  Timers.IncBeingEdited();
+  cMenuEditTimer::AddedTimer(); // to clear any leftovers
+  Set();
 }
 
 cMenuTimers::~cMenuTimers()
 {
-  Timers.DecBeingEdited();
 }
 
-cTimer *cMenuTimers::CurrentTimer(void)
+void cMenuTimers::Set(void)
+{
+  if (const cTimers *Timers = cTimers::GetTimersRead(timersStateKey)) {
+     const cTimer *CurrentTimer = GetTimer();
+     cMenuTimerItem *CurrentItem = NULL;
+     Clear();
+     for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
+         cMenuTimerItem *Item = new cMenuTimerItem(Timer);
+         Add(Item);
+         if (CurrentTimer && Timer->Id() == CurrentTimer->Id() && (!Timer->Remote() && !CurrentTimer->Remote() || Timer->Remote() && CurrentTimer->Remote() && strcmp(Timer->Remote(), CurrentTimer->Remote()) == 0))
+            CurrentItem = Item;
+         }
+     Sort();
+     SetCurrent(CurrentItem ? CurrentItem : First());
+     SetHelpKeys();
+     Display();
+     timersStateKey.Remove();
+     }
+}
+
+cTimer *cMenuTimers::GetTimer(void)
 {
   cMenuTimerItem *item = (cMenuTimerItem *)Get(Current());
-  return item ? item->Timer() : NULL;
+  return item ? (cTimer *)item->Timer() : NULL;
 }
 
 void cMenuTimers::SetHelpKeys(void)
 {
   int NewHelpKeys = 0;
-  cTimer *timer = CurrentTimer();
-  if (timer) {
-     if (timer->Event())
+  if (const cTimer *Timer = GetTimer()) {
+     if (Timer->Event())
         NewHelpKeys = 2;
      else
         NewHelpKeys = 1;
@@ -1188,18 +1329,25 @@
 {
   if (HasSubMenu())
      return osContinue;
-  cTimer *timer = CurrentTimer();
-  if (timer) {
-     timer->OnOff();
-     timer->SetEventFromSchedule();
+  cTimers::GetTimersWrite(timersStateKey);
+  cTimer *Timer = GetTimer();
+  if (Timer) {
+     Timer->OnOff();
+     if (Timer->Remote()) {
+        cStringList Response;
+        if (!ExecSVDRPCommand(Timer->Remote(), cString::sprintf("MODT %d %s", Timer->Id(), *Timer->ToText(true)), &Response) || SVDRPCode(Response[0]) != 250)
+           RemoteTimerError(Timer);
+        }
+     LOCK_SCHEDULES_READ;
+     Timer->SetEventFromSchedule(Schedules);
      RefreshCurrent();
      DisplayCurrent(true);
-     if (timer->FirstDay())
-        isyslog("timer %s first day set to %s", *timer->ToDescr(), *timer->PrintFirstDay());
+     if (Timer->FirstDay())
+        isyslog("set first day of timer %s to %s", *Timer->ToDescr(), *Timer->PrintFirstDay());
      else
-        isyslog("timer %s %sactivated", *timer->ToDescr(), timer->HasFlags(tfActive) ? "" : "de");
-     Timers.SetModified();
+        isyslog("%sactivated timer %s", Timer->HasFlags(tfActive) ? "" : "de", *Timer->ToDescr());
      }
+  timersStateKey.Remove(Timer != NULL);
   return osContinue;
 }
 
@@ -1207,38 +1355,50 @@
 {
   if (HasSubMenu() || Count() == 0)
      return osContinue;
-  isyslog("editing timer %s", *CurrentTimer()->ToDescr());
-  return AddSubMenu(new cMenuEditTimer(CurrentTimer()));
+  return AddSubMenu(new cMenuEditTimer(GetTimer()));
 }
 
 eOSState cMenuTimers::New(void)
 {
   if (HasSubMenu())
      return osContinue;
-  return AddSubMenu(new cMenuEditTimer(new cTimer, true));
+  cTimer *Timer = new cTimer;
+  if (*Setup.SVDRPDefaultHost)
+     Timer->SetRemote(Setup.SVDRPDefaultHost);
+  return AddSubMenu(new cMenuEditTimer(Timer, true));
 }
 
 eOSState cMenuTimers::Delete(void)
 {
+  cTimers *Timers = cTimers::GetTimersWrite(timersStateKey);
   // Check if this timer is active:
-  cTimer *ti = CurrentTimer();
-  if (ti) {
+  cTimer *Timer = GetTimer();
+  if (Timer) {
      if (Interface->Confirm(tr("Delete timer?"))) {
-        if (ti->Recording()) {
+        if (Timer->Recording()) {
            if (Interface->Confirm(tr("Timer still recording - really delete?"))) {
-              ti->Skip();
-              cRecordControls::Process(time(NULL));
+              if (!Timer->Remote()) {
+                 Timer->Skip();
+                 cRecordControls::Process(Timers, time(NULL));
+                 }
               }
            else
-              return osContinue;
+              Timer = NULL;
+           }
+        if (Timer) {
+           if (Timer->Remote()) {
+              cStringList Response;
+              if (!ExecSVDRPCommand(Timer->Remote(), cString::sprintf("DELT %d", Timer->Id()), &Response) || SVDRPCode(Response[0]) != 250)
+                 RemoteTimerError(Timer);
+              }
+           Timers->Del(Timer);
+           cOsdMenu::Del(Current());
+           Display();
+           isyslog("deleted timer %s", *Timer->ToDescr());
            }
-        isyslog("deleting timer %s", *ti->ToDescr());
-        Timers.Del(ti);
-        cOsdMenu::Del(Current());
-        Timers.SetModified();
-        Display();
         }
      }
+  timersStateKey.Remove(Timer != NULL);
   return osContinue;
 }
 
@@ -1246,17 +1406,19 @@
 {
   if (HasSubMenu() || Count() == 0)
      return osContinue;
-  cTimer *ti = CurrentTimer();
-  if (ti && ti->Event())
-     return AddSubMenu(new cMenuEvent(ti->Event()));
+  LOCK_TIMERS_READ;
+  LOCK_CHANNELS_READ;
+  cTimer *Timer = GetTimer();
+  if (Timer && Timer->Event())
+     return AddSubMenu(new cMenuEvent(Timers, Channels, Timer->Event()));
   return osContinue;
 }
 
 eOSState cMenuTimers::ProcessKey(eKeys Key)
 {
-  int TimerNumber = HasSubMenu() ? Count() : -1;
+  if (!HasSubMenu())
+     Set();
   eOSState state = cOsdMenu::ProcessKey(Key);
-
   if (state == osUnknown) {
      switch (Key) {
        case kOk:     return Edit();
@@ -1269,9 +1431,10 @@
        default: break;
        }
      }
-  if (TimerNumber >= 0 && !HasSubMenu() && Timers.Get(TimerNumber)) {
-     // a newly created timer was confirmed with Ok
-     Add(new cMenuTimerItem(Timers.Get(TimerNumber)), true);
+  if (const cTimer *Timer = cMenuEditTimer::AddedTimer()) {
+     // a newly created timer was confirmed with Ok and the proper item needs to be added:
+     LOCK_TIMERS_READ;
+     Add(new cMenuTimerItem(Timer), true);
      Display();
      }
   if (Key != kNone)
@@ -1281,19 +1444,19 @@
 
 // --- cMenuEvent ------------------------------------------------------------
 
-cMenuEvent::cMenuEvent(const cEvent *Event, bool CanSwitch, bool Buttons)
+cMenuEvent::cMenuEvent(const cTimers *Timers, const cChannels *Channels, const cEvent *Event, bool CanSwitch, bool Buttons)
 :cOsdMenu(tr("Event"))
 {
   SetMenuCategory(mcEvent);
   event = Event;
   if (event) {
-     cChannel *channel = Channels.GetByChannelID(event->ChannelID(), true);
-     if (channel) {
-        SetTitle(channel->Name());
-        eTimerMatch TimerMatch = tmNone;
-        Timers.GetMatch(event, &TimerMatch);
-        if (Buttons)
+     if (const cChannel *Channel = Channels->GetByChannelID(event->ChannelID(), true)) {
+        SetTitle(Channel->Name());
+        if (Buttons) {
+           eTimerMatch TimerMatch = tmNone;
+           Timers->GetMatch(event, &TimerMatch);
            SetHelp(TimerMatch == tmFull ? tr("Button$Timer") : tr("Button$Record"), NULL, NULL, CanSwitch ? tr("Button$Switch") : NULL);
+           }
         }
      }
 }
@@ -1349,24 +1512,24 @@
   const cChannel *channel;
   bool withDate;
   eTimerMatch timerMatch;
-  cMenuScheduleItem(const cEvent *Event, cChannel *Channel = NULL, bool WithDate = false);
+  cMenuScheduleItem(const cTimers *Timers, const cEvent *Event, const cChannel *Channel = NULL, bool WithDate = false);
   static void SetSortMode(eScheduleSortMode SortMode) { sortMode = SortMode; }
   static void IncSortMode(void) { sortMode = eScheduleSortMode((sortMode == ssmAllAll) ? ssmAllThis : sortMode + 1); }
   static eScheduleSortMode SortMode(void) { return sortMode; }
   virtual int Compare(const cListObject &ListObject) const;
-  bool Update(bool Force = false);
+  bool Update(const cTimers *Timers, bool Force = false);
   virtual void SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable);
   };
 
 cMenuScheduleItem::eScheduleSortMode cMenuScheduleItem::sortMode = ssmAllThis;
 
-cMenuScheduleItem::cMenuScheduleItem(const cEvent *Event, cChannel *Channel, bool WithDate)
+cMenuScheduleItem::cMenuScheduleItem(const cTimers *Timers, const cEvent *Event, const cChannel *Channel, bool WithDate)
 {
   event = Event;
   channel = Channel;
   withDate = WithDate;
   timerMatch = tmNone;
-  Update(true);
+  Update(Timers, true);
 }
 
 int cMenuScheduleItem::Compare(const cListObject &ListObject) const
@@ -1382,11 +1545,10 @@
 
 static const char *TimerMatchChars = " tT";
 
-bool cMenuScheduleItem::Update(bool Force)
+bool cMenuScheduleItem::Update(const cTimers *Timers, bool Force)
 {
-  bool result = false;
   eTimerMatch OldTimerMatch = timerMatch;
-  Timers.GetMatch(event, &timerMatch);
+  Timers->GetMatch(event, &timerMatch);
   if (Force || timerMatch != OldTimerMatch) {
      cString buffer;
      char t = TimerMatchChars[timerMatch];
@@ -1401,9 +1563,9 @@
      else
         buffer = cString::sprintf("%.*s\t%s\t%c%c%c\t%s", Utf8SymChars(eds, 6), *eds, *event->GetTimeString(), t, v, r, event->Title());
      SetText(buffer);
-     result = true;
+     return true;
      }
-  return result;
+  return false;
 }
 
 void cMenuScheduleItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable)
@@ -1419,7 +1581,7 @@
   bool now;
   bool canSwitch;
   int helpKeys;
-  int timerState;
+  cStateKey timersStateKey;
   eOSState Record(void);
   eOSState Switch(void);
   static int currentChannel;
@@ -1427,7 +1589,7 @@
   bool Update(void);
   void SetHelpKeys(void);
 public:
-  cMenuWhatsOn(const cSchedules *Schedules, bool Now, int CurrentChannelNr);
+  cMenuWhatsOn(const cTimers *Timers, const cChannels *Channels, const cSchedules *Schedules, bool Now, int CurrentChannelNr);
   static int CurrentChannel(void) { return currentChannel; }
   static void SetCurrentChannel(int ChannelNr) { currentChannel = ChannelNr; }
   static const cEvent *ScheduleEvent(void);
@@ -1437,22 +1599,18 @@
 int cMenuWhatsOn::currentChannel = 0;
 const cEvent *cMenuWhatsOn::scheduleEvent = NULL;
 
-cMenuWhatsOn::cMenuWhatsOn(const cSchedules *Schedules, bool Now, int CurrentChannelNr)
+cMenuWhatsOn::cMenuWhatsOn(const cTimers *Timers, const cChannels *Channels, const cSchedules *Schedules, bool Now, int CurrentChannelNr)
 :cOsdMenu(Now ? tr("What's on now?") : tr("What's on next?"), CHNUMWIDTH, CHNAMWIDTH, 6, 4)
 {
   SetMenuCategory(Now ? mcScheduleNow : mcScheduleNext);
   now = Now;
   canSwitch = false;
   helpKeys = 0;
-  timerState = 0;
-  Timers.Modified(timerState);
-  for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
+  for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
       if (!Channel->GroupSep()) {
-         const cSchedule *Schedule = Schedules->GetSchedule(Channel);
-         if (Schedule) {
-            const cEvent *Event = Now ? Schedule->GetPresentEvent() : Schedule->GetFollowingEvent();
-            if (Event)
-               Add(new cMenuScheduleItem(Event, Channel), Channel->Number() == CurrentChannelNr);
+         if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) {
+            if (const cEvent *Event = Now ? Schedule->GetPresentEvent() : Schedule->GetFollowingEvent())
+               Add(new cMenuScheduleItem(Timers, Event, Channel), Channel->Number() == CurrentChannelNr);
             }
          }
       }
@@ -1464,11 +1622,12 @@
 bool cMenuWhatsOn::Update(void)
 {
   bool result = false;
-  if (Timers.Modified(timerState)) {
+  if (const cTimers *Timers = cTimers::GetTimersRead(timersStateKey)) {
      for (cOsdItem *item = First(); item; item = Next(item)) {
-         if (((cMenuScheduleItem *)item)->Update())
+         if (((cMenuScheduleItem *)item)->Update(Timers))
             result = true;
          }
+     timersStateKey.Remove();
      }
   return result;
 }
@@ -1487,7 +1646,8 @@
         NewHelpKeys |= 0x04; // "Next"
      else
         NewHelpKeys |= 0x08; // "Now"
-     if (cChannel *Channel = Channels.GetByChannelID(item->event->ChannelID(), true)) {
+     LOCK_CHANNELS_READ;
+     if (const cChannel *Channel = Channels->GetByChannelID(item->event->ChannelID(), true)) {
         if (Channel->Number() != cDevice::CurrentChannel()) {
            NewHelpKeys |= 0x10; // "Switch"
            canSwitch = true;
@@ -1512,8 +1672,13 @@
 {
   cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
   if (item) {
-     cChannel *channel = Channels.GetByChannelID(item->event->ChannelID(), true);
-     if (channel && cDevice::PrimaryDevice()->SwitchChannel(channel, true))
+     LOCK_CHANNELS_READ;
+     const cChannel *Channel = Channels->GetByChannelID(item->event->ChannelID(), true);
+     if (Channel) {
+        if (!cDevice::PrimaryDevice()->SwitchChannel(Channel, true))
+           Channel = NULL;
+        }
+     if (Channel)
         return osEnd;
      }
   Skins.Message(mtError, tr("Can't switch channel!"));
@@ -1522,33 +1687,38 @@
 
 eOSState cMenuWhatsOn::Record(void)
 {
-  cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
-  if (item) {
-     if (item->timerMatch == tmFull) {
-        eTimerMatch tm = tmNone;
-        cTimer *timer = Timers.GetMatch(item->event, &tm);
-        if (timer)
-           return AddSubMenu(new cMenuEditTimer(timer));
-        }
-     cTimer *timer = new cTimer(item->event);
-     cTimer *t = Timers.GetTimer(timer);
-     if (t) {
-        delete timer;
-        timer = t;
-        return AddSubMenu(new cMenuEditTimer(timer));
-        }
-     else {
-        Timers.Add(timer);
-        Timers.SetModified();
-        isyslog("timer %s added (active)", *timer->ToDescr());
-        if (timer->Matches(0, false, NEWTIMERLIMIT))
-           return AddSubMenu(new cMenuEditTimer(timer));
-        if (HasSubMenu())
-           CloseSubMenu();
-        if (Update())
-           Display();
-        SetHelpKeys();
-        }
+  if (cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current())) {
+     {
+       LOCK_TIMERS_WRITE;
+       LOCK_SCHEDULES_READ;
+       Timers->SetExplicitModify();
+       if (item->timerMatch == tmFull) {
+          if (cTimer *Timer = Timers->GetMatch(item->event))
+             return AddSubMenu(new cMenuEditTimer(Timer));
+          }
+       cTimer *Timer = new cTimer(item->event);
+       if (*Setup.SVDRPDefaultHost)
+          Timer->SetRemote(Setup.SVDRPDefaultHost);
+       if (cTimer *t = Timers->GetTimer(Timer)) {
+          delete Timer;
+          Timer = t;
+          return AddSubMenu(new cMenuEditTimer(Timer));
+          }
+       if (Timer->Matches(0, false, NEWTIMERLIMIT))
+          return AddSubMenu(new cMenuEditTimer(Timer, true));
+       Timers->Add(Timer);
+       Timers->SetModified();
+       if (!HandleRemoteModifications(Timer)) {
+          // must add the timer before HandleRemoteModifications to get proper log messages with timer ids
+          Timers->Del(Timer);
+          delete Timer;
+          }
+     }
+     if (HasSubMenu())
+        CloseSubMenu();
+     if (Update())
+        Display();
+     SetHelpKeys();
      }
   return osContinue;
 }
@@ -1576,8 +1746,11 @@
                         return Switch();
                      break;
        case kInfo:
-       case kOk:     if (Count())
-                        return AddSubMenu(new cMenuEvent(((cMenuScheduleItem *)Get(Current()))->event, canSwitch, true));
+       case kOk:     if (Count()) {
+                        LOCK_TIMERS_READ;
+                        LOCK_CHANNELS_READ;
+                        return AddSubMenu(new cMenuEvent(Timers, Channels, ((cMenuScheduleItem *)Get(Current()))->event, canSwitch, true));
+                        }
                      break;
        default:      break;
        }
@@ -1595,19 +1768,20 @@
 
 class cMenuSchedule : public cOsdMenu {
 private:
-  cSchedulesLock schedulesLock;
-  const cSchedules *schedules;
+  cStateKey timersStateKey;
+  cStateKey schedulesStateKey;
+  int scheduleState;
   bool now, next;
   bool canSwitch;
   int helpKeys;
-  int timerState;
+  void Set(const cChannel *Channel = NULL, bool Force = false);
   eOSState Number(void);
   eOSState Record(void);
   eOSState Switch(void);
-  void PrepareScheduleAllThis(const cEvent *Event, const cChannel *Channel);
-  void PrepareScheduleThisThis(const cEvent *Event, const cChannel *Channel);
-  void PrepareScheduleThisAll(const cEvent *Event, const cChannel *Channel);
-  void PrepareScheduleAllAll(const cEvent *Event, const cChannel *Channel);
+  bool PrepareScheduleAllThis(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel);
+  bool PrepareScheduleThisThis(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel);
+  bool PrepareScheduleThisAll(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel);
+  bool PrepareScheduleAllAll(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel);
   bool Update(void);
   void SetHelpKeys(void);
 public:
@@ -1620,19 +1794,13 @@
 :cOsdMenu("")
 {
   SetMenuCategory(mcSchedule);
+  scheduleState = -1;
   now = next = false;
   canSwitch = false;
   helpKeys = 0;
-  timerState = 0;
-  Timers.Modified(timerState);
   cMenuScheduleItem::SetSortMode(cMenuScheduleItem::ssmAllThis);
-  cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());
-  if (channel) {
-     cMenuWhatsOn::SetCurrentChannel(channel->Number());
-     schedules = cSchedules::Schedules(schedulesLock);
-     PrepareScheduleAllThis(NULL, channel);
-     SetHelpKeys();
-     }
+  cMenuWhatsOn::SetCurrentChannel(cDevice::CurrentChannel());
+  Set(NULL, true);
 }
 
 cMenuSchedule::~cMenuSchedule()
@@ -1640,87 +1808,131 @@
   cMenuWhatsOn::ScheduleEvent(); // makes sure any posted data is cleared
 }
 
-void cMenuSchedule::PrepareScheduleAllThis(const cEvent *Event, const cChannel *Channel)
+void cMenuSchedule::Set(const cChannel *Channel, bool Force)
 {
-  Clear();
-  SetCols(7, 6, 4);
-  SetTitle(cString::sprintf(tr("Schedule - %s"), Channel->Name()));
-  if (schedules && Channel) {
-     const cSchedule *Schedule = schedules->GetSchedule(Channel);
-     if (Schedule) {
+  if (Force) {
+     schedulesStateKey.Reset();
+     scheduleState = -1;
+     }
+  LOCK_TIMERS_READ;
+  LOCK_CHANNELS_READ;
+  if (const cSchedules *Schedules = cSchedules::GetSchedulesRead(schedulesStateKey)) {
+     cMenuScheduleItem *CurrentItem = (cMenuScheduleItem *)Get(Current());
+     const cEvent *Event = NULL;
+     if (!Channel) {
+        if (CurrentItem) {
+           Event = CurrentItem->event;
+           Channel = Channels->GetByChannelID(Event->ChannelID(), true);
+           }
+        else
+           Channel = Channels->GetByNumber(cDevice::CurrentChannel());
+        }
+     bool Refresh = false;
+     switch (cMenuScheduleItem::SortMode()) {
+       case cMenuScheduleItem::ssmAllThis:  Refresh = PrepareScheduleAllThis(Timers, Schedules, Event, Channel); break;
+       case cMenuScheduleItem::ssmThisThis: Refresh = PrepareScheduleThisThis(Timers, Schedules, Event, Channel); break;
+       case cMenuScheduleItem::ssmThisAll:  Refresh = Force && PrepareScheduleThisAll(Timers, Schedules, Event, Channel); break;
+       case cMenuScheduleItem::ssmAllAll:   Refresh = Force && PrepareScheduleAllAll(Timers, Schedules, Event, Channel); break;
+       default: esyslog("ERROR: unknown SortMode %d (%s %d)", cMenuScheduleItem::SortMode(), __FUNCTION__, __LINE__);
+       }
+     if (Refresh) {
+        CurrentItem = (cMenuScheduleItem *)Get(Current());
+        Sort();
+        SetCurrent(CurrentItem);
+        SetHelpKeys();
+        Display();
+        }
+     schedulesStateKey.Remove();
+     }
+}
+
+bool cMenuSchedule::PrepareScheduleAllThis(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel)
+{
+  if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) {
+     if (Schedule->Modified(scheduleState)) {
+        Clear();
+        SetCols(7, 6, 4);
+        SetTitle(cString::sprintf(tr("Schedule - %s"), Channel->Name()));
         const cEvent *PresentEvent = Event ? Event : Schedule->GetPresentEvent();
         time_t now = time(NULL) - Setup.EPGLinger * 60;
         for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) {
             if (ev->EndTime() > now || ev == PresentEvent)
-               Add(new cMenuScheduleItem(ev), ev == PresentEvent);
+               Add(new cMenuScheduleItem(Timers, ev), ev == PresentEvent);
             }
+        return true;
         }
      }
+  return false;
 }
 
-void cMenuSchedule::PrepareScheduleThisThis(const cEvent *Event, const cChannel *Channel)
+bool cMenuSchedule::PrepareScheduleThisThis(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel)
 {
-  Clear();
-  SetCols(7, 6, 4);
-  SetTitle(cString::sprintf(tr("This event - %s"), Channel->Name()));
-  if (schedules && Channel && Event) {
-     const cSchedule *Schedule = schedules->GetSchedule(Channel);
-     if (Schedule) {
-        time_t now = time(NULL) - Setup.EPGLinger * 60;
-        for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) {
-            if ((ev->EndTime() > now || ev == Event) && !strcmp(ev->Title(), Event->Title()))
-               Add(new cMenuScheduleItem(ev), ev == Event);
-            }
+  if (Event) {
+     if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) {
+        if (Schedule->Modified(scheduleState)) {
+           Clear();
+           SetCols(7, 6, 4);
+           SetTitle(cString::sprintf(tr("This event - %s"), Channel->Name()));
+           time_t now = time(NULL) - Setup.EPGLinger * 60;
+           for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) {
+               if ((ev->EndTime() > now || ev == Event) && !strcmp(ev->Title(), Event->Title()))
+                  Add(new cMenuScheduleItem(Timers, ev), ev == Event);
+               }
+           return true;
+           }
         }
      }
+  return false;
 }
 
-void cMenuSchedule::PrepareScheduleThisAll(const cEvent *Event, const cChannel *Channel)
+bool cMenuSchedule::PrepareScheduleThisAll(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel)
 {
   Clear();
   SetCols(CHNUMWIDTH, CHNAMWIDTH, 7, 6, 4);
   SetTitle(tr("This event - all channels"));
-  if (schedules && Event) {
-     for (cChannel *ch = Channels.First(); ch; ch = Channels.Next(ch)) {
-         const cSchedule *Schedule = schedules->GetSchedule(ch);
-         if (Schedule) {
+  if (Event) {
+     LOCK_CHANNELS_READ;
+     for (const cChannel *ch = Channels->First(); ch; ch = Channels->Next(ch)) {
+         if (const cSchedule *Schedule = Schedules->GetSchedule(ch)) {
             time_t now = time(NULL) - Setup.EPGLinger * 60;
             for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) {
                 if ((ev->EndTime() > now || ev == Event) && !strcmp(ev->Title(), Event->Title()))
-                   Add(new cMenuScheduleItem(ev, ch, true), ev == Event && ch == Channel);
+                   Add(new cMenuScheduleItem(Timers, ev, ch, true), ev == Event && ch == Channel);
                 }
             }
          }
      }
+  return true;
 }
 
-void cMenuSchedule::PrepareScheduleAllAll(const cEvent *Event, const cChannel *Channel)
+bool cMenuSchedule::PrepareScheduleAllAll(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel)
 {
   Clear();
   SetCols(CHNUMWIDTH, CHNAMWIDTH, 7, 6, 4);
   SetTitle(tr("All events - all channels"));
-  if (schedules) {
-     for (cChannel *ch = Channels.First(); ch; ch = Channels.Next(ch)) {
-         const cSchedule *Schedule = schedules->GetSchedule(ch);
-         if (Schedule) {
-            time_t now = time(NULL) - Setup.EPGLinger * 60;
-            for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) {
-                if (ev->EndTime() > now || ev == Event)
-                   Add(new cMenuScheduleItem(ev, ch, true), ev == Event && ch == Channel);
-                }
-            }
+  LOCK_CHANNELS_READ;
+  cStateKey StateKey;
+  for (const cChannel *ch = Channels->First(); ch; ch = Channels->Next(ch)) {
+      if (const cSchedule *Schedule = Schedules->GetSchedule(ch)) {
+         time_t now = time(NULL) - Setup.EPGLinger * 60;
+         for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) {
+             if (ev->EndTime() > now || ev == Event)
+                Add(new cMenuScheduleItem(Timers, ev, ch, true), ev == Event && ch == Channel);
+             }
          }
-     }
+      }
+  return true;
 }
 
 bool cMenuSchedule::Update(void)
 {
   bool result = false;
-  if (Timers.Modified(timerState)) {
+  if (const cTimers *Timers = cTimers::GetTimersRead(timersStateKey)) {
      for (cOsdItem *item = First(); item; item = Next(item)) {
-         if (((cMenuScheduleItem *)item)->Update())
+         if (((cMenuScheduleItem *)item)->Update(Timers))
             result = true;
          }
+     timersStateKey.Remove();
      }
   return result;
 }
@@ -1735,7 +1947,8 @@
         NewHelpKeys |= 0x02; // "Timer"
      else
         NewHelpKeys |= 0x01; // "Record"
-     if (cChannel *Channel = Channels.GetByChannelID(item->event->ChannelID(), true)) {
+     LOCK_CHANNELS_READ;
+     if (const cChannel *Channel = Channels->GetByChannelID(item->event->ChannelID(), true)) {
         if (Channel->Number() != cDevice::CurrentChannel()) {
            NewHelpKeys |= 0x10; // "Switch"
            canSwitch = true;
@@ -1752,58 +1965,44 @@
 eOSState cMenuSchedule::Number(void)
 {
   cMenuScheduleItem::IncSortMode();
-  cMenuScheduleItem *CurrentItem = (cMenuScheduleItem *)Get(Current());
-  const cChannel *Channel = NULL;
-  const cEvent *Event = NULL;
-  if (CurrentItem) {
-     Event = CurrentItem->event;
-     Channel = Channels.GetByChannelID(Event->ChannelID(), true);
-     }
-  else
-     Channel = Channels.GetByNumber(cDevice::CurrentChannel());
-  switch (cMenuScheduleItem::SortMode()) {
-    case cMenuScheduleItem::ssmAllThis:  PrepareScheduleAllThis(Event, Channel); break;
-    case cMenuScheduleItem::ssmThisThis: PrepareScheduleThisThis(Event, Channel); break;
-    case cMenuScheduleItem::ssmThisAll:  PrepareScheduleThisAll(Event, Channel); break;
-    case cMenuScheduleItem::ssmAllAll:   PrepareScheduleAllAll(Event, Channel); break;
-    default: esyslog("ERROR: unknown SortMode %d (%s %d)", cMenuScheduleItem::SortMode(), __FUNCTION__, __LINE__);
-    }
-  CurrentItem = (cMenuScheduleItem *)Get(Current());
-  Sort();
-  SetCurrent(CurrentItem);
-  Display();
+  Set(NULL, true);
   return osContinue;
 }
 
 eOSState cMenuSchedule::Record(void)
 {
-  cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
-  if (item) {
-     if (item->timerMatch == tmFull) {
-        eTimerMatch tm = tmNone;
-        cTimer *timer = Timers.GetMatch(item->event, &tm);
-        if (timer)
-           return AddSubMenu(new cMenuEditTimer(timer));
-        }
-     cTimer *timer = new cTimer(item->event);
-     cTimer *t = Timers.GetTimer(timer);
-     if (t) {
-        delete timer;
-        timer = t;
-        return AddSubMenu(new cMenuEditTimer(timer));
-        }
-     else {
-        Timers.Add(timer);
-        Timers.SetModified();
-        isyslog("timer %s added (active)", *timer->ToDescr());
-        if (timer->Matches(0, false, NEWTIMERLIMIT))
-           return AddSubMenu(new cMenuEditTimer(timer));
-        if (HasSubMenu())
-           CloseSubMenu();
-        if (Update())
-           Display();
-        SetHelpKeys();
-        }
+  if (cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current())) {
+     {
+       LOCK_TIMERS_WRITE;
+       LOCK_SCHEDULES_READ;
+       Timers->SetExplicitModify();
+       if (item->timerMatch == tmFull) {
+          if (cTimer *Timer = Timers->GetMatch(item->event))
+             return AddSubMenu(new cMenuEditTimer(Timer));
+          }
+       cTimer *Timer = new cTimer(item->event);
+       if (*Setup.SVDRPDefaultHost)
+          Timer->SetRemote(Setup.SVDRPDefaultHost);
+       if (cTimer *t = Timers->GetTimer(Timer)) {
+          delete Timer;
+          Timer = t;
+          return AddSubMenu(new cMenuEditTimer(Timer));
+          }
+       if (Timer->Matches(0, false, NEWTIMERLIMIT))
+          return AddSubMenu(new cMenuEditTimer(Timer, true));
+       Timers->Add(Timer);
+       Timers->SetModified();
+       if (!HandleRemoteModifications(Timer)) {
+          // must add the timer before HandleRemoteModifications to get proper log messages with timer ids
+          Timers->Del(Timer);
+          delete Timer;
+          }
+     }
+     if (HasSubMenu())
+        CloseSubMenu();
+     if (Update())
+        Display();
+     SetHelpKeys();
      }
   return osContinue;
 }
@@ -1812,10 +2011,14 @@
 {
   cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
   if (item) {
-     if (cChannel *Channel = Channels.GetByChannelID(item->event->ChannelID(), true)) {
-        if (Channels.SwitchTo(Channel->Number()))
-           return osEnd;
+     LOCK_CHANNELS_READ;
+     const cChannel *Channel = NULL;
+     if (Channel = Channels->GetByChannelID(item->event->ChannelID(), true)) {
+        if (!Channels->SwitchTo(Channel->Number()))
+           Channel = NULL;
         }
+     if (Channel)
+        return osEnd;
      }
   Skins.Message(mtError, tr("Can't switch channel!"));
   return osContinue;
@@ -1823,6 +2026,8 @@
 
 eOSState cMenuSchedule::ProcessKey(eKeys Key)
 {
+  if (!HasSubMenu())
+     Set(); // react on any changes to the schedules list
   bool HadSubMenu = HasSubMenu();
   eOSState state = cOsdMenu::ProcessKey(Key);
 
@@ -1831,43 +2036,50 @@
        case k0:      return Number();
        case kRecord:
        case kRed:    return Record();
-       case kGreen:  if (schedules) {
-                        if (!now && !next) {
-                           int ChannelNr = 0;
-                           if (Count()) {
-                              cChannel *channel = Channels.GetByChannelID(((cMenuScheduleItem *)Get(Current()))->event->ChannelID(), true);
-                              if (channel)
-                                 ChannelNr = channel->Number();
-                              }
-                           now = true;
-                           return AddSubMenu(new cMenuWhatsOn(schedules, true, ChannelNr));
-                           }
-                        now = !now;
-                        next = !next;
-                        return AddSubMenu(new cMenuWhatsOn(schedules, now, cMenuWhatsOn::CurrentChannel()));
-                        }
-       case kYellow: if (schedules)
-                        return AddSubMenu(new cMenuWhatsOn(schedules, false, cMenuWhatsOn::CurrentChannel()));
-                     break;
+       case kGreen:  {
+                       LOCK_TIMERS_READ;
+                       LOCK_CHANNELS_READ;
+                       LOCK_SCHEDULES_READ;
+                       if (!now && !next) {
+                          int ChannelNr = 0;
+                          if (Count()) {
+                             if (const cChannel *Channel = Channels->GetByChannelID(((cMenuScheduleItem *)Get(Current()))->event->ChannelID(), true))
+                                ChannelNr = Channel->Number();
+                             }
+                          now = true;
+                          return AddSubMenu(new cMenuWhatsOn(Timers, Channels, Schedules, now, ChannelNr));
+                          }
+                       now = !now;
+                       next = !next;
+                       return AddSubMenu(new cMenuWhatsOn(Timers, Channels, Schedules, now, cMenuWhatsOn::CurrentChannel()));
+                     }
+       case kYellow: {
+                       LOCK_TIMERS_READ;
+                       LOCK_CHANNELS_READ;
+                       LOCK_SCHEDULES_READ;
+                       return AddSubMenu(new cMenuWhatsOn(Timers, Channels, Schedules, false, cMenuWhatsOn::CurrentChannel()));
+                     }
        case kBlue:   if (canSwitch)
                         return Switch();
                      break;
        case kInfo:
-       case kOk:     if (Count())
-                        return AddSubMenu(new cMenuEvent(((cMenuScheduleItem *)Get(Current()))->event, canSwitch, true));
+       case kOk:     if (Count()) {
+                        LOCK_TIMERS_READ;
+                        LOCK_CHANNELS_READ;
+                        LOCK_SCHEDULES_READ;
+                        return AddSubMenu(new cMenuEvent(Timers, Channels, ((cMenuScheduleItem *)Get(Current()))->event, canSwitch, true));
+                        }
                      break;
        default:      break;
        }
      }
   else if (!HasSubMenu()) {
      now = next = false;
-     const cEvent *ei = cMenuWhatsOn::ScheduleEvent();
-     if (ei) {
-        cChannel *channel = Channels.GetByChannelID(ei->ChannelID(), true);
-        if (channel) {
+     if (const cEvent *ei = cMenuWhatsOn::ScheduleEvent()) {
+        LOCK_CHANNELS_READ;
+        if (const cChannel *Channel = Channels->GetByChannelID(ei->ChannelID(), true)) {
            cMenuScheduleItem::SetSortMode(cMenuScheduleItem::ssmAllThis);
-           PrepareScheduleAllThis(NULL, channel);
-           Display();
+           Set(Channel, true);
            }
         }
      else if (HadSubMenu && Update())
@@ -2219,7 +2431,10 @@
   else
      s = path;
   strn0cpy(name, s, sizeof(name));
-  pathIsInUse = Recordings.PathIsInUse(path);
+  {
+    LOCK_RECORDINGS_READ;
+    pathIsInUse = Recordings->PathIsInUse(path);
+  }
   cOsdItem *p;
   Add(p = folderItem = new cMenuEditStrItem(tr("Folder"), folder, sizeof(folder)));
   p->SetSelectable(!pathIsInUse);
@@ -2258,14 +2473,17 @@
   cString NewPath = *folder ? cString::sprintf("%s%c%s", folder, FOLDERDELIMCHAR, name) : name;
   NewPath.CompactChars(FOLDERDELIMCHAR);
   if (strcmp(NewPath, path)) {
-     int NumRecordings = Recordings.GetNumRecordingsInPath(path);
+     LOCK_RECORDINGS_WRITE;
+     Recordings->SetExplicitModify();
+     int NumRecordings = Recordings->GetNumRecordingsInPath(path);
      if (NumRecordings > 1 && !Interface->Confirm(cString::sprintf(tr("Move entire folder containing %d recordings?"), NumRecordings)))
         return osContinue;
-     if (!Recordings.MoveRecordings(path, NewPath)) {
+     if (!Recordings->MoveRecordings(path, NewPath)) {
         Skins.Message(mtError, tr("Error while moving folder!"));
         return osContinue;
         }
      cMenuRecordings::SetPath(NewPath); // makes sure the Recordings menu will reposition to the new path
+     Recordings->SetModified();
      return osUser1;
      }
   return osBack;
@@ -2294,9 +2512,9 @@
 
 class cMenuRecordingEdit : public cOsdMenu {
 private:
-  cRecording *recording;
+  const cRecording *recording;
   cString originalFileName;
-  int recordingsState;
+  cStateKey recordingsStateKey;
   char folder[PATH_MAX];
   char name[NAME_MAX];
   int priority;
@@ -2319,17 +2537,16 @@
   eOSState DeleteMarks(void);
   eOSState ApplyChanges(void);
 public:
-  cMenuRecordingEdit(cRecording *Recording);
+  cMenuRecordingEdit(const cRecording *Recording);
   virtual eOSState ProcessKey(eKeys Key);
   };
 
-cMenuRecordingEdit::cMenuRecordingEdit(cRecording *Recording)
+cMenuRecordingEdit::cMenuRecordingEdit(const cRecording *Recording)
 :cOsdMenu(tr("Edit recording"), 12)
 {
   SetMenuCategory(mcRecordingEdit);
   recording = Recording;
   originalFileName = recording->FileName();
-  Recordings.StateChanged(recordingsState); // just to get the current state
   strn0cpy(folder, recording->Folder(), sizeof(folder));
   strn0cpy(name, recording->BaseName(), sizeof(name));
   priority = recording->Priority();
@@ -2390,13 +2607,15 @@
 
 bool cMenuRecordingEdit::RefreshRecording(void)
 {
-  if (Recordings.StateChanged(recordingsState)) {
-     if ((recording = Recordings.GetByName(originalFileName)) != NULL)
+  if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(recordingsStateKey)) {
+     if ((recording = Recordings->GetByName(originalFileName)) != NULL)
         Set();
      else {
+        recordingsStateKey.Remove();
         Skins.Message(mtWarning, tr("Recording vanished!"));
         return false;
         }
+     recordingsStateKey.Remove();
      }
   return true;
 }
@@ -2453,7 +2672,7 @@
 eOSState cMenuRecordingEdit::DeleteMarks(void)
 {
   if (buttonDeleteMarks && Interface->Confirm(tr("Delete editing marks for this recording?"))) {
-     if (recording->DeleteMarks())
+     if (cMarks::DeleteMarksFile(recording))
         SetHelpKeys();
      else
         Skins.Message(mtError, tr("Error while deleting editing marks!"));
@@ -2463,10 +2682,18 @@
 
 eOSState cMenuRecordingEdit::ApplyChanges(void)
 {
+  cStateKey StateKey;
+  cRecordings *Recordings = cRecordings::GetRecordingsWrite(StateKey);
+  cRecording *Recording = Recordings->GetByName(recording->FileName());
+  if (!Recording) {
+     Skins.Message(mtWarning, tr("Recording vanished!"));
+     return osBack;
+     }
   bool Modified = false;
   if (priority != recording->Priority() || lifetime != recording->Lifetime()) {
-     if (!recording->ChangePriorityLifetime(priority, lifetime)) {
+     if (!Recording->ChangePriorityLifetime(priority, lifetime)) {
         Skins.Message(mtError, tr("Error while changing priority/lifetime!"));
+        StateKey.Remove(Modified);
         return osContinue;
         }
      Modified = true;
@@ -2477,17 +2704,21 @@
      }
   cString NewName = *folder ? cString::sprintf("%s%c%s", folder, FOLDERDELIMCHAR, name) : name;
   NewName.CompactChars(FOLDERDELIMCHAR);
-  if (strcmp(NewName, recording->Name())) {
-     if (!recording->ChangeName(NewName)) {
+  if (strcmp(NewName, Recording->Name())) {
+     if (!Recording->ChangeName(NewName)) {
         Skins.Message(mtError, tr("Error while changing folder/name!"));
+        StateKey.Remove(Modified);
         return osContinue;
         }
      Modified = true;
      }
   if (Modified) {
-     cMenuRecordings::SetRecording(recording->FileName()); // makes sure the Recordings menu will reposition to the renamed recording
+     cMenuRecordings::SetRecording(Recording->FileName()); // makes sure the Recordings menu will reposition to the renamed recording
+     Recordings->TouchUpdate();
+     StateKey.Remove(Modified);
      return osUser1;
      }
+  StateKey.Remove(Modified);
   return osBack;
 }
 
@@ -2517,24 +2748,23 @@
 
 class cMenuRecording : public cOsdMenu {
 private:
-  cRecording *recording;
+  const cRecording *recording;
   cString originalFileName;
-  int recordingsState;
+  cStateKey recordingsStateKey;
   bool withButtons;
   bool RefreshRecording(void);
 public:
-  cMenuRecording(cRecording *Recording, bool WithButtons = false);
+  cMenuRecording(const cRecording *Recording, bool WithButtons = false);
   virtual void Display(void);
   virtual eOSState ProcessKey(eKeys Key);
 };
 
-cMenuRecording::cMenuRecording(cRecording *Recording, bool WithButtons)
+cMenuRecording::cMenuRecording(const cRecording *Recording, bool WithButtons)
 :cOsdMenu(tr("Recording info"))
 {
   SetMenuCategory(mcRecordingInfo);
   recording = Recording;
   originalFileName = recording->FileName();
-  Recordings.StateChanged(recordingsState); // just to get the current state
   withButtons = WithButtons;
   if (withButtons)
      SetHelp(tr("Button$Play"), tr("Button$Rewind"), NULL, tr("Button$Edit"));
@@ -2542,13 +2772,15 @@
 
 bool cMenuRecording::RefreshRecording(void)
 {
-  if (Recordings.StateChanged(recordingsState)) {
-     if ((recording = Recordings.GetByName(originalFileName)) != NULL)
+  if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(recordingsStateKey)) {
+     if ((recording = Recordings->GetByName(originalFileName)) != NULL)
         Display();
      else {
+        recordingsStateKey.Remove();
         Skins.Message(mtWarning, tr("Recording vanished!"));
         return false;
         }
+     recordingsStateKey.Remove();
      }
   return true;
 }
@@ -2611,23 +2843,23 @@
 
 class cMenuRecordingItem : public cOsdItem {
 private:
-  cRecording *recording;
+  const cRecording *recording;
   int level;
   char *name;
   int totalEntries, newEntries;
 public:
-  cMenuRecordingItem(cRecording *Recording, int Level);
+  cMenuRecordingItem(const cRecording *Recording, int Level);
   ~cMenuRecordingItem();
   void IncrementCounter(bool New);
   const char *Name(void) { return name; }
   int Level(void) { return level; }
-  cRecording *Recording(void) { return recording; }
+  const cRecording *Recording(void) { return recording; }
   bool IsDirectory(void) { return name != NULL; }
-  void SetRecording(cRecording *Recording) { recording = Recording; }
+  void SetRecording(const cRecording *Recording) { recording = Recording; }
   virtual void SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable);
   };
 
-cMenuRecordingItem::cMenuRecordingItem(cRecording *Recording, int Level)
+cMenuRecordingItem::cMenuRecordingItem(const cRecording *Recording, int Level)
 {
   recording = Recording;
   level = Level;
@@ -2669,7 +2901,6 @@
   base = Base ? strdup(Base) : NULL;
   level = Setup.RecordingDirs ? Level : -1;
   filter = Filter;
-  Recordings.StateChanged(recordingsState); // just to get the current state
   helpKeys = -1;
   Display(); // this keeps the higher level menus from showing up briefly when pressing 'Back' during replay
   Set();
@@ -2717,50 +2948,54 @@
 
 void cMenuRecordings::Set(bool Refresh)
 {
-  const char *CurrentRecording = *fileName ? *fileName : cReplayControl::LastReplayed();
-  cMenuRecordingItem *LastItem = NULL;
-  cThreadLock RecordingsLock(&Recordings);
-  if (Refresh) {
-     if (cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()))
-        CurrentRecording = ri->Recording()->FileName();
-     }
-  Clear();
-  GetRecordingsSortMode(DirectoryName());
-  Recordings.Sort();
-  for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) {
-      if ((!filter || filter->Filter(recording)) && (!base || (strstr(recording->Name(), base) == recording->Name() && recording->Name()[strlen(base)] == FOLDERDELIMCHAR))) {
-         cMenuRecordingItem *Item = new cMenuRecordingItem(recording, level);
-         cMenuRecordingItem *LastDir = NULL;
-         if (Item->IsDirectory()) {
-            // Sorting may ignore non-alphanumeric characters, so we need to explicitly handle directories in case they only differ in such characters:
-            for (cMenuRecordingItem *p = LastItem; p; p = dynamic_cast<cMenuRecordingItem *>(p->Prev())) {
-                if (p->Name() && strcmp(p->Name(), Item->Name()) == 0) {
-                   LastDir = p;
-                   break;
+  if (cRecordings::GetRecordingsRead(recordingsStateKey)) {
+     recordingsStateKey.Remove();
+     const char *CurrentRecording = *fileName ? *fileName : cReplayControl::LastReplayed();
+     cRecordings *Recordings = cRecordings::GetRecordingsWrite(recordingsStateKey); // write access is necessary for sorting!
+     cMenuRecordingItem *LastItem = NULL;
+     if (Refresh) {
+        if (cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()))
+           CurrentRecording = ri->Recording()->FileName();
+        }
+     Clear();
+     GetRecordingsSortMode(DirectoryName());
+     Recordings->Sort();
+     for (const cRecording *Recording = Recordings->First(); Recording; Recording = Recordings->Next(Recording)) {
+         if ((!filter || filter->Filter(Recording)) && (!base || (strstr(Recording->Name(), base) == Recording->Name() && Recording->Name()[strlen(base)] == FOLDERDELIMCHAR))) {
+            cMenuRecordingItem *Item = new cMenuRecordingItem(Recording, level);
+            cMenuRecordingItem *LastDir = NULL;
+            if (Item->IsDirectory()) {
+               // Sorting may ignore non-alphanumeric characters, so we need to explicitly handle directories in case they only differ in such characters:
+               for (cMenuRecordingItem *p = LastItem; p; p = dynamic_cast<cMenuRecordingItem *>(p->Prev())) {
+                   if (p->Name() && strcmp(p->Name(), Item->Name()) == 0) {
+                      LastDir = p;
+                      break;
+                      }
                    }
-                }
-            }
-         if (*Item->Text() && !LastDir) {
-            Add(Item);
-            LastItem = Item;
-            if (Item->IsDirectory())
-               LastDir = Item;
-            }
-         else
-            delete Item;
-         if (LastItem || LastDir) {
-            if (*path) {
-               if (strcmp(path, recording->Folder()) == 0)
+               }
+            if (*Item->Text() && !LastDir) {
+               Add(Item);
+               LastItem = Item;
+               if (Item->IsDirectory())
+                  LastDir = Item;
+               }
+            else
+               delete Item;
+            if (LastItem || LastDir) {
+               if (*path) {
+                  if (strcmp(path, Recording->Folder()) == 0)
+                     SetCurrent(LastDir ? LastDir : LastItem);
+                  }
+               else if (CurrentRecording && strcmp(CurrentRecording, Recording->FileName()) == 0)
                   SetCurrent(LastDir ? LastDir : LastItem);
                }
-            else if (CurrentRecording && strcmp(CurrentRecording, recording->FileName()) == 0)
-               SetCurrent(LastDir ? LastDir : LastItem);
+            if (LastDir)
+               LastDir->IncrementCounter(Recording->IsNew());
             }
-         if (LastDir)
-            LastDir->IncrementCounter(recording->IsNew());
          }
-      }
-  SetMenuSortMode(RecordingsSortMode == rsmName ? msmName : msmTime);
+     SetMenuSortMode(RecordingsSortMode == rsmName ? msmName : msmTime);
+     recordingsStateKey.Remove(false); // sorting doesn't count as a real modification
+     }
   if (Refresh)
      Display();
 }
@@ -2837,50 +3072,60 @@
   cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
   if (ri && !ri->IsDirectory()) {
      if (Interface->Confirm(tr("Delete recording?"))) {
-        cRecordControl *rc = cRecordControls::GetRecordControl(ri->Recording()->FileName());
-        if (rc) {
+        if (cRecordControl *rc = cRecordControls::GetRecordControl(ri->Recording()->FileName())) {
            if (Interface->Confirm(tr("Timer still recording - really delete?"))) {
-              cTimer *timer = rc->Timer();
-              if (timer) {
-                 timer->Skip();
-                 cRecordControls::Process(time(NULL));
-                 if (timer->IsSingleEvent()) {
-                    isyslog("deleting timer %s", *timer->ToDescr());
-                    Timers.Del(timer);
+              if (cTimer *Timer = rc->Timer()) {
+                 LOCK_TIMERS_WRITE;
+                 Timer->Skip();
+                 cRecordControls::Process(Timers, time(NULL));
+                 if (Timer->IsSingleEvent()) {
+                    Timers->Del(Timer);
+                    isyslog("deleted timer %s", *Timer->ToDescr());
                     }
-                 Timers.SetModified();
                  }
               }
            else
               return osContinue;
            }
-        cRecording *recording = ri->Recording();
-        cString FileName = recording->FileName();
+        cRecordings *Recordings = cRecordings::GetRecordingsWrite(recordingsStateKey);
+        Recordings->SetExplicitModify();
+        cRecording *Recording = Recordings->GetByName(ri->Recording()->FileName());
+        if (!Recording) {
+           Skins.Message(mtWarning, tr("Recording vanished!"));
+           recordingsStateKey.Remove();
+           return osContinue;
+           }
+        cString FileName = Recording->FileName();
         if (RecordingsHandler.GetUsage(FileName)) {
            if (Interface->Confirm(tr("Recording is being edited - really delete?"))) {
               RecordingsHandler.Del(FileName);
-              recording = Recordings.GetByName(FileName); // RecordingsHandler.Del() might have deleted it if it was the edited version
-              // we continue with the code below even if recording is NULL,
+              Recording = Recordings->GetByName(FileName); // RecordingsHandler.Del() might have deleted it if it was the edited version
+              // we continue with the code below even if Recording is NULL,
               // in order to have the menu updated etc.
               }
-           else
+           else {
+              recordingsStateKey.Remove();
               return osContinue;
+              }
            }
         if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), FileName) == 0)
            cControl::Shutdown();
-        if (!recording || recording->Delete()) {
+        if (!Recording || Recording->Delete()) {
            cReplayControl::ClearLastReplayed(FileName);
-           Recordings.DelByName(FileName);
+           Recordings->DelByName(FileName);
            cOsdMenu::Del(Current());
            SetHelpKeys();
            cVideoDiskUsage::ForceCheck();
            Display();
+           Recordings->SetModified();
+           recordingsStateKey.Remove();
            if (!Count())
               return osBack;
            return osUser2;
            }
         else
            Skins.Message(mtError, tr("Error while deleting recording!"));
+        recordingsStateKey.Remove();
         }
      }
   return osContinue;
@@ -2919,12 +3164,15 @@
   if (HasSubMenu())
      return osContinue;
   IncRecordingsSortMode(DirectoryName());
+  recordingsStateKey.Reset();
   Set(true);
   return osContinue;
 }
 
 eOSState cMenuRecordings::ProcessKey(eKeys Key)
 {
+  if (!HasSubMenu())
+     Set(); // react on any changes to the recordings list
   bool HadSubMenu = HasSubMenu();
   eOSState state = cOsdMenu::ProcessKey(Key);
 
@@ -2940,9 +3188,6 @@
        case kBlue:   return Info();
        case k0:      return Sort();
        case k1...k9: return Commands(Key);
-       case kNone:   if (Recordings.StateChanged(recordingsState))
-                        Set(true);
-                     break;
        default: break;
        }
      }
@@ -3006,6 +3251,7 @@
 class cMenuSetupOSD : public cMenuSetupBase {
 private:
   const char *useSmallFontTexts[3];
+  const char *recSortModeTexts[2];
   const char *keyColorTexts[4];
   int osdLanguageIndex;
   int numSkins;
@@ -3058,6 +3304,8 @@
   useSmallFontTexts[0] = tr("never");
   useSmallFontTexts[1] = tr("skin dependent");
   useSmallFontTexts[2] = tr("always");
+  recSortModeTexts[0] = tr("by name");
+  recSortModeTexts[1] = tr("by time");
   keyColorTexts[0] = tr("Key$Red");
   keyColorTexts[1] = tr("Key$Green");
   keyColorTexts[2] = tr("Key$Yellow");
@@ -3091,6 +3339,7 @@
   Add(new cMenuEditBoolItem(tr("Setup.OSD$Recording directories"),  &data.RecordingDirs));
   Add(new cMenuEditBoolItem(tr("Setup.OSD$Folders in timer menu"),  &data.FoldersInTimerMenu));
   Add(new cMenuEditBoolItem(tr("Setup.OSD$Always sort folders first"), &data.AlwaysSortFoldersFirst));
+  Add(new cMenuEditStraItem(tr("Setup.OSD$Default sort mode for recordings"), &data.DefaultSortModeRec, 2, recSortModeTexts));
   Add(new cMenuEditBoolItem(tr("Setup.OSD$Number keys for characters"), &data.NumberKeysForChars));
   Add(new cMenuEditStraItem(tr("Setup.OSD$Color key 0"),            &data.ColorKey0, 4, keyColorTexts));
   Add(new cMenuEditStraItem(tr("Setup.OSD$Color key 1"),            &data.ColorKey1, 4, keyColorTexts));
@@ -3132,8 +3381,10 @@
         ModifiedAppearance = true;
      if (strcmp(data.FontFix, Setup.FontFix) || !DoubleEqual(data.FontFixSizeP, Setup.FontFixSizeP))
         ModifiedAppearance = true;
-     if (data.AlwaysSortFoldersFirst != Setup.AlwaysSortFoldersFirst || data.RecordingDirs != Setup.RecordingDirs)
-        Recordings.ClearSortNames();
+     if (data.AlwaysSortFoldersFirst != Setup.AlwaysSortFoldersFirst || data.RecordingDirs != Setup.RecordingDirs) {
+        LOCK_RECORDINGS_WRITE;
+        Recordings->ClearSortNames();
+        }
      }
 
   int oldSkinIndex = skinIndex;
@@ -3507,6 +3758,7 @@
 
 bool cMenuSetupCAMItem::Changed(void)
 {
+  cString AssignedDevice("");
   const char *Activating = "";
   const char *CamName = camSlot->GetCamName();
   if (!CamName) {
@@ -3520,7 +3772,9 @@
   else if (camSlot->IsActivating())
      // TRANSLATORS: note the leading blank!
      Activating = tr(" (activating)");
-  cString buffer = cString::sprintf(" %d %s%s", camSlot->SlotNumber(), CamName, Activating);
+  if (cDevice *Device = camSlot->Device())
+     AssignedDevice = cString::sprintf(" %s %d", tr("@ device"), Device->CardIndex() + 1);
+  cString buffer = cString::sprintf(" %d %s%s%s", camSlot->SlotNumber(), CamName, *AssignedDevice, Activating);
   if (strcmp(buffer, Text()) != 0) {
      SetText(buffer);
      return true;
@@ -3607,7 +3861,8 @@
         CamSlot->CancelActivation();
      else if (CamSlot->CanActivate()) {
         if (CamSlot->Priority() < LIVEPRIORITY) { // don't interrupt recordings
-           if (cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel())) {
+           LOCK_CHANNELS_READ;
+           if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel())) {
               for (int i = 0; i < cDevice::NumDevices(); i++) {
                   if (cDevice *Device = cDevice::GetDevice(i)) {
                      if (Device->ProvidesChannel(Channel)) {
@@ -3672,6 +3927,7 @@
 
 class cMenuSetupRecord : public cMenuSetupBase {
 private:
+  const char *recordKeyHandlingTexts[3];
   const char *pauseKeyHandlingTexts[3];
   const char *delTimeshiftRecTexts[3];
 public:
@@ -3681,6 +3937,9 @@
 cMenuSetupRecord::cMenuSetupRecord(void)
 {
   SetMenuCategory(mcSetupRecord);
+  recordKeyHandlingTexts[0] = tr("no instant recording");
+  recordKeyHandlingTexts[1] = tr("confirm instant recording");
+  recordKeyHandlingTexts[2] = tr("record instantly");
   pauseKeyHandlingTexts[0] = tr("do not pause live video");
   pauseKeyHandlingTexts[1] = tr("confirm pause live video");
   pauseKeyHandlingTexts[2] = tr("pause live video");
@@ -3692,6 +3951,7 @@
   Add(new cMenuEditIntItem( tr("Setup.Recording$Margin at stop (min)"),      &data.MarginStop));
   Add(new cMenuEditIntItem( tr("Setup.Recording$Default priority"),          &data.DefaultPriority, 0, MAXPRIORITY));
   Add(new cMenuEditIntItem( tr("Setup.Recording$Default lifetime (d)"),      &data.DefaultLifetime, 0, MAXLIFETIME));
+  Add(new cMenuEditStraItem(tr("Setup.Recording$Record key handling"),       &data.RecordKeyHandling, 3, recordKeyHandlingTexts));
   Add(new cMenuEditStraItem(tr("Setup.Recording$Pause key handling"),        &data.PauseKeyHandling, 3, pauseKeyHandlingTexts));
   Add(new cMenuEditIntItem( tr("Setup.Recording$Pause priority"),            &data.PausePriority, 0, MAXPRIORITY));
   Add(new cMenuEditIntItem( tr("Setup.Recording$Pause lifetime (d)"),        &data.PauseLifetime, 0, MAXLIFETIME));
@@ -3738,25 +3998,47 @@
 
 void cMenuSetupReplay::Store(void)
 {
-  if (Setup.ResumeID != data.ResumeID)
-     Recordings.ResetResume();
+  if (Setup.ResumeID != data.ResumeID) {
+     LOCK_RECORDINGS_WRITE;
+     Recordings->ResetResume();
+     }
   cMenuSetupBase::Store();
 }
 
 // --- cMenuSetupMisc --------------------------------------------------------
 
 class cMenuSetupMisc : public cMenuSetupBase {
+private:
+  cStringList svdrpServerNames;
+  void Set(void);
 public:
   cMenuSetupMisc(void);
+  virtual eOSState ProcessKey(eKeys Key);
   };
 
 cMenuSetupMisc::cMenuSetupMisc(void)
 {
   SetMenuCategory(mcSetupMisc);
   SetSection(tr("Miscellaneous"));
+  Set();
+}
+
+void cMenuSetupMisc::Set(void)
+{
+  int current = Current();
+  Clear();
   Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Min. event timeout (min)"),   &data.MinEventTimeout));
   Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Min. user inactivity (min)"), &data.MinUserInactivity));
   Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$SVDRP timeout (s)"),          &data.SVDRPTimeout));
+  Add(new cMenuEditBoolItem(tr("Setup.Miscellaneous$SVDRP peering"),              &data.SVDRPPeering));
+  if (data.SVDRPPeering) {
+     Add(new cMenuEditStrItem( tr("Setup.Miscellaneous$SVDRP host name"), data.SVDRPHostName, sizeof(data.SVDRPHostName)));
+     if (GetSVDRPServerNames(&svdrpServerNames)) {
+        svdrpServerNames.Sort(true);
+        svdrpServerNames.Insert(strdup(""));
+        Add(new cMenuEditStrlItem(tr("Setup.Miscellaneous$SVDRP default host"), data.SVDRPDefaultHost, sizeof(data.SVDRPDefaultHost), &svdrpServerNames));
+        }
+     }
   Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Zap timeout (s)"),            &data.ZapTimeout));
   Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Channel entry timeout (ms)"), &data.ChannelEntryTimeout, 0));
   Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Remote control repeat delay (ms)"), &data.RcRepeatDelay, 0));
@@ -3768,6 +4050,33 @@
   Add(new cMenuEditBoolItem(tr("Setup.Miscellaneous$Channels wrap"),              &data.ChannelsWrap));
   Add(new cMenuEditBoolItem(tr("Setup.Miscellaneous$Show channel names with source"), &data.ShowChannelNamesWithSource));
   Add(new cMenuEditBoolItem(tr("Setup.Miscellaneous$Emergency exit"),             &data.EmergencyExit));
+  SetCurrent(Get(current));
+  Display();
+}
+
+eOSState cMenuSetupMisc::ProcessKey(eKeys Key)
+{
+  bool OldSVDRPPeering = data.SVDRPPeering;
+  bool ModifiedSVDRPSettings = false;
+  if (Key == kOk)
+     ModifiedSVDRPSettings = data.SVDRPPeering != Setup.SVDRPPeering | strcmp(data.SVDRPHostName, Setup.SVDRPHostName);
+  eOSState state = cMenuSetupBase::ProcessKey(Key);
+  if (data.SVDRPPeering != OldSVDRPPeering)
+     Set();
+  if (ModifiedSVDRPSettings) {
+     StopSVDRPClientHandler();
+     StopSVDRPServerHandler();
+     StartSVDRPServerHandler();
+     if (data.SVDRPPeering)
+        StartSVDRPClientHandler();
+     else {
+        LOCK_TIMERS_WRITE;
+        Timers->SetExplicitModify();
+        if (Timers->DelRemoteTimers())
+           Timers->SetModified();
+        }
+     }
+  return state;
 }
 
 // --- cMenuSetupPluginItem --------------------------------------------------
@@ -4018,7 +4327,7 @@
         stopReplayItem = NULL;
         }
      // Color buttons:
-     SetHelp(!replaying ? tr("Button$Record") : NULL, tr("Button$Audio"), replaying || !Setup.PauseKeyHandling ? NULL : tr("Button$Pause"), replaying ? tr("Button$Stop") : cReplayControl::LastReplayed() ? tr("Button$Resume") : tr("Button$Play"));
+     SetHelp(!replaying && Setup.RecordKeyHandling ? tr("Button$Record") : NULL, tr("Button$Audio"), replaying || !Setup.PauseKeyHandling ? NULL : tr("Button$Pause"), replaying ? tr("Button$Stop") : cReplayControl::LastReplayed() ? tr("Button$Resume") : tr("Button$Play"));
      result = true;
      }
 
@@ -4071,8 +4380,7 @@
     case osSetup:      return AddSubMenu(new cMenuSetup);
     case osCommands:   return AddSubMenu(new cMenuCommands(tr("Commands"), &Commands));
     case osStopRecord: if (Interface->Confirm(tr("Stop recording?"))) {
-                          cOsdItem *item = Get(Current());
-                          if (item) {
+                          if (cOsdItem *item = Get(Current())) {
                              cRecordControls::Stop(item->Text() + strlen(tr(STOP_RECORDING)));
                              return osEnd;
                              }
@@ -4105,7 +4413,7 @@
     default: switch (Key) {
                case kRecord:
                case kRed:    if (!HadSubMenu)
-                                state = replaying ? osContinue : osRecord;
+                                state = replaying || !Setup.RecordKeyHandling ? osContinue : osRecord;
                              break;
                case kGreen:  if (!HadSubMenu) {
                                 cRemote::Put(kAudio, true);
@@ -4139,25 +4447,20 @@
 {
   cDevice::PrimaryDevice()->ClrAvailableTracks(true);
   const cComponents *Components = NULL;
-  cSchedulesLock SchedulesLock;
   if (LiveChannel) {
-     cChannel *Channel = Channels.GetByNumber(LiveChannel);
-     if (Channel) {
-        const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
-        if (Schedules) {
-           const cSchedule *Schedule = Schedules->GetSchedule(Channel);
-           if (Schedule) {
-              const cEvent *Present = Schedule->GetPresentEvent();
-              if (Present)
-                 Components = Present->Components();
-              }
+     LOCK_CHANNELS_READ;
+     if (const cChannel *Channel = Channels->GetByNumber(LiveChannel)) {
+        LOCK_SCHEDULES_READ;
+        if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) {
+           const cEvent *Present = Schedule->GetPresentEvent();
+           if (Present)
+              Components = Present->Components();
            }
         }
      }
   else if (cReplayControl::NowReplaying()) {
-     cThreadLock RecordingsLock(&Recordings);
-     cRecording *Recording = Recordings.GetByName(cReplayControl::NowReplaying());
-     if (Recording)
+     LOCK_RECORDINGS_READ;
+     if (const cRecording *Recording = Recordings->GetByName(cReplayControl::NowReplaying()))
         Components = Recording->Info()->Components();
      }
   if (Components) {
@@ -4197,7 +4500,9 @@
   timeout = Switched || Setup.TimeoutRequChInfo;
   cOsdProvider::OsdSizeChanged(osdState); // just to get the current state
   positioner = NULL;
-  channel = Channels.GetByNumber(Number);
+  channel = NULL;
+  LOCK_CHANNELS_READ;
+  channel = Channels->GetByNumber(Number);
   lastPresent = lastFollowing = NULL;
   if (channel) {
      DisplayChannel();
@@ -4219,7 +4524,9 @@
   withInfo = Setup.ShowInfoOnChSwitch;
   displayChannel = Skins.Current()->DisplayChannel(withInfo);
   positioner = NULL;
-  channel = Channels.GetByNumber(cDevice::CurrentChannel());
+  channel = NULL;
+  LOCK_CHANNELS_READ;
+  channel = Channels->GetByNumber(cDevice::CurrentChannel());
   ProcessKey(FirstKey);
 }
 
@@ -4240,20 +4547,16 @@
 void cDisplayChannel::DisplayInfo(void)
 {
   if (withInfo && channel) {
-     cSchedulesLock SchedulesLock;
-     const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
-     if (Schedules) {
-        const cSchedule *Schedule = Schedules->GetSchedule(channel);
-        if (Schedule) {
-           const cEvent *Present = Schedule->GetPresentEvent();
-           const cEvent *Following = Schedule->GetFollowingEvent();
-           if (Present != lastPresent || Following != lastFollowing) {
-              SetTrackDescriptions(channel->Number());
-              displayChannel->SetEvents(Present, Following);
-              cStatus::MsgOsdProgramme(Present ? Present->StartTime() : 0, Present ? Present->Title() : NULL, Present ? Present->ShortText() : NULL, Following ? Following->StartTime() : 0, Following ? Following->Title() : NULL, Following ? Following->ShortText() : NULL);
-              lastPresent = Present;
-              lastFollowing = Following;
-              }
+     LOCK_SCHEDULES_READ;
+     if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) {
+        const cEvent *Present = Schedule->GetPresentEvent();
+        const cEvent *Following = Schedule->GetFollowingEvent();
+        if (Present != lastPresent || Following != lastFollowing) {
+           SetTrackDescriptions(channel->Number());
+           displayChannel->SetEvents(Present, Following);
+           cStatus::MsgOsdProgramme(Present ? Present->StartTime() : 0, Present ? Present->Title() : NULL, Present ? Present->ShortText() : NULL, Following ? Following->StartTime() : 0, Following ? Following->Title() : NULL, Following ? Following->ShortText() : NULL);
+           lastPresent = Present;
+           lastFollowing = Following;
            }
         }
      }
@@ -4265,13 +4568,14 @@
   displayChannel->SetEvents(NULL, NULL);
 }
 
-cChannel *cDisplayChannel::NextAvailableChannel(cChannel *Channel, int Direction)
+const cChannel *cDisplayChannel::NextAvailableChannel(const cChannel *Channel, int Direction)
 {
   if (Direction) {
+     LOCK_CHANNELS_READ;
      while (Channel) {
-           Channel = Direction > 0 ? Channels.Next(Channel) : Channels.Prev(Channel);
+           Channel = Direction > 0 ? Channels->Next(Channel) : Channels->Prev(Channel);
            if (!Channel && Setup.ChannelsWrap)
-              Channel = Direction > 0 ? Channels.First() : Channels.Last();
+              Channel = Direction > 0 ? Channels->First() : Channels->Last();
            if (Channel && !Channel->GroupSep() && cDevice::GetDevice(Channel, LIVEPRIORITY, true, true))
               return Channel;
            }
@@ -4285,7 +4589,7 @@
      delete displayChannel;
      displayChannel = Skins.Current()->DisplayChannel(withInfo);
      }
-  cChannel *NewChannel = NULL;
+  const cChannel *NewChannel = NULL;
   if (Key != kNone)
      lastTime.Set();
   switch (int(Key)) {
@@ -4298,18 +4602,19 @@
     case k1 ... k9:
          group = -1;
          if (number >= 0) {
-            if (number > Channels.MaxNumber())
+            if (number > cChannels::MaxNumber())
                number = Key - k0;
             else
                number = number * 10 + Key - k0;
-            channel = Channels.GetByNumber(number);
+            LOCK_CHANNELS_READ
+            channel = Channels->GetByNumber(number);
             Refresh();
             withInfo = false;
             // Lets see if there can be any useful further input:
             int n = channel ? number * 10 : 0;
             int m = 10;
-            cChannel *ch = channel;
-            while (ch && (ch = Channels.Next(ch)) != NULL) {
+            const cChannel *ch = channel;
+            while (ch && (ch = Channels->Next(ch)) != NULL) {
                   if (!ch->GroupSep()) {
                      if (n <= ch->Number() && ch->Number() < n + m) {
                         n = 0;
@@ -4337,23 +4642,23 @@
     case kNext|k_Repeat:
     case kNext:
     case kPrev|k_Repeat:
-    case kPrev:
+    case kPrev: {
          withInfo = false;
          number = 0;
+         LOCK_CHANNELS_READ;
          if (group < 0) {
-            cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());
-            if (channel)
-               group = channel->Index();
+            if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel()))
+               group = Channel->Index();
             }
          if (group >= 0) {
             int SaveGroup = group;
             if (NORMALKEY(Key) == kRight || NORMALKEY(Key) == kNext)
-               group = Channels.GetNextGroup(group) ;
+               group = Channels->GetNextGroup(group) ;
             else
-               group = Channels.GetPrevGroup(group < 1 ? 1 : group);
+               group = Channels->GetPrevGroup(group < 1 ? 1 : group);
             if (group < 0)
                group = SaveGroup;
-            channel = Channels.Get(group);
+            channel = Channels->Get(group);
             if (channel) {
                Refresh();
                if (!channel->GroupSep())
@@ -4361,6 +4666,7 @@
                }
             }
          break;
+         }
     case kUp|k_Repeat:
     case kUp:
     case kDown|k_Repeat:
@@ -4370,9 +4676,8 @@
     case kChanDn|k_Repeat:
     case kChanDn: {
          eKeys k = NORMALKEY(Key);
-         cChannel *ch = NextAvailableChannel(channel, (k == kUp || k == kChanUp) ? 1 : -1);
-         if (ch)
-            channel = ch;
+         if (const cChannel *Channel = NextAvailableChannel(channel, (k == kUp || k == kChanUp) ? 1 : -1))
+            channel = Channel;
          else if (channel && channel->Number() != cDevice::CurrentChannel())
             Key = k; // immediately switches channel when hitting the beginning/end of the channel list with k_Repeat
          }
@@ -4392,7 +4697,8 @@
          break;
     case kNone:
          if (number && Setup.ChannelEntryTimeout && int(lastTime.Elapsed()) > Setup.ChannelEntryTimeout) {
-            channel = Channels.GetByNumber(number);
+            LOCK_CHANNELS_READ;
+            channel = Channels->GetByNumber(number);
             if (channel)
                NewChannel = channel;
             withInfo = true;
@@ -4404,9 +4710,10 @@
     //TODO
     //XXX case kGreen:  return osEventNow;
     //XXX case kYellow: return osEventNext;
-    case kOk:
+    case kOk: {
+         LOCK_CHANNELS_READ;
          if (group >= 0) {
-            channel = Channels.Get(Channels.GetNextNormal(group));
+            channel = Channels->Get(Channels->GetNextNormal(group));
             if (channel)
                NewChannel = channel;
             withInfo = true;
@@ -4414,15 +4721,17 @@
             Refresh();
             }
          else if (number > 0) {
-            channel = Channels.GetByNumber(number);
+            channel = Channels->GetByNumber(number);
             if (channel)
                NewChannel = channel;
             withInfo = true;
             number = 0;
             Refresh();
             }
-         else
+         else {
             return osEnd;
+            }
+         }
          break;
     default:
          if ((Key & (k_Repeat | k_Release)) == 0) {
@@ -4431,16 +4740,17 @@
             }
     };
   if (positioner || !timeout || lastTime.Elapsed() < (uint64_t)(Setup.ChannelInfoTime * 1000)) {
+     LOCK_CHANNELS_READ;
      if (Key == kNone && !number && group < 0 && !NewChannel && channel && channel->Number() != cDevice::CurrentChannel()) {
         // makes sure a channel switch through the SVDRP CHAN command is displayed
-        channel = Channels.GetByNumber(cDevice::CurrentChannel());
+        channel = Channels->GetByNumber(cDevice::CurrentChannel());
         Refresh();
         lastTime.Set();
         }
      DisplayInfo();
      if (NewChannel) {
         SetTrackDescriptions(NewChannel->Number()); // to make them immediately visible in the channel display
-        Channels.SwitchTo(NewChannel->Number());
+        Channels->SwitchTo(NewChannel->Number());
         SetTrackDescriptions(NewChannel->Number()); // switching the channel has cleared them
         channel = NewChannel;
         }
@@ -4751,14 +5061,13 @@
 
 // --- cRecordControl --------------------------------------------------------
 
-cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause)
+cRecordControl::cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause)
 {
   // Whatever happens here, the timers will be modified in some way...
-  Timers.SetModified();
-  // We're going to manipulate an event here, so we need to prevent
+  Timers->SetModified();
+  // We're going to work with an event here, so we need to prevent
   // others from modifying any EPG data:
-  cSchedulesLock SchedulesLock;
-  cSchedules::Schedules(SchedulesLock);
+  LOCK_SCHEDULES_READ;
 
   event = NULL;
   fileName = NULL;
@@ -4768,7 +5077,7 @@
   timer = Timer;
   if (!timer) {
      timer = new cTimer(true, Pause);
-     Timers.Add(timer);
+     Timers->Add(timer);
      instantId = cString::sprintf(cDevice::NumDevices() > 1 ? "%s - %d" : "%s", timer->Channel()->Name(), device->CardIndex() + 1);
      }
   timer->SetPending(true);
@@ -4789,7 +5098,7 @@
         timer->OnOff();
         }
      else {
-        Timers.Del(timer);
+        Timers->Del(timer);
         if (!cReplayControl::LastReplayed()) // an instant recording, maybe from cRecordControls::PauseLiveVideo()
            cReplayControl::SetRecording(fileName);
         }
@@ -4807,21 +5116,8 @@
         cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true);
         if (!Timer && !cReplayControl::LastReplayed()) // an instant recording, maybe from cRecordControls::PauseLiveVideo()
            cReplayControl::SetRecording(fileName);
-        Recordings.AddByName(fileName);
-        if (Timer && !Timer->IsSingleEvent()) {
-           char *Directory = strdup(fileName);
-           // going up two directory levels to get the series folder
-           if (char *p = strrchr(Directory, '/')) {
-              while (p > Directory && *--p != '/')
-                    ;
-              *p = 0;
-              if (!HasRecordingsSortMode(Directory)) {
-                 dsyslog("setting %s to be sorted by time", Directory);
-                 SetRecordingsSortMode(Directory, rsmTime);
-                 }
-              }
-           free(Directory);
-           }
+        LOCK_RECORDINGS_WRITE;
+        Recordings->AddByName(fileName);
         return;
         }
      else
@@ -4830,7 +5126,7 @@
   else
      timer->SetDeferred(DEFERTIMER);
   if (!Timer) {
-     Timers.Del(timer);
+     Timers->Del(timer);
      timer = NULL;
      }
 }
@@ -4845,21 +5141,17 @@
 
 bool cRecordControl::GetEvent(void)
 {
-  const cChannel *channel = timer->Channel();
+  const cChannel *Channel = timer->Channel();
   time_t Time = timer->HasFlags(tfInstant) ? timer->StartTime() + INSTANT_REC_EPG_LOOKAHEAD : timer->StartTime() + (timer->StopTime() - timer->StartTime()) / 2;
   for (int seconds = 0; seconds <= MAXWAIT4EPGINFO; seconds++) {
       {
-        cSchedulesLock SchedulesLock;
-        const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
-        if (Schedules) {
-           const cSchedule *Schedule = Schedules->GetSchedule(channel);
-           if (Schedule) {
-              event = Schedule->GetEventAround(Time);
-              if (event) {
-                 if (seconds > 0)
-                    dsyslog("got EPG info after %d seconds", seconds);
-                 return true;
-                 }
+        LOCK_SCHEDULES_READ;
+        if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) {
+           event = Schedule->GetEventAround(Time);
+           if (event) {
+              if (seconds > 0)
+                 dsyslog("got EPG info after %d seconds", seconds);
+              return true;
               }
            }
       }
@@ -4880,7 +5172,6 @@
      cStatus::MsgRecording(device, NULL, fileName, false);
      if (ExecuteUserCommand)
         cRecordingUserCommand::InvokeCommand(RUC_AFTERRECORDING, fileName);
-     Timers.SetModified();
      }
 }
 
@@ -4900,7 +5191,7 @@
 cRecordControl *cRecordControls::RecordControls[MAXRECORDCONTROLS] = { NULL };
 int cRecordControls::state = 0;
 
-bool cRecordControls::Start(cTimer *Timer, bool Pause)
+bool cRecordControls::Start(cTimers *Timers, cTimer *Timer, bool Pause)
 {
   static time_t LastNoDiskSpaceMessage = 0;
   int FreeMB = 0;
@@ -4920,29 +5211,28 @@
   LastNoDiskSpaceMessage = 0;
 
   ChangeState();
+  LOCK_CHANNELS_READ;
   int ch = Timer ? Timer->Channel()->Number() : cDevice::CurrentChannel();
-  cChannel *channel = Channels.GetByNumber(ch);
-
-  if (channel) {
+  if (const cChannel *Channel = Channels->GetByNumber(ch)) {
      int Priority = Timer ? Timer->Priority() : Pause ? Setup.PausePriority : Setup.DefaultPriority;
-     cDevice *device = cDevice::GetDevice(channel, Priority, false);
+     cDevice *device = cDevice::GetDevice(Channel, Priority, false);
      if (device) {
-        dsyslog("switching device %d to channel %d (%s)", device->DeviceNumber() + 1, channel->Number(), channel->Name());
-        if (!device->SwitchChannel(channel, false)) {
+        dsyslog("switching device %d to channel %d (%s)", device->DeviceNumber() + 1, Channel->Number(), Channel->Name());
+        if (!device->SwitchChannel(Channel, false)) {
            ShutdownHandler.RequestEmergencyExit();
            return false;
            }
         if (!Timer || Timer->Matches()) {
            for (int i = 0; i < MAXRECORDCONTROLS; i++) {
                if (!RecordControls[i]) {
-                  RecordControls[i] = new cRecordControl(device, Timer, Pause);
+                  RecordControls[i] = new cRecordControl(device, Timers, Timer, Pause);
                   return RecordControls[i]->Process(time(NULL));
                   }
                }
            }
         }
      else if (!Timer || !Timer->Pending()) {
-        isyslog("no free DVB device to record channel %d (%s)!", ch, channel->Name());
+        isyslog("no free DVB device to record channel %d (%s)!", ch, Channel->Name());
         Skins.Message(mtError, tr("No free DVB device to record!"));
         }
      }
@@ -4951,19 +5241,25 @@
   return false;
 }
 
+bool cRecordControls::Start(bool Pause)
+{
+  LOCK_TIMERS_WRITE;
+  return Start(Timers, NULL, Pause);
+}
+
 void cRecordControls::Stop(const char *InstantId)
 {
+  LOCK_TIMERS_WRITE;
   ChangeState();
   for (int i = 0; i < MAXRECORDCONTROLS; i++) {
       if (RecordControls[i]) {
          const char *id = RecordControls[i]->InstantId();
          if (id && strcmp(id, InstantId) == 0) {
-            cTimer *timer = RecordControls[i]->Timer();
+            cTimer *Timer = RecordControls[i]->Timer();
             RecordControls[i]->Stop();
-            if (timer) {
-               isyslog("deleting timer %s", *timer->ToDescr());
-               Timers.Del(timer);
-               Timers.SetModified();
+            if (Timer) {
+               Timers->Del(Timer);
+               isyslog("deleted timer %s", *Timer->ToDescr());
                }
             break;
             }
@@ -4971,11 +5267,24 @@
       }
 }
 
+void cRecordControls::Stop(cTimer *Timer)
+{
+  for (int i = 0; i < MAXRECORDCONTROLS; i++) {
+      if (RecordControls[i]) {
+         if (RecordControls[i]->Timer() == Timer) {
+            DELETENULL(RecordControls[i]);
+            ChangeState();
+            break;
+            }
+         }
+      }
+}
+
 bool cRecordControls::PauseLiveVideo(void)
 {
   Skins.Message(mtStatus, tr("Pausing live video..."));
   cReplayControl::SetRecording(NULL); // make sure the new cRecordControl will set cReplayControl::LastReplayed()
-  if (Start(NULL, true)) {
+  if (Start(true)) {
      cReplayControl *rc = new cReplayControl(true);
      cControl::Launch(rc);
      cControl::Attach();
@@ -5019,19 +5328,22 @@
   return NULL;
 }
 
-void cRecordControls::Process(time_t t)
+bool cRecordControls::Process(cTimers *Timers, time_t t)
 {
+  bool Result = false;
   for (int i = 0; i < MAXRECORDCONTROLS; i++) {
       if (RecordControls[i]) {
          if (!RecordControls[i]->Process(t)) {
             DELETENULL(RecordControls[i]);
             ChangeState();
+            Result = true;
             }
          }
       }
+  return Result;
 }
 
-void cRecordControls::ChannelDataModified(cChannel *Channel)
+void cRecordControls::ChannelDataModified(const cChannel *Channel)
 {
   for (int i = 0; i < MAXRECORDCONTROLS; i++) {
       if (RecordControls[i]) {
@@ -5153,19 +5465,25 @@
      if (rc && rc->InstantId()) {
         if (Active()) {
            if (Setup.DelTimeshiftRec == 2 || Interface->Confirm(tr("Delete timeshift recording?"))) {
-              cTimer *timer = rc->Timer();
-              rc->Stop(false); // don't execute user command
-              if (timer) {
-                 isyslog("deleting timer %s", *timer->ToDescr());
-                 Timers.Del(timer);
-                 Timers.SetModified();
-                 }
+              {
+                LOCK_TIMERS_WRITE;
+                Timers->SetExplicitModify();
+                cTimer *Timer = rc->Timer();
+                rc->Stop(false); // don't execute user command
+                if (Timer) {
+                   Timers->Del(Timer);
+                   Timers->SetModified();
+                   isyslog("deleted timer %s", *Timer->ToDescr());
+                   }
+              }
               cDvbPlayerControl::Stop();
-              cRecording *recording = Recordings.GetByName(fileName);
-              if (recording) {
-                 if (recording->Delete()) {
-                    Recordings.DelByName(fileName);
+              LOCK_RECORDINGS_WRITE;
+              Recordings->SetExplicitModify();
+              if (cRecording *Recording = Recordings->GetByName(fileName)) {
+                 if (Recording->Delete()) {
+                    Recordings->DelByName(fileName);
                     ClearLastReplayed(fileName);
+                    Recordings->SetModified();
                     }
                  else
                     Skins.Message(mtError, tr("Error while deleting recording!"));
@@ -5191,7 +5509,8 @@
 
 const char *cReplayControl::LastReplayed(void)
 {
-  if (!Recordings.GetByName(fileName))
+  LOCK_RECORDINGS_READ;
+  if (!Recordings->GetByName(fileName))
      fileName = NULL;
   return fileName;
 }
@@ -5276,7 +5595,8 @@
         }
      if (Initial) {
         if (*fileName) {
-           if (cRecording *Recording = Recordings.GetByName(fileName))
+           LOCK_RECORDINGS_READ;
+           if (const cRecording *Recording = Recordings->GetByName(fileName))
               displayReplay->SetRecording(Recording);
            }
         lastCurrent = lastTotal = -1;
@@ -5399,15 +5719,12 @@
   int Current, Total;
   if (GetIndex(Current, Total, true)) {
      lastCurrent = -1; // triggers redisplay
-     if (cMark *m = marks.Get(Current)) {
-        marks.Lock();
+     cStateKey StateKey;
+     marks.Lock(StateKey);
+     if (cMark *m = marks.Get(Current))
         marks.Del(m);
-        marks.Unlock();
-        }
      else {
-        marks.Lock();
         marks.Add(Current);
-        marks.Unlock();
         bool Play, Forward;
         int Speed;
         if (Setup.PauseOnMarkSet || GetReplayMode(Play, Forward, Speed) && !Play) {
@@ -5415,6 +5732,7 @@
            displayFrames = true;
            }
         }
+     StateKey.Remove();
      ShowTimed(2);
      marksModified = true;
      }
@@ -5522,15 +5840,16 @@
 
 cOsdObject *cReplayControl::GetInfo(void)
 {
-  cRecording *Recording = Recordings.GetByName(cReplayControl::LastReplayed());
-  if (Recording)
+  LOCK_RECORDINGS_READ;
+  if (const cRecording *Recording = Recordings->GetByName(cReplayControl::LastReplayed()))
      return new cMenuRecording(Recording, false);
   return NULL;
 }
 
 const cRecording *cReplayControl::GetRecording(void)
 {
-  if (const cRecording *Recording = Recordings.GetByName(LastReplayed()))
+  LOCK_RECORDINGS_READ;
+  if (const cRecording *Recording = Recordings->GetByName(LastReplayed()))
      return Recording;
   return NULL;
 }
diff -ruN vdr-2.2.0/menu.h vdr-2.3.1/menu.h
--- vdr-2.2.0/menu.h	2015-02-06 10:47:30.000000000 +0100
+++ vdr-2.3.1/menu.h	2015-09-13 16:17:56.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: menu.h 3.8 2015/02/06 09:47:30 kls Exp $
+ * $Id: menu.h 4.4 2015/09/13 14:17:56 kls Exp $
  */
 
 #ifndef __MENU_H
@@ -72,10 +72,13 @@
 
 class cMenuEditTimer : public cOsdMenu {
 private:
+  static const cTimer *addedTimer;
   cTimer *timer;
   cTimer data;
   int channel;
   bool addIfConfirmed;
+  cStringList svdrpServerNames;
+  char remote[HOST_NAME_MAX];
   cMenuEditStrItem *file;
   cMenuEditDateItem *day;
   cMenuEditDateItem *firstday;
@@ -86,13 +89,14 @@
   cMenuEditTimer(cTimer *Timer, bool New = false);
   virtual ~cMenuEditTimer();
   virtual eOSState ProcessKey(eKeys Key);
+  static const cTimer *AddedTimer(void);
   };
 
 class cMenuEvent : public cOsdMenu {
 private:
   const cEvent *event;
 public:
-  cMenuEvent(const cEvent *Event, bool CanSwitch = false, bool Buttons = false);
+  cMenuEvent(const cTimers *Timers, const cChannels *Channels, const cEvent *Event, bool CanSwitch = false, bool Buttons = false);
   virtual void Display(void);
   virtual eOSState ProcessKey(eKeys Key);
   };
@@ -123,14 +127,14 @@
   bool timeout;
   int osdState;
   const cPositioner *positioner;
-  cChannel *channel;
+  const cChannel *channel;
   const cEvent *lastPresent;
   const cEvent *lastFollowing;
   static cDisplayChannel *currentDisplayChannel;
   void DisplayChannel(void);
   void DisplayInfo(void);
   void Refresh(void);
-  cChannel *NextAvailableChannel(cChannel *Channel, int Direction);
+  const cChannel *NextAvailableChannel(const cChannel *Channel, int Direction);
 public:
   cDisplayChannel(int Number, bool Switched);
   cDisplayChannel(eKeys FirstKey);
@@ -205,7 +209,7 @@
 private:
   char *base;
   int level;
-  int recordingsState;
+  cStateKey recordingsStateKey;
   int helpKeys;
   const cRecordingFilter *filter;
   static cString path;
@@ -239,7 +243,7 @@
   char *fileName;
   bool GetEvent(void);
 public:
-  cRecordControl(cDevice *Device, cTimer *Timer = NULL, bool Pause = false);
+  cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer = NULL, bool Pause = false);
   virtual ~cRecordControl();
   bool Process(time_t t);
   cDevice *Device(void) { return device; }
@@ -254,16 +258,18 @@
   static cRecordControl *RecordControls[];
   static int state;
 public:
-  static bool Start(cTimer *Timer = NULL, bool Pause = false);
+  static bool Start(cTimers *Timers, cTimer *Timer, bool Pause = false);
+  static bool Start(bool Pause = false);
   static void Stop(const char *InstantId);
+  static void Stop(cTimer *Timer);
   static bool PauseLiveVideo(void);
   static const char *GetInstantId(const char *LastInstantId);
   static cRecordControl *GetRecordControl(const char *FileName);
   static cRecordControl *GetRecordControl(const cTimer *Timer);
          ///< Returns the cRecordControl for the given Timer.
          ///< If there is no cRecordControl for Timer, NULL is returned.
-  static void Process(time_t t);
-  static void ChannelDataModified(cChannel *Channel);
+  static bool Process(cTimers *Timers, time_t t);
+  static void ChannelDataModified(const cChannel *Channel);
   static bool Active(void);
   static void Shutdown(void);
   static void ChangeState(void) { state++; }
diff -ruN vdr-2.2.0/menuitems.c vdr-2.3.1/menuitems.c
--- vdr-2.2.0/menuitems.c	2015-02-09 12:53:10.000000000 +0100
+++ vdr-2.3.1/menuitems.c	2015-09-08 12:25:23.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: menuitems.c 3.3 2015/02/09 11:53:10 kls Exp $
+ * $Id: menuitems.c 4.2 2015/09/08 10:25:23 kls Exp $
  */
 
 #include "menuitems.h"
@@ -774,10 +774,30 @@
   SetValue(strings[*value]);
 }
 
+// --- cMenuEditStrlItem -----------------------------------------------------
+
+cMenuEditStrlItem::cMenuEditStrlItem(const char *Name, char *Value, int Length, const cStringList *Strings)
+:cMenuEditIntItem(Name, &index, 0, Strings->Size() - 1)
+{
+  strings = Strings;
+  value = Value;
+  length = Length;
+  index = strings->Find(value);
+  if (index < 0)
+     index = 0;
+  Set();
+}
+
+void cMenuEditStrlItem::Set(void)
+{
+  strn0cpy(value, strings->At(index), length);
+  SetValue(value);
+}
+
 // --- cMenuEditChanItem -----------------------------------------------------
 
 cMenuEditChanItem::cMenuEditChanItem(const char *Name, int *Value, const char *NoneString)
-:cMenuEditIntItem(Name, Value, NoneString ? 0 : 1, Channels.MaxNumber())
+:cMenuEditIntItem(Name, Value, NoneString ? 0 : 1, cChannels::MaxNumber())
 {
   channelID = NULL;
   noneString = NoneString;
@@ -786,12 +806,13 @@
 }
 
 cMenuEditChanItem::cMenuEditChanItem(const char *Name, cString *ChannelID, const char *NoneString)
-:cMenuEditIntItem(Name, &dummyValue, NoneString ? 0 : 1, Channels.MaxNumber())
+:cMenuEditIntItem(Name, &dummyValue, NoneString ? 0 : 1, cChannels::MaxNumber())
 {
   channelID = ChannelID;
   noneString = NoneString;
-  cChannel *channel = Channels.GetByChannelID(tChannelID::FromString(*ChannelID));
-  dummyValue = channel ? channel->Number() : 0;
+  LOCK_CHANNELS_READ;
+  const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(*ChannelID));
+  dummyValue = Channel ? Channel->Number() : 0;
   Set();
 }
 
@@ -799,11 +820,12 @@
 {
   if (*value > 0) {
      char buf[255];
-     cChannel *channel = Channels.GetByNumber(*value);
-     snprintf(buf, sizeof(buf), "%d %s", *value, channel ? channel->Name() : "");
+     LOCK_CHANNELS_READ;
+     const cChannel *Channel = Channels->GetByNumber(*value);
+     snprintf(buf, sizeof(buf), "%d %s", *value, Channel ? Channel->Name() : "");
      SetValue(buf);
      if (channelID)
-        *channelID = channel ? channel->GetChannelID().ToString() : "";
+        *channelID = Channel ? Channel->GetChannelID().ToString() : "";
      }
   else if (noneString) {
      SetValue(noneString);
@@ -822,13 +844,14 @@
     case kRight|k_Repeat:
     case kRight:
                  {
-                   cChannel *channel = Channels.GetByNumber(*value + delta, delta);
-                   if (channel)
-                      *value = channel->Number();
+                   LOCK_CHANNELS_READ
+                   const cChannel *Channel = Channels->GetByNumber(*value + delta, delta);
+                   if (Channel)
+                      *value = Channel->Number();
                    else if (delta < 0 && noneString)
                       *value = 0;
                    if (channelID)
-                      *channelID = channel ? channel->GetChannelID().ToString() : "";
+                      *channelID = Channel ? Channel->GetChannelID().ToString() : "";
                    Set();
                  }
                  break;
@@ -845,13 +868,14 @@
   number = 0;
   source = Source;
   transponder = Value;
-  cChannel *channel = Channels.First();
-  while (channel) {
-        if (!channel->GroupSep() && *source == channel->Source() && ISTRANSPONDER(channel->Transponder(), *Value)) {
-           number = channel->Number();
+  LOCK_CHANNELS_READ;
+  const cChannel *Channel = Channels->First();
+  while (Channel) {
+        if (!Channel->GroupSep() && *source == Channel->Source() && ISTRANSPONDER(Channel->Transponder(), *Value)) {
+           number = Channel->Number();
            break;
            }
-        channel = (cChannel *)channel->Next();
+        Channel = Channels->Next(Channel);
         }
   Set();
 }
@@ -859,10 +883,10 @@
 eOSState cMenuEditTranItem::ProcessKey(eKeys Key)
 {
   eOSState state = cMenuEditChanItem::ProcessKey(Key);
-  cChannel *channel = Channels.GetByNumber(number);
-  if (channel) {
-     *source = channel->Source();
-     *transponder = channel->Transponder();
+  LOCK_CHANNELS_READ
+  if (const cChannel *Channel = Channels->GetByNumber(number)) {
+     *source = Channel->Source();
+     *transponder = Channel->Transponder();
      }
   else {
      *source = 0;
diff -ruN vdr-2.2.0/menuitems.h vdr-2.3.1/menuitems.h
--- vdr-2.2.0/menuitems.h	2013-05-24 12:19:55.000000000 +0200
+++ vdr-2.3.1/menuitems.h	2015-09-06 12:38:37.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: menuitems.h 3.1 2013/05/24 10:19:55 kls Exp $
+ * $Id: menuitems.h 4.1 2015/09/06 10:38:37 kls Exp $
  */
 
 #ifndef __MENUITEMS_H
@@ -146,6 +146,18 @@
   cMenuEditStraItem(const char *Name, int *Value, int NumStrings, const char * const *Strings);
   };
 
+class cMenuEditStrlItem : public cMenuEditIntItem {
+private:
+  const cStringList *strings;
+  int index;
+  char *value;
+  int length;
+protected:
+  virtual void Set(void);
+public:
+  cMenuEditStrlItem(const char *Name, char *Value, int Length, const cStringList *Strings);
+  };
+
 class cMenuEditChanItem : public cMenuEditIntItem {
 protected:
   const char *noneString;
diff -ruN vdr-2.2.0/newplugin vdr-2.3.1/newplugin
--- vdr-2.2.0/newplugin	2014-01-01 14:29:54.000000000 +0100
+++ vdr-2.3.1/newplugin	2015-09-10 13:11:14.000000000 +0200
@@ -12,7 +12,7 @@
 # See the main source file 'vdr.c' for copyright information and
 # how to reach the author.
 #
-# $Id: newplugin 3.2 2014/01/01 13:29:54 kls Exp $
+# $Id: newplugin 4.1 2015/09/10 11:11:14 kls Exp $
 
 $PLUGIN_NAME = $ARGV[0] || die "Usage: newplugin <name>\n";
 
@@ -330,6 +330,7 @@
 die "The directory $PLUGINS_SRC doesn't exist!\n" unless (-d "$PLUGINS_SRC");
 die "A plugin named '$PLUGIN_NAME' already exists in $PLUGINS_SRC!\n" if (-e "$PLUGINDIR");
 mkdir("$PLUGINDIR") || die "$!";
+mkdir("$PLUGINDIR/po") || die "$!";
 
 CreateFile("README", $README);
 CreateFile("HISTORY", $HISTORY);
diff -ruN vdr-2.2.0/nit.c vdr-2.3.1/nit.c
--- vdr-2.2.0/nit.c	2015-02-04 10:13:54.000000000 +0100
+++ vdr-2.3.1/nit.c	2015-07-26 11:24:36.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: nit.c 3.5 2015/02/04 09:13:54 kls Exp $
+ * $Id: nit.c 4.3 2015/07/26 09:24:36 kls Exp $
  */
 
 #include "nit.h"
@@ -19,19 +19,22 @@
 #define DVB_SYSTEM_1 0 // see also dvbdevice.c
 #define DVB_SYSTEM_2 1
 
+#define MAXNETWORKNAME Utf8BufSize(256)
+
+// Set to 'true' for debug output:
+static bool DebugNit = false;
+
+#define dbgnit(a...) if (DebugNit) fprintf(stderr, a)
+
 cNitFilter::cNitFilter(cSdtFilter *SdtFilter)
 {
   sdtFilter = SdtFilter;
-  numNits = 0;
-  networkId = 0;
-  Set(0x10, 0x40);  // NIT
+  Set(0x10, SI::TableIdNIT);
 }
 
 void cNitFilter::SetStatus(bool On)
 {
   cFilter::SetStatus(On);
-  numNits = 0;
-  networkId = 0;
   sectionSyncer.Reset();
 }
 
@@ -40,63 +43,31 @@
   SI::NIT nit(Data, false);
   if (!nit.CheckCRCAndParse())
      return;
-  // Some broadcasters send more than one NIT, with no apparent way of telling which
-  // one is the right one to use. This is an attempt to find the NIT that contains
-  // the transponder it was transmitted on and use only that one:
-  int ThisNIT = -1;
-  if (!networkId) {
-     for (int i = 0; i < numNits; i++) {
-         if (nits[i].networkId == nit.getNetworkId()) {
-            if (nit.getSectionNumber() == 0) {
-               // all NITs have passed by
-               for (int j = 0; j < numNits; j++) {
-                   if (nits[j].hasTransponder) {
-                      networkId = nits[j].networkId;
-                      //printf("taking NIT with network ID %d\n", networkId);
-                      //XXX what if more than one NIT contains this transponder???
-                      break;
-                      }
-                   }
-               if (!networkId) {
-                  //printf("none of the NITs contains transponder %d\n", Transponder());
-                  return;
-                  }
-               }
-            else {
-               ThisNIT = i;
-               break;
-               }
-            }
-         }
-     if (!networkId && ThisNIT < 0 && numNits < MAXNITS) {
-        if (nit.getSectionNumber() == 0) {
-           *nits[numNits].name = 0;
-           SI::Descriptor *d;
-           for (SI::Loop::Iterator it; (d = nit.commonDescriptors.getNext(it)); ) {
-               switch (d->getDescriptorTag()) {
-                 case SI::NetworkNameDescriptorTag: {
-                      SI::NetworkNameDescriptor *nnd = (SI::NetworkNameDescriptor *)d;
-                      nnd->name.getText(nits[numNits].name, MAXNETWORKNAME);
-                      }
-                      break;
-                 default: ;
-                 }
-               delete d;
-               }
-           nits[numNits].networkId = nit.getNetworkId();
-           nits[numNits].hasTransponder = false;
-           //printf("NIT[%d] %5d '%s'\n", numNits, nits[numNits].networkId, nits[numNits].name);
-           ThisNIT = numNits;
-           numNits++;
+  if (!sectionSyncer.Sync(nit.getVersionNumber(), nit.getSectionNumber(), nit.getLastSectionNumber()))
+     return;
+  if (DebugNit) {
+     char NetworkName[MAXNETWORKNAME] = "";
+     SI::Descriptor *d;
+     for (SI::Loop::Iterator it; (d = nit.commonDescriptors.getNext(it)); ) {
+         switch (d->getDescriptorTag()) {
+           case SI::NetworkNameDescriptorTag: {
+                SI::NetworkNameDescriptor *nnd = (SI::NetworkNameDescriptor *)d;
+                nnd->name.getText(NetworkName, MAXNETWORKNAME);
+                }
+                break;
+           default: ;
            }
-        }
+         delete d;
+         }
+     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);
      }
-  else if (networkId != nit.getNetworkId())
-     return; // ignore all other NITs
-  else if (!sectionSyncer.Sync(nit.getVersionNumber(), nit.getSectionNumber(), nit.getLastSectionNumber()))
-     return;
-  if (!Channels.Lock(true, 10))
+  cStateKey StateKey;
+  cChannels *Channels = cChannels::GetChannelsWrite(StateKey, 10);
+  if (!Channels) {
+     sectionSyncer.Repeat(); // let's not miss any section of the NIT
      return;
+     }
+  bool ChannelsModified = false;
   SI::NIT::TransportStream ts;
   for (SI::Loop::Iterator it; nit.transportStreamLoop.getNext(ts, it); ) {
       SI::Descriptor *d;
@@ -118,6 +89,7 @@
                   default: ;
                   }
                 Frequencies[n++] = f;
+                dbgnit("    Frequencies[%d] = %d\n", n - 1, f);
                 }
             }
          else
@@ -142,20 +114,11 @@
                  static int RollOffs[] = { ROLLOFF_35, ROLLOFF_25, ROLLOFF_20, ROLLOFF_AUTO };
                  dtp.SetRollOff(sd->getModulationSystem() ? RollOffs[sd->getRollOff()] : ROLLOFF_AUTO);
                  int SymbolRate = BCD2INT(sd->getSymbolRate()) / 10;
-                 if (ThisNIT >= 0) {
-                    for (int n = 0; n < NumFrequencies; n++) {
-                        if (ISTRANSPONDER(cChannel::Transponder(Frequencies[n], dtp.Polarization()), Transponder())) {
-                           nits[ThisNIT].hasTransponder = true;
-                           //printf("has transponder %d\n", Transponder());
-                           break;
-                           }
-                        }
-                    break;
-                    }
+                 dbgnit("    %s %d %c %d %d\n", *cSource::ToString(Source), Frequency, Polarizations[sd->getPolarization()], SymbolRate, cChannel::Transponder(Frequency, Polarizations[sd->getPolarization()]));
                  if (Setup.UpdateChannels >= 5) {
                     bool found = false;
                     bool forceTransponderUpdate = false;
-                    for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
+                    for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
                         if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
                            int transponder = Channel->Transponder();
                            found = true;
@@ -168,7 +131,7 @@
                                   }
                               }
                            if (ISTRANSPONDER(cChannel::Transponder(Frequency, dtp.Polarization()), Transponder())) // only modify channels if we're actually receiving this transponder
-                              Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('S'));
+                              ChannelsModified |= Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('S'));
                            else if (Channel->Srate() != SymbolRate || strcmp(Channel->Parameters(), dtp.ToString('S')))
                               forceTransponderUpdate = true; // get us receiving this transponder
                            }
@@ -176,7 +139,7 @@
                     if (!found || forceTransponderUpdate) {
                        for (int n = 0; n < NumFrequencies; n++) {
                            cChannel *Channel = new cChannel;
-                           Channel->SetId(ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
+                           Channel->SetId(NULL, ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
                            if (Channel->SetTransponderData(Source, Frequencies[n], SymbolRate, dtp.ToString('S')))
                               EITScanner.AddTransponder(Channel);
                            else
@@ -184,18 +147,19 @@
                            }
                        }
                     }
-                 sdtFilter->Trigger(Source);
+                 if (ISTRANSPONDER(cChannel::Transponder(Frequency, dtp.Polarization()), Transponder()))
+                    sdtFilter->Trigger(Source);
                  }
                  break;
             case SI::S2SatelliteDeliverySystemDescriptorTag: {
                  if (Setup.UpdateChannels >= 5) {
-                    for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
+                    for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
                         if (!Channel->GroupSep() && cSource::IsSat(Channel->Source()) && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
                            SI::S2SatelliteDeliverySystemDescriptor *sd = (SI::S2SatelliteDeliverySystemDescriptor *)d;
                            cDvbTransponderParameters dtp(Channel->Parameters());
                            dtp.SetSystem(DVB_SYSTEM_2);
                            dtp.SetStreamId(sd->getInputStreamIdentifier());
-                           Channel->SetTransponderData(Channel->Source(), Channel->Frequency(), Channel->Srate(), dtp.ToString('S'));
+                           ChannelsModified |= Channel->SetTransponderData(Channel->Source(), Channel->Frequency(), Channel->Srate(), dtp.ToString('S'));
                            break;
                            }
                         }
@@ -213,20 +177,11 @@
                  static int Modulations[] = { QPSK, QAM_16, QAM_32, QAM_64, QAM_128, QAM_256, QAM_AUTO };
                  dtp.SetModulation(Modulations[min(sd->getModulation(), 6)]);
                  int SymbolRate = BCD2INT(sd->getSymbolRate()) / 10;
-                 if (ThisNIT >= 0) {
-                    for (int n = 0; n < NumFrequencies; n++) {
-                        if (ISTRANSPONDER(Frequencies[n] / 1000, Transponder())) {
-                           nits[ThisNIT].hasTransponder = true;
-                           //printf("has transponder %d\n", Transponder());
-                           break;
-                           }
-                        }
-                    break;
-                    }
+                 dbgnit("    %s %d %d %d %d\n", *cSource::ToString(Source), Frequency, CodeRates[sd->getFecInner()], Modulations[min(sd->getModulation(), 6)], SymbolRate);
                  if (Setup.UpdateChannels >= 5) {
                     bool found = false;
                     bool forceTransponderUpdate = false;
-                    for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
+                    for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
                         if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
                            int transponder = Channel->Transponder();
                            found = true;
@@ -239,7 +194,7 @@
                                   }
                               }
                            if (ISTRANSPONDER(Frequency / 1000, Transponder())) // only modify channels if we're actually receiving this transponder
-                              Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('C'));
+                              ChannelsModified |= Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('C'));
                            else if (Channel->Srate() != SymbolRate || strcmp(Channel->Parameters(), dtp.ToString('C')))
                               forceTransponderUpdate = true; // get us receiving this transponder
                            }
@@ -247,7 +202,7 @@
                     if (!found || forceTransponderUpdate) {
                        for (int n = 0; n < NumFrequencies; n++) {
                            cChannel *Channel = new cChannel;
-                           Channel->SetId(ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
+                           Channel->SetId(NULL, ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
                            if (Channel->SetTransponderData(Source, Frequencies[n], SymbolRate, dtp.ToString('C')))
                               EITScanner.AddTransponder(Channel);
                            else
@@ -255,7 +210,8 @@
                            }
                        }
                     }
-                 sdtFilter->Trigger(Source);
+                 if (ISTRANSPONDER(Frequency / 1000, Transponder()))
+                    sdtFilter->Trigger(Source);
                  }
                  break;
             case SI::TerrestrialDeliverySystemDescriptorTag: {
@@ -277,20 +233,11 @@
                  dtp.SetGuard(GuardIntervals[sd->getGuardInterval()]);
                  static int TransmissionModes[] = { TRANSMISSION_MODE_2K, TRANSMISSION_MODE_8K, TRANSMISSION_MODE_4K, TRANSMISSION_MODE_AUTO };
                  dtp.SetTransmission(TransmissionModes[sd->getTransmissionMode()]);
-                 if (ThisNIT >= 0) {
-                    for (int n = 0; n < NumFrequencies; n++) {
-                        if (ISTRANSPONDER(Frequencies[n] / 1000000, Transponder())) {
-                           nits[ThisNIT].hasTransponder = true;
-                           //printf("has transponder %d\n", Transponder());
-                           break;
-                           }
-                        }
-                    break;
-                    }
+                 dbgnit("    %s %d %d %d %d %d %d %d %d\n", *cSource::ToString(Source), Frequency, Bandwidths[sd->getBandwidth()], Constellations[sd->getConstellation()], Hierarchies[sd->getHierarchy()], CodeRates[sd->getCodeRateHP()], CodeRates[sd->getCodeRateLP()], GuardIntervals[sd->getGuardInterval()], TransmissionModes[sd->getTransmissionMode()]);
                  if (Setup.UpdateChannels >= 5) {
                     bool found = false;
                     bool forceTransponderUpdate = false;
-                    for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
+                    for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
                         if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
                            int transponder = Channel->Transponder();
                            found = true;
@@ -303,7 +250,7 @@
                                   }
                               }
                            if (ISTRANSPONDER(Frequency / 1000000, Transponder())) // only modify channels if we're actually receiving this transponder
-                              Channel->SetTransponderData(Source, Frequency, 0, dtp.ToString('T'));
+                              ChannelsModified |= Channel->SetTransponderData(Source, Frequency, 0, dtp.ToString('T'));
                            else if (strcmp(Channel->Parameters(), dtp.ToString('T')))
                               forceTransponderUpdate = true; // get us receiving this transponder
                            }
@@ -311,7 +258,7 @@
                     if (!found || forceTransponderUpdate) {
                        for (int n = 0; n < NumFrequencies; n++) {
                            cChannel *Channel = new cChannel;
-                           Channel->SetId(ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
+                           Channel->SetId(NULL, ts.getOriginalNetworkId(), ts.getTransportStreamId(), 0, 0);
                            if (Channel->SetTransponderData(Source, Frequencies[n], 0, dtp.ToString('T')))
                               EITScanner.AddTransponder(Channel);
                            else
@@ -319,7 +266,8 @@
                            }
                        }
                     }
-                 sdtFilter->Trigger(Source);
+                 if (ISTRANSPONDER(Frequency / 1000000, Transponder()))
+                    sdtFilter->Trigger(Source);
                  }
                  break;
             case SI::ExtensionDescriptorTag: {
@@ -327,7 +275,7 @@
                  switch (sd->getExtensionDescriptorTag()) {
                    case SI::T2DeliverySystemDescriptorTag: {
                         if (Setup.UpdateChannels >= 5) {
-                           for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
+                           for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
                                int Source = cSource::FromData(cSource::stTerr);
                                if (!Channel->GroupSep() && Channel->Source() == Source && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
                                   SI::T2DeliverySystemDescriptor *td = (SI::T2DeliverySystemDescriptor *)d;
@@ -347,7 +295,7 @@
                                      dtp.SetTransmission(T2TransmissionModes[td->getTransmissionMode()]);
                                      //TODO add parsing of frequencies
                                      }
-                                  Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('T'));
+                                  ChannelsModified |= Channel->SetTransponderData(Source, Frequency, SymbolRate, dtp.ToString('T'));
                                   }
                                }
                            }
@@ -365,9 +313,9 @@
                         int lcn = LogicalChannel.getLogicalChannelNumber();
                         int sid = LogicalChannel.getServiceId();
                         if (LogicalChannel.getVisibleServiceFlag()) {
-                           for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
+                           for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
                                if (!Channel->GroupSep() && Channel->Sid() == sid && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
-                                  Channel->SetLcn(lcn);
+                                  ChannelsModified |= Channel->SetLcn(lcn);
                                   break;
                                   }
                                }
@@ -383,9 +331,9 @@
                         int lcn = HdSimulcastLogicalChannel.getLogicalChannelNumber();
                         int sid = HdSimulcastLogicalChannel.getServiceId();
                         if (HdSimulcastLogicalChannel.getVisibleServiceFlag()) {
-                           for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
+                           for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
                                if (!Channel->GroupSep() && Channel->Sid() == sid && Channel->Nid() == ts.getOriginalNetworkId() && Channel->Tid() == ts.getTransportStreamId()) {
-                                  Channel->SetLcn(lcn);
+                                  ChannelsModified |= Channel->SetLcn(lcn);
                                   break;
                                   }
                                }
@@ -398,5 +346,5 @@
           delete d;
           }
       }
-  Channels.Unlock();
+  StateKey.Remove(ChannelsModified);
 }
diff -ruN vdr-2.2.0/nit.h vdr-2.3.1/nit.h
--- vdr-2.2.0/nit.h	2014-03-10 15:12:05.000000000 +0100
+++ vdr-2.3.1/nit.h	2015-03-16 13:41:38.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: nit.h 3.1 2014/03/10 14:12:05 kls Exp $
+ * $Id: nit.h 4.1 2015/03/16 12:41:38 kls Exp $
  */
 
 #ifndef __NIT_H
@@ -13,24 +13,10 @@
 #include "filter.h"
 #include "sdt.h"
 
-#define MAXNITS 16
-#define MAXNETWORKNAME Utf8BufSize(256)
-
 class cNitFilter : public cFilter {
 private:
-
-  class cNit {
-  public:
-    u_short networkId;
-    char name[MAXNETWORKNAME];
-    bool hasTransponder;
-    };
-
   cSectionSyncer sectionSyncer;
   cSdtFilter *sdtFilter;
-  cNit nits[MAXNITS];
-  u_short networkId;
-  int numNits;
 protected:
   virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
 public:
diff -ruN vdr-2.2.0/osd.c vdr-2.3.1/osd.c
--- vdr-2.2.0/osd.c	2015-02-11 10:48:02.000000000 +0100
+++ vdr-2.3.1/osd.c	2015-09-10 16:12:06.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: osd.c 3.5 2015/02/11 09:48:02 kls Exp $
+ * $Id: osd.c 4.3 2015/09/10 14:12:06 kls Exp $
  */
 
 #include "osd.h"
@@ -1641,6 +1641,7 @@
 int cOsd::osdTop = 0;
 int cOsd::osdWidth = 0;
 int cOsd::osdHeight = 0;
+cSize cOsd::maxPixmapSize(2048, 2048);
 cVector<cOsd *> cOsd::Osds;
 cMutex cOsd::mutex;
 
@@ -1705,6 +1706,11 @@
   return Area < numBitmaps ? (isTrueColor ? bitmaps[0] : bitmaps[Area]) : NULL;
 }
 
+const cSize &cOsd::MaxPixmapSize(void) const
+{
+  return maxPixmapSize;
+}
+
 cPixmap *cOsd::CreatePixmap(int Layer, const cRect &ViewPort, const cRect &DrawPort)
 {
   if (isTrueColor) {
@@ -1836,7 +1842,10 @@
         width = Areas[0].x2 - Areas[0].x1 + 1;
         height = Areas[0].y2 - Areas[0].y1 + 1;
         cPixmap *Pixmap = CreatePixmap(0, cRect(Areas[0].x1, Areas[0].y1, width, height));
-        Pixmap->Clear();
+        if (Pixmap)
+           Pixmap->Clear();
+        else
+           Result = oeOutOfMemory;
         bitmaps[numBitmaps++] = new cBitmap(10, 10, 8); // dummy bitmap for GetBitmap()
         }
      else {
@@ -2033,8 +2042,8 @@
   if (Width != oldWidth || Height != oldHeight || !DoubleEqual(Aspect, oldAspect) || Force) {
      Setup.OSDLeft = int(round(Width * Setup.OSDLeftP));
      Setup.OSDTop = int(round(Height * Setup.OSDTopP));
-     Setup.OSDWidth = int(round(Width * Setup.OSDWidthP)) & ~0x07; // OSD width must be a multiple of 8
-     Setup.OSDHeight = int(round(Height * Setup.OSDHeightP));
+     Setup.OSDWidth = min(Width - Setup.OSDLeft, int(round(Width * Setup.OSDWidthP))) & ~0x07; // OSD width must be a multiple of 8
+     Setup.OSDHeight = min(Height - Setup.OSDTop, int(round(Height * Setup.OSDHeightP)));
      Setup.OSDAspect = Aspect;
      Setup.FontOsdSize = int(round(Height * Setup.FontOsdSizeP));
      Setup.FontFixSize = int(round(Height * Setup.FontFixSizeP));
diff -ruN vdr-2.2.0/osd.h vdr-2.3.1/osd.h
--- vdr-2.2.0/osd.h	2015-02-11 10:48:02.000000000 +0100
+++ vdr-2.3.1/osd.h	2015-04-19 14:18:25.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: osd.h 3.6 2015/02/11 09:48:02 kls Exp $
+ * $Id: osd.h 4.4 2015/04/19 12:18:25 kls Exp $
  */
 
 #ifndef __OSD_H
@@ -510,8 +510,10 @@
        ///< In order to allow devices that can handle only a limited number of layers,
        ///< the Layer parameter must be less than 8 (MAXPIXMAPLAYERS).
        ///< ViewPort defines the rectangle in which this pixmap will be rendered on
-       ///< the OSD. If no DrawPort is given, it defaults to the same size as the
-       ///< ViewPort, with its upper left corner set to (0, 0).
+       ///< the OSD. The coordinate (0, 0) corresponds to the upper left corner of the
+       ///< OSD. If no DrawPort is given, it defaults to the same size as the
+       ///< ViewPort, with its upper left corner set to (0, 0). The DrawPort's origin
+       ///< is relative to the ViewPort's origin.
        ///< All drawing operations will be executed relative to the origin of the
        ///< DrawPort rectangle, and will be clipped to the size of this rectangle.
        ///< The DrawPort may have a different size than the ViewPort. If it is smaller
@@ -522,6 +524,8 @@
        ///< intersects with the ViewPort will be visible on the OSD.
        ///< The drawing area of a newly created cPixmap is not initialized and may
        ///< contain random data.
+       ///< See cOsd::MaxPixmapSize() for information on the maximum size of pixmaps
+       ///< supported by the system.
   static void Lock(void) { mutex.Lock(); }
        ///< All public member functions of cPixmap set locks as necessary to make sure
        ///< they are thread-safe (unless noted otherwise). If several cPixmap member
@@ -722,6 +726,7 @@
 private:
   static int osdLeft, osdTop, osdWidth, osdHeight;
   static cVector<cOsd *> Osds;
+  static cSize maxPixmapSize;
   static cMutex mutex;
   bool isTrueColor;
   cBitmap *savedBitmap;
@@ -780,6 +785,18 @@
        ///< If there are no dirty pixmaps, or if this is not a true color OSD,
        ///< this function returns NULL.
        ///< The caller must call DestroyPixmap() for the returned pixmap after use.
+//#define DEPRECATED_GETBITMAP
+#ifdef DEPRECATED_GETBITMAP
+public:
+#endif
+  cBitmap *GetBitmap(int Area);
+       ///< Returns a pointer to the bitmap for the given Area, or NULL if no
+       ///< such bitmap exists.
+       ///< If this is a true color OSD, a pointer to a dummy bitmap with 8bpp
+       ///< is returned. This is done so that skins that call this function
+       ///< in order to preset the bitmap's palette won't crash.
+       ///< Use of this function outside of derived classes is deprecated and it
+       ///< may be made 'protected' in a future version.
 public:
   virtual ~cOsd();
        ///< Shuts down the OSD.
@@ -812,14 +829,8 @@
        ///< requested colors. By default the palette assumes there will be
        ///< 10 fixed colors and 10 color combinations.
        ///< If this is a true color OSD, this function does nothing.
-  cBitmap *GetBitmap(int Area);
-       ///< Returns a pointer to the bitmap for the given Area, or NULL if no
-       ///< such bitmap exists.
-       ///< If this is a true color OSD, a pointer to a dummy bitmap with 8bpp
-       ///< is returned. This is done so that skins that call this function
-       ///< in order to preset the bitmap's palette won't crash.
-       ///< Use of this function outside of derived classes is deprecated and it
-       ///< may be made 'protected' in a future version.
+  virtual const cSize &MaxPixmapSize(void) const;
+       ///< Returns the maximum possible size of a pixmap this OSD can create.
   virtual cPixmap *CreatePixmap(int Layer, const cRect &ViewPort, const cRect &DrawPort = cRect::Null);
        ///< Creates a new true color pixmap on this OSD (see cPixmap for details).
        ///< The caller must not delete the returned object, it will be deleted when
diff -ruN vdr-2.2.0/osdbase.c vdr-2.3.1/osdbase.c
--- vdr-2.2.0/osdbase.c	2015-01-15 11:11:11.000000000 +0100
+++ vdr-2.3.1/osdbase.c	2015-09-10 13:23:07.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: osdbase.c 3.3 2015/01/15 10:11:11 kls Exp $
+ * $Id: osdbase.c 4.1 2015/09/10 11:23:07 kls Exp $
  */
 
 #include "osdbase.h"
@@ -87,6 +87,7 @@
   title = NULL;
   menuCategory = mcUnknown;
   menuSortMode = msmUnknown;
+  menuOrientation = moVertical;
   SetTitle(Title);
   SetCols(c0, c1, c2, c3, c4);
   first = 0;
@@ -231,6 +232,7 @@
   if (menuCategory != displayMenu->MenuCategory())
      displayMenu->SetMenuCategory(menuCategory);
   displayMenu->SetMenuSortMode(menuSortMode);
+  menuOrientation = displayMenu->MenuOrientation();
   displayMenuItems = displayMenu->MaxItems();
   displayMenu->SetTabs(cols[0], cols[1], cols[2], cols[3], cols[4]);//XXX
   displayMenu->SetTitle(title);
@@ -541,13 +543,13 @@
     case k0:      return osUnknown;
     case k1...k9: return hasHotkeys ? HotKey(Key) : osUnknown;
     case kUp|k_Repeat:
-    case kUp:   CursorUp();   break;
+    case kUp:     if (menuOrientation == moHorizontal) PageUp();     else CursorUp(); break;
     case kDown|k_Repeat:
-    case kDown: CursorDown(); break;
+    case kDown:   if (menuOrientation == moHorizontal) PageDown();   else CursorDown(); break;
     case kLeft|k_Repeat:
-    case kLeft: PageUp(); break;
+    case kLeft:   if (menuOrientation == moHorizontal) CursorUp();   else PageUp(); break;
     case kRight|k_Repeat:
-    case kRight: PageDown(); break;
+    case kRight:  if (menuOrientation == moHorizontal) CursorDown(); else PageDown(); break;
     case kBack: return osBack;
     case kOk:   if (marked >= 0) {
                    SetStatus(NULL);
diff -ruN vdr-2.2.0/osdbase.h vdr-2.3.1/osdbase.h
--- vdr-2.2.0/osdbase.h	2015-01-15 11:09:18.000000000 +0100
+++ vdr-2.3.1/osdbase.h	2015-09-10 13:17:52.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: osdbase.h 3.2 2015/01/15 10:09:18 kls Exp $
+ * $Id: osdbase.h 4.1 2015/09/10 11:17:52 kls Exp $
  */
 
 #ifndef __OSDBASE_H
@@ -93,6 +93,7 @@
   int first, current, marked;
   eMenuCategory menuCategory;
   eMenuSortMode menuSortMode;
+  eMenuOrientation menuOrientation;
   cOsdMenu *subMenu;
   const char *helpRed, *helpGreen, *helpYellow, *helpBlue;
   bool helpDisplayed;
diff -ruN vdr-2.2.0/pat.c vdr-2.3.1/pat.c
--- vdr-2.2.0/pat.c	2015-01-04 14:14:01.000000000 +0100
+++ vdr-2.3.1/pat.c	2015-08-17 10:46:55.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: pat.c 3.5 2015/01/04 13:14:01 kls Exp $
+ * $Id: pat.c 4.1 2015/08/17 08:46:55 kls Exp $
  */
 
 #include "pat.h"
@@ -99,8 +99,8 @@
 
 bool cCaDescriptors::operator== (const cCaDescriptors &arg) const
 {
-  cCaDescriptor *ca1 = caDescriptors.First();
-  cCaDescriptor *ca2 = arg.caDescriptors.First();
+  const cCaDescriptor *ca1 = caDescriptors.First();
+  const cCaDescriptor *ca2 = arg.caDescriptors.First();
   while (ca1 && ca2) {
         if (!(*ca1 == *ca2))
            return false;
@@ -396,11 +396,14 @@
         SwitchToNextPmtPid();
         return;
         }
-     if (!Channels.Lock(true, 10))
+     cStateKey StateKey;
+     cChannels *Channels = cChannels::GetChannelsWrite(StateKey, 10);
+     if (!Channels)
         return;
+     bool ChannelsModified = false;
      PmtVersionChanged(Pid, pmt.getTableIdExtension(), pmt.getVersionNumber(), true);
      SwitchToNextPmtPid();
-     cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), pmt.getServiceId());
+     cChannel *Channel = Channels->GetByServiceID(Source(), Transponder(), pmt.getServiceId());
      if (Channel) {
         SI::CaDescriptor *d;
         cCaDescriptors *CaDescriptors = new cCaDescriptors(Channel->Source(), Channel->Transponder(), Channel->Sid(), Pid);
@@ -629,13 +632,13 @@
                }
             }
         if (Setup.UpdateChannels >= 2) {
-           Channel->SetPids(Vpid, Ppid, Vtype, Apids, Atypes, ALangs, Dpids, Dtypes, DLangs, Spids, SLangs, Tpid);
-           Channel->SetCaIds(CaDescriptors->CaIds());
-           Channel->SetSubtitlingDescriptors(SubtitlingTypes, CompositionPageIds, AncillaryPageIds);
+           ChannelsModified |= Channel->SetPids(Vpid, Ppid, Vtype, Apids, Atypes, ALangs, Dpids, Dtypes, DLangs, Spids, SLangs, Tpid);
+           ChannelsModified |= Channel->SetCaIds(CaDescriptors->CaIds());
+           ChannelsModified |= Channel->SetSubtitlingDescriptors(SubtitlingTypes, CompositionPageIds, AncillaryPageIds);
            }
-        Channel->SetCaDescriptors(CaDescriptorHandler.AddCaDescriptors(CaDescriptors));
+        ChannelsModified |= Channel->SetCaDescriptors(CaDescriptorHandler.AddCaDescriptors(CaDescriptors));
         }
-     Channels.Unlock();
+     StateKey.Remove(ChannelsModified);
      }
   if (timer.TimedOut()) {
      if (pmtIndex >= 0)
diff -ruN vdr-2.2.0/pat.h vdr-2.3.1/pat.h
--- vdr-2.2.0/pat.h	2015-01-04 14:17:22.000000000 +0100
+++ vdr-2.3.1/pat.h	2015-01-04 14:17:22.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: pat.h 3.4 2015/01/04 13:17:22 kls Exp $
+ * $Id: pat.h 4.0 2015/01/04 13:17:22 kls Exp $
  */
 
 #ifndef __PAT_H
diff -ruN vdr-2.2.0/player.c vdr-2.3.1/player.c
--- vdr-2.2.0/player.c	2012-04-28 13:52:50.000000000 +0200
+++ vdr-2.3.1/player.c	2012-04-28 13:52:50.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: player.c 3.0 2012/04/28 11:52:50 kls Exp $
+ * $Id: player.c 4.0 2012/04/28 11:52:50 kls Exp $
  */
 
 #include "player.h"
diff -ruN vdr-2.2.0/player.h vdr-2.3.1/player.h
--- vdr-2.2.0/player.h	2013-12-25 14:25:02.000000000 +0100
+++ vdr-2.3.1/player.h	2013-12-25 14:25:02.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: player.h 3.1 2013/12/25 13:25:02 kls Exp $
+ * $Id: player.h 4.0 2013/12/25 13:25:02 kls Exp $
  */
 
 #ifndef __PLAYER_H
diff -ruN vdr-2.2.0/plugin.c vdr-2.3.1/plugin.c
--- vdr-2.2.0/plugin.c	2012-09-01 15:10:27.000000000 +0200
+++ vdr-2.3.1/plugin.c	2015-04-18 16:51:20.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: plugin.c 3.0 2012/09/01 13:10:27 kls Exp $
+ * $Id: plugin.c 4.1 2015/04/18 14:51:20 kls Exp $
  */
 
 #include "plugin.h"
@@ -318,23 +318,23 @@
 void cPluginManager::AddPlugin(const char *Args)
 {
   if (strcmp(Args, "*") == 0) {
-     cReadDir d(directory);
-     struct dirent *e;
-     while ((e = d.Next()) != NULL) {
-           if (strstr(e->d_name, LIBVDR_PREFIX) == e->d_name) {
-              char *p = strstr(e->d_name, SO_INDICATOR);
-              if (p) {
-                 *p = 0;
-                 p += strlen(SO_INDICATOR);
-                 if (strcmp(p, APIVERSION) == 0) {
-                    char *name = e->d_name + strlen(LIBVDR_PREFIX);
-                    if (strcmp(name, "*") != 0) { // let's not get into a loop!
-                       AddPlugin(e->d_name + strlen(LIBVDR_PREFIX));
-                       }
-                    }
-                 }
-              }
-           }
+     cFileNameList Files(directory);
+     for (int i = 0; i < Files.Size(); i++) {
+         char *FileName = Files.At(i);
+         if (strstr(FileName, LIBVDR_PREFIX) == FileName) {
+            char *p = strstr(FileName, SO_INDICATOR);
+            if (p) {
+               *p = 0;
+               p += strlen(SO_INDICATOR);
+               if (strcmp(p, APIVERSION) == 0) {
+                  char *name = FileName + strlen(LIBVDR_PREFIX);
+                  if (strcmp(name, "*") != 0) { // let's not get into a loop!
+                     AddPlugin(FileName + strlen(LIBVDR_PREFIX));
+                     }
+                  }
+               }
+            }
+         }
      return;
      }
   char *s = strdup(skipspace(Args));
diff -ruN vdr-2.2.0/plugin.h vdr-2.3.1/plugin.h
--- vdr-2.2.0/plugin.h	2012-09-01 15:08:54.000000000 +0200
+++ vdr-2.3.1/plugin.h	2012-09-01 15:08:54.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: plugin.h 3.0 2012/09/01 13:08:54 kls Exp $
+ * $Id: plugin.h 4.0 2012/09/01 13:08:54 kls Exp $
  */
 
 #ifndef __PLUGIN_H
diff -ruN vdr-2.2.0/po/ar.po vdr-2.3.1/po/ar.po
--- vdr-2.2.0/po/ar.po	2015-02-19 10:12:22.263195225 +0100
+++ vdr-2.3.1/po/ar.po	2015-09-13 12:41:12.595056119 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2008-10-16 11:16-0400\n"
 "Last-Translator: Osama Alrawab <alrawab@hotmail.com>\n"
 "Language-Team: Arabic <ar@li.org>\n"
@@ -664,6 +664,9 @@
 msgid "File"
 msgstr "ملف"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Folder"
 
@@ -676,6 +679,12 @@
 msgid "First day"
 msgstr "اليوم الاول"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Select folder"
 
@@ -851,6 +860,12 @@
 msgid "always"
 msgstr "دائما"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "عرص على الشاشة"
 
@@ -938,6 +953,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "قم دائما بسرد المجلدات اولا"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Number keys for characters"
 
@@ -1122,6 +1140,9 @@
 msgid " (activating)"
 msgstr ""
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "الكامة"
 
@@ -1152,6 +1173,15 @@
 msgid "Can't reset CAM!"
 msgstr "تعذر اعادة تشغيل الكامة"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "لا تقم بايقاف الفديو الحى"
 
@@ -1182,6 +1212,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "معدل البقاء باليوم"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Pause key handling"
 
@@ -1281,6 +1314,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "SVDRP timeout (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Zap timeout (s)"
 
@@ -1534,6 +1576,9 @@
 msgid "Pause live video?"
 msgstr "ايقاف الفديو الحى"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "ابتداء التسجيل"
 
diff -ruN vdr-2.2.0/po/ca_ES.po vdr-2.3.1/po/ca_ES.po
--- vdr-2.2.0/po/ca_ES.po	2015-02-19 10:12:21.874178607 +0100
+++ vdr-2.3.1/po/ca_ES.po	2015-09-13 12:41:12.270028871 +0200
@@ -10,7 +10,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2008-03-02 19:02+0100\n"
 "Last-Translator: Luca Olivetti <luca@ventoso.org>\n"
 "Language-Team: Catalan <vdr@linuxtv.org>\n"
@@ -663,6 +663,9 @@
 msgid "File"
 msgstr "Arxiu"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Carpeta"
 
@@ -675,6 +678,12 @@
 msgid "First day"
 msgstr "Primer dia"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Seleccioni carpeta"
 
@@ -850,6 +859,12 @@
 msgid "always"
 msgstr "sempre"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "Informaci� en pantalla"
 
@@ -937,6 +952,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Sempre ordenar primer carpetes"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Tecles num�riques per a car�cters"
 
@@ -1121,6 +1139,9 @@
 msgid " (activating)"
 msgstr ""
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1151,6 +1172,15 @@
 msgid "Can't reset CAM!"
 msgstr "No puc reiniciar la CAM!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "no pausar emissi� en directe"
 
@@ -1181,6 +1211,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Durada predefinida"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Gesti� tecla pausa"
 
@@ -1280,6 +1313,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "SVDRP Timeout (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Temps d'espera Zap (s)"
 
@@ -1533,6 +1575,9 @@
 msgid "Pause live video?"
 msgstr "Pausar emissi� en directe?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Gravaci� comen�ada"
 
diff -ruN vdr-2.2.0/po/cs_CZ.po vdr-2.3.1/po/cs_CZ.po
--- vdr-2.2.0/po/cs_CZ.po	2015-02-19 10:12:22.190192082 +0100
+++ vdr-2.3.1/po/cs_CZ.po	2015-09-13 12:41:12.521049909 +0200
@@ -10,7 +10,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2010-05-06 11:00+0200\n"
 "Last-Translator: Aleš Juřík <ajurik@quick.cz>\n"
 "Language-Team: Czech <vdr@linuxtv.org>\n"
@@ -663,6 +663,9 @@
 msgid "File"
 msgstr "Soubor"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Složka"
 
@@ -675,6 +678,12 @@
 msgid "First day"
 msgstr "První den"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Vybrat složku"
 
@@ -850,6 +859,12 @@
 msgid "always"
 msgstr "vždy"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -937,6 +952,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Adresáře řadit vždy na začátek"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Psát písmena pomocí číselných kláves"
 
@@ -1121,6 +1139,9 @@
 msgid " (activating)"
 msgstr " (aktivuji)"
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1151,6 +1172,15 @@
 msgid "Can't reset CAM!"
 msgstr "CAM modul nelze restartovat!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "nepřerusovat běžící program"
 
@@ -1181,6 +1211,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Výchozí životnost"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Chování klávesy Přerušení"
 
@@ -1280,6 +1313,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "Časový limit SVDRP (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Časový limit Zap (s)"
 
@@ -1533,6 +1575,9 @@
 msgid "Pause live video?"
 msgstr "Přerušit běžící program?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Začalo nahrávání"
 
diff -ruN vdr-2.2.0/po/da_DK.po vdr-2.3.1/po/da_DK.po
--- vdr-2.2.0/po/da_DK.po	2015-02-19 10:12:21.957182179 +0100
+++ vdr-2.3.1/po/da_DK.po	2015-09-13 12:41:12.363036669 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2007-08-12 14:17+0200\n"
 "Last-Translator: Mogens Elneff <mogens@elneff.dk>\n"
 "Language-Team: Danish <vdr@linuxtv.org>\n"
@@ -660,6 +660,9 @@
 msgid "File"
 msgstr "Fil"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr ""
 
@@ -672,6 +675,12 @@
 msgid "First day"
 msgstr "F�rste dag"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr ""
 
@@ -847,6 +856,12 @@
 msgid "always"
 msgstr "altid"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -934,6 +949,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr ""
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr ""
 
@@ -1118,6 +1136,9 @@
 msgid " (activating)"
 msgstr ""
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1148,6 +1169,15 @@
 msgid "Can't reset CAM!"
 msgstr "Kan ikke nulstille CAM!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr ""
 
@@ -1178,6 +1208,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Standard levetid (d)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr ""
 
@@ -1277,6 +1310,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "SVDRP timeout (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Zap timeout (s)"
 
@@ -1530,6 +1572,9 @@
 msgid "Pause live video?"
 msgstr ""
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Optagelse startet"
 
diff -ruN vdr-2.2.0/po/de_DE.po vdr-2.3.1/po/de_DE.po
--- vdr-2.2.0/po/de_DE.po	2015-02-19 10:12:22.401201125 +0100
+++ vdr-2.3.1/po/de_DE.po	2015-09-13 12:41:12.671062486 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2015-02-10 13:45+0100\n"
 "Last-Translator: Klaus Schmidinger <vdr@tvdr.de>\n"
 "Language-Team: German <vdr@linuxtv.org>\n"
@@ -661,6 +661,9 @@
 msgid "File"
 msgstr "Datei"
 
+msgid "Record on"
+msgstr "Aufnehmen auf"
+
 msgid "Button$Folder"
 msgstr "Verzeichnis"
 
@@ -673,6 +676,12 @@
 msgid "First day"
 msgstr "Erster Tag"
 
+msgid "Error while accessing remote timer"
+msgstr "Fehler beim Ansprechen des fernen Timers"
+
+msgid "Timer has been deleted!"
+msgstr "Timer wurde gel�scht!"
+
 msgid "Select folder"
 msgstr "Verzeichnis w�hlen"
 
@@ -848,6 +857,12 @@
 msgid "always"
 msgstr "immer"
 
+msgid "by name"
+msgstr "nach Namen"
+
+msgid "by time"
+msgstr "nach Zeit"
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -935,6 +950,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Verzeichnisse immer zuerst einsortieren"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr "Standard Sortierreihenfolge f�r Aufnahmen"
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Nummerntasten f�r Zeichen"
 
@@ -1119,6 +1137,9 @@
 msgid " (activating)"
 msgstr " (wird aktiviert)"
 
+msgid "@ device"
+msgstr "@ Empf�nger"
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1149,6 +1170,15 @@
 msgid "Can't reset CAM!"
 msgstr "Zur�cksetzen des CAM fehlgeschlagen!"
 
+msgid "no instant recording"
+msgstr "keine Sofortaufnahme"
+
+msgid "confirm instant recording"
+msgstr "Sofortaufnahme best�tigen"
+
+msgid "record instantly"
+msgstr "sofort aufnehmen"
+
 msgid "do not pause live video"
 msgstr "Live-Signal nicht anhalten"
 
@@ -1179,6 +1209,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Default-Lebensdauer (d)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr "Funktion der Aufnehmen-Taste"
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Funktion der Pause-Taste"
 
@@ -1278,6 +1311,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "SVDRP trennen bei Inaktivit�t (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr "SVDRP Verbindung"
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr "SVDRP Maschinenname"
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr "SVDRP Standardmaschine"
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Mindestzeit f�r vorherigen Kanal (s)"
 
@@ -1531,6 +1573,9 @@
 msgid "Pause live video?"
 msgstr "Live-Signal anhalten?"
 
+msgid "Start recording?"
+msgstr "Aufnahme starten?"
+
 msgid "Recording started"
 msgstr "Aufzeichnung gestartet"
 
diff -ruN vdr-2.2.0/po/el_GR.po vdr-2.3.1/po/el_GR.po
--- vdr-2.2.0/po/el_GR.po	2015-02-19 10:12:22.057186423 +0100
+++ vdr-2.3.1/po/el_GR.po	2015-09-13 12:41:12.459044652 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2007-08-12 14:17+0200\n"
 "Last-Translator: Dimitrios Dimitrakos <mail@dimitrios.de>\n"
 "Language-Team: Greek <vdr@linuxtv.org>\n"
@@ -660,6 +660,9 @@
 msgid "File"
 msgstr "������"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr ""
 
@@ -672,6 +675,12 @@
 msgid "First day"
 msgstr "����� ����"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr ""
 
@@ -847,6 +856,12 @@
 msgid "always"
 msgstr "�����"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -934,6 +949,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr ""
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr ""
 
@@ -1118,6 +1136,9 @@
 msgid " (activating)"
 msgstr ""
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1148,6 +1169,15 @@
 msgid "Can't reset CAM!"
 msgstr "������� � ��������� ��� CAM"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr ""
 
@@ -1178,6 +1208,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "�������������� �������� ��������� (������)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr ""
 
@@ -1277,6 +1310,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "SVDRP ������� (�)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "������� ������� (�)"
 
@@ -1530,6 +1572,9 @@
 msgid "Pause live video?"
 msgstr ""
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr ""
 
diff -ruN vdr-2.2.0/po/es_ES.po vdr-2.3.1/po/es_ES.po
--- vdr-2.2.0/po/es_ES.po	2015-02-19 10:12:21.902179822 +0100
+++ vdr-2.3.1/po/es_ES.po	2015-09-13 12:41:12.313032471 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2015-02-19 23:00+0100\n"
 "Last-Translator: Gabriel Bonich <gbonich@gmail.com>\n"
 "Language-Team: Spanish <vdr@linuxtv.org>\n"
@@ -661,6 +661,9 @@
 msgid "File"
 msgstr "Fichero"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Carpeta"
 
@@ -673,6 +676,12 @@
 msgid "First day"
 msgstr "Primer d�a"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Seleccionar carpeta"
 
@@ -848,6 +857,12 @@
 msgid "always"
 msgstr "siempre"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "Men�s en pantalla"
 
@@ -935,6 +950,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Siempre ordenar primero carpetas"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Teclas num�ricas para caracteres"
 
@@ -1119,6 +1137,9 @@
 msgid " (activating)"
 msgstr " (activando)"
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1149,6 +1170,15 @@
 msgid "Can't reset CAM!"
 msgstr "�No se puede reiniciar CAM!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "no pausar emisi�n en directo"
 
@@ -1179,6 +1209,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Duraci�n por defecto (d�as)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Gesti�n tecla de pausa"
 
@@ -1278,6 +1311,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "Tiempo de espera de SVDRP (sg)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Considerar canal como visto (sg)"
 
@@ -1531,6 +1573,9 @@
 msgid "Pause live video?"
 msgstr "�Pausar emisi�n en directo?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Iniciando grabaci�n"
 
diff -ruN vdr-2.2.0/po/et_EE.po vdr-2.3.1/po/et_EE.po
--- vdr-2.2.0/po/et_EE.po	2015-02-19 10:12:21.851177873 +0100
+++ vdr-2.3.1/po/et_EE.po	2015-09-13 12:41:12.242026525 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2007-08-12 14:17+0200\n"
 "Last-Translator: Arthur Konovalov <artlov@gmail.com>\n"
 "Language-Team: Estonian <vdr@linuxtv.org>\n"
@@ -660,6 +660,9 @@
 msgid "File"
 msgstr "Fail"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Kaust"
 
@@ -672,6 +675,12 @@
 msgid "First day"
 msgstr "1. päev"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Kausta valik"
 
@@ -847,6 +856,12 @@
 msgid "always"
 msgstr "alati"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "Ekraanikuva"
 
@@ -934,6 +949,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Sorteerida kaustad alati ette"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Teksti sisestamine numbriklahvidega"
 
@@ -1118,6 +1136,9 @@
 msgid " (activating)"
 msgstr " (aktiveerimine)"
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1148,6 +1169,15 @@
 msgid "Can't reset CAM!"
 msgstr "CAM mooduli taaskäivitus ebaõnnestus!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "mitte peatada"
 
@@ -1178,6 +1208,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Salvestuse eluiga (päevi)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Pausi klahvi käsitlemine"
 
@@ -1277,6 +1310,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "SVDRP ooteaeg (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Kanalivahetuse ooteaeg (s)"
 
@@ -1530,6 +1572,9 @@
 msgid "Pause live video?"
 msgstr "Peatada otseülekanne?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Salvestamine käivitatud"
 
diff -ruN vdr-2.2.0/po/fi_FI.po vdr-2.3.1/po/fi_FI.po
--- vdr-2.2.0/po/fi_FI.po	2015-02-19 10:12:21.984183344 +0100
+++ vdr-2.3.1/po/fi_FI.po	2015-09-13 12:41:12.371037340 +0200
@@ -11,7 +11,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2007-08-15 15:52+0200\n"
 "Last-Translator: Matti Lehtimäki <matti.lehtimaki@gmail.com>\n"
 "Language-Team: Finnish <vdr@linuxtv.org>\n"
@@ -664,6 +664,9 @@
 msgid "File"
 msgstr "Tiedosto"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Kansio"
 
@@ -676,6 +679,12 @@
 msgid "First day"
 msgstr "1. päivä"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Valitse kansio"
 
@@ -851,6 +860,12 @@
 msgid "always"
 msgstr "aina"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "Kuvaruutunäyttö"
 
@@ -938,6 +953,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Näytä kansiot ensin"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Käytä numeronäppäimiä tekstisyötteessä"
 
@@ -1122,6 +1140,9 @@
 msgid " (activating)"
 msgstr " (aktivoidaan)"
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1152,6 +1173,15 @@
 msgid "Can't reset CAM!"
 msgstr "CA-moduulin nollaus epäonnistui!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "älä pysäytä lähetystä"
 
@@ -1182,6 +1212,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Tallenteen oletuselinikä (d)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Taukonäppäimen toiminta"
 
@@ -1281,6 +1314,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "SVDRP-komennon odotusaika (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Kanavavalinnan odotusaika (s)"
 
@@ -1534,6 +1576,9 @@
 msgid "Pause live video?"
 msgstr "Pysäytetäänkö lähetys?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Tallennus aloitettu"
 
diff -ruN vdr-2.2.0/po/fr_FR.po vdr-2.3.1/po/fr_FR.po
--- vdr-2.2.0/po/fr_FR.po	2015-02-19 10:12:22.241194200 +0100
+++ vdr-2.3.1/po/fr_FR.po	2015-09-13 12:41:12.571054105 +0200
@@ -18,7 +18,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-11 11:02+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2015-02-18 20:16+0100\n"
 "Last-Translator: Bernard Jaulin <bernard.jaulin@gmail.com>\n"
 "Language-Team: French <vdr@linuxtv.org>\n"
@@ -671,6 +671,9 @@
 msgid "File"
 msgstr "Fichier"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Dossier"
 
@@ -683,6 +686,12 @@
 msgid "First day"
 msgstr "Premier jour"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Sélectionner le dossier"
 
@@ -858,6 +867,12 @@
 msgid "always"
 msgstr "toujours"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "Affichage à l'écran"
 
@@ -945,6 +960,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Toujours trier les dossiers en premier"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Touches numériques pour caractères"
 
@@ -1129,6 +1147,9 @@
 msgid " (activating)"
 msgstr " (activation en cours)"
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1159,6 +1180,15 @@
 msgid "Can't reset CAM!"
 msgstr "Impossible de réinitialiser le CAM !"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "ne pas mettre en pause le direct"
 
@@ -1189,6 +1219,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Durée de vie par défaut (j)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Fonction de la touche Pause"
 
@@ -1288,6 +1321,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "Temps maxi SVDRP (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Prise en compte chaîne (s)"
 
@@ -1541,6 +1583,9 @@
 msgid "Pause live video?"
 msgstr "Arrêter le direct ?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "L'enregistrement a commencé"
 
diff -ruN vdr-2.2.0/po/hr_HR.po vdr-2.3.1/po/hr_HR.po
--- vdr-2.2.0/po/hr_HR.po	2015-02-19 10:12:22.335198304 +0100
+++ vdr-2.3.1/po/hr_HR.po	2015-09-13 12:41:12.626058718 +0200
@@ -9,7 +9,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2008-03-17 19:00+0100\n"
 "Last-Translator: Adrian Caval <anrxc@sysphere.org>\n"
 "Language-Team: Croatian <vdr@linuxtv.org>\n"
@@ -662,6 +662,9 @@
 msgid "File"
 msgstr "Datoteka"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr ""
 
@@ -674,6 +677,12 @@
 msgid "First day"
 msgstr "Prvi dan"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr ""
 
@@ -849,6 +858,12 @@
 msgid "always"
 msgstr "uvijek"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -936,6 +951,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr ""
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr ""
 
@@ -1120,6 +1138,9 @@
 msgid " (activating)"
 msgstr ""
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1150,6 +1171,15 @@
 msgid "Can't reset CAM!"
 msgstr "Ponovno pokretanje CAM-a neuspje�no!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr ""
 
@@ -1180,6 +1210,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Zadano trajanje (d)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr ""
 
@@ -1279,6 +1312,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "SVDRP vrijeme neaktivnosti"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Zap istje�e (s)"
 
@@ -1532,6 +1574,9 @@
 msgid "Pause live video?"
 msgstr ""
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Snimanje zapo�elo"
 
diff -ruN vdr-2.2.0/po/hu_HU.po vdr-2.3.1/po/hu_HU.po
--- vdr-2.2.0/po/hu_HU.po	2015-02-19 10:12:21.930181023 +0100
+++ vdr-2.3.1/po/hu_HU.po	2015-09-13 12:41:12.339034657 +0200
@@ -11,7 +11,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-01-30 13:14+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2015-02-13 09:36+0200\n"
 "Last-Translator: István Füley <ifuley@tigercomp.ro>\n"
 "Language-Team: Hungarian <vdr@linuxtv.org>\n"
@@ -665,6 +665,9 @@
 msgid "File"
 msgstr "Fájl"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Könyvtár"
 
@@ -677,6 +680,12 @@
 msgid "First day"
 msgstr "Első nap"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Könyvtár kiválasztása"
 
@@ -852,6 +861,12 @@
 msgid "always"
 msgstr "mindig"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -939,6 +954,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Könyvtárakat rendezd előre"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Betűk a számgombokon"
 
@@ -1123,6 +1141,9 @@
 msgid " (activating)"
 msgstr " (aktiválás)"
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1153,6 +1174,15 @@
 msgid "Can't reset CAM!"
 msgstr "CAM újraindítása nem sikerült!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "élőkép leállítása tiltva"
 
@@ -1183,6 +1213,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Alapértelmezett élettartam (n)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Szünet gomb működése"
 
@@ -1282,6 +1315,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "SVDRP szétkapcsol használat nélkül(mp)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Előző csatorna időkorlátja (mp)"
 
@@ -1535,6 +1577,9 @@
 msgid "Pause live video?"
 msgstr "Megállítsam az élő képet?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Felvétel elindítva"
 
diff -ruN vdr-2.2.0/po/it_IT.po vdr-2.3.1/po/it_IT.po
--- vdr-2.2.0/po/it_IT.po	2015-02-19 10:12:22.005184202 +0100
+++ vdr-2.3.1/po/it_IT.po	2015-09-13 12:41:12.397039515 +0200
@@ -11,7 +11,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2015-02-12 19:31+0100\n"
 "Last-Translator: Diego Pierotto <vdr-italian@tiscali.it>\n"
 "Language-Team: Italian <vdr@linuxtv.org>\n"
@@ -666,6 +666,9 @@
 msgid "File"
 msgstr "Nome"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Cartella"
 
@@ -678,6 +681,12 @@
 msgid "First day"
 msgstr "1° giorno"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Seleziona cartella"
 
@@ -853,6 +862,12 @@
 msgid "always"
 msgstr "sempre"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -940,6 +955,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Ordina sempre per prima le cartelle"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Tasti numerici per i caratteri"
 
@@ -1124,6 +1142,9 @@
 msgid " (activating)"
 msgstr " (attivazione)"
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "Accesso condizionato CAM"
 
@@ -1154,6 +1175,15 @@
 msgid "Can't reset CAM!"
 msgstr "Impossibile reimpostare il modulo CAM!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "non mettere in pausa il video dal vivo"
 
@@ -1184,6 +1214,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Scadenza predefinita (gg)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Gestione tasto Pausa"
 
@@ -1283,6 +1316,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "Scadenza SVDRP (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Scadenza Zapping (s)"
 
@@ -1536,6 +1578,9 @@
 msgid "Pause live video?"
 msgstr "Pausare video dal vivo?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Registrazione avviata"
 
diff -ruN vdr-2.2.0/po/lt_LT.po vdr-2.3.1/po/lt_LT.po
--- vdr-2.2.0/po/lt_LT.po	2015-02-19 10:12:22.079187361 +0100
+++ vdr-2.3.1/po/lt_LT.po	2015-09-13 12:41:12.459044652 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2015-02-11 14:02+0200\n"
 "Last-Translator: Valdemaras Pipiras <varas@ambernet.lt>\n"
 "Language-Team: Lithuanian <vdr@linuxtv.org>\n"
@@ -660,6 +660,9 @@
 msgid "File"
 msgstr "Failas"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Katalogas"
 
@@ -672,6 +675,12 @@
 msgid "First day"
 msgstr "Pirma diena"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Pasirinkite katalogą"
 
@@ -847,6 +856,12 @@
 msgid "always"
 msgstr "visada"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD (ekrano užsklanda)"
 
@@ -934,6 +949,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Visada pirmiau rūšiuoti katalogus"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Skaičių mygtukai simboliams"
 
@@ -1118,6 +1136,9 @@
 msgid " (activating)"
 msgstr " (aktyvuojama)"
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "Dekodavimo modulis (CAM)"
 
@@ -1148,6 +1169,15 @@
 msgid "Can't reset CAM!"
 msgstr "Negali perkrauti dekodavimo modulio!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "nepristabdyti 'gyvo' video srauto"
 
@@ -1178,6 +1208,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Numatytasis įrašo saugojimo laikas (d)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Pauzės mygtuko interpretavimas"
 
@@ -1277,6 +1310,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "SVDRP užlaikymas (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Kanalo perjungimo užlaikymas (s)"
 
@@ -1530,6 +1572,9 @@
 msgid "Pause live video?"
 msgstr "Sustabdyti 'gyvą' video srautą?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Įrašymas prasidėjo"
 
diff -ruN vdr-2.2.0/po/mk_MK.po vdr-2.3.1/po/mk_MK.po
--- vdr-2.2.0/po/mk_MK.po	2015-02-19 10:12:22.215193128 +0100
+++ vdr-2.3.1/po/mk_MK.po	2015-09-13 12:41:12.555052763 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2015-02-08 15:18+0100\n"
 "Last-Translator: Dimitar Petrovski <dimeptr@gmail.com>\n"
 "Language-Team: Macedonian <en@li.org>\n"
@@ -661,6 +661,9 @@
 msgid "File"
 msgstr "Датотека"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Директориум"
 
@@ -673,6 +676,12 @@
 msgid "First day"
 msgstr "Прв ден"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Избери директориум"
 
@@ -848,6 +857,12 @@
 msgid "always"
 msgstr "секогаш"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -935,6 +950,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Секогаш сортирај прво папки"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Нумерички копчиња за букви"
 
@@ -1119,6 +1137,9 @@
 msgid " (activating)"
 msgstr " (активирање)"
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1149,6 +1170,15 @@
 msgid "Can't reset CAM!"
 msgstr "Неуспешно рестартирање на CAM-от!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "не паузирај пренос во живо"
 
@@ -1179,6 +1209,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Стандардно веметраење (денови)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Подесување на копчето пауза"
 
@@ -1278,6 +1311,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "SVDRP тајмаут (сек)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Зап тајмаут (сек)"
 
@@ -1531,6 +1573,9 @@
 msgid "Pause live video?"
 msgstr "Паузирај пренос во живо?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Снимањето почна"
 
diff -ruN vdr-2.2.0/po/nl_NL.po vdr-2.3.1/po/nl_NL.po
--- vdr-2.2.0/po/nl_NL.po	2015-02-19 10:12:22.029185229 +0100
+++ vdr-2.3.1/po/nl_NL.po	2015-09-13 12:41:12.411040693 +0200
@@ -13,7 +13,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-11 10:51+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2015-02-10 19:43+0100\n"
 "Last-Translator: Erik Oomen <oomen.e@gmail.com>\n"
 "Language-Team: Dutch <vdr@linuxtv.org>\n"
@@ -666,6 +666,9 @@
 msgid "File"
 msgstr "Bestandnaam"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Map"
 
@@ -678,6 +681,12 @@
 msgid "First day"
 msgstr "Eerste dag"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Kies map"
 
@@ -853,6 +862,12 @@
 msgid "always"
 msgstr "altijd"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -940,6 +955,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Altijd mappen eerst sorteren"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Nummertoetsen voor karakters"
 
@@ -1124,6 +1142,9 @@
 msgid " (activating)"
 msgstr " (Activeren)"
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1154,6 +1175,15 @@
 msgid "Can't reset CAM!"
 msgstr "Kan CAM niet herstarten!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "live video niet pauzeren"
 
@@ -1184,6 +1214,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Standaard levensduur (d)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Gedrag pauze toets"
 
@@ -1283,6 +1316,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "SVDRP Timeout (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Zap timeout (s)"
 
@@ -1536,6 +1578,9 @@
 msgid "Pause live video?"
 msgstr "Pauzeer live video?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Opname is gestart!"
 
diff -ruN vdr-2.2.0/po/nn_NO.po vdr-2.3.1/po/nn_NO.po
--- vdr-2.2.0/po/nn_NO.po	2015-02-19 10:12:22.310197240 +0100
+++ vdr-2.3.1/po/nn_NO.po	2015-09-13 12:41:12.625058632 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2007-08-12 14:17+0200\n"
 "Last-Translator: Truls Slevigen <truls@slevigen.no>\n"
 "Language-Team: Norwegian Nynorsk <vdr@linuxtv.org>\n"
@@ -661,6 +661,9 @@
 msgid "File"
 msgstr "Filnavn"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr ""
 
@@ -673,6 +676,12 @@
 msgid "First day"
 msgstr "F�rste dag"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr ""
 
@@ -848,6 +857,12 @@
 msgid "always"
 msgstr ""
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -935,6 +950,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr ""
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr ""
 
@@ -1119,6 +1137,9 @@
 msgid " (activating)"
 msgstr ""
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1149,6 +1170,15 @@
 msgid "Can't reset CAM!"
 msgstr ""
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr ""
 
@@ -1179,6 +1209,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Normal levetid timer (d)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr ""
 
@@ -1278,6 +1311,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "Ubrukt SVDRP-levetid (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr ""
 
@@ -1531,6 +1573,9 @@
 msgid "Pause live video?"
 msgstr ""
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr ""
 
diff -ruN vdr-2.2.0/po/pl_PL.po vdr-2.3.1/po/pl_PL.po
--- vdr-2.2.0/po/pl_PL.po	2015-02-19 10:12:21.765173924 +0100
+++ vdr-2.3.1/po/pl_PL.po	2015-09-13 12:41:11.428958344 +0200
@@ -10,7 +10,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2015-02-12 00:59+0100\n"
 "Last-Translator: Tomasz Maciej Nowak <tmn505@gmail.com>\n"
 "Language-Team: Polish <vdr@linuxtv.org>\n"
@@ -663,6 +663,9 @@
 msgid "File"
 msgstr "Plik"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Katalog"
 
@@ -675,6 +678,12 @@
 msgid "First day"
 msgstr "Pierwszy dzie�"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Wybierz katalog"
 
@@ -850,6 +859,12 @@
 msgid "always"
 msgstr "zawsze"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -937,6 +952,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Sortuj najpierw katalogi"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Klawisze numeryczne dla liter"
 
@@ -1121,6 +1139,9 @@
 msgid " (activating)"
 msgstr " (aktywuj�)"
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1151,6 +1172,15 @@
 msgid "Can't reset CAM!"
 msgstr "Nie mo�na zresetowa� CAM!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "nie wstrzymuj transmisji na �ywo"
 
@@ -1181,6 +1211,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Domy�lny czas �ycia (d)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Obs�uga klawisza pauzy"
 
@@ -1280,6 +1313,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "Czas oczekiwania na SVDRP (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Czas oczekiwania na zap (s)"
 
@@ -1533,6 +1575,9 @@
 msgid "Pause live video?"
 msgstr "Zatrzyma� transmisj� na �ywo?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Rozpocz�to nagrywanie"
 
diff -ruN vdr-2.2.0/po/pt_PT.po vdr-2.3.1/po/pt_PT.po
--- vdr-2.2.0/po/pt_PT.po	2015-02-19 10:12:21.738172785 +0100
+++ vdr-2.3.1/po/pt_PT.po	2015-09-13 12:41:11.389955075 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2010-03-28 22:49+0100\n"
 "Last-Translator: Cris Silva <hudokkow@gmail.com>\n"
 "Language-Team: Portuguese <vdr@linuxtv.org>\n"
@@ -661,6 +661,9 @@
 msgid "File"
 msgstr "Ficheiro"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Pasta"
 
@@ -673,6 +676,12 @@
 msgid "First day"
 msgstr "1� dia"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Seleccione a pasta"
 
@@ -848,6 +857,12 @@
 msgid "always"
 msgstr "sempre"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -935,6 +950,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr ""
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Teclas num�ricas para caracteres"
 
@@ -1119,6 +1137,9 @@
 msgid " (activating)"
 msgstr ""
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1149,6 +1170,15 @@
 msgid "Can't reset CAM!"
 msgstr "Imposs�vel reiniciar a CAM!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "n�o pausar emiss�o"
 
@@ -1179,6 +1209,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Validade padr�o (d)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Comportamento da tecla Pausa"
 
@@ -1278,6 +1311,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "Tempo de espera SVDRP (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Tempo de espera entre mudan�a de canais (s)"
 
@@ -1531,6 +1573,9 @@
 msgid "Pause live video?"
 msgstr "Pausar emiss�o?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Grava��o iniciada"
 
diff -ruN vdr-2.2.0/po/ro_RO.po vdr-2.3.1/po/ro_RO.po
--- vdr-2.2.0/po/ro_RO.po	2015-02-19 10:12:21.820176167 +0100
+++ vdr-2.3.1/po/ro_RO.po	2015-09-13 12:41:12.213024094 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2015-02-11 22:26+0100\n"
 "Last-Translator: Lucian Muresan <lucianm@users.sourceforge.net>\n"
 "Language-Team: Romanian <vdr@linuxtv.org>\n"
@@ -662,6 +662,9 @@
 msgid "File"
 msgstr "Fişier"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Director"
 
@@ -674,6 +677,12 @@
 msgid "First day"
 msgstr "Prima zi"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Selectează directorul"
 
@@ -849,6 +858,12 @@
 msgid "always"
 msgstr "întotdeauna"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -936,6 +951,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Sortează întotdeauna directoarele la început"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Caractere pe tastele numerice"
 
@@ -1120,6 +1138,9 @@
 msgid " (activating)"
 msgstr " (activez)"
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1150,6 +1171,15 @@
 msgid "Can't reset CAM!"
 msgstr "Nu pot reseta CAM"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "nu înregistra emisiunea"
 
@@ -1180,6 +1210,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Timp de păstrare predefinit (zile)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Funcţia tastei 'pauză'"
 
@@ -1279,6 +1312,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "Timeout SVDRP (sec)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Interval zapping (s)"
 
@@ -1532,6 +1574,9 @@
 msgid "Pause live video?"
 msgstr "Înregistrez emisiunea?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "A început înregistrarea"
 
diff -ruN vdr-2.2.0/po/ru_RU.po vdr-2.3.1/po/ru_RU.po
--- vdr-2.2.0/po/ru_RU.po	2015-02-19 10:12:22.359199335 +0100
+++ vdr-2.3.1/po/ru_RU.po	2015-09-13 12:41:12.643060142 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2013-03-10 17:13+0100\n"
 "Last-Translator: Oleg Roitburd <oroitburd@gmail.com>\n"
 "Language-Team: Russian <vdr@linuxtv.org>\n"
@@ -661,6 +661,9 @@
 msgid "File"
 msgstr "����"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "����������"
 
@@ -673,6 +676,12 @@
 msgid "First day"
 msgstr "������ ����"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "����� ����������"
 
@@ -848,6 +857,12 @@
 msgid "always"
 msgstr "������"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "����"
 
@@ -935,6 +950,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "���������� ������ � ������ ������� �����������"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "���������� ������ ��� ��������"
 
@@ -1119,6 +1137,9 @@
 msgid " (activating)"
 msgstr ""
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "�������� ������"
 
@@ -1149,6 +1170,15 @@
 msgid "Can't reset CAM!"
 msgstr "������ ����������� CAM-������!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "�� ������������� live video"
 
@@ -1179,6 +1209,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "���� �������� ������ �� ��������� (�)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "������� ������ �����"
 
@@ -1278,6 +1311,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "�������� ������ ����. SVDRP (���)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "�������� ������������ ������ (���)"
 
@@ -1531,6 +1573,9 @@
 msgid "Pause live video?"
 msgstr "���������� live video?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "������ ������"
 
diff -ruN vdr-2.2.0/po/sk_SK.po vdr-2.3.1/po/sk_SK.po
--- vdr-2.2.0/po/sk_SK.po	2015-02-19 10:12:22.287196240 +0100
+++ vdr-2.3.1/po/sk_SK.po	2015-09-13 12:41:12.599056455 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2015-02-17 18:59+0100\n"
 "Last-Translator: Milan Hrala <hrala.milan@gmail.com>\n"
 "Language-Team: Slovak <vdr@linuxtv.org>\n"
@@ -661,6 +661,9 @@
 msgid "File"
 msgstr "S�bor"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Zlo�ka"
 
@@ -673,6 +676,12 @@
 msgid "First day"
 msgstr "Odo d�a"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Vybra� zlo�ku"
 
@@ -848,6 +857,12 @@
 msgid "always"
 msgstr "v�dy"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD (Menu na obrazovke)"
 
@@ -935,6 +950,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Zlo�ky v�dy najprv usporiada�"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "P�sa� znaky ��seln�mi tla�idlami"
 
@@ -1119,6 +1137,9 @@
 msgid " (activating)"
 msgstr " (aktivovan�)"
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM (modul podmienen�ho pr�stupu)"
 
@@ -1149,6 +1170,15 @@
 msgid "Can't reset CAM!"
 msgstr "CAM modul nejde re�tartova�!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "nepozastavova� �iv� vysielanie"
 
@@ -1179,6 +1209,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Predvolen� �ivotnos�"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Chovanie kl�vesy pozastavenia"
 
@@ -1278,6 +1311,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "�asov� limit odpovede SVDRP (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "pam�ta� predo�l� kan�l po ... (s)"
 
@@ -1531,6 +1573,9 @@
 msgid "Pause live video?"
 msgstr "Preru�i� �iv� vysielanie?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Za�alo nahr�vanie"
 
diff -ruN vdr-2.2.0/po/sl_SI.po vdr-2.3.1/po/sl_SI.po
--- vdr-2.2.0/po/sl_SI.po	2015-02-19 10:12:22.384200393 +0100
+++ vdr-2.3.1/po/sl_SI.po	2015-09-13 12:41:12.671062486 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2013-03-04 12:46+0100\n"
 "Last-Translator: Matjaz Thaler <matjaz.thaler@guest.arnes.si>\n"
 "Language-Team: Slovenian <vdr@linuxtv.org>\n"
@@ -661,6 +661,9 @@
 msgid "File"
 msgstr "Datoteka"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Direktorij"
 
@@ -673,6 +676,12 @@
 msgid "First day"
 msgstr "Prvi dan"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Izberi direkotrij"
 
@@ -848,6 +857,12 @@
 msgid "always"
 msgstr "vedno"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -935,6 +950,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr ""
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "�tevilo tipk za znake"
 
@@ -1119,6 +1137,9 @@
 msgid " (activating)"
 msgstr ""
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1149,6 +1170,15 @@
 msgid "Can't reset CAM!"
 msgstr "Ne morem resetirati CAM-a!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "ne ustavi videa v �ivo"
 
@@ -1179,6 +1209,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Privzeti �ivljenski �as (d)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Gumb za pavzo"
 
@@ -1278,6 +1311,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "SVDRP �as neaktivnosti (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "�as ugla�evanja (s)"
 
@@ -1531,6 +1573,9 @@
 msgid "Pause live video?"
 msgstr "Zaustavi video v �ivo"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Snemanje se je pri�elo"
 
diff -ruN vdr-2.2.0/po/sr_RS.po vdr-2.3.1/po/sr_RS.po
--- vdr-2.2.0/po/sr_RS.po	2015-02-19 10:12:21.793175106 +0100
+++ vdr-2.3.1/po/sr_RS.po	2015-09-13 12:41:11.462961220 +0200
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2013-03-16 15:05+0100\n"
 "Last-Translator: Zoran Turalija <zoran.turalija@gmail.com>\n"
 "Language-Team: Serbian <vdr@linuxtv.org>\n"
@@ -661,6 +661,9 @@
 msgid "File"
 msgstr "Datoteka"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Direktorijum"
 
@@ -673,6 +676,12 @@
 msgid "First day"
 msgstr "Prvi dan"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Izaberi direktorijum"
 
@@ -848,6 +857,12 @@
 msgid "always"
 msgstr "uvek"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -935,6 +950,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Uvek sortiraj direktorijume prve"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Pi�ite pomo�u numeri�kih dugmi�a (kao SMS)"
 
@@ -1119,6 +1137,9 @@
 msgid " (activating)"
 msgstr ""
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1149,6 +1170,15 @@
 msgid "Can't reset CAM!"
 msgstr "Ponovno pokretanje CAM-a neuspe�no!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "ne zaustavljaj signal u�ivo"
 
@@ -1179,6 +1209,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Podrazumevano trajanje (d)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Funkcija dugme za pauzu"
 
@@ -1278,6 +1311,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "Ga�enje SVDRP-a usled neaktivnosti (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Zap isti�e (s)"
 
@@ -1531,6 +1573,9 @@
 msgid "Pause live video?"
 msgstr "Zaustavi signal u�ivo?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Snimanje zapo�elo"
 
diff -ruN vdr-2.2.0/po/sv_SE.po vdr-2.3.1/po/sv_SE.po
--- vdr-2.2.0/po/sv_SE.po	2015-02-19 10:12:22.140189963 +0100
+++ vdr-2.3.1/po/sv_SE.po	2015-09-13 12:41:12.486046970 +0200
@@ -12,7 +12,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2015-02-12 21:58+0100\n"
 "Last-Translator: Magnus Sirvi� <sirwio@hotmail.com>\n"
 "Language-Team: Swedish <vdr@linuxtv.org>\n"
@@ -665,6 +665,9 @@
 msgid "File"
 msgstr "Filnamn"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Mapp"
 
@@ -677,6 +680,12 @@
 msgid "First day"
 msgstr "F�rsta dag"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "V�lj mapp"
 
@@ -852,6 +861,12 @@
 msgid "always"
 msgstr "alltid"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -939,6 +954,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Sortera alltid mappar f�rst"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Anv�nd sifferknappar f�r bokstavsinmatning"
 
@@ -1123,6 +1141,9 @@
 msgid " (activating)"
 msgstr " (aktiverar)"
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1153,6 +1174,15 @@
 msgid "Can't reset CAM!"
 msgstr "Kan inte �terst�lla CAM!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "pausa inte p�g�ende program"
 
@@ -1183,6 +1213,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Normal livstid (dagar)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Hantering av pausknapp"
 
@@ -1282,6 +1315,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "SVDRP-tidsgr�ns (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Zap-tidsgr�ns (s)"
 
@@ -1535,6 +1577,9 @@
 msgid "Pause live video?"
 msgstr "Pausa p�g�ende program?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Inspelningen har startat"
 
diff -ruN vdr-2.2.0/po/tr_TR.po vdr-2.3.1/po/tr_TR.po
--- vdr-2.2.0/po/tr_TR.po	2015-02-19 10:12:22.432202449 +0100
+++ vdr-2.3.1/po/tr_TR.po	2015-09-13 12:41:12.671062486 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2008-02-28 00:33+0100\n"
 "Last-Translator: Oktay Yolge�en <oktay_73@yahoo.de>\n"
 "Language-Team: Turkish <vdr@linuxtv.org>\n"
@@ -660,6 +660,9 @@
 msgid "File"
 msgstr "K�t�k"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr ""
 
@@ -672,6 +675,12 @@
 msgid "First day"
 msgstr "�lk g�n"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr ""
 
@@ -847,6 +856,12 @@
 msgid "always"
 msgstr "hep"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "OSD"
 
@@ -934,6 +949,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr ""
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr ""
 
@@ -1118,6 +1136,9 @@
 msgid " (activating)"
 msgstr ""
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM"
 
@@ -1148,6 +1169,15 @@
 msgid "Can't reset CAM!"
 msgstr "CAM s�f�rlanamad�!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr ""
 
@@ -1178,6 +1208,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Ola�an �ekim �mr� (g�n)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr ""
 
@@ -1277,6 +1310,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "SVDRP zaman a��m� (sn)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Zaping zaman a��m� (sn)"
 
@@ -1530,6 +1572,9 @@
 msgid "Pause live video?"
 msgstr ""
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Kay�t ba�land�"
 
diff -ruN vdr-2.2.0/po/uk_UA.po vdr-2.3.1/po/uk_UA.po
--- vdr-2.2.0/po/uk_UA.po	2015-02-19 10:12:22.113188803 +0100
+++ vdr-2.3.1/po/uk_UA.po	2015-09-13 12:41:12.486046969 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2015-02-13 18:14+0100\n"
 "Last-Translator: Yarema aka Knedlyk <yupadmin@gmail.com>\n"
 "Language-Team: Ukrainian <vdr@linuxtv.org>\n"
@@ -661,6 +661,9 @@
 msgid "File"
 msgstr "Файл"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "Тека"
 
@@ -673,6 +676,12 @@
 msgid "First day"
 msgstr "Перший день"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "Вибрати теку"
 
@@ -848,6 +857,12 @@
 msgid "always"
 msgstr "завжди"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "Меню"
 
@@ -935,6 +950,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "Завжди сортувати теки першими"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "Кількість клавіш для символів"
 
@@ -1119,6 +1137,9 @@
 msgid " (activating)"
 msgstr " (активування)"
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM (Умовний доступ)"
 
@@ -1149,6 +1170,15 @@
 msgid "Can't reset CAM!"
 msgstr "Помилка перезапуску CAM-модуля!"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "не призупиняти перегляд"
 
@@ -1179,6 +1209,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "Строк зберігання запису по замовчуванню (дні)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "Обробка клавіші відкладеного перегляду"
 
@@ -1278,6 +1311,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "Затримка обриву з'єднання SVDRP (сек)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Затримка переключання каналу (сек)"
 
@@ -1531,6 +1573,9 @@
 msgid "Pause live video?"
 msgstr "Призупинити перегляд?"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "Запис почався"
 
diff -ruN vdr-2.2.0/po/zh_CN.po vdr-2.3.1/po/zh_CN.po
--- vdr-2.2.0/po/zh_CN.po	2015-02-19 10:12:22.166191070 +0100
+++ vdr-2.3.1/po/zh_CN.po	2015-09-13 12:41:12.521049909 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: VDR 2.2.0\n"
 "Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
-"POT-Creation-Date: 2015-02-10 13:40+0100\n"
+"POT-Creation-Date: 2015-09-11 10:38+0200\n"
 "PO-Revision-Date: 2013-03-04 14:52+0800\n"
 "Last-Translator: NFVDR <nfvdr@live.com>\n"
 "Language-Team: Chinese (simplified) <nfvdr@live.com>\n"
@@ -662,6 +662,9 @@
 msgid "File"
 msgstr "文件"
 
+msgid "Record on"
+msgstr ""
+
 msgid "Button$Folder"
 msgstr "文件夹"
 
@@ -674,6 +677,12 @@
 msgid "First day"
 msgstr "第一天"
 
+msgid "Error while accessing remote timer"
+msgstr ""
+
+msgid "Timer has been deleted!"
+msgstr ""
+
 msgid "Select folder"
 msgstr "选择文件夹"
 
@@ -849,6 +858,12 @@
 msgid "always"
 msgstr "总是"
 
+msgid "by name"
+msgstr ""
+
+msgid "by time"
+msgstr ""
+
 msgid "OSD"
 msgstr "系统菜单设置"
 
@@ -936,6 +951,9 @@
 msgid "Setup.OSD$Always sort folders first"
 msgstr "总是排序文件夹"
 
+msgid "Setup.OSD$Default sort mode for recordings"
+msgstr ""
+
 msgid "Setup.OSD$Number keys for characters"
 msgstr "数字键的字符"
 
@@ -1120,6 +1138,9 @@
 msgid " (activating)"
 msgstr ""
 
+msgid "@ device"
+msgstr ""
+
 msgid "CAM"
 msgstr "CAM设置"
 
@@ -1150,6 +1171,15 @@
 msgid "Can't reset CAM!"
 msgstr "不能重启CAM"
 
+msgid "no instant recording"
+msgstr ""
+
+msgid "confirm instant recording"
+msgstr ""
+
+msgid "record instantly"
+msgstr ""
+
 msgid "do not pause live video"
 msgstr "取消暂停当前视频"
 
@@ -1180,6 +1210,9 @@
 msgid "Setup.Recording$Default lifetime (d)"
 msgstr "默认终身 (d)"
 
+msgid "Setup.Recording$Record key handling"
+msgstr ""
+
 msgid "Setup.Recording$Pause key handling"
 msgstr "暂停关键的处理"
 
@@ -1279,6 +1312,15 @@
 msgid "Setup.Miscellaneous$SVDRP timeout (s)"
 msgstr "SVDRP 超时 (s)"
 
+msgid "Setup.Miscellaneous$SVDRP peering"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP host name"
+msgstr ""
+
+msgid "Setup.Miscellaneous$SVDRP default host"
+msgstr ""
+
 msgid "Setup.Miscellaneous$Zap timeout (s)"
 msgstr "Zap 超时 (s)"
 
@@ -1532,6 +1574,9 @@
 msgid "Pause live video?"
 msgstr "是否暂停视频播放？"
 
+msgid "Start recording?"
+msgstr ""
+
 msgid "Recording started"
 msgstr "录像开始！"
 
diff -ruN vdr-2.2.0/positioner.c vdr-2.3.1/positioner.c
--- vdr-2.2.0/positioner.c	2015-02-14 12:54:31.000000000 +0100
+++ vdr-2.3.1/positioner.c	2015-02-14 12:54:31.000000000 +0100
@@ -8,7 +8,7 @@
  * http://www.vdr-portal.de/board17-developer/board97-vdr-core/p1154305-grundlagen-und-winkelberechnungen-f%C3%BCr-h-h-diseqc-motor-antennenanlagen
  * by Albert Danis.
  *
- * $Id: positioner.c 3.5 2015/02/14 11:54:31 kls Exp $
+ * $Id: positioner.c 4.0 2015/02/14 11:54:31 kls Exp $
  */
 
 #include "positioner.h"
diff -ruN vdr-2.2.0/positioner.h vdr-2.3.1/positioner.h
--- vdr-2.2.0/positioner.h	2013-12-28 12:15:56.000000000 +0100
+++ vdr-2.3.1/positioner.h	2013-12-28 12:15:56.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: positioner.h 3.3 2013/12/28 11:15:56 kls Exp $
+ * $Id: positioner.h 4.0 2013/12/28 11:15:56 kls Exp $
  */
 
 #ifndef __POSITIONER_H
diff -ruN vdr-2.2.0/receiver.c vdr-2.3.1/receiver.c
--- vdr-2.2.0/receiver.c	2015-01-12 15:04:31.000000000 +0100
+++ vdr-2.3.1/receiver.c	2015-01-12 15:04:31.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: receiver.c 3.3 2015/01/12 14:04:31 kls Exp $
+ * $Id: receiver.c 4.0 2015/01/12 14:04:31 kls Exp $
  */
 
 #include "receiver.h"
diff -ruN vdr-2.2.0/receiver.h vdr-2.3.1/receiver.h
--- vdr-2.2.0/receiver.h	2015-01-12 15:03:22.000000000 +0100
+++ vdr-2.3.1/receiver.h	2015-09-05 13:42:47.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: receiver.h 3.3 2015/01/12 14:03:22 kls Exp $
+ * $Id: receiver.h 4.1 2015/09/05 11:42:47 kls Exp $
  */
 
 #ifndef __RECEIVER_H
@@ -31,7 +31,7 @@
                ///< (On == true) and right after it gets detached from (On == false) a cDevice. It can be used
                ///< to do things like starting/stopping a thread.
                ///< It is guaranteed that Receive() will not be called before Activate(true).
-  virtual void Receive(uchar *Data, int Length) = 0;
+  virtual void Receive(const uchar *Data, int Length) = 0;
                ///< This function is called from the cDevice we are attached to, and
                ///< delivers one TS packet from the set of PIDs the cReceiver has requested.
                ///< The data packet must be accepted immediately, and the call must return
diff -ruN vdr-2.2.0/recorder.c vdr-2.3.1/recorder.c
--- vdr-2.2.0/recorder.c	2014-02-21 10:19:52.000000000 +0100
+++ vdr-2.3.1/recorder.c	2015-09-12 16:56:15.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: recorder.c 3.3 2014/02/21 09:19:52 kls Exp $
+ * $Id: recorder.c 4.4 2015/09/12 14:56:15 kls Exp $
  */
 
 #include "recorder.h"
@@ -106,9 +106,31 @@
      Cancel(3);
 }
 
-void cRecorder::Receive(uchar *Data, int Length)
+void cRecorder::Receive(const uchar *Data, int Length)
 {
   if (Running()) {
+     static const uchar aff[TS_SIZE - 4] = { 0xB7, 0x00,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+       0xFF, 0xFF}; // Length is always TS_SIZE!
+     if ((Data[3] & 0b00110000) == 0b00100000 && !memcmp(Data + 4, aff, sizeof(aff)))
+        return; // Adaptation Field Filler found, skipping
      int p = ringBuffer->Put(Data, Length);
      if (p != Length && Running())
         ringBuffer->ReportOverflow(Length - p);
@@ -135,7 +157,8 @@
                        if (frameDetector->FramesPerSecond() > 0 && DoubleEqual(RecordingInfo.FramesPerSecond(), DEFAULTFRAMESPERSECOND) && !DoubleEqual(RecordingInfo.FramesPerSecond(), frameDetector->FramesPerSecond())) {
                           RecordingInfo.SetFramesPerSecond(frameDetector->FramesPerSecond());
                           RecordingInfo.Write();
-                          Recordings.UpdateByName(recordingName);
+                          LOCK_RECORDINGS_WRITE;
+                          Recordings->UpdateByName(recordingName);
                           }
                        }
                     InfoWritten = true;
diff -ruN vdr-2.2.0/recorder.h vdr-2.3.1/recorder.h
--- vdr-2.2.0/recorder.h	2015-01-15 15:27:02.000000000 +0100
+++ vdr-2.3.1/recorder.h	2015-09-05 13:46:23.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: recorder.h 3.1 2015/01/15 14:27:02 kls Exp $
+ * $Id: recorder.h 4.1 2015/09/05 11:46:23 kls Exp $
  */
 
 #ifndef __RECORDER_H
@@ -35,7 +35,7 @@
        ///< member of the cReceiver class) from your own destructor in order
        ///< to properly get a call to Activate(false) when your object is
        ///< destroyed.
-  virtual void Receive(uchar *Data, int Length);
+  virtual void Receive(const uchar *Data, int Length);
   virtual void Action(void);
 public:
   cRecorder(const char *FileName, const cChannel *Channel, int Priority);
diff -ruN vdr-2.2.0/recording.c vdr-2.3.1/recording.c
--- vdr-2.2.0/recording.c	2015-02-16 08:49:14.000000000 +0100
+++ vdr-2.3.1/recording.c	2015-09-09 12:21:58.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: recording.c 3.28 2015/02/16 07:49:14 kls Exp $
+ * $Id: recording.c 4.4 2015/09/09 10:21:58 kls Exp $
  */
 
 #include "recording.h"
@@ -76,9 +76,6 @@
 bool DirectoryEncoding = false;
 int InstanceId = 0;
 
-cRecordings DeletedRecordings(true);
-static cRecordings VanishedRecordings;
-
 // --- cRemoveDeletedRecordingsThread ----------------------------------------
 
 class cRemoveDeletedRecordingsThread : public cThread {
@@ -100,8 +97,8 @@
   if (LockFile.Lock()) {
      time_t StartTime = time(NULL);
      bool deleted = false;
-     cThreadLock DeletedRecordingsLock(&DeletedRecordings);
-     for (cRecording *r = DeletedRecordings.First(); r; ) {
+     LOCK_DELETEDRECORDINGS_WRITE;
+     for (cRecording *r = DeletedRecordings->First(); r; ) {
          if (cIoThrottle::Engaged())
             return;
          if (time(NULL) - StartTime > MAXREMOVETIME)
@@ -109,14 +106,14 @@
          if (cRemote::HasKeys())
             return; // react immediately on user input
          if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) {
-            cRecording *next = DeletedRecordings.Next(r);
+            cRecording *next = DeletedRecordings->Next(r);
             r->Remove();
-            DeletedRecordings.Del(r);
+            DeletedRecordings->Del(r);
             r = next;
             deleted = true;
             continue;
             }
-         r = DeletedRecordings.Next(r);
+         r = DeletedRecordings->Next(r);
          }
      if (deleted) {
         const char *IgnoreFiles[] = { SORTMODEFILE, NULL };
@@ -134,8 +131,8 @@
   static time_t LastRemoveCheck = 0;
   if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) {
      if (!RemoveDeletedRecordingsThread.Active()) {
-        cThreadLock DeletedRecordingsLock(&DeletedRecordings);
-        for (cRecording *r = DeletedRecordings.First(); r; r = DeletedRecordings.Next(r)) {
+        LOCK_DELETEDRECORDINGS_READ;
+        for (const cRecording *r = DeletedRecordings->First(); r; r = DeletedRecordings->Next(r)) {
             if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) {
                RemoveDeletedRecordingsThread.Start();
                break;
@@ -163,37 +160,43 @@
            return;
         // Remove the oldest file that has been "deleted":
         isyslog("low disk space while recording, trying to remove a deleted recording...");
-        cThreadLock DeletedRecordingsLock(&DeletedRecordings);
-        if (DeletedRecordings.Count()) {
-           cRecording *r = DeletedRecordings.First();
-           cRecording *r0 = NULL;
-           while (r) {
-                 if (r->IsOnVideoDirectoryFileSystem()) { // only remove recordings that will actually increase the free video disk space
-                    if (!r0 || r->Start() < r0->Start())
-                       r0 = r;
-                    }
-                 r = DeletedRecordings.Next(r);
-                 }
-           if (r0) {
-              if (r0->Remove())
-                 LastFreeDiskCheck += REMOVELATENCY / Factor;
-              DeletedRecordings.Del(r0);
-              return;
-              }
-           }
-        else {
+        int NumDeletedRecordings = 0;
+        {
+          LOCK_DELETEDRECORDINGS_WRITE;
+          NumDeletedRecordings = DeletedRecordings->Count();
+          if (NumDeletedRecordings) {
+             cRecording *r = DeletedRecordings->First();
+             cRecording *r0 = NULL;
+             while (r) {
+                   if (r->IsOnVideoDirectoryFileSystem()) { // only remove recordings that will actually increase the free video disk space
+                      if (!r0 || r->Start() < r0->Start())
+                         r0 = r;
+                      }
+                   r = DeletedRecordings->Next(r);
+                   }
+             if (r0) {
+                if (r0->Remove())
+                   LastFreeDiskCheck += REMOVELATENCY / Factor;
+                DeletedRecordings->Del(r0);
+                return;
+                }
+             }
+        }
+        if (NumDeletedRecordings == 0) {
            // DeletedRecordings was empty, so to be absolutely sure there are no
            // deleted recordings we need to double check:
-           DeletedRecordings.Update(true);
-           if (DeletedRecordings.Count())
+           cRecordings::Update(true);
+           LOCK_DELETEDRECORDINGS_READ;
+           if (DeletedRecordings->Count())
               return; // the next call will actually remove it
            }
         // No "deleted" files to remove, so let's see if we can delete a recording:
         if (Priority > 0) {
            isyslog("...no deleted recording found, trying to delete an old recording...");
-           cThreadLock RecordingsLock(&Recordings);
-           if (Recordings.Count()) {
-              cRecording *r = Recordings.First();
+           LOCK_RECORDINGS_WRITE;
+           Recordings->SetExplicitModify();
+           if (Recordings->Count()) {
+              cRecording *r = Recordings->First();
               cRecording *r0 = NULL;
               while (r) {
                     if (r->IsOnVideoDirectoryFileSystem()) { // only delete recordings that will actually increase the free video disk space
@@ -209,10 +212,11 @@
                              }
                           }
                        }
-                    r = Recordings.Next(r);
+                    r = Recordings->Next(r);
                     }
               if (r0 && r0->Delete()) {
-                 Recordings.Del(r0);
+                 Recordings->Del(r0);
+                 Recordings->SetModified();
                  return;
                  }
               }
@@ -227,14 +231,6 @@
      }
 }
 
-// --- Clear vanished recordings ---------------------------------------------
-
-void ClearVanishedRecordings(void)
-{
-  cThreadLock RecordingsLock(&Recordings); // yes, it *is* Recordings!
-  VanishedRecordings.Clear();
-}
-
 // --- cResumeFile -----------------------------------------------------------
 
 cResumeFile::cResumeFile(const char *FileName, bool IsPesRecording)
@@ -309,7 +305,8 @@
            if (safe_write(f, &Index, sizeof(Index)) < 0)
               LOG_ERROR_STR(fileName);
            close(f);
-           Recordings.ResetResume(fileName);
+           LOCK_RECORDINGS_WRITE;
+           Recordings->ResetResume(fileName);
            return true;
            }
         }
@@ -318,7 +315,8 @@
         if (f) {
            fprintf(f, "I %d\n", Index);
            fclose(f);
-           Recordings.ResetResume(fileName);
+           LOCK_RECORDINGS_WRITE;
+           Recordings->ResetResume(fileName);
            }
         else
            LOG_ERROR_STR(fileName);
@@ -331,8 +329,10 @@
 void cResumeFile::Delete(void)
 {
   if (fileName) {
-     if (remove(fileName) == 0)
-        Recordings.ResetResume(fileName);
+     if (remove(fileName) == 0) {
+        LOCK_RECORDINGS_WRITE;
+        Recordings->ResetResume(fileName);
+        }
      else if (errno != ENOENT)
         LOG_ERROR_STR(fileName);
      }
@@ -787,10 +787,8 @@
            else
               break;
            }
-     if (Timer->IsSingleEvent()) {
+     if (Timer->IsSingleEvent())
         Timer->SetFile(name); // this was an instant recording, so let's set the actual data
-        Timers.SetModified();
-        }
      }
   else if (Timer->IsSingleEvent() || !Setup.UseSubtitle)
      name = strdup(Timer->File());
@@ -1017,7 +1015,7 @@
   return strcasecmp(SortName(), r->SortName());
 }
 
-bool cRecording::IsInPath(const char *Path)
+bool cRecording::IsInPath(const char *Path) const
 {
   if (isempty(Path))
      return true;
@@ -1154,20 +1152,14 @@
   return isOnVideoDirectoryFileSystem;
 }
 
-bool cRecording::HasMarks(void)
+bool cRecording::HasMarks(void) const
 {
   return access(cMarks::MarksFileName(this), F_OK) == 0;
 }
 
 bool cRecording::DeleteMarks(void)
 {
-  if (remove(cMarks::MarksFileName(this)) < 0) {
-     if (errno != ENOENT) {
-        LOG_ERROR_STR(fileName);
-        return false;
-        }
-     }
-  return true;
+  return cMarks::DeleteMarksFile(this);
 }
 
 void cRecording::ReadInfo(void)
@@ -1219,8 +1211,6 @@
         if (!WriteInfo())
            return false;
         }
-     Recordings.ChangeState();
-     Recordings.TouchUpdate();
      }
   return true;
 }
@@ -1245,8 +1235,6 @@
         }
      isOnVideoDirectoryFileSystem = -1; // it might have been moved to a different file system
      ClearSortName();
-     Recordings.ChangeState();
-     Recordings.TouchUpdate();
      }
   return true;
 }
@@ -1360,58 +1348,54 @@
   return fileSizeMB;
 }
 
-// --- cRecordings -----------------------------------------------------------
-
-cRecordings Recordings;
+// --- cVideoDirectoryScannerThread ------------------------------------------
 
-char *cRecordings::updateFileName = NULL;
+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();
+  };
 
-cRecordings::cRecordings(bool Deleted)
-:cThread("video directory scanner")
+cVideoDirectoryScannerThread::cVideoDirectoryScannerThread(cRecordings *Recordings, cRecordings *DeletedRecordings)
+:cThread("video directory scanner", true)
 {
-  deleted = Deleted;
+  recordings = Recordings;
+  deletedRecordings = DeletedRecordings;
   initial = true;
-  lastUpdate = 0;
-  state = 0;
 }
 
-cRecordings::~cRecordings()
+cVideoDirectoryScannerThread::~cVideoDirectoryScannerThread()
 {
   Cancel(3);
 }
 
-void cRecordings::Action(void)
-{
-  Refresh();
-}
-
-const char *cRecordings::UpdateFileName(void)
-{
-  if (!updateFileName)
-     updateFileName = strdup(AddDirectory(cVideoDirectory::Name(), ".update"));
-  return updateFileName;
-}
-
-void cRecordings::Refresh(bool Foreground)
+void cVideoDirectoryScannerThread::Action(void)
 {
-  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);
+  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());
 }
 
-bool cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLevel, int DirLevel)
+void cVideoDirectoryScannerThread::ScanVideoDir(const char *DirName, int LinkLevel, int DirLevel)
 {
-  bool DoChangeState = false;
   // Find any new recordings:
   cReadDir d(DirName);
   struct dirent *e;
-  while ((Foreground || Running()) && (e = d.Next()) != NULL) {
+  while (Running() && (e = d.Next()) != NULL) {
+        if (cIoThrottle::Engaged())
+           cCondWait::SleepMs(100);
         cString buffer = AddDirectory(DirName, e->d_name);
         struct stat st;
         if (lstat(buffer, &st) == 0) {
@@ -1426,57 +1410,73 @@
                  continue;
               }
            if (S_ISDIR(st.st_mode)) {
-              if (endswith(buffer, deleted ? DELEXT : RECEXT)) {
-                 if (deleted || initial || !GetByName(buffer)) {
+              cRecordings *Recordings = NULL;
+              if (endswith(buffer, RECEXT))
+                 Recordings = recordings;
+              else if (endswith(buffer, DELEXT))
+                 Recordings = deletedRecordings;
+              if (Recordings) {
+                 cStateKey StateKey;
+                 Recordings->Lock(StateKey, true);
+                 if (Recordings == deletedRecordings || initial || !Recordings->GetByName(buffer)) {
                     cRecording *r = new cRecording(buffer);
                     if (r->Name()) {
                        r->NumFrames(); // initializes the numFrames member
                        r->FileSizeMB(); // initializes the fileSizeMB member
                        r->IsOnVideoDirectoryFileSystem(); // initializes the isOnVideoDirectoryFileSystem member
-                       if (deleted)
-                          r->deleted = time(NULL);
-                       Lock();
-                       Add(r);
-                       if (initial)
-                          ChangeState();
-                       else
-                          DoChangeState = true;
-                       Unlock();
+                       if (Recordings == deletedRecordings)
+                          r->SetDeleted();
+                       Recordings->Add(r);
                        }
                     else
                        delete r;
                     }
+                 StateKey.Remove();
                  }
               else
-                 DoChangeState |= ScanVideoDir(buffer, Foreground, LinkLevel + Link, DirLevel + 1);
+                 ScanVideoDir(buffer, LinkLevel + Link, DirLevel + 1);
               }
            }
         }
   // Handle any vanished recordings:
-  if (!deleted && !initial && DirLevel == 0) {
-     for (cRecording *recording = First(); recording; ) {
-         cRecording *r = recording;
-         recording = Next(recording);
-         if (access(r->FileName(), F_OK) != 0) {
-            Lock();
-            Del(r, false);
-            VanishedRecordings.Add(r);
-            DoChangeState = true;
-            Unlock();
-            }
+  if (!initial && DirLevel == 0) {
+     cStateKey StateKey;
+     recordings->Lock(StateKey, true);
+     for (cRecording *Recording = recordings->First(); Recording; ) {
+         cRecording *r = Recording;
+         Recording = recordings->Next(Recording);
+         if (access(r->FileName(), F_OK) != 0)
+            recordings->Del(r);
          }
+     StateKey.Remove();
      }
-  if (DoChangeState && DirLevel == 0)
-     ChangeState();
-  return DoChangeState;
 }
 
-bool cRecordings::StateChanged(int &State)
+// --- cRecordings -----------------------------------------------------------
+
+cRecordings cRecordings::recordings;
+cRecordings cRecordings::deletedRecordings(true);
+char *cRecordings::updateFileName = NULL;
+cVideoDirectoryScannerThread *cRecordings::videoDirectoryScannerThread = NULL;
+time_t cRecordings::lastUpdate = 0;
+
+cRecordings::cRecordings(bool Deleted)
+:cList<cRecording>(Deleted ? "DelRecs" : "Recordings")
 {
-  int NewState = state;
-  bool Result = State != NewState;
-  State = state;
-  return Result;
+}
+
+cRecordings::~cRecordings()
+{
+  // The first one to be destructed deletes it:
+  delete videoDirectoryScannerThread;
+  videoDirectoryScannerThread = NULL;
+}
+
+const char *cRecordings::UpdateFileName(void)
+{
+  if (!updateFileName)
+     updateFileName = strdup(AddDirectory(cVideoDirectory::Name(), ".update"));
+  return updateFileName;
 }
 
 void cRecordings::TouchUpdate(void)
@@ -1495,24 +1495,24 @@
   return lastUpdate < lastModified;
 }
 
-bool cRecordings::Update(bool Wait)
+void cRecordings::Update(bool Wait)
 {
+  if (!videoDirectoryScannerThread)
+     videoDirectoryScannerThread = new cVideoDirectoryScannerThread(&recordings, &deletedRecordings);
+  lastUpdate = time(NULL); // doing this first to make sure we don't miss anything
+  videoDirectoryScannerThread->Start();
   if (Wait) {
-     Refresh(true);
-     return Count() > 0;
+     while (videoDirectoryScannerThread->Active())
+           cCondWait::SleepMs(100);
      }
-  else
-     Start();
-  return false;
 }
 
-cRecording *cRecordings::GetByName(const char *FileName)
+const cRecording *cRecordings::GetByName(const char *FileName) const
 {
   if (FileName) {
-     LOCK_THREAD;
-     for (cRecording *recording = First(); recording; recording = Next(recording)) {
-         if (strcmp(recording->FileName(), FileName) == 0)
-            return recording;
+     for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
+         if (strcmp(Recording->FileName(), FileName) == 0)
+            return Recording;
          }
      }
   return NULL;
@@ -1520,12 +1520,8 @@
 
 void cRecordings::AddByName(const char *FileName, bool TriggerUpdate)
 {
-  LOCK_THREAD;
-  cRecording *recording = GetByName(FileName);
-  if (!recording) {
-     recording = new cRecording(FileName);
-     Add(recording);
-     ChangeState();
+  if (!GetByName(FileName)) {
+     Add(new cRecording(FileName));
      if (TriggerUpdate)
         TouchUpdate();
      }
@@ -1533,58 +1529,52 @@
 
 void cRecordings::DelByName(const char *FileName)
 {
-  LOCK_THREAD;
-  cRecording *recording = GetByName(FileName);
+  cRecording *Recording = GetByName(FileName);
   cRecording *dummy = NULL;
-  if (!recording)
-     recording = dummy = new cRecording(FileName); // allows us to use a FileName that is not in the Recordings list
-  cThreadLock DeletedRecordingsLock(&DeletedRecordings);
+  if (!Recording)
+     Recording = dummy = new cRecording(FileName); // allows us to use a FileName that is not in the Recordings list
+  LOCK_DELETEDRECORDINGS_WRITE;
   if (!dummy)
-     Del(recording, false);
-  char *ext = strrchr(recording->fileName, '.');
+     Del(Recording, false);
+  char *ext = strrchr(Recording->fileName, '.');
   if (ext) {
      strncpy(ext, DELEXT, strlen(ext));
-     if (access(recording->FileName(), F_OK) == 0) {
-        recording->deleted = time(NULL);
-        DeletedRecordings.Add(recording);
-        recording = NULL; // to prevent it from being deleted below
+     if (access(Recording->FileName(), F_OK) == 0) {
+        Recording->SetDeleted();
+        DeletedRecordings->Add(Recording);
+        Recording = NULL; // to prevent it from being deleted below
         }
      }
-  delete recording;
-  ChangeState();
+  delete Recording;
   TouchUpdate();
 }
 
 void cRecordings::UpdateByName(const char *FileName)
 {
-  LOCK_THREAD;
-  cRecording *recording = GetByName(FileName);
-  if (recording)
-     recording->ReadInfo();
+  if (cRecording *Recording = GetByName(FileName))
+     Recording->ReadInfo();
 }
 
-int cRecordings::TotalFileSizeMB(void)
+int cRecordings::TotalFileSizeMB(void) const
 {
   int size = 0;
-  LOCK_THREAD;
-  for (cRecording *recording = First(); recording; recording = Next(recording)) {
-      int FileSizeMB = recording->FileSizeMB();
-      if (FileSizeMB > 0 && recording->IsOnVideoDirectoryFileSystem())
+  for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
+      int FileSizeMB = Recording->FileSizeMB();
+      if (FileSizeMB > 0 && Recording->IsOnVideoDirectoryFileSystem())
          size += FileSizeMB;
       }
   return size;
 }
 
-double cRecordings::MBperMinute(void)
+double cRecordings::MBperMinute(void) const
 {
   int size = 0;
   int length = 0;
-  LOCK_THREAD;
-  for (cRecording *recording = First(); recording; recording = Next(recording)) {
-      if (recording->IsOnVideoDirectoryFileSystem()) {
-         int FileSizeMB = recording->FileSizeMB();
+  for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
+      if (Recording->IsOnVideoDirectoryFileSystem()) {
+         int FileSizeMB = Recording->FileSizeMB();
          if (FileSizeMB > 0) {
-            int LengthInSeconds = recording->LengthInSeconds();
+            int LengthInSeconds = Recording->LengthInSeconds();
             if (LengthInSeconds > 0) {
                if (LengthInSeconds / FileSizeMB < LIMIT_SECS_PER_MB_RADIO) { // don't count radio recordings
                   size += FileSizeMB;
@@ -1597,23 +1587,21 @@
   return (size && length) ? double(size) * 60 / length : -1;
 }
 
-int cRecordings::PathIsInUse(const char *Path)
+int cRecordings::PathIsInUse(const char *Path) const
 {
-  LOCK_THREAD;
   int Use = ruNone;
-  for (cRecording *recording = First(); recording; recording = Next(recording)) {
-      if (recording->IsInPath(Path))
-         Use |= recording->IsInUse();
+  for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
+      if (Recording->IsInPath(Path))
+         Use |= Recording->IsInUse();
       }
   return Use;
 }
 
-int cRecordings::GetNumRecordingsInPath(const char *Path)
+int cRecordings::GetNumRecordingsInPath(const char *Path) const
 {
-  LOCK_THREAD;
   int n = 0;
-  for (cRecording *recording = First(); recording; recording = Next(recording)) {
-      if (recording->IsInPath(Path))
+  for (const cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
+      if (Recording->IsInPath(Path))
          n++;
       }
   return n;
@@ -1622,36 +1610,35 @@
 bool cRecordings::MoveRecordings(const char *OldPath, const char *NewPath)
 {
   if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
-     LOCK_THREAD;
      dsyslog("moving '%s' to '%s'", OldPath, NewPath);
-     for (cRecording *recording = First(); recording; recording = Next(recording)) {
-         if (recording->IsInPath(OldPath)) {
-            const char *p = recording->Name() + strlen(OldPath);
+     bool Moved = false;
+     for (cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
+         if (Recording->IsInPath(OldPath)) {
+            const char *p = Recording->Name() + strlen(OldPath);
             cString NewName = cString::sprintf("%s%s", NewPath, p);
-            if (!recording->ChangeName(NewName))
+            if (!Recording->ChangeName(NewName))
                return false;
-            ChangeState();
+            Moved = true;
             }
          }
+     if (Moved)
+        TouchUpdate();
      }
   return true;
 }
 
 void cRecordings::ResetResume(const char *ResumeFileName)
 {
-  LOCK_THREAD;
-  for (cRecording *recording = First(); recording; recording = Next(recording)) {
-      if (!ResumeFileName || strncmp(ResumeFileName, recording->FileName(), strlen(recording->FileName())) == 0)
-         recording->ResetResume();
+  for (cRecording *Recording = First(); Recording; Recording = Next(Recording)) {
+      if (!ResumeFileName || strncmp(ResumeFileName, Recording->FileName(), strlen(Recording->FileName())) == 0)
+         Recording->ResetResume();
       }
-  ChangeState();
 }
 
 void cRecordings::ClearSortNames(void)
 {
-  LOCK_THREAD;
-  for (cRecording *recording = First(); recording; recording = Next(recording))
-      recording->ClearSortName();
+  for (cRecording *Recording = First(); Recording; Recording = Next(Recording))
+      Recording->ClearSortName();
 }
 
 // --- cDirCopier ------------------------------------------------------------
@@ -1810,8 +1797,9 @@
   Cancel(3);
   if (error) {
      cVideoDirectory::RemoveVideoFile(dirNameDst);
-     Recordings.AddByName(dirNameSrc);
-     Recordings.DelByName(dirNameDst);
+     LOCK_RECORDINGS_WRITE;
+     Recordings->AddByName(dirNameSrc);
+     Recordings->DelByName(dirNameDst);
      }
 }
 
@@ -1891,17 +1879,19 @@
         copier->Start();
         }
      ClearPending();
-     Recordings.ChangeState();
+     LOCK_RECORDINGS_WRITE; // to trigger a state change
      return true;
      }
   // Clean up:
   if (CopierFinishedOk && (Usage() & ruMove) != 0) {
      cRecording Recording(FileNameSrc());
-     if (Recording.Delete())
-        Recordings.DelByName(Recording.FileName());
+     if (Recording.Delete()) {
+        LOCK_RECORDINGS_WRITE;
+        Recordings->DelByName(Recording.FileName());
+        }
      }
-  Recordings.ChangeState();
-  Recordings.TouchUpdate();
+  LOCK_RECORDINGS_WRITE; // to trigger a state change
+  Recordings->TouchUpdate();
   return false;
 }
 
@@ -1945,7 +1935,7 @@
               operations.Add(new cRecordingsHandlerEntry(Usage, FileNameSrc, FileNameDst));
               finished = false;
               Active(); // start it right away if possible
-              Recordings.ChangeState();
+              LOCK_RECORDINGS_WRITE; // to trigger a state change
               return true;
               }
            else
@@ -1967,7 +1957,7 @@
   cMutexLock MutexLock(&mutex);
   if (cRecordingsHandlerEntry *r = Get(FileName)) {
      operations.Del(r);
-     Recordings.ChangeState();
+     LOCK_RECORDINGS_WRITE; // to trigger a state change
      }
 }
 
@@ -1975,7 +1965,7 @@
 {
   cMutexLock MutexLock(&mutex);
   operations.Clear();
-  Recordings.ChangeState();
+  LOCK_RECORDINGS_WRITE; // to trigger a state change
 }
 
 int cRecordingsHandler::GetUsage(const char *FileName)
@@ -2028,7 +2018,7 @@
 
 cString cMark::ToText(void)
 {
-  return cString::sprintf("%s%s%s\n", *IndexToHMSF(position, true, framesPerSecond), Comment() ? " " : "", Comment() ? Comment() : "");
+  return cString::sprintf("%s%s%s", *IndexToHMSF(position, true, framesPerSecond), Comment() ? " " : "", Comment() ? Comment() : "");
 }
 
 bool cMark::Parse(const char *s)
@@ -2047,7 +2037,7 @@
 
 bool cMark::Save(FILE *f)
 {
-  return fprintf(f, "%s", *ToText()) > 0;
+  return fprintf(f, "%s\n", *ToText()) > 0;
 }
 
 // --- cMarks ----------------------------------------------------------------
@@ -2057,9 +2047,19 @@
   return AddDirectory(Recording->FileName(), Recording->IsPesRecording() ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX);
 }
 
+bool cMarks::DeleteMarksFile(const cRecording *Recording)
+{
+  if (remove(cMarks::MarksFileName(Recording)) < 0) {
+     if (errno != ENOENT) {
+        LOG_ERROR_STR(Recording->FileName());
+        return false;
+        }
+     }
+  return true;
+}
+
 bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool IsPesRecording)
 {
-  cMutexLock MutexLock(this);
   recordingFileName = RecordingFileName;
   fileName = AddDirectory(RecordingFileName, IsPesRecording ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX);
   framesPerSecond = FramesPerSecond;
@@ -2072,7 +2072,6 @@
 
 bool cMarks::Update(void)
 {
-  cMutexLock MutexLock(this);
   time_t t = time(NULL);
   if (t > nextUpdate && *fileName) {
      time_t LastModified = LastModifiedTime(fileName);
@@ -2104,7 +2103,6 @@
 
 bool cMarks::Save(void)
 {
-  cMutexLock MutexLock(this);
   if (cConfig<cMark>::Save()) {
      lastFileTime = LastModifiedTime(fileName);
      return true;
@@ -2114,7 +2112,6 @@
 
 void cMarks::Align(void)
 {
-  cMutexLock MutexLock(this);
   cIndexFile IndexFile(recordingFileName, false, isPesRecording);
   for (cMark *m = First(); m; m = Next(m)) {
       int p = IndexFile.GetClosestIFrame(m->Position());
@@ -2127,7 +2124,6 @@
 
 void cMarks::Sort(void)
 {
-  cMutexLock MutexLock(this);
   for (cMark *m1 = First(); m1; m1 = Next(m1)) {
       for (cMark *m2 = Next(m1); m2; m2 = Next(m2)) {
           if (m2->Position() < m1->Position()) {
@@ -2140,43 +2136,42 @@
 
 void cMarks::Add(int Position)
 {
-  cMutexLock MutexLock(this);
   cConfig<cMark>::Add(new cMark(Position, NULL, framesPerSecond));
   Sort();
 }
 
-cMark *cMarks::Get(int Position)
+const cMark *cMarks::Get(int Position) const
 {
-  for (cMark *mi = First(); mi; mi = Next(mi)) {
+  for (const cMark *mi = First(); mi; mi = Next(mi)) {
       if (mi->Position() == Position)
          return mi;
       }
   return NULL;
 }
 
-cMark *cMarks::GetPrev(int Position)
+const cMark *cMarks::GetPrev(int Position) const
 {
-  for (cMark *mi = Last(); mi; mi = Prev(mi)) {
+  for (const cMark *mi = Last(); mi; mi = Prev(mi)) {
       if (mi->Position() < Position)
          return mi;
       }
   return NULL;
 }
 
-cMark *cMarks::GetNext(int Position)
+const cMark *cMarks::GetNext(int Position) const
 {
-  for (cMark *mi = First(); mi; mi = Next(mi)) {
+  for (const cMark *mi = First(); mi; mi = Next(mi)) {
       if (mi->Position() > Position)
          return mi;
       }
   return NULL;
 }
 
-cMark *cMarks::GetNextBegin(cMark *EndMark)
+const cMark *cMarks::GetNextBegin(const cMark *EndMark) const
 {
-  cMark *BeginMark = EndMark ? Next(EndMark) : First();
+  const cMark *BeginMark = EndMark ? Next(EndMark) : First();
   if (BeginMark && EndMark && BeginMark->Position() == EndMark->Position()) {
-     while (cMark *NextMark = Next(BeginMark)) {
+     while (const cMark *NextMark = Next(BeginMark)) {
            if (BeginMark->Position() == NextMark->Position()) { // skip Begin/End at the same position
               if (!(BeginMark = Next(NextMark)))
                  break;
@@ -2188,13 +2183,13 @@
   return BeginMark;
 }
 
-cMark *cMarks::GetNextEnd(cMark *BeginMark)
+const cMark *cMarks::GetNextEnd(const cMark *BeginMark) const
 {
   if (!BeginMark)
      return NULL;
-  cMark *EndMark = Next(BeginMark);
+  const cMark *EndMark = Next(BeginMark);
   if (EndMark && BeginMark && BeginMark->Position() == EndMark->Position()) {
-     while (cMark *NextMark = Next(EndMark)) {
+     while (const cMark *NextMark = Next(EndMark)) {
            if (EndMark->Position() == NextMark->Position()) { // skip End/Begin at the same position
               if (!(EndMark = Next(NextMark)))
                  break;
@@ -2206,12 +2201,11 @@
   return EndMark;
 }
 
-int cMarks::GetNumSequences(void)
+int cMarks::GetNumSequences(void) const
 {
-  cMutexLock MutexLock(this);
   int NumSequences = 0;
-  if (cMark *BeginMark = GetNextBegin()) {
-     while (cMark *EndMark = GetNextEnd(BeginMark)) {
+  if (const cMark *BeginMark = GetNextBegin()) {
+     while (const cMark *EndMark = GetNextEnd(BeginMark)) {
            NumSequences++;
            BeginMark = GetNextBegin(EndMark);
            }
@@ -2401,7 +2395,8 @@
            if (FrameDetector.FramesPerSecond() > 0 && !DoubleEqual(RecordingInfo.FramesPerSecond(), FrameDetector.FramesPerSecond())) {
               RecordingInfo.SetFramesPerSecond(FrameDetector.FramesPerSecond());
               RecordingInfo.Write();
-              Recordings.UpdateByName(recordingName);
+              LOCK_RECORDINGS_WRITE;
+              Recordings->UpdateByName(recordingName);
               }
            }
         Skins.QueueMessage(mtInfo, tr("Index file regeneration complete"));
@@ -3018,7 +3013,7 @@
 
 // --- Recordings Sort Mode --------------------------------------------------
 
-eRecordingsSortMode RecordingsSortMode = rsmName;
+eRecordingsSortMode RecordingsSortMode = rsmTime;
 
 bool HasRecordingsSortMode(const char *Directory)
 {
@@ -3027,7 +3022,7 @@
 
 void GetRecordingsSortMode(const char *Directory)
 {
-  RecordingsSortMode = rsmName;
+  RecordingsSortMode = eRecordingsSortMode(constrain(Setup.DefaultSortModeRec, 0, int(rsmTime)));
   if (FILE *f = fopen(AddDirectory(Directory, SORTMODEFILE), "r")) {
      char buf[8];
      if (fgets(buf, sizeof(buf), f))
diff -ruN vdr-2.2.0/recording.h vdr-2.3.1/recording.h
--- vdr-2.2.0/recording.h	2015-02-07 15:29:14.000000000 +0100
+++ vdr-2.3.1/recording.h	2015-08-29 16:12:14.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: recording.h 3.8 2015/02/07 14:29:14 kls Exp $
+ * $Id: recording.h 4.3 2015/08/29 14:12:14 kls Exp $
  */
 
 #ifndef __RECORDING_H
@@ -41,7 +41,6 @@
   };
 
 void RemoveDeletedRecordings(void);
-void ClearVanishedRecordings(void);
 void AssertFreeDiskSpace(int Priority = 0, bool Force = false);
      ///< The special Priority value -1 means that we shall get rid of any
      ///< deleted recordings faster than normal (because we're cutting).
@@ -129,8 +128,9 @@
   int Priority(void) const { return priority; }
   int Lifetime(void) const { return lifetime; }
   time_t Deleted(void) const { return deleted; }
+  void SetDeleted(void) { deleted = time(NULL); }
   virtual int Compare(const cListObject &ListObject) const;
-  bool IsInPath(const char *Path);
+  bool IsInPath(const char *Path) const;
        ///< Returns true if this recording is stored anywhere under the given Path.
        ///< If Path is NULL or an empty string, the entire video directory is checked.
   cString Folder(void) const;
@@ -140,7 +140,7 @@
        ///< Returns the base name of this recording (without the
        ///< video directory and folder). For use in menus etc.
   const char *Name(void) const { return name; }
-       ///< Returns the full name of the recording (without the video directory.
+       ///< Returns the full name of the recording (without the video directory).
        ///< For use in menus etc.
   const char *FileName(void) const;
        ///< Returns the full path name to the recording directory, including the
@@ -166,7 +166,7 @@
   bool IsEdited(void) const;
   bool IsPesRecording(void) const { return isPesRecording; }
   bool IsOnVideoDirectoryFileSystem(void) const;
-  bool HasMarks(void);
+  bool HasMarks(void) const;
        ///< Returns true if this recording has any editing marks.
   bool DeleteMarks(void);
        ///< Deletes the editing marks from this recording (if any).
@@ -216,49 +216,54 @@
        ///< as in time-shift).
   };
 
-class cRecordings : public cList<cRecording>, public cThread {
+class cVideoDirectoryScannerThread;
+
+class cRecordings : public cList<cRecording> {
 private:
+  static cRecordings recordings;
+  static cRecordings deletedRecordings;
   static char *updateFileName;
-  bool deleted;
-  bool initial;
-  time_t lastUpdate;
-  int state;
-  const char *UpdateFileName(void);
-  void Refresh(bool Foreground = false);
-  bool ScanVideoDir(const char *DirName, bool Foreground = false, int LinkLevel = 0, int DirLevel = 0);
-protected:
-  void Action(void);
+  static time_t lastUpdate;
+  static cVideoDirectoryScannerThread *videoDirectoryScannerThread;
+  static const char *UpdateFileName(void);
 public:
   cRecordings(bool Deleted = false);
   virtual ~cRecordings();
-  bool Load(void) { return Update(true); }
-       ///< Loads the current list of recordings and returns true if there
-       ///< is anything in it (for compatibility with older plugins - use
-       ///< Update(true) instead).
-  bool Update(bool Wait = false);
+  static const cRecordings *GetRecordingsRead(cStateKey &StateKey, int TimeoutMs = 0) { return recordings.Lock(StateKey, false, TimeoutMs) ? &recordings : NULL; }
+       ///< Gets the list of recordings for read access.
+       ///< See cTimers::GetTimersRead() for details.
+  static cRecordings *GetRecordingsWrite(cStateKey &StateKey, int TimeoutMs = 0) { return recordings.Lock(StateKey, true, TimeoutMs) ? &recordings : NULL; }
+       ///< Gets the list of recordings for write access.
+       ///< See cTimers::GetTimersWrite() for details.
+  static const cRecordings *GetDeletedRecordingsRead(cStateKey &StateKey, int TimeoutMs = 0) { return deletedRecordings.Lock(StateKey, false, TimeoutMs) ? &deletedRecordings : NULL; }
+       ///< Gets the list of deleted recordings for read access.
+       ///< See cTimers::GetTimersRead() for details.
+  static cRecordings *GetDeletedRecordingsWrite(cStateKey &StateKey, int TimeoutMs = 0) { return deletedRecordings.Lock(StateKey, true, TimeoutMs) ? &deletedRecordings : NULL; }
+       ///< Gets the list of deleted recordings for write access.
+       ///< See cTimers::GetTimersWrite() for details.
+  static void Update(bool Wait = false);
        ///< Triggers an update of the list of recordings, which will run
        ///< as a separate thread if Wait is false. If Wait is true, the
        ///< function returns only after the update has completed.
-       ///< Returns true if Wait is true and there is anything in the list
-       ///< of recordings, false otherwise.
-  void TouchUpdate(void);
+  static void TouchUpdate(void);
        ///< Touches the '.update' file in the video directory, so that other
        ///< instances of VDR that access the same video directory can be triggered
        ///< to update their recordings list.
-  bool NeedsUpdate(void);
-  void ChangeState(void) { state++; }
-  bool StateChanged(int &State);
+       ///< This function is 'const', because it doesn't actually modify the list
+       ///< of recordings.
+  static bool NeedsUpdate(void);
   void ResetResume(const char *ResumeFileName = NULL);
   void ClearSortNames(void);
-  cRecording *GetByName(const char *FileName);
+  const cRecording *GetByName(const char *FileName) const;
+  cRecording *GetByName(const char *FileName) { return const_cast<cRecording *>(static_cast<const cRecordings *>(this)->GetByName(FileName)); }
   void AddByName(const char *FileName, bool TriggerUpdate = true);
   void DelByName(const char *FileName);
   void UpdateByName(const char *FileName);
-  int TotalFileSizeMB(void);
-  double MBperMinute(void);
+  int TotalFileSizeMB(void) const;
+  double MBperMinute(void) const;
        ///< Returns the average data rate (in MB/min) of all recordings, or -1 if
        ///< this value is unknown.
-  int PathIsInUse(const char *Path);
+  int PathIsInUse(const char *Path) const;
        ///< Checks whether any recording in the given Path is currently in use and therefore
        ///< the whole Path shall not be tampered with. Returns 0 (ruNone) if no recording
        ///< is in use.
@@ -266,7 +271,7 @@
        ///< If several recordings in the Path are currently in use, the return value will
        ///< be the combination of all individual recordings' flags.
        ///< If Path is NULL or an empty string, the entire video directory is checked.
-  int GetNumRecordingsInPath(const char *Path);
+  int GetNumRecordingsInPath(const char *Path) const;
        ///< Returns the total number of recordings in the given Path, including all
        ///< sub-folders of Path.
        ///< If Path is NULL or an empty string, the entire video directory is checked.
@@ -281,10 +286,19 @@
        ///< if all recordings have been successfully added to the RecordingsHandler.
   };
 
-/// Any access to Recordings that loops through the list of recordings
-/// needs to hold a thread lock on this object!
-extern cRecordings Recordings;
-extern cRecordings DeletedRecordings;
+// Provide lock controlled access to the list:
+
+DEF_LIST_LOCK(Recordings);
+DEF_LIST_LOCK2(Recordings, DeletedRecordings);
+
+// These macros provide a convenient way of locking the global recordings list
+// and making sure the lock is released as soon as the current scope is left
+// (note that these macros wait forever to obtain the lock!):
+
+#define LOCK_RECORDINGS_READ  USE_LIST_LOCK_READ(Recordings)
+#define LOCK_RECORDINGS_WRITE USE_LIST_LOCK_WRITE(Recordings)
+#define LOCK_DELETEDRECORDINGS_READ  USE_LIST_LOCK_READ2(Recordings, DeletedRecordings)
+#define LOCK_DELETEDRECORDINGS_WRITE USE_LIST_LOCK_WRITE2(Recordings, DeletedRecordings)
 
 class cRecordingsHandlerEntry;
 
@@ -350,7 +364,7 @@
   bool Save(FILE *f);
   };
 
-class cMarks : public cConfig<cMark>, public cMutex {
+class cMarks : public cConfig<cMark> {
 private:
   cString recordingFileName;
   cString fileName;
@@ -360,9 +374,11 @@
   time_t lastFileTime;
   time_t lastChange;
 public:
+  cMarks(void): cConfig<cMark>("Marks") {};
   static cString MarksFileName(const cRecording *Recording);
        ///< Returns the marks file name for the given Recording (regardless whether such
        ///< a file actually exists).
+  static bool DeleteMarksFile(const cRecording *Recording);
   bool Load(const char *RecordingFileName, double FramesPerSecond = DEFAULTFRAMESPERSECOND, bool IsPesRecording = false);
   bool Update(void);
   bool Save(void);
@@ -374,29 +390,35 @@
        ///< calls to Del(), or any of the functions that return a "cMark *", in case
        ///< an other thread might modifiy the list while the returned pointer is
        ///< considered valid.
-  cMark *Get(int Position);
-  cMark *GetPrev(int Position);
-  cMark *GetNext(int Position);
-  cMark *GetNextBegin(cMark *EndMark = NULL);
+  const cMark *Get(int Position) const;
+  const cMark *GetPrev(int Position) const;
+  const cMark *GetNext(int Position) const;
+  const cMark *GetNextBegin(const cMark *EndMark = NULL) const;
        ///< Returns the next "begin" mark after EndMark, skipping any marks at the
        ///< same position as EndMark. If EndMark is NULL, the first actual "begin"
        ///< will be returned (if any).
-  cMark *GetNextEnd(cMark *BeginMark);
+  const cMark *GetNextEnd(const cMark *BeginMark) const;
        ///< Returns the next "end" mark after BeginMark, skipping any marks at the
        ///< same position as BeginMark.
-  int GetNumSequences(void);
+  int GetNumSequences(void) const;
        ///< Returns the actual number of sequences to be cut from the recording.
        ///< If there is only one actual "begin" mark, and it is positioned at index
        ///< 0 (the beginning of the recording), and there is no "end" mark, the
        ///< return value is 0, which means that the result is the same as the original
        ///< recording.
+  cMark *Get(int Position) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->Get(Position)); }
+  cMark *GetPrev(int Position) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetPrev(Position)); }
+  cMark *GetNext(int Position) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetNext(Position)); }
+  cMark *GetNextBegin(const cMark *EndMark = NULL) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetNextBegin(EndMark)); }
+  cMark *GetNextEnd(const cMark *BeginMark) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetNextEnd(BeginMark)); }
   };
 
-#define RUC_BEFORERECORDING "before"
-#define RUC_STARTRECORDING  "started"
-#define RUC_AFTERRECORDING  "after"
-#define RUC_EDITEDRECORDING "edited"
-#define RUC_DELETERECORDING "deleted"
+#define RUC_BEFORERECORDING  "before"
+#define RUC_STARTRECORDING   "started"
+#define RUC_AFTERRECORDING   "after"
+#define RUC_EDITINGRECORDING "editing"
+#define RUC_EDITEDRECORDING  "edited"
+#define RUC_DELETERECORDING  "deleted"
 
 class cRecordingUserCommand {
 private:
diff -ruN vdr-2.2.0/remote.c vdr-2.3.1/remote.c
--- vdr-2.2.0/remote.c	2015-01-20 15:53:57.000000000 +0100
+++ vdr-2.3.1/remote.c	2015-01-20 15:53:57.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: remote.c 3.3 2015/01/20 14:53:57 kls Exp $
+ * $Id: remote.c 4.0 2015/01/20 14:53:57 kls Exp $
  */
 
 #include "remote.h"
diff -ruN vdr-2.2.0/remote.h vdr-2.3.1/remote.h
--- vdr-2.2.0/remote.h	2013-12-25 13:32:44.000000000 +0100
+++ vdr-2.3.1/remote.h	2013-12-25 13:32:44.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: remote.h 3.1 2013/12/25 12:32:44 kls Exp $
+ * $Id: remote.h 4.0 2013/12/25 12:32:44 kls Exp $
  */
 
 #ifndef __REMOTE_H
diff -ruN vdr-2.2.0/remux.c vdr-2.3.1/remux.c
--- vdr-2.2.0/remux.c	2015-01-14 10:57:09.000000000 +0100
+++ vdr-2.3.1/remux.c	2015-03-11 10:49:38.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: remux.c 3.9 2015/01/14 09:57:09 kls Exp $
+ * $Id: remux.c 4.1 2015/03/11 09:49:38 kls Exp $
  */
 
 #include "remux.h"
@@ -1552,7 +1552,9 @@
                        uint32_t Delta = ptsValues[0] / Div;
                        // determine frame info:
                        if (isVideo) {
-                          if (abs(Delta - 3600) <= 1)
+                          if (Delta == 3753)
+                             framesPerSecond = 24.0 / 1.001;
+                          else if (abs(Delta - 3600) <= 1)
                              framesPerSecond = 25.0;
                           else if (Delta % 3003 == 0)
                              framesPerSecond = 30.0 / 1.001;
diff -ruN vdr-2.2.0/remux.h vdr-2.3.1/remux.h
--- vdr-2.2.0/remux.h	2014-03-22 15:58:24.000000000 +0100
+++ vdr-2.3.1/remux.h	2014-03-22 15:58:24.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: remux.h 3.4 2014/03/22 14:58:24 kls Exp $
+ * $Id: remux.h 4.0 2014/03/22 14:58:24 kls Exp $
  */
 
 #ifndef __REMUX_H
diff -ruN vdr-2.2.0/ringbuffer.c vdr-2.3.1/ringbuffer.c
--- vdr-2.2.0/ringbuffer.c	2012-09-22 13:26:49.000000000 +0200
+++ vdr-2.3.1/ringbuffer.c	2012-09-22 13:26:49.000000000 +0200
@@ -7,7 +7,7 @@
  * Parts of this file were inspired by the 'ringbuffy.c' from the
  * LinuxDVB driver (see linuxtv.org).
  *
- * $Id: ringbuffer.c 3.0 2012/09/22 11:26:49 kls Exp $
+ * $Id: ringbuffer.c 4.0 2012/09/22 11:26:49 kls Exp $
  */
 
 #include "ringbuffer.h"
diff -ruN vdr-2.2.0/ringbuffer.h vdr-2.3.1/ringbuffer.h
--- vdr-2.2.0/ringbuffer.h	2013-02-16 16:20:37.000000000 +0100
+++ vdr-2.3.1/ringbuffer.h	2013-02-16 16:20:37.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: ringbuffer.h 3.0 2013/02/16 15:20:37 kls Exp $
+ * $Id: ringbuffer.h 4.0 2013/02/16 15:20:37 kls Exp $
  */
 
 #ifndef __RINGBUFFER_H
diff -ruN vdr-2.2.0/runvdr.template vdr-2.3.1/runvdr.template
--- vdr-2.2.0/runvdr.template	2015-02-05 11:28:53.000000000 +0100
+++ vdr-2.3.1/runvdr.template	2015-02-05 11:28:53.000000000 +0100
@@ -20,7 +20,7 @@
 # See the main source file 'vdr.c' for copyright information and
 # how to reach the author.
 #
-# $Id: runvdr.template 3.1 2015/02/05 10:28:53 kls Exp $
+# $Id: runvdr.template 4.0 2015/02/05 10:28:53 kls Exp $
 
 VDRPRG="./vdr"
 
diff -ruN vdr-2.2.0/sdt.c vdr-2.3.1/sdt.c
--- vdr-2.2.0/sdt.c	2015-01-04 15:33:35.000000000 +0100
+++ vdr-2.3.1/sdt.c	2015-08-02 13:33:23.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: sdt.c 3.4 2015/01/04 14:33:35 kls Exp $
+ * $Id: sdt.c 4.5 2015/08/02 11:33:23 kls Exp $
  */
 
 #include "sdt.h"
@@ -13,6 +13,11 @@
 #include "libsi/section.h"
 #include "libsi/descriptor.h"
 
+// Set to 'true' for debug output:
+static bool DebugSdt = false;
+
+#define dbgsdt(a...) if (DebugSdt) fprintf(stderr, a)
+
 // --- cSdtFilter ------------------------------------------------------------
 
 cSdtFilter::cSdtFilter(cPatFilter *PatFilter)
@@ -47,15 +52,21 @@
      return;
   if (!sectionSyncer.Sync(sdt.getVersionNumber(), sdt.getSectionNumber(), sdt.getLastSectionNumber()))
      return;
-  if (!Channels.Lock(true, 10))
+  cStateKey StateKey;
+  cChannels *Channels = cChannels::GetChannelsWrite(StateKey, 10);
+  if (!Channels) {
+     sectionSyncer.Repeat(); // let's not miss any section of the SDT
      return;
+     }
+  dbgsdt("SDT: %2d %2d %2d %s %d\n", sdt.getVersionNumber(), sdt.getSectionNumber(), sdt.getLastSectionNumber(), *cSource::ToString(source), Transponder());
+  bool ChannelsModified = false;
   SI::SDT::Service SiSdtService;
   for (SI::Loop::Iterator it; sdt.serviceLoop.getNext(SiSdtService, it); ) {
-      cChannel *channel = Channels.GetByChannelID(tChannelID(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId()));
-      if (!channel)
-         channel = Channels.GetByChannelID(tChannelID(source, 0, Transponder(), SiSdtService.getServiceId()));
-      if (channel)
-         channel->SetSeen();
+      cChannel *Channel = Channels->GetByChannelID(tChannelID(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId()));
+      if (!Channel)
+         Channel = Channels->GetByChannelID(tChannelID(source, 0, Transponder(), SiSdtService.getServiceId()));
+      if (Channel)
+         Channel->SetSeen();
 
       cLinkChannels *LinkChannels = NULL;
       SI::Descriptor *d;
@@ -93,18 +104,21 @@
                             }
                         sd->providerName.getText(ProviderNameBuf, sizeof(ProviderNameBuf));
                         char *pp = compactspace(ProviderNameBuf);
-                        if (channel) {
-                           channel->SetId(sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId());
+                        if (Channel) {
+                           ChannelsModified |= Channel->SetId(Channels, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId());
                            if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3)
-                              channel->SetName(pn, ps, pp);
+                              ChannelsModified |= Channel->SetName(pn, ps, pp);
                            // Using SiSdtService.getFreeCaMode() is no good, because some
                            // tv stations set this flag even for non-encrypted channels :-(
                            // The special value 0xFFFF was supposed to mean "unknown encryption"
                            // and would have been overwritten with real CA values later:
-                           // channel->SetCa(SiSdtService.getFreeCaMode() ? 0xFFFF : 0);
+                           // Channel->SetCa(SiSdtService.getFreeCaMode() ? 0xFFFF : 0);
                            }
                         else if (*pn && Setup.UpdateChannels >= 4) {
-                           channel = Channels.NewChannel(Channel(), pn, ps, pp, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId());
+                           dbgsdt("    %5d %5d %5d %s/%s %d %s\n", sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId(), *cSource::ToString(this->Channel()->Source()), *cSource::ToString(source), this->Channel()->Transponder(), pn);
+                           Channel = Channels->NewChannel(this->Channel(), pn, ps, pp, sdt.getOriginalNetworkId(), sdt.getTransportStreamId(), SiSdtService.getServiceId());
+                           Channel->SetSource(source); // in case this comes from a satellite with a slightly different position
+                           ChannelsModified = true;
                            patFilter->Trigger(SiSdtService.getServiceId());
                            }
                         }
@@ -117,9 +131,9 @@
             /*
             case SI::CaIdentifierDescriptorTag: {
                  SI::CaIdentifierDescriptor *cid = (SI::CaIdentifierDescriptor *)d;
-                 if (channel) {
+                 if (Channel) {
                     for (SI::Loop::Iterator it; cid->identifiers.hasNext(it); )
-                        channel->SetCa(cid->identifiers.getNext(it));
+                        Channel->SetCa(Channels, cid->identifiers.getNext(it));
                     }
                  }
                  break;
@@ -128,15 +142,17 @@
                  SI::NVODReferenceDescriptor *nrd = (SI::NVODReferenceDescriptor *)d;
                  SI::NVODReferenceDescriptor::Service Service;
                  for (SI::Loop::Iterator it; nrd->serviceLoop.getNext(Service, it); ) {
-                     cChannel *link = Channels.GetByChannelID(tChannelID(source, Service.getOriginalNetworkId(), Service.getTransportStream(), Service.getServiceId()));
+                     cChannel *link = Channels->GetByChannelID(tChannelID(source, Service.getOriginalNetworkId(), Service.getTransportStream(), Service.getServiceId()));
                      if (!link && Setup.UpdateChannels >= 4) {
-                        link = Channels.NewChannel(Channel(), "NVOD", "", "", Service.getOriginalNetworkId(), Service.getTransportStream(), Service.getServiceId());
+                        link = Channels->NewChannel(this->Channel(), "NVOD", "", "", Service.getOriginalNetworkId(), Service.getTransportStream(), Service.getServiceId());
                         patFilter->Trigger(Service.getServiceId());
+                        ChannelsModified = true;
                         }
                      if (link) {
                         if (!LinkChannels)
                            LinkChannels = new cLinkChannels;
                         LinkChannels->Add(new cLinkChannel(link));
+                        ChannelsModified = true;
                         }
                      }
                  }
@@ -146,15 +162,18 @@
           delete d;
           }
       if (LinkChannels) {
-         if (channel)
-            channel->SetLinkChannels(LinkChannels);
+         if (Channel)
+            ChannelsModified |= Channel->SetLinkChannels(LinkChannels);
          else
             delete LinkChannels;
          }
       }
   if (sdt.getSectionNumber() == sdt.getLastSectionNumber()) {
-     if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3)
-        Channels.MarkObsoleteChannels(Source(), sdt.getOriginalNetworkId(), sdt.getTransportStreamId());
+     if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3) {
+        ChannelsModified |= Channels->MarkObsoleteChannels(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId());
+        if (source != Source())
+           ChannelsModified |= Channels->MarkObsoleteChannels(Source(), sdt.getOriginalNetworkId(), sdt.getTransportStreamId());
+        }
      }
-  Channels.Unlock();
+  StateKey.Remove(ChannelsModified);
 }
diff -ruN vdr-2.2.0/sdt.h vdr-2.3.1/sdt.h
--- vdr-2.2.0/sdt.h	2014-03-10 15:40:54.000000000 +0100
+++ vdr-2.3.1/sdt.h	2014-03-10 15:40:54.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: sdt.h 3.1 2014/03/10 14:40:54 kls Exp $
+ * $Id: sdt.h 4.0 2014/03/10 14:40:54 kls Exp $
  */
 
 #ifndef __SDT_H
diff -ruN vdr-2.2.0/sections.c vdr-2.3.1/sections.c
--- vdr-2.2.0/sections.c	2015-01-14 12:35:53.000000000 +0100
+++ vdr-2.3.1/sections.c	2015-01-14 12:35:53.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: sections.c 3.1 2015/01/14 11:35:53 kls Exp $
+ * $Id: sections.c 4.0 2015/01/14 11:35:53 kls Exp $
  */
 
 #include "sections.h"
diff -ruN vdr-2.2.0/sections.h vdr-2.3.1/sections.h
--- vdr-2.2.0/sections.h	2005-08-13 13:23:55.000000000 +0200
+++ vdr-2.3.1/sections.h	2005-08-13 13:23:55.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: sections.h 3.0 2005/08/13 11:23:55 kls Exp $
+ * $Id: sections.h 4.0 2005/08/13 11:23:55 kls Exp $
  */
 
 #ifndef __SECTIONS_H
diff -ruN vdr-2.2.0/shutdown.c vdr-2.3.1/shutdown.c
--- vdr-2.2.0/shutdown.c	2013-10-02 11:02:01.000000000 +0200
+++ vdr-2.3.1/shutdown.c	2015-07-18 13:29:26.000000000 +0200
@@ -6,7 +6,7 @@
  *
  * Original version written by Udo Richter <udo_richter@gmx.de>.
  *
- * $Id: shutdown.c 3.1 2013/10/02 09:02:01 kls Exp $
+ * $Id: shutdown.c 4.1 2015/07/18 11:29:26 kls Exp $
  */
 
 #include "shutdown.h"
@@ -172,9 +172,10 @@
         return false;
      }
 
-  cTimer *timer = Timers.GetNextActiveTimer();
-  time_t Next = timer ? timer->StartTime() : 0;
-  time_t Delta = timer ? Next - time(NULL) : 0;
+  LOCK_TIMERS_READ;
+  const cTimer *Timer = Timers->GetNextActiveTimer();
+  time_t Next = Timer ? Timer->StartTime() : 0;
+  time_t Delta = Timer ? Next - time(NULL) : 0;
 
   if (cRecordControls::Active() || (Next && Delta <= 0)) {
      // VPS recordings in timer end margin may cause Delta <= 0
@@ -215,9 +216,10 @@
         return false;
      }
 
-  cTimer *timer = Timers.GetNextActiveTimer();
-  time_t Next  = timer ? timer->StartTime() : 0;
-  time_t Delta = timer ? Next - time(NULL) : 0;
+  LOCK_TIMERS_READ;
+  const cTimer *Timer = Timers->GetNextActiveTimer();
+  time_t Next = Timer ? Timer->StartTime() : 0;
+  time_t Delta = Timer ? Next - time(NULL) : 0;
 
   if (cRecordControls::Active() || (Next && Delta <= 0)) {
      // VPS recordings in timer end margin may cause Delta <= 0
@@ -233,15 +235,16 @@
 
 bool cShutdownHandler::DoShutdown(bool Force)
 {
+  LOCK_TIMERS_READ;
   time_t Now = time(NULL);
-  cTimer *timer = Timers.GetNextActiveTimer();
+  const cTimer *Timer = Timers->GetNextActiveTimer();
   cPlugin *Plugin = cPluginManager::GetNextWakeupPlugin();
 
-  time_t Next = timer ? timer->StartTime() : 0;
+  time_t Next = Timer ? Timer->StartTime() : 0;
   time_t NextPlugin = Plugin ? Plugin->WakeupTime() : 0;
   if (NextPlugin && (!Next || Next > NextPlugin)) {
      Next = NextPlugin;
-     timer = NULL;
+     Timer = NULL;
      }
   time_t Delta = Next ? Next - Now : 0;
 
@@ -250,13 +253,13 @@
         return false;
      Delta = Setup.MinEventTimeout * 60;
      Next = Now + Delta;
-     timer = NULL;
+     Timer = NULL;
      dsyslog("reboot at %s", *TimeToString(Next));
      }
 
-  if (Next && timer) {
+  if (Next && Timer) {
      dsyslog("next timer event at %s", *TimeToString(Next));
-     CallShutdownCommand(Next, timer->Channel()->Number(), timer->File(), Force);
+     CallShutdownCommand(Next, Timer->Channel()->Number(), Timer->File(), Force);
      }
   else if (Next && Plugin) {
      CallShutdownCommand(Next, 0, Plugin->Name(), Force);
diff -ruN vdr-2.2.0/shutdown.h vdr-2.3.1/shutdown.h
--- vdr-2.2.0/shutdown.h	2013-02-18 11:35:27.000000000 +0100
+++ vdr-2.3.1/shutdown.h	2013-02-18 11:35:27.000000000 +0100
@@ -6,7 +6,7 @@
  *
  * Original version written by Udo Richter <udo_richter@gmx.de>.
  *
- * $Id: shutdown.h 3.0 2013/02/18 10:35:27 kls Exp $
+ * $Id: shutdown.h 4.0 2013/02/18 10:35:27 kls Exp $
  */
 
 #ifndef __SHUTDOWN_H
diff -ruN vdr-2.2.0/skinclassic.c vdr-2.3.1/skinclassic.c
--- vdr-2.2.0/skinclassic.c	2013-03-03 16:26:09.000000000 +0100
+++ vdr-2.3.1/skinclassic.c	2013-03-03 16:26:09.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: skinclassic.c 3.0 2013/03/03 15:26:09 kls Exp $
+ * $Id: skinclassic.c 4.0 2013/03/03 15:26:09 kls Exp $
  */
 
 #include "skinclassic.h"
diff -ruN vdr-2.2.0/skinclassic.h vdr-2.3.1/skinclassic.h
--- vdr-2.2.0/skinclassic.h	2005-01-02 15:38:56.000000000 +0100
+++ vdr-2.3.1/skinclassic.h	2005-01-02 15:38:56.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: skinclassic.h 3.0 2005/01/02 14:38:56 kls Exp $
+ * $Id: skinclassic.h 4.0 2005/01/02 14:38:56 kls Exp $
  */
 
 #ifndef __SKINCLASSIC_H
diff -ruN vdr-2.2.0/skinlcars.c vdr-2.3.1/skinlcars.c
--- vdr-2.2.0/skinlcars.c	2014-06-12 10:48:15.000000000 +0200
+++ vdr-2.3.1/skinlcars.c	2015-09-01 12:07:07.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: skinlcars.c 3.8 2014/06/12 08:48:15 kls Exp $
+ * $Id: skinlcars.c 4.1 2015/09/01 10:07:07 kls Exp $
  */
 
 // "Star Trek: The Next Generation"(R) is a registered trademark of Paramount Pictures,
@@ -710,7 +710,7 @@
   int lastDiskUsageState;
   bool lastDiskAlert;
   double lastSystemLoad;
-  int lastTimersState;
+  cStateKey timersStateKey;
   time_t lastSignalDisplay;
   int lastLiveIndicatorY;
   bool lastLiveIndicatorTransferring;
@@ -775,7 +775,6 @@
   lastEvent = NULL;
   lastRecording = NULL;
   lastSeen = -1;
-  lastTimersState = -1;
   lastSignalDisplay = 0;
   lastLiveIndicatorY = -1;
   lastLiveIndicatorTransferring = false;
@@ -970,7 +969,7 @@
         xi01 = xm03;
         xi02 = xm04;
         xi03 = xm05;
-        lastTimersState = -1;
+        timersStateKey.Reset();
         DrawMainFrameLower();
         DrawMainBracket();
         DrawStatusElbows();
@@ -1237,19 +1236,22 @@
      }
   if (Event)
      osd->DrawText(xs00 + d, y + lineHeight - tinyFont->Height(), Event->Title(), ColorFg, ColorBg, tinyFont, xs03 - xs00 - 2 * d);
+  // The remote timer indicator:
+  if (Timer->Remote())
+     osd->DrawRectangle(xs00 - (lineHeight - Gap) / 2, y, xs00 - Gap - 1, y + lineHeight - 1, Timer->Recording() ? Theme.Color(clrMenuTimerRecording) : ColorBg);
   // The timer recording indicator:
-  if (Timer->Recording())
+  else if (Timer->Recording())
      osd->DrawRectangle(xs03 + Gap, y - (MultiRec ? Gap : 0), xs04 - Gap / 2 - 1, y + lineHeight - 1, Theme.Color(clrMenuTimerRecording));
 }
 
 void cSkinLCARSDisplayMenu::DrawTimers(void)
 {
-  if (Timers.Modified(lastTimersState)) {
+  if (const cTimers *Timers = cTimers::GetTimersRead(timersStateKey)) {
      deviceRecording.Clear();
      const cFont *font = cFont::GetFont(fontOsd);
-     osd->DrawRectangle(xs00, ys04, xs04 - 1, ys05 - 1, Theme.Color(clrBackground));
+     osd->DrawRectangle(xs00 - (lineHeight - Gap) / 2, ys04, xs04 - 1, ys05 - 1, Theme.Color(clrBackground));
      osd->DrawRectangle(xs07, ys04, xs13 - 1, ys05 - 1, Theme.Color(clrBackground));
-     cSortedTimers SortedTimers;
+     cSortedTimers SortedTimers(Timers);
      cVector<int> FreeDeviceSlots;
      int NumDevices = 0;
      int y = ys04;
@@ -1262,7 +1264,14 @@
                   break;
                if (const cTimer *Timer = SortedTimers[i]) {
                   if (Timer->Recording()) {
-                     if (cRecordControl *RecordControl = cRecordControls::GetRecordControl(Timer)) {
+                     if (Timer->Remote()) {
+                        if (!Device && Timer->HasFlags(tfActive)) {
+                           DrawTimer(Timer, y, false);
+                           FreeDeviceSlots.Append(y);
+                           y += lineHeight + Gap;
+                           }
+                        }
+                     else if (cRecordControl *RecordControl = cRecordControls::GetRecordControl(Timer)) {
                         if (!Device || Device == RecordControl->Device()) {
                            DrawTimer(Timer, y, NumTimers > 0);
                            NumTimers++;
@@ -1313,7 +1322,7 @@
          }
      // Total number of active timers:
      int NumTimers = 0;
-     for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
+     for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
          if (Timer->HasFlags(tfActive))
             NumTimers++;
          }
@@ -1321,6 +1330,7 @@
      osd->DrawText(xs08, ys00, itoa(NumDevices), Theme.Color(clrMenuFrameFg), frameColor, font, xs09 - xs08, ys01 - ys00, taBottom | taRight | taBorder);
      lastSignalDisplay = 0;
      initial = true; // forces redrawing of devices
+     timersStateKey.Remove();
      }
 }
 
@@ -1423,25 +1433,23 @@
      DrawSeen(0, 0);
      }
   // The current programme:
-  cSchedulesLock SchedulesLock;
-  if (const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock)) {
-     if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) {
-        const cEvent *Event = Schedule->GetPresentEvent();
-        if (initial || Event != lastEvent) {
-           DrawInfo(Event, true);
-           lastEvent = Event;
-           lastSeen = -1;
-           }
-        int Current = 0;
-        int Total = 0;
-        if (Event) {
-           time_t t = time(NULL);
-           if (t > Event->StartTime())
-              Current = t - Event->StartTime();
-           Total = Event->Duration();
-           }
-        DrawSeen(Current, Total);
+  LOCK_SCHEDULES_READ;
+  if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) {
+     const cEvent *Event = Schedule->GetPresentEvent();
+     if (initial || Event != lastEvent) {
+        DrawInfo(Event, true);
+        lastEvent = Event;
+        lastSeen = -1;
+        }
+     int Current = 0;
+     int Total = 0;
+     if (Event) {
+        time_t t = time(NULL);
+        if (t > Event->StartTime())
+           Current = t - Event->StartTime();
+        Total = Event->Duration();
         }
+     DrawSeen(Current, Total);
      }
 }
 
@@ -1731,7 +1739,8 @@
   if (MenuCategory() == mcMain) {
      cDevice *Device = cDevice::PrimaryDevice();
      if (!Device->Replaying() || Device->Transferring()) {
-        const cChannel *Channel = Channels.GetByNumber(cDevice::PrimaryDevice()->CurrentChannel());
+        LOCK_CHANNELS_READ;
+        const cChannel *Channel = Channels->GetByNumber(cDevice::PrimaryDevice()->CurrentChannel());
         DrawLive(Channel);
         }
      else if (cControl *Control = cControl::Control(true))
diff -ruN vdr-2.2.0/skinlcars.h vdr-2.3.1/skinlcars.h
--- vdr-2.2.0/skinlcars.h	2012-04-15 15:17:35.000000000 +0200
+++ vdr-2.3.1/skinlcars.h	2012-04-15 15:17:35.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: skinlcars.h 3.0 2012/04/15 13:17:35 kls Exp $
+ * $Id: skinlcars.h 4.0 2012/04/15 13:17:35 kls Exp $
  */
 
 #ifndef __SKINLCARS_H
diff -ruN vdr-2.2.0/skins.c vdr-2.3.1/skins.c
--- vdr-2.2.0/skins.c	2013-08-18 14:07:22.000000000 +0200
+++ vdr-2.3.1/skins.c	2013-08-18 14:07:22.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: skins.c 3.1 2013/08/18 12:07:22 kls Exp $
+ * $Id: skins.c 4.0 2013/08/18 12:07:22 kls Exp $
  */
 
 #include "skins.h"
diff -ruN vdr-2.2.0/skins.h vdr-2.3.1/skins.h
--- vdr-2.2.0/skins.h	2015-01-15 11:45:47.000000000 +0100
+++ vdr-2.3.1/skins.h	2015-09-10 13:19:48.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: skins.h 3.4 2015/01/15 10:45:47 kls Exp $
+ * $Id: skins.h 4.1 2015/09/10 11:19:48 kls Exp $
  */
 
 #ifndef __SKINS_H
@@ -129,6 +129,11 @@
   msmProvider
   };
 
+enum eMenuOrientation {
+  moVertical = 0,
+  moHorizontal
+  };
+
 class cSkinDisplayMenu : public cSkinDisplay {
        ///< This class implements the general purpose menu display, which is
        ///< used throughout the program to display information and let the
@@ -179,6 +184,10 @@
        ///< Sets the mode by which the items in this menu are sorted.
        ///< This is purely informative and may be used by a skin to display the
        ///< current sort mode by means of some text or symbol.
+  virtual eMenuOrientation MenuOrientation(void) { return moVertical; }
+       ///< Asks the skin for the orientation of the displayed menu.
+       ///< If menu orientation is set to horizontal, the keys left/right
+       ///< and up/down are just toggled.
   virtual void Scroll(bool Up, bool Page);
        ///< If this menu contains a text area that can be scrolled, this function
        ///< will be called to actually scroll the text. Up indicates whether the
diff -ruN vdr-2.2.0/skinsttng.c vdr-2.3.1/skinsttng.c
--- vdr-2.2.0/skinsttng.c	2013-11-15 16:33:14.000000000 +0100
+++ vdr-2.3.1/skinsttng.c	2013-11-15 16:33:14.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: skinsttng.c 3.1 2013/11/15 15:33:14 kls Exp $
+ * $Id: skinsttng.c 4.0 2013/11/15 15:33:14 kls Exp $
  */
 
 // "Star Trek: The Next Generation"(R) is a registered trademark of Paramount Pictures
diff -ruN vdr-2.2.0/skinsttng.h vdr-2.3.1/skinsttng.h
--- vdr-2.2.0/skinsttng.h	2005-01-02 15:39:29.000000000 +0100
+++ vdr-2.3.1/skinsttng.h	2005-01-02 15:39:29.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: skinsttng.h 3.0 2005/01/02 14:39:29 kls Exp $
+ * $Id: skinsttng.h 4.0 2005/01/02 14:39:29 kls Exp $
  */
 
 #ifndef __SKINSTTNG_H
diff -ruN vdr-2.2.0/sourceparams.c vdr-2.3.1/sourceparams.c
--- vdr-2.2.0/sourceparams.c	2014-03-09 13:03:09.000000000 +0100
+++ vdr-2.3.1/sourceparams.c	2015-08-02 13:56:39.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: sourceparams.c 3.1 2014/03/09 12:03:09 kls Exp $
+ * $Id: sourceparams.c 4.1 2015/08/02 11:56:39 kls Exp $
  */
 
 #include "sourceparams.h"
@@ -33,7 +33,7 @@
 
 cSourceParams SourceParams;
 
-cSourceParam *cSourceParams::Get(char Source) const
+cSourceParam *cSourceParams::Get(char Source)
 {
   for (cSourceParam *sp = First(); sp; sp = Next(sp)) {
       if (sp->Source() == Source)
diff -ruN vdr-2.2.0/sourceparams.h vdr-2.3.1/sourceparams.h
--- vdr-2.2.0/sourceparams.h	2010-02-28 12:58:03.000000000 +0100
+++ vdr-2.3.1/sourceparams.h	2015-08-02 13:56:25.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: sourceparams.h 3.0 2010/02/28 11:58:03 kls Exp $
+ * $Id: sourceparams.h 4.1 2015/08/02 11:56:25 kls Exp $
  */
 
 #ifndef __SOURCEPARAMS_H
@@ -45,7 +45,7 @@
 
 class cSourceParams : public cList<cSourceParam> {
 public:
-  cSourceParam *Get(char Source) const;
+  cSourceParam *Get(char Source);
   };
 
 extern cSourceParams SourceParams;
diff -ruN vdr-2.2.0/sources.c vdr-2.3.1/sources.c
--- vdr-2.2.0/sources.c	2014-03-09 13:05:42.000000000 +0100
+++ vdr-2.3.1/sources.c	2014-03-09 13:05:42.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: sources.c 3.6 2014/03/09 12:05:42 kls Exp $
+ * $Id: sources.c 4.0 2014/03/09 12:05:42 kls Exp $
  */
 
 #include "sources.h"
diff -ruN vdr-2.2.0/sources.h vdr-2.3.1/sources.h
--- vdr-2.2.0/sources.h	2014-03-09 12:59:49.000000000 +0100
+++ vdr-2.3.1/sources.h	2014-03-09 12:59:49.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: sources.h 3.3 2014/03/09 11:59:49 kls Exp $
+ * $Id: sources.h 4.0 2014/03/09 11:59:49 kls Exp $
  */
 
 #ifndef __SOURCES_H
diff -ruN vdr-2.2.0/spu.c vdr-2.3.1/spu.c
--- vdr-2.2.0/spu.c	2008-02-10 15:06:48.000000000 +0100
+++ vdr-2.3.1/spu.c	2008-02-10 15:06:48.000000000 +0100
@@ -6,7 +6,7 @@
  * This code is distributed under the terms and conditions of the
  * GNU GENERAL PUBLIC LICENSE. See the file COPYING for details.
  *
- * $Id: spu.c 3.0 2008/02/10 14:06:48 kls Exp $
+ * $Id: spu.c 4.0 2008/02/10 14:06:48 kls Exp $
  */
 
 #include "spu.h"
diff -ruN vdr-2.2.0/spu.h vdr-2.3.1/spu.h
--- vdr-2.2.0/spu.h	2006-04-17 14:48:55.000000000 +0200
+++ vdr-2.3.1/spu.h	2006-04-17 14:48:55.000000000 +0200
@@ -6,7 +6,7 @@
  * This code is distributed under the terms and conditions of the
  * GNU GENERAL PUBLIC LICENSE. See the file COPYING for details.
  *
- * $Id: spu.h 3.0 2006/04/17 12:48:55 kls Exp $
+ * $Id: spu.h 4.0 2006/04/17 12:48:55 kls Exp $
  */
 
 #ifndef __SPU_VDR_H
diff -ruN vdr-2.2.0/status.c vdr-2.3.1/status.c
--- vdr-2.2.0/status.c	2014-01-25 11:47:39.000000000 +0100
+++ vdr-2.3.1/status.c	2014-01-25 11:47:39.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: status.c 3.1 2014/01/25 10:47:39 kls Exp $
+ * $Id: status.c 4.0 2014/01/25 10:47:39 kls Exp $
  */
 
 #include "status.h"
diff -ruN vdr-2.2.0/status.h vdr-2.3.1/status.h
--- vdr-2.2.0/status.h	2014-01-25 11:47:39.000000000 +0100
+++ vdr-2.3.1/status.h	2015-08-02 12:34:44.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: status.h 3.1 2014/01/25 10:47:39 kls Exp $
+ * $Id: status.h 4.1 2015/08/02 10:34:44 kls Exp $
  */
 
 #ifndef __STATUS_H
@@ -15,7 +15,7 @@
 #include "player.h"
 #include "tools.h"
 
-enum eTimerChange { tcMod, tcAdd, tcDel };
+enum eTimerChange { tcMod, tcAdd, tcDel }; // tcMod is obsolete and no longer used!
 
 class cTimer;
 
@@ -29,10 +29,7 @@
                // require a retune.
   virtual void TimerChange(const cTimer *Timer, eTimerChange Change) {}
                // Indicates a change in the timer settings.
-               // If Change is tcAdd or tcDel, Timer points to the timer that has
-               // been added or will be deleted, respectively. In case of tcMod,
-               // Timer is NULL; this indicates that some timer has been changed.
-               // Note that tcAdd and tcDel are always also followed by a tcMod.
+               // Timer points to the timer that has been added or will be deleted, respectively.
   virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber, bool LiveView) {}
                // Indicates a channel switch on the given DVB device.
                // If ChannelNumber is 0, this is before the channel is being switched,
diff -ruN vdr-2.2.0/summary2info vdr-2.3.1/summary2info
--- vdr-2.2.0/summary2info	2011-12-04 15:17:35.000000000 +0100
+++ vdr-2.3.1/summary2info	2011-12-04 15:17:35.000000000 +0100
@@ -10,7 +10,7 @@
 # See the main source file 'vdr.c' for copyright information and
 # how to reach the author.
 #
-# $Id: summary2info 3.0 2011/12/04 14:17:35 kls Exp $
+# $Id: summary2info 4.0 2011/12/04 14:17:35 kls Exp $
 
 $VideoDir = $ARGV[0] || die "please provide the name of the video directory\n";
 
diff -ruN vdr-2.2.0/svdrp.c vdr-2.3.1/svdrp.c
--- vdr-2.2.0/svdrp.c	2015-01-12 12:16:27.000000000 +0100
+++ vdr-2.3.1/svdrp.c	2015-09-14 15:23:06.000000000 +0200
@@ -10,7 +10,7 @@
  * and interact with the Video Disk Recorder - or write a full featured
  * graphical interface that sits on top of an SVDRP connection.
  *
- * $Id: svdrp.c 3.6 2015/01/12 11:16:27 kls Exp $
+ * $Id: svdrp.c 4.9 2015/09/14 13:23:06 kls Exp $
  */
 
 #include "svdrp.h"
@@ -18,6 +18,7 @@
 #include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <ifaddrs.h>
 #include <netinet/in.h>
 #include <stdarg.h>
 #include <stdio.h>
@@ -33,19 +34,90 @@
 #include "keys.h"
 #include "menu.h"
 #include "plugin.h"
+#include "recording.h"
 #include "remote.h"
 #include "skins.h"
+#include "thread.h"
 #include "timers.h"
-#include "tools.h"
 #include "videodir.h"
 
+static bool DumpSVDRPDataTransfer = false;
+
+#define dbgsvdrp(a...) if (DumpSVDRPDataTransfer) fprintf(stderr, a)
+
+static int SVDRPTcpPort = 0;
+static int SVDRPUdpPort = 0;
+
+// --- cIpAddress ------------------------------------------------------------
+
+class cIpAddress {
+private:
+  cString address;
+  int port;
+  cString connection;
+public:
+  cIpAddress(void);
+  cIpAddress(const char *Address, int Port);
+  const char *Address(void) const { return address; }
+  int Port(void) const { return port; }
+  void Set(const char *Address, int Port);
+  void Set(const sockaddr *SockAddr);
+  const char *Connection(void) const { return connection; }
+  };
+
+cIpAddress::cIpAddress(void)
+{
+  Set(INADDR_ANY, 0);
+}
+
+cIpAddress::cIpAddress(const char *Address, int Port)
+{
+  Set(Address, Port);
+}
+
+void cIpAddress::Set(const char *Address, int Port)
+{
+  address = Address;
+  port = Port;
+  connection = cString::sprintf("%s:%d", *address, port);
+}
+
+void cIpAddress::Set(const sockaddr *SockAddr)
+{
+  const sockaddr_in *Addr = (sockaddr_in *)SockAddr;
+  Set(inet_ntoa(Addr->sin_addr), ntohs(Addr->sin_port));
+}
+
 // --- cSocket ---------------------------------------------------------------
 
-cSocket::cSocket(int Port, int Queue)
+#define MAXUDPBUF 1024
+
+class cSocket {
+private:
+  int port;
+  bool tcp;
+  int sock;
+  cIpAddress lastIpAddress;
+  bool IsOwnInterface(sockaddr_in *Addr);
+public:
+  cSocket(int Port, bool Tcp);
+  ~cSocket();
+  bool Listen(void);
+  bool Connect(const char *Address);
+  void Close(void);
+  int Port(void) const { return port; }
+  int Socket(void) const { return sock; }
+  static bool SendDgram(const char *Dgram, int Port, const char *Address = NULL);
+  int Accept(void);
+  cString Discover(void);
+  const cIpAddress *LastIpAddress(void) const { return &lastIpAddress; }
+  };
+
+cSocket::cSocket(int Port, bool Tcp)
 {
   port = Port;
+  tcp = Tcp;
   sock = -1;
-  queue = Queue;
 }
 
 cSocket::~cSocket()
@@ -53,6 +125,30 @@
   Close();
 }
 
+bool cSocket::IsOwnInterface(sockaddr_in *Addr)
+{
+  ifaddrs *ifaddr;
+  if (getifaddrs(&ifaddr) >= 0) {
+     bool Own = false;
+     for (ifaddrs *ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
+         if (ifa->ifa_addr) {
+            if (ifa->ifa_addr->sa_family == AF_INET) {
+               sockaddr_in *addr = (sockaddr_in *)ifa->ifa_addr;
+               if (addr->sin_addr.s_addr == Addr->sin_addr.s_addr) {
+                  Own = true;
+                  break;
+                  }
+               }
+            }
+         }
+     freeifaddrs(ifaddr);
+     return Own;
+     }
+  else
+     LOG_ERROR;
+  return false;
+}
+
 void cSocket::Close(void)
 {
   if (sock >= 0) {
@@ -61,75 +157,508 @@
      }
 }
 
-bool cSocket::Open(void)
+bool cSocket::Listen(void)
 {
   if (sock < 0) {
      // create socket:
-     sock = socket(PF_INET, SOCK_STREAM, 0);
+     sock = tcp ? socket(PF_INET, SOCK_STREAM, IPPROTO_IP) : socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
      if (sock < 0) {
         LOG_ERROR;
-        port = 0;
         return false;
         }
      // allow it to always reuse the same port:
      int ReUseAddr = 1;
      setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr, sizeof(ReUseAddr));
-     //
-     struct sockaddr_in name;
-     name.sin_family = AF_INET;
-     name.sin_port = htons(port);
-     name.sin_addr.s_addr = SVDRPhosts.LocalhostOnly() ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY);
-     if (bind(sock, (struct sockaddr *)&name, sizeof(name)) < 0) {
+     // configure port and ip:
+     sockaddr_in Addr;
+     memset(&Addr, 0, sizeof(Addr));
+     Addr.sin_family = AF_INET;
+     Addr.sin_port = htons(port);
+     Addr.sin_addr.s_addr = SVDRPhosts.LocalhostOnly() ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY);
+     if (bind(sock, (sockaddr *)&Addr, sizeof(Addr)) < 0) {
+        LOG_ERROR;
+        Close();
+        return false;
+        }
+     // make it non-blocking:
+     int Flags = fcntl(sock, F_GETFL, 0);
+     if (Flags < 0) {
+        LOG_ERROR;
+        return false;
+        }
+     Flags |= O_NONBLOCK;
+     if (fcntl(sock, F_SETFL, Flags) < 0) {
+        LOG_ERROR;
+        return false;
+        }
+     if (tcp) {
+        // listen to the socket:
+        if (listen(sock, 1) < 0) {
+           LOG_ERROR;
+           return false;
+           }
+        }
+     isyslog("SVDRP listening on port %d/%s", port, tcp ? "tcp" : "udp");
+     }
+  return true;
+}
+
+bool cSocket::Connect(const char *Address)
+{
+  if (sock < 0 && tcp) {
+     // create socket:
+     sock = socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
+     if (sock < 0) {
+        LOG_ERROR;
+        return false;
+        }
+     // configure port and ip:
+     sockaddr_in Addr;
+     memset(&Addr, 0, sizeof(Addr));
+     Addr.sin_family = AF_INET;
+     Addr.sin_port = htons(port);
+     Addr.sin_addr.s_addr = inet_addr(Address);
+     if (connect(sock, (sockaddr *)&Addr, sizeof(Addr)) < 0) {
         LOG_ERROR;
         Close();
         return false;
         }
      // make it non-blocking:
-     int oldflags = fcntl(sock, F_GETFL, 0);
-     if (oldflags < 0) {
+     int Flags = fcntl(sock, F_GETFL, 0);
+     if (Flags < 0) {
         LOG_ERROR;
         return false;
         }
-     oldflags |= O_NONBLOCK;
-     if (fcntl(sock, F_SETFL, oldflags) < 0) {
+     Flags |= O_NONBLOCK;
+     if (fcntl(sock, F_SETFL, Flags) < 0) {
         LOG_ERROR;
         return false;
         }
-     // listen to the socket:
-     if (listen(sock, queue) < 0) {
+     isyslog("SVDRP > %s:%d server connection established", Address, port);
+     return true;
+     }
+  return false;
+}
+
+bool cSocket::SendDgram(const char *Dgram, int Port, const char *Address)
+{
+  // Create a socket:
+  int Socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+  if (Socket < 0) {
+     LOG_ERROR;
+     return false;
+     }
+  if (!Address) {
+     // Enable broadcast:
+     int One = 1;
+     if (setsockopt(Socket, SOL_SOCKET, SO_BROADCAST, &One, sizeof(One)) < 0) {
         LOG_ERROR;
+        close(Socket);
         return false;
         }
      }
-  return true;
+  // Configure port and ip:
+  sockaddr_in Addr;
+  memset(&Addr, 0, sizeof(Addr));
+  Addr.sin_family = AF_INET;
+  Addr.sin_addr.s_addr = Address ? inet_addr(Address) : htonl(INADDR_BROADCAST);
+  Addr.sin_port = htons(Port);
+  // Send datagram:
+  dsyslog("SVDRP > %s:%d send dgram '%s'", inet_ntoa(Addr.sin_addr), Port, Dgram);
+  int Length = strlen(Dgram);
+  int Sent = sendto(Socket, Dgram, Length, 0, (sockaddr *)&Addr, sizeof(Addr));
+  if (Sent < 0)
+     LOG_ERROR;
+  close(Socket);
+  return Sent == Length;
 }
 
 int cSocket::Accept(void)
 {
-  if (Open()) {
-     struct sockaddr_in clientname;
-     uint size = sizeof(clientname);
-     int newsock = accept(sock, (struct sockaddr *)&clientname, &size);
-     if (newsock > 0) {
-        bool accepted = SVDRPhosts.Acceptable(clientname.sin_addr.s_addr);
-        if (!accepted) {
+  if (sock >= 0 && tcp) {
+     sockaddr_in Addr;
+     uint Size = sizeof(Addr);
+     int NewSock = accept(sock, (sockaddr *)&Addr, &Size);
+     if (NewSock >= 0) {
+        bool Accepted = SVDRPhosts.Acceptable(Addr.sin_addr.s_addr);
+        if (!Accepted) {
            const char *s = "Access denied!\n";
-           if (write(newsock, s, strlen(s)) < 0)
+           if (write(NewSock, s, strlen(s)) < 0)
               LOG_ERROR;
-           close(newsock);
-           newsock = -1;
+           close(NewSock);
+           NewSock = -1;
            }
-        isyslog("connect from %s, port %hu - %s", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port), accepted ? "accepted" : "DENIED");
+        lastIpAddress.Set((sockaddr *)&Addr);
+        isyslog("SVDRP < %s client connection %s", lastIpAddress.Connection(), Accepted ? "accepted" : "DENIED");
         }
-     else if (errno != EINTR && errno != EAGAIN)
+     else if (FATALERRNO)
         LOG_ERROR;
-     return newsock;
+     return NewSock;
      }
   return -1;
 }
 
+cString cSocket::Discover(void)
+{
+  if (sock >= 0 && !tcp) {
+     char buf[MAXUDPBUF];
+     sockaddr_in Addr;
+     uint Size = sizeof(Addr);
+     int NumBytes = recvfrom(sock, buf, sizeof(buf), 0, (sockaddr *)&Addr, &Size);
+     if (NumBytes >= 0) {
+        buf[NumBytes] = 0;
+        if (!IsOwnInterface(&Addr)) {
+           lastIpAddress.Set((sockaddr *)&Addr);
+           if (!SVDRPhosts.Acceptable(Addr.sin_addr.s_addr)) {
+              dsyslog("SVDRP < %s discovery ignored (%s)", lastIpAddress.Connection(), buf);
+              return NULL;
+              }
+           if (!startswith(buf, "SVDRP:discover")) {
+              dsyslog("SVDRP < %s discovery unrecognized (%s)", lastIpAddress.Connection(), buf);
+              return NULL;
+              }
+           isyslog("SVDRP < %s discovery received (%s)", lastIpAddress.Connection(), buf);
+           return buf;
+           }
+        }
+     else if (FATALERRNO)
+        LOG_ERROR;
+     }
+  return NULL;
+}
+
+// --- cSVDRPClient ----------------------------------------------------------
+
+class cSVDRPClient {
+private:
+  cIpAddress ipAddress;
+  cSocket socket;
+  cString serverName;
+  int timeout;
+  cTimeMs pingTime;
+  cFile file;
+  int fetchFlags;
+  void Close(void);
+public:
+  cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout);
+  ~cSVDRPClient();
+  const char *ServerName(void) const { return serverName; }
+  const char *Connection(void) const { return ipAddress.Connection(); }
+  bool HasAddress(const char *Address, int Port) const;
+  bool Send(const char *Command);
+  bool Process(cStringList *Response = NULL);
+  bool Execute(const char *Command, cStringList *Response = NULL);
+  void SetFetchFlag(eSvdrpFetchFlags Flag);
+  bool HasFetchFlag(eSvdrpFetchFlags Flag);
+  };
+
+static cPoller SVDRPClientPoller;
+
+cSVDRPClient::cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout)
+:ipAddress(Address, Port)
+,socket(Port, true)
+{
+  serverName = ServerName;
+  timeout = Timeout * 1000 * 9 / 10; // ping after 90% of timeout
+  pingTime.Set(timeout);
+  fetchFlags = sffTimers;
+  if (socket.Connect(Address)) {
+     if (file.Open(socket.Socket())) {
+        SVDRPClientPoller.Add(file, false);
+        dsyslog("SVDRP > %s client created for '%s'", ipAddress.Connection(), *serverName);
+        return;
+        }
+     }
+  esyslog("SVDRP > %s ERROR: failed to create client for '%s'", ipAddress.Connection(), *serverName);
+}
+
+cSVDRPClient::~cSVDRPClient()
+{
+  Close();
+  dsyslog("SVDRP > %s client destroyed for '%s'", ipAddress.Connection(), *serverName);
+}
+
+void cSVDRPClient::Close(void)
+{
+  if (file.IsOpen()) {
+     SVDRPClientPoller.Del(file, false);
+     file.Close();
+     socket.Close();
+     }
+}
+
+bool cSVDRPClient::HasAddress(const char *Address, int Port) const
+{
+  return strcmp(ipAddress.Address(), Address) == 0 && ipAddress.Port() == Port;
+}
+
+bool cSVDRPClient::Send(const char *Command)
+{
+  pingTime.Set(timeout);
+  dbgsvdrp("> %s: %s\n", *serverName, Command);
+  if (safe_write(file, Command, strlen(Command) + 1) < 0) {
+     LOG_ERROR;
+     return false;
+     }
+  return true;
+}
+
+bool cSVDRPClient::Process(cStringList *Response)
+{
+  if (file.IsOpen()) {
+     char input[BUFSIZ];
+     int numChars = 0;
+#define SVDRPResonseTimeout 5000 // ms
+     cTimeMs Timeout(SVDRPResonseTimeout);
+     for (;;) {
+         if (file.Ready(false)) {
+            unsigned char c;
+            int r = safe_read(file, &c, 1);
+            if (r > 0) {
+               if (c == '\n' || c == 0x00) {
+                  // strip trailing whitespace:
+                  while (numChars > 0 && strchr(" \t\r\n", input[numChars - 1]))
+                        input[--numChars] = 0;
+                  // make sure the string is terminated:
+                  input[numChars] = 0;
+                  dbgsvdrp("< %s: %s\n", *serverName, input);
+                  if (Response) {
+                     Response->Append(strdup(input));
+                     if (numChars >= 4 && input[3] != '-') // no more lines will follow
+                        break;
+                     }
+                  else {
+                     switch (atoi(input)) {
+                       case 220: if (numChars > 4) {
+                                    char *n = input + 4;
+                                    if (char *t = strchr(n, ' ')) {
+                                       *t = 0;
+                                       if (strcmp(n, serverName) != 0) {
+                                          serverName = n;
+                                          dsyslog("SVDRP < %s remote server name is '%s'", ipAddress.Connection(), *serverName);
+                                          }
+                                       }
+                                    }
+                                 break;
+                       case 221: dsyslog("SVDRP < %s remote server closed connection to '%s'", ipAddress.Connection(), *serverName);
+                                 Close();
+                                 break;
+                       }
+                     }
+                  numChars = 0;
+                  }
+               else {
+                  if (numChars >= int(sizeof(input))) {
+                     esyslog("SVDRP < %s ERROR: out of memory", ipAddress.Connection());
+                     Close();
+                     break;
+                     }
+                  input[numChars++] = c;
+                  input[numChars] = 0;
+                  }
+               Timeout.Set(SVDRPResonseTimeout);
+               }
+            else if (r <= 0) {
+               isyslog("SVDRP < %s lost connection to remote server '%s'", ipAddress.Connection(), *serverName);
+               Close();
+               }
+            }
+         else if (!Response)
+            break;
+         else if (Timeout.TimedOut()) {
+            esyslog("SVDRP < %s timeout while waiting for response from '%s'", ipAddress.Connection(), *serverName);
+            return false;
+            }
+         }
+     if (pingTime.TimedOut())
+        Execute("PING");
+     }
+  return file.IsOpen();
+}
+
+bool cSVDRPClient::Execute(const char *Command, cStringList *Response)
+{
+  if (Response)
+     Response->Clear();
+  return Send(Command) && Process(Response);
+}
+
+void cSVDRPClient::SetFetchFlag(eSvdrpFetchFlags Flags)
+{
+  fetchFlags |= Flags;
+}
+
+bool cSVDRPClient::HasFetchFlag(eSvdrpFetchFlags Flag)
+{
+  bool Result = (fetchFlags & Flag);
+  fetchFlags &= ~Flag;
+  return Result;
+}
+
+// --- cSVDRPClientHandler ---------------------------------------------------
+
+class cSVDRPClientHandler : public cThread {
+private:
+  cMutex mutex;
+  int tcpPort;
+  cSocket udpSocket;
+  cVector<cSVDRPClient *> clientConnections;
+  void HandleClientConnection(void);
+  void ProcessConnections(void);
+  cSVDRPClient *GetClientForServer(const char *ServerName);
+protected:
+  virtual void Action(void);
+public:
+  cSVDRPClientHandler(int TcpPort, int UdpPort);
+  virtual ~cSVDRPClientHandler();
+  void SendDiscover(const char *Address = NULL);
+  bool Execute(const char *ServerName, const char *Command, cStringList *Response = NULL);
+  bool GetServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlags = sffNone);
+  bool TriggerFetchingTimers(const char *ServerName);
+  };
+
+static cSVDRPClientHandler *SVDRPClientHandler = NULL;
+
+cSVDRPClientHandler::cSVDRPClientHandler(int TcpPort, int UdpPort)
+:cThread("SVDRP client handler", true)
+,udpSocket(UdpPort, false)
+{
+  tcpPort = TcpPort;
+}
+
+cSVDRPClientHandler::~cSVDRPClientHandler()
+{
+  Cancel(3);
+  for (int i = 0; i < clientConnections.Size(); i++)
+      delete clientConnections[i];
+}
+
+cSVDRPClient *cSVDRPClientHandler::GetClientForServer(const char *ServerName)
+{
+  for (int i = 0; i < clientConnections.Size(); i++) {
+      if (strcmp(clientConnections[i]->ServerName(), ServerName) == 0)
+         return clientConnections[i];
+      }
+  return NULL;
+}
+
+void cSVDRPClientHandler::SendDiscover(const char *Address)
+{
+  cMutexLock MutexLock(&mutex);
+  cString Dgram = cString::sprintf("SVDRP:discover name:%s port:%d vdrversion:%d apiversion:%d timeout:%d", Setup.SVDRPHostName, tcpPort, VDRVERSNUM, APIVERSNUM, Setup.SVDRPTimeout);
+  udpSocket.SendDgram(Dgram, udpSocket.Port(), Address);
+}
+
+void cSVDRPClientHandler::ProcessConnections(void)
+{
+  cMutexLock MutexLock(&mutex);
+  for (int i = 0; i < clientConnections.Size(); i++) {
+      if (!clientConnections[i]->Process()) {
+         delete clientConnections[i];
+         clientConnections.Remove(i);
+         i--;
+         }
+      }
+}
+
+void cSVDRPClientHandler::HandleClientConnection(void)
+{
+  cString NewDiscover = udpSocket.Discover();
+  if (*NewDiscover) {
+     cString p = strgetval(NewDiscover, "port", ':');
+     if (*p) {
+        int Port = atoi(p);
+        for (int i = 0; i < clientConnections.Size(); i++) {
+            if (clientConnections[i]->HasAddress(udpSocket.LastIpAddress()->Address(), Port)) {
+               dsyslog("SVDRP < %s connection to '%s' confirmed", clientConnections[i]->Connection(), clientConnections[i]->ServerName());
+               return;
+               }
+            }
+        cString ServerName = strgetval(NewDiscover, "name", ':');
+        if (*ServerName) {
+           cString t = strgetval(NewDiscover, "timeout", ':');
+           if (*t) {
+              int Timeout = atoi(t);
+              if (Timeout > 10) { // don't let it get too small
+                 const char *Address = udpSocket.LastIpAddress()->Address();
+                 clientConnections.Append(new cSVDRPClient(Address, Port, ServerName, Timeout));
+                 SendDiscover(Address);
+                 }
+              else
+                 esyslog("SVDRP < %s ERROR: invalid timeout (%d)", udpSocket.LastIpAddress()->Connection(), Timeout);
+              }
+           else
+              esyslog("SVDRP < %s ERROR: missing timeout", udpSocket.LastIpAddress()->Connection());
+           }
+        else
+           esyslog("SVDRP < %s ERROR: missing server name", udpSocket.LastIpAddress()->Connection());
+        }
+     else
+        esyslog("SVDRP < %s ERROR: missing port number", udpSocket.LastIpAddress()->Connection());
+     }
+}
+
+void cSVDRPClientHandler::Action(void)
+{
+  if (udpSocket.Listen()) {
+     SVDRPClientPoller.Add(udpSocket.Socket(), false);
+     SendDiscover();
+     while (Running()) {
+           SVDRPClientPoller.Poll(1000);
+           cMutexLock MutexLock(&mutex);
+           HandleClientConnection();
+           ProcessConnections();
+           }
+     SVDRPClientPoller.Del(udpSocket.Socket(), false);
+     udpSocket.Close();
+     }
+}
+
+bool cSVDRPClientHandler::Execute(const char *ServerName, const char *Command, cStringList *Response)
+{
+  cMutexLock MutexLock(&mutex);
+  if (cSVDRPClient *Client = GetClientForServer(ServerName))
+     return Client->Execute(Command, Response);
+  return false;
+}
+
+bool cSVDRPClientHandler::GetServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlag)
+{
+  cMutexLock MutexLock(&mutex);
+  ServerNames->Clear();
+  for (int i = 0; i < clientConnections.Size(); i++) {
+      cSVDRPClient *Client = clientConnections[i];
+      if (FetchFlag == sffNone || Client->HasFetchFlag(FetchFlag))
+         ServerNames->Append(strdup(Client->ServerName()));
+      }
+  return ServerNames->Size() > 0;
+}
+
+bool cSVDRPClientHandler::TriggerFetchingTimers(const char *ServerName)
+{
+  cMutexLock MutexLock(&mutex);
+  if (cSVDRPClient *Client = GetClientForServer(ServerName)) {
+     Client->SetFetchFlag(sffTimers);
+     return true;
+     }
+  return false;
+}
+
 // --- cPUTEhandler ----------------------------------------------------------
 
+class cPUTEhandler {
+private:
+  FILE *f;
+  int status;
+  const char *message;
+public:
+  cPUTEhandler(void);
+  ~cPUTEhandler();
+  bool Process(const char *s);
+  int Status(void) { return status; }
+  const char *Message(void) { return message; }
+  };
+
 cPUTEhandler::cPUTEhandler(void)
 {
   if ((f = tmpfile()) != NULL) {
@@ -175,7 +704,7 @@
   return false;
 }
 
-// --- cSVDRP ----------------------------------------------------------------
+// --- cSVDRPServer ----------------------------------------------------------
 
 #define MAXHELPTOPIC 10
 #define EITDISABLETIME 10 // seconds until EIT processing is enabled again after a CLRE command
@@ -244,7 +773,8 @@
   "    List timers. Without option, all timers are listed. Otherwise\n"
   "    only the given timer is listed. If the keyword 'id' is given, the\n"
   "    channels will be listed with their unique channel ids instead of\n"
-  "    their numbers.",
+  "    their numbers. This command lists only the timers that are defined\n"
+  "    locally on this VDR, not any remote timers from other VDRs.",
   "MESG <message>\n"
   "    Displays the given message on the OSD. The message will be queued\n"
   "    and displayed whenever this is suitable.\n",
@@ -278,6 +808,10 @@
   "    zero, this means that the timer is currently recording and has started\n"
   "    at the given time. The first value in the resulting line is the number\n"
   "    of the timer.",
+  "PING\n"
+  "    Used by peer-to-peer connections between VDRs to keep the connection\n"
+  "    from timing out. May be used at any time and simply returns a line of\n"
+  "    the form '<hostname> is alive'.",
   "PLAY <number> [ begin | <position> ]\n"
   "    Play the recording with the given number. Before a recording can be\n"
   "    played, an LSTR command must have been executed in order to retrieve\n"
@@ -298,6 +832,10 @@
   "    If 'help' is followed by a command, the detailed help for that command is\n"
   "    given. The keyword 'main' initiates a call to the main menu function of the\n"
   "    given plugin.\n",
+  "POLL timers\n"
+  "    Used by peer-to-peer connections between VDRs to inform other machines\n"
+  "    about changes to timers. The receiving VDR shall use LSTT to query the\n"
+  "    remote machine's timers and update its list of timers accordingly.\n",
   "PUTE [ file ]\n"
   "    Put data into the EPG list. The data entered has to strictly follow the\n"
   "    format defined in vdr(5) for the 'epg.data' file.  A '.' on a line\n"
@@ -317,7 +855,7 @@
   "UPDT <settings>\n"
   "    Updates a timer. Settings must be in the same format as returned\n"
   "    by the LSTT command. If a timer with the same channel, day, start\n"
-  "    and stop time does not yet exists, it will be created.",
+  "    and stop time does not yet exist, it will be created.",
   "UPDR\n"
   "    Initiates a re-read of the recordings directory, which is the SVDRP\n"
   "    equivalent to 'touch .update'.",
@@ -385,41 +923,103 @@
   return NULL;
 }
 
-char *cSVDRP::grabImageDir = NULL;
+static cString grabImageDir;
 
-cSVDRP::cSVDRP(int Port)
-:socket(Port)
+class cSVDRPServer {
+private:
+  int socket;
+  cString connection;
+  cFile file;
+  cPUTEhandler *PUTEhandler;
+  int numChars;
+  int length;
+  char *cmdLine;
+  time_t lastActivity;
+  void Close(bool SendReply = false, bool Timeout = false);
+  bool Send(const char *s, int length = -1);
+  void Reply(int Code, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
+  void PrintHelpTopics(const char **hp);
+  void CmdCHAN(const char *Option);
+  void CmdCLRE(const char *Option);
+  void CmdDELC(const char *Option);
+  void CmdDELR(const char *Option);
+  void CmdDELT(const char *Option);
+  void CmdEDIT(const char *Option);
+  void CmdGRAB(const char *Option);
+  void CmdHELP(const char *Option);
+  void CmdHITK(const char *Option);
+  void CmdLSTC(const char *Option);
+  void CmdLSTE(const char *Option);
+  void CmdLSTR(const char *Option);
+  void CmdLSTT(const char *Option);
+  void CmdMESG(const char *Option);
+  void CmdMODC(const char *Option);
+  void CmdMODT(const char *Option);
+  void CmdMOVC(const char *Option);
+  void CmdMOVR(const char *Option);
+  void CmdNEWC(const char *Option);
+  void CmdNEWT(const char *Option);
+  void CmdNEXT(const char *Option);
+  void CmdPING(const char *Option);
+  void CmdPLAY(const char *Option);
+  void CmdPLUG(const char *Option);
+  void CmdPOLL(const char *Option);
+  void CmdPUTE(const char *Option);
+  void CmdREMO(const char *Option);
+  void CmdSCAN(const char *Option);
+  void CmdSTAT(const char *Option);
+  void CmdUPDT(const char *Option);
+  void CmdUPDR(const char *Option);
+  void CmdVOLU(const char *Option);
+  void Execute(char *Cmd);
+public:
+  cSVDRPServer(int Socket, const char *Connection);
+  ~cSVDRPServer();
+  bool HasConnection(void) { return file.IsOpen(); }
+  bool Process(void);
+  };
+
+static cPoller SVDRPServerPoller;
+
+cSVDRPServer::cSVDRPServer(int Socket, const char *Connection)
 {
+  socket = Socket;
+  connection = Connection;
   PUTEhandler = NULL;
   numChars = 0;
   length = BUFSIZ;
   cmdLine = MALLOC(char, length);
-  lastActivity = 0;
-  isyslog("SVDRP listening on port %d", Port);
+  lastActivity = time(NULL);
+  if (file.Open(socket)) {
+     time_t now = time(NULL);
+     Reply(220, "%s SVDRP VideoDiskRecorder %s; %s; %s", Setup.SVDRPHostName, VDRVERSION, *TimeToString(now), cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8");
+     SVDRPServerPoller.Add(file, false);
+     }
+  dsyslog("SVDRP < %s server created", *connection);
 }
 
-cSVDRP::~cSVDRP()
+cSVDRPServer::~cSVDRPServer()
 {
   Close(true);
   free(cmdLine);
+  dsyslog("SVDRP < %s server destroyed", *connection);
 }
 
-void cSVDRP::Close(bool SendReply, bool Timeout)
+void cSVDRPServer::Close(bool SendReply, bool Timeout)
 {
   if (file.IsOpen()) {
      if (SendReply) {
-        //TODO how can we get the *full* hostname?
-        char buffer[BUFSIZ];
-        gethostname(buffer, sizeof(buffer));
-        Reply(221, "%s closing connection%s", buffer, Timeout ? " (timeout)" : "");
+        Reply(221, "%s closing connection%s", Setup.SVDRPHostName, Timeout ? " (timeout)" : "");
         }
-     isyslog("closing SVDRP connection"); //TODO store IP#???
+     isyslog("SVDRP < %s connection closed", *connection);
+     SVDRPServerPoller.Del(file, false);
      file.Close();
      DELETENULL(PUTEhandler);
      }
+  close(socket);
 }
 
-bool cSVDRP::Send(const char *s, int length)
+bool cSVDRPServer::Send(const char *s, int length)
 {
   if (length < 0)
      length = strlen(s);
@@ -431,7 +1031,7 @@
   return true;
 }
 
-void cSVDRP::Reply(int Code, const char *fmt, ...)
+void cSVDRPServer::Reply(int Code, const char *fmt, ...)
 {
   if (file.IsOpen()) {
      if (Code != 0) {
@@ -454,12 +1054,12 @@
         }
      else {
         Reply(451, "Zero return code - looks like a programming error!");
-        esyslog("SVDRP: zero return code!");
+        esyslog("SVDRP < %s zero return code!", *connection);
         }
      }
 }
 
-void cSVDRP::PrintHelpTopics(const char **hp)
+void cSVDRPServer::PrintHelpTopics(const char **hp)
 {
   int NumPages = 0;
   if (hp) {
@@ -485,14 +1085,15 @@
       }
 }
 
-void cSVDRP::CmdCHAN(const char *Option)
+void cSVDRPServer::CmdCHAN(const char *Option)
 {
+  LOCK_CHANNELS_READ;
   if (*Option) {
      int n = -1;
      int d = 0;
      if (isnumber(Option)) {
         int o = strtol(Option, NULL, 10);
-        if (o >= 1 && o <= Channels.MaxNumber())
+        if (o >= 1 && o <= cChannels::MaxNumber())
            n = o;
         }
      else if (strcmp(Option, "-") == 0) {
@@ -504,35 +1105,31 @@
         }
      else if (strcmp(Option, "+") == 0) {
         n = cDevice::CurrentChannel();
-        if (n < Channels.MaxNumber()) {
+        if (n < cChannels::MaxNumber()) {
            n++;
            d = 1;
            }
         }
+     else if (const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(Option)))
+        n = Channel->Number();
      else {
-        cChannel *channel = Channels.GetByChannelID(tChannelID::FromString(Option));
-        if (channel)
-           n = channel->Number();
-        else {
-           for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) {
-               if (!channel->GroupSep()) {
-                  if (strcasecmp(channel->Name(), Option) == 0) {
-                     n = channel->Number();
-                     break;
-                     }
+        for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
+            if (!Channel->GroupSep()) {
+               if (strcasecmp(Channel->Name(), Option) == 0) {
+                  n = Channel->Number();
+                  break;
                   }
                }
-           }
+            }
         }
      if (n < 0) {
         Reply(501, "Undefined channel \"%s\"", Option);
         return;
         }
      if (!d) {
-        cChannel *channel = Channels.GetByNumber(n);
-        if (channel) {
-           if (!cDevice::PrimaryDevice()->SwitchChannel(channel, true)) {
-              Reply(554, "Error switching to channel \"%d\"", channel->Number());
+        if (const cChannel *Channel = Channels->GetByNumber(n)) {
+           if (!cDevice::PrimaryDevice()->SwitchChannel(Channel, true)) {
+              Reply(554, "Error switching to channel \"%d\"", Channel->Number());
               return;
               }
            }
@@ -544,26 +1141,27 @@
      else
         cDevice::SwitchChannel(d);
      }
-  cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());
-  if (channel)
-     Reply(250, "%d %s", channel->Number(), channel->Name());
+  if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel()))
+     Reply(250, "%d %s", Channel->Number(), Channel->Name());
   else
      Reply(550, "Unable to find channel \"%d\"", cDevice::CurrentChannel());
 }
 
-void cSVDRP::CmdCLRE(const char *Option)
+void cSVDRPServer::CmdCLRE(const char *Option)
 {
   if (*Option) {
+     LOCK_TIMERS_WRITE;
+     LOCK_CHANNELS_READ;
      tChannelID ChannelID = tChannelID::InvalidID;
      if (isnumber(Option)) {
         int o = strtol(Option, NULL, 10);
-        if (o >= 1 && o <= Channels.MaxNumber())
-           ChannelID = Channels.GetByNumber(o)->GetChannelID();
+        if (o >= 1 && o <= cChannels::MaxNumber())
+           ChannelID = Channels->GetByNumber(o)->GetChannelID();
         }
      else {
         ChannelID = tChannelID::FromString(Option);
         if (ChannelID == tChannelID::InvalidID) {
-           for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
+           for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
                if (!Channel->GroupSep()) {
                   if (strcasecmp(Channel->Name(), Option) == 0) {
                      ChannelID = Channel->GetChannelID();
@@ -574,87 +1172,80 @@
            }
         }
      if (!(ChannelID == tChannelID::InvalidID)) {
-        cSchedulesLock SchedulesLock(true, 1000);
-        cSchedules *s = (cSchedules *)cSchedules::Schedules(SchedulesLock);
-        if (s) {
-           cSchedule *Schedule = NULL;
-           ChannelID.ClrRid();
-           for (cSchedule *p = s->First(); p; p = s->Next(p)) {
-               if (p->ChannelID() == ChannelID) {
-                  Schedule = p;
-                  break;
-                  }
+        LOCK_SCHEDULES_WRITE;
+        cSchedule *Schedule = NULL;
+        ChannelID.ClrRid();
+        for (cSchedule *p = Schedules->First(); p; p = Schedules->Next(p)) {
+            if (p->ChannelID() == ChannelID) {
+               Schedule = p;
+               break;
                }
-           if (Schedule) {
-              for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
-                  if (ChannelID == Timer->Channel()->GetChannelID().ClrRid())
-                     Timer->SetEvent(NULL);
-                  }
-              Schedule->Cleanup(INT_MAX);
-              cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME);
-              Reply(250, "EPG data of channel \"%s\" cleared", Option);
-              }
-           else {
-              Reply(550, "No EPG data found for channel \"%s\"", Option);
-              return;
-              }
+            }
+        if (Schedule) {
+           for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
+               if (ChannelID == Timer->Channel()->GetChannelID().ClrRid())
+                  Timer->SetEvent(NULL);
+               }
+           Schedule->Cleanup(INT_MAX);
+           cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME);
+           Reply(250, "EPG data of channel \"%s\" cleared", Option);
+           }
+        else {
+           Reply(550, "No EPG data found for channel \"%s\"", Option);
+           return;
            }
-        else
-           Reply(451, "Can't get EPG data");
         }
      else
         Reply(501, "Undefined channel \"%s\"", Option);
      }
   else {
+     LOCK_TIMERS_WRITE;
+     LOCK_SCHEDULES_WRITE;
+     for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer))
+         Timer->SetEvent(NULL); // processing all timers here (local *and* remote)
+     for (cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->Next(Schedule))
+         Schedule->Cleanup(INT_MAX);
      cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME);
-     if (cSchedules::ClearAll()) {
-        Reply(250, "EPG data cleared");
-        cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME);
-        }
-     else
-        Reply(451, "Error while clearing EPG data");
+     Reply(250, "EPG data cleared");
      }
 }
 
-void cSVDRP::CmdDELC(const char *Option)
+void cSVDRPServer::CmdDELC(const char *Option)
 {
   if (*Option) {
      if (isnumber(Option)) {
-        if (!Channels.BeingEdited()) {
-           cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10));
-           if (channel) {
-              for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) {
-                  if (timer->Channel() == channel) {
-                     Reply(550, "Channel \"%s\" is in use by timer %d", Option, timer->Index() + 1);
-                     return;
-                     }
-                  }
-              int CurrentChannelNr = cDevice::CurrentChannel();
-              cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
-              if (CurrentChannel && channel == CurrentChannel) {
-                 int n = Channels.GetNextNormal(CurrentChannel->Index());
-                 if (n < 0)
-                    n = Channels.GetPrevNormal(CurrentChannel->Index());
-                 CurrentChannel = Channels.Get(n);
-                 CurrentChannelNr = 0; // triggers channel switch below
-                 }
-              Channels.Del(channel);
-              Channels.ReNumber();
-              Channels.SetModified(true);
-              isyslog("channel %s deleted", Option);
-              if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
-                 if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
-                    Channels.SwitchTo(CurrentChannel->Number());
-                 else
-                    cDevice::SetCurrentChannel(CurrentChannel);
-                 }
-              Reply(250, "Channel \"%s\" deleted", Option);
+        LOCK_TIMERS_READ;
+        LOCK_CHANNELS_WRITE;
+        Channels->SetExplicitModify();
+        if (cChannel *Channel = Channels->GetByNumber(strtol(Option, NULL, 10))) {
+           if (const cTimer *Timer = Timers->UsesChannel(Channel)) {
+              Reply(550, "Channel \"%s\" is in use by timer %s", Option, *Timer->ToDescr());
+              return;
               }
-           else
-              Reply(501, "Channel \"%s\" not defined", Option);
+           int CurrentChannelNr = cDevice::CurrentChannel();
+           cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
+           if (CurrentChannel && Channel == CurrentChannel) {
+              int n = Channels->GetNextNormal(CurrentChannel->Index());
+              if (n < 0)
+                 n = Channels->GetPrevNormal(CurrentChannel->Index());
+              CurrentChannel = Channels->Get(n);
+              CurrentChannelNr = 0; // triggers channel switch below
+              }
+           Channels->Del(Channel);
+           Channels->ReNumber();
+           Channels->SetModifiedByUser();
+           Channels->SetModified();
+           isyslog("SVDRP < %s channel %s deleted", *connection, Option);
+           if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
+              if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
+                 Channels->SwitchTo(CurrentChannel->Number());
+              else
+                 cDevice::SetCurrentChannel(CurrentChannel);
+              }
+           Reply(250, "Channel \"%s\" deleted", Option);
            }
         else
-           Reply(550, "Channels are being edited - try again later");
+           Reply(501, "Channel \"%s\" not defined", Option);
         }
      else
         Reply(501, "Error in channel number \"%s\"", Option);
@@ -667,7 +1258,7 @@
 {
   cRecordControl *rc;
   if ((Reason & ruTimer) != 0 && (rc = cRecordControls::GetRecordControl(Recording->FileName())) != NULL)
-     return cString::sprintf("Recording \"%s\" is in use by timer %d", RecordingId, rc->Timer()->Index() + 1);
+     return cString::sprintf("Recording \"%s\" is in use by timer %d", RecordingId, rc->Timer()->Id());
   else if ((Reason & ruReplay) != 0)
      return cString::sprintf("Recording \"%s\" is being replayed", RecordingId);
   else if ((Reason & ruCut) != 0)
@@ -679,25 +1270,27 @@
   return NULL;
 }
 
-void cSVDRP::CmdDELR(const char *Option)
+void cSVDRPServer::CmdDELR(const char *Option)
 {
   if (*Option) {
      if (isnumber(Option)) {
-        cRecording *recording = recordings.Get(strtol(Option, NULL, 10) - 1);
-        if (recording) {
-           if (int RecordingInUse = recording->IsInUse())
-              Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, recording));
+        LOCK_RECORDINGS_WRITE;
+        Recordings->SetExplicitModify();
+        if (cRecording *Recording = Recordings->Get(strtol(Option, NULL, 10) - 1)) {
+           if (int RecordingInUse = Recording->IsInUse())
+              Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
            else {
-              if (recording->Delete()) {
+              if (Recording->Delete()) {
+                 Recordings->DelByName(Recording->FileName());
+                 Recordings->SetModified();
                  Reply(250, "Recording \"%s\" deleted", Option);
-                 Recordings.DelByName(recording->FileName());
                  }
               else
                  Reply(554, "Error while deleting recording!");
               }
            }
         else
-           Reply(550, "Recording \"%s\" not found%s", Option, recordings.Count() ? "" : " (use LSTR before deleting)");
+           Reply(550, "Recording \"%s\" not found", Option);
         }
      else
         Reply(501, "Error in recording number \"%s\"", Option);
@@ -706,27 +1299,22 @@
      Reply(501, "Missing recording number");
 }
 
-void cSVDRP::CmdDELT(const char *Option)
+void cSVDRPServer::CmdDELT(const char *Option)
 {
   if (*Option) {
      if (isnumber(Option)) {
-        if (!Timers.BeingEdited()) {
-           cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1);
-           if (timer) {
-              if (!timer->Recording()) {
-                 isyslog("deleting timer %s", *timer->ToDescr());
-                 Timers.Del(timer);
-                 Timers.SetModified();
-                 Reply(250, "Timer \"%s\" deleted", Option);
-                 }
-              else
-                 Reply(550, "Timer \"%s\" is recording", Option);
-              }
-           else
-              Reply(501, "Timer \"%s\" not defined", Option);
+        LOCK_TIMERS_WRITE;
+        Timers->SetExplicitModify();
+        if (cTimer *Timer = Timers->GetById(strtol(Option, NULL, 10))) {
+           if (Timer->Recording())
+              Timer->Skip();
+           Timers->Del(Timer);
+           Timers->SetModified();
+           isyslog("SVDRP < %s deleted timer %s", *connection, *Timer->ToDescr());
+           Reply(250, "Timer \"%s\" deleted", Option);
            }
         else
-           Reply(550, "Timers are being edited - try again later");
+           Reply(501, "Timer \"%s\" not defined", Option);
         }
      else
         Reply(501, "Error in timer number \"%s\"", Option);
@@ -735,16 +1323,16 @@
      Reply(501, "Missing timer number");
 }
 
-void cSVDRP::CmdEDIT(const char *Option)
+void cSVDRPServer::CmdEDIT(const char *Option)
 {
   if (*Option) {
      if (isnumber(Option)) {
-        cRecording *recording = recordings.Get(strtol(Option, NULL, 10) - 1);
-        if (recording) {
+        LOCK_RECORDINGS_READ;
+        if (const cRecording *Recording = Recordings->Get(strtol(Option, NULL, 10) - 1)) {
            cMarks Marks;
-           if (Marks.Load(recording->FileName(), recording->FramesPerSecond(), recording->IsPesRecording()) && Marks.Count()) {
-              if (RecordingsHandler.Add(ruCut, recording->FileName()))
-                 Reply(250, "Editing recording \"%s\" [%s]", Option, recording->Title());
+           if (Marks.Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.Count()) {
+              if (RecordingsHandler.Add(ruCut, Recording->FileName()))
+                 Reply(250, "Editing recording \"%s\" [%s]", Option, Recording->Title());
               else
                  Reply(554, "Can't start editing process");
               }
@@ -752,7 +1340,7 @@
               Reply(554, "No editing marks defined");
            }
         else
-           Reply(550, "Recording \"%s\" not found%s", Option, recordings.Count() ? "" : " (use LSTR before editing)");
+           Reply(550, "Recording \"%s\" not found", Option);
         }
      else
         Reply(501, "Error in recording number \"%s\"", Option);
@@ -761,7 +1349,7 @@
      Reply(501, "Missing recording number");
 }
 
-void cSVDRP::CmdGRAB(const char *Option)
+void cSVDRPServer::CmdGRAB(const char *Option)
 {
   const char *FileName = NULL;
   bool Jpeg = true;
@@ -831,7 +1419,7 @@
      // canonicalize the file name:
      char RealFileName[PATH_MAX];
      if (FileName) {
-        if (grabImageDir) {
+        if (*grabImageDir) {
            cString s(FileName);
            FileName = s;
            const char *slash = strrchr(FileName, '/');
@@ -868,7 +1456,7 @@
            int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
            if (fd >= 0) {
               if (safe_write(fd, Image, ImageSize) == ImageSize) {
-                 dsyslog("grabbed image to %s", FileName);
+                 dsyslog("SVDRP < %s grabbed image to %s", *connection, FileName);
                  Reply(250, "Grabbed image %s", Option);
                  }
               else {
@@ -898,7 +1486,7 @@
      Reply(501, "Missing filename");
 }
 
-void cSVDRP::CmdHELP(const char *Option)
+void cSVDRPServer::CmdHELP(const char *Option)
 {
   if (*Option) {
      const char *hp = GetHelpPage(Option, HelpPages);
@@ -926,7 +1514,7 @@
   Reply(214, "End of HELP info");
 }
 
-void cSVDRP::CmdHITK(const char *Option)
+void cSVDRPServer::CmdHITK(const char *Option)
 {
   if (*Option) {
      if (!cRemote::Enabled()) {
@@ -965,136 +1553,132 @@
      }
 }
 
-void cSVDRP::CmdLSTC(const char *Option)
+void cSVDRPServer::CmdLSTC(const char *Option)
 {
+  LOCK_CHANNELS_READ;
   bool WithGroupSeps = strcasecmp(Option, ":groups") == 0;
   if (*Option && !WithGroupSeps) {
      if (isnumber(Option)) {
-        cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10));
-        if (channel)
-           Reply(250, "%d %s", channel->Number(), *channel->ToText());
+        if (const cChannel *Channel = Channels->GetByNumber(strtol(Option, NULL, 10)))
+           Reply(250, "%d %s", Channel->Number(), *Channel->ToText());
         else
            Reply(501, "Channel \"%s\" not defined", Option);
         }
      else {
-        cChannel *next = Channels.GetByChannelID(tChannelID::FromString(Option));
-        if (!next) {
-           for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) {
-              if (!channel->GroupSep()) {
-                 if (strcasestr(channel->Name(), Option)) {
-                    if (next)
-                       Reply(-250, "%d %s", next->Number(), *next->ToText());
-                    next = channel;
+        const cChannel *Next = Channels->GetByChannelID(tChannelID::FromString(Option));
+        if (!Next) {
+           for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
+              if (!Channel->GroupSep()) {
+                 if (strcasestr(Channel->Name(), Option)) {
+                    if (Next)
+                       Reply(-250, "%d %s", Next->Number(), *Next->ToText());
+                    Next = Channel;
                     }
                  }
               }
            }
-        if (next)
-           Reply(250, "%d %s", next->Number(), *next->ToText());
+        if (Next)
+           Reply(250, "%d %s", Next->Number(), *Next->ToText());
         else
            Reply(501, "Channel \"%s\" not defined", Option);
         }
      }
-  else if (Channels.MaxNumber() >= 1) {
-     for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) {
+  else if (cChannels::MaxNumber() >= 1) {
+     for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
          if (WithGroupSeps)
-            Reply(channel->Next() ? -250: 250, "%d %s", channel->GroupSep() ? 0 : channel->Number(), *channel->ToText());
-         else if (!channel->GroupSep())
-            Reply(channel->Number() < Channels.MaxNumber() ? -250 : 250, "%d %s", channel->Number(), *channel->ToText());
+            Reply(Channel->Next() ? -250: 250, "%d %s", Channel->GroupSep() ? 0 : Channel->Number(), *Channel->ToText());
+         else if (!Channel->GroupSep())
+            Reply(Channel->Number() < cChannels::MaxNumber() ? -250 : 250, "%d %s", Channel->Number(), *Channel->ToText());
          }
      }
   else
      Reply(550, "No channels defined");
 }
 
-void cSVDRP::CmdLSTE(const char *Option)
+void cSVDRPServer::CmdLSTE(const char *Option)
 {
-  cSchedulesLock SchedulesLock;
-  const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
-  if (Schedules) {
-     const cSchedule* Schedule = NULL;
-     eDumpMode DumpMode = dmAll;
-     time_t AtTime = 0;
-     if (*Option) {
-        char buf[strlen(Option) + 1];
-        strcpy(buf, Option);
-        const char *delim = " \t";
-        char *strtok_next;
-        char *p = strtok_r(buf, delim, &strtok_next);
-        while (p && DumpMode == dmAll) {
-              if (strcasecmp(p, "NOW") == 0)
-                 DumpMode = dmPresent;
-              else if (strcasecmp(p, "NEXT") == 0)
-                 DumpMode = dmFollowing;
-              else if (strcasecmp(p, "AT") == 0) {
-                 DumpMode = dmAtTime;
-                 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
-                    if (isnumber(p))
-                       AtTime = strtol(p, NULL, 10);
-                    else {
-                       Reply(501, "Invalid time");
-                       return;
-                       }
-                    }
+  LOCK_SCHEDULES_READ;
+  const cSchedule* Schedule = NULL;
+  eDumpMode DumpMode = dmAll;
+  time_t AtTime = 0;
+  if (*Option) {
+     char buf[strlen(Option) + 1];
+     strcpy(buf, Option);
+     const char *delim = " \t";
+     char *strtok_next;
+     char *p = strtok_r(buf, delim, &strtok_next);
+     while (p && DumpMode == dmAll) {
+           if (strcasecmp(p, "NOW") == 0)
+              DumpMode = dmPresent;
+           else if (strcasecmp(p, "NEXT") == 0)
+              DumpMode = dmFollowing;
+           else if (strcasecmp(p, "AT") == 0) {
+              DumpMode = dmAtTime;
+              if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
+                 if (isnumber(p))
+                    AtTime = strtol(p, NULL, 10);
                  else {
-                    Reply(501, "Missing time");
+                    Reply(501, "Invalid time");
                     return;
                     }
                  }
-              else if (!Schedule) {
-                 cChannel* Channel = NULL;
-                 if (isnumber(p))
-                    Channel = Channels.GetByNumber(strtol(Option, NULL, 10));
-                 else
-                    Channel = Channels.GetByChannelID(tChannelID::FromString(Option));
-                 if (Channel) {
-                    Schedule = Schedules->GetSchedule(Channel);
-                    if (!Schedule) {
-                       Reply(550, "No schedule found");
-                       return;
-                       }
-                    }
-                 else {
-                    Reply(550, "Channel \"%s\" not defined", p);
+              else {
+                 Reply(501, "Missing time");
+                 return;
+                 }
+              }
+           else if (!Schedule) {
+              LOCK_CHANNELS_READ;
+              const cChannel* Channel = NULL;
+              if (isnumber(p))
+                 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
+              else
+                 Channel = Channels->GetByChannelID(tChannelID::FromString(Option));
+              if (Channel) {
+                 Schedule = Schedules->GetSchedule(Channel);
+                 if (!Schedule) {
+                    Reply(550, "No schedule found");
                     return;
                     }
                  }
               else {
-                 Reply(501, "Unknown option: \"%s\"", p);
+                 Reply(550, "Channel \"%s\" not defined", p);
                  return;
                  }
-              p = strtok_r(NULL, delim, &strtok_next);
               }
-        }
-     int fd = dup(file);
-     if (fd) {
-        FILE *f = fdopen(fd, "w");
-        if (f) {
-           if (Schedule)
-              Schedule->Dump(f, "215-", DumpMode, AtTime);
-           else
-              Schedules->Dump(f, "215-", DumpMode, AtTime);
-           fflush(f);
-           Reply(215, "End of EPG data");
-           fclose(f);
-           }
-        else {
-           Reply(451, "Can't open file connection");
-           close(fd);
+           else {
+              Reply(501, "Unknown option: \"%s\"", p);
+              return;
+              }
+           p = strtok_r(NULL, delim, &strtok_next);
            }
+     }
+  int fd = dup(file);
+  if (fd) {
+     FILE *f = fdopen(fd, "w");
+     if (f) {
+        if (Schedule)
+           Schedule->Dump(f, "215-", DumpMode, AtTime);
+        else
+           Schedules->Dump(f, "215-", DumpMode, AtTime);
+        fflush(f);
+        Reply(215, "End of EPG data");
+        fclose(f);
+        }
+     else {
+        Reply(451, "Can't open file connection");
+        close(fd);
         }
-     else
-        Reply(451, "Can't dup stream descriptor");
      }
   else
-     Reply(451, "Can't get EPG data");
+     Reply(451, "Can't dup stream descriptor");
 }
 
-void cSVDRP::CmdLSTR(const char *Option)
+void cSVDRPServer::CmdLSTR(const char *Option)
 {
   int Number = 0;
   bool Path = false;
-  recordings.Update(true);
+  LOCK_RECORDINGS_READ;
   if (*Option) {
      char buf[strlen(Option) + 1];
      strcpy(buf, Option);
@@ -1119,14 +1703,13 @@
            p = strtok_r(NULL, delim, &strtok_next);
            }
      if (Number) {
-        cRecording *recording = recordings.Get(strtol(Option, NULL, 10) - 1);
-        if (recording) {
+        if (const cRecording *Recording = Recordings->Get(strtol(Option, NULL, 10) - 1)) {
            FILE *f = fdopen(file, "w");
            if (f) {
               if (Path)
-                 Reply(250, "%s", recording->FileName());
+                 Reply(250, "%s", Recording->FileName());
               else {
-                 recording->Info()->Write(f, "215-");
+                 Recording->Info()->Write(f, "215-");
                  fflush(f);
                  Reply(215, "End of recording information");
                  }
@@ -1139,21 +1722,21 @@
            Reply(550, "Recording \"%s\" not found", Option);
         }
      }
-  else if (recordings.Count()) {
-     cRecording *recording = recordings.First();
-     while (recording) {
-           Reply(recording == recordings.Last() ? 250 : -250, "%d %s", recording->Index() + 1, recording->Title(' ', true));
-           recording = recordings.Next(recording);
+  else if (Recordings->Count()) {
+     const cRecording *Recording = Recordings->First();
+     while (Recording) {
+           Reply(Recording == Recordings->Last() ? 250 : -250, "%d %s", Recording->Index() + 1, Recording->Title(' ', true));
+           Recording = Recordings->Next(Recording);
            }
      }
   else
      Reply(550, "No recordings available");
 }
 
-void cSVDRP::CmdLSTT(const char *Option)
+void cSVDRPServer::CmdLSTT(const char *Option)
 {
-  int Number = 0;
-  bool Id = false;
+  int Id = 0;
+  bool UseChannelId = false;
   if (*Option) {
      char buf[strlen(Option) + 1];
      strcpy(buf, Option);
@@ -1162,9 +1745,9 @@
      char *p = strtok_r(buf, delim, &strtok_next);
      while (p) {
            if (isnumber(p))
-              Number = strtol(p, NULL, 10);
+              Id = strtol(p, NULL, 10);
            else if (strcasecmp(p, "ID") == 0)
-              Id = true;
+              UseChannelId = true;
            else {
               Reply(501, "Unknown option: \"%s\"", p);
               return;
@@ -1172,30 +1755,44 @@
            p = strtok_r(NULL, delim, &strtok_next);
            }
      }
-  if (Number) {
-     cTimer *timer = Timers.Get(Number - 1);
-     if (timer)
-        Reply(250, "%d %s", timer->Index() + 1, *timer->ToText(Id));
-     else
-        Reply(501, "Timer \"%s\" not defined", Option);
-     }
-  else if (Timers.Count()) {
-     for (int i = 0; i < Timers.Count(); i++) {
-         cTimer *timer = Timers.Get(i);
-        if (timer)
-           Reply(i < Timers.Count() - 1 ? -250 : 250, "%d %s", timer->Index() + 1, *timer->ToText(Id));
-        else
-           Reply(501, "Timer \"%d\" not found", i + 1);
+  LOCK_TIMERS_READ;
+  if (Id) {
+     for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
+         if (!Timer->Remote()) {
+            if (Timer->Id() == Id) {
+               Reply(250, "%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
+               return;
+               }
+            }
          }
+     Reply(501, "Timer \"%s\" not defined", Option);
+     return;
      }
-  else
-     Reply(550, "No timers defined");
+  else {
+     const cTimer *LastLocalTimer = Timers->Last();
+     while (LastLocalTimer) {
+           if (LastLocalTimer->Remote())
+              LastLocalTimer = Timers->Prev(LastLocalTimer);
+           else
+              break;
+           }
+     if (LastLocalTimer) {
+        for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
+            if (!Timer->Remote())
+               Reply(Timer != LastLocalTimer ? -250 : 250, "%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
+            if (Timer == LastLocalTimer)
+               break;
+            }
+        return;
+        }
+     }
+  Reply(550, "No timers defined");
 }
 
-void cSVDRP::CmdMESG(const char *Option)
+void cSVDRPServer::CmdMESG(const char *Option)
 {
   if (*Option) {
-     isyslog("SVDRP message: '%s'", Option);
+     isyslog("SVDRP < %s message '%s'", *connection, Option);
      Skins.QueueMessage(mtInfo, Option);
      Reply(250, "Message queued");
      }
@@ -1203,36 +1800,34 @@
      Reply(501, "Missing message");
 }
 
-void cSVDRP::CmdMODC(const char *Option)
+void cSVDRPServer::CmdMODC(const char *Option)
 {
   if (*Option) {
      char *tail;
      int n = strtol(Option, &tail, 10);
      if (tail && tail != Option) {
         tail = skipspace(tail);
-        if (!Channels.BeingEdited()) {
-           cChannel *channel = Channels.GetByNumber(n);
-           if (channel) {
-              cChannel ch;
-              if (ch.Parse(tail)) {
-                 if (Channels.HasUniqueChannelID(&ch, channel)) {
-                    *channel = ch;
-                    Channels.ReNumber();
-                    Channels.SetModified(true);
-                    isyslog("modifed channel %d %s", channel->Number(), *channel->ToText());
-                    Reply(250, "%d %s", channel->Number(), *channel->ToText());
-                    }
-                 else
-                    Reply(501, "Channel settings are not unique");
+        LOCK_CHANNELS_WRITE;
+        Channels->SetExplicitModify();
+        if (cChannel *Channel = Channels->GetByNumber(n)) {
+           cChannel ch;
+           if (ch.Parse(tail)) {
+              if (Channels->HasUniqueChannelID(&ch, Channel)) {
+                 *Channel = ch;
+                 Channels->ReNumber();
+                 Channels->SetModifiedByUser();
+                 Channels->SetModified();
+                 isyslog("SVDRP < %s modifed channel %d %s", *connection, Channel->Number(), *Channel->ToText());
+                 Reply(250, "%d %s", Channel->Number(), *Channel->ToText());
                  }
               else
-                 Reply(501, "Error in channel settings");
+                 Reply(501, "Channel settings are not unique");
               }
            else
-              Reply(501, "Channel \"%d\" not defined", n);
+              Reply(501, "Error in channel settings");
            }
         else
-           Reply(550, "Channels are being edited - try again later");
+           Reply(501, "Channel \"%d\" not defined", n);
         }
      else
         Reply(501, "Error in channel number");
@@ -1241,35 +1836,32 @@
      Reply(501, "Missing channel settings");
 }
 
-void cSVDRP::CmdMODT(const char *Option)
+void cSVDRPServer::CmdMODT(const char *Option)
 {
   if (*Option) {
      char *tail;
-     int n = strtol(Option, &tail, 10);
+     int Id = strtol(Option, &tail, 10);
      if (tail && tail != Option) {
         tail = skipspace(tail);
-        if (!Timers.BeingEdited()) {
-           cTimer *timer = Timers.Get(n - 1);
-           if (timer) {
-              cTimer t = *timer;
-              if (strcasecmp(tail, "ON") == 0)
-                 t.SetFlags(tfActive);
-              else if (strcasecmp(tail, "OFF") == 0)
-                 t.ClrFlags(tfActive);
-              else if (!t.Parse(tail)) {
-                 Reply(501, "Error in timer settings");
-                 return;
-                 }
-              *timer = t;
-              Timers.SetModified();
-              isyslog("timer %s modified (%s)", *timer->ToDescr(), timer->HasFlags(tfActive) ? "active" : "inactive");
-              Reply(250, "%d %s", timer->Index() + 1, *timer->ToText());
+        LOCK_TIMERS_WRITE;
+        Timers->SetExplicitModify();
+        if (cTimer *Timer = Timers->GetById(Id)) {
+           cTimer t = *Timer;
+           if (strcasecmp(tail, "ON") == 0)
+              t.SetFlags(tfActive);
+           else if (strcasecmp(tail, "OFF") == 0)
+              t.ClrFlags(tfActive);
+           else if (!t.Parse(tail)) {
+              Reply(501, "Error in timer settings");
+              return;
               }
-           else
-              Reply(501, "Timer \"%d\" not defined", n);
+           *Timer = t;
+           Timers->SetModified();
+           isyslog("SVDRP < %s modified timer %s (%s)", *connection, *Timer->ToDescr(), Timer->HasFlags(tfActive) ? "active" : "inactive");
+           Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
            }
         else
-           Reply(550, "Timers are being edited - try again later");
+           Reply(501, "Timer \"%d\" not defined", Id);
         }
      else
         Reply(501, "Error in timer number");
@@ -1278,60 +1870,60 @@
      Reply(501, "Missing timer settings");
 }
 
-void cSVDRP::CmdMOVC(const char *Option)
+void cSVDRPServer::CmdMOVC(const char *Option)
 {
   if (*Option) {
-     if (!Channels.BeingEdited() && !Timers.BeingEdited()) {
-        char *tail;
-        int From = strtol(Option, &tail, 10);
+     char *tail;
+     int From = strtol(Option, &tail, 10);
+     if (tail && tail != Option) {
+        tail = skipspace(tail);
         if (tail && tail != Option) {
-           tail = skipspace(tail);
-           if (tail && tail != Option) {
-              int To = strtol(tail, NULL, 10);
-              int CurrentChannelNr = cDevice::CurrentChannel();
-              cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
-              cChannel *FromChannel = Channels.GetByNumber(From);
-              if (FromChannel) {
-                 cChannel *ToChannel = Channels.GetByNumber(To);
-                 if (ToChannel) {
-                    int FromNumber = FromChannel->Number();
-                    int ToNumber = ToChannel->Number();
-                    if (FromNumber != ToNumber) {
-                       Channels.Move(FromChannel, ToChannel);
-                       Channels.ReNumber();
-                       Channels.SetModified(true);
-                       if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
-                          if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
-                             Channels.SwitchTo(CurrentChannel->Number());
-                          else
-                             cDevice::SetCurrentChannel(CurrentChannel);
-                          }
-                       isyslog("channel %d moved to %d", FromNumber, ToNumber);
-                       Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
+           LOCK_TIMERS_READ; // necessary to keep timers and channels in sync!
+           LOCK_CHANNELS_WRITE;
+           Channels->SetExplicitModify();
+           int To = strtol(tail, NULL, 10);
+           int CurrentChannelNr = cDevice::CurrentChannel();
+           const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
+           cChannel *FromChannel = Channels->GetByNumber(From);
+           if (FromChannel) {
+              cChannel *ToChannel = Channels->GetByNumber(To);
+              if (ToChannel) {
+                 int FromNumber = FromChannel->Number();
+                 int ToNumber = ToChannel->Number();
+                 if (FromNumber != ToNumber) {
+                    Channels->Move(FromChannel, ToChannel);
+                    Channels->ReNumber();
+                    Channels->SetModifiedByUser();
+                    Channels->SetModified();
+                    if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
+                       if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
+                          Channels->SwitchTo(CurrentChannel->Number());
+                       else
+                          cDevice::SetCurrentChannel(CurrentChannel);
                        }
-                    else
-                       Reply(501, "Can't move channel to same position");
+                    isyslog("SVDRP < %s channel %d moved to %d", *connection, FromNumber, ToNumber);
+                    Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
                     }
                  else
-                    Reply(501, "Channel \"%d\" not defined", To);
+                    Reply(501, "Can't move channel to same position");
                  }
               else
-                 Reply(501, "Channel \"%d\" not defined", From);
+                 Reply(501, "Channel \"%d\" not defined", To);
               }
            else
-              Reply(501, "Error in channel number");
+              Reply(501, "Channel \"%d\" not defined", From);
            }
         else
            Reply(501, "Error in channel number");
         }
      else
-        Reply(550, "Channels or timers are being edited - try again later");
+        Reply(501, "Error in channel number");
      }
   else
      Reply(501, "Missing channel number");
 }
 
-void cSVDRP::CmdMOVR(const char *Option)
+void cSVDRPServer::CmdMOVR(const char *Option)
 {
   if (*Option) {
      char *opt = strdup(Option);
@@ -1342,17 +1934,21 @@
      char c = *option;
      *option = 0;
      if (isnumber(num)) {
-        cRecording *recording = recordings.Get(strtol(num, NULL, 10) - 1);
-        if (recording) {
-           if (int RecordingInUse = recording->IsInUse())
-              Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, recording));
+        LOCK_RECORDINGS_WRITE;
+        Recordings->SetExplicitModify();
+        if (cRecording *Recording = Recordings->Get(strtol(num, NULL, 10) - 1)) {
+           if (int RecordingInUse = Recording->IsInUse())
+              Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
            else {
               if (c)
                  option = skipspace(++option);
               if (*option) {
-                 cString oldName = recording->Name();
-                 if ((recording = Recordings.GetByName(recording->FileName())) != NULL && recording->ChangeName(option))
-                    Reply(250, "Recording \"%s\" moved to \"%s\"", *oldName, recording->Name());
+                 cString oldName = Recording->Name();
+                 if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) {
+                    Recordings->SetModified();
+                    Recordings->TouchUpdate();
+                    Reply(250, "Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name());
+                    }
                  else
                     Reply(554, "Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
                  }
@@ -1361,7 +1957,7 @@
               }
            }
         else
-           Reply(550, "Recording \"%s\" not found%s", num, recordings.Count() ? "" : " (use LSTR before moving)");
+           Reply(550, "Recording \"%s\" not found", num);
         }
      else
         Reply(501, "Error in recording number \"%s\"", num);
@@ -1371,18 +1967,21 @@
      Reply(501, "Missing recording number");
 }
 
-void cSVDRP::CmdNEWC(const char *Option)
+void cSVDRPServer::CmdNEWC(const char *Option)
 {
   if (*Option) {
      cChannel ch;
      if (ch.Parse(Option)) {
-        if (Channels.HasUniqueChannelID(&ch)) {
+        LOCK_CHANNELS_WRITE;
+        Channels->SetExplicitModify();
+        if (Channels->HasUniqueChannelID(&ch)) {
            cChannel *channel = new cChannel;
            *channel = ch;
-           Channels.Add(channel);
-           Channels.ReNumber();
-           Channels.SetModified(true);
-           isyslog("new channel %d %s", channel->Number(), *channel->ToText());
+           Channels->Add(channel);
+           Channels->ReNumber();
+           Channels->SetModifiedByUser();
+           Channels->SetModified();
+           isyslog("SVDRP < %s new channel %d %s", *connection, channel->Number(), *channel->ToText());
            Reply(250, "%d %s", channel->Number(), *channel->ToText());
            }
         else
@@ -1395,37 +1994,38 @@
      Reply(501, "Missing channel settings");
 }
 
-void cSVDRP::CmdNEWT(const char *Option)
+void cSVDRPServer::CmdNEWT(const char *Option)
 {
   if (*Option) {
-     cTimer *timer = new cTimer;
-     if (timer->Parse(Option)) {
-        Timers.Add(timer);
-        Timers.SetModified();
-        isyslog("timer %s added", *timer->ToDescr());
-        Reply(250, "%d %s", timer->Index() + 1, *timer->ToText());
+     cTimer *Timer = new cTimer;
+     if (Timer->Parse(Option)) {
+        LOCK_TIMERS_WRITE;
+        Timer->ClrFlags(tfRecording);
+        Timers->Add(Timer);
+        isyslog("SVDRP < %s added timer %s", *connection, *Timer->ToDescr());
+        Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
         return;
         }
      else
         Reply(501, "Error in timer settings");
-     delete timer;
+     delete Timer;
      }
   else
      Reply(501, "Missing timer settings");
 }
 
-void cSVDRP::CmdNEXT(const char *Option)
+void cSVDRPServer::CmdNEXT(const char *Option)
 {
-  cTimer *t = Timers.GetNextActiveTimer();
-  if (t) {
+  LOCK_TIMERS_READ;
+  if (const cTimer *t = Timers->GetNextActiveTimer()) {
      time_t Start = t->StartTime();
-     int Number = t->Index() + 1;
+     int Id = t->Id();
      if (!*Option)
-        Reply(250, "%d %s", Number, *TimeToString(Start));
+        Reply(250, "%d %s", Id, *TimeToString(Start));
      else if (strcasecmp(Option, "ABS") == 0)
-        Reply(250, "%d %ld", Number, Start);
+        Reply(250, "%d %ld", Id, Start);
      else if (strcasecmp(Option, "REL") == 0)
-        Reply(250, "%d %ld", Number, Start - time(NULL));
+        Reply(250, "%d %ld", Id, Start - time(NULL));
      else
         Reply(501, "Unknown option: \"%s\"", Option);
      }
@@ -1433,7 +2033,12 @@
      Reply(550, "No active timers");
 }
 
-void cSVDRP::CmdPLAY(const char *Option)
+void cSVDRPServer::CmdPING(const char *Option)
+{
+  Reply(250, "%s is alive", Setup.SVDRPHostName);
+}
+
+void cSVDRPServer::CmdPLAY(const char *Option)
 {
   if (*Option) {
      char *opt = strdup(Option);
@@ -1444,8 +2049,8 @@
      char c = *option;
      *option = 0;
      if (isnumber(num)) {
-        cRecording *recording = recordings.Get(strtol(num, NULL, 10) - 1);
-        if (recording) {
+        LOCK_RECORDINGS_READ;
+        if (const cRecording *Recording = Recordings->Get(strtol(num, NULL, 10) - 1)) {
            if (c)
               option = skipspace(++option);
            cReplayControl::SetRecording(NULL);
@@ -1453,20 +2058,20 @@
            if (*option) {
               int pos = 0;
               if (strcasecmp(option, "BEGIN") != 0)
-                 pos = HMSFToIndex(option, recording->FramesPerSecond());
-              cResumeFile resume(recording->FileName(), recording->IsPesRecording());
+                 pos = HMSFToIndex(option, Recording->FramesPerSecond());
+              cResumeFile Resume(Recording->FileName(), Recording->IsPesRecording());
               if (pos <= 0)
-                 resume.Delete();
+                 Resume.Delete();
               else
-                 resume.Save(pos);
+                 Resume.Save(pos);
               }
-           cReplayControl::SetRecording(recording->FileName());
+           cReplayControl::SetRecording(Recording->FileName());
            cControl::Launch(new cReplayControl);
            cControl::Attach();
-           Reply(250, "Playing recording \"%s\" [%s]", num, recording->Title());
+           Reply(250, "Playing recording \"%s\" [%s]", num, Recording->Title());
            }
         else
-           Reply(550, "Recording \"%s\" not found%s", num, recordings.Count() ? "" : " (use LSTR before playing)");
+           Reply(550, "Recording \"%s\" not found", num);
         }
      else
         Reply(501, "Error in recording number \"%s\"", num);
@@ -1476,7 +2081,7 @@
      Reply(501, "Missing recording number");
 }
 
-void cSVDRP::CmdPLUG(const char *Option)
+void cSVDRPServer::CmdPLUG(const char *Option)
 {
   if (*Option) {
      char *opt = strdup(Option);
@@ -1547,7 +2152,37 @@
      }
 }
 
-void cSVDRP::CmdPUTE(const char *Option)
+void cSVDRPServer::CmdPOLL(const char *Option)
+{
+  if (*Option) {
+     char buf[strlen(Option) + 1];
+     char *p = strcpy(buf, Option);
+     const char *delim = " \t";
+     char *strtok_next;
+     char *RemoteName = strtok_r(p, delim, &strtok_next);
+     char *ListName = strtok_r(NULL, delim, &strtok_next);
+     if (SVDRPClientHandler) {
+        if (ListName) {
+           if (strcasecmp(ListName, "timers") == 0) {
+              if (SVDRPClientHandler->TriggerFetchingTimers(RemoteName))
+                 Reply(250, "OK");
+              else
+                 Reply(501, "No connection to \"%s\"", RemoteName);
+              }
+           else
+              Reply(501, "Unknown list name: \"%s\"", ListName);
+           }
+        else
+           Reply(501, "Missing list name");
+        }
+     else
+        Reply(501, "No SVDRP client connections");
+     }
+  else
+     Reply(501, "Missing parameters");
+}
+
+void cSVDRPServer::CmdPUTE(const char *Option)
 {
   if (*Option) {
      FILE *f = fopen(Option, "r");
@@ -1572,7 +2207,7 @@
      }
 }
 
-void cSVDRP::CmdREMO(const char *Option)
+void cSVDRPServer::CmdREMO(const char *Option)
 {
   if (*Option) {
      if (!strcasecmp(Option, "ON")) {
@@ -1590,13 +2225,13 @@
      Reply(250, "Remote control is %s", cRemote::Enabled() ? "enabled" : "disabled");
 }
 
-void cSVDRP::CmdSCAN(const char *Option)
+void cSVDRPServer::CmdSCAN(const char *Option)
 {
   EITScanner.ForceScan();
   Reply(250, "EPG scan triggered");
 }
 
-void cSVDRP::CmdSTAT(const char *Option)
+void cSVDRPServer::CmdSTAT(const char *Option)
 {
   if (*Option) {
      if (strcasecmp(Option, "DISK") == 0) {
@@ -1611,45 +2246,41 @@
      Reply(501, "No option given");
 }
 
-void cSVDRP::CmdUPDT(const char *Option)
+void cSVDRPServer::CmdUPDT(const char *Option)
 {
   if (*Option) {
-     cTimer *timer = new cTimer;
-     if (timer->Parse(Option)) {
-        if (!Timers.BeingEdited()) {
-           cTimer *t = Timers.GetTimer(timer);
-           if (t) {
-              t->Parse(Option);
-              delete timer;
-              timer = t;
-              isyslog("timer %s updated", *timer->ToDescr());
-              }
-           else {
-              Timers.Add(timer);
-              isyslog("timer %s added", *timer->ToDescr());
-              }
-           Timers.SetModified();
-           Reply(250, "%d %s", timer->Index() + 1, *timer->ToText());
-           return;
+     cTimer *Timer = new cTimer;
+     if (Timer->Parse(Option)) {
+        LOCK_TIMERS_WRITE;
+        if (cTimer *t = Timers->GetTimer(Timer)) {
+           t->Parse(Option);
+           delete Timer;
+           Timer = t;
+           isyslog("SVDRP < %s updated timer %s", *connection, *Timer->ToDescr());
            }
-        else
-           Reply(550, "Timers are being edited - try again later");
+        else {
+           Timers->Add(Timer);
+           isyslog("SVDRP < %s added timer %s", *connection, *Timer->ToDescr());
+           }
+        Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
+        return;
         }
      else
         Reply(501, "Error in timer settings");
-     delete timer;
+     delete Timer;
      }
   else
      Reply(501, "Missing timer settings");
 }
 
-void cSVDRP::CmdUPDR(const char *Option)
+void cSVDRPServer::CmdUPDR(const char *Option)
 {
-  Recordings.Update(false);
+  LOCK_RECORDINGS_WRITE;
+  Recordings->Update(false);
   Reply(250, "Re-read of recordings directory triggered");
 }
 
-void cSVDRP::CmdVOLU(const char *Option)
+void cSVDRPServer::CmdVOLU(const char *Option)
 {
   if (*Option) {
      if (isnumber(Option))
@@ -1673,7 +2304,7 @@
 
 #define CMD(c) (strcasecmp(Cmd, c) == 0)
 
-void cSVDRP::Execute(char *Cmd)
+void cSVDRPServer::Execute(char *Cmd)
 {
   // handle PUTE data:
   if (PUTEhandler) {
@@ -1714,8 +2345,10 @@
   else if (CMD("NEWC"))  CmdNEWC(s);
   else if (CMD("NEWT"))  CmdNEWT(s);
   else if (CMD("NEXT"))  CmdNEXT(s);
+  else if (CMD("PING"))  CmdPING(s);
   else if (CMD("PLAY"))  CmdPLAY(s);
   else if (CMD("PLUG"))  CmdPLUG(s);
+  else if (CMD("POLL"))  CmdPOLL(s);
   else if (CMD("PUTE"))  CmdPUTE(s);
   else if (CMD("REMO"))  CmdREMO(s);
   else if (CMD("SCAN"))  CmdSCAN(s);
@@ -1727,21 +2360,9 @@
   else                   Reply(500, "Command unrecognized: \"%s\"", Cmd);
 }
 
-bool cSVDRP::Process(void)
+bool cSVDRPServer::Process(void)
 {
-  bool NewConnection = !file.IsOpen();
-  bool SendGreeting = NewConnection;
-
-  if (file.IsOpen() || file.Open(socket.Accept())) {
-     if (SendGreeting) {
-        //TODO how can we get the *full* hostname?
-        char buffer[BUFSIZ];
-        gethostname(buffer, sizeof(buffer));
-        time_t now = time(NULL);
-        Reply(220, "%s SVDRP VideoDiskRecorder %s; %s; %s", buffer, VDRVERSION, *TimeToString(now), cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8");
-        }
-     if (NewConnection)
-        lastActivity = time(NULL);
+  if (file.IsOpen()) {
      while (file.Ready(false)) {
            unsigned char c;
            int r = safe_read(file, &c, 1);
@@ -1781,7 +2402,7 @@
                        cmdLine = NewBuffer;
                        }
                     else {
-                       esyslog("ERROR: out of memory");
+                       esyslog("SVDRP < %s ERROR: out of memory", *connection);
                        Close();
                        break;
                        }
@@ -1792,23 +2413,161 @@
               lastActivity = time(NULL);
               }
            else if (r <= 0) {
-              isyslog("lost connection to SVDRP client");
+              isyslog("SVDRP < %s lost connection to client", *connection);
               Close();
               }
            }
      if (Setup.SVDRPTimeout && time(NULL) - lastActivity > Setup.SVDRPTimeout) {
-        isyslog("timeout on SVDRP connection");
+        isyslog("SVDRP < %s timeout on connection", *connection);
         Close(true, true);
         }
-     return true;
      }
-  return false;
+  return file.IsOpen();
+}
+
+void SetSVDRPPorts(int TcpPort, int UdpPort)
+{
+  SVDRPTcpPort = TcpPort;
+  SVDRPUdpPort = UdpPort;
 }
 
-void cSVDRP::SetGrabImageDir(const char *GrabImageDir)
+void SetSVDRPGrabImageDir(const char *GrabImageDir)
 {
-  free(grabImageDir);
-  grabImageDir = GrabImageDir ? strdup(GrabImageDir) : NULL;
+  grabImageDir = GrabImageDir;
 }
 
-//TODO more than one connection???
+// --- cSVDRPServerHandler ---------------------------------------------------
+
+class cSVDRPServerHandler : public cThread {
+private:
+  cMutex mutex;
+  bool ready;
+  cSocket tcpSocket;
+  cVector<cSVDRPServer *> serverConnections;
+  void HandleServerConnection(void);
+  void ProcessConnections(void);
+protected:
+  virtual void Action(void);
+public:
+  cSVDRPServerHandler(int TcpPort);
+  virtual ~cSVDRPServerHandler();
+  void WaitUntilReady(void);
+  };
+
+static cSVDRPServerHandler *SVDRPServerHandler = NULL;
+
+cSVDRPServerHandler::cSVDRPServerHandler(int TcpPort)
+:cThread("SVDRP server handler", true)
+,tcpSocket(TcpPort, true)
+{
+  ready = false;
+}
+
+cSVDRPServerHandler::~cSVDRPServerHandler()
+{
+  Cancel(3);
+  for (int i = 0; i < serverConnections.Size(); i++)
+      delete serverConnections[i];
+}
+
+void cSVDRPServerHandler::WaitUntilReady(void)
+{
+  cTimeMs Timeout(3000);
+  while (!ready && !Timeout.TimedOut())
+        cCondWait::SleepMs(10);
+}
+
+void cSVDRPServerHandler::ProcessConnections(void)
+{
+  cMutexLock MutexLock(&mutex);
+  for (int i = 0; i < serverConnections.Size(); i++) {
+      if (!serverConnections[i]->Process()) {
+         delete serverConnections[i];
+         serverConnections.Remove(i);
+         i--;
+         }
+      }
+}
+
+void cSVDRPServerHandler::HandleServerConnection(void)
+{
+  int NewSocket = tcpSocket.Accept();
+  if (NewSocket >= 0)
+     serverConnections.Append(new cSVDRPServer(NewSocket, tcpSocket.LastIpAddress()->Connection()));
+}
+
+void cSVDRPServerHandler::Action(void)
+{
+  if (tcpSocket.Listen()) {
+     SVDRPServerPoller.Add(tcpSocket.Socket(), false);
+     ready = true;
+     while (Running()) {
+           SVDRPServerPoller.Poll(1000);
+           cMutexLock MutexLock(&mutex);
+           HandleServerConnection();
+           ProcessConnections();
+           }
+     SVDRPServerPoller.Del(tcpSocket.Socket(), false);
+     tcpSocket.Close();
+     }
+}
+
+// --- SVDRP Handler ---------------------------------------------------------
+
+static cMutex SVDRPHandlerMutex;
+
+void StartSVDRPServerHandler(void)
+{
+  cMutexLock MutexLock(&SVDRPHandlerMutex);
+  if (SVDRPTcpPort && !SVDRPServerHandler) {
+     SVDRPServerHandler = new cSVDRPServerHandler(SVDRPTcpPort);
+     SVDRPServerHandler->Start();
+     SVDRPServerHandler->WaitUntilReady();
+     }
+}
+
+void StartSVDRPClientHandler(void)
+{
+  cMutexLock MutexLock(&SVDRPHandlerMutex);
+  if (SVDRPTcpPort && SVDRPUdpPort && !SVDRPClientHandler) {
+     SVDRPClientHandler = new cSVDRPClientHandler(SVDRPTcpPort, SVDRPUdpPort);
+     SVDRPClientHandler->Start();
+     }
+}
+
+void StopSVDRPServerHandler(void)
+{
+  cMutexLock MutexLock(&SVDRPHandlerMutex);
+  delete SVDRPServerHandler;
+  SVDRPServerHandler = NULL;
+}
+
+void StopSVDRPClientHandler(void)
+{
+  cMutexLock MutexLock(&SVDRPHandlerMutex);
+  delete SVDRPClientHandler;
+  SVDRPClientHandler = NULL;
+}
+
+void SendSVDRPDiscover(const char *Address)
+{
+  cMutexLock MutexLock(&SVDRPHandlerMutex);
+  if (SVDRPClientHandler)
+     SVDRPClientHandler->SendDiscover(Address);
+}
+
+bool GetSVDRPServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlag)
+{
+  cMutexLock MutexLock(&SVDRPHandlerMutex);
+  if (SVDRPClientHandler)
+     return SVDRPClientHandler->GetServerNames(ServerNames, FetchFlag);
+  return false;
+}
+
+bool ExecSVDRPCommand(const char *ServerName, const char *Command, cStringList *Response)
+{
+  cMutexLock MutexLock(&SVDRPHandlerMutex);
+  if (SVDRPClientHandler)
+     return SVDRPClientHandler->Execute(ServerName, Command, Response);
+  return false;
+}
diff -ruN vdr-2.2.0/svdrp.h vdr-2.3.1/svdrp.h
--- vdr-2.2.0/svdrp.h	2013-10-21 09:42:03.000000000 +0200
+++ vdr-2.3.1/svdrp.h	2015-09-09 11:44:12.000000000 +0200
@@ -4,93 +4,49 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: svdrp.h 3.2 2013/10/21 07:42:03 kls Exp $
+ * $Id: svdrp.h 4.5 2015/09/09 09:44:12 kls Exp $
  */
 
 #ifndef __SVDRP_H
 #define __SVDRP_H
 
-#include "recording.h"
 #include "tools.h"
 
-class cSocket {
-private:
-  int port;
-  int sock;
-  int queue;
-  void Close(void);
-public:
-  cSocket(int Port, int Queue = 1);
-  ~cSocket();
-  bool Open(void);
-  int Accept(void);
+enum eSvdrpFetchFlags {
+  sffNone   = 0b0000,
+  sffTimers = 0b0001,
   };
 
-class cPUTEhandler {
-private:
-  FILE *f;
-  int status;
-  const char *message;
-public:
-  cPUTEhandler(void);
-  ~cPUTEhandler();
-  bool Process(const char *s);
-  int Status(void) { return status; }
-  const char *Message(void) { return message; }
-  };
-
-class cSVDRP {
-private:
-  cSocket socket;
-  cFile file;
-  cRecordings recordings;
-  cPUTEhandler *PUTEhandler;
-  int numChars;
-  int length;
-  char *cmdLine;
-  time_t lastActivity;
-  static char *grabImageDir;
-  void Close(bool SendReply = false, bool Timeout = false);
-  bool Send(const char *s, int length = -1);
-  void Reply(int Code, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
-  void PrintHelpTopics(const char **hp);
-  void CmdCHAN(const char *Option);
-  void CmdCLRE(const char *Option);
-  void CmdDELC(const char *Option);
-  void CmdDELR(const char *Option);
-  void CmdDELT(const char *Option);
-  void CmdEDIT(const char *Option);
-  void CmdGRAB(const char *Option);
-  void CmdHELP(const char *Option);
-  void CmdHITK(const char *Option);
-  void CmdLSTC(const char *Option);
-  void CmdLSTE(const char *Option);
-  void CmdLSTR(const char *Option);
-  void CmdLSTT(const char *Option);
-  void CmdMESG(const char *Option);
-  void CmdMODC(const char *Option);
-  void CmdMODT(const char *Option);
-  void CmdMOVC(const char *Option);
-  void CmdMOVR(const char *Option);
-  void CmdNEWC(const char *Option);
-  void CmdNEWT(const char *Option);
-  void CmdNEXT(const char *Option);
-  void CmdPLAY(const char *Option);
-  void CmdPLUG(const char *Option);
-  void CmdPUTE(const char *Option);
-  void CmdREMO(const char *Option);
-  void CmdSCAN(const char *Option);
-  void CmdSTAT(const char *Option);
-  void CmdUPDT(const char *Option);
-  void CmdUPDR(const char *Option);
-  void CmdVOLU(const char *Option);
-  void Execute(char *Cmd);
-public:
-  cSVDRP(int Port);
-  ~cSVDRP();
-  bool HasConnection(void) { return file.IsOpen(); }
-  bool Process(void);
-  static void SetGrabImageDir(const char *GrabImageDir);
-  };
+void SetSVDRPPorts(int TcpPort, int UdpPort);
+void SetSVDRPGrabImageDir(const char *GrabImageDir);
+void StartSVDRPServerHandler(void);
+void StartSVDRPClientHandler(void);
+void StopSVDRPServerHandler(void);
+void StopSVDRPClientHandler(void);
+void SendSVDRPDiscover(const char *Address = NULL);
+bool GetSVDRPServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlag = sffNone);
+     ///< Gets a list of all available VDRs this VDR is connected to via SVDRP,
+     ///< and stores it in the given ServerNames list. The list is cleared
+     ///< before getting the server names.
+     ///< If FetchFlag is given, only the server names for which the local
+     ///< client has this flag set will be returned, and the client's flag
+     ///< will be cleared.
+     ///< Returns true if the resulting list is not empty.
+bool ExecSVDRPCommand(const char *ServerName, const char *Command, cStringList *Response = NULL);
+     ///< Sends the given SVDRP Command string to the remote VDR identified
+     ///< by ServerName and collects all of the response strings in Response.
+     ///< If no Response parameter is given, the response from command execution
+     ///< is ignored.
+     ///< Returns true if the data exchange was successful. Whether or
+     ///< not the actual SVDRP command was successful depends on the
+     ///< resulting strings from the remote VDR, which can be accessed
+     ///< through Response. If Response is given, it will be cleared before
+     ///< the command is actually executed.
+inline int SVDRPCode(const char *s) { return s ? atoi(s) : 0; }
+     ///< Returns the value of the three digit reply code of the given
+     ///< SVDRP response string.
+inline const char *SVDRPValue(const char *s) { return s && s[0] && s[1] && s[2] && s[3] ? s + 4 : NULL; }
+     ///< Returns the actual value of the given SVDRP response string, skipping
+     ///< the three digit reply code and possible continuation line indicator.
 
 #endif //__SVDRP_H
diff -ruN vdr-2.2.0/themes.c vdr-2.3.1/themes.c
--- vdr-2.2.0/themes.c	2012-02-17 14:57:32.000000000 +0100
+++ vdr-2.3.1/themes.c	2012-02-17 14:57:32.000000000 +0100
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: themes.c 3.0 2012/02/17 13:57:32 kls Exp $
+ * $Id: themes.c 4.0 2012/02/17 13:57:32 kls Exp $
  */
 
 #include "themes.h"
diff -ruN vdr-2.2.0/themes.h vdr-2.3.1/themes.h
--- vdr-2.2.0/themes.h	2012-10-07 13:11:43.000000000 +0200
+++ vdr-2.3.1/themes.h	2012-10-07 13:11:43.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: themes.h 3.0 2012/10/07 11:11:43 kls Exp $
+ * $Id: themes.h 4.0 2012/10/07 11:11:43 kls Exp $
  */
 
 #ifndef __THEMES_H
diff -ruN vdr-2.2.0/thread.c vdr-2.3.1/thread.c
--- vdr-2.2.0/thread.c	2013-12-29 16:26:33.000000000 +0100
+++ vdr-2.3.1/thread.c	2015-08-29 16:43:03.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: thread.c 3.2 2013/12/29 15:26:33 kls Exp $
+ * $Id: thread.c 4.1 2015/08/29 14:43:03 kls Exp $
  */
 
 #include "thread.h"
@@ -21,6 +21,16 @@
 #include <unistd.h>
 #include "tools.h"
 
+#define ABORT { dsyslog("ABORT!"); abort(); } // use debugger to trace back the problem
+
+//#define DEBUG_LOCKING // uncomment this line to activate debug output for locking
+
+#ifdef DEBUG_LOCKING
+#define dbglocking(a...) fprintf(stderr, a)
+#else
+#define dbglocking(a...)
+#endif
+
 static bool GetAbsTime(struct timespec *Abstime, int MillisecondsFromNow)
 {
   struct timeval now;
@@ -403,6 +413,132 @@
   return false;
 }
 
+// --- cStateLock ------------------------------------------------------------
+
+cStateLock::cStateLock(const char *Name)
+:rwLock(true)
+{
+  name = Name;
+  threadId = 0;
+  state = 0;
+  explicitModify = false;
+}
+
+bool cStateLock::Lock(cStateKey &StateKey, bool Write, int TimeoutMs)
+{
+  dbglocking("%5d %-10s %10p   lock state = %d/%d write = %d timeout = %d\n", cThread::ThreadId(), name, &StateKey, state, StateKey.state, Write, TimeoutMs);
+  StateKey.timedOut = false;
+  if (StateKey.stateLock) {
+     esyslog("ERROR: StateKey already in use in call to cStateLock::Lock() (tid=%d, lock=%s)", StateKey.stateLock->threadId, name);
+     ABORT;
+     return false;
+     }
+  if (rwLock.Lock(Write, TimeoutMs)) {
+     StateKey.stateLock = this;
+     if (Write) {
+        dbglocking("%5d %-10s %10p   locked write\n", cThread::ThreadId(), name, &StateKey);
+        threadId = cThread::ThreadId();
+        StateKey.write = true;
+        return true;
+        }
+     else if (state != StateKey.state) {
+        dbglocking("%5d %-10s %10p   locked read\n", cThread::ThreadId(), name, &StateKey);
+        return true;
+        }
+     else {
+        dbglocking("%5d %-10s %10p   state unchanged\n", cThread::ThreadId(), name, &StateKey);
+        StateKey.stateLock = NULL;
+        rwLock.Unlock();
+        }
+     }
+  else if (TimeoutMs) {
+     dbglocking("%5d %-10s %10p   timeout\n", cThread::ThreadId(), name, &StateKey);
+     StateKey.timedOut = true;
+     }
+  return false;
+}
+
+void cStateLock::Unlock(cStateKey &StateKey, bool IncState)
+{
+  dbglocking("%5d %-10s %10p unlock state = %d/%d inc = %d\n", cThread::ThreadId(), name, &StateKey, state, StateKey.state, IncState);
+  if (StateKey.stateLock != this) {
+     esyslog("ERROR: cStateLock::Unlock() called with an unused key (tid=%d, lock=%s)", threadId, name);
+     ABORT;
+     return;
+     }
+  if (StateKey.write && threadId != cThread::ThreadId()) {
+     esyslog("ERROR: cStateLock::Unlock() called without holding a lock (tid=%d, lock=%s)", threadId, name);
+     ABORT;
+     return;
+     }
+  if (StateKey.write && IncState && !explicitModify)
+     state++;
+  StateKey.state = state;
+  StateKey.write = false;
+  threadId = 0;
+  explicitModify = false;
+  rwLock.Unlock();
+}
+
+void cStateLock::IncState(void)
+{
+  if (threadId != cThread::ThreadId()) {
+     esyslog("ERROR: cStateLock::IncState() called without holding a lock (tid=%d, lock=%s)", threadId, name);
+     ABORT;
+     }
+  else
+     state++;
+}
+
+// --- cStateKey -------------------------------------------------------------
+
+cStateKey::cStateKey(bool IgnoreFirst)
+{
+  stateLock = NULL;
+  write = false;
+  state = 0;
+  if (!IgnoreFirst)
+     Reset();
+}
+
+cStateKey::~cStateKey()
+{
+  if (stateLock) {
+     esyslog("ERROR: cStateKey::~cStateKey() called without releasing the lock first (tid=%d, lock=%s, key=%p)", stateLock->threadId, stateLock->name, this);
+     ABORT;
+     }
+}
+
+void cStateKey::Reset(void)
+{
+  state = -1; // lock and key are initialized differently, to make the first check return true
+}
+
+void cStateKey::Remove(bool IncState)
+{
+  if (stateLock) {
+     stateLock->Unlock(*this, IncState);
+     stateLock = NULL;
+     }
+  else {
+     esyslog("ERROR: cStateKey::Remove() called without holding a lock (key=%p)", this);
+     ABORT;
+     }
+}
+
+bool cStateKey::StateChanged(void)
+{
+  if (!stateLock) {
+     esyslog("ERROR: cStateKey::StateChanged() called without holding a lock (tid=%d, key=%p)", cThread::ThreadId(), this);
+     ABORT;
+     }
+  else if (write)
+     return state != stateLock->state;
+  else
+     return true;
+  return false;
+}
+
 // --- cIoThrottle -----------------------------------------------------------
 
 cMutex cIoThrottle::mutex;
diff -ruN vdr-2.2.0/thread.h vdr-2.3.1/thread.h
--- vdr-2.2.0/thread.h	2015-01-14 12:39:55.000000000 +0100
+++ vdr-2.3.1/thread.h	2015-08-17 15:06:24.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: thread.h 3.2 2015/01/14 11:39:55 kls Exp $
+ * $Id: thread.h 4.1 2015/08/17 13:06:24 kls Exp $
  */
 
 #ifndef __THREAD_H
@@ -164,6 +164,94 @@
 
 #define LOCK_THREAD cThreadLock ThreadLock(this)
 
+class cStateKey;
+
+class cStateLock {
+  friend class cStateKey;
+private:
+  const char *name;
+  tThreadId threadId;
+  cRwLock rwLock;
+  int state;
+  bool explicitModify;
+  void Unlock(cStateKey &StateKey, bool IncState = true);
+       ///< Releases a lock that has been obtained by a previous call to Lock()
+       ///< with the given StateKey. If this was a write-lock, and IncState is true,
+       ///< the state of the lock will be incremented. In any case, the (new) state
+       ///< of the lock will be copied to the StateKey's state.
+public:
+  cStateLock(const char *Name = NULL);
+  bool Lock(cStateKey &StateKey, bool Write = false, int TimeoutMs = 0);
+       ///< Tries to get a lock and returns true if successful.
+       ///< If TimoutMs is not 0, it waits for the given number of milliseconds
+       ///< and returns false if no lock has been obtained within that time.
+       ///< Otherwise it waits indefinitely for the lock. The given StateKey
+       ///< will store which lock it has been used with, and will use that
+       ///< information when its Remove() function is called.
+       ///< There are two possible locks, one only for read access, and one
+       ///< for reading and writing:
+       ///<
+       ///< If Write is false (i.e. a read-lock is requested), the lock's state
+       ///< is compared to the given StateKey's state, and true is returned if
+       ///< they differ.
+       ///< If true is returned, the read-lock is still in place and the
+       ///< protected data structures can be safely accessed (in read-only mode!).
+       ///< Once the necessary operations have been performed, the lock must
+       ///< be released by a call to the StateKey's Remove() function.
+       ///< If false is returned, the state has not changed since the last check,
+       ///< and the read-lock has been released. In that case the protected
+       ///< data structures have not changed since the last call, so no action
+       ///< is required. Note that if TimeoutMs is used with read-locks, Lock()
+       ///< might return false even if the states of lock and key differ, just
+       ///< because it was unable to obtain the lock within the given time.
+       ///< You can call cStateKey::TimedOut() to detect this.
+       ///<
+       ///< If Write is true (i.e. a write-lock is requested), the states of the
+       ///< lock and the given StateKey don't matter, it will always try to obtain
+       ///< a write lock.
+  void SetExplicitModify(void) { explicitModify = true; }
+       ///< If you have obtained a write lock on this lock, and you don't want its
+       ///< state to be automatically incremented when the lock is released, a call to
+       ///< this function will disable this, and you can explicitly call IncState()
+       ///< to increment the state.
+  void IncState(void);
+       ///< Increments the state of this lock.
+  };
+
+class cStateKey {
+  friend class cStateLock;
+private:
+  cStateLock *stateLock;
+  bool write;
+  int state;
+  bool timedOut;
+public:
+  cStateKey(bool IgnoreFirst = false);
+       ///< Sets up a new state key. If IgnoreFirst is true, the first use
+       ///< of this key with a lock will not return true if the lock's state
+       ///< hasn't explicitly changed.
+  ~cStateKey();
+  void Reset(void);
+       ///< Resets the state of this key, so that the next call to a lock's
+       ///< Lock() function with this key will return true, even if the
+       ///< lock's state hasn't changed.
+  void Remove(bool IncState = true);
+       ///< Removes this key from the lock it was previously used with.
+       ///< If this key was used to obtain a write lock, the state of the lock will
+       ///< be incremented and copied to this key. You can set IncState to false
+       ///< to prevent this.
+  bool StateChanged(void);
+       ///< Returns true if this key is used for obtaining a write lock, and the
+       ///< lock's state differs from that of the key. When used with a read lock,
+       ///< it always returns true, because otherwise the lock wouldn't have been
+       ///< obtained in the first place.
+  bool InLock(void) { return stateLock; }
+       ///< Returns true if this key is currently in a lock.
+  bool TimedOut(void) const { return timedOut; }
+       ///< Returns true if the last lock attempt this key was used with failed due
+       ///< to a timeout.
+  };
+
 class cIoThrottle {
 private:
   static cMutex mutex;
diff -ruN vdr-2.2.0/timers.c vdr-2.3.1/timers.c
--- vdr-2.2.0/timers.c	2013-12-28 12:33:08.000000000 +0100
+++ vdr-2.3.1/timers.c	2015-09-13 15:10:24.000000000 +0200
@@ -4,18 +4,18 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: timers.c 3.1 2013/12/28 11:33:08 kls Exp $
+ * $Id: timers.c 4.5 2015/09/13 13:10:24 kls Exp $
  */
 
 #include "timers.h"
 #include <ctype.h>
-#include "channels.h"
 #include "device.h"
 #include "i18n.h"
 #include "libsi/si.h"
 #include "recording.h"
 #include "remote.h"
 #include "status.h"
+#include "svdrp.h"
 
 // IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d'
 // format characters in order to allow any number of blanks after a numeric
@@ -23,19 +23,22 @@
 
 // --- cTimer ----------------------------------------------------------------
 
-cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel)
+cTimer::cTimer(bool Instant, bool Pause, const cChannel *Channel)
 {
+  id = 0;
   startTime = stopTime = 0;
-  lastSetEvent = 0;
+  scheduleState = -1;
   deferred = 0;
-  recording = pending = inVpsMargin = false;
+  pending = inVpsMargin = false;
   flags = tfNone;
   *file = 0;
   aux = NULL;
+  remote = NULL;
   event = NULL;
   if (Instant)
      SetFlags(tfActive | tfInstant);
-  channel = Channel ? Channel : Channels.GetByNumber(cDevice::CurrentChannel());
+  LOCK_CHANNELS_READ;
+  channel = Channel ? Channel : Channels->GetByNumber(cDevice::CurrentChannel());
   time_t t = time(NULL);
   struct tm tm_r;
   struct tm *now = localtime_r(&t, &tm_r);
@@ -44,27 +47,25 @@
   start = now->tm_hour * 100 + now->tm_min;
   stop = 0;
   if (!Setup.InstantRecordTime && channel && (Instant || Pause)) {
-     cSchedulesLock SchedulesLock;
-     if (const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock)) {
-        if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) {
-           if (const cEvent *Event = Schedule->GetPresentEvent()) {
-              time_t tstart = Event->StartTime();
-              time_t tstop = Event->EndTime();
-              if (Event->Vps() && Setup.UseVps) {
-                 SetFlags(tfVps);
-                 tstart = Event->Vps();
-                 }
-              else {
-                 tstop  += Setup.MarginStop * 60;
-                 tstart -= Setup.MarginStart * 60;
-                 }
-              day = SetTime(tstart, 0);
-              struct tm *time = localtime_r(&tstart, &tm_r);
-              start = time->tm_hour * 100 + time->tm_min;
-              time = localtime_r(&tstop, &tm_r);
-              stop = time->tm_hour * 100 + time->tm_min;
-              SetEvent(Event);
+     LOCK_SCHEDULES_READ;
+     if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) {
+        if (const cEvent *Event = Schedule->GetPresentEvent()) {
+           time_t tstart = Event->StartTime();
+           time_t tstop = Event->EndTime();
+           if (Event->Vps() && Setup.UseVps) {
+              SetFlags(tfVps);
+              tstart = Event->Vps();
               }
+           else {
+              tstop  += Setup.MarginStop * 60;
+              tstart -= Setup.MarginStart * 60;
+              }
+           day = SetTime(tstart, 0);
+           struct tm *time = localtime_r(&tstart, &tm_r);
+           start = time->tm_hour * 100 + time->tm_min;
+           time = localtime_r(&tstop, &tm_r);
+           stop = time->tm_hour * 100 + time->tm_min;
+           SetEvent(Event);
            }
         }
      }
@@ -82,17 +83,20 @@
 
 cTimer::cTimer(const cEvent *Event)
 {
+  id = 0;
   startTime = stopTime = 0;
-  lastSetEvent = 0;
+  scheduleState = -1;
   deferred = 0;
-  recording = pending = inVpsMargin = false;
+  pending = inVpsMargin = false;
   flags = tfActive;
   *file = 0;
   aux = NULL;
+  remote = NULL;
   event = NULL;
   if (Event->Vps() && Setup.UseVps)
      SetFlags(tfVps);
-  channel = Channels.GetByChannelID(Event->ChannelID(), true);
+  LOCK_CHANNELS_READ;
+  channel = Channels->GetByChannelID(Event->ChannelID(), true);
   time_t tstart = (flags & tfVps) ? Event->Vps() : Event->StartTime();
   time_t tstop = tstart + Event->Duration();
   if (!(HasFlags(tfVps))) {
@@ -120,6 +124,7 @@
 {
   channel = NULL;
   aux = NULL;
+  remote = NULL;
   event = NULL;
   flags = tfNone;
   *this = Timer;
@@ -127,21 +132,23 @@
 
 cTimer::~cTimer()
 {
+  if (event)
+     event->DecNumTimers();
   free(aux);
+  free(remote);
 }
 
 cTimer& cTimer::operator= (const cTimer &Timer)
 {
   if (&Timer != this) {
-     uint OldFlags = flags & tfRecording;
+     id           = Timer.id;
      startTime    = Timer.startTime;
      stopTime     = Timer.stopTime;
-     lastSetEvent = 0;
-     deferred = 0;
-     recording    = Timer.recording;
+     scheduleState = -1;
+     deferred     = 0;
      pending      = Timer.pending;
      inVpsMargin  = Timer.inVpsMargin;
-     flags        = Timer.flags | OldFlags;
+     flags        = Timer.flags;
      channel      = Timer.channel;
      day          = Timer.day;
      weekdays     = Timer.weekdays;
@@ -152,7 +159,13 @@
      strncpy(file, Timer.file, sizeof(file));
      free(aux);
      aux = Timer.aux ? strdup(Timer.aux) : NULL;
-     event = NULL;
+     free(remote);
+     remote = Timer.remote ? strdup(Timer.remote) : NULL;
+     if (event)
+        event->DecNumTimers();
+     event = Timer.event;
+     if (event)
+        event->IncNumTimers();
      }
   return *this;
 }
@@ -171,14 +184,14 @@
 cString cTimer::ToText(bool UseChannelID) const
 {
   strreplace(file, ':', '|');
-  cString buffer = cString::sprintf("%u:%s:%s:%04d:%04d:%d:%d:%s:%s\n", flags, UseChannelID ? *Channel()->GetChannelID().ToString() : *itoa(Channel()->Number()), *PrintDay(day, weekdays, true), start, stop, priority, lifetime, file, aux ? aux : "");
+  cString buffer = cString::sprintf("%u:%s:%s:%04d:%04d:%d:%d:%s:%s", flags, UseChannelID ? *Channel()->GetChannelID().ToString() : *itoa(Channel()->Number()), *PrintDay(day, weekdays, true), start, stop, priority, lifetime, file, aux ? aux : "");
   strreplace(file, '|', ':');
   return buffer;
 }
 
 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')", Id(), remote ? "@" : "", remote ? remote : "", Channel()->Number(), start, stop, HasFlags(tfVps) ? "VPS " : "", file);
 }
 
 int cTimer::TimeToInt(int t)
@@ -313,7 +326,6 @@
      }
   bool result = false;
   if (8 <= sscanf(s, "%u :%m[^:]:%m[^:]:%d :%d :%d :%d :%m[^:\n]:%m[^\n]", &flags, &channelbuffer, &daybuffer, &start, &stop, &priority, &lifetime, &filebuffer, &aux)) {
-     ClrFlags(tfRecording);
      if (aux && !*skipspace(aux)) {
         free(aux);
         aux = NULL;
@@ -322,10 +334,11 @@
      result = ParseDay(daybuffer, day, weekdays);
      Utf8Strn0Cpy(file, filebuffer, sizeof(file));
      strreplace(file, '|', ':');
+     LOCK_CHANNELS_READ;
      if (isnumber(channelbuffer))
-        channel = Channels.GetByNumber(atoi(channelbuffer));
+        channel = Channels->GetByNumber(atoi(channelbuffer));
      else
-        channel = Channels.GetByChannelID(tChannelID::FromString(channelbuffer), true, true);
+        channel = Channels->GetByChannelID(tChannelID::FromString(channelbuffer), true, true);
      if (!channel) {
         esyslog("ERROR: channel %s not defined", channelbuffer);
         result = false;
@@ -340,7 +353,9 @@
 
 bool cTimer::Save(FILE *f)
 {
-  return fprintf(f, "%s", *ToText(true)) > 0;
+  if (!Remote())
+     return fprintf(f, "%s\n", *ToText(true)) > 0;
+  return true;
 }
 
 bool cTimer::IsSingleEvent(void) const
@@ -441,10 +456,8 @@
            startTime = event->StartTime();
            stopTime = event->EndTime();
            if (!Margin) { // this is an actual check
-              if (event->Schedule()->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) { // VPS control can only work with up-to-date events...
-                 if (event->StartTime() > 0) // checks for "phased out" events
-                    return event->IsRunning(true);
-                 }
+              if (event->Schedule()->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) // VPS control can only work with up-to-date events...
+                 return event->IsRunning(true);
               return startTime <= t && t < stopTime; // ...otherwise we fall back to normal timer handling
               }
            }
@@ -511,27 +524,18 @@
 #define EPGLIMITBEFORE   (1 * 3600) // Time in seconds before a timer's start time and
 #define EPGLIMITAFTER    (1 * 3600) // after its stop time within which EPG events will be taken into consideration.
 
-void cTimer::SetEventFromSchedule(const cSchedules *Schedules)
+void cTimer::SetId(int Id)
+{
+  id = Id;
+}
+
+bool cTimer::SetEventFromSchedule(const cSchedules *Schedules)
 {
-  cSchedulesLock SchedulesLock;
-  if (!Schedules) {
-     lastSetEvent = 0; // forces setting the event, even if the schedule hasn't been modified
-     if (!(Schedules = cSchedules::Schedules(SchedulesLock)))
-        return;
-     }
   const cSchedule *Schedule = Schedules->GetSchedule(Channel());
   if (Schedule && Schedule->Events()->First()) {
-     time_t now = time(NULL);
-     if (!lastSetEvent || Schedule->Modified() >= lastSetEvent) {
-        lastSetEvent = now;
+     if (Schedule->Modified(scheduleState)) {
         const cEvent *Event = NULL;
         if (HasFlags(tfVps) && Schedule->Events()->First()->Vps()) {
-           if (event && event->StartTime() > 0) { // checks for "phased out" events
-              if (Recording())
-                 return; // let the recording end first
-              if (now <= event->EndTime() || Matches(0, true))
-                 return; // stay with the old event until the timer has completely expired
-              }
            // VPS timers only match if their start time exactly matches the event's VPS time:
            for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
                if (e->StartTime() && e->RunningStatus() != SI::RunningStatusNotRunning) { // skip outdated events
@@ -566,30 +570,39 @@
                   }
                }
            }
-        SetEvent(Event);
+        return SetEvent(Event);
         }
      }
+  return false;
 }
 
-void cTimer::SetEvent(const cEvent *Event)
+bool cTimer::SetEvent(const cEvent *Event)
 {
-  if (event != Event) { //XXX TODO check event data, too???
-     if (Event)
+  if (event != Event) {
+     if (event)
+        event->DecNumTimers();
+     if (Event) {
         isyslog("timer %s set to event %s", *ToDescr(), *Event->ToDescr());
-     else
+        Event->IncNumTimers();
+        Event->Schedule()->Modified(scheduleState); // to get the current state
+        }
+     else {
         isyslog("timer %s set to no event", *ToDescr());
+        scheduleState = -1;
+        }
      event = Event;
+     return true;
      }
+  return false;
 }
 
 void cTimer::SetRecording(bool Recording)
 {
-  recording = Recording;
-  if (recording)
+  if (Recording)
      SetFlags(tfRecording);
   else
      ClrFlags(tfRecording);
-  isyslog("timer %s %s", *ToDescr(), recording ? "start" : "stop");
+  isyslog("timer %s %s", *ToDescr(), Recording ? "start" : "stop");
 }
 
 void cTimer::SetPending(bool Pending)
@@ -637,7 +650,13 @@
 void cTimer::SetAux(const char *Aux)
 {
   free(aux);
-  aux = strdup(Aux);
+  aux = Aux ? strdup(Aux) : NULL;
+}
+
+void cTimer::SetRemote(const char *Remote)
+{
+  free(remote);
+  remote = Remote ? strdup(Remote) : NULL;
 }
 
 void cTimer::SetDeferred(int Seconds)
@@ -691,20 +710,49 @@
 
 // --- cTimers ---------------------------------------------------------------
 
-cTimers Timers;
+cTimers cTimers::timers;
+int cTimers::lastTimerId = 0;
 
 cTimers::cTimers(void)
+:cConfig<cTimer>("Timers")
 {
-  state = 0;
-  beingEdited = 0;;
-  lastSetEvents = 0;
   lastDeleteExpired = 0;
 }
 
+bool cTimers::Load(const char *FileName)
+{
+  LOCK_TIMERS_WRITE;
+  Timers->SetExplicitModify();
+  if (timers.cConfig<cTimer>::Load(FileName)) {
+     for (cTimer *ti = timers.First(); ti; ti = timers.Next(ti)) {
+         ti->SetId(NewTimerId());
+         ti->ClrFlags(tfRecording);
+         Timers->SetModified();
+         }
+     return true;
+     }
+  return false;
+}
+
+int cTimers::NewTimerId(void)
+{
+  return ++lastTimerId; // no need for locking, the caller must have a lock on the global Timers list
+}
+
+const cTimer *cTimers::GetById(int Id) const
+{
+  for (const cTimer *ti = First(); ti; ti = Next(ti)) {
+      if (!ti->Remote() && ti->Id() == Id)
+         return ti;
+      }
+  return NULL;
+}
+
 cTimer *cTimers::GetTimer(cTimer *Timer)
 {
   for (cTimer *ti = First(); ti; ti = Next(ti)) {
-      if (ti->Channel() == Timer->Channel() &&
+      if (!ti->Remote() &&
+          ti->Channel() == Timer->Channel() &&
           (ti->WeekDays() && ti->WeekDays() == Timer->WeekDays() || !ti->WeekDays() && ti->Day() == Timer->Day()) &&
           ti->Start() == Timer->Start() &&
           ti->Stop() == Timer->Stop())
@@ -713,12 +761,12 @@
   return NULL;
 }
 
-cTimer *cTimers::GetMatch(time_t t)
+const cTimer *cTimers::GetMatch(time_t t) const
 {
   static int LastPending = -1;
-  cTimer *t0 = NULL;
-  for (cTimer *ti = First(); ti; ti = Next(ti)) {
-      if (!ti->Recording() && ti->Matches(t)) {
+  const cTimer *t0 = NULL;
+  for (const cTimer *ti = First(); ti; ti = Next(ti)) {
+      if (!ti->Remote() && !ti->Recording() && ti->Matches(t)) {
          if (ti->Pending()) {
             if (ti->Index() > LastPending) {
                LastPending = ti->Index();
@@ -736,11 +784,11 @@
   return t0;
 }
 
-cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match)
+const cTimer *cTimers::GetMatch(const cEvent *Event, eTimerMatch *Match) const
 {
-  cTimer *t = NULL;
+  const cTimer *t = NULL;
   eTimerMatch m = tmNone;
-  for (cTimer *ti = First(); ti; ti = Next(ti)) {
+  for (const cTimer *ti = First(); ti; ti = Next(ti)) {
       eTimerMatch tm = ti->Matches(Event);
       if (tm > m) {
          t = ti;
@@ -754,25 +802,33 @@
   return t;
 }
 
-cTimer *cTimers::GetNextActiveTimer(void)
+const cTimer *cTimers::GetNextActiveTimer(void) const
 {
-  cTimer *t0 = NULL;
-  for (cTimer *ti = First(); ti; ti = Next(ti)) {
-      ti->Matches();
-      if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
-         t0 = ti;
+  const cTimer *t0 = NULL;
+  for (const cTimer *ti = First(); ti; ti = Next(ti)) {
+      if (!ti->Remote()) {
+         ti->Matches();
+         if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
+            t0 = ti;
+         }
       }
   return t0;
 }
 
-void cTimers::SetModified(void)
+const cTimers *cTimers::GetTimersRead(cStateKey &StateKey, int TimeoutMs)
 {
-  cStatus::MsgTimerChange(NULL, tcMod);
-  state++;
+  return timers.Lock(StateKey, false, TimeoutMs) ? &timers : NULL;
+}
+
+cTimers *cTimers::GetTimersWrite(cStateKey &StateKey, int TimeoutMs)
+{
+  return timers.Lock(StateKey, true, TimeoutMs) ? &timers : NULL;
 }
 
 void cTimers::Add(cTimer *Timer, cTimer *After)
 {
+  if (!Timer->Remote())
+     Timer->SetId(NewTimerId());
   cConfig<cTimer>::Add(Timer, After);
   cStatus::MsgTimerChange(Timer, tcAdd);
 }
@@ -789,46 +845,114 @@
   cConfig<cTimer>::Del(Timer, DeleteObject);
 }
 
-bool cTimers::Modified(int &State)
+const cTimer *cTimers::UsesChannel(const cChannel *Channel) const
 {
-  bool Result = state != State;
-  State = state;
-  return Result;
+  for (const cTimer *Timer = First(); Timer; Timer = Next(Timer)) {
+      if (Timer->Channel() == Channel)
+         return Timer;
+      }
+  return NULL;
 }
 
-void cTimers::SetEvents(void)
+bool cTimers::SetEvents(const cSchedules *Schedules)
 {
-  if (time(NULL) - lastSetEvents < 5)
-     return;
-  cSchedulesLock SchedulesLock(false, 100);
-  const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
-  if (Schedules) {
-     if (!lastSetEvents || Schedules->Modified() >= lastSetEvents) {
-        for (cTimer *ti = First(); ti; ti = Next(ti)) {
-            if (cRemote::HasKeys())
-               return; // react immediately on user input
-            ti->SetEventFromSchedule(Schedules);
-            }
-        }
-     }
-  lastSetEvents = time(NULL);
+  bool TimersModified = false;
+  for (cTimer *ti = First(); ti; ti = Next(ti))
+      TimersModified |= ti->SetEventFromSchedule(Schedules);
+  return TimersModified;
 }
 
-void cTimers::DeleteExpired(void)
+bool cTimers::DeleteExpired(void)
 {
   if (time(NULL) - lastDeleteExpired < 30)
-     return;
+     return false;
+  bool TimersModified = false;
   cTimer *ti = First();
   while (ti) {
         cTimer *next = Next(ti);
-        if (ti->Expired()) {
+        if (!ti->Remote() && ti->Expired()) {
            isyslog("deleting timer %s", *ti->ToDescr());
            Del(ti);
-           SetModified();
+           TimersModified = true;
            }
         ti = next;
         }
   lastDeleteExpired = time(NULL);
+  return TimersModified;
+}
+
+bool cTimers::GetRemoteTimers(const char *ServerName)
+{
+  bool Result = false;
+  if (ServerName) {
+     Result = DelRemoteTimers(ServerName);
+     cStringList Response;
+     if (ExecSVDRPCommand(ServerName, "LSTT ID", &Response)) {
+        for (int i = 0; i < Response.Size(); i++) {
+            const char *s = Response[i];
+            int Code = SVDRPCode(s);
+            if (Code == 250) {
+               if (const char *v = SVDRPValue(s)) {
+                  int Id = atoi(v);
+                  while (*v && *v != ' ')
+                        v++; // skip id
+                  cTimer *Timer = new cTimer;
+                  if (Timer->Parse(v)) {
+                     Timer->SetRemote(ServerName);
+                     Timer->SetId(Id);
+                     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) {
+     if (!ExecSVDRPCommand(ServerName, cString::sprintf("POLL %s TIMERS", Setup.SVDRPHostName)))
+        esyslog("ERROR: can't send 'POLL %s TIMERS' to '%s'", Setup.SVDRPHostName, ServerName);
+     }
+  else {
+     cStringList ServerNames;
+     if (GetSVDRPServerNames(&ServerNames)) {
+        for (int i = 0; i < ServerNames.Size(); i++)
+            TriggerRemoteTimerPoll(ServerNames[i]);
+        }
+     }
 }
 
 // --- cSortedTimers ---------------------------------------------------------
@@ -838,10 +962,10 @@
   return (*(const cTimer **)a)->Compare(**(const cTimer **)b);
 }
 
-cSortedTimers::cSortedTimers(void)
-:cVector<const cTimer *>(Timers.Count())
+cSortedTimers::cSortedTimers(const cTimers *Timers)
+:cVector<const cTimer *>(Timers->Count())
 {
-  for (const cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer))
+  for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer))
       Append(Timer);
   Sort(CompareTimers);
 }
diff -ruN vdr-2.2.0/timers.h vdr-2.3.1/timers.h
--- vdr-2.2.0/timers.h	2013-03-11 11:35:53.000000000 +0100
+++ vdr-2.3.1/timers.h	2015-09-09 12:40:24.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: timers.h 3.0 2013/03/11 10:35:53 kls Exp $
+ * $Id: timers.h 4.3 2015/09/09 10:40:24 kls Exp $
  */
 
 #ifndef __TIMERS_H
@@ -27,12 +27,13 @@
 class cTimer : public cListObject {
   friend class cMenuEditTimer;
 private:
+  int id;
   mutable time_t startTime, stopTime;
-  time_t lastSetEvent;
+  int scheduleState;
   mutable time_t deferred; ///< Matches(time_t, ...) will return false if the current time is before this value
-  bool recording, pending, inVpsMargin;
+  bool pending, inVpsMargin;
   uint flags;
-  cChannel *channel;
+  const cChannel *channel;
   mutable time_t day; ///< midnight of the day this timer shall hit, or of the first day it shall hit in case of a repeating timer
   int weekdays;       ///< bitmask, lowest bits: SSFTWTM  (the 'M' is the LSB)
   int start;
@@ -41,15 +42,17 @@
   int lifetime;
   mutable char file[NAME_MAX * 2 + 1]; // *2 to be able to hold 'title' and 'episode', which can each be up to 255 characters long
   char *aux;
+  char *remote;
   const cEvent *event;
 public:
-  cTimer(bool Instant = false, bool Pause = false, cChannel *Channel = NULL);
+  cTimer(bool Instant = false, bool Pause = false, const cChannel *Channel = NULL);
   cTimer(const cEvent *Event);
   cTimer(const cTimer &Timer);
   virtual ~cTimer();
   cTimer& operator= (const cTimer &Timer);
   virtual int Compare(const cListObject &ListObject) const;
-  bool Recording(void) const { return recording; }
+  int Id(void) const { return id; }
+  bool Recording(void) const { return HasFlags(tfRecording); }
   bool Pending(void) const { return pending; }
   bool InVpsMargin(void) const { return inVpsMargin; }
   uint Flags(void) const { return flags; }
@@ -63,6 +66,8 @@
   const char *File(void) const { return file; }
   time_t FirstDay(void) const { return weekdays ? day : 0; }
   const char *Aux(void) const { return aux; }
+  const char *Remote(void) const { return remote; }
+  bool Local(void) const { return !remote; } // convenience
   time_t Deferred(void) const { return deferred; }
   cString ToText(bool UseChannelID = false) const;
   cString ToDescr(void) const;
@@ -81,8 +86,9 @@
   bool Expired(void) const;
   time_t StartTime(void) const;
   time_t StopTime(void) const;
-  void SetEventFromSchedule(const cSchedules *Schedules = NULL);
-  void SetEvent(const cEvent *Event);
+  void SetId(int Id);
+  bool SetEventFromSchedule(const cSchedules *Schedules);
+  bool SetEvent(const cEvent *Event);
   void SetRecording(bool Recording);
   void SetPending(bool Pending);
   void SetInVpsMargin(bool InVpsMargin);
@@ -93,6 +99,7 @@
   void SetPriority(int Priority);
   void SetLifetime(int Lifetime);
   void SetAux(const char *Aux);
+  void SetRemote(const char *Remote);
   void SetDeferred(int Seconds);
   void SetFlags(uint Flags);
   void ClrFlags(uint Flags);
@@ -108,36 +115,104 @@
 
 class cTimers : public cConfig<cTimer> {
 private:
-  int state;
-  int beingEdited;
-  time_t lastSetEvents;
+  static cTimers timers;
+  static int lastTimerId;
   time_t lastDeleteExpired;
 public:
   cTimers(void);
+  static const cTimers *GetTimersRead(cStateKey &StateKey, int TimeoutMs = 0);
+      ///< Gets the list of timers for read access. If TimeoutMs is given,
+      ///< it will wait that long to get a write lock before giving up.
+      ///< Otherwise it will wait indefinitely. If no read lock can be
+      ///< obtained within the given timeout, NULL will be returned.
+      ///< The list is locked and a pointer to it is returned if the state
+      ///< of the list is different than the state of the given StateKey.
+      ///< If both states are equal, the list of timers has not been modified
+      ///< since the last call with the same StateKey, and NULL will be
+      ///< returned (and the list is not locked). After the returned list of
+      ///< timers is no longer needed, the StateKey's Remove() function must
+      ///< be called to release the list. The time between calling
+      ///< cTimers::GetTimersRead() and StateKey.Remove() should be as short
+      ///< as possible. After calling StateKey.Remove() the list returned from
+      ///< this call must not be accessed any more. If you need to access the
+      ///< timers again later, a new call to GetTimersRead() must be made.
+      ///< A typical code sequence would look like this:
+      ///< cStateKey StateKey;
+      ///< if (const cTimers *Timers = cTimers::GetTimersRead(StateKey)) {
+      ///<    // access the timers
+      ///<    StateKey.Remove();
+      ///<    }
+  static cTimers *GetTimersWrite(cStateKey &StateKey, int TimeoutMs = 0);
+      ///< Gets the list of timers for write access. If TimeoutMs is given,
+      ///< it will wait that long to get a write lock before giving up.
+      ///< Otherwise it will wait indefinitely. If no write lock can be
+      ///< obtained within the given timeout, NULL will be returned.
+      ///< If a write lock can be obtained, the list of timers will be
+      ///< returned, regardless of the state values of the timers or the
+      ///< given StateKey. After the returned list of timers is no longer
+      ///< needed, the StateKey's Remove() function must be called to release
+      ///< the list. The time between calling cTimers::GetTimersWrite() and
+      ///< StateKey.Remove() should be as short as possible. After calling
+      ///< StateKey.Remove() the list returned from this call must not be
+      ///< accessed any more. If you need to access the timers again later,
+      ///< a new call to GetTimersWrite() must be made. The call
+      ///< to StateKey.Remove() will increment the state of the list of
+      ///< timers and will copy the new state value to the StateKey. You can
+      ///< suppress this by using 'false' as the parameter to the call, in
+      ///< which case the state values are left untouched.
+      ///< A typical code sequence would look like this:
+      ///< cStateKey StateKey;
+      ///< if (cTimers *Timers = cTimers::GetTimersWrite(StateKey)) {
+      ///<    // access the timers
+      ///<    StateKey.Remove();
+      ///<    }
+  static bool Load(const char *FileName);
+  static int NewTimerId(void);
+  const cTimer *GetById(int Id) const;
+  cTimer *GetById(int Id) { return const_cast<cTimer *>(static_cast<const cTimers *>(this)->GetById(Id)); };
   cTimer *GetTimer(cTimer *Timer);
-  cTimer *GetMatch(time_t t);
-  cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL);
-  cTimer *GetNextActiveTimer(void);
-  int BeingEdited(void) { return beingEdited; }
-  void IncBeingEdited(void) { beingEdited++; }
-  void DecBeingEdited(void) { if (!--beingEdited) lastSetEvents = 0; }
-  void SetModified(void);
-  bool Modified(int &State);
-      ///< Returns true if any of the timers have been modified, which
-      ///< is detected by State being different than the internal state.
-      ///< Upon return the internal state will be stored in State.
-  void SetEvents(void);
-  void DeleteExpired(void);
+  const cTimer *GetMatch(time_t t) const;
+  cTimer *GetMatch(time_t t) { return const_cast<cTimer *>(static_cast<const cTimers *>(this)->GetMatch(t)); };
+  const cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL) const;
+  cTimer *GetMatch(const cEvent *Event, eTimerMatch *Match = NULL) { return const_cast<cTimer *>(static_cast<const cTimers *>(this)->GetMatch(Event, Match)); }
+  const cTimer *GetNextActiveTimer(void) const;
+  const cTimer *UsesChannel(const cChannel *Channel) const;
+  bool SetEvents(const cSchedules *Schedules);
+  bool DeleteExpired(void);
   void Add(cTimer *Timer, cTimer *After = NULL);
   void Ins(cTimer *Timer, cTimer *Before = NULL);
   void Del(cTimer *Timer, bool DeleteObject = true);
+  bool GetRemoteTimers(const char *ServerName = NULL);
+      ///< Gets the timers from the given remote machine and adds them to this
+      ///< list. If no ServerName is given, all timers from all known remote
+      ///< machines will be fetched. This function calls DelRemoteTimers() with
+      ///< the given ServerName first.
+      ///< Returns true if any remote timers have been added or deleted
+  bool DelRemoteTimers(const char *ServerName = NULL);
+      ///< Deletes all timers of the given remote machine from this list (leaves
+      ///< them untouched on the remote machine). If no ServerName is given, the
+      ///< timers of all remote machines will be deleted from the list.
+      ///< Returns true if any remote timers have been deleted.
+  void TriggerRemoteTimerPoll(const char *ServerName = NULL);
+      ///< Sends an SVDRP POLL command to the given remote machine.
+      ///< If no ServerName is given, the POLL command will be sent to all
+      ///< known remote machines.
   };
 
-extern cTimers Timers;
+// Provide lock controlled access to the list:
+
+DEF_LIST_LOCK(Timers);
+
+// These macros provide a convenient way of locking the global timers list
+// and making sure the lock is released as soon as the current scope is left
+// (note that these macros wait forever to obtain the lock!):
+
+#define LOCK_TIMERS_READ  USE_LIST_LOCK_READ(Timers)
+#define LOCK_TIMERS_WRITE USE_LIST_LOCK_WRITE(Timers)
 
 class cSortedTimers : public cVector<const cTimer *> {
 public:
-  cSortedTimers(void);
+  cSortedTimers(const cTimers *Timers);
   };
 
 #endif //__TIMERS_H
diff -ruN vdr-2.2.0/tools.c vdr-2.3.1/tools.c
--- vdr-2.2.0/tools.c	2015-02-07 16:09:17.000000000 +0100
+++ vdr-2.3.1/tools.c	2015-09-10 15:17:55.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: tools.c 3.4 2015/02/07 15:09:17 kls Exp $
+ * $Id: tools.c 4.4 2015/09/10 13:17:55 kls Exp $
  */
 
 #include "tools.h"
@@ -274,6 +274,28 @@
   return cString(s, t != NULL);
 }
 
+cString strgetval(const char *s, const char *name, char d)
+{
+  if (s && name) {
+     int l = strlen(name);
+     const char *t = s;
+     while (const char *p = strstr(t, name)) {
+           t = skipspace(p + l);
+           if (p == s || *(p - 1) <= ' ') {
+              if (*t == d) {
+                 t = skipspace(t + 1);
+                 const char *v = t;
+                 while (*t > ' ')
+                       t++;
+                 return cString(v, t);
+                 break;
+                 }
+              }
+           }
+     }
+  return NULL;
+}
+
 bool startswith(const char *s, const char *p)
 {
   while (*p) {
@@ -1061,6 +1083,17 @@
   return *this;
 }
 
+cString &cString::Append(const char *String)
+{
+  int l1 = strlen(s);
+  int l2 = strlen(String);
+  char *p = (char *)realloc(s, l1 + l2 + 1);
+  if (p != s)
+     strcpy(p, s);
+  strcpy(p + l1, String);
+  return *this;
+}
+
 cString &cString::Truncate(int Index)
 {
   int l = strlen(s);
@@ -1289,6 +1322,20 @@
   return jcd.mem;
 }
 
+// --- GetHostName -----------------------------------------------------------
+
+const char *GetHostName(void)
+{
+  static char buffer[HOST_NAME_MAX] = "";
+  if (!*buffer) {
+     if (gethostname(buffer, sizeof(buffer)) < 0) {
+        LOG_ERROR;
+        strcpy(buffer, "vdr");
+        }
+     }
+  return buffer;
+}
+
 // --- cBase64Encoder --------------------------------------------------------
 
 const char *cBase64Encoder::b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -1440,6 +1487,19 @@
   return false;
 }
 
+void cPoller::Del(int FileHandle, bool Out)
+{
+  if (FileHandle >= 0) {
+     for (int i = 0; i < numFileHandles; i++) {
+         if (pfd[i].fd == FileHandle && pfd[i].events == (Out ? POLLOUT : POLLIN)) {
+            if (i < numFileHandles - 1)
+               memmove(&pfd[i], &pfd[i + 1], (numFileHandles - i - 1) * sizeof(pollfd));
+            numFileHandles--;
+            }
+         }
+     }
+}
+
 bool cPoller::Poll(int TimeoutMs)
 {
   if (numFileHandles) {
@@ -1998,12 +2058,58 @@
   return i;
 }
 
+// --- cListGarbageCollector -------------------------------------------------
+
+#define LIST_GARBAGE_COLLECTOR_TIMEOUT 5 // seconds
+
+cListGarbageCollector ListGarbageCollector;
+
+cListGarbageCollector::cListGarbageCollector(void)
+{
+  objects = NULL;
+  lastPut = 0;
+}
+
+cListGarbageCollector::~cListGarbageCollector()
+{
+  if (objects)
+     esyslog("ERROR: ListGarbageCollector destroyed without prior Purge()!");
+}
+
+void cListGarbageCollector::Put(cListObject *Object)
+{
+  mutex.Lock();
+  Object->next = objects;
+  objects = Object;
+  lastPut = time(NULL);
+  mutex.Unlock();
+}
+
+void cListGarbageCollector::Purge(bool Force)
+{
+  mutex.Lock();
+  if (objects && (time(NULL) - lastPut > LIST_GARBAGE_COLLECTOR_TIMEOUT || Force)) {
+     // We make sure that any object stays in the garbage collector for at least
+     // LIST_GARBAGE_COLLECTOR_TIMEOUT seconds, to give objects that have pointers
+     // to them a chance to drop these references before the object is finally
+     // deleted.
+     while (cListObject *Object = objects) {
+           objects = Object->next;
+           delete Object;
+           }
+     }
+  mutex.Unlock();
+}
+
 // --- cListBase -------------------------------------------------------------
 
-cListBase::cListBase(void)
+cListBase::cListBase(const char *NeedsLocking)
+:stateLock(NeedsLocking)
 {
   objects = lastObject = NULL;
   count = 0;
+  needsLocking = NeedsLocking;
+  useGarbageCollector = needsLocking;
 }
 
 cListBase::~cListBase()
@@ -2011,6 +2117,15 @@
   Clear();
 }
 
+bool cListBase::Lock(cStateKey &StateKey, bool Write, int TimeoutMs) const
+{
+  if (needsLocking)
+     return stateLock.Lock(StateKey, Write, TimeoutMs);
+  else
+     esyslog("ERROR: cListBase::Lock() called for a list that doesn't require locking");
+  return false;
+}
+
 void cListBase::Add(cListObject *Object, cListObject *After)
 {
   if (After && After != lastObject) {
@@ -2050,8 +2165,12 @@
   if (Object == lastObject)
      lastObject = Object->Prev();
   Object->Unlink();
-  if (DeleteObject)
-     delete Object;
+  if (DeleteObject) {
+     if (useGarbageCollector)
+        ListGarbageCollector.Put(Object);
+     else
+        delete Object;
+     }
   count--;
 }
 
@@ -2095,11 +2214,30 @@
   count = 0;
 }
 
-cListObject *cListBase::Get(int Index) const
+bool cListBase::Contains(const cListObject *Object) const
+{
+  for (const cListObject *o = objects; o; o = o->Next()) {
+      if (o == Object)
+         return true;
+      }
+  return false;
+}
+
+void cListBase::SetExplicitModify(void)
+{
+  stateLock.SetExplicitModify();
+}
+
+void cListBase::SetModified(void)
+{
+  stateLock.IncState();
+}
+
+const cListObject *cListBase::Get(int Index) const
 {
   if (Index < 0)
      return NULL;
-  cListObject *object = objects;
+  const cListObject *object = objects;
   while (object && Index-- > 0)
         object = object->Next();
   return object;
@@ -2115,7 +2253,9 @@
 void cListBase::Sort(void)
 {
   int n = Count();
-  cListObject *a[n];
+  cListObject **a = MALLOC(cListObject *, n);
+  if (a == NULL)
+     return;
   cListObject *object = objects;
   int i = 0;
   while (object && i < n) {
@@ -2129,6 +2269,7 @@
       count--;
       Add(a[i]);
       }
+  free(a);
 }
 
 // --- cHashBase -------------------------------------------------------------
diff -ruN vdr-2.2.0/tools.h vdr-2.3.1/tools.h
--- vdr-2.2.0/tools.h	2015-02-07 16:12:26.000000000 +0100
+++ vdr-2.3.1/tools.h	2015-09-06 12:45:54.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: tools.h 3.7 2015/02/07 15:12:26 kls Exp $
+ * $Id: tools.h 4.3 2015/09/06 10:45:54 kls Exp $
  */
 
 #ifndef __TOOLS_H
@@ -26,6 +26,7 @@
 #include <syslog.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include "thread.h"
 
 typedef unsigned char uchar;
 
@@ -178,6 +179,7 @@
   const char * operator*() const { return s; } // for use in (const void *) context (printf() etc.)
   cString &operator=(const cString &String);
   cString &operator=(const char *String);
+  cString &Append(const char *String);
   cString &Truncate(int Index); ///< Truncate the string at the given Index (if Index is < 0 it is counted from the end of the string).
   cString &CompactChars(char c); ///< Compact any sequence of characters 'c' to a single character, and strip all of them from the beginning and end of this string.
   static cString sprintf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
@@ -209,6 +211,14 @@
 char *compactspace(char *s);
 char *compactchars(char *s, char c); ///< removes all occurrences of 'c' from the beginning an end of 's' and replaces sequences of multiple 'c's with a single 'c'.
 cString strescape(const char *s, const char *chars);
+cString strgetval(const char *s, const char *name, char d = '=');
+    ///< Returns the value part of a 'name=value' pair in s.
+    ///< name must either be at the beginning of s, or has to be preceded by white space.
+    ///< There may be any number of white space around the '=' sign. The value is
+    ///< everyting up to (and excluding) the next white space, or the end of s.
+    ///< If an other delimiter shall be used (like, e.g., ':'), it can be given
+    ///< as the third parameter.
+    ///< If name occurs more than once in s, only the first occurrence is taken.
 bool startswith(const char *s, const char *p);
 bool endswith(const char *s, const char *p);
 bool isempty(const char *s);
@@ -283,6 +293,8 @@
     ///< resulting image, where 100 is "best". The caller takes ownership of
     ///< the result and has to delete it once it is no longer needed.
     ///< The result may be NULL in case of an error.
+const char *GetHostName(void);
+    ///< Gets the host name of this machine.
 
 class cBase64Encoder {
 private:
@@ -356,12 +368,13 @@
 
 class cPoller {
 private:
-  enum { MaxPollFiles = 16 };
+  enum { MaxPollFiles = 64 };
   pollfd pfd[MaxPollFiles];
   int numFileHandles;
 public:
   cPoller(int FileHandle = -1, bool Out = false);
   bool Add(int FileHandle, bool Out);
+  void Del(int FileHandle, bool Out);
   bool Poll(int TimeoutMs = 0);
   };
 
@@ -452,6 +465,7 @@
   };
 
 class cListObject {
+  friend class cListGarbageCollector;
 private:
   cListObject *prev, *next;
 public:
@@ -468,32 +482,151 @@
   cListObject *Next(void) const { return next; }
   };
 
+class cListGarbageCollector {
+private:
+  cMutex mutex;
+  cListObject *objects;
+  time_t lastPut;
+public:
+  cListGarbageCollector(void);
+  ~cListGarbageCollector();
+  void Put(cListObject *Object);
+  void Purge(bool Force = false);
+  };
+
+extern cListGarbageCollector ListGarbageCollector;
+
 class cListBase {
 protected:
   cListObject *objects, *lastObject;
-  cListBase(void);
   int count;
+  mutable cStateLock stateLock;
+  const char *needsLocking;
+  bool useGarbageCollector;
+  cListBase(const char *NeedsLocking = NULL);
 public:
   virtual ~cListBase();
+  bool Lock(cStateKey &StateKey, bool Write = false, int TimeoutMs = 0) const;
+       ///< Tries to get a lock on this list and returns true if successful.
+       ///< By default a read lock is requested. Set Write to true to obtain
+       ///< a write lock. If TimeoutMs is not zero, it waits for the given
+       ///< number of milliseconds before giving up.
+       ///< If you need to lock more than one list at the same time, make sure
+       ///< you set TimeoutMs to a suitable value in all of the calls to
+       ///< Lock(), and be prepared to handle situations where you do not get all
+       ///< of the requested locks. In such cases you should release all the locks
+       ///< you have obtained so far and try again. StateKey.TimedOut() tells you
+       ///< whether the lock attempt failed due to a timeout or because the state
+       ///< of the lock hasn't changed since the previous locking attempt.
+       ///< To implicitly avoid deadlocks when locking more than one of the global
+       ///< lists of VDR at the same time, make sure you always lock Timers, Channels,
+       ///< Recordings and Schedules in this sequence.
+       ///< You may keep pointers to objects in this list, even after releasing
+       ///< the lock. However, you may only access such objects if you are
+       ///< holding a proper lock again. If an object has been deleted from the list
+       ///< while you did not hold a lock (for instance by an other thread), the
+       ///< object will still be there, but no longer within this list (it is then
+       ///< stored in the ListGarbageCollector). That way even if you access the object
+       ///< after it has been deleted, you won't cause a segfault. You can call the
+       ///< Contains() function to check whether an object you are holding a pointer
+       ///< to is still in the list. Note that the garbage collector is purged when
+       ///< the usual housekeeping is done.
+  void SetUseGarbageCollector(void) { useGarbageCollector = true; }
+  void SetExplicitModify(void);
+       ///< If you have obtained a write lock on this list, and you don't want it to
+       ///< be automatically marked as modified when the lock is released, a call to
+       ///< this function will disable this, and you can explicitly call SetModified()
+       ///< to have the list marked as modified.
+  void SetModified(void);
+       ///< Unconditionally marks this list as modified.
   void Add(cListObject *Object, cListObject *After = NULL);
   void Ins(cListObject *Object, cListObject *Before = NULL);
   void Del(cListObject *Object, bool DeleteObject = true);
   virtual void Move(int From, int To);
   void Move(cListObject *From, cListObject *To);
   virtual void Clear(void);
-  cListObject *Get(int Index) const;
+  bool Contains(const cListObject *Object) const;
+       ///< If a pointer to an object contained in this list has been obtained while
+       ///< holding a lock, and that lock has been released, but the pointer is kept for
+       ///< later use (after obtaining a new lock), Contains() can be called with that
+       ///< pointer to make sure the object it points to is still part of this list
+       ///< (it may have been deleted or otherwise removed from the list after the lock
+       ///< during which the pointer was initially retrieved has been released).
+  const cListObject *Get(int Index) const;
+  cListObject *Get(int Index) { return const_cast<cListObject *>(static_cast<const cListBase *>(this)->Get(Index)); }
   int Count(void) const { return count; }
   void Sort(void);
   };
 
 template<class T> class cList : public cListBase {
 public:
-  T *Get(int Index) const { return (T *)cListBase::Get(Index); }
-  T *First(void) const { return (T *)objects; }
-  T *Last(void) const { return (T *)lastObject; }
-  T *Prev(const T *object) const { return (T *)object->cListObject::Prev(); } // need to call cListObject's members to
-  T *Next(const T *object) const { return (T *)object->cListObject::Next(); } // avoid ambiguities in case of a "list of lists"
-  };
+  cList(const char *NeedsLocking = NULL): cListBase(NeedsLocking) {}
+      ///< Sets up a new cList of the given type T. If NeedsLocking is given, the list
+      ///< and any of its elements may only be accessed if the caller holds a lock
+      ///< obtained by a call to Lock() (see cListBase::Lock() for details).
+      ///< NeedsLocking is used as both a boolean flag to enable locking, and as
+      ///< a name to identify this list in debug output. It must be a static string
+      ///< and should be no longer than 10 characters. The string will not be copied!
+  const T *Get(int Index) const { return (T *)cListBase::Get(Index); }
+      ///< Returns the list element at the given Index, or NULL if no such element
+      ///< exists.
+  const T *First(void) const { return (T *)objects; }
+      ///< Returns the first element in this list, or NULL if the list is empty.
+  const T *Last(void) const { return (T *)lastObject; }
+      ///< Returns the last element in this list, or NULL if the list is empty.
+  const T *Prev(const T *Object) const { return (T *)Object->cListObject::Prev(); } // need to call cListObject's members to
+      ///< Returns the element immediately before Object in this list, or NULL
+      ///< if Object is the first element in the list. Object must not be NULL!
+  const T *Next(const T *Object) const { return (T *)Object->cListObject::Next(); } // avoid ambiguities in case of a "list of lists"
+      ///< Returns the element immediately following Object in this list, or NULL
+      ///< if Object is the last element in the list. Object must not be NULL!
+  T *Get(int Index) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Get(Index)); }
+      ///< Non-const version of Get().
+  T *First(void) { return const_cast<T *>(static_cast<const cList<T> *>(this)->First()); }
+      ///< Non-const version of First().
+  T *Last(void) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Last()); }
+      ///< Non-const version of Last().
+  T *Prev(const T *Object) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Prev(Object)); }
+      ///< Non-const version of Prev().
+  T *Next(const T *Object) { return const_cast<T *>(static_cast<const cList<T> *>(this)->Next(Object)); }
+      ///< Non-const version of Next().
+  };
+
+// The DEF_LIST_LOCK macro defines a convenience class that can be used to obtain
+// a lock on a cList and make sure the lock is released when the current scope
+// is left:
+
+#define DEF_LIST_LOCK2(Class, Name) \
+class c##Name##Lock { \
+private: \
+  cStateKey stateKey; \
+  const c##Class *list; \
+public: \
+  c##Name##Lock(bool Write = false) \
+  { \
+    if (Write) \
+       list = c##Class::Get##Name##Write(stateKey); \
+    else \
+       list = c##Class::Get##Name##Read(stateKey); \
+  } \
+  ~c##Name##Lock() { stateKey.Remove(); } \
+  const c##Class *Name(void) const { return list; } \
+  c##Class *Name(void) { return const_cast<c##Class *>(list); } \
+  }
+#define DEF_LIST_LOCK(Class) DEF_LIST_LOCK2(Class, Class)
+
+// The USE_LIST_LOCK macro sets up a local variable of a class defined by
+// a suitable DEF_LIST_LOCK, and also a pointer to the provided list:
+
+#define USE_LIST_LOCK_READ2(Class, Name) \
+c##Name##Lock Name##Lock(false); \
+const c##Class *Name __attribute__((unused)) = Name##Lock.Name();
+#define USE_LIST_LOCK_READ(Class) USE_LIST_LOCK_READ2(Class, Class)
+
+#define USE_LIST_LOCK_WRITE2(Class, Name) \
+c##Name##Lock Name##Lock(true); \
+c##Class *Name __attribute__((unused)) = Name##Lock.Name();
+#define USE_LIST_LOCK_WRITE(Class) USE_LIST_LOCK_WRITE2(Class, Class)
 
 template<class T> class cVector {
   ///< cVector may only be used for *simple* types, like int or pointers - not for class objects that allocate additional memory!
diff -ruN vdr-2.2.0/transfer.c vdr-2.3.1/transfer.c
--- vdr-2.2.0/transfer.c	2013-08-22 14:33:02.000000000 +0200
+++ vdr-2.3.1/transfer.c	2015-09-05 13:43:58.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: transfer.c 3.1 2013/08/22 12:33:02 kls Exp $
+ * $Id: transfer.c 4.1 2015/09/05 11:43:58 kls Exp $
  */
 
 #include "transfer.h"
@@ -38,7 +38,7 @@
 #define MAXRETRIES    20 // max. number of retries for a single TS packet
 #define RETRYWAIT      5 // time (in ms) between two retries
 
-void cTransfer::Receive(uchar *Data, int Length)
+void cTransfer::Receive(const uchar *Data, int Length)
 {
   if (cPlayer::IsAttached()) {
      // Transfer Mode means "live tv", so there's no point in doing any additional
diff -ruN vdr-2.2.0/transfer.h vdr-2.3.1/transfer.h
--- vdr-2.2.0/transfer.h	2013-03-01 10:49:46.000000000 +0100
+++ vdr-2.3.1/transfer.h	2015-09-05 13:43:08.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: transfer.h 3.0 2013/03/01 09:49:46 kls Exp $
+ * $Id: transfer.h 4.1 2015/09/05 11:43:08 kls Exp $
  */
 
 #ifndef __TRANSFER_H
@@ -19,7 +19,7 @@
   cPatPmtGenerator patPmtGenerator;
 protected:
   virtual void Activate(bool On);
-  virtual void Receive(uchar *Data, int Length);
+  virtual void Receive(const uchar *Data, int Length);
 public:
   cTransfer(const cChannel *Channel);
   virtual ~cTransfer();
diff -ruN vdr-2.2.0/vdr.1 vdr-2.3.1/vdr.1
--- vdr-2.2.0/vdr.1	2015-02-17 14:43:24.000000000 +0100
+++ vdr-2.3.1/vdr.1	2015-04-19 14:39:13.000000000 +0200
@@ -8,7 +8,7 @@
 .\" License as specified in the file COPYING that comes with the
 .\" vdr distribution.
 .\"
-.\" $Id: vdr.1 3.6 2015/02/17 13:43:24 kls Exp $
+.\" $Id: vdr.1 4.2 2015/04/19 12:39:13 kls Exp $
 .\"
 .TH vdr 1 "19 Feb 2015" "2.2" "Video Disk Recorder"
 .SH NAME
@@ -64,6 +64,8 @@
 .BI \-D\  num ,\ \-\-device= num
 Use only the given DVB device (\fInum\fR = 0, 1, 2...).
 There may be several \fB\-D\fR options (by default all DVB devices will be used).
+If \fB\-D\-\fR is given, no DVB devices will be used at all, independent of any
+other \-D options.
 .TP
 .BI \-\-dirnames= path [, name [, enc ]]
 Set the maximum directory path length to \fIpath\fR (default is the maximum value
@@ -204,7 +206,7 @@
 Starting vdr as 'root' is necessary if the system time shall
 be set from the transponder data, but for security reasons
 vdr can switch to a lesser privileged user id during normal
-operation.
+operation. \fIuser\fR can be a user name or a numerical id.
 .TP
 .BI \-\-updindex= rec
 Update the index file for the given recording.
diff -ruN vdr-2.2.0/vdr.5 vdr-2.3.1/vdr.5
--- vdr-2.2.0/vdr.5	2015-02-17 14:43:53.000000000 +0100
+++ vdr-2.3.1/vdr.5	2015-02-17 14:43:53.000000000 +0100
@@ -8,7 +8,7 @@
 .\" License as specified in the file COPYING that comes with the
 .\" vdr distribution.
 .\"
-.\" $Id: vdr.5 3.4 2015/02/17 13:43:53 kls Exp $
+.\" $Id: vdr.5 4.0 2015/02/17 13:43:53 kls Exp $
 .\"
 .TH vdr 5 "19 Feb 2015" "2.2" "Video Disk Recorder Files"
 .SH NAME
diff -ruN vdr-2.2.0/vdr.c vdr-2.3.1/vdr.c
--- vdr-2.2.0/vdr.c	2015-02-10 15:13:12.000000000 +0100
+++ vdr-2.3.1/vdr.c	2015-09-11 10:02:50.000000000 +0200
@@ -22,7 +22,7 @@
  *
  * The project's page is at http://www.tvdr.de
  *
- * $Id: vdr.c 3.16 2015/02/10 14:13:12 kls Exp $
+ * $Id: vdr.c 4.7 2015/09/11 08:02:50 kls Exp $
  */
 
 #include <getopt.h>
@@ -65,6 +65,7 @@
 #include "sourceparams.h"
 #include "sources.h"
 #include "status.h"
+#include "svdrp.h"
 #include "themes.h"
 #include "timers.h"
 #include "tools.h"
@@ -92,12 +93,12 @@
 
 static int LastSignal = 0;
 
-static bool SetUser(const char *UserName, bool UserDump)
+static bool SetUser(const char *User, bool UserDump)
 {
-  if (UserName) {
-     struct passwd *user = getpwnam(UserName);
+  if (User) {
+     struct passwd *user = isnumber(User) ? getpwuid(atoi(User)) : getpwnam(User);
      if (!user) {
-        fprintf(stderr, "vdr: unknown user: '%s'\n", UserName);
+        fprintf(stderr, "vdr: unknown user: '%s'\n", User);
         return false;
         }
      if (setgid(user->pw_gid) < 0) {
@@ -301,7 +302,11 @@
                     break;
           case 'd': DaemonMode = true;
                     break;
-          case 'D': if (isnumber(optarg)) {
+          case 'D': if (*optarg == '-') {
+                       cDvbDevice::useDvbDevices = false;
+                       break;
+                       }
+                    if (isnumber(optarg)) {
                        int n = atoi(optarg);
                        if (0 <= n && n < MAXDEVICES) {
                           cDevice::SetUseDevice(n);
@@ -371,7 +376,7 @@
                     break;
           case 'g' | 0x100:
                     return GenerateIndex(optarg) ? 0 : 2;
-          case 'g': cSVDRP::SetGrabImageDir(*optarg != '-' ? optarg : NULL);
+          case 'g': SetSVDRPGrabImageDir(*optarg != '-' ? optarg : NULL);
                     break;
           case 'h': DisplayHelp = true;
                     break;
@@ -505,7 +510,7 @@
 
   if (VdrUser && geteuid() == 0) {
      StartedAsRoot = true;
-     if (strcmp(VdrUser, "root")) {
+     if (strcmp(VdrUser, "root") && strcmp(VdrUser, "0")) {
         if (!SetKeepCaps(true))
            return 2;
         if (!SetUser(VdrUser, UserDump))
@@ -536,7 +541,9 @@
                "  -d,       --daemon       run in daemon mode\n"
                "  -D NUM,   --device=NUM   use only the given DVB device (NUM = 0, 1, 2...)\n"
                "                           there may be several -D options (default: all DVB\n"
-               "                           devices will be used)\n"
+               "                           devices will be used); if -D- is given, no DVB\n"
+               "                           devices will be used at all, independent of any\n"
+               "                           other -D options\n"
                "            --dirnames=PATH[,NAME[,ENC]]\n"
                "                           set the maximum directory path length to PATH\n"
                "                           (default: %d); if NAME is also given, it defines\n"
@@ -586,7 +593,7 @@
                "                           (default: %s)\n"
                "  -t TTY,   --terminal=TTY controlling tty\n"
                "  -u USER,  --user=USER    run as user USER; only applicable if started as\n"
-               "                           root\n"
+               "                           root; USER can be a user name or a numerical id\n"
                "            --updindex=REC update index for recording REC and exit\n"
                "            --userdump     allow coredumps if -u is given (debugging)\n"
                "  -v DIR,   --video=DIR    use DIR as video directory (default: %s)\n"
@@ -741,8 +748,8 @@
   Sources.Load(AddDirectory(ConfigDirectory, "sources.conf"), true, true);
   Diseqcs.Load(AddDirectory(ConfigDirectory, "diseqc.conf"), true, Setup.DiSEqC);
   Scrs.Load(AddDirectory(ConfigDirectory, "scr.conf"), true);
-  Channels.Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true);
-  Timers.Load(AddDirectory(ConfigDirectory, "timers.conf"));
+  cChannels::Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true);
+  cTimers::Load(AddDirectory(ConfigDirectory, "timers.conf"));
   Commands.Load(AddDirectory(ConfigDirectory, "commands.conf"));
   RecordingCommands.Load(AddDirectory(ConfigDirectory, "reccmds.conf"));
   SVDRPhosts.Load(AddDirectory(ConfigDirectory, "svdrphosts.conf"), true);
@@ -758,8 +765,7 @@
 
   // Recordings:
 
-  Recordings.Update();
-  DeletedRecordings.Update();
+  cRecordings::Update();
 
   // EPG data:
 
@@ -825,7 +831,7 @@
 
   // User interface:
 
-  Interface = new cInterface(SVDRPport);
+  Interface = new cInterface;
 
   // Default skins:
 
@@ -872,16 +878,20 @@
   if (!CamSlots.WaitForAllCamSlotsReady(DEVICEREADYTIMEOUT))
      dsyslog("not all CAM slots ready after %d seconds", DEVICEREADYTIMEOUT);
   if (*Setup.InitialChannel) {
+     LOCK_CHANNELS_READ;
      if (isnumber(Setup.InitialChannel)) { // for compatibility with old setup.conf files
-        if (cChannel *Channel = Channels.GetByNumber(atoi(Setup.InitialChannel)))
+        if (const cChannel *Channel = Channels->GetByNumber(atoi(Setup.InitialChannel)))
            Setup.InitialChannel = Channel->GetChannelID().ToString();
         }
-     if (cChannel *Channel = Channels.GetByChannelID(tChannelID::FromString(Setup.InitialChannel)))
+     if (const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(Setup.InitialChannel)))
         Setup.CurrentChannel = Channel->Number();
      }
   if (Setup.InitialVolume >= 0)
      Setup.CurrentVolume = Setup.InitialVolume;
-  Channels.SwitchTo(Setup.CurrentChannel);
+  {
+    LOCK_CHANNELS_READ;
+    Channels->SwitchTo(Setup.CurrentChannel);
+  }
   if (MuteAudio)
      cDevice::PrimaryDevice()->ToggleMute();
   else
@@ -907,6 +917,13 @@
   sd_notify(0, "READY=1\nSTATUS=Ready");
 #endif
 
+  // SVDRP:
+
+  SetSVDRPPorts(SVDRPport, DEFAULTSVDRPPORT);
+  StartSVDRPServerHandler();
+  if (Setup.SVDRPPeering)
+     StartSVDRPClientHandler();
+
   // Main program loop:
 
 #define DELETE_MENU ((IsInfoMenu &= (Menu == NULL)), delete Menu, Menu = NULL)
@@ -925,13 +942,14 @@
            static time_t lastTime = 0;
            if (!cDevice::PrimaryDevice()->HasProgramme()) {
               if (!CamMenuActive() && Now - lastTime > MINCHANNELWAIT) { // !CamMenuActive() to avoid interfering with the CAM if a CAM menu is open
-                 cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel());
+                 LOCK_CHANNELS_READ;
+                 const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel());
                  if (Channel && (Channel->Vpid() || Channel->Apid(0) || Channel->Dpid(0))) {
-                    if (cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(Channel->Number())) // try to switch to the original channel...
+                    if (cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels->SwitchTo(Channel->Number())) // try to switch to the original channel...
                        ;
                     else if (LastTimerChannel > 0) {
-                       Channel = Channels.GetByNumber(LastTimerChannel);
-                       if (Channel && cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(LastTimerChannel)) // ...or the one used by the last timer
+                       Channel = Channels->GetByNumber(LastTimerChannel);
+                       if (Channel && cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels->SwitchTo(LastTimerChannel)) // ...or the one used by the last timer
                           ;
                        }
                     }
@@ -959,40 +977,59 @@
               }
            }
         // Handle channel and timer modifications:
-        if (!Channels.BeingEdited() && !Timers.BeingEdited()) {
-           int modified = Channels.Modified();
-           static time_t ChannelSaveTimeout = 0;
-           static int TimerState = 0;
-           // Channels and timers need to be stored in a consistent manner,
-           // therefore if one of them is changed, we save both.
-           if (modified == CHANNELSMOD_USER || Timers.Modified(TimerState))
-              ChannelSaveTimeout = 1; // triggers an immediate save
-           else if (modified && !ChannelSaveTimeout)
-              ChannelSaveTimeout = Now + CHANNELSAVEDELTA;
-           bool timeout = ChannelSaveTimeout == 1 || ChannelSaveTimeout && Now > ChannelSaveTimeout && !cRecordControls::Active();
-           if ((modified || timeout) && Channels.Lock(false, 100)) {
-              if (timeout) {
-                 Channels.Save();
-                 Timers.Save();
-                 ChannelSaveTimeout = 0;
+        {
+          // Channels and timers need to be stored in a consistent manner,
+          // therefore if one of them is changed, we save both.
+          static time_t ChannelSaveTimeout = 0;
+          static cStateKey TimersStateKey(true);
+          static cStateKey ChannelsStateKey(true);
+          static int ChannelsModifiedByUser = 0;
+          const cTimers *Timers = cTimers::GetTimersRead(TimersStateKey);
+          const cChannels *Channels = cChannels::GetChannelsRead(ChannelsStateKey);
+          if (ChannelSaveTimeout != 1) {
+             if (Channels) {
+                if (Channels->ModifiedByUser(ChannelsModifiedByUser))
+                   ChannelSaveTimeout = 1; // triggers an immediate save
+                else if (!ChannelSaveTimeout)
+                   ChannelSaveTimeout = Now + CHANNELSAVEDELTA;
+                }
+             if (Timers)
+                ChannelSaveTimeout = 1; // triggers an immediate save
+             }
+          if (ChannelSaveTimeout && Now > ChannelSaveTimeout && !cRecordControls::Active())
+             ChannelSaveTimeout = 1; // triggers an immediate save
+          if (Timers && Channels) {
+             Channels->Save();
+             Timers->Save();
+             ChannelSaveTimeout = 0;
+             }
+          if (Channels) {
+             for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
+                 if (Channel->Modification(CHANNELMOD_RETUNE)) {
+                    cRecordControls::ChannelDataModified(Channel);
+                    if (Channel->Number() == cDevice::CurrentChannel() && cDevice::PrimaryDevice()->HasDecoder()) {
+                       if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) {
+                          if (cDevice::ActualDevice()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
+                             isyslog("retuning due to modification of channel %d (%s)", Channel->Number(), Channel->Name());
+                             Channels->SwitchTo(Channel->Number());
+                             }
+                          }
+                       }
+                    cStatus::MsgChannelChange(Channel);
+                    }
                  }
-              for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
-                  if (Channel->Modification(CHANNELMOD_RETUNE)) {
-                     cRecordControls::ChannelDataModified(Channel);
-                     if (Channel->Number() == cDevice::CurrentChannel() && cDevice::PrimaryDevice()->HasDecoder()) {
-                        if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) {
-                           if (cDevice::ActualDevice()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
-                              isyslog("retuning due to modification of channel %d (%s)", Channel->Number(), Channel->Name());
-                              Channels.SwitchTo(Channel->Number());
-                              }
-                           }
-                        }
-                     cStatus::MsgChannelChange(Channel);
-                     }
-                  }
-              Channels.Unlock();
-              }
-           }
+             }
+          // State keys are removed in reverse order!
+          if (Channels)
+             ChannelsStateKey.Remove();
+          if (Timers)
+             TimersStateKey.Remove();
+          if (ChannelSaveTimeout == 1) {
+             // Only one of them was modified, so we reset the state keys to handle them both in the next turn:
+             ChannelsStateKey.Reset();
+             TimersStateKey.Reset();
+             }
+        }
         // Channel display:
         if (!EITScanner.Active() && cDevice::CurrentChannel() != LastChannel) {
            if (!Menu)
@@ -1002,80 +1039,109 @@
            }
         if (Now - LastChannelChanged >= Setup.ZapTimeout && LastChannel != PreviousChannel[PreviousChannelIndex])
            PreviousChannel[PreviousChannelIndex ^= 1] = LastChannel;
-        // Timers and Recordings:
-        if (!Timers.BeingEdited()) {
-           // Assign events to timers:
-           Timers.SetEvents();
-           // Must do all following calls with the exact same time!
-           // Process ongoing recordings:
-           cRecordControls::Process(Now);
-           // Start new recordings:
-           cTimer *Timer = Timers.GetMatch(Now);
-           if (Timer) {
-              if (!cRecordControls::Start(Timer))
-                 Timer->SetPending(true);
-              else
-                 LastTimerChannel = Timer->Channel()->Number();
-              }
-           // Make sure timers "see" their channel early enough:
-           static time_t LastTimerCheck = 0;
-           if (Now - LastTimerCheck > TIMERCHECKDELTA) { // don't do this too often
-              InhibitEpgScan = false;
-              for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
-                  bool InVpsMargin = false;
-                  bool NeedsTransponder = false;
-                  if (Timer->HasFlags(tfActive) && !Timer->Recording()) {
-                     if (Timer->HasFlags(tfVps)) {
-                        if (Timer->Matches(Now, true, Setup.VpsMargin)) {
-                           InVpsMargin = true;
-                           Timer->SetInVpsMargin(InVpsMargin);
-                           }
-                        else if (Timer->Event()) {
-                           InVpsMargin = Timer->Event()->StartTime() <= Now && Now < Timer->Event()->EndTime();
-                           NeedsTransponder = Timer->Event()->StartTime() - Now < VPSLOOKAHEADTIME * 3600 && !Timer->Event()->SeenWithin(VPSUPTODATETIME);
-                           }
-                        else {
-                           cSchedulesLock SchedulesLock;
-                           const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
-                           if (Schedules) {
-                              const cSchedule *Schedule = Schedules->GetSchedule(Timer->Channel());
-                              InVpsMargin = !Schedule; // we must make sure we have the schedule
-                              NeedsTransponder = Schedule && !Schedule->PresentSeenWithin(VPSUPTODATETIME);
-                              }
-                           }
-                        InhibitEpgScan |= InVpsMargin | NeedsTransponder;
-                        }
-                     else
-                        NeedsTransponder = Timer->Matches(Now, true, TIMERLOOKAHEADTIME);
-                     }
-                  if (NeedsTransponder || InVpsMargin) {
-                     // Find a device that provides the required transponder:
-                     cDevice *Device = cDevice::GetDeviceForTransponder(Timer->Channel(), MINPRIORITY);
-                     if (!Device && InVpsMargin)
-                        Device = cDevice::GetDeviceForTransponder(Timer->Channel(), LIVEPRIORITY);
-                     // Switch the device to the transponder:
-                     if (Device) {
-                        bool HadProgramme = cDevice::PrimaryDevice()->HasProgramme();
-                        if (!Device->IsTunedToTransponder(Timer->Channel())) {
-                           if (Device == cDevice::ActualDevice() && !Device->IsPrimaryDevice())
-                              cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode
-                           dsyslog("switching device %d to channel %d (%s)", Device->DeviceNumber() + 1, Timer->Channel()->Number(), Timer->Channel()->Name());
-                           if (Device->SwitchChannel(Timer->Channel(), false))
-                              Device->SetOccupied(TIMERDEVICETIMEOUT);
-                           }
-                        if (cDevice::PrimaryDevice()->HasDecoder() && HadProgramme && !cDevice::PrimaryDevice()->HasProgramme())
-                           Skins.QueueMessage(mtInfo, tr("Upcoming recording!")); // the previous SwitchChannel() has switched away the current live channel
-                        }
-                     }
-                  }
-              LastTimerCheck = Now;
-              }
-           // Delete expired timers:
-           Timers.DeleteExpired();
-           }
-        if (!Menu && Recordings.NeedsUpdate()) {
-           Recordings.Update();
-           DeletedRecordings.Update();
+        {
+          // Timers and Recordings:
+          bool TimersModified = false;
+          bool TriggerRemoteTimerPoll = false;
+          static cStateKey TimersStateKey(true);
+          if (cTimers::GetTimersRead(TimersStateKey)) {
+             TriggerRemoteTimerPoll = true;
+             TimersStateKey.Remove();
+             }
+          cTimers *Timers = cTimers::GetTimersWrite(TimersStateKey);
+          // Get remote timers:
+          TimersModified |= Timers->GetRemoteTimers();
+          // Assign events to timers:
+          static cStateKey SchedulesStateKey;
+          if (const cSchedules *Schedules = cSchedules::GetSchedulesRead(SchedulesStateKey))
+             TimersModified |= Timers->SetEvents(Schedules);
+          // Must do all following calls with the exact same time!
+          // Process ongoing recordings:
+          if (cRecordControls::Process(Timers, Now)) {
+             TimersModified = true;
+             TriggerRemoteTimerPoll = true;
+             }
+          // Must keep the lock on the schedules until after processing the record
+          // controls, in order to avoid short interrupts in case the current event
+          // is replaced by a new one (which some broadcasters do, instead of just
+          // modifying the current event's data):
+          if (SchedulesStateKey.InLock())
+             SchedulesStateKey.Remove();
+          // Start new recordings:
+          if (cTimer *Timer = Timers->GetMatch(Now)) {
+             if (!cRecordControls::Start(Timers, Timer))
+                Timer->SetPending(true);
+             else
+                LastTimerChannel = Timer->Channel()->Number();
+             TimersModified = true;
+             TriggerRemoteTimerPoll = true;
+             }
+          // Make sure timers "see" their channel early enough:
+          static time_t LastTimerCheck = 0;
+          if (Now - LastTimerCheck > TIMERCHECKDELTA) { // don't do this too often
+             InhibitEpgScan = false;
+             for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
+                 if (Timer->Remote())
+                    continue;
+                 bool InVpsMargin = false;
+                 bool NeedsTransponder = false;
+                 if (Timer->HasFlags(tfActive) && !Timer->Recording()) {
+                    if (Timer->HasFlags(tfVps)) {
+                       if (Timer->Matches(Now, true, Setup.VpsMargin)) {
+                          InVpsMargin = true;
+                          Timer->SetInVpsMargin(InVpsMargin);
+                          }
+                       else if (Timer->Event()) {
+                          InVpsMargin = Timer->Event()->StartTime() <= Now && Now < Timer->Event()->EndTime();
+                          NeedsTransponder = Timer->Event()->StartTime() - Now < VPSLOOKAHEADTIME * 3600 && !Timer->Event()->SeenWithin(VPSUPTODATETIME);
+                          }
+                       else {
+                          LOCK_SCHEDULES_READ;
+                          const cSchedule *Schedule = Schedules->GetSchedule(Timer->Channel());
+                          InVpsMargin = !Schedule; // we must make sure we have the schedule
+                          NeedsTransponder = Schedule && !Schedule->PresentSeenWithin(VPSUPTODATETIME);
+                          }
+                       InhibitEpgScan |= InVpsMargin | NeedsTransponder;
+                       }
+                    else
+                       NeedsTransponder = Timer->Matches(Now, true, TIMERLOOKAHEADTIME);
+                    }
+                 if (NeedsTransponder || InVpsMargin) {
+                    // Find a device that provides the required transponder:
+                    cDevice *Device = cDevice::GetDeviceForTransponder(Timer->Channel(), MINPRIORITY);
+                    if (!Device && InVpsMargin)
+                       Device = cDevice::GetDeviceForTransponder(Timer->Channel(), LIVEPRIORITY);
+                    // Switch the device to the transponder:
+                    if (Device) {
+                       bool HadProgramme = cDevice::PrimaryDevice()->HasProgramme();
+                       if (!Device->IsTunedToTransponder(Timer->Channel())) {
+                          if (Device == cDevice::ActualDevice() && !Device->IsPrimaryDevice())
+                             cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode
+                          dsyslog("switching device %d to channel %d (%s)", Device->DeviceNumber() + 1, Timer->Channel()->Number(), Timer->Channel()->Name());
+                          if (Device->SwitchChannel(Timer->Channel(), false))
+                             Device->SetOccupied(TIMERDEVICETIMEOUT);
+                          }
+                       if (cDevice::PrimaryDevice()->HasDecoder() && HadProgramme && !cDevice::PrimaryDevice()->HasProgramme())
+                          Skins.QueueMessage(mtInfo, tr("Upcoming recording!")); // the previous SwitchChannel() has switched away the current live channel
+                       }
+                    }
+                 }
+             LastTimerCheck = Now;
+             }
+          // Delete expired timers:
+          if (Timers->DeleteExpired()) {
+             TimersModified = true;
+             TriggerRemoteTimerPoll = true;
+             }
+          // Trigger remote timer polls:
+          if (TriggerRemoteTimerPoll)
+             Timers->TriggerRemoteTimerPoll();
+          TimersStateKey.Remove(TimersModified);
+        }
+        // Recordings:
+        if (!Menu) {
+           if (cRecordings::NeedsUpdate())
+              cRecordings::Update();
            }
         // CAM control:
         if (!Menu && !cOsd::IsOpen())
@@ -1244,8 +1310,12 @@
           // Instant recording:
           case kRecord:
                if (!cControl::Control()) {
-                  if (cRecordControls::Start())
-                     Skins.QueueMessage(mtInfo, tr("Recording started"));
+                  if (Setup.RecordKeyHandling) {
+                     if (Setup.RecordKeyHandling > 1 || Interface->Confirm(tr("Start recording?"))) {
+                        if (cRecordControls::Start())
+                           Skins.QueueMessage(mtInfo, tr("Recording started"));
+                        }
+                     }
                   key = kNone; // nobody else needs to see this key
                   }
                break;
@@ -1348,7 +1418,8 @@
              case k0: {
                   if (PreviousChannel[PreviousChannelIndex ^ 1] == LastChannel || LastChannel != PreviousChannel[0] && LastChannel != PreviousChannel[1])
                      PreviousChannelIndex ^= 1;
-                  Channels.SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]);
+                  LOCK_CHANNELS_READ;
+                  Channels->SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]);
                   break;
                   }
              // Direct Channel Select:
@@ -1412,7 +1483,7 @@
         // Keep the recordings handler alive:
         RecordingsHandler.Active();
 
-        if ((Now - LastInteract) > ACTIVITYTIMEOUT && !cRecordControls::Active() && !RecordingsHandler.Active() && !Interface->HasSVDRPConnection() && (Now - cRemote::LastActivity()) > ACTIVITYTIMEOUT) {
+        if ((Now - LastInteract) > ACTIVITYTIMEOUT && !cRecordControls::Active() && !RecordingsHandler.Active() && (Now - cRemote::LastActivity()) > ACTIVITYTIMEOUT) {
            // Handle housekeeping tasks
 
            // Shutdown:
@@ -1436,7 +1507,7 @@
 
            // Disk housekeeping:
            RemoveDeletedRecordings();
-           ClearVanishedRecordings();
+           ListGarbageCollector.Purge();
            cSchedules::Cleanup();
            // Plugins housekeeping:
            PluginManager.Housekeeping();
@@ -1460,6 +1531,8 @@
   signal(SIGPIPE, SIG_DFL);
   signal(SIGALRM, SIG_DFL);
 
+  StopSVDRPClientHandler();
+  StopSVDRPServerHandler();
   PluginManager.StopPlugins();
   cRecordControls::Shutdown();
   RecordingsHandler.DelAll();
@@ -1480,8 +1553,9 @@
   cPositioner::DestroyPositioner();
   cVideoDirectory::Destroy();
   EpgHandlers.Clear();
-  PluginManager.Shutdown(true);
   cSchedules::Cleanup(true);
+  ListGarbageCollector.Purge(true);
+  PluginManager.Shutdown(true);
   ReportEpgBugFixStats(true);
   if (WatchdogTimeout > 0)
      dsyslog("max. latency time %d seconds", MaxLatencyTime);
diff -ruN vdr-2.2.0/videodir.c vdr-2.3.1/videodir.c
--- vdr-2.2.0/videodir.c	2013-10-11 11:38:07.000000000 +0200
+++ vdr-2.3.1/videodir.c	2015-08-11 15:39:59.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: videodir.c 3.4 2013/10/11 09:38:07 kls Exp $
+ * $Id: videodir.c 4.1 2015/08/11 13:39:59 kls Exp $
  */
 
 #include "videodir.h"
@@ -19,24 +19,31 @@
 #include "recording.h"
 #include "tools.h"
 
+cMutex cVideoDirectory::mutex;
 cString cVideoDirectory::name;
 cVideoDirectory *cVideoDirectory::current = NULL;
 
 cVideoDirectory::cVideoDirectory(void)
 {
+  mutex.Lock();
   delete current;
   current = this;
+  mutex.Unlock();
 }
 
 cVideoDirectory::~cVideoDirectory()
 {
+  mutex.Lock();
   current = NULL;
+  mutex.Unlock();
 }
 
 cVideoDirectory *cVideoDirectory::Current(void)
 {
+  mutex.Lock();
   if (!current)
-     current = new cVideoDirectory;
+     new cVideoDirectory;
+  mutex.Unlock();
   return current;
 }
 
@@ -141,7 +148,8 @@
 {
   int used = 0;
   int free = Current()->FreeMB(&used);
-  int deleted = DeletedRecordings.TotalFileSizeMB();
+  LOCK_DELETEDRECORDINGS_READ;
+  int deleted = DeletedRecordings->TotalFileSizeMB();
   if (deleted > used)
      deleted = used; // let's not get beyond 100%
   free += deleted;
@@ -202,7 +210,8 @@
      if (FreeMB != freeMB) {
         usedPercent = UsedPercent;
         freeMB = FreeMB;
-        double MBperMinute = Recordings.MBperMinute();
+        LOCK_RECORDINGS_READ;
+        double MBperMinute = Recordings->MBperMinute();
         if (MBperMinute <= 0)
            MBperMinute = MB_PER_MINUTE;
         freeMinutes = int(double(FreeMB) / MBperMinute);
diff -ruN vdr-2.2.0/videodir.h vdr-2.3.1/videodir.h
--- vdr-2.2.0/videodir.h	2013-10-11 11:37:48.000000000 +0200
+++ vdr-2.3.1/videodir.h	2015-08-10 15:21:29.000000000 +0200
@@ -4,7 +4,7 @@
  * See the main source file 'vdr.c' for copyright information and
  * how to reach the author.
  *
- * $Id: videodir.h 3.2 2013/10/11 09:37:48 kls Exp $
+ * $Id: videodir.h 4.1 2015/08/10 13:21:29 kls Exp $
  */
 
 #ifndef __VIDEODIR_H
@@ -15,6 +15,7 @@
 
 class cVideoDirectory {
 private:
+  static cMutex mutex;
   static cString name;
   static cVideoDirectory *current;
   static cVideoDirectory *Current(void);
