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