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