mirror of
https://github.com/VDR4Arch/vdr.git
synced 2023-10-10 13:36:52 +02:00
Implemented SVDRP
This commit is contained in:
parent
a91ff70bf2
commit
52514313fb
5
HISTORY
5
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.
|
||||
|
7
INSTALL
7
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:
|
||||
-------------------------
|
||||
|
||||
|
9
Makefile
9
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)
|
||||
|
1
TODO
1
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.
|
||||
|
45
config.c
45
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)
|
||||
|
21
config.h
21
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;
|
||||
|
620
svdrp.c
Normal file
620
svdrp.c
Normal 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
52
svdrp.h
Normal 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
40
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;
|
||||
|
5
tools.h
5
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);
|
||||
|
54
vdr.c
54
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();
|
||||
|
Loading…
Reference in New Issue
Block a user