vdr-plugin-streamdev/server/connectionVTP.c
schmirl bb1ac54c87 Set DSCP 41 on stream data packets. WMM capable WLAN accesspoints
should understand this and give precedence. For Internet streaming
it could be used by traffic shapers. Suggested by ollo@vdrportal (#237).
Modified Files:
	server/connectionHTTP.c server/connectionVTP.c tools/socket.c
	tools/socket.h
2007-05-09 09:12:42 +00:00

1060 lines
26 KiB
C

/*
* $Id: connectionVTP.c,v 1.14 2007/05/09 09:12:42 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)
{
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_FilterSocket(NULL),
m_FilterStreamer(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_FilterStreamer;
delete m_FilterSocket;
delete m_LSTTHandler;
delete m_LSTCHandler;
delete m_LSTEHandler;
}
inline bool cConnectionVTP::Abort(void) const
{
return m_LiveStreamer && m_LiveStreamer->Abort();
}
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) m_LiveStreamer->Detach();
if (m_FilterStreamer) m_FilterStreamer->Detach();
}
void cConnectionVTP::Attach(void)
{
if (m_LiveStreamer) m_LiveStreamer->Attach();
if (m_FilterStreamer) m_FilterStreamer->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();
else if (strcasecmp(Cmd, "SUSP") == 0) return CmdSUSP();
// 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)
{
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);
}
#if VDRVERSNUM >= 10300
//
// Deliver section filters data in separate, channel-independent data stream
//
if (strcasecmp(Opts, "FILTERS") == 0)
return Respond(220, "Capability \"%s\" accepted", Opts);
#endif
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 VDRVERSNUM >= 10300
if (id != siLive && id != siLiveFilter)
return Respond(501, "Wrong connection id %d", id);
#else
if (id != siLive)
return Respond(501, "Wrong connection id %d", id);
#endif
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);
#if VDRVERSNUM >= 10300
if (id == siLiveFilter) {
if(m_FilterStreamer)
m_FilterStreamer->Stop();
delete m_FilterSocket;
m_FilterSocket = new cTBSocket(SOCK_STREAM);
if (!m_FilterSocket->Connect(dataip, dataport)) {
esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s",
dataip, dataport, strerror(errno));
DELETENULL(m_FilterSocket);
return Respond(551, "Couldn't open data connection");
}
if(!m_FilterStreamer)
m_FilterStreamer = new cStreamdevFilterStreamer;
m_FilterStreamer->Start(m_FilterSocket);
m_FilterStreamer->Activate(true);
return Respond(220, "Port command ok, data connection opened");
}
#endif
if(m_LiveSocket && m_LiveStreamer)
m_LiveStreamer->Stop();
delete m_LiveSocket;
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 (!m_LiveSocket->SetDSCP())
LOG_ERROR_STR("unable to set DSCP sockopt");
if (m_LiveStreamer)
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);
if(m_LiveSocket)
m_LiveStreamer->Start(m_LiveSocket);
#if VDRVERSNUM >= 10300
if(!m_FilterStreamer)
m_FilterStreamer = new cStreamdevFilterStreamer;
m_FilterStreamer->SetDevice(dev);
//m_FilterStreamer->SetChannel(chan);
#endif
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_FilterStreamer == NULL)
return Respond(560, "Can't set filters without a filter 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_FilterStreamer->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_FilterStreamer == 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");
m_FilterStreamer->SetFilter(pid, tid, mask, false);
return Respond(220, "Filter %d stopped", 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 siLive:
DELETENULL(m_LiveStreamer);
DELETENULL(m_LiveSocket);
break;
#if VDRVERSNUM >= 10300
case siLiveFilter:
DELETENULL(m_FilterStreamer);
DELETENULL(m_FilterSocket);
break;
#endif
default:
return Respond(501, "Wrong connection id %d", id);
break;
}
return Respond(220, "Data connection closed");
}
bool cConnectionVTP::CmdQUIT(void)
{
DeferClose();
return Respond(221, "Video Disk Recorder closing connection");
}
bool cConnectionVTP::CmdSUSP(void)
{
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);
}