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;
const char * Title(char Delimiter=' ', bool NewIndicator=false, int Level=-1) const
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)
void Add(cListObject *Object, cListObject *After=NULL)
cString ToText(bool UseChannelID=false) const
static cString ToText(const cChannel *Channel)
virtual const char ** SVDRPHelpPages(void)
virtual const char * Version(void)=0
void CmdGRAB(const char *Option)
bool HasFlags(uint Flags) const
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
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.
const char * Name(void) const
Returns the full name of the recording (without the video directory.
cString ToDescr(void) const
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)
const char * GetHelpTopic(const char *HelpPage)
const char * GetHelpPage(const char *Cmd, const char **p)
const cRecordingInfo * Info(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)
void Dump(FILE *f, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0) 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.
void CmdNEXT(const char *Option)
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.
static void SetEnabled(bool Enabled)
int GetNextNormal(int Idx)
T * Next(const T *object) const
void CmdDELT(const char *Option)
bool GroupSep(void) const
void CmdCHAN(const char *Option)
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)
virtual uchar * GrabImage(int &Size, bool Jpeg=true, int Quality=-1, int SizeX=-1, int SizeY=-1)
Grabs the currently visible screen image.
const cSchedule * GetSchedule(tChannelID ChannelID) const
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)
bool IsPesRecording(void) const
const char * Name(void) const
cSocket(int Port, int Queue=1)
static bool Enabled(void)
void CmdREMO(const char *Option)
void CmdUPDT(const char *Option)
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.
tChannelID GetChannelID(void) const
void SetFlags(uint Flags)
void CmdMESG(const char *Option)
bool Recording(void) const
virtual void Move(int From, int To)
bool Transferring(void) const
Returns true if we are currently in Transfer Mode.
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)
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)
double FramesPerSecond(void) const
int IsInUse(void) const
Checks whether this recording is currently in use and therefore shall not be tampered with...
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)
bool Write(FILE *f, const char *Prefix="") const
void CmdDELR(const char *Option)
void CmdNEWC(const char *Option)
void CmdSTAT(const char *Option)
tChannelID & ClrRid(void)
void Del(cTimer *Timer, bool DeleteObject=true)
void void PrintHelpTopics(const char **hp)
static void Shutdown(void)
bool Replaying(void) const
Returns true if we are currently replaying.
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)
time_t StartTime(void) const
void CmdSCAN(const char *Option)
static bool CallPlugin(const char *Plugin)
Initiates calling the given plugin's main menu function.