vdr 2.8.1
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.17 2026/02/03 11:40:56 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 "DELC <number> | <id>\n"
866 " Delete the channel with the given number or channel id.",
867 "DELR <id>\n"
868 " Delete the recording with the given id. Before a recording can be\n"
869 " deleted, an LSTR command should have been executed in order to retrieve\n"
870 " the recording ids. The ids are unique and don't change while this\n"
871 " instance of VDR is running.\n"
872 " CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n"
873 " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
874 "DELT <id>\n"
875 " Delete the timer with the given id. If this timer is currently recording,\n"
876 " the recording will be stopped without any warning.",
877 "EDIT <id>\n"
878 " Edit the recording with the given id. Before a recording can be\n"
879 " edited, an LSTR command should have been executed in order to retrieve\n"
880 " the recording ids.",
881 "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n"
882 " Grab the current frame and save it to the given file. Images can\n"
883 " be stored as JPEG or PNM, depending on the given file name extension.\n"
884 " The quality of the grabbed image can be in the range 0..100, where 100\n"
885 " (the default) means \"best\" (only applies to JPEG). The size parameters\n"
886 " define the size of the resulting image (default is full screen).\n"
887 " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n"
888 " data will be sent to the SVDRP connection encoded in base64. The same\n"
889 " happens if '-' (a minus sign) is given as file name, in which case the\n"
890 " image format defaults to JPEG.",
891 "HELP [ <topic> ]\n"
892 " The HELP command gives help info.",
893 "HITK [ <key> ... ]\n"
894 " Hit the given remote control key. Without option a list of all\n"
895 " valid key names is given. If more than one key is given, they are\n"
896 " entered into the remote control queue in the given sequence. There\n"
897 " can be up to 31 keys.",
898 "LSTC [ :ids ] [ :groups | <number> | <name> | <id> ]\n"
899 " List channels. Without option, all channels are listed. Otherwise\n"
900 " only the given channel is listed. If a name is given, all channels\n"
901 " containing the given string as part of their name are listed.\n"
902 " If ':groups' is given, all channels are listed including group\n"
903 " separators. The channel number of a group separator is always 0.\n"
904 " With ':ids' the channel ids are listed following the channel numbers.\n"
905 " The special number 0 can be given to list the current channel.",
906 "LSTD\n"
907 " List all available devices. Each device is listed with its name and\n"
908 " whether it is currently the primary device ('P') or it implements a\n"
909 " decoder ('D') and can be used as output device.",
910 "LSTE [ <channel> ] [ now | next | at <time> ]\n"
911 " List EPG data. Without any parameters all data of all channels is\n"
912 " listed. If a channel is given (either by number or by channel ID),\n"
913 " only data for that channel is listed. 'now', 'next', or 'at <time>'\n"
914 " restricts the returned data to present events, following events, or\n"
915 " events at the given time (which must be in time_t form).",
916 "LSTR [ <id> [ path ] ]\n"
917 " List recordings. Without option, all recordings are listed. Otherwise\n"
918 " the information for the given recording is listed. If a recording\n"
919 " id and the keyword 'path' is given, the actual file name of that\n"
920 " recording's directory is listed.\n"
921 " Note that the ids of the recordings are not necessarily given in\n"
922 " numeric order.",
923 "LSTT [ <id> ] [ id ]\n"
924 " List timers. Without option, all timers are listed. Otherwise\n"
925 " only the timer with the given id is listed. If the keyword 'id' is\n"
926 " given, the channels will be listed with their unique channel ids\n"
927 " instead of their numbers. This command lists only the timers that are\n"
928 " defined locally on this VDR, not any remote timers from other VDRs.",
929 "MESG <message>\n"
930 " Displays the given message on the OSD. The message will be queued\n"
931 " and displayed whenever this is suitable.\n",
932 "MODC <number> <settings>\n"
933 " Modify a channel. Settings must be in the same format as returned\n"
934 " by the LSTC command.",
935 "MODT <id> on | off | <settings>\n"
936 " Modify a timer. Settings must be in the same format as returned\n"
937 " by the LSTT command. The special keywords 'on' and 'off' can be\n"
938 " used to easily activate or deactivate a timer.",
939 "MOVC <number> <to>\n"
940 " Move a channel to a new position.",
941 "MOVR <id> <new name>\n"
942 " Move the recording with the given id. Before a recording can be\n"
943 " moved, an LSTR command should have been executed in order to retrieve\n"
944 " the recording ids. The ids don't change during subsequent MOVR\n"
945 " commands.\n",
946 "NEWC <settings>\n"
947 " Create a new channel. Settings must be in the same format as returned\n"
948 " by the LSTC command.",
949 "NEWT <settings>\n"
950 " Create a new timer. Settings must be in the same format as returned\n"
951 " by the LSTT command. If a timer with the same channel, day, start\n"
952 " and stop time already exists, the data of the existing timer is returned\n"
953 " with code 550.",
954 "NEXT [ abs | rel ]\n"
955 " Show the next timer event. If no option is given, the output will be\n"
956 " in human readable form. With option 'abs' the absolute time of the next\n"
957 " event will be given as the number of seconds since the epoch (time_t\n"
958 " format), while with option 'rel' the relative time will be given as the\n"
959 " number of seconds from now until the event. If the absolute time given\n"
960 " is smaller than the current time, or if the relative time is less than\n"
961 " zero, this means that the timer is currently recording and has started\n"
962 " at the given time. The first value in the resulting line is the id\n"
963 " of the timer.",
964 "PING\n"
965 " Used by peer-to-peer connections between VDRs to keep the connection\n"
966 " from timing out. May be used at any time and simply returns a line of\n"
967 " the form '<hostname> is alive'.",
968 "PLAY [ <id> [ begin | <position> ] ]\n"
969 " Play the recording with the given id. Before a recording can be\n"
970 " played, an LSTR command should have been executed in order to retrieve\n"
971 " the recording ids.\n"
972 " The keyword 'begin' plays the recording from its very beginning, while\n"
973 " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n"
974 " position. If neither 'begin' nor a <position> are given, replay is resumed\n"
975 " at the position where any previous replay was stopped, or from the beginning\n"
976 " by default. To control or stop the replay session, use the usual remote\n"
977 " control keypresses via the HITK command.\n"
978 " Without any parameters PLAY returns the id and title of the recording that\n"
979 " is currently being played (if any).",
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 CmdDELC(const char *Option);
1108 void CmdDELR(const char *Option);
1109 void CmdDELT(const char *Option);
1110 void CmdEDIT(const char *Option);
1111 void CmdGRAB(const char *Option);
1112 void CmdHELP(const char *Option);
1113 void CmdHITK(const char *Option);
1114 void CmdLSTC(const char *Option);
1115 void CmdLSTD(const char *Option);
1116 void CmdLSTE(const char *Option);
1117 void CmdLSTR(const char *Option);
1118 void CmdLSTT(const char *Option);
1119 void CmdMESG(const char *Option);
1120 void CmdMODC(const char *Option);
1121 void CmdMODT(const char *Option);
1122 void CmdMOVC(const char *Option);
1123 void CmdMOVR(const char *Option);
1124 void CmdNEWC(const char *Option);
1125 void CmdNEWT(const char *Option);
1126 void CmdNEXT(const char *Option);
1127 void CmdPING(const char *Option);
1128 void CmdPLAY(const char *Option);
1129 void CmdPLUG(const char *Option);
1130 void CmdPOLL(const char *Option);
1131 void CmdPRIM(const char *Option);
1132 void CmdPUTE(const char *Option);
1133 void CmdREMO(const char *Option);
1134 void CmdSCAN(const char *Option);
1135 void CmdSTAT(const char *Option);
1136 void CmdUPDT(const char *Option);
1137 void CmdUPDR(const char *Option);
1138 void CmdVOLU(const char *Option);
1139 void Execute(char *Cmd);
1140public:
1141 cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress);
1142 ~cSVDRPServer();
1143 const char *ClientName(void) const { return clientName; }
1144 bool HasConnection(void) { return file.IsOpen(); }
1145 bool Process(void);
1146 };
1147
1149
1150cSVDRPServer::cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress)
1151{
1152 socket = Socket;
1153 clientIpAddress = *ClientIpAddress;
1154 clientName = clientIpAddress.Connection(); // will be set to actual name by a CONN command
1155 PUTEhandler = NULL;
1156 numChars = 0;
1157 length = BUFSIZ;
1158 cmdLine = MALLOC(char, length);
1159 lastActivity = time(NULL);
1160 if (file.Open(socket)) {
1161 time_t now = time(NULL);
1162 Reply(220, "%s SVDRP VideoDiskRecorder %s; %s; %s", Setup.SVDRPHostName, VDRVERSION, *TimeToString(now), cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8");
1163 SVDRPServerPoller.Add(file, false);
1164 }
1165 dsyslog("SVDRP %s > %s server created", Setup.SVDRPHostName, *clientName);
1166}
1167
1169{
1170 Close(true);
1171 free(cmdLine);
1172 dsyslog("SVDRP %s < %s server destroyed", Setup.SVDRPHostName, *clientName);
1173}
1174
1175void cSVDRPServer::Close(bool SendReply, bool Timeout)
1176{
1177 if (file.IsOpen()) {
1178 if (SendReply) {
1179 Reply(221, "%s closing connection%s", Setup.SVDRPHostName, Timeout ? " (timeout)" : "");
1180 }
1181 isyslog("SVDRP %s < %s connection closed", Setup.SVDRPHostName, *clientName);
1182 SVDRPServerPoller.Del(file, false);
1183 file.Close();
1185 }
1186 close(socket);
1187}
1188
1189bool cSVDRPServer::Send(const char *s)
1190{
1191 dbgsvdrp("> S %s: %s", *clientName, s); // terminating newline is already in the string!
1192 if (safe_write(file, s, strlen(s)) < 0) {
1193 LOG_ERROR;
1194 Close();
1195 return false;
1196 }
1197 return true;
1198}
1199
1200void cSVDRPServer::Reply(int Code, const char *fmt, ...)
1201{
1202 if (file.IsOpen()) {
1203 if (Code != 0) {
1204 char *buffer = NULL;
1205 va_list ap;
1206 va_start(ap, fmt);
1207 if (vasprintf(&buffer, fmt, ap) >= 0) {
1208 char *s = buffer;
1209 while (s && *s) {
1210 char *n = strchr(s, '\n');
1211 if (n)
1212 *n = 0;
1213 char cont = ' ';
1214 if (Code < 0 || n && *(n + 1)) // trailing newlines don't count!
1215 cont = '-';
1216 if (!Send(cString::sprintf("%03d%c%s\r\n", abs(Code), cont, s)))
1217 break;
1218 s = n ? n + 1 : NULL;
1219 }
1220 }
1221 else {
1222 Reply(451, "Bad format - looks like a programming error!");
1223 esyslog("SVDRP %s < %s bad format!", Setup.SVDRPHostName, *clientName);
1224 }
1225 va_end(ap);
1226 free(buffer);
1227 }
1228 else {
1229 Reply(451, "Zero return code - looks like a programming error!");
1230 esyslog("SVDRP %s < %s zero return code!", Setup.SVDRPHostName, *clientName);
1231 }
1232 }
1233}
1234
1236{
1237 int NumPages = 0;
1238 if (hp) {
1239 while (*hp) {
1240 NumPages++;
1241 hp++;
1242 }
1243 hp -= NumPages;
1244 }
1245 const int TopicsPerLine = 5;
1246 int x = 0;
1247 for (int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
1248 char buffer[TopicsPerLine * MAXHELPTOPIC + 5];
1249 char *q = buffer;
1250 q += sprintf(q, " ");
1251 for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
1252 const char *topic = GetHelpTopic(hp[(y * TopicsPerLine + x)]);
1253 if (topic)
1254 q += sprintf(q, "%*s", -MAXHELPTOPIC, topic);
1255 }
1256 x = 0;
1257 Reply(-214, "%s", buffer);
1258 }
1259}
1260
1261void cSVDRPServer::CmdAUDI(const char *Option)
1262{
1263 if (*Option) {
1264 if (isnumber(Option)) {
1265 int o = strtol(Option, NULL, 10);
1266 if (o >= ttAudioFirst && o <= ttDolbyLast) {
1267 const tTrackId *TrackId = cDevice::PrimaryDevice()->GetTrack(eTrackType(o));
1268 if (TrackId && TrackId->id) {
1270 Reply(250, "%d %s %s", eTrackType(o), *TrackId->language ? TrackId->language : "---", *TrackId->description ? TrackId->description : "-");
1271 }
1272 else
1273 Reply(501, "Audio track \"%s\" not available", Option);
1274 }
1275 else
1276 Reply(501, "Invalid audio track \"%s\"", Option);
1277 }
1278 else
1279 Reply(501, "Error in audio track \"%s\"", Option);
1280 }
1281 else {
1284 cString s;
1285 for (int i = ttAudioFirst; i <= ttDolbyLast; i++) {
1286 const tTrackId *TrackId = cDevice::PrimaryDevice()->GetTrack(eTrackType(i));
1287 if (TrackId && TrackId->id) {
1288 if (*s)
1289 Reply(-250, "%s", *s);
1290 s = cString::sprintf("%d %s %s%s", eTrackType(i), *TrackId->language ? TrackId->language : "---", i == CurrentAudioTrack ? "*" : "", *TrackId->description ? TrackId->description : "-");
1291 }
1292 }
1293 if (*s)
1294 Reply(250, "%s", *s);
1295 else
1296 Reply(550, "No audio tracks available");
1297 }
1298}
1299
1300void cSVDRPServer::CmdCHAN(const char *Option)
1301{
1303 if (*Option) {
1304 int n = -1;
1305 int d = 0;
1306 if (isnumber(Option)) {
1307 int o = strtol(Option, NULL, 10);
1308 if (o >= 1 && o <= cChannels::MaxNumber())
1309 n = o;
1310 }
1311 else if (strcmp(Option, "-") == 0) {
1313 if (n > 1) {
1314 n--;
1315 d = -1;
1316 }
1317 }
1318 else if (strcmp(Option, "+") == 0) {
1320 if (n < cChannels::MaxNumber()) {
1321 n++;
1322 d = 1;
1323 }
1324 }
1325 else if (const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(Option)))
1326 n = Channel->Number();
1327 else {
1328 for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1329 if (!Channel->GroupSep()) {
1330 if (strcasecmp(Channel->Name(), Option) == 0) {
1331 n = Channel->Number();
1332 break;
1333 }
1334 }
1335 }
1336 }
1337 if (n < 0) {
1338 Reply(501, "Undefined channel \"%s\"", Option);
1339 return;
1340 }
1341 if (!d) {
1342 if (const cChannel *Channel = Channels->GetByNumber(n)) {
1343 if (!cDevice::PrimaryDevice()->SwitchChannel(Channel, true)) {
1344 Reply(554, "Error switching to channel \"%d\"", Channel->Number());
1345 return;
1346 }
1347 }
1348 else {
1349 Reply(550, "Unable to find channel \"%s\"", Option);
1350 return;
1351 }
1352 }
1353 else
1355 }
1356 if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel()))
1357 Reply(250, "%d %s", Channel->Number(), Channel->Name());
1358 else
1359 Reply(550, "Unable to find channel \"%d\"", cDevice::CurrentChannel());
1360}
1361
1362void cSVDRPServer::CmdCLRE(const char *Option)
1363{
1364 if (*Option) {
1368 if (isnumber(Option)) {
1369 int o = strtol(Option, NULL, 10);
1370 if (o >= 1 && o <= cChannels::MaxNumber()) {
1371 if (const cChannel *Channel = Channels->GetByNumber(o))
1372 ChannelID = Channel->GetChannelID();
1373 }
1374 }
1375 else {
1376 ChannelID = tChannelID::FromString(Option);
1377 if (ChannelID == tChannelID::InvalidID) {
1378 for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1379 if (!Channel->GroupSep()) {
1380 if (strcasecmp(Channel->Name(), Option) == 0) {
1381 ChannelID = Channel->GetChannelID();
1382 break;
1383 }
1384 }
1385 }
1386 }
1387 }
1388 if (!(ChannelID == tChannelID::InvalidID)) {
1390 cSchedule *Schedule = NULL;
1391 ChannelID.ClrRid();
1392 for (cSchedule *p = Schedules->First(); p; p = Schedules->Next(p)) {
1393 if (p->ChannelID() == ChannelID) {
1394 Schedule = p;
1395 break;
1396 }
1397 }
1398 if (Schedule) {
1399 for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
1400 if (ChannelID == Timer->Channel()->GetChannelID().ClrRid())
1401 Timer->SetEvent(NULL);
1402 }
1403 Schedule->Cleanup(INT_MAX);
1405 Reply(250, "EPG data of channel \"%s\" cleared", Option);
1406 }
1407 else {
1408 Reply(550, "No EPG data found for channel \"%s\"", Option);
1409 return;
1410 }
1411 }
1412 else
1413 Reply(501, "Undefined channel \"%s\"", Option);
1414 }
1415 else {
1418 for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer))
1419 Timer->SetEvent(NULL); // processing all timers here (local *and* remote)
1420 for (cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->Next(Schedule))
1421 Schedule->Cleanup(INT_MAX);
1423 Reply(250, "EPG data cleared");
1424 }
1425}
1426
1427void cSVDRPServer::CmdCONN(const char *Option)
1428{
1429 if (*Option) {
1430 if (SVDRPClientHandler) {
1431 cSVDRPServerParams ServerParams(Option);
1432 if (ServerParams.Ok()) {
1433 clientName = ServerParams.Name();
1434 Reply(250, "OK"); // must finish this transaction before creating the new client
1435 SVDRPClientHandler->AddClient(ServerParams, clientIpAddress.Address());
1436 }
1437 else
1438 Reply(501, "Error in server parameters: %s", ServerParams.Error());
1439 }
1440 else
1441 Reply(451, "No SVDRP client handler");
1442 }
1443 else
1444 Reply(501, "Missing server parameters");
1445}
1446
1447void cSVDRPServer::CmdDELC(const char *Option)
1448{
1449 if (*Option) {
1452 Channels->SetExplicitModify();
1453 cChannel *Channel = NULL;
1454 if (isnumber(Option))
1455 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1456 else
1457 Channel = Channels->GetByChannelID(tChannelID::FromString(Option));
1458 if (Channel) {
1459 if (const cTimer *Timer = Timers->UsesChannel(Channel)) {
1460 Reply(550, "Channel \"%s\" is in use by timer %s", Option, *Timer->ToDescr());
1461 return;
1462 }
1463 int CurrentChannelNr = cDevice::CurrentChannel();
1464 cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
1465 if (CurrentChannel && Channel == CurrentChannel) {
1466 int n = Channels->GetNextNormal(CurrentChannel->Index());
1467 if (n < 0)
1468 n = Channels->GetPrevNormal(CurrentChannel->Index());
1469 if (n < 0) {
1470 Reply(501, "Can't delete channel \"%s\" - list would be empty", Option);
1471 return;
1472 }
1473 CurrentChannel = Channels->Get(n);
1474 CurrentChannelNr = 0; // triggers channel switch below
1475 }
1476 Channels->Del(Channel);
1477 Channels->ReNumber();
1478 Channels->SetModifiedByUser();
1479 Channels->SetModified();
1480 isyslog("SVDRP %s < %s deleted channel %s", Setup.SVDRPHostName, *clientName, Option);
1481 if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
1482 if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
1483 Channels->SwitchTo(CurrentChannel->Number());
1484 else
1485 cDevice::SetCurrentChannel(CurrentChannel->Number());
1486 }
1487 Reply(250, "Channel \"%s\" deleted", Option);
1488 }
1489 else
1490 Reply(501, "Channel \"%s\" not defined", Option);
1491 }
1492 else
1493 Reply(501, "Missing channel number or id");
1494}
1495
1496static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
1497{
1498 cRecordControl *rc;
1499 if ((Reason & ruTimer) != 0 && (rc = cRecordControls::GetRecordControl(Recording->FileName())) != NULL)
1500 return cString::sprintf("Recording \"%s\" is in use by timer %d", RecordingId, rc->Timer()->Id());
1501 else if ((Reason & ruReplay) != 0)
1502 return cString::sprintf("Recording \"%s\" is being replayed", RecordingId);
1503 else if ((Reason & ruCut) != 0)
1504 return cString::sprintf("Recording \"%s\" is being edited", RecordingId);
1505 else if ((Reason & (ruMove | ruCopy)) != 0)
1506 return cString::sprintf("Recording \"%s\" is being copied/moved", RecordingId);
1507 else if (Reason)
1508 return cString::sprintf("Recording \"%s\" is in use", RecordingId);
1509 return NULL;
1510}
1511
1512void cSVDRPServer::CmdDELR(const char *Option)
1513{
1514 if (*Option) {
1515 if (isnumber(Option)) {
1517 Recordings->SetExplicitModify();
1518 if (cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1519 if (int RecordingInUse = Recording->IsInUse())
1520 Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
1521 else {
1522 if (Recording->Delete()) {
1524 Recordings->Del(Recording, false);
1525 DeletedRecordings->Add(Recording);
1526 Recordings->SetModified();
1527 isyslog("SVDRP %s < %s deleted recording %s", Setup.SVDRPHostName, *clientName, Option);
1528 Reply(250, "Recording \"%s\" deleted", Option);
1529 }
1530 else
1531 Reply(554, "Error while deleting recording!");
1532 }
1533 }
1534 else
1535 Reply(550, "Recording \"%s\" not found", Option);
1536 }
1537 else
1538 Reply(501, "Error in recording id \"%s\"", Option);
1539 }
1540 else
1541 Reply(501, "Missing recording id");
1542}
1543
1544void cSVDRPServer::CmdDELT(const char *Option)
1545{
1546 if (*Option) {
1547 if (isnumber(Option)) {
1549 Timers->SetExplicitModify();
1550 if (cTimer *Timer = Timers->GetById(strtol(Option, NULL, 10))) {
1551 if (Timer->Recording()) {
1552 Timer->Skip();
1553 cRecordControls::Process(Timers, time(NULL));
1554 }
1555 Timer->TriggerRespawn();
1556 Timers->Del(Timer);
1557 Timers->SetModified();
1558 isyslog("SVDRP %s < %s deleted timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
1559 Reply(250, "Timer \"%s\" deleted", Option);
1560 }
1561 else
1562 Reply(501, "Timer \"%s\" not defined", Option);
1563 }
1564 else
1565 Reply(501, "Error in timer number \"%s\"", Option);
1566 }
1567 else
1568 Reply(501, "Missing timer number");
1569}
1570
1571void cSVDRPServer::CmdEDIT(const char *Option)
1572{
1573 if (*Option) {
1574 if (isnumber(Option)) {
1576 if (const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1577 cMarks Marks;
1578 if (Marks.Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.Count()) {
1579 if (!EnoughFreeDiskSpaceForEdit(Recording->FileName()))
1580 Reply(550, "Not enough free disk space to start editing process");
1581 else if (RecordingsHandler.Add(ruCut, Recording->FileName()))
1582 Reply(250, "Editing recording \"%s\" [%s]", Option, Recording->Title());
1583 else
1584 Reply(554, "Can't start editing process");
1585 }
1586 else
1587 Reply(554, "No editing marks defined");
1588 }
1589 else
1590 Reply(550, "Recording \"%s\" not found", Option);
1591 }
1592 else
1593 Reply(501, "Error in recording id \"%s\"", Option);
1594 }
1595 else
1596 Reply(501, "Missing recording id");
1597}
1598
1599void cSVDRPServer::CmdGRAB(const char *Option)
1600{
1601 const char *FileName = NULL;
1602 bool Jpeg = true;
1603 int Quality = -1, SizeX = -1, SizeY = -1;
1604 if (*Option) {
1605 char buf[strlen(Option) + 1];
1606 char *p = strcpy(buf, Option);
1607 const char *delim = " \t";
1608 char *strtok_next;
1609 FileName = strtok_r(p, delim, &strtok_next);
1610 // image type:
1611 const char *Extension = strrchr(FileName, '.');
1612 if (Extension) {
1613 if (strcasecmp(Extension, ".jpg") == 0 || strcasecmp(Extension, ".jpeg") == 0)
1614 Jpeg = true;
1615 else if (strcasecmp(Extension, ".pnm") == 0)
1616 Jpeg = false;
1617 else {
1618 Reply(501, "Unknown image type \"%s\"", Extension + 1);
1619 return;
1620 }
1621 if (Extension == FileName)
1622 FileName = NULL;
1623 }
1624 else if (strcmp(FileName, "-") == 0)
1625 FileName = NULL;
1626 // image quality (and obsolete type):
1627 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1628 if (strcasecmp(p, "JPEG") == 0 || strcasecmp(p, "PNM") == 0) {
1629 // tolerate for backward compatibility
1630 p = strtok_r(NULL, delim, &strtok_next);
1631 }
1632 if (p) {
1633 if (isnumber(p))
1634 Quality = atoi(p);
1635 else {
1636 Reply(501, "Invalid quality \"%s\"", p);
1637 return;
1638 }
1639 }
1640 }
1641 // image size:
1642 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1643 if (isnumber(p))
1644 SizeX = atoi(p);
1645 else {
1646 Reply(501, "Invalid sizex \"%s\"", p);
1647 return;
1648 }
1649 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1650 if (isnumber(p))
1651 SizeY = atoi(p);
1652 else {
1653 Reply(501, "Invalid sizey \"%s\"", p);
1654 return;
1655 }
1656 }
1657 else {
1658 Reply(501, "Missing sizey");
1659 return;
1660 }
1661 }
1662 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1663 Reply(501, "Unexpected parameter \"%s\"", p);
1664 return;
1665 }
1666 // canonicalize the file name:
1667 char RealFileName[PATH_MAX];
1668 if (FileName) {
1669 if (*grabImageDir) {
1670 cString s(FileName);
1671 FileName = s;
1672 const char *slash = strrchr(FileName, '/');
1673 if (!slash) {
1674 s = AddDirectory(grabImageDir, FileName);
1675 FileName = s;
1676 }
1677 slash = strrchr(FileName, '/'); // there definitely is one
1678 cString t(s);
1679 t.Truncate(slash - FileName);
1680 char *r = realpath(t, RealFileName);
1681 if (!r) {
1682 LOG_ERROR_STR(FileName);
1683 Reply(501, "Invalid file name \"%s\"", FileName);
1684 return;
1685 }
1686 strcat(RealFileName, slash);
1687 FileName = RealFileName;
1688 if (strncmp(FileName, grabImageDir, strlen(grabImageDir)) != 0) {
1689 Reply(501, "Invalid file name \"%s\"", FileName);
1690 return;
1691 }
1692 }
1693 else {
1694 Reply(550, "Grabbing to file not allowed (use \"GRAB -\" instead)");
1695 return;
1696 }
1697 }
1698 // actual grabbing:
1699 int ImageSize;
1700 uchar *Image = cDevice::PrimaryDevice()->GrabImage(ImageSize, Jpeg, Quality, SizeX, SizeY);
1701 if (Image) {
1702 if (FileName) {
1703 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
1704 if (fd >= 0) {
1705 if (safe_write(fd, Image, ImageSize) == ImageSize) {
1706 dsyslog("SVDRP %s < %s grabbed image to %s", Setup.SVDRPHostName, *clientName, FileName);
1707 Reply(250, "Grabbed image %s", Option);
1708 }
1709 else {
1710 LOG_ERROR_STR(FileName);
1711 Reply(451, "Can't write to '%s'", FileName);
1712 }
1713 close(fd);
1714 }
1715 else {
1716 LOG_ERROR_STR(FileName);
1717 Reply(451, "Can't open '%s'", FileName);
1718 }
1719 }
1720 else {
1721 cBase64Encoder Base64(Image, ImageSize);
1722 const char *s;
1723 while ((s = Base64.NextLine()) != NULL)
1724 Reply(-216, "%s", s);
1725 Reply(216, "Grabbed image %s", Option);
1726 }
1727 free(Image);
1728 }
1729 else
1730 Reply(451, "Grab image failed");
1731 }
1732 else
1733 Reply(501, "Missing filename");
1734}
1735
1736void cSVDRPServer::CmdHELP(const char *Option)
1737{
1738 if (*Option) {
1739 const char *hp = GetHelpPage(Option, HelpPages);
1740 if (hp)
1741 Reply(-214, "%s", hp);
1742 else {
1743 Reply(504, "HELP topic \"%s\" unknown", Option);
1744 return;
1745 }
1746 }
1747 else {
1748 Reply(-214, "This is VDR version %s", VDRVERSION);
1749 Reply(-214, "Topics:");
1751 cPlugin *plugin;
1752 for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++) {
1753 const char **hp = plugin->SVDRPHelpPages();
1754 if (hp)
1755 Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
1756 PrintHelpTopics(hp);
1757 }
1758 Reply(-214, "To report bugs in the implementation send email to");
1759 Reply(-214, " vdr-bugs@tvdr.de");
1760 }
1761 Reply(214, "End of HELP info");
1762}
1763
1764void cSVDRPServer::CmdHITK(const char *Option)
1765{
1766 if (*Option) {
1767 if (!cRemote::Enabled()) {
1768 Reply(550, "Remote control currently disabled (key \"%s\" discarded)", Option);
1769 return;
1770 }
1771 char buf[strlen(Option) + 1];
1772 strcpy(buf, Option);
1773 const char *delim = " \t";
1774 char *strtok_next;
1775 char *p = strtok_r(buf, delim, &strtok_next);
1776 int NumKeys = 0;
1777 while (p) {
1778 eKeys k = cKey::FromString(p);
1779 if (k != kNone) {
1780 if (!cRemote::Put(k)) {
1781 Reply(451, "Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
1782 return;
1783 }
1784 }
1785 else {
1786 Reply(504, "Unknown key: \"%s\"", p);
1787 return;
1788 }
1789 NumKeys++;
1790 p = strtok_r(NULL, delim, &strtok_next);
1791 }
1792 Reply(250, "Key%s \"%s\" accepted", NumKeys > 1 ? "s" : "", Option);
1793 }
1794 else {
1795 Reply(-214, "Valid <key> names for the HITK command:");
1796 for (int i = 0; i < kNone; i++) {
1797 Reply(-214, " %s", cKey::ToString(eKeys(i)));
1798 }
1799 Reply(214, "End of key list");
1800 }
1801}
1802
1803void cSVDRPServer::CmdLSTC(const char *Option)
1804{
1806 bool WithChannelIds = startswith(Option, ":ids") && (Option[4] == ' ' || Option[4] == 0);
1807 if (WithChannelIds)
1808 Option = skipspace(Option + 4);
1809 bool WithGroupSeps = strcasecmp(Option, ":groups") == 0;
1810 if (*Option && !WithGroupSeps) {
1811 if (isnumber(Option)) {
1812 int n = strtol(Option, NULL, 10);
1813 if (n == 0)
1815 if (const cChannel *Channel = Channels->GetByNumber(n))
1816 Reply(250, "%d%s%s %s", Channel->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Channel->GetChannelID().ToString() : "", *Channel->ToText());
1817 else
1818 Reply(501, "Channel \"%s\" not defined", Option);
1819 }
1820 else {
1821 const cChannel *Next = Channels->GetByChannelID(tChannelID::FromString(Option));
1822 if (!Next) {
1823 for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1824 if (!Channel->GroupSep()) {
1825 if (strcasestr(Channel->Name(), Option)) {
1826 if (Next)
1827 Reply(-250, "%d%s%s %s", Next->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Next->GetChannelID().ToString() : "", *Next->ToText());
1828 Next = Channel;
1829 }
1830 }
1831 }
1832 }
1833 if (Next)
1834 Reply(250, "%d%s%s %s", Next->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Next->GetChannelID().ToString() : "", *Next->ToText());
1835 else
1836 Reply(501, "Channel \"%s\" not defined", Option);
1837 }
1838 }
1839 else if (cChannels::MaxNumber() >= 1) {
1840 for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1841 if (WithGroupSeps)
1842 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());
1843 else if (!Channel->GroupSep())
1844 Reply(Channel->Number() < cChannels::MaxNumber() ? -250 : 250, "%d%s%s %s", Channel->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Channel->GetChannelID().ToString() : "", *Channel->ToText());
1845 }
1846 }
1847 else
1848 Reply(550, "No channels defined");
1849}
1850
1851void cSVDRPServer::CmdLSTD(const char *Option)
1852{
1853 if (cDevice::NumDevices()) {
1854 for (int i = 0; i < cDevice::NumDevices(); i++) {
1855 if (const cDevice *d = cDevice::GetDevice(i))
1856 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());
1857 }
1858 }
1859 else
1860 Reply(550, "No devices found");
1861}
1862
1863void cSVDRPServer::CmdLSTE(const char *Option)
1864{
1867 const cSchedule* Schedule = NULL;
1868 eDumpMode DumpMode = dmAll;
1869 time_t AtTime = 0;
1870 if (*Option) {
1871 char buf[strlen(Option) + 1];
1872 strcpy(buf, Option);
1873 const char *delim = " \t";
1874 char *strtok_next;
1875 char *p = strtok_r(buf, delim, &strtok_next);
1876 while (p && DumpMode == dmAll) {
1877 if (strcasecmp(p, "NOW") == 0)
1878 DumpMode = dmPresent;
1879 else if (strcasecmp(p, "NEXT") == 0)
1880 DumpMode = dmFollowing;
1881 else if (strcasecmp(p, "AT") == 0) {
1882 DumpMode = dmAtTime;
1883 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1884 if (isnumber(p))
1885 AtTime = strtol(p, NULL, 10);
1886 else {
1887 Reply(501, "Invalid time");
1888 return;
1889 }
1890 }
1891 else {
1892 Reply(501, "Missing time");
1893 return;
1894 }
1895 }
1896 else if (!Schedule) {
1897 const cChannel* Channel = NULL;
1898 if (isnumber(p))
1899 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1900 else
1901 Channel = Channels->GetByChannelID(tChannelID::FromString(Option));
1902 if (Channel) {
1903 Schedule = Schedules->GetSchedule(Channel);
1904 if (!Schedule) {
1905 Reply(550, "No schedule found");
1906 return;
1907 }
1908 }
1909 else {
1910 Reply(550, "Channel \"%s\" not defined", p);
1911 return;
1912 }
1913 }
1914 else {
1915 Reply(501, "Unknown option: \"%s\"", p);
1916 return;
1917 }
1918 p = strtok_r(NULL, delim, &strtok_next);
1919 }
1920 }
1921 int fd = dup(file);
1922 if (fd) {
1923 FILE *f = fdopen(fd, "w");
1924 if (f) {
1925 if (Schedule)
1926 Schedule->Dump(Channels, f, "215-", DumpMode, AtTime);
1927 else
1928 Schedules->Dump(f, "215-", DumpMode, AtTime);
1929 fflush(f);
1930 Reply(215, "End of EPG data");
1931 fclose(f);
1932 }
1933 else {
1934 Reply(451, "Can't open file connection");
1935 close(fd);
1936 }
1937 }
1938 else
1939 Reply(451, "Can't dup stream descriptor");
1940}
1941
1942void cSVDRPServer::CmdLSTR(const char *Option)
1943{
1944 int Number = 0;
1945 bool Path = false;
1947 if (*Option) {
1948 char buf[strlen(Option) + 1];
1949 strcpy(buf, Option);
1950 const char *delim = " \t";
1951 char *strtok_next;
1952 char *p = strtok_r(buf, delim, &strtok_next);
1953 while (p) {
1954 if (!Number) {
1955 if (isnumber(p))
1956 Number = strtol(p, NULL, 10);
1957 else {
1958 Reply(501, "Error in recording id \"%s\"", Option);
1959 return;
1960 }
1961 }
1962 else if (strcasecmp(p, "PATH") == 0)
1963 Path = true;
1964 else {
1965 Reply(501, "Unknown option: \"%s\"", p);
1966 return;
1967 }
1968 p = strtok_r(NULL, delim, &strtok_next);
1969 }
1970 if (Number) {
1971 if (const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1972 FILE *f = fdopen(file, "w");
1973 if (f) {
1974 if (Path)
1975 Reply(250, "%s", Recording->FileName());
1976 else {
1977 Recording->Info()->Write(f, "215-");
1978 fflush(f);
1979 Reply(215, "End of recording information");
1980 }
1981 // don't 'fclose(f)' here!
1982 }
1983 else
1984 Reply(451, "Can't open file connection");
1985 }
1986 else
1987 Reply(550, "Recording \"%s\" not found", Option);
1988 }
1989 }
1990 else if (Recordings->Count()) {
1991 const cRecording *Recording = Recordings->First();
1992 while (Recording) {
1993 Reply(Recording == Recordings->Last() ? 250 : -250, "%d %s", Recording->Id(), Recording->Title(' ', true));
1994 Recording = Recordings->Next(Recording);
1995 }
1996 }
1997 else
1998 Reply(550, "No recordings available");
1999}
2000
2001void cSVDRPServer::CmdLSTT(const char *Option)
2002{
2003 int Id = 0;
2004 bool UseChannelId = false;
2005 if (*Option) {
2006 char buf[strlen(Option) + 1];
2007 strcpy(buf, Option);
2008 const char *delim = " \t";
2009 char *strtok_next;
2010 char *p = strtok_r(buf, delim, &strtok_next);
2011 while (p) {
2012 if (isnumber(p))
2013 Id = strtol(p, NULL, 10);
2014 else if (strcasecmp(p, "ID") == 0)
2015 UseChannelId = true;
2016 else {
2017 Reply(501, "Unknown option: \"%s\"", p);
2018 return;
2019 }
2020 p = strtok_r(NULL, delim, &strtok_next);
2021 }
2022 }
2024 if (Id) {
2025 for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
2026 if (!Timer->Remote()) {
2027 if (Timer->Id() == Id) {
2028 Reply(250, "%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2029 return;
2030 }
2031 }
2032 }
2033 Reply(501, "Timer \"%s\" not defined", Option);
2034 return;
2035 }
2036 else {
2037 const cTimer *LastLocalTimer = Timers->Last();
2038 while (LastLocalTimer) {
2039 if (LastLocalTimer->Remote())
2040 LastLocalTimer = Timers->Prev(LastLocalTimer);
2041 else
2042 break;
2043 }
2044 if (LastLocalTimer) {
2045 for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
2046 if (!Timer->Remote())
2047 Reply(Timer != LastLocalTimer ? -250 : 250, "%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2048 if (Timer == LastLocalTimer)
2049 break;
2050 }
2051 return;
2052 }
2053 }
2054 Reply(550, "No timers defined");
2055}
2056
2057void cSVDRPServer::CmdMESG(const char *Option)
2058{
2059 if (*Option) {
2060 isyslog("SVDRP %s < %s message '%s'", Setup.SVDRPHostName, *clientName, Option);
2061 Skins.QueueMessage(mtInfo, Option);
2062 Reply(250, "Message queued");
2063 }
2064 else
2065 Reply(501, "Missing message");
2066}
2067
2068void cSVDRPServer::CmdMODC(const char *Option)
2069{
2070 if (*Option) {
2071 char *tail;
2072 int n = strtol(Option, &tail, 10);
2073 if (tail && tail != Option) {
2074 tail = skipspace(tail);
2076 Channels->SetExplicitModify();
2077 if (cChannel *Channel = Channels->GetByNumber(n)) {
2078 cChannel ch;
2079 if (ch.Parse(tail)) {
2080 if (Channels->HasUniqueChannelID(&ch, Channel)) {
2081 *Channel = ch;
2082 Channels->ReNumber();
2083 Channels->SetModifiedByUser();
2084 Channels->SetModified();
2085 isyslog("SVDRP %s < %s modified channel %d %s", Setup.SVDRPHostName, *clientName, Channel->Number(), *Channel->ToText());
2086 Reply(250, "%d %s", Channel->Number(), *Channel->ToText());
2087 }
2088 else
2089 Reply(501, "Channel settings are not unique");
2090 }
2091 else
2092 Reply(501, "Error in channel settings");
2093 }
2094 else
2095 Reply(501, "Channel \"%d\" not defined", n);
2096 }
2097 else
2098 Reply(501, "Error in channel number");
2099 }
2100 else
2101 Reply(501, "Missing channel settings");
2102}
2103
2104void cSVDRPServer::CmdMODT(const char *Option)
2105{
2106 if (*Option) {
2107 char *tail;
2108 int Id = strtol(Option, &tail, 10);
2109 if (tail && tail != Option) {
2110 tail = skipspace(tail);
2112 Timers->SetExplicitModify();
2113 if (cTimer *Timer = Timers->GetById(Id)) {
2114 bool IsRecording = Timer->HasFlags(tfRecording);
2115 cTimer t = *Timer;
2116 if (strcasecmp(tail, "ON") == 0)
2117 t.SetFlags(tfActive);
2118 else if (strcasecmp(tail, "OFF") == 0)
2119 t.ClrFlags(tfActive);
2120 else if (!t.Parse(tail)) {
2121 Reply(501, "Error in timer settings");
2122 return;
2123 }
2124 if (IsRecording && t.IsPatternTimer()) {
2125 Reply(550, "Timer is recording");
2126 return;
2127 }
2128 *Timer = t;
2129 if (IsRecording)
2130 Timer->SetFlags(tfRecording);
2131 else
2132 Timer->ClrFlags(tfRecording);
2133 Timers->SetModified();
2134 isyslog("SVDRP %s < %s modified timer %s (%s)", Setup.SVDRPHostName, *clientName, *Timer->ToDescr(), Timer->HasFlags(tfActive) ? "active" : "inactive");
2135 if (Timer->IsPatternTimer())
2136 Timer->SetEvent(NULL);
2137 Timer->TriggerRespawn();
2138 Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2139 }
2140 else
2141 Reply(501, "Timer \"%d\" not defined", Id);
2142 }
2143 else
2144 Reply(501, "Error in timer id");
2145 }
2146 else
2147 Reply(501, "Missing timer settings");
2148}
2149
2150void cSVDRPServer::CmdMOVC(const char *Option)
2151{
2152 if (*Option) {
2153 char *tail;
2154 int From = strtol(Option, &tail, 10);
2155 if (tail && tail != Option) {
2156 tail = skipspace(tail);
2157 if (tail && tail != Option) {
2158 LOCK_TIMERS_READ; // necessary to keep timers and channels in sync!
2160 Channels->SetExplicitModify();
2161 int To = strtol(tail, NULL, 10);
2162 int CurrentChannelNr = cDevice::CurrentChannel();
2163 const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
2164 cChannel *FromChannel = Channels->GetByNumber(From);
2165 if (FromChannel) {
2166 cChannel *ToChannel = Channels->GetByNumber(To);
2167 if (ToChannel) {
2168 int FromNumber = FromChannel->Number();
2169 int ToNumber = ToChannel->Number();
2170 if (FromNumber != ToNumber) {
2171 if (Channels->MoveNeedsDecrement(FromChannel, ToChannel))
2172 ToChannel = Channels->Prev(ToChannel); // cListBase::Move() doesn't know about the channel list's numbered groups!
2173 Channels->Move(FromChannel, ToChannel);
2174 Channels->ReNumber();
2175 Channels->SetModifiedByUser();
2176 Channels->SetModified();
2177 if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
2178 if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
2179 Channels->SwitchTo(CurrentChannel->Number());
2180 else
2181 cDevice::SetCurrentChannel(CurrentChannel->Number());
2182 }
2183 isyslog("SVDRP %s < %s moved channel %d to %d", Setup.SVDRPHostName, *clientName, FromNumber, ToNumber);
2184 Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
2185 }
2186 else
2187 Reply(501, "Can't move channel to same position");
2188 }
2189 else
2190 Reply(501, "Channel \"%d\" not defined", To);
2191 }
2192 else
2193 Reply(501, "Channel \"%d\" not defined", From);
2194 }
2195 else
2196 Reply(501, "Error in channel number");
2197 }
2198 else
2199 Reply(501, "Error in channel number");
2200 }
2201 else
2202 Reply(501, "Missing channel number");
2203}
2204
2205void cSVDRPServer::CmdMOVR(const char *Option)
2206{
2207 if (*Option) {
2208 char *opt = strdup(Option);
2209 char *num = skipspace(opt);
2210 char *option = num;
2211 while (*option && !isspace(*option))
2212 option++;
2213 char c = *option;
2214 *option = 0;
2215 if (isnumber(num)) {
2217 Recordings->SetExplicitModify();
2218 if (cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2219 if (int RecordingInUse = Recording->IsInUse())
2220 Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
2221 else {
2222 if (c)
2223 option = skipspace(++option);
2224 if (*option) {
2225 cString oldName = Recording->Name();
2226 if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) {
2227 Recordings->SetModified();
2228 Recordings->TouchUpdate();
2229 Reply(250, "Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name());
2230 }
2231 else
2232 Reply(554, "Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
2233 }
2234 else
2235 Reply(501, "Missing new recording name");
2236 }
2237 }
2238 else
2239 Reply(550, "Recording \"%s\" not found", num);
2240 }
2241 else
2242 Reply(501, "Error in recording id \"%s\"", num);
2243 free(opt);
2244 }
2245 else
2246 Reply(501, "Missing recording id");
2247}
2248
2249void cSVDRPServer::CmdNEWC(const char *Option)
2250{
2251 if (*Option) {
2252 cChannel ch;
2253 if (ch.Parse(Option)) {
2255 Channels->SetExplicitModify();
2256 if (Channels->HasUniqueChannelID(&ch)) {
2257 cChannel *channel = new cChannel;
2258 *channel = ch;
2259 Channels->Add(channel);
2260 Channels->ReNumber();
2261 Channels->SetModifiedByUser();
2262 Channels->SetModified();
2263 isyslog("SVDRP %s < %s new channel %d %s", Setup.SVDRPHostName, *clientName, channel->Number(), *channel->ToText());
2264 Reply(250, "%d %s", channel->Number(), *channel->ToText());
2265 }
2266 else
2267 Reply(501, "Channel settings are not unique");
2268 }
2269 else
2270 Reply(501, "Error in channel settings");
2271 }
2272 else
2273 Reply(501, "Missing channel settings");
2274}
2275
2276void cSVDRPServer::CmdNEWT(const char *Option)
2277{
2278 if (*Option) {
2279 cTimer *Timer = new cTimer;
2280 if (Timer->Parse(Option)) {
2282 const cTimer *t = Timers->GetTimer(Timer);
2283 if (!t || t->IsPatternTimer() || Timer->IsPatternTimer()) {
2284 Timer->ClrFlags(tfRecording);
2285 Timers->Add(Timer);
2286 isyslog("SVDRP %s < %s added timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2287 Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2288 }
2289 else {
2290 isyslog("SVDRP %s < %s attempted to add timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2291 isyslog("SVDRP %s < %s timer already exists as %s", Setup.SVDRPHostName, *clientName, *t->ToDescr());
2292 delete Timer;
2293 Reply(550, "%d %s", t->Id(), *t->ToText(true));
2294 }
2295 return;
2296 }
2297 else
2298 Reply(501, "Error in timer settings");
2299 delete Timer;
2300 }
2301 else
2302 Reply(501, "Missing timer settings");
2303}
2304
2305void cSVDRPServer::CmdNEXT(const char *Option)
2306{
2308 if (const cTimer *t = Timers->GetNextActiveTimer()) {
2309 time_t Start = t->StartTime();
2310 int Id = t->Id();
2311 if (!*Option)
2312 Reply(250, "%d %s", Id, *TimeToString(Start));
2313 else if (strcasecmp(Option, "ABS") == 0)
2314 Reply(250, "%d %jd", Id, intmax_t(Start));
2315 else if (strcasecmp(Option, "REL") == 0)
2316 Reply(250, "%d %jd", Id, intmax_t(Start - time(NULL)));
2317 else
2318 Reply(501, "Unknown option: \"%s\"", Option);
2319 }
2320 else
2321 Reply(550, "No active timers");
2322}
2323
2324void cSVDRPServer::CmdPING(const char *Option)
2325{
2326 Reply(250, "%s is alive", Setup.SVDRPHostName);
2327}
2328
2329void cSVDRPServer::CmdPLAY(const char *Option)
2330{
2331 if (*Option) {
2332 char *opt = strdup(Option);
2333 char *num = skipspace(opt);
2334 char *option = num;
2335 while (*option && !isspace(*option))
2336 option++;
2337 char c = *option;
2338 *option = 0;
2339 if (isnumber(num)) {
2340 cStateKey StateKey;
2341 if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(StateKey)) {
2342 if (const cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2343 cString FileName = Recording->FileName();
2344 cString Title = Recording->Title();
2345 int FramesPerSecond = Recording->FramesPerSecond();
2346 bool IsPesRecording = Recording->IsPesRecording();
2347 StateKey.Remove(); // must give up the lock for the call to cControl::Shutdown()
2348 if (c)
2349 option = skipspace(++option);
2352 if (*option) {
2353 int pos = 0;
2354 if (strcasecmp(option, "BEGIN") != 0)
2355 pos = HMSFToIndex(option, FramesPerSecond);
2356 cResumeFile Resume(FileName, IsPesRecording);
2357 if (pos <= 0)
2358 Resume.Delete();
2359 else
2360 Resume.Save(pos);
2361 }
2365 Reply(250, "Playing recording \"%s\" [%s]", num, *Title);
2366 }
2367 else {
2368 StateKey.Remove();
2369 Reply(550, "Recording \"%s\" not found", num);
2370 }
2371 }
2372 }
2373 else
2374 Reply(501, "Error in recording id \"%s\"", num);
2375 free(opt);
2376 }
2377 else if (const char *FileName = cReplayControl::NowReplaying()) {
2379 if (const cRecording *Recording = Recordings->GetByName(FileName))
2380 Reply(250, "%d %s", Recording->Id(), Recording->Title(' ', true));
2381 else
2382 Reply(550, "Recording \"%s\" not found", FileName);
2383 }
2384 else
2385 Reply(550, "Not playing");
2386}
2387
2388void cSVDRPServer::CmdPLUG(const char *Option)
2389{
2390 if (*Option) {
2391 char *opt = strdup(Option);
2392 char *name = skipspace(opt);
2393 char *option = name;
2394 while (*option && !isspace(*option))
2395 option++;
2396 char c = *option;
2397 *option = 0;
2398 cPlugin *plugin = cPluginManager::GetPlugin(name);
2399 if (plugin) {
2400 if (c)
2401 option = skipspace(++option);
2402 char *cmd = option;
2403 while (*option && !isspace(*option))
2404 option++;
2405 if (*option) {
2406 *option++ = 0;
2407 option = skipspace(option);
2408 }
2409 if (!*cmd || strcasecmp(cmd, "HELP") == 0) {
2410 if (*cmd && *option) {
2411 const char *hp = GetHelpPage(option, plugin->SVDRPHelpPages());
2412 if (hp) {
2413 Reply(-214, "%s", hp);
2414 Reply(214, "End of HELP info");
2415 }
2416 else
2417 Reply(504, "HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->Name());
2418 }
2419 else {
2420 Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
2421 const char **hp = plugin->SVDRPHelpPages();
2422 if (hp) {
2423 Reply(-214, "SVDRP commands:");
2424 PrintHelpTopics(hp);
2425 Reply(214, "End of HELP info");
2426 }
2427 else
2428 Reply(214, "This plugin has no SVDRP commands");
2429 }
2430 }
2431 else if (strcasecmp(cmd, "MAIN") == 0) {
2432 if (cRemote::CallPlugin(plugin->Name()))
2433 Reply(250, "Initiated call to main menu function of plugin \"%s\"", plugin->Name());
2434 else
2435 Reply(550, "A plugin call is already pending - please try again later");
2436 }
2437 else {
2438 int ReplyCode = 900;
2439 cString s = plugin->SVDRPCommand(cmd, option, ReplyCode);
2440 if (*s)
2441 Reply(abs(ReplyCode), "%s", *s);
2442 else
2443 Reply(500, "Command unrecognized: \"%s\"", cmd);
2444 }
2445 }
2446 else
2447 Reply(550, "Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
2448 free(opt);
2449 }
2450 else {
2451 Reply(-214, "Available plugins:");
2452 cPlugin *plugin;
2453 for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++)
2454 Reply(-214, "%s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
2455 Reply(214, "End of plugin list");
2456 }
2457}
2458
2459void cSVDRPServer::CmdPOLL(const char *Option)
2460{
2461 if (*Option) {
2462 char buf[strlen(Option) + 1];
2463 char *p = strcpy(buf, Option);
2464 const char *delim = " \t";
2465 char *strtok_next;
2466 char *RemoteName = strtok_r(p, delim, &strtok_next);
2467 char *ListName = strtok_r(NULL, delim, &strtok_next);
2468 if (SVDRPClientHandler) {
2469 if (ListName) {
2470 if (strcasecmp(ListName, "timers") == 0) {
2471 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
2472 SVDRPClientHandler->TriggerFetchingTimers(RemoteName);
2473 }
2474 else
2475 Reply(501, "Unknown list name: \"%s\"", ListName);
2476 }
2477 else
2478 Reply(501, "Missing list name");
2479 }
2480 else
2481 Reply(501, "No SVDRP client connections");
2482 }
2483 else
2484 Reply(501, "Missing parameters");
2485}
2486
2487void cSVDRPServer::CmdPRIM(const char *Option)
2488{
2489 int n = -1;
2490 if (*Option) {
2491 if (isnumber(Option)) {
2492 int o = strtol(Option, NULL, 10);
2493 if (o > 0 && o <= cDevice::NumDevices())
2494 n = o;
2495 else
2496 Reply(501, "Invalid device number \"%s\"", Option);
2497 }
2498 else
2499 Reply(501, "Invalid parameter \"%s\"", Option);
2500 if (n >= 0) {
2501 Setup.PrimaryDVB = n;
2502 Reply(250, "Primary device set to %d", n);
2503 }
2504 }
2505 else {
2506 if (const cDevice *d = cDevice::PrimaryDevice())
2507 Reply(250, "%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ? "D" : "-", d->DeviceNumber() + 1 == Setup.PrimaryDVB ? "P" : "-", *d->DeviceName());
2508 else
2509 Reply(501, "Failed to get primary device");
2510 }
2511}
2512
2513void cSVDRPServer::CmdPUTE(const char *Option)
2514{
2515 if (*Option) {
2516 FILE *f = fopen(Option, "r");
2517 if (f) {
2518 if (cSchedules::Read(f)) {
2519 cSchedules::Cleanup(true);
2520 Reply(250, "EPG data processed from \"%s\"", Option);
2521 }
2522 else
2523 Reply(451, "Error while processing EPG from \"%s\"", Option);
2524 fclose(f);
2525 }
2526 else
2527 Reply(501, "Cannot open file \"%s\"", Option);
2528 }
2529 else {
2530 delete PUTEhandler;
2532 Reply(PUTEhandler->Status(), "%s", PUTEhandler->Message());
2533 if (PUTEhandler->Status() != 354)
2535 }
2536}
2537
2538void cSVDRPServer::CmdREMO(const char *Option)
2539{
2540 if (*Option) {
2541 if (!strcasecmp(Option, "ON")) {
2542 cRemote::SetEnabled(true);
2543 Reply(250, "Remote control enabled");
2544 }
2545 else if (!strcasecmp(Option, "OFF")) {
2546 cRemote::SetEnabled(false);
2547 Reply(250, "Remote control disabled");
2548 }
2549 else
2550 Reply(501, "Invalid Option \"%s\"", Option);
2551 }
2552 else
2553 Reply(250, "Remote control is %s", cRemote::Enabled() ? "enabled" : "disabled");
2554}
2555
2556void cSVDRPServer::CmdSCAN(const char *Option)
2557{
2558 EITScanner.ForceScan();
2559 Reply(250, "EPG scan triggered");
2560}
2561
2562void cSVDRPServer::CmdSTAT(const char *Option)
2563{
2564 if (*Option) {
2565 if (strcasecmp(Option, "DISK") == 0) {
2566 int FreeMB, UsedMB;
2567 int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB);
2568 Reply(250, "%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
2569 }
2570 else
2571 Reply(501, "Invalid Option \"%s\"", Option);
2572 }
2573 else
2574 Reply(501, "No option given");
2575}
2576
2577void cSVDRPServer::CmdUPDT(const char *Option)
2578{
2579 if (*Option) {
2580 cTimer *Timer = new cTimer;
2581 if (Timer->Parse(Option)) {
2583 if (cTimer *t = Timers->GetTimer(Timer)) {
2584 bool IsRecording = t->HasFlags(tfRecording);
2585 t->Parse(Option);
2586 delete Timer;
2587 Timer = t;
2588 if (IsRecording)
2589 Timer->SetFlags(tfRecording);
2590 else
2591 Timer->ClrFlags(tfRecording);
2592 isyslog("SVDRP %s < %s updated timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2593 }
2594 else {
2595 Timer->ClrFlags(tfRecording);
2596 Timers->Add(Timer);
2597 isyslog("SVDRP %s < %s added timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2598 }
2599 Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2600 return;
2601 }
2602 else
2603 Reply(501, "Error in timer settings");
2604 delete Timer;
2605 }
2606 else
2607 Reply(501, "Missing timer settings");
2608}
2609
2610void cSVDRPServer::CmdUPDR(const char *Option)
2611{
2613 Recordings->Update(false);
2614 Reply(250, "Re-read of recordings directory triggered");
2615}
2616
2617void cSVDRPServer::CmdVOLU(const char *Option)
2618{
2619 if (*Option) {
2620 if (isnumber(Option))
2621 cDevice::PrimaryDevice()->SetVolume(strtol(Option, NULL, 10), true);
2622 else if (strcmp(Option, "+") == 0)
2624 else if (strcmp(Option, "-") == 0)
2626 else if (strcasecmp(Option, "MUTE") == 0)
2628 else {
2629 Reply(501, "Unknown option: \"%s\"", Option);
2630 return;
2631 }
2632 }
2633 if (cDevice::PrimaryDevice()->IsMute())
2634 Reply(250, "Audio is mute");
2635 else
2636 Reply(250, "Audio volume is %d", cDevice::CurrentVolume());
2637}
2638
2639#define CMD(c) (strcasecmp(Cmd, c) == 0)
2640
2642{
2643 // handle PUTE data:
2644 if (PUTEhandler) {
2645 if (!PUTEhandler->Process(Cmd)) {
2646 Reply(PUTEhandler->Status(), "%s", PUTEhandler->Message());
2648 }
2649 cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME); // re-trigger the timeout, in case there is very much EPG data
2650 return;
2651 }
2652 // skip leading whitespace:
2653 Cmd = skipspace(Cmd);
2654 // find the end of the command word:
2655 char *s = Cmd;
2656 while (*s && !isspace(*s))
2657 s++;
2658 if (*s)
2659 *s++ = 0;
2660 s = skipspace(s);
2661 if (CMD("AUDI")) CmdAUDI(s);
2662 else if (CMD("CHAN")) CmdCHAN(s);
2663 else if (CMD("CLRE")) CmdCLRE(s);
2664 else if (CMD("CONN")) CmdCONN(s);
2665 else if (CMD("DELC")) CmdDELC(s);
2666 else if (CMD("DELR")) CmdDELR(s);
2667 else if (CMD("DELT")) CmdDELT(s);
2668 else if (CMD("EDIT")) CmdEDIT(s);
2669 else if (CMD("GRAB")) CmdGRAB(s);
2670 else if (CMD("HELP")) CmdHELP(s);
2671 else if (CMD("HITK")) CmdHITK(s);
2672 else if (CMD("LSTC")) CmdLSTC(s);
2673 else if (CMD("LSTD")) CmdLSTD(s);
2674 else if (CMD("LSTE")) CmdLSTE(s);
2675 else if (CMD("LSTR")) CmdLSTR(s);
2676 else if (CMD("LSTT")) CmdLSTT(s);
2677 else if (CMD("MESG")) CmdMESG(s);
2678 else if (CMD("MODC")) CmdMODC(s);
2679 else if (CMD("MODT")) CmdMODT(s);
2680 else if (CMD("MOVC")) CmdMOVC(s);
2681 else if (CMD("MOVR")) CmdMOVR(s);
2682 else if (CMD("NEWC")) CmdNEWC(s);
2683 else if (CMD("NEWT")) CmdNEWT(s);
2684 else if (CMD("NEXT")) CmdNEXT(s);
2685 else if (CMD("PING")) CmdPING(s);
2686 else if (CMD("PLAY")) CmdPLAY(s);
2687 else if (CMD("PLUG")) CmdPLUG(s);
2688 else if (CMD("POLL")) CmdPOLL(s);
2689 else if (CMD("PRIM")) CmdPRIM(s);
2690 else if (CMD("PUTE")) CmdPUTE(s);
2691 else if (CMD("REMO")) CmdREMO(s);
2692 else if (CMD("SCAN")) CmdSCAN(s);
2693 else if (CMD("STAT")) CmdSTAT(s);
2694 else if (CMD("UPDR")) CmdUPDR(s);
2695 else if (CMD("UPDT")) CmdUPDT(s);
2696 else if (CMD("VOLU")) CmdVOLU(s);
2697 else if (CMD("QUIT")) Close(true);
2698 else Reply(500, "Command unrecognized: \"%s\"", Cmd);
2699}
2700
2702{
2703 if (file.IsOpen()) {
2704 while (file.Ready(false)) {
2705 unsigned char c;
2706 int r = safe_read(file, &c, 1);
2707 if (r > 0) {
2708 if (c == '\n' || c == 0x00) {
2709 // strip trailing whitespace:
2710 while (numChars > 0 && strchr(" \t\r\n", cmdLine[numChars - 1]))
2711 cmdLine[--numChars] = 0;
2712 // make sure the string is terminated:
2713 cmdLine[numChars] = 0;
2714 // showtime!
2715 dbgsvdrp("< S %s: %s\n", *clientName, cmdLine);
2717 numChars = 0;
2718 if (length > BUFSIZ) {
2719 free(cmdLine); // let's not tie up too much memory
2720 length = BUFSIZ;
2721 cmdLine = MALLOC(char, length);
2722 }
2723 }
2724 else if (c == 0x04 && numChars == 0) {
2725 // end of file (only at beginning of line)
2726 Close(true);
2727 }
2728 else if (c == 0x08 || c == 0x7F) {
2729 // backspace or delete (last character)
2730 if (numChars > 0)
2731 numChars--;
2732 }
2733 else if (c <= 0x03 || c == 0x0D) {
2734 // ignore control characters
2735 }
2736 else {
2737 if (numChars >= length - 1) {
2738 int NewLength = length + BUFSIZ;
2739 if (char *NewBuffer = (char *)realloc(cmdLine, NewLength)) {
2740 length = NewLength;
2741 cmdLine = NewBuffer;
2742 }
2743 else {
2744 esyslog("SVDRP %s < %s ERROR: out of memory", Setup.SVDRPHostName, *clientName);
2745 Close();
2746 break;
2747 }
2748 }
2749 cmdLine[numChars++] = c;
2750 cmdLine[numChars] = 0;
2751 }
2752 lastActivity = time(NULL);
2753 }
2754 else if (r <= 0) {
2755 isyslog("SVDRP %s < %s lost connection to client", Setup.SVDRPHostName, *clientName);
2756 Close();
2757 }
2758 }
2759 if (Setup.SVDRPTimeout && time(NULL) - lastActivity > Setup.SVDRPTimeout) {
2760 isyslog("SVDRP %s < %s timeout on connection", Setup.SVDRPHostName, *clientName);
2761 Close(true, true);
2762 }
2763 }
2764 return file.IsOpen();
2765}
2766
2767void SetSVDRPPorts(int TcpPort, int UdpPort)
2768{
2769 SVDRPTcpPort = TcpPort;
2770 SVDRPUdpPort = UdpPort;
2771}
2772
2773void SetSVDRPGrabImageDir(const char *GrabImageDir)
2774{
2775 grabImageDir = GrabImageDir;
2776}
2777
2778// --- cSVDRPServerHandler ---------------------------------------------------
2779
2781private:
2782 bool ready;
2785 void HandleServerConnection(void);
2786 void ProcessConnections(void);
2787protected:
2788 virtual void Action(void) override;
2789public:
2790 cSVDRPServerHandler(int TcpPort);
2791 virtual ~cSVDRPServerHandler() override;
2792 void WaitUntilReady(void);
2793 };
2794
2796
2798:cThread("SVDRP server handler", true)
2799,tcpSocket(TcpPort, true)
2800{
2801 ready = false;
2802}
2803
2805{
2806 Cancel(3);
2807 for (int i = 0; i < serverConnections.Size(); i++)
2808 delete serverConnections[i];
2809}
2810
2812{
2813 cTimeMs Timeout(3000);
2814 while (!ready && !Timeout.TimedOut())
2816}
2817
2819{
2820 for (int i = 0; i < serverConnections.Size(); i++) {
2821 if (!serverConnections[i]->Process()) {
2823 SVDRPClientHandler->CloseClient(serverConnections[i]->ClientName());
2824 delete serverConnections[i];
2825 serverConnections.Remove(i);
2826 i--;
2827 }
2828 }
2829}
2830
2832{
2833 int NewSocket = tcpSocket.Accept();
2834 if (NewSocket >= 0)
2835 serverConnections.Append(new cSVDRPServer(NewSocket, tcpSocket.LastIpAddress()));
2836}
2837
2839{
2840 if (tcpSocket.Listen()) {
2841 SVDRPServerPoller.Add(tcpSocket.Socket(), false);
2842 ready = true;
2843 while (Running()) {
2844 SVDRPServerPoller.Poll(1000);
2847 }
2848 SVDRPServerPoller.Del(tcpSocket.Socket(), false);
2849 tcpSocket.Close();
2850 }
2851}
2852
2853// --- SVDRP Handler ---------------------------------------------------------
2854
2856
2858{
2859 cMutexLock MutexLock(&SVDRPHandlerMutex);
2860 if (SVDRPTcpPort) {
2861 if (!SVDRPServerHandler) {
2863 SVDRPServerHandler->Start();
2864 SVDRPServerHandler->WaitUntilReady();
2865 }
2866 if (Setup.SVDRPPeering && SVDRPUdpPort && !SVDRPClientHandler) {
2868 SVDRPClientHandler->Start();
2869 }
2870 }
2871}
2872
2874{
2875 cMutexLock MutexLock(&SVDRPHandlerMutex);
2876 delete SVDRPClientHandler;
2877 SVDRPClientHandler = NULL;
2878 delete SVDRPServerHandler;
2879 SVDRPServerHandler = NULL;
2880}
2881
2883{
2884 bool Result = false;
2885 cMutexLock MutexLock(&SVDRPHandlerMutex);
2887 Result = SVDRPClientHandler->GetServerNames(ServerNames);
2888 return Result;
2889}
2890
2891bool ExecSVDRPCommand(const char *ServerName, const char *Command, cStringList *Response)
2892{
2893 bool Result = false;
2894 cMutexLock MutexLock(&SVDRPHandlerMutex);
2896 Result = SVDRPClientHandler->Execute(ServerName, Command, Response);
2897 return Result;
2898}
2899
2900void BroadcastSVDRPCommand(const char *Command)
2901{
2902 cMutexLock MutexLock(&SVDRPHandlerMutex);
2903 cStringList ServerNames;
2904 if (SVDRPClientHandler) {
2905 if (SVDRPClientHandler->GetServerNames(&ServerNames)) {
2906 for (int i = 0; i < ServerNames.Size(); i++)
2907 ExecSVDRPCommand(ServerNames[i], Command);
2908 }
2909 }
2910}
#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:1456
bool Parse(const char *s)
Definition channels.c:616
static cString ToText(const cChannel *Channel)
Definition channels.c:554
int Number(void) const
Definition channels.h:179
tChannelID GetChannelID(void) const
Definition channels.h:191
static int MaxNumber(void)
Definition channels.h:249
static const char * SystemCharacterTable(void)
Definition tools.h:174
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
Definition thread.c:73
static void Shutdown(void)
Definition player.c:99
static void Attach(void)
Definition player.c:86
static void Launch(cControl *Control)
Definition player.c:79
virtual uchar * GrabImage(int &Size, bool Jpeg=true, int Quality=-1, int SizeX=-1, int SizeY=-1)
Grabs the currently visible screen image.
Definition device.c:474
static cDevice * PrimaryDevice(void)
Returns the primary device.
Definition device.h:148
static cDevice * GetDevice(int Index)
Gets the device with the given Index.
Definition device.c:230
eTrackType GetCurrentAudioTrack(void) const
Definition device.h:593
bool SwitchChannel(const cChannel *Channel, bool LiveView)
Switches the device to the given Channel, initiating transfer mode if necessary.
Definition device.c:825
static int CurrentChannel(void)
Returns the number of the current channel on the primary device.
Definition device.h:371
const tTrackId * GetTrack(eTrackType Type)
Returns a pointer to the given track id, or NULL if Type is not less than ttMaxTrackTypes.
Definition device.c:1143
static void SetCurrentChannel(int ChannelNumber)
Sets the number of the current channel on the primary device, without actually switching to it.
Definition device.h:373
void SetVolume(int Volume, bool Absolute=false)
Sets the volume to the given value, either absolutely or relative to the current volume.
Definition device.c:1076
static int NumDevices(void)
Returns the total number of devices.
Definition device.h:129
static int CurrentVolume(void)
Definition device.h:648
bool ToggleMute(void)
Turns the volume off or on and returns the new mute state.
Definition device.c:1047
bool SetCurrentAudioTrack(eTrackType Type)
Sets the current audio track to the given Type.
Definition device.c:1168
static void SetDisableUntil(time_t Time)
Definition eit.c:509
Definition tools.h:476
const char * Connection(void) const
Definition svdrp.c:71
cString address
Definition svdrp.c:61
const char * Address(void) const
Definition svdrp.c:67
int Port(void) const
Definition svdrp.c:68
void Set(const char *Address, int Port)
Definition svdrp.c:84
cString connection
Definition svdrp.c:63
int port
Definition svdrp.c:62
cIpAddress(void)
Definition svdrp.c:74
static const char * ToString(eKeys Key, bool Translate=false)
Definition keys.c:138
static eKeys FromString(const char *Name)
Definition keys.c:123
int Count(void) const
Definition tools.h:640
cListObject * Prev(void) const
Definition tools.h:559
int Index(void) const
Definition tools.c:2114
cListObject * Next(void) const
Definition tools.h:560
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
Definition recording.c:2452
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:262
static bool Process(cTimers *Timers, time_t t)
Definition menu.c:5898
static cRecordControl * GetRecordControl(const char *FileName)
Definition menu.c:5878
int Id(void) const
Definition recording.h:162
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
Definition recording.c:1239
const char * Title(char Delimiter=' ', bool NewIndicator=false, int Level=-1) const
Definition recording.c:1257
static const cRecordings * GetRecordingsRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of recordings for read access.
Definition recording.h:285
bool Put(uint64_t Code, bool Repeat=false, bool Release=false)
Definition remote.c: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:6091
static const char * NowReplaying(void)
Definition menu.c:6096
bool Save(int Index)
Definition recording.c:333
void Delete(void)
Definition recording.c:378
bool Execute(const char *ServerName, const char *Command, cStringList *Response=NULL)
Definition svdrp.c: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:2804
void HandleServerConnection(void)
Definition svdrp.c:2831
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:2838
void ProcessConnections(void)
Definition svdrp.c:2818
cSVDRPServerHandler(int TcpPort)
Definition svdrp.c:2797
void WaitUntilReady(void)
Definition svdrp.c:2811
cSocket tcpSocket
Definition svdrp.c:2783
cVector< cSVDRPServer * > serverConnections
Definition svdrp.c:2784
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:2057
const char * ClientName(void) const
Definition svdrp.c:1143
void CmdPOLL(const char *Option)
Definition svdrp.c:2459
bool Send(const char *s)
Definition svdrp.c:1189
void CmdLSTT(const char *Option)
Definition svdrp.c:2001
time_t lastActivity
Definition svdrp.c:1098
void CmdCLRE(const char *Option)
Definition svdrp.c:1362
void Reply(int Code, const char *fmt,...) __attribute__((format(printf
Definition svdrp.c:1200
void CmdGRAB(const char *Option)
Definition svdrp.c:1599
void CmdMODC(const char *Option)
Definition svdrp.c:2068
cFile file
Definition svdrp.c:1093
cPUTEhandler * PUTEhandler
Definition svdrp.c:1094
void CmdDELC(const char *Option)
Definition svdrp.c:1447
void CmdPLUG(const char *Option)
Definition svdrp.c:2388
void CmdMODT(const char *Option)
Definition svdrp.c:2104
cIpAddress clientIpAddress
Definition svdrp.c:1091
cString clientName
Definition svdrp.c:1092
void CmdLSTC(const char *Option)
Definition svdrp.c:1803
void CmdSCAN(const char *Option)
Definition svdrp.c:2556
void Close(bool SendReply=false, bool Timeout=false)
Definition svdrp.c:1175
~cSVDRPServer()
Definition svdrp.c:1168
void CmdPUTE(const char *Option)
Definition svdrp.c:2513
void CmdLSTR(const char *Option)
Definition svdrp.c:1942
void CmdSTAT(const char *Option)
Definition svdrp.c:2562
void CmdCHAN(const char *Option)
Definition svdrp.c:1300
void CmdHELP(const char *Option)
Definition svdrp.c:1736
bool Process(void)
Definition svdrp.c:2701
void CmdUPDT(const char *Option)
Definition svdrp.c:2577
void CmdREMO(const char *Option)
Definition svdrp.c:2538
void CmdAUDI(const char *Option)
Definition svdrp.c:1261
void CmdLSTE(const char *Option)
Definition svdrp.c:1863
void CmdCONN(const char *Option)
Definition svdrp.c:1427
void CmdDELR(const char *Option)
Definition svdrp.c:1512
void Execute(char *Cmd)
Definition svdrp.c:2641
bool HasConnection(void)
Definition svdrp.c:1144
void CmdUPDR(const char *Option)
Definition svdrp.c:2610
void CmdVOLU(const char *Option)
Definition svdrp.c:2617
void CmdNEWT(const char *Option)
Definition svdrp.c:2276
void CmdEDIT(const char *Option)
Definition svdrp.c:1571
void CmdPLAY(const char *Option)
Definition svdrp.c:2329
void CmdDELT(const char *Option)
Definition svdrp.c:1544
void CmdLSTD(const char *Option)
Definition svdrp.c:1851
cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress)
Definition svdrp.c:1150
void CmdNEXT(const char *Option)
Definition svdrp.c:2305
void CmdHITK(const char *Option)
Definition svdrp.c:1764
int numChars
Definition svdrp.c:1095
void CmdNEWC(const char *Option)
Definition svdrp.c:2249
void CmdPRIM(const char *Option)
Definition svdrp.c:2487
void CmdMOVR(const char *Option)
Definition svdrp.c:2205
void CmdPING(const char *Option)
Definition svdrp.c:2324
char * cmdLine
Definition svdrp.c:1097
void CmdMOVC(const char *Option)
Definition svdrp.c:2150
void void PrintHelpTopics(const char **hp)
Definition svdrp.c:1235
void Cleanup(time_t Time)
Definition epg.c:1156
void Dump(const cChannels *Channels, FILE *f, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0) const
Definition epg.c:1167
static void Cleanup(bool Force=false)
Definition epg.c:1308
static bool Read(FILE *f=NULL)
Definition epg.c:1353
int port
Definition svdrp.c:103
void Close(void)
Definition svdrp.c:133
bool tcp
Definition svdrp.c:104
const cIpAddress * LastIpAddress(void) const
Definition svdrp.c:118
static bool SendDgram(const char *Dgram, int Port)
Definition svdrp.c: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:870
virtual void Clear(void) override
Definition tools.c:1658
void SortNumerically(void)
Definition tools.h:866
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition tools.c:1212
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:1196
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:812
bool TimedOut(void) const
Returns true if the number of milliseconds given in the last call to Set() have passed.
Definition tools.c:820
void ClrFlags(uint Flags)
Definition timers.c:1125
void SetFlags(uint Flags)
Definition timers.c:1120
bool IsPatternTimer(void) const
Definition timers.h:98
cString ToDescr(void) const
Definition timers.c:333
const char * Remote(void) const
Definition timers.h:81
int Id(void) const
Definition timers.h:65
bool Parse(const char *s)
Definition timers.c:446
cString ToText(bool UseChannelID=false) const
Definition timers.c:323
bool StoreRemoteTimers(const char *ServerName=NULL, const cStringList *RemoteTimers=NULL)
Stores the given list of RemoteTimers, which come from the VDR ServerName, in this list.
Definition timers.c:1391
static cTimers * GetTimersWrite(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for write access.
Definition timers.c:1300
static const cTimers * GetTimersRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for read access.
Definition timers.c:1295
int Size(void) const
Definition tools.h:767
virtual void Append(T Data)
Definition tools.h:787
static int VideoDiskSpace(int *FreeMB=NULL, int *UsedMB=NULL)
Definition videodir.c:152
cSetup Setup
Definition config.c:372
cSVDRPhosts SVDRPhosts
Definition config.c:280
#define APIVERSNUM
Definition config.h:31
#define VDRVERSION
Definition config.h:25
#define VDRVERSNUM
Definition config.h:26
eTrackType
Definition device.h:63
@ ttDolbyLast
Definition device.h:69
@ ttAudioFirst
Definition device.h:65
#define VOLUMEDELTA
Definition device.h:33
cEITScanner EITScanner
Definition eitscan.c:104
#define LOCK_SCHEDULES_READ
Definition epg.h:232
eDumpMode
Definition epg.h:43
@ dmAtTime
Definition epg.h:43
@ dmPresent
Definition epg.h:43
@ dmFollowing
Definition epg.h:43
@ dmAll
Definition epg.h:43
#define LOCK_SCHEDULES_WRITE
Definition epg.h:233
eKeys
Definition keys.h:16
@ kNone
Definition keys.h:55
void SetTrackDescriptions(int LiveChannel)
Definition menu.c:4970
bool EnoughFreeDiskSpaceForEdit(const char *FileName)
Definition recording.c:3661
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
Definition recording.c:3540
cRecordingsHandler RecordingsHandler
Definition recording.c:2259
struct __attribute__((packed))
Definition recording.c:2861
@ ruCut
Definition recording.h:34
@ ruReplay
Definition recording.h:32
@ ruCopy
Definition recording.h:36
@ ruTimer
Definition recording.h:31
@ ruMove
Definition recording.h:35
#define LOCK_RECORDINGS_READ
Definition recording.h:352
#define LOCK_DELETEDRECORDINGS_WRITE
Definition recording.h:355
#define LOCK_RECORDINGS_WRITE
Definition recording.h:353
cSkins Skins
Definition skins.c: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:2873
static cPoller SVDRPClientPoller
Definition svdrp.c:342
void SetSVDRPGrabImageDir(const char *GrabImageDir)
Definition svdrp.c:2773
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:2882
static int SVDRPTcpPort
Definition svdrp.c:47
static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
Definition svdrp.c:1496
const char * HelpPages[]
Definition svdrp.c:842
static cMutex SVDRPHandlerMutex
Definition svdrp.c:2855
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:2891
static cPoller SVDRPServerPoller
Definition svdrp.c:1148
static cSVDRPServerHandler * SVDRPServerHandler
Definition svdrp.c:2795
void StartSVDRPHandler(void)
Definition svdrp.c:2857
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:2900
#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:2639
#define SVDRPDiscoverDelta
void SetSVDRPPorts(int TcpPort, int UdpPort)
Definition svdrp.c:2767
@ spmOnly
Definition svdrp.h:19
int SVDRPCode(const char *s)
Returns the value of the three digit reply code of the given SVDRP response string.
Definition svdrp.h:47
cStateKey StateKeySVDRPRemoteTimersPoll
Controls whether a change to the local list of timers needs to result in sending a POLL to the remote...
#define LOCK_TIMERS_READ
Definition timers.h:275
#define LOCK_TIMERS_WRITE
Definition timers.h:276
@ tfActive
Definition timers.h:19
@ tfRecording
Definition timers.h:22
cString TimeToString(time_t t)
Converts the given time to a string of the form "www mmm dd hh:mm:ss yyyy".
Definition tools.c:1288
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:415
#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