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