vdr 2.6.9
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.8 2024/06/13 09:31:11 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 (!EnoughFreeDiskSpaceForEdit(Recording->FileName()))
1565 Reply(550, "Not enough free disk space to start editing process");
1566 else if (RecordingsHandler.Add(ruCut, Recording->FileName()))
1567 Reply(250, "Editing recording \"%s\" [%s]", Option, Recording->Title());
1568 else
1569 Reply(554, "Can't start editing process");
1570 }
1571 else
1572 Reply(554, "No editing marks defined");
1573 }
1574 else
1575 Reply(550, "Recording \"%s\" not found", Option);
1576 }
1577 else
1578 Reply(501, "Error in recording id \"%s\"", Option);
1579 }
1580 else
1581 Reply(501, "Missing recording id");
1582}
1583
1584void cSVDRPServer::CmdGRAB(const char *Option)
1585{
1586 const char *FileName = NULL;
1587 bool Jpeg = true;
1588 int Quality = -1, SizeX = -1, SizeY = -1;
1589 if (*Option) {
1590 char buf[strlen(Option) + 1];
1591 char *p = strcpy(buf, Option);
1592 const char *delim = " \t";
1593 char *strtok_next;
1594 FileName = strtok_r(p, delim, &strtok_next);
1595 // image type:
1596 const char *Extension = strrchr(FileName, '.');
1597 if (Extension) {
1598 if (strcasecmp(Extension, ".jpg") == 0 || strcasecmp(Extension, ".jpeg") == 0)
1599 Jpeg = true;
1600 else if (strcasecmp(Extension, ".pnm") == 0)
1601 Jpeg = false;
1602 else {
1603 Reply(501, "Unknown image type \"%s\"", Extension + 1);
1604 return;
1605 }
1606 if (Extension == FileName)
1607 FileName = NULL;
1608 }
1609 else if (strcmp(FileName, "-") == 0)
1610 FileName = NULL;
1611 // image quality (and obsolete type):
1612 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1613 if (strcasecmp(p, "JPEG") == 0 || strcasecmp(p, "PNM") == 0) {
1614 // tolerate for backward compatibility
1615 p = strtok_r(NULL, delim, &strtok_next);
1616 }
1617 if (p) {
1618 if (isnumber(p))
1619 Quality = atoi(p);
1620 else {
1621 Reply(501, "Invalid quality \"%s\"", p);
1622 return;
1623 }
1624 }
1625 }
1626 // image size:
1627 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1628 if (isnumber(p))
1629 SizeX = atoi(p);
1630 else {
1631 Reply(501, "Invalid sizex \"%s\"", p);
1632 return;
1633 }
1634 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1635 if (isnumber(p))
1636 SizeY = atoi(p);
1637 else {
1638 Reply(501, "Invalid sizey \"%s\"", p);
1639 return;
1640 }
1641 }
1642 else {
1643 Reply(501, "Missing sizey");
1644 return;
1645 }
1646 }
1647 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1648 Reply(501, "Unexpected parameter \"%s\"", p);
1649 return;
1650 }
1651 // canonicalize the file name:
1652 char RealFileName[PATH_MAX];
1653 if (FileName) {
1654 if (*grabImageDir) {
1655 cString s(FileName);
1656 FileName = s;
1657 const char *slash = strrchr(FileName, '/');
1658 if (!slash) {
1659 s = AddDirectory(grabImageDir, FileName);
1660 FileName = s;
1661 }
1662 slash = strrchr(FileName, '/'); // there definitely is one
1663 cString t(s);
1664 t.Truncate(slash - FileName);
1665 char *r = realpath(t, RealFileName);
1666 if (!r) {
1667 LOG_ERROR_STR(FileName);
1668 Reply(501, "Invalid file name \"%s\"", FileName);
1669 return;
1670 }
1671 strcat(RealFileName, slash);
1672 FileName = RealFileName;
1673 if (strncmp(FileName, grabImageDir, strlen(grabImageDir)) != 0) {
1674 Reply(501, "Invalid file name \"%s\"", FileName);
1675 return;
1676 }
1677 }
1678 else {
1679 Reply(550, "Grabbing to file not allowed (use \"GRAB -\" instead)");
1680 return;
1681 }
1682 }
1683 // actual grabbing:
1684 int ImageSize;
1685 uchar *Image = cDevice::PrimaryDevice()->GrabImage(ImageSize, Jpeg, Quality, SizeX, SizeY);
1686 if (Image) {
1687 if (FileName) {
1688 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
1689 if (fd >= 0) {
1690 if (safe_write(fd, Image, ImageSize) == ImageSize) {
1691 dsyslog("SVDRP %s < %s grabbed image to %s", Setup.SVDRPHostName, *clientName, FileName);
1692 Reply(250, "Grabbed image %s", Option);
1693 }
1694 else {
1695 LOG_ERROR_STR(FileName);
1696 Reply(451, "Can't write to '%s'", FileName);
1697 }
1698 close(fd);
1699 }
1700 else {
1701 LOG_ERROR_STR(FileName);
1702 Reply(451, "Can't open '%s'", FileName);
1703 }
1704 }
1705 else {
1706 cBase64Encoder Base64(Image, ImageSize);
1707 const char *s;
1708 while ((s = Base64.NextLine()) != NULL)
1709 Reply(-216, "%s", s);
1710 Reply(216, "Grabbed image %s", Option);
1711 }
1712 free(Image);
1713 }
1714 else
1715 Reply(451, "Grab image failed");
1716 }
1717 else
1718 Reply(501, "Missing filename");
1719}
1720
1721void cSVDRPServer::CmdHELP(const char *Option)
1722{
1723 if (*Option) {
1724 const char *hp = GetHelpPage(Option, HelpPages);
1725 if (hp)
1726 Reply(-214, "%s", hp);
1727 else {
1728 Reply(504, "HELP topic \"%s\" unknown", Option);
1729 return;
1730 }
1731 }
1732 else {
1733 Reply(-214, "This is VDR version %s", VDRVERSION);
1734 Reply(-214, "Topics:");
1736 cPlugin *plugin;
1737 for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++) {
1738 const char **hp = plugin->SVDRPHelpPages();
1739 if (hp)
1740 Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
1741 PrintHelpTopics(hp);
1742 }
1743 Reply(-214, "To report bugs in the implementation send email to");
1744 Reply(-214, " vdr-bugs@tvdr.de");
1745 }
1746 Reply(214, "End of HELP info");
1747}
1748
1749void cSVDRPServer::CmdHITK(const char *Option)
1750{
1751 if (*Option) {
1752 if (!cRemote::Enabled()) {
1753 Reply(550, "Remote control currently disabled (key \"%s\" discarded)", Option);
1754 return;
1755 }
1756 char buf[strlen(Option) + 1];
1757 strcpy(buf, Option);
1758 const char *delim = " \t";
1759 char *strtok_next;
1760 char *p = strtok_r(buf, delim, &strtok_next);
1761 int NumKeys = 0;
1762 while (p) {
1763 eKeys k = cKey::FromString(p);
1764 if (k != kNone) {
1765 if (!cRemote::Put(k)) {
1766 Reply(451, "Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
1767 return;
1768 }
1769 }
1770 else {
1771 Reply(504, "Unknown key: \"%s\"", p);
1772 return;
1773 }
1774 NumKeys++;
1775 p = strtok_r(NULL, delim, &strtok_next);
1776 }
1777 Reply(250, "Key%s \"%s\" accepted", NumKeys > 1 ? "s" : "", Option);
1778 }
1779 else {
1780 Reply(-214, "Valid <key> names for the HITK command:");
1781 for (int i = 0; i < kNone; i++) {
1782 Reply(-214, " %s", cKey::ToString(eKeys(i)));
1783 }
1784 Reply(214, "End of key list");
1785 }
1786}
1787
1788void cSVDRPServer::CmdLSTC(const char *Option)
1789{
1791 bool WithChannelIds = startswith(Option, ":ids") && (Option[4] == ' ' || Option[4] == 0);
1792 if (WithChannelIds)
1793 Option = skipspace(Option + 4);
1794 bool WithGroupSeps = strcasecmp(Option, ":groups") == 0;
1795 if (*Option && !WithGroupSeps) {
1796 if (isnumber(Option)) {
1797 int n = strtol(Option, NULL, 10);
1798 if (n == 0)
1800 if (const cChannel *Channel = Channels->GetByNumber(n))
1801 Reply(250, "%d%s%s %s", Channel->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Channel->GetChannelID().ToString() : "", *Channel->ToText());
1802 else
1803 Reply(501, "Channel \"%s\" not defined", Option);
1804 }
1805 else {
1806 const cChannel *Next = Channels->GetByChannelID(tChannelID::FromString(Option));
1807 if (!Next) {
1808 for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1809 if (!Channel->GroupSep()) {
1810 if (strcasestr(Channel->Name(), Option)) {
1811 if (Next)
1812 Reply(-250, "%d%s%s %s", Next->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Next->GetChannelID().ToString() : "", *Next->ToText());
1813 Next = Channel;
1814 }
1815 }
1816 }
1817 }
1818 if (Next)
1819 Reply(250, "%d%s%s %s", Next->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Next->GetChannelID().ToString() : "", *Next->ToText());
1820 else
1821 Reply(501, "Channel \"%s\" not defined", Option);
1822 }
1823 }
1824 else if (cChannels::MaxNumber() >= 1) {
1825 for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1826 if (WithGroupSeps)
1827 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());
1828 else if (!Channel->GroupSep())
1829 Reply(Channel->Number() < cChannels::MaxNumber() ? -250 : 250, "%d%s%s %s", Channel->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Channel->GetChannelID().ToString() : "", *Channel->ToText());
1830 }
1831 }
1832 else
1833 Reply(550, "No channels defined");
1834}
1835
1836void cSVDRPServer::CmdLSTD(const char *Option)
1837{
1838 if (cDevice::NumDevices()) {
1839 for (int i = 0; i < cDevice::NumDevices(); i++) {
1840 if (const cDevice *d = cDevice::GetDevice(i))
1841 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());
1842 }
1843 }
1844 else
1845 Reply(550, "No devices found");
1846}
1847
1848void cSVDRPServer::CmdLSTE(const char *Option)
1849{
1852 const cSchedule* Schedule = NULL;
1853 eDumpMode DumpMode = dmAll;
1854 time_t AtTime = 0;
1855 if (*Option) {
1856 char buf[strlen(Option) + 1];
1857 strcpy(buf, Option);
1858 const char *delim = " \t";
1859 char *strtok_next;
1860 char *p = strtok_r(buf, delim, &strtok_next);
1861 while (p && DumpMode == dmAll) {
1862 if (strcasecmp(p, "NOW") == 0)
1863 DumpMode = dmPresent;
1864 else if (strcasecmp(p, "NEXT") == 0)
1865 DumpMode = dmFollowing;
1866 else if (strcasecmp(p, "AT") == 0) {
1867 DumpMode = dmAtTime;
1868 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1869 if (isnumber(p))
1870 AtTime = strtol(p, NULL, 10);
1871 else {
1872 Reply(501, "Invalid time");
1873 return;
1874 }
1875 }
1876 else {
1877 Reply(501, "Missing time");
1878 return;
1879 }
1880 }
1881 else if (!Schedule) {
1882 const cChannel* Channel = NULL;
1883 if (isnumber(p))
1884 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1885 else
1886 Channel = Channels->GetByChannelID(tChannelID::FromString(Option));
1887 if (Channel) {
1888 Schedule = Schedules->GetSchedule(Channel);
1889 if (!Schedule) {
1890 Reply(550, "No schedule found");
1891 return;
1892 }
1893 }
1894 else {
1895 Reply(550, "Channel \"%s\" not defined", p);
1896 return;
1897 }
1898 }
1899 else {
1900 Reply(501, "Unknown option: \"%s\"", p);
1901 return;
1902 }
1903 p = strtok_r(NULL, delim, &strtok_next);
1904 }
1905 }
1906 int fd = dup(file);
1907 if (fd) {
1908 FILE *f = fdopen(fd, "w");
1909 if (f) {
1910 if (Schedule)
1911 Schedule->Dump(Channels, f, "215-", DumpMode, AtTime);
1912 else
1913 Schedules->Dump(f, "215-", DumpMode, AtTime);
1914 fflush(f);
1915 Reply(215, "End of EPG data");
1916 fclose(f);
1917 }
1918 else {
1919 Reply(451, "Can't open file connection");
1920 close(fd);
1921 }
1922 }
1923 else
1924 Reply(451, "Can't dup stream descriptor");
1925}
1926
1927void cSVDRPServer::CmdLSTR(const char *Option)
1928{
1929 int Number = 0;
1930 bool Path = false;
1932 if (*Option) {
1933 char buf[strlen(Option) + 1];
1934 strcpy(buf, Option);
1935 const char *delim = " \t";
1936 char *strtok_next;
1937 char *p = strtok_r(buf, delim, &strtok_next);
1938 while (p) {
1939 if (!Number) {
1940 if (isnumber(p))
1941 Number = strtol(p, NULL, 10);
1942 else {
1943 Reply(501, "Error in recording id \"%s\"", Option);
1944 return;
1945 }
1946 }
1947 else if (strcasecmp(p, "PATH") == 0)
1948 Path = true;
1949 else {
1950 Reply(501, "Unknown option: \"%s\"", p);
1951 return;
1952 }
1953 p = strtok_r(NULL, delim, &strtok_next);
1954 }
1955 if (Number) {
1956 if (const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1957 FILE *f = fdopen(file, "w");
1958 if (f) {
1959 if (Path)
1960 Reply(250, "%s", Recording->FileName());
1961 else {
1962 Recording->Info()->Write(f, "215-");
1963 fflush(f);
1964 Reply(215, "End of recording information");
1965 }
1966 // don't 'fclose(f)' here!
1967 }
1968 else
1969 Reply(451, "Can't open file connection");
1970 }
1971 else
1972 Reply(550, "Recording \"%s\" not found", Option);
1973 }
1974 }
1975 else if (Recordings->Count()) {
1976 const cRecording *Recording = Recordings->First();
1977 while (Recording) {
1978 Reply(Recording == Recordings->Last() ? 250 : -250, "%d %s", Recording->Id(), Recording->Title(' ', true));
1979 Recording = Recordings->Next(Recording);
1980 }
1981 }
1982 else
1983 Reply(550, "No recordings available");
1984}
1985
1986void cSVDRPServer::CmdLSTT(const char *Option)
1987{
1988 int Id = 0;
1989 bool UseChannelId = false;
1990 if (*Option) {
1991 char buf[strlen(Option) + 1];
1992 strcpy(buf, Option);
1993 const char *delim = " \t";
1994 char *strtok_next;
1995 char *p = strtok_r(buf, delim, &strtok_next);
1996 while (p) {
1997 if (isnumber(p))
1998 Id = strtol(p, NULL, 10);
1999 else if (strcasecmp(p, "ID") == 0)
2000 UseChannelId = true;
2001 else {
2002 Reply(501, "Unknown option: \"%s\"", p);
2003 return;
2004 }
2005 p = strtok_r(NULL, delim, &strtok_next);
2006 }
2007 }
2009 if (Id) {
2010 for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
2011 if (!Timer->Remote()) {
2012 if (Timer->Id() == Id) {
2013 Reply(250, "%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2014 return;
2015 }
2016 }
2017 }
2018 Reply(501, "Timer \"%s\" not defined", Option);
2019 return;
2020 }
2021 else {
2022 const cTimer *LastLocalTimer = Timers->Last();
2023 while (LastLocalTimer) {
2024 if (LastLocalTimer->Remote())
2025 LastLocalTimer = Timers->Prev(LastLocalTimer);
2026 else
2027 break;
2028 }
2029 if (LastLocalTimer) {
2030 for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
2031 if (!Timer->Remote())
2032 Reply(Timer != LastLocalTimer ? -250 : 250, "%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2033 if (Timer == LastLocalTimer)
2034 break;
2035 }
2036 return;
2037 }
2038 }
2039 Reply(550, "No timers defined");
2040}
2041
2042void cSVDRPServer::CmdMESG(const char *Option)
2043{
2044 if (*Option) {
2045 isyslog("SVDRP %s < %s message '%s'", Setup.SVDRPHostName, *clientName, Option);
2046 Skins.QueueMessage(mtInfo, Option);
2047 Reply(250, "Message queued");
2048 }
2049 else
2050 Reply(501, "Missing message");
2051}
2052
2053void cSVDRPServer::CmdMODC(const char *Option)
2054{
2055 if (*Option) {
2056 char *tail;
2057 int n = strtol(Option, &tail, 10);
2058 if (tail && tail != Option) {
2059 tail = skipspace(tail);
2061 Channels->SetExplicitModify();
2062 if (cChannel *Channel = Channels->GetByNumber(n)) {
2063 cChannel ch;
2064 if (ch.Parse(tail)) {
2065 if (Channels->HasUniqueChannelID(&ch, Channel)) {
2066 *Channel = ch;
2067 Channels->ReNumber();
2068 Channels->SetModifiedByUser();
2069 Channels->SetModified();
2070 isyslog("SVDRP %s < %s modified channel %d %s", Setup.SVDRPHostName, *clientName, Channel->Number(), *Channel->ToText());
2071 Reply(250, "%d %s", Channel->Number(), *Channel->ToText());
2072 }
2073 else
2074 Reply(501, "Channel settings are not unique");
2075 }
2076 else
2077 Reply(501, "Error in channel settings");
2078 }
2079 else
2080 Reply(501, "Channel \"%d\" not defined", n);
2081 }
2082 else
2083 Reply(501, "Error in channel number");
2084 }
2085 else
2086 Reply(501, "Missing channel settings");
2087}
2088
2089void cSVDRPServer::CmdMODT(const char *Option)
2090{
2091 if (*Option) {
2092 char *tail;
2093 int Id = strtol(Option, &tail, 10);
2094 if (tail && tail != Option) {
2095 tail = skipspace(tail);
2097 Timers->SetExplicitModify();
2098 if (cTimer *Timer = Timers->GetById(Id)) {
2099 bool IsRecording = Timer->HasFlags(tfRecording);
2100 cTimer t = *Timer;
2101 if (strcasecmp(tail, "ON") == 0)
2102 t.SetFlags(tfActive);
2103 else if (strcasecmp(tail, "OFF") == 0)
2104 t.ClrFlags(tfActive);
2105 else if (!t.Parse(tail)) {
2106 Reply(501, "Error in timer settings");
2107 return;
2108 }
2109 if (IsRecording && t.IsPatternTimer()) {
2110 Reply(550, "Timer is recording");
2111 return;
2112 }
2113 *Timer = t;
2114 if (IsRecording)
2115 Timer->SetFlags(tfRecording);
2116 else
2117 Timer->ClrFlags(tfRecording);
2118 Timers->SetModified();
2119 isyslog("SVDRP %s < %s modified timer %s (%s)", Setup.SVDRPHostName, *clientName, *Timer->ToDescr(), Timer->HasFlags(tfActive) ? "active" : "inactive");
2120 if (Timer->IsPatternTimer())
2121 Timer->SetEvent(NULL);
2122 Timer->TriggerRespawn();
2123 Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2124 }
2125 else
2126 Reply(501, "Timer \"%d\" not defined", Id);
2127 }
2128 else
2129 Reply(501, "Error in timer id");
2130 }
2131 else
2132 Reply(501, "Missing timer settings");
2133}
2134
2135void cSVDRPServer::CmdMOVC(const char *Option)
2136{
2137 if (*Option) {
2138 char *tail;
2139 int From = strtol(Option, &tail, 10);
2140 if (tail && tail != Option) {
2141 tail = skipspace(tail);
2142 if (tail && tail != Option) {
2143 LOCK_TIMERS_READ; // necessary to keep timers and channels in sync!
2145 Channels->SetExplicitModify();
2146 int To = strtol(tail, NULL, 10);
2147 int CurrentChannelNr = cDevice::CurrentChannel();
2148 const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
2149 cChannel *FromChannel = Channels->GetByNumber(From);
2150 if (FromChannel) {
2151 cChannel *ToChannel = Channels->GetByNumber(To);
2152 if (ToChannel) {
2153 int FromNumber = FromChannel->Number();
2154 int ToNumber = ToChannel->Number();
2155 if (FromNumber != ToNumber) {
2156 if (Channels->MoveNeedsDecrement(FromChannel, ToChannel))
2157 ToChannel = Channels->Prev(ToChannel); // cListBase::Move() doesn't know about the channel list's numbered groups!
2158 Channels->Move(FromChannel, ToChannel);
2159 Channels->ReNumber();
2160 Channels->SetModifiedByUser();
2161 Channels->SetModified();
2162 if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
2163 if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
2164 Channels->SwitchTo(CurrentChannel->Number());
2165 else
2166 cDevice::SetCurrentChannel(CurrentChannel->Number());
2167 }
2168 isyslog("SVDRP %s < %s moved channel %d to %d", Setup.SVDRPHostName, *clientName, FromNumber, ToNumber);
2169 Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
2170 }
2171 else
2172 Reply(501, "Can't move channel to same position");
2173 }
2174 else
2175 Reply(501, "Channel \"%d\" not defined", To);
2176 }
2177 else
2178 Reply(501, "Channel \"%d\" not defined", From);
2179 }
2180 else
2181 Reply(501, "Error in channel number");
2182 }
2183 else
2184 Reply(501, "Error in channel number");
2185 }
2186 else
2187 Reply(501, "Missing channel number");
2188}
2189
2190void cSVDRPServer::CmdMOVR(const char *Option)
2191{
2192 if (*Option) {
2193 char *opt = strdup(Option);
2194 char *num = skipspace(opt);
2195 char *option = num;
2196 while (*option && !isspace(*option))
2197 option++;
2198 char c = *option;
2199 *option = 0;
2200 if (isnumber(num)) {
2202 Recordings->SetExplicitModify();
2203 if (cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2204 if (int RecordingInUse = Recording->IsInUse())
2205 Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
2206 else {
2207 if (c)
2208 option = skipspace(++option);
2209 if (*option) {
2210 cString oldName = Recording->Name();
2211 if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) {
2212 Recordings->SetModified();
2213 Recordings->TouchUpdate();
2214 Reply(250, "Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name());
2215 }
2216 else
2217 Reply(554, "Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
2218 }
2219 else
2220 Reply(501, "Missing new recording name");
2221 }
2222 }
2223 else
2224 Reply(550, "Recording \"%s\" not found", num);
2225 }
2226 else
2227 Reply(501, "Error in recording id \"%s\"", num);
2228 free(opt);
2229 }
2230 else
2231 Reply(501, "Missing recording id");
2232}
2233
2234void cSVDRPServer::CmdNEWC(const char *Option)
2235{
2236 if (*Option) {
2237 cChannel ch;
2238 if (ch.Parse(Option)) {
2240 Channels->SetExplicitModify();
2241 if (Channels->HasUniqueChannelID(&ch)) {
2242 cChannel *channel = new cChannel;
2243 *channel = ch;
2244 Channels->Add(channel);
2245 Channels->ReNumber();
2246 Channels->SetModifiedByUser();
2247 Channels->SetModified();
2248 isyslog("SVDRP %s < %s new channel %d %s", Setup.SVDRPHostName, *clientName, channel->Number(), *channel->ToText());
2249 Reply(250, "%d %s", channel->Number(), *channel->ToText());
2250 }
2251 else
2252 Reply(501, "Channel settings are not unique");
2253 }
2254 else
2255 Reply(501, "Error in channel settings");
2256 }
2257 else
2258 Reply(501, "Missing channel settings");
2259}
2260
2261void cSVDRPServer::CmdNEWT(const char *Option)
2262{
2263 if (*Option) {
2264 cTimer *Timer = new cTimer;
2265 if (Timer->Parse(Option)) {
2267 Timer->ClrFlags(tfRecording);
2268 Timers->Add(Timer);
2269 isyslog("SVDRP %s < %s added timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2270 Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2271 return;
2272 }
2273 else
2274 Reply(501, "Error in timer settings");
2275 delete Timer;
2276 }
2277 else
2278 Reply(501, "Missing timer settings");
2279}
2280
2281void cSVDRPServer::CmdNEXT(const char *Option)
2282{
2284 if (const cTimer *t = Timers->GetNextActiveTimer()) {
2285 time_t Start = t->StartTime();
2286 int Id = t->Id();
2287 if (!*Option)
2288 Reply(250, "%d %s", Id, *TimeToString(Start));
2289 else if (strcasecmp(Option, "ABS") == 0)
2290 Reply(250, "%d %jd", Id, intmax_t(Start));
2291 else if (strcasecmp(Option, "REL") == 0)
2292 Reply(250, "%d %jd", Id, intmax_t(Start - time(NULL)));
2293 else
2294 Reply(501, "Unknown option: \"%s\"", Option);
2295 }
2296 else
2297 Reply(550, "No active timers");
2298}
2299
2300void cSVDRPServer::CmdPING(const char *Option)
2301{
2302 Reply(250, "%s is alive", Setup.SVDRPHostName);
2303}
2304
2305void cSVDRPServer::CmdPLAY(const char *Option)
2306{
2307 if (*Option) {
2308 char *opt = strdup(Option);
2309 char *num = skipspace(opt);
2310 char *option = num;
2311 while (*option && !isspace(*option))
2312 option++;
2313 char c = *option;
2314 *option = 0;
2315 if (isnumber(num)) {
2316 cStateKey StateKey;
2317 if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(StateKey)) {
2318 if (const cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2319 cString FileName = Recording->FileName();
2320 cString Title = Recording->Title();
2321 int FramesPerSecond = Recording->FramesPerSecond();
2322 bool IsPesRecording = Recording->IsPesRecording();
2323 StateKey.Remove(); // must give up the lock for the call to cControl::Shutdown()
2324 if (c)
2325 option = skipspace(++option);
2328 if (*option) {
2329 int pos = 0;
2330 if (strcasecmp(option, "BEGIN") != 0)
2331 pos = HMSFToIndex(option, FramesPerSecond);
2332 cResumeFile Resume(FileName, IsPesRecording);
2333 if (pos <= 0)
2334 Resume.Delete();
2335 else
2336 Resume.Save(pos);
2337 }
2341 Reply(250, "Playing recording \"%s\" [%s]", num, *Title);
2342 }
2343 else {
2344 StateKey.Remove();
2345 Reply(550, "Recording \"%s\" not found", num);
2346 }
2347 }
2348 }
2349 else
2350 Reply(501, "Error in recording id \"%s\"", num);
2351 free(opt);
2352 }
2353 else
2354 Reply(501, "Missing recording id");
2355}
2356
2357void cSVDRPServer::CmdPLUG(const char *Option)
2358{
2359 if (*Option) {
2360 char *opt = strdup(Option);
2361 char *name = skipspace(opt);
2362 char *option = name;
2363 while (*option && !isspace(*option))
2364 option++;
2365 char c = *option;
2366 *option = 0;
2367 cPlugin *plugin = cPluginManager::GetPlugin(name);
2368 if (plugin) {
2369 if (c)
2370 option = skipspace(++option);
2371 char *cmd = option;
2372 while (*option && !isspace(*option))
2373 option++;
2374 if (*option) {
2375 *option++ = 0;
2376 option = skipspace(option);
2377 }
2378 if (!*cmd || strcasecmp(cmd, "HELP") == 0) {
2379 if (*cmd && *option) {
2380 const char *hp = GetHelpPage(option, plugin->SVDRPHelpPages());
2381 if (hp) {
2382 Reply(-214, "%s", hp);
2383 Reply(214, "End of HELP info");
2384 }
2385 else
2386 Reply(504, "HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->Name());
2387 }
2388 else {
2389 Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
2390 const char **hp = plugin->SVDRPHelpPages();
2391 if (hp) {
2392 Reply(-214, "SVDRP commands:");
2393 PrintHelpTopics(hp);
2394 Reply(214, "End of HELP info");
2395 }
2396 else
2397 Reply(214, "This plugin has no SVDRP commands");
2398 }
2399 }
2400 else if (strcasecmp(cmd, "MAIN") == 0) {
2401 if (cRemote::CallPlugin(plugin->Name()))
2402 Reply(250, "Initiated call to main menu function of plugin \"%s\"", plugin->Name());
2403 else
2404 Reply(550, "A plugin call is already pending - please try again later");
2405 }
2406 else {
2407 int ReplyCode = 900;
2408 cString s = plugin->SVDRPCommand(cmd, option, ReplyCode);
2409 if (*s)
2410 Reply(abs(ReplyCode), "%s", *s);
2411 else
2412 Reply(500, "Command unrecognized: \"%s\"", cmd);
2413 }
2414 }
2415 else
2416 Reply(550, "Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
2417 free(opt);
2418 }
2419 else {
2420 Reply(-214, "Available plugins:");
2421 cPlugin *plugin;
2422 for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++)
2423 Reply(-214, "%s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
2424 Reply(214, "End of plugin list");
2425 }
2426}
2427
2428void cSVDRPServer::CmdPOLL(const char *Option)
2429{
2430 if (*Option) {
2431 char buf[strlen(Option) + 1];
2432 char *p = strcpy(buf, Option);
2433 const char *delim = " \t";
2434 char *strtok_next;
2435 char *RemoteName = strtok_r(p, delim, &strtok_next);
2436 char *ListName = strtok_r(NULL, delim, &strtok_next);
2437 if (SVDRPClientHandler) {
2438 if (ListName) {
2439 if (strcasecmp(ListName, "timers") == 0) {
2440 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
2442 }
2443 else
2444 Reply(501, "Unknown list name: \"%s\"", ListName);
2445 }
2446 else
2447 Reply(501, "Missing list name");
2448 }
2449 else
2450 Reply(501, "No SVDRP client connections");
2451 }
2452 else
2453 Reply(501, "Missing parameters");
2454}
2455
2456void cSVDRPServer::CmdPRIM(const char *Option)
2457{
2458 int n = -1;
2459 if (*Option) {
2460 if (isnumber(Option)) {
2461 int o = strtol(Option, NULL, 10);
2462 if (o > 0 && o <= cDevice::NumDevices())
2463 n = o;
2464 else
2465 Reply(501, "Invalid device number \"%s\"", Option);
2466 }
2467 else
2468 Reply(501, "Invalid parameter \"%s\"", Option);
2469 if (n >= 0) {
2470 Setup.PrimaryDVB = n;
2471 Reply(250, "Primary device set to %d", n);
2472 }
2473 }
2474 else {
2475 if (const cDevice *d = cDevice::PrimaryDevice())
2476 Reply(250, "%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ? "D" : "-", d->DeviceNumber() + 1 == Setup.PrimaryDVB ? "P" : "-", *d->DeviceName());
2477 else
2478 Reply(501, "Failed to get primary device");
2479 }
2480}
2481
2482void cSVDRPServer::CmdPUTE(const char *Option)
2483{
2484 if (*Option) {
2485 FILE *f = fopen(Option, "r");
2486 if (f) {
2487 if (cSchedules::Read(f)) {
2488 cSchedules::Cleanup(true);
2489 Reply(250, "EPG data processed from \"%s\"", Option);
2490 }
2491 else
2492 Reply(451, "Error while processing EPG from \"%s\"", Option);
2493 fclose(f);
2494 }
2495 else
2496 Reply(501, "Cannot open file \"%s\"", Option);
2497 }
2498 else {
2499 delete PUTEhandler;
2502 if (PUTEhandler->Status() != 354)
2504 }
2505}
2506
2507void cSVDRPServer::CmdREMO(const char *Option)
2508{
2509 if (*Option) {
2510 if (!strcasecmp(Option, "ON")) {
2511 cRemote::SetEnabled(true);
2512 Reply(250, "Remote control enabled");
2513 }
2514 else if (!strcasecmp(Option, "OFF")) {
2515 cRemote::SetEnabled(false);
2516 Reply(250, "Remote control disabled");
2517 }
2518 else
2519 Reply(501, "Invalid Option \"%s\"", Option);
2520 }
2521 else
2522 Reply(250, "Remote control is %s", cRemote::Enabled() ? "enabled" : "disabled");
2523}
2524
2525void cSVDRPServer::CmdSCAN(const char *Option)
2526{
2528 Reply(250, "EPG scan triggered");
2529}
2530
2531void cSVDRPServer::CmdSTAT(const char *Option)
2532{
2533 if (*Option) {
2534 if (strcasecmp(Option, "DISK") == 0) {
2535 int FreeMB, UsedMB;
2536 int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB);
2537 Reply(250, "%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
2538 }
2539 else
2540 Reply(501, "Invalid Option \"%s\"", Option);
2541 }
2542 else
2543 Reply(501, "No option given");
2544}
2545
2546void cSVDRPServer::CmdUPDT(const char *Option)
2547{
2548 if (*Option) {
2549 cTimer *Timer = new cTimer;
2550 if (Timer->Parse(Option)) {
2552 if (cTimer *t = Timers->GetTimer(Timer)) {
2553 bool IsRecording = t->HasFlags(tfRecording);
2554 t->Parse(Option);
2555 delete Timer;
2556 Timer = t;
2557 if (IsRecording)
2558 Timer->SetFlags(tfRecording);
2559 else
2560 Timer->ClrFlags(tfRecording);
2561 isyslog("SVDRP %s < %s updated timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2562 }
2563 else {
2564 Timer->ClrFlags(tfRecording);
2565 Timers->Add(Timer);
2566 isyslog("SVDRP %s < %s added timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2567 }
2568 Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2569 return;
2570 }
2571 else
2572 Reply(501, "Error in timer settings");
2573 delete Timer;
2574 }
2575 else
2576 Reply(501, "Missing timer settings");
2577}
2578
2579void cSVDRPServer::CmdUPDR(const char *Option)
2580{
2582 Recordings->Update(false);
2583 Reply(250, "Re-read of recordings directory triggered");
2584}
2585
2586void cSVDRPServer::CmdVOLU(const char *Option)
2587{
2588 if (*Option) {
2589 if (isnumber(Option))
2590 cDevice::PrimaryDevice()->SetVolume(strtol(Option, NULL, 10), true);
2591 else if (strcmp(Option, "+") == 0)
2593 else if (strcmp(Option, "-") == 0)
2595 else if (strcasecmp(Option, "MUTE") == 0)
2597 else {
2598 Reply(501, "Unknown option: \"%s\"", Option);
2599 return;
2600 }
2601 }
2602 if (cDevice::PrimaryDevice()->IsMute())
2603 Reply(250, "Audio is mute");
2604 else
2605 Reply(250, "Audio volume is %d", cDevice::CurrentVolume());
2606}
2607
2608#define CMD(c) (strcasecmp(Cmd, c) == 0)
2609
2611{
2612 // handle PUTE data:
2613 if (PUTEhandler) {
2614 if (!PUTEhandler->Process(Cmd)) {
2617 }
2618 cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME); // re-trigger the timeout, in case there is very much EPG data
2619 return;
2620 }
2621 // skip leading whitespace:
2622 Cmd = skipspace(Cmd);
2623 // find the end of the command word:
2624 char *s = Cmd;
2625 while (*s && !isspace(*s))
2626 s++;
2627 if (*s)
2628 *s++ = 0;
2629 s = skipspace(s);
2630 if (CMD("CHAN")) CmdCHAN(s);
2631 else if (CMD("CLRE")) CmdCLRE(s);
2632 else if (CMD("CONN")) CmdCONN(s);
2633 else if (CMD("DELC")) CmdDELC(s);
2634 else if (CMD("DELR")) CmdDELR(s);
2635 else if (CMD("DELT")) CmdDELT(s);
2636 else if (CMD("EDIT")) CmdEDIT(s);
2637 else if (CMD("GRAB")) CmdGRAB(s);
2638 else if (CMD("HELP")) CmdHELP(s);
2639 else if (CMD("HITK")) CmdHITK(s);
2640 else if (CMD("LSTC")) CmdLSTC(s);
2641 else if (CMD("LSTD")) CmdLSTD(s);
2642 else if (CMD("LSTE")) CmdLSTE(s);
2643 else if (CMD("LSTR")) CmdLSTR(s);
2644 else if (CMD("LSTT")) CmdLSTT(s);
2645 else if (CMD("MESG")) CmdMESG(s);
2646 else if (CMD("MODC")) CmdMODC(s);
2647 else if (CMD("MODT")) CmdMODT(s);
2648 else if (CMD("MOVC")) CmdMOVC(s);
2649 else if (CMD("MOVR")) CmdMOVR(s);
2650 else if (CMD("NEWC")) CmdNEWC(s);
2651 else if (CMD("NEWT")) CmdNEWT(s);
2652 else if (CMD("NEXT")) CmdNEXT(s);
2653 else if (CMD("PING")) CmdPING(s);
2654 else if (CMD("PLAY")) CmdPLAY(s);
2655 else if (CMD("PLUG")) CmdPLUG(s);
2656 else if (CMD("POLL")) CmdPOLL(s);
2657 else if (CMD("PRIM")) CmdPRIM(s);
2658 else if (CMD("PUTE")) CmdPUTE(s);
2659 else if (CMD("REMO")) CmdREMO(s);
2660 else if (CMD("SCAN")) CmdSCAN(s);
2661 else if (CMD("STAT")) CmdSTAT(s);
2662 else if (CMD("UPDR")) CmdUPDR(s);
2663 else if (CMD("UPDT")) CmdUPDT(s);
2664 else if (CMD("VOLU")) CmdVOLU(s);
2665 else if (CMD("QUIT")) Close(true);
2666 else Reply(500, "Command unrecognized: \"%s\"", Cmd);
2667}
2668
2670{
2671 if (file.IsOpen()) {
2672 while (file.Ready(false)) {
2673 unsigned char c;
2674 int r = safe_read(file, &c, 1);
2675 if (r > 0) {
2676 if (c == '\n' || c == 0x00) {
2677 // strip trailing whitespace:
2678 while (numChars > 0 && strchr(" \t\r\n", cmdLine[numChars - 1]))
2679 cmdLine[--numChars] = 0;
2680 // make sure the string is terminated:
2681 cmdLine[numChars] = 0;
2682 // showtime!
2683 dbgsvdrp("< S %s: %s\n", *clientName, cmdLine);
2685 numChars = 0;
2686 if (length > BUFSIZ) {
2687 free(cmdLine); // let's not tie up too much memory
2688 length = BUFSIZ;
2689 cmdLine = MALLOC(char, length);
2690 }
2691 }
2692 else if (c == 0x04 && numChars == 0) {
2693 // end of file (only at beginning of line)
2694 Close(true);
2695 }
2696 else if (c == 0x08 || c == 0x7F) {
2697 // backspace or delete (last character)
2698 if (numChars > 0)
2699 numChars--;
2700 }
2701 else if (c <= 0x03 || c == 0x0D) {
2702 // ignore control characters
2703 }
2704 else {
2705 if (numChars >= length - 1) {
2706 int NewLength = length + BUFSIZ;
2707 if (char *NewBuffer = (char *)realloc(cmdLine, NewLength)) {
2708 length = NewLength;
2709 cmdLine = NewBuffer;
2710 }
2711 else {
2712 esyslog("SVDRP %s < %s ERROR: out of memory", Setup.SVDRPHostName, *clientName);
2713 Close();
2714 break;
2715 }
2716 }
2717 cmdLine[numChars++] = c;
2718 cmdLine[numChars] = 0;
2719 }
2720 lastActivity = time(NULL);
2721 }
2722 else if (r <= 0) {
2723 isyslog("SVDRP %s < %s lost connection to client", Setup.SVDRPHostName, *clientName);
2724 Close();
2725 }
2726 }
2727 if (Setup.SVDRPTimeout && time(NULL) - lastActivity > Setup.SVDRPTimeout) {
2728 isyslog("SVDRP %s < %s timeout on connection", Setup.SVDRPHostName, *clientName);
2729 Close(true, true);
2730 }
2731 }
2732 return file.IsOpen();
2733}
2734
2735void SetSVDRPPorts(int TcpPort, int UdpPort)
2736{
2737 SVDRPTcpPort = TcpPort;
2738 SVDRPUdpPort = UdpPort;
2739}
2740
2741void SetSVDRPGrabImageDir(const char *GrabImageDir)
2742{
2743 grabImageDir = GrabImageDir;
2744}
2745
2746// --- cSVDRPServerHandler ---------------------------------------------------
2747
2749private:
2750 bool ready;
2753 void HandleServerConnection(void);
2754 void ProcessConnections(void);
2755protected:
2756 virtual void Action(void);
2757public:
2758 cSVDRPServerHandler(int TcpPort);
2759 virtual ~cSVDRPServerHandler();
2760 void WaitUntilReady(void);
2761 };
2762
2764
2766:cThread("SVDRP server handler", true)
2767,tcpSocket(TcpPort, true)
2768{
2769 ready = false;
2770}
2771
2773{
2774 Cancel(3);
2775 for (int i = 0; i < serverConnections.Size(); i++)
2776 delete serverConnections[i];
2777}
2778
2780{
2781 cTimeMs Timeout(3000);
2782 while (!ready && !Timeout.TimedOut())
2784}
2785
2787{
2788 for (int i = 0; i < serverConnections.Size(); i++) {
2789 if (!serverConnections[i]->Process()) {
2790 delete serverConnections[i];
2792 i--;
2793 }
2794 }
2795}
2796
2798{
2799 int NewSocket = tcpSocket.Accept();
2800 if (NewSocket >= 0)
2802}
2803
2805{
2806 if (tcpSocket.Listen()) {
2808 ready = true;
2809 while (Running()) {
2810 SVDRPServerPoller.Poll(1000);
2813 }
2815 tcpSocket.Close();
2816 }
2817}
2818
2819// --- SVDRP Handler ---------------------------------------------------------
2820
2822
2838
2840{
2841 cMutexLock MutexLock(&SVDRPHandlerMutex);
2842 delete SVDRPClientHandler;
2843 SVDRPClientHandler = NULL;
2844 delete SVDRPServerHandler;
2845 SVDRPServerHandler = NULL;
2846}
2847
2849{
2850 bool Result = false;
2851 cMutexLock MutexLock(&SVDRPHandlerMutex);
2853 Result = SVDRPClientHandler->GetServerNames(ServerNames);
2854 return Result;
2855}
2856
2857bool ExecSVDRPCommand(const char *ServerName, const char *Command, cStringList *Response)
2858{
2859 bool Result = false;
2860 cMutexLock MutexLock(&SVDRPHandlerMutex);
2862 Result = SVDRPClientHandler->Execute(ServerName, Command, Response);
2863 return Result;
2864}
2865
2866void BroadcastSVDRPCommand(const char *Command)
2867{
2868 cMutexLock MutexLock(&SVDRPHandlerMutex);
2869 cStringList ServerNames;
2870 if (SVDRPClientHandler) {
2871 if (SVDRPClientHandler->GetServerNames(&ServerNames)) {
2872 for (int i = 0; i < ServerNames.Size(); i++)
2873 ExecSVDRPCommand(ServerNames[i], Command);
2874 }
2875 }
2876}
#define LOCK_CHANNELS_READ
Definition channels.h:270
#define LOCK_CHANNELS_WRITE
Definition channels.h:271
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:1424
bool Parse(const char *s)
Definition channels.c:616
static cString ToText(const cChannel *Channel)
Definition channels.c:554
int Number(void) const
Definition channels.h:179
tChannelID GetChannelID(void) const
Definition channels.h:191
static int MaxNumber(void)
Definition channels.h:249
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:474
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:230
bool SwitchChannel(const cChannel *Channel, bool LiveView)
Switches the device to the given Channel, initiating transfer mode if necessary.
Definition device.c:825
static int CurrentChannel(void)
Returns the number of the current channel on the primary device.
Definition device.h:371
static void SetCurrentChannel(int ChannelNumber)
Sets the number of the current channel on the primary device, without actually switching to it.
Definition device.h:379
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:1076
static int NumDevices(void)
Returns the total number of devices.
Definition device.h:129
static int CurrentVolume(void)
Definition device.h:652
bool ToggleMute(void)
Turns the volume off or on and returns the new mute state.
Definition device.c:1047
void ForceScan(void)
Definition eitscan.c:129
static void SetDisableUntil(time_t Time)
Definition eit.c:508
Definition tools.h:466
bool Ready(bool Wait=true)
Definition tools.c:1728
bool Open(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
Definition tools.c:1682
void Close(void)
Definition tools.c:1717
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:2139
cListObject * Next(void) const
Definition tools.h:560
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
Definition recording.c:2295
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:1538
bool Poll(int TimeoutMs=0)
Definition tools.c:1570
void Del(int FileHandle, bool Out)
Definition tools.c:1557
cTimer * Timer(void)
Definition menu.h:254
static bool Process(cTimers *Timers, time_t t)
Definition menu.c:5686
static cRecordControl * GetRecordControl(const char *FileName)
Definition menu.c:5666
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:2154
static const cRecordings * GetRecordingsRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of recordings for read access.
Definition recording.h:260
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:5870
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:2797
void ProcessConnections(void)
Definition svdrp.c:2786
cSVDRPServerHandler(int TcpPort)
Definition svdrp.c:2765
void WaitUntilReady(void)
Definition svdrp.c:2779
virtual ~cSVDRPServerHandler()
Definition svdrp.c:2772
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:2804
cSocket tcpSocket
Definition svdrp.c:2751
cVector< cSVDRPServer * > serverConnections
Definition svdrp.c:2752
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:2042
const char * ClientName(void) const
Definition svdrp.c:1118
void CmdPOLL(const char *Option)
Definition svdrp.c:2428
bool Send(const char *s)
Definition svdrp.c:1164
void CmdLSTT(const char *Option)
Definition svdrp.c:1986
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:1584
void CmdMODC(const char *Option)
Definition svdrp.c:2053
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:2357
void CmdMODT(const char *Option)
Definition svdrp.c:2089
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:1788
void CmdSCAN(const char *Option)
Definition svdrp.c:2525
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:2482
void CmdLSTR(const char *Option)
Definition svdrp.c:1927
void CmdSTAT(const char *Option)
Definition svdrp.c:2531
void CmdCHAN(const char *Option)
Definition svdrp.c:1236
void CmdHELP(const char *Option)
Definition svdrp.c:1721
bool Process(void)
Definition svdrp.c:2669
void CmdUPDT(const char *Option)
Definition svdrp.c:2546
void CmdREMO(const char *Option)
Definition svdrp.c:2507
void CmdLSTE(const char *Option)
Definition svdrp.c:1848
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:2610
bool HasConnection(void)
Definition svdrp.c:1119
void CmdUPDR(const char *Option)
Definition svdrp.c:2579
void CmdVOLU(const char *Option)
Definition svdrp.c:2586
void CmdNEWT(const char *Option)
Definition svdrp.c:2261
void CmdEDIT(const char *Option)
Definition svdrp.c:1556
void CmdPLAY(const char *Option)
Definition svdrp.c:2305
void CmdDELT(const char *Option)
Definition svdrp.c:1529
void CmdLSTD(const char *Option)
Definition svdrp.c:1836
cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress)
Definition svdrp.c:1125
void CmdNEXT(const char *Option)
Definition svdrp.c:2281
void CmdHITK(const char *Option)
Definition svdrp.c:1749
int numChars
Definition svdrp.c:1070
void CmdNEWC(const char *Option)
Definition svdrp.c:2234
void CmdPRIM(const char *Option)
Definition svdrp.c:2456
void CmdMOVR(const char *Option)
Definition svdrp.c:2190
void CmdPING(const char *Option)
Definition svdrp.c:2300
char * cmdLine
Definition svdrp.c:1072
void CmdMOVC(const char *Option)
Definition svdrp.c:2135
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:1151
void Dump(const cChannels *Channels, FILE *f, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0) const
Definition epg.c:1162
static void Cleanup(bool Force=false)
Definition epg.c:1303
static bool Read(FILE *f=NULL)
Definition epg.c:1348
char SVDRPDefaultHost[HOST_NAME_MAX]
Definition config.h:306
int SVDRPTimeout
Definition config.h:303
int SVDRPPeering
Definition config.h:304
int PrimaryDVB
Definition config.h:268
char SVDRPHostName[HOST_NAME_MAX]
Definition config.h:305
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:1624
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:1174
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition tools.c:1180
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:1164
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:797
bool TimedOut(void) const
Definition tools.c:802
void ClrFlags(uint Flags)
Definition timers.c:1032
void SetFlags(uint Flags)
Definition timers.c:1027
bool IsPatternTimer(void) const
Definition timers.h:97
cString ToDescr(void) const
Definition timers.c:329
bool HasFlags(uint Flags) const
Definition timers.c:1042
const char * Remote(void) const
Definition timers.h:80
int Id(void) const
Definition timers.h:64
bool Parse(const char *s)
Definition timers.c:442
cString ToText(bool UseChannelID=false) const
Definition timers.c:319
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:1296
static cTimers * GetTimersWrite(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for write access.
Definition timers.c:1205
static const cTimers * GetTimersRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for read access.
Definition timers.c:1200
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:104
#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
bool EnoughFreeDiskSpaceForEdit(const char *FileName)
Definition recording.c:3473
char * ExchangeChars(char *s, bool ToFileSystem)
Definition recording.c:675
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
Definition recording.c:3352
cRecordingsHandler RecordingsHandler
Definition recording.c:2102
struct __attribute__((packed))
Definition recording.c:2692
@ 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:327
#define FOLDERDELIMCHAR
Definition recording.h:22
#define LOCK_RECORDINGS_WRITE
Definition recording.h:328
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:2839
static cPoller SVDRPClientPoller
Definition svdrp.c:344
void SetSVDRPGrabImageDir(const char *GrabImageDir)
Definition svdrp.c:2741
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:2848
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:2821
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:2857
static cPoller SVDRPServerPoller
Definition svdrp.c:1123
static cSVDRPServerHandler * SVDRPServerHandler
Definition svdrp.c:2763
void StartSVDRPHandler(void)
Definition svdrp.c:2823
#define MAXUDPBUF
Definition svdrp.c:99
void BroadcastSVDRPCommand(const char *Command)
Sends the given SVDRP Command string to all remote VDRs.
Definition svdrp.c:2866
#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:2608
void SetSVDRPPorts(int TcpPort, int UdpPort)
Definition svdrp.c:2735
@ 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:246
#define LOCK_TIMERS_WRITE
Definition timers.h:247
@ 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:1256
bool MakeDirs(const char *FileName, bool IsDirectory)
Definition tools.c:504
bool startswith(const char *s, const char *p)
Definition tools.c:334
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:322
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:300
ssize_t safe_write(int filedes, const void *buffer, size_t size)
Definition tools.c:65
bool isnumber(const char *s)
Definition tools.c:369
cString AddDirectory(const char *DirName, const char *FileName)
Definition tools.c:407
#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