vdr 2.6.1
svdrp.c
Go to the documentation of this file.
1/*
2 * svdrp.c: Simple Video Disk Recorder Protocol
3 *
4 * See the main source file 'vdr.c' for copyright information and
5 * how to reach the author.
6 *
7 * The "Simple Video Disk Recorder Protocol" (SVDRP) was inspired
8 * by the "Simple Mail Transfer Protocol" (SMTP) and is fully ASCII
9 * text based. Therefore you can simply 'telnet' to your VDR port
10 * and interact with the Video Disk Recorder - or write a full featured
11 * graphical interface that sits on top of an SVDRP connection.
12 *
13 * $Id: svdrp.c 5.3 2021/01/14 10:29:05 kls Exp $
14 */
15
16#include "svdrp.h"
17#include <arpa/inet.h>
18#include <ctype.h>
19#include <errno.h>
20#include <fcntl.h>
21#include <ifaddrs.h>
22#include <netinet/in.h>
23#include <stdarg.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <sys/socket.h>
28#include <sys/time.h>
29#include <unistd.h>
30#include "channels.h"
31#include "config.h"
32#include "device.h"
33#include "eitscan.h"
34#include "keys.h"
35#include "menu.h"
36#include "plugin.h"
37#include "recording.h"
38#include "remote.h"
39#include "skins.h"
40#include "timers.h"
41#include "videodir.h"
42
43static bool DumpSVDRPDataTransfer = false;
44
45#define dbgsvdrp(a...) if (DumpSVDRPDataTransfer) fprintf(stderr, a)
46
47static int SVDRPTcpPort = 0;
48static int SVDRPUdpPort = 0;
49
51 sffNone = 0b00000000,
52 sffConn = 0b00000001,
53 sffPing = 0b00000010,
54 sffTimers = 0b00000100,
55 };
56
57// --- cIpAddress ------------------------------------------------------------
58
60private:
62 int port;
64public:
65 cIpAddress(void);
66 cIpAddress(const char *Address, int Port);
67 const char *Address(void) const { return address; }
68 int Port(void) const { return port; }
69 void Set(const char *Address, int Port);
70 void Set(const sockaddr *SockAddr);
71 const char *Connection(void) const { return connection; }
72 };
73
75{
76 Set(INADDR_ANY, 0);
77}
78
79cIpAddress::cIpAddress(const char *Address, int Port)
80{
82}
83
84void cIpAddress::Set(const char *Address, int Port)
85{
87 port = Port;
89}
90
91void cIpAddress::Set(const sockaddr *SockAddr)
92{
93 const sockaddr_in *Addr = (sockaddr_in *)SockAddr;
94 Set(inet_ntoa(Addr->sin_addr), ntohs(Addr->sin_port));
95}
96
97// --- cSocket ---------------------------------------------------------------
98
99#define MAXUDPBUF 1024
100
101class cSocket {
102private:
103 int port;
104 bool tcp;
105 int sock;
107public:
108 cSocket(int Port, bool Tcp);
109 ~cSocket();
110 bool Listen(void);
111 bool Connect(const char *Address);
112 void Close(void);
113 int Port(void) const { return port; }
114 int Socket(void) const { return sock; }
115 static bool SendDgram(const char *Dgram, int Port);
116 int Accept(void);
117 cString Discover(void);
118 const cIpAddress *LastIpAddress(void) const { return &lastIpAddress; }
119 };
120
121cSocket::cSocket(int Port, bool Tcp)
122{
123 port = Port;
124 tcp = Tcp;
125 sock = -1;
126}
127
129{
130 Close();
131}
132
134{
135 if (sock >= 0) {
136 close(sock);
137 sock = -1;
138 }
139}
140
142{
143 if (sock < 0) {
144 isyslog("SVDRP %s opening port %d/%s", Setup.SVDRPHostName, port, tcp ? "tcp" : "udp");
145 // create socket:
146 sock = tcp ? socket(PF_INET, SOCK_STREAM, IPPROTO_IP) : socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
147 if (sock < 0) {
148 LOG_ERROR;
149 return false;
150 }
151 // allow it to always reuse the same port:
152 int ReUseAddr = 1;
153 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr, sizeof(ReUseAddr));
154 // configure port and ip:
155 sockaddr_in Addr;
156 memset(&Addr, 0, sizeof(Addr));
157 Addr.sin_family = AF_INET;
158 Addr.sin_port = htons(port);
159 Addr.sin_addr.s_addr = SVDRPhosts.LocalhostOnly() ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY);
160 if (bind(sock, (sockaddr *)&Addr, sizeof(Addr)) < 0) {
161 LOG_ERROR;
162 Close();
163 return false;
164 }
165 // make it non-blocking:
166 int Flags = fcntl(sock, F_GETFL, 0);
167 if (Flags < 0) {
168 LOG_ERROR;
169 return false;
170 }
171 Flags |= O_NONBLOCK;
172 if (fcntl(sock, F_SETFL, Flags) < 0) {
173 LOG_ERROR;
174 return false;
175 }
176 if (tcp) {
177 // listen to the socket:
178 if (listen(sock, 1) < 0) {
179 LOG_ERROR;
180 return false;
181 }
182 }
183 isyslog("SVDRP %s listening on port %d/%s", Setup.SVDRPHostName, port, tcp ? "tcp" : "udp");
184 }
185 return true;
186}
187
188bool cSocket::Connect(const char *Address)
189{
190 if (sock < 0 && tcp) {
191 // create socket:
192 sock = socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
193 if (sock < 0) {
194 LOG_ERROR;
195 return false;
196 }
197 // configure port and ip:
198 sockaddr_in Addr;
199 memset(&Addr, 0, sizeof(Addr));
200 Addr.sin_family = AF_INET;
201 Addr.sin_port = htons(port);
202 Addr.sin_addr.s_addr = inet_addr(Address);
203 if (connect(sock, (sockaddr *)&Addr, sizeof(Addr)) < 0) {
204 LOG_ERROR;
205 Close();
206 return false;
207 }
208 // make it non-blocking:
209 int Flags = fcntl(sock, F_GETFL, 0);
210 if (Flags < 0) {
211 LOG_ERROR;
212 return false;
213 }
214 Flags |= O_NONBLOCK;
215 if (fcntl(sock, F_SETFL, Flags) < 0) {
216 LOG_ERROR;
217 return false;
218 }
219 dbgsvdrp("> %s:%d server connection established\n", Address, port);
220 isyslog("SVDRP %s > %s:%d server connection established", Setup.SVDRPHostName, Address, port);
221 return true;
222 }
223 return false;
224}
225
226bool cSocket::SendDgram(const char *Dgram, int Port)
227{
228 // Create a socket:
229 int Socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
230 if (Socket < 0) {
231 LOG_ERROR;
232 return false;
233 }
234 // Enable broadcast:
235 int One = 1;
236 if (setsockopt(Socket, SOL_SOCKET, SO_BROADCAST, &One, sizeof(One)) < 0) {
237 LOG_ERROR;
238 close(Socket);
239 return false;
240 }
241 // Configure port and ip:
242 sockaddr_in Addr;
243 memset(&Addr, 0, sizeof(Addr));
244 Addr.sin_family = AF_INET;
245 Addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
246 Addr.sin_port = htons(Port);
247 // Send datagram:
248 dbgsvdrp("> %s:%d %s\n", inet_ntoa(Addr.sin_addr), Port, Dgram);
249 dsyslog("SVDRP %s > %s:%d send dgram '%s'", Setup.SVDRPHostName, inet_ntoa(Addr.sin_addr), Port, Dgram);
250 int Length = strlen(Dgram);
251 int Sent = sendto(Socket, Dgram, Length, 0, (sockaddr *)&Addr, sizeof(Addr));
252 if (Sent < 0)
253 LOG_ERROR;
254 close(Socket);
255 return Sent == Length;
256}
257
259{
260 if (sock >= 0 && tcp) {
261 sockaddr_in Addr;
262 uint Size = sizeof(Addr);
263 int NewSock = accept(sock, (sockaddr *)&Addr, &Size);
264 if (NewSock >= 0) {
265 bool Accepted = SVDRPhosts.Acceptable(Addr.sin_addr.s_addr);
266 if (!Accepted) {
267 const char *s = "Access denied!\n";
268 if (write(NewSock, s, strlen(s)) < 0)
269 LOG_ERROR;
270 close(NewSock);
271 NewSock = -1;
272 }
273 lastIpAddress.Set((sockaddr *)&Addr);
274 dbgsvdrp("< %s client connection %s\n", lastIpAddress.Connection(), Accepted ? "accepted" : "DENIED");
275 isyslog("SVDRP %s < %s client connection %s", Setup.SVDRPHostName, lastIpAddress.Connection(), Accepted ? "accepted" : "DENIED");
276 }
277 else if (FATALERRNO)
278 LOG_ERROR;
279 return NewSock;
280 }
281 return -1;
282}
283
285{
286 if (sock >= 0 && !tcp) {
287 char buf[MAXUDPBUF];
288 sockaddr_in Addr;
289 uint Size = sizeof(Addr);
290 int NumBytes = recvfrom(sock, buf, sizeof(buf), 0, (sockaddr *)&Addr, &Size);
291 if (NumBytes >= 0) {
292 buf[NumBytes] = 0;
293 lastIpAddress.Set((sockaddr *)&Addr);
294 if (!SVDRPhosts.Acceptable(Addr.sin_addr.s_addr)) {
295 dsyslog("SVDRP %s < %s discovery ignored (%s)", Setup.SVDRPHostName, lastIpAddress.Connection(), buf);
296 return NULL;
297 }
298 if (!startswith(buf, "SVDRP:discover")) {
299 dsyslog("SVDRP %s < %s discovery unrecognized (%s)", Setup.SVDRPHostName, lastIpAddress.Connection(), buf);
300 return NULL;
301 }
302 if (strcmp(strgetval(buf, "name", ':'), Setup.SVDRPHostName) != 0) { // ignore our own broadcast
303 dbgsvdrp("< %s discovery received (%s)\n", lastIpAddress.Connection(), buf);
304 isyslog("SVDRP %s < %s discovery received (%s)", Setup.SVDRPHostName, lastIpAddress.Connection(), buf);
305 return buf;
306 }
307 }
308 else if (FATALERRNO)
309 LOG_ERROR;
310 }
311 return NULL;
312}
313
314// --- cSVDRPClient ----------------------------------------------------------
315
317private:
322 char *input;
328 bool Send(const char *Command);
329 void Close(void);
330public:
331 cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout);
333 const char *ServerName(void) const { return serverName; }
334 const char *Connection(void) const { return serverIpAddress.Connection(); }
335 bool HasAddress(const char *Address, int Port) const;
336 bool Process(cStringList *Response = NULL);
337 bool Execute(const char *Command, cStringList *Response = NULL);
338 bool Connected(void) const { return connected; }
339 void SetFetchFlag(int Flag);
340 bool HasFetchFlag(int Flag);
341 bool GetRemoteTimers(cStringList &Response);
342 };
343
345
346cSVDRPClient::cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout)
347:serverIpAddress(Address, Port)
348,socket(Port, true)
349{
351 length = BUFSIZ;
352 input = MALLOC(char, length);
353 timeout = Timeout * 1000 * 9 / 10; // ping after 90% of timeout
356 connected = false;
357 if (socket.Connect(Address)) {
358 if (file.Open(socket.Socket())) {
359 SVDRPClientPoller.Add(file, false);
360 dsyslog("SVDRP %s > %s client created for '%s'", Setup.SVDRPHostName, serverIpAddress.Connection(), *serverName);
361 return;
362 }
363 }
364 esyslog("SVDRP %s > %s ERROR: failed to create client for '%s'", Setup.SVDRPHostName, serverIpAddress.Connection(), *serverName);
365}
366
368{
369 Close();
370 free(input);
371 dsyslog("SVDRP %s > %s client destroyed for '%s'", Setup.SVDRPHostName, serverIpAddress.Connection(), *serverName);
372}
373
375{
376 if (file.IsOpen()) {
377 SVDRPClientPoller.Del(file, false);
378 file.Close();
379 socket.Close();
380 }
381}
382
383bool cSVDRPClient::HasAddress(const char *Address, int Port) const
384{
385 return strcmp(serverIpAddress.Address(), Address) == 0 && serverIpAddress.Port() == Port;
386}
387
388bool cSVDRPClient::Send(const char *Command)
389{
391 dbgsvdrp("> C %s: %s\n", *serverName, Command);
392 if (safe_write(file, Command, strlen(Command) + 1) < 0) {
393 LOG_ERROR;
394 return false;
395 }
396 return true;
397}
398
400{
401 if (file.IsOpen()) {
402 int numChars = 0;
403#define SVDRPResonseTimeout 5000 // ms
405 for (;;) {
406 if (file.Ready(false)) {
407 unsigned char c;
408 int r = safe_read(file, &c, 1);
409 if (r > 0) {
410 if (c == '\n' || c == 0x00) {
411 // strip trailing whitespace:
412 while (numChars > 0 && strchr(" \t\r\n", input[numChars - 1]))
413 input[--numChars] = 0;
414 // make sure the string is terminated:
415 input[numChars] = 0;
416 dbgsvdrp("< C %s: %s\n", *serverName, input);
417 if (Response)
418 Response->Append(strdup(input));
419 else {
420 switch (atoi(input)) {
421 case 220: if (numChars > 4) {
422 char *n = input + 4;
423 if (char *t = strchr(n, ' ')) {
424 *t = 0;
425 if (strcmp(n, serverName) != 0) {
426 serverName = n;
427 dsyslog("SVDRP %s < %s remote server name is '%s'", Setup.SVDRPHostName, serverIpAddress.Connection(), *serverName);
428 }
430 connected = true;
431 }
432 }
433 break;
434 case 221: dsyslog("SVDRP %s < %s remote server closed connection to '%s'", Setup.SVDRPHostName, serverIpAddress.Connection(), *serverName);
435 connected = false;
436 Close();
437 break;
438 }
439 }
440 if (numChars >= 4 && input[3] != '-') // no more lines will follow
441 break;
442 numChars = 0;
443 }
444 else {
445 if (numChars >= length - 1) {
446 int NewLength = length + BUFSIZ;
447 if (char *NewBuffer = (char *)realloc(input, NewLength)) {
448 length = NewLength;
449 input = NewBuffer;
450 }
451 else {
452 esyslog("SVDRP %s < %s ERROR: out of memory", Setup.SVDRPHostName, serverIpAddress.Connection());
453 Close();
454 break;
455 }
456 }
457 input[numChars++] = c;
458 input[numChars] = 0;
459 }
460 Timeout.Set(SVDRPResonseTimeout);
461 }
462 else if (r <= 0) {
463 isyslog("SVDRP %s < %s lost connection to remote server '%s'", Setup.SVDRPHostName, serverIpAddress.Connection(), *serverName);
464 Close();
465 return false;
466 }
467 }
468 else if (Timeout.TimedOut()) {
469 esyslog("SVDRP %s < %s timeout while waiting for response from '%s'", Setup.SVDRPHostName, serverIpAddress.Connection(), *serverName);
470 return false;
471 }
472 else if (!Response && numChars == 0)
473 break; // we read all or nothing!
474 }
475 if (pingTime.TimedOut())
477 }
478 return file.IsOpen();
479}
480
481bool cSVDRPClient::Execute(const char *Command, cStringList *Response)
482{
483 cStringList Dummy;
484 if (Response)
485 Response->Clear();
486 else
487 Response = &Dummy;
488 return Send(Command) && Process(Response);
489}
490
492{
493 fetchFlags |= Flags;
494}
495
497{
498 bool Result = (fetchFlags & Flag);
499 fetchFlags &= ~Flag;
500 return Result;
501}
502
504{
505 if (Execute("LSTT ID", &Response)) {
506 for (int i = 0; i < Response.Size(); i++) {
507 char *s = Response[i];
508 int Code = SVDRPCode(s);
509 if (Code == 250)
510 strshift(s, 4);
511 else if (Code == 550)
512 Response.Clear();
513 else {
514 esyslog("ERROR: %s: %s", ServerName(), s);
515 return false;
516 }
517 }
518 Response.SortNumerically();
519 return true;
520 }
521 return false;
522}
523
524
525// --- cSVDRPServerParams ----------------------------------------------------
526
528private:
530 int port;
536public:
537 cSVDRPServerParams(const char *Params);
538 const char *Name(void) const { return name; }
539 const int Port(void) const { return port; }
540 const char *VdrVersion(void) const { return vdrversion; }
541 const char *ApiVersion(void) const { return apiversion; }
542 const int Timeout(void) const { return timeout; }
543 const char *Host(void) const { return host; }
544 bool Ok(void) const { return !*error; }
545 const char *Error(void) const { return error; }
546 };
547
549{
550 if (Params && *Params) {
551 name = strgetval(Params, "name", ':');
552 if (*name) {
553 cString p = strgetval(Params, "port", ':');
554 if (*p) {
555 port = atoi(p);
556 vdrversion = strgetval(Params, "vdrversion", ':');
557 if (*vdrversion) {
558 apiversion = strgetval(Params, "apiversion", ':');
559 if (*apiversion) {
560 cString t = strgetval(Params, "timeout", ':');
561 if (*t) {
562 timeout = atoi(t);
563 if (timeout > 10) { // don't let it get too small
564 host = strgetval(Params, "host", ':');
565 // no error if missing - this parameter is optional!
566 }
567 else
568 error = "invalid timeout";
569 }
570 else
571 error = "missing server timeout";
572 }
573 else
574 error = "missing server apiversion";
575 }
576 else
577 error = "missing server vdrversion";
578 }
579 else
580 error = "missing server port";
581 }
582 else
583 error = "missing server name";
584 }
585 else
586 error = "missing server parameters";
587}
588
589// --- cSVDRPClientHandler ---------------------------------------------------
590
592
594private:
599 void SendDiscover(void);
600 void HandleClientConnection(void);
601 void ProcessConnections(void);
602 cSVDRPClient *GetClientForServer(const char *ServerName);
603protected:
604 virtual void Action(void);
605public:
606 cSVDRPClientHandler(int TcpPort, int UdpPort);
607 virtual ~cSVDRPClientHandler();
608 void Lock(void) { mutex.Lock(); }
609 void Unlock(void) { mutex.Unlock(); }
610 void AddClient(cSVDRPServerParams &ServerParams, const char *IpAddress);
611 bool Execute(const char *ServerName, const char *Command, cStringList *Response = NULL);
612 bool GetServerNames(cStringList *ServerNames);
613 bool TriggerFetchingTimers(const char *ServerName);
614 };
615
617
619:cThread("SVDRP client handler", true)
620,udpSocket(UdpPort, false)
621{
622 tcpPort = TcpPort;
623}
624
626{
627 Cancel(3);
628 for (int i = 0; i < clientConnections.Size(); i++)
629 delete clientConnections[i];
630}
631
633{
634 for (int i = 0; i < clientConnections.Size(); i++) {
635 if (strcmp(clientConnections[i]->ServerName(), ServerName) == 0)
636 return clientConnections[i];
637 }
638 return NULL;
639}
640
642{
643 cString Dgram = cString::sprintf("SVDRP:discover name:%s port:%d vdrversion:%d apiversion:%d timeout:%d%s", Setup.SVDRPHostName, tcpPort, VDRVERSNUM, APIVERSNUM, Setup.SVDRPTimeout, (Setup.SVDRPPeering == spmOnly && *Setup.SVDRPDefaultHost) ? *cString::sprintf(" host:%s", Setup.SVDRPDefaultHost) : "");
645}
646
648{
649 cString PollTimersCmd;
651 PollTimersCmd = cString::sprintf("POLL %s TIMERS", Setup.SVDRPHostName);
653 }
655 return; // try again next time
656 for (int i = 0; i < clientConnections.Size(); i++) {
657 cSVDRPClient *Client = clientConnections[i];
658 if (Client->Process()) {
659 if (Client->HasFetchFlag(sffConn))
660 Client->Execute(cString::sprintf("CONN name:%s port:%d vdrversion:%d apiversion:%d timeout:%d", Setup.SVDRPHostName, SVDRPTcpPort, VDRVERSNUM, APIVERSNUM, Setup.SVDRPTimeout));
661 if (Client->HasFetchFlag(sffPing))
662 Client->Execute("PING");
663 if (Client->HasFetchFlag(sffTimers)) {
664 cStringList RemoteTimers;
665 if (Client->GetRemoteTimers(RemoteTimers)) {
667 bool TimersModified = Timers->StoreRemoteTimers(Client->ServerName(), &RemoteTimers);
669 }
670 else
671 Client->SetFetchFlag(sffTimers); // try again next time
672 }
673 }
674 if (*PollTimersCmd) {
675 if (!Client->Execute(PollTimersCmd))
676 esyslog("ERROR: can't send '%s' to '%s'", *PollTimersCmd, Client->ServerName());
677 }
678 }
679 else {
681 bool TimersModified = Timers->StoreRemoteTimers(Client->ServerName(), NULL);
683 delete Client;
685 i--;
686 }
687 }
688}
689
690void cSVDRPClientHandler::AddClient(cSVDRPServerParams &ServerParams, const char *IpAddress)
691{
692 cMutexLock MutexLock(&mutex);
693 for (int i = 0; i < clientConnections.Size(); i++) {
694 if (clientConnections[i]->HasAddress(IpAddress, ServerParams.Port()))
695 return;
696 }
697 if (Setup.SVDRPPeering == spmOnly && strcmp(ServerParams.Name(), Setup.SVDRPDefaultHost) != 0)
698 return; // we only want to peer with the default host, but this isn't the default host
699 if (ServerParams.Host() && strcmp(ServerParams.Host(), Setup.SVDRPHostName) != 0)
700 return; // the remote VDR requests a specific host, but it's not us
701 clientConnections.Append(new cSVDRPClient(IpAddress, ServerParams.Port(), ServerParams.Name(), ServerParams.Timeout()));
702}
703
705{
706 cString NewDiscover = udpSocket.Discover();
707 if (*NewDiscover) {
708 cSVDRPServerParams ServerParams(NewDiscover);
709 if (ServerParams.Ok())
710 AddClient(ServerParams, udpSocket.LastIpAddress()->Address());
711 else
712 esyslog("SVDRP %s < %s ERROR: %s", Setup.SVDRPHostName, udpSocket.LastIpAddress()->Connection(), ServerParams.Error());
713 }
714}
715
717{
718 if (udpSocket.Listen()) {
720 SendDiscover();
721 while (Running()) {
723 cMutexLock MutexLock(&mutex);
726 }
729 }
730}
731
732bool cSVDRPClientHandler::Execute(const char *ServerName, const char *Command, cStringList *Response)
733{
734 cMutexLock MutexLock(&mutex);
735 if (cSVDRPClient *Client = GetClientForServer(ServerName))
736 return Client->Execute(Command, Response);
737 return false;
738}
739
741{
742 cMutexLock MutexLock(&mutex);
743 ServerNames->Clear();
744 for (int i = 0; i < clientConnections.Size(); i++) {
745 cSVDRPClient *Client = clientConnections[i];
746 if (Client->Connected())
747 ServerNames->Append(strdup(Client->ServerName()));
748 }
749 return ServerNames->Size() > 0;
750}
751
753{
754 cMutexLock MutexLock(&mutex);
755 if (cSVDRPClient *Client = GetClientForServer(ServerName)) {
756 Client->SetFetchFlag(sffTimers);
757 return true;
758 }
759 return false;
760}
761
762// --- cPUTEhandler ----------------------------------------------------------
763
765private:
766 FILE *f;
768 const char *message;
769public:
770 cPUTEhandler(void);
772 bool Process(const char *s);
773 int Status(void) { return status; }
774 const char *Message(void) { return message; }
775 };
776
778{
779 if ((f = tmpfile()) != NULL) {
780 status = 354;
781 message = "Enter EPG data, end with \".\" on a line by itself";
782 }
783 else {
784 LOG_ERROR;
785 status = 554;
786 message = "Error while opening temporary file";
787 }
788}
789
791{
792 if (f)
793 fclose(f);
794}
795
796bool cPUTEhandler::Process(const char *s)
797{
798 if (f) {
799 if (strcmp(s, ".") != 0) {
800 fputs(s, f);
801 fputc('\n', f);
802 return true;
803 }
804 else {
805 rewind(f);
806 if (cSchedules::Read(f)) {
808 status = 250;
809 message = "EPG data processed";
810 }
811 else {
812 status = 451;
813 message = "Error while processing EPG data";
814 }
815 fclose(f);
816 f = NULL;
817 }
818 }
819 return false;
820}
821
822// --- cSVDRPServer ----------------------------------------------------------
823
824#define MAXHELPTOPIC 10
825#define EITDISABLETIME 10 // seconds until EIT processing is enabled again after a CLRE command
826 // adjust the help for CLRE accordingly if changing this!
827
828const char *HelpPages[] = {
829 "CHAN [ + | - | <number> | <name> | <id> ]\n"
830 " Switch channel up, down or to the given channel number, name or id.\n"
831 " Without option (or after successfully switching to the channel)\n"
832 " it returns the current channel number and name.",
833 "CLRE [ <number> | <name> | <id> ]\n"
834 " Clear the EPG list of the given channel number, name or id.\n"
835 " Without option it clears the entire EPG list.\n"
836 " After a CLRE command, no further EPG processing is done for 10\n"
837 " seconds, so that data sent with subsequent PUTE commands doesn't\n"
838 " interfere with data from the broadcasters.",
839 "CONN name:<name> port:<port> vdrversion:<vdrversion> apiversion:<apiversion> timeout:<timeout>\n"
840 " Used by peer-to-peer connections between VDRs to tell the other VDR\n"
841 " to establish a connection to this VDR. The name is the SVDRP host name\n"
842 " of this VDR, which may differ from its DNS name.",
843 "CPYR <number> <new name>\n"
844 " Copy the recording with the given number. Before a recording can be\n"
845 " copied, an LSTR command must have been executed in order to retrieve\n"
846 " the recording numbers.\n",
847 "DELC <number> | <id>\n"
848 " Delete the channel with the given number or channel id.",
849 "DELR <id>\n"
850 " Delete the recording with the given id. Before a recording can be\n"
851 " deleted, an LSTR command should have been executed in order to retrieve\n"
852 " the recording ids. The ids are unique and don't change while this\n"
853 " instance of VDR is running.\n"
854 " CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n"
855 " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
856 "DELT <id>\n"
857 " Delete the timer with the given id. If this timer is currently recording,\n"
858 " the recording will be stopped without any warning.",
859 "EDIT <id>\n"
860 " Edit the recording with the given id. Before a recording can be\n"
861 " edited, an LSTR command should have been executed in order to retrieve\n"
862 " the recording ids.",
863 "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n"
864 " Grab the current frame and save it to the given file. Images can\n"
865 " be stored as JPEG or PNM, depending on the given file name extension.\n"
866 " The quality of the grabbed image can be in the range 0..100, where 100\n"
867 " (the default) means \"best\" (only applies to JPEG). The size parameters\n"
868 " define the size of the resulting image (default is full screen).\n"
869 " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n"
870 " data will be sent to the SVDRP connection encoded in base64. The same\n"
871 " happens if '-' (a minus sign) is given as file name, in which case the\n"
872 " image format defaults to JPEG.",
873 "HELP [ <topic> ]\n"
874 " The HELP command gives help info.",
875 "HITK [ <key> ... ]\n"
876 " Hit the given remote control key. Without option a list of all\n"
877 " valid key names is given. If more than one key is given, they are\n"
878 " entered into the remote control queue in the given sequence. There\n"
879 " can be up to 31 keys.",
880 "LSTC [ :ids ] [ :groups | <number> | <name> | <id> ]\n"
881 " List channels. Without option, all channels are listed. Otherwise\n"
882 " only the given channel is listed. If a name is given, all channels\n"
883 " containing the given string as part of their name are listed.\n"
884 " If ':groups' is given, all channels are listed including group\n"
885 " separators. The channel number of a group separator is always 0.\n"
886 " With ':ids' the channel ids are listed following the channel numbers.\n"
887 " The special number 0 can be given to list the current channel.",
888 "LSTD\n"
889 " List all available devices. Each device is listed with its name and\n"
890 " whether it is currently the primary device ('P') or it implements a\n"
891 " decoder ('D') and can be used as output device.",
892 "LSTE [ <channel> ] [ now | next | at <time> ]\n"
893 " List EPG data. Without any parameters all data of all channels is\n"
894 " listed. If a channel is given (either by number or by channel ID),\n"
895 " only data for that channel is listed. 'now', 'next', or 'at <time>'\n"
896 " restricts the returned data to present events, following events, or\n"
897 " events at the given time (which must be in time_t form).",
898 "LSTR [ <id> [ path ] ]\n"
899 " List recordings. Without option, all recordings are listed. Otherwise\n"
900 " the information for the given recording is listed. If a recording\n"
901 " id and the keyword 'path' is given, the actual file name of that\n"
902 " recording's directory is listed.\n"
903 " Note that the ids of the recordings are not necessarily given in\n"
904 " numeric order.",
905 "LSTT [ <id> ] [ id ]\n"
906 " List timers. Without option, all timers are listed. Otherwise\n"
907 " only the timer with the given id is listed. If the keyword 'id' is\n"
908 " given, the channels will be listed with their unique channel ids\n"
909 " instead of their numbers. This command lists only the timers that are\n"
910 " defined locally on this VDR, not any remote timers from other VDRs.",
911 "MESG <message>\n"
912 " Displays the given message on the OSD. The message will be queued\n"
913 " and displayed whenever this is suitable.\n",
914 "MODC <number> <settings>\n"
915 " Modify a channel. Settings must be in the same format as returned\n"
916 " by the LSTC command.",
917 "MODT <id> on | off | <settings>\n"
918 " Modify a timer. Settings must be in the same format as returned\n"
919 " by the LSTT command. The special keywords 'on' and 'off' can be\n"
920 " used to easily activate or deactivate a timer.",
921 "MOVC <number> <to>\n"
922 " Move a channel to a new position.",
923 "MOVR <id> <new name>\n"
924 " Move the recording with the given id. Before a recording can be\n"
925 " moved, an LSTR command should have been executed in order to retrieve\n"
926 " the recording ids. The ids don't change during subsequent MOVR\n"
927 " commands.\n",
928 "NEWC <settings>\n"
929 " Create a new channel. Settings must be in the same format as returned\n"
930 " by the LSTC command.",
931 "NEWT <settings>\n"
932 " Create a new timer. Settings must be in the same format as returned\n"
933 " by the LSTT command.",
934 "NEXT [ abs | rel ]\n"
935 " Show the next timer event. If no option is given, the output will be\n"
936 " in human readable form. With option 'abs' the absolute time of the next\n"
937 " event will be given as the number of seconds since the epoch (time_t\n"
938 " format), while with option 'rel' the relative time will be given as the\n"
939 " number of seconds from now until the event. If the absolute time given\n"
940 " is smaller than the current time, or if the relative time is less than\n"
941 " zero, this means that the timer is currently recording and has started\n"
942 " at the given time. The first value in the resulting line is the id\n"
943 " of the timer.",
944 "PING\n"
945 " Used by peer-to-peer connections between VDRs to keep the connection\n"
946 " from timing out. May be used at any time and simply returns a line of\n"
947 " the form '<hostname> is alive'.",
948 "PLAY <id> [ begin | <position> ]\n"
949 " Play the recording with the given id. Before a recording can be\n"
950 " played, an LSTR command should have been executed in order to retrieve\n"
951 " the recording ids.\n"
952 " The keyword 'begin' plays the recording from its very beginning, while\n"
953 " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n"
954 " position. If neither 'begin' nor a <position> are given, replay is resumed\n"
955 " at the position where any previous replay was stopped, or from the beginning\n"
956 " by default. To control or stop the replay session, use the usual remote\n"
957 " control keypresses via the HITK command.",
958 "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n"
959 " Send a command to a plugin.\n"
960 " The PLUG command without any parameters lists all plugins.\n"
961 " If only a name is given, all commands known to that plugin are listed.\n"
962 " If a command is given (optionally followed by parameters), that command\n"
963 " is sent to the plugin, and the result will be displayed.\n"
964 " The keyword 'help' lists all the SVDRP commands known to the named plugin.\n"
965 " If 'help' is followed by a command, the detailed help for that command is\n"
966 " given. The keyword 'main' initiates a call to the main menu function of the\n"
967 " given plugin.\n",
968 "POLL <name> timers\n"
969 " Used by peer-to-peer connections between VDRs to inform other machines\n"
970 " about changes to timers. The receiving VDR shall use LSTT to query the\n"
971 " remote machine with the given name about its timers and update its list\n"
972 " of timers accordingly.\n",
973 "PRIM [ <number> ]\n"
974 " Make the device with the given number the primary device.\n"
975 " Without option it returns the currently active primary device in the same\n"
976 " format as used by the LSTD command.",
977 "PUTE [ <file> ]\n"
978 " Put data into the EPG list. The data entered has to strictly follow the\n"
979 " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n"
980 " by itself terminates the input and starts processing of the data (all\n"
981 " entered data is buffered until the terminating '.' is seen).\n"
982 " If a file name is given, epg data will be read from this file (which\n"
983 " must be accessible under the given name from the machine VDR is running\n"
984 " on). In case of file input, no terminating '.' shall be given.\n",
985 "REMO [ on | off ]\n"
986 " Turns the remote control on or off. Without a parameter, the current\n"
987 " status of the remote control is reported.",
988 "SCAN\n"
989 " Forces an EPG scan. If this is a single DVB device system, the scan\n"
990 " will be done on the primary device unless it is currently recording.",
991 "STAT disk\n"
992 " Return information about disk usage (total, free, percent).",
993 "UPDT <settings>\n"
994 " Updates a timer. Settings must be in the same format as returned\n"
995 " by the LSTT command. If a timer with the same channel, day, start\n"
996 " and stop time does not yet exist, it will be created.",
997 "UPDR\n"
998 " Initiates a re-read of the recordings directory, which is the SVDRP\n"
999 " equivalent to 'touch .update'.",
1000 "VOLU [ <number> | + | - | mute ]\n"
1001 " Set the audio volume to the given number (which is limited to the range\n"
1002 " 0...255). If the special options '+' or '-' are given, the volume will\n"
1003 " be turned up or down, respectively. The option 'mute' will toggle the\n"
1004 " audio muting. If no option is given, the current audio volume level will\n"
1005 " be returned.",
1006 "QUIT\n"
1007 " Exit vdr (SVDRP).\n"
1008 " You can also hit Ctrl-D to exit.",
1009 NULL
1010 };
1011
1012/* SVDRP Reply Codes:
1013
1014 214 Help message
1015 215 EPG or recording data record
1016 216 Image grab data (base 64)
1017 220 VDR service ready
1018 221 VDR service closing transmission channel
1019 250 Requested VDR action okay, completed
1020 354 Start sending EPG data
1021 451 Requested action aborted: local error in processing
1022 500 Syntax error, command unrecognized
1023 501 Syntax error in parameters or arguments
1024 502 Command not implemented
1025 504 Command parameter not implemented
1026 550 Requested action not taken
1027 554 Transaction failed
1028 900 Default plugin reply code
1029 901..999 Plugin specific reply codes
1030
1031*/
1032
1033const char *GetHelpTopic(const char *HelpPage)
1034{
1035 static char topic[MAXHELPTOPIC];
1036 const char *q = HelpPage;
1037 while (*q) {
1038 if (isspace(*q)) {
1039 uint n = q - HelpPage;
1040 if (n >= sizeof(topic))
1041 n = sizeof(topic) - 1;
1042 strncpy(topic, HelpPage, n);
1043 topic[n] = 0;
1044 return topic;
1045 }
1046 q++;
1047 }
1048 return NULL;
1049}
1050
1051const char *GetHelpPage(const char *Cmd, const char **p)
1052{
1053 if (p) {
1054 while (*p) {
1055 const char *t = GetHelpTopic(*p);
1056 if (strcasecmp(Cmd, t) == 0)
1057 return *p;
1058 p++;
1059 }
1060 }
1061 return NULL;
1062}
1063
1065
1067private:
1075 char *cmdLine;
1077 void Close(bool SendReply = false, bool Timeout = false);
1078 bool Send(const char *s);
1079 void Reply(int Code, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
1080 void PrintHelpTopics(const char **hp);
1081 void CmdCHAN(const char *Option);
1082 void CmdCLRE(const char *Option);
1083 void CmdCONN(const char *Option);
1084 void CmdCPYR(const char *Option);
1085 void CmdDELC(const char *Option);
1086 void CmdDELR(const char *Option);
1087 void CmdDELT(const char *Option);
1088 void CmdEDIT(const char *Option);
1089 void CmdGRAB(const char *Option);
1090 void CmdHELP(const char *Option);
1091 void CmdHITK(const char *Option);
1092 void CmdLSTC(const char *Option);
1093 void CmdLSTD(const char *Option);
1094 void CmdLSTE(const char *Option);
1095 void CmdLSTR(const char *Option);
1096 void CmdLSTT(const char *Option);
1097 void CmdMESG(const char *Option);
1098 void CmdMODC(const char *Option);
1099 void CmdMODT(const char *Option);
1100 void CmdMOVC(const char *Option);
1101 void CmdMOVR(const char *Option);
1102 void CmdNEWC(const char *Option);
1103 void CmdNEWT(const char *Option);
1104 void CmdNEXT(const char *Option);
1105 void CmdPING(const char *Option);
1106 void CmdPLAY(const char *Option);
1107 void CmdPLUG(const char *Option);
1108 void CmdPOLL(const char *Option);
1109 void CmdPRIM(const char *Option);
1110 void CmdPUTE(const char *Option);
1111 void CmdREMO(const char *Option);
1112 void CmdSCAN(const char *Option);
1113 void CmdSTAT(const char *Option);
1114 void CmdUPDT(const char *Option);
1115 void CmdUPDR(const char *Option);
1116 void CmdVOLU(const char *Option);
1117 void Execute(char *Cmd);
1118public:
1119 cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress);
1120 ~cSVDRPServer();
1121 const char *ClientName(void) const { return clientName; }
1122 bool HasConnection(void) { return file.IsOpen(); }
1123 bool Process(void);
1124 };
1125
1127
1128cSVDRPServer::cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress)
1129{
1130 socket = Socket;
1131 clientIpAddress = *ClientIpAddress;
1132 clientName = clientIpAddress.Connection(); // will be set to actual name by a CONN command
1133 PUTEhandler = NULL;
1134 numChars = 0;
1135 length = BUFSIZ;
1136 cmdLine = MALLOC(char, length);
1137 lastActivity = time(NULL);
1138 if (file.Open(socket)) {
1139 time_t now = time(NULL);
1140 Reply(220, "%s SVDRP VideoDiskRecorder %s; %s; %s", Setup.SVDRPHostName, VDRVERSION, *TimeToString(now), cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8");
1141 SVDRPServerPoller.Add(file, false);
1142 }
1143 dsyslog("SVDRP %s > %s server created", Setup.SVDRPHostName, *clientName);
1144}
1145
1147{
1148 Close(true);
1149 free(cmdLine);
1150 dsyslog("SVDRP %s < %s server destroyed", Setup.SVDRPHostName, *clientName);
1151}
1152
1153void cSVDRPServer::Close(bool SendReply, bool Timeout)
1154{
1155 if (file.IsOpen()) {
1156 if (SendReply) {
1157 Reply(221, "%s closing connection%s", Setup.SVDRPHostName, Timeout ? " (timeout)" : "");
1158 }
1159 isyslog("SVDRP %s < %s connection closed", Setup.SVDRPHostName, *clientName);
1160 SVDRPServerPoller.Del(file, false);
1161 file.Close();
1163 }
1164 close(socket);
1165}
1166
1167bool cSVDRPServer::Send(const char *s)
1168{
1169 dbgsvdrp("> S %s: %s", *clientName, s); // terminating newline is already in the string!
1170 if (safe_write(file, s, strlen(s)) < 0) {
1171 LOG_ERROR;
1172 Close();
1173 return false;
1174 }
1175 return true;
1176}
1177
1178void cSVDRPServer::Reply(int Code, const char *fmt, ...)
1179{
1180 if (file.IsOpen()) {
1181 if (Code != 0) {
1182 char *buffer = NULL;
1183 va_list ap;
1184 va_start(ap, fmt);
1185 if (vasprintf(&buffer, fmt, ap) >= 0) {
1186 char *s = buffer;
1187 while (s && *s) {
1188 char *n = strchr(s, '\n');
1189 if (n)
1190 *n = 0;
1191 char cont = ' ';
1192 if (Code < 0 || n && *(n + 1)) // trailing newlines don't count!
1193 cont = '-';
1194 if (!Send(cString::sprintf("%03d%c%s\r\n", abs(Code), cont, s)))
1195 break;
1196 s = n ? n + 1 : NULL;
1197 }
1198 }
1199 else {
1200 Reply(451, "Bad format - looks like a programming error!");
1201 esyslog("SVDRP %s < %s bad format!", Setup.SVDRPHostName, *clientName);
1202 }
1203 va_end(ap);
1204 free(buffer);
1205 }
1206 else {
1207 Reply(451, "Zero return code - looks like a programming error!");
1208 esyslog("SVDRP %s < %s zero return code!", Setup.SVDRPHostName, *clientName);
1209 }
1210 }
1211}
1212
1214{
1215 int NumPages = 0;
1216 if (hp) {
1217 while (*hp) {
1218 NumPages++;
1219 hp++;
1220 }
1221 hp -= NumPages;
1222 }
1223 const int TopicsPerLine = 5;
1224 int x = 0;
1225 for (int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
1226 char buffer[TopicsPerLine * MAXHELPTOPIC + 5];
1227 char *q = buffer;
1228 q += sprintf(q, " ");
1229 for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
1230 const char *topic = GetHelpTopic(hp[(y * TopicsPerLine + x)]);
1231 if (topic)
1232 q += sprintf(q, "%*s", -MAXHELPTOPIC, topic);
1233 }
1234 x = 0;
1235 Reply(-214, "%s", buffer);
1236 }
1237}
1238
1239void cSVDRPServer::CmdCHAN(const char *Option)
1240{
1242 if (*Option) {
1243 int n = -1;
1244 int d = 0;
1245 if (isnumber(Option)) {
1246 int o = strtol(Option, NULL, 10);
1247 if (o >= 1 && o <= cChannels::MaxNumber())
1248 n = o;
1249 }
1250 else if (strcmp(Option, "-") == 0) {
1252 if (n > 1) {
1253 n--;
1254 d = -1;
1255 }
1256 }
1257 else if (strcmp(Option, "+") == 0) {
1259 if (n < cChannels::MaxNumber()) {
1260 n++;
1261 d = 1;
1262 }
1263 }
1264 else if (const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(Option)))
1265 n = Channel->Number();
1266 else {
1267 for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1268 if (!Channel->GroupSep()) {
1269 if (strcasecmp(Channel->Name(), Option) == 0) {
1270 n = Channel->Number();
1271 break;
1272 }
1273 }
1274 }
1275 }
1276 if (n < 0) {
1277 Reply(501, "Undefined channel \"%s\"", Option);
1278 return;
1279 }
1280 if (!d) {
1281 if (const cChannel *Channel = Channels->GetByNumber(n)) {
1282 if (!cDevice::PrimaryDevice()->SwitchChannel(Channel, true)) {
1283 Reply(554, "Error switching to channel \"%d\"", Channel->Number());
1284 return;
1285 }
1286 }
1287 else {
1288 Reply(550, "Unable to find channel \"%s\"", Option);
1289 return;
1290 }
1291 }
1292 else
1294 }
1295 if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel()))
1296 Reply(250, "%d %s", Channel->Number(), Channel->Name());
1297 else
1298 Reply(550, "Unable to find channel \"%d\"", cDevice::CurrentChannel());
1299}
1300
1301void cSVDRPServer::CmdCLRE(const char *Option)
1302{
1303 if (*Option) {
1307 if (isnumber(Option)) {
1308 int o = strtol(Option, NULL, 10);
1309 if (o >= 1 && o <= cChannels::MaxNumber()) {
1310 if (const cChannel *Channel = Channels->GetByNumber(o))
1311 ChannelID = Channel->GetChannelID();
1312 }
1313 }
1314 else {
1315 ChannelID = tChannelID::FromString(Option);
1316 if (ChannelID == tChannelID::InvalidID) {
1317 for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1318 if (!Channel->GroupSep()) {
1319 if (strcasecmp(Channel->Name(), Option) == 0) {
1320 ChannelID = Channel->GetChannelID();
1321 break;
1322 }
1323 }
1324 }
1325 }
1326 }
1327 if (!(ChannelID == tChannelID::InvalidID)) {
1329 cSchedule *Schedule = NULL;
1330 ChannelID.ClrRid();
1331 for (cSchedule *p = Schedules->First(); p; p = Schedules->Next(p)) {
1332 if (p->ChannelID() == ChannelID) {
1333 Schedule = p;
1334 break;
1335 }
1336 }
1337 if (Schedule) {
1338 for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
1339 if (ChannelID == Timer->Channel()->GetChannelID().ClrRid())
1340 Timer->SetEvent(NULL);
1341 }
1342 Schedule->Cleanup(INT_MAX);
1344 Reply(250, "EPG data of channel \"%s\" cleared", Option);
1345 }
1346 else {
1347 Reply(550, "No EPG data found for channel \"%s\"", Option);
1348 return;
1349 }
1350 }
1351 else
1352 Reply(501, "Undefined channel \"%s\"", Option);
1353 }
1354 else {
1357 for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer))
1358 Timer->SetEvent(NULL); // processing all timers here (local *and* remote)
1359 for (cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->Next(Schedule))
1360 Schedule->Cleanup(INT_MAX);
1362 Reply(250, "EPG data cleared");
1363 }
1364}
1365
1366void cSVDRPServer::CmdCONN(const char *Option)
1367{
1368 if (*Option) {
1369 if (SVDRPClientHandler) {
1370 cSVDRPServerParams ServerParams(Option);
1371 if (ServerParams.Ok()) {
1372 clientName = ServerParams.Name();
1373 Reply(250, "OK"); // must finish this transaction before creating the new client
1375 }
1376 else
1377 Reply(501, "Error in server parameters: %s", ServerParams.Error());
1378 }
1379 else
1380 Reply(451, "No SVDRP client handler");
1381 }
1382 else
1383 Reply(501, "Missing server parameters");
1384}
1385
1386void cSVDRPServer::CmdDELC(const char *Option)
1387{
1388 if (*Option) {
1391 Channels->SetExplicitModify();
1392 cChannel *Channel = NULL;
1393 if (isnumber(Option))
1394 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1395 else
1396 Channel = Channels->GetByChannelID(tChannelID::FromString(Option));
1397 if (Channel) {
1398 if (const cTimer *Timer = Timers->UsesChannel(Channel)) {
1399 Reply(550, "Channel \"%s\" is in use by timer %s", Option, *Timer->ToDescr());
1400 return;
1401 }
1402 int CurrentChannelNr = cDevice::CurrentChannel();
1403 cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
1404 if (CurrentChannel && Channel == CurrentChannel) {
1405 int n = Channels->GetNextNormal(CurrentChannel->Index());
1406 if (n < 0)
1407 n = Channels->GetPrevNormal(CurrentChannel->Index());
1408 if (n < 0) {
1409 Reply(501, "Can't delete channel \"%s\" - list would be empty", Option);
1410 return;
1411 }
1412 CurrentChannel = Channels->Get(n);
1413 CurrentChannelNr = 0; // triggers channel switch below
1414 }
1415 Channels->Del(Channel);
1416 Channels->ReNumber();
1417 Channels->SetModifiedByUser();
1418 Channels->SetModified();
1419 isyslog("SVDRP %s < %s deleted channel %s", Setup.SVDRPHostName, *clientName, Option);
1420 if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
1421 if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
1422 Channels->SwitchTo(CurrentChannel->Number());
1423 else
1424 cDevice::SetCurrentChannel(CurrentChannel->Number());
1425 }
1426 Reply(250, "Channel \"%s\" deleted", Option);
1427 }
1428 else
1429 Reply(501, "Channel \"%s\" not defined", Option);
1430 }
1431 else
1432 Reply(501, "Missing channel number or id");
1433}
1434
1435static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
1436{
1437 cRecordControl *rc;
1438 if ((Reason & ruTimer) != 0 && (rc = cRecordControls::GetRecordControl(Recording->FileName())) != NULL)
1439 return cString::sprintf("Recording \"%s\" is in use by timer %d", RecordingId, rc->Timer()->Id());
1440 else if ((Reason & ruReplay) != 0)
1441 return cString::sprintf("Recording \"%s\" is being replayed", RecordingId);
1442 else if ((Reason & ruCut) != 0)
1443 return cString::sprintf("Recording \"%s\" is being edited", RecordingId);
1444 else if ((Reason & (ruMove | ruCopy)) != 0)
1445 return cString::sprintf("Recording \"%s\" is being copied/moved", RecordingId);
1446 else if (Reason)
1447 return cString::sprintf("Recording \"%s\" is in use", RecordingId);
1448 return NULL;
1449}
1450
1451void cSVDRPServer::CmdCPYR(const char *Option)
1452{
1453 if (*Option) {
1454 char *opt = strdup(Option);
1455 char *num = skipspace(opt);
1456 char *option = num;
1457 while (*option && !isspace(*option))
1458 option++;
1459 char c = *option;
1460 *option = 0;
1461 if (isnumber(num)) {
1463 Recordings->SetExplicitModify();
1464 if (cRecording *Recording = Recordings->Get(strtol(num, NULL, 10) - 1)) {
1465 if (int RecordingInUse = Recording->IsInUse())
1466 Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
1467 else {
1468 if (c)
1469 option = skipspace(++option);
1470 if (*option) {
1471 cString newName = option;
1473 if (strcmp(newName, Recording->Name())) {
1474 cString fromName = cString(ExchangeChars(strdup(Recording->Name()), true), true);
1475 cString toName = cString(ExchangeChars(strdup(*newName), true), true);
1476 cString fileName = cString(strreplace(strdup(Recording->FileName()), *fromName, *toName), true);
1477 if (MakeDirs(fileName, true) && !RecordingsHandler.Add(ruCopy, Recording->FileName(), fileName)) {
1478 Recordings->AddByName(fileName);
1479 Reply(250, "Recording \"%s\" copied to \"%s\"", Recording->Name(), *newName);
1480 }
1481 else
1482 Reply(554, "Error while copying recording \"%s\" to \"%s\"!", Recording->Name(), *newName);
1483 }
1484 else
1485 Reply(501, "Identical new recording name");
1486 }
1487 else
1488 Reply(501, "Missing new recording name");
1489 }
1490 }
1491 else
1492 Reply(550, "Recording \"%s\" not found", num);
1493 }
1494 else
1495 Reply(501, "Error in recording number \"%s\"", num);
1496 free(opt);
1497 }
1498 else
1499 Reply(501, "Missing recording number");
1500}
1501
1502void cSVDRPServer::CmdDELR(const char *Option)
1503{
1504 if (*Option) {
1505 if (isnumber(Option)) {
1507 Recordings->SetExplicitModify();
1508 if (cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1509 if (int RecordingInUse = Recording->IsInUse())
1510 Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
1511 else {
1512 if (Recording->Delete()) {
1513 Recordings->DelByName(Recording->FileName());
1514 Recordings->SetModified();
1515 isyslog("SVDRP %s < %s deleted recording %s", Setup.SVDRPHostName, *clientName, Option);
1516 Reply(250, "Recording \"%s\" deleted", Option);
1517 }
1518 else
1519 Reply(554, "Error while deleting recording!");
1520 }
1521 }
1522 else
1523 Reply(550, "Recording \"%s\" not found", Option);
1524 }
1525 else
1526 Reply(501, "Error in recording id \"%s\"", Option);
1527 }
1528 else
1529 Reply(501, "Missing recording id");
1530}
1531
1532void cSVDRPServer::CmdDELT(const char *Option)
1533{
1534 if (*Option) {
1535 if (isnumber(Option)) {
1537 Timers->SetExplicitModify();
1538 if (cTimer *Timer = Timers->GetById(strtol(Option, NULL, 10))) {
1539 if (Timer->Recording()) {
1540 Timer->Skip();
1541 cRecordControls::Process(Timers, time(NULL));
1542 }
1543 Timer->TriggerRespawn();
1544 Timers->Del(Timer);
1545 Timers->SetModified();
1546 isyslog("SVDRP %s < %s deleted timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
1547 Reply(250, "Timer \"%s\" deleted", Option);
1548 }
1549 else
1550 Reply(501, "Timer \"%s\" not defined", Option);
1551 }
1552 else
1553 Reply(501, "Error in timer number \"%s\"", Option);
1554 }
1555 else
1556 Reply(501, "Missing timer number");
1557}
1558
1559void cSVDRPServer::CmdEDIT(const char *Option)
1560{
1561 if (*Option) {
1562 if (isnumber(Option)) {
1564 if (const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1565 cMarks Marks;
1566 if (Marks.Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.Count()) {
1567 if (RecordingsHandler.Add(ruCut, Recording->FileName()))
1568 Reply(250, "Editing recording \"%s\" [%s]", Option, Recording->Title());
1569 else
1570 Reply(554, "Can't start editing process");
1571 }
1572 else
1573 Reply(554, "No editing marks defined");
1574 }
1575 else
1576 Reply(550, "Recording \"%s\" not found", Option);
1577 }
1578 else
1579 Reply(501, "Error in recording id \"%s\"", Option);
1580 }
1581 else
1582 Reply(501, "Missing recording id");
1583}
1584
1585void cSVDRPServer::CmdGRAB(const char *Option)
1586{
1587 const char *FileName = NULL;
1588 bool Jpeg = true;
1589 int Quality = -1, SizeX = -1, SizeY = -1;
1590 if (*Option) {
1591 char buf[strlen(Option) + 1];
1592 char *p = strcpy(buf, Option);
1593 const char *delim = " \t";
1594 char *strtok_next;
1595 FileName = strtok_r(p, delim, &strtok_next);
1596 // image type:
1597 const char *Extension = strrchr(FileName, '.');
1598 if (Extension) {
1599 if (strcasecmp(Extension, ".jpg") == 0 || strcasecmp(Extension, ".jpeg") == 0)
1600 Jpeg = true;
1601 else if (strcasecmp(Extension, ".pnm") == 0)
1602 Jpeg = false;
1603 else {
1604 Reply(501, "Unknown image type \"%s\"", Extension + 1);
1605 return;
1606 }
1607 if (Extension == FileName)
1608 FileName = NULL;
1609 }
1610 else if (strcmp(FileName, "-") == 0)
1611 FileName = NULL;
1612 // image quality (and obsolete type):
1613 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1614 if (strcasecmp(p, "JPEG") == 0 || strcasecmp(p, "PNM") == 0) {
1615 // tolerate for backward compatibility
1616 p = strtok_r(NULL, delim, &strtok_next);
1617 }
1618 if (p) {
1619 if (isnumber(p))
1620 Quality = atoi(p);
1621 else {
1622 Reply(501, "Invalid quality \"%s\"", p);
1623 return;
1624 }
1625 }
1626 }
1627 // image size:
1628 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1629 if (isnumber(p))
1630 SizeX = atoi(p);
1631 else {
1632 Reply(501, "Invalid sizex \"%s\"", p);
1633 return;
1634 }
1635 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1636 if (isnumber(p))
1637 SizeY = atoi(p);
1638 else {
1639 Reply(501, "Invalid sizey \"%s\"", p);
1640 return;
1641 }
1642 }
1643 else {
1644 Reply(501, "Missing sizey");
1645 return;
1646 }
1647 }
1648 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1649 Reply(501, "Unexpected parameter \"%s\"", p);
1650 return;
1651 }
1652 // canonicalize the file name:
1653 char RealFileName[PATH_MAX];
1654 if (FileName) {
1655 if (*grabImageDir) {
1656 cString s(FileName);
1657 FileName = s;
1658 const char *slash = strrchr(FileName, '/');
1659 if (!slash) {
1660 s = AddDirectory(grabImageDir, FileName);
1661 FileName = s;
1662 }
1663 slash = strrchr(FileName, '/'); // there definitely is one
1664 cString t(s);
1665 t.Truncate(slash - FileName);
1666 char *r = realpath(t, RealFileName);
1667 if (!r) {
1668 LOG_ERROR_STR(FileName);
1669 Reply(501, "Invalid file name \"%s\"", FileName);
1670 return;
1671 }
1672 strcat(RealFileName, slash);
1673 FileName = RealFileName;
1674 if (strncmp(FileName, grabImageDir, strlen(grabImageDir)) != 0) {
1675 Reply(501, "Invalid file name \"%s\"", FileName);
1676 return;
1677 }
1678 }
1679 else {
1680 Reply(550, "Grabbing to file not allowed (use \"GRAB -\" instead)");
1681 return;
1682 }
1683 }
1684 // actual grabbing:
1685 int ImageSize;
1686 uchar *Image = cDevice::PrimaryDevice()->GrabImage(ImageSize, Jpeg, Quality, SizeX, SizeY);
1687 if (Image) {
1688 if (FileName) {
1689 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
1690 if (fd >= 0) {
1691 if (safe_write(fd, Image, ImageSize) == ImageSize) {
1692 dsyslog("SVDRP %s < %s grabbed image to %s", Setup.SVDRPHostName, *clientName, FileName);
1693 Reply(250, "Grabbed image %s", Option);
1694 }
1695 else {
1696 LOG_ERROR_STR(FileName);
1697 Reply(451, "Can't write to '%s'", FileName);
1698 }
1699 close(fd);
1700 }
1701 else {
1702 LOG_ERROR_STR(FileName);
1703 Reply(451, "Can't open '%s'", FileName);
1704 }
1705 }
1706 else {
1707 cBase64Encoder Base64(Image, ImageSize);
1708 const char *s;
1709 while ((s = Base64.NextLine()) != NULL)
1710 Reply(-216, "%s", s);
1711 Reply(216, "Grabbed image %s", Option);
1712 }
1713 free(Image);
1714 }
1715 else
1716 Reply(451, "Grab image failed");
1717 }
1718 else
1719 Reply(501, "Missing filename");
1720}
1721
1722void cSVDRPServer::CmdHELP(const char *Option)
1723{
1724 if (*Option) {
1725 const char *hp = GetHelpPage(Option, HelpPages);
1726 if (hp)
1727 Reply(-214, "%s", hp);
1728 else {
1729 Reply(504, "HELP topic \"%s\" unknown", Option);
1730 return;
1731 }
1732 }
1733 else {
1734 Reply(-214, "This is VDR version %s", VDRVERSION);
1735 Reply(-214, "Topics:");
1737 cPlugin *plugin;
1738 for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++) {
1739 const char **hp = plugin->SVDRPHelpPages();
1740 if (hp)
1741 Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
1742 PrintHelpTopics(hp);
1743 }
1744 Reply(-214, "To report bugs in the implementation send email to");
1745 Reply(-214, " vdr-bugs@tvdr.de");
1746 }
1747 Reply(214, "End of HELP info");
1748}
1749
1750void cSVDRPServer::CmdHITK(const char *Option)
1751{
1752 if (*Option) {
1753 if (!cRemote::Enabled()) {
1754 Reply(550, "Remote control currently disabled (key \"%s\" discarded)", Option);
1755 return;
1756 }
1757 char buf[strlen(Option) + 1];
1758 strcpy(buf, Option);
1759 const char *delim = " \t";
1760 char *strtok_next;
1761 char *p = strtok_r(buf, delim, &strtok_next);
1762 int NumKeys = 0;
1763 while (p) {
1764 eKeys k = cKey::FromString(p);
1765 if (k != kNone) {
1766 if (!cRemote::Put(k)) {
1767 Reply(451, "Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
1768 return;
1769 }
1770 }
1771 else {
1772 Reply(504, "Unknown key: \"%s\"", p);
1773 return;
1774 }
1775 NumKeys++;
1776 p = strtok_r(NULL, delim, &strtok_next);
1777 }
1778 Reply(250, "Key%s \"%s\" accepted", NumKeys > 1 ? "s" : "", Option);
1779 }
1780 else {
1781 Reply(-214, "Valid <key> names for the HITK command:");
1782 for (int i = 0; i < kNone; i++) {
1783 Reply(-214, " %s", cKey::ToString(eKeys(i)));
1784 }
1785 Reply(214, "End of key list");
1786 }
1787}
1788
1789void cSVDRPServer::CmdLSTC(const char *Option)
1790{
1792 bool WithChannelIds = startswith(Option, ":ids") && (Option[4] == ' ' || Option[4] == 0);
1793 if (WithChannelIds)
1794 Option = skipspace(Option + 4);
1795 bool WithGroupSeps = strcasecmp(Option, ":groups") == 0;
1796 if (*Option && !WithGroupSeps) {
1797 if (isnumber(Option)) {
1798 int n = strtol(Option, NULL, 10);
1799 if (n == 0)
1801 if (const cChannel *Channel = Channels->GetByNumber(n))
1802 Reply(250, "%d%s%s %s", Channel->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Channel->GetChannelID().ToString() : "", *Channel->ToText());
1803 else
1804 Reply(501, "Channel \"%s\" not defined", Option);
1805 }
1806 else {
1807 const cChannel *Next = Channels->GetByChannelID(tChannelID::FromString(Option));
1808 if (!Next) {
1809 for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1810 if (!Channel->GroupSep()) {
1811 if (strcasestr(Channel->Name(), Option)) {
1812 if (Next)
1813 Reply(-250, "%d%s%s %s", Next->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Next->GetChannelID().ToString() : "", *Next->ToText());
1814 Next = Channel;
1815 }
1816 }
1817 }
1818 }
1819 if (Next)
1820 Reply(250, "%d%s%s %s", Next->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Next->GetChannelID().ToString() : "", *Next->ToText());
1821 else
1822 Reply(501, "Channel \"%s\" not defined", Option);
1823 }
1824 }
1825 else if (cChannels::MaxNumber() >= 1) {
1826 for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1827 if (WithGroupSeps)
1828 Reply(Channel->Next() ? -250: 250, "%d%s%s %s", Channel->GroupSep() ? 0 : Channel->Number(), (WithChannelIds && !Channel->GroupSep()) ? " " : "", (WithChannelIds && !Channel->GroupSep()) ? *Channel->GetChannelID().ToString() : "", *Channel->ToText());
1829 else if (!Channel->GroupSep())
1830 Reply(Channel->Number() < cChannels::MaxNumber() ? -250 : 250, "%d%s%s %s", Channel->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Channel->GetChannelID().ToString() : "", *Channel->ToText());
1831 }
1832 }
1833 else
1834 Reply(550, "No channels defined");
1835}
1836
1837void cSVDRPServer::CmdLSTD(const char *Option)
1838{
1839 if (cDevice::NumDevices()) {
1840 for (int i = 0; i < cDevice::NumDevices(); i++) {
1841 if (const cDevice *d = cDevice::GetDevice(i))
1842 Reply(d->DeviceNumber() + 1 == cDevice::NumDevices() ? 250 : -250, "%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ? "D" : "-", d->DeviceNumber() + 1 == Setup.PrimaryDVB ? "P" : "-", *d->DeviceName());
1843 }
1844 }
1845 else
1846 Reply(550, "No devices found");
1847}
1848
1849void cSVDRPServer::CmdLSTE(const char *Option)
1850{
1853 const cSchedule* Schedule = NULL;
1854 eDumpMode DumpMode = dmAll;
1855 time_t AtTime = 0;
1856 if (*Option) {
1857 char buf[strlen(Option) + 1];
1858 strcpy(buf, Option);
1859 const char *delim = " \t";
1860 char *strtok_next;
1861 char *p = strtok_r(buf, delim, &strtok_next);
1862 while (p && DumpMode == dmAll) {
1863 if (strcasecmp(p, "NOW") == 0)
1864 DumpMode = dmPresent;
1865 else if (strcasecmp(p, "NEXT") == 0)
1866 DumpMode = dmFollowing;
1867 else if (strcasecmp(p, "AT") == 0) {
1868 DumpMode = dmAtTime;
1869 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1870 if (isnumber(p))
1871 AtTime = strtol(p, NULL, 10);
1872 else {
1873 Reply(501, "Invalid time");
1874 return;
1875 }
1876 }
1877 else {
1878 Reply(501, "Missing time");
1879 return;
1880 }
1881 }
1882 else if (!Schedule) {
1883 const cChannel* Channel = NULL;
1884 if (isnumber(p))
1885 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1886 else
1887 Channel = Channels->GetByChannelID(tChannelID::FromString(Option));
1888 if (Channel) {
1889 Schedule = Schedules->GetSchedule(Channel);
1890 if (!Schedule) {
1891 Reply(550, "No schedule found");
1892 return;
1893 }
1894 }
1895 else {
1896 Reply(550, "Channel \"%s\" not defined", p);
1897 return;
1898 }
1899 }
1900 else {
1901 Reply(501, "Unknown option: \"%s\"", p);
1902 return;
1903 }
1904 p = strtok_r(NULL, delim, &strtok_next);
1905 }
1906 }
1907 int fd = dup(file);
1908 if (fd) {
1909 FILE *f = fdopen(fd, "w");
1910 if (f) {
1911 if (Schedule)
1912 Schedule->Dump(Channels, f, "215-", DumpMode, AtTime);
1913 else
1914 Schedules->Dump(f, "215-", DumpMode, AtTime);
1915 fflush(f);
1916 Reply(215, "End of EPG data");
1917 fclose(f);
1918 }
1919 else {
1920 Reply(451, "Can't open file connection");
1921 close(fd);
1922 }
1923 }
1924 else
1925 Reply(451, "Can't dup stream descriptor");
1926}
1927
1928void cSVDRPServer::CmdLSTR(const char *Option)
1929{
1930 int Number = 0;
1931 bool Path = false;
1933 if (*Option) {
1934 char buf[strlen(Option) + 1];
1935 strcpy(buf, Option);
1936 const char *delim = " \t";
1937 char *strtok_next;
1938 char *p = strtok_r(buf, delim, &strtok_next);
1939 while (p) {
1940 if (!Number) {
1941 if (isnumber(p))
1942 Number = strtol(p, NULL, 10);
1943 else {
1944 Reply(501, "Error in recording id \"%s\"", Option);
1945 return;
1946 }
1947 }
1948 else if (strcasecmp(p, "PATH") == 0)
1949 Path = true;
1950 else {
1951 Reply(501, "Unknown option: \"%s\"", p);
1952 return;
1953 }
1954 p = strtok_r(NULL, delim, &strtok_next);
1955 }
1956 if (Number) {
1957 if (const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1958 FILE *f = fdopen(file, "w");
1959 if (f) {
1960 if (Path)
1961 Reply(250, "%s", Recording->FileName());
1962 else {
1963 Recording->Info()->Write(f, "215-");
1964 fflush(f);
1965 Reply(215, "End of recording information");
1966 }
1967 // don't 'fclose(f)' here!
1968 }
1969 else
1970 Reply(451, "Can't open file connection");
1971 }
1972 else
1973 Reply(550, "Recording \"%s\" not found", Option);
1974 }
1975 }
1976 else if (Recordings->Count()) {
1977 const cRecording *Recording = Recordings->First();
1978 while (Recording) {
1979 Reply(Recording == Recordings->Last() ? 250 : -250, "%d %s", Recording->Id(), Recording->Title(' ', true));
1980 Recording = Recordings->Next(Recording);
1981 }
1982 }
1983 else
1984 Reply(550, "No recordings available");
1985}
1986
1987void cSVDRPServer::CmdLSTT(const char *Option)
1988{
1989 int Id = 0;
1990 bool UseChannelId = false;
1991 if (*Option) {
1992 char buf[strlen(Option) + 1];
1993 strcpy(buf, Option);
1994 const char *delim = " \t";
1995 char *strtok_next;
1996 char *p = strtok_r(buf, delim, &strtok_next);
1997 while (p) {
1998 if (isnumber(p))
1999 Id = strtol(p, NULL, 10);
2000 else if (strcasecmp(p, "ID") == 0)
2001 UseChannelId = true;
2002 else {
2003 Reply(501, "Unknown option: \"%s\"", p);
2004 return;
2005 }
2006 p = strtok_r(NULL, delim, &strtok_next);
2007 }
2008 }
2010 if (Id) {
2011 for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
2012 if (!Timer->Remote()) {
2013 if (Timer->Id() == Id) {
2014 Reply(250, "%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2015 return;
2016 }
2017 }
2018 }
2019 Reply(501, "Timer \"%s\" not defined", Option);
2020 return;
2021 }
2022 else {
2023 const cTimer *LastLocalTimer = Timers->Last();
2024 while (LastLocalTimer) {
2025 if (LastLocalTimer->Remote())
2026 LastLocalTimer = Timers->Prev(LastLocalTimer);
2027 else
2028 break;
2029 }
2030 if (LastLocalTimer) {
2031 for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
2032 if (!Timer->Remote())
2033 Reply(Timer != LastLocalTimer ? -250 : 250, "%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2034 if (Timer == LastLocalTimer)
2035 break;
2036 }
2037 return;
2038 }
2039 }
2040 Reply(550, "No timers defined");
2041}
2042
2043void cSVDRPServer::CmdMESG(const char *Option)
2044{
2045 if (*Option) {
2046 isyslog("SVDRP %s < %s message '%s'", Setup.SVDRPHostName, *clientName, Option);
2047 Skins.QueueMessage(mtInfo, Option);
2048 Reply(250, "Message queued");
2049 }
2050 else
2051 Reply(501, "Missing message");
2052}
2053
2054void cSVDRPServer::CmdMODC(const char *Option)
2055{
2056 if (*Option) {
2057 char *tail;
2058 int n = strtol(Option, &tail, 10);
2059 if (tail && tail != Option) {
2060 tail = skipspace(tail);
2062 Channels->SetExplicitModify();
2063 if (cChannel *Channel = Channels->GetByNumber(n)) {
2064 cChannel ch;
2065 if (ch.Parse(tail)) {
2066 if (Channels->HasUniqueChannelID(&ch, Channel)) {
2067 *Channel = ch;
2068 Channels->ReNumber();
2069 Channels->SetModifiedByUser();
2070 Channels->SetModified();
2071 isyslog("SVDRP %s < %s modified channel %d %s", Setup.SVDRPHostName, *clientName, Channel->Number(), *Channel->ToText());
2072 Reply(250, "%d %s", Channel->Number(), *Channel->ToText());
2073 }
2074 else
2075 Reply(501, "Channel settings are not unique");
2076 }
2077 else
2078 Reply(501, "Error in channel settings");
2079 }
2080 else
2081 Reply(501, "Channel \"%d\" not defined", n);
2082 }
2083 else
2084 Reply(501, "Error in channel number");
2085 }
2086 else
2087 Reply(501, "Missing channel settings");
2088}
2089
2090void cSVDRPServer::CmdMODT(const char *Option)
2091{
2092 if (*Option) {
2093 char *tail;
2094 int Id = strtol(Option, &tail, 10);
2095 if (tail && tail != Option) {
2096 tail = skipspace(tail);
2098 Timers->SetExplicitModify();
2099 if (cTimer *Timer = Timers->GetById(Id)) {
2100 bool IsRecording = Timer->HasFlags(tfRecording);
2101 cTimer t = *Timer;
2102 if (strcasecmp(tail, "ON") == 0)
2103 t.SetFlags(tfActive);
2104 else if (strcasecmp(tail, "OFF") == 0)
2105 t.ClrFlags(tfActive);
2106 else if (!t.Parse(tail)) {
2107 Reply(501, "Error in timer settings");
2108 return;
2109 }
2110 if (IsRecording && t.IsPatternTimer()) {
2111 Reply(550, "Timer is recording");
2112 return;
2113 }
2114 *Timer = t;
2115 if (IsRecording)
2116 Timer->SetFlags(tfRecording);
2117 else
2118 Timer->ClrFlags(tfRecording);
2119 Timers->SetModified();
2120 isyslog("SVDRP %s < %s modified timer %s (%s)", Setup.SVDRPHostName, *clientName, *Timer->ToDescr(), Timer->HasFlags(tfActive) ? "active" : "inactive");
2121 if (Timer->IsPatternTimer())
2122 Timer->SetEvent(NULL);
2123 Timer->TriggerRespawn();
2124 Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2125 }
2126 else
2127 Reply(501, "Timer \"%d\" not defined", Id);
2128 }
2129 else
2130 Reply(501, "Error in timer id");
2131 }
2132 else
2133 Reply(501, "Missing timer settings");
2134}
2135
2136void cSVDRPServer::CmdMOVC(const char *Option)
2137{
2138 if (*Option) {
2139 char *tail;
2140 int From = strtol(Option, &tail, 10);
2141 if (tail && tail != Option) {
2142 tail = skipspace(tail);
2143 if (tail && tail != Option) {
2144 LOCK_TIMERS_READ; // necessary to keep timers and channels in sync!
2146 Channels->SetExplicitModify();
2147 int To = strtol(tail, NULL, 10);
2148 int CurrentChannelNr = cDevice::CurrentChannel();
2149 const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
2150 cChannel *FromChannel = Channels->GetByNumber(From);
2151 if (FromChannel) {
2152 cChannel *ToChannel = Channels->GetByNumber(To);
2153 if (ToChannel) {
2154 int FromNumber = FromChannel->Number();
2155 int ToNumber = ToChannel->Number();
2156 if (FromNumber != ToNumber) {
2157 if (Channels->MoveNeedsDecrement(FromChannel, ToChannel))
2158 ToChannel = Channels->Prev(ToChannel); // cListBase::Move() doesn't know about the channel list's numbered groups!
2159 Channels->Move(FromChannel, ToChannel);
2160 Channels->ReNumber();
2161 Channels->SetModifiedByUser();
2162 Channels->SetModified();
2163 if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
2164 if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
2165 Channels->SwitchTo(CurrentChannel->Number());
2166 else
2167 cDevice::SetCurrentChannel(CurrentChannel->Number());
2168 }
2169 isyslog("SVDRP %s < %s moved channel %d to %d", Setup.SVDRPHostName, *clientName, FromNumber, ToNumber);
2170 Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
2171 }
2172 else
2173 Reply(501, "Can't move channel to same position");
2174 }
2175 else
2176 Reply(501, "Channel \"%d\" not defined", To);
2177 }
2178 else
2179 Reply(501, "Channel \"%d\" not defined", From);
2180 }
2181 else
2182 Reply(501, "Error in channel number");
2183 }
2184 else
2185 Reply(501, "Error in channel number");
2186 }
2187 else
2188 Reply(501, "Missing channel number");
2189}
2190
2191void cSVDRPServer::CmdMOVR(const char *Option)
2192{
2193 if (*Option) {
2194 char *opt = strdup(Option);
2195 char *num = skipspace(opt);
2196 char *option = num;
2197 while (*option && !isspace(*option))
2198 option++;
2199 char c = *option;
2200 *option = 0;
2201 if (isnumber(num)) {
2203 Recordings->SetExplicitModify();
2204 if (cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2205 if (int RecordingInUse = Recording->IsInUse())
2206 Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
2207 else {
2208 if (c)
2209 option = skipspace(++option);
2210 if (*option) {
2211 cString oldName = Recording->Name();
2212 if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) {
2213 Recordings->SetModified();
2214 Recordings->TouchUpdate();
2215 Reply(250, "Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name());
2216 }
2217 else
2218 Reply(554, "Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
2219 }
2220 else
2221 Reply(501, "Missing new recording name");
2222 }
2223 }
2224 else
2225 Reply(550, "Recording \"%s\" not found", num);
2226 }
2227 else
2228 Reply(501, "Error in recording id \"%s\"", num);
2229 free(opt);
2230 }
2231 else
2232 Reply(501, "Missing recording id");
2233}
2234
2235void cSVDRPServer::CmdNEWC(const char *Option)
2236{
2237 if (*Option) {
2238 cChannel ch;
2239 if (ch.Parse(Option)) {
2241 Channels->SetExplicitModify();
2242 if (Channels->HasUniqueChannelID(&ch)) {
2243 cChannel *channel = new cChannel;
2244 *channel = ch;
2245 Channels->Add(channel);
2246 Channels->ReNumber();
2247 Channels->SetModifiedByUser();
2248 Channels->SetModified();
2249 isyslog("SVDRP %s < %s new channel %d %s", Setup.SVDRPHostName, *clientName, channel->Number(), *channel->ToText());
2250 Reply(250, "%d %s", channel->Number(), *channel->ToText());
2251 }
2252 else
2253 Reply(501, "Channel settings are not unique");
2254 }
2255 else
2256 Reply(501, "Error in channel settings");
2257 }
2258 else
2259 Reply(501, "Missing channel settings");
2260}
2261
2262void cSVDRPServer::CmdNEWT(const char *Option)
2263{
2264 if (*Option) {
2265 cTimer *Timer = new cTimer;
2266 if (Timer->Parse(Option)) {
2268 Timer->ClrFlags(tfRecording);
2269 Timers->Add(Timer);
2270 isyslog("SVDRP %s < %s added timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2271 Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2272 return;
2273 }
2274 else
2275 Reply(501, "Error in timer settings");
2276 delete Timer;
2277 }
2278 else
2279 Reply(501, "Missing timer settings");
2280}
2281
2282void cSVDRPServer::CmdNEXT(const char *Option)
2283{
2285 if (const cTimer *t = Timers->GetNextActiveTimer()) {
2286 time_t Start = t->StartTime();
2287 int Id = t->Id();
2288 if (!*Option)
2289 Reply(250, "%d %s", Id, *TimeToString(Start));
2290 else if (strcasecmp(Option, "ABS") == 0)
2291 Reply(250, "%d %ld", Id, Start);
2292 else if (strcasecmp(Option, "REL") == 0)
2293 Reply(250, "%d %ld", Id, Start - time(NULL));
2294 else
2295 Reply(501, "Unknown option: \"%s\"", Option);
2296 }
2297 else
2298 Reply(550, "No active timers");
2299}
2300
2301void cSVDRPServer::CmdPING(const char *Option)
2302{
2303 Reply(250, "%s is alive", Setup.SVDRPHostName);
2304}
2305
2306void cSVDRPServer::CmdPLAY(const char *Option)
2307{
2308 if (*Option) {
2309 char *opt = strdup(Option);
2310 char *num = skipspace(opt);
2311 char *option = num;
2312 while (*option && !isspace(*option))
2313 option++;
2314 char c = *option;
2315 *option = 0;
2316 if (isnumber(num)) {
2317 cStateKey StateKey;
2318 if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(StateKey)) {
2319 if (const cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2320 cString FileName = Recording->FileName();
2321 cString Title = Recording->Title();
2322 int FramesPerSecond = Recording->FramesPerSecond();
2323 bool IsPesRecording = Recording->IsPesRecording();
2324 StateKey.Remove(); // must give up the lock for the call to cControl::Shutdown()
2325 if (c)
2326 option = skipspace(++option);
2329 if (*option) {
2330 int pos = 0;
2331 if (strcasecmp(option, "BEGIN") != 0)
2332 pos = HMSFToIndex(option, FramesPerSecond);
2333 cResumeFile Resume(FileName, IsPesRecording);
2334 if (pos <= 0)
2335 Resume.Delete();
2336 else
2337 Resume.Save(pos);
2338 }
2342 Reply(250, "Playing recording \"%s\" [%s]", num, *Title);
2343 }
2344 else {
2345 StateKey.Remove();
2346 Reply(550, "Recording \"%s\" not found", num);
2347 }
2348 }
2349 }
2350 else
2351 Reply(501, "Error in recording id \"%s\"", num);
2352 free(opt);
2353 }
2354 else
2355 Reply(501, "Missing recording id");
2356}
2357
2358void cSVDRPServer::CmdPLUG(const char *Option)
2359{
2360 if (*Option) {
2361 char *opt = strdup(Option);
2362 char *name = skipspace(opt);
2363 char *option = name;
2364 while (*option && !isspace(*option))
2365 option++;
2366 char c = *option;
2367 *option = 0;
2368 cPlugin *plugin = cPluginManager::GetPlugin(name);
2369 if (plugin) {
2370 if (c)
2371 option = skipspace(++option);
2372 char *cmd = option;
2373 while (*option && !isspace(*option))
2374 option++;
2375 if (*option) {
2376 *option++ = 0;
2377 option = skipspace(option);
2378 }
2379 if (!*cmd || strcasecmp(cmd, "HELP") == 0) {
2380 if (*cmd && *option) {
2381 const char *hp = GetHelpPage(option, plugin->SVDRPHelpPages());
2382 if (hp) {
2383 Reply(-214, "%s", hp);
2384 Reply(214, "End of HELP info");
2385 }
2386 else
2387 Reply(504, "HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->Name());
2388 }
2389 else {
2390 Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
2391 const char **hp = plugin->SVDRPHelpPages();
2392 if (hp) {
2393 Reply(-214, "SVDRP commands:");
2394 PrintHelpTopics(hp);
2395 Reply(214, "End of HELP info");
2396 }
2397 else
2398 Reply(214, "This plugin has no SVDRP commands");
2399 }
2400 }
2401 else if (strcasecmp(cmd, "MAIN") == 0) {
2402 if (cRemote::CallPlugin(plugin->Name()))
2403 Reply(250, "Initiated call to main menu function of plugin \"%s\"", plugin->Name());
2404 else
2405 Reply(550, "A plugin call is already pending - please try again later");
2406 }
2407 else {
2408 int ReplyCode = 900;
2409 cString s = plugin->SVDRPCommand(cmd, option, ReplyCode);
2410 if (*s)
2411 Reply(abs(ReplyCode), "%s", *s);
2412 else
2413 Reply(500, "Command unrecognized: \"%s\"", cmd);
2414 }
2415 }
2416 else
2417 Reply(550, "Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
2418 free(opt);
2419 }
2420 else {
2421 Reply(-214, "Available plugins:");
2422 cPlugin *plugin;
2423 for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++)
2424 Reply(-214, "%s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
2425 Reply(214, "End of plugin list");
2426 }
2427}
2428
2429void cSVDRPServer::CmdPOLL(const char *Option)
2430{
2431 if (*Option) {
2432 char buf[strlen(Option) + 1];
2433 char *p = strcpy(buf, Option);
2434 const char *delim = " \t";
2435 char *strtok_next;
2436 char *RemoteName = strtok_r(p, delim, &strtok_next);
2437 char *ListName = strtok_r(NULL, delim, &strtok_next);
2438 if (SVDRPClientHandler) {
2439 if (ListName) {
2440 if (strcasecmp(ListName, "timers") == 0) {
2442 Reply(250, "OK");
2443 else
2444 Reply(501, "No connection to \"%s\"", RemoteName);
2445 }
2446 else
2447 Reply(501, "Unknown list name: \"%s\"", ListName);
2448 }
2449 else
2450 Reply(501, "Missing list name");
2451 }
2452 else
2453 Reply(501, "No SVDRP client connections");
2454 }
2455 else
2456 Reply(501, "Missing parameters");
2457}
2458
2459void cSVDRPServer::CmdPRIM(const char *Option)
2460{
2461 int n = -1;
2462 if (*Option) {
2463 if (isnumber(Option)) {
2464 int o = strtol(Option, NULL, 10);
2465 if (o > 0 && o <= cDevice::NumDevices())
2466 n = o;
2467 else
2468 Reply(501, "Invalid device number \"%s\"", Option);
2469 }
2470 else
2471 Reply(501, "Invalid parameter \"%s\"", Option);
2472 if (n >= 0) {
2473 Setup.PrimaryDVB = n;
2474 Reply(250, "Primary device set to %d", n);
2475 }
2476 }
2477 else {
2478 if (const cDevice *d = cDevice::PrimaryDevice())
2479 Reply(250, "%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ? "D" : "-", d->DeviceNumber() + 1 == Setup.PrimaryDVB ? "P" : "-", *d->DeviceName());
2480 else
2481 Reply(501, "Failed to get primary device");
2482 }
2483}
2484
2485void cSVDRPServer::CmdPUTE(const char *Option)
2486{
2487 if (*Option) {
2488 FILE *f = fopen(Option, "r");
2489 if (f) {
2490 if (cSchedules::Read(f)) {
2491 cSchedules::Cleanup(true);
2492 Reply(250, "EPG data processed from \"%s\"", Option);
2493 }
2494 else
2495 Reply(451, "Error while processing EPG from \"%s\"", Option);
2496 fclose(f);
2497 }
2498 else
2499 Reply(501, "Cannot open file \"%s\"", Option);
2500 }
2501 else {
2502 delete PUTEhandler;
2505 if (PUTEhandler->Status() != 354)
2507 }
2508}
2509
2510void cSVDRPServer::CmdREMO(const char *Option)
2511{
2512 if (*Option) {
2513 if (!strcasecmp(Option, "ON")) {
2514 cRemote::SetEnabled(true);
2515 Reply(250, "Remote control enabled");
2516 }
2517 else if (!strcasecmp(Option, "OFF")) {
2518 cRemote::SetEnabled(false);
2519 Reply(250, "Remote control disabled");
2520 }
2521 else
2522 Reply(501, "Invalid Option \"%s\"", Option);
2523 }
2524 else
2525 Reply(250, "Remote control is %s", cRemote::Enabled() ? "enabled" : "disabled");
2526}
2527
2528void cSVDRPServer::CmdSCAN(const char *Option)
2529{
2531 Reply(250, "EPG scan triggered");
2532}
2533
2534void cSVDRPServer::CmdSTAT(const char *Option)
2535{
2536 if (*Option) {
2537 if (strcasecmp(Option, "DISK") == 0) {
2538 int FreeMB, UsedMB;
2539 int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB);
2540 Reply(250, "%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
2541 }
2542 else
2543 Reply(501, "Invalid Option \"%s\"", Option);
2544 }
2545 else
2546 Reply(501, "No option given");
2547}
2548
2549void cSVDRPServer::CmdUPDT(const char *Option)
2550{
2551 if (*Option) {
2552 cTimer *Timer = new cTimer;
2553 if (Timer->Parse(Option)) {
2555 if (cTimer *t = Timers->GetTimer(Timer)) {
2556 bool IsRecording = t->HasFlags(tfRecording);
2557 t->Parse(Option);
2558 delete Timer;
2559 Timer = t;
2560 if (IsRecording)
2561 Timer->SetFlags(tfRecording);
2562 else
2563 Timer->ClrFlags(tfRecording);
2564 isyslog("SVDRP %s < %s updated timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2565 }
2566 else {
2567 Timer->ClrFlags(tfRecording);
2568 Timers->Add(Timer);
2569 isyslog("SVDRP %s < %s added timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2570 }
2571 Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2572 return;
2573 }
2574 else
2575 Reply(501, "Error in timer settings");
2576 delete Timer;
2577 }
2578 else
2579 Reply(501, "Missing timer settings");
2580}
2581
2582void cSVDRPServer::CmdUPDR(const char *Option)
2583{
2585 Recordings->Update(false);
2586 Reply(250, "Re-read of recordings directory triggered");
2587}
2588
2589void cSVDRPServer::CmdVOLU(const char *Option)
2590{
2591 if (*Option) {
2592 if (isnumber(Option))
2593 cDevice::PrimaryDevice()->SetVolume(strtol(Option, NULL, 10), true);
2594 else if (strcmp(Option, "+") == 0)
2596 else if (strcmp(Option, "-") == 0)
2598 else if (strcasecmp(Option, "MUTE") == 0)
2600 else {
2601 Reply(501, "Unknown option: \"%s\"", Option);
2602 return;
2603 }
2604 }
2605 if (cDevice::PrimaryDevice()->IsMute())
2606 Reply(250, "Audio is mute");
2607 else
2608 Reply(250, "Audio volume is %d", cDevice::CurrentVolume());
2609}
2610
2611#define CMD(c) (strcasecmp(Cmd, c) == 0)
2612
2614{
2615 // handle PUTE data:
2616 if (PUTEhandler) {
2617 if (!PUTEhandler->Process(Cmd)) {
2620 }
2621 cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME); // re-trigger the timeout, in case there is very much EPG data
2622 return;
2623 }
2624 // skip leading whitespace:
2625 Cmd = skipspace(Cmd);
2626 // find the end of the command word:
2627 char *s = Cmd;
2628 while (*s && !isspace(*s))
2629 s++;
2630 if (*s)
2631 *s++ = 0;
2632 s = skipspace(s);
2633 if (CMD("CHAN")) CmdCHAN(s);
2634 else if (CMD("CLRE")) CmdCLRE(s);
2635 else if (CMD("CONN")) CmdCONN(s);
2636 else if (CMD("DELC")) CmdDELC(s);
2637 else if (CMD("DELR")) CmdDELR(s);
2638 else if (CMD("DELT")) CmdDELT(s);
2639 else if (CMD("EDIT")) CmdEDIT(s);
2640 else if (CMD("GRAB")) CmdGRAB(s);
2641 else if (CMD("HELP")) CmdHELP(s);
2642 else if (CMD("HITK")) CmdHITK(s);
2643 else if (CMD("LSTC")) CmdLSTC(s);
2644 else if (CMD("LSTD")) CmdLSTD(s);
2645 else if (CMD("LSTE")) CmdLSTE(s);
2646 else if (CMD("LSTR")) CmdLSTR(s);
2647 else if (CMD("LSTT")) CmdLSTT(s);
2648 else if (CMD("MESG")) CmdMESG(s);
2649 else if (CMD("MODC")) CmdMODC(s);
2650 else if (CMD("MODT")) CmdMODT(s);
2651 else if (CMD("MOVC")) CmdMOVC(s);
2652 else if (CMD("MOVR")) CmdMOVR(s);
2653 else if (CMD("NEWC")) CmdNEWC(s);
2654 else if (CMD("NEWT")) CmdNEWT(s);
2655 else if (CMD("NEXT")) CmdNEXT(s);
2656 else if (CMD("PING")) CmdPING(s);
2657 else if (CMD("PLAY")) CmdPLAY(s);
2658 else if (CMD("PLUG")) CmdPLUG(s);
2659 else if (CMD("POLL")) CmdPOLL(s);
2660 else if (CMD("PRIM")) CmdPRIM(s);
2661 else if (CMD("PUTE")) CmdPUTE(s);
2662 else if (CMD("REMO")) CmdREMO(s);
2663 else if (CMD("SCAN")) CmdSCAN(s);
2664 else if (CMD("STAT")) CmdSTAT(s);
2665 else if (CMD("UPDR")) CmdUPDR(s);
2666 else if (CMD("UPDT")) CmdUPDT(s);
2667 else if (CMD("VOLU")) CmdVOLU(s);
2668 else if (CMD("QUIT")) Close(true);
2669 else Reply(500, "Command unrecognized: \"%s\"", Cmd);
2670}
2671
2673{
2674 if (file.IsOpen()) {
2675 while (file.Ready(false)) {
2676 unsigned char c;
2677 int r = safe_read(file, &c, 1);
2678 if (r > 0) {
2679 if (c == '\n' || c == 0x00) {
2680 // strip trailing whitespace:
2681 while (numChars > 0 && strchr(" \t\r\n", cmdLine[numChars - 1]))
2682 cmdLine[--numChars] = 0;
2683 // make sure the string is terminated:
2684 cmdLine[numChars] = 0;
2685 // showtime!
2686 dbgsvdrp("< S %s: %s\n", *clientName, cmdLine);
2688 numChars = 0;
2689 if (length > BUFSIZ) {
2690 free(cmdLine); // let's not tie up too much memory
2691 length = BUFSIZ;
2692 cmdLine = MALLOC(char, length);
2693 }
2694 }
2695 else if (c == 0x04 && numChars == 0) {
2696 // end of file (only at beginning of line)
2697 Close(true);
2698 }
2699 else if (c == 0x08 || c == 0x7F) {
2700 // backspace or delete (last character)
2701 if (numChars > 0)
2702 numChars--;
2703 }
2704 else if (c <= 0x03 || c == 0x0D) {
2705 // ignore control characters
2706 }
2707 else {
2708 if (numChars >= length - 1) {
2709 int NewLength = length + BUFSIZ;
2710 if (char *NewBuffer = (char *)realloc(cmdLine, NewLength)) {
2711 length = NewLength;
2712 cmdLine = NewBuffer;
2713 }
2714 else {
2715 esyslog("SVDRP %s < %s ERROR: out of memory", Setup.SVDRPHostName, *clientName);
2716 Close();
2717 break;
2718 }
2719 }
2720 cmdLine[numChars++] = c;
2721 cmdLine[numChars] = 0;
2722 }
2723 lastActivity = time(NULL);
2724 }
2725 else if (r <= 0) {
2726 isyslog("SVDRP %s < %s lost connection to client", Setup.SVDRPHostName, *clientName);
2727 Close();
2728 }
2729 }
2730 if (Setup.SVDRPTimeout && time(NULL) - lastActivity > Setup.SVDRPTimeout) {
2731 isyslog("SVDRP %s < %s timeout on connection", Setup.SVDRPHostName, *clientName);
2732 Close(true, true);
2733 }
2734 }
2735 return file.IsOpen();
2736}
2737
2738void SetSVDRPPorts(int TcpPort, int UdpPort)
2739{
2740 SVDRPTcpPort = TcpPort;
2741 SVDRPUdpPort = UdpPort;
2742}
2743
2744void SetSVDRPGrabImageDir(const char *GrabImageDir)
2745{
2746 grabImageDir = GrabImageDir;
2747}
2748
2749// --- cSVDRPServerHandler ---------------------------------------------------
2750
2752private:
2753 bool ready;
2756 void HandleServerConnection(void);
2757 void ProcessConnections(void);
2758protected:
2759 virtual void Action(void);
2760public:
2761 cSVDRPServerHandler(int TcpPort);
2762 virtual ~cSVDRPServerHandler();
2763 void WaitUntilReady(void);
2764 };
2765
2767
2769:cThread("SVDRP server handler", true)
2770,tcpSocket(TcpPort, true)
2771{
2772 ready = false;
2773}
2774
2776{
2777 Cancel(3);
2778 for (int i = 0; i < serverConnections.Size(); i++)
2779 delete serverConnections[i];
2780}
2781
2783{
2784 cTimeMs Timeout(3000);
2785 while (!ready && !Timeout.TimedOut())
2787}
2788
2790{
2791 for (int i = 0; i < serverConnections.Size(); i++) {
2792 if (!serverConnections[i]->Process()) {
2793 delete serverConnections[i];
2795 i--;
2796 }
2797 }
2798}
2799
2801{
2802 int NewSocket = tcpSocket.Accept();
2803 if (NewSocket >= 0)
2805}
2806
2808{
2809 if (tcpSocket.Listen()) {
2811 ready = true;
2812 while (Running()) {
2813 SVDRPServerPoller.Poll(1000);
2816 }
2818 tcpSocket.Close();
2819 }
2820}
2821
2822// --- SVDRP Handler ---------------------------------------------------------
2823
2825
2827{
2828 cMutexLock MutexLock(&SVDRPHandlerMutex);
2829 if (SVDRPTcpPort) {
2830 if (!SVDRPServerHandler) {
2834 }
2838 }
2839 }
2840}
2841
2843{
2844 cMutexLock MutexLock(&SVDRPHandlerMutex);
2845 delete SVDRPClientHandler;
2846 SVDRPClientHandler = NULL;
2847 delete SVDRPServerHandler;
2848 SVDRPServerHandler = NULL;
2849}
2850
2852{
2853 bool Result = false;
2854 cMutexLock MutexLock(&SVDRPHandlerMutex);
2855 if (SVDRPClientHandler) {
2857 Result = SVDRPClientHandler->GetServerNames(ServerNames);
2859 }
2860 return Result;
2861}
2862
2863bool ExecSVDRPCommand(const char *ServerName, const char *Command, cStringList *Response)
2864{
2865 bool Result = false;
2866 cMutexLock MutexLock(&SVDRPHandlerMutex);
2867 if (SVDRPClientHandler) {
2869 Result = SVDRPClientHandler->Execute(ServerName, Command, Response);
2871 }
2872 return Result;
2873}
2874
2875void BroadcastSVDRPCommand(const char *Command)
2876{
2877 cMutexLock MutexLock(&SVDRPHandlerMutex);
2878 cStringList ServerNames;
2879 if (SVDRPClientHandler) {
2881 if (SVDRPClientHandler->GetServerNames(&ServerNames)) {
2882 for (int i = 0; i < ServerNames.Size(); i++)
2883 ExecSVDRPCommand(ServerNames[i], Command);
2884 }
2886 }
2887}
#define LOCK_CHANNELS_READ
Definition: channels.h:269
#define LOCK_CHANNELS_WRITE
Definition: channels.h:270
const char * NextLine(void)
Returns the next line of encoded data (terminated by '\0'), or NULL if there is no more encoded data.
Definition: tools.c:1393
bool Parse(const char *s)
Definition: channels.c:613
static cString ToText(const cChannel *Channel)
Definition: channels.c:551
int Number(void) const
Definition: channels.h:178
tChannelID GetChannelID(void) const
Definition: channels.h:190
static int MaxNumber(void)
Definition: channels.h:248
static const char * SystemCharacterTable(void)
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
Definition: thread.c:72
static void Shutdown(void)
Definition: player.c:108
static void Attach(void)
Definition: player.c:95
static void Launch(cControl *Control)
Definition: player.c:87
virtual uchar * GrabImage(int &Size, bool Jpeg=true, int Quality=-1, int SizeX=-1, int SizeY=-1)
Grabs the currently visible screen image.
Definition: device.c:466
static cDevice * PrimaryDevice(void)
Returns the primary device.
Definition: device.h:148
static cDevice * GetDevice(int Index)
Gets the device with the given Index.
Definition: device.c:228
bool SwitchChannel(const cChannel *Channel, bool LiveView)
Switches the device to the given Channel, initiating transfer mode if necessary.
Definition: device.c:807
static int CurrentChannel(void)
Returns the number of the current channel on the primary device.
Definition: device.h:358
static void SetCurrentChannel(int ChannelNumber)
Sets the number of the current channel on the primary device, without actually switching to it.
Definition: device.h:366
void SetVolume(int Volume, bool Absolute=false)
Sets the volume to the given value, either absolutely or relative to the current volume.
Definition: device.c:1041
static int NumDevices(void)
Returns the total number of devices.
Definition: device.h:129
static int CurrentVolume(void)
Definition: device.h:634
bool ToggleMute(void)
Turns the volume off or on and returns the new mute state.
Definition: device.c:1012
void ForceScan(void)
Definition: eitscan.c:113
static void SetDisableUntil(time_t Time)
Definition: eit.c:508
bool Ready(bool Wait=true)
Definition: tools.c:1697
bool Open(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
Definition: tools.c:1651
void Close(void)
Definition: tools.c:1686
bool IsOpen(void)
const char * Connection(void) const
Definition: svdrp.c:71
cString address
Definition: svdrp.c:61
const char * Address(void) const
Definition: svdrp.c:67
int Port(void) const
Definition: svdrp.c:68
void Set(const char *Address, int Port)
Definition: svdrp.c:84
cString connection
Definition: svdrp.c:63
int port
Definition: svdrp.c:62
cIpAddress(void)
Definition: svdrp.c:74
static const char * ToString(eKeys Key, bool Translate=false)
Definition: keys.c:138
static eKeys FromString(const char *Name)
Definition: keys.c:123
int Count(void) const
cListObject * Prev(void) const
int Index(void) const
Definition: tools.c:2104
cListObject * Next(void) const
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
Definition: recording.c:2175
void Lock(void)
Definition: thread.c:222
void Unlock(void)
Definition: thread.c:228
bool Process(const char *s)
Definition: svdrp.c:796
cPUTEhandler(void)
Definition: svdrp.c:777
int status
Definition: svdrp.c:767
int Status(void)
Definition: svdrp.c:773
const char * Message(void)
Definition: svdrp.c:774
FILE * f
Definition: svdrp.c:766
const char * message
Definition: svdrp.c:768
~cPUTEhandler()
Definition: svdrp.c:790
static cPlugin * GetPlugin(int Index)
Definition: plugin.c:469
virtual const char * Version(void)=0
const char * Name(void)
virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
Definition: plugin.c:130
virtual const char * Description(void)=0
virtual const char ** SVDRPHelpPages(void)
Definition: plugin.c:125
bool Add(int FileHandle, bool Out)
Definition: tools.c:1507
bool Poll(int TimeoutMs=0)
Definition: tools.c:1539
void Del(int FileHandle, bool Out)
Definition: tools.c:1526
cTimer * Timer(void)
static bool Process(cTimers *Timers, time_t t)
Definition: menu.c:5680
static cRecordControl * GetRecordControl(const char *FileName)
Definition: menu.c:5660
int Id(void) const
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
Definition: recording.c:1066
const char * Title(char Delimiter=' ', bool NewIndicator=false, int Level=-1) const
Definition: recording.c:1084
bool Add(int Usage, const char *FileNameSrc, const char *FileNameDst=NULL)
Adds the given FileNameSrc to the recordings handler for (later) processing.
Definition: recording.c:2052
static const cRecordings * GetRecordingsRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of recordings for read access.
bool Put(uint64_t Code, bool Repeat=false, bool Release=false)
Definition: remote.c:124
static bool Enabled(void)
static bool CallPlugin(const char *Plugin)
Initiates calling the given plugin's main menu function.
Definition: remote.c:151
static void SetEnabled(bool Enabled)
static void SetRecording(const char *FileName)
Definition: menu.c:5864
bool Save(int Index)
Definition: recording.c:307
void Delete(void)
Definition: recording.c:337
bool Execute(const char *ServerName, const char *Command, cStringList *Response=NULL)
Definition: svdrp.c:732
void AddClient(cSVDRPServerParams &ServerParams, const char *IpAddress)
Definition: svdrp.c:690
void Unlock(void)
Definition: svdrp.c:609
virtual ~cSVDRPClientHandler()
Definition: svdrp.c:625
void SendDiscover(void)
Definition: svdrp.c:641
void ProcessConnections(void)
Definition: svdrp.c:647
bool GetServerNames(cStringList *ServerNames)
Definition: svdrp.c:740
cSVDRPClientHandler(int TcpPort, int UdpPort)
Definition: svdrp.c:618
void HandleClientConnection(void)
Definition: svdrp.c:704
cSVDRPClient * GetClientForServer(const char *ServerName)
Definition: svdrp.c:632
cVector< cSVDRPClient * > clientConnections
Definition: svdrp.c:598
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
Definition: svdrp.c:716
bool TriggerFetchingTimers(const char *ServerName)
Definition: svdrp.c:752
void Lock(void)
Definition: svdrp.c:608
cSocket udpSocket
Definition: svdrp.c:597
int length
Definition: svdrp.c:321
bool connected
Definition: svdrp.c:327
int timeout
Definition: svdrp.c:323
cString serverName
Definition: svdrp.c:320
cIpAddress serverIpAddress
Definition: svdrp.c:318
bool Connected(void) const
Definition: svdrp.c:338
bool Execute(const char *Command, cStringList *Response=NULL)
Definition: svdrp.c:481
cTimeMs pingTime
Definition: svdrp.c:324
void Close(void)
Definition: svdrp.c:374
bool HasAddress(const char *Address, int Port) const
Definition: svdrp.c:383
cSocket socket
Definition: svdrp.c:319
cFile file
Definition: svdrp.c:325
const char * ServerName(void) const
Definition: svdrp.c:333
bool Send(const char *Command)
Definition: svdrp.c:388
cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout)
Definition: svdrp.c:346
int fetchFlags
Definition: svdrp.c:326
bool GetRemoteTimers(cStringList &Response)
Definition: svdrp.c:503
bool Process(cStringList *Response=NULL)
Definition: svdrp.c:399
void SetFetchFlag(int Flag)
Definition: svdrp.c:491
~cSVDRPClient()
Definition: svdrp.c:367
char * input
Definition: svdrp.c:322
const char * Connection(void) const
Definition: svdrp.c:334
bool HasFetchFlag(int Flag)
Definition: svdrp.c:496
void HandleServerConnection(void)
Definition: svdrp.c:2800
void ProcessConnections(void)
Definition: svdrp.c:2789
cSVDRPServerHandler(int TcpPort)
Definition: svdrp.c:2768
void WaitUntilReady(void)
Definition: svdrp.c:2782
virtual ~cSVDRPServerHandler()
Definition: svdrp.c:2775
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
Definition: svdrp.c:2807
cSocket tcpSocket
Definition: svdrp.c:2754
cVector< cSVDRPServer * > serverConnections
Definition: svdrp.c:2755
const char * Host(void) const
Definition: svdrp.c:543
cString error
Definition: svdrp.c:535
cString name
Definition: svdrp.c:529
const int Timeout(void) const
Definition: svdrp.c:542
const char * ApiVersion(void) const
Definition: svdrp.c:541
cString apiversion
Definition: svdrp.c:532
cSVDRPServerParams(const char *Params)
Definition: svdrp.c:548
const char * VdrVersion(void) const
Definition: svdrp.c:540
const char * Name(void) const
Definition: svdrp.c:538
cString vdrversion
Definition: svdrp.c:531
const char * Error(void) const
Definition: svdrp.c:545
const int Port(void) const
Definition: svdrp.c:539
cString host
Definition: svdrp.c:534
bool Ok(void) const
Definition: svdrp.c:544
void CmdMESG(const char *Option)
Definition: svdrp.c:2043
const char * ClientName(void) const
Definition: svdrp.c:1121
void CmdPOLL(const char *Option)
Definition: svdrp.c:2429
bool Send(const char *s)
Definition: svdrp.c:1167
void CmdLSTT(const char *Option)
Definition: svdrp.c:1987
time_t lastActivity
Definition: svdrp.c:1076
void CmdCLRE(const char *Option)
Definition: svdrp.c:1301
void Reply(int Code, const char *fmt,...) __attribute__((format(printf
Definition: svdrp.c:1178
void CmdGRAB(const char *Option)
Definition: svdrp.c:1585
void CmdMODC(const char *Option)
Definition: svdrp.c:2054
cFile file
Definition: svdrp.c:1071
cPUTEhandler * PUTEhandler
Definition: svdrp.c:1072
void CmdDELC(const char *Option)
Definition: svdrp.c:1386
void CmdPLUG(const char *Option)
Definition: svdrp.c:2358
void CmdMODT(const char *Option)
Definition: svdrp.c:2090
cIpAddress clientIpAddress
Definition: svdrp.c:1069
void CmdCPYR(const char *Option)
Definition: svdrp.c:1451
cString clientName
Definition: svdrp.c:1070
void CmdLSTC(const char *Option)
Definition: svdrp.c:1789
void CmdSCAN(const char *Option)
Definition: svdrp.c:2528
void Close(bool SendReply=false, bool Timeout=false)
Definition: svdrp.c:1153
~cSVDRPServer()
Definition: svdrp.c:1146
void CmdPUTE(const char *Option)
Definition: svdrp.c:2485
void CmdLSTR(const char *Option)
Definition: svdrp.c:1928
void CmdSTAT(const char *Option)
Definition: svdrp.c:2534
void CmdCHAN(const char *Option)
Definition: svdrp.c:1239
void CmdHELP(const char *Option)
Definition: svdrp.c:1722
bool Process(void)
Definition: svdrp.c:2672
void CmdUPDT(const char *Option)
Definition: svdrp.c:2549
void CmdREMO(const char *Option)
Definition: svdrp.c:2510
void CmdLSTE(const char *Option)
Definition: svdrp.c:1849
int length
Definition: svdrp.c:1074
void CmdCONN(const char *Option)
Definition: svdrp.c:1366
void CmdDELR(const char *Option)
Definition: svdrp.c:1502
void Execute(char *Cmd)
Definition: svdrp.c:2613
bool HasConnection(void)
Definition: svdrp.c:1122
void CmdUPDR(const char *Option)
Definition: svdrp.c:2582
void CmdVOLU(const char *Option)
Definition: svdrp.c:2589
void CmdNEWT(const char *Option)
Definition: svdrp.c:2262
void CmdEDIT(const char *Option)
Definition: svdrp.c:1559
void CmdPLAY(const char *Option)
Definition: svdrp.c:2306
void CmdDELT(const char *Option)
Definition: svdrp.c:1532
int socket
Definition: svdrp.c:1068
void CmdLSTD(const char *Option)
Definition: svdrp.c:1837
cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress)
Definition: svdrp.c:1128
void CmdNEXT(const char *Option)
Definition: svdrp.c:2282
void CmdHITK(const char *Option)
Definition: svdrp.c:1750
int numChars
Definition: svdrp.c:1073
void CmdNEWC(const char *Option)
Definition: svdrp.c:2235
void CmdPRIM(const char *Option)
Definition: svdrp.c:2459
void CmdMOVR(const char *Option)
Definition: svdrp.c:2191
void CmdPING(const char *Option)
Definition: svdrp.c:2301
char * cmdLine
Definition: svdrp.c:1075
void CmdMOVC(const char *Option)
Definition: svdrp.c:2136
void void PrintHelpTopics(const char **hp)
Definition: svdrp.c:1213
bool LocalhostOnly(void)
Definition: config.c:282
bool Acceptable(in_addr_t Address)
Definition: config.c:293
Definition: epg.h:152
void Cleanup(time_t Time)
Definition: epg.c:1134
void Dump(const cChannels *Channels, FILE *f, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0) const
Definition: epg.c:1145
static void Cleanup(bool Force=false)
Definition: epg.c:1286
static bool Read(FILE *f=NULL)
Definition: epg.c:1331
int SVDRPTimeout
Definition: config.h:301
int SVDRPPeering
Definition: config.h:302
char SVDRPDefaultHost[HOST_NAME_MAX]
Definition: config.h:304
int PrimaryDVB
Definition: config.h:268
char SVDRPHostName[HOST_NAME_MAX]
Definition: config.h:303
int QueueMessage(eMessageType Type, const char *s, int Seconds=0, int Timeout=0)
Like Message(), but this function may be called from a background thread.
Definition: skins.c:296
Definition: svdrp.c:101
int port
Definition: svdrp.c:103
void Close(void)
Definition: svdrp.c:133
bool tcp
Definition: svdrp.c:104
const cIpAddress * LastIpAddress(void) const
Definition: svdrp.c:118
static bool SendDgram(const char *Dgram, int Port)
Definition: svdrp.c:226
int Port(void) const
Definition: svdrp.c:113
int Socket(void) const
Definition: svdrp.c:114
cIpAddress lastIpAddress
Definition: svdrp.c:106
int sock
Definition: svdrp.c:105
bool Listen(void)
Definition: svdrp.c:141
int Accept(void)
Definition: svdrp.c:258
cString Discover(void)
Definition: svdrp.c:284
cSocket(int Port, bool Tcp)
Definition: svdrp.c:121
~cSocket()
Definition: svdrp.c:128
bool Connect(const char *Address)
Definition: svdrp.c:188
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
Definition: thread.c:859
bool TimedOut(void) const
Returns true if the last lock attempt this key was used with failed due to a timeout.
virtual void Clear(void)
Definition: tools.c:1593
void SortNumerically(void)
cString & CompactChars(char c)
Compact any sequence of characters 'c' to a single character, and strip all of them from the beginnin...
Definition: tools.c:1143
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition: tools.c:1149
cString & Truncate(int Index)
Truncate the string at the given Index (if Index is < 0 it is counted from the end of the string).
Definition: tools.c:1133
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
Definition: thread.c:304
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting 'running' to false, so that the Action() loop can finish in an or...
Definition: thread.c:354
void Set(int Ms=0)
Sets the timer.
Definition: tools.c:792
bool TimedOut(void) const
Definition: tools.c:797
void ClrFlags(uint Flags)
Definition: timers.c:1000
void SetFlags(uint Flags)
Definition: timers.c:995
bool IsPatternTimer(void) const
cString ToDescr(void) const
Definition: timers.c:321
const char * Remote(void) const
int Id(void) const
bool Parse(const char *s)
Definition: timers.c:434
cString ToText(bool UseChannelID=false) const
Definition: timers.c:311
bool StoreRemoteTimers(const char *ServerName=NULL, const cStringList *RemoteTimers=NULL)
Stores the given list of RemoteTimers, which come from the VDR ServerName, in this list.
Definition: timers.c:1264
static cTimers * GetTimersWrite(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for write access.
Definition: timers.c:1173
static const cTimers * GetTimersRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for read access.
Definition: timers.c:1168
int Size(void) const
virtual void Append(T Data)
virtual void Remove(int Index)
static int VideoDiskSpace(int *FreeMB=NULL, int *UsedMB=NULL)
Definition: videodir.c:152
cSetup Setup
Definition: config.c:372
cSVDRPhosts SVDRPhosts
Definition: config.c:280
#define APIVERSNUM
Definition: config.h:31
#define VDRVERSION
Definition: config.h:25
#define VDRVERSNUM
Definition: config.h:26
#define VOLUMEDELTA
Definition: device.h:33
cEITScanner EITScanner
Definition: eitscan.c:90
#define LOCK_SCHEDULES_READ
Definition: epg.h:233
eDumpMode
Definition: epg.h:42
@ dmAtTime
Definition: epg.h:42
@ dmPresent
Definition: epg.h:42
@ dmFollowing
Definition: epg.h:42
@ dmAll
Definition: epg.h:42
#define LOCK_SCHEDULES_WRITE
Definition: epg.h:234
@ kNone
int HMSFToIndex(const char *HMSF, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
Definition: recording.c:3194
#define LOCK_RECORDINGS_READ
char * ExchangeChars(char *s, bool ToFileSystem)
Definition: recording.c:600
#define FOLDERDELIMCHAR
cRecordingsHandler RecordingsHandler
Definition: recording.c:2000
#define LOCK_RECORDINGS_WRITE
cSkins Skins
Definition: skins.c:219
@ mtInfo
@ spmOnly
int SVDRPCode(const char *s)
Returns the value of the three digit reply code of the given SVDRP response string.
#define LOCK_TIMERS_READ
#define LOCK_TIMERS_WRITE
@ tfActive
@ tfRecording
#define FATALERRNO
char * strreplace(char *s, char c1, char c2)
Definition: tools.c:139
cString TimeToString(time_t t)
Converts the given time to a string of the form "www mmm dd hh:mm:ss yyyy".
Definition: tools.c:1225
bool MakeDirs(const char *FileName, bool IsDirectory=false)
Definition: tools.c:499
bool startswith(const char *s, const char *p)
Definition: tools.c:329
#define LOG_ERROR_STR(s)
unsigned char uchar
char * strshift(char *s, int n)
Shifts the given string to the left by the given number of bytes, thus removing the first n bytes fro...
Definition: tools.c:317
#define dsyslog(a...)
cString strgetval(const char *s, const char *name, char d='=')
Returns the value part of a 'name=value' pair in s.
Definition: tools.c:295
#define MALLOC(type, size)
ssize_t safe_read(int filedes, void *buffer, size_t size)
Definition: tools.c:53
char * skipspace(const char *s)
void DELETENULL(T *&p)
ssize_t safe_write(int filedes, const void *buffer, size_t size)
Definition: tools.c:65
#define esyslog(a...)
#define LOG_ERROR
#define isyslog(a...)
bool isnumber(const char *s)
Definition: tools.c:364
cString AddDirectory(const char *DirName, const char *FileName)
Definition: tools.c:402
struct __attribute__((packed))
Definition: recording.c:2534
tChannelID & ClrRid(void)
Definition: channels.h:59
static tChannelID FromString(const char *s)
Definition: channels.c:23
cString ToString(void) const
Definition: channels.c:40
static const tChannelID InvalidID
Definition: channels.h:68
#define dbgsvdrp(a...)
Definition: svdrp.c:45
static int SVDRPUdpPort
Definition: svdrp.c:48
void StopSVDRPHandler(void)
Definition: svdrp.c:2842
static cPoller SVDRPClientPoller
Definition: svdrp.c:344
void SetSVDRPGrabImageDir(const char *GrabImageDir)
Definition: svdrp.c:2744
static cString grabImageDir
Definition: svdrp.c:1064
eSvdrpFetchFlags
Definition: svdrp.c:50
@ sffTimers
Definition: svdrp.c:54
@ sffNone
Definition: svdrp.c:51
@ sffPing
Definition: svdrp.c:53
@ sffConn
Definition: svdrp.c:52
#define EITDISABLETIME
Definition: svdrp.c:825
#define MAXHELPTOPIC
Definition: svdrp.c:824
bool GetSVDRPServerNames(cStringList *ServerNames)
Gets a list of all available VDRs this VDR is connected to via SVDRP, and stores it in the given Serv...
Definition: svdrp.c:2851
static int SVDRPTcpPort
Definition: svdrp.c:47
static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
Definition: svdrp.c:1435
const char * HelpPages[]
Definition: svdrp.c:828
static cMutex SVDRPHandlerMutex
Definition: svdrp.c:2824
bool ExecSVDRPCommand(const char *ServerName, const char *Command, cStringList *Response)
Sends the given SVDRP Command string to the remote VDR identified by ServerName and collects all of t...
Definition: svdrp.c:2863
static cPoller SVDRPServerPoller
Definition: svdrp.c:1126
static cSVDRPServerHandler * SVDRPServerHandler
Definition: svdrp.c:2766
void StartSVDRPHandler(void)
Definition: svdrp.c:2826
cStateKey StateKeySVDRPRemoteTimersPoll(true)
#define MAXUDPBUF
Definition: svdrp.c:99
void BroadcastSVDRPCommand(const char *Command)
Sends the given SVDRP Command string to all remote VDRs.
Definition: svdrp.c:2875
#define SVDRPResonseTimeout
const char * GetHelpPage(const char *Cmd, const char **p)
Definition: svdrp.c:1051
static cSVDRPClientHandler * SVDRPClientHandler
Definition: svdrp.c:616
static bool DumpSVDRPDataTransfer
Definition: svdrp.c:43
const char * GetHelpTopic(const char *HelpPage)
Definition: svdrp.c:1033
#define CMD(c)
Definition: svdrp.c:2611
void SetSVDRPPorts(int TcpPort, int UdpPort)
Definition: svdrp.c:2738