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