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