diff options
Diffstat (limited to 'server/connectionVTP.c')
-rw-r--r-- | server/connectionVTP.c | 553 |
1 files changed, 553 insertions, 0 deletions
diff --git a/server/connectionVTP.c b/server/connectionVTP.c new file mode 100644 index 0000000..2177905 --- /dev/null +++ b/server/connectionVTP.c @@ -0,0 +1,553 @@ +/* + * $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); +} + |