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