added XBMC support by extending VTP capabilities (thanks to Alwin Esch)

Modified Files:
	CONTRIBUTORS HISTORY Makefile common.h server/connectionVTP.c
	server/connectionVTP.h
This commit is contained in:
schmirl 2009-07-01 10:46:15 +00:00
parent 7b8e396f77
commit 052a94db5a
6 changed files with 774 additions and 97 deletions

View File

@ -34,6 +34,7 @@ Rolf Ahrenberg
for replacing private members by cThread::Running()/Active() for replacing private members by cThread::Running()/Active()
for improving externremux script termination for improving externremux script termination
for fixing PAT repacker version field for fixing PAT repacker version field
for correcting LIMIKUUTIO patch detection
Rantanen Teemu Rantanen Teemu
for providing vdr-incompletesections.diff for providing vdr-incompletesections.diff
@ -116,3 +117,6 @@ Joachim K
Artem Makhutov Artem Makhutov
for suggesting and heavy testing IGMP based multicast streaming for suggesting and heavy testing IGMP based multicast streaming
Alwin Esch
for adding XBMC support by extending VTP capabilities

View File

@ -1,6 +1,7 @@
VDR Plugin 'streamdev' Revision History VDR Plugin 'streamdev' Revision History
--------------------------------------- ---------------------------------------
- added XBMC support by extending VTP capabilities (thanks to Alwin Esch)
- now there's a common baseclass for all remuxers, make use of it - now there's a common baseclass for all remuxers, make use of it
- added cDevice::NumProvidedSystems() which was introduced in VDR 1.7.0 - added cDevice::NumProvidedSystems() which was introduced in VDR 1.7.0
- added namespace to remuxers - added namespace to remuxers

View File

@ -1,7 +1,7 @@
# #
# Makefile for a Video Disk Recorder plugin # Makefile for a Video Disk Recorder plugin
# #
# $Id: Makefile,v 1.18 2009/06/19 06:32:38 schmirl Exp $ # $Id: Makefile,v 1.19 2009/07/01 10:46:15 schmirl Exp $
# The official name of this plugin. # The official name of this plugin.
# This name will be used in the '-P...' option of VDR to load the plugin. # This name will be used in the '-P...' option of VDR to load the plugin.
@ -61,7 +61,7 @@ SERVEROBJS = $(PLUGIN)-server.o \
server/componentVTP.o server/componentHTTP.o server/componentIGMP.o \ server/componentVTP.o server/componentHTTP.o server/componentIGMP.o \
server/connectionVTP.o server/connectionHTTP.o server/connectionIGMP.o \ server/connectionVTP.o server/connectionHTTP.o server/connectionIGMP.o \
server/streamer.o server/livestreamer.o server/livefilter.o \ server/streamer.o server/livestreamer.o server/livefilter.o \
server/suspend.o server/setup.o server/menuHTTP.o \ server/suspend.o server/setup.o server/menuHTTP.o server/recplayer.o \
remux/tsremux.o remux/ts2pes.o remux/ts2ps.o remux/ts2es.o remux/extern.o remux/tsremux.o remux/ts2pes.o remux/ts2ps.o remux/ts2es.o remux/extern.o
ifdef DEBUG ifdef DEBUG

View File

@ -1,5 +1,5 @@
/* /*
* $Id: common.h,v 1.13 2009/06/19 06:32:38 schmirl Exp $ * $Id: common.h,v 1.14 2009/07/01 10:46:16 schmirl Exp $
*/ */
#ifndef VDR_STREAMDEV_COMMON_H #ifndef VDR_STREAMDEV_COMMON_H
@ -72,6 +72,7 @@ enum eSocketId {
siLive, siLive,
siReplay, siReplay,
siLiveFilter, siLiveFilter,
siDataRespond,
si_Count si_Count
}; };

View File

