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