vdr 2.6.6
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.7 2023/02/16 17:20:09 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// --- cSVDRPServerParams ----------------------------------------------------
525
527private:
529 int port;
535public:
536 cSVDRPServerParams(const char *Params);
537 const char *Name(void) const { return name; }
538 const int Port(void) const { return port; }
539 const char *VdrVersion(void) const { return vdrversion; }
540 const char *ApiVersion(void) const { return apiversion; }
541 const int Timeout(void) const { return timeout; }
542 const char *Host(void) const { return host; }
543 bool Ok(void) const { return !*error; }
544 const char *Error(void) const { return error; }
545 };
546
548{
549 if (Params && *Params) {
550 name = strgetval(Params, "name", ':');
551 if (*name) {
552 cString p = strgetval(Params, "port", ':');
553 if (*p) {
554 port = atoi(p);
555 vdrversion = strgetval(Params, "vdrversion", ':');
556 if (*vdrversion) {
557 apiversion = strgetval(Params, "apiversion", ':');
558 if (*apiversion) {
559 cString t = strgetval(Params, "timeout", ':');
560 if (*t) {
561 timeout = atoi(t);
562 if (timeout > 10) { // don't let it get too small
563 host = strgetval(Params, "host", ':');
564 // no error if missing - this parameter is optional!
565 }
566 else
567 error = "invalid timeout";
568 }
569 else
570 error = "missing server timeout";
571 }
572 else
573 error = "missing server apiversion";
574 }
575 else
576 error = "missing server vdrversion";
577 }
578 else
579 error = "missing server port";
580 }
581 else
582 error = "missing server name";
583 }
584 else
585 error = "missing server parameters";
586}
587
588// --- cSVDRPClientHandler ---------------------------------------------------
589
591
593private:
598 void SendDiscover(void);
599 void HandleClientConnection(void);
600 void ProcessConnections(void);
601 cSVDRPClient *GetClientForServer(const char *ServerName);
602protected:
603 virtual void Action(void);
604public:
605 cSVDRPClientHandler(int TcpPort, int UdpPort);
606 virtual ~cSVDRPClientHandler();
607 void AddClient(cSVDRPServerParams &ServerParams, const char *IpAddress);
608 bool Execute(const char *ServerName, const char *Command, cStringList *Response = NULL);
609 bool GetServerNames(cStringList *ServerNames);
610 bool TriggerFetchingTimers(const char *ServerName);
611 };
612
614
616:cThread("SVDRP client handler", true)
617,udpSocket(UdpPort, false)
618{
619 tcpPort = TcpPort;
620}
621
623{
624 Cancel(3);
625 for (int i = 0; i < clientConnections.Size(); i++)
626 delete clientConnections[i];
627}
628
630{
631 for (int i = 0; i < clientConnections.Size(); i++) {
632 if (strcmp(clientConnections[i]->ServerName(), ServerName) == 0)
633 return clientConnections[i];
634 }
635 return NULL;
636}
637
639{
640 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) : "");
642}
643
645{
646 cString PollTimersCmd;
648 PollTimersCmd = cString::sprintf("POLL %s TIMERS", Setup.SVDRPHostName);
650 }
652 return; // try again next time
653 for (int i = 0; i < clientConnections.Size(); i++) {
654 cSVDRPClient *Client = clientConnections[i];
655 if (Client->Process()) {
656 if (Client->HasFetchFlag(sffConn))
657 Client->Execute(cString::sprintf("CONN name:%s port:%d vdrversion:%d apiversion:%d timeout:%d", Setup.SVDRPHostName, SVDRPTcpPort, VDRVERSNUM, APIVERSNUM, Setup.SVDRPTimeout));
658 if (Client->HasFetchFlag(sffPing))
659 Client->Execute("PING");
660 if (Client->HasFetchFlag(sffTimers)) {
661 cStringList RemoteTimers;
662 if (Client->GetRemoteTimers(RemoteTimers)) {
664 bool TimersModified = Timers->StoreRemoteTimers(Client->ServerName(), &RemoteTimers);
666 }
667 else
668 Client->SetFetchFlag(sffTimers); // try again next time
669 }
670 }
671 if (*PollTimersCmd) {
672 if (!Client->Execute(PollTimersCmd))
673 esyslog("ERROR: can't send '%s' to '%s'", *PollTimersCmd, Client->ServerName());
674 }
675 }
676 else {
678 bool TimersModified = Timers->StoreRemoteTimers(Client->ServerName(), NULL);
680 delete Client;
682 i--;
683 }
684 }
685}
686
687void cSVDRPClientHandler::AddClient(cSVDRPServerParams &ServerParams, const char *IpAddress)
688{
689 cMutexLock MutexLock(&mutex);
690 for (int i = 0; i < clientConnections.Size(); i++) {
691 if (clientConnections[i]->HasAddress(IpAddress, ServerParams.Port()))
692 return;
693 }
694 if (Setup.SVDRPPeering == spmOnly && strcmp(ServerParams.Name(), Setup.SVDRPDefaultHost) != 0)
695 return; // we only want to peer with the default host, but this isn't the default host
696 if (ServerParams.Host() && strcmp(ServerParams.Host(), Setup.SVDRPHostName) != 0)
697 return; // the remote VDR requests a specific host, but it's not us
698 clientConnections.Append(new cSVDRPClient(IpAddress, ServerParams.Port(), ServerParams.Name(), ServerParams.Timeout()));
699}
700
702{
703 cString NewDiscover = udpSocket.Discover();
704 if (*NewDiscover) {
705 cSVDRPServerParams ServerParams(NewDiscover);
706 if (ServerParams.Ok())
707 AddClient(ServerParams, udpSocket.LastIpAddress()->Address());
708 else
709 esyslog("SVDRP %s < %s ERROR: %s", Setup.SVDRPHostName, udpSocket.LastIpAddress()->Connection(), ServerParams.Error());
710 }
711}
712
714{
715 if (udpSocket.Listen()) {
717 SendDiscover();
718 while (Running()) {
720 cMutexLock MutexLock(&mutex);
723 }
726 }
727}
728
729bool cSVDRPClientHandler::Execute(const char *ServerName, const char *Command, cStringList *Response)
730{
731 cMutexLock MutexLock(&mutex);
732 if (cSVDRPClient *Client = GetClientForServer(ServerName))
733 return Client->Execute(Command, Response);
734 return false;
735}
736
738{
739 cMutexLock MutexLock(&mutex);
740 ServerNames->Clear();
741 for (int i = 0; i < clientConnections.Size(); i++) {
742 cSVDRPClient *Client = clientConnections[i];
743 if (Client->Connected())
744 ServerNames->Append(strdup(Client->ServerName()));
745 }
746 return ServerNames->Size() > 0;
747}
748
750{
751 cMutexLock MutexLock(&mutex);
752 if (cSVDRPClient *Client = GetClientForServer(ServerName)) {
753 Client->SetFetchFlag(sffTimers);
754 return true;
755 }
756 return false;
757}
758
759// --- cPUTEhandler ----------------------------------------------------------
760
762private:
763 FILE *f;
765 const char *message;
766public:
767 cPUTEhandler(void);
769 bool Process(const char *s);
770 int Status(void) { return status; }
771 const char *Message(void) { return message; }
772 };
773
775{
776 if ((f = tmpfile()) != NULL) {
777 status = 354;
778 message = "Enter EPG data, end with \".\" on a line by itself";
779 }
780 else {
781 LOG_ERROR;
782 status = 554;
783 message = "Error while opening temporary file";
784 }
785}
786
788{
789 if (f)
790 fclose(f);
791}
792
793bool cPUTEhandler::Process(const char *s)
794{
795 if (f) {
796 if (strcmp(s, ".") != 0) {
797 fputs(s, f);
798 fputc('\n', f);
799 return true;
800 }
801 else {
802 rewind(f);
803 if (cSchedules::Read(f)) {
805 status = 250;
806 message = "EPG data processed";
807 }
808 else {
809 status = 451;
810 message = "Error while processing EPG data";
811 }
812 fclose(f);
813 f = NULL;
814 }
815 }
816 return false;
817}
818
819// --- cSVDRPServer ----------------------------------------------------------
820
821#define MAXHELPTOPIC 10
822#define EITDISABLETIME 10 // seconds until EIT processing is enabled again after a CLRE command
823 // adjust the help for CLRE accordingly if changing this!
824
825const char *HelpPages[] = {
826 "CHAN [ + | - | <number> | <name> | <id> ]\n"
827 " Switch channel up, down or to the given channel number, name or id.\n"
828 " Without option (or after successfully switching to the channel)\n"
829 " it returns the current channel number and name.",
830 "CLRE [ <number> | <name> | <id> ]\n"
831 " Clear the EPG list of the given channel number, name or id.\n"
832 " Without option it clears the entire EPG list.\n"
833 " After a CLRE command, no further EPG processing is done for 10\n"
834 " seconds, so that data sent with subsequent PUTE commands doesn't\n"
835 " interfere with data from the broadcasters.",
836 "CONN name:<name> port:<port> vdrversion:<vdrversion> apiversion:<apiversion> timeout:<timeout>\n"
837 " Used by peer-to-peer connections between VDRs to tell the other VDR\n"
838 " to establish a connection to this VDR. The name is the SVDRP host name\n"
839 " of this VDR, which may differ from its DNS name.",
840 "CPYR <number> <new name>\n"
841 " Copy the recording with the given number. Before a recording can be\n"
842 " copied, an LSTR command must have been executed in order to retrieve\n"
843 " the recording numbers.\n",
844 "DELC <number> | <id>\n"
845 " Delete the channel with the given number or channel id.",
846 "DELR <id>\n"
847 " Delete the recording with the given id. Before a recording can be\n"
848 " deleted, an LSTR command should have been executed in order to retrieve\n"
849 " the recording ids. The ids are unique and don't change while this\n"
850 " instance of VDR is running.\n"
851 " CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n"
852 " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
853 "DELT <id>\n"
854 " Delete the timer with the given id. If this timer is currently recording,\n"
855 " the recording will be stopped without any warning.",
856 "EDIT <id>\n"
857 " Edit the recording with the given id. Before a recording can be\n"
858 " edited, an LSTR command should have been executed in order to retrieve\n"
859 " the recording ids.",
860 "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n"
861 " Grab the current frame and save it to the given file. Images can\n"
862 " be stored as JPEG or PNM, depending on the given file name extension.\n"
863 " The quality of the grabbed image can be in the range 0..100, where 100\n"
864 " (the default) means \"best\" (only applies to JPEG). The size parameters\n"
865 " define the size of the resulting image (default is full screen).\n"
866 " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n"
867 " data will be sent to the SVDRP connection encoded in base64. The same\n"
868 " happens if '-' (a minus sign) is given as file name, in which case the\n"
869 " image format defaults to JPEG.",
870 "HELP [ <topic> ]\n"
871 " The HELP command gives help info.",
872 "HITK [ <key> ... ]\n"
873 " Hit the given remote control key. Without option a list of all\n"
874 " valid key names is given. If more than one key is given, they are\n"
875 " entered into the remote control queue in the given sequence. There\n"
876 " can be up to 31 keys.",
877 "LSTC [ :ids ] [ :groups | <number> | <name> | <id> ]\n"
878 " List channels. Without option, all channels are listed. Otherwise\n"
879 " only the given channel is listed. If a name is given, all channels\n"
880 " containing the given string as part of their name are listed.\n"
881 " If ':groups' is given, all channels are listed including group\n"
882 " separators. The channel number of a group separator is always 0.\n"
883 " With ':ids' the channel ids are listed following the channel numbers.\n"
884 " The special number 0 can be given to list the current channel.",
885 "LSTD\n"
886 " List all available devices. Each device is listed with its name and\n"
887 " whether it is currently the primary device ('P') or it implements a\n"
888 " decoder ('D') and can be used as output device.",
889 "LSTE [ <channel> ] [ now | next | at <time> ]\n"
890 " List EPG data. Without any parameters all data of all channels is\n"
891 " listed. If a channel is given (either by number or by channel ID),\n"
892 " only data for that channel is listed. 'now', 'next', or 'at <time>'\n"
893 " restricts the returned data to present events, following events, or\n"
894 " events at the given time (which must be in time_t form).",
895 "LSTR [ <id> [ path ] ]\n"
896 " List recordings. Without option, all recordings are listed. Otherwise\n"
897 " the information for the given recording is listed. If a recording\n"
898 " id and the keyword 'path' is given, the actual file name of that\n"
899 " recording's directory is listed.\n"
900 " Note that the ids of the recordings are not necessarily given in\n"
901 " numeric order.",
902 "LSTT [ <id> ] [ id ]\n"
903 " List timers. Without option, all timers are listed. Otherwise\n"
904 " only the timer with the given id is listed. If the keyword 'id' is\n"
905 " given, the channels will be listed with their unique channel ids\n"
906 " instead of their numbers. This command lists only the timers that are\n"
907 " defined locally on this VDR, not any remote timers from other VDRs.",
908 "MESG <message>\n"
909 " Displays the given message on the OSD. The message will be queued\n"
910 " and displayed whenever this is suitable.\n",
911 "MODC <number> <settings>\n"
912 " Modify a channel. Settings must be in the same format as returned\n"
913 " by the LSTC command.",
914 "MODT <id> on | off | <settings>\n"
915 " Modify a timer. Settings must be in the same format as returned\n"
916 " by the LSTT command. The special keywords 'on' and 'off' can be\n"
917 " used to easily activate or deactivate a timer.",
918 "MOVC <number> <to>\n"
919 " Move a channel to a new position.",
920 "MOVR <id> <new name>\n"
921 " Move the recording with the given id. Before a recording can be\n"
922 " moved, an LSTR command should have been executed in order to retrieve\n"
923 " the recording ids. The ids don't change during subsequent MOVR\n"
924 " commands.\n",
925 "NEWC <settings>\n"
926 " Create a new channel. Settings must be in the same format as returned\n"
927 " by the LSTC command.",
928 "NEWT <settings>\n"
929 " Create a new timer. Settings must be in the same format as returned\n"
930 " by the LSTT command.",
931 "NEXT [ abs | rel ]\n"
932 " Show the next timer event. If no option is given, the output will be\n"
933 " in human readable form. With option 'abs' the absolute time of the next\n"
934 " event will be given as the number of seconds since the epoch (time_t\n"
935 " format), while with option 'rel' the relative time will be given as the\n"
936 " number of seconds from now until the event. If the absolute time given\n"
937 " is smaller than the current time, or if the relative time is less than\n"
938 " zero, this means that the timer is currently recording and has started\n"
939 " at the given time. The first value in the resulting line is the id\n"
940 " of the timer.",
941 "PING\n"
942 " Used by peer-to-peer connections between VDRs to keep the connection\n"
943 " from timing out. May be used at any time and simply returns a line of\n"
944 " the form '<hostname> is alive'.",
945 "PLAY <id> [ begin | <position> ]\n"
946 " Play the recording with the given id. Before a recording can be\n"
947 " played, an LSTR command should have been executed in order to retrieve\n"
948 " the recording ids.\n"
949 " The keyword 'begin' plays the recording from its very beginning, while\n"
950 " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n"
951 " position. If neither 'begin' nor a <position> are given, replay is resumed\n"
952 " at the position where any previous replay was stopped, or from the beginning\n"
953 " by default. To control or stop the replay session, use the usual remote\n"
954 " control keypresses via the HITK command.",
955 "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n"
956 " Send a command to a plugin.\n"
957 " The PLUG command without any parameters lists all plugins.\n"
958 " If only a name is given, all commands known to that plugin are listed.\n"
959 " If a command is given (optionally followed by parameters), that command\n"
960 " is sent to the plugin, and the result will be displayed.\n"
961 " The keyword 'help' lists all the SVDRP commands known to the named plugin.\n"
962 " If 'help' is followed by a command, the detailed help for that command is\n"
963 " given. The keyword 'main' initiates a call to the main menu function of the\n"
964 " given plugin.\n",
965 "POLL <name> timers\n"
966 " Used by peer-to-peer connections between VDRs to inform other machines\n"
967 " about changes to timers. The receiving VDR shall use LSTT to query the\n"
968 " remote machine with the given name about its timers and update its list\n"
969 " of timers accordingly.\n",
970 "PRIM [ <number> ]\n"
971 " Make the device with the given number the primary device.\n"
972 " Without option it returns the currently active primary device in the same\n"
973 " format as used by the LSTD command.",
974 "PUTE [ <file> ]\n"
975 " Put data into the EPG list. The data entered has to strictly follow the\n"
976 " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n"
977 " by itself terminates the input and starts processing of the data (all\n"
978 " entered data is buffered until the terminating '.' is seen).\n"
979 " If a file name is given, epg data will be read from this file (which\n"
980 " must be accessible under the given name from the machine VDR is running\n"
981 " on). In case of file input, no terminating '.' shall be given.\n",
982 "REMO [ on | off ]\n"
983 " Turns the remote control on or off. Without a parameter, the current\n"
984 " status of the remote control is reported.",
985 "SCAN\n"
986 " Forces an EPG scan. If this is a single DVB device system, the scan\n"
987 " will be done on the primary device unless it is currently recording.",
988 "STAT disk\n"
989 " Return information about disk usage (total, free, percent).",
990 "UPDT <settings>\n"
991 " Updates a timer. Settings must be in the same format as returned\n"
992 " by the LSTT command. If a timer with the same channel, day, start\n"
993 " and stop time does not yet exist, it will be created.",
994 "UPDR\n"
995 " Initiates a re-read of the recordings directory, which is the SVDRP\n"
996 " equivalent to 'touch .update'.",
997 "VOLU [ <number> | + | - | mute ]\n"
998 " Set the audio volume to the given number (which is limited to the range\n"
999 " 0...255). If the special options '+' or '-' are given, the volume will\n"
1000 " be turned up or down, respectively. The option 'mute' will toggle the\n"
1001 " audio muting. If no option is given, the current audio volume level will\n"
1002 " be returned.",
1003 "QUIT\n"
1004 " Exit vdr (SVDRP).\n"
1005 " You can also hit Ctrl-D to exit.",
1006 NULL
1007 };
1008
1009/* SVDRP Reply Codes:
1010
1011 214 Help message
1012 215 EPG or recording data record
1013 216 Image grab data (base 64)
1014 220 VDR service ready
1015 221 VDR service closing transmission channel
1016 250 Requested VDR action okay, completed
1017 354 Start sending EPG data
1018 451 Requested action aborted: local error in processing
1019 500 Syntax error, command unrecognized
1020 501 Syntax error in parameters or arguments
1021 502 Command not implemented
1022 504 Command parameter not implemented
1023 550 Requested action not taken
1024 554 Transaction failed
1025 900 Default plugin reply code
1026 901..999 Plugin specific reply codes
1027
1028*/
1029
1030const char *GetHelpTopic(const char *HelpPage)
1031{
1032 static char topic[MAXHELPTOPIC];
1033 const char *q = HelpPage;
1034 while (*q) {
1035 if (isspace(*q)) {
1036 uint n = q - HelpPage;
1037 if (n >= sizeof(topic))
1038 n = sizeof(topic) - 1;
1039 strncpy(topic, HelpPage, n);
1040 topic[n] = 0;
1041 return topic;
1042 }
1043 q++;
1044 }
1045 return NULL;
1046}
1047
1048const char *GetHelpPage(const char *Cmd, const char **p)
1049{
1050 if (p) {
1051 while (*p) {
1052 const char *t = GetHelpTopic(*p);
1053 if (strcasecmp(Cmd, t) == 0)
1054 return *p;
1055 p++;
1056 }
1057 }
1058 return NULL;
1059}
1060
1062
1064private:
1072 char *cmdLine;
1074 void Close(bool SendReply = false, bool Timeout = false);
1075 bool Send(const char *s);
1076 void Reply(int Code, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
1077 void PrintHelpTopics(const char **hp);
1078 void CmdCHAN(const char *Option);
1079 void CmdCLRE(const char *Option);
1080 void CmdCONN(const char *Option);
1081 void CmdCPYR(const char *Option);
1082 void CmdDELC(const char *Option);
1083 void CmdDELR(const char *Option);
1084 void CmdDELT(const char *Option);
1085 void CmdEDIT(const char *Option);
1086 void CmdGRAB(const char *Option);
1087 void CmdHELP(const char *Option);
1088 void CmdHITK(const char *Option);
1089 void CmdLSTC(const char *Option);
1090 void CmdLSTD(const char *Option);
1091 void CmdLSTE(const char *Option);
1092 void CmdLSTR(const char *Option);
1093 void CmdLSTT(const char *Option);
1094 void CmdMESG(const char *Option);
1095 void CmdMODC(const char *Option);
1096 void CmdMODT(const char *Option);
1097 void CmdMOVC(const char *Option);
1098 void CmdMOVR(const char *Option);
1099 void CmdNEWC(const char *Option);
1100 void CmdNEWT(const char *Option);
1101 void CmdNEXT(const char *Option);
1102 void CmdPING(const char *Option);
1103 void CmdPLAY(const char *Option);
1104 void CmdPLUG(const char *Option);
1105 void CmdPOLL(const char *Option);
1106 void CmdPRIM(const char *Option);
1107 void CmdPUTE(const char *Option);
1108 void CmdREMO(const char *Option);
1109 void CmdSCAN(const char *Option);
1110 void CmdSTAT(const char *Option);
1111 void CmdUPDT(const char *Option);
1112 void CmdUPDR(const char *Option);
1113 void CmdVOLU(const char *Option);
1114 void Execute(char *Cmd);
1115public:
1116 cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress);
1117 ~cSVDRPServer();
1118 const char *ClientName(void) const { return clientName; }
1119 bool HasConnection(void) { return file.IsOpen(); }
1120 bool Process(void);
1121 };
1122
1124
1125cSVDRPServer::cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress)
1126{
1127 socket = Socket;
1128 clientIpAddress = *ClientIpAddress;
1129 clientName = clientIpAddress.Connection(); // will be set to actual name by a CONN command
1130 PUTEhandler = NULL;
1131 numChars = 0;
1132 length = BUFSIZ;
1133 cmdLine = MALLOC(char, length);
1134 lastActivity = time(NULL);
1135 if (file.Open(socket)) {
1136 time_t now = time(NULL);
1137 Reply(220, "%s SVDRP VideoDiskRecorder %s; %s; %s", Setup.SVDRPHostName, VDRVERSION, *TimeToString(now), cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8");
1138 SVDRPServerPoller.Add(file, false);
1139 }
1140 dsyslog("SVDRP %s > %s server created", Setup.SVDRPHostName, *clientName);
1141}
1142
1144{
1145 Close(true);
1146 free(cmdLine);
1147 dsyslog("SVDRP %s < %s server destroyed", Setup.SVDRPHostName, *clientName);
1148}
1149
1150void cSVDRPServer::Close(bool SendReply, bool Timeout)
1151{
1152 if (file.IsOpen()) {
1153 if (SendReply) {
1154 Reply(221, "%s closing connection%s", Setup.SVDRPHostName, Timeout ? " (timeout)" : "");
1155 }
1156 isyslog("SVDRP %s < %s connection closed", Setup.SVDRPHostName, *clientName);
1157 SVDRPServerPoller.Del(file, false);
1158 file.Close();
1160 }
1161 close(socket);
1162}
1163
1164bool cSVDRPServer::Send(const char *s)
1165{
1166 dbgsvdrp("> S %s: %s", *clientName, s); // terminating newline is already in the string!
1167 if (safe_write(file, s, strlen(s)) < 0) {
1168 LOG_ERROR;
1169 Close();
1170 return false;
1171 }
1172 return true;
1173}
1174
1175void cSVDRPServer::Reply(int Code, const char *fmt, ...)
1176{
1177 if (file.IsOpen()) {
1178 if (Code != 0) {
1179 char *buffer = NULL;
1180 va_list ap;
1181 va_start(ap, fmt);
1182 if (vasprintf(&buffer, fmt, ap) >= 0) {
1183 char *s = buffer;
1184 while (s && *s) {
1185 char *n = strchr(s, '\n');
1186 if (n)
1187 *n = 0;
1188 char cont = ' ';
1189 if (Code < 0 || n && *(n + 1)) // trailing newlines don't count!
1190 cont = '-';
1191 if (!Send(cString::sprintf("%03d%c%s\r\n", abs(Code), cont, s)))
1192 break;
1193 s = n ? n + 1 : NULL;
1194 }
1195 }
1196 else {
1197 Reply(451, "Bad format - looks like a programming error!");
1198 esyslog("SVDRP %s < %s bad format!", Setup.SVDRPHostName, *clientName);
1199 }
1200 va_end(ap);
1201 free(buffer);
1202 }
1203 else {
1204 Reply(451, "Zero return code - looks like a programming error!");
1205 esyslog("SVDRP %s < %s zero return code!", Setup.SVDRPHostName, *clientName);
1206 }
1207 }
1208}
1209
1211{
1212 int NumPages = 0;
1213 if (hp) {
1214 while (*hp) {
1215 NumPages++;
1216 hp++;
1217 }
1218 hp -= NumPages;
1219 }
1220 const int TopicsPerLine = 5;
1221 int x = 0;
1222 for (int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
1223 char buffer[TopicsPerLine * MAXHELPTOPIC + 5];
1224 char *q = buffer;
1225 q += sprintf(q, " ");
1226 for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
1227 const char *topic = GetHelpTopic(hp[(y * TopicsPerLine + x)]);
1228 if (topic)
1229 q += sprintf(q, "%*s", -MAXHELPTOPIC, topic);
1230 }
1231 x = 0;
1232 Reply(-214, "%s", buffer);
1233 }
1234}
1235
1236void cSVDRPServer::CmdCHAN(const char *Option)
1237{
1239 if (*Option) {
1240 int n = -1;
1241 int d = 0;
1242 if (isnumber(Option)) {
1243 int o = strtol(Option, NULL, 10);
1244 if (o >= 1 && o <= cChannels::MaxNumber())
1245 n = o;
1246 }
1247 else if (strcmp(Option, "-") == 0) {
1249 if (n > 1) {
1250 n--;
1251 d = -1;
1252 }
1253 }
1254 else if (strcmp(Option, "+") == 0) {
1256 if (n < cChannels::MaxNumber()) {
1257 n++;
1258 d = 1;
1259 }
1260 }
1261 else if (const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(Option)))
1262 n = Channel->Number();
1263 else {
1264 for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1265 if (!Channel->GroupSep()) {
1266 if (strcasecmp(Channel->Name(), Option) == 0) {
1267 n = Channel->Number();
1268 break;
1269 }
1270 }
1271 }
1272 }
1273 if (n < 0) {
1274 Reply(501, "Undefined channel \"%s\"", Option);
1275 return;
1276 }
1277 if (!d) {
1278 if (const cChannel *Channel = Channels->GetByNumber(n)) {
1279 if (!cDevice::PrimaryDevice()->SwitchChannel(Channel, true)) {
1280 Reply(554, "Error switching to channel \"%d\"", Channel->Number());
1281 return;
1282 }
1283 }
1284 else {
1285 Reply(550, "Unable to find channel \"%s\"", Option);
1286 return;
1287 }
1288 }
1289 else
1291 }
1292 if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel()))
1293 Reply(250, "%d %s", Channel->Number(), Channel->Name());
1294 else
1295 Reply(550, "Unable to find channel \"%d\"", cDevice::CurrentChannel());
1296}
1297
1298void cSVDRPServer::CmdCLRE(const char *Option)
1299{
1300 if (*Option) {
1304 if (isnumber(Option)) {
1305 int o = strtol(Option, NULL, 10);
1306 if (o >= 1 && o <= cChannels::MaxNumber()) {
1307 if (const cChannel *Channel = Channels->GetByNumber(o))
1308 ChannelID = Channel->GetChannelID();
1309 }
1310 }
1311 else {
1312 ChannelID = tChannelID::FromString(Option);
1313 if (ChannelID == tChannelID::InvalidID) {
1314 for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1315 if (!Channel->GroupSep()) {
1316 if (strcasecmp(Channel->Name(), Option) == 0) {
1317 ChannelID = Channel->GetChannelID();
1318 break;
1319 }
1320 }
1321 }
1322 }
1323 }
1324 if (!(ChannelID == tChannelID::InvalidID)) {
1326 cSchedule *Schedule = NULL;
1327 ChannelID.ClrRid();
1328 for (cSchedule *p = Schedules->First(); p; p = Schedules->Next(p)) {
1329 if (p->ChannelID() == ChannelID) {
1330 Schedule = p;
1331 break;
1332 }
1333 }
1334 if (Schedule) {
1335 for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
1336 if (ChannelID == Timer->Channel()->GetChannelID().ClrRid())
1337 Timer->SetEvent(NULL);
1338 }
1339 Schedule->Cleanup(INT_MAX);
1341 Reply(250, "EPG data of channel \"%s\" cleared", Option);
1342 }
1343 else {
1344 Reply(550, "No EPG data found for channel \"%s\"", Option);
1345 return;
1346 }
1347 }
1348 else
1349 Reply(501, "Undefined channel \"%s\"", Option);
1350 }
1351 else {
1354 for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer))
1355 Timer->SetEvent(NULL); // processing all timers here (local *and* remote)
1356 for (cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->Next(Schedule))
1357 Schedule->Cleanup(INT_MAX);
1359 Reply(250, "EPG data cleared");
1360 }
1361}
1362
1363void cSVDRPServer::CmdCONN(const char *Option)
1364{
1365 if (*Option) {
1366 if (SVDRPClientHandler) {
1367 cSVDRPServerParams ServerParams(Option);
1368 if (ServerParams.Ok()) {
1369 clientName = ServerParams.Name();
1370 Reply(250, "OK"); // must finish this transaction before creating the new client
1372 }
1373 else
1374 Reply(501, "Error in server parameters: %s", ServerParams.Error());
1375 }
1376 else
1377 Reply(451, "No SVDRP client handler");
1378 }
1379 else
1380 Reply(501, "Missing server parameters");
1381}
1382
1383void cSVDRPServer::CmdDELC(const char *Option)
1384{
1385 if (*Option) {
1388 Channels->SetExplicitModify();
1389 cChannel *Channel = NULL;
1390 if (isnumber(Option))
1391 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1392 else
1393 Channel = Channels->GetByChannelID(tChannelID::FromString(Option));
1394 if (Channel) {
1395 if (const cTimer *Timer = Timers->UsesChannel(Channel)) {
1396 Reply(550, "Channel \"%s\" is in use by timer %s", Option, *Timer->ToDescr());
1397 return;
1398 }
1399 int CurrentChannelNr = cDevice::CurrentChannel();
1400 cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
1401 if (CurrentChannel && Channel == CurrentChannel) {
1402 int n = Channels->GetNextNormal(CurrentChannel->Index());
1403 if (n < 0)
1404 n = Channels->GetPrevNormal(CurrentChannel->Index());
1405 if (n < 0) {
1406 Reply(501, "Can't delete channel \"%s\" - list would be empty", Option);
1407 return;
1408 }
1409 CurrentChannel = Channels->Get(n);
1410 CurrentChannelNr = 0; // triggers channel switch below
1411 }
1412 Channels->Del(Channel);
1413 Channels->ReNumber();
1414 Channels->SetModifiedByUser();
1415 Channels->SetModified();
1416 isyslog("SVDRP %s < %s deleted channel %s", Setup.SVDRPHostName, *clientName, Option);
1417 if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
1418 if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
1419 Channels->SwitchTo(CurrentChannel->Number());
1420 else
1421 cDevice::SetCurrentChannel(CurrentChannel->Number());
1422 }
1423 Reply(250, "Channel \"%s\" deleted", Option);
1424 }
1425 else
1426 Reply(501, "Channel \"%s\" not defined", Option);
1427 }
1428 else
1429 Reply(501, "Missing channel number or id");
1430}
1431
1432static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
1433{
1434 cRecordControl *rc;
1435 if ((Reason & ruTimer) != 0 && (rc = cRecordControls::GetRecordControl(Recording->FileName())) != NULL)
1436 return cString::sprintf("Recording \"%s\" is in use by timer %d", RecordingId, rc->Timer()->Id());
1437 else if ((Reason & ruReplay) != 0)
1438 return cString::sprintf("Recording \"%s\" is being replayed", RecordingId);
1439 else if ((Reason & ruCut) != 0)
1440 return cString::sprintf("Recording \"%s\" is being edited", RecordingId);
1441 else if ((Reason & (ruMove | ruCopy)) != 0)
1442 return cString::sprintf("Recording \"%s\" is being copied/moved", RecordingId);
1443 else if (Reason)
1444 return cString::sprintf("Recording \"%s\" is in use", RecordingId);
1445 return NULL;
1446}
1447
1448void cSVDRPServer::CmdCPYR(const char *Option)
1449{
1450 if (*Option) {
1451 char *opt = strdup(Option);
1452 char *num = skipspace(opt);
1453 char *option = num;
1454 while (*option && !isspace(*option))
1455 option++;
1456 char c = *option;
1457 *option = 0;
1458 if (isnumber(num)) {
1460 Recordings->SetExplicitModify();
1461 if (cRecording *Recording = Recordings->Get(strtol(num, NULL, 10) - 1)) {
1462 if (int RecordingInUse = Recording->IsInUse())
1463 Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
1464 else {
1465 if (c)
1466 option = skipspace(++option);
1467 if (*option) {
1468 cString newName = option;
1470 if (strcmp(newName, Recording->Name())) {
1471 cString fromName = cString(ExchangeChars(strdup(Recording->Name()), true), true);
1472 cString toName = cString(ExchangeChars(strdup(*newName), true), true);
1473 cString fileName = cString(strreplace(strdup(Recording->FileName()), *fromName, *toName), true);
1474 if (MakeDirs(fileName, true) && !RecordingsHandler.Add(ruCopy, Recording->FileName(), fileName)) {
1475 Recordings->AddByName(fileName);
1476 Reply(250, "Recording \"%s\" copied to \"%s\"", Recording->Name(), *newName);
1477 }
1478 else
1479 Reply(554, "Error while copying recording \"%s\" to \"%s\"!", Recording->Name(), *newName);
1480 }
1481 else
1482 Reply(501, "Identical new recording name");
1483 }
1484 else
1485 Reply(501, "Missing new recording name");
1486 }
1487 }
1488 else
1489 Reply(550, "Recording \"%s\" not found", num);
1490 }
1491 else
1492 Reply(501, "Error in recording number \"%s\"", num);
1493 free(opt);
1494 }
1495 else
1496 Reply(501, "Missing recording number");
1497}
1498
1499void cSVDRPServer::CmdDELR(const char *Option)
1500{
1501 if (*Option) {
1502 if (isnumber(Option)) {
1504 Recordings->SetExplicitModify();
1505 if (cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1506 if (int RecordingInUse = Recording->IsInUse())
1507 Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
1508 else {
1509 if (Recording->Delete()) {
1510 Recordings->DelByName(Recording->FileName());
1511 Recordings->SetModified();
1512 isyslog("SVDRP %s < %s deleted recording %s", Setup.SVDRPHostName, *clientName, Option);
1513 Reply(250, "Recording \"%s\" deleted", Option);
1514 }
1515 else
1516 Reply(554, "Error while deleting recording!");
1517 }
1518 }
1519 else
1520 Reply(550, "Recording \"%s\" not found", Option);
1521 }
1522 else
1523 Reply(501, "Error in recording id \"%s\"", Option);
1524 }
1525 else
1526 Reply(501, "Missing recording id");
1527}
1528
1529void cSVDRPServer::CmdDELT(const char *Option)
1530{
1531 if (*Option) {
1532 if (isnumber(Option)) {
1534 Timers->SetExplicitModify();
1535 if (cTimer *Timer = Timers->GetById(strtol(Option, NULL, 10))) {
1536 if (Timer->Recording()) {
1537 Timer->Skip();
1538 cRecordControls::Process(Timers, time(NULL));
1539 }
1540 Timer->TriggerRespawn();
1541 Timers->Del(Timer);
1542 Timers->SetModified();
1543 isyslog("SVDRP %s < %s deleted timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
1544 Reply(250, "Timer \"%s\" deleted", Option);
1545 }
1546 else
1547 Reply(501, "Timer \"%s\" not defined", Option);
1548 }
1549 else
1550 Reply(501, "Error in timer number \"%s\"", Option);
1551 }
1552 else
1553 Reply(501, "Missing timer number");
1554}
1555
1556void cSVDRPServer::CmdEDIT(const char *Option)
1557{
1558 if (*Option) {
1559 if (isnumber(Option)) {
1561 if (const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1562 cMarks Marks;
1563 if (Marks.Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.Count()) {
1564 if (RecordingsHandler.Add(ruCut, Recording->FileName()))
1565 Reply(250, "Editing recording \"%s\" [%s]", Option, Recording->Title());
1566 else
1567 Reply(554, "Can't start editing process");
1568 }
1569 else
1570 Reply(554, "No editing marks defined");
1571 }
1572 else
1573 Reply(550, "Recording \"%s\" not found", Option);
1574 }
1575 else
1576 Reply(501, "Error in recording id \"%s\"", Option);
1577 }
1578 else
1579 Reply(501, "Missing recording id");
1580}
1581
1582void cSVDRPServer::CmdGRAB(const char *Option)
1583{
1584 const char *FileName = NULL;
1585 bool Jpeg = true;
1586 int Quality = -1, SizeX = -1, SizeY = -1;
1587 if (*Option) {
1588 char buf[strlen(Option) + 1];
1589 char *p = strcpy(buf, Option);
1590 const char *delim = " \t";
1591 char *strtok_next;
1592 FileName = strtok_r(p, delim, &strtok_next);
1593 // image type:
1594 const char *Extension = strrchr(FileName, '.');
1595 if (Extension) {
1596 if (strcasecmp(Extension, ".jpg") == 0 || strcasecmp(Extension, ".jpeg") == 0)
1597 Jpeg = true;
1598 else if (strcasecmp(Extension, ".pnm") == 0)
1599 Jpeg = false;
1600 else {
1601 Reply(501, "Unknown image type \"%s\"", Extension + 1);
1602 return;
1603 }
1604 if (Extension == FileName)
1605 FileName = NULL;
1606 }
1607 else if (strcmp(FileName, "-") == 0)
1608 FileName = NULL;
1609 // image quality (and obsolete type):
1610 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1611 if (strcasecmp(p, "JPEG") == 0 || strcasecmp(p, "PNM") == 0) {
1612 // tolerate for backward compatibility
1613 p = strtok_r(NULL, delim, &strtok_next);
1614 }
1615 if (p) {
1616 if (isnumber(p))
1617 Quality = atoi(p);
1618 else {
1619 Reply(501, "Invalid quality \"%s\"", p);
1620 return;
1621 }
1622 }
1623 }
1624 // image size:
1625 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1626 if (isnumber(p))
1627 SizeX = atoi(p);
1628 else {
1629 Reply(501, "Invalid sizex \"%s\"", p);
1630 return;
1631 }
1632 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1633 if (isnumber(p))
1634 SizeY = atoi(p);
1635 else {
1636 Reply(501, "Invalid sizey \"%s\"", p);
1637 return;
1638 }
1639 }
1640 else {
1641 Reply(501, "Missing sizey");
1642 return;
1643 }
1644 }
1645 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1646 Reply(501, "Unexpected parameter \"%s\"", p);
1647 return;
1648 }
1649 // canonicalize the file name:
1650 char RealFileName[PATH_MAX];
1651 if (FileName) {
1652 if (*grabImageDir) {
1653 cString s(FileName);
1654 FileName = s;
1655 const char *slash = strrchr(FileName, '/');
1656 if (!slash) {
1657 s = AddDirectory(grabImageDir, FileName);
1658 FileName = s;
1659 }
1660 slash = strrchr(FileName, '/'); // there definitely is one
1661 cString t(s);
1662 t.Truncate(slash - FileName);
1663 char *r = realpath(t, RealFileName);
1664 if (!r) {
1665 LOG_ERROR_STR(FileName);
1666 Reply(501, "Invalid file name \"%s\"", FileName);
1667 return;
1668 }
1669 strcat(RealFileName, slash);
1670 FileName = RealFileName;
1671 if (strncmp(FileName, grabImageDir, strlen(grabImageDir)) != 0) {
1672 Reply(501, "Invalid file name \"%s\"", FileName);
1673 return;
1674 }
1675 }
1676 else {
1677 Reply(550, "Grabbing to file not allowed (use \"GRAB -\" instead)");
1678 return;
1679 }
1680 }
1681 // actual grabbing:
1682 int ImageSize;
1683 uchar *Image = cDevice::PrimaryDevice()->GrabImage(ImageSize, Jpeg, Quality, SizeX, SizeY);
1684 if (Image) {
1685 if (FileName) {
1686 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
1687 if (fd >= 0) {
1688 if (safe_write(fd, Image, ImageSize) == ImageSize) {
1689 dsyslog("SVDRP %s < %s grabbed image to %s", Setup.SVDRPHostName, *clientName, FileName);
1690 Reply(250, "Grabbed image %s", Option);
1691 }
1692 else {
1693 LOG_ERROR_STR(FileName);
1694 Reply(451, "Can't write to '%s'", FileName);
1695 }
1696 close(fd);
1697 }
1698 else {
1699 LOG_ERROR_STR(FileName);
1700 Reply(451, "Can't open '%s'", FileName);
1701 }
1702 }
1703 else {
1704 cBase64Encoder Base64(Image, ImageSize);
1705 const char *s;
1706 while ((s = Base64.NextLine()) != NULL)
1707 Reply(-216, "%s", s);
1708 Reply(216, "Grabbed image %s", Option);
1709 }
1710 free(Image);
1711 }
1712 else
1713 Reply(451, "Grab image failed");
1714 }
1715 else
1716 Reply(501, "Missing filename");
1717}
1718
1719void cSVDRPServer::CmdHELP(const char *Option)
1720{
1721 if (*Option) {
1722 const char *hp = GetHelpPage(Option, HelpPages);
1723 if (hp)
1724 Reply(-214, "%s", hp);
1725 else {
1726 Reply(504, "HELP topic \"%s\" unknown", Option);
1727 return;
1728 }
1729 }
1730 else {
1731 Reply(-214, "This is VDR version %s", VDRVERSION);
1732 Reply(-214, "Topics:");
1734 cPlugin *plugin;
1735 for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++) {
1736 const char **hp = plugin->SVDRPHelpPages();
1737 if (hp)
1738 Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
1739 PrintHelpTopics(hp);
1740 }
1741 Reply(-214, "To report bugs in the implementation send email to");
1742 Reply(-214, " vdr-bugs@tvdr.de");
1743 }
1744 Reply(214, "End of HELP info");
1745}
1746
1747void cSVDRPServer::CmdHITK(const char *Option)
1748{
1749 if (*Option) {
1750 if (!cRemote::Enabled()) {
1751 Reply(550, "Remote control currently disabled (key \"%s\" discarded)", Option);
1752 return;
1753 }
1754 char buf[strlen(Option) + 1];
1755 strcpy(buf, Option);
1756 const char *delim = " \t";
1757 char *strtok_next;
1758 char *p = strtok_r(buf, delim, &strtok_next);
1759 int NumKeys = 0;
1760 while (p) {
1761 eKeys k = cKey::FromString(p);
1762 if (k != kNone) {
1763 if (!cRemote::Put(k)) {
1764 Reply(451, "Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
1765 return;
1766 }
1767 }
1768 else {
1769 Reply(504, "Unknown key: \"%s\"", p);
1770 return;
1771 }
1772 NumKeys++;
1773 p = strtok_r(NULL, delim, &strtok_next);
1774 }
1775 Reply(250, "Key%s \"%s\" accepted", NumKeys > 1 ? "s" : "", Option);
1776 }
1777 else {
1778 Reply(-214, "Valid <key> names for the HITK command:");
1779 for (int i = 0; i < kNone; i++) {
1780 Reply(-214, " %s", cKey::ToString(eKeys(i)));
1781 }
1782 Reply(214, "End of key list");
1783 }
1784}
1785
1786void cSVDRPServer::CmdLSTC(const char *Option)
1787{
1789 bool WithChannelIds = startswith(Option, ":ids") && (Option[4] == ' ' || Option[4] == 0);
1790 if (WithChannelIds)
1791 Option = skipspace(Option + 4);
1792 bool WithGroupSeps = strcasecmp(Option, ":groups") == 0;
1793 if (*Option && !WithGroupSeps) {
1794 if (isnumber(Option)) {
1795 int n = strtol(Option, NULL, 10);
1796 if (n == 0)
1798 if (const cChannel *Channel = Channels->GetByNumber(n))
1799 Reply(250, "%d%s%s %s", Channel->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Channel->GetChannelID().ToString() : "", *Channel->ToText());
1800 else
1801 Reply(501, "Channel \"%s\" not defined", Option);
1802 }
1803 else {
1804 const cChannel *Next = Channels->GetByChannelID(tChannelID::FromString(Option));
1805 if (!Next) {
1806 for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1807 if (!Channel->GroupSep()) {
1808 if (strcasestr(Channel->Name(), Option)) {
1809 if (Next)
1810 Reply(-250, "%d%s%s %s", Next->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Next->GetChannelID().ToString() : "", *Next->ToText());
1811 Next = Channel;
1812 }
1813 }
1814 }
1815 }
1816 if (Next)
1817 Reply(250, "%d%s%s %s", Next->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Next->GetChannelID().ToString() : "", *Next->ToText());
1818 else
1819 Reply(501, "Channel \"%s\" not defined", Option);
1820 }
1821 }
1822 else if (cChannels::MaxNumber() >= 1) {
1823 for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1824 if (WithGroupSeps)
1825 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());
1826 else if (!Channel->GroupSep())
1827 Reply(Channel->Number() < cChannels::MaxNumber() ? -250 : 250, "%d%s%s %s", Channel->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Channel->GetChannelID().ToString() : "", *Channel->ToText());
1828 }
1829 }
1830 else
1831 Reply(550, "No channels defined");
1832}
1833
1834void cSVDRPServer::CmdLSTD(const char *Option)
1835{
1836 if (cDevice::NumDevices()) {
1837 for (int i = 0; i < cDevice::NumDevices(); i++) {
1838 if (const cDevice *d = cDevice::GetDevice(i))
1839 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());
1840 }
1841 }
1842 else
1843 Reply(550, "No devices found");
1844}
1845
1846void cSVDRPServer::CmdLSTE(const char *Option)
1847{
1850 const cSchedule* Schedule = NULL;
1851 eDumpMode DumpMode = dmAll;
1852 time_t AtTime = 0;
1853 if (*Option) {
1854 char buf[strlen(Option) + 1];
1855 strcpy(buf, Option);
1856 const char *delim = " \t";
1857 char *strtok_next;
1858 char *p = strtok_r(buf, delim, &strtok_next);
1859 while (p && DumpMode == dmAll) {
1860 if (strcasecmp(p, "NOW") == 0)
1861 DumpMode = dmPresent;
1862 else if (strcasecmp(p, "NEXT") == 0)
1863 DumpMode = dmFollowing;
1864 else if (strcasecmp(p, "AT") == 0) {
1865 DumpMode = dmAtTime;
1866 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1867 if (isnumber(p))
1868 AtTime = strtol(p, NULL, 10);
1869 else {
1870 Reply(501, "Invalid time");
1871 return;
1872 }
1873 }
1874 else {
1875 Reply(501, "Missing time");
1876 return;
1877 }
1878 }
1879 else if (!Schedule) {
1880 const cChannel* Channel = NULL;
1881 if (isnumber(p))
1882 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1883 else
1884 Channel = Channels->GetByChannelID(tChannelID::FromString(Option));
1885 if (Channel) {
1886 Schedule = Schedules->GetSchedule(Channel);
1887 if (!Schedule) {
1888 Reply(550, "No schedule found");
1889 return;
1890 }
1891 }
1892 else {
1893 Reply(550, "Channel \"%s\" not defined", p);
1894 return;
1895 }
1896 }
1897 else {
1898 Reply(501, "Unknown option: \"%s\"", p);
1899 return;
1900 }
1901 p = strtok_r(NULL, delim, &strtok_next);
1902 }
1903 }
1904 int fd = dup(file);
1905 if (fd) {
1906 FILE *f = fdopen(fd, "w");
1907 if (f) {
1908 if (Schedule)
1909 Schedule->Dump(Channels, f, "215-", DumpMode, AtTime);
1910 else
1911 Schedules->Dump(f, "215-", DumpMode, AtTime);
1912 fflush(f);
1913 Reply(215, "End of EPG data");
1914 fclose(f);
1915 }
1916 else {
1917 Reply(451, "Can't open file connection");
1918 close(fd);
1919 }
1920 }
1921 else
1922 Reply(451, "Can't dup stream descriptor");
1923}
1924
1925void cSVDRPServer::CmdLSTR(const char *Option)
1926{
1927 int Number = 0;
1928 bool Path = false;
1930 if (*Option) {
1931 char buf[strlen(Option) + 1];
1932 strcpy(buf, Option);
1933 const char *delim = " \t";
1934 char *strtok_next;
1935 char *p = strtok_r(buf, delim, &strtok_next);
1936 while (p) {
1937 if (!Number) {
1938 if (isnumber(p))
1939 Number = strtol(p, NULL, 10);
1940 else {
1941 Reply(501, "Error in recording id \"%s\"", Option);
1942 return;
1943 }
1944 }
1945 else if (strcasecmp(p, "PATH") == 0)
1946 Path = true;
1947 else {
1948 Reply(501, "Unknown option: \"%s\"", p);
1949 return;
1950 }
1951 p = strtok_r(NULL, delim, &strtok_next);
1952 }
1953 if (Number) {
1954 if (const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1955 FILE *f = fdopen(file, "w");
1956 if (f) {
1957 if (Path)
1958 Reply(250, "%s", Recording->FileName());
1959 else {
1960 Recording->Info()->Write(f, "215-");
1961 fflush(f);
1962 Reply(215, "End of recording information");
1963 }
1964 // don't 'fclose(f)' here!
1965 }
1966 else
1967 Reply(451, "Can't open file connection");
1968 }
1969 else
1970 Reply(550, "Recording \"%s\" not found", Option);
1971 }
1972 }
1973 else if (Recordings->Count()) {
1974 const cRecording *Recording = Recordings->First();
1975 while (Recording) {
1976 Reply(Recording == Recordings->Last() ? 250 : -250, "%d %s", Recording->Id(), Recording->Title(' ', true));
1977 Recording = Recordings->Next(Recording);
1978 }
1979 }
1980 else
1981 Reply(550, "No recordings available");
1982}
1983
1984void cSVDRPServer::CmdLSTT(const char *Option)
1985{
1986 int Id = 0;
1987 bool UseChannelId = false;
1988 if (*Option) {
1989 char buf[strlen(Option) + 1];
1990 strcpy(buf, Option);
1991 const char *delim = " \t";
1992 char *strtok_next;
1993 char *p = strtok_r(buf, delim, &strtok_next);
1994 while (p) {
1995 if (isnumber(p))
1996 Id = strtol(p, NULL, 10);
1997 else if (strcasecmp(p, "ID") == 0)
1998 UseChannelId = true;
1999 else {
2000 Reply(501, "Unknown option: \"%s\"", p);
2001 return;
2002 }
2003 p = strtok_r(NULL, delim, &strtok_next);
2004 }
2005 }
2007 if (Id) {
2008 for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
2009 if (!Timer->Remote()) {
2010 if (Timer->Id() == Id) {
2011 Reply(250, "%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2012 return;
2013 }
2014 }
2015 }
2016 Reply(501, "Timer \"%s\" not defined", Option);
2017 return;
2018 }
2019 else {
2020 const cTimer *LastLocalTimer = Timers->Last();
2021 while (LastLocalTimer) {
2022 if (LastLocalTimer->Remote())
2023 LastLocalTimer = Timers->Prev(LastLocalTimer);
2024 else
2025 break;
2026 }
2027 if (LastLocalTimer) {
2028 for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
2029 if (!Timer->Remote())
2030 Reply(Timer != LastLocalTimer ? -250 : 250, "%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2031 if (Timer == LastLocalTimer)
2032 break;
2033 }
2034 return;
2035 }
2036 }
2037 Reply(550, "No timers defined");
2038}
2039
2040void cSVDRPServer::CmdMESG(const char *Option)
2041{
2042 if (*Option) {
2043 isyslog("SVDRP %s < %s message '%s'", Setup.SVDRPHostName, *clientName, Option);
2044 Skins.QueueMessage(mtInfo, Option);
2045 Reply(250, "Message queued");
2046 }
2047 else
2048 Reply(501, "Missing message");
2049}
2050
2051void cSVDRPServer::CmdMODC(const char *Option)
2052{
2053 if (*Option) {
2054 char *tail;
2055 int n = strtol(Option, &tail, 10);
2056 if (tail && tail != Option) {
2057 tail = skipspace(tail);
2059 Channels->SetExplicitModify();
2060 if (cChannel *Channel = Channels->GetByNumber(n)) {
2061 cChannel ch;
2062 if (ch.Parse(tail)) {
2063 if (Channels->HasUniqueChannelID(&ch, Channel)) {
2064 *Channel = ch;
2065 Channels->ReNumber();
2066 Channels->SetModifiedByUser();
2067 Channels->SetModified();
2068 isyslog("SVDRP %s < %s modified channel %d %s", Setup.SVDRPHostName, *clientName, Channel->Number(), *Channel->ToText());
2069 Reply(250, "%d %s", Channel->Number(), *Channel->ToText());
2070 }
2071 else
2072 Reply(501, "Channel settings are not unique");
2073 }
2074 else
2075 Reply(501, "Error in channel settings");
2076 }
2077 else
2078 Reply(501, "Channel \"%d\" not defined", n);
2079 }
2080 else
2081 Reply(501, "Error in channel number");
2082 }
2083 else
2084 Reply(501, "Missing channel settings");
2085}
2086
2087void cSVDRPServer::CmdMODT(const char *Option)
2088{
2089 if (*Option) {
2090 char *tail;
2091 int Id = strtol(Option, &tail, 10);
2092 if (tail && tail != Option) {
2093 tail = skipspace(tail);
2095 Timers->SetExplicitModify();
2096 if (cTimer *Timer = Timers->GetById(Id)) {
2097 bool IsRecording = Timer->HasFlags(tfRecording);
2098 cTimer t = *Timer;
2099 if (strcasecmp(tail, "ON") == 0)
2100 t.SetFlags(tfActive);
2101 else if (strcasecmp(tail, "OFF") == 0)
2102 t.ClrFlags(tfActive);
2103 else if (!t.Parse(tail)) {
2104 Reply(501, "Error in timer settings");
2105 return;
2106 }
2107 if (IsRecording && t.IsPatternTimer()) {
2108 Reply(550, "Timer is recording");
2109 return;
2110 }
2111 *Timer = t;
2112 if (IsRecording)
2113 Timer->SetFlags(tfRecording);
2114 else
2115 Timer->ClrFlags(tfRecording);
2116 Timers->SetModified();
2117 isyslog("SVDRP %s < %s modified timer %s (%s)", Setup.SVDRPHostName, *clientName, *Timer->ToDescr(), Timer->HasFlags(tfActive) ? "active" : "inactive");
2118 if (Timer->IsPatternTimer())
2119 Timer->SetEvent(NULL);
2120 Timer->TriggerRespawn();
2121 Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2122 }
2123 else
2124 Reply(501, "Timer \"%d\" not defined", Id);
2125 }
2126 else
2127 Reply(501, "Error in timer id");
2128 }
2129 else
2130 Reply(501, "Missing timer settings");
2131}
2132
2133void cSVDRPServer::CmdMOVC(const char *Option)
2134{
2135 if (*Option) {
2136 char *tail;
2137 int From = strtol(Option, &tail, 10);
2138 if (tail && tail != Option) {
2139 tail = skipspace(tail);
2140 if (tail && tail != Option) {
2141 LOCK_TIMERS_READ; // necessary to keep timers and channels in sync!
2143 Channels->SetExplicitModify();
2144 int To = strtol(tail, NULL, 10);
2145 int CurrentChannelNr = cDevice::CurrentChannel();
2146 const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
2147 cChannel *FromChannel = Channels->GetByNumber(From);
2148 if (FromChannel) {
2149 cChannel *ToChannel = Channels->GetByNumber(To);
2150 if (ToChannel) {
2151 int FromNumber = FromChannel->Number();
2152 int ToNumber = ToChannel->Number();
2153 if (FromNumber != ToNumber) {
2154 if (Channels->MoveNeedsDecrement(FromChannel, ToChannel))
2155 ToChannel = Channels->Prev(ToChannel); // cListBase::Move() doesn't know about the channel list's numbered groups!
2156 Channels->Move(FromChannel, ToChannel);
2157 Channels->ReNumber();
2158 Channels->SetModifiedByUser();
2159 Channels->SetModified();
2160 if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
2161 if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
2162 Channels->SwitchTo(CurrentChannel->Number());
2163 else
2164 cDevice::SetCurrentChannel(CurrentChannel->Number());
2165 }
2166 isyslog("SVDRP %s < %s moved channel %d to %d", Setup.SVDRPHostName, *clientName, FromNumber, ToNumber);
2167 Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
2168 }
2169 else
2170 Reply(501, "Can't move channel to same position");
2171 }
2172 else
2173 Reply(501, "Channel \"%d\" not defined", To);
2174 }
2175 else
2176 Reply(501, "Channel \"%d\" not defined", From);
2177 }
2178 else
2179 Reply(501, "Error in channel number");
2180 }
2181 else
2182 Reply(501, "Error in channel number");
2183 }
2184 else
2185 Reply(501, "Missing channel number");
2186}
2187
2188void cSVDRPServer::CmdMOVR(const char *Option)
2189{
2190 if (*Option) {
2191 char *opt = strdup(Option);
2192 char *num = skipspace(opt);
2193 char *option = num;
2194 while (*option && !isspace(*option))
2195 option++;
2196 char c = *option;
2197 *option = 0;
2198 if (isnumber(num)) {
2200 Recordings->SetExplicitModify();
2201 if (cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2202 if (int RecordingInUse = Recording->IsInUse())
2203 Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
2204 else {
2205 if (c)
2206 option = skipspace(++option);
2207 if (*option) {
2208 cString oldName = Recording->Name();
2209 if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) {
2210 Recordings->SetModified();
2211 Recordings->TouchUpdate();
2212 Reply(250, "Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name());
2213 }
2214 else
2215 Reply(554, "Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
2216 }
2217 else
2218 Reply(501, "Missing new recording name");
2219 }
2220 }
2221 else
2222 Reply(550, "Recording \"%s\" not found", num);
2223 }
2224 else
2225 Reply(501, "Error in recording id \"%s\"", num);
2226 free(opt);
2227 }
2228 else
2229 Reply(501, "Missing recording id");
2230}
2231
2232void cSVDRPServer::CmdNEWC(const char *Option)
2233{
2234 if (*Option) {
2235 cChannel ch;
2236 if (ch.Parse(Option)) {
2238 Channels->SetExplicitModify();
2239 if (Channels->HasUniqueChannelID(&ch)) {
2240 cChannel *channel = new cChannel;
2241 *channel = ch;
2242 Channels->Add(channel);
2243 Channels->ReNumber();
2244 Channels->SetModifiedByUser();
2245 Channels->SetModified();
2246 isyslog("SVDRP %s < %s new channel %d %s", Setup.SVDRPHostName, *clientName, channel->Number(), *channel->ToText());
2247 Reply(250, "%d %s", channel->Number(), *channel->ToText());
2248 }
2249 else
2250 Reply(501, "Channel settings are not unique");
2251 }
2252 else
2253 Reply(501, "Error in channel settings");
2254 }
2255 else
2256 Reply(501, "Missing channel settings");
2257}
2258
2259void cSVDRPServer::CmdNEWT(const char *Option)
2260{
2261 if (*Option) {
2262 cTimer *Timer = new cTimer;
2263 if (Timer->Parse(Option)) {
2265 Timer->ClrFlags(tfRecording);
2266 Timers->Add(Timer);
2267 isyslog("SVDRP %s < %s added timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2268 Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2269 return;
2270 }
2271 else
2272 Reply(501, "Error in timer settings");
2273 delete Timer;
2274 }
2275 else
2276 Reply(501, "Missing timer settings");
2277}
2278
2279void cSVDRPServer::CmdNEXT(const char *Option)
2280{
2282 if (const cTimer *t = Timers->GetNextActiveTimer()) {
2283 time_t Start = t->StartTime();
2284 int Id = t->Id();
2285 if (!*Option)
2286 Reply(250, "%d %s", Id, *TimeToString(Start));
2287 else if (strcasecmp(Option, "ABS") == 0)
2288 Reply(250, "%d %jd", Id, intmax_t(Start));
2289 else if (strcasecmp(Option, "REL") == 0)
2290 Reply(250, "%d %jd", Id, intmax_t(Start - time(NULL)));
2291 else
2292 Reply(501, "Unknown option: \"%s\"", Option);
2293 }
2294 else
2295 Reply(550, "No active timers");
2296}
2297
2298void cSVDRPServer::CmdPING(const char *Option)
2299{
2300 Reply(250, "%s is alive", Setup.SVDRPHostName);
2301}
2302
2303void cSVDRPServer::CmdPLAY(const char *Option)
2304{
2305 if (*Option) {
2306 char *opt = strdup(Option);
2307 char *num = skipspace(opt);
2308 char *option = num;
2309 while (*option && !isspace(*option))
2310 option++;
2311 char c = *option;
2312 *option = 0;
2313 if (isnumber(num)) {
2314 cStateKey StateKey;
2315 if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(StateKey)) {
2316 if (const cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2317 cString FileName = Recording->FileName();
2318 cString Title = Recording->Title();
2319 int FramesPerSecond = Recording->FramesPerSecond();
2320 bool IsPesRecording = Recording->IsPesRecording();
2321 StateKey.Remove(); // must give up the lock for the call to cControl::Shutdown()
2322 if (c)
2323 option = skipspace(++option);
2326 if (*option) {
2327 int pos = 0;
2328 if (strcasecmp(option, "BEGIN") != 0)
2329 pos = HMSFToIndex(option, FramesPerSecond);
2330 cResumeFile Resume(FileName, IsPesRecording);
2331 if (pos <= 0)
2332 Resume.Delete();
2333 else
2334 Resume.Save(pos);
2335 }
2339 Reply(250, "Playing recording \"%s\" [%s]", num, *Title);
2340 }
2341 else {
2342 StateKey.Remove();
2343 Reply(550, "Recording \"%s\" not found", num);
2344 }
2345 }
2346 }
2347 else
2348 Reply(501, "Error in recording id \"%s\"", num);
2349 free(opt);
2350 }
2351 else
2352 Reply(501, "Missing recording id");
2353}
2354
2355void cSVDRPServer::CmdPLUG(const char *Option)
2356{
2357 if (*Option) {
2358 char *opt = strdup(Option);
2359 char *name = skipspace(opt);
2360 char *option = name;
2361 while (*option && !isspace(*option))
2362 option++;
2363 char c = *option;
2364 *option = 0;
2365 cPlugin *plugin = cPluginManager::GetPlugin(name);
2366 if (plugin) {
2367 if (c)
2368 option = skipspace(++option);
2369 char *cmd = option;
2370 while (*option && !isspace(*option))
2371 option++;
2372 if (*option) {
2373 *option++ = 0;
2374 option = skipspace(option);
2375 }
2376 if (!*cmd || strcasecmp(cmd, "HELP") == 0) {
2377 if (*cmd && *option) {
2378 const char *hp = GetHelpPage(option, plugin->SVDRPHelpPages());
2379 if (hp) {
2380 Reply(-214, "%s", hp);
2381 Reply(214, "End of HELP info");
2382 }
2383 else
2384 Reply(504, "HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->Name());
2385 }
2386 else {
2387 Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
2388 const char **hp = plugin->SVDRPHelpPages();
2389 if (hp) {
2390 Reply(-214, "SVDRP commands:");
2391 PrintHelpTopics(hp);
2392 Reply(214, "End of HELP info");
2393 }
2394 else
2395 Reply(214, "This plugin has no SVDRP commands");
2396 }
2397 }
2398 else if (strcasecmp(cmd, "MAIN") == 0) {
2399 if (cRemote::CallPlugin(plugin->Name()))
2400 Reply(250, "Initiated call to main menu function of plugin \"%s\"", plugin->Name());
2401 else
2402 Reply(550, "A plugin call is already pending - please try again later");
2403 }
2404 else {
2405 int ReplyCode = 900;
2406 cString s = plugin->SVDRPCommand(cmd, option, ReplyCode);
2407 if (*s)
2408 Reply(abs(ReplyCode), "%s", *s);
2409 else
2410 Reply(500, "Command unrecognized: \"%s\"", cmd);
2411 }
2412 }
2413 else
2414 Reply(550, "Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
2415 free(opt);
2416 }
2417 else {
2418 Reply(-214, "Available plugins:");
2419 cPlugin *plugin;
2420 for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++)
2421 Reply(-214, "%s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
2422 Reply(214, "End of plugin list");
2423 }
2424}
2425
2426void cSVDRPServer::CmdPOLL(const char *Option)
2427{
2428 if (*Option) {
2429 char buf[strlen(Option) + 1];
2430 char *p = strcpy(buf, Option);
2431 const char *delim = " \t";
2432 char *strtok_next;
2433 char *RemoteName = strtok_r(p, delim, &strtok_next);
2434 char *ListName = strtok_r(NULL, delim, &strtok_next);
2435 if (SVDRPClientHandler) {
2436 if (ListName) {
2437 if (strcasecmp(ListName, "timers") == 0) {
2438 Reply(250, "OK"); // must send reply before calling TriggerFetchingTimers() to avoid a deadlock if two clients send each other POLL commands at the same time
2440 }
2441 else
2442 Reply(501, "Unknown list name: \"%s\"", ListName);
2443 }
2444 else
2445 Reply(501, "Missing list name");
2446 }
2447 else
2448 Reply(501, "No SVDRP client connections");
2449 }
2450 else
2451 Reply(501, "Missing parameters");
2452}
2453
2454void cSVDRPServer::CmdPRIM(const char *Option)
2455{
2456 int n = -1;
2457 if (*Option) {
2458 if (isnumber(Option)) {
2459 int o = strtol(Option, NULL, 10);
2460 if (o > 0 && o <= cDevice::NumDevices())
2461 n = o;
2462 else
2463 Reply(501, "Invalid device number \"%s\"", Option);
2464 }
2465 else
2466 Reply(501, "Invalid parameter \"%s\"", Option);
2467 if (n >= 0) {
2468 Setup.PrimaryDVB = n;
2469 Reply(250, "Primary device set to %d", n);
2470 }
2471 }
2472 else {
2473 if (const cDevice *d = cDevice::PrimaryDevice())
2474 Reply(250, "%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ? "D" : "-", d->DeviceNumber() + 1 == Setup.PrimaryDVB ? "P" : "-", *d->DeviceName());
2475 else
2476 Reply(501, "Failed to get primary device");
2477 }
2478}
2479
2480void cSVDRPServer::CmdPUTE(const char *Option)
2481{
2482 if (*Option) {
2483 FILE *f = fopen(Option, "r");
2484 if (f) {
2485 if (cSchedules::Read(f)) {
2486 cSchedules::Cleanup(true);
2487 Reply(250, "EPG data processed from \"%s\"", Option);
2488 }
2489 else
2490 Reply(451, "Error while processing EPG from \"%s\"", Option);
2491 fclose(f);
2492 }
2493 else
2494 Reply(501, "Cannot open file \"%s\"", Option);
2495 }
2496 else {
2497 delete PUTEhandler;
2500 if (PUTEhandler->Status() != 354)
2502 }
2503}
2504
2505void cSVDRPServer::CmdREMO(const char *Option)
2506{
2507 if (*Option) {
2508 if (!strcasecmp(Option, "ON")) {
2509 cRemote::SetEnabled(true);
2510 Reply(250, "Remote control enabled");
2511 }
2512 else if (!strcasecmp(Option, "OFF")) {
2513 cRemote::SetEnabled(false);
2514 Reply(250, "Remote control disabled");
2515 }
2516 else
2517 Reply(501, "Invalid Option \"%s\"", Option);
2518 }
2519 else
2520 Reply(250, "Remote control is %s", cRemote::Enabled() ? "enabled" : "disabled");
2521}
2522
2523void cSVDRPServer::CmdSCAN(const char *Option)
2524{
2526 Reply(250, "EPG scan triggered");
2527}
2528
2529void cSVDRPServer::CmdSTAT(const char *Option)
2530{
2531 if (*Option) {
2532 if (strcasecmp(Option, "DISK") == 0) {
2533 int FreeMB, UsedMB;
2534 int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB);
2535 Reply(250, "%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
2536 }
2537 else
2538 Reply(501, "Invalid Option \"%s\"", Option);
2539 }
2540 else
2541 Reply(501, "No option given");
2542}
2543
2544void cSVDRPServer::CmdUPDT(const char *Option)
2545{
2546 if (*Option) {
2547 cTimer *Timer = new cTimer;
2548 if (Timer->Parse(Option)) {
2550 if (cTimer *t = Timers->GetTimer(Timer)) {
2551 bool IsRecording = t->HasFlags(tfRecording);
2552 t->Parse(Option);
2553 delete Timer;
2554 Timer = t;
2555 if (IsRecording)
2556 Timer->SetFlags(tfRecording);
2557 else
2558 Timer->ClrFlags(tfRecording);
2559 isyslog("SVDRP %s < %s updated timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2560 }
2561 else {
2562 Timer->ClrFlags(tfRecording);
2563 Timers->Add(Timer);
2564 isyslog("SVDRP %s < %s added timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2565 }
2566 Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2567 return;
2568 }
2569 else
2570 Reply(501, "Error in timer settings");
2571 delete Timer;
2572 }
2573 else
2574 Reply(501, "Missing timer settings");
2575}
2576
2577void cSVDRPServer::CmdUPDR(const char *Option)
2578{
2580 Recordings->Update(false);
2581 Reply(250, "Re-read of recordings directory triggered");
2582}
2583
2584void cSVDRPServer::CmdVOLU(const char *Option)
2585{
2586 if (*Option) {
2587 if (isnumber(Option))
2588 cDevice::PrimaryDevice()->SetVolume(strtol(Option, NULL, 10), true);
2589 else if (strcmp(Option, "+") == 0)
2591 else if (strcmp(Option, "-") == 0)
2593 else if (strcasecmp(Option, "MUTE") == 0)
2595 else {
2596 Reply(501, "Unknown option: \"%s\"", Option);
2597 return;
2598 }
2599 }
2600 if (cDevice::PrimaryDevice()->IsMute())
2601 Reply(250, "Audio is mute");
2602 else
2603 Reply(250, "Audio volume is %d", cDevice::CurrentVolume());
2604}
2605
2606#define CMD(c) (strcasecmp(Cmd, c) == 0)
2607
2609{
2610 // handle PUTE data:
2611 if (PUTEhandler) {
2612 if (!PUTEhandler->Process(Cmd)) {
2615 }
2616 cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME); // re-trigger the timeout, in case there is very much EPG data
2617 return;
2618 }
2619 // skip leading whitespace:
2620 Cmd = skipspace(Cmd);
2621 // find the end of the command word:
2622 char *s = Cmd;
2623 while (*s && !isspace(*s))
2624 s++;
2625 if (*s)
2626 *s++ = 0;
2627 s = skipspace(s);
2628 if (CMD("CHAN")) CmdCHAN(s);
2629 else if (CMD("CLRE")) CmdCLRE(s);
2630 else if (CMD("CONN")) CmdCONN(s);
2631 else if (CMD("DELC")) CmdDELC(s);
2632 else if (CMD("DELR")) CmdDELR(s);
2633 else if (CMD("DELT")) CmdDELT(s);
2634 else if (CMD("EDIT")) CmdEDIT(s);
2635 else if (CMD("GRAB")) CmdGRAB(s);
2636 else if (CMD("HELP")) CmdHELP(s);
2637 else if (CMD("HITK")) CmdHITK(s);
2638 else if (CMD("LSTC")) CmdLSTC(s);
2639 else if (CMD("LSTD")) CmdLSTD(s);
2640 else if (CMD("LSTE")) CmdLSTE(s);
2641 else if (CMD("LSTR")) CmdLSTR(s);
2642 else if (CMD("LSTT")) CmdLSTT(s);
2643 else if (CMD("MESG")) CmdMESG(s);
2644 else if (CMD("MODC")) CmdMODC(s);
2645 else if (CMD("MODT")) CmdMODT(s);
2646 else if (CMD("MOVC")) CmdMOVC(s);
2647 else if (CMD("MOVR")) CmdMOVR(s);
2648 else if (CMD("NEWC")) CmdNEWC(s);
2649 else if (CMD("NEWT")) CmdNEWT(s);
2650 else if (CMD("NEXT")) CmdNEXT(s);
2651 else if (CMD("PING")) CmdPING(s);
2652 else if (CMD("PLAY")) CmdPLAY(s);
2653 else if (CMD("PLUG")) CmdPLUG(s);
2654 else if (CMD("POLL")) CmdPOLL(s);
2655 else if (CMD("PRIM")) CmdPRIM(s);
2656 else if (CMD("PUTE")) CmdPUTE(s);
2657 else if (CMD("REMO")) CmdREMO(s);
2658 else if (CMD("SCAN")) CmdSCAN(s);
2659 else if (CMD("STAT")) CmdSTAT(s);
2660 else if (CMD("UPDR")) CmdUPDR(s);
2661 else if (CMD("UPDT")) CmdUPDT(s);
2662 else if (CMD("VOLU")) CmdVOLU(s);
2663 else if (CMD("QUIT")) Close(true);
2664 else Reply(500, "Command unrecognized: \"%s\"", Cmd);
2665}
2666
2668{
2669 if (file.IsOpen()) {
2670 while (file.Ready(false)) {
2671 unsigned char c;
2672 int r = safe_read(file, &c, 1);
2673 if (r > 0) {
2674 if (c == '\n' || c == 0x00) {
2675 // strip trailing whitespace:
2676 while (numChars > 0 && strchr(" \t\r\n", cmdLine[numChars - 1]))
2677 cmdLine[--numChars] = 0;
2678 // make sure the string is terminated:
2679 cmdLine[numChars] = 0;
2680 // showtime!
2681 dbgsvdrp("< S %s: %s\n", *clientName, cmdLine);
2683 numChars = 0;
2684 if (length > BUFSIZ) {
2685 free(cmdLine); // let's not tie up too much memory
2686 length = BUFSIZ;
2687 cmdLine = MALLOC(char, length);
2688 }
2689 }
2690 else if (c == 0x04 && numChars == 0) {
2691 // end of file (only at beginning of line)
2692 Close(true);
2693 }
2694 else if (c == 0x08 || c == 0x7F) {
2695 // backspace or delete (last character)
2696 if (numChars > 0)
2697 numChars--;
2698 }
2699 else if (c <= 0x03 || c == 0x0D) {
2700 // ignore control characters
2701 }
2702 else {
2703 if (numChars >= length - 1) {
2704 int NewLength = length + BUFSIZ;
2705 if (char *NewBuffer = (char *)realloc(cmdLine, NewLength)) {
2706 length = NewLength;
2707 cmdLine = NewBuffer;
2708 }
2709 else {
2710 esyslog("SVDRP %s < %s ERROR: out of memory", Setup.SVDRPHostName, *clientName);
2711 Close();
2712 break;
2713 }
2714 }
2715 cmdLine[numChars++] = c;
2716 cmdLine[numChars] = 0;
2717 }
2718 lastActivity = time(NULL);
2719 }
2720 else if (r <= 0) {
2721 isyslog("SVDRP %s < %s lost connection to client", Setup.SVDRPHostName, *clientName);
2722 Close();
2723 }
2724 }
2725 if (Setup.SVDRPTimeout && time(NULL) - lastActivity > Setup.SVDRPTimeout) {
2726 isyslog("SVDRP %s < %s timeout on connection", Setup.SVDRPHostName, *clientName);
2727 Close(true, true);
2728 }
2729 }
2730 return file.IsOpen();
2731}
2732
2733void SetSVDRPPorts(int TcpPort, int UdpPort)
2734{
2735 SVDRPTcpPort = TcpPort;
2736 SVDRPUdpPort = UdpPort;
2737}
2738
2739void SetSVDRPGrabImageDir(const char *GrabImageDir)
2740{
2741 grabImageDir = GrabImageDir;
2742}
2743
2744// --- cSVDRPServerHandler ---------------------------------------------------
2745
2747private:
2748 bool ready;
2751 void HandleServerConnection(void);
2752 void ProcessConnections(void);
2753protected:
2754 virtual void Action(void);
2755public:
2756 cSVDRPServerHandler(int TcpPort);
2757 virtual ~cSVDRPServerHandler();
2758 void WaitUntilReady(void);
2759 };
2760
2762
2764:cThread("SVDRP server handler", true)
2765,tcpSocket(TcpPort, true)
2766{
2767 ready = false;
2768}
2769
2771{
2772 Cancel(3);
2773 for (int i = 0; i < serverConnections.Size(); i++)
2774 delete serverConnections[i];
2775}
2776
2778{
2779 cTimeMs Timeout(3000);
2780 while (!ready && !Timeout.TimedOut())
2782}
2783
2785{
2786 for (int i = 0; i < serverConnections.Size(); i++) {
2787 if (!serverConnections[i]->Process()) {
2788 delete serverConnections[i];
2790 i--;
2791 }
2792 }
2793}
2794
2796{
2797 int NewSocket = tcpSocket.Accept();
2798 if (NewSocket >= 0)
2800}
2801
2803{
2804 if (tcpSocket.Listen()) {
2806 ready = true;
2807 while (Running()) {
2808 SVDRPServerPoller.Poll(1000);
2811 }
2813 tcpSocket.Close();
2814 }
2815}
2816
2817// --- SVDRP Handler ---------------------------------------------------------
2818
2820
2836
2838{
2839 cMutexLock MutexLock(&SVDRPHandlerMutex);
2840 delete SVDRPClientHandler;
2841 SVDRPClientHandler = NULL;
2842 delete SVDRPServerHandler;
2843 SVDRPServerHandler = NULL;
2844}
2845
2847{
2848 bool Result = false;
2849 cMutexLock MutexLock(&SVDRPHandlerMutex);
2851 Result = SVDRPClientHandler->GetServerNames(ServerNames);
2852 return Result;
2853}
2854
2855bool ExecSVDRPCommand(const char *ServerName, const char *Command, cStringList *Response)
2856{
2857 bool Result = false;
2858 cMutexLock MutexLock(&SVDRPHandlerMutex);
2860 Result = SVDRPClientHandler->Execute(ServerName, Command, Response);
2861 return Result;
2862}
2863
2864void BroadcastSVDRPCommand(const char *Command)
2865{
2866 cMutexLock MutexLock(&SVDRPHandlerMutex);
2867 cStringList ServerNames;
2868 if (SVDRPClientHandler) {
2869 if (SVDRPClientHandler->GetServerNames(&ServerNames)) {
2870 for (int i = 0; i < ServerNames.Size(); i++)
2871 ExecSVDRPCommand(ServerNames[i], Command);
2872 }
2873 }
2874}
#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:1417
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)
Definition tools.h:174
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
Definition tools.h:466
bool Ready(bool Wait=true)
Definition tools.c:1721
bool Open(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
Definition tools.c:1675
void Close(void)
Definition tools.c:1710
bool IsOpen(void)
Definition tools.h:480
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
Definition tools.h:640
cListObject * Prev(void) const
Definition tools.h:559
int Index(void) const
Definition tools.c:2132
cListObject * Next(void) const
Definition tools.h:560
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
Definition recording.c:2255
bool Process(const char *s)
Definition svdrp.c:793
cPUTEhandler(void)
Definition svdrp.c:774
int status
Definition svdrp.c:764
int Status(void)
Definition svdrp.c:770
const char * Message(void)
Definition svdrp.c:771
FILE * f
Definition svdrp.c:763
const char * message
Definition svdrp.c:765
~cPUTEhandler()
Definition svdrp.c:787
static cPlugin * GetPlugin(int Index)
Definition plugin.c:469
virtual const char * Version(void)=0
const char * Name(void)
Definition plugin.h:36
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:1531
bool Poll(int TimeoutMs=0)
Definition tools.c:1563
void Del(int FileHandle, bool Out)
Definition tools.c:1550
cTimer * Timer(void)
Definition menu.h:255
static bool Process(cTimers *Timers, time_t t)
Definition menu.c:5679
static cRecordControl * GetRecordControl(const char *FileName)
Definition menu.c:5659
int Id(void) const
Definition recording.h:146
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
Definition recording.c:1141
const char * Title(char Delimiter=' ', bool NewIndicator=false, int Level=-1) const
Definition recording.c:1159
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:2132
static const cRecordings * GetRecordingsRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of recordings for read access.
Definition recording.h:253
bool Put(uint64_t Code, bool Repeat=false, bool Release=false)
Definition remote.c:124
static bool Enabled(void)
Definition remote.h:49
static bool CallPlugin(const char *Plugin)
Initiates calling the given plugin's main menu function.
Definition remote.c:151
static void SetEnabled(bool Enabled)
Definition remote.h:50
static void SetRecording(const char *FileName)
Definition menu.c:5863
bool Save(int Index)
Definition recording.c:305
void Delete(void)
Definition recording.c:343
bool Execute(const char *ServerName, const char *Command, cStringList *Response=NULL)
Definition svdrp.c:729
void AddClient(cSVDRPServerParams &ServerParams, const char *IpAddress)
Definition svdrp.c:687
virtual ~cSVDRPClientHandler()
Definition svdrp.c:622
void SendDiscover(void)
Definition svdrp.c:638
void ProcessConnections(void)
Definition svdrp.c:644
bool GetServerNames(cStringList *ServerNames)
Definition svdrp.c:737
cSVDRPClientHandler(int TcpPort, int UdpPort)
Definition svdrp.c:615
void HandleClientConnection(void)
Definition svdrp.c:701
cSVDRPClient * GetClientForServer(const char *ServerName)
Definition svdrp.c:629
cVector< cSVDRPClient * > clientConnections
Definition svdrp.c:597
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:713
bool TriggerFetchingTimers(const char *ServerName)
Definition svdrp.c:749
cSocket udpSocket
Definition svdrp.c:596
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:2795
void ProcessConnections(void)
Definition svdrp.c:2784
cSVDRPServerHandler(int TcpPort)
Definition svdrp.c:2763
void WaitUntilReady(void)
Definition svdrp.c:2777
virtual ~cSVDRPServerHandler()
Definition svdrp.c:2770
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:2802
cSocket tcpSocket
Definition svdrp.c:2749
cVector< cSVDRPServer * > serverConnections
Definition svdrp.c:2750
const char * Host(void) const
Definition svdrp.c:542
cString error
Definition svdrp.c:534
const int Timeout(void) const
Definition svdrp.c:541
const char * ApiVersion(void) const
Definition svdrp.c:540
cString apiversion
Definition svdrp.c:531
cSVDRPServerParams(const char *Params)
Definition svdrp.c:547
const char * VdrVersion(void) const
Definition svdrp.c:539
const char * Name(void) const
Definition svdrp.c:537
cString vdrversion
Definition svdrp.c:530
const char * Error(void) const
Definition svdrp.c:544
const int Port(void) const
Definition svdrp.c:538
bool Ok(void) const
Definition svdrp.c:543
void CmdMESG(const char *Option)
Definition svdrp.c:2040
const char * ClientName(void) const
Definition svdrp.c:1118
void CmdPOLL(const char *Option)
Definition svdrp.c:2426
bool Send(const char *s)
Definition svdrp.c:1164
void CmdLSTT(const char *Option)
Definition svdrp.c:1984
time_t lastActivity
Definition svdrp.c:1073
void CmdCLRE(const char *Option)
Definition svdrp.c:1298
void Reply(int Code, const char *fmt,...) __attribute__((format(printf
Definition svdrp.c:1175
void CmdGRAB(const char *Option)
Definition svdrp.c:1582
void CmdMODC(const char *Option)
Definition svdrp.c:2051
cFile file
Definition svdrp.c:1068
cPUTEhandler * PUTEhandler
Definition svdrp.c:1069
void CmdDELC(const char *Option)
Definition svdrp.c:1383
void CmdPLUG(const char *Option)
Definition svdrp.c:2355
void CmdMODT(const char *Option)
Definition svdrp.c:2087
cIpAddress clientIpAddress
Definition svdrp.c:1066
void CmdCPYR(const char *Option)
Definition svdrp.c:1448
cString clientName
Definition svdrp.c:1067
void CmdLSTC(const char *Option)
Definition svdrp.c:1786
void CmdSCAN(const char *Option)
Definition svdrp.c:2523
void Close(bool SendReply=false, bool Timeout=false)
Definition svdrp.c:1150
~cSVDRPServer()
Definition svdrp.c:1143
void CmdPUTE(const char *Option)
Definition svdrp.c:2480
void CmdLSTR(const char *Option)
Definition svdrp.c:1925
void CmdSTAT(const char *Option)
Definition svdrp.c:2529
void CmdCHAN(const char *Option)
Definition svdrp.c:1236
void CmdHELP(const char *Option)
Definition svdrp.c:1719
bool Process(void)
Definition svdrp.c:2667
void CmdUPDT(const char *Option)
Definition svdrp.c:2544
void CmdREMO(const char *Option)
Definition svdrp.c:2505
void CmdLSTE(const char *Option)
Definition svdrp.c:1846
void CmdCONN(const char *Option)
Definition svdrp.c:1363
void CmdDELR(const char *Option)
Definition svdrp.c:1499
void Execute(char *Cmd)
Definition svdrp.c:2608
bool HasConnection(void)
Definition svdrp.c:1119
void CmdUPDR(const char *Option)
Definition svdrp.c:2577
void CmdVOLU(const char *Option)
Definition svdrp.c:2584
void CmdNEWT(const char *Option)
Definition svdrp.c:2259
void CmdEDIT(const char *Option)
Definition svdrp.c:1556
void CmdPLAY(const char *Option)
Definition svdrp.c:2303
void CmdDELT(const char *Option)
Definition svdrp.c:1529
void CmdLSTD(const char *Option)
Definition svdrp.c:1834
cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress)
Definition svdrp.c:1125
void CmdNEXT(const char *Option)
Definition svdrp.c:2279
void CmdHITK(const char *Option)
Definition svdrp.c:1747
int numChars
Definition svdrp.c:1070
void CmdNEWC(const char *Option)
Definition svdrp.c:2232
void CmdPRIM(const char *Option)
Definition svdrp.c:2454
void CmdMOVR(const char *Option)
Definition svdrp.c:2188
void CmdPING(const char *Option)
Definition svdrp.c:2298
char * cmdLine
Definition svdrp.c:1072
void CmdMOVC(const char *Option)
Definition svdrp.c:2133
void void PrintHelpTopics(const char **hp)
Definition svdrp.c:1210
bool LocalhostOnly(void)
Definition config.c:282
bool Acceptable(in_addr_t Address)
Definition config.c:293
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
char SVDRPDefaultHost[HOST_NAME_MAX]
Definition config.h:304
int SVDRPTimeout
Definition config.h:301
int SVDRPPeering
Definition config.h:302
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
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:867
bool TimedOut(void) const
Returns true if the last lock attempt this key was used with failed due to a timeout.
Definition thread.h:262
virtual void Clear(void)
Definition tools.c:1617
void SortNumerically(void)
Definition tools.h:863
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:1167
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition tools.c:1173
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:1157
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.
Definition thread.h:101
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
Definition timers.h:95
cString ToDescr(void) const
Definition timers.c:321
bool HasFlags(uint Flags) const
Definition timers.c:1010
const char * Remote(void) const
Definition timers.h:78
int Id(void) const
Definition timers.h:62
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
Definition tools.h:767
virtual void Append(T Data)
Definition tools.h:787
virtual void Remove(int Index)
Definition tools.h:801
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
eKeys
Definition keys.h:16
@ kNone
Definition keys.h:55
char * ExchangeChars(char *s, bool ToFileSystem)
Definition recording.c:675
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
Definition recording.c:3280
cRecordingsHandler RecordingsHandler
Definition recording.c:2080
struct __attribute__((packed))
Definition recording.c:2620
@ ruCut
Definition recording.h:34
@ ruReplay
Definition recording.h:32
@ ruCopy
Definition recording.h:36
@ ruTimer
Definition recording.h:31
@ ruMove
Definition recording.h:35
#define LOCK_RECORDINGS_READ
Definition recording.h:320
#define FOLDERDELIMCHAR
Definition recording.h:22
#define LOCK_RECORDINGS_WRITE
Definition recording.h:321
cSkins Skins
Definition skins.c:219
@ mtInfo
Definition skins.h:37
tChannelID & ClrRid(void)
Definition channels.h:59
static const tChannelID InvalidID
Definition channels.h:68
static tChannelID FromString(const char *s)
Definition channels.c:23
cString ToString(void) const
Definition channels.c:40
#define dbgsvdrp(a...)
Definition svdrp.c:45
static int SVDRPUdpPort
Definition svdrp.c:48
void StopSVDRPHandler(void)
Definition svdrp.c:2837
static cPoller SVDRPClientPoller
Definition svdrp.c:344
void SetSVDRPGrabImageDir(const char *GrabImageDir)
Definition svdrp.c:2739
static cString grabImageDir
Definition svdrp.c:1061
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:822
#define MAXHELPTOPIC
Definition svdrp.c:821
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:2846
static int SVDRPTcpPort
Definition svdrp.c:47
static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
Definition svdrp.c:1432
const char * HelpPages[]
Definition svdrp.c:825
static cMutex SVDRPHandlerMutex
Definition svdrp.c:2819
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:2855
static cPoller SVDRPServerPoller
Definition svdrp.c:1123
static cSVDRPServerHandler * SVDRPServerHandler
Definition svdrp.c:2761
void StartSVDRPHandler(void)
Definition svdrp.c:2821
#define MAXUDPBUF
Definition svdrp.c:99
void BroadcastSVDRPCommand(const char *Command)
Sends the given SVDRP Command string to all remote VDRs.
Definition svdrp.c:2864
#define SVDRPResonseTimeout
const char * GetHelpPage(const char *Cmd, const char **p)
Definition svdrp.c:1048
static cSVDRPClientHandler * SVDRPClientHandler
Definition svdrp.c:613
static bool DumpSVDRPDataTransfer
Definition svdrp.c:43
const char * GetHelpTopic(const char *HelpPage)
Definition svdrp.c:1030
#define CMD(c)
Definition svdrp.c:2606
void SetSVDRPPorts(int TcpPort, int UdpPort)
Definition svdrp.c:2733
@ spmOnly
Definition svdrp.h:19
int SVDRPCode(const char *s)
Returns the value of the three digit reply code of the given SVDRP response string.
Definition svdrp.h:47
cStateKey StateKeySVDRPRemoteTimersPoll
Controls whether a change to the local list of timers needs to result in sending a POLL to the remote...
#define LOCK_TIMERS_READ
Definition timers.h:244
#define LOCK_TIMERS_WRITE
Definition timers.h:245
@ tfActive
Definition timers.h:19
@ tfRecording
Definition timers.h:22
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:1249
bool MakeDirs(const char *FileName, bool IsDirectory)
Definition tools.c:499
bool startswith(const char *s, const char *p)
Definition tools.c:329
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
ssize_t safe_read(int filedes, void *buffer, size_t size)
Definition tools.c:53
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
ssize_t safe_write(int filedes, const void *buffer, size_t size)
Definition tools.c:65
bool isnumber(const char *s)
Definition tools.c:364
cString AddDirectory(const char *DirName, const char *FileName)
Definition tools.c:402
#define FATALERRNO
Definition tools.h:52
#define LOG_ERROR_STR(s)
Definition tools.h:40
unsigned char uchar
Definition tools.h:31
#define dsyslog(a...)
Definition tools.h:37
#define MALLOC(type, size)
Definition tools.h:47
char * skipspace(const char *s)
Definition tools.h:244
void DELETENULL(T *&p)
Definition tools.h:49
#define esyslog(a...)
Definition tools.h:35
#define LOG_ERROR
Definition tools.h:39
#define isyslog(a...)
Definition tools.h:36