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