The SVDRP port now accepts multiple concurrent connections

This commit is contained in:
Klaus Schmidinger 2015-04-29 13:10:06 +02:00
parent b6af7a9cf9
commit 2b9e988dd5
6 changed files with 221 additions and 147 deletions

14
HISTORY
View File

@ -8596,7 +8596,7 @@ Video Disk Recorder Revision History
- Bumped all version numbers to 2.2.0.
- Official release.
2015-04-19: Version 2.3.1
2015-04-29: 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
@ -8645,3 +8645,15 @@ Video Disk Recorder Revision History
//#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 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.

View File

@ -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

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: interface.h 1.31 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);

234
svdrp.c
View File

@ -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.1 2015/04/29 13:10:01 kls Exp $
*/
#include "svdrp.h"
@ -33,14 +33,32 @@
#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"
// --- cSocket ---------------------------------------------------------------
class cSocket {
private:
int port;
int sock;
int queue;
cString lastAcceptedConnection;
public:
cSocket(int Port, int Queue = 1);
~cSocket();
bool Open(void);
void Close(void);
int Socket(void) const { return sock; }
int Accept(void);
const char *LastAcceptedConnection(void) const { return lastAcceptedConnection; }
};
cSocket::cSocket(int Port, int Queue)
{
port = Port;
@ -100,6 +118,7 @@ bool cSocket::Open(void)
LOG_ERROR;
return false;
}
isyslog("SVDRP listening on port %d/tcp", port);
}
return true;
}
@ -110,7 +129,7 @@ int cSocket::Accept(void)
struct sockaddr_in clientname;
uint size = sizeof(clientname);
int newsock = accept(sock, (struct sockaddr *)&clientname, &size);
if (newsock > 0) {
if (newsock >= 0) {
bool accepted = SVDRPhosts.Acceptable(clientname.sin_addr.s_addr);
if (!accepted) {
const char *s = "Access denied!\n";
@ -119,7 +138,8 @@ int cSocket::Accept(void)
close(newsock);
newsock = -1;
}
isyslog("connect from %s, port %hu - %s", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port), accepted ? "accepted" : "DENIED");
lastAcceptedConnection = cString::sprintf("%s:%hu", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port));
isyslog("SVDRP %s connection %s", *lastAcceptedConnection, accepted ? "accepted" : "DENIED");
}
else if (errno != EINTR && errno != EAGAIN)
LOG_ERROR;
@ -130,6 +150,19 @@ int cSocket::Accept(void)
// --- 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) {
@ -385,17 +418,77 @@ const char *GetHelpPage(const char *Cmd, const char **p)
return NULL;
}
char *cSVDRP::grabImageDir = NULL;
static cString grabImageDir;
cSVDRP::cSVDRP(int Port)
:socket(Port)
class cSVDRP {
private:
int socket;
cString connection;
cFile file;
cRecordings recordings;
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 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 Socket, const char *Connection);
~cSVDRP();
bool HasConnection(void) { return file.IsOpen(); }
bool Process(void);
};
cSVDRP::cSVDRP(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)) {
//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");
}
}
cSVDRP::~cSVDRP()
@ -413,10 +506,11 @@ void cSVDRP::Close(bool SendReply, bool Timeout)
gethostname(buffer, sizeof(buffer));
Reply(221, "%s closing connection%s", buffer, Timeout ? " (timeout)" : "");
}
isyslog("closing SVDRP connection"); //TODO store IP#???
isyslog("SVDRP %s connection closed", *connection);
file.Close();
DELETENULL(PUTEhandler);
}
close(socket);
}
bool cSVDRP::Send(const char *s, int length)
@ -454,7 +548,7 @@ void cSVDRP::Reply(int Code, const char *fmt, ...)
}
else {
Reply(451, "Zero return code - looks like a programming error!");
esyslog("SVDRP: zero return code!");
esyslog("SVDRP %s zero return code!", *connection);
}
}
}
@ -641,7 +735,7 @@ void cSVDRP::CmdDELC(const char *Option)
Channels.Del(channel);
Channels.ReNumber();
Channels.SetModified(true);
isyslog("channel %s deleted", 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());
@ -714,7 +808,7 @@ void cSVDRP::CmdDELT(const char *Option)
cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1);
if (timer) {
if (!timer->Recording()) {
isyslog("deleting timer %s", *timer->ToDescr());
isyslog("SVDRP %s deleting timer %s", *connection, *timer->ToDescr());
Timers.Del(timer);
Timers.SetModified();
Reply(250, "Timer \"%s\" deleted", Option);
@ -831,7 +925,7 @@ void cSVDRP::CmdGRAB(const char *Option)
// 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 +962,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("grabbed image to %s", FileName);
dsyslog("SVDRP %s grabbed image to %s", *connection, FileName);
Reply(250, "Grabbed image %s", Option);
}
else {
@ -1195,7 +1289,7 @@ void cSVDRP::CmdLSTT(const char *Option)
void cSVDRP::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");
}
@ -1219,7 +1313,7 @@ void cSVDRP::CmdMODC(const char *Option)
*channel = ch;
Channels.ReNumber();
Channels.SetModified(true);
isyslog("modifed channel %d %s", 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
@ -1262,7 +1356,7 @@ void cSVDRP::CmdMODT(const char *Option)
}
*timer = t;
Timers.SetModified();
isyslog("timer %s modified (%s)", *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
@ -1306,7 +1400,7 @@ void cSVDRP::CmdMOVC(const char *Option)
else
cDevice::SetCurrentChannel(CurrentChannel);
}
isyslog("channel %d moved to %d", FromNumber, ToNumber);
isyslog("SVDRP %s channel %d moved to %d", *connection, FromNumber, ToNumber);
Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
}
else
@ -1382,7 +1476,7 @@ void cSVDRP::CmdNEWC(const char *Option)
Channels.Add(channel);
Channels.ReNumber();
Channels.SetModified(true);
isyslog("new channel %d %s", 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
@ -1402,7 +1496,7 @@ void cSVDRP::CmdNEWT(const char *Option)
if (timer->Parse(Option)) {
Timers.Add(timer);
Timers.SetModified();
isyslog("timer %s added", *timer->ToDescr());
isyslog("SVDRP %s timer %s added", *connection, *timer->ToDescr());
Reply(250, "%d %s", timer->Index() + 1, *timer->ToText());
return;
}
@ -1622,11 +1716,11 @@ void cSVDRP::CmdUPDT(const char *Option)
t->Parse(Option);
delete timer;
timer = t;
isyslog("timer %s updated", *timer->ToDescr());
isyslog("SVDRP %s timer %s updated", *connection, *timer->ToDescr());
}
else {
Timers.Add(timer);
isyslog("timer %s added", *timer->ToDescr());
isyslog("SVDRP %s timer %s added", *connection, *timer->ToDescr());
}
Timers.SetModified();
Reply(250, "%d %s", timer->Index() + 1, *timer->ToText());
@ -1729,19 +1823,7 @@ void cSVDRP::Execute(char *Cmd)
bool cSVDRP::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 +1863,7 @@ bool cSVDRP::Process(void)
cmdLine = NewBuffer;
}
else {
esyslog("ERROR: out of memory");
esyslog("SVDRP %s ERROR: out of memory", *connection);
Close();
break;
}
@ -1792,23 +1874,87 @@ bool cSVDRP::Process(void)
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 cSVDRP::SetGrabImageDir(const char *GrabImageDir)
void SetSVDRPGrabImageDir(const char *GrabImageDir)
{
free(grabImageDir);
grabImageDir = GrabImageDir ? strdup(GrabImageDir) : NULL;
grabImageDir = GrabImageDir;
}
//TODO more than one connection???
// --- cSVDRPHandler ---------------------------------------------------------
class cSVDRPHandler : public cThread {
private:
cSocket socket;
cVector<cSVDRP *> connections;
void ProcessConnections(void);
protected:
virtual void Action(void);
public:
cSVDRPHandler(int Port);
virtual ~cSVDRPHandler();
};
cSVDRPHandler::cSVDRPHandler(int Port)
:cThread("SVDRP handler", true)
,socket(Port)
{
}
cSVDRPHandler::~cSVDRPHandler()
{
Cancel(3);
}
void cSVDRPHandler::ProcessConnections(void)
{
for (int i = 0; i < connections.Size(); i++) {
if (connections[i]) {
if (!connections[i]->Process()) {
delete connections[i];
connections.Remove(i);
i--;
}
}
}
}
void cSVDRPHandler::Action(void)
{
if (socket.Open()) {
while (Running()) {
cFile::AnyFileReady(socket.Socket(), 1000);
int NewSocket = socket.Accept();
if (NewSocket >= 0)
connections.Append(new cSVDRP(NewSocket, socket.LastAcceptedConnection()));
ProcessConnections();
}
socket.Close();
}
}
static cSVDRPHandler *SVDRPHandler = NULL;
void StartSVDRPHandler(int Port)
{
if (Port && !SVDRPHandler) {
SVDRPHandler = new cSVDRPHandler(Port);
SVDRPHandler->Start();
}
}
void StopSVDRPHandler(void)
{
delete SVDRPHandler;
SVDRPHandler = NULL;
}

