17 #include <arpa/inet.h> 21 #include <netinet/in.h> 26 #include <sys/socket.h> 68 sock = socket(PF_INET, SOCK_STREAM, 0);
76 setsockopt(
sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr,
sizeof(ReUseAddr));
78 struct sockaddr_in name;
79 name.sin_family = AF_INET;
80 name.sin_port = htons(
port);
82 if (bind(
sock, (
struct sockaddr *)&name,
sizeof(name)) < 0) {
88 int oldflags = fcntl(
sock, F_GETFL, 0);
93 oldflags |= O_NONBLOCK;
94 if (fcntl(
sock, F_SETFL, oldflags) < 0) {
110 struct sockaddr_in clientname;
111 uint size =
sizeof(clientname);
112 int newsock = accept(
sock, (
struct sockaddr *)&clientname, &size);
116 const char *s =
"Access denied!\n";
117 if (write(newsock, s, strlen(s)) < 0)
122 isyslog(
"connect from %s, port %hu - %s", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port), accepted ?
"accepted" :
"DENIED");
124 else if (errno != EINTR && errno != EAGAIN)
135 if ((f = tmpfile()) != NULL) {
137 message =
"Enter EPG data, end with \".\" on a line by itself";
142 message =
"Error while opening temporary file";
155 if (strcmp(s,
".") != 0) {
165 message =
"EPG data processed";
169 message =
"Error while processing EPG data";
180 #define MAXHELPTOPIC 10 181 #define EITDISABLETIME 10 // seconds until EIT processing is enabled again after a CLRE command 185 "CHAN [ + | - | <number> | <name> | <id> ]\n" 186 " Switch channel up, down or to the given channel number, name or id.\n" 187 " Without option (or after successfully switching to the channel)\n" 188 " it returns the current channel number and name.",
189 "CLRE [ <number> | <name> | <id> ]\n" 190 " Clear the EPG list of the given channel number, name or id.\n" 191 " Without option it clears the entire EPG list.\n" 192 " After a CLRE command, no further EPG processing is done for 10\n" 193 " seconds, so that data sent with subsequent PUTE commands doesn't\n" 194 " interfere with data from the broadcasters.",
198 " Delete the recording with the given number. Before a recording can be\n" 199 " deleted, an LSTR command must have been executed in order to retrieve\n" 200 " the recording numbers. The numbers don't change during subsequent DELR\n" 201 " commands. CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n" 202 " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
206 " Edit the recording with the given number. Before a recording can be\n" 207 " edited, an LSTR command must have been executed in order to retrieve\n" 208 " the recording numbers.",
209 "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n" 210 " Grab the current frame and save it to the given file. Images can\n" 211 " be stored as JPEG or PNM, depending on the given file name extension.\n" 212 " The quality of the grabbed image can be in the range 0..100, where 100\n" 213 " (the default) means \"best\" (only applies to JPEG). The size parameters\n" 214 " define the size of the resulting image (default is full screen).\n" 215 " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n" 216 " data will be sent to the SVDRP connection encoded in base64. The same\n" 217 " happens if '-' (a minus sign) is given as file name, in which case the\n" 218 " image format defaults to JPEG.",
220 " The HELP command gives help info.",
221 "HITK [ <key> ... ]\n" 222 " Hit the given remote control key. Without option a list of all\n" 223 " valid key names is given. If more than one key is given, they are\n" 224 " entered into the remote control queue in the given sequence. There\n" 225 " can be up to 31 keys.",
226 "LSTC [ :groups | <number> | <name> | <id> ]\n" 227 " List channels. Without option, all channels are listed. Otherwise\n" 228 " only the given channel is listed. If a name is given, all channels\n" 229 " containing the given string as part of their name are listed.\n" 230 " If ':groups' is given, all channels are listed including group\n" 231 " separators. The channel number of a group separator is always 0.",
232 "LSTE [ <channel> ] [ now | next | at <time> ]\n" 233 " List EPG data. Without any parameters all data of all channels is\n" 234 " listed. If a channel is given (either by number or by channel ID),\n" 235 " only data for that channel is listed. 'now', 'next', or 'at <time>'\n" 236 " restricts the returned data to present events, following events, or\n" 237 " events at the given time (which must be in time_t form).",
238 "LSTR [ <number> [ path ] ]\n" 239 " List recordings. Without option, all recordings are listed. Otherwise\n" 240 " the information for the given recording is listed. If a recording\n" 241 " number and the keyword 'path' is given, the actual file name of that\n" 242 " recording's directory is listed.",
243 "LSTT [ <number> ] [ id ]\n" 244 " List timers. Without option, all timers are listed. Otherwise\n" 245 " only the given timer is listed. If the keyword 'id' is given, the\n" 246 " channels will be listed with their unique channel ids instead of\n" 249 " Displays the given message on the OSD. The message will be queued\n" 250 " and displayed whenever this is suitable.\n",
251 "MODC <number> <settings>\n" 252 " Modify a channel. Settings must be in the same format as returned\n" 253 " by the LSTC command.",
254 "MODT <number> on | off | <settings>\n" 255 " Modify a timer. Settings must be in the same format as returned\n" 256 " by the LSTT command. The special keywords 'on' and 'off' can be\n" 257 " used to easily activate or deactivate a timer.",
258 "MOVC <number> <to>\n" 259 " Move a channel to a new position.",
260 "MOVR <number> <new name>\n" 261 " Move the recording with the given number. Before a recording can be\n" 262 " moved, an LSTR command must have been executed in order to retrieve\n" 263 " the recording numbers. The numbers don't change during subsequent MOVR\n" 266 " Create a new channel. Settings must be in the same format as returned\n" 267 " by the LSTC command.",
269 " Create a new timer. Settings must be in the same format as returned\n" 270 " by the LSTT command.",
271 "NEXT [ abs | rel ]\n" 272 " Show the next timer event. If no option is given, the output will be\n" 273 " in human readable form. With option 'abs' the absolute time of the next\n" 274 " event will be given as the number of seconds since the epoch (time_t\n" 275 " format), while with option 'rel' the relative time will be given as the\n" 276 " number of seconds from now until the event. If the absolute time given\n" 277 " is smaller than the current time, or if the relative time is less than\n" 278 " zero, this means that the timer is currently recording and has started\n" 279 " at the given time. The first value in the resulting line is the number\n" 281 "PLAY <number> [ begin | <position> ]\n" 282 " Play the recording with the given number. Before a recording can be\n" 283 " played, an LSTR command must have been executed in order to retrieve\n" 284 " the recording numbers.\n" 285 " The keyword 'begin' plays the recording from its very beginning, while\n" 286 " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n" 287 " position. If neither 'begin' nor a <position> are given, replay is resumed\n" 288 " at the position where any previous replay was stopped, or from the beginning\n" 289 " by default. To control or stop the replay session, use the usual remote\n" 290 " control keypresses via the HITK command.",
291 "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n" 292 " Send a command to a plugin.\n" 293 " The PLUG command without any parameters lists all plugins.\n" 294 " If only a name is given, all commands known to that plugin are listed.\n" 295 " If a command is given (optionally followed by parameters), that command\n" 296 " is sent to the plugin, and the result will be displayed.\n" 297 " The keyword 'help' lists all the SVDRP commands known to the named plugin.\n" 298 " If 'help' is followed by a command, the detailed help for that command is\n" 299 " given. The keyword 'main' initiates a call to the main menu function of the\n" 302 " Put data into the EPG list. The data entered has to strictly follow the\n" 303 " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n" 304 " by itself terminates the input and starts processing of the data (all\n" 305 " entered data is buffered until the terminating '.' is seen).\n" 306 " If a file name is given, epg data will be read from this file (which\n" 307 " must be accessible under the given name from the machine VDR is running\n" 308 " on). In case of file input, no terminating '.' shall be given.\n",
309 "REMO [ on | off ]\n" 310 " Turns the remote control on or off. Without a parameter, the current\n" 311 " status of the remote control is reported.",
313 " Forces an EPG scan. If this is a single DVB device system, the scan\n" 314 " will be done on the primary device unless it is currently recording.",
316 " Return information about disk usage (total, free, percent).",
318 " Updates a timer. Settings must be in the same format as returned\n" 319 " by the LSTT command. If a timer with the same channel, day, start\n" 320 " and stop time does not yet exists, it will be created.",
322 " Initiates a re-read of the recordings directory, which is the SVDRP\n" 323 " equivalent to 'touch .update'.",
324 "VOLU [ <number> | + | - | mute ]\n" 325 " Set the audio volume to the given number (which is limited to the range\n" 326 " 0...255). If the special options '+' or '-' are given, the volume will\n" 327 " be turned up or down, respectively. The option 'mute' will toggle the\n" 328 " audio muting. If no option is given, the current audio volume level will\n" 331 " Exit vdr (SVDRP).\n" 332 " You can also hit Ctrl-D to exit.",
360 const char *q = HelpPage;
363 uint n = q - HelpPage;
364 if (n >=
sizeof(topic))
365 n =
sizeof(topic) - 1;
366 strncpy(topic, HelpPage, n);
380 if (strcasecmp(Cmd, t) == 0)
398 isyslog(
"SVDRP listening on port %d", Port);
413 gethostname(buffer,
sizeof(buffer));
414 Reply(221,
"%s closing connection%s", buffer, Timeout ?
" (timeout)" :
"");
416 isyslog(
"closing SVDRP connection");
442 const char *s = buffer;
444 const char *n = strchr(s,
'\n');
446 if (Code < 0 || n && *(n + 1))
449 sprintf(number,
"%03d%c", abs(Code), cont);
450 if (!(
Send(number) &&
Send(s, n ? n - s : -1) &&
Send(
"\r\n")))
452 s = n ? n + 1 : NULL;
456 Reply(451,
"Zero return code - looks like a programming error!");
457 esyslog(
"SVDRP: zero return code!");
472 const int TopicsPerLine = 5;
474 for (
int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
477 q += sprintf(q,
" ");
478 for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
479 const char *topic =
GetHelpTopic(hp[(y * TopicsPerLine + x)]);
484 Reply(-214,
"%s", buffer);
494 int o = strtol(Option, NULL, 10);
498 else if (strcmp(Option,
"-") == 0) {
505 else if (strcmp(Option,
"+") == 0) {
519 if (strcasecmp(channel->
Name(), Option) == 0) {
528 Reply(501,
"Undefined channel \"%s\"", Option);
535 Reply(554,
"Error switching to channel \"%d\"", channel->
Number());
540 Reply(550,
"Unable to find channel \"%s\"", Option);
559 int o = strtol(Option, NULL, 10);
567 if (!Channel->GroupSep()) {
568 if (strcasecmp(Channel->Name(), Option) == 0) {
569 ChannelID = Channel->GetChannelID();
583 if (p->ChannelID() == ChannelID) {
590 if (ChannelID == Timer->Channel()->GetChannelID().
ClrRid())
591 Timer->SetEvent(NULL);
595 Reply(250,
"EPG data of channel \"%s\" cleared", Option);
598 Reply(550,
"No EPG data found for channel \"%s\"", Option);
603 Reply(451,
"Can't get EPG data");
606 Reply(501,
"Undefined channel \"%s\"", Option);
611 Reply(250,
"EPG data cleared");
615 Reply(451,
"Error while clearing EPG data");
627 if (timer->Channel() == channel) {
628 Reply(550,
"Channel \"%s\" is in use by timer %d", Option, timer->Index() + 1);
634 if (CurrentChannel && channel == CurrentChannel) {
639 CurrentChannelNr = 0;
644 isyslog(
"channel %s deleted", Option);
645 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
651 Reply(250,
"Channel \"%s\" deleted", Option);
654 Reply(501,
"Channel \"%s\" not defined", Option);
657 Reply(550,
"Channels are being edited - try again later");
660 Reply(501,
"Error in channel number \"%s\"", Option);
663 Reply(501,
"Missing channel number");
673 else if ((Reason &
ruCut) != 0)
676 return cString::sprintf(
"Recording \"%s\" is being copied/moved", RecordingId);
688 if (
int RecordingInUse = recording->
IsInUse())
691 if (recording->
Delete()) {
692 Reply(250,
"Recording \"%s\" deleted", Option);
696 Reply(554,
"Error while deleting recording!");
700 Reply(550,
"Recording \"%s\" not found%s", Option,
recordings.
Count() ?
"" :
" (use LSTR before deleting)");
703 Reply(501,
"Error in recording number \"%s\"", Option);
706 Reply(501,
"Missing recording number");
720 Reply(250,
"Timer \"%s\" deleted", Option);
723 Reply(550,
"Timer \"%s\" is recording", Option);
726 Reply(501,
"Timer \"%s\" not defined", Option);
729 Reply(550,
"Timers are being edited - try again later");
732 Reply(501,
"Error in timer number \"%s\"", Option);
735 Reply(501,
"Missing timer number");
747 Reply(250,
"Editing recording \"%s\" [%s]", Option, recording->
Title());
749 Reply(554,
"Can't start editing process");
752 Reply(554,
"No editing marks defined");
755 Reply(550,
"Recording \"%s\" not found%s", Option,
recordings.
Count() ?
"" :
" (use LSTR before editing)");
758 Reply(501,
"Error in recording number \"%s\"", Option);
761 Reply(501,
"Missing recording number");
766 const char *FileName = NULL;
768 int Quality = -1, SizeX = -1, SizeY = -1;
770 char buf[strlen(Option) + 1];
771 char *p = strcpy(buf, Option);
772 const char *delim =
" \t";
774 FileName = strtok_r(p, delim, &strtok_next);
776 const char *Extension = strrchr(FileName,
'.');
778 if (strcasecmp(Extension,
".jpg") == 0 || strcasecmp(Extension,
".jpeg") == 0)
780 else if (strcasecmp(Extension,
".pnm") == 0)
783 Reply(501,
"Unknown image type \"%s\"", Extension + 1);
786 if (Extension == FileName)
789 else if (strcmp(FileName,
"-") == 0)
792 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
793 if (strcasecmp(p,
"JPEG") == 0 || strcasecmp(p,
"PNM") == 0) {
795 p = strtok_r(NULL, delim, &strtok_next);
801 Reply(501,
"Invalid quality \"%s\"", p);
807 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
811 Reply(501,
"Invalid sizex \"%s\"", p);
814 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
818 Reply(501,
"Invalid sizey \"%s\"", p);
823 Reply(501,
"Missing sizey");
827 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
828 Reply(501,
"Unexpected parameter \"%s\"", p);
832 char RealFileName[PATH_MAX];
837 const char *slash = strrchr(FileName,
'/');
842 slash = strrchr(FileName,
'/');
845 char *r = realpath(t, RealFileName);
848 Reply(501,
"Invalid file name \"%s\"", FileName);
851 strcat(RealFileName, slash);
852 FileName = RealFileName;
854 Reply(501,
"Invalid file name \"%s\"", FileName);
859 Reply(550,
"Grabbing to file not allowed (use \"GRAB -\" instead)");
868 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
870 if (
safe_write(fd, Image, ImageSize) == ImageSize) {
871 dsyslog(
"grabbed image to %s", FileName);
872 Reply(250,
"Grabbed image %s", Option);
876 Reply(451,
"Can't write to '%s'", FileName);
882 Reply(451,
"Can't open '%s'", FileName);
888 while ((s = Base64.
NextLine()) != NULL)
889 Reply(-216,
"%s", s);
890 Reply(216,
"Grabbed image %s", Option);
895 Reply(451,
"Grab image failed");
898 Reply(501,
"Missing filename");
906 Reply(-214,
"%s", hp);
908 Reply(504,
"HELP topic \"%s\" unknown", Option);
914 Reply(-214,
"Topics:");
923 Reply(-214,
"To report bugs in the implementation send email to");
924 Reply(-214,
" vdr-bugs@tvdr.de");
926 Reply(214,
"End of HELP info");
933 Reply(550,
"Remote control currently disabled (key \"%s\" discarded)", Option);
936 char buf[strlen(Option) + 1];
938 const char *delim =
" \t";
940 char *p = strtok_r(buf, delim, &strtok_next);
946 Reply(451,
"Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
951 Reply(504,
"Unknown key: \"%s\"", p);
955 p = strtok_r(NULL, delim, &strtok_next);
957 Reply(250,
"Key%s \"%s\" accepted", NumKeys > 1 ?
"s" :
"", Option);
960 Reply(-214,
"Valid <key> names for the HITK command:");
961 for (
int i = 0; i <
kNone; i++) {
964 Reply(214,
"End of key list");
970 bool WithGroupSeps = strcasecmp(Option,
":groups") == 0;
971 if (*Option && !WithGroupSeps) {
977 Reply(501,
"Channel \"%s\" not defined", Option);
983 if (!channel->GroupSep()) {
984 if (strcasestr(channel->Name(), Option)) {
995 Reply(501,
"Channel \"%s\" not defined", Option);
1001 Reply(channel->Next() ? -250: 250,
"%d %s", channel->GroupSep() ? 0 : channel->Number(), *channel->ToText());
1002 else if (!channel->GroupSep())
1003 Reply(channel->Number() <
Channels.
MaxNumber() ? -250 : 250,
"%d %s", channel->Number(), *channel->ToText());
1007 Reply(550,
"No channels defined");
1019 char buf[strlen(Option) + 1];
1020 strcpy(buf, Option);
1021 const char *delim =
" \t";
1023 char *p = strtok_r(buf, delim, &strtok_next);
1024 while (p && DumpMode ==
dmAll) {
1025 if (strcasecmp(p,
"NOW") == 0)
1027 else if (strcasecmp(p,
"NEXT") == 0)
1029 else if (strcasecmp(p,
"AT") == 0) {
1031 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1033 AtTime = strtol(p, NULL, 10);
1035 Reply(501,
"Invalid time");
1040 Reply(501,
"Missing time");
1044 else if (!Schedule) {
1053 Reply(550,
"No schedule found");
1058 Reply(550,
"Channel \"%s\" not defined", p);
1063 Reply(501,
"Unknown option: \"%s\"", p);
1066 p = strtok_r(NULL, delim, &strtok_next);
1071 FILE *f = fdopen(fd,
"w");
1074 Schedule->
Dump(f,
"215-", DumpMode, AtTime);
1076 Schedules->
Dump(f,
"215-", DumpMode, AtTime);
1078 Reply(215,
"End of EPG data");
1082 Reply(451,
"Can't open file connection");
1087 Reply(451,
"Can't dup stream descriptor");
1090 Reply(451,
"Can't get EPG data");
1099 char buf[strlen(Option) + 1];
1100 strcpy(buf, Option);
1101 const char *delim =
" \t";
1103 char *p = strtok_r(buf, delim, &strtok_next);
1107 Number = strtol(p, NULL, 10);
1109 Reply(501,
"Error in recording number \"%s\"", Option);
1113 else if (strcasecmp(p,
"PATH") == 0)
1116 Reply(501,
"Unknown option: \"%s\"", p);
1119 p = strtok_r(NULL, delim, &strtok_next);
1124 FILE *f = fdopen(
file,
"w");
1131 Reply(215,
"End of recording information");
1136 Reply(451,
"Can't open file connection");
1139 Reply(550,
"Recording \"%s\" not found", Option);
1150 Reply(550,
"No recordings available");
1158 char buf[strlen(Option) + 1];
1159 strcpy(buf, Option);
1160 const char *delim =
" \t";
1162 char *p = strtok_r(buf, delim, &strtok_next);
1165 Number = strtol(p, NULL, 10);
1166 else if (strcasecmp(p,
"ID") == 0)
1169 Reply(501,
"Unknown option: \"%s\"", p);
1172 p = strtok_r(NULL, delim, &strtok_next);
1180 Reply(501,
"Timer \"%s\" not defined", Option);
1188 Reply(501,
"Timer \"%d\" not found", i + 1);
1192 Reply(550,
"No timers defined");
1198 isyslog(
"SVDRP message: '%s'", Option);
1200 Reply(250,
"Message queued");
1203 Reply(501,
"Missing message");
1210 int n = strtol(Option, &tail, 10);
1211 if (tail && tail != Option) {
1217 if (ch.
Parse(tail)) {
1226 Reply(501,
"Channel settings are not unique");
1229 Reply(501,
"Error in channel settings");
1232 Reply(501,
"Channel \"%d\" not defined", n);
1235 Reply(550,
"Channels are being edited - try again later");
1238 Reply(501,
"Error in channel number");
1241 Reply(501,
"Missing channel settings");
1248 int n = strtol(Option, &tail, 10);
1249 if (tail && tail != Option) {
1255 if (strcasecmp(tail,
"ON") == 0)
1257 else if (strcasecmp(tail,
"OFF") == 0)
1259 else if (!t.
Parse(tail)) {
1260 Reply(501,
"Error in timer settings");
1269 Reply(501,
"Timer \"%d\" not defined", n);
1272 Reply(550,
"Timers are being edited - try again later");
1275 Reply(501,
"Error in timer number");
1278 Reply(501,
"Missing timer settings");
1286 int From = strtol(Option, &tail, 10);
1287 if (tail && tail != Option) {
1289 if (tail && tail != Option) {
1290 int To = strtol(tail, NULL, 10);
1297 int FromNumber = FromChannel->
Number();
1298 int ToNumber = ToChannel->
Number();
1299 if (FromNumber != ToNumber) {
1303 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
1309 isyslog(
"channel %d moved to %d", FromNumber, ToNumber);
1310 Reply(250,
"Channel \"%d\" moved to \"%d\"", From, To);
1313 Reply(501,
"Can't move channel to same position");
1316 Reply(501,
"Channel \"%d\" not defined", To);
1319 Reply(501,
"Channel \"%d\" not defined", From);
1322 Reply(501,
"Error in channel number");
1325 Reply(501,
"Error in channel number");
1328 Reply(550,
"Channels or timers are being edited - try again later");
1331 Reply(501,
"Missing channel number");
1337 char *opt = strdup(Option);
1340 while (*option && !isspace(*option))
1347 if (
int RecordingInUse = recording->
IsInUse())
1355 Reply(250,
"Recording \"%s\" moved to \"%s\"", *oldName, recording->
Name());
1357 Reply(554,
"Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
1360 Reply(501,
"Missing new recording name");
1364 Reply(550,
"Recording \"%s\" not found%s", num,
recordings.
Count() ?
"" :
" (use LSTR before moving)");
1367 Reply(501,
"Error in recording number \"%s\"", num);
1371 Reply(501,
"Missing recording number");
1378 if (ch.
Parse(Option)) {
1389 Reply(501,
"Channel settings are not unique");
1392 Reply(501,
"Error in channel settings");
1395 Reply(501,
"Missing channel settings");
1402 if (timer->
Parse(Option)) {
1410 Reply(501,
"Error in timer settings");
1414 Reply(501,
"Missing timer settings");
1422 int Number = t->
Index() + 1;
1425 else if (strcasecmp(Option,
"ABS") == 0)
1426 Reply(250,
"%d %ld", Number, Start);
1427 else if (strcasecmp(Option,
"REL") == 0)
1428 Reply(250,
"%d %ld", Number, Start - time(NULL));
1430 Reply(501,
"Unknown option: \"%s\"", Option);
1433 Reply(550,
"No active timers");
1439 char *opt = strdup(Option);
1442 while (*option && !isspace(*option))
1455 if (strcasecmp(option,
"BEGIN") != 0)
1466 Reply(250,
"Playing recording \"%s\" [%s]", num, recording->
Title());
1469 Reply(550,
"Recording \"%s\" not found%s", num,
recordings.
Count() ?
"" :
" (use LSTR before playing)");
1472 Reply(501,
"Error in recording number \"%s\"", num);
1476 Reply(501,
"Missing recording number");
1482 char *opt = strdup(Option);
1484 char *option = name;
1485 while (*option && !isspace(*option))
1494 while (*option && !isspace(*option))
1500 if (!*cmd || strcasecmp(cmd,
"HELP") == 0) {
1501 if (*cmd && *option) {
1504 Reply(-214,
"%s", hp);
1505 Reply(214,
"End of HELP info");
1508 Reply(504,
"HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->
Name());
1514 Reply(-214,
"SVDRP commands:");
1516 Reply(214,
"End of HELP info");
1519 Reply(214,
"This plugin has no SVDRP commands");
1522 else if (strcasecmp(cmd,
"MAIN") == 0) {
1524 Reply(250,
"Initiated call to main menu function of plugin \"%s\"", plugin->
Name());
1526 Reply(550,
"A plugin call is already pending - please try again later");
1529 int ReplyCode = 900;
1532 Reply(abs(ReplyCode),
"%s", *s);
1534 Reply(500,
"Command unrecognized: \"%s\"", cmd);
1538 Reply(550,
"Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
1542 Reply(-214,
"Available plugins:");
1546 Reply(214,
"End of plugin list");
1553 FILE *f = fopen(Option,
"r");
1557 Reply(250,
"EPG data processed from \"%s\"", Option);
1560 Reply(451,
"Error while processing EPG from \"%s\"", Option);
1564 Reply(501,
"Cannot open file \"%s\"", Option);
1578 if (!strcasecmp(Option,
"ON")) {
1580 Reply(250,
"Remote control enabled");
1582 else if (!strcasecmp(Option,
"OFF")) {
1584 Reply(250,
"Remote control disabled");
1587 Reply(501,
"Invalid Option \"%s\"", Option);
1596 Reply(250,
"EPG scan triggered");
1602 if (strcasecmp(Option,
"DISK") == 0) {
1605 Reply(250,
"%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
1608 Reply(501,
"Invalid Option \"%s\"", Option);
1611 Reply(501,
"No option given");
1618 if (timer->
Parse(Option)) {
1636 Reply(550,
"Timers are being edited - try again later");
1639 Reply(501,
"Error in timer settings");
1643 Reply(501,
"Missing timer settings");
1649 Reply(250,
"Re-read of recordings directory triggered");
1657 else if (strcmp(Option,
"+") == 0)
1659 else if (strcmp(Option,
"-") == 0)
1661 else if (strcasecmp(Option,
"MUTE") == 0)
1664 Reply(501,
"Unknown option: \"%s\"", Option);
1669 Reply(250,
"Audio is mute");
1674 #define CMD(c) (strcasecmp(Cmd, c) == 0) 1691 while (*s && !isspace(*s))
1727 else Reply(500,
"Command unrecognized: \"%s\"", Cmd);
1733 bool SendGreeting = NewConnection;
1738 char buffer[BUFSIZ];
1739 gethostname(buffer,
sizeof(buffer));
1740 time_t now = time(NULL);
1749 if (c ==
'\n' || c == 0x00) {
1764 else if (c == 0x04 &&
numChars == 0) {
1768 else if (c == 0x08 || c == 0x7F) {
1773 else if (c <= 0x03 || c == 0x0D) {
1778 int NewLength =
length + BUFSIZ;
1779 if (
char *NewBuffer = (
char *)realloc(
cmdLine, NewLength)) {
1784 esyslog(
"ERROR: out of memory");
1795 isyslog(
"lost connection to SVDRP client");
1800 isyslog(
"timeout on SVDRP connection");
1811 grabImageDir = GrabImageDir ? strdup(GrabImageDir) : NULL;
bool Replaying(void) const
Returns true if we are currently replaying.
void CmdMODT(const char *Option)
virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
const char * Message(void)
bool Update(bool Wait=false)
Triggers an update of the list of recordings, which will run as a separate thread if Wait is false...
void CmdLSTT(const char *Option)
void CmdCLRE(const char *Option)
static tChannelID FromString(const char *s)
bool ToggleMute(void)
Turns the volume off or on and returns the new mute state.
bool Ready(bool Wait=true)
void CmdPLAY(const char *Option)
const cRecordingInfo * Info(void) const
void Add(cListObject *Object, cListObject *After=NULL)
static cString ToText(const cChannel *Channel)
virtual const char ** SVDRPHelpPages(void)
double FramesPerSecond(void) const
virtual const char * Version(void)=0
void CmdGRAB(const char *Option)
const char * Title(char Delimiter= ' ', bool NewIndicator=false, int Level=-1) const
static const char * SystemCharacterTable(void)
static void SetDisableUntil(time_t Time)
static cString sprintf(const char *fmt,...) __attribute__((format(printf
int QueueMessage(eMessageType Type, const char *s, int Seconds=0, int Timeout=0)
Like Message(), but this function may be called from a background thread.
static eKeys FromString(const char *Name)
cString & Truncate(int Index)
Truncate the string at the given Index (if Index is < 0 it is counted from the end of the string)...
bool Parse(const char *s)
cPUTEhandler * PUTEhandler
void CmdMOVC(const char *Option)
bool GroupSep(void) const
const char * GetHelpTopic(const char *HelpPage)
const char * GetHelpPage(const char *Cmd, const char **p)
time_t StartTime(void) const
static char * grabImageDir
static bool Dump(FILE *f=NULL, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0)
void CmdLSTC(const char *Option)
cTimer * GetNextActiveTimer(void)
void Add(cTimer *Timer, cTimer *After=NULL)
static const cSchedules * Schedules(cSchedulesLock &SchedulesLock)
Caller must provide a cSchedulesLock which has to survive the entire time the returned cSchedules is ...
void CmdNEWT(const char *Option)
bool Send(const char *s, int length=-1)
void CmdHITK(const char *Option)
static void SetRecording(const char *FileName)
static int CurrentVolume(void)
void CmdEDIT(const char *Option)
bool ChangeName(const char *NewName)
Changes the name of this recording to the given value.
virtual const char * Description(void)=0
static cString static cString vsprintf(const char *fmt, va_list &ap)
bool Transferring(void) const
Returns true if we are currently in Transfer Mode.
bool Recording(void) const
cTimer * GetTimer(cTimer *Timer)
cRecording * GetByName(const char *FileName)
static int CurrentChannel(void)
Returns the number of the current channel on the primary device.
const char * Name(void) const
void CmdNEXT(const char *Option)
T * Next(const T *object) const
bool Process(const char *s)
void SetVolume(int Volume, bool Absolute=false)
Sets the volume to the given value, either absolutely or relative to the current volume.
bool Parse(const char *s)
bool SwitchChannel(const cChannel *Channel, bool LiveView)
Switches the device to the given Channel, initiating transfer mode if necessary.
bool Write(FILE *f, const char *Prefix="") const
static void SetEnabled(bool Enabled)
int GetNextNormal(int Idx)
void CmdDELT(const char *Option)
void CmdCHAN(const char *Option)
bool HasFlags(uint Flags) const
void Cleanup(time_t Time)
void CmdPLUG(const char *Option)
int GetPrevNormal(int Idx)
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
static bool Read(FILE *f=NULL)
void CmdUPDR(const char *Option)
bool Delete(void)
Changes the file name so that it will no longer be visible in the "Recordings" menu Returns false in ...
bool Put(uint64_t Code, bool Repeat=false, bool Release=false)
bool Open(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
const char * NextLine(void)
Returns the next line of encoded data (terminated by '\0'), or NULL if there is no more encoded data...
static void Cleanup(bool Force=false)
tChannelID GetChannelID(void) const
virtual uchar * GrabImage(int &Size, bool Jpeg=true, int Quality=-1, int SizeX=-1, int SizeY=-1)
Grabs the currently visible screen image.
void CmdMODC(const char *Option)
void SetModified(bool ByUser=false)
void CmdDELC(const char *Option)
cRecordingsHandler RecordingsHandler
static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
cChannel * GetByChannelID(tChannelID ChannelID, bool TryWithoutRid=false, bool TryWithoutPolarization=false)
static void Launch(cControl *Control)
void CmdHELP(const char *Option)
cSocket(int Port, int Queue=1)
static bool Enabled(void)
void CmdREMO(const char *Option)
const cSchedule * GetSchedule(tChannelID ChannelID) const
void CmdUPDT(const char *Option)
cString ToDescr(void) const
static int VideoDiskSpace(int *FreeMB=NULL, int *UsedMB=NULL)
void CmdPUTE(const char *Option)
bool HasUniqueChannelID(cChannel *NewChannel, cChannel *OldChannel=NULL)
void CmdVOLU(const char *Option)
void Del(cListObject *Object, bool DeleteObject=true)
void Reply(int Code, const char *fmt,...) __attribute__((format(printf
cChannel * GetByNumber(int Number, int SkipGap=0)
static cDevice * PrimaryDevice(void)
Returns the primary device.
void SetFlags(uint Flags)
void CmdMESG(const char *Option)
virtual void Move(int From, int To)
cRecordings Recordings
Any access to Recordings that loops through the list of recordings needs to hold a thread lock on thi...
static void SetCurrentChannel(const cChannel *Channel)
Sets the number of the current channel on the primary device, without actually switching to it...
static void SetGrabImageDir(const char *GrabImageDir)
const char * Name(void) const
Returns the full name of the recording (without the video directory.
static bool ClearAll(void)
void CmdLSTR(const char *Option)
static cRecordControl * GetRecordControl(const char *FileName)
void DelByName(const char *FileName)
void CmdMOVR(const char *Option)
static cPlugin * GetPlugin(int Index)
bool Acceptable(in_addr_t Address)
void ClrFlags(uint Flags)
static const tChannelID InvalidID
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
bool SwitchTo(int Number)
void Close(bool SendReply=false, bool Timeout=false)
cString ToText(bool UseChannelID=false) const
void CmdDELR(const char *Option)
void Dump(FILE *f, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0) const
void CmdNEWC(const char *Option)
void CmdSTAT(const char *Option)
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
tChannelID & ClrRid(void)
void Del(cTimer *Timer, bool DeleteObject=true)
void void PrintHelpTopics(const char **hp)
static void Shutdown(void)
bool IsPesRecording(void) const
int IsInUse(void) const
Checks whether this recording is currently in use and therefore shall not be tampered with...
bool Add(int Usage, const char *FileNameSrc, const char *FileNameDst=NULL)
Adds the given FileNameSrc to the recordings handler for (later) processing.
void CmdLSTE(const char *Option)
static const char * ToString(eKeys Key, bool Translate=false)
void CmdSCAN(const char *Option)
static bool CallPlugin(const char *Plugin)
Initiates calling the given plugin's main menu function.