summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKlaus Schmidinger <vdr@tvdr.de>2015-05-22 13:44:43 +0200
committerKlaus Schmidinger <vdr@tvdr.de>2015-05-22 13:44:43 +0200
commitc3b03475569b7a59186b5a30c09325a4b3408cbe (patch)
tree38fa011cc6c2ca151a6233d4ec8308288cf11be1
parent2b9e988dd563d66a8a341a359d17c51032cbca40 (diff)
downloadvdr-c3b03475569b7a59186b5a30c09325a4b3408cbe.tar.gz
vdr-c3b03475569b7a59186b5a30c09325a4b3408cbe.tar.bz2
Implemented SVDRP peering
-rw-r--r--HISTORY24
-rw-r--r--PLUGINS.html18
-rw-r--r--svdrp.c830
-rw-r--r--svdrp.h46
-rw-r--r--tools.c48
-rw-r--r--tools.h14
-rw-r--r--vdr.c4
7 files changed, 827 insertions, 157 deletions
diff --git a/HISTORY b/HISTORY
index 20a6b682..e65f99d7 100644
--- a/HISTORY
+++ b/HISTORY
@@ -8652,8 +8652,22 @@ Video Disk Recorder Revision History
"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 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.
+- 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 class cSVDRPCommand can be used to execute an SVDRP command on one of
+ the servers this VDR is connected to, and retrieve the result.
diff --git a/PLUGINS.html b/PLUGINS.html
index 6f47f2f5..dd7a154f 100644
--- a/PLUGINS.html
+++ b/PLUGINS.html
@@ -38,7 +38,7 @@ Copyright &copy; 2015 Klaus Schmidinger<br>
<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 @@ structures and allows it to hook itself into specific areas to perform special a
<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 @@ The returned string may consist of several lines, separated by the newline chara
('<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 bool SetPlayMode(ePlayMode PlayMode);
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 @@ new cMyDeviceHook;
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 @@ You should create your derived positioner object in the
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 @@ to signal VDR that no other EPG handlers shall be queried after this one.
<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 @@ You should create your derived video directory object in the
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 --git a/svdrp.c b/svdrp.c
index f249d169..d621363d 100644
--- a/svdrp.c
+++ b/svdrp.c
@@ -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 4.1 2015/04/29 13:10:01 kls Exp $
+ * $Id: svdrp.c 4.2 2015/05/22 11:01:33 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>
@@ -38,32 +39,94 @@
#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 const char *HostName(void)
+{
+ static char buffer[HOST_NAME_MAX] = "";
+ if (!*buffer) {
+ if (gethostname(buffer, sizeof(buffer)) < 0) {
+ LOG_ERROR;
+ strcpy(buffer, "vdr");
+ }
+ }
+ return buffer;
+}
+
+// --- 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 ---------------------------------------------------------------
+#define MAXUDPBUF 1024
+
class cSocket {
private:
int port;
+ bool tcp;
int sock;
- int queue;
- cString lastAcceptedConnection;
+ cIpAddress lastIpAddress;
+ bool IsOwnInterface(sockaddr_in *Addr);
public:
- cSocket(int Port, int Queue = 1);
+ cSocket(int Port, bool Tcp);
~cSocket();
- bool Open(void);
+ 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);
- const char *LastAcceptedConnection(void) const { return lastAcceptedConnection; }
+ cString Discover(void);
+ const cIpAddress *LastIpAddress(void) const { return &lastIpAddress; }
};
-cSocket::cSocket(int Port, int Queue)
+cSocket::cSocket(int Port, bool Tcp)
{
port = Port;
+ tcp = Tcp;
sock = -1;
- queue = Queue;
}
cSocket::~cSocket()
@@ -71,6 +134,30 @@ cSocket::~cSocket()
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) {
@@ -79,75 +166,176 @@ void cSocket::Close(void)
}
}
-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 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;
}
- isyslog("SVDRP listening on port %d/tcp", port);
}
- 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;
}
- lastAcceptedConnection = cString::sprintf("%s:%hu", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port));
- isyslog("SVDRP %s connection %s", *lastAcceptedConnection, accepted ? "accepted" : "DENIED");
+ lastIpAddress.Set((sockaddr *)&Addr);
+ isyslog("SVDRP < %s 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;
+}
+
// --- cPUTEhandler ----------------------------------------------------------
class cPUTEhandler {
@@ -208,7 +396,7 @@ bool cPUTEhandler::Process(const char *s)
return false;
}
-// --- cSVDRP ----------------------------------------------------------------
+// --- cSVDRPServer ----------------------------------------------------------
#define MAXHELPTOPIC 10
#define EITDISABLETIME 10 // seconds until EIT processing is enabled again after a CLRE command
@@ -311,6 +499,10 @@ const char *HelpPages[] = {
" 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"
@@ -420,7 +612,7 @@ const char *GetHelpPage(const char *Cmd, const char **p)
static cString grabImageDir;
-class cSVDRP {
+class cSVDRPServer {
private:
int socket;
cString connection;
@@ -456,6 +648,7 @@ private:
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 CmdPUTE(const char *Option);
@@ -467,13 +660,16 @@ private:
void CmdVOLU(const char *Option);
void Execute(char *Cmd);
public:
- cSVDRP(int Socket, const char *Connection);
- ~cSVDRP();
+ cSVDRPServer(int Socket, const char *Connection);
+ ~cSVDRPServer();
bool HasConnection(void) { return file.IsOpen(); }
bool Process(void);
};
-cSVDRP::cSVDRP(int Socket, const char *Connection)
+static cPoller SVDRPServerPoller;
+static cPoller SVDRPClientPoller;
+
+cSVDRPServer::cSVDRPServer(int Socket, const char *Connection)
{
socket = Socket;
connection = Connection;
@@ -483,37 +679,35 @@ cSVDRP::cSVDRP(int Socket, const char *Connection)
cmdLine = MALLOC(char, length);
lastActivity = time(NULL);
if (file.Open(socket)) {
- //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");
+ Reply(220, "%s SVDRP VideoDiskRecorder %s; %s; %s", HostName(), 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", HostName(), Timeout ? " (timeout)" : "");
}
- isyslog("SVDRP %s connection closed", *connection);
+ 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);
@@ -525,7 +719,7 @@ bool cSVDRP::Send(const char *s, int length)
return true;
}
-void cSVDRP::Reply(int Code, const char *fmt, ...)
+void cSVDRPServer::Reply(int Code, const char *fmt, ...)
{
if (file.IsOpen()) {
if (Code != 0) {
@@ -548,12 +742,12 @@ void cSVDRP::Reply(int Code, const char *fmt, ...)
}
else {
Reply(451, "Zero return code - looks like a programming error!");
- esyslog("SVDRP %s zero return code!", *connection);
+ esyslog("SVDRP < %s zero return code!", *connection);
}
}
}
-void cSVDRP::PrintHelpTopics(const char **hp)
+void cSVDRPServer::PrintHelpTopics(const char **hp)
{
int NumPages = 0;
if (hp) {
@@ -579,7 +773,7 @@ void cSVDRP::PrintHelpTopics(const char **hp)
}
}
-void cSVDRP::CmdCHAN(const char *Option)
+void cSVDRPServer::CmdCHAN(const char *Option)
{
if (*Option) {
int n = -1;
@@ -645,7 +839,7 @@ void cSVDRP::CmdCHAN(const char *Option)
Reply(550, "Unable to find channel \"%d\"", cDevice::CurrentChannel());
}
-void cSVDRP::CmdCLRE(const char *Option)
+void cSVDRPServer::CmdCLRE(const char *Option)
{
if (*Option) {
tChannelID ChannelID = tChannelID::InvalidID;
@@ -710,7 +904,7 @@ void cSVDRP::CmdCLRE(const char *Option)
}
}
-void cSVDRP::CmdDELC(const char *Option)
+void cSVDRPServer::CmdDELC(const char *Option)
{
if (*Option) {
if (isnumber(Option)) {
@@ -735,7 +929,7 @@ void cSVDRP::CmdDELC(const char *Option)
Channels.Del(channel);
Channels.ReNumber();
Channels.SetModified(true);
- isyslog("SVDRP %s channel %s deleted", *connection, Option);
+ isyslog("SVDRP < %s channel %s deleted", *connection, Option);
if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
Channels.SwitchTo(CurrentChannel->Number());
@@ -773,7 +967,7 @@ static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecor
return NULL;
}
-void cSVDRP::CmdDELR(const char *Option)
+void cSVDRPServer::CmdDELR(const char *Option)
{
if (*Option) {
if (isnumber(Option)) {
@@ -800,7 +994,7 @@ void cSVDRP::CmdDELR(const char *Option)
Reply(501, "Missing recording number");
}
-void cSVDRP::CmdDELT(const char *Option)
+void cSVDRPServer::CmdDELT(const char *Option)
{
if (*Option) {
if (isnumber(Option)) {
@@ -808,7 +1002,7 @@ void cSVDRP::CmdDELT(const char *Option)
cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1);
if (timer) {
if (!timer->Recording()) {
- isyslog("SVDRP %s deleting timer %s", *connection, *timer->ToDescr());
+ isyslog("SVDRP < %s deleting timer %s", *connection, *timer->ToDescr());
Timers.Del(timer);
Timers.SetModified();
Reply(250, "Timer \"%s\" deleted", Option);
@@ -829,7 +1023,7 @@ void cSVDRP::CmdDELT(const char *Option)
Reply(501, "Missing timer number");
}
-void cSVDRP::CmdEDIT(const char *Option)
+void cSVDRPServer::CmdEDIT(const char *Option)
{
if (*Option) {
if (isnumber(Option)) {
@@ -855,7 +1049,7 @@ void cSVDRP::CmdEDIT(const char *Option)
Reply(501, "Missing recording number");
}
-void cSVDRP::CmdGRAB(const char *Option)
+void cSVDRPServer::CmdGRAB(const char *Option)
{
const char *FileName = NULL;
bool Jpeg = true;
@@ -962,7 +1156,7 @@ void cSVDRP::CmdGRAB(const char *Option)
int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
if (fd >= 0) {
if (safe_write(fd, Image, ImageSize) == ImageSize) {
- dsyslog("SVDRP %s grabbed image to %s", *connection, FileName);
+ dsyslog("SVDRP < %s grabbed image to %s", *connection, FileName);
Reply(250, "Grabbed image %s", Option);
}
else {
@@ -992,7 +1186,7 @@ void cSVDRP::CmdGRAB(const char *Option)
Reply(501, "Missing filename");
}
-void cSVDRP::CmdHELP(const char *Option)
+void cSVDRPServer::CmdHELP(const char *Option)
{
if (*Option) {
const char *hp = GetHelpPage(Option, HelpPages);
@@ -1020,7 +1214,7 @@ void cSVDRP::CmdHELP(const char *Option)
Reply(214, "End of HELP info");
}
-void cSVDRP::CmdHITK(const char *Option)
+void cSVDRPServer::CmdHITK(const char *Option)
{
if (*Option) {
if (!cRemote::Enabled()) {
@@ -1059,7 +1253,7 @@ void cSVDRP::CmdHITK(const char *Option)
}
}
-void cSVDRP::CmdLSTC(const char *Option)
+void cSVDRPServer::CmdLSTC(const char *Option)
{
bool WithGroupSeps = strcasecmp(Option, ":groups") == 0;
if (*Option && !WithGroupSeps) {
@@ -1101,7 +1295,7 @@ void cSVDRP::CmdLSTC(const char *Option)
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);
@@ -1184,7 +1378,7 @@ void cSVDRP::CmdLSTE(const char *Option)
Reply(451, "Can't get EPG data");
}
-void cSVDRP::CmdLSTR(const char *Option)
+void cSVDRPServer::CmdLSTR(const char *Option)
{
int Number = 0;
bool Path = false;
@@ -1244,7 +1438,7 @@ void cSVDRP::CmdLSTR(const char *Option)
Reply(550, "No recordings available");
}
-void cSVDRP::CmdLSTT(const char *Option)
+void cSVDRPServer::CmdLSTT(const char *Option)
{
int Number = 0;
bool Id = false;
@@ -1286,10 +1480,10 @@ void cSVDRP::CmdLSTT(const char *Option)
Reply(550, "No timers defined");
}
-void cSVDRP::CmdMESG(const char *Option)
+void cSVDRPServer::CmdMESG(const char *Option)
{
if (*Option) {
- isyslog("SVDRP %s message '%s'", *connection, Option);
+ isyslog("SVDRP < %s message '%s'", *connection, Option);
Skins.QueueMessage(mtInfo, Option);
Reply(250, "Message queued");
}
@@ -1297,7 +1491,7 @@ void cSVDRP::CmdMESG(const char *Option)
Reply(501, "Missing message");
}
-void cSVDRP::CmdMODC(const char *Option)
+void cSVDRPServer::CmdMODC(const char *Option)
{
if (*Option) {
char *tail;
@@ -1313,7 +1507,7 @@ void cSVDRP::CmdMODC(const char *Option)
*channel = ch;
Channels.ReNumber();
Channels.SetModified(true);
- isyslog("SVDRP %s modifed channel %d %s", *connection, channel->Number(), *channel->ToText());
+ isyslog("SVDRP < %s modifed channel %d %s", *connection, channel->Number(), *channel->ToText());
Reply(250, "%d %s", channel->Number(), *channel->ToText());
}
else
@@ -1335,7 +1529,7 @@ void cSVDRP::CmdMODC(const char *Option)
Reply(501, "Missing channel settings");
}
-void cSVDRP::CmdMODT(const char *Option)
+void cSVDRPServer::CmdMODT(const char *Option)
{
if (*Option) {
char *tail;
@@ -1356,7 +1550,7 @@ void cSVDRP::CmdMODT(const char *Option)
}
*timer = t;
Timers.SetModified();
- isyslog("SVDRP %s timer %s modified (%s)", *connection, *timer->ToDescr(), timer->HasFlags(tfActive) ? "active" : "inactive");
+ isyslog("SVDRP < %s timer %s modified (%s)", *connection, *timer->ToDescr(), timer->HasFlags(tfActive) ? "active" : "inactive");
Reply(250, "%d %s", timer->Index() + 1, *timer->ToText());
}
else
@@ -1372,7 +1566,7 @@ void cSVDRP::CmdMODT(const char *Option)
Reply(501, "Missing timer settings");
}
-void cSVDRP::CmdMOVC(const char *Option)
+void cSVDRPServer::CmdMOVC(const char *Option)
{
if (*Option) {
if (!Channels.BeingEdited() && !Timers.BeingEdited()) {
@@ -1400,7 +1594,7 @@ void cSVDRP::CmdMOVC(const char *Option)
else
cDevice::SetCurrentChannel(CurrentChannel);
}
- isyslog("SVDRP %s channel %d moved to %d", *connection, FromNumber, ToNumber);
+ isyslog("SVDRP < %s channel %d moved to %d", *connection, FromNumber, ToNumber);
Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
}
else
@@ -1425,7 +1619,7 @@ void cSVDRP::CmdMOVC(const char *Option)
Reply(501, "Missing channel number");
}
-void cSVDRP::CmdMOVR(const char *Option)
+void cSVDRPServer::CmdMOVR(const char *Option)
{
if (*Option) {
char *opt = strdup(Option);
@@ -1465,7 +1659,7 @@ void cSVDRP::CmdMOVR(const char *Option)
Reply(501, "Missing recording number");
}
-void cSVDRP::CmdNEWC(const char *Option)
+void cSVDRPServer::CmdNEWC(const char *Option)
{
if (*Option) {
cChannel ch;
@@ -1476,7 +1670,7 @@ void cSVDRP::CmdNEWC(const char *Option)
Channels.Add(channel);
Channels.ReNumber();
Channels.SetModified(true);
- isyslog("SVDRP %s new channel %d %s", *connection, channel->Number(), *channel->ToText());
+ isyslog("SVDRP < %s new channel %d %s", *connection, channel->Number(), *channel->ToText());
Reply(250, "%d %s", channel->Number(), *channel->ToText());
}
else
@@ -1489,14 +1683,14 @@ void cSVDRP::CmdNEWC(const char *Option)
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("SVDRP %s timer %s added", *connection, *timer->ToDescr());
+ isyslog("SVDRP < %s timer %s added", *connection, *timer->ToDescr());
Reply(250, "%d %s", timer->Index() + 1, *timer->ToText());
return;
}
@@ -1508,7 +1702,7 @@ void cSVDRP::CmdNEWT(const char *Option)
Reply(501, "Missing timer settings");
}
-void cSVDRP::CmdNEXT(const char *Option)
+void cSVDRPServer::CmdNEXT(const char *Option)
{
cTimer *t = Timers.GetNextActiveTimer();
if (t) {
@@ -1527,7 +1721,12 @@ void cSVDRP::CmdNEXT(const char *Option)
Reply(550, "No active timers");
}
-void cSVDRP::CmdPLAY(const char *Option)
+void cSVDRPServer::CmdPING(const char *Option)
+{
+ Reply(250, "%s is alive", HostName());
+}
+
+void cSVDRPServer::CmdPLAY(const char *Option)
{
if (*Option) {
char *opt = strdup(Option);
@@ -1570,7 +1769,7 @@ void cSVDRP::CmdPLAY(const char *Option)
Reply(501, "Missing recording number");
}
-void cSVDRP::CmdPLUG(const char *Option)
+void cSVDRPServer::CmdPLUG(const char *Option)
{
if (*Option) {
char *opt = strdup(Option);
@@ -1641,7 +1840,7 @@ void cSVDRP::CmdPLUG(const char *Option)
}
}
-void cSVDRP::CmdPUTE(const char *Option)
+void cSVDRPServer::CmdPUTE(const char *Option)
{
if (*Option) {
FILE *f = fopen(Option, "r");
@@ -1666,7 +1865,7 @@ void cSVDRP::CmdPUTE(const char *Option)
}
}
-void cSVDRP::CmdREMO(const char *Option)
+void cSVDRPServer::CmdREMO(const char *Option)
{
if (*Option) {
if (!strcasecmp(Option, "ON")) {
@@ -1684,13 +1883,13 @@ void cSVDRP::CmdREMO(const char *Option)
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) {
@@ -1705,7 +1904,7 @@ void cSVDRP::CmdSTAT(const char *Option)
Reply(501, "No option given");
}
-void cSVDRP::CmdUPDT(const char *Option)
+void cSVDRPServer::CmdUPDT(const char *Option)
{
if (*Option) {
cTimer *timer = new cTimer;
@@ -1716,11 +1915,11 @@ void cSVDRP::CmdUPDT(const char *Option)
t->Parse(Option);
delete timer;
timer = t;
- isyslog("SVDRP %s timer %s updated", *connection, *timer->ToDescr());
+ isyslog("SVDRP < %s timer %s updated", *connection, *timer->ToDescr());
}
else {
Timers.Add(timer);
- isyslog("SVDRP %s timer %s added", *connection, *timer->ToDescr());
+ isyslog("SVDRP < %s timer %s added", *connection, *timer->ToDescr());
}
Timers.SetModified();
Reply(250, "%d %s", timer->Index() + 1, *timer->ToText());
@@ -1737,13 +1936,13 @@ void cSVDRP::CmdUPDT(const char *Option)
Reply(501, "Missing timer settings");
}
-void cSVDRP::CmdUPDR(const char *Option)
+void cSVDRPServer::CmdUPDR(const char *Option)
{
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))
@@ -1767,7 +1966,7 @@ void cSVDRP::CmdVOLU(const char *Option)
#define CMD(c) (strcasecmp(Cmd, c) == 0)
-void cSVDRP::Execute(char *Cmd)
+void cSVDRPServer::Execute(char *Cmd)
{
// handle PUTE data:
if (PUTEhandler) {
@@ -1808,6 +2007,7 @@ void cSVDRP::Execute(char *Cmd)
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("PUTE")) CmdPUTE(s);
@@ -1821,7 +2021,7 @@ void cSVDRP::Execute(char *Cmd)
else Reply(500, "Command unrecognized: \"%s\"", Cmd);
}
-bool cSVDRP::Process(void)
+bool cSVDRPServer::Process(void)
{
if (file.IsOpen()) {
while (file.Ready(false)) {
@@ -1863,7 +2063,7 @@ bool cSVDRP::Process(void)
cmdLine = NewBuffer;
}
else {
- esyslog("SVDRP %s ERROR: out of memory", *connection);
+ esyslog("SVDRP < %s ERROR: out of memory", *connection);
Close();
break;
}
@@ -1874,12 +2074,12 @@ bool cSVDRP::Process(void)
lastActivity = time(NULL);
}
else if (r <= 0) {
- isyslog("SVDRP %s lost connection to client", *connection);
+ isyslog("SVDRP < %s lost connection to client", *connection);
Close();
}
}
if (Setup.SVDRPTimeout && time(NULL) - lastActivity > Setup.SVDRPTimeout) {
- isyslog("SVDRP %s timeout on connection", *connection);
+ isyslog("SVDRP < %s timeout on connection", *connection);
Close(true, true);
}
}
@@ -1891,70 +2091,426 @@ void SetSVDRPGrabImageDir(const char *GrabImageDir)
grabImageDir = GrabImageDir;
}
-// --- cSVDRPHandler ---------------------------------------------------------
+// --- cSVDRPClient ----------------------------------------------------------
-class cSVDRPHandler : public cThread {
+class cSVDRPClient {
private:
+ cIpAddress ipAddress;
cSocket socket;
- cVector<cSVDRP *> connections;
+ cString serverName;
+ int timeout;
+ cTimeMs pingTime;
+ cFile file;
+ 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 Ping(void);
+ bool Process(cStringList *Response = NULL);
+ bool Execute(const char *Command, cStringList *Response);
+ };
+
+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);
+ if (socket.Connect(Address)) {
+ if (file.Open(socket.Socket()))
+ SVDRPClientPoller.Add(file, false);
+ }
+ dsyslog("SVDRP > %s client created for '%s'", ipAddress.Connection(), *serverName);
+ SendSVDRPDiscover(Address);
+}
+
+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::Ping(void)
+{
+ cSVDRPCommand Cmd(serverName, "PING");
+ if (Cmd.Execute())
+ return Cmd.Response(0) && atoi(Cmd.Response(0)) == 250;
+ return false;
+}
+
+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 (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())
+ Ping();
+ }
+ return file.IsOpen();
+}
+
+bool cSVDRPClient::Execute(const char *Command, cStringList *Response)
+{
+ Response->Clear();
+ return Send(Command) && Process(Response);
+}
+
+// --- 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:
- cSVDRPHandler(int Port);
- virtual ~cSVDRPHandler();
+ cSVDRPServerHandler(int TcpPort);
+ virtual ~cSVDRPServerHandler();
+ void WaitUntilReady(void);
};
-cSVDRPHandler::cSVDRPHandler(int Port)
-:cThread("SVDRP handler", true)
-,socket(Port)
+cSVDRPServerHandler::cSVDRPServerHandler(int TcpPort)
+:cThread("SVDRP server handler", true)
+,tcpSocket(TcpPort, true)
{
+ ready = false;
}
-cSVDRPHandler::~cSVDRPHandler()
+cSVDRPServerHandler::~cSVDRPServerHandler()
{
Cancel(3);
+ for (int i = 0; i < serverConnections.Size(); i++)
+ delete serverConnections[i];
}
-void cSVDRPHandler::ProcessConnections(void)
+void cSVDRPServerHandler::WaitUntilReady(void)
{
- for (int i = 0; i < connections.Size(); i++) {
- if (connections[i]) {
- if (!connections[i]->Process()) {
- delete connections[i];
- connections.Remove(i);
- i--;
- }
+ 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 cSVDRPHandler::Action(void)
+void cSVDRPServerHandler::HandleServerConnection(void)
+{
+ int NewSocket = tcpSocket.Accept();
+ if (NewSocket >= 0)
+ serverConnections.Append(new cSVDRPServer(NewSocket, tcpSocket.LastIpAddress()->Connection()));
+}
+
+void cSVDRPServerHandler::Action(void)
{
- if (socket.Open()) {
+ if (tcpSocket.Listen()) {
+ SVDRPServerPoller.Add(tcpSocket.Socket(), false);
+ ready = true;
while (Running()) {
- cFile::AnyFileReady(socket.Socket(), 1000);
- int NewSocket = socket.Accept();
- if (NewSocket >= 0)
- connections.Append(new cSVDRP(NewSocket, socket.LastAcceptedConnection()));
+ SVDRPServerPoller.Poll(1000);
+ cMutexLock MutexLock(&mutex);
+ HandleServerConnection();
ProcessConnections();
}
- socket.Close();
+ SVDRPServerPoller.Del(tcpSocket.Socket(), false);
+ tcpSocket.Close();
+ }
+}
+
+// --- cSVDRPClientHandler ---------------------------------------------------
+
+class cSVDRPClientHandler : public cThread {
+private:
+ cMutex mutex;
+ cSocket udpSocket;
+ int tcpPort;
+ cVector<cSVDRPClient *> clientConnections;
+ void HandleClientConnection(void);
+ void ProcessConnections(void);
+ cSVDRPClient *GetClientForServer(const char *ServerName);
+protected:
+ virtual void Action(void);
+public:
+ cSVDRPClientHandler(int UdpPort, int TcpPort);
+ virtual ~cSVDRPClientHandler();
+ void SendDiscover(const char *Address = NULL);
+ bool Execute(const char *ServerName, const char *Command, cStringList *Response);
+ bool GetServerNames(cStringList *ServerNames);
+ };
+
+cSVDRPClientHandler::cSVDRPClientHandler(int UdpPort, int TcpPort)
+: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", HostName(), 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
+ clientConnections.Append(new cSVDRPClient(udpSocket.LastIpAddress()->Address(), Port, ServerName, Timeout));
+ 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());
}
}
-static cSVDRPHandler *SVDRPHandler = NULL;
+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();
+ }
+}
-void StartSVDRPHandler(int Port)
+bool cSVDRPClientHandler::Execute(const char *ServerName, const char *Command, cStringList *Response)
{
- if (Port && !SVDRPHandler) {
- SVDRPHandler = new cSVDRPHandler(Port);
- SVDRPHandler->Start();
+ cMutexLock MutexLock(&mutex);
+ if (cSVDRPClient *Client = GetClientForServer(ServerName))
+ return Client->Execute(Command, Response);
+ return false;
+}
+
+bool cSVDRPClientHandler::GetServerNames(cStringList *ServerNames)
+{
+ cMutexLock MutexLock(&mutex);
+ ServerNames->Clear();
+ for (int i = 0; i < clientConnections.Size(); i++)
+ ServerNames->Append(strdup(clientConnections[i]->ServerName()));
+ return ServerNames->Size() > 0;
+}
+
+// --- SVDRP Handler ---------------------------------------------------------
+
+static cMutex SVDRPHandlerMutex;
+static cSVDRPServerHandler *SVDRPServerHandler = NULL;
+static cSVDRPClientHandler *SVDRPClientHandler = NULL;
+
+void StartSVDRPHandler(int TcpPort, int UdpPort)
+{
+ cMutexLock MutexLock(&SVDRPHandlerMutex);
+ if (TcpPort && !SVDRPServerHandler) {
+ SVDRPServerHandler = new cSVDRPServerHandler(TcpPort);
+ SVDRPServerHandler->Start();
+ SVDRPServerHandler->WaitUntilReady();
+ }
+ if (UdpPort && !SVDRPClientHandler) {
+ SVDRPClientHandler = new cSVDRPClientHandler(UdpPort, TcpPort);
+ SVDRPClientHandler->Start();
}
}
void StopSVDRPHandler(void)
{
- delete SVDRPHandler;
- SVDRPHandler = NULL;
+ cMutexLock MutexLock(&SVDRPHandlerMutex);
+ delete SVDRPServerHandler;
+ SVDRPServerHandler = NULL;
+ delete SVDRPClientHandler;
+ SVDRPClientHandler = NULL;
+}
+
+void SendSVDRPDiscover(const char *Address)
+{
+ cMutexLock MutexLock(&SVDRPHandlerMutex);
+ if (SVDRPClientHandler)
+ SVDRPClientHandler->SendDiscover(Address);
+}
+
+bool GetSVDRPServerNames(cStringList *ServerNames)
+{
+ cMutexLock MutexLock(&SVDRPHandlerMutex);
+ if (SVDRPClientHandler)
+ return SVDRPClientHandler->GetServerNames(ServerNames);
+ return false;
+}
+
+// --- cSVDRPCommand ---------------------------------------------------------
+
+cSVDRPCommand::cSVDRPCommand(const char *ServerName, const char *Command)
+{
+ serverName = ServerName;
+ command = Command;
+}
+
+cSVDRPCommand::~cSVDRPCommand()
+{
+}
+
+bool cSVDRPCommand::Execute(void)
+{
+ cMutexLock MutexLock(&SVDRPHandlerMutex);
+ if (SVDRPClientHandler)
+ return SVDRPClientHandler->Execute(serverName, command, &response);
+ return false;
}
diff --git a/svdrp.h b/svdrp.h
index 2b426ed6..86ee0ab5 100644
--- a/svdrp.h
+++ b/svdrp.h
@@ -4,14 +4,56 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: svdrp.h 4.1 2015/04/29 13:10:06 kls Exp $
+ * $Id: svdrp.h 4.2 2015/05/22 13:44:43 kls Exp $
*/
#ifndef __SVDRP_H
#define __SVDRP_H
+#include "tools.h"
+
+class cSVDRPCommand {
+protected:
+ cString serverName;
+ cString command;
+ cStringList response;
+public:
+ cSVDRPCommand(const char *ServerName, const char *Command);
+ ///< Sets up an SVDRP Command to be executed on the VDR with the given
+ ///< ServerName. A list of all available servers can be retrieved by
+ ///< calling GetSVDRPServerNames().
+ ///< Command is one SVDRP command, followed by optional parameters,
+ ///< just as it can be given in a normal SVDRP connection. It doesn't
+ ///< need to be terminated with a newline.
+ virtual ~cSVDRPCommand();
+ bool Execute(void);
+ ///< Sends the Command given in the constructor to the remote VDR
+ ///< and collects all of the response strings.
+ ///< 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
+ ///< by calling Response(). Execute() can be called any number of
+ ///< times. The list of response strings will be cleared before
+ ///< the command is actually executed.
+ const cStringList *Response(void) const { return &response; }
+ ///< Returns the list of strings the remote VDR has sent in response
+ ///< to the command. The response strings are exactly as received,
+ ///< with the leading three digit reply code and possible continuation
+ ///< line indicator ('-') in place.
+ const char *Response(int Index) { return (Index > 0 && Index < response.Size()) ? response[Index] : NULL; }
+ ///< This is a convenience function for accessing the response strings.
+ ///< Returns the string at the given Index, or NULL if Index is out
+ ///< of range.
+ };
+
void SetSVDRPGrabImageDir(const char *GrabImageDir);
-void StartSVDRPHandler(int Port);
+void StartSVDRPHandler(int TcpPort, int UdpPort);
void StopSVDRPHandler(void);
+void SendSVDRPDiscover(const char *Address = NULL);
+bool GetSVDRPServerNames(cStringList *ServerNames);
+ ///< 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.
+ ///< Returns true if the resulting list is not empty.
#endif //__SVDRP_H
diff --git a/tools.c b/tools.c
index 5418f5e2..d8e4ac7d 100644
--- a/tools.c
+++ b/tools.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: tools.c 3.4 2015/02/07 16:07:22 kls Exp $
+ * $Id: tools.c 4.1 2015/05/11 14:15:15 kls Exp $
*/
#include "tools.h"
@@ -274,6 +274,28 @@ cString strescape(const char *s, const char *chars)
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 @@ cString &cString::operator=(const char *String)
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);
@@ -1440,6 +1473,19 @@ bool cPoller::Add(int FileHandle, bool Out)
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) {
diff --git a/tools.h b/tools.h
index 0987234b..8f56620c 100644
--- a/tools.h
+++ b/tools.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: tools.h 3.7 2015/02/07 16:07:22 kls Exp $
+ * $Id: tools.h 4.1 2015/05/21 14:37:00 kls Exp $
*/
#ifndef __TOOLS_H
@@ -178,6 +178,7 @@ public:
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 +210,14 @@ char *stripspace(char *s);
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);
@@ -356,12 +365,13 @@ public:
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);
};
diff --git a/vdr.c b/vdr.c
index c196f8a3..b4d3f91b 100644
--- a/vdr.c
+++ b/vdr.c
@@ -22,7 +22,7 @@
*
* The project's page is at http://www.tvdr.de
*
- * $Id: vdr.c 4.3 2015/04/29 09:18:54 kls Exp $
+ * $Id: vdr.c 4.4 2015/05/21 13:58:33 kls Exp $
*/
#include <getopt.h>
@@ -916,7 +916,7 @@ int main(int argc, char *argv[])
// SVDRP:
- StartSVDRPHandler(SVDRPport);
+ StartSVDRPHandler(SVDRPport, DEFAULTSVDRPPORT);
// Main program loop: