vdr-plugin-streamdev/server/connectionVTP.c

554 lines
16 KiB
C

/*
* $Id: connectionVTP.c,v 1.1 2004/12/30 22:44:21 lordjaxom 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 <errno.h>
#include <stdarg.h>
/* VTP Response codes:
220: Service ready
221: Service closing connection
500: Syntax error or Command unrecognized
501: Wrong parameters or missing parameters
550: Action not done
551: Data connection not accepted
560: Channel not available currently
561: Capability not known
562: Pid not available currently
563: Recording not available (currently?)
*/
cConnectionVTP::cConnectionVTP(void): cServerConnection("VTP") {
m_StreamPIDS = false;
m_LiveStreamer = NULL;
m_ClientCaps = stTS;
memset(m_DataSockets, 0, sizeof(cTBSocket*) * si_Count);
}
cConnectionVTP::~cConnectionVTP() {
if (m_LiveStreamer != NULL) delete m_LiveStreamer;
for (int idx = 0; idx < si_Count; ++idx)
if (m_DataSockets[idx] != NULL) delete m_DataSockets[idx];
}
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 *ep;
if ((ep = strchr(Cmd, ' ')) != NULL)
*(ep++) = '\0';
else
ep = Cmd + strlen(Cmd);
if (strcasecmp(Cmd, "CAPS") == 0) return CmdCAPS(ep);
else if (strcasecmp(Cmd, "PROV") == 0) return CmdPROV(ep);
else if (strcasecmp(Cmd, "PORT") == 0) return CmdPORT(ep);
else if (strcasecmp(Cmd, "TUNE") == 0) return CmdTUNE(ep);
else if (strcasecmp(Cmd, "ADDP") == 0) return CmdADDP(ep);
else if (strcasecmp(Cmd, "DELP") == 0) return CmdDELP(ep);
else if (strcasecmp(Cmd, "ADDF") == 0) return CmdADDF(ep);
else if (strcasecmp(Cmd, "DELF") == 0) return CmdDELF(ep);
else if (strcasecmp(Cmd, "ABRT") == 0) return CmdABRT(ep);
else if (strcasecmp(Cmd, "QUIT") == 0) return CmdQUIT(ep);
else if (strcasecmp(Cmd, "SUSP") == 0) return CmdSUSP(ep);
// Commands adopted from SVDRP
else if (strcasecmp(Cmd, "LSTE") == 0) return CmdLSTE(ep);
else if (strcasecmp(Cmd, "LSTR") == 0) return CmdLSTR(ep);
else if (strcasecmp(Cmd, "DELR") == 0) return CmdDELR(ep);
else if (strcasecmp(Cmd, "LSTT") == 0) return CmdLSTT(ep);
else if (strcasecmp(Cmd, "MODT") == 0) return CmdMODT(ep);
else if (strcasecmp(Cmd, "NEWT") == 0) return CmdNEWT(ep);
else if (strcasecmp(Cmd, "DELT") == 0) return CmdDELT(ep);
else
return Respond(500, (cTBString)"Unknown Command '" + Cmd + "'");
}
bool cConnectionVTP::CmdCAPS(char *Opts) {
if (strcasecmp(Opts, "TSPIDS") == 0) m_StreamPIDS = true;
else {
int idx = 0;
while (idx < st_Count && strcasecmp(Opts, StreamTypes[idx]) != 0)
++idx;
if (idx == st_Count)
return Respond(561, (cTBString)"Capability \"" + Opts + "\" not known");
m_ClientCaps = (eStreamType)idx;
}
return Respond(220, (cTBString)"Capability \"" + Opts + "\" accepted");
}
bool cConnectionVTP::CmdPROV(char *Opts) {
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, (cTBString)"Undefined channel \"" + 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 >= si_Count)
return Respond(501, "Wrong connection id " + cTBString::Number(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_DataSockets[id] = new cTBSocket(SOCK_STREAM);
if (!m_DataSockets[id]->Connect(dataip, dataport)) {
esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s",
dataip, dataport, strerror(errno));
DELETENULL(m_DataSockets[id]);
return Respond(551, "Couldn't open data connection");
}
if (id == siLive)
m_LiveStreamer->Start(m_DataSockets[id]);
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, (cTBString)"Undefined channel \"" + 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_ClientCaps, m_StreamPIDS);
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 " + cTBString::Number(pid) + " available")
: Respond(560, "Pid " + cTBString::Number(pid) + " not available");
}
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 " + cTBString::Number(pid) + " stopped")
: Respond(560, "Pid " + cTBString::Number(pid) + " not transferring");
}
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 " + cTBString::Number(pid) + " transferring")
: Respond(560, "Filter " + cTBString::Number(pid) + " not available");
#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 " + cTBString::Number(pid) + " stopped")
: Respond(560, "Filter " + cTBString::Number(pid) + " not transferring");
#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");
time_t st = time_ms();
if (id == siLive)
DELETENULL(m_LiveStreamer);
Dprintf("ABRT took %ld ms\n", time_ms() - st);
DELETENULL(m_DataSockets[id]);
return Respond(220, "Data connection closed");
}
bool cConnectionVTP::CmdQUIT(char *Opts) {
if (!Respond(221, "Video Disk Recorder closing connection"))
return false;
DeferClose();
return true;
}
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 adopted from SVDRP
#define INIT_WRAPPER() bool _res = true
#define Reply(x...) _res &= ReplyWrapper(x)
#define EXIT_WRAPPER() return _res
bool cConnectionVTP::ReplyWrapper(int Code, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
char *buffer;
vasprintf(&buffer, fmt, ap);
int npos;
if (buffer[npos = strlen(buffer)-1] == '\n')
buffer[npos] = '\0';
bool res = Respond(Code, buffer);
free(buffer);
return res;
}
bool cConnectionVTP::CmdLSTE(char *Option) {
#if VDRVERSNUM < 10300
cMutexLock MutexLock;
#else
cSchedulesLock MutexLock;
#endif
INIT_WRAPPER();
/* we need to create a blocking copy of the socket here */
int dupfd = dup(*this);
fcntl(dupfd, F_SETFL, fcntl(dupfd, F_GETFL) & ~O_NONBLOCK);
#if VDRVERSNUM < 10300
const cSchedules *Schedules = cSIProcessor::Schedules(MutexLock);
#else
const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
#endif
if (Schedules) {
FILE *f = fdopen(dupfd, "w");
if (f) {
Schedules->Dump(f, "215-");
fflush(f);
Reply(215, "End of EPG data");
fclose(f);
}
else
Reply(451, "Can't open file connection");
}
else
Reply(451, "Can't get EPG data");
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::CmdLSTT(char *Option) {
INIT_WRAPPER();
if (*Option) {
if (isnumber(Option)) {
cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1);
if (timer)
Reply(250, "%d %s", timer->Index() + 1, timer->ToText(true));
else
Reply(501, "Timer \"%s\" not defined", Option);
}
else
Reply(501, "Error in timer number \"%s\"", Option);
}
else if (Timers.Count()) {
for (int i = 0; i < Timers.Count(); i++) {
cTimer *timer = Timers.Get(i);
if (timer)
Reply(i < Timers.Count() - 1 ? -250 : 250, "%d %s", timer->Index() + 1, timer->ToText(true));
else
Reply(501, "Timer \"%d\" not found", i + 1);
}
}
else
Reply(550, "No timers defined");
EXIT_WRAPPER();
}
bool cConnectionVTP::CmdMODT(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)
#if VDRVERSNUM < 10300
t.SetActive(taActive);
#else
t.SetFlags(tfActive);
#endif
else if (strcasecmp(tail, "OFF") == 0)
#if VDRVERSNUM < 10300
t.SetActive(taInactive);
#else
t.ClrFlags(tfActive);
#endif
else if (!t.Parse(tail)) {
Reply(501, "Error in timer settings");
EXIT_WRAPPER();
}
*timer = t;
Timers.Save();
#if VDRVERSNUM < 10300
isyslog("timer %d modified (%s)", timer->Index() + 1,
timer->Active() ? "active" : "inactive");
#else
isyslog("timer %d modified (%s)", timer->Index() + 1,
timer->HasFlags(tfActive) ? "active" : "inactive");
#endif
Reply(250, "%d %s", timer->Index() + 1, timer->ToText(true));
}
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(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.Save();
isyslog("timer %d added", timer->Index() + 1);
Reply(250, "%d %s", timer->Index() + 1, timer->ToText(true));
EXIT_WRAPPER();
}
else
Reply(550, "Timer already defined: %d %s", t->Index() + 1, t->ToText(true));
}
else
Reply(501, "Error in timer settings");
delete timer;
}
else
Reply(501, "Missing timer settings");
EXIT_WRAPPER();
}
bool cConnectionVTP::CmdDELT(char *Option) {
INIT_WRAPPER();
if (*Option) {
if (isnumber(Option)) {
cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1);
if (timer) {
if (!timer->Recording()) {
Timers.Del(timer);
Timers.Save();
isyslog("timer %s deleted", Option);
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::Respond(int Code, const char *Message) {
cTBString pkt;
if (Code < 0)
pkt.Format("%03d-%s", -Code, Message);
else
pkt.Format("%03d %s", Code, Message);
return cServerConnection::Respond((const char*)pkt);
}