Implemented SVDRP

This commit is contained in:
Klaus Schmidinger 2000-07-23 15:01:31 +02:00
parent a91ff70bf2
commit 52514313fb
11 changed files with 835 additions and 24 deletions

View File

@ -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.

View File

@ -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:
-------------------------

View File

@ -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)

1
TODO
View File

@ -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.

View File

@ -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)

View File

@ -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;

620
svdrp.c Normal file
View File

@ -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???

52
svdrp.h Normal file
View File

@ -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

40
tools.c
View File

@ -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;

View File

@ -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);

54
vdr.c
View File

@ -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();