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