vdr  1.7.27
svdrp.c
Go to the documentation of this file.
00001 /*
00002  * svdrp.c: Simple Video Disk Recorder Protocol
00003  *
00004  * See the main source file 'vdr.c' for copyright information and
00005  * how to reach the author.
00006  *
00007  * The "Simple Video Disk Recorder Protocol" (SVDRP) was inspired
00008  * by the "Simple Mail Transfer Protocol" (SMTP) and is fully ASCII
00009  * text based. Therefore you can simply 'telnet' to your VDR port
00010  * and interact with the Video Disk Recorder - or write a full featured
00011  * graphical interface that sits on top of an SVDRP connection.
00012  *
00013  * $Id: svdrp.c 2.16 2012/03/04 12:05:56 kls Exp $
00014  */
00015 
00016 #include "svdrp.h"
00017 #include <arpa/inet.h>
00018 #include <ctype.h>
00019 #include <errno.h>
00020 #include <fcntl.h>
00021 #include <netinet/in.h>
00022 #include <stdarg.h>
00023 #include <stdio.h>
00024 #include <stdlib.h>
00025 #include <string.h>
00026 #include <sys/socket.h>
00027 #include <sys/time.h>
00028 #include <unistd.h>
00029 #include "channels.h"
00030 #include "config.h"
00031 #include "cutter.h"
00032 #include "device.h"
00033 #include "eitscan.h"
00034 #include "filetransfer.h"
00035 #include "keys.h"
00036 #include "menu.h"
00037 #include "plugin.h"
00038 #include "remote.h"
00039 #include "skins.h"
00040 #include "timers.h"
00041 #include "tools.h"
00042 #include "videodir.h"
00043 
00044 // --- cSocket ---------------------------------------------------------------
00045 
00046 cSocket::cSocket(int Port, int Queue)
00047 {
00048   port = Port;
00049   sock = -1;
00050   queue = Queue;
00051 }
00052 
00053 cSocket::~cSocket()
00054 {
00055   Close();
00056 }
00057 
00058 void cSocket::Close(void)
00059 {
00060   if (sock >= 0) {
00061      close(sock);
00062      sock = -1;
00063      }
00064 }
00065 
00066 bool cSocket::Open(void)
00067 {
00068   if (sock < 0) {
00069      // create socket:
00070      sock = socket(PF_INET, SOCK_STREAM, 0);
00071      if (sock < 0) {
00072         LOG_ERROR;
00073         port = 0;
00074         return false;
00075         }
00076      // allow it to always reuse the same port:
00077      int ReUseAddr = 1;
00078      setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr, sizeof(ReUseAddr));
00079      //
00080      struct sockaddr_in name;
00081      name.sin_family = AF_INET;
00082      name.sin_port = htons(port);
00083      name.sin_addr.s_addr = SVDRPhosts.LocalhostOnly() ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY);
00084      if (bind(sock, (struct sockaddr *)&name, sizeof(name)) < 0) {
00085         LOG_ERROR;
00086         Close();
00087         return false;
00088         }
00089      // make it non-blocking:
00090      int oldflags = fcntl(sock, F_GETFL, 0);
00091      if (oldflags < 0) {
00092         LOG_ERROR;
00093         return false;
00094         }
00095      oldflags |= O_NONBLOCK;
00096      if (fcntl(sock, F_SETFL, oldflags) < 0) {
00097         LOG_ERROR;
00098         return false;
00099         }
00100      // listen to the socket:
00101      if (listen(sock, queue) < 0) {
00102         LOG_ERROR;
00103         return false;
00104         }
00105      }
00106   return true;
00107 }
00108 
00109 int cSocket::Accept(void)
00110 {
00111   if (Open()) {
00112      struct sockaddr_in clientname;
00113      uint size = sizeof(clientname);
00114      int newsock = accept(sock, (struct sockaddr *)&clientname, &size);
00115      if (newsock > 0) {
00116         bool accepted = SVDRPhosts.Acceptable(clientname.sin_addr.s_addr);
00117         if (!accepted) {
00118            const char *s = "Access denied!\n";
00119            if (write(newsock, s, strlen(s)) < 0)
00120               LOG_ERROR;
00121            close(newsock);
00122            newsock = -1;
00123            }
00124         isyslog("connect from %s, port %hu - %s", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port), accepted ? "accepted" : "DENIED");
00125         }
00126      else if (errno != EINTR && errno != EAGAIN)
00127         LOG_ERROR;
00128      return newsock;
00129      }
00130   return -1;
00131 }
00132 
00133 // --- cPUTEhandler ----------------------------------------------------------
00134 
00135 cPUTEhandler::cPUTEhandler(void)
00136 {
00137   if ((f = tmpfile()) != NULL) {
00138      status = 354;
00139      message = "Enter EPG data, end with \".\" on a line by itself";
00140      }
00141   else {
00142      LOG_ERROR;
00143      status = 554;
00144      message = "Error while opening temporary file";
00145      }
00146 }
00147 
00148 cPUTEhandler::~cPUTEhandler()
00149 {
00150   if (f)
00151      fclose(f);
00152 }
00153 
00154 bool cPUTEhandler::Process(const char *s)
00155 {
00156   if (f) {
00157      if (strcmp(s, ".") != 0) {
00158         fputs(s, f);
00159         fputc('\n', f);
00160         return true;
00161         }
00162      else {
00163         rewind(f);
00164         if (cSchedules::Read(f)) {
00165            cSchedules::Cleanup(true);
00166            status = 250;
00167            message = "EPG data processed";
00168            }
00169         else {
00170            status = 451;
00171            message = "Error while processing EPG data";
00172            }
00173         fclose(f);
00174         f = NULL;
00175         }
00176      }
00177   return false;
00178 }
00179 
00180 // --- cSVDRP ----------------------------------------------------------------
00181 
00182 #define MAXHELPTOPIC 10
00183 #define EITDISABLETIME 10 // seconds until EIT processing is enabled again after a CLRE command
00184                           // adjust the help for CLRE accordingly if changing this!
00185 
00186 const char *HelpPages[] = {
00187   "CHAN [ + | - | <number> | <name> | <id> ]\n"
00188   "    Switch channel up, down or to the given channel number, name or id.\n"
00189   "    Without option (or after successfully switching to the channel)\n"
00190   "    it returns the current channel number and name.",
00191   "CLRE [ <number> | <name> | <id> ]\n"
00192   "    Clear the EPG list of the given channel number, name or id.\n"
00193   "    Without option it clears the entire EPG list.\n"
00194   "    After a CLRE command, no further EPG processing is done for 10\n"
00195   "    seconds, so that data sent with subsequent PUTE commands doesn't\n"
00196   "    interfere with data from the broadcasters.",
00197   "CPYR <number> <new name>\n"
00198   "    Copy the recording with the given number. Before a recording can be\n"
00199   "    copied, an LSTR command must have been executed in order to retrieve\n"
00200   "    the recording numbers. The numbers don't change during subsequent CPYR\n"
00201   "    commands.",
00202   "DELC <number>\n"
00203   "    Delete channel.",
00204   "DELR <number>\n"
00205   "    Delete the recording with the given number. Before a recording can be\n"
00206   "    deleted, an LSTR command must have been executed in order to retrieve\n"
00207   "    the recording numbers. The numbers don't change during subsequent DELR\n"
00208   "    commands. CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n"
00209   "    RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
00210   "DELT <number>\n"
00211   "    Delete timer.",
00212   "EDIT <number>\n"
00213   "    Edit the recording with the given number. Before a recording can be\n"
00214   "    edited, an LSTR command must have been executed in order to retrieve\n"
00215   "    the recording numbers.",
00216   "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n"
00217   "    Grab the current frame and save it to the given file. Images can\n"
00218   "    be stored as JPEG or PNM, depending on the given file name extension.\n"
00219   "    The quality of the grabbed image can be in the range 0..100, where 100\n"
00220   "    (the default) means \"best\" (only applies to JPEG). The size parameters\n"
00221   "    define the size of the resulting image (default is full screen).\n"
00222   "    If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n"
00223   "    data will be sent to the SVDRP connection encoded in base64. The same\n"
00224   "    happens if '-' (a minus sign) is given as file name, in which case the\n"
00225   "    image format defaults to JPEG.",
00226   "HELP [ <topic> ]\n"
00227   "    The HELP command gives help info.",
00228   "HITK [ <key> ... ]\n"
00229   "    Hit the given remote control key. Without option a list of all\n"
00230   "    valid key names is given. If more than one key is given, they are\n"
00231   "    entered into the remote control queue in the given sequence. There\n"
00232   "    can be up to 31 keys.",
00233   "LSTC [ :groups | <number> | <name> | <id> ]\n"
00234   "    List channels. Without option, all channels are listed. Otherwise\n"
00235   "    only the given channel is listed. If a name is given, all channels\n"
00236   "    containing the given string as part of their name are listed.\n"
00237   "    If ':groups' is given, all channels are listed including group\n"
00238   "    separators. The channel number of a group separator is always 0.",
00239   "LSTE [ <channel> ] [ now | next | at <time> ]\n"
00240   "    List EPG data. Without any parameters all data of all channels is\n"
00241   "    listed. If a channel is given (either by number or by channel ID),\n"
00242   "    only data for that channel is listed. 'now', 'next', or 'at <time>'\n"
00243   "    restricts the returned data to present events, following events, or\n"
00244   "    events at the given time (which must be in time_t form).",
00245   "LSTR [ <number> ]\n"
00246   "    List recordings. Without option, all recordings are listed. Otherwise\n"
00247   "    the information for the given recording is listed.",
00248   "LSTT [ <number> ] [ id ]\n"
00249   "    List timers. Without option, all timers are listed. Otherwise\n"
00250   "    only the given timer is listed. If the keyword 'id' is given, the\n"
00251   "    channels will be listed with their unique channel ids instead of\n"
00252   "    their numbers.",
00253   "MESG <message>\n"
00254   "    Displays the given message on the OSD. The message will be queued\n"
00255   "    and displayed whenever this is suitable.\n",
00256   "MODC <number> <settings>\n"
00257   "    Modify a channel. Settings must be in the same format as returned\n"
00258   "    by the LSTC command.",
00259   "MODT <number> on | off | <settings>\n"
00260   "    Modify a timer. Settings must be in the same format as returned\n"
00261   "    by the LSTT command. The special keywords 'on' and 'off' can be\n"
00262   "    used to easily activate or deactivate a timer.",
00263   "MOVC <number> <to>\n"
00264   "    Move a channel to a new position.",
00265   "MOVR <number> <new name>\n"
00266   "    Move the recording with the given number. Before a recording can be\n"
00267   "    moved, an LSTR command must have been executed in order to retrieve\n"
00268   "    the recording numbers. The numbers don't change during subsequent MOVR\n"
00269   "    commands.",
00270   "NEWC <settings>\n"
00271   "    Create a new channel. Settings must be in the same format as returned\n"
00272   "    by the LSTC command.",
00273   "NEWT <settings>\n"
00274   "    Create a new timer. Settings must be in the same format as returned\n"
00275   "    by the LSTT command. It is an error if a timer with the same channel,\n"
00276   "    day, start and stop time already exists.",
00277   "NEXT [ abs | rel ]\n"
00278   "    Show the next timer event. If no option is given, the output will be\n"
00279   "    in human readable form. With option 'abs' the absolute time of the next\n"
00280   "    event will be given as the number of seconds since the epoch (time_t\n"
00281   "    format), while with option 'rel' the relative time will be given as the\n"
00282   "    number of seconds from now until the event. If the absolute time given\n"
00283   "    is smaller than the current time, or if the relative time is less than\n"
00284   "    zero, this means that the timer is currently recording and has started\n"
00285   "    at the given time. The first value in the resulting line is the number\n"
00286   "    of the timer.",
00287   "PLAY <number> [ begin | <position> ]\n"
00288   "    Play the recording with the given number. Before a recording can be\n"
00289   "    played, an LSTR command must have been executed in order to retrieve\n"
00290   "    the recording numbers.\n"
00291   "    The keyword 'begin' plays the recording from its very beginning, while\n"
00292   "    a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n"
00293   "    position. If neither 'begin' nor a <position> are given, replay is resumed\n"
00294   "    at the position where any previous replay was stopped, or from the beginning\n"
00295   "    by default. To control or stop the replay session, use the usual remote\n"
00296   "    control keypresses via the HITK command.",
00297   "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n"
00298   "    Send a command to a plugin.\n"
00299   "    The PLUG command without any parameters lists all plugins.\n"
00300   "    If only a name is given, all commands known to that plugin are listed.\n"
00301   "    If a command is given (optionally followed by parameters), that command\n"
00302   "    is sent to the plugin, and the result will be displayed.\n"
00303   "    The keyword 'help' lists all the SVDRP commands known to the named plugin.\n"
00304   "    If 'help' is followed by a command, the detailed help for that command is\n"
00305   "    given. The keyword 'main' initiates a call to the main menu function of the\n"
00306   "    given plugin.\n",
00307   "PUTE [ file ]\n"
00308   "    Put data into the EPG list. The data entered has to strictly follow the\n"
00309   "    format defined in vdr(5) for the 'epg.data' file.  A '.' on a line\n"
00310   "    by itself terminates the input and starts processing of the data (all\n"
00311   "    entered data is buffered until the terminating '.' is seen).\n"
00312   "    If a file name is given, epg data will be read from this file (which\n"
00313   "    must be accessible under the given name from the machine VDR is running\n"
00314   "    on). In case of file input, no terminating '.' shall be given.\n",
00315   "REMO [ on | off ]\n"
00316   "    Turns the remote control on or off. Without a parameter, the current\n"
00317   "    status of the remote control is reported.",
00318   "SCAN\n"
00319   "    Forces an EPG scan. If this is a single DVB device system, the scan\n"
00320   "    will be done on the primary device unless it is currently recording.",
00321   "STAT disk\n"
00322   "    Return information about disk usage (total, free, percent).",
00323   "UPDT <settings>\n"
00324   "    Updates a timer. Settings must be in the same format as returned\n"
00325   "    by the LSTT command. If a timer with the same channel, day, start\n"
00326   "    and stop time does not yet exists, it will be created.",
00327   "UPDR\n"
00328   "    Initiates a re-read of the recordings directory, which is the SVDRP\n"
00329   "    equivalent to 'touch .update'.",
00330   "VOLU [ <number> | + | - | mute ]\n"
00331   "    Set the audio volume to the given number (which is limited to the range\n"
00332   "    0...255). If the special options '+' or '-' are given, the volume will\n"
00333   "    be turned up or down, respectively. The option 'mute' will toggle the\n"
00334   "    audio muting. If no option is given, the current audio volume level will\n"
00335   "    be returned.",
00336   "QUIT\n"
00337   "    Exit vdr (SVDRP).\n"
00338   "    You can also hit Ctrl-D to exit.",
00339   NULL
00340   };
00341 
00342 /* SVDRP Reply Codes:
00343 
00344  214 Help message
00345  215 EPG or recording data record
00346  216 Image grab data (base 64)
00347  220 VDR service ready
00348  221 VDR service closing transmission channel
00349  250 Requested VDR action okay, completed
00350  354 Start sending EPG data
00351  451 Requested action aborted: local error in processing
00352  500 Syntax error, command unrecognized
00353  501 Syntax error in parameters or arguments
00354  502 Command not implemented
00355  504 Command parameter not implemented
00356  550 Requested action not taken
00357  554 Transaction failed
00358  900 Default plugin reply code
00359  901..999 Plugin specific reply codes
00360 
00361 */
00362 
00363 const char *GetHelpTopic(const char *HelpPage)
00364 {
00365   static char topic[MAXHELPTOPIC];
00366   const char *q = HelpPage;
00367   while (*q) {
00368         if (isspace(*q)) {
00369            uint n = q - HelpPage;
00370            if (n >= sizeof(topic))
00371               n = sizeof(topic) - 1;
00372            strncpy(topic, HelpPage, n);
00373            topic[n] = 0;
00374            return topic;
00375            }
00376         q++;
00377         }
00378   return NULL;
00379 }
00380 
00381 const char *GetHelpPage(const char *Cmd, const char **p)
00382 {
00383   if (p) {
00384      while (*p) {
00385            const char *t = GetHelpTopic(*p);
00386            if (strcasecmp(Cmd, t) == 0)
00387               return *p;
00388            p++;
00389            }
00390      }
00391   return NULL;
00392 }
00393 
00394 char *cSVDRP::grabImageDir = NULL;
00395 
00396 cSVDRP::cSVDRP(int Port)
00397 :socket(Port)
00398 {
00399   PUTEhandler = NULL;
00400   numChars = 0;
00401   length = BUFSIZ;
00402   cmdLine = MALLOC(char, length);
00403   lastActivity = 0;
00404   isyslog("SVDRP listening on port %d", Port);
00405 }
00406 
00407 cSVDRP::~cSVDRP()
00408 {
00409   Close(true);
00410   free(cmdLine);
00411 }
00412 
00413 void cSVDRP::Close(bool SendReply, bool Timeout)
00414 {
00415   if (file.IsOpen()) {
00416      if (SendReply) {
00417         //TODO how can we get the *full* hostname?
00418         char buffer[BUFSIZ];
00419         gethostname(buffer, sizeof(buffer));
00420         Reply(221, "%s closing connection%s", buffer, Timeout ? " (timeout)" : "");
00421         }
00422      isyslog("closing SVDRP connection"); //TODO store IP#???
00423      file.Close();
00424      DELETENULL(PUTEhandler);
00425      }
00426 }
00427 
00428 bool cSVDRP::Send(const char *s, int length)
00429 {
00430   if (length < 0)
00431      length = strlen(s);
00432   if (safe_write(file, s, length) < 0) {
00433      LOG_ERROR;
00434      Close();
00435      return false;
00436      }
00437   return true;
00438 }
00439 
00440 void cSVDRP::Reply(int Code, const char *fmt, ...)
00441 {
00442   if (file.IsOpen()) {
00443      if (Code != 0) {
00444         va_list ap;
00445         va_start(ap, fmt);
00446         cString buffer = cString::sprintf(fmt, ap);
00447         va_end(ap);
00448         const char *s = buffer;
00449         while (s && *s) {
00450               const char *n = strchr(s, '\n');
00451               char cont = ' ';
00452               if (Code < 0 || n && *(n + 1)) // trailing newlines don't count!
00453                  cont = '-';
00454               char number[16];
00455               sprintf(number, "%03d%c", abs(Code), cont);
00456               if (!(Send(number) && Send(s, n ? n - s : -1) && Send("\r\n")))
00457                  break;
00458               s = n ? n + 1 : NULL;
00459               }
00460         }
00461      else {
00462         Reply(451, "Zero return code - looks like a programming error!");
00463         esyslog("SVDRP: zero return code!");
00464         }
00465      }
00466 }
00467 
00468 void cSVDRP::PrintHelpTopics(const char **hp)
00469 {
00470   int NumPages = 0;
00471   if (hp) {
00472      while (*hp) {
00473            NumPages++;
00474            hp++;
00475            }
00476      hp -= NumPages;
00477      }
00478   const int TopicsPerLine = 5;
00479   int x = 0;
00480   for (int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
00481       char buffer[TopicsPerLine * MAXHELPTOPIC + 5];
00482       char *q = buffer;
00483       q += sprintf(q, "    ");
00484       for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
00485           const char *topic = GetHelpTopic(hp[(y * TopicsPerLine + x)]);
00486           if (topic)
00487              q += sprintf(q, "%*s", -MAXHELPTOPIC, topic);
00488           }
00489       x = 0;
00490       Reply(-214, "%s", buffer);
00491       }
00492 }
00493 
00494 void cSVDRP::CmdCHAN(const char *Option)
00495 {
00496   if (*Option) {
00497      int n = -1;
00498      int d = 0;
00499      if (isnumber(Option)) {
00500         int o = strtol(Option, NULL, 10);
00501         if (o >= 1 && o <= Channels.MaxNumber())
00502            n = o;
00503         }
00504      else if (strcmp(Option, "-") == 0) {
00505         n = cDevice::CurrentChannel();
00506         if (n > 1) {
00507            n--;
00508            d = -1;
00509            }
00510         }
00511      else if (strcmp(Option, "+") == 0) {
00512         n = cDevice::CurrentChannel();
00513         if (n < Channels.MaxNumber()) {
00514            n++;
00515            d = 1;
00516            }
00517         }
00518      else {
00519         cChannel *channel = Channels.GetByChannelID(tChannelID::FromString(Option));
00520         if (channel)
00521            n = channel->Number();
00522         else {
00523            for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) {
00524                if (!channel->GroupSep()) {
00525                   if (strcasecmp(channel->Name(), Option) == 0) {
00526                      n = channel->Number();
00527                      break;
00528                      }
00529                   }
00530                }
00531            }
00532         }
00533      if (n < 0) {
00534         Reply(501, "Undefined channel \"%s\"", Option);
00535         return;
00536         }
00537      if (!d) {
00538         cChannel *channel = Channels.GetByNumber(n);
00539         if (channel) {
00540            if (!cDevice::PrimaryDevice()->SwitchChannel(channel, true)) {
00541               Reply(554, "Error switching to channel \"%d\"", channel->Number());
00542               return;
00543               }
00544            }
00545         else {
00546            Reply(550, "Unable to find channel \"%s\"", Option);
00547            return;
00548            }
00549         }
00550      else
00551         cDevice::SwitchChannel(d);
00552      }
00553   cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());
00554   if (channel)
00555      Reply(250, "%d %s", channel->Number(), channel->Name());
00556   else
00557      Reply(550, "Unable to find channel \"%d\"", cDevice::CurrentChannel());
00558 }
00559 
00560 void cSVDRP::CmdCLRE(const char *Option)
00561 {
00562   if (*Option) {
00563      tChannelID ChannelID = tChannelID::InvalidID;
00564      if (isnumber(Option)) {
00565         int o = strtol(Option, NULL, 10);
00566         if (o >= 1 && o <= Channels.MaxNumber())
00567            ChannelID = Channels.GetByNumber(o)->GetChannelID();
00568         }
00569      else {
00570         ChannelID = tChannelID::FromString(Option);
00571         if (ChannelID == tChannelID::InvalidID) {
00572            for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
00573                if (!Channel->GroupSep()) {
00574                   if (strcasecmp(Channel->Name(), Option) == 0) {
00575                      ChannelID = Channel->GetChannelID();
00576                      break;
00577                      }
00578                   }
00579                }
00580            }
00581         }
00582      if (!(ChannelID == tChannelID::InvalidID)) {
00583         cSchedulesLock SchedulesLock(true, 1000);
00584         cSchedules *s = (cSchedules *)cSchedules::Schedules(SchedulesLock);
00585         if (s) {
00586            cSchedule *Schedule = NULL;
00587            ChannelID.ClrRid();
00588            for (cSchedule *p = s->First(); p; p = s->Next(p)) {
00589                if (p->ChannelID() == ChannelID) {
00590                   Schedule = p;
00591                   break;
00592                   }
00593                }
00594            if (Schedule) {
00595               for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
00596                   if (ChannelID == Timer->Channel()->GetChannelID().ClrRid())
00597                      Timer->SetEvent(NULL);
00598                   }
00599               Schedule->Cleanup(INT_MAX);
00600               cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME);
00601               Reply(250, "EPG data of channel \"%s\" cleared", Option);
00602               }
00603            else {
00604               Reply(550, "No EPG data found for channel \"%s\"", Option);
00605               return;
00606               }
00607            }
00608         else
00609            Reply(451, "Can't get EPG data");
00610         }
00611      else
00612         Reply(501, "Undefined channel \"%s\"", Option);
00613      }
00614   else {
00615      cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME);
00616      if (cSchedules::ClearAll()) {
00617         Reply(250, "EPG data cleared");
00618         cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME);
00619         }
00620      else
00621         Reply(451, "Error while clearing EPG data");
00622      }
00623 }
00624 
00625 void cSVDRP::CmdCPYR(const char *Option)
00626 {
00627   if (*Option) {
00628      char *tail;
00629      int n = strtol(Option, &tail, 10);
00630      cRecording *recording = Recordings.Get(n - 1);
00631      if (recording && tail && tail != Option) {
00632         char *oldName = strdup(recording->Name());
00633         tail = skipspace(tail);
00634         if (!cFileTransfer::Active()) {
00635            if (cFileTransfer::Start(recording, tail, true))
00636               Reply(250, "Copying recording \"%s\" to \"%s\"", oldName, tail);
00637            else
00638               Reply(554, "Can't start file transfer");
00639            }
00640         else
00641            Reply(554, "File transfer already active");
00642         free(oldName);
00643         }
00644      else
00645         Reply(550, "Recording \"%d\" not found%s", n, Recordings.Count() ? "" : " (use LSTR before copying)");
00646      }
00647   else
00648      Reply(501, "Invalid Option \"%s\"", Option);
00649 }
00650 
00651 void cSVDRP::CmdDELC(const char *Option)
00652 {
00653   if (*Option) {
00654      if (isnumber(Option)) {
00655         if (!Channels.BeingEdited()) {
00656            cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10));
00657            if (channel) {
00658               for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) {
00659                   if (timer->Channel() == channel) {
00660                      Reply(550, "Channel \"%s\" is in use by timer %d", Option, timer->Index() + 1);
00661                      return;
00662                      }
00663                   }
00664               int CurrentChannelNr = cDevice::CurrentChannel();
00665               cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
00666               if (CurrentChannel && channel == CurrentChannel) {
00667                  int n = Channels.GetNextNormal(CurrentChannel->Index());
00668                  if (n < 0)
00669                     n = Channels.GetPrevNormal(CurrentChannel->Index());
00670                  CurrentChannel = Channels.Get(n);
00671                  CurrentChannelNr = 0; // triggers channel switch below
00672                  }
00673               Channels.Del(channel);
00674               Channels.ReNumber();
00675               Channels.SetModified(true);
00676               isyslog("channel %s deleted", Option);
00677               if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
00678                  if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
00679                     Channels.SwitchTo(CurrentChannel->Number());
00680                  else
00681                     cDevice::SetCurrentChannel(CurrentChannel);
00682                  }
00683               Reply(250, "Channel \"%s\" deleted", Option);
00684               }
00685            else
00686               Reply(501, "Channel \"%s\" not defined", Option);
00687            }
00688         else
00689            Reply(550, "Channels are being edited - try again later");
00690         }
00691      else
00692         Reply(501, "Error in channel number \"%s\"", Option);
00693      }
00694   else
00695      Reply(501, "Missing channel number");
00696 }
00697 
00698 void cSVDRP::CmdDELR(const char *Option)
00699 {
00700   if (*Option) {
00701      if (isnumber(Option)) {
00702         cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
00703         if (recording) {
00704            cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
00705            if (!rc) {
00706               if (!cCutter::Active(recording->FileName())) {
00707                  if (recording->Delete()) {
00708                     Reply(250, "Recording \"%s\" deleted", Option);
00709                     ::Recordings.DelByName(recording->FileName());
00710                     }
00711                  else
00712                     Reply(554, "Error while deleting recording!");
00713                  }
00714               else
00715                  Reply(550, "Recording \"%s\" is being edited", Option);
00716               }
00717            else
00718               Reply(550, "Recording \"%s\" is in use by timer %d", Option, rc->Timer()->Index() + 1);
00719            }
00720         else
00721            Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)");
00722         }
00723      else
00724         Reply(501, "Error in recording number \"%s\"", Option);
00725      }
00726   else
00727      Reply(501, "Missing recording number");
00728 }
00729 
00730 void cSVDRP::CmdDELT(const char *Option)
00731 {
00732   if (*Option) {
00733      if (isnumber(Option)) {
00734         if (!Timers.BeingEdited()) {
00735            cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1);
00736            if (timer) {
00737               if (!timer->Recording()) {
00738                  isyslog("deleting timer %s", *timer->ToDescr());
00739                  Timers.Del(timer);
00740                  Timers.SetModified();
00741                  Reply(250, "Timer \"%s\" deleted", Option);
00742                  }
00743               else
00744                  Reply(550, "Timer \"%s\" is recording", Option);
00745               }
00746            else
00747               Reply(501, "Timer \"%s\" not defined", Option);
00748            }
00749         else
00750            Reply(550, "Timers are being edited - try again later");
00751         }
00752      else
00753         Reply(501, "Error in timer number \"%s\"", Option);
00754      }
00755   else
00756      Reply(501, "Missing timer number");
00757 }
00758 
00759 void cSVDRP::CmdEDIT(const char *Option)
00760 {
00761   if (*Option) {
00762      if (isnumber(Option)) {
00763         cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
00764         if (recording) {
00765            cMarks Marks;
00766            if (Marks.Load(recording->FileName(), recording->FramesPerSecond(), recording->IsPesRecording()) && Marks.Count()) {
00767               if (!cCutter::Active()) {
00768                  if (cCutter::Start(recording->FileName()))
00769                     Reply(250, "Editing recording \"%s\" [%s]", Option, recording->Title());
00770                  else
00771                     Reply(554, "Can't start editing process");
00772                  }
00773               else
00774                  Reply(554, "Editing process already active");
00775               }
00776            else
00777               Reply(554, "No editing marks defined");
00778            }
00779         else
00780            Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before editing)");
00781         }
00782      else
00783         Reply(501, "Error in recording number \"%s\"", Option);
00784      }
00785   else
00786      Reply(501, "Missing recording number");
00787 }
00788 
00789 void cSVDRP::CmdGRAB(const char *Option)
00790 {
00791   const char *FileName = NULL;
00792   bool Jpeg = true;
00793   int Quality = -1, SizeX = -1, SizeY = -1;
00794   if (*Option) {
00795      char buf[strlen(Option) + 1];
00796      char *p = strcpy(buf, Option);
00797      const char *delim = " \t";
00798      char *strtok_next;
00799      FileName = strtok_r(p, delim, &strtok_next);
00800      // image type:
00801      const char *Extension = strrchr(FileName, '.');
00802      if (Extension) {
00803         if (strcasecmp(Extension, ".jpg") == 0 || strcasecmp(Extension, ".jpeg") == 0)
00804            Jpeg = true;
00805         else if (strcasecmp(Extension, ".pnm") == 0)
00806            Jpeg = false;
00807         else {
00808            Reply(501, "Unknown image type \"%s\"", Extension + 1);
00809            return;
00810            }
00811         if (Extension == FileName)
00812            FileName = NULL;
00813         }
00814      else if (strcmp(FileName, "-") == 0)
00815         FileName = NULL;
00816      // image quality (and obsolete type):
00817      if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
00818         if (strcasecmp(p, "JPEG") == 0 || strcasecmp(p, "PNM") == 0) {
00819            // tolerate for backward compatibility
00820            p = strtok_r(NULL, delim, &strtok_next);
00821            }
00822         if (p) {
00823            if (isnumber(p))
00824               Quality = atoi(p);
00825            else {
00826               Reply(501, "Invalid quality \"%s\"", p);
00827               return;
00828               }
00829            }
00830         }
00831      // image size:
00832      if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
00833         if (isnumber(p))
00834            SizeX = atoi(p);
00835         else {
00836            Reply(501, "Invalid sizex \"%s\"", p);
00837            return;
00838            }
00839         if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
00840            if (isnumber(p))
00841               SizeY = atoi(p);
00842            else {
00843               Reply(501, "Invalid sizey \"%s\"", p);
00844               return;
00845               }
00846            }
00847         else {
00848            Reply(501, "Missing sizey");
00849            return;
00850            }
00851         }
00852      if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
00853         Reply(501, "Unexpected parameter \"%s\"", p);
00854         return;
00855         }
00856      // canonicalize the file name:
00857      char RealFileName[PATH_MAX];
00858      if (FileName) {
00859         if (grabImageDir) {
00860            cString s(FileName);
00861            FileName = s;
00862            const char *slash = strrchr(FileName, '/');
00863            if (!slash) {
00864               s = AddDirectory(grabImageDir, FileName);
00865               FileName = s;
00866               }
00867            slash = strrchr(FileName, '/'); // there definitely is one
00868            cString t(s);
00869            t.Truncate(slash - FileName);
00870            char *r = realpath(t, RealFileName);
00871            if (!r) {
00872               LOG_ERROR_STR(FileName);
00873               Reply(501, "Invalid file name \"%s\"", FileName);
00874               return;
00875               }
00876            strcat(RealFileName, slash);
00877            FileName = RealFileName;
00878            if (strncmp(FileName, grabImageDir, strlen(grabImageDir)) != 0) {
00879               Reply(501, "Invalid file name \"%s\"", FileName);
00880               return;
00881               }
00882            }
00883         else {
00884            Reply(550, "Grabbing to file not allowed (use \"GRAB -\" instead)");
00885            return;
00886            }
00887         }
00888      // actual grabbing:
00889      int ImageSize;
00890      uchar *Image = cDevice::PrimaryDevice()->GrabImage(ImageSize, Jpeg, Quality, SizeX, SizeY);
00891      if (Image) {
00892         if (FileName) {
00893            int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
00894            if (fd >= 0) {
00895               if (safe_write(fd, Image, ImageSize) == ImageSize) {
00896                  dsyslog("grabbed image to %s", FileName);
00897                  Reply(250, "Grabbed image %s", Option);
00898                  }
00899               else {
00900                  LOG_ERROR_STR(FileName);
00901                  Reply(451, "Can't write to '%s'", FileName);
00902                  }
00903               close(fd);
00904               }
00905            else {
00906               LOG_ERROR_STR(FileName);
00907               Reply(451, "Can't open '%s'", FileName);
00908               }
00909            }
00910         else {
00911            cBase64Encoder Base64(Image, ImageSize);
00912            const char *s;
00913            while ((s = Base64.NextLine()) != NULL)
00914                  Reply(-216, "%s", s);
00915            Reply(216, "Grabbed image %s", Option);
00916            }
00917         free(Image);
00918         }
00919      else
00920         Reply(451, "Grab image failed");
00921      }
00922   else
00923      Reply(501, "Missing filename");
00924 }
00925 
00926 void cSVDRP::CmdHELP(const char *Option)
00927 {
00928   if (*Option) {
00929      const char *hp = GetHelpPage(Option, HelpPages);
00930      if (hp)
00931         Reply(-214, "%s", hp);
00932      else {
00933         Reply(504, "HELP topic \"%s\" unknown", Option);
00934         return;
00935         }
00936      }
00937   else {
00938      Reply(-214, "This is VDR version %s", VDRVERSION);
00939      Reply(-214, "Topics:");
00940      PrintHelpTopics(HelpPages);
00941      cPlugin *plugin;
00942      for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++) {
00943          const char **hp = plugin->SVDRPHelpPages();
00944          if (hp)
00945             Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
00946          PrintHelpTopics(hp);
00947          }
00948      Reply(-214, "To report bugs in the implementation send email to");
00949      Reply(-214, "    vdr-bugs@tvdr.de");
00950      }
00951   Reply(214, "End of HELP info");
00952 }
00953 
00954 void cSVDRP::CmdHITK(const char *Option)
00955 {
00956   if (*Option) {
00957      char buf[strlen(Option) + 1];
00958      strcpy(buf, Option);
00959      const char *delim = " \t";
00960      char *strtok_next;
00961      char *p = strtok_r(buf, delim, &strtok_next);
00962      int NumKeys = 0;
00963      while (p) {
00964            eKeys k = cKey::FromString(p);
00965            if (k != kNone) {
00966               if (!cRemote::Put(k)) {
00967                  Reply(451, "Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
00968                  return;
00969                  }
00970               }
00971            else {
00972               Reply(504, "Unknown key: \"%s\"", p);
00973               return;
00974               }
00975            NumKeys++;
00976            p = strtok_r(NULL, delim, &strtok_next);
00977            }
00978      Reply(250, "Key%s \"%s\" accepted", NumKeys > 1 ? "s" : "", Option);
00979      }
00980   else {
00981      Reply(-214, "Valid <key> names for the HITK command:");
00982      for (int i = 0; i < kNone; i++) {
00983          Reply(-214, "    %s", cKey::ToString(eKeys(i)));
00984          }
00985      Reply(214, "End of key list");
00986      }
00987 }
00988 
00989 void cSVDRP::CmdLSTC(const char *Option)
00990 {
00991   bool WithGroupSeps = strcasecmp(Option, ":groups") == 0;
00992   if (*Option && !WithGroupSeps) {
00993      if (isnumber(Option)) {
00994         cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10));
00995         if (channel)
00996            Reply(250, "%d %s", channel->Number(), *channel->ToText());
00997         else
00998            Reply(501, "Channel \"%s\" not defined", Option);
00999         }
01000      else {
01001         cChannel *next = Channels.GetByChannelID(tChannelID::FromString(Option));
01002         if (!next) {
01003            for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) {
01004               if (!channel->GroupSep()) {
01005                  if (strcasestr(channel->Name(), Option)) {
01006                     if (next)
01007                        Reply(-250, "%d %s", next->Number(), *next->ToText());
01008                     next = channel;
01009                     }
01010                  }
01011               }
01012            }
01013         if (next)
01014            Reply(250, "%d %s", next->Number(), *next->ToText());
01015         else
01016            Reply(501, "Channel \"%s\" not defined", Option);
01017         }
01018      }
01019   else if (Channels.MaxNumber() >= 1) {
01020      for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) {
01021          if (WithGroupSeps)
01022             Reply(channel->Next() ? -250: 250, "%d %s", channel->GroupSep() ? 0 : channel->Number(), *channel->ToText());
01023          else if (!channel->GroupSep())
01024             Reply(channel->Number() < Channels.MaxNumber() ? -250 : 250, "%d %s", channel->Number(), *channel->ToText());
01025          }
01026      }
01027   else
01028      Reply(550, "No channels defined");
01029 }
01030 
01031 void cSVDRP::CmdLSTE(const char *Option)
01032 {
01033   cSchedulesLock SchedulesLock;
01034   const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
01035   if (Schedules) {
01036      const cSchedule* Schedule = NULL;
01037      eDumpMode DumpMode = dmAll;
01038      time_t AtTime = 0;
01039      if (*Option) {
01040         char buf[strlen(Option) + 1];
01041         strcpy(buf, Option);
01042         const char *delim = " \t";
01043         char *strtok_next;
01044         char *p = strtok_r(buf, delim, &strtok_next);
01045         while (p && DumpMode == dmAll) {
01046               if (strcasecmp(p, "NOW") == 0)
01047                  DumpMode = dmPresent;
01048               else if (strcasecmp(p, "NEXT") == 0)
01049                  DumpMode = dmFollowing;
01050               else if (strcasecmp(p, "AT") == 0) {
01051                  DumpMode = dmAtTime;
01052                  if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
01053                     if (isnumber(p))
01054                        AtTime = strtol(p, NULL, 10);
01055                     else {
01056                        Reply(501, "Invalid time");
01057                        return;
01058                        }
01059                     }
01060                  else {
01061                     Reply(501, "Missing time");
01062                     return;
01063                     }
01064                  }
01065               else if (!Schedule) {
01066                  cChannel* Channel = NULL;
01067                  if (isnumber(p))
01068                     Channel = Channels.GetByNumber(strtol(Option, NULL, 10));
01069                  else
01070                     Channel = Channels.GetByChannelID(tChannelID::FromString(Option));
01071                  if (Channel) {
01072                     Schedule = Schedules->GetSchedule(Channel);
01073                     if (!Schedule) {
01074                        Reply(550, "No schedule found");
01075                        return;
01076                        }
01077                     }
01078                  else {
01079                     Reply(550, "Channel \"%s\" not defined", p);
01080                     return;
01081                     }
01082                  }
01083               else {
01084                  Reply(501, "Unknown option: \"%s\"", p);
01085                  return;
01086                  }
01087               p = strtok_r(NULL, delim, &strtok_next);
01088               }
01089         }
01090      int fd = dup(file);
01091      if (fd) {
01092         FILE *f = fdopen(fd, "w");
01093         if (f) {
01094            if (Schedule)
01095               Schedule->Dump(f, "215-", DumpMode, AtTime);
01096            else
01097               Schedules->Dump(f, "215-", DumpMode, AtTime);
01098            fflush(f);
01099            Reply(215, "End of EPG data");
01100            fclose(f);
01101            }
01102         else {
01103            Reply(451, "Can't open file connection");
01104            close(fd);
01105            }
01106         }
01107      else
01108         Reply(451, "Can't dup stream descriptor");
01109      }
01110   else
01111      Reply(451, "Can't get EPG data");
01112 }
01113 
01114 void cSVDRP::CmdLSTR(const char *Option)
01115 {
01116   bool recordings = Recordings.Update(true);
01117   if (*Option) {
01118      if (isnumber(Option)) {
01119         cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
01120         if (recording) {
01121            FILE *f = fdopen(file, "w");
01122            if (f) {
01123               recording->Info()->Write(f, "215-");
01124               fflush(f);
01125               Reply(215, "End of recording information");
01126               // don't 'fclose(f)' here!
01127               }
01128            else
01129               Reply(451, "Can't open file connection");
01130            }
01131         else
01132            Reply(550, "Recording \"%s\" not found", Option);
01133         }
01134      else
01135         Reply(501, "Error in recording number \"%s\"", Option);
01136      }
01137   else if (recordings) {
01138      cRecording *recording = Recordings.First();
01139      while (recording) {
01140            Reply(recording == Recordings.Last() ? 250 : -250, "%d %s", recording->Index() + 1, recording->Title(' ', true));
01141            recording = Recordings.Next(recording);
01142            }
01143      }
01144   else
01145      Reply(550, "No recordings available");
01146 }
01147 
01148 void cSVDRP::CmdLSTT(const char *Option)
01149 {
01150   int Number = 0;
01151   bool Id = false;
01152   if (*Option) {
01153      char buf[strlen(Option) + 1];
01154      strcpy(buf, Option);
01155      const char *delim = " \t";
01156      char *strtok_next;
01157      char *p = strtok_r(buf, delim, &strtok_next);
01158      while (p) {
01159            if (isnumber(p))
01160               Number = strtol(p, NULL, 10);
01161            else if (strcasecmp(p, "ID") == 0)
01162               Id = true;
01163            else {
01164               Reply(501, "Unknown option: \"%s\"", p);
01165               return;
01166               }
01167            p = strtok_r(NULL, delim, &strtok_next);
01168            }
01169      }
01170   if (Number) {
01171      cTimer *timer = Timers.Get(Number - 1);
01172      if (timer)
01173         Reply(250, "%d %s", timer->Index() + 1, *timer->ToText(Id));
01174      else
01175         Reply(501, "Timer \"%s\" not defined", Option);
01176      }
01177   else if (Timers.Count()) {
01178      for (int i = 0; i < Timers.Count(); i++) {
01179          cTimer *timer = Timers.Get(i);
01180         if (timer)
01181            Reply(i < Timers.Count() - 1 ? -250 : 250, "%d %s", timer->Index() + 1, *timer->ToText(Id));
01182         else
01183            Reply(501, "Timer \"%d\" not found", i + 1);
01184          }
01185      }
01186   else
01187      Reply(550, "No timers defined");
01188 }
01189 
01190 void cSVDRP::CmdMESG(const char *Option)
01191 {
01192   if (*Option) {
01193      isyslog("SVDRP message: '%s'", Option);
01194      Skins.QueueMessage(mtInfo, Option);
01195      Reply(250, "Message queued");
01196      }
01197   else
01198      Reply(501, "Missing message");
01199 }
01200 
01201 void cSVDRP::CmdMODC(const char *Option)
01202 {
01203   if (*Option) {
01204      char *tail;
01205      int n = strtol(Option, &tail, 10);
01206      if (tail && tail != Option) {
01207         tail = skipspace(tail);
01208         if (!Channels.BeingEdited()) {
01209            cChannel *channel = Channels.GetByNumber(n);
01210            if (channel) {
01211               cChannel ch;
01212               if (ch.Parse(tail)) {
01213                  if (Channels.HasUniqueChannelID(&ch, channel)) {
01214                     *channel = ch;
01215                     Channels.ReNumber();
01216                     Channels.SetModified(true);
01217                     isyslog("modifed channel %d %s", channel->Number(), *channel->ToText());
01218                     Reply(250, "%d %s", channel->Number(), *channel->ToText());
01219                     }
01220                  else
01221                     Reply(501, "Channel settings are not unique");
01222                  }
01223               else
01224                  Reply(501, "Error in channel settings");
01225               }
01226            else
01227               Reply(501, "Channel \"%d\" not defined", n);
01228            }
01229         else
01230            Reply(550, "Channels are being edited - try again later");
01231         }
01232      else
01233         Reply(501, "Error in channel number");
01234      }
01235   else
01236      Reply(501, "Missing channel settings");
01237 }
01238 
01239 void cSVDRP::CmdMODT(const char *Option)
01240 {
01241   if (*Option) {
01242      char *tail;
01243      int n = strtol(Option, &tail, 10);
01244      if (tail && tail != Option) {
01245         tail = skipspace(tail);
01246         if (!Timers.BeingEdited()) {
01247            cTimer *timer = Timers.Get(n - 1);
01248            if (timer) {
01249               cTimer t = *timer;
01250               if (strcasecmp(tail, "ON") == 0)
01251                  t.SetFlags(tfActive);
01252               else if (strcasecmp(tail, "OFF") == 0)
01253                  t.ClrFlags(tfActive);
01254               else if (!t.Parse(tail)) {
01255                  Reply(501, "Error in timer settings");
01256                  return;
01257                  }
01258               *timer = t;
01259               Timers.SetModified();
01260               isyslog("timer %s modified (%s)", *timer->ToDescr(), timer->HasFlags(tfActive) ? "active" : "inactive");
01261               Reply(250, "%d %s", timer->Index() + 1, *timer->ToText());
01262               }
01263            else
01264               Reply(501, "Timer \"%d\" not defined", n);
01265            }
01266         else
01267            Reply(550, "Timers are being edited - try again later");
01268         }
01269      else
01270         Reply(501, "Error in timer number");
01271      }
01272   else
01273      Reply(501, "Missing timer settings");
01274 }
01275 
01276 void cSVDRP::CmdMOVC(const char *Option)
01277 {
01278   if (*Option) {
01279      if (!Channels.BeingEdited() && !Timers.BeingEdited()) {
01280         char *tail;
01281         int From = strtol(Option, &tail, 10);
01282         if (tail && tail != Option) {
01283            tail = skipspace(tail);
01284            if (tail && tail != Option) {
01285               int To = strtol(tail, NULL, 10);
01286               int CurrentChannelNr = cDevice::CurrentChannel();
01287               cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
01288               cChannel *FromChannel = Channels.GetByNumber(From);
01289               if (FromChannel) {
01290                  cChannel *ToChannel = Channels.GetByNumber(To);
01291                  if (ToChannel) {
01292                     int FromNumber = FromChannel->Number();
01293                     int ToNumber = ToChannel->Number();
01294                     if (FromNumber != ToNumber) {
01295                        Channels.Move(FromChannel, ToChannel);
01296                        Channels.ReNumber();
01297                        Channels.SetModified(true);
01298                        if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
01299                           if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
01300                              Channels.SwitchTo(CurrentChannel->Number());
01301                           else
01302                              cDevice::SetCurrentChannel(CurrentChannel);
01303                           }
01304                        isyslog("channel %d moved to %d", FromNumber, ToNumber);
01305                        Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
01306                        }
01307                     else
01308                        Reply(501, "Can't move channel to same postion");
01309                     }
01310                  else
01311                     Reply(501, "Channel \"%d\" not defined", To);
01312                  }
01313               else
01314                  Reply(501, "Channel \"%d\" not defined", From);
01315               }
01316            else
01317               Reply(501, "Error in channel number");
01318            }
01319         else
01320            Reply(501, "Error in channel number");
01321         }
01322      else
01323         Reply(550, "Channels or timers are being edited - try again later");
01324      }
01325   else
01326      Reply(501, "Missing channel number");
01327 }
01328 
01329 void cSVDRP::CmdMOVR(const char *Option)
01330 {
01331   if (*Option) {
01332      char *tail;
01333      int n = strtol(Option, &tail, 10);
01334      cRecording *recording = Recordings.Get(n - 1);
01335      if (recording && tail && tail != Option) {
01336         char *oldName = strdup(recording->Name());
01337         tail = skipspace(tail);
01338         if (!cFileTransfer::Active()) {
01339            if (cFileTransfer::Start(recording, tail))
01340               Reply(250, "Moving recording \"%s\" to \"%s\"", oldName, tail);
01341            else
01342               Reply(554, "Can't start file transfer");
01343            }
01344         else
01345            Reply(554, "File transfer already active");
01346         free(oldName);
01347         }
01348      else
01349         Reply(550, "Recording \"%d\" not found%s", n, Recordings.Count() ? "" : " (use LSTR before moving)");
01350      }
01351   else
01352      Reply(501, "Invalid Option \"%s\"", Option);
01353 }
01354 
01355 void cSVDRP::CmdNEWC(const char *Option)
01356 {
01357   if (*Option) {
01358      cChannel ch;
01359      if (ch.Parse(Option)) {
01360         if (Channels.HasUniqueChannelID(&ch)) {
01361            cChannel *channel = new cChannel;
01362            *channel = ch;
01363            Channels.Add(channel);
01364            Channels.ReNumber();
01365            Channels.SetModified(true);
01366            isyslog("new channel %d %s", channel->Number(), *channel->ToText());
01367            Reply(250, "%d %s", channel->Number(), *channel->ToText());
01368            }
01369         else
01370            Reply(501, "Channel settings are not unique");
01371         }
01372      else
01373         Reply(501, "Error in channel settings");
01374      }
01375   else
01376      Reply(501, "Missing channel settings");
01377 }
01378 
01379 void cSVDRP::CmdNEWT(const char *Option)
01380 {
01381   if (*Option) {
01382      cTimer *timer = new cTimer;
01383      if (timer->Parse(Option)) {
01384         cTimer *t = Timers.GetTimer(timer);
01385         if (!t) {
01386            Timers.Add(timer);
01387            Timers.SetModified();
01388            isyslog("timer %s added", *timer->ToDescr());
01389            Reply(250, "%d %s", timer->Index() + 1, *timer->ToText());
01390            return;
01391            }
01392         else
01393            Reply(550, "Timer already defined: %d %s", t->Index() + 1, *t->ToText());
01394         }
01395      else
01396         Reply(501, "Error in timer settings");
01397      delete timer;
01398      }
01399   else
01400      Reply(501, "Missing timer settings");
01401 }
01402 
01403 void cSVDRP::CmdNEXT(const char *Option)
01404 {
01405   cTimer *t = Timers.GetNextActiveTimer();
01406   if (t) {
01407      time_t Start = t->StartTime();
01408      int Number = t->Index() + 1;
01409      if (!*Option)
01410         Reply(250, "%d %s", Number, *TimeToString(Start));
01411      else if (strcasecmp(Option, "ABS") == 0)
01412         Reply(250, "%d %ld", Number, Start);
01413      else if (strcasecmp(Option, "REL") == 0)
01414         Reply(250, "%d %ld", Number, Start - time(NULL));
01415      else
01416         Reply(501, "Unknown option: \"%s\"", Option);
01417      }
01418   else
01419      Reply(550, "No active timers");
01420 }
01421 
01422 void cSVDRP::CmdPLAY(const char *Option)
01423 {
01424   if (*Option) {
01425      char *opt = strdup(Option);
01426      char *num = skipspace(opt);
01427      char *option = num;
01428      while (*option && !isspace(*option))
01429            option++;
01430      char c = *option;
01431      *option = 0;
01432      if (isnumber(num)) {
01433         cRecording *recording = Recordings.Get(strtol(num, NULL, 10) - 1);
01434         if (recording) {
01435            if (c)
01436               option = skipspace(++option);
01437            cReplayControl::SetRecording(NULL, NULL);
01438            cControl::Shutdown();
01439            if (*option) {
01440               int pos = 0;
01441               if (strcasecmp(option, "BEGIN") != 0)
01442                  pos = HMSFToIndex(option, recording->FramesPerSecond());
01443               cResumeFile resume(recording->FileName(), recording->IsPesRecording());
01444               if (pos <= 0)
01445                  resume.Delete();
01446               else
01447                  resume.Save(pos);
01448               }
01449            cReplayControl::SetRecording(recording->FileName(), recording->Title());
01450            cControl::Launch(new cReplayControl);
01451            cControl::Attach();
01452            Reply(250, "Playing recording \"%s\" [%s]", num, recording->Title());
01453            }
01454         else
01455            Reply(550, "Recording \"%s\" not found%s", num, Recordings.Count() ? "" : " (use LSTR before playing)");
01456         }
01457      else
01458         Reply(501, "Error in recording number \"%s\"", num);
01459      free(opt);
01460      }
01461   else
01462      Reply(501, "Missing recording number");
01463 }
01464 
01465 void cSVDRP::CmdPLUG(const char *Option)
01466 {
01467   if (*Option) {
01468      char *opt = strdup(Option);
01469      char *name = skipspace(opt);
01470      char *option = name;
01471      while (*option && !isspace(*option))
01472         option++;
01473      char c = *option;
01474      *option = 0;
01475      cPlugin *plugin = cPluginManager::GetPlugin(name);
01476      if (plugin) {
01477         if (c)
01478            option = skipspace(++option);
01479         char *cmd = option;
01480         while (*option && !isspace(*option))
01481               option++;
01482         if (*option) {
01483            *option++ = 0;
01484            option = skipspace(option);
01485            }
01486         if (!*cmd || strcasecmp(cmd, "HELP") == 0) {
01487            if (*cmd && *option) {
01488               const char *hp = GetHelpPage(option, plugin->SVDRPHelpPages());
01489               if (hp) {
01490                  Reply(-214, "%s", hp);
01491                  Reply(214, "End of HELP info");
01492                  }
01493               else
01494                  Reply(504, "HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->Name());
01495               }
01496            else {
01497               Reply(-214, "Plugin %s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
01498               const char **hp = plugin->SVDRPHelpPages();
01499               if (hp) {
01500                  Reply(-214, "SVDRP commands:");
01501                  PrintHelpTopics(hp);
01502                  Reply(214, "End of HELP info");
01503                  }
01504               else
01505                  Reply(214, "This plugin has no SVDRP commands");
01506               }
01507            }
01508         else if (strcasecmp(cmd, "MAIN") == 0) {
01509            if (cRemote::CallPlugin(plugin->Name()))
01510               Reply(250, "Initiated call to main menu function of plugin \"%s\"", plugin->Name());
01511            else
01512               Reply(550, "A plugin call is already pending - please try again later");
01513            }
01514         else {
01515            int ReplyCode = 900;
01516            cString s = plugin->SVDRPCommand(cmd, option, ReplyCode);
01517            if (*s)
01518               Reply(abs(ReplyCode), "%s", *s);
01519            else
01520               Reply(500, "Command unrecognized: \"%s\"", cmd);
01521            }
01522         }
01523      else
01524         Reply(550, "Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
01525      free(opt);
01526      }
01527   else {
01528      Reply(-214, "Available plugins:");
01529      cPlugin *plugin;
01530      for (int i = 0; (plugin = cPluginManager::GetPlugin(i)) != NULL; i++)
01531          Reply(-214, "%s v%s - %s", plugin->Name(), plugin->Version(), plugin->Description());
01532      Reply(214, "End of plugin list");
01533      }
01534 }
01535 
01536 void cSVDRP::CmdPUTE(const char *Option)
01537 {
01538   if (*Option) {
01539      FILE *f = fopen(Option, "r");
01540      if (f) {
01541         if (cSchedules::Read(f)) {
01542            cSchedules::Cleanup(true);
01543            Reply(250, "EPG data processed from \"%s\"", Option);
01544            }
01545         else
01546            Reply(451, "Error while processing EPG from \"%s\"", Option);
01547         fclose(f);
01548         }
01549      else
01550         Reply(501, "Cannot open file \"%s\"", Option);
01551      }
01552   else {     
01553      delete PUTEhandler;
01554      PUTEhandler = new cPUTEhandler;
01555      Reply(PUTEhandler->Status(), "%s", PUTEhandler->Message());
01556      if (PUTEhandler->Status() != 354)
01557         DELETENULL(PUTEhandler);
01558      }
01559 }
01560 
01561 void cSVDRP::CmdREMO(const char *Option)
01562 {
01563   if (*Option) {
01564      if (!strcasecmp(Option, "ON")) {
01565         cRemote::SetEnabled(true);
01566         Reply(250, "Remote control enabled");
01567         }
01568      else if (!strcasecmp(Option, "OFF")) {
01569         cRemote::SetEnabled(false);
01570         Reply(250, "Remote control disabled");
01571         }
01572      else
01573         Reply(501, "Invalid Option \"%s\"", Option);
01574      }
01575   else
01576      Reply(250, "Remote control is %s", cRemote::Enabled() ? "enabled" : "disabled");
01577 }
01578 
01579 void cSVDRP::CmdSCAN(const char *Option)
01580 {
01581   EITScanner.ForceScan();
01582   Reply(250, "EPG scan triggered");
01583 }
01584 
01585 void cSVDRP::CmdSTAT(const char *Option)
01586 {
01587   if (*Option) {
01588      if (strcasecmp(Option, "DISK") == 0) {
01589         int FreeMB, UsedMB;
01590         int Percent = VideoDiskSpace(&FreeMB, &UsedMB);
01591         Reply(250, "%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
01592         }
01593      else
01594         Reply(501, "Invalid Option \"%s\"", Option);
01595      }
01596   else
01597      Reply(501, "No option given");
01598 }
01599 
01600 void cSVDRP::CmdUPDT(const char *Option)
01601 {
01602   if (*Option) {
01603      cTimer *timer = new cTimer;
01604      if (timer->Parse(Option)) {
01605         if (!Timers.BeingEdited()) {
01606            cTimer *t = Timers.GetTimer(timer);
01607            if (t) {
01608               t->Parse(Option);
01609               delete timer;
01610               timer = t;
01611               isyslog("timer %s updated", *timer->ToDescr());
01612               }
01613            else {
01614               Timers.Add(timer);
01615               isyslog("timer %s added", *timer->ToDescr());
01616               }
01617            Timers.SetModified();
01618            Reply(250, "%d %s", timer->Index() + 1, *timer->ToText());
01619            return;
01620            }
01621         else
01622            Reply(550, "Timers are being edited - try again later");
01623         }
01624      else
01625         Reply(501, "Error in timer settings");
01626      delete timer;
01627      }
01628   else
01629      Reply(501, "Missing timer settings");
01630 }
01631 
01632 void cSVDRP::CmdUPDR(const char *Option)
01633 {
01634   Recordings.Update(false);
01635   Reply(250, "Re-read of recordings directory triggered");
01636 }
01637 
01638 void cSVDRP::CmdVOLU(const char *Option)
01639 {
01640   if (*Option) {
01641      if (isnumber(Option))
01642         cDevice::PrimaryDevice()->SetVolume(strtol(Option, NULL, 10), true);
01643      else if (strcmp(Option, "+") == 0)
01644         cDevice::PrimaryDevice()->SetVolume(VOLUMEDELTA);
01645      else if (strcmp(Option, "-") == 0)
01646         cDevice::PrimaryDevice()->SetVolume(-VOLUMEDELTA);
01647      else if (strcasecmp(Option, "MUTE") == 0)
01648         cDevice::PrimaryDevice()->ToggleMute();
01649      else {
01650         Reply(501, "Unknown option: \"%s\"", Option);
01651         return;
01652         }
01653      }
01654   if (cDevice::PrimaryDevice()->IsMute())
01655      Reply(250, "Audio is mute");
01656   else
01657      Reply(250, "Audio volume is %d", cDevice::CurrentVolume());
01658 }
01659 
01660 #define CMD(c) (strcasecmp(Cmd, c) == 0)
01661 
01662 void cSVDRP::Execute(char *Cmd)
01663 {
01664   // handle PUTE data:
01665   if (PUTEhandler) {
01666      if (!PUTEhandler->Process(Cmd)) {
01667         Reply(PUTEhandler->Status(), "%s", PUTEhandler->Message());
01668         DELETENULL(PUTEhandler);
01669         }
01670      cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME); // re-trigger the timeout, in case there is very much EPG data
01671      return;
01672      }
01673   // skip leading whitespace:
01674   Cmd = skipspace(Cmd);
01675   // find the end of the command word:
01676   char *s = Cmd;
01677   while (*s && !isspace(*s))
01678         s++;
01679   if (*s)
01680      *s++ = 0;
01681   s = skipspace(s);
01682   if      (CMD("CHAN"))  CmdCHAN(s);
01683   else if (CMD("CLRE"))  CmdCLRE(s);
01684   else if (CMD("CPYR"))  CmdCPYR(s);
01685   else if (CMD("DELC"))  CmdDELC(s);
01686   else if (CMD("DELR"))  CmdDELR(s);
01687   else if (CMD("DELT"))  CmdDELT(s);
01688   else if (CMD("EDIT"))  CmdEDIT(s);
01689   else if (CMD("GRAB"))  CmdGRAB(s);
01690   else if (CMD("HELP"))  CmdHELP(s);
01691   else if (CMD("HITK"))  CmdHITK(s);
01692   else if (CMD("LSTC"))  CmdLSTC(s);
01693   else if (CMD("LSTE"))  CmdLSTE(s);
01694   else if (CMD("LSTR"))  CmdLSTR(s);
01695   else if (CMD("LSTT"))  CmdLSTT(s);
01696   else if (CMD("MESG"))  CmdMESG(s);
01697   else if (CMD("MODC"))  CmdMODC(s);
01698   else if (CMD("MODT"))  CmdMODT(s);
01699   else if (CMD("MOVC"))  CmdMOVC(s);
01700   else if (CMD("MOVR"))  CmdMOVR(s);
01701   else if (CMD("NEWC"))  CmdNEWC(s);
01702   else if (CMD("NEWT"))  CmdNEWT(s);
01703   else if (CMD("NEXT"))  CmdNEXT(s);
01704   else if (CMD("PLAY"))  CmdPLAY(s);
01705   else if (CMD("PLUG"))  CmdPLUG(s);
01706   else if (CMD("PUTE"))  CmdPUTE(s);
01707   else if (CMD("REMO"))  CmdREMO(s);
01708   else if (CMD("SCAN"))  CmdSCAN(s);
01709   else if (CMD("STAT"))  CmdSTAT(s);
01710   else if (CMD("UPDR"))  CmdUPDR(s);
01711   else if (CMD("UPDT"))  CmdUPDT(s);
01712   else if (CMD("VOLU"))  CmdVOLU(s);
01713   else if (CMD("QUIT"))  Close(true);
01714   else                   Reply(500, "Command unrecognized: \"%s\"", Cmd);
01715 }
01716 
01717 bool cSVDRP::Process(void)
01718 {
01719   bool NewConnection = !file.IsOpen();
01720   bool SendGreeting = NewConnection;
01721 
01722   if (file.IsOpen() || file.Open(socket.Accept())) {
01723      if (SendGreeting) {
01724         //TODO how can we get the *full* hostname?
01725         char buffer[BUFSIZ];
01726         gethostname(buffer, sizeof(buffer));
01727         time_t now = time(NULL);
01728         Reply(220, "%s SVDRP VideoDiskRecorder %s; %s; %s", buffer, VDRVERSION, *TimeToString(now), cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8");
01729         }
01730      if (NewConnection)
01731         lastActivity = time(NULL);
01732      while (file.Ready(false)) {
01733            unsigned char c;
01734            int r = safe_read(file, &c, 1);
01735            if (r > 0) {
01736               if (c == '\n' || c == 0x00) {
01737                  // strip trailing whitespace:
01738                  while (numChars > 0 && strchr(" \t\r\n", cmdLine[numChars - 1]))
01739                        cmdLine[--numChars] = 0;
01740                  // make sure the string is terminated:
01741                  cmdLine[numChars] = 0;
01742                  // showtime!
01743                  Execute(cmdLine);
01744                  numChars = 0;
01745                  if (length > BUFSIZ) {
01746                     free(cmdLine); // let's not tie up too much memory
01747                     length = BUFSIZ;
01748                     cmdLine = MALLOC(char, length);
01749                     }
01750                  }
01751               else if (c == 0x04 && numChars == 0) {
01752                  // end of file (only at beginning of line)
01753                  Close(true);
01754                  }
01755               else if (c == 0x08 || c == 0x7F) {
01756                  // backspace or delete (last character)
01757                  if (numChars > 0)
01758                     numChars--;
01759                  }
01760               else if (c <= 0x03 || c == 0x0D) {
01761                  // ignore control characters
01762                  }
01763               else {
01764                  if (numChars >= length - 1) {
01765                     int NewLength = length + BUFSIZ;
01766                     if (char *NewBuffer = (char *)realloc(cmdLine, NewLength)) {
01767                        length = NewLength;
01768                        cmdLine = NewBuffer;
01769                        }
01770                     else {
01771                        esyslog("ERROR: out of memory");
01772                        Close();
01773                        break;
01774                        }
01775                     }
01776                  cmdLine[numChars++] = c;
01777                  cmdLine[numChars] = 0;
01778                  }
01779               lastActivity = time(NULL);
01780               }
01781            else if (r <= 0) {
01782               isyslog("lost connection to SVDRP client");
01783               Close();
01784               }
01785            }
01786      if (Setup.SVDRPTimeout && time(NULL) - lastActivity > Setup.SVDRPTimeout) {
01787         isyslog("timeout on SVDRP connection");
01788         Close(true, true);
01789         }
01790      return true;
01791      }
01792   return false;
01793 }
01794 
01795 void cSVDRP::SetGrabImageDir(const char *GrabImageDir)
01796 {
01797   free(grabImageDir);
01798   grabImageDir = GrabImageDir ? strdup(GrabImageDir) : NULL;
01799 }
01800 
01801 //TODO more than one connection???