summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--HISTORY5
-rw-r--r--INSTALL7
-rw-r--r--Makefile9
-rw-r--r--TODO1
-rw-r--r--config.c45
-rw-r--r--config.h21
-rw-r--r--svdrp.c620
-rw-r--r--svdrp.h52
-rw-r--r--tools.c40
-rw-r--r--tools.h5
-rw-r--r--vdr.c54
11 files changed, 835 insertions, 24 deletions
diff --git a/HISTORY b/HISTORY
index 838cc65f..2e58d0da 100644
--- a/HISTORY
+++ b/HISTORY
@@ -56,7 +56,7 @@ Video Disk Recorder Revision History
the PC keyboard to better resemble the "up-down-left-right-ok" layout on
menu controlling remote control units.
-2000-07-15: Version 0.06
+2000-07-23: Version 0.06
- Added support for LIRC remote control (thanks to Carsten Koch!).
There are now three different remote control modes: KBD (PC-Keyboard), RCU
@@ -83,3 +83,6 @@ Video Disk Recorder Revision History
- The polarization can now be given in uppercase or lowercase characters in
channels.conf.
- Fixed buffer initialization to work with DVB driver version 0.6.
+- Implemented the "Simple Video Disk Recorder Protocol" (SVDRP) to control
+ the VDR over a network connection.
+- Implemented command line option handling.
diff --git a/INSTALL b/INSTALL
index 1a6442d9..aec1bda5 100644
--- a/INSTALL
+++ b/INSTALL
@@ -40,6 +40,13 @@ When running, the 'vdr' program writes status information into the
system log file (/var/log/messages). You may want to watch these
messages (tail -f /var/log/mesages) to see if there are any problems.
+The program can be controlled via a network connection to its SVDRP
+port ("Simple Video Disk Recorder Protocol"). By default, it listens
+on port 2001 (use the --port=PORT option to change this). For details
+about the SVDRP syntax see the source file 'svdrp.c'.
+
+Use "vdr --help" for a list of available command line options.
+
The video data directory:
-------------------------
diff --git a/Makefile b/Makefile
index cfa7ef7f..8fb678bc 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,12 @@
#
-# Makefile for the On Screen Menu of the Video Disk Recorder
+# Makefile for the Video Disk Recorder
#
# See the main source file 'vdr.c' for copyright information and
# how to reach the author.
#
-# $Id: Makefile 1.4 2000/06/24 15:09:30 kls Exp $
+# $Id: Makefile 1.5 2000/07/23 11:57:14 kls Exp $
-OBJS = config.o dvbapi.o interface.o menu.o osd.o recording.o remote.o tools.o vdr.o
+OBJS = config.o dvbapi.o interface.o menu.o osd.o recording.o remote.o svdrp.o tools.o vdr.o
ifndef REMOTE
REMOTE = KBD
@@ -28,9 +28,10 @@ dvbapi.o : dvbapi.c config.h dvbapi.h interface.h tools.h
interface.o: interface.c config.h dvbapi.h interface.h remote.h tools.h
menu.o : menu.c config.h dvbapi.h interface.h menu.h osd.h recording.h tools.h
osd.o : osd.c config.h dvbapi.h interface.h osd.h tools.h
-vdr.o : vdr.c config.h dvbapi.h interface.h menu.h osd.h recording.h tools.h
+vdr.o : vdr.c config.h dvbapi.h interface.h menu.h osd.h recording.h svdrp.h tools.h
recording.o: recording.c config.h dvbapi.h interface.h recording.h tools.h
remote.o : remote.c remote.h tools.h
+svdrp.o : svdrp.c svdrp.h config.h interface.h tools.h
tools.o : tools.c tools.h
vdr: $(OBJS)
diff --git a/TODO b/TODO
index 64b31b23..09e9a10b 100644
--- a/TODO
+++ b/TODO
@@ -8,3 +8,4 @@ TODO list for the Video Disk Recorder project
commercial breaks).
* Implement channel scanning.
* Better support for encrypted channels.
+* Implement remaining commands in SVDRP.
diff --git a/config.c b/config.c
index 872e4f21..72043c13 100644
--- a/config.c
+++ b/config.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: config.c 1.12 2000/07/21 13:10:50 kls Exp $
+ * $Id: config.c 1.13 2000/07/23 11:56:06 kls Exp $
*/
#include "config.h"
@@ -60,7 +60,7 @@ void cKeys::SetDummyValues(void)
k->code = k->type + 1; // '+1' to avoid 0
}
-bool cKeys::Load(char *FileName)
+bool cKeys::Load(const char *FileName)
{
isyslog(LOG_INFO, "loading %s", FileName);
bool result = false;
@@ -175,6 +175,8 @@ void cKeys::Set(eKeys Key, unsigned int Code)
// -- cChannel ---------------------------------------------------------------
+char *cChannel::buffer = NULL;
+
cChannel::cChannel(void)
{
*name = 0;
@@ -193,7 +195,18 @@ cChannel::cChannel(const cChannel *Channel)
pnr = Channel ? Channel->pnr : 0;
}
-bool cChannel::Parse(char *s)
+const char *cChannel::ToText(cChannel *Channel)
+{
+ asprintf(&buffer, "%s:%d:%c:%d:%d:%d:%d:%d:%d\n", Channel->name, Channel->frequency, Channel->polarization, Channel->diseqc, Channel->srate, Channel->vpid, Channel->apid, Channel->ca, Channel->pnr);
+ return buffer;
+}
+
+const char *cChannel::ToText(void)
+{
+ return ToText(this);
+}
+
+bool cChannel::Parse(const char *s)
{
char *buffer = NULL;
if (9 == sscanf(s, "%a[^:]:%d:%c:%d:%d:%d:%d:%d:%d", &buffer, &frequency, &polarization, &diseqc, &srate, &vpid, &apid, &ca, &pnr)) {
@@ -207,7 +220,7 @@ bool cChannel::Parse(char *s)
bool cChannel::Save(FILE *f)
{
- return fprintf(f, "%s:%d:%c:%d:%d:%d:%d:%d:%d\n", name, frequency, polarization, diseqc, srate, vpid, apid, ca, pnr) > 0;
+ return fprintf(f, ToText()) > 0;
}
bool cChannel::Switch(cDvbApi *DvbApi)
@@ -242,6 +255,8 @@ const char *cChannel::GetChannelName(int i)
// -- cTimer -----------------------------------------------------------------
+char *cTimer::buffer = NULL;
+
cTimer::cTimer(bool Instant)
{
startTime = stopTime = 0;
@@ -263,6 +278,17 @@ cTimer::cTimer(bool Instant)
snprintf(file, sizeof(file), "@%s", cChannel::GetChannelName(CurrentChannel));
}
+const char *cTimer::ToText(cTimer *Timer)
+{
+ asprintf(&buffer, "%d:%d:%s:%d:%d:%d:%d:%s\n", Timer->active, Timer->channel, PrintDay(Timer->day), Timer->start, Timer->stop, Timer->priority, Timer->lifetime, Timer->file);
+ return buffer;
+}
+
+const char *cTimer::ToText(void)
+{
+ return ToText(this);
+}
+
int cTimer::TimeToInt(int t)
{
return (t / 100 * 60 + t % 100) * 60;
@@ -275,7 +301,7 @@ time_t cTimer::Day(time_t t)
return mktime(&d);
}
-int cTimer::ParseDay(char *s)
+int cTimer::ParseDay(const char *s)
{
char *tail;
int d = strtol(s, &tail, 10);
@@ -283,7 +309,7 @@ int cTimer::ParseDay(char *s)
d = 0;
if (tail == s) {
if (strlen(s) == 7) {
- for (char *p = s + 6; p >= s; p--) {
+ for (const char *p = s + 6; p >= s; p--) {
d <<= 1;
d |= (*p != '-');
}
@@ -296,7 +322,7 @@ int cTimer::ParseDay(char *s)
return d;
}
-char *cTimer::PrintDay(int d)
+const char *cTimer::PrintDay(int d)
{
static char buffer[8];
if ((d & 0x80000000) != 0) {
@@ -314,11 +340,12 @@ char *cTimer::PrintDay(int d)
return buffer;
}
-bool cTimer::Parse(char *s)
+bool cTimer::Parse(const char *s)
{
char *buffer1 = NULL;
char *buffer2 = NULL;
if (8 == sscanf(s, "%d:%d:%a[^:]:%d:%d:%d:%d:%a[^:\n]", &active, &channel, &buffer1, &start, &stop, &priority, &lifetime, &buffer2)) {
+ //TODO add more plausibility checks
day = ParseDay(buffer1);
strncpy(file, buffer2, MaxFileName - 1);
file[strlen(buffer2)] = 0;
@@ -331,7 +358,7 @@ bool cTimer::Parse(char *s)
bool cTimer::Save(FILE *f)
{
- return fprintf(f, "%d:%d:%s:%d:%d:%d:%d:%s\n", active, channel, PrintDay(day), start, stop, priority, lifetime, file) > 0;
+ return fprintf(f, ToText()) > 0;
}
bool cTimer::IsSingleEvent(void)
diff --git a/config.h b/config.h
index d7f9f092..28d07edf 100644
--- a/config.h
+++ b/config.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: config.h 1.9 2000/07/16 11:41:51 kls Exp $
+ * $Id: config.h 1.10 2000/07/23 11:54:53 kls Exp $
*/
#ifndef __CONFIG_H
@@ -51,7 +51,7 @@ public:
cKeys(void);
void Clear(void);
void SetDummyValues(void);
- bool Load(char *FileName = NULL);
+ bool Load(const char *FileName = NULL);
bool Save(void);
unsigned int Encode(const char *Command);
eKeys Get(unsigned int Code);
@@ -59,6 +59,9 @@ public:
};
class cChannel : public cListObject {
+private:
+ static char *buffer;
+ static const char *ToText(cChannel *Channel);
public:
enum { MaxChannelName = 32 }; // 31 chars + terminating 0!
char name[MaxChannelName];
@@ -72,7 +75,8 @@ public:
int pnr;
cChannel(void);
cChannel(const cChannel *Channel);
- bool Parse(char *s);
+ const char *ToText(void);
+ bool Parse(const char *s);
bool Save(FILE *f);
bool Switch(cDvbApi *DvbApi = NULL);
static bool SwitchTo(int i, cDvbApi *DvbApi = NULL);
@@ -82,6 +86,8 @@ public:
class cTimer : public cListObject {
private:
time_t startTime, stopTime;
+ static char *buffer;
+ static const char *ToText(cTimer *Timer);
public:
enum { MaxFileName = 256 };
bool recording;
@@ -95,7 +101,8 @@ public:
int lifetime;
char file[MaxFileName];
cTimer(bool Instant = false);
- bool Parse(char *s);
+ const char *ToText(void);
+ bool Parse(const char *s);
bool Save(FILE *f);
bool IsSingleEvent(void);
bool Matches(time_t t = 0);
@@ -105,8 +112,8 @@ public:
static cTimer *GetMatch(void);
static int TimeToInt(int t);
static time_t Day(time_t t);
- static int ParseDay(char *s);
- static char *PrintDay(int d);
+ static int ParseDay(const char *s);
+ static const char *PrintDay(int d);
};
template<class T> class cConfig : public cList<T> {
@@ -118,7 +125,7 @@ private:
cList<T>::Clear();
}
public:
- bool Load(char *FileName)
+ bool Load(const char *FileName)
{
isyslog(LOG_INFO, "loading %s", FileName);
bool result = true;
diff --git a/svdrp.c b/svdrp.c
new file mode 100644
index 00000000..c1347a31
--- /dev/null
+++ b/svdrp.c
@@ -0,0 +1,620 @@
+/*
+ * svdrp.c: Simple Video Disk Recorder Protocol
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * The "Simple Video Disk Recorder Protocol" (SVDRP) was inspired
+ * by the "Simple Mail Transfer Protocol" (SMTP) and is fully ASCII
+ * text based. Therefore you can simply 'telnet' to your VDR port
+ * and interact with the Video Disk Recorder - or write a full featured
+ * graphical interface that sits on top of an SVDRP connection.
+ *
+ * $Id: svdrp.c 1.1 2000/07/23 14:55:03 kls Exp $
+ */
+
+#define _GNU_SOURCE
+
+#include "svdrp.h"
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include "config.h"
+#include "interface.h"
+#include "tools.h"
+
+// --- cSocket ---------------------------------------------------------------
+
+cSocket::cSocket(int Port, int Queue)
+{
+ port = Port;
+ sock = -1;
+}
+
+cSocket::~cSocket()
+{
+ Close();
+}
+
+void cSocket::Close(void)
+{
+ if (sock >= 0) {
+ close(sock);
+ sock = -1;
+ }
+}
+
+bool cSocket::Open(void)
+{
+ if (sock < 0) {
+ // create socket:
+ sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (sock < 0) {
+ LOG_ERROR;
+ port = 0;
+ return false;
+ }
+ struct sockaddr_in name;
+ name.sin_family = AF_INET;
+ name.sin_port = htons(port);
+ name.sin_addr.s_addr = htonl(INADDR_ANY);
+ if (bind(sock, (struct sockaddr *)&name, sizeof(name)) < 0) {
+ LOG_ERROR;
+ Close();
+ return false;
+ }
+ // make it non-blocking:
+ int oldflags = fcntl(sock, F_GETFL, 0);
+ if (oldflags < 0) {
+ LOG_ERROR;
+ return false;
+ }
+ oldflags |= O_NONBLOCK;
+ if (fcntl(sock, F_SETFL, oldflags) < 0) {
+ LOG_ERROR;
+ return false;
+ }
+ // listen to the socket:
+ if (listen(sock, queue) < 0) {
+ LOG_ERROR;
+ return false;
+ }
+ }
+ return true;
+}
+
+int cSocket::Accept(void)
+{
+ if (Open()) {
+ struct sockaddr_in clientname;
+ uint size = sizeof(clientname);
+ int newsock = accept(sock, (struct sockaddr *)&clientname, &size);
+ if (newsock > 0)
+ isyslog(LOG_INFO, "connect from %s, port %hd", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port));
+ else if (errno != EINTR)
+ LOG_ERROR;
+ return newsock;
+ }
+ return -1;
+}
+
+// --- cSVDRP ----------------------------------------------------------------
+
+#define MAXCMDBUFFER 10000
+#define MAXHELPTOPIC 10
+
+const char *HelpPages[] = {
+ "CHAN [ + | - | <number> | <name> ]\n"
+ " Switch channel up, down or to the given channel number or name.\n"
+ " Without option (or after successfully switching to the channel)\n"
+ " it returns the current channel number and name.",
+ "DELC <number>\n"
+ " Delete channel.",
+ "DELT <number>\n"
+ " Delete timer.",
+ "HELP [ <topic> ]\n"
+ " The HELP command gives help info.",
+ "LSTC [ <number> | <name> ]\n"
+ " List channels. Without option, all channels are listed. Otherwise\n"
+ " only the given channel is listed. If a name is given, all channels\n"
+ " containing the given string as part of their name are listed.",
+ "LSTT [ <number> ]\n"
+ " List timers. Without option, all timers are listed. Otherwise\n"
+ " only the given timer is listed.",
+ "MODC <number> <settings>\n"
+ " Modify a channel. Settings must be in the same format as returned\n"
+ " by the LSTC command.",
+ "MODT <number> on | off | <settings>\n"
+ " Modify a timer. Settings must be in the same format as returned\n"
+ " by the LSTT command. The special keywords 'on' and 'off' can be\n"
+ " used to easily activate or deactivate a timer.",
+ "MOVC <number> <to>\n"
+ " Move a channel to a new position.",
+ "MOVT <number> <to>\n"
+ " Move a timer to a new position.",
+ "NEWC <settings>\n"
+ " Create a new channel. Settings must be in the same format as returned\n"
+ " by the LSTC command.",
+ "NEWT <settings>\n"
+ " Create a new timer. Settings must be in the same format as returned\n"
+ " by the LSTT command.",
+ "QUIT\n"
+ " Exit vdr (SVDRP).\n"
+ " You can also hit Ctrl-D to exit.",
+ NULL
+ };
+
+/* SVDRP Reply Codes:
+
+ 214 Help message
+ 220 VDR service ready
+ 221 VDR service closing transmission channel
+ 250 Requested VDR action okay, completed
+ 451 Requested action aborted: local error in processing
+ 500 Syntax error, command unrecognized
+ 501 Syntax error in parameters or arguments
+ 502 Command not implemented
+ 504 Command parameter not implemented
+ 550 Requested action not taken
+ 554 Transaction failed
+
+*/
+
+const char *GetHelpTopic(const char *HelpPage)
+{
+ static char topic[MAXHELPTOPIC];
+ const char *q = HelpPage;
+ while (*q) {
+ if (isspace(*q)) {
+ uint n = q - HelpPage;
+ if (n >= sizeof(topic))
+ n = sizeof(topic) - 1;
+ strncpy(topic, HelpPage, n);
+ topic[n] = 0;
+ return topic;
+ }
+ q++;
+ }
+ return NULL;
+}
+
+const char *GetHelpPage(const char *Cmd)
+{
+ const char **p = HelpPages;
+ while (*p) {
+ const char *t = GetHelpTopic(*p);
+ if (strcasecmp(Cmd, t) == 0)
+ return *p;
+ p++;
+ }
+ return NULL;
+}
+
+cSVDRP::cSVDRP(int Port)
+:socket(Port)
+{
+ filedes = -1;
+ isyslog(LOG_INFO, "SVDRP listening on port %d", Port);
+}
+
+cSVDRP::~cSVDRP()
+{
+ Close();
+}
+
+void cSVDRP::Close(void)
+{
+ if (filedes >= 0) {
+ //TODO how can we get the *full* hostname?
+ char buffer[MAXCMDBUFFER];
+ gethostname(buffer, sizeof(buffer));
+ Reply(221, "%s closing connection", buffer);
+ isyslog(LOG_INFO, "closing connection"); //TODO store IP#???
+ close(filedes);
+ filedes = -1;
+ }
+}
+
+bool cSVDRP::Send(const char *s, int length)
+{
+ if (length < 0)
+ length = strlen(s);
+ int wbytes = write(filedes, s, length);
+ if (wbytes == length)
+ return true;
+ if (wbytes < 0)
+ LOG_ERROR;
+ else //XXX while...???
+ esyslog(LOG_ERR, "Wrote %d bytes to client while expecting %d\n", wbytes, length);
+ return false;
+}
+
+void cSVDRP::Reply(int Code, const char *fmt, ...)
+{
+ if (filedes >= 0) {
+ if (Code != 0) {
+ va_list ap;
+ va_start(ap, fmt);
+ char *buffer;
+ vasprintf(&buffer, fmt, ap);
+ char *nl = strchr(buffer, '\n');
+ if (Code > 0 && nl && *(nl + 1)) // trailing newlines don't count!
+ Code = -Code;
+ char number[16];
+ sprintf(number, "%03d%c", abs(Code), Code < 0 ? '-' : ' ');
+ const char *s = buffer;
+ while (s && *s) {
+ const char *n = strchr(s, '\n');
+ if (!(Send(number) && Send(s, n ? n - s : -1) && Send("\r\n"))) {
+ Close();
+ break;
+ }
+ s = n ? n + 1 : NULL;
+ }
+ delete buffer;
+ va_end(ap);
+ }
+ else {
+ Reply(451, "Zero return code - looks like a programming error!");
+ esyslog(LOG_ERR, "SVDRP: zero return code!");
+ }
+ }
+}
+
+void cSVDRP::CmdChan(const char *Option)
+{
+ if (*Option) {
+ int n = -1;
+ if (isnumber(Option)) {
+ int o = strtol(Option, NULL, 10) - 1;
+ if (o >= 0 && o < Channels.Count())
+ n = o;
+ }
+ else if (strcmp(Option, "-") == 0) {
+ n = CurrentChannel;
+ if (CurrentChannel > 0)
+ n--;
+ }
+ else if (strcmp(Option, "+") == 0) {
+ n = CurrentChannel;
+ if (CurrentChannel < Channels.Count() - 1)
+ n++;
+ }
+ else {
+ int i = 0;
+ cChannel *channel;
+ while ((channel = Channels.Get(i)) != NULL) {
+ if (strcasecmp(channel->name, Option) == 0) {
+ n = i;
+ break;
+ }
+ i++;
+ }
+ }
+ if (n < 0) {
+ Reply(501, "Undefined channel \"%s\"", Option);
+ return;
+ }
+ if (Interface.Recording()) {
+ Reply(550, "Can't switch channel, interface is recording");
+ return;
+ }
+ cChannel *channel = Channels.Get(n);
+ if (channel) {
+ if (!channel->Switch()) {
+ Reply(554, "Error switching to channel \"%d\"", channel->Index() + 1);
+ return;
+ }
+ }
+ else {
+ Reply(550, "Unable to find channel \"%s\"", Option);
+ return;
+ }
+ }
+ cChannel *channel = Channels.Get(CurrentChannel);
+ if (channel)
+ Reply(250, "%d %s", CurrentChannel + 1, channel->name);
+ else
+ Reply(550, "Unable to find channel \"%d\"", CurrentChannel);
+}
+
+void cSVDRP::CmdDelc(const char *Option)
+{
+ //TODO combine this with menu action (timers must be updated)
+ Reply(502, "DELC not yet implemented");
+}
+
+void cSVDRP::CmdDelt(const char *Option)
+{
+ if (*Option) {
+ if (isnumber(Option)) {
+ cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1);
+ if (timer) {
+ if (!timer->recording) {
+ Timers.Del(timer);
+ Timers.Save();
+ isyslog(LOG_INFO, "timer %s deleted", Option);
+ Reply(250, "Timer \"%s\" deleted", Option);
+ }
+ else
+ Reply(550, "Timer \"%s\" is recording", Option);
+ }
+ else
+ Reply(501, "Timer \"%s\" not defined", Option);
+ }
+ else
+ Reply(501, "Error in timer number \"%s\"", Option);
+ }
+ else
+ Reply(501, "Missing timer number");
+}
+
+void cSVDRP::CmdHelp(const char *Option)
+{
+ if (*Option) {
+ const char *hp = GetHelpPage(Option);
+ if (hp)
+ Reply(214, hp);
+ else {
+ Reply(504, "HELP topic \"%s\" unknown", Option);
+ return;
+ }
+ }
+ else {
+ Reply(-214, "This is VDR version 0.060"); //XXX dynamically insert version number
+ Reply(-214, "Topics:");
+ const char **hp = HelpPages;
+ while (*hp) {
+ //TODO multi-column???
+ const char *topic = GetHelpTopic(*hp);
+ if (topic)
+ Reply(-214, " %s", topic);
+ hp++;
+ }
+ Reply(-214, "To report bugs in the implementation send email to");
+ Reply(-214, " vdr-bugs@cadsoft.de");
+ }
+ Reply(214, "End of HELP info");
+}
+
+void cSVDRP::CmdLstc(const char *Option)
+{
+ if (*Option) {
+ if (isnumber(Option)) {
+ cChannel *channel = Channels.Get(strtol(Option, NULL, 10) - 1);
+ if (channel)
+ Reply(250, "%d %s", channel->Index() + 1, channel->ToText());
+ else
+ Reply(501, "Channel \"%s\" not defined", Option);
+ }
+ else {
+ int i = 0;
+ cChannel *next = NULL;
+ while (i < Channels.Count()) {
+ cChannel *channel = Channels.Get(i);
+ if (channel) {
+ if (strcasestr(channel->name, Option)) {
+ if (next)
+ Reply(-250, "%d %s", next->Index() + 1, next->ToText());
+ next = channel;
+ }
+ }
+ else {
+ Reply(501, "Channel \"%d\" not found", i + 1);
+ return;
+ }
+ i++;
+ }
+ if (next)
+ Reply(250, "%d %s", next->Index() + 1, next->ToText());
+ }
+ }
+ else {
+ for (int i = 0; i < Channels.Count(); i++) {
+ cChannel *channel = Channels.Get(i);
+ if (channel)
+ Reply(i < Channels.Count() - 1 ? -250 : 250, "%d %s", channel->Index() + 1, channel->ToText());
+ else
+ Reply(501, "Channel \"%d\" not found", i + 1);
+ }
+ }
+}
+
+void cSVDRP::CmdLstt(const char *Option)
+{
+ if (*Option) {
+ if (isnumber(Option)) {
+ cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1);
+ if (timer)
+ Reply(250, "%d %s", timer->Index() + 1, timer->ToText());
+ else
+ Reply(501, "Timer \"%s\" not defined", Option);
+ }
+ else
+ Reply(501, "Error in timer number \"%s\"", Option);
+ }
+ else {
+ for (int i = 0; i < Timers.Count(); i++) {
+ cTimer *timer = Timers.Get(i);
+ if (timer)
+ Reply(i < Timers.Count() - 1 ? -250 : 250, "%d %s", timer->Index() + 1, timer->ToText());
+ else
+ Reply(501, "Timer \"%d\" not found", i + 1);
+ }
+ }
+}
+
+void cSVDRP::CmdModc(const char *Option)
+{
+ if (*Option) {
+ char *tail;
+ int n = strtol(Option, &tail, 10);
+ if (tail && tail != Option) {
+ tail = skipspace(tail);
+ cChannel *channel = Channels.Get(n - 1);
+ if (channel) {
+ cChannel c = *channel;
+ if (!c.Parse(tail)) {
+ Reply(501, "Error in channel settings");
+ return;
+ }
+ *channel = c;
+ Channels.Save();
+ isyslog(LOG_INFO, "channel %d modified", channel->Index() + 1);
+ Reply(250, "%d %s", channel->Index() + 1, channel->ToText());
+ }
+ else
+ Reply(501, "Channel \"%d\" not defined", n);
+ }
+ else
+ Reply(501, "Error in channel number");
+ }
+ else
+ Reply(501, "Missing channel settings");
+}
+
+void cSVDRP::CmdModt(const char *Option)
+{
+ 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.active = 1;
+ else if (strcasecmp(tail, "OFF") == 0)
+ t.active = 0;
+ else if (!t.Parse(tail)) {
+ Reply(501, "Error in timer settings");
+ return;
+ }
+ *timer = t;
+ Timers.Save();
+ isyslog(LOG_INFO, "timer %d modified (%s)", timer->Index() + 1, timer->active ? "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");
+}
+
+void cSVDRP::CmdMovc(const char *Option)
+{
+ //TODO combine this with menu action (timers must be updated)
+ Reply(502, "MOVC not yet implemented");
+}
+
+void cSVDRP::CmdMovt(const char *Option)
+{
+ //TODO combine this with menu action
+ Reply(502, "MOVT not yet implemented");
+}
+
+void cSVDRP::CmdNewc(const char *Option)
+{
+ if (*Option) {
+ cChannel *channel = new cChannel;
+ if (channel->Parse(Option)) {
+ Channels.Add(channel);
+ Channels.Save();
+ isyslog(LOG_INFO, "channel %d added", channel->Index() + 1);
+ Reply(250, "%d %s", channel->Index() + 1, channel->ToText());
+ }
+ else
+ Reply(501, "Error in channel settings");
+ }
+ else
+ Reply(501, "Missing channel settings");
+}
+
+void cSVDRP::CmdNewt(const char *Option)
+{
+ if (*Option) {
+ cTimer *timer = new cTimer;
+ if (timer->Parse(Option)) {
+ Timers.Add(timer);
+ Timers.Save();
+ isyslog(LOG_INFO, "timer %d added", timer->Index() + 1);
+ Reply(250, "%d %s", timer->Index() + 1, timer->ToText());
+ }
+ else
+ Reply(501, "Error in timer settings");
+ }
+ else
+ Reply(501, "Missing timer settings");
+}
+
+#define CMD(c) (strcasecmp(Cmd, c) == 0)
+
+void cSVDRP::Execute(char *Cmd)
+{
+ // skip leading whitespace:
+ Cmd = skipspace(Cmd);
+ // find the end of the command word:
+ char *s = Cmd;
+ while (*s && !isspace(*s))
+ s++;
+ *s++ = 0;
+ if (CMD("CHAN")) CmdChan(s);
+ else if (CMD("DELC")) CmdDelc(s);
+ else if (CMD("DELT")) CmdDelt(s);
+ else if (CMD("HELP")) CmdHelp(s);
+ else if (CMD("LSTC")) CmdLstc(s);
+ else if (CMD("LSTT")) CmdLstt(s);
+ else if (CMD("MODC")) CmdModc(s);
+ else if (CMD("MODT")) CmdModt(s);
+ else if (CMD("MOVC")) CmdMovc(s);
+ else if (CMD("MOVT")) CmdMovt(s);
+ else if (CMD("NEWC")) CmdNewc(s);
+ else if (CMD("NEWT")) CmdNewt(s);
+ else if (CMD("QUIT")
+ || CMD("\x04")) Close();
+ else Reply(500, "Command unrecognized: \"%s\"", Cmd);
+}
+
+void cSVDRP::Process(void)
+{
+ bool SendGreeting = filedes < 0;
+
+ if (filedes >= 0 || (filedes = socket.Accept()) >= 0) {
+ char buffer[MAXCMDBUFFER];
+ if (SendGreeting) {
+ //TODO how can we get the *full* hostname?
+ gethostname(buffer, sizeof(buffer));
+ time_t now = time(NULL);
+ Reply(220, "%s SVDRP VideoDiskRecorder 0.060; %s", buffer, ctime(&now));//XXX dynamically insert version number
+ }
+ int rbytes = readstring(filedes, buffer, sizeof(buffer) - 1);
+ if (rbytes > 0) {
+ //XXX overflow check???
+ // strip trailing whitespace:
+ while (rbytes > 0 && strchr(" \t\r\n", buffer[rbytes - 1]))
+ buffer[--rbytes] = 0;
+ // make sure the string is terminated:
+ buffer[rbytes] = 0;
+ // showtime!
+ Execute(buffer);
+ }
+ else if (rbytes < 0)
+ Close();
+ }
+}
+
+//TODO timeout???
+//TODO more than one connection???
diff --git a/svdrp.h b/svdrp.h
new file mode 100644
index 00000000..c6385424
--- /dev/null
+++ b/svdrp.h
@@ -0,0 +1,52 @@
+/*
+ * svdrp.h: Simple Video Disk Recorder Protocol
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: svdrp.h 1.1 2000/07/23 14:49:30 kls Exp $
+ */
+
+#ifndef __SVDRP_H
+#define __SVDRP_H
+
+class cSocket {
+private:
+ int port;
+ int sock;
+ int queue;
+ void Close(void);
+public:
+ cSocket(int Port, int Queue = 1);
+ ~cSocket();
+ bool Open(void);
+ int Accept(void);
+ };
+
+class cSVDRP {
+private:
+ cSocket socket;
+ int filedes;
+ void Close(void);
+ bool Send(const char *s, int length = -1);
+ void Reply(int Code, const char *fmt, ...);
+ void CmdChan(const char *Option);
+ void CmdDelc(const char *Option);
+ void CmdDelt(const char *Option);
+ void CmdHelp(const char *Option);
+ void CmdLstc(const char *Option);
+ void CmdLstt(const char *Option);
+ void CmdModc(const char *Option);
+ void CmdModt(const char *Option);
+ void CmdMovc(const char *Option);
+ void CmdMovt(const char *Option);
+ void CmdNewc(const char *Option);
+ void CmdNewt(const char *Option);
+ void Execute(char *Cmd);
+public:
+ cSVDRP(int Port);
+ ~cSVDRP();
+ void Process(void);
+ };
+
+#endif //__SVDRP_H
diff --git a/tools.c b/tools.c
index b2c95f37..998d91b3 100644
--- a/tools.c
+++ b/tools.c
@@ -4,11 +4,12 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: tools.c 1.9 2000/07/16 14:14:44 kls Exp $
+ * $Id: tools.c 1.10 2000/07/23 13:16:54 kls Exp $
*/
#define _GNU_SOURCE
#include "tools.h"
+#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <signal.h>
@@ -58,6 +59,26 @@ bool readint(int filedes, int &n)
return DataAvailable(filedes) && read(filedes, &n, sizeof(n)) == sizeof(n);
}
+int readstring(int filedes, char *buffer, int size, bool wait = false)
+{
+ int rbytes = 0;
+
+ while (DataAvailable(filedes, wait)) {
+ int n = read(filedes, buffer + rbytes, size - rbytes);
+ if (n == 0)
+ break; // EOF
+ if (n < 0) {
+ LOG_ERROR;
+ break;
+ }
+ rbytes += n;
+ if (rbytes == size)
+ break;
+ wait = false;
+ }
+ return rbytes;
+}
+
void purge(int filedes)
{
while (DataAvailable(filedes))
@@ -88,6 +109,13 @@ char *strreplace(char *s, char c1, char c2)
return s;
}
+char *skipspace(char *s)
+{
+ while (*s && isspace(*s))
+ s++;
+ return s;
+}
+
int time_ms(void)
{
static time_t t0 = 0;
@@ -107,6 +135,16 @@ void delay_ms(int ms)
;
}
+bool isnumber(const char *s)
+{
+ while (*s) {
+ if (!isdigit(*s))
+ return false;
+ s++;
+ }
+ return true;
+}
+
bool MakeDirs(const char *FileName, bool IsDirectory)
{
bool result = true;
diff --git a/tools.h b/tools.h
index 0e26ec21..ecf42be6 100644
--- a/tools.h
+++ b/tools.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: tools.h 1.9 2000/07/16 14:11:34 kls Exp $
+ * $Id: tools.h 1.10 2000/07/23 13:16:37 kls Exp $
*/
#ifndef __TOOLS_H
@@ -35,11 +35,14 @@ void writechar(int filedes, char c);
void writeint(int filedes, int n);
char readchar(int filedes);
bool readint(int filedes, int &n);
+int readstring(int filedes, char *buffer, int size, bool wait = false);
void purge(int filedes);
char *readline(FILE *f);
char *strreplace(char *s, char c1, char c2);
+char *skipspace(char *s);
int time_ms(void);
void delay_ms(int ms);
+bool isnumber(const char *s);
bool MakeDirs(const char *FileName, bool IsDirectory = false);
bool RemoveFileOrDir(const char *FileName);
bool CheckProcess(pid_t pid);
diff --git a/vdr.c b/vdr.c
index a065bfd2..06e9ea88 100644
--- a/vdr.c
+++ b/vdr.c
@@ -22,15 +22,18 @@
*
* The project's page is at http://www.cadsoft.de/people/kls/vdr
*
- * $Id: vdr.c 1.21 2000/07/15 16:26:57 kls Exp $
+ * $Id: vdr.c 1.22 2000/07/23 14:53:22 kls Exp $
*/
+#include <getopt.h>
#include <signal.h>
+#include <stdlib.h>
#include "config.h"
#include "dvbapi.h"
#include "interface.h"
#include "menu.h"
#include "recording.h"
+#include "svdrp.h"
#include "tools.h"
#ifdef REMOTE_KBD
@@ -50,12 +53,53 @@ void SignalHandler(int signum)
int main(int argc, char *argv[])
{
+ // Command line options:
+
+#define DEFAULTSVDRPPORT 2001
+
+ int SVDRPport = DEFAULTSVDRPPORT;
+
+ static struct option long_options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "port", required_argument, NULL, 'p' },
+ { 0 }
+ };
+
+ int c;
+ int option_index = 0;
+ while ((c = getopt_long(argc, argv, "hp:", long_options, &option_index)) != -1) {
+ switch (c) {
+ case 'h': printf("Usage: vdr [OPTION]\n\n"
+ " -h, --help display this help and exit\n"
+ " -p PORT, --port=PORT use PORT for SVDRP ('0' turns off SVDRP)\n"
+ "\n"
+ "Report bugs to <vdr-bugs@cadsoft.de>\n"
+ );
+ return 0;
+ break;
+ case 'p': if (isnumber(optarg))
+ SVDRPport = strtol(optarg, NULL, 10);
+ else {
+ fprintf(stderr, "vdr: invalid port number: %s\n", optarg);
+ return 1;
+ }
+ break;
+ default: abort();
+ }
+ }
+
+ // Log file:
+
openlog("vdr", LOG_PID | LOG_CONS, LOG_USER);
isyslog(LOG_INFO, "started");
+ // DVB interfaces:
+
if (!cDvbApi::Init())
return 1;
+ // Configuration data:
+
Channels.Load("channels.conf");
Timers.Load("timers.conf");
#ifdef REMOTE_LIRC
@@ -68,10 +112,15 @@ int main(int argc, char *argv[])
cChannel::SwitchTo(CurrentChannel);
+ // Signal handlers:
+
if (signal(SIGHUP, SignalHandler) == SIG_IGN) signal(SIGHUP, SIG_IGN);
if (signal(SIGINT, SignalHandler) == SIG_IGN) signal(SIGINT, SIG_IGN);
if (signal(SIGTERM, SignalHandler) == SIG_IGN) signal(SIGTERM, SIG_IGN);
+ // Main program loop:
+
+ cSVDRP *SVDRP = SVDRPport ? new cSVDRP(SVDRPport) : NULL;
cMenuMain *Menu = NULL;
cReplayControl *ReplayControl = NULL;
int dcTime = 0, dcNumber = 0;
@@ -157,10 +206,13 @@ int main(int argc, char *argv[])
default: break;
}
}
+ if (SVDRP)
+ SVDRP->Process();//TODO lock menu vs. SVDRP?
}
isyslog(LOG_INFO, "caught signal %d", Interrupted);
delete Menu;
delete ReplayControl;
+ delete SVDRP;
cDvbApi::Cleanup();
isyslog(LOG_INFO, "exiting");
closelog();