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
|
the PC keyboard to better resemble the "up-down-left-right-ok" layout on
|
||||||
menu controlling remote control units.
|
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!).
|
- Added support for LIRC remote control (thanks to Carsten Koch!).
|
||||||
There are now three different remote control modes: KBD (PC-Keyboard), RCU
|
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
|
- The polarization can now be given in uppercase or lowercase characters in
|
||||||
channels.conf.
|
channels.conf.
|
||||||
- Fixed buffer initialization to work with DVB driver version 0.6.
|
- 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
|
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.
|
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:
|
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
|
# See the main source file 'vdr.c' for copyright information and
|
||||||
# how to reach the author.
|
# 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
|
ifndef REMOTE
|
||||||
REMOTE = KBD
|
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
|
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
|
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
|
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
|
recording.o: recording.c config.h dvbapi.h interface.h recording.h tools.h
|
||||||
remote.o : remote.c remote.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
|
tools.o : tools.c tools.h
|
||||||
|
|
||||||
vdr: $(OBJS)
|
vdr: $(OBJS)
|
||||||
|
1
TODO
1
TODO
@ -8,3 +8,4 @@ TODO list for the Video Disk Recorder project
|
|||||||
commercial breaks).
|
commercial breaks).
|
||||||
* Implement channel scanning.
|
* Implement channel scanning.
|
||||||
* Better support for encrypted channels.
|
* 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
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* 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"
|
#include "config.h"
|
||||||
@ -60,7 +60,7 @@ void cKeys::SetDummyValues(void)
|
|||||||
k->code = k->type + 1; // '+1' to avoid 0
|
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);
|
isyslog(LOG_INFO, "loading %s", FileName);
|
||||||
bool result = false;
|
bool result = false;
|
||||||
@ -175,6 +175,8 @@ void cKeys::Set(eKeys Key, unsigned int Code)
|
|||||||
|
|
||||||
// -- cChannel ---------------------------------------------------------------
|
// -- cChannel ---------------------------------------------------------------
|
||||||
|
|
||||||
|
char *cChannel::buffer = NULL;
|
||||||
|
|
||||||
cChannel::cChannel(void)
|
cChannel::cChannel(void)
|
||||||
{
|
{
|
||||||
*name = 0;
|
*name = 0;
|
||||||
@ -193,7 +195,18 @@ cChannel::cChannel(const cChannel *Channel)
|
|||||||
pnr = Channel ? Channel->pnr : 0;
|
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;
|
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)) {
|
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)
|
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)
|
bool cChannel::Switch(cDvbApi *DvbApi)
|
||||||
@ -242,6 +255,8 @@ const char *cChannel::GetChannelName(int i)
|
|||||||
|
|
||||||
// -- cTimer -----------------------------------------------------------------
|
// -- cTimer -----------------------------------------------------------------
|
||||||
|
|
||||||
|
char *cTimer::buffer = NULL;
|
||||||
|
|
||||||
cTimer::cTimer(bool Instant)
|
cTimer::cTimer(bool Instant)
|
||||||
{
|
{
|
||||||
startTime = stopTime = 0;
|
startTime = stopTime = 0;
|
||||||
@ -263,6 +278,17 @@ cTimer::cTimer(bool Instant)
|
|||||||
snprintf(file, sizeof(file), "@%s", cChannel::GetChannelName(CurrentChannel));
|
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)
|
int cTimer::TimeToInt(int t)
|
||||||
{
|
{
|
||||||
return (t / 100 * 60 + t % 100) * 60;
|
return (t / 100 * 60 + t % 100) * 60;
|
||||||
@ -275,7 +301,7 @@ time_t cTimer::Day(time_t t)
|
|||||||
return mktime(&d);
|
return mktime(&d);
|
||||||
}
|
}
|
||||||
|
|
||||||
int cTimer::ParseDay(char *s)
|
int cTimer::ParseDay(const char *s)
|
||||||
{
|
{
|
||||||
char *tail;
|
char *tail;
|
||||||
int d = strtol(s, &tail, 10);
|
int d = strtol(s, &tail, 10);
|
||||||
@ -283,7 +309,7 @@ int cTimer::ParseDay(char *s)
|
|||||||
d = 0;
|
d = 0;
|
||||||
if (tail == s) {
|
if (tail == s) {
|
||||||
if (strlen(s) == 7) {
|
if (strlen(s) == 7) {
|
||||||
for (char *p = s + 6; p >= s; p--) {
|
for (const char *p = s + 6; p >= s; p--) {
|
||||||
d <<= 1;
|
d <<= 1;
|
||||||
d |= (*p != '-');
|
d |= (*p != '-');
|
||||||
}
|
}
|
||||||
@ -296,7 +322,7 @@ int cTimer::ParseDay(char *s)
|
|||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *cTimer::PrintDay(int d)
|
const char *cTimer::PrintDay(int d)
|
||||||
{
|
{
|
||||||
static char buffer[8];
|
static char buffer[8];
|
||||||
if ((d & 0x80000000) != 0) {
|
if ((d & 0x80000000) != 0) {
|
||||||
@ -314,11 +340,12 @@ char *cTimer::PrintDay(int d)
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cTimer::Parse(char *s)
|
bool cTimer::Parse(const char *s)
|
||||||
{
|
{
|
||||||
char *buffer1 = NULL;
|
char *buffer1 = NULL;
|
||||||
char *buffer2 = 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)) {
|
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);
|
day = ParseDay(buffer1);
|
||||||
strncpy(file, buffer2, MaxFileName - 1);
|
strncpy(file, buffer2, MaxFileName - 1);
|
||||||
file[strlen(buffer2)] = 0;
|
file[strlen(buffer2)] = 0;
|
||||||
@ -331,7 +358,7 @@ bool cTimer::Parse(char *s)
|
|||||||
|
|
||||||
bool cTimer::Save(FILE *f)
|
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)
|
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
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* 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
|
#ifndef __CONFIG_H
|
||||||
@ -51,7 +51,7 @@ public:
|
|||||||
cKeys(void);
|
cKeys(void);
|
||||||
void Clear(void);
|
void Clear(void);
|
||||||
void SetDummyValues(void);
|
void SetDummyValues(void);
|
||||||
bool Load(char *FileName = NULL);
|
bool Load(const char *FileName = NULL);
|
||||||
bool Save(void);
|
bool Save(void);
|
||||||
unsigned int Encode(const char *Command);
|
unsigned int Encode(const char *Command);
|
||||||
eKeys Get(unsigned int Code);
|
eKeys Get(unsigned int Code);
|
||||||
@ -59,6 +59,9 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
class cChannel : public cListObject {
|
class cChannel : public cListObject {
|
||||||
|
private:
|
||||||
|
static char *buffer;
|
||||||
|
static const char *ToText(cChannel *Channel);
|
||||||
public:
|
public:
|
||||||
enum { MaxChannelName = 32 }; // 31 chars + terminating 0!
|
enum { MaxChannelName = 32 }; // 31 chars + terminating 0!
|
||||||
char name[MaxChannelName];
|
char name[MaxChannelName];
|
||||||
@ -72,7 +75,8 @@ public:
|
|||||||
int pnr;
|
int pnr;
|
||||||
cChannel(void);
|
cChannel(void);
|
||||||
cChannel(const cChannel *Channel);
|
cChannel(const cChannel *Channel);
|
||||||
bool Parse(char *s);
|
const char *ToText(void);
|
||||||
|
bool Parse(const char *s);
|
||||||
bool Save(FILE *f);
|
bool Save(FILE *f);
|
||||||
bool Switch(cDvbApi *DvbApi = NULL);
|
bool Switch(cDvbApi *DvbApi = NULL);
|
||||||
static bool SwitchTo(int i, cDvbApi *DvbApi = NULL);
|
static bool SwitchTo(int i, cDvbApi *DvbApi = NULL);
|
||||||
@ -82,6 +86,8 @@ public:
|
|||||||
class cTimer : public cListObject {
|
class cTimer : public cListObject {
|
||||||
private:
|
private:
|
||||||
time_t startTime, stopTime;
|
time_t startTime, stopTime;
|
||||||
|
static char *buffer;
|
||||||
|
static const char *ToText(cTimer *Timer);
|
||||||
public:
|
public:
|
||||||
enum { MaxFileName = 256 };
|
enum { MaxFileName = 256 };
|
||||||
bool recording;
|
bool recording;
|
||||||
@ -95,7 +101,8 @@ public:
|
|||||||
int lifetime;
|
int lifetime;
|
||||||
char file[MaxFileName];
|
char file[MaxFileName];
|
||||||
cTimer(bool Instant = false);
|
cTimer(bool Instant = false);
|
||||||
bool Parse(char *s);
|
const char *ToText(void);
|
||||||
|
bool Parse(const char *s);
|
||||||
bool Save(FILE *f);
|
bool Save(FILE *f);
|
||||||
bool IsSingleEvent(void);
|
bool IsSingleEvent(void);
|
||||||
bool Matches(time_t t = 0);
|
bool Matches(time_t t = 0);
|
||||||
@ -105,8 +112,8 @@ public:
|
|||||||
static cTimer *GetMatch(void);
|
static cTimer *GetMatch(void);
|
||||||
static int TimeToInt(int t);
|
static int TimeToInt(int t);
|
||||||
static time_t Day(time_t t);
|
static time_t Day(time_t t);
|
||||||
static int ParseDay(char *s);
|
static int ParseDay(const char *s);
|
||||||
static char *PrintDay(int d);
|
static const char *PrintDay(int d);
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class T> class cConfig : public cList<T> {
|
template<class T> class cConfig : public cList<T> {
|
||||||
@ -118,7 +125,7 @@ private:
|
|||||||
cList<T>::Clear();
|
cList<T>::Clear();
|
||||||
}
|
}
|
||||||
public:
|
public:
|
||||||
bool Load(char *FileName)
|
bool Load(const char *FileName)
|
||||||
{
|
{
|
||||||
isyslog(LOG_INFO, "loading %s", FileName);
|
isyslog(LOG_INFO, "loading %s", FileName);
|
||||||
bool result = true;
|
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
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* 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
|
#define _GNU_SOURCE
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
|
#include <ctype.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
@ -58,6 +59,26 @@ bool readint(int filedes, int &n)
|
|||||||
return DataAvailable(filedes) && read(filedes, &n, sizeof(n)) == sizeof(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)
|
void purge(int filedes)
|
||||||
{
|
{
|
||||||
while (DataAvailable(filedes))
|
while (DataAvailable(filedes))
|
||||||
@ -88,6 +109,13 @@ char *strreplace(char *s, char c1, char c2)
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *skipspace(char *s)
|
||||||
|
{
|
||||||
|
while (*s && isspace(*s))
|
||||||
|
s++;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
int time_ms(void)
|
int time_ms(void)
|
||||||
{
|
{
|
||||||
static time_t t0 = 0;
|
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 MakeDirs(const char *FileName, bool IsDirectory)
|
||||||
{
|
{
|
||||||
bool result = true;
|
bool result = true;
|
||||||
|
5
tools.h
5
tools.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* 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
|
#ifndef __TOOLS_H
|
||||||
@ -35,11 +35,14 @@ void writechar(int filedes, char c);
|
|||||||
void writeint(int filedes, int n);
|
void writeint(int filedes, int n);
|
||||||
char readchar(int filedes);
|
char readchar(int filedes);
|
||||||
bool readint(int filedes, int &n);
|
bool readint(int filedes, int &n);
|
||||||
|
int readstring(int filedes, char *buffer, int size, bool wait = false);
|
||||||
void purge(int filedes);
|
void purge(int filedes);
|
||||||
char *readline(FILE *f);
|
char *readline(FILE *f);
|
||||||
char *strreplace(char *s, char c1, char c2);
|
char *strreplace(char *s, char c1, char c2);
|
||||||
|
char *skipspace(char *s);
|
||||||
int time_ms(void);
|
int time_ms(void);
|
||||||
void delay_ms(int ms);
|
void delay_ms(int ms);
|
||||||
|
bool isnumber(const char *s);
|
||||||
bool MakeDirs(const char *FileName, bool IsDirectory = false);
|
bool MakeDirs(const char *FileName, bool IsDirectory = false);
|
||||||
bool RemoveFileOrDir(const char *FileName);
|
bool RemoveFileOrDir(const char *FileName);
|
||||||
bool CheckProcess(pid_t pid);
|
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
|
* 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 <signal.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "dvbapi.h"
|
#include "dvbapi.h"
|
||||||
#include "interface.h"
|
#include "interface.h"
|
||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
#include "recording.h"
|
#include "recording.h"
|
||||||
|
#include "svdrp.h"
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
|
|
||||||
#ifdef REMOTE_KBD
|
#ifdef REMOTE_KBD
|
||||||
@ -50,12 +53,53 @@ void SignalHandler(int signum)
|
|||||||
|
|
||||||
int main(int argc, char *argv[])
|
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);
|
openlog("vdr", LOG_PID | LOG_CONS, LOG_USER);
|
||||||
isyslog(LOG_INFO, "started");
|
isyslog(LOG_INFO, "started");
|
||||||
|
|
||||||
|
// DVB interfaces:
|
||||||
|
|
||||||
if (!cDvbApi::Init())
|
if (!cDvbApi::Init())
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
// Configuration data:
|
||||||
|
|
||||||
Channels.Load("channels.conf");
|
Channels.Load("channels.conf");
|
||||||
Timers.Load("timers.conf");
|
Timers.Load("timers.conf");
|
||||||
#ifdef REMOTE_LIRC
|
#ifdef REMOTE_LIRC
|
||||||
@ -68,10 +112,15 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
cChannel::SwitchTo(CurrentChannel);
|
cChannel::SwitchTo(CurrentChannel);
|
||||||
|
|
||||||
|
// Signal handlers:
|
||||||
|
|
||||||
if (signal(SIGHUP, SignalHandler) == SIG_IGN) signal(SIGHUP, SIG_IGN);
|
if (signal(SIGHUP, SignalHandler) == SIG_IGN) signal(SIGHUP, SIG_IGN);
|
||||||
if (signal(SIGINT, SignalHandler) == SIG_IGN) signal(SIGINT, SIG_IGN);
|
if (signal(SIGINT, SignalHandler) == SIG_IGN) signal(SIGINT, SIG_IGN);
|
||||||
if (signal(SIGTERM, SignalHandler) == SIG_IGN) signal(SIGTERM, 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;
|
cMenuMain *Menu = NULL;
|
||||||
cReplayControl *ReplayControl = NULL;
|
cReplayControl *ReplayControl = NULL;
|
||||||
int dcTime = 0, dcNumber = 0;
|
int dcTime = 0, dcNumber = 0;
|
||||||
@ -157,10 +206,13 @@ int main(int argc, char *argv[])
|
|||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (SVDRP)
|
||||||
|
SVDRP->Process();//TODO lock menu vs. SVDRP?
|
||||||
}
|
}
|
||||||
isyslog(LOG_INFO, "caught signal %d", Interrupted);
|
isyslog(LOG_INFO, "caught signal %d", Interrupted);
|
||||||
delete Menu;
|
delete Menu;
|
||||||
delete ReplayControl;
|
delete ReplayControl;
|
||||||
|
delete SVDRP;
|
||||||
cDvbApi::Cleanup();
|
cDvbApi::Cleanup();
|
||||||
isyslog(LOG_INFO, "exiting");
|
isyslog(LOG_INFO, "exiting");
|
||||||
closelog();
|
closelog();
|
||||||
|
Loading…
Reference in New Issue
Block a user