summaryrefslogtreecommitdiff
path: root/plugins/streamdev/streamdev-cvs/server/connectionVTP.c
diff options
context:
space:
mode:
authorkwacker <vdr@w-i-r.com>2010-04-11 13:46:11 +0200
committerkwacker <vdr@w-i-r.com>2010-04-11 13:46:11 +0200
commit9b144d30e0ea8ce900c37b96ba2cbdda14b0ae88 (patch)
tree3a52de029f950dcd9f9856a53fd67abef8519e68 /plugins/streamdev/streamdev-cvs/server/connectionVTP.c
parent9cd931834ecadbf5efefdf484abb966e9248ebbb (diff)
downloadx-vdr-9b144d30e0ea8ce900c37b96ba2cbdda14b0ae88.tar.gz
x-vdr-9b144d30e0ea8ce900c37b96ba2cbdda14b0ae88.tar.bz2
Burn 0.2.0-beta3 und Streamdev mit Paches aktualisiert
Diffstat (limited to 'plugins/streamdev/streamdev-cvs/server/connectionVTP.c')
-rw-r--r--plugins/streamdev/streamdev-cvs/server/connectionVTP.c1768
1 files changed, 1768 insertions, 0 deletions
diff --git a/plugins/streamdev/streamdev-cvs/server/connectionVTP.c b/plugins/streamdev/streamdev-cvs/server/connectionVTP.c
new file mode 100644
index 0000000..7ff60e5
--- /dev/null
+++ b/plugins/streamdev/streamdev-cvs/server/connectionVTP.c
@@ -0,0 +1,1768 @@
+/*
+ * $Id: connectionVTP.c,v 1.27 2010/01/29 12:03:02 schmirl Exp $
+ */
+
+#include "server/connectionVTP.h"
+#include "server/livestreamer.h"
+#include "server/suspend.h"
+#include "setup.h"
+
+#include <vdr/tools.h>
+#include <vdr/videodir.h>
+#include <vdr/menu.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?)
+*/
+
+enum eDumpModeStreamdev { dmsdAll, dmsdPresent, dmsdFollowing, dmsdAtTime, dmsdFromToTime };
+
+// --- cLSTEHandler -----------------------------------------------------------
+
+class cLSTEHandler
+{
+private:
+#if defined(USE_PARENTALRATING) || defined(PARENTALRATINGCONTENTVERSNUM)
+ enum eStates { Channel, Event, Title, Subtitle, Description, Vps, Content,
+ EndEvent, EndChannel, EndEPG };
+#elif APIVERSNUM >= 10711
+ enum eStates { Channel, Event, Title, Subtitle, Description, Vps, Content, Rating,
+ EndEvent, EndChannel, EndEPG };
+#else
+ enum eStates { Channel, Event, Title, Subtitle, Description, Vps,
+ EndEvent, EndChannel, EndEPG };
+#endif /* PARENTALRATING */
+ cConnectionVTP *m_Client;
+ cSchedulesLock *m_SchedulesLock;
+ const cSchedules *m_Schedules;
+ const cSchedule *m_Schedule;
+ const cEvent *m_Event;
+ int m_Errno;
+ cString m_Error;
+ eStates m_State;
+ bool m_Traverse;
+ time_t m_ToTime;
+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_State(Channel),
+ m_Traverse(false),
+ m_ToTime(0)
+{
+ eDumpModeStreamdev dumpmode = dmsdAll;
+ time_t attime = 0;
+ time_t fromtime = 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 == dmsdAll) {
+ if (strcasecmp(p, "NOW") == 0)
+ dumpmode = dmsdPresent;
+ else if (strcasecmp(p, "NEXT") == 0)
+ dumpmode = dmsdFollowing;
+ else if (strcasecmp(p, "AT") == 0) {
+ dumpmode = dmsdAtTime;
+ if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
+ if (isnumber(p))
+ attime = strtol(p, NULL, 10);
+ else {
+ m_Errno = 501;
+ m_Error = "Invalid time";
+ break;
+ }
+ } else {
+ m_Errno = 501;
+ m_Error = "Missing time";
+ break;
+ }
+ }
+ else if (strcasecmp(p, "FROM") == 0) {
+ dumpmode = dmsdFromToTime;
+ if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
+ if (isnumber(p))
+ fromtime = strtol(p, NULL, 10);
+ else {
+ m_Errno = 501;
+ m_Error = "Invalid time";
+ break;
+ }
+ if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
+ if (strcasecmp(p, "TO") == 0) {
+ if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
+ if (isnumber(p))
+ m_ToTime = strtol(p, NULL, 10);
+ else {
+ m_Errno = 501;
+ m_Error = "Invalid time";
+ break;
+ }
+ } else {
+ m_Errno = 501;
+ m_Error = "Missing time";
+ break;
+ }
+ }
+ }
+ } else {
+ m_Errno = 501;
+ m_Error = "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 = "No schedule found";
+ break;
+ }
+ } else {
+ m_Errno = 550;
+ m_Error = cString::sprintf("Channel \"%s\" not defined", p);
+ break;
+ }
+ } else {
+ m_Errno = 501;
+ m_Error = cString::sprintf("Unknown option: \"%s\"", p);
+ break;
+ }
+ p = strtok_r(NULL, delim, &strtok_next);
+ }
+ } else if (m_Schedules == NULL) {
+ m_Errno = 451;
+ m_Error = "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 dmsdAll: m_Event = m_Schedule->Events()->First();
+ m_Traverse = true;
+ break;
+ case dmsdPresent: m_Event = m_Schedule->GetPresentEvent();
+ break;
+ case dmsdFollowing: m_Event = m_Schedule->GetFollowingEvent();
+ break;
+ case dmsdAtTime: m_Event = m_Schedule->GetEventAround(attime);
+ break;
+ case dmsdFromToTime:
+ if (m_Schedule->Events()->Count() <= 1) {
+ m_Event = m_Schedule->Events()->First();
+ break;
+ }
+ if (fromtime < m_Schedule->Events()->First()->StartTime()) {
+ fromtime = m_Schedule->Events()->First()->StartTime();
+ }
+ if (m_ToTime > m_Schedule->Events()->Last()->EndTime()) {
+ m_ToTime = m_Schedule->Events()->Last()->EndTime();
+ }
+ m_Event = m_Schedule->GetEventAround(fromtime);
+ m_Traverse = true;
+ break;
+ }
+ }
+ }
+}
+
+cLSTEHandler::~cLSTEHandler()
+{
+ delete m_SchedulesLock;
+}
+
+bool cLSTEHandler::Next(bool &Last)
+{
+ if (*m_Error != NULL) {
+ Last = true;
+ cString str(m_Error);
+ m_Error = NULL;
+ return m_Client->Respond(m_Errno, "%s", *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;
+#ifdef __FreeBSD__
+ return m_Client->Respond(-215, "E %u %d %d %X", m_Event->EventID(),
+#else
+ return m_Client->Respond(-215, "E %u %ld %d %X", m_Event->EventID(),
+#endif
+ 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:
+#if defined(USE_PARENTALRATING) || defined(PARENTALRATINGCONTENTVERSNUM) || APIVERSNUM >= 10711
+ m_State = Content;
+#else
+ m_State = EndEvent;
+#endif /* PARENTALRATING */
+ if (m_Event->Vps())
+#ifdef __FreeBSD__
+ return m_Client->Respond(-215, "V %d", m_Event->Vps());
+#else
+ return m_Client->Respond(-215, "V %ld", m_Event->Vps());
+#endif
+ else
+ return Next(Last);
+ break;
+
+#if defined(USE_PARENTALRATING) || defined(PARENTALRATINGCONTENTVERSNUM)
+ case Content:
+ m_State = EndEvent;
+ if (!isempty(m_Event->GetContentsString())) {
+ char *copy = strdup(m_Event->GetContentsString());
+ cString cpy(copy, true);
+ strreplace(copy, '\n', '|');
+ return m_Client->Respond(-215, "G %i %i %s", m_Event->Contents() & 0xF0, m_Event->Contents() & 0x0F, copy);
+ } else
+ return Next(Last);
+ break;
+#elif APIVERSNUM >= 10711
+ case Content:
+ m_State = Rating;
+ if (!isempty(m_Event->ContentToString(m_Event->Contents()))) {
+ char *copy = strdup(m_Event->ContentToString(m_Event->Contents()));
+ cString cpy(copy, true);
+ strreplace(copy, '\n', '|');
+ return m_Client->Respond(-215, "G %i %i %s", m_Event->Contents() & 0xF0, m_Event->Contents() & 0x0F, copy);
+ } else
+ return Next(Last);
+ break;
+
+ case Rating:
+ m_State = EndEvent;
+ if (m_Event->ParentalRating())
+ return m_Client->Respond(-215, "R %d", m_Event->ParentalRating());
+ else
+ return Next(Last);
+ break;
+#endif
+
+ case EndEvent:
+ if (m_Traverse) {
+ m_Event = m_Schedule->Events()->Next(m_Event);
+ if ((m_Event != NULL) && (m_ToTime != 0) && (m_Event->StartTime() > m_ToTime)) {
+ m_Event = NULL;
+ }
+ }
+ 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;
+ cString 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_Traverse(false)
+{
+ if (!Channels.Lock(false, 500)) {
+ m_Errno = 451;
+ m_Error = "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;
+ m_Error = cString::sprintf("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;
+ m_Error = cString::sprintf("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 = "No channels defined";
+ }
+}
+
+cLSTCHandler::~cLSTCHandler()
+{
+ Channels.Unlock();
+ if (m_Option != NULL)
+ free(m_Option);
+}
+
+bool cLSTCHandler::Next(bool &Last)
+{
+ if (*m_Error != NULL) {
+ Last = true;
+ cString str(m_Error);
+ m_Error = NULL;
+ return m_Client->Respond(m_Errno, "%s", *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;
+ m_Error = cString::sprintf("Channel \"%d\" not found", i);
+ }
+ }
+
+ if (i < Channels.MaxNumber() + 1)
+ 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;
+ cString 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_Traverse(false)
+{
+ if (*Option) {
+ if (isnumber(Option)) {
+ m_Timer = Timers.Get(strtol(Option, NULL, 10) - 1);
+ if (m_Timer == NULL) {
+ m_Errno = 501;
+ m_Error = cString::sprintf("Timer \"%s\" not defined", Option);
+ }
+ } else {
+ m_Errno = 501;
+ m_Error = cString::sprintf("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;
+ m_Error = cString::sprintf("Timer \"%d\" not found", m_Index + 1);
+ }
+ } else {
+ m_Errno = 550;
+ m_Error = "No timers defined";
+ }
+}
+
+cLSTTHandler::~cLSTTHandler()
+{
+}
+
+bool cLSTTHandler::Next(bool &Last)
+{
+ if (*m_Error != NULL) {
+ Last = true;
+ cString str(m_Error);
+ m_Error = NULL;
+ return m_Client->Respond(m_Errno, "%s", *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;
+ m_Error = cString::sprintf("Timer \"%d\" not found", m_Index + 1);
+ }
+ }
+ return result;
+}
+
+// --- cLSTRHandler -----------------------------------------------------------
+
+class cLSTRHandler
+{
+private:
+ enum eStates { Recording, Event, Title, Subtitle, Description, Components, Vps,
+ EndRecording };
+ cConnectionVTP *m_Client;
+ cRecording *m_Recording;
+ const cEvent *m_Event;
+ int m_Index;
+ int m_Errno;
+ cString m_Error;
+ bool m_Traverse;
+ bool m_Info;
+ eStates m_State;
+ int m_CurrentComponent;
+public:
+ cLSTRHandler(cConnectionVTP *Client, const char *Option);
+ ~cLSTRHandler();
+ bool Next(bool &Last);
+};
+
+cLSTRHandler::cLSTRHandler(cConnectionVTP *Client, const char *Option):
+ m_Client(Client),
+ m_Recording(NULL),
+ m_Event(NULL),
+ m_Index(0),
+ m_Errno(0),
+ m_Traverse(false),
+ m_Info(false),
+ m_State(Recording),
+ m_CurrentComponent(0)
+{
+ if (*Option) {
+ if (isnumber(Option)) {
+ m_Recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
+#if defined(USE_STREAMDEVEXT) || APIVERSNUM >= 10705
+ m_Event = m_Recording->Info()->GetEvent();
+#endif
+ m_Info = true;
+ if (m_Recording == NULL) {
+ m_Errno = 501;
+ m_Error = cString::sprintf("Recording \"%s\" not found", Option);
+ }
+ }
+ else {
+ m_Errno = 501;
+ m_Error = cString::sprintf("Error in Recording number \"%s\"", Option);
+ }
+ }
+ else if (Recordings.Count()) {
+ m_Traverse = true;
+ m_Index = 0;
+ m_Recording = Recordings.Get(m_Index);
+ if (m_Recording == NULL) {
+ m_Errno = 501;
+ m_Error = cString::sprintf("Recording \"%d\" not found", m_Index + 1);
+ }
+ }
+ else {
+ m_Errno = 550;
+ m_Error = "No recordings available";
+ }
+}
+
+cLSTRHandler::~cLSTRHandler()
+{
+}
+
+bool cLSTRHandler::Next(bool &Last)
+{
+ if (*m_Error != NULL) {
+ Last = true;
+ cString str(m_Error);
+ m_Error = NULL;
+ return m_Client->Respond(m_Errno, "%s", *str);
+ }
+
+ if (m_Info) {
+ Last = false;
+ switch (m_State) {
+ case Recording:
+ if (m_Recording != NULL) {
+ m_State = Event;
+ return m_Client->Respond(-215, "C %s%s%s",
+ *m_Recording->Info()->ChannelID().ToString(),
+ m_Recording->Info()->ChannelName() ? " " : "",
+ m_Recording->Info()->ChannelName() ? m_Recording->Info()->ChannelName() : "");
+ }
+ else {
+ m_State = EndRecording;
+ return Next(Last);
+ }
+ break;
+
+ case Event:
+ m_State = Title;
+ if (m_Event != NULL) {
+ return m_Client->Respond(-215, "E %u %ld %d %X %X", (unsigned int) m_Event->EventID(),
+ m_Event->StartTime(), m_Event->Duration(),
+ m_Event->TableID(), m_Event->Version());
+ }
+ return Next(Last);
+
+ case Title:
+ m_State = Subtitle;
+ return m_Client->Respond(-215, "T %s", m_Recording->Info()->Title());
+
+ case Subtitle:
+ m_State = Description;
+ if (!isempty(m_Recording->Info()->ShortText())) {
+ return m_Client->Respond(-215, "S %s", m_Recording->Info()->ShortText());
+ }
+ return Next(Last);
+
+ case Description:
+ m_State = Components;
+ if (!isempty(m_Recording->Info()->Description())) {
+ m_State = Components;
+ char *copy = strdup(m_Recording->Info()->Description());
+ cString cpy(copy, true);
+ strreplace(copy, '\n', '|');
+ return m_Client->Respond(-215, "D %s", copy);
+ }
+ return Next(Last);
+
+ case Components:
+ if (m_Recording->Info()->Components()) {
+ if (m_CurrentComponent < m_Recording->Info()->Components()->NumComponents()) {
+ tComponent *p = m_Recording->Info()->Components()->Component(m_CurrentComponent);
+ m_CurrentComponent++;
+ if (!Setup.UseDolbyDigital && p->stream == 0x02 && p->type == 0x05)
+ return Next(Last);
+
+ return m_Client->Respond(-215, "X %s", *p->ToString());
+ }
+ }
+ m_State = Vps;
+ return Next(Last);
+
+ case Vps:
+ m_State = EndRecording;
+ if (m_Event != NULL) {
+ if (m_Event->Vps()) {
+ return m_Client->Respond(-215, "V %ld", m_Event->Vps());
+ }
+ }
+ return Next(Last);
+
+ case EndRecording:
+ Last = true;
+ return m_Client->Respond(215, "End of recording information");
+ }
+ }
+ else {
+ bool result;
+ Last = !m_Traverse || m_Index >= Recordings.Count() - 1;
+ result = m_Client->Respond(Last ? 250 : -250, "%d %s", m_Recording->Index() + 1, m_Recording->Title(' ', true));
+
+ if (m_Traverse && !Last) {
+ m_Recording = Recordings.Get(++m_Index);
+ if (m_Recording == NULL) {
+ m_Errno = 501;
+ m_Error = cString::sprintf("Recording \"%d\" not found", m_Index + 1);
+ }
+ }
+ return result;
+ }
+ return false;
+}
+
+// --- cConnectionVTP ---------------------------------------------------------
+
+cConnectionVTP::cConnectionVTP(void):
+ cServerConnection("VTP"),
+ m_LiveSocket(NULL),
+ m_LiveStreamer(NULL),
+ m_FilterSocket(NULL),
+ m_FilterStreamer(NULL),
+ m_RecSocket(NULL),
+ m_DataSocket(NULL),
+ m_LastCommand(NULL),
+ m_StreamType(stTSPIDS),
+ m_FiltersSupport(false),
+ m_RecPlayer(NULL),
+ m_LSTEHandler(NULL),
+ m_LSTCHandler(NULL),
+ m_LSTTHandler(NULL),
+ m_LSTRHandler(NULL)
+{
+}
+
+cConnectionVTP::~cConnectionVTP()
+{
+ if (m_LastCommand != NULL)
+ free(m_LastCommand);
+ delete m_LiveStreamer;
+ delete m_LiveSocket;
+ delete m_RecSocket;
+ delete m_FilterStreamer;
+ delete m_FilterSocket;
+ delete m_DataSocket;
+ delete m_LSTTHandler;
+ delete m_LSTCHandler;
+ delete m_LSTEHandler;
+ delete m_LSTRHandler;
+ delete m_RecPlayer;
+}
+
+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, "READ") == 0) return CmdREAD(param);
+ else if (strcasecmp(Cmd, "TUNE") == 0) return CmdTUNE(param);
+ else if (strcasecmp(Cmd, "PLAY") == 0) return CmdPLAY(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, "STAT") == 0) return CmdSTAT(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 if (strcasecmp(Cmd, "NEXT") == 0) return CmdNEXT(param);
+ else if (strcasecmp(Cmd, "NEWC") == 0) return CmdNEWC(param);
+ else if (strcasecmp(Cmd, "MODC") == 0) return CmdMODC(param);
+ else if (strcasecmp(Cmd, "MOVC") == 0) return CmdMOVC(param);
+ else if (strcasecmp(Cmd, "DELC") == 0) return CmdDELC(param);
+ else if (strcasecmp(Cmd, "DELR") == 0) return CmdDELR(param);
+ else if (strcasecmp(Cmd, "RENR") == 0) return CmdRENR(param);
+ else
+ return Respond(500, "Unknown Command \"%s\"", Cmd);
+}
+
+bool cConnectionVTP::CmdCAPS(char *Opts)
+{
+ if (strcasecmp(Opts, "TS") == 0) {
+ m_StreamType = stTS;
+ return Respond(220, "Capability \"%s\" accepted", Opts);
+ }
+
+ if (strcasecmp(Opts, "TSPIDS") == 0) {
+ m_StreamType = stTSPIDS;
+ return Respond(220, "Capability \"%s\" accepted", Opts);
+ }
+
+ if (strcasecmp(Opts, "PS") == 0) {
+ m_StreamType = stPS;
+ return Respond(220, "Capability \"%s\" accepted", Opts);
+ }
+
+ if (strcasecmp(Opts, "PES") == 0) {
+ m_StreamType = stPES;
+ return Respond(220, "Capability \"%s\" accepted", Opts);
+ }
+
+ if (strcasecmp(Opts, "EXTERN") == 0) {
+ m_StreamType = stExtern;
+ return Respond(220, "Capability \"%s\" accepted", Opts);
+ }
+
+ //
+ // Deliver section filters data in separate, channel-independent data stream
+ //
+ if (strcasecmp(Opts, "FILTERS") == 0) {
+ m_FiltersSupport = true;
+ 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 >= si_Count)
+ 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);
+
+ switch (id) {
+ case siLiveFilter:
+ m_FiltersSupport = true;
+ 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");
+ break;
+
+ case siLive:
+ 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");
+ break;
+
+ case siReplay:
+ delete m_RecSocket;
+
+ m_RecSocket = new cTBSocket(SOCK_STREAM);
+ if (!m_RecSocket->Connect(dataip, dataport)) {
+ esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s",
+ dataip, dataport, strerror(errno));
+ DELETENULL(m_RecSocket);
+ return Respond(551, "Couldn't open data connection");
+ }
+
+ if (!m_RecSocket->SetDSCP())
+ LOG_ERROR_STR("unable to set DSCP sockopt");
+
+ return Respond(220, "Port command ok, data connection opened");
+ break;
+
+ case siDataRespond:
+ delete m_DataSocket;
+
+ m_DataSocket = new cTBSocket(SOCK_STREAM);
+ if (!m_DataSocket->Connect(dataip, dataport)) {
+ esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s",
+ dataip, dataport, strerror(errno));
+ DELETENULL(m_DataSocket);
+ return Respond(551, "Couldn't open data connection");
+ }
+ return Respond(220, "Port command ok, data connection opened");
+ break;
+
+ default:
+ return Respond(501, "No handler for id %u", id);
+ }
+}
+
+bool cConnectionVTP::CmdREAD(char *Opts)
+{
+ if (*Opts) {
+ char *tail;
+ uint64_t position = strtoll(Opts, &tail, 10);
+ if (tail && tail != Opts) {
+ tail = skipspace(tail);
+ if (tail && tail != Opts) {
+ int size = strtol(tail, NULL, 10);
+ uint8_t* data = (uint8_t*)malloc(size+4);
+ unsigned long count_readed = m_RecPlayer->getBlock(data, position, size);
+ unsigned long count_written = m_RecSocket->SysWrite(data, count_readed);
+
+ free(data);
+ return Respond(220, "%lu Bytes submitted", count_written);
+ }
+ else {
+ return Respond(501, "Missing position");
+ }
+ }
+ else {
+ return Respond(501, "Missing size");
+ }
+ }
+ else {
+ return Respond(501, "Missing position");
+ }
+}
+
+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_StreamType);
+ m_LiveStreamer->SetDevice(dev);
+ if(m_LiveSocket)
+ m_LiveStreamer->Start(m_LiveSocket);
+
+ if(m_FiltersSupport) {
+ if(!m_FilterStreamer)
+ m_FilterStreamer = new cStreamdevFilterStreamer;
+ m_FilterStreamer->SetDevice(dev);
+ //m_FilterStreamer->SetChannel(chan);
+ }
+
+ return Respond(220, "Channel tuned");
+}
+
+bool cConnectionVTP::CmdPLAY(char *Opts)
+{
+ if (*Opts) {
+ if (isnumber(Opts)) {
+ cRecording *recording = Recordings.Get(strtol(Opts, NULL, 10) - 1);
+ if (recording) {
+ if (m_RecPlayer) {
+ delete m_RecPlayer;
+ }
+ m_RecPlayer = new RecPlayer(recording);
+ return Respond(220, "%llu (Bytes), %u (Frames)", (long long unsigned int) m_RecPlayer->getLengthBytes(), (unsigned int) m_RecPlayer->getLengthFrames());
+ }
+ else {
+ return Respond(550, "Recording \"%s\" not found", Opts);
+ }
+ }
+ else {
+ return Respond(500, "Use: PLAY record");
+ }
+ }
+ else {
+ return Respond(500, "Use: PLAY record");
+ }
+}
+
+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)
+{
+ 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);
+}
+
+bool cConnectionVTP::CmdDELF(char *Opts)
+{
+ 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);
+}
+
+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;
+ case siLiveFilter:
+ DELETENULL(m_FilterStreamer);
+ DELETENULL(m_FilterSocket);
+ break;
+ case siReplay:
+ DELETENULL(m_RecPlayer);
+ DELETENULL(m_RecSocket);
+ break;
+ case siDataRespond:
+ DELETENULL(m_DataSocket);
+ break;
+ 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 = false;
+ bool 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);
+}
+
+bool cConnectionVTP::CmdLSTR(char *Option)
+{
+ return CmdLSTX(m_LSTRHandler, 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::CmdSTAT(const char *Option)
+{
+ INIT_WRAPPER();
+ if (*Option) {
+ if (strcasecmp(Option, "DISK") == 0) {
+ int FreeMB, UsedMB;
+ int Percent = VideoDiskSpace(&FreeMB, &UsedMB);
+ Reply(250, "%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
+ }
+ else if (strcasecmp(Option, "NAME") == 0) {
+ Reply(250, "vdr - The Video Disk Recorder with Streamdev-Server");
+ }
+ else if (strcasecmp(Option, "VERSION") == 0) {
+ Reply(250, "VDR: %s | Streamdev: %s", VDRVERSION, VERSION);
+ }
+ else if (strcasecmp(Option, "RECORDS") == 0) {
+ bool recordings = Recordings.Load();
+ Recordings.Sort();
+ if (recordings) {
+ cRecording *recording = Recordings.Last();
+ Reply(250, "%d", recording->Index() + 1);
+ }
+ else {
+ Reply(250, "0");
+ }
+ }
+ else if (strcasecmp(Option, "CHANNELS") == 0) {
+ Reply(250, "%d", Channels.MaxNumber());
+ }
+ else if (strcasecmp(Option, "TIMERS") == 0) {
+ Reply(250, "%d", Timers.Count());
+ }
+ else if (strcasecmp(Option, "CHARSET") == 0) {
+ Reply(250, "%s", cCharSetConv::SystemCharacterTable());
+ }
+ else if (strcasecmp(Option, "TIME") == 0) {
+ time_t timeNow = time(NULL);
+ struct tm* timeStruct = localtime(&timeNow);
+ int timeOffset = timeStruct->tm_gmtoff;
+
+ Reply(250, "%lu %i", (unsigned long) timeNow, timeOffset);
+ }
+ else
+ Reply(501, "Invalid Option \"%s\"", Option);
+ }
+ else
+ Reply(501, "No option given");
+ EXIT_WRAPPER();
+}
+
+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) {
+ int number = 0;
+ bool force = false;
+ char buf[strlen(Option) + 1];
+ strcpy(buf, Option);
+ const char *delim = " \t";
+ char *strtok_next;
+ char *p = strtok_r(buf, delim, &strtok_next);
+
+ if (isnumber(p)) {
+ number = strtol(p, NULL, 10) - 1;
+ }
+ else if (strcasecmp(p, "FORCE") == 0) {
+ force = true;
+ }
+ if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
+ if (isnumber(p)) {
+ number = strtol(p, NULL, 10) - 1;
+ }
+ else if (strcasecmp(p, "FORCE") == 0) {
+ force = true;
+ }
+ else {
+ Reply(501, "Timer not found or wrong syntax");
+ }
+ }
+
+ cTimer *timer = Timers.Get(number);
+ if (timer) {
+ if (timer->Recording()) {
+ if (force) {
+ timer->Skip();
+ cRecordControls::Process(time(NULL));
+ }
+ else {
+ Reply(550, "Timer \"%i\" is recording", number);
+ EXIT_WRAPPER();
+ }
+ }
+ isyslog("deleting timer %s", *timer->ToDescr());
+ Timers.Del(timer);
+ Timers.SetModified();
+ Reply(250, "Timer \"%i\" deleted", number);
+ } else
+ Reply(501, "Timer \"%i\" not defined", number);
+ } else
+ Reply(501, "Missing timer option");
+ EXIT_WRAPPER();
+}
+
+bool cConnectionVTP::CmdNEXT(const char *Option)
+{
+ INIT_WRAPPER();
+ cTimer *t = Timers.GetNextActiveTimer();
+ if (t) {
+ time_t Start = t->StartTime();
+ int Number = t->Index() + 1;
+ if (!*Option)
+ Reply(250, "%d %s", Number, *TimeToString(Start));
+ else if (strcasecmp(Option, "ABS") == 0)
+ Reply(250, "%d %ld", Number, Start);
+ else if (strcasecmp(Option, "REL") == 0)
+ Reply(250, "%d %ld", Number, Start - time(NULL));
+ else
+ Reply(501, "Unknown option: \"%s\"", Option);
+ }
+ else
+ Reply(550, "No active timers");
+ EXIT_WRAPPER();
+}
+
+bool cConnectionVTP::CmdNEWC(const char *Option)
+{
+ INIT_WRAPPER();
+ if (*Option) {
+ cChannel ch;
+ if (ch.Parse(Option)) {
+ if (Channels.HasUniqueChannelID(&ch)) {
+ cChannel *channel = new cChannel;
+ *channel = ch;
+ Channels.Add(channel);
+ Channels.ReNumber();
+ Channels.SetModified(true);
+ isyslog("new channel %d %s", channel->Number(), *channel->ToText());
+ Reply(250, "%d %s", channel->Number(), *channel->ToText());
+ }
+ else {
+ Reply(501, "Channel settings are not unique");
+ }
+ }
+ else {
+ Reply(501, "Error in channel settings");
+ }
+ }
+ else {
+ Reply(501, "Missing channel settings");
+ }
+ EXIT_WRAPPER();
+}
+
+bool cConnectionVTP::CmdMODC(const char *Option)
+{
+ INIT_WRAPPER();
+ if (*Option) {
+ char *tail;
+ int n = strtol(Option, &tail, 10);
+ if (tail && tail != Option) {
+ tail = skipspace(tail);
+ if (!Channels.BeingEdited()) {
+ cChannel *channel = Channels.GetByNumber(n);
+ if (channel) {
+ cChannel ch;
+ if (ch.Parse(tail)) {
+ if (Channels.HasUniqueChannelID(&ch, channel)) {
+ *channel = ch;
+ Channels.ReNumber();
+ Channels.SetModified(true);
+ isyslog("modifed channel %d %s", channel->Number(), *channel->ToText());
+ Reply(250, "%d %s", channel->Number(), *channel->ToText());
+ }
+ else {
+ Reply(501, "Channel settings are not unique");
+ }
+ }
+ else {
+ Reply(501, "Error in channel settings");
+ }
+ }
+ else {
+ Reply(501, "Channel \"%d\" not defined", n);
+ }
+ }
+ else {
+ Reply(550, "Channels are being edited - try again later");
+ }
+ }
+ else {
+ Reply(501, "Error in channel number");
+ }
+ }
+ else {
+ Reply(501, "Missing channel settings");
+ }
+ EXIT_WRAPPER();
+}
+
+bool cConnectionVTP::CmdMOVC(const char *Option)
+{
+ INIT_WRAPPER();
+ if (*Option) {
+ if (!Channels.BeingEdited() && !Timers.BeingEdited()) {
+ char *tail;
+ int From = strtol(Option, &tail, 10);
+ if (tail && tail != Option) {
+ tail = skipspace(tail);
+ if (tail && tail != Option) {
+ int To = strtol(tail, NULL, 10);
+ int CurrentChannelNr = cDevice::CurrentChannel();
+ cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
+ cChannel *FromChannel = Channels.GetByNumber(From);
+ if (FromChannel) {
+ cChannel *ToChannel = Channels.GetByNumber(To);
+ if (ToChannel) {
+ int FromNumber = FromChannel->Number();
+ int ToNumber = ToChannel->Number();
+ if (FromNumber != ToNumber) {
+ Channels.Move(FromChannel, ToChannel);
+ Channels.ReNumber();
+ Channels.SetModified(true);
+ if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
+ if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) {
+ Channels.SwitchTo(CurrentChannel->Number());
+ }
+ else {
+ cDevice::SetCurrentChannel(CurrentChannel);
+ }
+ }
+ isyslog("channel %d moved to %d", FromNumber, ToNumber);
+ Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
+ }
+ else {
+ Reply(501, "Can't move channel to same postion");
+ }
+ }
+ else {
+ Reply(501, "Channel \"%d\" not defined", To);
+ }
+ }
+ else {
+ Reply(501, "Channel \"%d\" not defined", From);
+ }
+ }
+ else {
+ Reply(501, "Error in channel number");
+ }
+ }
+ else {
+ Reply(501, "Error in channel number");
+ }
+ }
+ else {
+ Reply(550, "Channels or timers are being edited - try again later");
+ }
+ }
+ else {
+ Reply(501, "Missing channel number");
+ }
+ EXIT_WRAPPER();
+}
+
+bool cConnectionVTP::CmdDELC(const char *Option)
+{
+ INIT_WRAPPER();
+ if (*Option) {
+ if (isnumber(Option)) {
+ if (!Channels.BeingEdited()) {
+ cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10));
+ if (channel) {
+ for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) {
+ if (timer->Channel() == channel) {
+ Reply(550, "Channel \"%s\" is in use by timer %d", Option, timer->Index() + 1);
+ return false;
+ }
+ }
+ int CurrentChannelNr = cDevice::CurrentChannel();
+ cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
+ if (CurrentChannel && channel == CurrentChannel) {
+ int n = Channels.GetNextNormal(CurrentChannel->Index());
+ if (n < 0)
+ n = Channels.GetPrevNormal(CurrentChannel->Index());
+ CurrentChannel = Channels.Get(n);
+ CurrentChannelNr = 0; // triggers channel switch below
+ }
+ Channels.Del(channel);
+ Channels.ReNumber();
+ Channels.SetModified(true);
+ isyslog("channel %s deleted", Option);
+ if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
+ if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
+ Channels.SwitchTo(CurrentChannel->Number());
+ else
+ cDevice::SetCurrentChannel(CurrentChannel);
+ }
+ Reply(250, "Channel \"%s\" deleted", Option);
+ }
+ else
+ Reply(501, "Channel \"%s\" not defined", Option);
+ }
+ else
+ Reply(550, "Channels are being edited - try again later");
+ }
+ else
+ Reply(501, "Error in channel number \"%s\"", Option);
+ }
+ else {
+ Reply(501, "Missing channel number");
+ }
+ EXIT_WRAPPER();
+}
+
+bool cConnectionVTP::CmdDELR(const char *Option)
+{
+ INIT_WRAPPER();
+ if (*Option) {
+ if (isnumber(Option)) {
+ cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
+ if (recording) {
+ cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
+ if (!rc) {
+ if (recording->Delete()) {
+ Reply(250, "Recording \"%s\" deleted", Option);
+ ::Recordings.DelByName(recording->FileName());
+ }
+ else
+ Reply(554, "Error while deleting recording!");
+ }
+ else
+ Reply(550, "Recording \"%s\" is in use by timer %d", Option, rc->Timer()->Index() + 1);
+ }
+ 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::CmdRENR(const char *Option)
+{
+ INIT_WRAPPER();
+#if defined(LIEMIKUUTIO)
+ bool recordings = Recordings.Update(true);
+ if (recordings) {
+ if (*Option) {
+ char *tail;
+ int n = strtol(Option, &tail, 10);
+ cRecording *recording = Recordings.Get(n - 1);
+ if (recording && tail && tail != Option) {
+#if APIVERSNUM < 10704
+ int priority = recording->priority;
+ int lifetime = recording->lifetime;
+#endif
+ char *oldName = strdup(recording->Name());
+ tail = skipspace(tail);
+#if APIVERSNUM < 10704
+ if (recording->Rename(tail, &priority, &lifetime)) {
+#else
+ if (recording->Rename(tail)) {
+#endif
+ Reply(250, "Renamed \"%s\" to \"%s\"", oldName, recording->Name());
+ Recordings.ChangeState();
+ Recordings.TouchUpdate();
+ }
+ else {
+ Reply(501, "Renaming \"%s\" to \"%s\" failed", oldName, tail);
+ }
+ free(oldName);
+ }
+ else {
+ Reply(501, "Recording not found or wrong syntax");
+ }
+ }
+ else {
+ Reply(501, "Missing Input settings");
+ }
+ }
+ else {
+ Reply(550, "No recordings available");
+ }
+#else
+ Reply(501, "Rename not supported, please use LIEMIEXT");
+#endif /* LIEMIKUUTIO */
+ EXIT_WRAPPER();
+}
+
+bool cConnectionVTP::Respond(int Code, const char *Message, ...)
+{
+ va_list ap;
+ va_start(ap, Message);
+#if APIVERSNUM < 10515
+ char *buffer;
+ if (vasprintf(&buffer, Message, ap) < 0)
+ buffer = strdup("???");
+ cString str(buffer, true);
+#else
+ cString str = cString::sprintf(Message, ap);
+#endif
+ va_end(ap);
+
+ 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 ? '-' : ' ', *str);
+}