Snapshot 2007-03-20
This commit is contained in:
48
server/component.c
Normal file
48
server/component.c
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* $Id: component.c,v 1.3 2005/05/09 20:22:29 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#include "server/component.h"
|
||||
#include "server/connection.h"
|
||||
|
||||
cServerComponent::cServerComponent(const char *Protocol, const char *ListenIp,
|
||||
uint ListenPort):
|
||||
m_Protocol(Protocol),
|
||||
m_ListenIp(ListenIp),
|
||||
m_ListenPort(ListenPort)
|
||||
{
|
||||
}
|
||||
|
||||
cServerComponent::~cServerComponent()
|
||||
{
|
||||
}
|
||||
|
||||
bool cServerComponent::Initialize(void)
|
||||
{
|
||||
if (!m_Listen.Listen(m_ListenIp, m_ListenPort, 5)) {
|
||||
esyslog("Streamdev: Couldn't listen (%s) %s:%d: %m",
|
||||
m_Protocol, m_ListenIp, m_ListenPort);
|
||||
return false;
|
||||
}
|
||||
isyslog("Streamdev: Listening (%s) on port %d", m_Protocol, m_ListenPort);
|
||||
return true;
|
||||
}
|
||||
|
||||
void cServerComponent::Destruct(void)
|
||||
{
|
||||
m_Listen.Close();
|
||||
}
|
||||
|
||||
cServerConnection *cServerComponent::Accept(void)
|
||||
{
|
||||
cServerConnection *client = NewClient();
|
||||
if (client->Accept(m_Listen)) {
|
||||
isyslog("Streamdev: Accepted new client (%s) %s:%d", m_Protocol,
|
||||
client->RemoteIp().c_str(), client->RemotePort());
|
||||
return client;
|
||||
} else {
|
||||
esyslog("Streamdev: Couldn't accept (%s): %m", m_Protocol);
|
||||
delete client;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
51
server/component.h
Normal file
51
server/component.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* $Id: component.h,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SERVERS_COMPONENT_H
|
||||
#define VDR_STREAMDEV_SERVERS_COMPONENT_H
|
||||
|
||||
#include "tools/socket.h"
|
||||
#include "tools/select.h"
|
||||
|
||||
#include <vdr/tools.h>
|
||||
|
||||
class cServerConnection;
|
||||
|
||||
/* Basic TCP listen server, all functions virtual if a derivation wants to do
|
||||
things different */
|
||||
|
||||
class cServerComponent: public cListObject {
|
||||
private:
|
||||
cTBSocket m_Listen;
|
||||
const char *m_Protocol;
|
||||
const char *m_ListenIp;
|
||||
uint m_ListenPort;
|
||||
|
||||
protected:
|
||||
/* Returns a new connection object for Accept() */
|
||||
virtual cServerConnection *NewClient(void) = 0;
|
||||
|
||||
public:
|
||||
cServerComponent(const char *Protocol, const char *ListenIp, uint ListenPort);
|
||||
virtual ~cServerComponent();
|
||||
|
||||
/* Starts listening on the specified Port, override if you want to do things
|
||||
different */
|
||||
virtual bool Initialize(void);
|
||||
|
||||
/* Stops listening, override if you want to do things different */
|
||||
virtual void Destruct(void);
|
||||
|
||||
/* Get the listening socket's file number */
|
||||
virtual int Socket(void) const { return (int)m_Listen; }
|
||||
|
||||
/* Adds the listening socket to the Select object */
|
||||
virtual void Add(cTBSelect &Select) const { Select.Add(m_Listen); }
|
||||
|
||||
/* Accepts the connection on a NewClient() object and calls the
|
||||
Welcome() on it, override if you want to do things different */
|
||||
virtual cServerConnection *Accept(void);
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEV_SERVERS_COMPONENT_H
|
||||
18
server/componentHTTP.c
Normal file
18
server/componentHTTP.c
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* $Id: componentHTTP.c,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#include "server/componentHTTP.h"
|
||||
#include "server/connectionHTTP.h"
|
||||
#include "server/setup.h"
|
||||
|
||||
cComponentHTTP::cComponentHTTP(void):
|
||||
cServerComponent("HTTP", StreamdevServerSetup.HTTPBindIP,
|
||||
StreamdevServerSetup.HTTPServerPort)
|
||||
{
|
||||
}
|
||||
|
||||
cServerConnection *cComponentHTTP::NewClient(void)
|
||||
{
|
||||
return new cConnectionHTTP;
|
||||
}
|
||||
18
server/componentHTTP.h
Normal file
18
server/componentHTTP.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* $Id: componentHTTP.h,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_HTTPSERVER_H
|
||||
#define VDR_STREAMDEV_HTTPSERVER_H
|
||||
|
||||
#include "server/component.h"
|
||||
|
||||
class cComponentHTTP: public cServerComponent {
|
||||
protected:
|
||||
virtual cServerConnection *NewClient(void);
|
||||
|
||||
public:
|
||||
cComponentHTTP(void);
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEV_HTTPSERVER_H
|
||||
18
server/componentVTP.c
Normal file
18
server/componentVTP.c
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* $Id: componentVTP.c,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#include "server/componentVTP.h"
|
||||
#include "server/connectionVTP.h"
|
||||
#include "server/setup.h"
|
||||
|
||||
cComponentVTP::cComponentVTP(void):
|
||||
cServerComponent("VTP", StreamdevServerSetup.VTPBindIP,
|
||||
StreamdevServerSetup.VTPServerPort)
|
||||
{
|
||||
}
|
||||
|
||||
cServerConnection *cComponentVTP::NewClient(void)
|
||||
{
|
||||
return new cConnectionVTP;
|
||||
}
|
||||
18
server/componentVTP.h
Normal file
18
server/componentVTP.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* $Id: componentVTP.h,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SERVERS_SERVERVTP_H
|
||||
#define VDR_STREAMDEV_SERVERS_SERVERVTP_H
|
||||
|
||||
#include "server/component.h"
|
||||
|
||||
class cComponentVTP: public cServerComponent {
|
||||
protected:
|
||||
virtual cServerConnection *NewClient(void);
|
||||
|
||||
public:
|
||||
cComponentVTP(void);
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEV_SERVERS_SERVERVTP_H
|
||||
198
server/connection.c
Normal file
198
server/connection.c
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* $Id: connection.c,v 1.8 2007/01/15 12:00:19 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include "server/connection.h"
|
||||
#include "server/setup.h"
|
||||
#include "server/suspend.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <vdr/tools.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
|
||||
cServerConnection::cServerConnection(const char *Protocol):
|
||||
m_Protocol(Protocol),
|
||||
m_DeferClose(false),
|
||||
m_Pending(false),
|
||||
m_ReadBytes(0),
|
||||
m_WriteBytes(0),
|
||||
m_WriteIndex(0)
|
||||
{
|
||||
}
|
||||
|
||||
cServerConnection::~cServerConnection()
|
||||
{
|
||||
}
|
||||
|
||||
bool cServerConnection::Read(void)
|
||||
{
|
||||
int b;
|
||||
if ((b = cTBSocket::Read(m_ReadBuffer + m_ReadBytes,
|
||||
sizeof(m_ReadBuffer) - m_ReadBytes - 1)) < 0) {
|
||||
esyslog("ERROR: read from client (%s) %s:%d failed: %m",
|
||||
m_Protocol, RemoteIp().c_str(), RemotePort());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (b == 0) {
|
||||
isyslog("client (%s) %s:%d has closed connection",
|
||||
m_Protocol, RemoteIp().c_str(), RemotePort());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ReadBytes += b;
|
||||
m_ReadBuffer[m_ReadBytes] = '\0';
|
||||
|
||||
char *end;
|
||||
bool result = true;
|
||||
while ((end = strchr(m_ReadBuffer, '\012')) != NULL) {
|
||||
*end = '\0';
|
||||
if (end > m_ReadBuffer && *(end - 1) == '\015')
|
||||
*(end - 1) = '\0';
|
||||
|
||||
if (!Command(m_ReadBuffer))
|
||||
return false;
|
||||
|
||||
m_ReadBytes -= ++end - m_ReadBuffer;
|
||||
if (m_ReadBytes > 0)
|
||||
memmove(m_ReadBuffer, end, m_ReadBytes);
|
||||
}
|
||||
|
||||
if (m_ReadBytes == sizeof(m_ReadBuffer) - 1) {
|
||||
esyslog("ERROR: streamdev: input buffer overflow (%s) for %s:%d",
|
||||
m_Protocol, RemoteIp().c_str(), RemotePort());
|
||||
return false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool cServerConnection::Write(void)
|
||||
{
|
||||
int b;
|
||||
if ((b = cTBSocket::Write(m_WriteBuffer + m_WriteIndex,
|
||||
m_WriteBytes - m_WriteIndex)) < 0) {
|
||||
esyslog("ERROR: streamdev: write to client (%s) %s:%d failed: %m",
|
||||
m_Protocol, RemoteIp().c_str(), RemotePort());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_WriteIndex += b;
|
||||
if (m_WriteIndex == m_WriteBytes) {
|
||||
m_WriteIndex = 0;
|
||||
m_WriteBytes = 0;
|
||||
if (m_Pending)
|
||||
Command(NULL);
|
||||
if (m_DeferClose)
|
||||
return false;
|
||||
Flushed();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cServerConnection::Respond(const char *Message, bool Last, ...)
|
||||
{
|
||||
char *buffer;
|
||||
int length;
|
||||
va_list ap;
|
||||
va_start(ap, Last);
|
||||
length = vasprintf(&buffer, Message, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (m_WriteBytes + length + 2 > sizeof(m_WriteBuffer)) {
|
||||
esyslog("ERROR: streamdev: output buffer overflow (%s) for %s:%d",
|
||||
m_Protocol, RemoteIp().c_str(), RemotePort());
|
||||
return false;
|
||||
}
|
||||
Dprintf("OUT: |%s|\n", buffer);
|
||||
memcpy(m_WriteBuffer + m_WriteBytes, buffer, length);
|
||||
free(buffer);
|
||||
|
||||
m_WriteBytes += length;
|
||||
m_WriteBuffer[m_WriteBytes++] = '\015';
|
||||
m_WriteBuffer[m_WriteBytes++] = '\012';
|
||||
m_Pending = !Last;
|
||||
return true;
|
||||
}
|
||||
|
||||
cDevice *cServerConnection::GetDevice(const cChannel *Channel, int Priority)
|
||||
{
|
||||
cDevice *device = NULL;
|
||||
|
||||
/*Dprintf("+ Statistics:\n");
|
||||
Dprintf("+ Current Channel: %d\n", cDevice::CurrentChannel());
|
||||
Dprintf("+ Current Device: %d\n", cDevice::ActualDevice()->CardIndex());
|
||||
Dprintf("+ Transfer Mode: %s\n", cDevice::ActualDevice()
|
||||
== cDevice::PrimaryDevice() ? "false" : "true");
|
||||
Dprintf("+ Replaying: %s\n", cDevice::PrimaryDevice()->Replaying() ? "true"
|
||||
: "false");*/
|
||||
|
||||
Dprintf(" * GetDevice(const cChannel*, int)\n");
|
||||
Dprintf(" * -------------------------------\n");
|
||||
|
||||
#if VDRVERSNUM < 10500
|
||||
device = cDevice::GetDevice(Channel, Priority);
|
||||
#else
|
||||
device = cDevice::GetDevice(Channel, Priority, false);
|
||||
#endif
|
||||
|
||||
Dprintf(" * Found following device: %p (%d)\n", device,
|
||||
device ? device->CardIndex() + 1 : 0);
|
||||
if (device == cDevice::ActualDevice())
|
||||
Dprintf(" * is actual device\n");
|
||||
if (!cSuspendCtl::IsActive() && StreamdevServerSetup.SuspendMode != smAlways)
|
||||
Dprintf(" * NOT suspended\n");
|
||||
|
||||
if (!device || (device == cDevice::ActualDevice()
|
||||
&& !cSuspendCtl::IsActive()
|
||||
&& StreamdevServerSetup.SuspendMode != smAlways)) {
|
||||
// mustn't switch actual device
|
||||
// maybe a device would be free if THIS connection did turn off its streams?
|
||||
Dprintf(" * trying again...\n");
|
||||
const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel());
|
||||
isyslog("streamdev-server: Detaching current receiver");
|
||||
Detach();
|
||||
#if VDRVERSNUM < 10500
|
||||
device = cDevice::GetDevice(Channel, Priority);
|
||||
#else
|
||||
device = cDevice::GetDevice(Channel, Priority, false);
|
||||
#endif
|
||||
Attach();
|
||||
Dprintf(" * Found following device: %p (%d)\n", device,
|
||||
device ? device->CardIndex() + 1 : 0);
|
||||
if (device == cDevice::ActualDevice())
|
||||
Dprintf(" * is actual device\n");
|
||||
if (!cSuspendCtl::IsActive()
|
||||
&& StreamdevServerSetup.SuspendMode != smAlways)
|
||||
Dprintf(" * NOT suspended\n");
|
||||
if (current && !TRANSPONDER(Channel, current))
|
||||
Dprintf(" * NOT same transponder\n");
|
||||
if (device && (device == cDevice::ActualDevice()
|
||||
&& !cSuspendCtl::IsActive()
|
||||
&& StreamdevServerSetup.SuspendMode != smAlways
|
||||
&& current != NULL
|
||||
&& !TRANSPONDER(Channel, current))) {
|
||||
// now we would have to switch away live tv...let's see if live tv
|
||||
// can be handled by another device
|
||||
cDevice *newdev = NULL;
|
||||
for (int i = 0; i < cDevice::NumDevices(); ++i) {
|
||||
cDevice *dev = cDevice::GetDevice(i);
|
||||
if (dev->ProvidesChannel(current, 0) && dev != device) {
|
||||
newdev = dev;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Dprintf(" * Found device for live tv: %p (%d)\n", newdev,
|
||||
newdev ? newdev->CardIndex() + 1 : 0);
|
||||
if (newdev == NULL || newdev == device)
|
||||
// no suitable device to continue live TV, giving up...
|
||||
device = NULL;
|
||||
else
|
||||
newdev->SwitchChannel(current, true);
|
||||
}
|
||||
}
|
||||
|
||||
return device;
|
||||
}
|
||||
90
server/connection.h
Normal file
90
server/connection.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* $Id: connection.h,v 1.3 2005/05/09 20:22:29 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SERVER_CONNECTION_H
|
||||
#define VDR_STREAMDEV_SERVER_CONNECTION_H
|
||||
|
||||
#include "tools/socket.h"
|
||||
#include "common.h"
|
||||
|
||||
class cChannel;
|
||||
class cDevice;
|
||||
|
||||
/* Basic capabilities of a straight text-based protocol, most functions
|
||||
virtual to support more complicated protocols */
|
||||
|
||||
class cServerConnection: public cListObject, public cTBSocket
|
||||
{
|
||||
private:
|
||||
const char *m_Protocol;
|
||||
bool m_DeferClose;
|
||||
bool m_Pending;
|
||||
|
||||
char m_ReadBuffer[MAXPARSEBUFFER];
|
||||
uint m_ReadBytes;
|
||||
|
||||
char m_WriteBuffer[MAXPARSEBUFFER];
|
||||
uint m_WriteBytes;
|
||||
uint m_WriteIndex;
|
||||
|
||||
protected:
|
||||
/* Will be called when a command terminated by a newline has been
|
||||
received */
|
||||
virtual bool Command(char *Cmd) = 0;
|
||||
|
||||
/* Will put Message into the response queue, which will be sent in the next
|
||||
server cycle. Note that Message will be line-terminated by Respond.
|
||||
Only one line at a time may be sent. If there are lines to follow, set
|
||||
Last to false. Command(NULL) will be called in the next cycle, so you can
|
||||
post the next line. */
|
||||
virtual bool Respond(const char *Message, bool Last = true, ...)
|
||||
__attribute__ ((format (printf, 2, 4)));
|
||||
|
||||
public:
|
||||
/* If you derive, specify a short string such as HTTP for Protocol, which
|
||||
will be displayed in error messages */
|
||||
cServerConnection(const char *Protocol);
|
||||
virtual ~cServerConnection();
|
||||
|
||||
/* Gets called if the client has been accepted by the core */
|
||||
virtual void Welcome(void) { }
|
||||
|
||||
/* Gets called if the client has been rejected by the core */
|
||||
virtual void Reject(void) { DeferClose(); }
|
||||
|
||||
/* Get the client socket's file number */
|
||||
virtual int Socket(void) const { return (int)*this; }
|
||||
|
||||
/* Determine if there is data to send or any command pending */
|
||||
virtual bool HasData(void) const;
|
||||
|
||||
/* Gets called by server when the socket can accept more data. Writes
|
||||
the buffer filled up by Respond(). Calls Command(NULL) if there is a
|
||||
command pending. Returns false in case of an error */
|
||||
virtual bool Write(void);
|
||||
|
||||
/* Gets called by server when there is incoming data to read. Calls
|
||||
Command() for each line. Returns false in case of an error, or if
|
||||
the connection shall be closed and removed by the server */
|
||||
virtual bool Read(void);
|
||||
|
||||
/* Will make the socket close after sending all queued output data */
|
||||
void DeferClose(void) { m_DeferClose = true; }
|
||||
|
||||
/* Will retrieve an unused device for transmitting data. Use the returned
|
||||
cDevice in a following call to StartTransfer */
|
||||
cDevice *GetDevice(const cChannel *Channel, int Priority);
|
||||
|
||||
virtual void Flushed(void) {}
|
||||
|
||||
virtual void Detach(void) = 0;
|
||||
virtual void Attach(void) = 0;
|
||||
};
|
||||
|
||||
inline bool cServerConnection::HasData(void) const
|
||||
{
|
||||
return m_WriteBytes > 0 || m_Pending || m_DeferClose;
|
||||
}
|
||||
|
||||
#endif // VDR_STREAMDEV_SERVER_CONNECTION_H
|
||||
200
server/connectionHTTP.c
Normal file
200
server/connectionHTTP.c
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* $Id: connectionHTTP.c,v 1.10 2006/01/26 19:40:18 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include "server/connectionHTTP.h"
|
||||
#include "server/setup.h"
|
||||
|
||||
cConnectionHTTP::cConnectionHTTP(void):
|
||||
cServerConnection("HTTP"),
|
||||
m_Status(hsRequest),
|
||||
m_LiveStreamer(NULL),
|
||||
m_Channel(NULL),
|
||||
m_Apid(0),
|
||||
m_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType),
|
||||
m_ListChannel(NULL)
|
||||
{
|
||||
Dprintf("constructor hsRequest\n");
|
||||
}
|
||||
|
||||
cConnectionHTTP::~cConnectionHTTP()
|
||||
{
|
||||
delete m_LiveStreamer;
|
||||
}
|
||||
|
||||
bool cConnectionHTTP::Command(char *Cmd)
|
||||
{
|
||||
Dprintf("command %s\n", Cmd);
|
||||
switch (m_Status) {
|
||||
case hsRequest:
|
||||
Dprintf("Request\n");
|
||||
m_Request = Cmd;
|
||||
m_Status = hsHeaders;
|
||||
return true;
|
||||
|
||||
case hsHeaders:
|
||||
if (*Cmd == '\0') {
|
||||
m_Status = hsBody;
|
||||
return ProcessRequest();
|
||||
}
|
||||
Dprintf("header\n");
|
||||
return true;
|
||||
}
|
||||
return false; // ??? shouldn't happen
|
||||
}
|
||||
|
||||
bool cConnectionHTTP::ProcessRequest(void)
|
||||
{
|
||||
Dprintf("process\n");
|
||||
if (m_Request.substr(0, 4) == "GET " && CmdGET(m_Request.substr(4))) {
|
||||
switch (m_Job) {
|
||||
case hjListing:
|
||||
return Respond("HTTP/1.0 200 OK")
|
||||
&& Respond("Content-Type: text/html")
|
||||
&& Respond("")
|
||||
&& Respond("<html><head><title>VDR Channel Listing</title></head>")
|
||||
&& Respond("<body><ul>");
|
||||
|
||||
case hjTransfer:
|
||||
if (m_Channel == NULL) {
|
||||
DeferClose();
|
||||
return Respond("HTTP/1.0 404 not found");
|
||||
}
|
||||
|
||||
m_LiveStreamer = new cStreamdevLiveStreamer(0);
|
||||
cDevice *device = GetDevice(m_Channel, 0);
|
||||
if (device != NULL) {
|
||||
device->SwitchChannel(m_Channel, false);
|
||||
if (m_LiveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid)) {
|
||||
m_LiveStreamer->SetDevice(device);
|
||||
if (m_StreamType == stES && (m_Apid != 0 || ISRADIO(m_Channel))) {
|
||||
return Respond("HTTP/1.0 200 OK")
|
||||
&& Respond("Content-Type: audio/mpeg")
|
||||
&& Respond("icy-name: %s", true, m_Channel->Name())
|
||||
&& Respond("");
|
||||
} else {
|
||||
return Respond("HTTP/1.0 200 OK")
|
||||
&& Respond("Content-Type: video/mpeg")
|
||||
&& Respond("");
|
||||
}
|
||||
}
|
||||
}
|
||||
DELETENULL(m_LiveStreamer);
|
||||
DeferClose();
|
||||
return Respond("HTTP/1.0 409 Channel not available")
|
||||
&& Respond("");
|
||||
}
|
||||
}
|
||||
|
||||
DeferClose();
|
||||
return Respond("HTTP/1.0 400 Bad Request")
|
||||
&& Respond("");
|
||||
}
|
||||
|
||||
void cConnectionHTTP::Flushed(void)
|
||||
{
|
||||
std::string line;
|
||||
|
||||
if (m_Status != hsBody)
|
||||
return;
|
||||
|
||||
switch (m_Job) {
|
||||
case hjListing:
|
||||
if (m_ListChannel == NULL) {
|
||||
Respond("</ul></body></html>");
|
||||
DeferClose();
|
||||
m_Status = hsFinished;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ListChannel->GroupSep())
|
||||
line = (std::string)"<li>--- " + m_ListChannel->Name() + "---</li>";
|
||||
else {
|
||||
int index = 1;
|
||||
line = (std::string)"<li><a href=\"http://" + LocalIp() + ":"
|
||||
+ (const char*)itoa(StreamdevServerSetup.HTTPServerPort) + "/"
|
||||
+ StreamTypes[m_StreamType] + "/"
|
||||
+ (const char*)m_ListChannel->GetChannelID().ToString() + "\">"
|
||||
+ m_ListChannel->Name() + "</a> ";
|
||||
for (int i = 0; m_ListChannel->Apid(i) != 0; ++i, ++index) {
|
||||
line += "<a href=\"http://" + LocalIp() + ":"
|
||||
+ (const char*)itoa(StreamdevServerSetup.HTTPServerPort) + "/"
|
||||
+ StreamTypes[m_StreamType] + "/"
|
||||
+ (const char*)m_ListChannel->GetChannelID().ToString() + "+"
|
||||
+ (const char*)itoa(index) + "\">("
|
||||
+ m_ListChannel->Alang(i) + ")</a> ";
|
||||
}
|
||||
for (int i = 0; m_ListChannel->Dpid(i) != 0; ++i, ++index) {
|
||||
line += "<a href=\"http://" + LocalIp() + ":"
|
||||
+ (const char*)itoa(StreamdevServerSetup.HTTPServerPort) + "/"
|
||||
+ StreamTypes[m_StreamType] + "/"
|
||||
+ (const char*)m_ListChannel->GetChannelID().ToString() + "+"
|
||||
+ (const char*)itoa(index) + "\">("
|
||||
+ m_ListChannel->Dlang(i) + ")</a> ";
|
||||
}
|
||||
line += "</li>";
|
||||
}
|
||||
if (!Respond(line.c_str()))
|
||||
DeferClose();
|
||||
m_ListChannel = Channels.Next(m_ListChannel);
|
||||
break;
|
||||
|
||||
case hjTransfer:
|
||||
Dprintf("streamer start\n");
|
||||
m_LiveStreamer->Start(this);
|
||||
m_Status = hsFinished;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool cConnectionHTTP::CmdGET(const std::string &Opts)
|
||||
{
|
||||
const char *sp = Opts.c_str(), *ptr = sp, *ep;
|
||||
const cChannel *chan;
|
||||
int apid = 0, pos;
|
||||
|
||||
ptr = skipspace(ptr);
|
||||
while (*ptr == '/')
|
||||
++ptr;
|
||||
|
||||
if (strncasecmp(ptr, "PS/", 3) == 0) {
|
||||
m_StreamType = stPS;
|
||||
ptr += 3;
|
||||
} else if (strncasecmp(ptr, "PES/", 4) == 0) {
|
||||
m_StreamType = stPES;
|
||||
ptr += 4;
|
||||
} else if (strncasecmp(ptr, "TS/", 3) == 0) {
|
||||
m_StreamType = stTS;
|
||||
ptr += 3;
|
||||
} else if (strncasecmp(ptr, "ES/", 3) == 0) {
|
||||
m_StreamType = stES;
|
||||
ptr += 3;
|
||||
} else if (strncasecmp(ptr, "Extern/", 3) == 0) {
|
||||
m_StreamType = stExtern;
|
||||
ptr += 7;
|
||||
}
|
||||
|
||||
while (*ptr == '/')
|
||||
++ptr;
|
||||
for (ep = ptr + strlen(ptr); ep >= ptr && !isspace(*ep); --ep)
|
||||
;
|
||||
|
||||
std::string filespec = Opts.substr(ptr - sp, ep - ptr);
|
||||
Dprintf("substr: %s\n", filespec.c_str());
|
||||
|
||||
Dprintf("before channelfromstring\n");
|
||||
if (filespec == "" || filespec.substr(0, 12) == "channels.htm") {
|
||||
m_ListChannel = Channels.First();
|
||||
m_Job = hjListing;
|
||||
} else if ((chan = ChannelFromString(filespec.c_str(), &apid)) != NULL) {
|
||||
m_Channel = chan;
|
||||
m_Apid = apid;
|
||||
Dprintf("Apid is %d\n", apid);
|
||||
m_Job = hjTransfer;
|
||||
}
|
||||
Dprintf("after channelfromstring\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
58
server/connectionHTTP.h
Normal file
58
server/connectionHTTP.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* $Id: connectionHTTP.h,v 1.3 2005/02/11 16:44:15 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H
|
||||
#define VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H
|
||||
|
||||
#include "connection.h"
|
||||
#include "server/livestreamer.h"
|
||||
|
||||
#include <tools/select.h>
|
||||
|
||||
class cChannel;
|
||||
class cStreamdevLiveStreamer;
|
||||
|
||||
class cConnectionHTTP: public cServerConnection {
|
||||
private:
|
||||
enum eHTTPStatus {
|
||||
hsRequest,
|
||||
hsHeaders,
|
||||
hsBody,
|
||||
hsFinished,
|
||||
};
|
||||
|
||||
enum eHTTPJob {
|
||||
hjTransfer,
|
||||
hjListing,
|
||||
};
|
||||
|
||||
std::string m_Request;
|
||||
//std::map<std::string,std::string> m_Headers; TODO: later?
|
||||
eHTTPStatus m_Status;
|
||||
eHTTPJob m_Job;
|
||||
// job: transfer
|
||||
cStreamdevLiveStreamer *m_LiveStreamer;
|
||||
const cChannel *m_Channel;
|
||||
int m_Apid;
|
||||
eStreamType m_StreamType;
|
||||
// job: listing
|
||||
const cChannel *m_ListChannel;
|
||||
|
||||
protected:
|
||||
bool ProcessRequest(void);
|
||||
|
||||
public:
|
||||
cConnectionHTTP(void);
|
||||
virtual ~cConnectionHTTP();
|
||||
|
||||
virtual void Attach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Attach(); }
|
||||
virtual void Detach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Detach(); }
|
||||
|
||||
virtual bool Command(char *Cmd);
|
||||
bool CmdGET(const std::string &Opts);
|
||||
|
||||
virtual void Flushed(void);
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H
|
||||
990
server/connectionVTP.c
Normal file
990
server/connectionVTP.c
Normal file
@@ -0,0 +1,990 @@
|
||||
/*
|
||||
* $Id: connectionVTP.c,v 1.8 2007/03/02 15:27:07 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include "server/connectionVTP.h"
|
||||
#include "server/livestreamer.h"
|
||||
#include "server/suspend.h"
|
||||
#include "setup.h"
|
||||
|
||||
#include <vdr/tools.h>
|
||||
#include <tools/select.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
/* VTP Response codes:
|
||||
220: Service ready
|
||||
221: Service closing connection
|
||||
451: Requested action aborted: try again
|
||||
500: Syntax error or Command unrecognized
|
||||
501: Wrong parameters or missing parameters
|
||||
550: Requested action not taken
|
||||
551: Data connection not accepted
|
||||
560: Channel not available currently
|
||||
561: Capability not known
|
||||
562: Pid not available currently
|
||||
563: Recording not available (currently?)
|
||||
*/
|
||||
|
||||
// --- cLSTEHandler -----------------------------------------------------------
|
||||
|
||||
class cLSTEHandler
|
||||
{
|
||||
private:
|
||||
enum eStates { Channel, Event, Title, Subtitle, Description, Vps,
|
||||
EndEvent, EndChannel, EndEPG };
|
||||
cConnectionVTP *m_Client;
|
||||
cSchedulesLock *m_SchedulesLock;
|
||||
const cSchedules *m_Schedules;
|
||||
const cSchedule *m_Schedule;
|
||||
const cEvent *m_Event;
|
||||
int m_Errno;
|
||||
char *m_Error;
|
||||
eStates m_State;
|
||||
bool m_Traverse;
|
||||
public:
|
||||
cLSTEHandler(cConnectionVTP *Client, const char *Option);
|
||||
~cLSTEHandler();
|
||||
bool Next(bool &Last);
|
||||
};
|
||||
|
||||
cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
|
||||
m_Client(Client),
|
||||
m_SchedulesLock(new cSchedulesLock(false, 500)),
|
||||
m_Schedules(cSchedules::Schedules(*m_SchedulesLock)),
|
||||
m_Schedule(NULL),
|
||||
m_Event(NULL),
|
||||
m_Errno(0),
|
||||
m_Error(NULL),
|
||||
m_State(Channel),
|
||||
m_Traverse(false)
|
||||
{
|
||||
eDumpMode dumpmode = dmAll;
|
||||
time_t attime = 0;
|
||||
|
||||
if (m_Schedules != NULL && *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 {
|
||||
m_Errno = 501;
|
||||
m_Error = strdup("Invalid time");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
m_Errno = 501;
|
||||
m_Error = strdup("Missing time");
|
||||
break;
|
||||
}
|
||||
} else if (!m_Schedule) {
|
||||
cChannel* Channel = NULL;
|
||||
if (isnumber(p))
|
||||
Channel = Channels.GetByNumber(strtol(Option, NULL, 10));
|
||||
else
|
||||
Channel = Channels.GetByChannelID(tChannelID::FromString(
|
||||
Option));
|
||||
if (Channel) {
|
||||
m_Schedule = m_Schedules->GetSchedule(Channel->GetChannelID());
|
||||
if (!m_Schedule) {
|
||||
m_Errno = 550;
|
||||
m_Error = strdup("No schedule found");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
m_Errno = 550;
|
||||
asprintf(&m_Error, "Channel \"%s\" not defined", p);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
m_Errno = 501;
|
||||
asprintf(&m_Error, "Unknown option: \"%s\"", p);
|
||||
break;
|
||||
}
|
||||
p = strtok_r(NULL, delim, &strtok_next);
|
||||
}
|
||||
} else if (m_Schedules == NULL) {
|
||||
m_Errno = 451;
|
||||
m_Error = strdup("EPG data is being modified, try again");
|
||||
}
|
||||
|
||||
if (m_Error == NULL) {
|
||||
if (m_Schedule != NULL)
|
||||
m_Schedules = NULL;
|
||||
else if (m_Schedules != NULL)
|
||||
m_Schedule = m_Schedules->First();
|
||||
|
||||
if (m_Schedule != NULL && m_Schedule->Events() != NULL) {
|
||||
switch (dumpmode) {
|
||||
case dmAll: m_Event = m_Schedule->Events()->First();
|
||||
m_Traverse = true;
|
||||
break;
|
||||
case dmPresent: m_Event = m_Schedule->GetPresentEvent();
|
||||
break;
|
||||
case dmFollowing: m_Event = m_Schedule->GetFollowingEvent();
|
||||
break;
|
||||
case dmAtTime: m_Event = m_Schedule->GetEventAround(attime);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cLSTEHandler::~cLSTEHandler()
|
||||
{
|
||||
delete m_SchedulesLock;
|
||||
if (m_Error != NULL)
|
||||
free(m_Error);
|
||||
}
|
||||
|
||||
bool cLSTEHandler::Next(bool &Last)
|
||||
{
|
||||
char *buffer;
|
||||
|
||||
if (m_Error != NULL) {
|
||||
Last = true;
|
||||
cString str(m_Error, true);
|
||||
m_Error = NULL;
|
||||
return m_Client->Respond(m_Errno, *str);
|
||||
}
|
||||
|
||||
Last = false;
|
||||
switch (m_State) {
|
||||
case Channel:
|
||||
if (m_Schedule != NULL) {
|
||||
cChannel *channel = Channels.GetByChannelID(m_Schedule->ChannelID(),
|
||||
true);
|
||||
if (channel != NULL) {
|
||||
m_State = Event;
|
||||
return m_Client->Respond(-215, "C %s %s",
|
||||
*channel->GetChannelID().ToString(),
|
||||
channel->Name());
|
||||
} else {
|
||||
esyslog("ERROR: vdr streamdev: unable to find channel %s by ID",
|
||||
*m_Schedule->ChannelID().ToString());
|
||||
m_State = EndChannel;
|
||||
return Next(Last);
|
||||
}
|
||||
} else {
|
||||
m_State = EndEPG;
|
||||
return Next(Last);
|
||||
}
|
||||
break;
|
||||
|
||||
case Event:
|
||||
if (m_Event != NULL) {
|
||||
m_State = Title;
|
||||
return m_Client->Respond(-215, "E %u %ld %d %X", m_Event->EventID(),
|
||||
m_Event->StartTime(), m_Event->Duration(),
|
||||
m_Event->TableID());
|
||||
} else {
|
||||
m_State = EndChannel;
|
||||
return Next(Last);
|
||||
}
|
||||
break;
|
||||
|
||||
case Title:
|
||||
m_State = Subtitle;
|
||||
if (!isempty(m_Event->Title()))
|
||||
return m_Client->Respond(-215, "T %s", m_Event->Title());
|
||||
else
|
||||
return Next(Last);
|
||||
break;
|
||||
|
||||
case Subtitle:
|
||||
m_State = Description;
|
||||
if (!isempty(m_Event->ShortText()))
|
||||
return m_Client->Respond(-215, "S %s", m_Event->ShortText());
|
||||
else
|
||||
return Next(Last);
|
||||
break;
|
||||
|
||||
case Description:
|
||||
m_State = Vps;
|
||||
if (!isempty(m_Event->Description())) {
|
||||
char *copy = strdup(m_Event->Description());
|
||||
cString cpy(copy, true);
|
||||
strreplace(copy, '\n', '|');
|
||||
return m_Client->Respond(-215, "D %s", copy);
|
||||
} else
|
||||
return Next(Last);
|
||||
break;
|
||||
|
||||
case Vps:
|
||||
m_State = EndEvent;
|
||||
if (m_Event->Vps())
|
||||
return m_Client->Respond(-215, "V %ld", m_Event->Vps());
|
||||
else
|
||||
return Next(Last);
|
||||
break;
|
||||
|
||||
case EndEvent:
|
||||
if (m_Traverse)
|
||||
m_Event = m_Schedule->Events()->Next(m_Event);
|
||||
else
|
||||
m_Event = NULL;
|
||||
|
||||
if (m_Event != NULL)
|
||||
m_State = Event;
|
||||
else
|
||||
m_State = EndChannel;
|
||||
|
||||
return m_Client->Respond(-215, "e");
|
||||
|
||||
case EndChannel:
|
||||
if (m_Schedules != NULL) {
|
||||
m_Schedule = m_Schedules->Next(m_Schedule);
|
||||
if (m_Schedule != NULL) {
|
||||
if (m_Schedule->Events() != NULL)
|
||||
m_Event = m_Schedule->Events()->First();
|
||||
m_State = Channel;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Schedules == NULL || m_Schedule == NULL)
|
||||
m_State = EndEPG;
|
||||
|
||||
return m_Client->Respond(-215, "c");
|
||||
|
||||
case EndEPG:
|
||||
Last = true;
|
||||
return m_Client->Respond(215, "End of EPG data");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- cLSTCHandler -----------------------------------------------------------
|
||||
|
||||
class cLSTCHandler
|
||||
{
|
||||
private:
|
||||
cConnectionVTP *m_Client;
|
||||
const cChannel *m_Channel;
|
||||
char *m_Option;
|
||||
int m_Errno;
|
||||
char *m_Error;
|
||||
bool m_Traverse;
|
||||
public:
|
||||
cLSTCHandler(cConnectionVTP *Client, const char *Option);
|
||||
~cLSTCHandler();
|
||||
bool Next(bool &Last);
|
||||
};
|
||||
|
||||
cLSTCHandler::cLSTCHandler(cConnectionVTP *Client, const char *Option):
|
||||
m_Client(Client),
|
||||
m_Channel(NULL),
|
||||
m_Option(NULL),
|
||||
m_Errno(0),
|
||||
m_Error(NULL),
|
||||
m_Traverse(false)
|
||||
{
|
||||
if (!Channels.Lock(false, 500)) {
|
||||
m_Errno = 451;
|
||||
m_Error = strdup("Channels are being modified - try again");
|
||||
} else if (*Option) {
|
||||
if (isnumber(Option)) {
|
||||
m_Channel = Channels.GetByNumber(strtol(Option, NULL, 10));
|
||||
if (m_Channel == NULL) {
|
||||
m_Errno = 501;
|
||||
asprintf(&m_Error, "Channel \"%s\" not defined", Option);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
int i = 1;
|
||||
m_Traverse = true;
|
||||
m_Option = strdup(Option);
|
||||
while (i <= Channels.MaxNumber()) {
|
||||
m_Channel = Channels.GetByNumber(i, 1);
|
||||
if (strcasestr(m_Channel->Name(), Option) != NULL)
|
||||
break;
|
||||
i = m_Channel->Number() + 1;
|
||||
}
|
||||
|
||||
if (i > Channels.MaxNumber()) {
|
||||
m_Errno = 501;
|
||||
asprintf(&m_Error, "Channel \"%s\" not defined", Option);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (Channels.MaxNumber() >= 1) {
|
||||
m_Channel = Channels.GetByNumber(1, 1);
|
||||
m_Traverse = true;
|
||||
} else {
|
||||
m_Errno = 550;
|
||||
m_Error = strdup("No channels defined");
|
||||
}
|
||||
}
|
||||
|
||||
cLSTCHandler::~cLSTCHandler()
|
||||
{
|
||||
Channels.Unlock();
|
||||
if (m_Error != NULL)
|
||||
free(m_Error);
|
||||
if (m_Option != NULL)
|
||||
free(m_Option);
|
||||
}
|
||||
|
||||
bool cLSTCHandler::Next(bool &Last)
|
||||
{
|
||||
if (m_Error != NULL) {
|
||||
Last = true;
|
||||
cString str(m_Error, true);
|
||||
m_Error = NULL;
|
||||
return m_Client->Respond(m_Errno, *str);
|
||||
}
|
||||
|
||||
int number;
|
||||
char *buffer;
|
||||
|
||||
number = m_Channel->Number();
|
||||
buffer = strdup(*m_Channel->ToText());
|
||||
buffer[strlen(buffer) - 1] = '\0'; // remove \n
|
||||
cString str(buffer, true);
|
||||
|
||||
Last = true;
|
||||
if (m_Traverse) {
|
||||
int i = m_Channel->Number() + 1;
|
||||
while (i <= Channels.MaxNumber()) {
|
||||
m_Channel = Channels.GetByNumber(i, 1);
|
||||
if (m_Channel != NULL) {
|
||||
if (m_Option == NULL || strcasestr(m_Channel->Name(),
|
||||
m_Option) != NULL)
|
||||
break;
|
||||
i = m_Channel->Number() + 1;
|
||||
} else {
|
||||
m_Errno = 501;
|
||||
asprintf(&m_Error, "Channel \"%d\" not found", i);
|
||||
}
|
||||
}
|
||||
|
||||
if (i < Channels.MaxNumber())
|
||||
Last = false;
|
||||
}
|
||||
|
||||
return m_Client->Respond(Last ? 250 : -250, "%d %s", number, buffer);
|
||||
}
|
||||
|
||||
// --- cLSTTHandler -----------------------------------------------------------
|
||||
|
||||
class cLSTTHandler
|
||||
{
|
||||
private:
|
||||
cConnectionVTP *m_Client;
|
||||
cTimer *m_Timer;
|
||||
int m_Index;
|
||||
int m_Errno;
|
||||
char *m_Error;
|
||||
bool m_Traverse;
|
||||
public:
|
||||
cLSTTHandler(cConnectionVTP *Client, const char *Option);
|
||||
~cLSTTHandler();
|
||||
bool Next(bool &Last);
|
||||
};
|
||||
|
||||
cLSTTHandler::cLSTTHandler(cConnectionVTP *Client, const char *Option):
|
||||
m_Client(Client),
|
||||
m_Timer(NULL),
|
||||
m_Index(0),
|
||||
m_Errno(0),
|
||||
m_Error(NULL),
|
||||
m_Traverse(false)
|
||||
{
|
||||
if (*Option) {
|
||||
if (isnumber(Option)) {
|
||||
m_Timer = Timers.Get(strtol(Option, NULL, 10) - 1);
|
||||
if (m_Timer == NULL) {
|
||||
m_Errno = 501;
|
||||
asprintf(&m_Error, "Timer \"%s\" not defined", Option);
|
||||
}
|
||||
} else {
|
||||
m_Errno = 501;
|
||||
asprintf(&m_Error, "Error in timer number \"%s\"", Option);
|
||||
}
|
||||
} else if (Timers.Count()) {
|
||||
m_Traverse = true;
|
||||
m_Index = 0;
|
||||
m_Timer = Timers.Get(m_Index);
|
||||
if (m_Timer == NULL) {
|
||||
m_Errno = 501;
|
||||
asprintf(&m_Error, "Timer \"%d\" not found", m_Index + 1);
|
||||
}
|
||||
} else {
|
||||
m_Errno = 550;
|
||||
m_Error = strdup("No timers defined");
|
||||
}
|
||||
}
|
||||
|
||||
cLSTTHandler::~cLSTTHandler()
|
||||
{
|
||||
if (m_Error != NULL)
|
||||
free(m_Error);
|
||||
}
|
||||
|
||||
bool cLSTTHandler::Next(bool &Last)
|
||||
{
|
||||
if (m_Error != NULL) {
|
||||
Last = true;
|
||||
cString str(m_Error, true);
|
||||
m_Error = NULL;
|
||||
return m_Client->Respond(m_Errno, *str);
|
||||
}
|
||||
|
||||
bool result;
|
||||
char *buffer;
|
||||
Last = !m_Traverse || m_Index >= Timers.Count() - 1;
|
||||
buffer = strdup(*m_Timer->ToText());
|
||||
buffer[strlen(buffer) - 1] = '\0'; // strip \n
|
||||
result = m_Client->Respond(Last ? 250 : -250, "%d %s", m_Timer->Index() + 1,
|
||||
buffer);
|
||||
free(buffer);
|
||||
|
||||
if (m_Traverse && !Last) {
|
||||
m_Timer = Timers.Get(++m_Index);
|
||||
if (m_Timer == NULL) {
|
||||
m_Errno = 501;
|
||||
asprintf(&m_Error, "Timer \"%d\" not found", m_Index + 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- cConnectionVTP ---------------------------------------------------------
|
||||
|
||||
cConnectionVTP::cConnectionVTP(void):
|
||||
cServerConnection("VTP"),
|
||||
m_LiveSocket(NULL),
|
||||
m_LiveStreamer(NULL),
|
||||
m_LastCommand(NULL),
|
||||
m_NoTSPIDS(false),
|
||||
m_LSTEHandler(NULL),
|
||||
m_LSTCHandler(NULL),
|
||||
m_LSTTHandler(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
cConnectionVTP::~cConnectionVTP()
|
||||
{
|
||||
if (m_LastCommand != NULL)
|
||||
free(m_LastCommand);
|
||||
delete m_LiveStreamer;
|
||||
delete m_LiveSocket;
|
||||
delete m_LSTTHandler;
|
||||
delete m_LSTCHandler;
|
||||
delete m_LSTEHandler;
|
||||
}
|
||||
|
||||
void cConnectionVTP::Welcome(void)
|
||||
{
|
||||
Respond(220, "Welcome to Video Disk Recorder (VTP)");
|
||||
}
|
||||
|
||||
void cConnectionVTP::Reject(void)
|
||||
{
|
||||
Respond(221, "Too many clients or client not allowed to connect");
|
||||
cServerConnection::Reject();
|
||||
}
|
||||
|
||||
void cConnectionVTP::Detach(void)
|
||||
{
|
||||
if (m_LiveStreamer != NULL) m_LiveStreamer->Detach();
|
||||
}
|
||||
|
||||
void cConnectionVTP::Attach(void)
|
||||
{
|
||||
if (m_LiveStreamer != NULL) m_LiveStreamer->Attach();
|
||||
}
|
||||
|
||||
bool cConnectionVTP::Command(char *Cmd)
|
||||
{
|
||||
char *param = NULL;
|
||||
|
||||
if (Cmd != NULL) {
|
||||
if (m_LastCommand != NULL) {
|
||||
esyslog("ERROR: streamdev: protocol violation (VTP) from %s:%d",
|
||||
RemoteIp().c_str(), RemotePort());
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((param = strchr(Cmd, ' ')) != NULL)
|
||||
*(param++) = '\0';
|
||||
else
|
||||
param = Cmd + strlen(Cmd);
|
||||
m_LastCommand = strdup(Cmd);
|
||||
} else {
|
||||
Cmd = m_LastCommand;
|
||||
param = NULL;
|
||||
}
|
||||
|
||||
if (strcasecmp(Cmd, "LSTE") == 0) return CmdLSTE(param);
|
||||
//else if (strcasecmp(Cmd, "LSTR") == 0) return CmdLSTR(param);
|
||||
else if (strcasecmp(Cmd, "LSTT") == 0) return CmdLSTT(param);
|
||||
else if (strcasecmp(Cmd, "LSTC") == 0) return CmdLSTC(param);
|
||||
|
||||
if (param == NULL) {
|
||||
esyslog("ERROR: streamdev: this seriously shouldn't happen at %s:%d",
|
||||
__FILE__, __LINE__);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strcasecmp(Cmd, "CAPS") == 0) return CmdCAPS(param);
|
||||
else if (strcasecmp(Cmd, "PROV") == 0) return CmdPROV(param);
|
||||
else if (strcasecmp(Cmd, "PORT") == 0) return CmdPORT(param);
|
||||
else if (strcasecmp(Cmd, "TUNE") == 0) return CmdTUNE(param);
|
||||
else if (strcasecmp(Cmd, "ADDP") == 0) return CmdADDP(param);
|
||||
else if (strcasecmp(Cmd, "DELP") == 0) return CmdDELP(param);
|
||||
else if (strcasecmp(Cmd, "ADDF") == 0) return CmdADDF(param);
|
||||
else if (strcasecmp(Cmd, "DELF") == 0) return CmdDELF(param);
|
||||
else if (strcasecmp(Cmd, "ABRT") == 0) return CmdABRT(param);
|
||||
else if (strcasecmp(Cmd, "QUIT") == 0) return CmdQUIT(param);
|
||||
else if (strcasecmp(Cmd, "SUSP") == 0) return CmdSUSP(param);
|
||||
// Commands adopted from SVDRP
|
||||
//else if (strcasecmp(Cmd, "DELR") == 0) return CmdDELR(param);
|
||||
else if (strcasecmp(Cmd, "MODT") == 0) return CmdMODT(param);
|
||||
else if (strcasecmp(Cmd, "NEWT") == 0) return CmdNEWT(param);
|
||||
else if (strcasecmp(Cmd, "DELT") == 0) return CmdDELT(param);
|
||||
else
|
||||
return Respond(500, "Unknown Command \"%s\"", Cmd);
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdCAPS(char *Opts)
|
||||
{
|
||||
char *buffer;
|
||||
|
||||
if (strcasecmp(Opts, "TS") == 0) {
|
||||
m_NoTSPIDS = true;
|
||||
return Respond(220, "Ignored, capability \"%s\" accepted for "
|
||||
"compatibility", Opts);
|
||||
}
|
||||
|
||||
if (strcasecmp(Opts, "TSPIDS") == 0) {
|
||||
m_NoTSPIDS = false;
|
||||
return Respond(220, "Capability \"%s\" accepted", Opts);
|
||||
}
|
||||
|
||||
return Respond(561, "Capability \"%s\" not known", Opts);
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdPROV(char *Opts)
|
||||
{
|
||||
const cChannel *chan;
|
||||
int prio;
|
||||
char *ep;
|
||||
|
||||
prio = strtol(Opts, &ep, 10);
|
||||
if (ep == Opts || !isspace(*ep))
|
||||
return Respond(501, "Use: PROV Priority Channel");
|
||||
|
||||
Opts = skipspace(ep);
|
||||
if ((chan = ChannelFromString(Opts)) == NULL)
|
||||
return Respond(550, "Undefined channel \"%s\"", Opts);
|
||||
|
||||
return GetDevice(chan, prio) != NULL
|
||||
? Respond(220, "Channel available")
|
||||
: Respond(560, "Channel not available");
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdPORT(char *Opts)
|
||||
{
|
||||
uint id, dataport = 0;
|
||||
char dataip[20];
|
||||
char *ep, *ipoffs;
|
||||
int n;
|
||||
|
||||
id = strtoul(Opts, &ep, 10);
|
||||
if (ep == Opts || !isspace(*ep))
|
||||
return Respond(500, "Use: PORT Id Destination");
|
||||
|
||||
if (id != 0)
|
||||
return Respond(501, "Wrong connection id %d", id);
|
||||
|
||||
Opts = skipspace(ep);
|
||||
n = 0;
|
||||
ipoffs = dataip;
|
||||
while ((ep = strchr(Opts, ',')) != NULL) {
|
||||
if (n < 4) {
|
||||
memcpy(ipoffs, Opts, ep - Opts);
|
||||
ipoffs += ep - Opts;
|
||||
if (n < 3) *(ipoffs++) = '.';
|
||||
} else if (n == 4) {
|
||||
*ep = 0;
|
||||
dataport = strtoul(Opts, NULL, 10) << 8;
|
||||
} else
|
||||
break;
|
||||
Opts = ep + 1;
|
||||
++n;
|
||||
}
|
||||
*ipoffs = '\0';
|
||||
|
||||
if (n != 5)
|
||||
return Respond(501, "Argument count invalid (must be 6 values)");
|
||||
|
||||
dataport |= strtoul(Opts, NULL, 10);
|
||||
|
||||
isyslog("Streamdev: Setting data connection to %s:%d", dataip, dataport);
|
||||
|
||||
m_LiveSocket = new cTBSocket(SOCK_STREAM);
|
||||
if (!m_LiveSocket->Connect(dataip, dataport)) {
|
||||
esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s",
|
||||
dataip, dataport, strerror(errno));
|
||||
DELETENULL(m_LiveSocket);
|
||||
return Respond(551, "Couldn't open data connection");
|
||||
}
|
||||
|
||||
if (id == siLive)
|
||||
m_LiveStreamer->Start(m_LiveSocket);
|
||||
|
||||
return Respond(220, "Port command ok, data connection opened");
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdTUNE(char *Opts)
|
||||
{
|
||||
const cChannel *chan;
|
||||
cDevice *dev;
|
||||
|
||||
if ((chan = ChannelFromString(Opts)) == NULL)
|
||||
return Respond(550, "Undefined channel \"%s\"", Opts);
|
||||
|
||||
if ((dev = GetDevice(chan, 0)) == NULL)
|
||||
return Respond(560, "Channel not available");
|
||||
|
||||
if (!dev->SwitchChannel(chan, false))
|
||||
return Respond(560, "Channel not available");
|
||||
|
||||
delete m_LiveStreamer;
|
||||
m_LiveStreamer = new cStreamdevLiveStreamer(1);
|
||||
m_LiveStreamer->SetChannel(chan, m_NoTSPIDS ? stTS : stTSPIDS);
|
||||
m_LiveStreamer->SetDevice(dev);
|
||||
|
||||
return Respond(220, "Channel tuned");
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdADDP(char *Opts)
|
||||
{
|
||||
int pid;
|
||||
char *end;
|
||||
|
||||
pid = strtoul(Opts, &end, 10);
|
||||
if (end == Opts || (*end != '\0' && *end != ' '))
|
||||
return Respond(500, "Use: ADDP Pid");
|
||||
|
||||
return m_LiveStreamer && m_LiveStreamer->SetPid(pid, true)
|
||||
? Respond(220, "Pid %d available", pid)
|
||||
: Respond(560, "Pid %d not available", pid);
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdDELP(char *Opts)
|
||||
{
|
||||
int pid;
|
||||
char *end;
|
||||
|
||||
pid = strtoul(Opts, &end, 10);
|
||||
if (end == Opts || (*end != '\0' && *end != ' '))
|
||||
return Respond(500, "Use: DELP Pid");
|
||||
|
||||
return m_LiveStreamer && m_LiveStreamer->SetPid(pid, false)
|
||||
? Respond(220, "Pid %d stopped", pid)
|
||||
: Respond(560, "Pid %d not transferring", pid);
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdADDF(char *Opts)
|
||||
{
|
||||
#if VDRVERSNUM >= 10300
|
||||
int pid, tid, mask;
|
||||
char *ep;
|
||||
|
||||
if (m_LiveStreamer == NULL)
|
||||
return Respond(560, "Can't set filters without a stream");
|
||||
|
||||
pid = strtol(Opts, &ep, 10);
|
||||
if (ep == Opts || (*ep != ' '))
|
||||
return Respond(500, "Use: ADDF Pid Tid Mask");
|
||||
Opts = skipspace(ep);
|
||||
tid = strtol(Opts, &ep, 10);
|
||||
if (ep == Opts || (*ep != ' '))
|
||||
return Respond(500, "Use: ADDF Pid Tid Mask");
|
||||
Opts = skipspace(ep);
|
||||
mask = strtol(Opts, &ep, 10);
|
||||
if (ep == Opts || (*ep != '\0' && *ep != ' '))
|
||||
return Respond(500, "Use: ADDF Pid Tid Mask");
|
||||
|
||||
return m_LiveStreamer->SetFilter(pid, tid, mask, true)
|
||||
? Respond(220, "Filter %d transferring", pid)
|
||||
: Respond(560, "Filter %d not available", pid);
|
||||
#else
|
||||
return Respond(500, "ADDF known but unimplemented with VDR < 1.3.0");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdDELF(char *Opts)
|
||||
{
|
||||
#if VDRVERSNUM >= 10307
|
||||
int pid, tid, mask;
|
||||
char *ep;
|
||||
|
||||
if (m_LiveStreamer == NULL)
|
||||
return Respond(560, "Can't delete filters without a stream");
|
||||
|
||||
pid = strtol(Opts, &ep, 10);
|
||||
if (ep == Opts || (*ep != ' '))
|
||||
return Respond(500, "Use: DELF Pid Tid Mask");
|
||||
Opts = skipspace(ep);
|
||||
tid = strtol(Opts, &ep, 10);
|
||||
if (ep == Opts || (*ep != ' '))
|
||||
return Respond(500, "Use: DELF Pid Tid Mask");
|
||||
Opts = skipspace(ep);
|
||||
mask = strtol(Opts, &ep, 10);
|
||||
if (ep == Opts || (*ep != '\0' && *ep != ' '))
|
||||
return Respond(500, "Use: DELF Pid Tid Mask");
|
||||
|
||||
return m_LiveStreamer->SetFilter(pid, tid, mask, false)
|
||||
? Respond(220, "Filter %d stopped", pid)
|
||||
: Respond(560, "Filter %d not transferring", pid);
|
||||
#else
|
||||
return Respond(500, "DELF known but unimplemented with VDR < 1.3.0");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdABRT(char *Opts)
|
||||
{
|
||||
uint id;
|
||||
char *ep;
|
||||
|
||||
id = strtoul(Opts, &ep, 10);
|
||||
if (ep == Opts || (*ep != '\0' && *ep != ' '))
|
||||
return Respond(500, "Use: ABRT Id");
|
||||
|
||||
switch (id) {
|
||||
case 0: DELETENULL(m_LiveStreamer); break;
|
||||
}
|
||||
|
||||
DELETENULL(m_LiveSocket);
|
||||
return Respond(220, "Data connection closed");
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdQUIT(char *Opts)
|
||||
{
|
||||
DeferClose();
|
||||
return Respond(221, "Video Disk Recorder closing connection");
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdSUSP(char *Opts)
|
||||
{
|
||||
if (StreamdevServerSetup.SuspendMode == smAlways || cSuspendCtl::IsActive())
|
||||
return Respond(220, "Server is suspended");
|
||||
else if (StreamdevServerSetup.SuspendMode == smOffer
|
||||
&& StreamdevServerSetup.AllowSuspend) {
|
||||
cControl::Launch(new cSuspendCtl);
|
||||
return Respond(220, "Server is suspended");
|
||||
} else
|
||||
return Respond(550, "Client may not suspend server");
|
||||
}
|
||||
|
||||
// Functions extended from SVDRP
|
||||
|
||||
template<class cHandler>
|
||||
bool cConnectionVTP::CmdLSTX(cHandler *&Handler, char *Option)
|
||||
{
|
||||
if (Option != NULL) {
|
||||
delete Handler;
|
||||
Handler = new cHandler(this, Option);
|
||||
}
|
||||
|
||||
bool last, result = false;
|
||||
if (Handler != NULL)
|
||||
result = Handler->Next(last);
|
||||
else
|
||||
esyslog("ERROR: vdr streamdev: Handler in LSTX command is NULL");
|
||||
if (!result || last)
|
||||
DELETENULL(Handler);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdLSTE(char *Option)
|
||||
{
|
||||
return CmdLSTX(m_LSTEHandler, Option);
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdLSTC(char *Option)
|
||||
{
|
||||
return CmdLSTX(m_LSTCHandler, Option);
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdLSTT(char *Option)
|
||||
{
|
||||
return CmdLSTX(m_LSTTHandler, Option);
|
||||
}
|
||||
|
||||
// Functions adopted from SVDRP
|
||||
#define INIT_WRAPPER() bool _res
|
||||
#define Reply(c,m...) _res = Respond(c,m)
|
||||
#define EXIT_WRAPPER() return _res
|
||||
|
||||
bool cConnectionVTP::CmdMODT(const char *Option)
|
||||
{
|
||||
INIT_WRAPPER();
|
||||
if (*Option) {
|
||||
char *tail;
|
||||
int n = strtol(Option, &tail, 10);
|
||||
if (tail && tail != Option) {
|
||||
tail = skipspace(tail);
|
||||
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");
|
||||
EXIT_WRAPPER();
|
||||
}
|
||||
*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());
|
||||
} else
|
||||
Reply(501, "Timer \"%d\" not defined", n);
|
||||
} else
|
||||
Reply(501, "Error in timer number");
|
||||
} else
|
||||
Reply(501, "Missing timer settings");
|
||||
EXIT_WRAPPER();
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdNEWT(const char *Option)
|
||||
{
|
||||
INIT_WRAPPER();
|
||||
if (*Option) {
|
||||
cTimer *timer = new cTimer;
|
||||
if (timer->Parse(Option)) {
|
||||
cTimer *t = Timers.GetTimer(timer);
|
||||
if (!t) {
|
||||
Timers.Add(timer);
|
||||
Timers.SetModified();
|
||||
isyslog("timer %s added", *timer->ToDescr());
|
||||
Reply(250, "%d %s", timer->Index() + 1, *timer->ToText());
|
||||
EXIT_WRAPPER();
|
||||
} else
|
||||
Reply(550, "Timer already defined: %d %s", t->Index() + 1,
|
||||
*t->ToText());
|
||||
} else
|
||||
Reply(501, "Error in timer settings");
|
||||
delete timer;
|
||||
} else
|
||||
Reply(501, "Missing timer settings");
|
||||
EXIT_WRAPPER();
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdDELT(const char *Option)
|
||||
{
|
||||
INIT_WRAPPER();
|
||||
if (*Option) {
|
||||
if (isnumber(Option)) {
|
||||
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);
|
||||
} else
|
||||
Reply(501, "Error in timer number \"%s\"", Option);
|
||||
} else
|
||||
Reply(501, "Missing timer number");
|
||||
EXIT_WRAPPER();
|
||||
}
|
||||
|
||||
/*bool cConnectionVTP::CmdLSTR(char *Option) {
|
||||
INIT_WRAPPER();
|
||||
bool recordings = Recordings.Load();
|
||||
Recordings.Sort();
|
||||
if (*Option) {
|
||||
if (isnumber(Option)) {
|
||||
cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
|
||||
if (recording) {
|
||||
if (recording->Summary()) {
|
||||
char *summary = strdup(recording->Summary());
|
||||
Reply(250, "%s", strreplace(summary,'\n','|'));
|
||||
free(summary);
|
||||
}
|
||||
else
|
||||
Reply(550, "No summary availabe");
|
||||
}
|
||||
else
|
||||
Reply(550, "Recording \"%s\" not found", Option);
|
||||
}
|
||||
else
|
||||
Reply(501, "Error in recording number \"%s\"", Option);
|
||||
}
|
||||
else if (recordings) {
|
||||
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");
|
||||
EXIT_WRAPPER();
|
||||
}
|
||||
|
||||
bool cConnectionVTP::CmdDELR(char *Option) {
|
||||
INIT_WRAPPER();
|
||||
if (*Option) {
|
||||
if (isnumber(Option)) {
|
||||
cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
|
||||
if (recording) {
|
||||
if (recording->Delete())
|
||||
Reply(250, "Recording \"%s\" deleted", Option);
|
||||
else
|
||||
Reply(554, "Error while deleting recording!");
|
||||
}
|
||||
else
|
||||
Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)");
|
||||
}
|
||||
else
|
||||
Reply(501, "Error in recording number \"%s\"", Option);
|
||||
}
|
||||
else
|
||||
Reply(501, "Missing recording number");
|
||||
EXIT_WRAPPER();
|
||||
}*/
|
||||
|
||||
bool cConnectionVTP::Respond(int Code, const char *Message, ...)
|
||||
{
|
||||
char *buffer;
|
||||
va_list ap;
|
||||
va_start(ap, Message);
|
||||
vasprintf(&buffer, Message, ap);
|
||||
va_end(ap);
|
||||
cString str(buffer, true);
|
||||
|
||||
if (Code >= 0 && m_LastCommand != NULL) {
|
||||
free(m_LastCommand);
|
||||
m_LastCommand = NULL;
|
||||
}
|
||||
|
||||
return cServerConnection::Respond("%03d%c%s", Code >= 0,
|
||||
Code < 0 ? -Code : Code,
|
||||
Code < 0 ? '-' : ' ', buffer);
|
||||
}
|
||||
75
server/connectionVTP.h
Normal file
75
server/connectionVTP.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H
|
||||
#define VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H
|
||||
|
||||
#include "server/connection.h"
|
||||
|
||||
class cTBSocket;
|
||||
class cStreamdevLiveStreamer;
|
||||
class cLSTEHandler;
|
||||
class cLSTCHandler;
|
||||
class cLSTTHandler;
|
||||
|
||||
class cConnectionVTP: public cServerConnection {
|
||||
friend class cLSTEHandler;
|
||||
// if your compiler doesn't understand the following statement
|
||||
// (e.g. gcc 2.x), simply remove it and try again ;-)
|
||||
using cServerConnection::Respond;
|
||||
|
||||
private:
|
||||
cTBSocket *m_LiveSocket;
|
||||
cStreamdevLiveStreamer *m_LiveStreamer;
|
||||
|
||||
char *m_LastCommand;
|
||||
bool m_NoTSPIDS;
|
||||
|
||||
// Members adopted for SVDRP
|
||||
cRecordings Recordings;
|
||||
cLSTEHandler *m_LSTEHandler;
|
||||
cLSTCHandler *m_LSTCHandler;
|
||||
cLSTTHandler *m_LSTTHandler;
|
||||
|
||||
protected:
|
||||
template<class cHandler>
|
||||
bool CmdLSTX(cHandler *&Handler, char *Option);
|
||||
|
||||
public:
|
||||
cConnectionVTP(void);
|
||||
virtual ~cConnectionVTP();
|
||||
|
||||
virtual void Welcome(void);
|
||||
virtual void Reject(void);
|
||||
|
||||
virtual void Detach(void);
|
||||
virtual void Attach(void);
|
||||
|
||||
virtual bool Command(char *Cmd);
|
||||
bool CmdCAPS(char *Opts);
|
||||
bool CmdPROV(char *Opts);
|
||||
bool CmdPORT(char *Opts);
|
||||
bool CmdTUNE(char *Opts);
|
||||
bool CmdADDP(char *Opts);
|
||||
bool CmdDELP(char *Opts);
|
||||
bool CmdADDF(char *Opts);
|
||||
bool CmdDELF(char *Opts);
|
||||
bool CmdABRT(char *Opts);
|
||||
bool CmdQUIT(char *Opts);
|
||||
bool CmdSUSP(char *Opts);
|
||||
|
||||
// Thread-safe implementations of SVDRP commands
|
||||
bool CmdLSTE(char *Opts);
|
||||
bool CmdLSTC(char *Opts);
|
||||
bool CmdLSTT(char *Opts);
|
||||
|
||||
// Commands adopted from SVDRP
|
||||
bool CmdMODT(const char *Option);
|
||||
bool CmdNEWT(const char *Option);
|
||||
bool CmdDELT(const char *Option);
|
||||
|
||||
//bool CmdLSTR(char *Opts);
|
||||
//bool CmdDELR(char *Opts);
|
||||
|
||||
bool Respond(int Code, const char *Message, ...)
|
||||
__attribute__ ((format (printf, 3, 4)));
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H
|
||||
41
server/livefilter.c
Normal file
41
server/livefilter.c
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* $Id: livefilter.c,v 1.2 2005/02/08 13:59:16 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#include "server/livefilter.h"
|
||||
#include "server/livestreamer.h"
|
||||
#include "common.h"
|
||||
|
||||
#if VDRVERSNUM >= 10300
|
||||
|
||||
cStreamdevLiveFilter::cStreamdevLiveFilter(cStreamdevLiveStreamer *Streamer) {
|
||||
m_Streamer = Streamer;
|
||||
}
|
||||
|
||||
cStreamdevLiveFilter::~cStreamdevLiveFilter() {
|
||||
}
|
||||
|
||||
void cStreamdevLiveFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
|
||||
{
|
||||
uchar buffer[TS_SIZE];
|
||||
int length = Length;
|
||||
int pos = 0;
|
||||
|
||||
while (length > 0) {
|
||||
int chunk = min(length, TS_SIZE - 5);
|
||||
buffer[0] = TS_SYNC_BYTE;
|
||||
buffer[1] = (Pid >> 8) & 0xff;
|
||||
buffer[2] = Pid & 0xff;
|
||||
buffer[3] = Tid;
|
||||
buffer[4] = (uchar)chunk;
|
||||
memcpy(buffer + 5, Data + pos, chunk);
|
||||
length -= chunk;
|
||||
pos += chunk;
|
||||
|
||||
int p = m_Streamer->Put(buffer, TS_SIZE);
|
||||
if (p != TS_SIZE)
|
||||
m_Streamer->ReportOverflow(TS_SIZE - p);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // VDRVERSNUM >= 10300
|
||||
31
server/livefilter.h
Normal file
31
server/livefilter.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* $Id: livefilter.h,v 1.2 2005/11/07 19:28:41 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMEV_LIVEFILTER_H
|
||||
#define VDR_STREAMEV_LIVEFILTER_H
|
||||
|
||||
#include <vdr/config.h>
|
||||
|
||||
# if VDRVERSNUM >= 10300
|
||||
|
||||
#include <vdr/filter.h>
|
||||
|
||||
class cStreamdevLiveStreamer;
|
||||
|
||||
class cStreamdevLiveFilter: public cFilter {
|
||||
friend class cStreamdevLiveStreamer;
|
||||
|
||||
private:
|
||||
cStreamdevLiveStreamer *m_Streamer;
|
||||
|
||||
protected:
|
||||
virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length);
|
||||
|
||||
public:
|
||||
cStreamdevLiveFilter(cStreamdevLiveStreamer *Streamer);
|
||||
virtual ~cStreamdevLiveFilter();
|
||||
};
|
||||
|
||||
# endif // VDRVERSNUM >= 10300
|
||||
#endif // VDR_STREAMEV_LIVEFILTER_H
|
||||
298
server/livestreamer.c
Normal file
298
server/livestreamer.c
Normal file
@@ -0,0 +1,298 @@
|
||||
#include <vdr/ringbuffer.h>
|
||||
|
||||
#include "server/livestreamer.h"
|
||||
#include "remux/ts2ps.h"
|
||||
#include "remux/ts2es.h"
|
||||
#include "remux/extern.h"
|
||||
#include "common.h"
|
||||
|
||||
// --- cStreamdevLiveReceiver -------------------------------------------------
|
||||
|
||||
#if VDRVERSNUM < 10500
|
||||
cStreamdevLiveReceiver::cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, int Ca,
|
||||
int Priority, const int *Pids):
|
||||
cReceiver(Ca, Priority, 0, Pids),
|
||||
#else
|
||||
cStreamdevLiveReceiver::cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, tChannelID ChannelID,
|
||||
int Priority, const int *Pids):
|
||||
cReceiver(ChannelID, Priority, 0, Pids),
|
||||
#endif
|
||||
m_Streamer(Streamer)
|
||||
{
|
||||
}
|
||||
|
||||
cStreamdevLiveReceiver::~cStreamdevLiveReceiver()
|
||||
{
|
||||
Dprintf("Killing live receiver\n");
|
||||
Detach();
|
||||
}
|
||||
|
||||
void cStreamdevLiveReceiver::Receive(uchar *Data, int Length) {
|
||||
int p = m_Streamer->Receive(Data, Length);
|
||||
if (p != Length)
|
||||
m_Streamer->ReportOverflow(Length - p);
|
||||
}
|
||||
|
||||
// --- cStreamdevLiveStreamer -------------------------------------------------
|
||||
|
||||
cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority):
|
||||
cStreamdevStreamer("streamdev-livestreaming"),
|
||||
m_Priority(Priority),
|
||||
m_NumPids(0),
|
||||
m_StreamType(stTSPIDS),
|
||||
m_Channel(NULL),
|
||||
m_Device(NULL),
|
||||
m_Receiver(NULL),
|
||||
m_PESRemux(NULL),
|
||||
m_ESRemux(NULL),
|
||||
m_PSRemux(NULL),
|
||||
m_ExtRemux(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
cStreamdevLiveStreamer::~cStreamdevLiveStreamer()
|
||||
{
|
||||
Dprintf("Desctructing Live streamer\n");
|
||||
Stop();
|
||||
delete m_Receiver;
|
||||
delete m_PESRemux;
|
||||
delete m_ESRemux;
|
||||
delete m_PSRemux;
|
||||
delete m_ExtRemux;
|
||||
#if VDRVERSNUM >= 10300
|
||||
//delete m_Filter; TODO
|
||||
#endif
|
||||
}
|
||||
|
||||
bool cStreamdevLiveStreamer::SetPid(int Pid, bool On)
|
||||
{
|
||||
int idx;
|
||||
|
||||
if (Pid == 0)
|
||||
return true;
|
||||
|
||||
if (On) {
|
||||
for (idx = 0; idx < m_NumPids; ++idx) {
|
||||
if (m_Pids[idx] == Pid)
|
||||
return true; // No change needed
|
||||
}
|
||||
|
||||
if (m_NumPids == MAXRECEIVEPIDS) {
|
||||
esyslog("ERROR: Streamdev: No free slot to receive pid %d\n", Pid);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Pids[m_NumPids++] = Pid;
|
||||
m_Pids[m_NumPids] = 0;
|
||||
} else {
|
||||
for (idx = 0; idx < m_NumPids; ++idx) {
|
||||
if (m_Pids[idx] == Pid) {
|
||||
--m_NumPids;
|
||||
memmove(&m_Pids[idx], &m_Pids[idx + 1], sizeof(int) * (m_NumPids - idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DELETENULL(m_Receiver);
|
||||
if (m_NumPids > 0) {
|
||||
Dprintf("Creating Receiver to respect changed pids\n");
|
||||
#if VDRVERSNUM < 10500
|
||||
m_Receiver = new cStreamdevLiveReceiver(this, m_Channel->Ca(), m_Priority, m_Pids);
|
||||
#else
|
||||
m_Receiver = new cStreamdevLiveReceiver(this, m_Channel->GetChannelID(), m_Priority, m_Pids);
|
||||
#endif
|
||||
if (IsRunning() && m_Device != NULL) {
|
||||
Dprintf("Attaching new receiver\n");
|
||||
Attach();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType StreamType, int Apid)
|
||||
{
|
||||
Dprintf("Initializing Remuxer for full channel transfer\n");
|
||||
printf("ca pid: %d\n", Channel->Ca());
|
||||
m_Channel = Channel;
|
||||
m_StreamType = StreamType;
|
||||
switch (m_StreamType) {
|
||||
case stES:
|
||||
{
|
||||
int pid = ISRADIO(m_Channel) ? m_Channel->Apid(0) : m_Channel->Vpid();
|
||||
if (Apid != 0)
|
||||
pid = Apid;
|
||||
m_ESRemux = new cTS2ESRemux(pid);
|
||||
return SetPid(pid, true);
|
||||
}
|
||||
|
||||
case stPES:
|
||||
Dprintf("PES\n");
|
||||
m_PESRemux = new cRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
|
||||
m_Channel->Spids(), false);
|
||||
if (Apid != 0)
|
||||
return SetPid(m_Channel->Vpid(), true)
|
||||
&& SetPid(Apid, true);
|
||||
else
|
||||
return SetPid(m_Channel->Vpid(), true)
|
||||
&& SetPid(m_Channel->Apid(0), true)
|
||||
&& SetPid(m_Channel->Dpid(0), true);
|
||||
|
||||
case stPS:
|
||||
m_PSRemux = new cTS2PSRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
|
||||
m_Channel->Spids());
|
||||
if (Apid != 0)
|
||||
return SetPid(m_Channel->Vpid(), true)
|
||||
&& SetPid(Apid, true);
|
||||
else
|
||||
return SetPid(m_Channel->Vpid(), true)
|
||||
&& SetPid(m_Channel->Apid(0), true)
|
||||
&& SetPid(m_Channel->Dpid(0), true);
|
||||
|
||||
case stTS:
|
||||
if (Apid != 0)
|
||||
return SetPid(m_Channel->Vpid(), true)
|
||||
&& SetPid(Apid, true);
|
||||
else
|
||||
return SetPid(m_Channel->Vpid(), true)
|
||||
&& SetPid(m_Channel->Apid(0), true)
|
||||
&& SetPid(m_Channel->Dpid(0), true);
|
||||
|
||||
case stExtern:
|
||||
m_ExtRemux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
|
||||
m_Channel->Spids());
|
||||
if (Apid != 0)
|
||||
return SetPid(m_Channel->Vpid(), true)
|
||||
&& SetPid(Apid, true);
|
||||
else
|
||||
return SetPid(m_Channel->Vpid(), true)
|
||||
&& SetPid(m_Channel->Apid(0), true)
|
||||
&& SetPid(m_Channel->Dpid(0), true);
|
||||
|
||||
case stTSPIDS:
|
||||
Dprintf("pid streaming mode\n");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool cStreamdevLiveStreamer::SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On)
|
||||
{
|
||||
#if 0
|
||||
Dprintf("setting filter\n");
|
||||
if (On) {
|
||||
if (m_Filter == NULL) {
|
||||
m_Filter = new cStreamdevLiveFilter(this);
|
||||
Dprintf("attaching filter to device\n");
|
||||
m_Device->AttachFilter(m_Filter);
|
||||
}
|
||||
m_Filter->Set(Pid, Tid, Mask);
|
||||
} else if (m_Filter != NULL)
|
||||
m_Filter->Del(Pid, Tid, Mask);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
int cStreamdevLiveStreamer::Put(const uchar *Data, int Count)
|
||||
{
|
||||
switch (m_StreamType) {
|
||||
case stTS:
|
||||
case stTSPIDS:
|
||||
return cStreamdevStreamer::Put(Data, Count);
|
||||
|
||||
case stPES:
|
||||
return m_PESRemux->Put(Data, Count);
|
||||
|
||||
case stES:
|
||||
return m_ESRemux->Put(Data, Count);
|
||||
|
||||
case stPS:
|
||||
return m_PSRemux->Put(Data, Count);
|
||||
|
||||
case stExtern:
|
||||
return m_ExtRemux->Put(Data, Count);
|
||||
|
||||
default: // shouldn't happen???
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uchar *cStreamdevLiveStreamer::Get(int &Count)
|
||||
{
|
||||
switch (m_StreamType) {
|
||||
case stTS:
|
||||
case stTSPIDS:
|
||||
return cStreamdevStreamer::Get(Count);
|
||||
|
||||
case stPES:
|
||||
return m_PESRemux->Get(Count);
|
||||
|
||||
case stES:
|
||||
return m_ESRemux->Get(Count);
|
||||
|
||||
case stPS:
|
||||
return m_PSRemux->Get(Count);
|
||||
|
||||
case stExtern:
|
||||
return m_ExtRemux->Get(Count);
|
||||
|
||||
default: // shouldn't happen???
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void cStreamdevLiveStreamer::Del(int Count)
|
||||
{
|
||||
switch (m_StreamType) {
|
||||
case stTS:
|
||||
case stTSPIDS:
|
||||
cStreamdevStreamer::Del(Count);
|
||||
break;
|
||||
|
||||
case stPES:
|
||||
m_PESRemux->Del(Count);
|
||||
break;
|
||||
|
||||
case stES:
|
||||
m_ESRemux->Del(Count);
|
||||
break;
|
||||
|
||||
case stPS:
|
||||
m_PSRemux->Del(Count);
|
||||
break;
|
||||
|
||||
case stExtern:
|
||||
m_ExtRemux->Del(Count);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void cStreamdevLiveStreamer::Attach(void)
|
||||
{
|
||||
printf("RIGHT ATTACH\n");
|
||||
m_Device->AttachReceiver(m_Receiver);
|
||||
}
|
||||
|
||||
void cStreamdevLiveStreamer::Detach(void)
|
||||
{
|
||||
printf("RIGHT DETACH\n");
|
||||
m_Device->Detach(m_Receiver);
|
||||
}
|
||||
|
||||
std::string cStreamdevLiveStreamer::Report(void)
|
||||
{
|
||||
std::string result;
|
||||
|
||||
if (m_Device != NULL)
|
||||
result += (std::string)"+- Device is " + (const char*)itoa(m_Device->CardIndex()) + "\n";
|
||||
if (m_Receiver != NULL)
|
||||
result += "+- Receiver is allocated\n";
|
||||
|
||||
result += "+- Pids are ";
|
||||
for (int i = 0; i < MAXRECEIVEPIDS; ++i)
|
||||
if (m_Pids[i] != 0)
|
||||
result += (std::string)(const char*)itoa(m_Pids[i]) + ", ";
|
||||
result += "\n";
|
||||
return result;
|
||||
}
|
||||
81
server/livestreamer.h
Normal file
81
server/livestreamer.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#ifndef VDR_STREAMDEV_LIVESTREAMER_H
|
||||
#define VDR_STREAMDEV_LIVESTREAMER_H
|
||||
|
||||
#include <vdr/config.h>
|
||||
#include <vdr/receiver.h>
|
||||
|
||||
#include "server/streamer.h"
|
||||
#include "server/livefilter.h"
|
||||
#include "common.h"
|
||||
|
||||
class cTS2PSRemux;
|
||||
class cTS2ESRemux;
|
||||
class cExternRemux;
|
||||
class cRemux;
|
||||
|
||||
// --- cStreamdevLiveReceiver -------------------------------------------------
|
||||
|
||||
class cStreamdevLiveReceiver: public cReceiver {
|
||||
friend class cStreamdevLiveStreamer;
|
||||
|
||||
private:
|
||||
cStreamdevLiveStreamer *m_Streamer;
|
||||
|
||||
protected:
|
||||
virtual void Activate(bool On);
|
||||
virtual void Receive(uchar *Data, int Length);
|
||||
|
||||
public:
|
||||
#if VDRVERSNUM < 10500
|
||||
cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, int Ca, int Priority, const int *Pids);
|
||||
#else
|
||||
cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, tChannelID ChannelID, int Priority, const int *Pids);
|
||||
#endif
|
||||
virtual ~cStreamdevLiveReceiver();
|
||||
};
|
||||
|
||||
// --- cStreamdevLiveStreamer -------------------------------------------------
|
||||
|
||||
class cStreamdevLiveStreamer: public cStreamdevStreamer {
|
||||
private:
|
||||
int m_Priority;
|
||||
int m_Pids[MAXRECEIVEPIDS + 1];
|
||||
int m_NumPids;
|
||||
eStreamType m_StreamType;
|
||||
const cChannel *m_Channel;
|
||||
cDevice *m_Device;
|
||||
cStreamdevLiveReceiver *m_Receiver;
|
||||
cRemux *m_PESRemux;
|
||||
cTS2ESRemux *m_ESRemux;
|
||||
cTS2PSRemux *m_PSRemux;
|
||||
cExternRemux *m_ExtRemux;
|
||||
|
||||
public:
|
||||
cStreamdevLiveStreamer(int Priority);
|
||||
virtual ~cStreamdevLiveStreamer();
|
||||
|
||||
void SetDevice(cDevice *Device) { m_Device = Device; }
|
||||
bool SetPid(int Pid, bool On);
|
||||
bool SetChannel(const cChannel *Channel, eStreamType StreamType, int Apid = 0);
|
||||
bool SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On);
|
||||
|
||||
virtual int Put(const uchar *Data, int Count);
|
||||
virtual uchar *Get(int &Count);
|
||||
virtual void Del(int Count);
|
||||
|
||||
virtual void Attach(void);
|
||||
virtual void Detach(void);
|
||||
|
||||
// Statistical purposes:
|
||||
virtual std::string Report(void);
|
||||
};
|
||||
|
||||
// --- cStreamdevLiveReceiver reverse inlines ---------------------------------
|
||||
|
||||
inline void cStreamdevLiveReceiver::Activate(bool On)
|
||||
{
|
||||
Dprintf("LiveReceiver->Activate(%d)\n", On);
|
||||
m_Streamer->Activate(On);
|
||||
}
|
||||
|
||||
#endif // VDR_STREAMDEV_LIVESTREAMER_H
|
||||
158
server/server.c
Normal file
158
server/server.c
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* $Id: server.c,v 1.4 2006/11/10 11:52:41 schmirl Exp $
|
||||
*/
|
||||
|
||||
#include "server/server.h"
|
||||
#include "server/componentVTP.h"
|
||||
#include "server/componentHTTP.h"
|
||||
#include "server/setup.h"
|
||||
|
||||
#include <vdr/tools.h>
|
||||
#include <tools/select.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
cSVDRPhosts StreamdevHosts;
|
||||
|
||||
cStreamdevServer *cStreamdevServer::m_Instance = NULL;
|
||||
cList<cServerComponent> cStreamdevServer::m_Servers;
|
||||
cList<cServerConnection> cStreamdevServer::m_Clients;
|
||||
|
||||
cStreamdevServer::cStreamdevServer(void):
|
||||
cThread("streamdev server"),
|
||||
m_Active(false)
|
||||
{
|
||||
Start();
|
||||
}
|
||||
|
||||
cStreamdevServer::~cStreamdevServer()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
void cStreamdevServer::Initialize(void)
|
||||
{
|
||||
if (m_Instance == NULL) {
|
||||
if (StreamdevServerSetup.StartVTPServer) Register(new cComponentVTP);
|
||||
if (StreamdevServerSetup.StartHTTPServer) Register(new cComponentHTTP);
|
||||
|
||||
m_Instance = new cStreamdevServer;
|
||||
}
|
||||
}
|
||||
|
||||
void cStreamdevServer::Destruct(void)
|
||||
{
|
||||
DELETENULL(m_Instance);
|
||||
}
|
||||
|
||||
void cStreamdevServer::Stop(void)
|
||||
{
|
||||
if (m_Active) {
|
||||
m_Active = false;
|
||||
Cancel(3);
|
||||
}
|
||||
}
|
||||
|
||||
void cStreamdevServer::Register(cServerComponent *Server)
|
||||
{
|
||||
m_Servers.Add(Server);
|
||||
}
|
||||
|
||||
void cStreamdevServer::Action(void)
|
||||
{
|
||||
m_Active = true;
|
||||
|
||||
/* Initialize Server components, deleting those that failed */
|
||||
for (cServerComponent *c = m_Servers.First(); c;) {
|
||||
cServerComponent *next = m_Servers.Next(c);
|
||||
if (!c->Initialize())
|
||||
m_Servers.Del(c);
|
||||
c = next;
|
||||
}
|
||||
|
||||
if (m_Servers.Count() == 0) {
|
||||
esyslog("ERROR: no streamdev server activated, exiting");
|
||||
m_Active = false;
|
||||
}
|
||||
|
||||
cTBSelect select;
|
||||
while (m_Active) {
|
||||
select.Clear();
|
||||
|
||||
/* Ask all Server components to register to the selector */
|
||||
for (cServerComponent *c = m_Servers.First(); c; c = m_Servers.Next(c))
|
||||
select.Add(c->Socket(), false);
|
||||
|
||||
/* Ask all Client connections to register to the selector */
|
||||
for (cServerConnection *s = m_Clients.First(); s; s = m_Clients.Next(s))
|
||||
{
|
||||
select.Add(s->Socket(), false);
|
||||
if (s->HasData())
|
||||
select.Add(s->Socket(), true);
|
||||
}
|
||||
|
||||
int result;
|
||||
while ((result = select.Select(100)) < 0 && errno == ETIMEDOUT) {
|
||||
if (!m_Active) break;
|
||||
}
|
||||
|
||||
if (result < 0) {
|
||||
if (m_Active) // no exit was requested while polling
|
||||
esyslog("fatal error, server exiting: %m");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Ask all Server components to act on signalled sockets */
|
||||
for (cServerComponent *c = m_Servers.First(); c; c = m_Servers.Next(c)){
|
||||
if (select.CanRead(c->Socket())) {
|
||||
cServerConnection *client = c->Accept();
|
||||
m_Clients.Add(client);
|
||||
|
||||
if (m_Clients.Count() > StreamdevServerSetup.MaxClients) {
|
||||
esyslog("streamdev: too many clients, rejecting %s:%d",
|
||||
client->RemoteIp().c_str(), client->RemotePort());
|
||||
client->Reject();
|
||||
} else if (!StreamdevHosts.Acceptable(client->RemoteIpAddr())) {
|
||||
esyslog("streamdev: client %s:%d not allowed to connect",
|
||||
client->RemoteIp().c_str(), client->RemotePort());
|
||||
client->Reject();
|
||||
} else
|
||||
client->Welcome();
|
||||
}
|
||||
}
|
||||
|
||||
/* Ask all Client connections to act on signalled sockets */
|
||||
for (cServerConnection *s = m_Clients.First(); s;) {
|
||||
bool result = true;
|
||||
|
||||
if (select.CanWrite(s->Socket()))
|
||||
result = s->Write();
|
||||
|
||||
if (result && select.CanRead(s->Socket()))
|
||||
result = s->Read();
|
||||
|
||||
cServerConnection *next = m_Clients.Next(s);
|
||||
if (!result) {
|
||||
isyslog("streamdev: closing streamdev connection to %s:%d",
|
||||
s->RemoteIp().c_str(), s->RemotePort());
|
||||
s->Close();
|
||||
m_Clients.Del(s);
|
||||
}
|
||||
s = next;
|
||||
}
|
||||
}
|
||||
|
||||
while (m_Clients.Count() > 0) {
|
||||
cServerConnection *s = m_Clients.First();
|
||||
s->Close();
|
||||
m_Clients.Del(s);
|
||||
}
|
||||
|
||||
while (m_Servers.Count() > 0) {
|
||||
cServerComponent *c = m_Servers.First();
|
||||
c->Destruct();
|
||||
m_Servers.Del(c);
|
||||
}
|
||||
|
||||
m_Active = false;
|
||||
}
|
||||
47
server/server.h
Normal file
47
server/server.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* $Id: server.h,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SERVER_H
|
||||
#define VDR_STREAMDEV_SERVER_H
|
||||
|
||||
#include <vdr/thread.h>
|
||||
|
||||
#include "server/component.h"
|
||||
#include "server/connection.h"
|
||||
|
||||
#define STREAMDEVHOSTSPATH (*AddDirectory(cPlugin::ConfigDirectory(), "streamdevhosts.conf"))
|
||||
|
||||
class cStreamdevServer: public cThread {
|
||||
private:
|
||||
bool m_Active;
|
||||
|
||||
static cStreamdevServer *m_Instance;
|
||||
static cList<cServerComponent> m_Servers;
|
||||
static cList<cServerConnection> m_Clients;
|
||||
|
||||
protected:
|
||||
void Stop(void);
|
||||
|
||||
virtual void Action(void);
|
||||
|
||||
static void Register(cServerComponent *Server);
|
||||
|
||||
public:
|
||||
cStreamdevServer(void);
|
||||
virtual ~cStreamdevServer();
|
||||
|
||||
static void Initialize(void);
|
||||
static void Destruct(void);
|
||||
static bool Active(void);
|
||||
};
|
||||
|
||||
inline bool cStreamdevServer::Active(void)
|
||||
{
|
||||
return m_Instance != NULL
|
||||
&& m_Instance->m_Clients.Count() > 0;
|
||||
}
|
||||
|
||||
extern cSVDRPhosts StreamdevHosts;
|
||||
|
||||
#endif // VDR_STREAMDEV_SERVER_H
|
||||
94
server/setup.c
Normal file
94
server/setup.c
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* $Id: setup.c,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#include <vdr/menuitems.h>
|
||||
|
||||
#include "server/setup.h"
|
||||
#include "server/server.h"
|
||||
#include "i18n.h"
|
||||
|
||||
cStreamdevServerSetup StreamdevServerSetup;
|
||||
|
||||
cStreamdevServerSetup::cStreamdevServerSetup(void) {
|
||||
MaxClients = 5;
|
||||
StartVTPServer = true;
|
||||
VTPServerPort = 2004;
|
||||
StartHTTPServer = true;
|
||||
HTTPServerPort = 3000;
|
||||
HTTPStreamType = stPES;
|
||||
SuspendMode = smOffer;
|
||||
AllowSuspend = false;
|
||||
strcpy(VTPBindIP, "0.0.0.0");
|
||||
strcpy(HTTPBindIP, "0.0.0.0");
|
||||
}
|
||||
|
||||
bool cStreamdevServerSetup::SetupParse(const char *Name, const char *Value) {
|
||||
if (strcmp(Name, "MaxClients") == 0) MaxClients = atoi(Value);
|
||||
else if (strcmp(Name, "StartServer") == 0) StartVTPServer = atoi(Value);
|
||||
else if (strcmp(Name, "ServerPort") == 0) VTPServerPort = atoi(Value);
|
||||
else if (strcmp(Name, "VTPBindIP") == 0) strcpy(VTPBindIP, Value);
|
||||
else if (strcmp(Name, "StartHTTPServer") == 0) StartHTTPServer = atoi(Value);
|
||||
else if (strcmp(Name, "HTTPServerPort") == 0) HTTPServerPort = atoi(Value);
|
||||
else if (strcmp(Name, "HTTPStreamType") == 0) HTTPStreamType = atoi(Value);
|
||||
else if (strcmp(Name, "HTTPBindIP") == 0) strcpy(HTTPBindIP, Value);
|
||||
else if (strcmp(Name, "SuspendMode") == 0) SuspendMode = atoi(Value);
|
||||
else if (strcmp(Name, "AllowSuspend") == 0) AllowSuspend = atoi(Value);
|
||||
else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
cStreamdevServerMenuSetupPage::cStreamdevServerMenuSetupPage(void) {
|
||||
m_NewSetup = StreamdevServerSetup;
|
||||
|
||||
AddCategory (tr("Common Settings"));
|
||||
AddRangeEdit(tr("Maximum Number of Clients"), m_NewSetup.MaxClients, 0, 100);
|
||||
AddSuspEdit (tr("Suspend behaviour"), m_NewSetup.SuspendMode);
|
||||
AddBoolEdit (tr("Client may suspend"), m_NewSetup.AllowSuspend);
|
||||
|
||||
AddCategory (tr("VDR-to-VDR Server"));
|
||||
AddBoolEdit (tr("Start VDR-to-VDR Server"), m_NewSetup.StartVTPServer);
|
||||
AddShortEdit(tr("VDR-to-VDR Server Port"), m_NewSetup.VTPServerPort);
|
||||
AddIpEdit (tr("Bind to IP"), m_NewSetup.VTPBindIP);
|
||||
|
||||
AddCategory (tr("HTTP Server"));
|
||||
AddBoolEdit (tr("Start HTTP Server"), m_NewSetup.StartHTTPServer);
|
||||
AddShortEdit(tr("HTTP Server Port"), m_NewSetup.HTTPServerPort);
|
||||
AddTypeEdit (tr("HTTP Streamtype"), m_NewSetup.HTTPStreamType);
|
||||
AddIpEdit (tr("Bind to IP"), m_NewSetup.HTTPBindIP);
|
||||
|
||||
SetCurrent(Get(1));
|
||||
}
|
||||
|
||||
cStreamdevServerMenuSetupPage::~cStreamdevServerMenuSetupPage() {
|
||||
}
|
||||
|
||||
void cStreamdevServerMenuSetupPage::Store(void) {
|
||||
bool restart = false;
|
||||
if (m_NewSetup.StartVTPServer != StreamdevServerSetup.StartVTPServer
|
||||
|| m_NewSetup.VTPServerPort != StreamdevServerSetup.VTPServerPort
|
||||
|| strcmp(m_NewSetup.VTPBindIP, StreamdevServerSetup.VTPBindIP) != 0
|
||||
|| m_NewSetup.StartHTTPServer != StreamdevServerSetup.StartHTTPServer
|
||||
|| m_NewSetup.HTTPServerPort != StreamdevServerSetup.HTTPServerPort
|
||||
|| strcmp(m_NewSetup.HTTPBindIP, StreamdevServerSetup.HTTPBindIP) != 0) {
|
||||
restart = true;
|
||||
cStreamdevServer::Destruct();
|
||||
}
|
||||
|
||||
SetupStore("MaxClients", m_NewSetup.MaxClients);
|
||||
SetupStore("StartServer", m_NewSetup.StartVTPServer);
|
||||
SetupStore("ServerPort", m_NewSetup.VTPServerPort);
|
||||
SetupStore("VTPBindIP", m_NewSetup.VTPBindIP);
|
||||
SetupStore("StartHTTPServer", m_NewSetup.StartHTTPServer);
|
||||
SetupStore("HTTPServerPort", m_NewSetup.HTTPServerPort);
|
||||
SetupStore("HTTPStreamType", m_NewSetup.HTTPStreamType);
|
||||
SetupStore("HTTPBindIP", m_NewSetup.HTTPBindIP);
|
||||
SetupStore("SuspendMode", m_NewSetup.SuspendMode);
|
||||
SetupStore("AllowSuspend", m_NewSetup.AllowSuspend);
|
||||
|
||||
StreamdevServerSetup = m_NewSetup;
|
||||
|
||||
if (restart)
|
||||
cStreamdevServer::Initialize();
|
||||
}
|
||||
|
||||
41
server/setup.h
Normal file
41
server/setup.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* $Id: setup.h,v 1.1.1.1 2004/12/30 22:44:21 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SETUPSERVER_H
|
||||
#define VDR_STREAMDEV_SETUPSERVER_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
struct cStreamdevServerSetup {
|
||||
cStreamdevServerSetup(void);
|
||||
|
||||
bool SetupParse(const char *Name, const char *Value);
|
||||
|
||||
int MaxClients;
|
||||
int StartVTPServer;
|
||||
int VTPServerPort;
|
||||
char VTPBindIP[20];
|
||||
int StartHTTPServer;
|
||||
int HTTPServerPort;
|
||||
int HTTPStreamType;
|
||||
char HTTPBindIP[20];
|
||||
int SuspendMode;
|
||||
int AllowSuspend;
|
||||
};
|
||||
|
||||
extern cStreamdevServerSetup StreamdevServerSetup;
|
||||
|
||||
class cStreamdevServerMenuSetupPage: public cStreamdevMenuSetupPage {
|
||||
private:
|
||||
cStreamdevServerSetup m_NewSetup;
|
||||
|
||||
protected:
|
||||
virtual void Store(void);
|
||||
|
||||
public:
|
||||
cStreamdevServerMenuSetupPage(void);
|
||||
virtual ~cStreamdevServerMenuSetupPage();
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEV_SETUPSERVER_H
|
||||
146
server/streamer.c
Normal file
146
server/streamer.c
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* $Id: streamer.c,v 1.14 2005/05/09 20:22:29 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#include <vdr/ringbuffer.h>
|
||||
#include <vdr/device.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "server/streamer.h"
|
||||
#include "server/suspend.h"
|
||||
#include "server/setup.h"
|
||||
#include "tools/socket.h"
|
||||
#include "tools/select.h"
|
||||
#include "common.h"
|
||||
|
||||
// --- cStreamdevWriter -------------------------------------------------------
|
||||
|
||||
cStreamdevWriter::cStreamdevWriter(cTBSocket *Socket,
|
||||
cStreamdevStreamer *Streamer):
|
||||
cThread("streamdev-writer"),
|
||||
m_Streamer(Streamer),
|
||||
m_Socket(Socket),
|
||||
m_Active(false)
|
||||
{
|
||||
}
|
||||
|
||||
cStreamdevWriter::~cStreamdevWriter()
|
||||
{
|
||||
Dprintf("destructing writer\n");
|
||||
m_Active = false;
|
||||
Cancel(3);
|
||||
}
|
||||
|
||||
void cStreamdevWriter::Action(void)
|
||||
{
|
||||
cTBSelect sel;
|
||||
Dprintf("Writer start\n");
|
||||
int max = 0;
|
||||
uchar *block = NULL;
|
||||
int count, offset = 0;
|
||||
m_Active = true;
|
||||
while (m_Active) {
|
||||
if (block == NULL) {
|
||||
block = m_Streamer->Get(count);
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
if (block != NULL) {
|
||||
sel.Clear();
|
||||
sel.Add(*m_Socket, true);
|
||||
if (sel.Select(500) == -1) {
|
||||
esyslog("ERROR: streamdev-server: couldn't send data: %m");
|
||||
break;
|
||||
}
|
||||
|
||||
if (sel.CanWrite(*m_Socket)) {
|
||||
int written;
|
||||
if ((written = m_Socket->Write(block + offset, count)) == -1) {
|
||||
esyslog("ERROR: streamdev-server: couldn't send data: %m");
|
||||
break;
|
||||
}
|
||||
if (count > max)
|
||||
max = count;
|
||||
|
||||
offset += written;
|
||||
count -= written;
|
||||
if (count == 0) {
|
||||
m_Streamer->Del(offset);
|
||||
block = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_Active = false;
|
||||
Dprintf("Max. Transmit Blocksize was: %d\n", max);
|
||||
}
|
||||
|
||||
// --- cStreamdevStreamer -----------------------------------------------------
|
||||
|
||||
cStreamdevStreamer::cStreamdevStreamer(const char *Name):
|
||||
cThread(Name),
|
||||
m_Active(false),
|
||||
m_Running(false),
|
||||
m_Writer(NULL),
|
||||
m_RingBuffer(new cRingBufferLinear(STREAMERBUFSIZE, TS_SIZE * 2,
|
||||
true, "streamdev-streamer")),
|
||||
m_SendBuffer(new cRingBufferLinear(WRITERBUFSIZE, TS_SIZE * 2))
|
||||
{
|
||||
m_RingBuffer->SetTimeouts(0, 100);
|
||||
m_SendBuffer->SetTimeouts(0, 100);
|
||||
}
|
||||
|
||||
cStreamdevStreamer::~cStreamdevStreamer()
|
||||
{
|
||||
Dprintf("Desctructing streamer\n");
|
||||
delete m_RingBuffer;
|
||||
delete m_SendBuffer;
|
||||
}
|
||||
|
||||
void cStreamdevStreamer::Start(cTBSocket *Socket)
|
||||
{
|
||||
Dprintf("start streamer\n");
|
||||
m_Writer = new cStreamdevWriter(Socket, this);
|
||||
m_Running = true;
|
||||
Attach();
|
||||
}
|
||||
|
||||
void cStreamdevStreamer::Activate(bool On)
|
||||
{
|
||||
if (On && !m_Active) {
|
||||
Dprintf("activate streamer\n");
|
||||
m_Writer->Start();
|
||||
cThread::Start();
|
||||
}
|
||||
}
|
||||
|
||||
void cStreamdevStreamer::Stop(void)
|
||||
{
|
||||
if (m_Active) {
|
||||
Dprintf("stopping streamer\n");
|
||||
m_Active = false;
|
||||
Cancel(3);
|
||||
}
|
||||
if (m_Running) {
|
||||
Detach();
|
||||
m_Running = false;
|
||||
DELETENULL(m_Writer);
|
||||
}
|
||||
}
|
||||
|
||||
void cStreamdevStreamer::Action(void)
|
||||
{
|
||||
m_Active = true;
|
||||
while (m_Active) {
|
||||
int got;
|
||||
uchar *block = m_RingBuffer->Get(got);
|
||||
|
||||
if (block) {
|
||||
int count = Put(block, got);
|
||||
if (count)
|
||||
m_RingBuffer->Del(count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
69
server/streamer.h
Normal file
69
server/streamer.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* $Id: streamer.h,v 1.7 2005/03/12 12:54:19 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_STREAMER_H
|
||||
#define VDR_STREAMDEV_STREAMER_H
|
||||
|
||||
#include <vdr/thread.h>
|
||||
#include <vdr/ringbuffer.h>
|
||||
#include <vdr/tools.h>
|
||||
|
||||
class cTBSocket;
|
||||
class cStreamdevStreamer;
|
||||
|
||||
#define STREAMERBUFSIZE MEGABYTE(4)
|
||||
#define WRITERBUFSIZE KILOBYTE(256)
|
||||
|
||||
// --- cStreamdevWriter -------------------------------------------------------
|
||||
|
||||
class cStreamdevWriter: public cThread {
|
||||
private:
|
||||
cStreamdevStreamer *m_Streamer;
|
||||
cTBSocket *m_Socket;
|
||||
bool m_Active;
|
||||
|
||||
protected:
|
||||
virtual void Action(void);
|
||||
|
||||
public:
|
||||
cStreamdevWriter(cTBSocket *Socket, cStreamdevStreamer *Streamer);
|
||||
virtual ~cStreamdevWriter();
|
||||
};
|
||||
|
||||
// --- cStreamdevStreamer -----------------------------------------------------
|
||||
|
||||
class cStreamdevStreamer: public cThread {
|
||||
private:
|
||||
bool m_Active;
|
||||
bool m_Running;
|
||||
cStreamdevWriter *m_Writer;
|
||||
cRingBufferLinear *m_RingBuffer;
|
||||
cRingBufferLinear *m_SendBuffer;
|
||||
|
||||
protected:
|
||||
virtual void Action(void);
|
||||
|
||||
bool IsRunning(void) const { return m_Running; }
|
||||
|
||||
public:
|
||||
cStreamdevStreamer(const char *Name);
|
||||
virtual ~cStreamdevStreamer();
|
||||
|
||||
virtual void Start(cTBSocket *Socket);
|
||||
virtual void Stop(void);
|
||||
|
||||
void Activate(bool On);
|
||||
int Receive(uchar *Data, int Length) { return m_RingBuffer->Put(Data, Length); }
|
||||
void ReportOverflow(int Bytes) { m_RingBuffer->ReportOverflow(Bytes); }
|
||||
|
||||
virtual int Put(const uchar *Data, int Count) { return m_SendBuffer->Put(Data, Count); }
|
||||
virtual uchar *Get(int &Count) { return m_SendBuffer->Get(Count); }
|
||||
virtual void Del(int Count) { m_SendBuffer->Del(Count); }
|
||||
|
||||
virtual void Detach(void) {}
|
||||
virtual void Attach(void) {}
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEV_STREAMER_H
|
||||
|
||||
69
server/suspend.c
Normal file
69
server/suspend.c
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* $Id: suspend.c,v 1.1.1.1 2004/12/30 22:44:21 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#include "server/suspend.h"
|
||||
#include "server/suspend.dat"
|
||||
#include "common.h"
|
||||
|
||||
cSuspendLive::cSuspendLive(void)
|
||||
#if VDRVERSNUM >= 10300
|
||||
: cThread("Streamdev: server suspend")
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
cSuspendLive::~cSuspendLive() {
|
||||
Detach();
|
||||
}
|
||||
|
||||
void cSuspendLive::Activate(bool On) {
|
||||
Dprintf("Activate cSuspendLive %d\n", On);
|
||||
if (On)
|
||||
Start();
|
||||
else
|
||||
Stop();
|
||||
}
|
||||
|
||||
void cSuspendLive::Stop(void) {
|
||||
if (m_Active) {
|
||||
m_Active = false;
|
||||
Cancel(3);
|
||||
}
|
||||
}
|
||||
|
||||
void cSuspendLive::Action(void) {
|
||||
#if VDRVERSNUM < 10300
|
||||
isyslog("Streamdev: Suspend Live thread started (pid = %d)", getpid());
|
||||
#endif
|
||||
|
||||
m_Active = true;
|
||||
while (m_Active) {
|
||||
DeviceStillPicture(suspend_mpg, sizeof(suspend_mpg));
|
||||
usleep(100000);
|
||||
}
|
||||
|
||||
#if VDRVERSNUM < 10300
|
||||
isyslog("Streamdev: Suspend Live thread stopped");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool cSuspendCtl::m_Active = false;
|
||||
|
||||
cSuspendCtl::cSuspendCtl(void):
|
||||
cControl(m_Suspend = new cSuspendLive) {
|
||||
m_Active = true;
|
||||
}
|
||||
|
||||
cSuspendCtl::~cSuspendCtl() {
|
||||
m_Active = false;
|
||||
DELETENULL(m_Suspend);
|
||||
}
|
||||
|
||||
eOSState cSuspendCtl::ProcessKey(eKeys Key) {
|
||||
if (!m_Suspend->IsActive() || Key == kBack) {
|
||||
DELETENULL(m_Suspend);
|
||||
return osEnd;
|
||||
}
|
||||
return osContinue;
|
||||
}
|
||||
1206
server/suspend.dat
Normal file
1206
server/suspend.dat
Normal file
File diff suppressed because it is too large
Load Diff
41
server/suspend.h
Normal file
41
server/suspend.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* $Id: suspend.h,v 1.1.1.1 2004/12/30 22:44:26 lordjaxom Exp $
|
||||
*/
|
||||
|
||||
#ifndef VDR_STREAMDEV_SUSPEND_H
|
||||
#define VDR_STREAMDEV_SUSPEND_H
|
||||
|
||||
#include <vdr/player.h>
|
||||
|
||||
class cSuspendLive: public cPlayer, cThread {
|
||||
private:
|
||||
bool m_Active;
|
||||
|
||||
protected:
|
||||
virtual void Activate(bool On);
|
||||
virtual void Action(void);
|
||||
|
||||
void Stop(void);
|
||||
|
||||
public:
|
||||
cSuspendLive(void);
|
||||
virtual ~cSuspendLive();
|
||||
|
||||
bool IsActive(void) const { return m_Active; }
|
||||
};
|
||||
|
||||
class cSuspendCtl: public cControl {
|
||||
private:
|
||||
cSuspendLive *m_Suspend;
|
||||
static bool m_Active;
|
||||
|
||||
public:
|
||||
cSuspendCtl(void);
|
||||
virtual ~cSuspendCtl();
|
||||
virtual void Hide(void) {}
|
||||
virtual eOSState ProcessKey(eKeys Key);
|
||||
|
||||
static bool IsActive(void) { return m_Active; }
|
||||
};
|
||||
|
||||
#endif // VDR_STREAMDEV_SUSPEND_H
|
||||
Reference in New Issue
Block a user