87
svdrp.h
View File

@ -4,93 +4,14 @@
* 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.1 2015/04/29 13:10:06 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);
};
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 SetSVDRPGrabImageDir(const char *GrabImageDir);
void StartSVDRPHandler(int Port);
void StopSVDRPHandler(void);
#endif //__SVDRP_H

14
vdr.c
View File

@ -22,7 +22,7 @@
*
* The project's page is at http://www.tvdr.de
*
* $Id: vdr.c 4.2 2015/04/19 12:38:12 kls Exp $
* $Id: vdr.c 4.3 2015/04/29 09:18:54 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"
@ -375,7 +376,7 @@ int main(int argc, char *argv[])
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;
@ -831,7 +832,7 @@ int main(int argc, char *argv[])
// User interface:
Interface = new cInterface(SVDRPport);
Interface = new cInterface;
// Default skins:
@ -913,6 +914,10 @@ int main(int argc, char *argv[])
sd_notify(0, "READY=1\nSTATUS=Ready");
#endif
// SVDRP:
StartSVDRPHandler(SVDRPport);
// Main program loop:
#define DELETE_MENU ((IsInfoMenu &= (Menu == NULL)), delete Menu, Menu = NULL)
@ -1418,7 +1423,7 @@ int main(int argc, char *argv[])
// 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:
@ -1466,6 +1471,7 @@ Exit:
signal(SIGPIPE, SIG_DFL);
signal(SIGALRM, SIG_DFL);
StopSVDRPHandler();
PluginManager.StopPlugins();
cRecordControls::Shutdown();
RecordingsHandler.DelAll();