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