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