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