@ -1,5 +1,5 @@
/* /*
* $Id: connectionVTP.c,v 1.20 2009/06/19 06:32:45 schmirl Exp $ * $Id: connectionVTP.c,v 1.21 2009/07/01 10:46:16 schmirl Exp $
*/ */
#include "server/connectionVTP.h" #include "server/connectionVTP.h"
@ -8,6 +8,8 @@
#include "setup.h" #include "setup.h"
#include <vdr/tools.h> #include <vdr/tools.h>
#include <vdr/videodir.h>
#include <vdr/menu.h>
#include <tools/select.h> #include <tools/select.h>
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
@ -28,13 +30,20 @@
563: Recording not available (currently?) 563: Recording not available (currently?)
*/ */
enum eDumpModeStreamdev { dmsdAll, dmsdPresent, dmsdFollowing, dmsdAtTime, dmsdFromToTime };
// --- cLSTEHandler ----------------------------------------------------------- // --- cLSTEHandler -----------------------------------------------------------
class cLSTEHandler class cLSTEHandler
{ {
private: private:
#ifdef USE_PARENTALRATING
enum eStates { Channel, Event, Title, Subtitle, Description, Vps, Content,
EndEvent, EndChannel, EndEPG };
#else
enum eStates { Channel, Event, Title, Subtitle, Description, Vps, enum eStates { Channel, Event, Title, Subtitle, Description, Vps,
EndEvent, EndChannel, EndEPG }; EndEvent, EndChannel, EndEPG };
#endif /* PARENTALRATING */
cConnectionVTP *m_Client; cConnectionVTP *m_Client;
cSchedulesLock *m_SchedulesLock; cSchedulesLock *m_SchedulesLock;
const cSchedules *m_Schedules; const cSchedules *m_Schedules;
@ -44,6 +53,7 @@ private:
char *m_Error; char *m_Error;
eStates m_State; eStates m_State;
bool m_Traverse; bool m_Traverse;
time_t m_ToTime;
public: public:
cLSTEHandler(cConnectionVTP *Client, const char *Option); cLSTEHandler(cConnectionVTP *Client, const char *Option);
~cLSTEHandler(); ~cLSTEHandler();
@ -59,10 +69,12 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
m_Errno(0), m_Errno(0),
m_Error(NULL), m_Error(NULL),
m_State(Channel), m_State(Channel),
m_Traverse(false) m_Traverse(false),
m_ToTime(0)
{ {
eDumpMode dumpmode = dmAll; eDumpModeStreamdev dumpmode = dmsdAll;
time_t attime = 0; time_t attime = 0;
time_t fromtime = 0;
if (m_Schedules != NULL && *Option) { if (m_Schedules != NULL && *Option) {
char buf[strlen(Option) + 1]; char buf[strlen(Option) + 1];
@ -70,13 +82,13 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
const char *delim = " \t"; const char *delim = " \t";
char *strtok_next; char *strtok_next;
char *p = strtok_r(buf, delim, &strtok_next); char *p = strtok_r(buf, delim, &strtok_next);
while (p && dumpmode == dmAll) { while (p && dumpmode == dmsdAll) {
if (strcasecmp(p, "NOW") == 0) if (strcasecmp(p, "NOW") == 0)
dumpmode = dmPresent; dumpmode = dmsdPresent;
else if (strcasecmp(p, "NEXT") == 0) else if (strcasecmp(p, "NEXT") == 0)
dumpmode = dmFollowing; dumpmode = dmsdFollowing;
else if (strcasecmp(p, "AT") == 0) { else if (strcasecmp(p, "AT") == 0) {
dumpmode = dmAtTime; dumpmode = dmsdAtTime;
if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) { if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
if (isnumber(p)) if (isnumber(p))
attime = strtol(p, NULL, 10); attime = strtol(p, NULL, 10);
@ -90,6 +102,39 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
m_Error = strdup("Missing time"); m_Error = strdup("Missing time");
break; 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 = strdup("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 = strdup("Invalid time");
break;
}
} else {
m_Errno = 501;
m_Error = strdup("Missing time");
break;
}
}
}
} else {
m_Errno = 501;
m_Error = strdup("Missing time");
break;
}
} else if (!m_Schedule) { } else if (!m_Schedule) {
cChannel* Channel = NULL; cChannel* Channel = NULL;
if (isnumber(p)) if (isnumber(p))
@ -129,16 +174,29 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
if (m_Schedule != NULL && m_Schedule->Events() != NULL) { if (m_Schedule != NULL && m_Schedule->Events() != NULL) {
switch (dumpmode) { switch (dumpmode) {
case dmAll: m_Event = m_Schedule->Events()->First(); case dmsdAll: m_Event = m_Schedule->Events()->First();
m_Traverse = true; m_Traverse = true;
break; break;
case dmPresent: m_Event = m_Schedule->GetPresentEvent(); case dmsdPresent: m_Event = m_Schedule->GetPresentEvent();
break; break;
case dmFollowing: m_Event = m_Schedule->GetFollowingEvent(); case dmsdFollowing: m_Event = m_Schedule->GetFollowingEvent();
break; break;
case dmAtTime: m_Event = m_Schedule->GetEventAround(attime); 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; break;
} }
} }
} }
@ -227,7 +285,11 @@ bool cLSTEHandler::Next(bool &Last)
break; break;
case Vps: case Vps:
#ifdef USE_PARENTALRATING
m_State = Content;
#else
m_State = EndEvent; m_State = EndEvent;
#endif /* PARENTALRATING */
if (m_Event->Vps()) if (m_Event->Vps())
#ifdef __FreeBSD__ #ifdef __FreeBSD__
return m_Client->Respond(-215, "V %d", m_Event->Vps()); return m_Client->Respond(-215, "V %d", m_Event->Vps());
@ -238,9 +300,26 @@ bool cLSTEHandler::Next(bool &Last)
return Next(Last); return Next(Last);
break; break;
#ifdef USE_PARENTALRATING
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;
#endif
case EndEvent: case EndEvent:
if (m_Traverse) if (m_Traverse) {
m_Event = m_Schedule->Events()->Next(m_Event); m_Event = m_Schedule->Events()->Next(m_Event);
if ((m_Event != NULL) && (m_ToTime != 0) && (m_Event->StartTime() > m_ToTime)) {
m_Event = NULL;
}
}
else else
m_Event = NULL; m_Event = NULL;
@ -377,7 +456,7 @@ bool cLSTCHandler::Next(bool &Last)
} }
} }
if (i < Channels.MaxNumber()) if (i < Channels.MaxNumber() + 1)
Last = false; Last = false;
} }
@ -468,6 +547,181 @@ bool cLSTTHandler::Next(bool &Last)
return result; 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;
char *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_Error(NULL),
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;
asprintf(&m_Error, "Recording \"%s\" not found", Option);
}
}
else {
m_Errno = 501;
asprintf(&m_Error, "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;
asprintf(&m_Error, "Recording \"%d\" not found", m_Index + 1);
}
}
else {
m_Errno = 550;
m_Error = strdup("No recordings available");
}
}
cLSTRHandler::~cLSTRHandler()
{
if (m_Error != NULL)
free(m_Error);
}
bool cLSTRHandler::Next(bool &Last)
{
if (m_Error != NULL) {
Last = true;
cString str(m_Error, true);
m_Error = NULL;
return m_Client->Respond(m_Errno, *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;
asprintf(&m_Error, "Recording \"%d\" not found", m_Index + 1);
}
}
return result;
}
return false;
}
// --- cConnectionVTP --------------------------------------------------------- // --- cConnectionVTP ---------------------------------------------------------
cConnectionVTP::cConnectionVTP(void): cConnectionVTP::cConnectionVTP(void):
@ -476,12 +730,16 @@ cConnectionVTP::cConnectionVTP(void):
m_LiveStreamer(NULL), m_LiveStreamer(NULL),
m_FilterSocket(NULL), m_FilterSocket(NULL),
m_FilterStreamer(NULL), m_FilterStreamer(NULL),
m_RecSocket(NULL),
m_DataSocket(NULL),
m_LastCommand(NULL), m_LastCommand(NULL),
m_StreamType(stTSPIDS), m_StreamType(stTSPIDS),
m_FiltersSupport(false), m_FiltersSupport(false),
m_RecPlayer(NULL),
m_LSTEHandler(NULL), m_LSTEHandler(NULL),
m_LSTCHandler(NULL), m_LSTCHandler(NULL),
m_LSTTHandler(NULL) m_LSTTHandler(NULL),
m_LSTRHandler(NULL)
{ {
} }
@ -491,11 +749,15 @@ cConnectionVTP::~cConnectionVTP()
free(m_LastCommand); free(m_LastCommand);
delete m_LiveStreamer; delete m_LiveStreamer;
delete m_LiveSocket; delete m_LiveSocket;
delete m_RecSocket;
delete m_FilterStreamer; delete m_FilterStreamer;
delete m_FilterSocket; delete m_FilterSocket;
delete m_DataSocket;
delete m_LSTTHandler; delete m_LSTTHandler;
delete m_LSTCHandler; delete m_LSTCHandler;
delete m_LSTEHandler; delete m_LSTEHandler;
delete m_LSTRHandler;
delete m_RecPlayer;
} }
inline bool cConnectionVTP::Abort(void) const inline bool cConnectionVTP::Abort(void) const
@ -548,7 +810,7 @@ bool cConnectionVTP::Command(char *Cmd)
} }
if (strcasecmp(Cmd, "LSTE") == 0) return CmdLSTE(param); if (strcasecmp(Cmd, "LSTE") == 0) return CmdLSTE(param);
//else if (strcasecmp(Cmd, "LSTR") == 0) return CmdLSTR(param); else if (strcasecmp(Cmd, "LSTR") == 0) return CmdLSTR(param);
else if (strcasecmp(Cmd, "LSTT") == 0) return CmdLSTT(param); else if (strcasecmp(Cmd, "LSTT") == 0) return CmdLSTT(param);
else if (strcasecmp(Cmd, "LSTC") == 0) return CmdLSTC(param); else if (strcasecmp(Cmd, "LSTC") == 0) return CmdLSTC(param);
@ -561,7 +823,9 @@ bool cConnectionVTP::Command(char *Cmd)
if (strcasecmp(Cmd, "CAPS") == 0) return CmdCAPS(param); if (strcasecmp(Cmd, "CAPS") == 0) return CmdCAPS(param);
else if (strcasecmp(Cmd, "PROV") == 0) return CmdPROV(param); else if (strcasecmp(Cmd, "PROV") == 0) return CmdPROV(param);
else if (strcasecmp(Cmd, "PORT") == 0) return CmdPORT(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, "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, "ADDP") == 0) return CmdADDP(param);
else if (strcasecmp(Cmd, "DELP") == 0) return CmdDELP(param); else if (strcasecmp(Cmd, "DELP") == 0) return CmdDELP(param);
else if (strcasecmp(Cmd, "ADDF") == 0) return CmdADDF(param); else if (strcasecmp(Cmd, "ADDF") == 0) return CmdADDF(param);
@ -570,10 +834,17 @@ bool cConnectionVTP::Command(char *Cmd)
else if (strcasecmp(Cmd, "QUIT") == 0) return CmdQUIT(); else if (strcasecmp(Cmd, "QUIT") == 0) return CmdQUIT();
else if (strcasecmp(Cmd, "SUSP") == 0) return CmdSUSP(); else if (strcasecmp(Cmd, "SUSP") == 0) return CmdSUSP();
// Commands adopted from SVDRP // Commands adopted from SVDRP
//else if (strcasecmp(Cmd, "DELR") == 0) return CmdDELR(param); else if (strcasecmp(Cmd, "STAT") == 0) return CmdSTAT(param);
else if (strcasecmp(Cmd, "MODT") == 0) return CmdMODT(param); else if (strcasecmp(Cmd, "MODT") == 0) return CmdMODT(param);
else if (strcasecmp(Cmd, "NEWT") == 0) return CmdNEWT(param); else if (strcasecmp(Cmd, "NEWT") == 0) return CmdNEWT(param);
else if (strcasecmp(Cmd, "DELT") == 0) return CmdDELT(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 else
return Respond(500, "Unknown Command \"%s\"", Cmd); return Respond(500, "Unknown Command \"%s\"", Cmd);
} }
@ -646,7 +917,7 @@ bool cConnectionVTP::CmdPORT(char *Opts)
if (ep == Opts || !isspace(*ep)) if (ep == Opts || !isspace(*ep))
return Respond(500, "Use: PORT Id Destination"); return Respond(500, "Use: PORT Id Destination");
if (id != siLive && id != siLiveFilter) if (id >= si_Count)
return Respond(501, "Wrong connection id %d", id); return Respond(501, "Wrong connection id %d", id);
Opts = skipspace(ep); Opts = skipspace(ep);
@ -674,7 +945,8 @@ bool cConnectionVTP::CmdPORT(char *Opts)
isyslog("Streamdev: Setting data connection to %s:%d", dataip, dataport); isyslog("Streamdev: Setting data connection to %s:%d", dataip, dataport);
if (id == siLiveFilter) { switch (id) {
case siLiveFilter:
m_FiltersSupport = true; m_FiltersSupport = true;
if(m_FilterStreamer) if(m_FilterStreamer)
m_FilterStreamer->Stop(); m_FilterStreamer->Stop();
@ -694,8 +966,9 @@ bool cConnectionVTP::CmdPORT(char *Opts)
m_FilterStreamer->Activate(true); m_FilterStreamer->Activate(true);
return Respond(220, "Port command ok, data connection opened"); return Respond(220, "Port command ok, data connection opened");
} break;
case siLive:
if(m_LiveSocket && m_LiveStreamer) if(m_LiveSocket && m_LiveStreamer)
m_LiveStreamer->Stop(); m_LiveStreamer->Stop();
delete m_LiveSocket; delete m_LiveSocket;
@ -714,6 +987,70 @@ bool cConnectionVTP::CmdPORT(char *Opts)
m_LiveStreamer->Start(m_LiveSocket); m_LiveStreamer->Start(m_LiveSocket);
return Respond(220, "Port command ok, data connection opened"); 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) bool cConnectionVTP::CmdTUNE(char *Opts)
@ -747,6 +1084,32 @@ bool cConnectionVTP::CmdTUNE(char *Opts)
return Respond(220, "Channel tuned"); return Respond(220, "Channel tuned");
} }
bool cConnectionVTP::CmdPLAY(char *Opts)
{
Recordings.Update(true);
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) bool cConnectionVTP::CmdADDP(char *Opts)
{ {
int pid; int pid;
@ -842,6 +1205,13 @@ bool cConnectionVTP::CmdABRT(char *Opts)
DELETENULL(m_FilterStreamer); DELETENULL(m_FilterStreamer);
DELETENULL(m_FilterSocket); DELETENULL(m_FilterSocket);
break; break;
case siReplay:
DELETENULL(m_RecPlayer);
DELETENULL(m_RecSocket);
break;
case siDataRespond:
DELETENULL(m_DataSocket);
break;
default: default:
return Respond(501, "Wrong connection id %d", id); return Respond(501, "Wrong connection id %d", id);
break; break;
@ -879,7 +1249,8 @@ bool cConnectionVTP::CmdLSTX(cHandler *&Handler, char *Option)
Handler = new cHandler(this, Option); Handler = new cHandler(this, Option);
} }
bool last, result = false; bool last = false;
bool result = false;
if (Handler != NULL) if (Handler != NULL)
result = Handler->Next(last); result = Handler->Next(last);
else else
@ -905,11 +1276,66 @@ bool cConnectionVTP::CmdLSTT(char *Option)
return CmdLSTX(m_LSTTHandler, Option); return CmdLSTX(m_LSTTHandler, Option);
} }
bool cConnectionVTP::CmdLSTR(char *Option)
{
return CmdLSTX(m_LSTRHandler, Option);
}
// Functions adopted from SVDRP // Functions adopted from SVDRP
#define INIT_WRAPPER() bool _res #define INIT_WRAPPER() bool _res
#define Reply(c,m...) _res = Respond(c,m) #define Reply(c,m...) _res = Respond(c,m)
#define EXIT_WRAPPER() return _res #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) bool cConnectionVTP::CmdMODT(const char *Option)
{ {
INIT_WRAPPER(); INIT_WRAPPER();
@ -990,51 +1416,235 @@ bool cConnectionVTP::CmdDELT(const char *Option)
EXIT_WRAPPER(); EXIT_WRAPPER();
} }
/*bool cConnectionVTP::CmdLSTR(char *Option) { bool cConnectionVTP::CmdNEXT(const char *Option)
{
INIT_WRAPPER(); INIT_WRAPPER();
bool recordings = Recordings.Load(); cTimer *t = Timers.GetNextActiveTimer();
Recordings.Sort(); if (t) {
if (*Option) { time_t Start = t->StartTime();
if (isnumber(Option)) { int Number = t->Index() + 1;
cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1); if (!*Option)
if (recording) { Reply(250, "%d %s", Number, *TimeToString(Start));
if (recording->Summary()) { else if (strcasecmp(Option, "ABS") == 0)
char *summary = strdup(recording->Summary()); Reply(250, "%d %ld", Number, Start);
Reply(250, "%s", strreplace(summary,'\n','|')); else if (strcasecmp(Option, "REL") == 0)
free(summary); Reply(250, "%d %ld", Number, Start - time(NULL));
else
Reply(501, "Unknown option: \"%s\"", Option);
} }
else else
Reply(550, "No summary availabe"); Reply(550, "No active timers");
}
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(); EXIT_WRAPPER();
} }
bool cConnectionVTP::CmdDELR(char *Option) { 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(); INIT_WRAPPER();
if (*Option) { if (*Option) {
if (isnumber(Option)) { if (isnumber(Option)) {
cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1); cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
if (recording) { if (recording) {
if (recording->Delete()) cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
if (!rc) {
if (recording->Delete()) {
Reply(250, "Recording \"%s\" deleted", Option); Reply(250, "Recording \"%s\" deleted", Option);
::Recordings.DelByName(recording->FileName());
}
else else
Reply(554, "Error while deleting recording!"); Reply(554, "Error while deleting recording!");
} }
else
Reply(550, "Recording \"%s\" is in use by timer %d", Option, rc->Timer()->Index() + 1);
}
else else
Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)"); Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)");
} }
@ -1044,7 +1654,55 @@ bool cConnectionVTP::CmdDELR(char *Option) {
else else
Reply(501, "Missing recording number"); Reply(501, "Missing recording number");
EXIT_WRAPPER(); 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, ...) bool cConnectionVTP::Respond(int Code, const char *Message, ...)
{ {

View File

@ -2,6 +2,7 @@
#define VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H #define VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H
#include "server/connection.h" #include "server/connection.h"
#include "server/recplayer.h"
class cTBSocket; class cTBSocket;
class cStreamdevLiveStreamer; class cStreamdevLiveStreamer;
@ -9,6 +10,7 @@ class cStreamdevFilterStreamer;
class cLSTEHandler; class cLSTEHandler;
class cLSTCHandler; class cLSTCHandler;
class cLSTTHandler; class cLSTTHandler;
class cLSTRHandler;
class cConnectionVTP: public cServerConnection { class cConnectionVTP: public cServerConnection {
friend class cLSTEHandler; friend class cLSTEHandler;
@ -21,16 +23,19 @@ private:
cStreamdevLiveStreamer *m_LiveStreamer; cStreamdevLiveStreamer *m_LiveStreamer;
cTBSocket *m_FilterSocket; cTBSocket *m_FilterSocket;
cStreamdevFilterStreamer *m_FilterStreamer; cStreamdevFilterStreamer *m_FilterStreamer;
cTBSocket *m_RecSocket;
cTBSocket *m_DataSocket;
char *m_LastCommand; char *m_LastCommand;
eStreamType m_StreamType; eStreamType m_StreamType;
bool m_FiltersSupport; bool m_FiltersSupport;
RecPlayer *m_RecPlayer;
// Members adopted for SVDRP // Members adopted for SVDRP
cRecordings Recordings;
cLSTEHandler *m_LSTEHandler; cLSTEHandler *m_LSTEHandler;
cLSTCHandler *m_LSTCHandler; cLSTCHandler *m_LSTCHandler;
cLSTTHandler *m_LSTTHandler; cLSTTHandler *m_LSTTHandler;
cLSTRHandler *m_LSTRHandler;
protected: protected:
template<class cHandler> template<class cHandler>
@ -51,7 +56,9 @@ public:
bool CmdCAPS(char *Opts); bool CmdCAPS(char *Opts);
bool CmdPROV(char *Opts); bool CmdPROV(char *Opts);
bool CmdPORT(char *Opts); bool CmdPORT(char *Opts);
bool CmdREAD(char *Opts);
bool CmdTUNE(char *Opts); bool CmdTUNE(char *Opts);
bool CmdPLAY(char *Opts);
bool CmdADDP(char *Opts); bool CmdADDP(char *Opts);
bool CmdDELP(char *Opts); bool CmdDELP(char *Opts);
bool CmdADDF(char *Opts); bool CmdADDF(char *Opts);
@ -64,14 +71,20 @@ public:
bool CmdLSTE(char *Opts); bool CmdLSTE(char *Opts);
bool CmdLSTC(char *Opts); bool CmdLSTC(char *Opts);
bool CmdLSTT(char *Opts); bool CmdLSTT(char *Opts);
bool CmdLSTR(char *Opts);
// Commands adopted from SVDRP // Commands adopted from SVDRP
bool CmdSTAT(const char *Option);
bool CmdMODT(const char *Option); bool CmdMODT(const char *Option);
bool CmdNEWT(const char *Option); bool CmdNEWT(const char *Option);
bool CmdDELT(const char *Option); bool CmdDELT(const char *Option);
bool CmdNEXT(const char *Option);
//bool CmdLSTR(char *Opts); bool CmdNEWC(const char *Option);
//bool CmdDELR(char *Opts); bool CmdMODC(const char *Option);
bool CmdMOVC(const char *Option);
bool CmdDELC(const char *Option);
bool CmdDELR(const char *Option);
bool CmdRENR(const char *Option);
bool Respond(int Code, const char *Message, ...) bool Respond(int Code, const char *Message, ...)
__attribute__ ((format (printf, 3, 4))); __attribute__ ((format (printf, 3, 4)));