Home

Dokumentation

Impressum

Dokumentation VDR
 

Main Page   Class Hierarchy   Alphabetical List   Data Structures   File List   Data Fields   Globals  

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.49 2002/11/10 12:09: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 "device.h"
00032 #include "keys.h"
00033 #include "remote.h"
00034 #include "timers.h"
00035 #include "tools.h"
00036 
00037 // --- cSocket ---------------------------------------------------------------
00038 
00039 cSocket::cSocket(int Port, int Queue)
00040 {
00041   port = Port;
00042   sock = -1;
00043   queue = Queue;
00044 }
00045 
00046 cSocket::~cSocket()
00047 {
00048   Close();
00049 }
00050 
00051 void cSocket::Close(void)
00052 {
00053   if (sock >= 0) {
00054      close(sock);
00055      sock = -1;
00056      }
00057 }
00058 
00059 bool cSocket::Open(void)
00060 {
00061   if (sock < 0) {
00062      // create socket:
00063      sock = socket(PF_INET, SOCK_STREAM, 0);
00064      if (sock < 0) {
00065         LOG_ERROR;
00066         port = 0;
00067         return false;
00068         }
00069      // allow it to always reuse the same port:
00070      int ReUseAddr = 1;
00071      setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr, sizeof(ReUseAddr));
00072      //
00073      struct sockaddr_in name;
00074      name.sin_family = AF_INET;
00075      name.sin_port = htons(port);
00076      name.sin_addr.s_addr = htonl(INADDR_ANY);
00077      if (bind(sock, (struct sockaddr *)&name, sizeof(name)) < 0) {
00078         LOG_ERROR;
00079         Close();
00080         return false;
00081         }
00082      // make it non-blocking:
00083      int oldflags = fcntl(sock, F_GETFL, 0);
00084      if (oldflags < 0) {
00085         LOG_ERROR;
00086         return false;
00087         }
00088      oldflags |= O_NONBLOCK;
00089      if (fcntl(sock, F_SETFL, oldflags) < 0) {
00090         LOG_ERROR;
00091         return false;
00092         }
00093      // listen to the socket:
00094      if (listen(sock, queue) < 0) {
00095         LOG_ERROR;
00096         return false;
00097         }
00098      }
00099   return true;
00100 }
00101 
00102 int cSocket::Accept(void)
00103 {
00104   if (Open()) {
00105      struct sockaddr_in clientname;
00106      uint size = sizeof(clientname);
00107      int newsock = accept(sock, (struct sockaddr *)&clientname, &size);
00108      if (newsock > 0) {
00109         bool accepted = SVDRPhosts.Acceptable(clientname.sin_addr.s_addr);
00110         if (!accepted) {
00111            const char *s = "Access denied!\n";
00112            write(newsock, s, strlen(s));
00113            close(newsock);
00114            newsock = -1;
00115            }
00116         isyslog("connect from %s, port %hd - %s", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port), accepted ? "accepted" : "DENIED");
00117         }
00118      else if (errno != EINTR && errno != EAGAIN)
00119         LOG_ERROR;
00120      return newsock;
00121      }
00122   return -1;
00123 }
00124 
00125 // --- cPUTEhandler ----------------------------------------------------------
00126 
00127 cPUTEhandler::cPUTEhandler(void)
00128 {
00129   if ((f = tmpfile()) != NULL) {
00130      status = 354;
00131      message = "Enter EPG data, end with \".\" on a line by itself";
00132      }
00133   else {
00134      LOG_ERROR;
00135      status = 554;
00136      message = "Error while opening temporary file";
00137      }
00138 }
00139 
00140 cPUTEhandler::~cPUTEhandler()
00141 {
00142   if (f)
00143      fclose(f);
00144 }
00145 
00146 bool cPUTEhandler::Process(const char *s)
00147 {
00148   if (f) {
00149      if (strcmp(s, ".") != 0) {
00150         fputs(s, f);
00151         fputc('\n', f);
00152         return true;
00153         }
00154      else {
00155         rewind(f);
00156         if (cSchedules::Read(f)) {
00157            cSIProcessor::TriggerDump();
00158            status = 250;
00159            message = "EPG data processed";
00160            }
00161         else {
00162            status = 451;
00163            message = "Error while processing EPG data";
00164            }
00165         fclose(f);
00166         f = NULL;
00167         }
00168      }
00169   return false;
00170 }
00171 
00172 // --- cSVDRP ----------------------------------------------------------------
00173 
00174 #define MAXHELPTOPIC 10
00175 
00176 const char *HelpPages[] = {
00177   "CHAN [ + | - | <number> | <name> ]\n"
00178   "    Switch channel up, down or to the given channel number or name.\n"
00179   "    Without option (or after successfully switching to the channel)\n"
00180   "    it returns the current channel number and name.",
00181   "CLRE\n"
00182   "    Clear the entire EPG list.",
00183   "DELC <number>\n"
00184   "    Delete channel.",
00185   "DELR <number>\n"
00186   "    Delete the recording with the given number. Before a recording can be\n"
00187   "    deleted, an LSTR command must have been executed in order to retrieve\n"
00188   "    the recording numbers. The numbers don't change during subsequent DELR\n"
00189   "    commands. CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n"
00190   "    RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
00191   "DELT <number>\n"
00192   "    Delete timer.",
00193   "GRAB <filename> [ jpeg | pnm [ <quality> [ <sizex> <sizey> ] ] ]\n"
00194   "    Grab the current frame and save it to the given file. Images can\n"
00195   "    be stored as JPEG (default) or PNM, at the given quality (default\n"
00196   "    is 'maximum', only applies to JPEG) and size (default is full screen).",
00197   "HELP [ <topic> ]\n"
00198   "    The HELP command gives help info.",
00199   "HITK [ <key> ]\n"
00200   "    Hit the given remote control key. Without option a list of all\n"
00201   "    valid key names is given.",
00202   "LSTC [ <number> | <name> ]\n"
00203   "    List channels. Without option, all channels are listed. Otherwise\n"
00204   "    only the given channel is listed. If a name is given, all channels\n"
00205   "    containing the given string as part of their name are listed.",
00206   "LSTE\n"
00207   "    List EPG data.",
00208   "LSTR [ <number> ]\n"
00209   "    List recordings. Without option, all recordings are listed. Otherwise\n"
00210   "    the summary for the given recording is listed.",
00211   "LSTT [ <number> ]\n"
00212   "    List timers. Without option, all timers are listed. Otherwise\n"
00213   "    only the given timer is listed.",
00214   "MESG [ <message> ]\n"
00215   "    Displays the given message on the OSD. If message is omitted, the\n"
00216   "    currently pending message (if any) will be returned. The message\n"
00217   "    will be displayed for a few seconds as soon as the OSD has become\n"
00218   "    idle. If a new MESG command is entered while the previous message\n"
00219   "    has not yet been displayed, the old message will be overwritten.",
00220   "MODC <number> <settings>\n"
00221   "    Modify a channel. Settings must be in the same format as returned\n"
00222   "    by the LSTC command.",
00223   "MODT <number> on | off | <settings>\n"
00224   "    Modify a timer. Settings must be in the same format as returned\n"
00225   "    by the LSTT command. The special keywords 'on' and 'off' can be\n"
00226   "    used to easily activate or deactivate a timer.",
00227   "MOVC <number> <to>\n"
00228   "    Move a channel to a new position.",
00229   "MOVT <number> <to>\n"
00230   "    Move a timer to a new position.",
00231   "NEWC <settings>\n"
00232   "    Create a new channel. Settings must be in the same format as returned\n"
00233   "    by the LSTC command.",
00234   "NEWT <settings>\n"
00235   "    Create a new timer. Settings must be in the same format as returned\n"
00236   "    by the LSTT command. It is an error if a timer with the same channel,\n"
00237   "    day, start and stop time already exists.",
00238   "NEXT [ abs | rel ]\n"
00239   "    Show the next timer event. If no option is given, the output will be\n"
00240   "    in human readable form. With option 'abs' the absolute time of the next\n"
00241   "    event will be given as the number of seconds since the epoch (time_t\n"
00242   "    format), while with option 'rel' the relative time will be given as the\n"
00243   "    number of seconds from now until the event. If the absolute time given\n"
00244   "    is smaller than the current time, or if the relative time is less than\n"
00245   "    zero, this means that the timer is currently recording and has started\n"
00246   "    at the given time. The first value in the resulting line is the number\n"
00247   "    of the timer.",
00248   "PUTE\n"
00249   "    Put data into the EPG list. The data entered has to strictly follow the\n"
00250   "    format defined in vdr(5) for the 'epg.data' file.  A '.' on a line\n"
00251   "    by itself terminates the input and starts processing of the data (all\n"
00252   "    entered data is buffered until the terminating '.' is seen).",
00253   "UPDT <settings>\n"
00254   "    Updates a timer. Settings must be in the same format as returned\n"
00255   "    by the LSTT command. If a timer with the same channel, day, start\n"
00256   "    and stop time does not yet exists, it will be created.",
00257   "VOLU [ <number> | + | - | mute ]\n"
00258   "    Set the audio volume to the given number (which is limited to the range\n"
00259   "    0...255). If the special options '+' or '-' are given, the volume will\n"
00260   "    be turned up or down, respectively. The option 'mute' will toggle the\n"
00261   "    audio muting. If no option is given, the current audio volume level will\n"
00262   "    be returned.",
00263   "QUIT\n"
00264   "    Exit vdr (SVDRP).\n"
00265   "    You can also hit Ctrl-D to exit.",
00266   NULL
00267   };
00268 
00269 /* SVDRP Reply Codes:
00270 
00271  214 Help message
00272  215 EPG data record
00273  220 VDR service ready
00274  221 VDR service closing transmission channel
00275  250 Requested VDR action okay, completed
00276  354 Start sending EPG data
00277  451 Requested action aborted: local error in processing
00278  500 Syntax error, command unrecognized
00279  501 Syntax error in parameters or arguments
00280  502 Command not implemented
00281  504 Command parameter not implemented
00282  550 Requested action not taken
00283  554 Transaction failed
00284 
00285 */
00286 
00287 const char *GetHelpTopic(const char *HelpPage)
00288 {
00289   static char topic[MAXHELPTOPIC];
00290   const char *q = HelpPage;
00291   while (*q) {
00292         if (isspace(*q)) {
00293            uint n = q - HelpPage;
00294            if (n >= sizeof(topic))
00295               n = sizeof(topic) - 1;
00296            strncpy(topic, HelpPage, n);
00297            topic[n] = 0;
00298            return topic;
00299            }
00300         q++;
00301         }
00302   return NULL;
00303 }
00304 
00305 const char *GetHelpPage(const char *Cmd)
00306 {
00307   const char **p = HelpPages;
00308   while (*p) {
00309         const char *t = GetHelpTopic(*p);
00310         if (strcasecmp(Cmd, t) == 0)
00311            return *p;
00312         p++;
00313         }
00314   return NULL;
00315 }
00316 
00317 cSVDRP::cSVDRP(int Port)
00318 :socket(Port)
00319 {
00320   PUTEhandler = NULL;
00321   numChars = 0;
00322   message = NULL;
00323   lastActivity = 0;
00324   isyslog("SVDRP listening on port %d", Port);
00325 }
00326 
00327 cSVDRP::~cSVDRP()
00328 {
00329   Close();
00330   free(message);
00331 }
00332 
00333 void cSVDRP::Close(bool Timeout)
00334 {
00335   if (file.IsOpen()) {
00336      //TODO how can we get the *full* hostname?
00337      char buffer[BUFSIZ];
00338      gethostname(buffer, sizeof(buffer));
00339      Reply(221, "%s closing connection%s", buffer, Timeout ? " (timeout)" : "");
00340      isyslog("closing SVDRP connection"); //TODO store IP#???
00341      file.Close();
00342      DELETENULL(PUTEhandler);
00343      }
00344 }
00345 
00346 bool cSVDRP::Send(const char *s, int length)
00347 {
00348   if (length < 0)
00349      length = strlen(s);
00350   if (safe_write(file, s, length) < 0) {
00351      LOG_ERROR;
00352      file.Close();
00353      return false;
00354      }
00355   return true;
00356 }
00357 
00358 void cSVDRP::Reply(int Code, const char *fmt, ...)
00359 {
00360   if (file.IsOpen()) {
00361      if (Code != 0) {
00362         va_list ap;
00363         va_start(ap, fmt);
00364         char *buffer;
00365         vasprintf(&buffer, fmt, ap);
00366         char *nl = strchr(buffer, '\n');
00367         if (Code > 0 && nl && *(nl + 1)) // trailing newlines don't count!
00368            Code = -Code;
00369         char number[16];
00370         sprintf(number, "%03d%c", abs(Code), Code < 0 ? '-' : ' ');
00371         const char *s = buffer;
00372         while (s && *s) {
00373               const char *n = strchr(s, '\n');
00374               if (!(Send(number) && Send(s, n ? n - s : -1) && Send("\r\n"))) {
00375                  Close();
00376                  break;
00377                  }
00378               s = n ? n + 1 : NULL;
00379               }
00380         free(buffer);
00381         va_end(ap);
00382         }
00383      else {
00384         Reply(451, "Zero return code - looks like a programming error!");
00385         esyslog("SVDRP: zero return code!");
00386         }
00387      }
00388 }
00389 
00390 void cSVDRP::CmdCHAN(const char *Option)
00391 {
00392   if (*Option) {
00393      int n = -1;
00394      int d = 0;
00395      if (isnumber(Option)) {
00396         int o = strtol(Option, NULL, 10);
00397         if (o >= 1 && o <= Channels.MaxNumber())
00398            n = o;
00399         }
00400      else if (strcmp(Option, "-") == 0) {
00401         n = cDevice::CurrentChannel();
00402         if (n > 1) {
00403            n--;
00404            d = -1;
00405            }
00406         }
00407      else if (strcmp(Option, "+") == 0) {
00408         n = cDevice::CurrentChannel();
00409         if (n < Channels.MaxNumber()) {
00410            n++;
00411            d = 1;
00412            }
00413         }
00414      else {
00415         int i = 1;
00416         cChannel *channel;
00417         while ((channel = Channels.GetByNumber(i, 1)) != NULL) {
00418               if (strcasecmp(channel->Name(), Option) == 0) {
00419                  n = i;
00420                  break;
00421                  }
00422               i = channel->Number() + 1;
00423               }
00424         }
00425      if (n < 0) {
00426         Reply(501, "Undefined channel \"%s\"", Option);
00427         return;
00428         }
00429      if (!d) {
00430         cChannel *channel = Channels.GetByNumber(n);
00431         if (channel) {
00432            if (!cDevice::PrimaryDevice()->SwitchChannel(channel, true)) {
00433               Reply(554, "Error switching to channel \"%d\"", channel->Number());
00434               return;
00435               }
00436            }
00437         else {
00438            Reply(550, "Unable to find channel \"%s\"", Option);
00439            return;
00440            }
00441         }
00442      else
00443         cDevice::SwitchChannel(d);
00444      }
00445   cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());
00446   if (channel)
00447      Reply(250, "%d %s", channel->Number(), channel->Name());
00448   else
00449      Reply(550, "Unable to find channel \"%d\"", cDevice::CurrentChannel());
00450 }
00451 
00452 void cSVDRP::CmdCLRE(const char *Option)
00453 {
00454   cSIProcessor::Clear();
00455   Reply(250, "EPG data cleared");
00456 }
00457 
00458 void cSVDRP::CmdDELC(const char *Option)
00459 {
00460   //TODO combine this with menu action (timers must be updated)
00461   Reply(502, "DELC not yet implemented");
00462 }
00463 
00464 void cSVDRP::CmdDELR(const char *Option)
00465 {
00466   if (*Option) {
00467      if (isnumber(Option)) {
00468         cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
00469         if (recording) {
00470            if (recording->Delete())
00471               Reply(250, "Recording \"%s\" deleted", Option);
00472            else
00473               Reply(554, "Error while deleting recording!");
00474            }
00475         else
00476            Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)");
00477         }
00478      else
00479         Reply(501, "Error in recording number \"%s\"", Option);
00480      }
00481   else
00482      Reply(501, "Missing recording number");
00483 }
00484 
00485 void cSVDRP::CmdDELT(const char *Option)
00486 {
00487   if (*Option) {
00488      if (isnumber(Option)) {
00489         cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1);
00490         if (timer) {
00491            if (!timer->Recording()) {
00492               Timers.Del(timer);
00493               Timers.Save();
00494               isyslog("timer %s deleted", Option);
00495               Reply(250, "Timer \"%s\" deleted", Option);
00496               }
00497            else
00498               Reply(550, "Timer \"%s\" is recording", Option);
00499            }
00500         else
00501            Reply(501, "Timer \"%s\" not defined", Option);
00502         }
00503      else
00504         Reply(501, "Error in timer number \"%s\"", Option);
00505      }
00506   else
00507      Reply(501, "Missing timer number");
00508 }
00509 
00510 void cSVDRP::CmdGRAB(const char *Option)
00511 {
00512   char *FileName = NULL;
00513   bool Jpeg = true;
00514   int Quality = -1, SizeX = -1, SizeY = -1;
00515   if (*Option) {
00516      char buf[strlen(Option) + 1];
00517      char *p = strcpy(buf, Option);
00518      const char *delim = " \t";
00519      FileName = strtok(p, delim);
00520      if ((p = strtok(NULL, delim)) != NULL) {
00521         if (strcasecmp(p, "JPEG") == 0)
00522            Jpeg = true;
00523         else if (strcasecmp(p, "PNM") == 0)
00524            Jpeg = false;
00525         else {
00526            Reply(501, "Unknown image type \"%s\"", p);
00527            return;
00528            }
00529         }
00530      if ((p = strtok(NULL, delim)) != NULL) {
00531         if (isnumber(p))
00532            Quality = atoi(p);
00533         else {
00534            Reply(501, "Illegal quality \"%s\"", p);
00535            return;
00536            }
00537         }
00538      if ((p = strtok(NULL, delim)) != NULL) {
00539         if (isnumber(p))
00540            SizeX = atoi(p);
00541         else {
00542            Reply(501, "Illegal sizex \"%s\"", p);
00543            return;
00544            }
00545         if ((p = strtok(NULL, delim)) != NULL) {
00546            if (isnumber(p))
00547               SizeY = atoi(p);
00548            else {
00549               Reply(501, "Illegal sizey \"%s\"", p);
00550               return;
00551               }
00552            }
00553         else {
00554            Reply(501, "Missing sizey");
00555            return;
00556            }
00557         }
00558      if ((p = strtok(NULL, delim)) != NULL) {
00559         Reply(501, "Unexpected parameter \"%s\"", p);
00560         return;
00561         }
00562      if (cDevice::PrimaryDevice()->GrabImage(FileName, Jpeg, Quality, SizeX, SizeY))
00563         Reply(250, "Grabbed image %s", Option);
00564      else
00565         Reply(451, "Grab image failed");
00566      }
00567   else
00568      Reply(501, "Missing filename");
00569 }
00570 
00571 void cSVDRP::CmdHELP(const char *Option)
00572 {
00573   if (*Option) {
00574      const char *hp = GetHelpPage(Option);
00575      if (hp)
00576         Reply(214, hp);
00577      else {
00578         Reply(504, "HELP topic \"%s\" unknown", Option);
00579         return;
00580         }
00581      }
00582   else {
00583      Reply(-214, "This is VDR version %s", VDRVERSION);
00584      Reply(-214, "Topics:");
00585      const char **hp = HelpPages;
00586      int NumPages = 0;
00587      while (*hp) {
00588            NumPages++;
00589            hp++;
00590            }
00591      const int TopicsPerLine = 5;
00592      int x = 0;
00593      for (int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
00594          char buffer[TopicsPerLine * (MAXHELPTOPIC + 5)];
00595          char *q = buffer;
00596          for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
00597              const char *topic = GetHelpTopic(HelpPages[(y * TopicsPerLine + x)]);
00598              if (topic)
00599                 q += sprintf(q, "    %s", topic);
00600              }
00601          x = 0;
00602          Reply(-214, buffer);
00603          }
00604      Reply(-214, "To report bugs in the implementation send email to");
00605      Reply(-214, "    vdr-bugs@cadsoft.de");
00606      }
00607   Reply(214, "End of HELP info");
00608 }
00609 
00610 void cSVDRP::CmdHITK(const char *Option)
00611 {
00612   if (*Option) {
00613      eKeys k = cKey::FromString(Option);
00614      if (k != kNone) {
00615         cRemote::Put(k);
00616         Reply(250, "Key \"%s\" accepted", Option);
00617         }
00618      else
00619         Reply(504, "Unknown key: \"%s\"", Option);
00620      }
00621   else {
00622      Reply(-214, "Valid <key> names for the HITK command:");
00623      for (int i = 0; i < kNone; i++) {
00624          Reply(-214, "    %s", cKey::ToString(eKeys(i)));
00625          }
00626      Reply(214, "End of key list");
00627      }
00628 }
00629 
00630 void cSVDRP::CmdLSTC(const char *Option)
00631 {
00632   if (*Option) {
00633      if (isnumber(Option)) {
00634         cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10));
00635         if (channel)
00636            Reply(250, "%d %s", channel->Number(), channel->ToText());
00637         else
00638            Reply(501, "Channel \"%s\" not defined", Option);
00639         }
00640      else {
00641         int i = 1;
00642         cChannel *next = NULL;
00643         while (i <= Channels.MaxNumber()) {
00644               cChannel *channel = Channels.GetByNumber(i, 1);
00645               if (channel) {
00646                  if (strcasestr(channel->Name(), Option)) {
00647                     if (next)
00648                        Reply(-250, "%d %s", next->Number(), next->ToText());
00649                     next = channel;
00650                     }
00651                  }
00652               else {
00653                  Reply(501, "Channel \"%d\" not found", i);
00654                  return;
00655                  }
00656               i = channel->Number() + 1;
00657               }
00658         if (next)
00659            Reply(250, "%d %s", next->Number(), next->ToText());
00660         else
00661            Reply(501, "Channel \"%s\" not defined", Option);
00662         }
00663      }
00664   else if (Channels.MaxNumber() >= 1) {
00665      int i = 1;
00666      while (i <= Channels.MaxNumber()) {
00667            cChannel *channel = Channels.GetByNumber(i, 1);
00668            if (channel)
00669               Reply(channel->Number() < Channels.MaxNumber() ? -250 : 250, "%d %s", channel->Number(), channel->ToText());
00670            else
00671               Reply(501, "Channel \"%d\" not found", i);
00672            i = channel->Number() + 1;
00673            }
00674      }
00675   else
00676      Reply(550, "No channels defined");
00677 }
00678 
00679 void cSVDRP::CmdLSTE(const char *Option)
00680 {
00681   cMutexLock MutexLock;
00682   const cSchedules *Schedules = cSIProcessor::Schedules(MutexLock);
00683   if (Schedules) {
00684      FILE *f = fdopen(file, "w");
00685      if (f) {
00686         Schedules->Dump(f, "215-");
00687         fflush(f);
00688         Reply(215, "End of EPG data");
00689         // don't 'fclose(f)' here!
00690         }
00691      else
00692         Reply(451, "Can't open file connection");
00693      }
00694   else
00695      Reply(451, "Can't get EPG data");
00696 }
00697 
00698 void cSVDRP::CmdLSTR(const char *Option)
00699 {
00700   bool recordings = Recordings.Load();
00701   if (*Option) {
00702      if (isnumber(Option)) {
00703         cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
00704         if (recording) {
00705            if (recording->Summary()) {
00706               char *summary = strdup(recording->Summary());
00707               Reply(250, "%s", strreplace(summary,'\n','|'));
00708               free(summary);
00709               }
00710            else
00711               Reply(550, "No summary availabe");
00712            }
00713         else
00714            Reply(550, "Recording \"%s\" not found", Option);
00715         }
00716      else
00717         Reply(501, "Error in recording number \"%s\"", Option);
00718      }
00719   else if (recordings) {
00720      cRecording *recording = Recordings.First();
00721      while (recording) {
00722            Reply(recording == Recordings.Last() ? 250 : -250, "%d %s", recording->Index() + 1, recording->Title(' ', true));
00723            recording = Recordings.Next(recording);
00724            }
00725      }
00726   else
00727      Reply(550, "No recordings available");
00728 }
00729 
00730 void cSVDRP::CmdLSTT(const char *Option)
00731 {
00732   if (*Option) {
00733      if (isnumber(Option)) {
00734         cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1);
00735         if (timer)
00736            Reply(250, "%d %s", timer->Index() + 1, timer->ToText());
00737         else
00738            Reply(501, "Timer \"%s\" not defined", Option);
00739         }
00740      else
00741         Reply(501, "Error in timer number \"%s\"", Option);
00742      }
00743   else if (Timers.Count()) {
00744      for (int i = 0; i < Timers.Count(); i++) {
00745          cTimer *timer = Timers.Get(i);
00746         if (timer)
00747            Reply(i < Timers.Count() - 1 ? -250 : 250, "%d %s", timer->Index() + 1, timer->ToText());
00748         else
00749            Reply(501, "Timer \"%d\" not found", i + 1);
00750          }
00751      }
00752   else
00753      Reply(550, "No timers defined");
00754 }
00755 
00756 void cSVDRP::CmdMESG(const char *Option)
00757 {
00758   if (*Option) {
00759      free(message);
00760      message = strdup(Option);
00761      isyslog("SVDRP message: '%s'", message);
00762      Reply(250, "Message stored");
00763      }
00764   else if (message)
00765      Reply(250, "%s", message);
00766   else
00767      Reply(550, "No pending message");
00768 }
00769 
00770 void cSVDRP::CmdMODC(const char *Option)
00771 {
00772   if (*Option) {
00773      char *tail;
00774      int n = strtol(Option, &tail, 10);
00775      if (tail && tail != Option) {
00776         tail = skipspace(tail);
00777         cChannel *channel = Channels.GetByNumber(n);
00778         if (channel) {
00779            cChannel ch;
00780            if (ch.Parse(tail, true)) {
00781               if (Channels.HasUniqueChannelID(&ch, channel)) {
00782                  *channel = ch;
00783                  Channels.ReNumber();
00784                  Channels.Save();
00785                  isyslog("modifed channel %d %s", channel->Number(), channel->ToText());
00786                  Reply(250, "%d %s", channel->Number(), channel->ToText());
00787                  }
00788               else
00789                  Reply(501, "Channel settings are not unique");
00790               }
00791            else
00792               Reply(501, "Error in channel settings");
00793            }
00794         else
00795            Reply(501, "Channel \"%d\" not defined", n);
00796         }
00797      else
00798         Reply(501, "Error in channel number");
00799      }
00800   else
00801      Reply(501, "Missing channel settings");
00802 }
00803 
00804 void cSVDRP::CmdMODT(const char *Option)
00805 {
00806   if (*Option) {
00807      char *tail;
00808      int n = strtol(Option, &tail, 10);
00809      if (tail && tail != Option) {
00810         tail = skipspace(tail);
00811         cTimer *timer = Timers.Get(n - 1);
00812         if (timer) {
00813            cTimer t = *timer;
00814            if (strcasecmp(tail, "ON") == 0)
00815               t.SetActive(taActive);
00816            else if (strcasecmp(tail, "OFF") == 0)
00817               t.SetActive(taInactive);
00818            else if (!t.Parse(tail)) {
00819               Reply(501, "Error in timer settings");
00820               return;
00821               }
00822            *timer = t;
00823            Timers.Save();
00824            isyslog("timer %d modified (%s)", timer->Index() + 1, timer->Active() ? "active" : "inactive");
00825            Reply(250, "%d %s", timer->Index() + 1, timer->ToText());
00826            }
00827         else
00828            Reply(501, "Timer \"%d\" not defined", n);
00829         }
00830      else
00831         Reply(501, "Error in timer number");
00832      }
00833   else
00834      Reply(501, "Missing timer settings");
00835 }
00836 
00837 void cSVDRP::CmdMOVC(const char *Option)
00838 {
00839   //TODO combine this with menu action (timers must be updated)
00840   Reply(502, "MOVC not yet implemented");
00841 }
00842 
00843 void cSVDRP::CmdMOVT(const char *Option)
00844 {
00845   //TODO combine this with menu action
00846   Reply(502, "MOVT not yet implemented");
00847 }
00848 
00849 void cSVDRP::CmdNEWC(const char *Option)
00850 {
00851   if (*Option) {
00852      cChannel ch;
00853      if (ch.Parse(Option, true)) {
00854         if (Channels.HasUniqueChannelID(&ch)) {
00855            cChannel *channel = new cChannel;
00856            *channel = ch;
00857            Channels.Add(channel);
00858            Channels.ReNumber();
00859            Channels.Save();
00860            isyslog("new channel %d %s", channel->Number(), channel->ToText());
00861            Reply(250, "%d %s", channel->Number(), channel->ToText());
00862            }
00863         else
00864            Reply(501, "Channel settings are not unique");
00865         }
00866      else
00867         Reply(501, "Error in channel settings");
00868      }
00869   else
00870      Reply(501, "Missing channel settings");
00871 }
00872 
00873 void cSVDRP::CmdNEWT(const char *Option)
00874 {
00875   if (*Option) {
00876      cTimer *timer = new cTimer;
00877      if (timer->Parse(Option)) {
00878         cTimer *t = Timers.GetTimer(timer);
00879         if (!t) {
00880            Timers.Add(timer);
00881            Timers.Save();
00882            isyslog("timer %d added", timer->Index() + 1);
00883            Reply(250, "%d %s", timer->Index() + 1, timer->ToText());
00884            return;
00885            }
00886         else
00887            Reply(550, "Timer already defined: %d %s", t->Index() + 1, t->ToText());
00888         }
00889      else
00890         Reply(501, "Error in timer settings");
00891      delete timer;
00892      }
00893   else
00894      Reply(501, "Missing timer settings");
00895 }
00896 
00897 void cSVDRP::CmdNEXT(const char *Option)
00898 {
00899   cTimer *t = Timers.GetNextActiveTimer();
00900   if (t) {
00901      time_t Start = t->StartTime();
00902      int Number = t->Index() + 1;
00903      if (!*Option) {
00904         char *s = ctime(&Start);
00905         s[strlen(s) - 1] = 0; // strip trailing newline
00906         Reply(250, "%d %s", Number, s);
00907         }
00908      else if (strcasecmp(Option, "ABS") == 0)
00909         Reply(250, "%d %ld", Number, Start);
00910      else if (strcasecmp(Option, "REL") == 0)
00911         Reply(250, "%d %ld", Number, Start - time(NULL));
00912      else
00913         Reply(501, "Unknown option: \"%s\"", Option);
00914      }
00915   else
00916      Reply(550, "No active timers");
00917 }
00918 
00919 void cSVDRP::CmdPUTE(const char *Option)
00920 {
00921   delete PUTEhandler;
00922   PUTEhandler = new cPUTEhandler;
00923   Reply(PUTEhandler->Status(), PUTEhandler->Message());
00924   if (PUTEhandler->Status() != 354)
00925      DELETENULL(PUTEhandler);
00926 }
00927 
00928 void cSVDRP::CmdUPDT(const char *Option)
00929 {
00930   if (*Option) {
00931      cTimer *timer = new cTimer;
00932      if (timer->Parse(Option)) {
00933         cTimer *t = Timers.GetTimer(timer);
00934         if (t) {
00935            t->Parse(Option);
00936            delete timer;
00937            timer = t;
00938            isyslog("timer %d updated", timer->Index() + 1);
00939            }
00940         else {
00941            Timers.Add(timer);
00942            isyslog("timer %d added", timer->Index() + 1);
00943            }
00944         Timers.Save();
00945         Reply(250, "%d %s", timer->Index() + 1, timer->ToText());
00946         return;
00947         }
00948      else
00949         Reply(501, "Error in timer settings");
00950      delete timer;
00951      }
00952   else
00953      Reply(501, "Missing timer settings");
00954 }
00955 
00956 void cSVDRP::CmdVOLU(const char *Option)
00957 {
00958   if (*Option) {
00959      if (isnumber(Option))
00960         cDevice::PrimaryDevice()->SetVolume(strtol(Option, NULL, 10), true);
00961      else if (strcmp(Option, "+") == 0)
00962         cDevice::PrimaryDevice()->SetVolume(VOLUMEDELTA);
00963      else if (strcmp(Option, "-") == 0)
00964         cDevice::PrimaryDevice()->SetVolume(-VOLUMEDELTA);
00965      else if (strcasecmp(Option, "MUTE") == 0)
00966         cDevice::PrimaryDevice()->ToggleMute();
00967      else {
00968         Reply(501, "Unknown option: \"%s\"", Option);
00969         return;
00970         }
00971      }
00972   if (cDevice::PrimaryDevice()->IsMute())
00973      Reply(250, "Audio is mute");
00974   else
00975      Reply(250, "Audio volume is %d", cDevice::CurrentVolume());
00976 }
00977 
00978 #define CMD(c) (strcasecmp(Cmd, c) == 0)
00979 
00980 void cSVDRP::Execute(char *Cmd)
00981 {
00982   // handle PUTE data:
00983   if (PUTEhandler) {
00984      if (!PUTEhandler->Process(Cmd)) {
00985         Reply(PUTEhandler->Status(), PUTEhandler->Message());
00986         DELETENULL(PUTEhandler);
00987         }
00988      return;
00989      }
00990   // skip leading whitespace:
00991   Cmd = skipspace(Cmd);
00992   // find the end of the command word:
00993   char *s = Cmd;
00994   while (*s && !isspace(*s))
00995         s++;
00996   if (*s)
00997      *s++ = 0;
00998   s = skipspace(s);
00999   if      (CMD("CHAN"))  CmdCHAN(s);
01000   else if (CMD("CLRE"))  CmdCLRE(s);
01001   else if (CMD("DELC"))  CmdDELC(s);
01002   else if (CMD("DELR"))  CmdDELR(s);
01003   else if (CMD("DELT"))  CmdDELT(s);
01004   else if (CMD("GRAB"))  CmdGRAB(s);
01005   else if (CMD("HELP"))  CmdHELP(s);
01006   else if (CMD("HITK"))  CmdHITK(s);
01007   else if (CMD("LSTC"))  CmdLSTC(s);
01008   else if (CMD("LSTE"))  CmdLSTE(s);
01009   else if (CMD("LSTR"))  CmdLSTR(s);
01010   else if (CMD("LSTT"))  CmdLSTT(s);
01011   else if (CMD("MESG"))  CmdMESG(s);
01012   else if (CMD("MODC"))  CmdMODC(s);
01013   else if (CMD("MODT"))  CmdMODT(s);
01014   else if (CMD("MOVC"))  CmdMOVC(s);
01015   else if (CMD("MOVT"))  CmdMOVT(s);
01016   else if (CMD("NEWC"))  CmdNEWC(s);
01017   else if (CMD("NEWT"))  CmdNEWT(s);
01018   else if (CMD("NEXT"))  CmdNEXT(s);
01019   else if (CMD("PUTE"))  CmdPUTE(s);
01020   else if (CMD("UPDT"))  CmdUPDT(s);
01021   else if (CMD("VOLU"))  CmdVOLU(s);
01022   else if (CMD("QUIT"))  Close();
01023   else                   Reply(500, "Command unrecognized: \"%s\"", Cmd);
01024 }
01025 
01026 bool cSVDRP::Process(void)
01027 {
01028   bool NewConnection = !file.IsOpen();
01029   bool SendGreeting = NewConnection;
01030 
01031   if (file.IsOpen() || file.Open(socket.Accept())) {
01032      if (SendGreeting) {
01033         //TODO how can we get the *full* hostname?
01034         char buffer[BUFSIZ];
01035         gethostname(buffer, sizeof(buffer));
01036         time_t now = time(NULL);
01037         Reply(220, "%s SVDRP VideoDiskRecorder %s; %s", buffer, VDRVERSION, ctime(&now));
01038         }
01039      if (NewConnection)
01040         lastActivity = time(NULL);
01041      while (file.Ready(false)) {
01042            unsigned char c;
01043            int r = safe_read(file, &c, 1);
01044            if (r > 0) {
01045               if (c == '\n' || c == 0x00) {
01046                  // strip trailing whitespace:
01047                  while (numChars > 0 && strchr(" \t\r\n", cmdLine[numChars - 1]))
01048                        cmdLine[--numChars] = 0;
01049                  // make sure the string is terminated:
01050                  cmdLine[numChars] = 0;
01051                  // showtime!
01052                  Execute(cmdLine);
01053                  numChars = 0;
01054                  }
01055               else if (c == 0x04 && numChars == 0) {
01056                  // end of file (only at beginning of line)
01057                  Close();
01058                  }
01059               else if (c == 0x08 || c == 0x7F) {
01060                  // backspace or delete (last character)
01061                  if (numChars > 0)
01062                     numChars--;
01063                  }
01064               else if (c <= 0x03 || c == 0x0D) {
01065                  // ignore control characters
01066                  }
01067               else if (numChars < sizeof(cmdLine) - 1) {
01068                  cmdLine[numChars++] = c;
01069                  cmdLine[numChars] = 0;
01070                  }
01071               else {
01072                  Reply(501, "Command line too long");
01073                  esyslog("SVDRP: command line too long: '%s'", cmdLine);
01074                  numChars = 0;
01075                  }
01076               lastActivity = time(NULL);
01077               }
01078            else if (r <= 0) {
01079               isyslog("lost connection to SVDRP client");
01080               Close();
01081               }
01082            }
01083      if (Setup.SVDRPTimeout && time(NULL) - lastActivity > Setup.SVDRPTimeout) {
01084         isyslog("timeout on SVDRP connection");
01085         Close(true);
01086         }
01087      return true;
01088      }
01089   return false;
01090 }
01091 
01092 char *cSVDRP::GetMessage(void)
01093 {
01094   char *s = message;
01095   message = NULL;
01096   return s;
01097 }
01098 
01099 //TODO more than one connection???

Generated on Wed Feb 5 23:30:12 2003 for VDR by doxygen1.3-rc2