/* * $Id: connectionVTP.c,v 1.4 2005/02/08 17:22:35 lordjaxom Exp $ */ #include "server/connectionVTP.h" #include "server/livestreamer.h" #include "server/suspend.h" #include "setup.h" #include #include #include #include #include /* 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_LiveStreamer = NULL; 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, (std::string)"Unknown Command '" + Cmd + "'"); } bool cConnectionVTP::CmdCAPS(char *Opts) { if (strcasecmp(Opts, "TSPIDS") == 0) return Respond(220, (std::string)"Capability \"" + Opts + "\" accepted"); return Respond(561, (std::string)"Capability \"" + Opts + "\" not known"); } 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, (std::string)"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, (std::string)"Wrong connection id " + (const char*)itoa(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, (std::string)"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, 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, (std::string)"Pid " + (const char*)itoa(pid) + " available") : Respond(560, (std::string)"Pid " + (const char*)itoa(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, (std::string)"Pid " + (const char*)itoa(pid) + " stopped") : Respond(560, (std::string)"Pid " + (const char*)itoa(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, (std::string)"Filter " + (const char*)itoa(pid) + " transferring") : Respond(560, (std::string)"Filter " + (const char*)itoa(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, (std::string)"Filter " + (const char*)itoa(pid) + " stopped") : Respond(560, (std::string)"Filter " + (const char*)itoa(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"); cTimeMs starttime; if (id == siLive) DELETENULL(m_LiveStreamer); Dprintf("ABRT took %ld ms\n", starttime.Elapsed()); 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, (const char*)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, (const char*)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, (const char*)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, (const char*)timer->ToText(true)); EXIT_WRAPPER(); } else Reply(550, "Timer already defined: %d %s", t->Index() + 1, (const char*)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 std::string &Message) { char *buffer; bool result; asprintf(&buffer, "%03d%c%s", Code < 0 ? -Code : Code, Code < 0 ? '-' : ' ', Message.c_str()); result = cServerConnection::Respond(buffer); free(buffer); return result; }