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

Generated on Tue Nov 6 19:57:55 2007 for VDR by  doxygen 1.5.3-20071008