vdr  2.6.9
svdrp.c
Go to the documentation of this file.
1 /*
2  * svdrp.c: Simple Video Disk Recorder Protocol
3  *
4  * See the main source file 'vdr.c' for copyright information and
5  * how to reach the author.
6  *
7  * The "Simple Video Disk Recorder Protocol" (SVDRP) was inspired
8  * by the "Simple Mail Transfer Protocol" (SMTP) and is fully ASCII
9  * text based. Therefore you can simply 'telnet' to your VDR port
10  * and interact with the Video Disk Recorder - or write a full featured
11  * graphical interface that sits on top of an SVDRP connection.
12  *
13  * $Id: svdrp.c 5.8 2024/06/13 09:31:11 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 // --- cSVDRPServerParams ----------------------------------------------------
525 
527 private:
529  int port;
532  int timeout;
535 public:
536  cSVDRPServerParams(const char *Params);
537  const char *Name(void) const { return name; }
538  const int Port(void) const { return port; }
539  const char *VdrVersion(void) const { return vdrversion; }
540  const char *ApiVersion(void) const { return apiversion; }
541  const int Timeout(void) const { return timeout; }
542  const char *Host(void) const { return host; }
543  bool Ok(void) const { return !*error; }
544  const char *Error(void) const { return error; }
545  };
546 
548 {
549  if (Params && *Params) {
550  name = strgetval(Params, "name", ':');
551  if (*name) {
552  cString p = strgetval(Params, "port", ':');
553  if (*p) {
554  port = atoi(p);
555  vdrversion = strgetval(Params, "vdrversion", ':');
556  if (*vdrversion) {
557  apiversion = strgetval(Params, "apiversion", ':');
558  if (*apiversion) {
559  cString t = strgetval(Params, "timeout", ':');
560  if (*t) {
561  timeout = atoi(t);
562  if (timeout > 10) { // don't let it get too small
563  host = strgetval(Params, "host", ':');
564  // no error if missing - this parameter is optional!
565  }
566  else
567  error = "invalid timeout";
568  }
569  else
570  error = "missing server timeout";
571  }
572  else
573  error = "missing server apiversion";
574  }
575  else
576  error = "missing server vdrversion";
577  }
578  else
579  error = "missing server port";
580  }
581  else
582  error = "missing server name";
583  }
584  else
585  error = "missing server parameters";
586 }
587 
588 // --- cSVDRPClientHandler ---------------------------------------------------
589 
591 
592 class cSVDRPClientHandler : public cThread {
593 private:
595  int tcpPort;
598  void SendDiscover(void);
599  void HandleClientConnection(void);
600  void ProcessConnections(void);
601  cSVDRPClient *GetClientForServer(const char *ServerName);
602 protected:
603  virtual void Action(void);
604 public:
605  cSVDRPClientHandler(int TcpPort, int UdpPort);
606  virtual ~cSVDRPClientHandler();
607  void AddClient(cSVDRPServerParams &ServerParams, const char *IpAddress);
608  bool Execute(const char *ServerName, const char *Command, cStringList *Response = NULL);
609  bool GetServerNames(cStringList *ServerNames);
610  bool TriggerFetchingTimers(const char *ServerName);
611  };
612 
614 
616 :cThread("SVDRP client handler", true)
617 ,udpSocket(UdpPort, false)
618 {
619  tcpPort = TcpPort;
620 }
621 
623 {
624  Cancel(3);
625  for (int i = 0; i < clientConnections.Size(); i++)
626  delete clientConnections[i];
627 }
628 
630 {
631  for (int i = 0; i < clientConnections.Size(); i++) {
632  if (strcmp(clientConnections[i]->ServerName(), ServerName) == 0)
633  return clientConnections[i];
634  }
635  return NULL;
636 }
637 
639 {
640  cString Dgram = cString::sprintf("SVDRP:discover name:%s port:%d vdrversion:%d apiversion:%d timeout:%d%s", Setup.SVDRPHostName, tcpPort, VDRVERSNUM, APIVERSNUM, Setup.SVDRPTimeout, (Setup.SVDRPPeering == spmOnly && *Setup.SVDRPDefaultHost) ? *cString::sprintf(" host:%s", Setup.SVDRPDefaultHost) : "");
641  udpSocket.SendDgram(Dgram, udpSocket.Port());
642 }
643 
645 {
646  cString PollTimersCmd;
648  PollTimersCmd = cString::sprintf("POLL %s TIMERS", Setup.SVDRPHostName);
650  }
652  return; // try again next time
653  for (int i = 0; i < clientConnections.Size(); i++) {
654  cSVDRPClient *Client = clientConnections[i];
655  if (Client->Process()) {
656  if (Client->HasFetchFlag(sffConn))
657  Client->Execute(cString::sprintf("CONN name:%s port:%d vdrversion:%d apiversion:%d timeout:%d", Setup.SVDRPHostName, SVDRPTcpPort, VDRVERSNUM, APIVERSNUM, Setup.SVDRPTimeout));
658  if (Client->HasFetchFlag(sffPing))
659  Client->Execute("PING");
660  if (Client->HasFetchFlag(sffTimers)) {
661  cStringList RemoteTimers;
662  if (Client->GetRemoteTimers(RemoteTimers)) {
664  bool TimersModified = Timers->StoreRemoteTimers(Client->ServerName(), &RemoteTimers);
665  StateKeySVDRPRemoteTimersPoll.Remove(TimersModified);
666  }
667  else
668  Client->SetFetchFlag(sffTimers); // try again next time
669  }
670  }
671  if (*PollTimersCmd) {
672  if (!Client->Execute(PollTimersCmd))
673  esyslog("ERROR: can't send '%s' to '%s'", *PollTimersCmd, Client->ServerName());
674  }
675  }
676  else {
678  bool TimersModified = Timers->StoreRemoteTimers(Client->ServerName(), NULL);
679  StateKeySVDRPRemoteTimersPoll.Remove(TimersModified);
680  delete Client;
682  i--;
683  }
684  }
685 }
686 
687 void cSVDRPClientHandler::AddClient(cSVDRPServerParams &ServerParams, const char *IpAddress)
688 {
689  cMutexLock MutexLock(&mutex);
690  for (int i = 0; i < clientConnections.Size(); i++) {
691  if (clientConnections[i]->HasAddress(IpAddress, ServerParams.Port()))
692  return;
693  }
694  if (Setup.SVDRPPeering == spmOnly && strcmp(ServerParams.Name(), Setup.SVDRPDefaultHost) != 0)
695  return; // we only want to peer with the default host, but this isn't the default host
696  if (ServerParams.Host() && strcmp(ServerParams.Host(), Setup.SVDRPHostName) != 0)
697  return; // the remote VDR requests a specific host, but it's not us
698  clientConnections.Append(new cSVDRPClient(IpAddress, ServerParams.Port(), ServerParams.Name(), ServerParams.Timeout()));
699 }
700 
702 {
703  cString NewDiscover = udpSocket.Discover();
704  if (*NewDiscover) {
705  cSVDRPServerParams ServerParams(NewDiscover);
706  if (ServerParams.Ok())
707  AddClient(ServerParams, udpSocket.LastIpAddress()->Address());
708  else
709  esyslog("SVDRP %s < %s ERROR: %s", Setup.SVDRPHostName, udpSocket.LastIpAddress()->Connection(), ServerParams.Error());
710  }
711 }
712 
714 {
715  if (udpSocket.Listen()) {
717  SendDiscover();
718  while (Running()) {
719  SVDRPClientPoller.Poll(1000);
720  cMutexLock MutexLock(&mutex);
723  }
725  udpSocket.Close();
726  }
727 }
728 
729 bool cSVDRPClientHandler::Execute(const char *ServerName, const char *Command, cStringList *Response)
730 {
731  cMutexLock MutexLock(&mutex);
732  if (cSVDRPClient *Client = GetClientForServer(ServerName))
733  return Client->Execute(Command, Response);
734  return false;
735 }
736 
738 {
739  cMutexLock MutexLock(&mutex);
740  ServerNames->Clear();
741  for (int i = 0; i < clientConnections.Size(); i++) {
742  cSVDRPClient *Client = clientConnections[i];
743  if (Client->Connected())
744  ServerNames->Append(strdup(Client->ServerName()));
745  }
746  return ServerNames->Size() > 0;
747 }
748 
749 bool cSVDRPClientHandler::TriggerFetchingTimers(const char *ServerName)
750 {
751  cMutexLock MutexLock(&mutex);
752  if (cSVDRPClient *Client = GetClientForServer(ServerName)) {
753  Client->SetFetchFlag(sffTimers);
754  return true;
755  }
756  return false;
757 }
758 
759 // --- cPUTEhandler ----------------------------------------------------------
760 
762 private:
763  FILE *f;
764  int status;
765  const char *message;
766 public:
767  cPUTEhandler(void);
768  ~cPUTEhandler();
769  bool Process(const char *s);
770  int Status(void) { return status; }
771  const char *Message(void) { return message; }
772  };
773 
775 {
776  if ((f = tmpfile()) != NULL) {
777  status = 354;
778  message = "Enter EPG data, end with \".\" on a line by itself";
779  }
780  else {
781  LOG_ERROR;
782  status = 554;
783  message = "Error while opening temporary file";
784  }
785 }
786 
788 {
789  if (f)
790  fclose(f);
791 }
792 
793 bool cPUTEhandler::Process(const char *s)
794 {
795  if (f) {
796  if (strcmp(s, ".") != 0) {
797  fputs(s, f);
798  fputc('\n', f);
799  return true;
800  }
801  else {
802  rewind(f);
803  if (cSchedules::Read(f)) {
804  cSchedules::Cleanup(true);
805  status = 250;
806  message = "EPG data processed";
807  }
808  else {
809  status = 451;
810  message = "Error while processing EPG data";
811  }
812  fclose(f);
813  f = NULL;
814  }
815  }
816  return false;
817 }
818 
819 // --- cSVDRPServer ----------------------------------------------------------
820 
821 #define MAXHELPTOPIC 10
822 #define EITDISABLETIME 10 // seconds until EIT processing is enabled again after a CLRE command
823  // adjust the help for CLRE accordingly if changing this!
824 
825 const char *HelpPages[] = {
826  "CHAN [ + | - | <number> | <name> | <id> ]\n"
827  " Switch channel up, down or to the given channel number, name or id.\n"
828  " Without option (or after successfully switching to the channel)\n"
829  " it returns the current channel number and name.",
830  "CLRE [ <number> | <name> | <id> ]\n"
831  " Clear the EPG list of the given channel number, name or id.\n"
832  " Without option it clears the entire EPG list.\n"
833  " After a CLRE command, no further EPG processing is done for 10\n"
834  " seconds, so that data sent with subsequent PUTE commands doesn't\n"
835  " interfere with data from the broadcasters.",
836  "CONN name:<name> port:<port> vdrversion:<vdrversion> apiversion:<apiversion> timeout:<timeout>\n"
837  " Used by peer-to-peer connections between VDRs to tell the other VDR\n"
838  " to establish a connection to this VDR. The name is the SVDRP host name\n"
839  " of this VDR, which may differ from its DNS name.",
840  "CPYR <number> <new name>\n"
841  " Copy the recording with the given number. Before a recording can be\n"
842  " copied, an LSTR command must have been executed in order to retrieve\n"
843  " the recording numbers.\n",
844  "DELC <number> | <id>\n"
845  " Delete the channel with the given number or channel id.",
846  "DELR <id>\n"
847  " Delete the recording with the given id. Before a recording can be\n"
848  " deleted, an LSTR command should have been executed in order to retrieve\n"
849  " the recording ids. The ids are unique and don't change while this\n"
850  " instance of VDR is running.\n"
851  " CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n"
852  " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
853  "DELT <id>\n"
854  " Delete the timer with the given id. If this timer is currently recording,\n"
855  " the recording will be stopped without any warning.",
856  "EDIT <id>\n"
857  " Edit the recording with the given id. Before a recording can be\n"
858  " edited, an LSTR command should have been executed in order to retrieve\n"
859  " the recording ids.",
860  "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n"
861  " Grab the current frame and save it to the given file. Images can\n"
862  " be stored as JPEG or PNM, depending on the given file name extension.\n"
863  " The quality of the grabbed image can be in the range 0..100, where 100\n"
864  " (the default) means \"best\" (only applies to JPEG). The size parameters\n"
865  " define the size of the resulting image (default is full screen).\n"
866  " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n"
867  " data will be sent to the SVDRP connection encoded in base64. The same\n"
868  " happens if '-' (a minus sign) is given as file name, in which case the\n"
869  " image format defaults to JPEG.",
870  "HELP [ <topic> ]\n"
871  " The HELP command gives help info.",
872  "HITK [ <key> ... ]\n"
873  " Hit the given remote control key. Without option a list of all\n"
874  " valid key names is given. If more than one key is given, they are\n"
875  " entered into the remote control queue in the given sequence. There\n"
876  " can be up to 31 keys.",
877  "LSTC [ :ids ] [ :groups | <number> | <name> | <id> ]\n"
878  " List channels. Without option, all channels are listed. Otherwise\n"
879  " only the given channel is listed. If a name is given, all channels\n"
880  " containing the given string as part of their name are listed.\n"
881  " If ':groups' is given, all channels are listed including group\n"
882  " separators. The channel number of a group separator is always 0.\n"
883  " With ':ids' the channel ids are listed following the channel numbers.\n"
884  " The special number 0 can be given to list the current channel.",
885  "LSTD\n"
886  " List all available devices. Each device is listed with its name and\n"
887  " whether it is currently the primary device ('P') or it implements a\n"
888  " decoder ('D') and can be used as output device.",
889  "LSTE [ <channel> ] [ now | next | at <time> ]\n"
890  " List EPG data. Without any parameters all data of all channels is\n"
891  " listed. If a channel is given (either by number or by channel ID),\n"
892  " only data for that channel is listed. 'now', 'next', or 'at <time>'\n"
893  " restricts the returned data to present events, following events, or\n"
894  " events at the given time (which must be in time_t form).",
895  "LSTR [ <id> [ path ] ]\n"
896  " List recordings. Without option, all recordings are listed. Otherwise\n"
897  " the information for the given recording is listed. If a recording\n"
898  " id and the keyword 'path' is given, the actual file name of that\n"
899  " recording's directory is listed.\n"
900  " Note that the ids of the recordings are not necessarily given in\n"
901  " numeric order.",
902  "LSTT [ <id> ] [ id ]\n"
903  " List timers. Without option, all timers are listed. Otherwise\n"
904  " only the timer with the given id is listed. If the keyword 'id' is\n"
905  " given, the channels will be listed with their unique channel ids\n"
906  " instead of their numbers. This command lists only the timers that are\n"
907  " defined locally on this VDR, not any remote timers from other VDRs.",
908  "MESG <message>\n"
909  " Displays the given message on the OSD. The message will be queued\n"
910  " and displayed whenever this is suitable.\n",
911  "MODC <number> <settings>\n"
912  " Modify a channel. Settings must be in the same format as returned\n"
913  " by the LSTC command.",
914  "MODT <id> on | off | <settings>\n"
915  " Modify a timer. Settings must be in the same format as returned\n"
916  " by the LSTT command. The special keywords 'on' and 'off' can be\n"
917  " used to easily activate or deactivate a timer.",
918  "MOVC <number> <to>\n"
919  " Move a channel to a new position.",
920  "MOVR <id> <new name>\n"
921  " Move the recording with the given id. Before a recording can be\n"
922  " moved, an LSTR command should have been executed in order to retrieve\n"
923  " the recording ids. The ids don't change during subsequent MOVR\n"
924  " commands.\n",
925  "NEWC <settings>\n"
926  " Create a new channel. Settings must be in the same format as returned\n"
927  " by the LSTC command.",
928  "NEWT <settings>\n"
929  " Create a new timer. Settings must be in the same format as returned\n"
930  " by the LSTT command.",
931  "NEXT [ abs | rel ]\n"
932  " Show the next timer event. If no option is given, the output will be\n"
933  " in human readable form. With option 'abs' the absolute time of the next\n"
934  " event will be given as the number of seconds since the epoch (time_t\n"
935  " format), while with option 'rel' the relative time will be given as the\n"
936  " number of seconds from now until the event. If the absolute time given\n"
937  " is smaller than the current time, or if the relative time is less than\n"
938  " zero, this means that the timer is currently recording and has started\n"
939  " at the given time. The first value in the resulting line is the id\n"
940  " of the timer.",
941  "PING\n"
942  " Used by peer-to-peer connections between VDRs to keep the connection\n"
943  " from timing out. May be used at any time and simply returns a line of\n"
944  " the form '<hostname> is alive'.",
945  "PLAY <id> [ begin | <position> ]\n"
946  " Play the recording with the given id. Before a recording can be\n"
947  " played, an LSTR command should have been executed in order to retrieve\n"
948  " the recording ids.\n"
949  " The keyword 'begin' plays the recording from its very beginning, while\n"
950  " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n"
951  " position. If neither 'begin' nor a <position> are given, replay is resumed\n"
952  " at the position where any previous replay was stopped, or from the beginning\n"
953  " by default. To control or stop the replay session, use the usual remote\n"
954  " control keypresses via the HITK command.",
955  "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n"
956  " Send a command to a plugin.\n"
957  " The PLUG command without any parameters lists all plugins.\n"
958  " If only a name is given, all commands known to that plugin are listed.\n"
959  " If a command is given (optionally followed by parameters), that command\n"
960  " is sent to the plugin, and the result will be displayed.\n"
961  " The keyword 'help' lists all the SVDRP commands known to the named plugin.\n"
962  " If 'help' is followed by a command, the detailed help for that command is\n"
963  " given. The keyword 'main' initiates a call to the main menu function of the\n"
964  " given plugin.\n",
965  "POLL <name> timers\n"
966  " Used by peer-to-peer connections between VDRs to inform other machines\n"
967  " about changes to timers. The receiving VDR shall use LSTT to query the\n"
968  " remote machine with the given name about its timers and update its list\n"
969  " of timers accordingly.\n",
970  "PRIM [ <number> ]\n"
971  " Make the device with the given number the primary device.\n"
972  " Without option it returns the currently active primary device in the same\n"
973  " format as used by the LSTD command.",
974  "PUTE [ <file> ]\n"
975  " Put data into the EPG list. The data entered has to strictly follow the\n"
976  " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n"
977  " by itself terminates the input and starts processing of the data (all\n"
978  " entered data is buffered until the terminating '.' is seen).\n"
979  " If a file name is given, epg data will be read from this file (which\n"
980  " must be accessible under the given name from the machine VDR is running\n"
981  " on). In case of file input, no terminating '.' shall be given.\n",
982  "REMO [ on | off ]\n"
983  " Turns the remote control on or off. Without a parameter, the current\n"
984  " status of the remote control is reported.",
985  "SCAN\n"
986  " Forces an EPG scan. If this is a single DVB device system, the scan\n"
987  " will be done on the primary device unless it is currently recording.",
988  "STAT disk\n"
989  " Return information about disk usage (total, free, percent).",
990  "UPDT <settings>\n"
991  " Updates a timer. Settings must be in the same format as returned\n"
992  " by the LSTT command. If a timer with the same channel, day, start\n"
993  " and stop time does not yet exist, it will be created.",
994  "UPDR\n"
995  " Initiates a re-read of the recordings directory, which is the SVDRP\n"
996  " equivalent to 'touch .update'.",
997  "VOLU [ <number> | + | - | mute ]\n"
998  " Set the audio volume to the given number (which is limited to the range\n"
999  " 0...255). If the special options '+' or '-' are given, the volume will\n"
1000  " be turned up or down, respectively. The option 'mute' will toggle the\n"
1001  " audio muting. If no option is given, the current audio volume level will\n"
1002  " be returned.",
1003  "QUIT\n"
1004  " Exit vdr (SVDRP).\n"
1005  " You can also hit Ctrl-D to exit.",
1006  NULL
1007  };
1008 
1009 /* SVDRP Reply Codes:
1010 
1011  214 Help message
1012  215 EPG or recording data record
1013  216 Image grab data (base 64)
1014  220 VDR service ready
1015  221 VDR service closing transmission channel
1016  250 Requested VDR action okay, completed
1017  354 Start sending EPG data
1018  451 Requested action aborted: local error in processing
1019  500 Syntax error, command unrecognized
1020  501 Syntax error in parameters or arguments
1021  502 Command not implemented
1022  504 Command parameter not implemented
1023  550 Requested action not taken
1024  554 Transaction failed
1025  900 Default plugin reply code
1026  901..999 Plugin specific reply codes
1027 
1028 */
1029 
1030 const char *GetHelpTopic(const char *HelpPage)
1031 {
1032  static char topic[MAXHELPTOPIC];
1033  const char *q = HelpPage;
1034  while (*q) {
1035  if (isspace(*q)) {
1036  uint n = q - HelpPage;
1037  if (n >= sizeof(topic))
1038  n = sizeof(topic) - 1;
1039  strncpy(topic, HelpPage, n);
1040  topic[n] = 0;
1041  return topic;
1042  }
1043  q++;
1044  }
1045  return NULL;
1046 }
1047 
1048 const char *GetHelpPage(const char *Cmd, const char **p)
1049 {
1050  if (p) {
1051  while (*p) {
1052  const char *t = GetHelpTopic(*p);
1053  if (strcasecmp(Cmd, t) == 0)
1054  return *p;
1055  p++;
1056  }
1057  }
1058  return NULL;
1059 }
1060 
1062 
1064 private:
1065  int socket;
1071  int length;
1072  char *cmdLine;
1074  void Close(bool SendReply = false, bool Timeout = false);
1075  bool Send(const char *s);
1076  void Reply(int Code, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
1077  void PrintHelpTopics(const char **hp);
1078  void CmdCHAN(const char *Option);
1079  void CmdCLRE(const char *Option);
1080  void CmdCONN(const char *Option);
1081  void CmdCPYR(const char *Option);
1082  void CmdDELC(const char *Option);
1083  void CmdDELR(const char *Option);
1084  void CmdDELT(const char *Option);
1085  void CmdEDIT(const char *Option);
1086  void CmdGRAB(const char *Option);
1087  void CmdHELP(const char *Option);
1088  void CmdHITK(const char *Option);
1089  void CmdLSTC(const char *Option);
1090  void CmdLSTD(const char *Option);
1091  void CmdLSTE(const char *Option);
1092  void CmdLSTR(const char *Option);
1093  void CmdLSTT(const char *Option);
1094  void CmdMESG(const char *Option);
1095  void CmdMODC(const char *Option);
1096  void CmdMODT(const char *Option);
1097  void CmdMOVC(const char *Option);
1098  void CmdMOVR(const char *Option);
1099  void CmdNEWC(const char *Option);
1100  void CmdNEWT(const char *Option);
1101  void CmdNEXT(const char *Option);
1102  void CmdPING(const char *Option);
1103  void CmdPLAY(const char *Option);
1104  void CmdPLUG(const char *Option);
1105  void CmdPOLL(const char *Option);
1106  void CmdPRIM(const char *Option);
1107  void CmdPUTE(const char *Option);
1108  void CmdREMO(const char *Option);
1109  void CmdSCAN(const char *Option);
1110  void CmdSTAT(const char *Option);
1111  void CmdUPDT(const char *Option);
1112  void CmdUPDR(const char *Option);
1113  void CmdVOLU(const char *Option);
1114  void Execute(char *Cmd);
1115 public:
1116  cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress);
1117  ~cSVDRPServer();
1118  const char *ClientName(void) const { return clientName; }
1119  bool HasConnection(void) { return file.IsOpen(); }
1120  bool Process(void);
1121  };
1122 
1124 
1125 cSVDRPServer::cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress)
1126 {
1127  socket = Socket;
1128  clientIpAddress = *ClientIpAddress;
1129  clientName = clientIpAddress.Connection(); // will be set to actual name by a CONN command
1130  PUTEhandler = NULL;
1131  numChars = 0;
1132  length = BUFSIZ;
1133  cmdLine = MALLOC(char, length);
1134  lastActivity = time(NULL);
1135  if (file.Open(socket)) {
1136  time_t now = time(NULL);
1137  Reply(220, "%s SVDRP VideoDiskRecorder %s; %s; %s", Setup.SVDRPHostName, VDRVERSION, *TimeToString(now), cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8");
1138  SVDRPServerPoller.Add(file, false);
1139  }
1140  dsyslog("SVDRP %s > %s server created", Setup.SVDRPHostName, *clientName);
1141 }
1142 
1144 {
1145  Close(true);
1146  free(cmdLine);
1147  dsyslog("SVDRP %s < %s server destroyed", Setup.SVDRPHostName, *clientName);
1148 }
1149 
1150 void cSVDRPServer::Close(bool SendReply, bool Timeout)
1151 {
1152  if (file.IsOpen()) {
1153  if (SendReply) {
1154  Reply(221, "%s closing connection%s", Setup.SVDRPHostName, Timeout ? " (timeout)" : "");
1155  }
1156  isyslog("SVDRP %s < %s connection closed", Setup.SVDRPHostName, *clientName);
1157  SVDRPServerPoller.Del(file, false);
1158  file.Close();
1160  }
1161  close(socket);
1162 }
1163 
1164 bool cSVDRPServer::Send(const char *s)
1165 {
1166  dbgsvdrp("> S %s: %s", *clientName, s); // terminating newline is already in the string!
1167  if (safe_write(file, s, strlen(s)) < 0) {
1168  LOG_ERROR;
1169  Close();
1170  return false;
1171  }
1172  return true;
1173 }
1174 
1175 void cSVDRPServer::Reply(int Code, const char *fmt, ...)
1176 {
1177  if (file.IsOpen()) {
1178  if (Code != 0) {
1179  char *buffer = NULL;
1180  va_list ap;
1181  va_start(ap, fmt);
1182  if (vasprintf(&buffer, fmt, ap) >= 0) {
1183  char *s = buffer;
1184  while (s && *s) {
1185  char *n = strchr(s, '\n');
1186  if (n)
1187  *n = 0;
1188  char cont = ' ';
1189  if (Code < 0 || n && *(n + 1)) // trailing newlines don't count!
1190  cont = '-';
1191  if (!Send(cString::sprintf("%03d%c%s\r\n", abs(Code), cont, s)))
1192  break;
1193  s = n ? n + 1 : NULL;
1194  }
1195  }
1196  else {
1197  Reply(451, "Bad format - looks like a programming error!");
1198  esyslog("SVDRP %s < %s bad format!", Setup.SVDRPHostName, *clientName);
1199  }
1200  va_end(ap);
1201  free(buffer);
1202  }
1203  else {
1204  Reply(451, "Zero return code - looks like a programming error!");
1205  esyslog("SVDRP %s < %s zero return code!", Setup.SVDRPHostName, *clientName);
1206  }
1207  }
1208 }
1209 
1210 void cSVDRPServer::PrintHelpTopics(const char **hp)
1211 {
1212  int NumPages = 0;
1213  if (hp) {
1214  while (*hp) {
1215  NumPages++;
1216  hp++;
1217  }
1218  hp -= NumPages;
1219  }
1220  const int TopicsPerLine = 5;
1221  int x = 0;
1222  for (int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
1223  char buffer[TopicsPerLine * MAXHELPTOPIC + 5];
1224  char *q = buffer;
1225  q += sprintf(q, " ");
1226  for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
1227  const char *topic = GetHelpTopic(hp[(y * TopicsPerLine + x)]);
1228  if (topic)
1229  q += sprintf(q, "%*s", -MAXHELPTOPIC, topic);
1230  }
1231  x = 0;
1232  Reply(-214, "%s", buffer);
1233  }
1234 }
1235 
1236 void cSVDRPServer::CmdCHAN(const char *Option)
1237 {
1239  if (*Option) {
1240  int n = -1;
1241  int d = 0;
1242  if (isnumber(Option)) {
1243  int o = strtol(Option, NULL, 10);
1244  if (o >= 1 && o <= cChannels::MaxNumber())
1245  n = o;
1246  }
1247  else if (strcmp(Option, "-") == 0) {
1249  if (n > 1) {
1250  n--;
1251  d = -1;
1252  }
1253  }
1254  else if (strcmp(Option, "+") == 0) {
1256  if (n < cChannels::MaxNumber()) {
1257  n++;
1258  d = 1;
1259  }
1260  }
1261  else if (const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(Option)))
1262  n = Channel->Number();
1263  else {
1264  for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1265  if (!Channel->GroupSep()) {
1266  if (strcasecmp(Channel->Name(), Option) == 0) {
1267  n = Channel->Number();
1268  break;
1269  }
1270  }
1271  }
1272  }
1273  if (n < 0) {
1274  Reply(501, "Undefined channel \"%s\"", Option);
1275  return;
1276  }
1277  if (!d) {
1278  if (const cChannel *Channel = Channels->GetByNumber(n)) {
1279  if (!cDevice::PrimaryDevice()->SwitchChannel(Channel, true)) {
1280  Reply(554, "Error switching to channel \"%d\"", Channel->Number());
1281  return;
1282  }
1283  }
1284  else {
1285  Reply(550, "Unable to find channel \"%s\"", Option);
1286  return;
1287  }
1288  }
1289  else
1291  }
1292  if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel()))
1293  Reply(250, "%d %s", Channel->Number(), Channel->Name());
1294  else
1295  Reply(550, "Unable to find channel \"%d\"", cDevice::CurrentChannel());
1296 }
1297 
1298 void cSVDRPServer::CmdCLRE(const char *Option)
1299 {
1300  if (*Option) {
1303  tChannelID ChannelID = tChannelID::InvalidID;
1304  if (isnumber(Option)) {
1305  int o = strtol(Option, NULL, 10);
1306  if (o >= 1 && o <= cChannels::MaxNumber()) {
1307  if (const cChannel *Channel = Channels->GetByNumber(o))
1308  ChannelID = Channel->GetChannelID();
1309  }
1310  }
1311  else {
1312  ChannelID = tChannelID::FromString(Option);
1313  if (ChannelID == tChannelID::InvalidID) {
1314  for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1315  if (!Channel->GroupSep()) {
1316  if (strcasecmp(Channel->Name(), Option) == 0) {
1317  ChannelID = Channel->GetChannelID();
1318  break;
1319  }
1320  }
1321  }
1322  }
1323  }
1324  if (!(ChannelID == tChannelID::InvalidID)) {
1326  cSchedule *Schedule = NULL;
1327  ChannelID.ClrRid();
1328  for (cSchedule *p = Schedules->First(); p; p = Schedules->Next(p)) {
1329  if (p->ChannelID() == ChannelID) {
1330  Schedule = p;
1331  break;
1332  }
1333  }
1334  if (Schedule) {
1335  for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
1336  if (ChannelID == Timer->Channel()->GetChannelID().ClrRid())
1337  Timer->SetEvent(NULL);
1338  }
1339  Schedule->Cleanup(INT_MAX);
1341  Reply(250, "EPG data of channel \"%s\" cleared", Option);
1342  }
1343  else {
1344  Reply(550, "No EPG data found for channel \"%s\"", Option);
1345  return;
1346  }
1347  }
1348  else
1349  Reply(501, "Undefined channel \"%s\"", Option);
1350  }
1351  else {
1354  for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer))
1355  Timer->SetEvent(NULL); // processing all timers here (local *and* remote)
1356  for (cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->Next(Schedule))
1357  Schedule->Cleanup(INT_MAX);
1359  Reply(250, "EPG data cleared");
1360  }
1361 }
1362 
1363 void cSVDRPServer::CmdCONN(const char *Option)
1364 {
1365  if (*Option) {
1366  if (SVDRPClientHandler) {
1367  cSVDRPServerParams ServerParams(Option);
1368  if (ServerParams.Ok()) {
1369  clientName = ServerParams.Name();
1370  Reply(250, "OK"); // must finish this transaction before creating the new client
1372  }
1373  else
1374  Reply(501, "Error in server parameters: %s", ServerParams.Error());
1375  }
1376  else
1377  Reply(451, "No SVDRP client handler");
1378  }
1379  else
1380  Reply(501, "Missing server parameters");
1381 }
1382 
1383 void cSVDRPServer::CmdDELC(const char *Option)
1384 {
1385  if (*Option) {
1388  Channels->SetExplicitModify();
1389  cChannel *Channel = NULL;
1390  if (isnumber(Option))
1391  Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1392  else
1393  Channel = Channels->GetByChannelID(tChannelID::FromString(Option));
1394  if (Channel) {
1395  if (const cTimer *Timer = Timers->UsesChannel(Channel)) {
1396  Reply(550, "Channel \"%s\" is in use by timer %s", Option, *Timer->ToDescr());
1397  return;
1398  }
1399  int CurrentChannelNr = cDevice::CurrentChannel();
1400  cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
1401  if (CurrentChannel && Channel == CurrentChannel) {
1402  int n = Channels->GetNextNormal(CurrentChannel->Index());
1403  if (n < 0)
1404  n = Channels->GetPrevNormal(CurrentChannel->Index());
1405  if (n < 0) {
1406  Reply(501, "Can't delete channel \"%s\" - list would be empty", Option);
1407  return;
1408  }
1409  CurrentChannel = Channels->Get(n);
1410  CurrentChannelNr = 0; // triggers channel switch below
1411  }
1412  Channels->Del(Channel);
1413  Channels->ReNumber();
1414  Channels->SetModifiedByUser();
1415  Channels->SetModified();
1416  isyslog("SVDRP %s < %s deleted channel %s", Setup.SVDRPHostName, *clientName, Option);
1417  if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
1418  if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
1419  Channels->SwitchTo(CurrentChannel->Number());
1420  else
1421  cDevice::SetCurrentChannel(CurrentChannel->Number());
1422  }
1423  Reply(250, "Channel \"%s\" deleted", Option);
1424  }
1425  else
1426  Reply(501, "Channel \"%s\" not defined", Option);
1427  }
1428  else
1429  Reply(501, "Missing channel number or id");
1430 }
1431 
1432 static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
1433 {
1434  cRecordControl *rc;
1435  if ((Reason & ruTimer) != 0 && (rc = cRecordControls::GetRecordControl(Recording->FileName())) != NULL)
1436  return cString::sprintf("Recording \"%s\" is in use by timer %d", RecordingId, rc->Timer()->Id());
1437  else if ((Reason & ruReplay) != 0)
1438  return cString::sprintf("Recording \"%s\" is being replayed", RecordingId);
1439  else if ((Reason & ruCut) != 0)
1440  return cString::sprintf("Recording \"%s\" is being edited", RecordingId);
1441  else if ((Reason & (ruMove | ruCopy)) != 0)
1442  return cString::sprintf("Recording \"%s\" is being copied/moved", RecordingId);
1443  else if (Reason)
1444  return cString::sprintf("Recording \"%s\" is in use", RecordingId);
1445  return NULL;
1446 }
1447 
1448 void cSVDRPServer::CmdCPYR(const char *Option)
1449 {
1450  if (*Option) {
1451  char *opt = strdup(Option);
1452  char *num = skipspace(opt);
1453  char *option = num;
1454  while (*option && !isspace(*option))
1455  option++;
1456  char c = *option;
1457  *option = 0;
1458  if (isnumber(num)) {
1460  Recordings->SetExplicitModify();
1461  if (cRecording *Recording = Recordings->Get(strtol(num, NULL, 10) - 1)) {
1462  if (int RecordingInUse = Recording->IsInUse())
1463  Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
1464  else {
1465  if (c)
1466  option = skipspace(++option);
1467  if (*option) {
1468  cString newName = option;
1469  newName.CompactChars(FOLDERDELIMCHAR);
1470  if (strcmp(newName, Recording->Name())) {
1471  cString fromName = cString(ExchangeChars(strdup(Recording->Name()), true), true);
1472  cString toName = cString(ExchangeChars(strdup(*newName), true), true);
1473  cString fileName = cString(strreplace(strdup(Recording->FileName()), *fromName, *toName), true);
1474  if (MakeDirs(fileName, true) && !RecordingsHandler.Add(ruCopy, Recording->FileName(), fileName)) {
1475  Recordings->AddByName(fileName);
1476  Reply(250, "Recording \"%s\" copied to \"%s\"", Recording->Name(), *newName);
1477  }
1478  else
1479  Reply(554, "Error while copying recording \"%s\" to \"%s\"!", Recording->Name(), *newName);
1480  }
1481  else
1482  Reply(501, "Identical new recording name");
1483  }
1484  else
1485  Reply(501, "Missing new recording name");
1486  }
1487  }
1488  else
1489  Reply(550, "Recording \"%s\" not found", num);
1490  }
1491  else
1492  Reply(501, "Error in recording number \"%s\"", num);
1493  free(opt);
1494  }
1495  else
1496  Reply(501, "Missing recording number");
1497 }
1498 
1499 void cSVDRPServer::CmdDELR(const char *Option)
1500 {
1501  if (*Option) {
1502  if (isnumber(Option)) {
1504  Recordings->SetExplicitModify();
1505  if (cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1506  if (int RecordingInUse = Recording->IsInUse())
1507  Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
1508  else {
1509  if (Recording->Delete()) {
1510  Recordings->DelByName(Recording->FileName());
1511  Recordings->SetModified();
1512  isyslog("SVDRP %s < %s deleted recording %s", Setup.SVDRPHostName, *clientName, Option);
1513  Reply(250, "Recording \"%s\" deleted", Option);
1514  }
1515  else
1516  Reply(554, "Error while deleting recording!");
1517  }
1518  }
1519  else
1520  Reply(550, "Recording \"%s\" not found", Option);
1521  }
1522  else
1523  Reply(501, "Error in recording id \"%s\"", Option);
1524  }
1525  else
1526  Reply(501, "Missing recording id");
1527 }
1528 
1529 void cSVDRPServer::CmdDELT(const char *Option)
1530 {
1531  if (*Option) {
1532  if (isnumber(Option)) {
1534  Timers->SetExplicitModify();
1535  if (cTimer *Timer = Timers->GetById(strtol(Option, NULL, 10))) {
1536  if (Timer->Recording()) {
1537  Timer->Skip();
1538  cRecordControls::Process(Timers, time(NULL));
1539  }
1540  Timer->TriggerRespawn();
1541  Timers->Del(Timer);
1542  Timers->SetModified();
1543  isyslog("SVDRP %s < %s deleted timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
1544  Reply(250, "Timer \"%s\" deleted", Option);
1545  }
1546  else
1547  Reply(501, "Timer \"%s\" not defined", Option);
1548  }
1549  else
1550  Reply(501, "Error in timer number \"%s\"", Option);
1551  }
1552  else
1553  Reply(501, "Missing timer number");
1554 }
1555 
1556 void cSVDRPServer::CmdEDIT(const char *Option)
1557 {
1558  if (*Option) {
1559  if (isnumber(Option)) {
1561  if (const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1562  cMarks Marks;
1563  if (Marks.Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.Count()) {
1564  if (!EnoughFreeDiskSpaceForEdit(Recording->FileName()))
1565  Reply(550, "Not enough free disk space to start editing process");
1566  else 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  if (IsRecording && t.IsPatternTimer()) {
2110  Reply(550, "Timer is recording");
2111  return;
2112  }
2113  *Timer = t;
2114  if (IsRecording)
2115  Timer->SetFlags(tfRecording);
2116  else
2117  Timer->ClrFlags(tfRecording);
2118  Timers->SetModified();
2119  isyslog("SVDRP %s < %s modified timer %s (%s)", Setup.SVDRPHostName, *clientName, *Timer->ToDescr(), Timer->HasFlags(tfActive) ? "active" : "inactive");
2120  if (Timer->IsPatternTimer())
2121  Timer->SetEvent(NULL);
2122  Timer->TriggerRespawn();
2123  Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2124  }
2125  else
2126  Reply(501, "Timer \"%d\" not defined", Id);
2127  }
2128  else
2129  Reply(501, "Error in timer id");
2130  }
2131  else
2132  Reply(501, "Missing timer settings");
2133 }
2134 
2135 void cSVDRPServer::CmdMOVC(const char *Option)
2136 {
2137  if (*Option) {
2138  char *tail;
2139  int From = strtol(Option, &tail, 10);
2140  if (tail && tail != Option) {
2141  tail = skipspace(tail);
2142  if (tail && tail != Option) {
2143  LOCK_TIMERS_READ; // necessary to keep timers and channels in sync!
2145  Channels->SetExplicitModify();
2146  int To = strtol(tail, NULL, 10);
2147  int CurrentChannelNr = cDevice::CurrentChannel();
2148  const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
2149  cChannel *FromChannel = Channels->GetByNumber(From);
2150  if (FromChannel) {
2151  cChannel *ToChannel = Channels->GetByNumber(To);
2152  if (ToChannel) {
2153  int FromNumber = FromChannel->Number();
2154  int ToNumber = ToChannel->Number();
2155  if (FromNumber != ToNumber) {
2156  if (Channels->MoveNeedsDecrement(FromChannel, ToChannel))
2157  ToChannel = Channels->Prev(ToChannel); // cListBase::Move() doesn't know about the channel list's numbered groups!
2158  Channels->Move(FromChannel, ToChannel);
2159  Channels->ReNumber();
2160  Channels->SetModifiedByUser();
2161  Channels->SetModified();
2162  if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
2163  if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
2164  Channels->SwitchTo(CurrentChannel->Number());
2165  else
2166  cDevice::SetCurrentChannel(CurrentChannel->Number());
2167  }
2168  isyslog("SVDRP %s < %s moved channel %d to %d", Setup.SVDRPHostName, *clientName, FromNumber, ToNumber);
2169  Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
2170  }
2171  else
2172  Reply(501, "Can't move channel to same position");
2173  }
2174  else
2175  Reply(501, "Channel \"%d\" not defined", To);
2176  }
2177  else
2178  Reply(501, "Channel \"%d\" not defined", From);
2179  }
2180  else
2181  Reply(501, "Error in channel number");
2182  }
2183  else
2184  Reply(501, "Error in channel number");
2185  }
2186  else
2187  Reply(501, "Missing channel number");
2188 }
2189 
2190 void cSVDRPServer::CmdMOVR(const char *Option)
2191 {
2192  if (*Option) {
2193  char *opt = strdup(Option);
2194  char *num = skipspace(opt);
2195  char *option = num;
2196  while (*option && !isspace(*option))
2197  option++;
2198  char c = *option;
2199  *option = 0;
2200  if (isnumber(num)) {
2202  Recordings->SetExplicitModify();
2203  if (cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2204  if (int RecordingInUse = Recording->IsInUse())
2205  Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
2206  else {
2207  if (c)
2208  option = skipspace(++option);
2209  if (*option) {
2210  cString oldName = Recording->Name();
2211  if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) {
2212  Recordings->SetModified();
2213  Recordings->TouchUpdate();
2214  Reply(250, "Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name());
2215  }
2216  else
2217  Reply(554, "Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
2218  }
2219  else
2220  Reply(501, "Missing new recording name");
2221  }
2222  }
2223  else
2224  Reply(550, "Recording \"%s\" not found", num);
2225  }
2226  else
2227  Reply(501, "Error in recording id \"%s\"", num);
2228  free(opt);
2229  }
2230  else
2231  Reply(501, "Missing recording id");
2232 }
2233 
2234 void cSVDRPServer::CmdNEWC(const char *Option)
2235 {
2236  if (*Option) {
2237  cChannel ch;
2238  if (ch.Parse(Option)) {
2240  Channels->SetExplicitModify();
2241  if (Channels->HasUniqueChannelID(&ch)) {
2242  cChannel *channel = new cChannel;
2243  *channel = ch;
2244  Channels->Add(channel);
2245  Channels->ReNumber();
2246  Channels->SetModifiedByUser();
2247  Channels->SetModified();
2248  isyslog("SVDRP %s < %s new channel %d %s", Setup.SVDRPHostName, *clientName, channel->Number(), *channel->ToText());
2249  Reply(250, "%d %s", channel->Number(), *channel->ToText());
2250  }
2251  else
2252  Reply(501, "Channel settings are not unique");
2253  }
2254  else
2255  Reply(501, "Error in channel settings");
2256  }
2257  else
2258  Reply(501, "Missing channel settings");
2259 }
2260 
2261 void cSVDRPServer::CmdNEWT(const char *Option)
2262 {
2263  if (*Option) {
2264  cTimer *Timer = new cTimer;
2265  if (Timer->Parse(Option)) {
2267  Timer->ClrFlags(tfRecording);
2268  Timers->Add(Timer);
2269  isyslog("SVDRP %s < %s added timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2270  Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2271  return;
2272  }
2273  else
2274  Reply(501, "Error in timer settings");
2275  delete Timer;
2276  }
2277  else
2278  Reply(501, "Missing timer settings");
2279 }
2280 
2281 void cSVDRPServer::CmdNEXT(const char *Option)
2282 {
2284  if (const cTimer *t = Timers->GetNextActiveTimer()) {
2285  time_t Start = t->StartTime();
2286  int Id = t->Id();
2287  if (!*Option)
2288  Reply(250, "%d %s", Id, *TimeToString(Start));
2289  else if (strcasecmp(Option, "ABS") == 0)
2290  Reply(250, "%d %jd", Id, intmax_t(Start));
2291  else if (strcasecmp(Option, "REL") == 0)
2292  Reply(250, "%d %jd", Id, intmax_t(Start - time(NULL)));
2293  else
2294  Reply(501, "Unknown option: \"%s\"", Option);
2295  }
2296  else
2297  Reply(550, "No active timers");
2298 }
2299 
2300 void cSVDRPServer::CmdPING(const char *Option)
2301 {
2302  Reply(250, "%s is alive", Setup.SVDRPHostName);
2303 }
2304 
2305 void cSVDRPServer::CmdPLAY(const char *Option)
2306 {
2307  if (*Option) {
2308  char *opt = strdup(Option);
2309  char *num = skipspace(opt);
2310  char *option = num;
2311  while (*option && !isspace(*option))
2312  option++;
2313  char c = *option;
2314  *option = 0;
2315  if (isnumber(num)) {
2316  cStateKey StateKey;
2317  if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(StateKey)) {
2318  if (const cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2319  cString FileName = Recording->FileName();
2320  cString Title = Recording->Title();
2321  int FramesPerSecond = Recording->FramesPerSecond();
2322  bool IsPesRecording = Recording->IsPesRecording();
2323  StateKey.Remove(); // must give up the lock for the call to cControl::Shutdown()
2324  if (c)
2325  option = skipspace(++option);
2328  if (*option) {
2329  int pos = 0;
2330  if (strcasecmp(option, "BEGIN") != 0)
2331  pos = HMSFToIndex(option, FramesPerSecond);
2332  cResumeFile Resume(FileName, IsPesRecording);
2333  if (pos <= 0)
2334  Resume.Delete();
2335  else
2336  Resume.Save(pos);
2337  }
2338  cReplayControl::SetRecording(FileName);
2340  cControl::Attach();
2341  Reply(250, "Playing recording \"%s\" [%s]", num, *Title);
2342  }
2343  else {
2344  StateKey.Remove();
2345  Reply(550, "Recording \"%s\" not found", num);
2346  }
2347  }
2348  }
2349  else
2350  Reply(501, "Error in recording id \"%s\"", num);
2351  free(opt);
2352  }
2353  else
2354  Reply(501, "Missing recording id");
2355 }
2356 
2357 void cSVDRPServer::CmdPLUG(const char *Option)
2358 {
2359  if (*Option) {
2360  char *opt = strdup(Option);
2361  char *name = skipspace(opt);
2362  char *option = name;
2363  while (*option && !isspace(*option))
2364  option++;
2365  char c = *option;
2366  *option = 0;
2367  cPlugin *plugin = cPluginManager::GetPlugin(name);
2368  if (plugin) {
2369  if (c)
2370  option = skipspace(++option);
2371  char *cmd = option;
2372  while (*option && !isspace(*option))
2373  option++;
2374  if (*option) {
2375  *option++ = 0;
2376  option = skipspace(option);
2377  }
2378  if (!*cmd || strcasecmp(cmd, "HELP") == 0) {
2379  if (*cmd && *option) {
2380  const char *hp = GetHelpPage(option, plugin->SVDRPHelpPages());
2381  if (hp) {
2382  Reply(-214, "%s", hp);
2383  Reply(214, "End of HELP info");
2384  }
2385  else
2386  Reply(504, "HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->Name());
2387  }
2388  else {
2389  Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
2390  const char **hp = plugin->SVDRPHelpPages();
2391  if (hp) {
2392  Reply(-214, "SVDRP commands:");
2393  PrintHelpTopics(hp);
2394  Reply(214, "End of HELP info");
2395  }
2396  else
2397  Reply(214, "This plugin has no SVDRP commands");
2398  }
2399  }
2400  else if (strcasecmp(cmd, "MAIN") == 0) {
2401  if (cRemote::CallPlugin(plugin->Name()))
2402  Reply(250, "Initiated call to main menu function of plugin \"%s\"", plugin->Name());
2403  else
2404  Reply(550, "A plugin call is already pending - please try again later");
2405  }
2406  else {
2407  int ReplyCode = 900;
2408  cString s = plugin->SVDRPCommand(cmd, option, ReplyCode);
2409  if (*s)
2410  Reply(abs(ReplyCode), "%s", *s);
2411  else
2412  Reply(500, "Command unrecognized: \"%s\"", cmd);
2413  }
2414  }
2415  else
2416  Reply(550, "Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
2417  free(opt);
2418  }
2419  else {
2420  Reply(-214, "Available plugins:");
2421  cPlugin *plugin;
2422  for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++)
2423  Reply(-214, "%s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
2424  Reply(214, "End of plugin list");
2425  }
2426 }
2427 
2428 void cSVDRPServer::CmdPOLL(const char *Option)
2429 {
2430  if (*Option) {
2431  char buf[strlen(Option) + 1];
2432  char *p = strcpy(buf, Option);
2433  const char *delim = " \t";
2434  char *strtok_next;
2435  char *RemoteName = strtok_r(p, delim, &strtok_next);
2436  char *ListName = strtok_r(NULL, delim, &strtok_next);
2437  if (SVDRPClientHandler) {
2438  if (ListName) {
2439  if (strcasecmp(ListName, "timers") == 0) {
2440  Reply(250, "OK"); // must send reply before calling TriggerFetchingTimers() to avoid a deadlock if two clients send each other POLL commands at the same time
2442  }
2443  else
2444  Reply(501, "Unknown list name: \"%s\"", ListName);
2445  }
2446  else
2447  Reply(501, "Missing list name");
2448  }
2449  else
2450  Reply(501, "No SVDRP client connections");
2451  }
2452  else
2453  Reply(501, "Missing parameters");
2454 }
2455 
2456 void cSVDRPServer::CmdPRIM(const char *Option)
2457 {
2458  int n = -1;
2459  if (*Option) {
2460  if (isnumber(Option)) {
2461  int o = strtol(Option, NULL, 10);
2462  if (o > 0 && o <= cDevice::NumDevices())
2463  n = o;
2464  else
2465  Reply(501, "Invalid device number \"%s\"", Option);
2466  }
2467  else
2468  Reply(501, "Invalid parameter \"%s\"", Option);
2469  if (n >= 0) {
2470  Setup.PrimaryDVB = n;
2471  Reply(250, "Primary device set to %d", n);
2472  }
2473  }
2474  else {
2475  if (const cDevice *d = cDevice::PrimaryDevice())
2476  Reply(250, "%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ? "D" : "-", d->DeviceNumber() + 1 == Setup.PrimaryDVB ? "P" : "-", *d->DeviceName());
2477  else
2478  Reply(501, "Failed to get primary device");
2479  }
2480 }
2481 
2482 void cSVDRPServer::CmdPUTE(const char *Option)
2483 {
2484  if (*Option) {
2485  FILE *f = fopen(Option, "r");
2486  if (f) {
2487  if (cSchedules::Read(f)) {
2488  cSchedules::Cleanup(true);
2489  Reply(250, "EPG data processed from \"%s\"", Option);
2490  }
2491  else
2492  Reply(451, "Error while processing EPG from \"%s\"", Option);
2493  fclose(f);
2494  }
2495  else
2496  Reply(501, "Cannot open file \"%s\"", Option);
2497  }
2498  else {
2499  delete PUTEhandler;
2500  PUTEhandler = new cPUTEhandler;
2501  Reply(PUTEhandler->Status(), "%s", PUTEhandler->Message());
2502  if (PUTEhandler->Status() != 354)
2504  }
2505 }
2506 
2507 void cSVDRPServer::CmdREMO(const char *Option)
2508 {
2509  if (*Option) {
2510  if (!strcasecmp(Option, "ON")) {
2511  cRemote::SetEnabled(true);
2512  Reply(250, "Remote control enabled");
2513  }
2514  else if (!strcasecmp(Option, "OFF")) {
2515  cRemote::SetEnabled(false);
2516  Reply(250, "Remote control disabled");
2517  }
2518  else
2519  Reply(501, "Invalid Option \"%s\"", Option);
2520  }
2521  else
2522  Reply(250, "Remote control is %s", cRemote::Enabled() ? "enabled" : "disabled");
2523 }
2524 
2525 void cSVDRPServer::CmdSCAN(const char *Option)
2526 {
2528  Reply(250, "EPG scan triggered");
2529 }
2530 
2531 void cSVDRPServer::CmdSTAT(const char *Option)
2532 {
2533  if (*Option) {
2534  if (strcasecmp(Option, "DISK") == 0) {
2535  int FreeMB, UsedMB;
2536  int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB);
2537  Reply(250, "%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
2538  }
2539  else
2540  Reply(501, "Invalid Option \"%s\"", Option);
2541  }
2542  else
2543  Reply(501, "No option given");
2544 }
2545 
2546 void cSVDRPServer::CmdUPDT(const char *Option)
2547 {
2548  if (*Option) {
2549  cTimer *Timer = new cTimer;
2550  if (Timer->Parse(Option)) {
2552  if (cTimer *t = Timers->GetTimer(Timer)) {
2553  bool IsRecording = t->HasFlags(tfRecording);
2554  t->Parse(Option);
2555  delete Timer;
2556  Timer = t;
2557  if (IsRecording)
2558  Timer->SetFlags(tfRecording);
2559  else
2560  Timer->ClrFlags(tfRecording);
2561  isyslog("SVDRP %s < %s updated timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2562  }
2563  else {
2564  Timer->ClrFlags(tfRecording);
2565  Timers->Add(Timer);
2566  isyslog("SVDRP %s < %s added timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2567  }
2568  Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2569  return;
2570  }
2571  else
2572  Reply(501, "Error in timer settings");
2573  delete Timer;
2574  }
2575  else
2576  Reply(501, "Missing timer settings");
2577 }
2578 
2579 void cSVDRPServer::CmdUPDR(const char *Option)
2580 {
2582  Recordings->Update(false);
2583  Reply(250, "Re-read of recordings directory triggered");
2584 }
2585 
2586 void cSVDRPServer::CmdVOLU(const char *Option)
2587 {
2588  if (*Option) {
2589  if (isnumber(Option))
2590  cDevice::PrimaryDevice()->SetVolume(strtol(Option, NULL, 10), true);
2591  else if (strcmp(Option, "+") == 0)
2593  else if (strcmp(Option, "-") == 0)
2595  else if (strcasecmp(Option, "MUTE") == 0)
2597  else {
2598  Reply(501, "Unknown option: \"%s\"", Option);
2599  return;
2600  }
2601  }
2602  if (cDevice::PrimaryDevice()->IsMute())
2603  Reply(250, "Audio is mute");
2604  else
2605  Reply(250, "Audio volume is %d", cDevice::CurrentVolume());
2606 }
2607 
2608 #define CMD(c) (strcasecmp(Cmd, c) == 0)
2609 
2610 void cSVDRPServer::Execute(char *Cmd)
2611 {
2612  // handle PUTE data:
2613  if (PUTEhandler) {
2614  if (!PUTEhandler->Process(Cmd)) {
2615  Reply(PUTEhandler->Status(), "%s", PUTEhandler->Message());
2617  }
2618  cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME); // re-trigger the timeout, in case there is very much EPG data
2619  return;
2620  }
2621  // skip leading whitespace:
2622  Cmd = skipspace(Cmd);
2623  // find the end of the command word:
2624  char *s = Cmd;
2625  while (*s && !isspace(*s))
2626  s++;
2627  if (*s)
2628  *s++ = 0;
2629  s = skipspace(s);
2630  if (CMD("CHAN")) CmdCHAN(s);
2631  else if (CMD("CLRE")) CmdCLRE(s);
2632  else if (CMD("CONN")) CmdCONN(s);
2633  else if (CMD("DELC")) CmdDELC(s);
2634  else if (CMD("DELR")) CmdDELR(s);
2635  else if (CMD("DELT")) CmdDELT(s);
2636  else if (CMD("EDIT")) CmdEDIT(s);
2637  else if (CMD("GRAB")) CmdGRAB(s);
2638  else if (CMD("HELP")) CmdHELP(s);
2639  else if (CMD("HITK")) CmdHITK(s);
2640  else if (CMD("LSTC")) CmdLSTC(s);
2641  else if (CMD("LSTD")) CmdLSTD(s);
2642  else if (CMD("LSTE")) CmdLSTE(s);
2643  else if (CMD("LSTR")) CmdLSTR(s);
2644  else if (CMD("LSTT")) CmdLSTT(s);
2645  else if (CMD("MESG")) CmdMESG(s);
2646  else if (CMD("MODC")) CmdMODC(s);
2647  else if (CMD("MODT")) CmdMODT(s);
2648  else if (CMD("MOVC")) CmdMOVC(s);
2649  else if (CMD("MOVR")) CmdMOVR(s);
2650  else if (CMD("NEWC")) CmdNEWC(s);
2651  else if (CMD("NEWT")) CmdNEWT(s);
2652  else if (CMD("NEXT")) CmdNEXT(s);
2653  else if (CMD("PING")) CmdPING(s);
2654  else if (CMD("PLAY")) CmdPLAY(s);
2655  else if (CMD("PLUG")) CmdPLUG(s);
2656  else if (CMD("POLL")) CmdPOLL(s);
2657  else if (CMD("PRIM")) CmdPRIM(s);
2658  else if (CMD("PUTE")) CmdPUTE(s);
2659  else if (CMD("REMO")) CmdREMO(s);
2660  else if (CMD("SCAN")) CmdSCAN(s);
2661  else if (CMD("STAT")) CmdSTAT(s);
2662  else if (CMD("UPDR")) CmdUPDR(s);
2663  else if (CMD("UPDT")) CmdUPDT(s);
2664  else if (CMD("VOLU")) CmdVOLU(s);
2665  else if (CMD("QUIT")) Close(true);
2666  else Reply(500, "Command unrecognized: \"%s\"", Cmd);
2667 }
2668 
2670 {
2671  if (file.IsOpen()) {
2672  while (file.Ready(false)) {
2673  unsigned char c;
2674  int r = safe_read(file, &c, 1);
2675  if (r > 0) {
2676  if (c == '\n' || c == 0x00) {
2677  // strip trailing whitespace:
2678  while (numChars > 0 && strchr(" \t\r\n", cmdLine[numChars - 1]))
2679  cmdLine[--numChars] = 0;
2680  // make sure the string is terminated:
2681  cmdLine[numChars] = 0;
2682  // showtime!
2683  dbgsvdrp("< S %s: %s\n", *clientName, cmdLine);
2684  Execute(cmdLine);
2685  numChars = 0;
2686  if (length > BUFSIZ) {
2687  free(cmdLine); // let's not tie up too much memory
2688  length = BUFSIZ;
2689  cmdLine = MALLOC(char, length);
2690  }
2691  }
2692  else if (c == 0x04 && numChars == 0) {
2693  // end of file (only at beginning of line)
2694  Close(true);
2695  }
2696  else if (c == 0x08 || c == 0x7F) {
2697  // backspace or delete (last character)
2698  if (numChars > 0)
2699  numChars--;
2700  }
2701  else if (c <= 0x03 || c == 0x0D) {
2702  // ignore control characters
2703  }
2704  else {
2705  if (numChars >= length - 1) {
2706  int NewLength = length + BUFSIZ;
2707  if (char *NewBuffer = (char *)realloc(cmdLine, NewLength)) {
2708  length = NewLength;
2709  cmdLine = NewBuffer;
2710  }
2711  else {
2712  esyslog("SVDRP %s < %s ERROR: out of memory", Setup.SVDRPHostName, *clientName);
2713  Close();
2714  break;
2715  }
2716  }
2717  cmdLine[numChars++] = c;
2718  cmdLine[numChars] = 0;
2719  }
2720  lastActivity = time(NULL);
2721  }
2722  else if (r <= 0) {
2723  isyslog("SVDRP %s < %s lost connection to client", Setup.SVDRPHostName, *clientName);
2724  Close();
2725  }
2726  }
2727  if (Setup.SVDRPTimeout && time(NULL) - lastActivity > Setup.SVDRPTimeout) {
2728  isyslog("SVDRP %s < %s timeout on connection", Setup.SVDRPHostName, *clientName);
2729  Close(true, true);
2730  }
2731  }
2732  return file.IsOpen();
2733 }
2734 
2735 void SetSVDRPPorts(int TcpPort, int UdpPort)
2736 {
2737  SVDRPTcpPort = TcpPort;
2738  SVDRPUdpPort = UdpPort;
2739 }
2740 
2741 void SetSVDRPGrabImageDir(const char *GrabImageDir)
2742 {
2743  grabImageDir = GrabImageDir;
2744 }
2745 
2746 // --- cSVDRPServerHandler ---------------------------------------------------
2747 
2749 private:
2750  bool ready;
2753  void HandleServerConnection(void);
2754  void ProcessConnections(void);
2755 protected:
2756  virtual void Action(void);
2757 public:
2758  cSVDRPServerHandler(int TcpPort);
2759  virtual ~cSVDRPServerHandler();
2760  void WaitUntilReady(void);
2761  };
2762 
2764 
2766 :cThread("SVDRP server handler", true)
2767 ,tcpSocket(TcpPort, true)
2768 {
2769  ready = false;
2770 }
2771 
2773 {
2774  Cancel(3);
2775  for (int i = 0; i < serverConnections.Size(); i++)
2776  delete serverConnections[i];
2777 }
2778 
2780 {
2781  cTimeMs Timeout(3000);
2782  while (!ready && !Timeout.TimedOut())
2783  cCondWait::SleepMs(10);
2784 }
2785 
2787 {
2788  for (int i = 0; i < serverConnections.Size(); i++) {
2789  if (!serverConnections[i]->Process()) {
2790  delete serverConnections[i];
2792  i--;
2793  }
2794  }
2795 }
2796 
2798 {
2799  int NewSocket = tcpSocket.Accept();
2800  if (NewSocket >= 0)
2802 }
2803 
2805 {
2806  if (tcpSocket.Listen()) {
2808  ready = true;
2809  while (Running()) {
2810  SVDRPServerPoller.Poll(1000);
2813  }
2815  tcpSocket.Close();
2816  }
2817 }
2818 
2819 // --- SVDRP Handler ---------------------------------------------------------
2820 
2822 
2824 {
2825  cMutexLock MutexLock(&SVDRPHandlerMutex);
2826  if (SVDRPTcpPort) {
2827  if (!SVDRPServerHandler) {
2831  }
2835  }
2836  }
2837 }
2838 
2840 {
2841  cMutexLock MutexLock(&SVDRPHandlerMutex);
2842  delete SVDRPClientHandler;
2843  SVDRPClientHandler = NULL;
2844  delete SVDRPServerHandler;
2845  SVDRPServerHandler = NULL;
2846 }
2847 
2849 {
2850  bool Result = false;
2851  cMutexLock MutexLock(&SVDRPHandlerMutex);
2852  if (SVDRPClientHandler)
2853  Result = SVDRPClientHandler->GetServerNames(ServerNames);
2854  return Result;
2855 }
2856 
2857 bool ExecSVDRPCommand(const char *ServerName, const char *Command, cStringList *Response)
2858 {
2859  bool Result = false;
2860  cMutexLock MutexLock(&SVDRPHandlerMutex);
2861  if (SVDRPClientHandler)
2862  Result = SVDRPClientHandler->Execute(ServerName, Command, Response);
2863  return Result;
2864 }
2865 
2866 void BroadcastSVDRPCommand(const char *Command)
2867 {
2868  cMutexLock MutexLock(&SVDRPHandlerMutex);
2869  cStringList ServerNames;
2870  if (SVDRPClientHandler) {
2871  if (SVDRPClientHandler->GetServerNames(&ServerNames)) {
2872  for (int i = 0; i < ServerNames.Size(); i++)
2873  ExecSVDRPCommand(ServerNames[i], Command);
2874  }
2875  }
2876 }
#define LOCK_CHANNELS_READ
Definition: channels.h:273
#define LOCK_CHANNELS_WRITE
Definition: channels.h:274
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:1424
bool Parse(const char *s)
Definition: channels.c:617
static cString ToText(const cChannel *Channel)
Definition: channels.c:555
int Number(void) const
Definition: channels.h:181
tChannelID GetChannelID(void) const
Definition: channels.h:194
static int MaxNumber(void)
Definition: channels.h:252
static const char * SystemCharacterTable(void)
Definition: tools.h:174
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
Definition: thread.c: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:474
static cDevice * GetDevice(int Index)
Gets the device with the given Index.
Definition: device.c:230
bool SwitchChannel(const cChannel *Channel, bool LiveView)
Switches the device to the given Channel, initiating transfer mode if necessary.
Definition: device.c:825
static int CurrentChannel(void)
Returns the number of the current channel on the primary device.
Definition: device.h:371
static void SetCurrentChannel(int ChannelNumber)
Sets the number of the current channel on the primary device, without actually switching to it.
Definition: device.h:379
void SetVolume(int Volume, bool Absolute=false)
Sets the volume to the given value, either absolutely or relative to the current volume.
Definition: device.c:1076
static int NumDevices(void)
Returns the total number of devices.
Definition: device.h:129
static int CurrentVolume(void)
Definition: device.h:652
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:1047
void ForceScan(void)
Definition: eitscan.c:129
static void SetDisableUntil(time_t Time)
Definition: eit.c:508
Definition: tools.h:466
bool Ready(bool Wait=true)
Definition: tools.c:1728
bool Open(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
Definition: tools.c:1682
void Close(void)
Definition: tools.c:1717
bool IsOpen(void)
Definition: tools.h:480
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:640
cListObject * Prev(void) const
Definition: tools.h:559
int Index(void) const
Definition: tools.c:2139
cListObject * Next(void) const
Definition: tools.h:560
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
Definition: recording.c:2295
Definition: thread.h:67
bool Process(const char *s)
Definition: svdrp.c:793
cPUTEhandler(void)
Definition: svdrp.c:774
int status
Definition: svdrp.c:764
int Status(void)
Definition: svdrp.c:770
FILE * f
Definition: svdrp.c:763
const char * message
Definition: svdrp.c:765
~cPUTEhandler()
Definition: svdrp.c:787
const char * Message(void)
Definition: svdrp.c:771
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:434
bool Add(int FileHandle, bool Out)
Definition: tools.c:1538
bool Poll(int TimeoutMs=0)
Definition: tools.c:1570
void Del(int FileHandle, bool Out)
Definition: tools.c:1557
cTimer * Timer(void)
Definition: menu.h:254
static bool Process(cTimers *Timers, time_t t)
Definition: menu.c:5692
static cRecordControl * GetRecordControl(const char *FileName)
Definition: menu.c:5672
int Id(void) const
Definition: recording.h:146
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
Definition: recording.c:1141
const char * Title(char Delimiter=' ', bool NewIndicator=false, int Level=-1) const
Definition: recording.c:1159
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:2154
static const cRecordings * GetRecordingsRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of recordings for read access.
Definition: recording.h:260
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:5876
bool Save(int Index)
Definition: recording.c:305
void Delete(void)
Definition: recording.c:343
bool Execute(const char *ServerName, const char *Command, cStringList *Response=NULL)
Definition: svdrp.c:729
void AddClient(cSVDRPServerParams &ServerParams, const char *IpAddress)
Definition: svdrp.c:687
virtual ~cSVDRPClientHandler()
Definition: svdrp.c:622
void SendDiscover(void)
Definition: svdrp.c:638
void ProcessConnections(void)
Definition: svdrp.c:644
bool GetServerNames(cStringList *ServerNames)
Definition: svdrp.c:737
cSVDRPClientHandler(int TcpPort, int UdpPort)
Definition: svdrp.c:615
void HandleClientConnection(void)
Definition: svdrp.c:701
cSVDRPClient * GetClientForServer(const char *ServerName)
Definition: svdrp.c:629
cVector< cSVDRPClient * > clientConnections
Definition: svdrp.c:597
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:713
bool TriggerFetchingTimers(const char *ServerName)
Definition: svdrp.c:749
cSocket udpSocket
Definition: svdrp.c:596
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:2797
void ProcessConnections(void)
Definition: svdrp.c:2786
cSVDRPServerHandler(int TcpPort)
Definition: svdrp.c:2765
void WaitUntilReady(void)
Definition: svdrp.c:2779
virtual ~cSVDRPServerHandler()
Definition: svdrp.c:2772
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:2804
cSocket tcpSocket
Definition: svdrp.c:2751
cVector< cSVDRPServer * > serverConnections
Definition: svdrp.c:2752
cString error
Definition: svdrp.c:534
cString name
Definition: svdrp.c:528
const int Timeout(void) const
Definition: svdrp.c:541
cString apiversion
Definition: svdrp.c:531
cSVDRPServerParams(const char *Params)
Definition: svdrp.c:547
const char * Host(void) const
Definition: svdrp.c:542
const char * VdrVersion(void) const
Definition: svdrp.c:539
const char * ApiVersion(void) const
Definition: svdrp.c:540
const char * Error(void) const
Definition: svdrp.c:544
cString vdrversion
Definition: svdrp.c:530
const char * Name(void) const
Definition: svdrp.c:537
const int Port(void) const
Definition: svdrp.c:538
cString host
Definition: svdrp.c:533
bool Ok(void) const
Definition: svdrp.c:543
void CmdMESG(const char *Option)
Definition: svdrp.c:2042
void CmdPOLL(const char *Option)
Definition: svdrp.c:2428
bool Send(const char *s)
Definition: svdrp.c:1164
void CmdLSTT(const char *Option)
Definition: svdrp.c:1986
time_t lastActivity
Definition: svdrp.c:1073
void CmdCLRE(const char *Option)
Definition: svdrp.c:1298
void Reply(int Code, const char *fmt,...) __attribute__((format(printf
Definition: svdrp.c:1175
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:1118
cFile file
Definition: svdrp.c:1068
cPUTEhandler * PUTEhandler
Definition: svdrp.c:1069
void CmdDELC(const char *Option)
Definition: svdrp.c:1383
void CmdPLUG(const char *Option)
Definition: svdrp.c:2357
void CmdMODT(const char *Option)
Definition: svdrp.c:2089
cIpAddress clientIpAddress
Definition: svdrp.c:1066
void CmdCPYR(const char *Option)
Definition: svdrp.c:1448
cString clientName
Definition: svdrp.c:1067
void CmdLSTC(const char *Option)
Definition: svdrp.c:1788
void CmdSCAN(const char *Option)
Definition: svdrp.c:2525
void Close(bool SendReply=false, bool Timeout=false)
Definition: svdrp.c:1150
~cSVDRPServer()
Definition: svdrp.c:1143
void CmdPUTE(const char *Option)
Definition: svdrp.c:2482
void CmdLSTR(const char *Option)
Definition: svdrp.c:1927
void CmdSTAT(const char *Option)
Definition: svdrp.c:2531
void CmdCHAN(const char *Option)
Definition: svdrp.c:1236
void CmdHELP(const char *Option)
Definition: svdrp.c:1721
bool Process(void)
Definition: svdrp.c:2669
void CmdUPDT(const char *Option)
Definition: svdrp.c:2546
void CmdREMO(const char *Option)
Definition: svdrp.c:2507
void CmdLSTE(const char *Option)
Definition: svdrp.c:1848
int length
Definition: svdrp.c:1071
void CmdCONN(const char *Option)
Definition: svdrp.c:1363
void CmdDELR(const char *Option)
Definition: svdrp.c:1499
void Execute(char *Cmd)
Definition: svdrp.c:2610
bool HasConnection(void)
Definition: svdrp.c:1119
void CmdUPDR(const char *Option)
Definition: svdrp.c:2579
void CmdVOLU(const char *Option)
Definition: svdrp.c:2586
void CmdNEWT(const char *Option)
Definition: svdrp.c:2261
void CmdEDIT(const char *Option)
Definition: svdrp.c:1556
void CmdPLAY(const char *Option)
Definition: svdrp.c:2305
void CmdDELT(const char *Option)
Definition: svdrp.c:1529
int socket
Definition: svdrp.c:1065
void CmdLSTD(const char *Option)
Definition: svdrp.c:1836
cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress)
Definition: svdrp.c:1125
void CmdNEXT(const char *Option)
Definition: svdrp.c:2281
void CmdHITK(const char *Option)
Definition: svdrp.c:1749
int numChars
Definition: svdrp.c:1070
void CmdNEWC(const char *Option)
Definition: svdrp.c:2234
void CmdPRIM(const char *Option)
Definition: svdrp.c:2456
void CmdMOVR(const char *Option)
Definition: svdrp.c:2190
void CmdPING(const char *Option)
Definition: svdrp.c:2300
char * cmdLine
Definition: svdrp.c:1072
void CmdMOVC(const char *Option)
Definition: svdrp.c:2135
void void PrintHelpTopics(const char **hp)
Definition: svdrp.c:1210
bool LocalhostOnly(void)
Definition: config.c:282
bool Acceptable(in_addr_t Address)
Definition: config.c:293
Definition: epg.h:152
void Cleanup(time_t Time)
Definition: epg.c:1151
void Dump(const cChannels *Channels, FILE *f, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0) const
Definition: epg.c:1162
static void Cleanup(bool Force=false)
Definition: epg.c:1303
static bool Read(FILE *f=NULL)
Definition: epg.c:1348
char SVDRPDefaultHost[HOST_NAME_MAX]
Definition: config.h:306
int SVDRPTimeout
Definition: config.h:303
int SVDRPPeering
Definition: config.h:304
int PrimaryDVB
Definition: config.h:268
char SVDRPHostName[HOST_NAME_MAX]
Definition: config.h:305
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:867
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:1624
void SortNumerically(void)
Definition: tools.h:863
Definition: tools.h:178
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:1174
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition: tools.c:1180
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:1164
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:404
void Set(int Ms=0)
Sets the timer.
Definition: tools.c:797
bool TimedOut(void) const
Definition: tools.c:802
Definition: timers.h:31
const char * Remote(void) const
Definition: timers.h:80
void ClrFlags(uint Flags)
Definition: timers.c:1032
void SetFlags(uint Flags)
Definition: timers.c:1027
bool IsPatternTimer(void) const
Definition: timers.h:97
cString ToDescr(void) const
Definition: timers.c:329
int Id(void) const
Definition: timers.h:64
bool Parse(const char *s)
Definition: timers.c:442
cString ToText(bool UseChannelID=false) const
Definition: timers.c:319
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:1296
static cTimers * GetTimersWrite(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for write access.
Definition: timers.c:1205
static const cTimers * GetTimersRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for read access.
Definition: timers.c:1200
int Size(void) const
Definition: tools.h:767
virtual void Append(T Data)
Definition: tools.h:787
virtual void Remove(int Index)
Definition: tools.h:801
static int VideoDiskSpace(int *FreeMB=NULL, int *UsedMB=NULL)
Definition: videodir.c:152
cSetup Setup
Definition: config.c:372
cSVDRPhosts SVDRPhosts
Definition: config.c:280
#define APIVERSNUM
Definition: config.h:31
#define VDRVERSION
Definition: config.h:25
#define VDRVERSNUM
Definition: config.h:26
#define VOLUMEDELTA
Definition: device.h:33
cEITScanner EITScanner
Definition: eitscan.c:104
#define LOCK_SCHEDULES_READ
Definition: epg.h:233
eDumpMode
Definition: epg.h:42
@ dmAtTime
Definition: epg.h:42
@ dmPresent
Definition: epg.h:42
@ dmFollowing
Definition: epg.h:42
@ dmAll
Definition: epg.h:42
#define LOCK_SCHEDULES_WRITE
Definition: epg.h:234
eKeys
Definition: keys.h:16
@ kNone
Definition: keys.h:55
char * ExchangeChars(char *s, bool ToFileSystem)
Definition: recording.c:675
bool EnoughFreeDiskSpaceForEdit(const char *FileName)
Definition: recording.c:3473
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
Definition: recording.c:3352
cRecordingsHandler RecordingsHandler
Definition: recording.c:2102
struct __attribute__((packed))
Definition: recording.c:2692
@ ruCut
Definition: recording.h:34
@ ruReplay
Definition: recording.h:32
@ ruCopy
Definition: recording.h:36
@ ruTimer
Definition: recording.h:31
@ ruMove
Definition: recording.h:35
#define LOCK_RECORDINGS_READ
Definition: recording.h:327
#define FOLDERDELIMCHAR
Definition: recording.h:22
#define LOCK_RECORDINGS_WRITE
Definition: recording.h:328
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:1048
#define dbgsvdrp(a...)
Definition: svdrp.c:45
static int SVDRPUdpPort
Definition: svdrp.c:48
void StopSVDRPHandler(void)
Definition: svdrp.c:2839
static cPoller SVDRPClientPoller
Definition: svdrp.c:344
void SetSVDRPGrabImageDir(const char *GrabImageDir)
Definition: svdrp.c:2741
static cString grabImageDir
Definition: svdrp.c:1061
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:822
#define MAXHELPTOPIC
Definition: svdrp.c:821
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:2848
static int SVDRPTcpPort
Definition: svdrp.c:47
static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
Definition: svdrp.c:1432
const char * HelpPages[]
Definition: svdrp.c:825
static cMutex SVDRPHandlerMutex
Definition: svdrp.c:2821
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:2857
static cPoller SVDRPServerPoller
Definition: svdrp.c:1123
const char * GetHelpTopic(const char *HelpPage)
Definition: svdrp.c:1030
static cSVDRPServerHandler * SVDRPServerHandler
Definition: svdrp.c:2763
void StartSVDRPHandler(void)
Definition: svdrp.c:2823
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:2866
#define SVDRPResonseTimeout
static cSVDRPClientHandler * SVDRPClientHandler
Definition: svdrp.c:613
static bool DumpSVDRPDataTransfer
Definition: svdrp.c:43
#define CMD(c)
Definition: svdrp.c:2608
void SetSVDRPPorts(int TcpPort, int UdpPort)
Definition: svdrp.c:2735
@ 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:246
#define LOCK_TIMERS_WRITE
Definition: timers.h:247
@ 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:1256
bool MakeDirs(const char *FileName, bool IsDirectory)
Definition: tools.c:504
bool startswith(const char *s, const char *p)
Definition: tools.c:334
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:300
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:322
bool isnumber(const char *s)
Definition: tools.c:369
cString AddDirectory(const char *DirName, const char *FileName)
Definition: tools.c:407
#define FATALERRNO
Definition: tools.h:52
char * skipspace(const char *s)
Definition: tools.h:244
#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