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