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