summaryrefslogtreecommitdiff
path: root/plugins/streamdev
diff options
context:
space:
mode:
authorroot <root@elwms02.(none)>2010-04-06 16:13:08 +0200
committerroot <root@elwms02.(none)>2010-04-06 16:13:08 +0200
commit0e7005fcc7483c01aa102fbea358c5ac65a48d62 (patch)
tree11517ce0d3d2977c6732b3aa583b0008083e0bd3 /plugins/streamdev
downloadx-vdr-0e7005fcc7483c01aa102fbea358c5ac65a48d62.tar.gz
x-vdr-0e7005fcc7483c01aa102fbea358c5ac65a48d62.tar.bz2
hello world
Diffstat (limited to 'plugins/streamdev')
-rwxr-xr-xplugins/streamdev/plugin.sh76
-rw-r--r--plugins/streamdev/streamdev-cvs040409_xbmc-v5-ext69.patch1475
2 files changed, 1551 insertions, 0 deletions
diff --git a/plugins/streamdev/plugin.sh b/plugins/streamdev/plugin.sh
new file mode 100755
index 0000000..81e6278
--- /dev/null
+++ b/plugins/streamdev/plugin.sh
@@ -0,0 +1,76 @@
+#!/bin/sh
+
+# x-vdr (Installations-Skript fuer einen VDR mit Debian als Basis)
+# von Marc Wernecke - www.zulu-entertainment.de
+# 07.04.2009
+#
+# vdr-streamdev
+
+# defaults
+source ./../../x-vdr.conf
+source ./../../setup.conf
+source ./../../functions
+
+WEB="http://www.zulu-entertainment.de/files/vdr-streamdev/vdr-streamdev-0.5.0-pre.tgz"
+VERSION="streamdev-0.5.0-pre"
+LINK="streamdev"
+CVS="0"
+
+[ "$CVS" = "1" ] && VERSION="streamdev-cvs"
+
+VAR=`basename $WEB`
+DIR=`pwd`
+
+# plugin entfernen
+function clean_plugin() {
+ cd $SOURCEDIR/VDR/PLUGINS/src
+ rm -rf $LINK*
+ rm -f $VDRLIBDIR/libvdr-$LINK*
+ log "cleaning $LINK"
+}
+
+# plugin installieren
+function install_plugin() {
+ if [ "$CVS" = "1" ] ; then
+ cd $DIR
+ rm -rf streamdev $VERSION
+ echo "CVS password: [Just press enter]"
+ cvs -d:pserver:anoncvs@vdr-developer.org:/var/cvsroot login
+ cvs -d:pserver:anoncvs@vdr-developer.org:/var/cvsroot co streamdev
+ mv -f streamdev $VERSION
+ cp -R $VERSION $SOURCEDIR/VDR/PLUGINS/src
+ else
+ download_plugin
+ extract_plugin
+ fi
+ cd $SOURCEDIR/VDR/PLUGINS/src
+ rm -f $LINK
+ ln -vfs $VERSION $LINK
+ patch_plugin
+ patch_p1_plugin
+
+ ## plugin specials - start ##
+ [ -d $VDRCONFDIR/plugins/streamdev ] || mkdir -p $VDRCONFDIR/plugins/streamdev
+
+ if [ -f $VDRCONFDIR/plugins/streamdevhosts.conf ]; then
+ mv $VDRCONFDIR/plugins/streamdevhosts.conf $VDRCONFDIR/plugins/streamdev/streamdevhosts.conf
+ elif [ -f $DIR/streamdevhosts.conf ]; then
+ cp $DIR/streamdevhosts.conf $VDRCONFDIR/plugins/streamdev
+ else
+ cp $VDRCONFDIR/svdrphosts.conf $VDRCONFDIR/plugins/streamdev/streamdevhosts.conf
+ fi
+
+ chown -R $VDRUSER:$VDRGROUP $VDRCONFDIR/plugins/streamdev
+ ## plugin specials - ende ##
+}
+
+# plugin commands
+if [ $# \> 0 ]; then
+ cmd=$1
+ cmd_plugin
+else
+ install_plugin
+ log "install-plugin fuer $VERSION ist fertig"
+fi
+
+exit 0
diff --git a/plugins/streamdev/streamdev-cvs040409_xbmc-v5-ext69.patch b/plugins/streamdev/streamdev-cvs040409_xbmc-v5-ext69.patch
new file mode 100644
index 0000000..4a2dd69
--- /dev/null
+++ b/plugins/streamdev/streamdev-cvs040409_xbmc-v5-ext69.patch
@@ -0,0 +1,1475 @@
+diff -NaurwB streamdev-unpatched/common.h streamdev/common.h
+--- streamdev-unpatched/common.h 2009-01-16 12:35:43.000000000 +0100
++++ streamdev/common.h 2009-04-04 22:01:41.000000000 +0200
+@@ -74,9 +74,12 @@
+ siLive,
+ siReplay,
+ siLiveFilter,
++ siDataRespond,
+ si_Count
+ };
+
++#define MAX_RESPONSE_SIZE 1024
++
+ extern const char *VERSION;
+ extern const char *StreamTypes[st_Count];
+ extern const char *SuspendModes[sm_Count];
+diff -NaurwB streamdev-unpatched/Makefile streamdev/Makefile
+--- streamdev-unpatched/Makefile 2009-02-13 11:39:20.000000000 +0100
++++ streamdev/Makefile 2009-04-04 22:01:41.000000000 +0200
+@@ -61,7 +61,7 @@
+ server/componentVTP.o server/componentHTTP.o server/componentIGMP.o \
+ server/connectionVTP.o server/connectionHTTP.o server/connectionIGMP.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/ts2ps.o remux/ts2es.o remux/extern.o
+
+ ifdef DEBUG
+diff -NaurwB streamdev-unpatched/server/connectionVTP.c streamdev/server/connectionVTP.c
+--- streamdev-unpatched/server/connectionVTP.c 2009-01-16 12:35:44.000000000 +0100
++++ streamdev/server/connectionVTP.c 2009-04-04 23:01:48.000000000 +0200
+@@ -8,11 +8,16 @@
+ #include "setup.h"
+
+ #include <vdr/tools.h>
++#include <vdr/videodir.h>
++#include <vdr/menu.h>
++#include <vdr/epg.h>
+ #include <tools/select.h>
+ #include <string.h>
+ #include <ctype.h>
+ #include <errno.h>
+ #include <stdarg.h>
++#include <stdio.h>
++#include <stdlib.h>
+
+ /* VTP Response codes:
+ 220: Service ready
+@@ -28,13 +33,20 @@
+ 563: Recording not available (currently?)
+ */
+
++enum eDumpModeStreamdev { dmsdAll, dmsdPresent, dmsdFollowing, dmsdAtTime, dmsdFromToTime };
++
+ // --- cLSTEHandler -----------------------------------------------------------
+
+ class cLSTEHandler
+ {
+ 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,
+ EndEvent, EndChannel, EndEPG };
++#endif /* PARENTALRATING */
+ cConnectionVTP *m_Client;
+ cSchedulesLock *m_SchedulesLock;
+ const cSchedules *m_Schedules;
+@@ -44,6 +56,7 @@
+ char *m_Error;
+ eStates m_State;
+ bool m_Traverse;
++ time_t m_ToTime;
+ public:
+ cLSTEHandler(cConnectionVTP *Client, const char *Option);
+ ~cLSTEHandler();
+@@ -59,10 +72,12 @@
+ m_Errno(0),
+ m_Error(NULL),
+ m_State(Channel),
+- m_Traverse(false)
++ m_Traverse(false),
++ m_ToTime(0)
+ {
+- eDumpMode dumpmode = dmAll;
++ eDumpModeStreamdev dumpmode = dmsdAll;
+ time_t attime = 0;
++ time_t fromtime = 0;
+
+ if (m_Schedules != NULL && *Option) {
+ char buf[strlen(Option) + 1];
+@@ -70,13 +85,13 @@
+ const char *delim = " \t";
+ char *strtok_next;
+ char *p = strtok_r(buf, delim, &strtok_next);
+- while (p && dumpmode == dmAll) {
++ while (p && dumpmode == dmsdAll) {
+ if (strcasecmp(p, "NOW") == 0)
+- dumpmode = dmPresent;
++ dumpmode = dmsdPresent;
+ else if (strcasecmp(p, "NEXT") == 0)
+- dumpmode = dmFollowing;
++ dumpmode = dmsdFollowing;
+ else if (strcasecmp(p, "AT") == 0) {
+- dumpmode = dmAtTime;
++ dumpmode = dmsdAtTime;
+ if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
+ if (isnumber(p))
+ attime = strtol(p, NULL, 10);
+@@ -90,6 +105,39 @@
+ m_Error = strdup("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 = 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) {
+ cChannel* Channel = NULL;
+ if (isnumber(p))
+@@ -129,16 +177,29 @@
+
+ if (m_Schedule != NULL && m_Schedule->Events() != NULL) {
+ switch (dumpmode) {
+- case dmAll: m_Event = m_Schedule->Events()->First();
++ case dmsdAll: m_Event = m_Schedule->Events()->First();
+ m_Traverse = true;
+ break;
+- case dmPresent: m_Event = m_Schedule->GetPresentEvent();
++ case dmsdPresent: m_Event = m_Schedule->GetPresentEvent();
+ break;
+- case dmFollowing: m_Event = m_Schedule->GetFollowingEvent();
++ case dmsdFollowing: m_Event = m_Schedule->GetFollowingEvent();
+ 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;
+-
+ }
+ }
+ }
+@@ -227,7 +288,11 @@
+ break;
+
+ case Vps:
++#ifdef USE_PARENTALRATING
++ 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());
+@@ -238,9 +303,26 @@
+ return Next(Last);
+ 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:
+- if (m_Traverse)
++ 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;
+
+@@ -476,9 +558,12 @@
+ 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)
+@@ -491,11 +576,14 @@
+ 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_RecPlayer;
+ }
+
+ inline bool cConnectionVTP::Abort(void) const
+@@ -548,7 +636,7 @@
+ }
+
+ 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, "LSTC") == 0) return CmdLSTC(param);
+
+@@ -561,7 +649,9 @@
+ 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);
+@@ -570,10 +660,21 @@
+ else if (strcasecmp(Cmd, "QUIT") == 0) return CmdQUIT();
+ else if (strcasecmp(Cmd, "SUSP") == 0) return CmdSUSP();
+ // Commands adopted from SVDRP
+- //else if (strcasecmp(Cmd, "DELR") == 0) return CmdDELR(param);
++ else if (strcasecmp(Cmd, "DELR") == 0) return CmdDELR(param);
++#if defined(USE_LIEMIKUUTIO) || defined(USE_LIEMIEXT)
++ else if (strcasecmp(Cmd, "RENR") == 0) return CmdRENR(param);
++#endif /* LIEMIKUUTIO */
++ 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, "MARK") == 0) return CmdMARK(param);
++ else if (strcasecmp(Cmd, "FRAM") == 0) return CmdFRAM(param);
+ else
+ return Respond(500, "Unknown Command \"%s\"", Cmd);
+ }
+@@ -648,7 +749,7 @@
+ if (ep == Opts || !isspace(*ep))
+ return Respond(500, "Use: PORT Id Destination");
+
+- if (id != siLive && id != siLiveFilter)
++ if (id != siLive && id != siLiveFilter && id != siReplay && id != siDataRespond)
+ return Respond(501, "Wrong connection id %d", id);
+
+ Opts = skipspace(ep);
+@@ -698,6 +799,7 @@
+ return Respond(220, "Port command ok, data connection opened");
+ }
+
++ if (id == siLive) {
+ if(m_LiveSocket && m_LiveStreamer)
+ m_LiveStreamer->Stop();
+ delete m_LiveSocket;
+@@ -718,6 +820,71 @@
+ return Respond(220, "Port command ok, data connection opened");
+ }
+
++ if (id == 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");
++ }
++
++ if (id == 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");
++ }
++
++ if (!m_DataSocket->SetDSCP())
++ LOG_ERROR_STR("unable to set DSCP sockopt");
++
++ return Respond(220, "Port command ok, data connection opened");
++ }
++ return Respond(551, "Couldn't open data connection");
++}
++
++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;
+@@ -749,6 +916,32 @@
+ 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)
+ {
+ int pid;
+@@ -844,6 +1037,13 @@
+ 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;
+@@ -912,6 +1112,56 @@
+ #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()-1);
++ }
++ 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, "%u %i", 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();
+@@ -973,40 +1223,114 @@
+ {
+ INIT_WRAPPER();
+ if (*Option) {
+- if (isnumber(Option)) {
+- cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1);
++ 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 (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 \"%s\" deleted", Option);
++ Reply(250, "Timer \"%i\" deleted", number);
+ } else
+- Reply(550, "Timer \"%s\" is recording", Option);
++ Reply(501, "Timer \"%i\" not defined", number);
+ } else
+- Reply(501, "Timer \"%s\" not defined", Option);
+- } else
+- Reply(501, "Error in timer number \"%s\"", Option);
+- } else
+- Reply(501, "Missing timer number");
++ 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::CmdLSTR(char *Option) {
++bool cConnectionVTP::CmdLSTR(char *Option)
++{
+ INIT_WRAPPER();
+- bool recordings = Recordings.Load();
+- Recordings.Sort();
++ bool recordings = Recordings.Update(true);
+ 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);
++
++ Reply(-215, "C %s%s%s", *recording->Info()->ChannelID().ToString(), recording->Info()->ChannelName() ? " " : "", recording->Info()->ChannelName() ? recording->Info()->ChannelName() : "");
++#ifdef USE_STREAMDEVEXT
++ const cEvent* event = recording->Info()->GetEvent();
++ Reply(-215, "E %u %ld %d %X", (unsigned int) event->EventID(), event->StartTime(), event->Duration(), event->TableID(), event->Version());
++#endif
++ Reply(-215, "T %s", recording->Info()->Title());
++ if (!isempty(recording->Info()->ShortText())) {
++ Reply(-215, "S %s", recording->Info()->ShortText());
+ }
+- else
+- Reply(550, "No summary availabe");
++ if (!isempty(recording->Info()->Description())) {
++ char *copy = strdup(recording->Info()->Description());
++ cString cpy(copy, true);
++ strreplace(copy, '\n', '|');
++ Reply(-215, "D %s", copy);
++ }
++ if (recording->Info()->Components()) {
++ for (int i = 0; i < recording->Info()->Components()->NumComponents(); i++) {
++ tComponent *p = recording->Info()->Components()->Component(i);
++ if (!Setup.UseDolbyDigital && p->stream == 0x02 && p->type == 0x05)
++ continue;
++ Reply(-215, "X %s", *p->ToString());
++ }
++ }
++#ifdef USE_STREAMDEVEXT
++ if (event->Vps())
++ Reply(-215, "V %ld", event->Vps());
++#endif
++
++ Reply(215, "End of recording information");
+ }
+ else
+ Reply(550, "Recording \"%s\" not found", Option);
+@@ -1016,28 +1340,36 @@
+ }
+ else if (recordings) {
+ cRecording *recording = Recordings.First();
+- while (recording) {
++ do {
+ Reply(recording == Recordings.Last() ? 250 : -250, "%d %s", recording->Index() + 1, recording->Title(' ', true));
+ recording = Recordings.Next(recording);
+- }
++ } while (recording);
+ }
+ else
+ Reply(550, "No recordings available");
+ EXIT_WRAPPER();
+ }
+
+-bool cConnectionVTP::CmdDELR(char *Option) {
++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())
++ 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
+@@ -1046,7 +1378,320 @@
+ else
+ Reply(501, "Missing recording number");
+ EXIT_WRAPPER();
+-}*/
++}
++
++#if defined(USE_LIEMIKUUTIO) || defined(USE_LIEMIEXT)
++bool cConnectionVTP::CmdRENR(char *Option)
++{
++ INIT_WRAPPER();
++ 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");
++ }
++ EXIT_WRAPPER();
++}
++#endif /* LIEMIKUUTIO */
++
++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);
++#ifdef USE_STREAMDEVEXT
++ cStatus::MsgChannelChange(channel, scAdd);
++#endif
++ 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);
++ }
++ }
++#ifdef USE_STREAMDEVEXT
++ cStatus::MsgChannelChange(ToChannel, scMod);
++#endif
++ 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);
++#ifdef USE_STREAMDEVEXT
++ cStatus::MsgChannelChange(NULL, scDel);
++#endif
++ 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::CmdMARK(const char *Option)
++{
++ INIT_WRAPPER();
++ if (*Option) {
++ if (isnumber(Option)) {
++ cMarks Marks;
++ Recordings.Load();
++ cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
++ if (recording) {
++ Marks.Load(recording->FileName());
++ if (Marks.Count()) {
++ const cMark *m = Marks.First();
++ do
++ {
++ Reply(Marks.Next(m) ? -250 : 250, "%i frame position", m->position);
++ m = Marks.Next(m);
++ } while (m);
++ }
++ else {
++ Reply(550, "No cutting marks defined");
++ }
++ }
++ else {
++ Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)");
++ }
++ }
++ else {
++ Reply(501, "Error in channel number \"%s\"", Option);
++ }
++ }
++ else {
++ Reply(501, "Missing recording number");
++ }
++ EXIT_WRAPPER();
++}
++
++bool cConnectionVTP::CmdFRAM(const char *Option)
++{
++ INIT_WRAPPER();
++ if (*Option) {
++ char *tail;
++ int recording = strtol(Option, &tail, 10);
++ if (tail && tail != Option) {
++ tail = skipspace(tail);
++ if (tail && tail != Option) {
++ uint64_t position = strtoll(tail, NULL, 10);
++
++ if (m_RecPlayer) {
++ long retval = m_RecPlayer->frameNumberFromPosition(position);
++ Reply(250,"Frame %lu", retval);
++ }
++ else {
++ Reply(501, "No recording running");
++ }
++ }
++ else {
++ Reply(501, "Error in recording number");
++ }
++ }
++ else {
++ Reply(501, "Error in recording number");
++ }
++ }
++ else {
++ Reply(501, "Missing recording number");
++ }
++ EXIT_WRAPPER();
++}
+
+ bool cConnectionVTP::Respond(int Code, const char *Message, ...)
+ {
+@@ -1066,3 +1711,59 @@
+ Code < 0 ? -Code : Code,
+ Code < 0 ? '-' : ' ', buffer);
+ }
++
++void cConnectionVTP::TimerChange(const cTimer *Timer, eTimerChange Change)
++{
++ if (m_DataSocket) {
++ char buf[MAX_RESPONSE_SIZE];
++ if (Change == tcMod) {
++ snprintf(buf, MAX_RESPONSE_SIZE, "MODT %s\0", Timer ? *Timer->ToText(true) : "-");
++ }
++ if (Change == tcAdd) {
++ snprintf(buf, MAX_RESPONSE_SIZE, "ADDT %s\0", Timer ? *Timer->ToText(true) : "-");
++ }
++ if (Change == tcDel) {
++ snprintf(buf, MAX_RESPONSE_SIZE, "DELT %s\0", Timer ? *Timer->ToText(true) : "-");
++ }
++
++ m_DataSocket->SysWrite(buf, strlen(buf));
++ }
++}
++
++#ifdef USE_STREAMDEVEXT
++void cConnectionVTP::RecordingChange(const cRecording *Recording, eStatusChange Change)
++{
++ if (m_DataSocket) {
++ char buf[MAX_RESPONSE_SIZE];
++ if (Change == scMod) {
++ snprintf(buf, MAX_RESPONSE_SIZE, "MODR\0");
++ }
++ if (Change == scAdd) {
++ snprintf(buf, MAX_RESPONSE_SIZE, "ADDR\0");
++ }
++ if (Change == scDel) {
++ snprintf(buf, MAX_RESPONSE_SIZE, "DELR\0");
++ }
++
++ m_DataSocket->SysWrite(buf, strlen(buf));
++ }
++}
++
++void cConnectionVTP::ChannelChange(const cChannel *Channel, eStatusChange Change)
++{
++ if (m_DataSocket) {
++ char buf[MAX_RESPONSE_SIZE];
++ if (Change == scMod) {
++ snprintf(buf, MAX_RESPONSE_SIZE, "MODC %s\0", Channel ? *Channel->ToText() : "-");
++ }
++ if (Change == scAdd) {
++ snprintf(buf, MAX_RESPONSE_SIZE, "ADDC %s\0", Channel ? *Channel->ToText() : "-");
++ }
++ if (Change == scDel) {
++ snprintf(buf, MAX_RESPONSE_SIZE, "DELC %s\0", Channel ? *Channel->ToText() : "-");
++ }
++
++ m_DataSocket->SysWrite(buf, strlen(buf));
++ }
++}
++#endif /* STREAMDEVEXTENSION */
+diff -NaurwB streamdev-unpatched/server/connectionVTP.h streamdev/server/connectionVTP.h
+--- streamdev-unpatched/server/connectionVTP.h 2008-07-16 08:00:48.000000000 +0200
++++ streamdev/server/connectionVTP.h 2009-04-04 22:01:41.000000000 +0200
+@@ -2,6 +2,8 @@
+ #define VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H
+
+ #include "server/connection.h"
++#include "server/recplayer.h"
++#include <vdr/status.h>
+
+ class cTBSocket;
+ class cStreamdevLiveStreamer;
+@@ -10,7 +12,8 @@
+ class cLSTCHandler;
+ class cLSTTHandler;
+
+-class cConnectionVTP: public cServerConnection {
++class cConnectionVTP: public cServerConnection
++ , public cStatus {
+ friend class cLSTEHandler;
+ #if !defined __GNUC__ || __GNUC__ >= 3
+ using cServerConnection::Respond;
+@@ -21,10 +24,13 @@
+ cStreamdevLiveStreamer *m_LiveStreamer;
+ cTBSocket *m_FilterSocket;
+ cStreamdevFilterStreamer *m_FilterStreamer;
++ cTBSocket *m_RecSocket;
++ cTBSocket *m_DataSocket;
+
+ char *m_LastCommand;
+ eStreamType m_StreamType;
+ bool m_FiltersSupport;
++ RecPlayer *m_RecPlayer;
+
+ // Members adopted for SVDRP
+ cRecordings Recordings;
+@@ -36,6 +42,12 @@
+ template<class cHandler>
+ bool CmdLSTX(cHandler *&Handler, char *Option);
+
++ virtual void TimerChange(const cTimer *Timer, eTimerChange Change);
++#ifdef USE_STREAMDEVEXT
++ virtual void RecordingChange(const cRecording *Recording, eStatusChange Change);
++ virtual void ChannelChange(const cChannel *Channel, eStatusChange Change);
++#endif /* STREAMDEVEXTENSION */
++
+ public:
+ cConnectionVTP(void);
+ virtual ~cConnectionVTP();
+@@ -51,7 +63,9 @@
+ bool CmdCAPS(char *Opts);
+ bool CmdPROV(char *Opts);
+ bool CmdPORT(char *Opts);
++ bool CmdREAD(char *Opts);
+ bool CmdTUNE(char *Opts);
++ bool CmdPLAY(char *Opts);
+ bool CmdADDP(char *Opts);
+ bool CmdDELP(char *Opts);
+ bool CmdADDF(char *Opts);
+@@ -66,12 +80,23 @@
+ bool CmdLSTT(char *Opts);
+
+ // Commands adopted from SVDRP
++ bool CmdSTAT(const char *Option);
+ bool CmdMODT(const char *Option);
+ bool CmdNEWT(const char *Option);
+ bool CmdDELT(const char *Option);
+-
+- //bool CmdLSTR(char *Opts);
+- //bool CmdDELR(char *Opts);
++ bool CmdNEXT(const char *Option);
++ bool CmdNEWC(const char *Option);
++ bool CmdMODC(const char *Option);
++ bool CmdMOVC(const char *Option);
++ bool CmdDELC(const char *Option);
++ bool CmdMARK(const char *Option);
++ bool CmdFRAM(const char *Option);
++
++ bool CmdLSTR(char *Opts);
++ bool CmdDELR(char *Opts);
++#if defined(USE_LIEMIKUUTIO) || defined(USE_LIEMIEXT)
++ bool CmdRENR(char *Opts);
++#endif
+
+ bool Respond(int Code, const char *Message, ...)
+ __attribute__ ((format (printf, 3, 4)));
+diff -NaurwB streamdev-unpatched/server/recplayer.c streamdev/server/recplayer.c
+--- streamdev-unpatched/server/recplayer.c 1970-01-01 01:00:00.000000000 +0100
++++ streamdev/server/recplayer.c 2009-04-04 23:17:55.000000000 +0200
+@@ -0,0 +1,273 @@
++/*
++ Copyright 2004-2005 Chris Tallon
++
++ This file is part of VOMP.
++
++ VOMP is free software; you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation; either version 2 of the License, or
++ (at your option) any later version.
++
++ VOMP is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with VOMP; if not, write to the Free Software
++ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
++*/
++
++#include "recplayer.h"
++
++#define _XOPEN_SOURCE 600
++#include <fcntl.h>
++
++RecPlayer::RecPlayer(cRecording* rec)
++{
++ file = NULL;
++ fileOpen = 0;
++ lastPosition = 0;
++ recording = rec;
++ for(int i = 1; i < 1000; i++) segments[i] = NULL;
++
++ // FIXME find out max file path / name lengths
++
++ indexFile = new cIndexFile(recording->FileName(), false);
++ if (!indexFile) esyslog("ERROR: Streamdev: Failed to create indexfile!");
++
++ scan();
++}
++
++void RecPlayer::scan()
++{
++ if (file) fclose(file);
++ totalLength = 0;
++ fileOpen = 0;
++ totalFrames = 0;
++
++ int i = 1;
++ while(segments[i++]) delete segments[i];
++
++ char fileName[2048];
++ for(i = 1; i < 1000; i++)
++ {
++ snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i);
++ //log->log("RecPlayer", Log::DEBUG, "FILENAME: %s", fileName);
++ file = fopen(fileName, "r");
++ if (!file) break;
++
++ segments[i] = new Segment();
++ segments[i]->start = totalLength;
++ fseek(file, 0, SEEK_END);
++ totalLength += ftell(file);
++ totalFrames = indexFile->Last();
++ //log->log("RecPlayer", Log::DEBUG, "File %i found, totalLength now %llu, numFrames = %lu", i, totalLength, totalFrames);
++ segments[i]->end = totalLength;
++ fclose(file);
++ }
++
++ file = NULL;
++}
++
++RecPlayer::~RecPlayer()
++{
++ //log->log("RecPlayer", Log::DEBUG, "destructor");
++ int i = 1;
++ while(segments[i++]) delete segments[i];
++ if (file) fclose(file);
++}
++
++int RecPlayer::openFile(int index)
++{
++ if (file) fclose(file);
++
++ char fileName[2048];
++ snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), index);
++ //log->log("RecPlayer", Log::DEBUG, "openFile called for index %i string:%s", index, fileName);
++
++ file = fopen(fileName, "r");
++ if (!file)
++ {
++ //log->log("RecPlayer", Log::DEBUG, "file failed to open");
++ fileOpen = 0;
++ return 0;
++ }
++ fileOpen = index;
++ return 1;
++}
++
++uint64_t RecPlayer::getLengthBytes()
++{
++ return totalLength;
++}
++
++uint32_t RecPlayer::getLengthFrames()
++{
++ return totalFrames;
++}
++
++unsigned long RecPlayer::getBlock(unsigned char* buffer, uint64_t position, unsigned long amount)
++{
++ if ((amount > totalLength) || (amount > 500000))
++ {
++ //log->log("RecPlayer", Log::DEBUG, "Amount %lu requested and rejected", amount);
++ return 0;
++ }
++
++ if (position >= totalLength)
++ {
++ //log->log("RecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!");
++ return 0;
++ }
++
++ if ((position + amount) > totalLength)
++ {
++ //log->log("RecPlayer", Log::DEBUG, "Client asked for some data past the end of recording, adjusting amount");
++ amount = totalLength - position;
++ }
++
++ // work out what block position is in
++ int segmentNumber;
++ for(segmentNumber = 1; segmentNumber < 1000; segmentNumber++)
++ {
++ if ((position >= segments[segmentNumber]->start) && (position < segments[segmentNumber]->end)) break;
++ // position is in this block
++ }
++
++ // we could be seeking around
++ if (segmentNumber != fileOpen)
++ {
++ if (!openFile(segmentNumber)) return 0;
++ }
++
++ uint64_t currentPosition = position;
++ uint32_t yetToGet = amount;
++ uint32_t got = 0;
++ uint32_t getFromThisSegment = 0;
++ uint32_t filePosition;
++
++ while(got < amount)
++ {
++ if (got)
++ {
++ // if(got) then we have already got some and we are back around
++ // advance the file pointer to the next file
++ if (!openFile(++segmentNumber)) return 0;
++ }
++
++ // is the request completely in this block?
++ if ((currentPosition + yetToGet) <= segments[segmentNumber]->end)
++ getFromThisSegment = yetToGet;
++ else
++ getFromThisSegment = segments[segmentNumber]->end - currentPosition;
++
++ filePosition = currentPosition - segments[segmentNumber]->start;
++ fseek(file, filePosition, SEEK_SET);
++ if (fread(&buffer[got], getFromThisSegment, 1, file) != 1) return 0; // umm, big problem.
++
++ // Tell linux not to bother keeping the data in the FS cache
++ posix_fadvise(file->_fileno, filePosition, getFromThisSegment, POSIX_FADV_DONTNEED);
++
++ got += getFromThisSegment;
++ currentPosition += getFromThisSegment;
++ yetToGet -= getFromThisSegment;
++ }
++
++ lastPosition = position;
++ return got;
++}
++
++uint64_t RecPlayer::getLastPosition()
++{
++ return lastPosition;
++}
++
++cRecording* RecPlayer::getCurrentRecording()
++{
++ return recording;
++}
++
++uint64_t RecPlayer::positionFromFrameNumber(uint32_t frameNumber)
++{
++ if (!indexFile) return 0;
++
++#if APIVERSNUM < 10704
++ uint8_t retFileNumber;
++ int retFileOffset;
++ uint8_t retPicType;
++ int retLength;
++#else
++ uint16_t retFileNumber;
++ off_t retFileOffset;
++ bool retIndependent;
++ int retLength;
++#endif
++
++#if APIVERSNUM < 10704
++ if (!indexFile->Get((int)frameNumber, &retFileNumber, &retFileOffset, &retPicType, &retLength))
++#else
++ if (!indexFile->Get((int)frameNumber, &retFileNumber, &retFileOffset, &retIndependent, &retLength))
++#endif
++ {
++ return 0;
++ }
++
++// log->log("RecPlayer", Log::DEBUG, "FN: %u FO: %i", retFileNumber, retFileOffset);
++ if (!segments[retFileNumber]) return 0;
++ uint64_t position = segments[retFileNumber]->start + retFileOffset;
++// log->log("RecPlayer", Log::DEBUG, "Pos: %llu", position);
++
++ return position;
++}
++
++uint32_t RecPlayer::frameNumberFromPosition(uint64_t position)
++{
++ if (!indexFile) return 0;
++
++ if (position >= totalLength)
++ {
++ //log->log("RecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!");
++ return 0;
++ }
++
++ uint8_t segmentNumber;
++ for(segmentNumber = 1; segmentNumber < 255; segmentNumber++)
++ {
++ if ((position >= segments[segmentNumber]->start) && (position < segments[segmentNumber]->end)) break;
++ // position is in this block
++ }
++ uint32_t askposition = position - segments[segmentNumber]->start;
++ return indexFile->Get((int)segmentNumber, askposition);
++
++}
++
++
++bool RecPlayer::getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength)
++{
++ // 0 = backwards
++ // 1 = forwards
++
++ if (!indexFile) return false;
++
++ uint8_t waste1;
++ int waste2;
++
++ int iframeLength;
++ int indexReturnFrameNumber;
++
++#if APIVERSNUM < 10704
++ indexReturnFrameNumber = (uint32_t)indexFile->GetNextIFrame(frameNumber, (direction==1 ? true : false), &waste1, &waste2, &iframeLength);
++#else
++ indexReturnFrameNumber = (uint32_t)indexFile->GetNextIFrame(frameNumber, (direction==1 ? true : false), NULL, NULL, &iframeLength);
++#endif
++ //log->log("RecPlayer", Log::DEBUG, "GNIF input framenumber:%lu, direction=%lu, output:framenumber=%i, framelength=%i", frameNumber, direction, indexReturnFrameNumber, iframeLength);
++
++ if (indexReturnFrameNumber == -1) return false;
++
++ *rfilePosition = positionFromFrameNumber(indexReturnFrameNumber);
++ *rframeNumber = (uint32_t)indexReturnFrameNumber;
++ *rframeLength = (uint32_t)iframeLength;
++
++ return true;
++}
+diff -NaurwB streamdev-unpatched/server/recplayer.h streamdev/server/recplayer.h
+--- streamdev-unpatched/server/recplayer.h 1970-01-01 01:00:00.000000000 +0100
++++ streamdev/server/recplayer.h 2009-04-04 22:01:41.000000000 +0200
+@@ -0,0 +1,63 @@
++/*
++ Copyright 2004-2005 Chris Tallon
++
++ This file is part of VOMP.
++
++ VOMP is free software; you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation; either version 2 of the License, or
++ (at your option) any later version.
++
++ VOMP is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with VOMP; if not, write to the Free Software
++ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
++*/
++
++#ifndef RECPLAYER_H
++#define RECPLAYER_H
++
++#include <stdio.h>
++#include <vdr/recording.h>
++
++#include "server/streamer.h"
++
++class Segment
++{
++ public:
++ uint64_t start;
++ uint64_t end;
++};
++
++class RecPlayer
++{
++ public:
++ RecPlayer(cRecording* rec);
++ ~RecPlayer();
++ uint64_t getLengthBytes();
++ uint32_t getLengthFrames();
++ unsigned long getBlock(unsigned char* buffer, uint64_t position, unsigned long amount);
++ int openFile(int index);
++ uint64_t getLastPosition();
++ cRecording* getCurrentRecording();
++ void scan();
++ uint64_t positionFromFrameNumber(uint32_t frameNumber);
++ uint32_t frameNumberFromPosition(uint64_t position);
++ bool getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength);
++
++ private:
++ cRecording* recording;
++ cIndexFile* indexFile;
++ FILE* file;
++ int fileOpen;
++ Segment* segments[1000];
++ uint64_t totalLength;
++ uint64_t lastPosition;
++ uint32_t totalFrames;
++};
++
++#endif