vdr  2.4.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 4.37.1.2 2019/05/06 15:20:40 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  }
654  else if (StateKeySVDRPRemoteTimersPoll.TimedOut())
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>\n"
848  " Delete channel.",
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) {
1251  n = cDevice::CurrentChannel();
1252  if (n > 1) {
1253  n--;
1254  d = -1;
1255  }
1256  }
1257  else if (strcmp(Option, "+") == 0) {
1258  n = cDevice::CurrentChannel();
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  ChannelID = Channels->GetByNumber(o)->GetChannelID();
1311  }
1312  else {
1313  ChannelID = tChannelID::FromString(Option);
1314  if (ChannelID == tChannelID::InvalidID) {
1315  for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1316  if (!Channel->GroupSep()) {
1317  if (strcasecmp(Channel->Name(), Option) == 0) {
1318  ChannelID = Channel->GetChannelID();
1319  break;
1320  }
1321  }
1322  }
1323  }
1324  }
1325  if (!(ChannelID == tChannelID::InvalidID)) {
1327  cSchedule *Schedule = NULL;
1328  ChannelID.ClrRid();
1329  for (cSchedule *p = Schedules->First(); p; p = Schedules->Next(p)) {
1330  if (p->ChannelID() == ChannelID) {
1331  Schedule = p;
1332  break;
1333  }
1334  }
1335  if (Schedule) {
1336  for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
1337  if (ChannelID == Timer->Channel()->GetChannelID().ClrRid())
1338  Timer->SetEvent(NULL);
1339  }
1340  Schedule->Cleanup(INT_MAX);
1342  Reply(250, "EPG data of channel \"%s\" cleared", Option);
1343  }
1344  else {
1345  Reply(550, "No EPG data found for channel \"%s\"", Option);
1346  return;
1347  }
1348  }
1349  else
1350  Reply(501, "Undefined channel \"%s\"", Option);
1351  }
1352  else {
1355  for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer))
1356  Timer->SetEvent(NULL); // processing all timers here (local *and* remote)
1357  for (cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->Next(Schedule))
1358  Schedule->Cleanup(INT_MAX);
1360  Reply(250, "EPG data cleared");
1361  }
1362 }
1363 
1364 void cSVDRPServer::CmdCONN(const char *Option)
1365 {
1366  if (*Option) {
1367  if (SVDRPClientHandler) {
1368  cSVDRPServerParams ServerParams(Option);
1369  if (ServerParams.Ok()) {
1370  clientName = ServerParams.Name();
1371  Reply(250, "OK"); // must finish this transaction before creating the new client
1373  }
1374  else
1375  Reply(501, "Error in server parameters: %s", ServerParams.Error());
1376  }
1377  else
1378  Reply(451, "No SVDRP client handler");
1379  }
1380  else
1381  Reply(501, "Missing server parameters");
1382 }
1383 
1384 void cSVDRPServer::CmdDELC(const char *Option)
1385 {
1386  if (*Option) {
1387  if (isnumber(Option)) {
1390  Channels->SetExplicitModify();
1391  if (cChannel *Channel = Channels->GetByNumber(strtol(Option, NULL, 10))) {
1392  if (const cTimer *Timer = Timers->UsesChannel(Channel)) {
1393  Reply(550, "Channel \"%s\" is in use by timer %s", Option, *Timer->ToDescr());
1394  return;
1395  }
1396  int CurrentChannelNr = cDevice::CurrentChannel();
1397  cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
1398  if (CurrentChannel && Channel == CurrentChannel) {
1399  int n = Channels->GetNextNormal(CurrentChannel->Index());
1400  if (n < 0)
1401  n = Channels->GetPrevNormal(CurrentChannel->Index());
1402  if (n < 0) {
1403  Reply(501, "Can't delete channel \"%s\" - list would be empty", Option);
1404  return;
1405  }
1406  CurrentChannel = Channels->Get(n);
1407  CurrentChannelNr = 0; // triggers channel switch below
1408  }
1409  Channels->Del(Channel);
1410  Channels->ReNumber();
1411  Channels->SetModifiedByUser();
1412  Channels->SetModified();
1413  isyslog("SVDRP %s < %s deleted channel %s", Setup.SVDRPHostName, *clientName, Option);
1414  if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
1415  if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
1416  Channels->SwitchTo(CurrentChannel->Number());
1417  else
1418  cDevice::SetCurrentChannel(CurrentChannel->Number());
1419  }
1420  Reply(250, "Channel \"%s\" deleted", Option);
1421  }
1422  else
1423  Reply(501, "Channel \"%s\" not defined", Option);
1424  }
1425  else
1426  Reply(501, "Error in channel number \"%s\"", Option);
1427  }
1428  else
1429  Reply(501, "Missing channel number");
1430 }
1431 
1432 static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
1433 {
1434  cRecordControl *rc;
1435  if ((Reason & ruTimer) != 0 && (rc = cRecordControls::GetRecordControl(Recording->FileName())) != NULL)
1436  return cString::sprintf("Recording \"%s\" is in use by timer %d", RecordingId, rc->Timer()->Id());
1437  else if ((Reason & ruReplay) != 0)
1438  return cString::sprintf("Recording \"%s\" is being replayed", RecordingId);
1439  else if ((Reason & ruCut) != 0)
1440  return cString::sprintf("Recording \"%s\" is being edited", RecordingId);
1441  else if ((Reason & (ruMove | ruCopy)) != 0)
1442  return cString::sprintf("Recording \"%s\" is being copied/moved", RecordingId);
1443  else if (Reason)
1444  return cString::sprintf("Recording \"%s\" is in use", RecordingId);
1445  return NULL;
1446 }
1447 
1448 void cSVDRPServer::CmdCPYR(const char *Option)
1449 {
1450  if (*Option) {
1451  char *opt = strdup(Option);
1452  char *num = skipspace(opt);
1453  char *option = num;
1454  while (*option && !isspace(*option))
1455  option++;
1456  char c = *option;
1457  *option = 0;
1458  if (isnumber(num)) {
1460  Recordings->SetExplicitModify();
1461  if (cRecording *Recording = Recordings->Get(strtol(num, NULL, 10) - 1)) {
1462  if (int RecordingInUse = Recording->IsInUse())
1463  Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
1464  else {
1465  if (c)
1466  option = skipspace(++option);
1467  if (*option) {
1468  cString newName = option;
1469  newName.CompactChars(FOLDERDELIMCHAR);
1470  if (strcmp(newName, Recording->Name())) {
1471  cString fromName = cString(ExchangeChars(strdup(Recording->Name()), true), true);
1472  cString toName = cString(ExchangeChars(strdup(*newName), true), true);
1473  cString fileName = cString(strreplace(strdup(Recording->FileName()), *fromName, *toName), true);
1474  if (MakeDirs(fileName, true) && !RecordingsHandler.Add(ruCopy, Recording->FileName(), fileName)) {
1475  Recordings->AddByName(fileName);
1476  Reply(250, "Recording \"%s\" copied to \"%s\"", Recording->Name(), *newName);
1477  }
1478  else
1479  Reply(554, "Error while copying recording \"%s\" to \"%s\"!", Recording->Name(), *newName);
1480  }
1481  else
1482  Reply(501, "Identical new recording name");
1483  }
1484  else
1485  Reply(501, "Missing new recording name");
1486  }
1487  }
1488  else
1489  Reply(550, "Recording \"%s\" not found", num);
1490  }
1491  else
1492  Reply(501, "Error in recording number \"%s\"", num);
1493  free(opt);
1494  }
1495  else
1496  Reply(501, "Missing recording number");
1497 }
1498 
1499 void cSVDRPServer::CmdDELR(const char *Option)
1500 {
1501  if (*Option) {
1502  if (isnumber(Option)) {
1504  Recordings->SetExplicitModify();
1505  if (cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1506  if (int RecordingInUse = Recording->IsInUse())
1507  Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
1508  else {
1509  if (Recording->Delete()) {
1510  Recordings->DelByName(Recording->FileName());
1511  Recordings->SetModified();
1512  isyslog("SVDRP %s < %s deleted recording %s", Setup.SVDRPHostName, *clientName, Option);
1513  Reply(250, "Recording \"%s\" deleted", Option);
1514  }
1515  else
1516  Reply(554, "Error while deleting recording!");
1517  }
1518  }
1519  else
1520  Reply(550, "Recording \"%s\" not found", Option);
1521  }
1522  else
1523  Reply(501, "Error in recording id \"%s\"", Option);
1524  }
1525  else
1526  Reply(501, "Missing recording id");
1527 }
1528 
1529 void cSVDRPServer::CmdDELT(const char *Option)
1530 {
1531  if (*Option) {
1532  if (isnumber(Option)) {
1534  Timers->SetExplicitModify();
1535  if (cTimer *Timer = Timers->GetById(strtol(Option, NULL, 10))) {
1536  if (Timer->Recording()) {
1537  Timer->Skip();
1538  cRecordControls::Process(Timers, time(NULL));
1539  }
1540  Timers->Del(Timer);
1541  Timers->SetModified();
1542  isyslog("SVDRP %s < %s deleted timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
1543  Reply(250, "Timer \"%s\" deleted", Option);
1544  }
1545  else
1546  Reply(501, "Timer \"%s\" not defined", Option);
1547  }
1548  else
1549  Reply(501, "Error in timer number \"%s\"", Option);
1550  }
1551  else
1552  Reply(501, "Missing timer number");
1553 }
1554 
1555 void cSVDRPServer::CmdEDIT(const char *Option)
1556 {
1557  if (*Option) {
1558  if (isnumber(Option)) {
1560  if (const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1561  cMarks Marks;
1562  if (Marks.Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.Count()) {
1563  if (RecordingsHandler.Add(ruCut, Recording->FileName()))
1564  Reply(250, "Editing recording \"%s\" [%s]", Option, Recording->Title());
1565  else
1566  Reply(554, "Can't start editing process");
1567  }
1568  else
1569  Reply(554, "No editing marks defined");
1570  }
1571  else
1572  Reply(550, "Recording \"%s\" not found", Option);
1573  }
1574  else
1575  Reply(501, "Error in recording id \"%s\"", Option);
1576  }
1577  else
1578  Reply(501, "Missing recording id");
1579 }
1580 
1581 void cSVDRPServer::CmdGRAB(const char *Option)
1582 {
1583  const char *FileName = NULL;
1584  bool Jpeg = true;
1585  int Quality = -1, SizeX = -1, SizeY = -1;
1586  if (*Option) {
1587  char buf[strlen(Option) + 1];
1588  char *p = strcpy(buf, Option);
1589  const char *delim = " \t";
1590  char *strtok_next;
1591  FileName = strtok_r(p, delim, &strtok_next);
1592  // image type:
1593  const char *Extension = strrchr(FileName, '.');
1594  if (Extension) {
1595  if (strcasecmp(Extension, ".jpg") == 0 || strcasecmp(Extension, ".jpeg") == 0)
1596  Jpeg = true;
1597  else if (strcasecmp(Extension, ".pnm") == 0)
1598  Jpeg = false;
1599  else {
1600  Reply(501, "Unknown image type \"%s\"", Extension + 1);
1601  return;
1602  }
1603  if (Extension == FileName)
1604  FileName = NULL;
1605  }
1606  else if (strcmp(FileName, "-") == 0)
1607  FileName = NULL;
1608  // image quality (and obsolete type):
1609  if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1610  if (strcasecmp(p, "JPEG") == 0 || strcasecmp(p, "PNM") == 0) {
1611  // tolerate for backward compatibility
1612  p = strtok_r(NULL, delim, &strtok_next);
1613  }
1614  if (p) {
1615  if (isnumber(p))
1616  Quality = atoi(p);
1617  else {
1618  Reply(501, "Invalid quality \"%s\"", p);
1619  return;
1620  }
1621  }
1622  }
1623  // image size:
1624  if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1625  if (isnumber(p))
1626  SizeX = atoi(p);
1627  else {
1628  Reply(501, "Invalid sizex \"%s\"", p);
1629  return;
1630  }
1631  if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1632  if (isnumber(p))
1633  SizeY = atoi(p);
1634  else {
1635  Reply(501, "Invalid sizey \"%s\"", p);
1636  return;
1637  }
1638  }
1639  else {
1640  Reply(501, "Missing sizey");
1641  return;
1642  }
1643  }
1644  if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1645  Reply(501, "Unexpected parameter \"%s\"", p);
1646  return;
1647  }
1648  // canonicalize the file name:
1649  char RealFileName[PATH_MAX];
1650  if (FileName) {
1651  if (*grabImageDir) {
1652  cString s(FileName);
1653  FileName = s;
1654  const char *slash = strrchr(FileName, '/');
1655  if (!slash) {
1656  s = AddDirectory(grabImageDir, FileName);
1657  FileName = s;
1658  }
1659  slash = strrchr(FileName, '/'); // there definitely is one
1660  cString t(s);
1661  t.Truncate(slash - FileName);
1662  char *r = realpath(t, RealFileName);
1663  if (!r) {
1664  LOG_ERROR_STR(FileName);
1665  Reply(501, "Invalid file name \"%s\"", FileName);
1666  return;
1667  }
1668  strcat(RealFileName, slash);
1669  FileName = RealFileName;
1670  if (strncmp(FileName, grabImageDir, strlen(grabImageDir)) != 0) {
1671  Reply(501, "Invalid file name \"%s\"", FileName);
1672  return;
1673  }
1674  }
1675  else {
1676  Reply(550, "Grabbing to file not allowed (use \"GRAB -\" instead)");
1677  return;
1678  }
1679  }
1680  // actual grabbing:
1681  int ImageSize;
1682  uchar *Image = cDevice::PrimaryDevice()->GrabImage(ImageSize, Jpeg, Quality, SizeX, SizeY);
1683  if (Image) {
1684  if (FileName) {
1685  int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
1686  if (fd >= 0) {
1687  if (safe_write(fd, Image, ImageSize) == ImageSize) {
1688  dsyslog("SVDRP %s < %s grabbed image to %s", Setup.SVDRPHostName, *clientName, FileName);
1689  Reply(250, "Grabbed image %s", Option);
1690  }
1691  else {
1692  LOG_ERROR_STR(FileName);
1693  Reply(451, "Can't write to '%s'", FileName);
1694  }
1695  close(fd);
1696  }
1697  else {
1698  LOG_ERROR_STR(FileName);
1699  Reply(451, "Can't open '%s'", FileName);
1700  }
1701  }
1702  else {
1703  cBase64Encoder Base64(Image, ImageSize);
1704  const char *s;
1705  while ((s = Base64.NextLine()) != NULL)
1706  Reply(-216, "%s", s);
1707  Reply(216, "Grabbed image %s", Option);
1708  }
1709  free(Image);
1710  }
1711  else
1712  Reply(451, "Grab image failed");
1713  }
1714  else
1715  Reply(501, "Missing filename");
1716 }
1717 
1718 void cSVDRPServer::CmdHELP(const char *Option)
1719 {
1720  if (*Option) {
1721  const char *hp = GetHelpPage(Option, HelpPages);
1722  if (hp)
1723  Reply(-214, "%s", hp);
1724  else {
1725  Reply(504, "HELP topic \"%s\" unknown", Option);
1726  return;
1727  }
1728  }
1729  else {
1730  Reply(-214, "This is VDR version %s", VDRVERSION);
1731  Reply(-214, "Topics:");
1733  cPlugin *plugin;
1734  for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++) {
1735  const char **hp = plugin->SVDRPHelpPages();
1736  if (hp)
1737  Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
1738  PrintHelpTopics(hp);
1739  }
1740  Reply(-214, "To report bugs in the implementation send email to");
1741  Reply(-214, " vdr-bugs@tvdr.de");
1742  }
1743  Reply(214, "End of HELP info");
1744 }
1745 
1746 void cSVDRPServer::CmdHITK(const char *Option)
1747 {
1748  if (*Option) {
1749  if (!cRemote::Enabled()) {
1750  Reply(550, "Remote control currently disabled (key \"%s\" discarded)", Option);
1751  return;
1752  }
1753  char buf[strlen(Option) + 1];
1754  strcpy(buf, Option);
1755  const char *delim = " \t";
1756  char *strtok_next;
1757  char *p = strtok_r(buf, delim, &strtok_next);
1758  int NumKeys = 0;
1759  while (p) {
1760  eKeys k = cKey::FromString(p);
1761  if (k != kNone) {
1762  if (!cRemote::Put(k)) {
1763  Reply(451, "Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
1764  return;
1765  }
1766  }
1767  else {
1768  Reply(504, "Unknown key: \"%s\"", p);
1769  return;
1770  }
1771  NumKeys++;
1772  p = strtok_r(NULL, delim, &strtok_next);
1773  }
1774  Reply(250, "Key%s \"%s\" accepted", NumKeys > 1 ? "s" : "", Option);
1775  }
1776  else {
1777  Reply(-214, "Valid <key> names for the HITK command:");
1778  for (int i = 0; i < kNone; i++) {
1779  Reply(-214, " %s", cKey::ToString(eKeys(i)));
1780  }
1781  Reply(214, "End of key list");
1782  }
1783 }
1784 
1785 void cSVDRPServer::CmdLSTC(const char *Option)
1786 {
1788  bool WithChannelIds = startswith(Option, ":ids") && (Option[4] == ' ' || Option[4] == 0);
1789  if (WithChannelIds)
1790  Option = skipspace(Option + 4);
1791  bool WithGroupSeps = strcasecmp(Option, ":groups") == 0;
1792  if (*Option && !WithGroupSeps) {
1793  if (isnumber(Option)) {
1794  int n = strtol(Option, NULL, 10);
1795  if (n == 0)
1796  n = cDevice::CurrentChannel();
1797  if (const cChannel *Channel = Channels->GetByNumber(n))
1798  Reply(250, "%d%s%s %s", Channel->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Channel->GetChannelID().ToString() : "", *Channel->ToText());
1799  else
1800  Reply(501, "Channel \"%s\" not defined", Option);
1801  }
1802  else {
1803  const cChannel *Next = Channels->GetByChannelID(tChannelID::FromString(Option));
1804  if (!Next) {
1805  for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1806  if (!Channel->GroupSep()) {
1807  if (strcasestr(Channel->Name(), Option)) {
1808  if (Next)
1809  Reply(-250, "%d%s%s %s", Next->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Next->GetChannelID().ToString() : "", *Next->ToText());
1810  Next = Channel;
1811  }
1812  }
1813  }
1814  }
1815  if (Next)
1816  Reply(250, "%d%s%s %s", Next->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Next->GetChannelID().ToString() : "", *Next->ToText());
1817  else
1818  Reply(501, "Channel \"%s\" not defined", Option);
1819  }
1820  }
1821  else if (cChannels::MaxNumber() >= 1) {
1822  for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
1823  if (WithGroupSeps)
1824  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());
1825  else if (!Channel->GroupSep())
1826  Reply(Channel->Number() < cChannels::MaxNumber() ? -250 : 250, "%d%s%s %s", Channel->Number(), WithChannelIds ? " " : "", WithChannelIds ? *Channel->GetChannelID().ToString() : "", *Channel->ToText());
1827  }
1828  }
1829  else
1830  Reply(550, "No channels defined");
1831 }
1832 
1833 void cSVDRPServer::CmdLSTD(const char *Option)
1834 {
1835  if (cDevice::NumDevices()) {
1836  for (int i = 0; i < cDevice::NumDevices(); i++) {
1837  if (const cDevice *d = cDevice::GetDevice(i))
1838  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());
1839  }
1840  }
1841  else
1842  Reply(550, "No devices found");
1843 }
1844 
1845 void cSVDRPServer::CmdLSTE(const char *Option)
1846 {
1849  const cSchedule* Schedule = NULL;
1850  eDumpMode DumpMode = dmAll;
1851  time_t AtTime = 0;
1852  if (*Option) {
1853  char buf[strlen(Option) + 1];
1854  strcpy(buf, Option);
1855  const char *delim = " \t";
1856  char *strtok_next;
1857  char *p = strtok_r(buf, delim, &strtok_next);
1858  while (p && DumpMode == dmAll) {
1859  if (strcasecmp(p, "NOW") == 0)
1860  DumpMode = dmPresent;
1861  else if (strcasecmp(p, "NEXT") == 0)
1862  DumpMode = dmFollowing;
1863  else if (strcasecmp(p, "AT") == 0) {
1864  DumpMode = dmAtTime;
1865  if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1866  if (isnumber(p))
1867  AtTime = strtol(p, NULL, 10);
1868  else {
1869  Reply(501, "Invalid time");
1870  return;
1871  }
1872  }
1873  else {
1874  Reply(501, "Missing time");
1875  return;
1876  }
1877  }
1878  else if (!Schedule) {
1879  const cChannel* Channel = NULL;
1880  if (isnumber(p))
1881  Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1882  else
1883  Channel = Channels->GetByChannelID(tChannelID::FromString(Option));
1884  if (Channel) {
1885  Schedule = Schedules->GetSchedule(Channel);
1886  if (!Schedule) {
1887  Reply(550, "No schedule found");
1888  return;
1889  }
1890  }
1891  else {
1892  Reply(550, "Channel \"%s\" not defined", p);
1893  return;
1894  }
1895  }
1896  else {
1897  Reply(501, "Unknown option: \"%s\"", p);
1898  return;
1899  }
1900  p = strtok_r(NULL, delim, &strtok_next);
1901  }
1902  }
1903  int fd = dup(file);
1904  if (fd) {
1905  FILE *f = fdopen(fd, "w");
1906  if (f) {
1907  if (Schedule)
1908  Schedule->Dump(Channels, f, "215-", DumpMode, AtTime);
1909  else
1910  Schedules->Dump(f, "215-", DumpMode, AtTime);
1911  fflush(f);
1912  Reply(215, "End of EPG data");
1913  fclose(f);
1914  }
1915  else {
1916  Reply(451, "Can't open file connection");
1917  close(fd);
1918  }
1919  }
1920  else
1921  Reply(451, "Can't dup stream descriptor");
1922 }
1923 
1924 void cSVDRPServer::CmdLSTR(const char *Option)
1925 {
1926  int Number = 0;
1927  bool Path = false;
1929  if (*Option) {
1930  char buf[strlen(Option) + 1];
1931  strcpy(buf, Option);
1932  const char *delim = " \t";
1933  char *strtok_next;
1934  char *p = strtok_r(buf, delim, &strtok_next);
1935  while (p) {
1936  if (!Number) {
1937  if (isnumber(p))
1938  Number = strtol(p, NULL, 10);
1939  else {
1940  Reply(501, "Error in recording id \"%s\"", Option);
1941  return;
1942  }
1943  }
1944  else if (strcasecmp(p, "PATH") == 0)
1945  Path = true;
1946  else {
1947  Reply(501, "Unknown option: \"%s\"", p);
1948  return;
1949  }
1950  p = strtok_r(NULL, delim, &strtok_next);
1951  }
1952  if (Number) {
1953  if (const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1954  FILE *f = fdopen(file, "w");
1955  if (f) {
1956  if (Path)
1957  Reply(250, "%s", Recording->FileName());
1958  else {
1959  Recording->Info()->Write(f, "215-");
1960  fflush(f);
1961  Reply(215, "End of recording information");
1962  }
1963  // don't 'fclose(f)' here!
1964  }
1965  else
1966  Reply(451, "Can't open file connection");
1967  }
1968  else
1969  Reply(550, "Recording \"%s\" not found", Option);
1970  }
1971  }
1972  else if (Recordings->Count()) {
1973  const cRecording *Recording = Recordings->First();
1974  while (Recording) {
1975  Reply(Recording == Recordings->Last() ? 250 : -250, "%d %s", Recording->Id(), Recording->Title(' ', true));
1976  Recording = Recordings->Next(Recording);
1977  }
1978  }
1979  else
1980  Reply(550, "No recordings available");
1981 }
1982 
1983 void cSVDRPServer::CmdLSTT(const char *Option)
1984 {
1985  int Id = 0;
1986  bool UseChannelId = false;
1987  if (*Option) {
1988  char buf[strlen(Option) + 1];
1989  strcpy(buf, Option);
1990  const char *delim = " \t";
1991  char *strtok_next;
1992  char *p = strtok_r(buf, delim, &strtok_next);
1993  while (p) {
1994  if (isnumber(p))
1995  Id = strtol(p, NULL, 10);
1996  else if (strcasecmp(p, "ID") == 0)
1997  UseChannelId = true;
1998  else {
1999  Reply(501, "Unknown option: \"%s\"", p);
2000  return;
2001  }
2002  p = strtok_r(NULL, delim, &strtok_next);
2003  }
2004  }
2006  if (Id) {
2007  for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
2008  if (!Timer->Remote()) {
2009  if (Timer->Id() == Id) {
2010  Reply(250, "%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2011  return;
2012  }
2013  }
2014  }
2015  Reply(501, "Timer \"%s\" not defined", Option);
2016  return;
2017  }
2018  else {
2019  const cTimer *LastLocalTimer = Timers->Last();
2020  while (LastLocalTimer) {
2021  if (LastLocalTimer->Remote())
2022  LastLocalTimer = Timers->Prev(LastLocalTimer);
2023  else
2024  break;
2025  }
2026  if (LastLocalTimer) {
2027  for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
2028  if (!Timer->Remote())
2029  Reply(Timer != LastLocalTimer ? -250 : 250, "%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2030  if (Timer == LastLocalTimer)
2031  break;
2032  }
2033  return;
2034  }
2035  }
2036  Reply(550, "No timers defined");
2037 }
2038 
2039 void cSVDRPServer::CmdMESG(const char *Option)
2040 {
2041  if (*Option) {
2042  isyslog("SVDRP %s < %s message '%s'", Setup.SVDRPHostName, *clientName, Option);
2043  Skins.QueueMessage(mtInfo, Option);
2044  Reply(250, "Message queued");
2045  }
2046  else
2047  Reply(501, "Missing message");
2048 }
2049 
2050 void cSVDRPServer::CmdMODC(const char *Option)
2051 {
2052  if (*Option) {
2053  char *tail;
2054  int n = strtol(Option, &tail, 10);
2055  if (tail && tail != Option) {
2056  tail = skipspace(tail);
2058  Channels->SetExplicitModify();
2059  if (cChannel *Channel = Channels->GetByNumber(n)) {
2060  cChannel ch;
2061  if (ch.Parse(tail)) {
2062  if (Channels->HasUniqueChannelID(&ch, Channel)) {
2063  *Channel = ch;
2064  Channels->ReNumber();
2065  Channels->SetModifiedByUser();
2066  Channels->SetModified();
2067  isyslog("SVDRP %s < %s modifed channel %d %s", Setup.SVDRPHostName, *clientName, Channel->Number(), *Channel->ToText());
2068  Reply(250, "%d %s", Channel->Number(), *Channel->ToText());
2069  }
2070  else
2071  Reply(501, "Channel settings are not unique");
2072  }
2073  else
2074  Reply(501, "Error in channel settings");
2075  }
2076  else
2077  Reply(501, "Channel \"%d\" not defined", n);
2078  }
2079  else
2080  Reply(501, "Error in channel number");
2081  }
2082  else
2083  Reply(501, "Missing channel settings");
2084 }
2085 
2086 void cSVDRPServer::CmdMODT(const char *Option)
2087 {
2088  if (*Option) {
2089  char *tail;
2090  int Id = strtol(Option, &tail, 10);
2091  if (tail && tail != Option) {
2092  tail = skipspace(tail);
2094  Timers->SetExplicitModify();
2095  if (cTimer *Timer = Timers->GetById(Id)) {
2096  bool IsRecording = Timer->HasFlags(tfRecording);
2097  cTimer t = *Timer;
2098  if (strcasecmp(tail, "ON") == 0)
2099  t.SetFlags(tfActive);
2100  else if (strcasecmp(tail, "OFF") == 0)
2101  t.ClrFlags(tfActive);
2102  else if (!t.Parse(tail)) {
2103  Reply(501, "Error in timer settings");
2104  return;
2105  }
2106  *Timer = t;
2107  if (IsRecording)
2108  Timer->SetFlags(tfRecording);
2109  else
2110  Timer->ClrFlags(tfRecording);
2111  Timers->SetModified();
2112  isyslog("SVDRP %s < %s modified timer %s (%s)", Setup.SVDRPHostName, *clientName, *Timer->ToDescr(), Timer->HasFlags(tfActive) ? "active" : "inactive");
2113  Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2114  }
2115  else
2116  Reply(501, "Timer \"%d\" not defined", Id);
2117  }
2118  else
2119  Reply(501, "Error in timer id");
2120  }
2121  else
2122  Reply(501, "Missing timer settings");
2123 }
2124 
2125 void cSVDRPServer::CmdMOVC(const char *Option)
2126 {
2127  if (*Option) {
2128  char *tail;
2129  int From = strtol(Option, &tail, 10);
2130  if (tail && tail != Option) {
2131  tail = skipspace(tail);
2132  if (tail && tail != Option) {
2133  LOCK_TIMERS_READ; // necessary to keep timers and channels in sync!
2135  Channels->SetExplicitModify();
2136  int To = strtol(tail, NULL, 10);
2137  int CurrentChannelNr = cDevice::CurrentChannel();
2138  const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
2139  cChannel *FromChannel = Channels->GetByNumber(From);
2140  if (FromChannel) {
2141  cChannel *ToChannel = Channels->GetByNumber(To);
2142  if (ToChannel) {
2143  int FromNumber = FromChannel->Number();
2144  int ToNumber = ToChannel->Number();
2145  if (FromNumber != ToNumber) {
2146  Channels->Move(FromChannel, ToChannel);
2147  Channels->ReNumber();
2148  Channels->SetModifiedByUser();
2149  Channels->SetModified();
2150  if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
2151  if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
2152  Channels->SwitchTo(CurrentChannel->Number());
2153  else
2154  cDevice::SetCurrentChannel(CurrentChannel->Number());
2155  }
2156  isyslog("SVDRP %s < %s moved channel %d to %d", Setup.SVDRPHostName, *clientName, FromNumber, ToNumber);
2157  Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
2158  }
2159  else
2160  Reply(501, "Can't move channel to same position");
2161  }
2162  else
2163  Reply(501, "Channel \"%d\" not defined", To);
2164  }
2165  else
2166  Reply(501, "Channel \"%d\" not defined", From);
2167  }
2168  else
2169  Reply(501, "Error in channel number");
2170  }
2171  else
2172  Reply(501, "Error in channel number");
2173  }
2174  else
2175  Reply(501, "Missing channel number");
2176 }
2177 
2178 void cSVDRPServer::CmdMOVR(const char *Option)
2179 {
2180  if (*Option) {
2181  char *opt = strdup(Option);
2182  char *num = skipspace(opt);
2183  char *option = num;
2184  while (*option && !isspace(*option))
2185  option++;
2186  char c = *option;
2187  *option = 0;
2188  if (isnumber(num)) {
2190  Recordings->SetExplicitModify();
2191  if (cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2192  if (int RecordingInUse = Recording->IsInUse())
2193  Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording));
2194  else {
2195  if (c)
2196  option = skipspace(++option);
2197  if (*option) {
2198  cString oldName = Recording->Name();
2199  if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) {
2200  Recordings->SetModified();
2201  Recordings->TouchUpdate();
2202  Reply(250, "Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name());
2203  }
2204  else
2205  Reply(554, "Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
2206  }
2207  else
2208  Reply(501, "Missing new recording name");
2209  }
2210  }
2211  else
2212  Reply(550, "Recording \"%s\" not found", num);
2213  }
2214  else
2215  Reply(501, "Error in recording id \"%s\"", num);
2216  free(opt);
2217  }
2218  else
2219  Reply(501, "Missing recording id");
2220 }
2221 
2222 void cSVDRPServer::CmdNEWC(const char *Option)
2223 {
2224  if (*Option) {
2225  cChannel ch;
2226  if (ch.Parse(Option)) {
2228  Channels->SetExplicitModify();
2229  if (Channels->HasUniqueChannelID(&ch)) {
2230  cChannel *channel = new cChannel;
2231  *channel = ch;
2232  Channels->Add(channel);
2233  Channels->ReNumber();
2234  Channels->SetModifiedByUser();
2235  Channels->SetModified();
2236  isyslog("SVDRP %s < %s new channel %d %s", Setup.SVDRPHostName, *clientName, channel->Number(), *channel->ToText());
2237  Reply(250, "%d %s", channel->Number(), *channel->ToText());
2238  }
2239  else
2240  Reply(501, "Channel settings are not unique");
2241  }
2242  else
2243  Reply(501, "Error in channel settings");
2244  }
2245  else
2246  Reply(501, "Missing channel settings");
2247 }
2248 
2249 void cSVDRPServer::CmdNEWT(const char *Option)
2250 {
2251  if (*Option) {
2252  cTimer *Timer = new cTimer;
2253  if (Timer->Parse(Option)) {
2255  Timer->ClrFlags(tfRecording);
2256  Timers->Add(Timer);
2257  isyslog("SVDRP %s < %s added timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2258  Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2259  return;
2260  }
2261  else
2262  Reply(501, "Error in timer settings");
2263  delete Timer;
2264  }
2265  else
2266  Reply(501, "Missing timer settings");
2267 }
2268 
2269 void cSVDRPServer::CmdNEXT(const char *Option)
2270 {
2272  if (const cTimer *t = Timers->GetNextActiveTimer()) {
2273  time_t Start = t->StartTime();
2274  int Id = t->Id();
2275  if (!*Option)
2276  Reply(250, "%d %s", Id, *TimeToString(Start));
2277  else if (strcasecmp(Option, "ABS") == 0)
2278  Reply(250, "%d %ld", Id, Start);
2279  else if (strcasecmp(Option, "REL") == 0)
2280  Reply(250, "%d %ld", Id, Start - time(NULL));
2281  else
2282  Reply(501, "Unknown option: \"%s\"", Option);
2283  }
2284  else
2285  Reply(550, "No active timers");
2286 }
2287 
2288 void cSVDRPServer::CmdPING(const char *Option)
2289 {
2290  Reply(250, "%s is alive", Setup.SVDRPHostName);
2291 }
2292 
2293 void cSVDRPServer::CmdPLAY(const char *Option)
2294 {
2295  if (*Option) {
2296  char *opt = strdup(Option);
2297  char *num = skipspace(opt);
2298  char *option = num;
2299  while (*option && !isspace(*option))
2300  option++;
2301  char c = *option;
2302  *option = 0;
2303  if (isnumber(num)) {
2304  cStateKey StateKey;
2305  if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(StateKey)) {
2306  if (const cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2307  cString FileName = Recording->FileName();
2308  cString Title = Recording->Title();
2309  int FramesPerSecond = Recording->FramesPerSecond();
2310  bool IsPesRecording = Recording->IsPesRecording();
2311  StateKey.Remove(); // must give up the lock for the call to cControl::Shutdown()
2312  if (c)
2313  option = skipspace(++option);
2316  if (*option) {
2317  int pos = 0;
2318  if (strcasecmp(option, "BEGIN") != 0)
2319  pos = HMSFToIndex(option, FramesPerSecond);
2320  cResumeFile Resume(FileName, IsPesRecording);
2321  if (pos <= 0)
2322  Resume.Delete();
2323  else
2324  Resume.Save(pos);
2325  }
2326  cReplayControl::SetRecording(FileName);
2328  cControl::Attach();
2329  Reply(250, "Playing recording \"%s\" [%s]", num, *Title);
2330  }
2331  else {
2332  StateKey.Remove();
2333  Reply(550, "Recording \"%s\" not found", num);
2334  }
2335  }
2336  }
2337  else
2338  Reply(501, "Error in recording id \"%s\"", num);
2339  free(opt);
2340  }
2341  else
2342  Reply(501, "Missing recording id");
2343 }
2344 
2345 void cSVDRPServer::CmdPLUG(const char *Option)
2346 {
2347  if (*Option) {
2348  char *opt = strdup(Option);
2349  char *name = skipspace(opt);
2350  char *option = name;
2351  while (*option && !isspace(*option))
2352  option++;
2353  char c = *option;
2354  *option = 0;
2355  cPlugin *plugin = cPluginManager::GetPlugin(name);
2356  if (plugin) {
2357  if (c)
2358  option = skipspace(++option);
2359  char *cmd = option;
2360  while (*option && !isspace(*option))
2361  option++;
2362  if (*option) {
2363  *option++ = 0;
2364  option = skipspace(option);
2365  }
2366  if (!*cmd || strcasecmp(cmd, "HELP") == 0) {
2367  if (*cmd && *option) {
2368  const char *hp = GetHelpPage(option, plugin->SVDRPHelpPages());
2369  if (hp) {
2370  Reply(-214, "%s", hp);
2371  Reply(214, "End of HELP info");
2372  }
2373  else
2374  Reply(504, "HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->Name());
2375  }
2376  else {
2377  Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
2378  const char **hp = plugin->SVDRPHelpPages();
2379  if (hp) {
2380  Reply(-214, "SVDRP commands:");
2381  PrintHelpTopics(hp);
2382  Reply(214, "End of HELP info");
2383  }
2384  else
2385  Reply(214, "This plugin has no SVDRP commands");
2386  }
2387  }
2388  else if (strcasecmp(cmd, "MAIN") == 0) {
2389  if (cRemote::CallPlugin(plugin->Name()))
2390  Reply(250, "Initiated call to main menu function of plugin \"%s\"", plugin->Name());
2391  else
2392  Reply(550, "A plugin call is already pending - please try again later");
2393  }
2394  else {
2395  int ReplyCode = 900;
2396  cString s = plugin->SVDRPCommand(cmd, option, ReplyCode);
2397  if (*s)
2398  Reply(abs(ReplyCode), "%s", *s);
2399  else
2400  Reply(500, "Command unrecognized: \"%s\"", cmd);
2401  }
2402  }
2403  else
2404  Reply(550, "Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
2405  free(opt);
2406  }
2407  else {
2408  Reply(-214, "Available plugins:");
2409  cPlugin *plugin;
2410  for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++)
2411  Reply(-214, "%s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
2412  Reply(214, "End of plugin list");
2413  }
2414 }
2415 
2416 void cSVDRPServer::CmdPOLL(const char *Option)
2417 {
2418  if (*Option) {
2419  char buf[strlen(Option) + 1];
2420  char *p = strcpy(buf, Option);
2421  const char *delim = " \t";
2422  char *strtok_next;
2423  char *RemoteName = strtok_r(p, delim, &strtok_next);
2424  char *ListName = strtok_r(NULL, delim, &strtok_next);
2425  if (SVDRPClientHandler) {
2426  if (ListName) {
2427  if (strcasecmp(ListName, "timers") == 0) {
2428  if (SVDRPClientHandler->TriggerFetchingTimers(RemoteName))
2429  Reply(250, "OK");
2430  else
2431  Reply(501, "No connection to \"%s\"", RemoteName);
2432  }
2433  else
2434  Reply(501, "Unknown list name: \"%s\"", ListName);
2435  }
2436  else
2437  Reply(501, "Missing list name");
2438  }
2439  else
2440  Reply(501, "No SVDRP client connections");
2441  }
2442  else
2443  Reply(501, "Missing parameters");
2444 }
2445 
2446 void cSVDRPServer::CmdPRIM(const char *Option)
2447 {
2448  int n = -1;
2449  if (*Option) {
2450  if (isnumber(Option)) {
2451  int o = strtol(Option, NULL, 10);
2452  if (o > 0 && o <= cDevice::NumDevices())
2453  n = o;
2454  else
2455  Reply(501, "Invalid device number \"%s\"", Option);
2456  }
2457  else
2458  Reply(501, "Invalid parameter \"%s\"", Option);
2459  if (n >= 0) {
2460  Setup.PrimaryDVB = n;
2461  Reply(250, "Primary device set to %d", n);
2462  }
2463  }
2464  else {
2465  if (const cDevice *d = cDevice::PrimaryDevice())
2466  Reply(250, "%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ? "D" : "-", d->DeviceNumber() + 1 == Setup.PrimaryDVB ? "P" : "-", *d->DeviceName());
2467  else
2468  Reply(501, "Failed to get primary device");
2469  }
2470 }
2471 
2472 void cSVDRPServer::CmdPUTE(const char *Option)
2473 {
2474  if (*Option) {
2475  FILE *f = fopen(Option, "r");
2476  if (f) {
2477  if (cSchedules::Read(f)) {
2478  cSchedules::Cleanup(true);
2479  Reply(250, "EPG data processed from \"%s\"", Option);
2480  }
2481  else
2482  Reply(451, "Error while processing EPG from \"%s\"", Option);
2483  fclose(f);
2484  }
2485  else
2486  Reply(501, "Cannot open file \"%s\"", Option);
2487  }
2488  else {
2489  delete PUTEhandler;
2490  PUTEhandler = new cPUTEhandler;
2491  Reply(PUTEhandler->Status(), "%s", PUTEhandler->Message());
2492  if (PUTEhandler->Status() != 354)
2494  }
2495 }
2496 
2497 void cSVDRPServer::CmdREMO(const char *Option)
2498 {
2499  if (*Option) {
2500  if (!strcasecmp(Option, "ON")) {
2501  cRemote::SetEnabled(true);
2502  Reply(250, "Remote control enabled");
2503  }
2504  else if (!strcasecmp(Option, "OFF")) {
2505  cRemote::SetEnabled(false);
2506  Reply(250, "Remote control disabled");
2507  }
2508  else
2509  Reply(501, "Invalid Option \"%s\"", Option);
2510  }
2511  else
2512  Reply(250, "Remote control is %s", cRemote::Enabled() ? "enabled" : "disabled");
2513 }
2514 
2515 void cSVDRPServer::CmdSCAN(const char *Option)
2516 {
2518  Reply(250, "EPG scan triggered");
2519 }
2520 
2521 void cSVDRPServer::CmdSTAT(const char *Option)
2522 {
2523  if (*Option) {
2524  if (strcasecmp(Option, "DISK") == 0) {
2525  int FreeMB, UsedMB;
2526  int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB);
2527  Reply(250, "%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
2528  }
2529  else
2530  Reply(501, "Invalid Option \"%s\"", Option);
2531  }
2532  else
2533  Reply(501, "No option given");
2534 }
2535 
2536 void cSVDRPServer::CmdUPDT(const char *Option)
2537 {
2538  if (*Option) {
2539  cTimer *Timer = new cTimer;
2540  if (Timer->Parse(Option)) {
2542  if (cTimer *t = Timers->GetTimer(Timer)) {
2543  bool IsRecording = t->HasFlags(tfRecording);
2544  t->Parse(Option);
2545  delete Timer;
2546  Timer = t;
2547  if (IsRecording)
2548  Timer->SetFlags(tfRecording);
2549  else
2550  Timer->ClrFlags(tfRecording);
2551  isyslog("SVDRP %s < %s updated timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2552  }
2553  else {
2554  Timer->ClrFlags(tfRecording);
2555  Timers->Add(Timer);
2556  isyslog("SVDRP %s < %s added timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr());
2557  }
2558  Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
2559  return;
2560  }
2561  else
2562  Reply(501, "Error in timer settings");
2563  delete Timer;
2564  }
2565  else
2566  Reply(501, "Missing timer settings");
2567 }
2568 
2569 void cSVDRPServer::CmdUPDR(const char *Option)
2570 {
2572  Recordings->Update(false);
2573  Reply(250, "Re-read of recordings directory triggered");
2574 }
2575 
2576 void cSVDRPServer::CmdVOLU(const char *Option)
2577 {
2578  if (*Option) {
2579  if (isnumber(Option))
2580  cDevice::PrimaryDevice()->SetVolume(strtol(Option, NULL, 10), true);
2581  else if (strcmp(Option, "+") == 0)
2582  cDevice::PrimaryDevice()->SetVolume(VOLUMEDELTA);
2583  else if (strcmp(Option, "-") == 0)
2584  cDevice::PrimaryDevice()->SetVolume(-VOLUMEDELTA);
2585  else if (strcasecmp(Option, "MUTE") == 0)
2586  cDevice::PrimaryDevice()->ToggleMute();
2587  else {
2588  Reply(501, "Unknown option: \"%s\"", Option);
2589  return;
2590  }
2591  }
2592  if (cDevice::PrimaryDevice()->IsMute())
2593  Reply(250, "Audio is mute");
2594  else
2595  Reply(250, "Audio volume is %d", cDevice::CurrentVolume());
2596 }
2597 
2598 #define CMD(c) (strcasecmp(Cmd, c) == 0)
2599 
2600 void cSVDRPServer::Execute(char *Cmd)
2601 {
2602  // handle PUTE data:
2603  if (PUTEhandler) {
2604  if (!PUTEhandler->Process(Cmd)) {
2605  Reply(PUTEhandler->Status(), "%s", PUTEhandler->Message());
2607  }
2608  cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME); // re-trigger the timeout, in case there is very much EPG data
2609  return;
2610  }
2611  // skip leading whitespace:
2612  Cmd = skipspace(Cmd);
2613  // find the end of the command word:
2614  char *s = Cmd;
2615  while (*s && !isspace(*s))
2616  s++;
2617  if (*s)
2618  *s++ = 0;
2619  s = skipspace(s);
2620  if (CMD("CHAN")) CmdCHAN(s);
2621  else if (CMD("CLRE")) CmdCLRE(s);
2622  else if (CMD("CONN")) CmdCONN(s);
2623  else if (CMD("DELC")) CmdDELC(s);
2624  else if (CMD("DELR")) CmdDELR(s);
2625  else if (CMD("DELT")) CmdDELT(s);
2626  else if (CMD("EDIT")) CmdEDIT(s);
2627  else if (CMD("GRAB")) CmdGRAB(s);
2628  else if (CMD("HELP")) CmdHELP(s);
2629  else if (CMD("HITK")) CmdHITK(s);
2630  else if (CMD("LSTC")) CmdLSTC(s);
2631  else if (CMD("LSTD")) CmdLSTD(s);
2632  else if (CMD("LSTE")) CmdLSTE(s);
2633  else if (CMD("LSTR")) CmdLSTR(s);
2634  else if (CMD("LSTT")) CmdLSTT(s);
2635  else if (CMD("MESG")) CmdMESG(s);
2636  else if (CMD("MODC")) CmdMODC(s);
2637  else if (CMD("MODT")) CmdMODT(s);
2638  else if (CMD("MOVC")) CmdMOVC(s);
2639  else if (CMD("MOVR")) CmdMOVR(s);
2640  else if (CMD("NEWC")) CmdNEWC(s);
2641  else if (CMD("NEWT")) CmdNEWT(s);
2642  else if (CMD("NEXT")) CmdNEXT(s);
2643  else if (CMD("PING")) CmdPING(s);
2644  else if (CMD("PLAY")) CmdPLAY(s);
2645  else if (CMD("PLUG")) CmdPLUG(s);
2646  else if (CMD("POLL")) CmdPOLL(s);
2647  else if (CMD("PRIM")) CmdPRIM(s);
2648  else if (CMD("PUTE")) CmdPUTE(s);
2649  else if (CMD("REMO")) CmdREMO(s);
2650  else if (CMD("SCAN")) CmdSCAN(s);
2651  else if (CMD("STAT")) CmdSTAT(s);
2652  else if (CMD("UPDR")) CmdUPDR(s);
2653  else if (CMD("UPDT")) CmdUPDT(s);
2654  else if (CMD("VOLU")) CmdVOLU(s);
2655  else if (CMD("QUIT")) Close(true);
2656  else Reply(500, "Command unrecognized: \"%s\"", Cmd);
2657 }
2658 
2660 {
2661  if (file.IsOpen()) {
2662  while (file.Ready(false)) {
2663  unsigned char c;
2664  int r = safe_read(file, &c, 1);
2665  if (r > 0) {
2666  if (c == '\n' || c == 0x00) {
2667  // strip trailing whitespace:
2668  while (numChars > 0 && strchr(" \t\r\n", cmdLine[numChars - 1]))
2669  cmdLine[--numChars] = 0;
2670  // make sure the string is terminated:
2671  cmdLine[numChars] = 0;
2672  // showtime!
2673  dbgsvdrp("< S %s: %s\n", *clientName, cmdLine);
2674  Execute(cmdLine);
2675  numChars = 0;
2676  if (length > BUFSIZ) {
2677  free(cmdLine); // let's not tie up too much memory
2678  length = BUFSIZ;
2679  cmdLine = MALLOC(char, length);
2680  }
2681  }
2682  else if (c == 0x04 && numChars == 0) {
2683  // end of file (only at beginning of line)
2684  Close(true);
2685  }
2686  else if (c == 0x08 || c == 0x7F) {
2687  // backspace or delete (last character)
2688  if (numChars > 0)
2689  numChars--;
2690  }
2691  else if (c <= 0x03 || c == 0x0D) {
2692  // ignore control characters
2693  }
2694  else {
2695  if (numChars >= length - 1) {
2696  int NewLength = length + BUFSIZ;
2697  if (char *NewBuffer = (char *)realloc(cmdLine, NewLength)) {
2698  length = NewLength;
2699  cmdLine = NewBuffer;
2700  }
2701  else {
2702  esyslog("SVDRP %s < %s ERROR: out of memory", Setup.SVDRPHostName, *clientName);
2703  Close();
2704  break;
2705  }
2706  }
2707  cmdLine[numChars++] = c;
2708  cmdLine[numChars] = 0;
2709  }
2710  lastActivity = time(NULL);
2711  }
2712  else if (r <= 0) {
2713  isyslog("SVDRP %s < %s lost connection to client", Setup.SVDRPHostName, *clientName);
2714  Close();
2715  }
2716  }
2717  if (Setup.SVDRPTimeout && time(NULL) - lastActivity > Setup.SVDRPTimeout) {
2718  isyslog("SVDRP %s < %s timeout on connection", Setup.SVDRPHostName, *clientName);
2719  Close(true, true);
2720  }
2721  }
2722  return file.IsOpen();
2723 }
2724 
2725 void SetSVDRPPorts(int TcpPort, int UdpPort)
2726 {
2727  SVDRPTcpPort = TcpPort;
2728  SVDRPUdpPort = UdpPort;
2729 }
2730 
2731 void SetSVDRPGrabImageDir(const char *GrabImageDir)
2732 {
2733  grabImageDir = GrabImageDir;
2734 }
2735 
2736 // --- cSVDRPServerHandler ---------------------------------------------------
2737 
2739 private:
2740  bool ready;
2743  void HandleServerConnection(void);
2744  void ProcessConnections(void);
2745 protected:
2746  virtual void Action(void);
2747 public:
2748  cSVDRPServerHandler(int TcpPort);
2749  virtual ~cSVDRPServerHandler();
2750  void WaitUntilReady(void);
2751  };
2752 
2754 
2756 :cThread("SVDRP server handler", true)
2757 ,tcpSocket(TcpPort, true)
2758 {
2759  ready = false;
2760 }
2761 
2763 {
2764  Cancel(3);
2765  for (int i = 0; i < serverConnections.Size(); i++)
2766  delete serverConnections[i];
2767 }
2768 
2770 {
2771  cTimeMs Timeout(3000);
2772  while (!ready && !Timeout.TimedOut())
2773  cCondWait::SleepMs(10);
2774 }
2775 
2777 {
2778  for (int i = 0; i < serverConnections.Size(); i++) {
2779  if (!serverConnections[i]->Process()) {
2780  delete serverConnections[i];
2782  i--;
2783  }
2784  }
2785 }
2786 
2788 {
2789  int NewSocket = tcpSocket.Accept();
2790  if (NewSocket >= 0)
2792 }
2793 
2795 {
2796  if (tcpSocket.Listen()) {
2798  ready = true;
2799  while (Running()) {
2800  SVDRPServerPoller.Poll(1000);
2803  }
2805  tcpSocket.Close();
2806  }
2807 }
2808 
2809 // --- SVDRP Handler ---------------------------------------------------------
2810 
2812 
2814 {
2815  cMutexLock MutexLock(&SVDRPHandlerMutex);
2816  if (SVDRPTcpPort) {
2817  if (!SVDRPServerHandler) {
2821  }
2825  }
2826  }
2827 }
2828 
2830 {
2831  cMutexLock MutexLock(&SVDRPHandlerMutex);
2832  delete SVDRPClientHandler;
2833  SVDRPClientHandler = NULL;
2834  delete SVDRPServerHandler;
2835  SVDRPServerHandler = NULL;
2836 }
2837 
2839 {
2840  bool Result = false;
2841  cMutexLock MutexLock(&SVDRPHandlerMutex);
2842  if (SVDRPClientHandler) {
2844  Result = SVDRPClientHandler->GetServerNames(ServerNames);
2846  }
2847  return Result;
2848 }
2849 
2850 bool ExecSVDRPCommand(const char *ServerName, const char *Command, cStringList *Response)
2851 {
2852  bool Result = false;
2853  cMutexLock MutexLock(&SVDRPHandlerMutex);
2854  if (SVDRPClientHandler) {
2856  Result = SVDRPClientHandler->Execute(ServerName, Command, Response);
2858  }
2859  return Result;
2860 }
2861 
2862 void BroadcastSVDRPCommand(const char *Command)
2863 {
2864  cMutexLock MutexLock(&SVDRPHandlerMutex);
2865  cStringList ServerNames;
2866  if (SVDRPClientHandler) {
2868  if (SVDRPClientHandler->GetServerNames(&ServerNames)) {
2869  for (int i = 0; i < ServerNames.Size(); i++)
2870  ExecSVDRPCommand(ServerNames[i], Command);
2871  }
2873  }
2874 }
cSVDRPClient::ServerName
const char * ServerName(void) const
Definition: svdrp.c:333
cSVDRPServerHandler::HandleServerConnection
void HandleServerConnection(void)
Definition: svdrp.c:2787
cCharSetConv::SystemCharacterTable
static const char * SystemCharacterTable(void)
Definition: tools.h:172
cSocket::Socket
int Socket(void) const
Definition: svdrp.c:114
cSocket::Close
void Close(void)
Definition: svdrp.c:133
cSVDRPServer::clientIpAddress
cIpAddress clientIpAddress
Definition: svdrp.c:1069
cPoller::Poll
bool Poll(int TimeoutMs=0)
Definition: tools.c:1517
FOLDERDELIMCHAR
#define FOLDERDELIMCHAR
Definition: recording.h:21
cPoller
Definition: tools.h:393
cSVDRPServer::CmdVOLU
void CmdVOLU(const char *Option)
Definition: svdrp.c:2576
cSVDRPServer::Reply
void Reply(int Code, const char *fmt,...) __attribute__((format(printf
Definition: svdrp.c:1178
cSVDRPClient::Execute
bool Execute(const char *Command, cStringList *Response=NULL)
Definition: svdrp.c:481
cRecordControls::GetRecordControl
static cRecordControl * GetRecordControl(const char *FileName)
Definition: menu.c:5556
cString::sprintf
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition: tools.c:1127
cBase64Encoder
Definition: tools.h:323
cIpAddress::connection
cString connection
Definition: svdrp.c:63
AddDirectory
cString AddDirectory(const char *DirName, const char *FileName)
Definition: tools.c:384
cSVDRPClient::socket
cSocket socket
Definition: svdrp.c:319
cString::Truncate
cString & Truncate(int Index)
Truncate the string at the given Index (if Index is < 0 it is counted from the end of the string).
Definition: tools.c:1111
recording.h
cSocket::~cSocket
~cSocket()
Definition: svdrp.c:128
ruMove
@ ruMove
Definition: recording.h:34
cSVDRPServer::CmdCPYR
void CmdCPYR(const char *Option)
Definition: svdrp.c:1448
cChannel::ToText
static cString ToText(const cChannel *Channel)
Definition: channels.c:547
cSVDRPServerParams::Name
const char * Name(void) const
Definition: svdrp.c:538
cListObject::Index
int Index(void) const
Definition: tools.c:2072
cStringList
Definition: tools.h:801
remote.h
StopSVDRPHandler
void StopSVDRPHandler(void)
Definition: svdrp.c:2829
cSVDRPServer::CmdEDIT
void CmdEDIT(const char *Option)
Definition: svdrp.c:1555
VDRVERSNUM
#define VDRVERSNUM
Definition: config.h:26
cPUTEhandler
Definition: svdrp.c:764
DumpSVDRPDataTransfer
static bool DumpSVDRPDataTransfer
Definition: svdrp.c:43
tfRecording
@ tfRecording
Definition: timers.h:22
cStringList::Clear
virtual void Clear(void)
Definition: tools.c:1571
sffNone
@ sffNone
Definition: svdrp.c:51
kNone
@ kNone
Definition: keys.h:55
cPluginManager::GetPlugin
static cPlugin * GetPlugin(int Index)
Definition: plugin.c:457
cTimer
Definition: timers.h:27
RecordingsHandler
cRecordingsHandler RecordingsHandler
Definition: recording.c:1967
LOCK_CHANNELS_READ
#define LOCK_CHANNELS_READ
Definition: channels.h:267
cSVDRPServer::CmdMOVC
void CmdMOVC(const char *Option)
Definition: svdrp.c:2125
cSVDRPServer::cmdLine
char * cmdLine
Definition: svdrp.c:1075
cPlugin
Definition: plugin.h:20
keys.h
cRecordControls::Process
static bool Process(cTimers *Timers, time_t t)
Definition: menu.c:5576
cSVDRPServerHandler::Action
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:2794
cSVDRPServer::CmdMOVR
void CmdMOVR(const char *Option)
Definition: svdrp.c:2178
cFile::Ready
bool Ready(bool Wait=true)
Definition: tools.c:1669
cSVDRPServer::CmdSTAT
void CmdSTAT(const char *Option)
Definition: svdrp.c:2521
cChannel::Number
int Number(void) const
Definition: channels.h:179
cTimers::StoreRemoteTimers
bool StoreRemoteTimers(const char *ServerName=NULL, const cStringList *RemoteTimers=NULL)
Stores the given list of RemoteTimers, which come from the VDR ServerName, in this list.
Definition: timers.c:909
sffPing
@ sffPing
Definition: svdrp.c:53
menu.h
cSVDRPServer::socket
int socket
Definition: svdrp.c:1068
cSVDRPServer::CmdLSTC
void CmdLSTC(const char *Option)
Definition: svdrp.c:1785
cTimers::GetTimersRead
static const cTimers * GetTimersRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for read access.
Definition: timers.c:843
cSVDRPServer::lastActivity
time_t lastActivity
Definition: svdrp.c:1076
StateKeySVDRPRemoteTimersPoll
cStateKey StateKeySVDRPRemoteTimersPoll(true)
SVDRPhosts
cSVDRPhosts SVDRPhosts
Definition: config.c:280
EITScanner
cEITScanner EITScanner
Definition: eitscan.c:90
cSVDRPServer::CmdUPDR
void CmdUPDR(const char *Option)
Definition: svdrp.c:2569
SVDRPTcpPort
static int SVDRPTcpPort
Definition: svdrp.c:47
LOCK_SCHEDULES_READ
#define LOCK_SCHEDULES_READ
Definition: epg.h:224
cSVDRPServer::CmdCONN
void CmdCONN(const char *Option)
Definition: svdrp.c:1364
cSVDRPClientHandler::GetServerNames
bool GetServerNames(cStringList *ServerNames)
Definition: svdrp.c:740
strreplace
char * strreplace(char *s, char c1, char c2)
Definition: tools.c:139
cSVDRPServer::CmdPING
void CmdPING(const char *Option)
Definition: svdrp.c:2288
cSetup::SVDRPHostName
char SVDRPHostName[HOST_NAME_MAX]
Definition: config.h:296
cSocket
Definition: svdrp.c:101
cSVDRPServer::~cSVDRPServer
~cSVDRPServer()
Definition: svdrp.c:1146
VOLUMEDELTA
#define VOLUMEDELTA
Definition: device.h:33
APIVERSNUM
#define APIVERSNUM
Definition: config.h:31
cSVDRPServer::Close
void Close(bool SendReply=false, bool Timeout=false)
Definition: svdrp.c:1153
cIpAddress
Definition: svdrp.c:59
cTimer::SetFlags
void SetFlags(uint Flags)
Definition: timers.c:681
cSVDRPServer::CmdLSTE
void CmdLSTE(const char *Option)
Definition: svdrp.c:1845
cPlugin::Version
virtual const char * Version(void)=0
cSVDRPClient::serverIpAddress
cIpAddress serverIpAddress
Definition: svdrp.c:318
cSVDRPClient::file
cFile file
Definition: svdrp.c:325
cSVDRPServerParams::Timeout
const int Timeout(void) const
Definition: svdrp.c:542
cPUTEhandler::f
FILE * f
Definition: svdrp.c:766
cRecordingsHandler::Add
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:2019
eKeys
eKeys
Definition: keys.h:16
cListBase::Count
int Count(void) const
Definition: tools.h:590
plugin.h
cEITScanner::ForceScan
void ForceScan(void)
Definition: eitscan.c:113
SVDRPClientPoller
static cPoller SVDRPClientPoller
Definition: svdrp.c:344
strgetval
cString strgetval(const char *s, const char *name, char d)
Returns the value part of a 'name=value' pair in s.
Definition: tools.c:277
cFile
Definition: tools.h:422
sffConn
@ sffConn
Definition: svdrp.c:52
cPlugin::Description
virtual const char * Description(void)=0
cPlugin::Name
const char * Name(void)
Definition: plugin.h:34
cTimer::Id
int Id(void) const
Definition: timers.h:54
cSVDRPServerParams::Host
const char * Host(void) const
Definition: svdrp.c:543
ruCut
@ ruCut
Definition: recording.h:33
cSVDRPClientHandler::Lock
void Lock(void)
Definition: svdrp.c:608
cSVDRPServerParams::ApiVersion
const char * ApiVersion(void) const
Definition: svdrp.c:541
cRemote::Put
bool Put(uint64_t Code, bool Repeat=false, bool Release=false)
Definition: remote.c:124
cSVDRPServerParams::cSVDRPServerParams
cSVDRPServerParams(const char *Params)
Definition: svdrp.c:548
GetHelpTopic
const char * GetHelpTopic(const char *HelpPage)
Definition: svdrp.c:1033
cRecording::Title
const char * Title(char Delimiter=' ', bool NewIndicator=false, int Level=-1) const
Definition: recording.c:1076
cPUTEhandler::cPUTEhandler
cPUTEhandler(void)
Definition: svdrp.c:777
cSVDRPServerHandler::serverConnections
cVector< cSVDRPServer * > serverConnections
Definition: svdrp.c:2742
cPlugin::SVDRPHelpPages
virtual const char ** SVDRPHelpPages(void)
Definition: plugin.c:125
ruCopy
@ ruCopy
Definition: recording.h:35
cRecording::FileName
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
Definition: recording.c:1058
cThread::Cancel
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
cSVDRPClientHandler
Definition: svdrp.c:593
cMutexLock
Definition: thread.h:141
cTimer::ToText
cString ToText(bool UseChannelID=false) const
Definition: timers.c:184
GetSVDRPServerNames
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:2838
tChannelID
Definition: channels.h:49
LOCK_RECORDINGS_READ
#define LOCK_RECORDINGS_READ
Definition: recording.h:306
cDevice::CurrentVolume
static int CurrentVolume(void)
Definition: device.h:622
Setup
cSetup Setup
Definition: config.c:372
cSVDRPServer::Send
bool Send(const char *s)
Definition: svdrp.c:1167
device.h
cSVDRPClient::~cSVDRPClient
~cSVDRPClient()
Definition: svdrp.c:367
svdrp.h
cSchedules::Cleanup
static void Cleanup(bool Force=false)
Definition: epg.c:1248
DELETENULL
void DELETENULL(T *&p)
Definition: tools.h:49
cSkins::QueueMessage
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
cSVDRPServer::CmdUPDT
void CmdUPDT(const char *Option)
Definition: svdrp.c:2536
cSVDRPServerHandler::~cSVDRPServerHandler
virtual ~cSVDRPServerHandler()
Definition: svdrp.c:2762
cSVDRPClientHandler::tcpPort
int tcpPort
Definition: svdrp.c:596
cFile::IsOpen
bool IsOpen(void)
Definition: tools.h:434
HelpPages
const char * HelpPages[]
Definition: svdrp.c:828
cListObject::Prev
cListObject * Prev(void) const
Definition: tools.h:509
cVector< cSVDRPClient * >
cSocket::Port
int Port(void) const
Definition: svdrp.c:113
cStateKey
Definition: thread.h:233
cTimeMs::Set
void Set(int Ms=0)
Definition: tools.c:774
cDevice::SetCurrentChannel
static void SetCurrentChannel(const cChannel *Channel)
Definition: device.h:356
SVDRPServerHandler
static cSVDRPServerHandler * SVDRPServerHandler
Definition: svdrp.c:2753
cSVDRPClient::input
char * input
Definition: svdrp.c:322
spmOnly
@ spmOnly
Definition: svdrp.h:19
cMarks
Definition: recording.h:371
cDevice::GetDevice
static cDevice * GetDevice(int Index)
Gets the device with the given Index.
Definition: device.c:223
cTimer::ClrFlags
void ClrFlags(uint Flags)
Definition: timers.c:686
cSVDRPClient
Definition: svdrp.c:316
cSVDRPServer::CmdSCAN
void CmdSCAN(const char *Option)
Definition: svdrp.c:2515
cSVDRPClientHandler::mutex
cMutex mutex
Definition: svdrp.c:595
cIpAddress::port
int port
Definition: svdrp.c:62
cRemote::Enabled
static bool Enabled(void)
Definition: remote.h:49
cSVDRPServer::CmdMESG
void CmdMESG(const char *Option)
Definition: svdrp.c:2039
cSVDRPServer::CmdMODC
void CmdMODC(const char *Option)
Definition: svdrp.c:2050
cSVDRPServerParams::Error
const char * Error(void) const
Definition: svdrp.c:545
ruTimer
@ ruTimer
Definition: recording.h:30
cSVDRPServer::CmdHELP
void CmdHELP(const char *Option)
Definition: svdrp.c:1718
isnumber
bool isnumber(const char *s)
Definition: tools.c:346
cSVDRPServerParams::Port
const int Port(void) const
Definition: svdrp.c:539
cSocket::Discover
cString Discover(void)
Definition: svdrp.c:284
cSVDRPServer::Process
bool Process(void)
Definition: svdrp.c:2659
cRecordControl
Definition: menu.h:236
cIpAddress::Address
const char * Address(void) const
Definition: svdrp.c:67
cMutex
Definition: thread.h:67
cSVDRPhosts::LocalhostOnly
bool LocalhostOnly(void)
Definition: config.c:282
cSVDRPServerParams::error
cString error
Definition: svdrp.c:535
cRecordControl::Timer
cTimer * Timer(void)
Definition: menu.h:253
cSVDRPServer::CmdCLRE
void CmdCLRE(const char *Option)
Definition: svdrp.c:1301
channels.h
cSVDRPServer::CmdPRIM
void CmdPRIM(const char *Option)
Definition: svdrp.c:2446
cSVDRPClient::Process
bool Process(cStringList *Response=NULL)
Definition: svdrp.c:399
dmFollowing
@ dmFollowing
Definition: epg.h:40
BroadcastSVDRPCommand
void BroadcastSVDRPCommand(const char *Command)
Sends the given SVDRP Command string to all remote VDRs.
Definition: svdrp.c:2862
cChannels::MaxNumber
static int MaxNumber(void)
Definition: channels.h:246
SVDRPClientHandler
static cSVDRPClientHandler * SVDRPClientHandler
Definition: svdrp.c:616
cSocket::sock
int sock
Definition: svdrp.c:105
cSVDRPClientHandler::TriggerFetchingTimers
bool TriggerFetchingTimers(const char *ServerName)
Definition: svdrp.c:752
cSVDRPClientHandler::HandleClientConnection
void HandleClientConnection(void)
Definition: svdrp.c:704
cSVDRPServer::Execute
void Execute(char *Cmd)
Definition: svdrp.c:2600
cPUTEhandler::status
int status
Definition: svdrp.c:767
cSchedule::Dump
void Dump(const cChannels *Channels, FILE *f, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0) const
Definition: epg.c:1107
cControl::Launch
static void Launch(cControl *Control)
Definition: player.c:79
eDumpMode
eDumpMode
Definition: epg.h:40
cSocket::Accept
int Accept(void)
Definition: svdrp.c:258
cSetup::PrimaryDVB
int PrimaryDVB
Definition: config.h:261
EITDISABLETIME
#define EITDISABLETIME
Definition: svdrp.c:825
cSVDRPServer::CmdNEXT
void CmdNEXT(const char *Option)
Definition: svdrp.c:2269
cDevice
Definition: device.h:117
uchar
unsigned char uchar
Definition: tools.h:31
cSVDRPClient::serverName
cString serverName
Definition: svdrp.c:320
cSVDRPClientHandler::ProcessConnections
void ProcessConnections(void)
Definition: svdrp.c:647
cIpAddress::address
cString address
Definition: svdrp.c:61
cSVDRPClientHandler::SendDiscover
void SendDiscover(void)
Definition: svdrp.c:641
cSVDRPClient::Connected
bool Connected(void) const
Definition: svdrp.c:338
LOCK_TIMERS_READ
#define LOCK_TIMERS_READ
Definition: timers.h:223
cSetup::SVDRPPeering
int SVDRPPeering
Definition: config.h:295
cVector::Remove
virtual void Remove(int Index)
Definition: tools.h:751
SetSVDRPPorts
void SetSVDRPPorts(int TcpPort, int UdpPort)
Definition: svdrp.c:2725
videodir.h
cVideoDirectory::VideoDiskSpace
static int VideoDiskSpace(int *FreeMB=NULL, int *UsedMB=NULL)
Definition: videodir.c:147
sffTimers
@ sffTimers
Definition: svdrp.c:54
cFile::Open
bool Open(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
Definition: tools.c:1627
cSchedules::Read
static bool Read(FILE *f=NULL)
Definition: epg.c:1293
cMutex::Unlock
void Unlock(void)
Definition: thread.c:228
tChannelID::ToString
cString ToString(void) const
Definition: channels.c:41
cPlugin::SVDRPCommand
virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
Definition: plugin.c:130
cTimeMs
Definition: tools.h:369
cSVDRPServer::CmdPOLL
void CmdPOLL(const char *Option)
Definition: svdrp.c:2416
cStateKey::Remove
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
Definition: thread.c:859
cSVDRPServer::CmdCHAN
void CmdCHAN(const char *Option)
Definition: svdrp.c:1239
cPUTEhandler::Message
const char * Message(void)
Definition: svdrp.c:774
cSVDRPServer::CmdHITK
void CmdHITK(const char *Option)
Definition: svdrp.c:1746
VDRVERSION
#define VDRVERSION
Definition: config.h:25
cResumeFile::Save
bool Save(int Index)
Definition: recording.c:307
SetSVDRPGrabImageDir
void SetSVDRPGrabImageDir(const char *GrabImageDir)
Definition: svdrp.c:2731
GetHelpPage
const char * GetHelpPage(const char *Cmd, const char **p)
Definition: svdrp.c:1051
cRecording
Definition: recording.h:98
cSVDRPServerParams::host
cString host
Definition: svdrp.c:534
SVDRPUdpPort
static int SVDRPUdpPort
Definition: svdrp.c:48
cSVDRPClientHandler::~cSVDRPClientHandler
virtual ~cSVDRPClientHandler()
Definition: svdrp.c:625
cChannel::Parse
bool Parse(const char *s)
Definition: channels.c:609
LOCK_SCHEDULES_WRITE
#define LOCK_SCHEDULES_WRITE
Definition: epg.h:225
cSVDRPServer::CmdLSTD
void CmdLSTD(const char *Option)
Definition: svdrp.c:1833
cTimer::Remote
const char * Remote(void) const
Definition: timers.h:69
cVector::Size
int Size(void) const
Definition: tools.h:717
cMutex::Lock
void Lock(void)
Definition: thread.c:222
cString::CompactChars
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:1121
cSocket::LastIpAddress
const cIpAddress * LastIpAddress(void) const
Definition: svdrp.c:118
cSVDRPServerParams::timeout
int timeout
Definition: svdrp.c:533
cSocket::cSocket
cSocket(int Port, bool Tcp)
Definition: svdrp.c:121
cSVDRPClient::Send
bool Send(const char *Command)
Definition: svdrp.c:388
cSVDRPServerHandler::cSVDRPServerHandler
cSVDRPServerHandler(int TcpPort)
Definition: svdrp.c:2755
cIpAddress::Connection
const char * Connection(void) const
Definition: svdrp.c:71
cSVDRPServer::CmdLSTR
void CmdLSTR(const char *Option)
Definition: svdrp.c:1924
cSVDRPClient::HasFetchFlag
bool HasFetchFlag(int Flag)
Definition: svdrp.c:496
startswith
bool startswith(const char *s, const char *p)
Definition: tools.c:311
cSVDRPClientHandler::Execute
bool Execute(const char *ServerName, const char *Command, cStringList *Response=NULL)
Definition: svdrp.c:732
cSVDRPServer::PrintHelpTopics
void void PrintHelpTopics(const char **hp)
Definition: svdrp.c:1213
cKey::ToString
static const char * ToString(eKeys Key, bool Translate=false)
Definition: keys.c:138
cSVDRPServer::cSVDRPServer
cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress)
Definition: svdrp.c:1128
cSVDRPServer::file
cFile file
Definition: svdrp.c:1071
cSVDRPClient::connected
bool connected
Definition: svdrp.c:327
cSVDRPServer::clientName
cString clientName
Definition: svdrp.c:1070
CMD
#define CMD(c)
Definition: svdrp.c:2598
cSVDRPClientHandler::udpSocket
cSocket udpSocket
Definition: svdrp.c:597
cSVDRPServer::length
int length
Definition: svdrp.c:1074
cSVDRPServer::PUTEhandler
cPUTEhandler * PUTEhandler
Definition: svdrp.c:1072
dsyslog
#define dsyslog(a...)
Definition: tools.h:37
cBase64Encoder::NextLine
const char * NextLine(void)
Returns the next line of encoded data (terminated by '\0'), or NULL if there is no more encoded data.
Definition: tools.c:1371
cSetup::SVDRPDefaultHost
char SVDRPDefaultHost[HOST_NAME_MAX]
Definition: config.h:297
cChannel
Definition: channels.h:89
cSVDRPServer::CmdDELC
void CmdDELC(const char *Option)
Definition: svdrp.c:1384
cSVDRPServer::CmdGRAB
void CmdGRAB(const char *Option)
Definition: svdrp.c:1581
cEitFilter::SetDisableUntil
static void SetDisableUntil(time_t Time)
Definition: eit.c:432
StartSVDRPHandler
void StartSVDRPHandler(void)
Definition: svdrp.c:2813
cSVDRPServerHandler::WaitUntilReady
void WaitUntilReady(void)
Definition: svdrp.c:2769
MALLOC
#define MALLOC(type, size)
Definition: tools.h:47
TimeToString
cString TimeToString(time_t t)
Converts the given time to a string of the form "www mmm dd hh:mm:ss yyyy".
Definition: tools.c:1203
cSVDRPClient::GetRemoteTimers
bool GetRemoteTimers(cStringList &Response)
Definition: svdrp.c:503
tChannelID::InvalidID
static const tChannelID InvalidID
Definition: channels.h:70
cString
Definition: tools.h:176
SVDRPHandlerMutex
static cMutex SVDRPHandlerMutex
Definition: svdrp.c:2811
LOG_ERROR
#define LOG_ERROR
Definition: tools.h:39
cIpAddress::Set
void Set(const char *Address, int Port)
Definition: svdrp.c:84
cSocket::lastIpAddress
cIpAddress lastIpAddress
Definition: svdrp.c:106
ExchangeChars
char * ExchangeChars(char *s, bool ToFileSystem)
Definition: recording.c:590
cTimer::ToDescr
cString ToDescr(void) const
Definition: timers.c:192
cRemote::CallPlugin
static bool CallPlugin(const char *Plugin)
Initiates calling the given plugin's main menu function.
Definition: remote.c:151
cSVDRPServer::CmdPUTE
void CmdPUTE(const char *Option)
Definition: svdrp.c:2472
cSVDRPClientHandler::AddClient
void AddClient(cSVDRPServerParams &ServerParams, const char *IpAddress)
Definition: svdrp.c:690
skipspace
char * skipspace(const char *s)
Definition: tools.h:209
cSVDRPServerParams::VdrVersion
const char * VdrVersion(void) const
Definition: svdrp.c:540
cReplayControl::SetRecording
static void SetRecording(const char *FileName)
Definition: menu.c:5760
cIpAddress::cIpAddress
cIpAddress(void)
Definition: svdrp.c:74
cSVDRPServerParams::Ok
bool Ok(void) const
Definition: svdrp.c:544
dbgsvdrp
#define dbgsvdrp(a...)
Definition: svdrp.c:45
cSVDRPClientHandler::clientConnections
cVector< cSVDRPClient * > clientConnections
Definition: svdrp.c:598
cSVDRPServerHandler::ProcessConnections
void ProcessConnections(void)
Definition: svdrp.c:2776
cSocket::SendDgram
static bool SendDgram(const char *Dgram, int Port)
Definition: svdrp.c:226
cSVDRPClient::Connection
const char * Connection(void) const
Definition: svdrp.c:334
timers.h
cSVDRPServer::CmdDELR
void CmdDELR(const char *Option)
Definition: svdrp.c:1499
cPUTEhandler::message
const char * message
Definition: svdrp.c:768
SVDRPResonseTimeout
#define SVDRPResonseTimeout
cMarks::Load
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
Definition: recording.c:2142
cListObject::Next
cListObject * Next(void) const
Definition: tools.h:510
cThread
Definition: thread.h:79
ExecSVDRPCommand
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:2850
cCondWait::SleepMs
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
Skins
cSkins Skins
Definition: skins.c:219
cSVDRPClient::length
int length
Definition: svdrp.c:321
cSocket::tcp
bool tcp
Definition: svdrp.c:104
cReplayControl
Definition: menu.h:292
cSVDRPServer::CmdDELT
void CmdDELT(const char *Option)
Definition: svdrp.c:1529
cFile::Close
void Close(void)
Definition: tools.c:1660
skins.h
cSVDRPClient::pingTime
cTimeMs pingTime
Definition: svdrp.c:324
eitscan.h
cIpAddress::Port
int Port(void) const
Definition: svdrp.c:68
cTimers
Definition: timers.h:116
cTimeMs::TimedOut
bool TimedOut(void) const
Definition: tools.c:779
cSVDRPServer::HasConnection
bool HasConnection(void)
Definition: svdrp.c:1122
cDevice::SwitchChannel
bool SwitchChannel(const cChannel *Channel, bool LiveView)
Switches the device to the given Channel, initiating transfer mode if necessary.
Definition: device.c:785
cChannel::GetChannelID
tChannelID GetChannelID(void) const
Definition: channels.h:190
dmAtTime
@ dmAtTime
Definition: epg.h:40
cSVDRPClient::Close
void Close(void)
Definition: svdrp.c:374
cTimers::GetTimersWrite
static cTimers * GetTimersWrite(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for write access.
Definition: timers.c:848
strshift
char * strshift(char *s, int n)
Shifts the given string to the left by the given number of bytes, thus removing the first n bytes fro...
Definition: tools.c:299
safe_read
ssize_t safe_read(int filedes, void *buffer, size_t size)
Definition: tools.c:53
MAXUDPBUF
#define MAXUDPBUF
Definition: svdrp.c:99
cTimer::Parse
bool Parse(const char *s)
Definition: timers.c:305
cSVDRPClient::timeout
int timeout
Definition: svdrp.c:323
mtInfo
@ mtInfo
Definition: skins.h:37
tfActive
@ tfActive
Definition: timers.h:19
cPoller::Del
void Del(int FileHandle, bool Out)
Definition: tools.c:1504
cSVDRPClientHandler::Action
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
grabImageDir
static cString grabImageDir
Definition: svdrp.c:1064
cStringList::SortNumerically
void SortNumerically(void)
Definition: tools.h:813
cResumeFile
Definition: recording.h:51
cRemote::SetEnabled
static void SetEnabled(bool Enabled)
Definition: remote.h:50
cSocket::port
int port
Definition: svdrp.c:103
config.h
cSchedule::Cleanup
void Cleanup(time_t Time)
Definition: epg.c:1096
cSVDRPClient::HasAddress
bool HasAddress(const char *Address, int Port) const
Definition: svdrp.c:383
cSVDRPClientHandler::cSVDRPClientHandler
cSVDRPClientHandler(int TcpPort, int UdpPort)
Definition: svdrp.c:618
cSVDRPServer::CmdNEWT
void CmdNEWT(const char *Option)
Definition: svdrp.c:2249
cSVDRPServer::CmdPLAY
void CmdPLAY(const char *Option)
Definition: svdrp.c:2293
cSocket::Connect
bool Connect(const char *Address)
Definition: svdrp.c:188
cRecordings
Definition: recording.h:225
cSVDRPServer::CmdLSTT
void CmdLSTT(const char *Option)
Definition: svdrp.c:1983
cSVDRPServer::ClientName
const char * ClientName(void) const
Definition: svdrp.c:1121
cThread::Start
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
cSVDRPClient::cSVDRPClient
cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout)
Definition: svdrp.c:346
cVector::Append
virtual void Append(T Data)
Definition: tools.h:737
cSVDRPServer::numChars
int numChars
Definition: svdrp.c:1073
cSVDRPClientHandler::GetClientForServer
cSVDRPClient * GetClientForServer(const char *ServerName)
Definition: svdrp.c:632
cSVDRPServerParams::port
int port
Definition: svdrp.c:530
HMSFToIndex
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
Definition: recording.c:3063
cSVDRPhosts::Acceptable
bool Acceptable(in_addr_t Address)
Definition: config.c:293
cPoller::Add
bool Add(int FileHandle, bool Out)
Definition: tools.c:1485
safe_write
ssize_t safe_write(int filedes, const void *buffer, size_t size)
Definition: tools.c:65
cSVDRPClientHandler::Unlock
void Unlock(void)
Definition: svdrp.c:609
cSVDRPServerHandler::tcpSocket
cSocket tcpSocket
Definition: svdrp.c:2741
cSVDRPServer::CmdPLUG
void CmdPLUG(const char *Option)
Definition: svdrp.c:2345
cPUTEhandler::Status
int Status(void)
Definition: svdrp.c:773
cSVDRPServer::CmdREMO
void CmdREMO(const char *Option)
Definition: svdrp.c:2497
isyslog
#define isyslog(a...)
Definition: tools.h:36
cSchedule
Definition: epg.h:150
MakeDirs
bool MakeDirs(const char *FileName, bool IsDirectory)
Definition: tools.c:481
cSocket::Listen
bool Listen(void)
Definition: svdrp.c:141
LOCK_CHANNELS_WRITE
#define LOCK_CHANNELS_WRITE
Definition: channels.h:268
cSVDRPServer::CmdNEWC
void CmdNEWC(const char *Option)
Definition: svdrp.c:2222
cSVDRPServer::CmdMODT
void CmdMODT(const char *Option)
Definition: svdrp.c:2086
dmAll
@ dmAll
Definition: epg.h:40
cSVDRPServerParams::apiversion
cString apiversion
Definition: svdrp.c:532
RecordingInUseMessage
static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
Definition: svdrp.c:1432
cSVDRPClient::SetFetchFlag
void SetFetchFlag(int Flag)
Definition: svdrp.c:491
FATALERRNO
#define FATALERRNO
Definition: tools.h:52
cSVDRPServerParams::vdrversion
cString vdrversion
Definition: svdrp.c:531
cSVDRPServer
Definition: svdrp.c:1066
tChannelID::FromString
static tChannelID FromString(const char *s)
Definition: channels.c:24
cSVDRPServerHandler::ready
bool ready
Definition: svdrp.c:2740
cResumeFile::Delete
void Delete(void)
Definition: recording.c:337
cPUTEhandler::~cPUTEhandler
~cPUTEhandler()
Definition: svdrp.c:790
cSVDRPClient::fetchFlags
int fetchFlags
Definition: svdrp.c:326
esyslog
#define esyslog(a...)
Definition: tools.h:35
cSetup::SVDRPTimeout
int SVDRPTimeout
Definition: config.h:294
cKey::FromString
static eKeys FromString(const char *Name)
Definition: keys.c:123
eSvdrpFetchFlags
eSvdrpFetchFlags
Definition: svdrp.c:50
tChannelID::ClrRid
tChannelID & ClrRid(void)
Definition: channels.h:61
cSVDRPServerParams::name
cString name
Definition: svdrp.c:529
cSVDRPServerHandler
Definition: svdrp.c:2738
cControl::Attach
static void Attach(void)
Definition: player.c:87
dmPresent
@ dmPresent
Definition: epg.h:40
LOG_ERROR_STR
#define LOG_ERROR_STR(s)
Definition: tools.h:40
LOCK_RECORDINGS_WRITE
#define LOCK_RECORDINGS_WRITE
Definition: recording.h:307
cControl::Shutdown
static void Shutdown(void)
Definition: player.c:100
cRecording::Id
int Id(void) const
Definition: recording.h:130
cPUTEhandler::Process
bool Process(const char *s)
Definition: svdrp.c:796
ruReplay
@ ruReplay
Definition: recording.h:31
MAXHELPTOPIC
#define MAXHELPTOPIC
Definition: svdrp.c:824
__attribute__
struct __attribute__((packed))
Definition: recording.c:2501
cSVDRPServerParams
Definition: svdrp.c:527
SVDRPServerPoller
static cPoller SVDRPServerPoller
Definition: svdrp.c:1126
LOCK_TIMERS_WRITE
#define LOCK_TIMERS_WRITE
Definition: timers.h:224