17 #include <arpa/inet.h>
21 #include <netinet/in.h>
26 #include <sys/socket.h>
69 sock = socket(PF_INET, SOCK_STREAM, 0);
77 setsockopt(
sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr,
sizeof(ReUseAddr));
79 struct sockaddr_in name;
80 name.sin_family = AF_INET;
81 name.sin_port = htons(
port);
83 if (bind(
sock, (
struct sockaddr *)&name,
sizeof(name)) < 0) {
89 int oldflags = fcntl(
sock, F_GETFL, 0);
94 oldflags |= O_NONBLOCK;
95 if (fcntl(
sock, F_SETFL, oldflags) < 0) {
111 struct sockaddr_in clientname;
112 uint size =
sizeof(clientname);
113 int newsock = accept(
sock, (
struct sockaddr *)&clientname, &size);
117 const char *s =
"Access denied!\n";
118 if (write(newsock, s, strlen(s)) < 0)
123 isyslog(
"connect from %s, port %hu - %s", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port), accepted ?
"accepted" :
"DENIED");
125 else if (errno != EINTR && errno != EAGAIN)
136 if ((
f = tmpfile()) != NULL) {
138 message =
"Enter EPG data, end with \".\" on a line by itself";
143 message =
"Error while opening temporary file";
156 if (strcmp(s,
".") != 0) {
166 message =
"EPG data processed";
170 message =
"Error while processing EPG data";
181 #define MAXHELPTOPIC 10
182 #define EITDISABLETIME 10 // seconds until EIT processing is enabled again after a CLRE command
186 "CHAN [ + | - | <number> | <name> | <id> ]\n"
187 " Switch channel up, down or to the given channel number, name or id.\n"
188 " Without option (or after successfully switching to the channel)\n"
189 " it returns the current channel number and name.",
190 "CLRE [ <number> | <name> | <id> ]\n"
191 " Clear the EPG list of the given channel number, name or id.\n"
192 " Without option it clears the entire EPG list.\n"
193 " After a CLRE command, no further EPG processing is done for 10\n"
194 " seconds, so that data sent with subsequent PUTE commands doesn't\n"
195 " interfere with data from the broadcasters.",
199 " Delete the recording with the given number. Before a recording can be\n"
200 " deleted, an LSTR command must have been executed in order to retrieve\n"
201 " the recording numbers. The numbers don't change during subsequent DELR\n"
202 " commands. CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n"
203 " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
207 " Edit the recording with the given number. Before a recording can be\n"
208 " edited, an LSTR command must have been executed in order to retrieve\n"
209 " the recording numbers.",
210 "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n"
211 " Grab the current frame and save it to the given file. Images can\n"
212 " be stored as JPEG or PNM, depending on the given file name extension.\n"
213 " The quality of the grabbed image can be in the range 0..100, where 100\n"
214 " (the default) means \"best\" (only applies to JPEG). The size parameters\n"
215 " define the size of the resulting image (default is full screen).\n"
216 " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n"
217 " data will be sent to the SVDRP connection encoded in base64. The same\n"
218 " happens if '-' (a minus sign) is given as file name, in which case the\n"
219 " image format defaults to JPEG.",
221 " The HELP command gives help info.",
222 "HITK [ <key> ... ]\n"
223 " Hit the given remote control key. Without option a list of all\n"
224 " valid key names is given. If more than one key is given, they are\n"
225 " entered into the remote control queue in the given sequence. There\n"
226 " can be up to 31 keys.",
227 "LSTC [ :groups | <number> | <name> | <id> ]\n"
228 " List channels. Without option, all channels are listed. Otherwise\n"
229 " only the given channel is listed. If a name is given, all channels\n"
230 " containing the given string as part of their name are listed.\n"
231 " If ':groups' is given, all channels are listed including group\n"
232 " separators. The channel number of a group separator is always 0.",
233 "LSTE [ <channel> ] [ now | next | at <time> ]\n"
234 " List EPG data. Without any parameters all data of all channels is\n"
235 " listed. If a channel is given (either by number or by channel ID),\n"
236 " only data for that channel is listed. 'now', 'next', or 'at <time>'\n"
237 " restricts the returned data to present events, following events, or\n"
238 " events at the given time (which must be in time_t form).",
239 "LSTR [ <number> [ path ] ]\n"
240 " List recordings. Without option, all recordings are listed. Otherwise\n"
241 " the information for the given recording is listed. If a recording\n"
242 " number and the keyword 'path' is given, the actual file name of that\n"
243 " recording's directory is listed.",
244 "LSTT [ <number> ] [ id ]\n"
245 " List timers. Without option, all timers are listed. Otherwise\n"
246 " only the given timer is listed. If the keyword 'id' is given, the\n"
247 " channels will be listed with their unique channel ids instead of\n"
250 " Displays the given message on the OSD. The message will be queued\n"
251 " and displayed whenever this is suitable.\n",
252 "MODC <number> <settings>\n"
253 " Modify a channel. Settings must be in the same format as returned\n"
254 " by the LSTC command.",
255 "MODT <number> on | off | <settings>\n"
256 " Modify a timer. Settings must be in the same format as returned\n"
257 " by the LSTT command. The special keywords 'on' and 'off' can be\n"
258 " used to easily activate or deactivate a timer.",
259 "MOVC <number> <to>\n"
260 " Move a channel to a new position.",
262 " Create a new channel. Settings must be in the same format as returned\n"
263 " by the LSTC command.",
265 " Create a new timer. Settings must be in the same format as returned\n"
266 " by the LSTT command.",
267 "NEXT [ abs | rel ]\n"
268 " Show the next timer event. If no option is given, the output will be\n"
269 " in human readable form. With option 'abs' the absolute time of the next\n"
270 " event will be given as the number of seconds since the epoch (time_t\n"
271 " format), while with option 'rel' the relative time will be given as the\n"
272 " number of seconds from now until the event. If the absolute time given\n"
273 " is smaller than the current time, or if the relative time is less than\n"
274 " zero, this means that the timer is currently recording and has started\n"
275 " at the given time. The first value in the resulting line is the number\n"
277 "PLAY <number> [ begin | <position> ]\n"
278 " Play the recording with the given number. Before a recording can be\n"
279 " played, an LSTR command must have been executed in order to retrieve\n"
280 " the recording numbers.\n"
281 " The keyword 'begin' plays the recording from its very beginning, while\n"
282 " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n"
283 " position. If neither 'begin' nor a <position> are given, replay is resumed\n"
284 " at the position where any previous replay was stopped, or from the beginning\n"
285 " by default. To control or stop the replay session, use the usual remote\n"
286 " control keypresses via the HITK command.",
287 "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n"
288 " Send a command to a plugin.\n"
289 " The PLUG command without any parameters lists all plugins.\n"
290 " If only a name is given, all commands known to that plugin are listed.\n"
291 " If a command is given (optionally followed by parameters), that command\n"
292 " is sent to the plugin, and the result will be displayed.\n"
293 " The keyword 'help' lists all the SVDRP commands known to the named plugin.\n"
294 " If 'help' is followed by a command, the detailed help for that command is\n"
295 " given. The keyword 'main' initiates a call to the main menu function of the\n"
298 " Put data into the EPG list. The data entered has to strictly follow the\n"
299 " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n"
300 " by itself terminates the input and starts processing of the data (all\n"
301 " entered data is buffered until the terminating '.' is seen).\n"
302 " If a file name is given, epg data will be read from this file (which\n"
303 " must be accessible under the given name from the machine VDR is running\n"
304 " on). In case of file input, no terminating '.' shall be given.\n",
305 "REMO [ on | off ]\n"
306 " Turns the remote control on or off. Without a parameter, the current\n"
307 " status of the remote control is reported.",
309 " Forces an EPG scan. If this is a single DVB device system, the scan\n"
310 " will be done on the primary device unless it is currently recording.",
312 " Return information about disk usage (total, free, percent).",
314 " Updates a timer. Settings must be in the same format as returned\n"
315 " by the LSTT command. If a timer with the same channel, day, start\n"
316 " and stop time does not yet exists, it will be created.",
318 " Initiates a re-read of the recordings directory, which is the SVDRP\n"
319 " equivalent to 'touch .update'.",
320 "VOLU [ <number> | + | - | mute ]\n"
321 " Set the audio volume to the given number (which is limited to the range\n"
322 " 0...255). If the special options '+' or '-' are given, the volume will\n"
323 " be turned up or down, respectively. The option 'mute' will toggle the\n"
324 " audio muting. If no option is given, the current audio volume level will\n"
327 " Exit vdr (SVDRP).\n"
328 " You can also hit Ctrl-D to exit.",
356 const char *q = HelpPage;
359 uint n = q - HelpPage;
360 if (n >=
sizeof(topic))
361 n =
sizeof(topic) - 1;
362 strncpy(topic, HelpPage, n);
376 if (strcasecmp(Cmd, t) == 0)
394 isyslog(
"SVDRP listening on port %d", Port);
409 gethostname(buffer,
sizeof(buffer));
410 Reply(221,
"%s closing connection%s", buffer, Timeout ?
" (timeout)" :
"");
412 isyslog(
"closing SVDRP connection");
438 const char *s = buffer;
440 const char *n = strchr(s,
'\n');
442 if (Code < 0 || n && *(n + 1))
445 sprintf(number,
"%03d%c", abs(Code), cont);
446 if (!(
Send(number) &&
Send(s, n ? n - s : -1) &&
Send(
"\r\n")))
448 s = n ? n + 1 : NULL;
452 Reply(451,
"Zero return code - looks like a programming error!");
453 esyslog(
"SVDRP: zero return code!");
468 const int TopicsPerLine = 5;
470 for (
int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
473 q += sprintf(q,
" ");
474 for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
475 const char *topic =
GetHelpTopic(hp[(y * TopicsPerLine + x)]);
480 Reply(-214,
"%s", buffer);
490 int o = strtol(Option, NULL, 10);
494 else if (strcmp(Option,
"-") == 0) {
501 else if (strcmp(Option,
"+") == 0) {
515 if (strcasecmp(channel->
Name(), Option) == 0) {
524 Reply(501,
"Undefined channel \"%s\"", Option);
531 Reply(554,
"Error switching to channel \"%d\"", channel->
Number());
536 Reply(550,
"Unable to find channel \"%s\"", Option);
555 int o = strtol(Option, NULL, 10);
563 if (!Channel->GroupSep()) {
564 if (strcasecmp(Channel->Name(), Option) == 0) {
565 ChannelID = Channel->GetChannelID();
579 if (p->ChannelID() == ChannelID) {
586 if (ChannelID == Timer->Channel()->GetChannelID().
ClrRid())
587 Timer->SetEvent(NULL);
591 Reply(250,
"EPG data of channel \"%s\" cleared", Option);
594 Reply(550,
"No EPG data found for channel \"%s\"", Option);
599 Reply(451,
"Can't get EPG data");
602 Reply(501,
"Undefined channel \"%s\"", Option);
607 Reply(250,
"EPG data cleared");
611 Reply(451,
"Error while clearing EPG data");
623 if (timer->Channel() == channel) {
624 Reply(550,
"Channel \"%s\" is in use by timer %d", Option, timer->Index() + 1);
630 if (CurrentChannel && channel == CurrentChannel) {
635 CurrentChannelNr = 0;
640 isyslog(
"channel %s deleted", Option);
641 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
647 Reply(250,
"Channel \"%s\" deleted", Option);
650 Reply(501,
"Channel \"%s\" not defined", Option);
653 Reply(550,
"Channels are being edited - try again later");
656 Reply(501,
"Error in channel number \"%s\"", Option);
659 Reply(501,
"Missing channel number");
671 if (recording->
Delete()) {
672 Reply(250,
"Recording \"%s\" deleted", Option);
676 Reply(554,
"Error while deleting recording!");
679 Reply(550,
"Recording \"%s\" is being edited", Option);
682 Reply(550,
"Recording \"%s\" is in use by timer %d", Option, rc->
Timer()->
Index() + 1);
685 Reply(550,
"Recording \"%s\" not found%s", Option,
recordings.
Count() ?
"" :
" (use LSTR before deleting)");
688 Reply(501,
"Error in recording number \"%s\"", Option);
691 Reply(501,
"Missing recording number");
705 Reply(250,
"Timer \"%s\" deleted", Option);
708 Reply(550,
"Timer \"%s\" is recording", Option);
711 Reply(501,
"Timer \"%s\" not defined", Option);
714 Reply(550,
"Timers are being edited - try again later");
717 Reply(501,
"Error in timer number \"%s\"", Option);
720 Reply(501,
"Missing timer number");
733 Reply(250,
"Editing recording \"%s\" [%s]", Option, recording->
Title());
735 Reply(554,
"Can't start editing process");
738 Reply(554,
"Editing process already active");
741 Reply(554,
"No editing marks defined");
744 Reply(550,
"Recording \"%s\" not found%s", Option,
recordings.
Count() ?
"" :
" (use LSTR before editing)");
747 Reply(501,
"Error in recording number \"%s\"", Option);
750 Reply(501,
"Missing recording number");
755 const char *FileName = NULL;
757 int Quality = -1, SizeX = -1, SizeY = -1;
759 char buf[strlen(Option) + 1];
760 char *p = strcpy(buf, Option);
761 const char *delim =
" \t";
763 FileName = strtok_r(p, delim, &strtok_next);
765 const char *Extension = strrchr(FileName,
'.');
767 if (strcasecmp(Extension,
".jpg") == 0 || strcasecmp(Extension,
".jpeg") == 0)
769 else if (strcasecmp(Extension,
".pnm") == 0)
772 Reply(501,
"Unknown image type \"%s\"", Extension + 1);
775 if (Extension == FileName)
778 else if (strcmp(FileName,
"-") == 0)
781 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
782 if (strcasecmp(p,
"JPEG") == 0 || strcasecmp(p,
"PNM") == 0) {
784 p = strtok_r(NULL, delim, &strtok_next);
790 Reply(501,
"Invalid quality \"%s\"", p);
796 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
800 Reply(501,
"Invalid sizex \"%s\"", p);
803 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
807 Reply(501,
"Invalid sizey \"%s\"", p);
812 Reply(501,
"Missing sizey");
816 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
817 Reply(501,
"Unexpected parameter \"%s\"", p);
821 char RealFileName[PATH_MAX];
826 const char *slash = strrchr(FileName,
'/');
831 slash = strrchr(FileName,
'/');
834 char *r = realpath(t, RealFileName);
837 Reply(501,
"Invalid file name \"%s\"", FileName);
840 strcat(RealFileName, slash);
841 FileName = RealFileName;
843 Reply(501,
"Invalid file name \"%s\"", FileName);
848 Reply(550,
"Grabbing to file not allowed (use \"GRAB -\" instead)");
857 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
859 if (
safe_write(fd, Image, ImageSize) == ImageSize) {
860 dsyslog(
"grabbed image to %s", FileName);
861 Reply(250,
"Grabbed image %s", Option);
865 Reply(451,
"Can't write to '%s'", FileName);
871 Reply(451,
"Can't open '%s'", FileName);
877 while ((s = Base64.
NextLine()) != NULL)
878 Reply(-216,
"%s", s);
879 Reply(216,
"Grabbed image %s", Option);
884 Reply(451,
"Grab image failed");
887 Reply(501,
"Missing filename");
895 Reply(-214,
"%s", hp);
897 Reply(504,
"HELP topic \"%s\" unknown", Option);
903 Reply(-214,
"Topics:");
912 Reply(-214,
"To report bugs in the implementation send email to");
913 Reply(-214,
" vdr-bugs@tvdr.de");
915 Reply(214,
"End of HELP info");
922 Reply(550,
"Remote control currently disabled (key \"%s\" discarded)", Option);
925 char buf[strlen(Option) + 1];
927 const char *delim =
" \t";
929 char *p = strtok_r(buf, delim, &strtok_next);
935 Reply(451,
"Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
940 Reply(504,
"Unknown key: \"%s\"", p);
944 p = strtok_r(NULL, delim, &strtok_next);
946 Reply(250,
"Key%s \"%s\" accepted", NumKeys > 1 ?
"s" :
"", Option);
949 Reply(-214,
"Valid <key> names for the HITK command:");
950 for (
int i = 0; i <
kNone; i++) {
953 Reply(214,
"End of key list");
959 bool WithGroupSeps = strcasecmp(Option,
":groups") == 0;
960 if (*Option && !WithGroupSeps) {
966 Reply(501,
"Channel \"%s\" not defined", Option);
972 if (!channel->GroupSep()) {
973 if (strcasestr(channel->Name(), Option)) {
984 Reply(501,
"Channel \"%s\" not defined", Option);
990 Reply(channel->Next() ? -250: 250,
"%d %s", channel->GroupSep() ? 0 : channel->Number(), *channel->ToText());
991 else if (!channel->GroupSep())
992 Reply(channel->Number() <
Channels.
MaxNumber() ? -250 : 250,
"%d %s", channel->Number(), *channel->ToText());
996 Reply(550,
"No channels defined");
1008 char buf[strlen(Option) + 1];
1009 strcpy(buf, Option);
1010 const char *delim =
" \t";
1012 char *p = strtok_r(buf, delim, &strtok_next);
1013 while (p && DumpMode ==
dmAll) {
1014 if (strcasecmp(p,
"NOW") == 0)
1016 else if (strcasecmp(p,
"NEXT") == 0)
1018 else if (strcasecmp(p,
"AT") == 0) {
1020 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1022 AtTime = strtol(p, NULL, 10);
1024 Reply(501,
"Invalid time");
1029 Reply(501,
"Missing time");
1033 else if (!Schedule) {
1042 Reply(550,
"No schedule found");
1047 Reply(550,
"Channel \"%s\" not defined", p);
1052 Reply(501,
"Unknown option: \"%s\"", p);
1055 p = strtok_r(NULL, delim, &strtok_next);
1060 FILE *f = fdopen(fd,
"w");
1063 Schedule->
Dump(f,
"215-", DumpMode, AtTime);
1065 Schedules->
Dump(f,
"215-", DumpMode, AtTime);
1067 Reply(215,
"End of EPG data");
1071 Reply(451,
"Can't open file connection");
1076 Reply(451,
"Can't dup stream descriptor");
1079 Reply(451,
"Can't get EPG data");
1088 char buf[strlen(Option) + 1];
1089 strcpy(buf, Option);
1090 const char *delim =
" \t";
1092 char *p = strtok_r(buf, delim, &strtok_next);
1096 Number = strtol(p, NULL, 10);
1098 Reply(501,
"Error in recording number \"%s\"", Option);
1102 else if (strcasecmp(p,
"PATH") == 0)
1105 Reply(501,
"Unknown option: \"%s\"", p);
1108 p = strtok_r(NULL, delim, &strtok_next);
1113 FILE *f = fdopen(
file,
"w");
1120 Reply(215,
"End of recording information");
1125 Reply(451,
"Can't open file connection");
1128 Reply(550,
"Recording \"%s\" not found", Option);
1139 Reply(550,
"No recordings available");
1147 char buf[strlen(Option) + 1];
1148 strcpy(buf, Option);
1149 const char *delim =
" \t";
1151 char *p = strtok_r(buf, delim, &strtok_next);
1154 Number = strtol(p, NULL, 10);
1155 else if (strcasecmp(p,
"ID") == 0)
1158 Reply(501,
"Unknown option: \"%s\"", p);
1161 p = strtok_r(NULL, delim, &strtok_next);
1169 Reply(501,
"Timer \"%s\" not defined", Option);
1177 Reply(501,
"Timer \"%d\" not found", i + 1);
1181 Reply(550,
"No timers defined");
1187 isyslog(
"SVDRP message: '%s'", Option);
1189 Reply(250,
"Message queued");
1192 Reply(501,
"Missing message");
1199 int n = strtol(Option, &tail, 10);
1200 if (tail && tail != Option) {
1206 if (ch.
Parse(tail)) {
1215 Reply(501,
"Channel settings are not unique");
1218 Reply(501,
"Error in channel settings");
1221 Reply(501,
"Channel \"%d\" not defined", n);
1224 Reply(550,
"Channels are being edited - try again later");
1227 Reply(501,
"Error in channel number");
1230 Reply(501,
"Missing channel settings");
1237 int n = strtol(Option, &tail, 10);
1238 if (tail && tail != Option) {
1244 if (strcasecmp(tail,
"ON") == 0)
1246 else if (strcasecmp(tail,
"OFF") == 0)
1248 else if (!t.
Parse(tail)) {
1249 Reply(501,
"Error in timer settings");
1258 Reply(501,
"Timer \"%d\" not defined", n);
1261 Reply(550,
"Timers are being edited - try again later");
1264 Reply(501,
"Error in timer number");
1267 Reply(501,
"Missing timer settings");
1275 int From = strtol(Option, &tail, 10);
1276 if (tail && tail != Option) {
1278 if (tail && tail != Option) {
1279 int To = strtol(tail, NULL, 10);
1286 int FromNumber = FromChannel->
Number();
1287 int ToNumber = ToChannel->
Number();
1288 if (FromNumber != ToNumber) {
1292 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
1298 isyslog(
"channel %d moved to %d", FromNumber, ToNumber);
1299 Reply(250,
"Channel \"%d\" moved to \"%d\"", From, To);
1302 Reply(501,
"Can't move channel to same position");
1305 Reply(501,
"Channel \"%d\" not defined", To);
1308 Reply(501,
"Channel \"%d\" not defined", From);
1311 Reply(501,
"Error in channel number");
1314 Reply(501,
"Error in channel number");
1317 Reply(550,
"Channels or timers are being edited - try again later");
1320 Reply(501,
"Missing channel number");
1327 if (ch.
Parse(Option)) {
1338 Reply(501,
"Channel settings are not unique");
1341 Reply(501,
"Error in channel settings");
1344 Reply(501,
"Missing channel settings");
1351 if (timer->
Parse(Option)) {
1359 Reply(501,
"Error in timer settings");
1363 Reply(501,
"Missing timer settings");
1371 int Number = t->
Index() + 1;
1374 else if (strcasecmp(Option,
"ABS") == 0)
1375 Reply(250,
"%d %ld", Number, Start);
1376 else if (strcasecmp(Option,
"REL") == 0)
1377 Reply(250,
"%d %ld", Number, Start - time(NULL));
1379 Reply(501,
"Unknown option: \"%s\"", Option);
1382 Reply(550,
"No active timers");
1388 char *opt = strdup(Option);
1391 while (*option && !isspace(*option))
1404 if (strcasecmp(option,
"BEGIN") != 0)
1415 Reply(250,
"Playing recording \"%s\" [%s]", num, recording->
Title());
1418 Reply(550,
"Recording \"%s\" not found%s", num,
recordings.
Count() ?
"" :
" (use LSTR before playing)");
1421 Reply(501,
"Error in recording number \"%s\"", num);
1425 Reply(501,
"Missing recording number");
1431 char *opt = strdup(Option);
1433 char *option = name;
1434 while (*option && !isspace(*option))
1443 while (*option && !isspace(*option))
1449 if (!*cmd || strcasecmp(cmd,
"HELP") == 0) {
1450 if (*cmd && *option) {
1453 Reply(-214,
"%s", hp);
1454 Reply(214,
"End of HELP info");
1457 Reply(504,
"HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->
Name());
1463 Reply(-214,
"SVDRP commands:");
1465 Reply(214,
"End of HELP info");
1468 Reply(214,
"This plugin has no SVDRP commands");
1471 else if (strcasecmp(cmd,
"MAIN") == 0) {
1473 Reply(250,
"Initiated call to main menu function of plugin \"%s\"", plugin->
Name());
1475 Reply(550,
"A plugin call is already pending - please try again later");
1478 int ReplyCode = 900;
1481 Reply(abs(ReplyCode),
"%s", *s);
1483 Reply(500,
"Command unrecognized: \"%s\"", cmd);
1487 Reply(550,
"Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
1491 Reply(-214,
"Available plugins:");
1495 Reply(214,
"End of plugin list");
1502 FILE *f = fopen(Option,
"r");
1506 Reply(250,
"EPG data processed from \"%s\"", Option);
1509 Reply(451,
"Error while processing EPG from \"%s\"", Option);
1513 Reply(501,
"Cannot open file \"%s\"", Option);
1527 if (!strcasecmp(Option,
"ON")) {
1529 Reply(250,
"Remote control enabled");
1531 else if (!strcasecmp(Option,
"OFF")) {
1533 Reply(250,
"Remote control disabled");
1536 Reply(501,
"Invalid Option \"%s\"", Option);
1545 Reply(250,
"EPG scan triggered");
1551 if (strcasecmp(Option,
"DISK") == 0) {
1554 Reply(250,
"%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
1557 Reply(501,
"Invalid Option \"%s\"", Option);
1560 Reply(501,
"No option given");
1567 if (timer->
Parse(Option)) {
1585 Reply(550,
"Timers are being edited - try again later");
1588 Reply(501,
"Error in timer settings");
1592 Reply(501,
"Missing timer settings");
1598 Reply(250,
"Re-read of recordings directory triggered");
1606 else if (strcmp(Option,
"+") == 0)
1608 else if (strcmp(Option,
"-") == 0)
1610 else if (strcasecmp(Option,
"MUTE") == 0)
1613 Reply(501,
"Unknown option: \"%s\"", Option);
1618 Reply(250,
"Audio is mute");
1623 #define CMD(c) (strcasecmp(Cmd, c) == 0)
1640 while (*s && !isspace(*s))
1675 else Reply(500,
"Command unrecognized: \"%s\"", Cmd);
1681 bool SendGreeting = NewConnection;
1686 char buffer[BUFSIZ];
1687 gethostname(buffer,
sizeof(buffer));
1688 time_t now = time(NULL);
1697 if (c ==
'\n' || c == 0x00) {
1712 else if (c == 0x04 &&
numChars == 0) {
1716 else if (c == 0x08 || c == 0x7F) {
1721 else if (c <= 0x03 || c == 0x0D) {
1726 int NewLength =
length + BUFSIZ;
1727 if (
char *NewBuffer = (
char *)realloc(
cmdLine, NewLength)) {
1732 esyslog(
"ERROR: out of memory");
1743 isyslog(
"lost connection to SVDRP client");
1748 isyslog(
"timeout on SVDRP connection");
1759 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)
static unsigned char buf(long p)
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)
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)
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)
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)
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
static bool Active(const char *FileName=NULL)
Returns true if the cutter is currently active.
void CmdUPDT(const char *Option)
cString ToDescr(void) const
int VideoDiskSpace(int *FreeMB, int *UsedMB)
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)
static bool ClearAll(void)
void CmdLSTR(const char *Option)
static cRecordControl * GetRecordControl(const char *FileName)
void DelByName(const char *FileName)
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
tChannelID & ClrRid(void)
void Del(cTimer *Timer, bool DeleteObject=true)
static bool Start(const char *FileName)
void void PrintHelpTopics(const char **hp)
static void Shutdown(void)
bool IsPesRecording(void) const
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.