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