vdr  2.2.0
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 3.6 2015/01/12 11:16:27 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 <netinet/in.h>
22 #include <stdarg.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <sys/socket.h>
27 #include <sys/time.h>
28 #include <unistd.h>
29 #include "channels.h"
30 #include "config.h"
31 #include "device.h"
32 #include "eitscan.h"
33 #include "keys.h"
34 #include "menu.h"
35 #include "plugin.h"
36 #include "remote.h"
37 #include "skins.h"
38 #include "timers.h"
39 #include "tools.h"
40 #include "videodir.h"
41 
42 // --- cSocket ---------------------------------------------------------------
43 
44 cSocket::cSocket(int Port, int Queue)
45 {
46  port = Port;
47  sock = -1;
48  queue = Queue;
49 }
50 
52 {
53  Close();
54 }
55 
56 void cSocket::Close(void)
57 {
58  if (sock >= 0) {
59  close(sock);
60  sock = -1;
61  }
62 }
63 
64 bool cSocket::Open(void)
65 {
66  if (sock < 0) {
67  // create socket:
68  sock = socket(PF_INET, SOCK_STREAM, 0);
69  if (sock < 0) {
70  LOG_ERROR;
71  port = 0;
72  return false;
73  }
74  // allow it to always reuse the same port:
75  int ReUseAddr = 1;
76  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr, sizeof(ReUseAddr));
77  //
78  struct sockaddr_in name;
79  name.sin_family = AF_INET;
80  name.sin_port = htons(port);
81  name.sin_addr.s_addr = SVDRPhosts.LocalhostOnly() ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY);
82  if (bind(sock, (struct sockaddr *)&name, sizeof(name)) < 0) {
83  LOG_ERROR;
84  Close();
85  return false;
86  }
87  // make it non-blocking:
88  int oldflags = fcntl(sock, F_GETFL, 0);
89  if (oldflags < 0) {
90  LOG_ERROR;
91  return false;
92  }
93  oldflags |= O_NONBLOCK;
94  if (fcntl(sock, F_SETFL, oldflags) < 0) {
95  LOG_ERROR;
96  return false;
97  }
98  // listen to the socket:
99  if (listen(sock, queue) < 0) {
100  LOG_ERROR;
101  return false;
102  }
103  }
104  return true;
105 }
106 
108 {
109  if (Open()) {
110  struct sockaddr_in clientname;
111  uint size = sizeof(clientname);
112  int newsock = accept(sock, (struct sockaddr *)&clientname, &size);
113  if (newsock > 0) {
114  bool accepted = SVDRPhosts.Acceptable(clientname.sin_addr.s_addr);
115  if (!accepted) {
116  const char *s = "Access denied!\n";
117  if (write(newsock, s, strlen(s)) < 0)
118  LOG_ERROR;
119  close(newsock);
120  newsock = -1;
121  }
122  isyslog("connect from %s, port %hu - %s", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port), accepted ? "accepted" : "DENIED");
123  }
124  else if (errno != EINTR && errno != EAGAIN)
125  LOG_ERROR;
126  return newsock;
127  }
128  return -1;
129 }
130 
131 // --- cPUTEhandler ----------------------------------------------------------
132 
134 {
135  if ((f = tmpfile()) != NULL) {
136  status = 354;
137  message = "Enter EPG data, end with \".\" on a line by itself";
138  }
139  else {
140  LOG_ERROR;
141  status = 554;
142  message = "Error while opening temporary file";
143  }
144 }
145 
147 {
148  if (f)
149  fclose(f);
150 }
151 
152 bool cPUTEhandler::Process(const char *s)
153 {
154  if (f) {
155  if (strcmp(s, ".") != 0) {
156  fputs(s, f);
157  fputc('\n', f);
158  return true;
159  }
160  else {
161  rewind(f);
162  if (cSchedules::Read(f)) {
163  cSchedules::Cleanup(true);
164  status = 250;
165  message = "EPG data processed";
166  }
167  else {
168  status = 451;
169  message = "Error while processing EPG data";
170  }
171  fclose(f);
172  f = NULL;
173  }
174  }
175  return false;
176 }
177 
178 // --- cSVDRP ----------------------------------------------------------------
179 
180 #define MAXHELPTOPIC 10
181 #define EITDISABLETIME 10 // seconds until EIT processing is enabled again after a CLRE command
182  // adjust the help for CLRE accordingly if changing this!
183 
184 const char *HelpPages[] = {
185  "CHAN [ + | - | <number> | <name> | <id> ]\n"
186  " Switch channel up, down or to the given channel number, name or id.\n"
187  " Without option (or after successfully switching to the channel)\n"
188  " it returns the current channel number and name.",
189  "CLRE [ <number> | <name> | <id> ]\n"
190  " Clear the EPG list of the given channel number, name or id.\n"
191  " Without option it clears the entire EPG list.\n"
192  " After a CLRE command, no further EPG processing is done for 10\n"
193  " seconds, so that data sent with subsequent PUTE commands doesn't\n"
194  " interfere with data from the broadcasters.",
195  "CPYR <number> <new name>\n"
196  " Copy the recording with the given number. Before a recording can be\n"
197  " copied, an LSTR command must have been executed in order to retrieve\n"
198  " the recording numbers.\n"
199  "DELC <number>\n"
200  " Delete channel.",
201  "DELR <number>\n"
202  " Delete the recording with the given number. Before a recording can be\n"
203  " deleted, an LSTR command must have been executed in order to retrieve\n"
204  " the recording numbers. The numbers don't change during subsequent DELR\n"
205  " commands. CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n"
206  " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
207  "DELT <number>\n"
208  " Delete timer.",
209  "EDIT <number>\n"
210  " Edit the recording with the given number. Before a recording can be\n"
211  " edited, an LSTR command must have been executed in order to retrieve\n"
212  " the recording numbers.",
213  "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n"
214  " Grab the current frame and save it to the given file. Images can\n"
215  " be stored as JPEG or PNM, depending on the given file name extension.\n"
216  " The quality of the grabbed image can be in the range 0..100, where 100\n"
217  " (the default) means \"best\" (only applies to JPEG). The size parameters\n"
218  " define the size of the resulting image (default is full screen).\n"
219  " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n"
220  " data will be sent to the SVDRP connection encoded in base64. The same\n"
221  " happens if '-' (a minus sign) is given as file name, in which case the\n"
222  " image format defaults to JPEG.",
223  "HELP [ <topic> ]\n"
224  " The HELP command gives help info.",
225  "HITK [ <key> ... ]\n"
226  " Hit the given remote control key. Without option a list of all\n"
227  " valid key names is given. If more than one key is given, they are\n"
228  " entered into the remote control queue in the given sequence. There\n"
229  " can be up to 31 keys.",
230  "LSTC [ :groups | <number> | <name> | <id> ]\n"
231  " List channels. Without option, all channels are listed. Otherwise\n"
232  " only the given channel is listed. If a name is given, all channels\n"
233  " containing the given string as part of their name are listed.\n"
234  " If ':groups' is given, all channels are listed including group\n"
235  " separators. The channel number of a group separator is always 0.",
236  "LSTE [ <channel> ] [ now | next | at <time> ]\n"
237  " List EPG data. Without any parameters all data of all channels is\n"
238  " listed. If a channel is given (either by number or by channel ID),\n"
239  " only data for that channel is listed. 'now', 'next', or 'at <time>'\n"
240  " restricts the returned data to present events, following events, or\n"
241  " events at the given time (which must be in time_t form).",
242  "LSTR [ <number> [ path ] ]\n"
243  " List recordings. Without option, all recordings are listed. Otherwise\n"
244  " the information for the given recording is listed. If a recording\n"
245  " number and the keyword 'path' is given, the actual file name of that\n"
246  " recording's directory is listed.",
247  "LSTT [ <number> ] [ id ]\n"
248  " List timers. Without option, all timers are listed. Otherwise\n"
249  " only the given timer is listed. If the keyword 'id' is given, the\n"
250  " channels will be listed with their unique channel ids instead of\n"
251  " their numbers.",
252  "MESG <message>\n"
253  " Displays the given message on the OSD. The message will be queued\n"
254  " and displayed whenever this is suitable.\n",
255  "MODC <number> <settings>\n"
256  " Modify a channel. Settings must be in the same format as returned\n"
257  " by the LSTC command.",
258  "MODT <number> on | off | <settings>\n"
259  " Modify a timer. Settings must be in the same format as returned\n"
260  " by the LSTT command. The special keywords 'on' and 'off' can be\n"
261  " used to easily activate or deactivate a timer.",
262  "MOVC <number> <to>\n"
263  " Move a channel to a new position.",
264  "MOVR <number> <new name>\n"
265  " Move the recording with the given number. Before a recording can be\n"
266  " moved, an LSTR command must have been executed in order to retrieve\n"
267  " the recording numbers. The numbers don't change during subsequent MOVR\n"
268  " commands.\n",
269  "NEWC <settings>\n"
270  " Create a new channel. Settings must be in the same format as returned\n"
271  " by the LSTC command.",
272  "NEWT <settings>\n"
273  " Create a new timer. Settings must be in the same format as returned\n"
274  " by the LSTT command.",
275  "NEXT [ abs | rel ]\n"
276  " Show the next timer event. If no option is given, the output will be\n"
277  " in human readable form. With option 'abs' the absolute time of the next\n"
278  " event will be given as the number of seconds since the epoch (time_t\n"
279  " format), while with option 'rel' the relative time will be given as the\n"
280  " number of seconds from now until the event. If the absolute time given\n"
281  " is smaller than the current time, or if the relative time is less than\n"
282  " zero, this means that the timer is currently recording and has started\n"
283  " at the given time. The first value in the resulting line is the number\n"
284  " of the timer.",
285  "PLAY <number> [ begin | <position> ]\n"
286  " Play the recording with the given number. Before a recording can be\n"
287  " played, an LSTR command must have been executed in order to retrieve\n"
288  " the recording numbers.\n"
289  " The keyword 'begin' plays the recording from its very beginning, while\n"
290  " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n"
291  " position. If neither 'begin' nor a <position> are given, replay is resumed\n"
292  " at the position where any previous replay was stopped, or from the beginning\n"
293  " by default. To control or stop the replay session, use the usual remote\n"
294  " control keypresses via the HITK command.",
295  "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n"
296  " Send a command to a plugin.\n"
297  " The PLUG command without any parameters lists all plugins.\n"
298  " If only a name is given, all commands known to that plugin are listed.\n"
299  " If a command is given (optionally followed by parameters), that command\n"
300  " is sent to the plugin, and the result will be displayed.\n"
301  " The keyword 'help' lists all the SVDRP commands known to the named plugin.\n"
302  " If 'help' is followed by a command, the detailed help for that command is\n"
303  " given. The keyword 'main' initiates a call to the main menu function of the\n"
304  " given plugin.\n",
305  "PUTE [ file ]\n"
306  " Put data into the EPG list. The data entered has to strictly follow the\n"
307  " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n"
308  " by itself terminates the input and starts processing of the data (all\n"
309  " entered data is buffered until the terminating '.' is seen).\n"
310  " If a file name is given, epg data will be read from this file (which\n"
311  " must be accessible under the given name from the machine VDR is running\n"
312  " on). In case of file input, no terminating '.' shall be given.\n",
313  "REMO [ on | off ]\n"
314  " Turns the remote control on or off. Without a parameter, the current\n"
315  " status of the remote control is reported.",
316  "SCAN\n"
317  " Forces an EPG scan. If this is a single DVB device system, the scan\n"
318  " will be done on the primary device unless it is currently recording.",
319  "STAT disk\n"
320  " Return information about disk usage (total, free, percent).",
321  "UPDT <settings>\n"
322  " Updates a timer. Settings must be in the same format as returned\n"
323  " by the LSTT command. If a timer with the same channel, day, start\n"
324  " and stop time does not yet exists, it will be created.",
325  "UPDR\n"
326  " Initiates a re-read of the recordings directory, which is the SVDRP\n"
327  " equivalent to 'touch .update'.",
328  "VOLU [ <number> | + | - | mute ]\n"
329  " Set the audio volume to the given number (which is limited to the range\n"
330  " 0...255). If the special options '+' or '-' are given, the volume will\n"
331  " be turned up or down, respectively. The option 'mute' will toggle the\n"
332  " audio muting. If no option is given, the current audio volume level will\n"
333  " be returned.",
334  "QUIT\n"
335  " Exit vdr (SVDRP).\n"
336  " You can also hit Ctrl-D to exit.",
337  NULL
338  };
339 
340 /* SVDRP Reply Codes:
341 
342  214 Help message
343  215 EPG or recording data record
344  216 Image grab data (base 64)
345  220 VDR service ready
346  221 VDR service closing transmission channel
347  250 Requested VDR action okay, completed
348  354 Start sending EPG data
349  451 Requested action aborted: local error in processing
350  500 Syntax error, command unrecognized
351  501 Syntax error in parameters or arguments
352  502 Command not implemented
353  504 Command parameter not implemented
354  550 Requested action not taken
355  554 Transaction failed
356  900 Default plugin reply code
357  901..999 Plugin specific reply codes
358 
359 */
360 
361 const char *GetHelpTopic(const char *HelpPage)
362 {
363  static char topic[MAXHELPTOPIC];
364  const char *q = HelpPage;
365  while (*q) {
366  if (isspace(*q)) {
367  uint n = q - HelpPage;
368  if (n >= sizeof(topic))
369  n = sizeof(topic) - 1;
370  strncpy(topic, HelpPage, n);
371  topic[n] = 0;
372  return topic;
373  }
374  q++;
375  }
376  return NULL;
377 }
378 
379 const char *GetHelpPage(const char *Cmd, const char **p)
380 {
381  if (p) {
382  while (*p) {
383  const char *t = GetHelpTopic(*p);
384  if (strcasecmp(Cmd, t) == 0)
385  return *p;
386  p++;
387  }
388  }
389  return NULL;
390 }
391 
392 char *cSVDRP::grabImageDir = NULL;
393 
394 cSVDRP::cSVDRP(int Port)
395 :socket(Port)
396 {
397  PUTEhandler = NULL;
398  numChars = 0;
399  length = BUFSIZ;
400  cmdLine = MALLOC(char, length);
401  lastActivity = 0;
402  isyslog("SVDRP listening on port %d", Port);
403 }
404 
406 {
407  Close(true);
408  free(cmdLine);
409 }
410 
411 void cSVDRP::Close(bool SendReply, bool Timeout)
412 {
413  if (file.IsOpen()) {
414  if (SendReply) {
415  //TODO how can we get the *full* hostname?
416  char buffer[BUFSIZ];
417  gethostname(buffer, sizeof(buffer));
418  Reply(221, "%s closing connection%s", buffer, Timeout ? " (timeout)" : "");
419  }
420  isyslog("closing SVDRP connection"); //TODO store IP#???
421  file.Close();
423  }
424 }
425 
426 bool cSVDRP::Send(const char *s, int length)
427 {
428  if (length < 0)
429  length = strlen(s);
430  if (safe_write(file, s, length) < 0) {
431  LOG_ERROR;
432  Close();
433  return false;
434  }
435  return true;
436 }
437 
438 void cSVDRP::Reply(int Code, const char *fmt, ...)
439 {
440  if (file.IsOpen()) {
441  if (Code != 0) {
442  va_list ap;
443  va_start(ap, fmt);
444  cString buffer = cString::vsprintf(fmt, ap);
445  va_end(ap);
446  const char *s = buffer;
447  while (s && *s) {
448  const char *n = strchr(s, '\n');
449  char cont = ' ';
450  if (Code < 0 || n && *(n + 1)) // trailing newlines don't count!
451  cont = '-';
452  char number[16];
453  sprintf(number, "%03d%c", abs(Code), cont);
454  if (!(Send(number) && Send(s, n ? n - s : -1) && Send("\r\n")))
455  break;
456  s = n ? n + 1 : NULL;
457  }
458  }
459  else {
460  Reply(451, "Zero return code - looks like a programming error!");
461  esyslog("SVDRP: zero return code!");
462  }
463  }
464 }
465 
466 void cSVDRP::PrintHelpTopics(const char **hp)
467 {
468  int NumPages = 0;
469  if (hp) {
470  while (*hp) {
471  NumPages++;
472  hp++;
473  }
474  hp -= NumPages;
475  }
476  const int TopicsPerLine = 5;
477  int x = 0;
478  for (int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
479  char buffer[TopicsPerLine * MAXHELPTOPIC + 5];
480  char *q = buffer;
481  q += sprintf(q, " ");
482  for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
483  const char *topic = GetHelpTopic(hp[(y * TopicsPerLine + x)]);
484  if (topic)
485  q += sprintf(q, "%*s", -MAXHELPTOPIC, topic);
486  }
487  x = 0;
488  Reply(-214, "%s", buffer);
489  }
490 }
491 
492 void cSVDRP::CmdCHAN(const char *Option)
493 {
494  if (*Option) {
495  int n = -1;
496  int d = 0;
497  if (isnumber(Option)) {
498  int o = strtol(Option, NULL, 10);
499  if (o >= 1 && o <= Channels.MaxNumber())
500  n = o;
501  }
502  else if (strcmp(Option, "-") == 0) {
504  if (n > 1) {
505  n--;
506  d = -1;
507  }
508  }
509  else if (strcmp(Option, "+") == 0) {
511  if (n < Channels.MaxNumber()) {
512  n++;
513  d = 1;
514  }
515  }
516  else {
518  if (channel)
519  n = channel->Number();
520  else {
521  for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) {
522  if (!channel->GroupSep()) {
523  if (strcasecmp(channel->Name(), Option) == 0) {
524  n = channel->Number();
525  break;
526  }
527  }
528  }
529  }
530  }
531  if (n < 0) {
532  Reply(501, "Undefined channel \"%s\"", Option);
533  return;
534  }
535  if (!d) {
536  cChannel *channel = Channels.GetByNumber(n);
537  if (channel) {
538  if (!cDevice::PrimaryDevice()->SwitchChannel(channel, true)) {
539  Reply(554, "Error switching to channel \"%d\"", channel->Number());
540  return;
541  }
542  }
543  else {
544  Reply(550, "Unable to find channel \"%s\"", Option);
545  return;
546  }
547  }
548  else
550  }
552  if (channel)
553  Reply(250, "%d %s", channel->Number(), channel->Name());
554  else
555  Reply(550, "Unable to find channel \"%d\"", cDevice::CurrentChannel());
556 }
557 
558 void cSVDRP::CmdCLRE(const char *Option)
559 {
560  if (*Option) {
561  tChannelID ChannelID = tChannelID::InvalidID;
562  if (isnumber(Option)) {
563  int o = strtol(Option, NULL, 10);
564  if (o >= 1 && o <= Channels.MaxNumber())
565  ChannelID = Channels.GetByNumber(o)->GetChannelID();
566  }
567  else {
568  ChannelID = tChannelID::FromString(Option);
569  if (ChannelID == tChannelID::InvalidID) {
570  for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
571  if (!Channel->GroupSep()) {
572  if (strcasecmp(Channel->Name(), Option) == 0) {
573  ChannelID = Channel->GetChannelID();
574  break;
575  }
576  }
577  }
578  }
579  }
580  if (!(ChannelID == tChannelID::InvalidID)) {
581  cSchedulesLock SchedulesLock(true, 1000);
582  cSchedules *s = (cSchedules *)cSchedules::Schedules(SchedulesLock);
583  if (s) {
584  cSchedule *Schedule = NULL;
585  ChannelID.ClrRid();
586  for (cSchedule *p = s->First(); p; p = s->Next(p)) {
587  if (p->ChannelID() == ChannelID) {
588  Schedule = p;
589  break;
590  }
591  }
592  if (Schedule) {
593  for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
594  if (ChannelID == Timer->Channel()->GetChannelID().ClrRid())
595  Timer->SetEvent(NULL);
596  }
597  Schedule->Cleanup(INT_MAX);
599  Reply(250, "EPG data of channel \"%s\" cleared", Option);
600  }
601  else {
602  Reply(550, "No EPG data found for channel \"%s\"", Option);
603  return;
604  }
605  }
606  else
607  Reply(451, "Can't get EPG data");
608  }
609  else
610  Reply(501, "Undefined channel \"%s\"", Option);
611  }
612  else {
614  if (cSchedules::ClearAll()) {
615  Reply(250, "EPG data cleared");
617  }
618  else
619  Reply(451, "Error while clearing EPG data");
620  }
621 }
622 
623 void cSVDRP::CmdDELC(const char *Option)
624 {
625  if (*Option) {
626  if (isnumber(Option)) {
627  if (!Channels.BeingEdited()) {
628  cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10));
629  if (channel) {
630  for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) {
631  if (timer->Channel() == channel) {
632  Reply(550, "Channel \"%s\" is in use by timer %d", Option, timer->Index() + 1);
633  return;
634  }
635  }
636  int CurrentChannelNr = cDevice::CurrentChannel();
637  cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
638  if (CurrentChannel && channel == CurrentChannel) {
639  int n = Channels.GetNextNormal(CurrentChannel->Index());
640  if (n < 0)
641  n = Channels.GetPrevNormal(CurrentChannel->Index());
642  CurrentChannel = Channels.Get(n);
643  CurrentChannelNr = 0; // triggers channel switch below
644  }
645  Channels.Del(channel);
646  Channels.ReNumber();
647  Channels.SetModified(true);
648  isyslog("channel %s deleted", Option);
649  if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
651  Channels.SwitchTo(CurrentChannel->Number());
652  else
653  cDevice::SetCurrentChannel(CurrentChannel);
654  }
655  Reply(250, "Channel \"%s\" deleted", Option);
656  }
657  else
658  Reply(501, "Channel \"%s\" not defined", Option);
659  }
660  else
661  Reply(550, "Channels are being edited - try again later");
662  }
663  else
664  Reply(501, "Error in channel number \"%s\"", Option);
665  }
666  else
667  Reply(501, "Missing channel number");
668 }
669 
670 static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
671 {
672  cRecordControl *rc;
673  if ((Reason & ruTimer) != 0 && (rc = cRecordControls::GetRecordControl(Recording->FileName())) != NULL)
674  return cString::sprintf("Recording \"%s\" is in use by timer %d", RecordingId, rc->Timer()->Index() + 1);
675  else if ((Reason & ruReplay) != 0)
676  return cString::sprintf("Recording \"%s\" is being replayed", RecordingId);
677  else if ((Reason & ruCut) != 0)
678  return cString::sprintf("Recording \"%s\" is being edited", RecordingId);
679  else if ((Reason & (ruMove | ruCopy)) != 0)
680  return cString::sprintf("Recording \"%s\" is being copied/moved", RecordingId);
681  else if (Reason)
682  return cString::sprintf("Recording \"%s\" is in use", RecordingId);
683  return NULL;
684 }
685 
686 void cSVDRP::CmdCPYR(const char *Option)
687 {
688  if (*Option) {
689  char *opt = strdup(Option);
690  char *num = skipspace(opt);
691  char *option = num;
692  while (*option && !isspace(*option))
693  option++;
694  char c = *option;
695  *option = 0;
696  if (isnumber(num)) {
697  cRecording *recording = recordings.Get(strtol(num, NULL, 10) - 1);
698  if (recording) {
699  if (int RecordingInUse = recording->IsInUse())
700  Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, recording));
701  else {
702  if (c)
703  option = skipspace(++option);
704  if (*option) {
705  cString newName = option;
706  newName.CompactChars(FOLDERDELIMCHAR);
707  if (strcmp(newName, recording->Name())) {
708  cString fromName = cString(ExchangeChars(strdup(recording->Name()), true), true);
709  cString toName = cString(ExchangeChars(strdup(*newName), true), true);
710  cString fileName = cString(strreplace(strdup(recording->FileName()), *fromName, *toName), true);
711  if (MakeDirs(fileName, true) && !RecordingsHandler.Add(ruCopy, recording->FileName(), fileName)) {
712  Recordings.AddByName(fileName);
713  Reply(250, "Recording \"%s\" copied to \"%s\"", recording->Name(), *newName);
714  }
715  else
716  Reply(554, "Error while copying recording \"%s\" to \"%s\"!", recording->Name(), *newName);
717  }
718  else
719  Reply(501, "Identical new recording name");
720  }
721  else
722  Reply(501, "Missing new recording name");
723  }
724  }
725  else
726  Reply(550, "Recording \"%s\" not found%s", num, recordings.Count() ? "" : " (use LSTR before moving)");
727  }
728  else
729  Reply(501, "Error in recording number \"%s\"", num);
730  free(opt);
731  }
732  else
733  Reply(501, "Missing recording number");
734 }
735 
736 void cSVDRP::CmdDELR(const char *Option)
737 {
738  if (*Option) {
739  if (isnumber(Option)) {
740  cRecording *recording = recordings.Get(strtol(Option, NULL, 10) - 1);
741  if (recording) {
742  if (int RecordingInUse = recording->IsInUse())
743  Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, recording));
744  else {
745  if (recording->Delete()) {
746  Reply(250, "Recording \"%s\" deleted", Option);
747  Recordings.DelByName(recording->FileName());
748  }
749  else
750  Reply(554, "Error while deleting recording!");
751  }
752  }
753  else
754  Reply(550, "Recording \"%s\" not found%s", Option, recordings.Count() ? "" : " (use LSTR before deleting)");
755  }
756  else
757  Reply(501, "Error in recording number \"%s\"", Option);
758  }
759  else
760  Reply(501, "Missing recording number");
761 }
762 
763 void cSVDRP::CmdDELT(const char *Option)
764 {
765  if (*Option) {
766  if (isnumber(Option)) {
767  if (!Timers.BeingEdited()) {
768  cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1);
769  if (timer) {
770  if (!timer->Recording()) {
771  isyslog("deleting timer %s", *timer->ToDescr());
772  Timers.Del(timer);
774  Reply(250, "Timer \"%s\" deleted", Option);
775  }
776  else
777  Reply(550, "Timer \"%s\" is recording", Option);
778  }
779  else
780  Reply(501, "Timer \"%s\" not defined", Option);
781  }
782  else
783  Reply(550, "Timers are being edited - try again later");
784  }
785  else
786  Reply(501, "Error in timer number \"%s\"", Option);
787  }
788  else
789  Reply(501, "Missing timer number");
790 }
791 
792 void cSVDRP::CmdEDIT(const char *Option)
793 {
794  if (*Option) {
795  if (isnumber(Option)) {
796  cRecording *recording = recordings.Get(strtol(Option, NULL, 10) - 1);
797  if (recording) {
798  cMarks Marks;
799  if (Marks.Load(recording->FileName(), recording->FramesPerSecond(), recording->IsPesRecording()) && Marks.Count()) {
800  if (RecordingsHandler.Add(ruCut, recording->FileName()))
801  Reply(250, "Editing recording \"%s\" [%s]", Option, recording->Title());
802  else
803  Reply(554, "Can't start editing process");
804  }
805  else
806  Reply(554, "No editing marks defined");
807  }
808  else
809  Reply(550, "Recording \"%s\" not found%s", Option, recordings.Count() ? "" : " (use LSTR before editing)");
810  }
811  else
812  Reply(501, "Error in recording number \"%s\"", Option);
813  }
814  else
815  Reply(501, "Missing recording number");
816 }
817 
818 void cSVDRP::CmdGRAB(const char *Option)
819 {
820  const char *FileName = NULL;
821  bool Jpeg = true;
822  int Quality = -1, SizeX = -1, SizeY = -1;
823  if (*Option) {
824  char buf[strlen(Option) + 1];
825  char *p = strcpy(buf, Option);
826  const char *delim = " \t";
827  char *strtok_next;
828  FileName = strtok_r(p, delim, &strtok_next);
829  // image type:
830  const char *Extension = strrchr(FileName, '.');
831  if (Extension) {
832  if (strcasecmp(Extension, ".jpg") == 0 || strcasecmp(Extension, ".jpeg") == 0)
833  Jpeg = true;
834  else if (strcasecmp(Extension, ".pnm") == 0)
835  Jpeg = false;
836  else {
837  Reply(501, "Unknown image type \"%s\"", Extension + 1);
838  return;
839  }
840  if (Extension == FileName)
841  FileName = NULL;
842  }
843  else if (strcmp(FileName, "-") == 0)
844  FileName = NULL;
845  // image quality (and obsolete type):
846  if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
847  if (strcasecmp(p, "JPEG") == 0 || strcasecmp(p, "PNM") == 0) {
848  // tolerate for backward compatibility
849  p = strtok_r(NULL, delim, &strtok_next);
850  }
851  if (p) {
852  if (isnumber(p))
853  Quality = atoi(p);
854  else {
855  Reply(501, "Invalid quality \"%s\"", p);
856  return;
857  }
858  }
859  }
860  // image size:
861  if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
862  if (isnumber(p))
863  SizeX = atoi(p);
864  else {
865  Reply(501, "Invalid sizex \"%s\"", p);
866  return;
867  }
868  if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
869  if (isnumber(p))
870  SizeY = atoi(p);
871  else {
872  Reply(501, "Invalid sizey \"%s\"", p);
873  return;
874  }
875  }
876  else {
877  Reply(501, "Missing sizey");
878  return;
879  }
880  }
881  if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
882  Reply(501, "Unexpected parameter \"%s\"", p);
883  return;
884  }
885  // canonicalize the file name:
886  char RealFileName[PATH_MAX];
887  if (FileName) {
888  if (grabImageDir) {
889  cString s(FileName);
890  FileName = s;
891  const char *slash = strrchr(FileName, '/');
892  if (!slash) {
893  s = AddDirectory(grabImageDir, FileName);
894  FileName = s;
895  }
896  slash = strrchr(FileName, '/'); // there definitely is one
897  cString t(s);
898  t.Truncate(slash - FileName);
899  char *r = realpath(t, RealFileName);
900  if (!r) {
901  LOG_ERROR_STR(FileName);
902  Reply(501, "Invalid file name \"%s\"", FileName);
903  return;
904  }
905  strcat(RealFileName, slash);
906  FileName = RealFileName;
907  if (strncmp(FileName, grabImageDir, strlen(grabImageDir)) != 0) {
908  Reply(501, "Invalid file name \"%s\"", FileName);
909  return;
910  }
911  }
912  else {
913  Reply(550, "Grabbing to file not allowed (use \"GRAB -\" instead)");
914  return;
915  }
916  }
917  // actual grabbing:
918  int ImageSize;
919  uchar *Image = cDevice::PrimaryDevice()->GrabImage(ImageSize, Jpeg, Quality, SizeX, SizeY);
920  if (Image) {
921  if (FileName) {
922  int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
923  if (fd >= 0) {
924  if (safe_write(fd, Image, ImageSize) == ImageSize) {
925  dsyslog("grabbed image to %s", FileName);
926  Reply(250, "Grabbed image %s", Option);
927  }
928  else {
929  LOG_ERROR_STR(FileName);
930  Reply(451, "Can't write to '%s'", FileName);
931  }
932  close(fd);
933  }
934  else {
935  LOG_ERROR_STR(FileName);
936  Reply(451, "Can't open '%s'", FileName);
937  }
938  }
939  else {
940  cBase64Encoder Base64(Image, ImageSize);
941  const char *s;
942  while ((s = Base64.NextLine()) != NULL)
943  Reply(-216, "%s", s);
944  Reply(216, "Grabbed image %s", Option);
945  }
946  free(Image);
947  }
948  else
949  Reply(451, "Grab image failed");
950  }
951  else
952  Reply(501, "Missing filename");
953 }
954 
955 void cSVDRP::CmdHELP(const char *Option)
956 {
957  if (*Option) {
958  const char *hp = GetHelpPage(Option, HelpPages);
959  if (hp)
960  Reply(-214, "%s", hp);
961  else {
962  Reply(504, "HELP topic \"%s\" unknown", Option);
963  return;
964  }
965  }
966  else {
967  Reply(-214, "This is VDR version %s", VDRVERSION);
968  Reply(-214, "Topics:");
970  cPlugin *plugin;
971  for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++) {
972  const char **hp = plugin->SVDRPHelpPages();
973  if (hp)
974  Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
975  PrintHelpTopics(hp);
976  }
977  Reply(-214, "To report bugs in the implementation send email to");
978  Reply(-214, " vdr-bugs@tvdr.de");
979  }
980  Reply(214, "End of HELP info");
981 }
982 
983 void cSVDRP::CmdHITK(const char *Option)
984 {
985  if (*Option) {
986  if (!cRemote::Enabled()) {
987  Reply(550, "Remote control currently disabled (key \"%s\" discarded)", Option);
988  return;
989  }
990  char buf[strlen(Option) + 1];
991  strcpy(buf, Option);
992  const char *delim = " \t";
993  char *strtok_next;
994  char *p = strtok_r(buf, delim, &strtok_next);
995  int NumKeys = 0;
996  while (p) {
997  eKeys k = cKey::FromString(p);
998  if (k != kNone) {
999  if (!cRemote::Put(k)) {
1000  Reply(451, "Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
1001  return;
1002  }
1003  }
1004  else {
1005  Reply(504, "Unknown key: \"%s\"", p);
1006  return;
1007  }
1008  NumKeys++;
1009  p = strtok_r(NULL, delim, &strtok_next);
1010  }
1011  Reply(250, "Key%s \"%s\" accepted", NumKeys > 1 ? "s" : "", Option);
1012  }
1013  else {
1014  Reply(-214, "Valid <key> names for the HITK command:");
1015  for (int i = 0; i < kNone; i++) {
1016  Reply(-214, " %s", cKey::ToString(eKeys(i)));
1017  }
1018  Reply(214, "End of key list");
1019  }
1020 }
1021 
1022 void cSVDRP::CmdLSTC(const char *Option)
1023 {
1024  bool WithGroupSeps = strcasecmp(Option, ":groups") == 0;
1025  if (*Option && !WithGroupSeps) {
1026  if (isnumber(Option)) {
1027  cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10));
1028  if (channel)
1029  Reply(250, "%d %s", channel->Number(), *channel->ToText());
1030  else
1031  Reply(501, "Channel \"%s\" not defined", Option);
1032  }
1033  else {
1035  if (!next) {
1036  for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) {
1037  if (!channel->GroupSep()) {
1038  if (strcasestr(channel->Name(), Option)) {
1039  if (next)
1040  Reply(-250, "%d %s", next->Number(), *next->ToText());
1041  next = channel;
1042  }
1043  }
1044  }
1045  }
1046  if (next)
1047  Reply(250, "%d %s", next->Number(), *next->ToText());
1048  else
1049  Reply(501, "Channel \"%s\" not defined", Option);
1050  }
1051  }
1052  else if (Channels.MaxNumber() >= 1) {
1053  for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) {
1054  if (WithGroupSeps)
1055  Reply(channel->Next() ? -250: 250, "%d %s", channel->GroupSep() ? 0 : channel->Number(), *channel->ToText());
1056  else if (!channel->GroupSep())
1057  Reply(channel->Number() < Channels.MaxNumber() ? -250 : 250, "%d %s", channel->Number(), *channel->ToText());
1058  }
1059  }
1060  else
1061  Reply(550, "No channels defined");
1062 }
1063 
1064 void cSVDRP::CmdLSTE(const char *Option)
1065 {
1066  cSchedulesLock SchedulesLock;
1067  const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
1068  if (Schedules) {
1069  const cSchedule* Schedule = NULL;
1070  eDumpMode DumpMode = dmAll;
1071  time_t AtTime = 0;
1072  if (*Option) {
1073  char buf[strlen(Option) + 1];
1074  strcpy(buf, Option);
1075  const char *delim = " \t";
1076  char *strtok_next;
1077  char *p = strtok_r(buf, delim, &strtok_next);
1078  while (p && DumpMode == dmAll) {
1079  if (strcasecmp(p, "NOW") == 0)
1080  DumpMode = dmPresent;
1081  else if (strcasecmp(p, "NEXT") == 0)
1082  DumpMode = dmFollowing;
1083  else if (strcasecmp(p, "AT") == 0) {
1084  DumpMode = dmAtTime;
1085  if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1086  if (isnumber(p))
1087  AtTime = strtol(p, NULL, 10);
1088  else {
1089  Reply(501, "Invalid time");
1090  return;
1091  }
1092  }
1093  else {
1094  Reply(501, "Missing time");
1095  return;
1096  }
1097  }
1098  else if (!Schedule) {
1099  cChannel* Channel = NULL;
1100  if (isnumber(p))
1101  Channel = Channels.GetByNumber(strtol(Option, NULL, 10));
1102  else
1103  Channel = Channels.GetByChannelID(tChannelID::FromString(Option));
1104  if (Channel) {
1105  Schedule = Schedules->GetSchedule(Channel);
1106  if (!Schedule) {
1107  Reply(550, "No schedule found");
1108  return;
1109  }
1110  }
1111  else {
1112  Reply(550, "Channel \"%s\" not defined", p);
1113  return;
1114  }
1115  }
1116  else {
1117  Reply(501, "Unknown option: \"%s\"", p);
1118  return;
1119  }
1120  p = strtok_r(NULL, delim, &strtok_next);
1121  }
1122  }
1123  int fd = dup(file);
1124  if (fd) {
1125  FILE *f = fdopen(fd, "w");
1126  if (f) {
1127  if (Schedule)
1128  Schedule->Dump(f, "215-", DumpMode, AtTime);
1129  else
1130  Schedules->Dump(f, "215-", DumpMode, AtTime);
1131  fflush(f);
1132  Reply(215, "End of EPG data");
1133  fclose(f);
1134  }
1135  else {
1136  Reply(451, "Can't open file connection");
1137  close(fd);
1138  }
1139  }
1140  else
1141  Reply(451, "Can't dup stream descriptor");
1142  }
1143  else
1144  Reply(451, "Can't get EPG data");
1145 }
1146 
1147 void cSVDRP::CmdLSTR(const char *Option)
1148 {
1149  int Number = 0;
1150  bool Path = false;
1151  recordings.Update(true);
1152  if (*Option) {
1153  char buf[strlen(Option) + 1];
1154  strcpy(buf, Option);
1155  const char *delim = " \t";
1156  char *strtok_next;
1157  char *p = strtok_r(buf, delim, &strtok_next);
1158  while (p) {
1159  if (!Number) {
1160  if (isnumber(p))
1161  Number = strtol(p, NULL, 10);
1162  else {
1163  Reply(501, "Error in recording number \"%s\"", Option);
1164  return;
1165  }
1166  }
1167  else if (strcasecmp(p, "PATH") == 0)
1168  Path = true;
1169  else {
1170  Reply(501, "Unknown option: \"%s\"", p);
1171  return;
1172  }
1173  p = strtok_r(NULL, delim, &strtok_next);
1174  }
1175  if (Number) {
1176  cRecording *recording = recordings.Get(strtol(Option, NULL, 10) - 1);
1177  if (recording) {
1178  FILE *f = fdopen(file, "w");
1179  if (f) {
1180  if (Path)
1181  Reply(250, "%s", recording->FileName());
1182  else {
1183  recording->Info()->Write(f, "215-");
1184  fflush(f);
1185  Reply(215, "End of recording information");
1186  }
1187  // don't 'fclose(f)' here!
1188  }
1189  else
1190  Reply(451, "Can't open file connection");
1191  }
1192  else
1193  Reply(550, "Recording \"%s\" not found", Option);
1194  }
1195  }
1196  else if (recordings.Count()) {
1197  cRecording *recording = recordings.First();
1198  while (recording) {
1199  Reply(recording == recordings.Last() ? 250 : -250, "%d %s", recording->Index() + 1, recording->Title(' ', true));
1200  recording = recordings.Next(recording);
1201  }
1202  }
1203  else
1204  Reply(550, "No recordings available");
1205 }
1206 
1207 void cSVDRP::CmdLSTT(const char *Option)
1208 {
1209  int Number = 0;
1210  bool Id = false;
1211  if (*Option) {
1212  char buf[strlen(Option) + 1];
1213  strcpy(buf, Option);
1214  const char *delim = " \t";
1215  char *strtok_next;
1216  char *p = strtok_r(buf, delim, &strtok_next);
1217  while (p) {
1218  if (isnumber(p))
1219  Number = strtol(p, NULL, 10);
1220  else if (strcasecmp(p, "ID") == 0)
1221  Id = true;
1222  else {
1223  Reply(501, "Unknown option: \"%s\"", p);
1224  return;
1225  }
1226  p = strtok_r(NULL, delim, &strtok_next);
1227  }
1228  }
1229  if (Number) {
1230  cTimer *timer = Timers.Get(Number - 1);
1231  if (timer)
1232  Reply(250, "%d %s", timer->Index() + 1, *timer->ToText(Id));
1233  else
1234  Reply(501, "Timer \"%s\" not defined", Option);
1235  }
1236  else if (Timers.Count()) {
1237  for (int i = 0; i < Timers.Count(); i++) {
1238  cTimer *timer = Timers.Get(i);
1239  if (timer)
1240  Reply(i < Timers.Count() - 1 ? -250 : 250, "%d %s", timer->Index() + 1, *timer->ToText(Id));
1241  else
1242  Reply(501, "Timer \"%d\" not found", i + 1);
1243  }
1244  }
1245  else
1246  Reply(550, "No timers defined");
1247 }
1248 
1249 void cSVDRP::CmdMESG(const char *Option)
1250 {
1251  if (*Option) {
1252  isyslog("SVDRP message: '%s'", Option);
1253  Skins.QueueMessage(mtInfo, Option);
1254  Reply(250, "Message queued");
1255  }
1256  else
1257  Reply(501, "Missing message");
1258 }
1259 
1260 void cSVDRP::CmdMODC(const char *Option)
1261 {
1262  if (*Option) {
1263  char *tail;
1264  int n = strtol(Option, &tail, 10);
1265  if (tail && tail != Option) {
1266  tail = skipspace(tail);
1267  if (!Channels.BeingEdited()) {
1268  cChannel *channel = Channels.GetByNumber(n);
1269  if (channel) {
1270  cChannel ch;
1271  if (ch.Parse(tail)) {
1272  if (Channels.HasUniqueChannelID(&ch, channel)) {
1273  *channel = ch;
1274  Channels.ReNumber();
1275  Channels.SetModified(true);
1276  isyslog("modifed channel %d %s", channel->Number(), *channel->ToText());
1277  Reply(250, "%d %s", channel->Number(), *channel->ToText());
1278  }
1279  else
1280  Reply(501, "Channel settings are not unique");
1281  }
1282  else
1283  Reply(501, "Error in channel settings");
1284  }
1285  else
1286  Reply(501, "Channel \"%d\" not defined", n);
1287  }
1288  else
1289  Reply(550, "Channels are being edited - try again later");
1290  }
1291  else
1292  Reply(501, "Error in channel number");
1293  }
1294  else
1295  Reply(501, "Missing channel settings");
1296 }
1297 
1298 void cSVDRP::CmdMODT(const char *Option)
1299 {
1300  if (*Option) {
1301  char *tail;
1302  int n = strtol(Option, &tail, 10);
1303  if (tail && tail != Option) {
1304  tail = skipspace(tail);
1305  if (!Timers.BeingEdited()) {
1306  cTimer *timer = Timers.Get(n - 1);
1307  if (timer) {
1308  cTimer t = *timer;
1309  if (strcasecmp(tail, "ON") == 0)
1310  t.SetFlags(tfActive);
1311  else if (strcasecmp(tail, "OFF") == 0)
1312  t.ClrFlags(tfActive);
1313  else if (!t.Parse(tail)) {
1314  Reply(501, "Error in timer settings");
1315  return;
1316  }
1317  *timer = t;
1318  Timers.SetModified();
1319  isyslog("timer %s modified (%s)", *timer->ToDescr(), timer->HasFlags(tfActive) ? "active" : "inactive");
1320  Reply(250, "%d %s", timer->Index() + 1, *timer->ToText());
1321  }
1322  else
1323  Reply(501, "Timer \"%d\" not defined", n);
1324  }
1325  else
1326  Reply(550, "Timers are being edited - try again later");
1327  }
1328  else
1329  Reply(501, "Error in timer number");
1330  }
1331  else
1332  Reply(501, "Missing timer settings");
1333 }
1334 
1335 void cSVDRP::CmdMOVC(const char *Option)
1336 {
1337  if (*Option) {
1338  if (!Channels.BeingEdited() && !Timers.BeingEdited()) {
1339  char *tail;
1340  int From = strtol(Option, &tail, 10);
1341  if (tail && tail != Option) {
1342  tail = skipspace(tail);
1343  if (tail && tail != Option) {
1344  int To = strtol(tail, NULL, 10);
1345  int CurrentChannelNr = cDevice::CurrentChannel();
1346  cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
1347  cChannel *FromChannel = Channels.GetByNumber(From);
1348  if (FromChannel) {
1349  cChannel *ToChannel = Channels.GetByNumber(To);
1350  if (ToChannel) {
1351  int FromNumber = FromChannel->Number();
1352  int ToNumber = ToChannel->Number();
1353  if (FromNumber != ToNumber) {
1354  Channels.Move(FromChannel, ToChannel);
1355  Channels.ReNumber();
1356  Channels.SetModified(true);
1357  if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
1359  Channels.SwitchTo(CurrentChannel->Number());
1360  else
1361  cDevice::SetCurrentChannel(CurrentChannel);
1362  }
1363  isyslog("channel %d moved to %d", FromNumber, ToNumber);
1364  Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
1365  }
1366  else
1367  Reply(501, "Can't move channel to same position");
1368  }
1369  else
1370  Reply(501, "Channel \"%d\" not defined", To);
1371  }
1372  else
1373  Reply(501, "Channel \"%d\" not defined", From);
1374  }
1375  else
1376  Reply(501, "Error in channel number");
1377  }
1378  else
1379  Reply(501, "Error in channel number");
1380  }
1381  else
1382  Reply(550, "Channels or timers are being edited - try again later");
1383  }
1384  else
1385  Reply(501, "Missing channel number");
1386 }
1387 
1388 void cSVDRP::CmdMOVR(const char *Option)
1389 {
1390  if (*Option) {
1391  char *opt = strdup(Option);
1392  char *num = skipspace(opt);
1393  char *option = num;
1394  while (*option && !isspace(*option))
1395  option++;
1396  char c = *option;
1397  *option = 0;
1398  if (isnumber(num)) {
1399  cRecording *recording = recordings.Get(strtol(num, NULL, 10) - 1);
1400  if (recording) {
1401  if (int RecordingInUse = recording->IsInUse())
1402  Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, recording));
1403  else {
1404  if (c)
1405  option = skipspace(++option);
1406  if (*option) {
1407  cString oldName = recording->Name();
1408  if ((recording = Recordings.GetByName(recording->FileName())) != NULL && recording->ChangeName(option))
1409  Reply(250, "Recording \"%s\" moved to \"%s\"", *oldName, recording->Name());
1410  else
1411  Reply(554, "Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
1412  }
1413  else
1414  Reply(501, "Missing new recording name");
1415  }
1416  }
1417  else
1418  Reply(550, "Recording \"%s\" not found%s", num, recordings.Count() ? "" : " (use LSTR before moving)");
1419  }
1420  else
1421  Reply(501, "Error in recording number \"%s\"", num);
1422  free(opt);
1423  }
1424  else
1425  Reply(501, "Missing recording number");
1426 }
1427 
1428 void cSVDRP::CmdNEWC(const char *Option)
1429 {
1430  if (*Option) {
1431  cChannel ch;
1432  if (ch.Parse(Option)) {
1433  if (Channels.HasUniqueChannelID(&ch)) {
1434  cChannel *channel = new cChannel;
1435  *channel = ch;
1436  Channels.Add(channel);
1437  Channels.ReNumber();
1438  Channels.SetModified(true);
1439  isyslog("new channel %d %s", channel->Number(), *channel->ToText());
1440  Reply(250, "%d %s", channel->Number(), *channel->ToText());
1441  }
1442  else
1443  Reply(501, "Channel settings are not unique");
1444  }
1445  else
1446  Reply(501, "Error in channel settings");
1447  }
1448  else
1449  Reply(501, "Missing channel settings");
1450 }
1451 
1452 void cSVDRP::CmdNEWT(const char *Option)
1453 {
1454  if (*Option) {
1455  cTimer *timer = new cTimer;
1456  if (timer->Parse(Option)) {
1457  Timers.Add(timer);
1458  Timers.SetModified();
1459  isyslog("timer %s added", *timer->ToDescr());
1460  Reply(250, "%d %s", timer->Index() + 1, *timer->ToText());
1461  return;
1462  }
1463  else
1464  Reply(501, "Error in timer settings");
1465  delete timer;
1466  }
1467  else
1468  Reply(501, "Missing timer settings");
1469 }
1470 
1471 void cSVDRP::CmdNEXT(const char *Option)
1472 {
1474  if (t) {
1475  time_t Start = t->StartTime();
1476  int Number = t->Index() + 1;
1477  if (!*Option)
1478  Reply(250, "%d %s", Number, *TimeToString(Start));
1479  else if (strcasecmp(Option, "ABS") == 0)
1480  Reply(250, "%d %ld", Number, Start);
1481  else if (strcasecmp(Option, "REL") == 0)
1482  Reply(250, "%d %ld", Number, Start - time(NULL));
1483  else
1484  Reply(501, "Unknown option: \"%s\"", Option);
1485  }
1486  else
1487  Reply(550, "No active timers");
1488 }
1489 
1490 void cSVDRP::CmdPLAY(const char *Option)
1491 {
1492  if (*Option) {
1493  char *opt = strdup(Option);
1494  char *num = skipspace(opt);
1495  char *option = num;
1496  while (*option && !isspace(*option))
1497  option++;
1498  char c = *option;
1499  *option = 0;
1500  if (isnumber(num)) {
1501  cRecording *recording = recordings.Get(strtol(num, NULL, 10) - 1);
1502  if (recording) {
1503  if (c)
1504  option = skipspace(++option);
1507  if (*option) {
1508  int pos = 0;
1509  if (strcasecmp(option, "BEGIN") != 0)
1510  pos = HMSFToIndex(option, recording->FramesPerSecond());
1511  cResumeFile resume(recording->FileName(), recording->IsPesRecording());
1512  if (pos <= 0)
1513  resume.Delete();
1514  else
1515  resume.Save(pos);
1516  }
1517  cReplayControl::SetRecording(recording->FileName());
1519  cControl::Attach();
1520  Reply(250, "Playing recording \"%s\" [%s]", num, recording->Title());
1521  }
1522  else
1523  Reply(550, "Recording \"%s\" not found%s", num, recordings.Count() ? "" : " (use LSTR before playing)");
1524  }
1525  else
1526  Reply(501, "Error in recording number \"%s\"", num);
1527  free(opt);
1528  }
1529  else
1530  Reply(501, "Missing recording number");
1531 }
1532 
1533 void cSVDRP::CmdPLUG(const char *Option)
1534 {
1535  if (*Option) {
1536  char *opt = strdup(Option);
1537  char *name = skipspace(opt);
1538  char *option = name;
1539  while (*option && !isspace(*option))
1540  option++;
1541  char c = *option;
1542  *option = 0;
1543  cPlugin *plugin = cPluginManager::GetPlugin(name);
1544  if (plugin) {
1545  if (c)
1546  option = skipspace(++option);
1547  char *cmd = option;
1548  while (*option && !isspace(*option))
1549  option++;
1550  if (*option) {
1551  *option++ = 0;
1552  option = skipspace(option);
1553  }
1554  if (!*cmd || strcasecmp(cmd, "HELP") == 0) {
1555  if (*cmd && *option) {
1556  const char *hp = GetHelpPage(option, plugin->SVDRPHelpPages());
1557  if (hp) {
1558  Reply(-214, "%s", hp);
1559  Reply(214, "End of HELP info");
1560  }
1561  else
1562  Reply(504, "HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->Name());
1563  }
1564  else {
1565  Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
1566  const char **hp = plugin->SVDRPHelpPages();
1567  if (hp) {
1568  Reply(-214, "SVDRP commands:");
1569  PrintHelpTopics(hp);
1570  Reply(214, "End of HELP info");
1571  }
1572  else
1573  Reply(214, "This plugin has no SVDRP commands");
1574  }
1575  }
1576  else if (strcasecmp(cmd, "MAIN") == 0) {
1577  if (cRemote::CallPlugin(plugin->Name()))
1578  Reply(250, "Initiated call to main menu function of plugin \"%s\"", plugin->Name());
1579  else
1580  Reply(550, "A plugin call is already pending - please try again later");
1581  }
1582  else {
1583  int ReplyCode = 900;
1584  cString s = plugin->SVDRPCommand(cmd, option, ReplyCode);
1585  if (*s)
1586  Reply(abs(ReplyCode), "%s", *s);
1587  else
1588  Reply(500, "Command unrecognized: \"%s\"", cmd);
1589  }
1590  }
1591  else
1592  Reply(550, "Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
1593  free(opt);
1594  }
1595  else {
1596  Reply(-214, "Available plugins:");
1597  cPlugin *plugin;
1598  for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++)
1599  Reply(-214, "%s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
1600  Reply(214, "End of plugin list");
1601  }
1602 }
1603 
1604 void cSVDRP::CmdPUTE(const char *Option)
1605 {
1606  if (*Option) {
1607  FILE *f = fopen(Option, "r");
1608  if (f) {
1609  if (cSchedules::Read(f)) {
1610  cSchedules::Cleanup(true);
1611  Reply(250, "EPG data processed from \"%s\"", Option);
1612  }
1613  else
1614  Reply(451, "Error while processing EPG from \"%s\"", Option);
1615  fclose(f);
1616  }
1617  else
1618  Reply(501, "Cannot open file \"%s\"", Option);
1619  }
1620  else {
1621  delete PUTEhandler;
1622  PUTEhandler = new cPUTEhandler;
1623  Reply(PUTEhandler->Status(), "%s", PUTEhandler->Message());
1624  if (PUTEhandler->Status() != 354)
1626  }
1627 }
1628 
1629 void cSVDRP::CmdREMO(const char *Option)
1630 {
1631  if (*Option) {
1632  if (!strcasecmp(Option, "ON")) {
1633  cRemote::SetEnabled(true);
1634  Reply(250, "Remote control enabled");
1635  }
1636  else if (!strcasecmp(Option, "OFF")) {
1637  cRemote::SetEnabled(false);
1638  Reply(250, "Remote control disabled");
1639  }
1640  else
1641  Reply(501, "Invalid Option \"%s\"", Option);
1642  }
1643  else
1644  Reply(250, "Remote control is %s", cRemote::Enabled() ? "enabled" : "disabled");
1645 }
1646 
1647 void cSVDRP::CmdSCAN(const char *Option)
1648 {
1650  Reply(250, "EPG scan triggered");
1651 }
1652 
1653 void cSVDRP::CmdSTAT(const char *Option)
1654 {
1655  if (*Option) {
1656  if (strcasecmp(Option, "DISK") == 0) {
1657  int FreeMB, UsedMB;
1658  int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB);
1659  Reply(250, "%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
1660  }
1661  else
1662  Reply(501, "Invalid Option \"%s\"", Option);
1663  }
1664  else
1665  Reply(501, "No option given");
1666 }
1667 
1668 void cSVDRP::CmdUPDT(const char *Option)
1669 {
1670  if (*Option) {
1671  cTimer *timer = new cTimer;
1672  if (timer->Parse(Option)) {
1673  if (!Timers.BeingEdited()) {
1674  cTimer *t = Timers.GetTimer(timer);
1675  if (t) {
1676  t->Parse(Option);
1677  delete timer;
1678  timer = t;
1679  isyslog("timer %s updated", *timer->ToDescr());
1680  }
1681  else {
1682  Timers.Add(timer);
1683  isyslog("timer %s added", *timer->ToDescr());
1684  }
1685  Timers.SetModified();
1686  Reply(250, "%d %s", timer->Index() + 1, *timer->ToText());
1687  return;
1688  }
1689  else
1690  Reply(550, "Timers are being edited - try again later");
1691  }
1692  else
1693  Reply(501, "Error in timer settings");
1694  delete timer;
1695  }
1696  else
1697  Reply(501, "Missing timer settings");
1698 }
1699 
1700 void cSVDRP::CmdUPDR(const char *Option)
1701 {
1702  Recordings.Update(false);
1703  Reply(250, "Re-read of recordings directory triggered");
1704 }
1705 
1706 void cSVDRP::CmdVOLU(const char *Option)
1707 {
1708  if (*Option) {
1709  if (isnumber(Option))
1710  cDevice::PrimaryDevice()->SetVolume(strtol(Option, NULL, 10), true);
1711  else if (strcmp(Option, "+") == 0)
1713  else if (strcmp(Option, "-") == 0)
1715  else if (strcasecmp(Option, "MUTE") == 0)
1717  else {
1718  Reply(501, "Unknown option: \"%s\"", Option);
1719  return;
1720  }
1721  }
1722  if (cDevice::PrimaryDevice()->IsMute())
1723  Reply(250, "Audio is mute");
1724  else
1725  Reply(250, "Audio volume is %d", cDevice::CurrentVolume());
1726 }
1727 
1728 #define CMD(c) (strcasecmp(Cmd, c) == 0)
1729 
1730 void cSVDRP::Execute(char *Cmd)
1731 {
1732  // handle PUTE data:
1733  if (PUTEhandler) {
1734  if (!PUTEhandler->Process(Cmd)) {
1735  Reply(PUTEhandler->Status(), "%s", PUTEhandler->Message());
1737  }
1738  cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME); // re-trigger the timeout, in case there is very much EPG data
1739  return;
1740  }
1741  // skip leading whitespace:
1742  Cmd = skipspace(Cmd);
1743  // find the end of the command word:
1744  char *s = Cmd;
1745  while (*s && !isspace(*s))
1746  s++;
1747  if (*s)
1748  *s++ = 0;
1749  s = skipspace(s);
1750  if (CMD("CHAN")) CmdCHAN(s);
1751  else if (CMD("CLRE")) CmdCLRE(s);
1752  else if (CMD("DELC")) CmdDELC(s);
1753  else if (CMD("DELR")) CmdDELR(s);
1754  else if (CMD("DELT")) CmdDELT(s);
1755  else if (CMD("EDIT")) CmdEDIT(s);
1756  else if (CMD("GRAB")) CmdGRAB(s);
1757  else if (CMD("HELP")) CmdHELP(s);
1758  else if (CMD("HITK")) CmdHITK(s);
1759  else if (CMD("LSTC")) CmdLSTC(s);
1760  else if (CMD("LSTE")) CmdLSTE(s);
1761  else if (CMD("LSTR")) CmdLSTR(s);
1762  else if (CMD("LSTT")) CmdLSTT(s);
1763  else if (CMD("MESG")) CmdMESG(s);
1764  else if (CMD("MODC")) CmdMODC(s);
1765  else if (CMD("MODT")) CmdMODT(s);
1766  else if (CMD("MOVC")) CmdMOVC(s);
1767  else if (CMD("MOVR")) CmdMOVR(s);
1768  else if (CMD("NEWC")) CmdNEWC(s);
1769  else if (CMD("NEWT")) CmdNEWT(s);
1770  else if (CMD("NEXT")) CmdNEXT(s);
1771  else if (CMD("PLAY")) CmdPLAY(s);
1772  else if (CMD("PLUG")) CmdPLUG(s);
1773  else if (CMD("PUTE")) CmdPUTE(s);
1774  else if (CMD("REMO")) CmdREMO(s);
1775  else if (CMD("SCAN")) CmdSCAN(s);
1776  else if (CMD("STAT")) CmdSTAT(s);
1777  else if (CMD("UPDR")) CmdUPDR(s);
1778  else if (CMD("UPDT")) CmdUPDT(s);
1779  else if (CMD("VOLU")) CmdVOLU(s);
1780  else if (CMD("QUIT")) Close(true);
1781  else Reply(500, "Command unrecognized: \"%s\"", Cmd);
1782 }
1783 
1785 {
1786  bool NewConnection = !file.IsOpen();
1787  bool SendGreeting = NewConnection;
1788 
1789  if (file.IsOpen() || file.Open(socket.Accept())) {
1790  if (SendGreeting) {
1791  //TODO how can we get the *full* hostname?
1792  char buffer[BUFSIZ];
1793  gethostname(buffer, sizeof(buffer));
1794  time_t now = time(NULL);
1795  Reply(220, "%s SVDRP VideoDiskRecorder %s; %s; %s", buffer, VDRVERSION, *TimeToString(now), cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8");
1796  }
1797  if (NewConnection)
1798  lastActivity = time(NULL);
1799  while (file.Ready(false)) {
1800  unsigned char c;
1801  int r = safe_read(file, &c, 1);
1802  if (r > 0) {
1803  if (c == '\n' || c == 0x00) {
1804  // strip trailing whitespace:
1805  while (numChars > 0 && strchr(" \t\r\n", cmdLine[numChars - 1]))
1806  cmdLine[--numChars] = 0;
1807  // make sure the string is terminated:
1808  cmdLine[numChars] = 0;
1809  // showtime!
1810  Execute(cmdLine);
1811  numChars = 0;
1812  if (length > BUFSIZ) {
1813  free(cmdLine); // let's not tie up too much memory
1814  length = BUFSIZ;
1815  cmdLine = MALLOC(char, length);
1816  }
1817  }
1818  else if (c == 0x04 && numChars == 0) {
1819  // end of file (only at beginning of line)
1820  Close(true);
1821  }
1822  else if (c == 0x08 || c == 0x7F) {
1823  // backspace or delete (last character)
1824  if (numChars > 0)
1825  numChars--;
1826  }
1827  else if (c <= 0x03 || c == 0x0D) {
1828  // ignore control characters
1829  }
1830  else {
1831  if (numChars >= length - 1) {
1832  int NewLength = length + BUFSIZ;
1833  if (char *NewBuffer = (char *)realloc(cmdLine, NewLength)) {
1834  length = NewLength;
1835  cmdLine = NewBuffer;
1836  }
1837  else {
1838  esyslog("ERROR: out of memory");
1839  Close();
1840  break;
1841  }
1842  }
1843  cmdLine[numChars++] = c;
1844  cmdLine[numChars] = 0;
1845  }
1846  lastActivity = time(NULL);
1847  }
1848  else if (r <= 0) {
1849  isyslog("lost connection to SVDRP client");
1850  Close();
1851  }
1852  }
1853  if (Setup.SVDRPTimeout && time(NULL) - lastActivity > Setup.SVDRPTimeout) {
1854  isyslog("timeout on SVDRP connection");
1855  Close(true, true);
1856  }
1857  return true;
1858  }
1859  return false;
1860 }
1861 
1862 void cSVDRP::SetGrabImageDir(const char *GrabImageDir)
1863 {
1864  free(grabImageDir);
1865  grabImageDir = GrabImageDir ? strdup(GrabImageDir) : NULL;
1866 }
1867 
1868 //TODO more than one connection???
bool Replaying(void) const
Returns true if we are currently replaying.
Definition: device.c:1217
void SetModified(void)
Definition: timers.c:768
unsigned char uchar
Definition: tools.h:30
void CmdMODT(const char *Option)
Definition: svdrp.c:1298
virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
Definition: plugin.c:130
const char * HelpPages[]
Definition: svdrp.c:184
~cSocket()
Definition: svdrp.c:51
const char * Message(void)
Definition: svdrp.h:39
int Number(void) const
Definition: channels.h:197
bool Update(bool Wait=false)
Triggers an update of the list of recordings, which will run as a separate thread if Wait is false...
Definition: recording.c:1498
int port
Definition: svdrp.h:18
void CmdLSTT(const char *Option)
Definition: svdrp.c:1207
cChannels Channels
Definition: channels.c:864
void CmdCLRE(const char *Option)
Definition: svdrp.c:558
static tChannelID FromString(const char *s)
Definition: channels.c:26
bool ToggleMute(void)
Turns the volume off or on and returns the new mute state.
Definition: device.c:891
#define dsyslog(a...)
Definition: tools.h:36
cString AddDirectory(const char *DirName, const char *FileName)
Definition: tools.c:350
int Index(void) const
Definition: tools.c:1989
void CmdCPYR(const char *Option)
Definition: svdrp.c:686
bool isnumber(const char *s)
Definition: tools.c:312
int MaxNumber(void)
Definition: channels.h:256
Definition: epg.h:40
cRecordings recordings
Definition: svdrp.h:46
bool Ready(bool Wait=true)
Definition: tools.c:1591
void CmdPLAY(const char *Option)
Definition: svdrp.c:1490
#define LOG_ERROR
Definition: tools.h:38
const cRecordingInfo * Info(void) const
Definition: recording.h:149
cEITScanner EITScanner
Definition: eitscan.c:90
void Add(cListObject *Object, cListObject *After=NULL)
Definition: tools.c:2014
const char * Name(void)
Definition: plugin.h:34
static cString ToText(const cChannel *Channel)
Definition: channels.c:540
virtual const char ** SVDRPHelpPages(void)
Definition: plugin.c:125
double FramesPerSecond(void) const
Definition: recording.h:153
virtual const char * Version(void)=0
void CmdGRAB(const char *Option)
Definition: svdrp.c:818
const char * Title(char Delimiter= ' ', bool NewIndicator=false, int Level=-1) const
Definition: recording.c:1060
static const char * SystemCharacterTable(void)
Definition: tools.h:164
bool MakeDirs(const char *FileName, bool IsDirectory)
Definition: tools.c:445
cTimers Timers
Definition: timers.c:694
static void SetDisableUntil(time_t Time)
Definition: eit.c:375
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition: tools.c:1080
int sock
Definition: svdrp.h:19
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:293
static eKeys FromString(const char *Name)
Definition: keys.c:123
Definition: plugin.h:20
#define esyslog(a...)
Definition: tools.h:34
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:1064
cTimer * Timer(void)
Definition: menu.h:249
T * Get(int Index) const
Definition: tools.h:491
bool Parse(const char *s)
Definition: timers.c:292
cPUTEhandler * PUTEhandler
Definition: svdrp.h:47
void CmdMOVC(const char *Option)
Definition: svdrp.c:1335
#define LOG_ERROR_STR(s)
Definition: tools.h:39
bool GroupSep(void) const
Definition: channels.h:199
const char * GetHelpTopic(const char *HelpPage)
Definition: svdrp.c:361
const char * GetHelpPage(const char *Cmd, const char **p)
Definition: svdrp.c:379
#define MAXHELPTOPIC
Definition: svdrp.c:180
int Status(void)
Definition: svdrp.h:38
#define VDRVERSION
Definition: config.h:25
void ReNumber(void)
Definition: channels.c:945
time_t StartTime(void) const
Definition: timers.c:497
void ForceScan(void)
Definition: eitscan.c:113
static char * grabImageDir
Definition: svdrp.h:52
int Count(void) const
Definition: tools.h:485
static bool Dump(FILE *f=NULL, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0)
Definition: epg.c:1257
void CmdLSTC(const char *Option)
Definition: svdrp.c:1022
cTimer * GetNextActiveTimer(void)
Definition: timers.c:757
void Add(cTimer *Timer, cTimer *After=NULL)
Definition: timers.c:774
int Accept(void)
Definition: svdrp.c:107
bool LocalhostOnly(void)
Definition: config.c:283
cSVDRP(int Port)
Definition: svdrp.c:394
static const cSchedules * Schedules(cSchedulesLock &SchedulesLock)
Caller must provide a cSchedulesLock which has to survive the entire time the returned cSchedules is ...
Definition: epg.c:1201
void CmdNEWT(const char *Option)
Definition: svdrp.c:1452
bool Send(const char *s, int length=-1)
Definition: svdrp.c:426
~cSVDRP()
Definition: svdrp.c:405
void CmdHITK(const char *Option)
Definition: svdrp.c:983
#define MALLOC(type, size)
Definition: tools.h:46
time_t lastActivity
Definition: svdrp.h:51
static void SetRecording(const char *FileName)
Definition: menu.c:5452
static int CurrentVolume(void)
Definition: device.h:595
void CmdEDIT(const char *Option)
Definition: svdrp.c:792
char * cmdLine
Definition: svdrp.h:50
int length
Definition: svdrp.h:49
Definition: keys.h:55
Definition: timers.h:27
bool IsOpen(void)
Definition: tools.h:395
bool ChangeName(const char *NewName)
Changes the name of this recording to the given value.
Definition: recording.c:1228
virtual const char * Description(void)=0
T * Last(void) const
Definition: tools.h:493
Definition: epg.h:40
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:1156
static cString static cString vsprintf(const char *fmt, va_list &ap)
Definition: tools.c:1093
bool Transferring(void) const
Returns true if we are currently in Transfer Mode.
Definition: device.c:1222
bool Recording(void) const
Definition: timers.h:52
cTimer * GetTimer(cTimer *Timer)
Definition: timers.c:704
cRecording * GetByName(const char *FileName)
Definition: recording.c:1509
static int CurrentChannel(void)
Returns the number of the current channel on the primary device.
Definition: device.h:323
const char * Name(void) const
Definition: channels.c:123
void CmdNEXT(const char *Option)
Definition: svdrp.c:1471
cSVDRPhosts SVDRPhosts
Definition: config.c:281
T * Next(const T *object) const
Definition: tools.h:495
bool Open(void)
Definition: svdrp.c:64
ssize_t safe_write(int filedes, const void *buffer, size_t size)
Definition: tools.c:65
cSocket socket
Definition: svdrp.h:44
void Execute(char *Cmd)
Definition: svdrp.c:1730
#define EITDISABLETIME
Definition: svdrp.c:181
~cPUTEhandler()
Definition: svdrp.c:146
bool Process(const char *s)
Definition: svdrp.c:152
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:920
bool Parse(const char *s)
Definition: channels.c:609
bool SwitchChannel(const cChannel *Channel, bool LiveView)
Switches the device to the given Channel, initiating transfer mode if necessary.
Definition: device.c:697
#define FOLDERDELIMCHAR
Definition: recording.h:21
bool Write(FILE *f, const char *Prefix="") const
Definition: recording.c:510
static void SetEnabled(bool Enabled)
Definition: remote.h:50
int queue
Definition: svdrp.h:20
int GetNextNormal(int Idx)
Definition: channels.c:929
void CmdDELT(const char *Option)
Definition: svdrp.c:763
void CmdCHAN(const char *Option)
Definition: svdrp.c:492
int SVDRPTimeout
Definition: config.h:296
bool HasFlags(uint Flags) const
Definition: timers.c:664
#define CMD(c)
Definition: svdrp.c:1728
Definition: epg.h:40
void Cleanup(time_t Time)
Definition: epg.c:1056
void CmdPLUG(const char *Option)
Definition: svdrp.c:1533
int GetPrevNormal(int Idx)
Definition: channels.c:937
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
Definition: recording.c:2987
static bool Read(FILE *f=NULL)
Definition: epg.c:1284
Definition: skins.h:24
void CmdUPDR(const char *Option)
Definition: svdrp.c:1700
bool Delete(void)
Changes the file name so that it will no longer be visible in the "Recordings" menu Returns false in ...
Definition: recording.c:1254
cSetup Setup
Definition: config.c:373
bool Put(uint64_t Code, bool Repeat=false, bool Release=false)
Definition: remote.c:124
bool Open(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
Definition: tools.c:1549
const char * NextLine(void)
Returns the next line of encoded data (terminated by &#39;\0&#39;), or NULL if there is no more encoded data...
Definition: tools.c:1310
static void Cleanup(bool Force=false)
Definition: epg.c:1219
tChannelID GetChannelID(void) const
Definition: channels.h:208
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:376
void CmdMODC(const char *Option)
Definition: svdrp.c:1260
void SetModified(bool ByUser=false)
Definition: channels.c:1102
void CmdDELC(const char *Option)
Definition: svdrp.c:623
cRecordingsHandler RecordingsHandler
Definition: recording.c:1910
static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
Definition: svdrp.c:670
cChannel * GetByChannelID(tChannelID ChannelID, bool TryWithoutRid=false, bool TryWithoutPolarization=false)
Definition: channels.c:1023
cString & CompactChars(char c)
Compact any sequence of characters &#39;c&#39; to a single character, and strip all of them from the beginnin...
Definition: tools.c:1074
static void Launch(cControl *Control)
Definition: player.c:79
void CmdHELP(const char *Option)
Definition: svdrp.c:955
cSocket(int Port, int Queue=1)
Definition: svdrp.c:44
int BeingEdited(void)
Definition: timers.h:121
static bool Enabled(void)
Definition: remote.h:49
void CmdREMO(const char *Option)
Definition: svdrp.c:1629
const cSchedule * GetSchedule(tChannelID ChannelID) const
Definition: epg.c:1328
void CmdUPDT(const char *Option)
Definition: svdrp.c:1668
cString ToDescr(void) const
Definition: timers.c:179
static int VideoDiskSpace(int *FreeMB=NULL, int *UsedMB=NULL)
Definition: videodir.c:140
void CmdPUTE(const char *Option)
Definition: svdrp.c:1604
bool HasUniqueChannelID(cChannel *NewChannel, cChannel *OldChannel=NULL)
Definition: channels.c:1064
int numChars
Definition: svdrp.h:48
void CmdVOLU(const char *Option)
Definition: svdrp.c:1706
T * First(void) const
Definition: tools.h:492
void Del(cListObject *Object, bool DeleteObject=true)
Definition: tools.c:2046
static void Attach(void)
Definition: player.c:87
void Reply(int Code, const char *fmt,...) __attribute__((format(printf
Definition: svdrp.c:438
cChannel * GetByNumber(int Number, int SkipGap=0)
Definition: channels.c:995
static cDevice * PrimaryDevice(void)
Returns the primary device.
Definition: device.h:137
void SetFlags(uint Flags)
Definition: timers.c:649
void CmdMESG(const char *Option)
Definition: svdrp.c:1249
Definition: epg.h:143
void Delete(void)
Definition: recording.c:331
virtual void Move(int From, int To)
Definition: tools.c:2058
cRecordings Recordings
Any access to Recordings that loops through the list of recordings needs to hold a thread lock on thi...
Definition: recording.c:1365
void DELETENULL(T *&p)
Definition: tools.h:48
char * skipspace(const char *s)
Definition: tools.h:200
static void SetCurrentChannel(const cChannel *Channel)
Sets the number of the current channel on the primary device, without actually switching to it...
Definition: device.h:325
static void SetGrabImageDir(const char *GrabImageDir)
Definition: svdrp.c:1862
const char * Name(void) const
Returns the full name of the recording (without the video directory.
Definition: recording.h:142
static bool ClearAll(void)
Definition: epg.c:1243
#define isyslog(a...)
Definition: tools.h:35
void CmdLSTR(const char *Option)
Definition: svdrp.c:1147
static cRecordControl * GetRecordControl(const char *FileName)
Definition: menu.c:5272
void DelByName(const char *FileName)
Definition: recording.c:1534
void CmdMOVR(const char *Option)
Definition: svdrp.c:1388
eDumpMode
Definition: epg.h:40
static cPlugin * GetPlugin(int Index)
Definition: plugin.c:457
ssize_t safe_read(int filedes, void *buffer, size_t size)
Definition: tools.c:53
bool Acceptable(in_addr_t Address)
Definition: config.c:294
void ClrFlags(uint Flags)
Definition: timers.c:654
static const tChannelID InvalidID
Definition: channels.h:75
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
Definition: recording.c:2060
bool SwitchTo(int Number)
Definition: channels.c:1074
void Close(bool SendReply=false, bool Timeout=false)
Definition: svdrp.c:411
cString ToText(bool UseChannelID=false) const
Definition: timers.c:171
void Close(void)
Definition: svdrp.c:56
void CmdDELR(const char *Option)
Definition: svdrp.c:736
char * ExchangeChars(char *s, bool ToFileSystem)
Definition: recording.c:582
cPUTEhandler(void)
Definition: svdrp.c:133
void Dump(FILE *f, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0) const
Definition: epg.c:1067
void CmdNEWC(const char *Option)
Definition: svdrp.c:1428
void Close(void)
Definition: tools.c:1582
void CmdSTAT(const char *Option)
Definition: svdrp.c:1653
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual &#39;...
Definition: recording.c:1042
tChannelID & ClrRid(void)
Definition: channels.h:66
void Del(cTimer *Timer, bool DeleteObject=true)
Definition: timers.c:786
void void PrintHelpTopics(const char **hp)
Definition: svdrp.c:466
static void Shutdown(void)
Definition: player.c:100
bool Process(void)
Definition: svdrp.c:1784
char * strreplace(char *s, char c1, char c2)
Definition: tools.c:139
#define VOLUMEDELTA
Definition: device.h:33
int BeingEdited(void)
Definition: channels.h:251
void AddByName(const char *FileName, bool TriggerUpdate=true)
Definition: recording.c:1521
eKeys
Definition: keys.h:16
bool IsPesRecording(void) const
Definition: recording.h:167
int IsInUse(void) const
Checks whether this recording is currently in use and therefore shall not be tampered with...
Definition: recording.c:1317
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:1933
void CmdLSTE(const char *Option)
Definition: svdrp.c:1064
static const char * ToString(eKeys Key, bool Translate=false)
Definition: keys.c:138
cFile file
Definition: svdrp.h:45
Definition: tools.h:168
void CmdSCAN(const char *Option)
Definition: svdrp.c:1647
static bool CallPlugin(const char *Plugin)
Initiates calling the given plugin&#39;s main menu function.
Definition: remote.c:151
cSkins Skins
Definition: skins.c:219