17 #include <arpa/inet.h>
21 #include <netinet/in.h>
26 #include <sys/socket.h>
70 sock = socket(PF_INET, SOCK_STREAM, 0);
78 setsockopt(
sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr,
sizeof(ReUseAddr));
80 struct sockaddr_in name;
81 name.sin_family = AF_INET;
82 name.sin_port = htons(
port);
84 if (bind(
sock, (
struct sockaddr *)&name,
sizeof(name)) < 0) {
90 int oldflags = fcntl(
sock, F_GETFL, 0);
95 oldflags |= O_NONBLOCK;
96 if (fcntl(
sock, F_SETFL, oldflags) < 0) {
112 struct sockaddr_in clientname;
113 uint size =
sizeof(clientname);
114 int newsock = accept(
sock, (
struct sockaddr *)&clientname, &size);
118 const char *s =
"Access denied!\n";
119 if (write(newsock, s, strlen(s)) < 0)
124 isyslog(
"connect from %s, port %hu - %s", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port), accepted ?
"accepted" :
"DENIED");
126 else if (errno != EINTR && errno != EAGAIN)
137 if ((
f = tmpfile()) != NULL) {
139 message =
"Enter EPG data, end with \".\" on a line by itself";
144 message =
"Error while opening temporary file";
157 if (strcmp(s,
".") != 0) {
167 message =
"EPG data processed";
171 message =
"Error while processing EPG data";
182 #define MAXHELPTOPIC 10
183 #define EITDISABLETIME 10 // seconds until EIT processing is enabled again after a CLRE command
187 "CHAN [ + | - | <number> | <name> | <id> ]\n"
188 " Switch channel up, down or to the given channel number, name or id.\n"
189 " Without option (or after successfully switching to the channel)\n"
190 " it returns the current channel number and name.",
191 "CLRE [ <number> | <name> | <id> ]\n"
192 " Clear the EPG list of the given channel number, name or id.\n"
193 " Without option it clears the entire EPG list.\n"
194 " After a CLRE command, no further EPG processing is done for 10\n"
195 " seconds, so that data sent with subsequent PUTE commands doesn't\n"
196 " interfere with data from the broadcasters.",
197 "CPYR <number> <new name>\n"
198 " Copy the recording with the given number. Before a recording can be\n"
199 " copied, an LSTR command must have been executed in order to retrieve\n"
200 " the recording numbers. The numbers don't change during subsequent CPYR\n"
205 " Delete the recording with the given number. Before a recording can be\n"
206 " deleted, an LSTR command must have been executed in order to retrieve\n"
207 " the recording numbers. The numbers don't change during subsequent DELR\n"
208 " commands. CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n"
209 " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
213 " Edit the recording with the given number. Before a recording can be\n"
214 " edited, an LSTR command must have been executed in order to retrieve\n"
215 " the recording numbers.",
216 "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n"
217 " Grab the current frame and save it to the given file. Images can\n"
218 " be stored as JPEG or PNM, depending on the given file name extension.\n"
219 " The quality of the grabbed image can be in the range 0..100, where 100\n"
220 " (the default) means \"best\" (only applies to JPEG). The size parameters\n"
221 " define the size of the resulting image (default is full screen).\n"
222 " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n"
223 " data will be sent to the SVDRP connection encoded in base64. The same\n"
224 " happens if '-' (a minus sign) is given as file name, in which case the\n"
225 " image format defaults to JPEG.",
227 " The HELP command gives help info.",
228 "HITK [ <key> ... ]\n"
229 " Hit the given remote control key. Without option a list of all\n"
230 " valid key names is given. If more than one key is given, they are\n"
231 " entered into the remote control queue in the given sequence. There\n"
232 " can be up to 31 keys.",
233 "LSTC [ :groups | <number> | <name> | <id> ]\n"
234 " List channels. Without option, all channels are listed. Otherwise\n"
235 " only the given channel is listed. If a name is given, all channels\n"
236 " containing the given string as part of their name are listed.\n"
237 " If ':groups' is given, all channels are listed including group\n"
238 " separators. The channel number of a group separator is always 0.",
239 "LSTE [ <channel> ] [ now | next | at <time> ]\n"
240 " List EPG data. Without any parameters all data of all channels is\n"
241 " listed. If a channel is given (either by number or by channel ID),\n"
242 " only data for that channel is listed. 'now', 'next', or 'at <time>'\n"
243 " restricts the returned data to present events, following events, or\n"
244 " events at the given time (which must be in time_t form).",
245 "LSTR [ <number> [ path ] ]\n"
246 " List recordings. Without option, all recordings are listed. Otherwise\n"
247 " the information for the given recording is listed. If a recording\n"
248 " number and the keyword 'path' is given, the actual file name of that\n"
249 " recording's directory is listed.",
250 "LSTT [ <number> ] [ id ]\n"
251 " List timers. Without option, all timers are listed. Otherwise\n"
252 " only the given timer is listed. If the keyword 'id' is given, the\n"
253 " channels will be listed with their unique channel ids instead of\n"
256 " Displays the given message on the OSD. The message will be queued\n"
257 " and displayed whenever this is suitable.\n",
258 "MODC <number> <settings>\n"
259 " Modify a channel. Settings must be in the same format as returned\n"
260 " by the LSTC command.",
261 "MODT <number> on | off | <settings>\n"
262 " Modify a timer. Settings must be in the same format as returned\n"
263 " by the LSTT command. The special keywords 'on' and 'off' can be\n"
264 " used to easily activate or deactivate a timer.",
265 "MOVC <number> <to>\n"
266 " Move a channel to a new position.",
267 "MOVR <number> <new name>\n"
268 " Move the recording with the given number. Before a recording can be\n"
269 " moved, an LSTR command must have been executed in order to retrieve\n"
270 " the recording numbers. The numbers don't change during subsequent MOVR\n"
273 " Create a new channel. Settings must be in the same format as returned\n"
274 " by the LSTC command.",
276 " Create a new timer. Settings must be in the same format as returned\n"
277 " by the LSTT command.",
278 "NEXT [ abs | rel ]\n"
279 " Show the next timer event. If no option is given, the output will be\n"
280 " in human readable form. With option 'abs' the absolute time of the next\n"
281 " event will be given as the number of seconds since the epoch (time_t\n"
282 " format), while with option 'rel' the relative time will be given as the\n"
283 " number of seconds from now until the event. If the absolute time given\n"
284 " is smaller than the current time, or if the relative time is less than\n"
285 " zero, this means that the timer is currently recording and has started\n"
286 " at the given time. The first value in the resulting line is the number\n"
288 "PLAY <number> [ begin | <position> ]\n"
289 " Play the recording with the given number. Before a recording can be\n"
290 " played, an LSTR command must have been executed in order to retrieve\n"
291 " the recording numbers.\n"
292 " The keyword 'begin' plays the recording from its very beginning, while\n"
293 " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n"
294 " position. If neither 'begin' nor a <position> are given, replay is resumed\n"
295 " at the position where any previous replay was stopped, or from the beginning\n"
296 " by default. To control or stop the replay session, use the usual remote\n"
297 " control keypresses via the HITK command.",
298 "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n"
299 " Send a command to a plugin.\n"
300 " The PLUG command without any parameters lists all plugins.\n"
301 " If only a name is given, all commands known to that plugin are listed.\n"
302 " If a command is given (optionally followed by parameters), that command\n"
303 " is sent to the plugin, and the result will be displayed.\n"
304 " The keyword 'help' lists all the SVDRP commands known to the named plugin.\n"
305 " If 'help' is followed by a command, the detailed help for that command is\n"
306 " given. The keyword 'main' initiates a call to the main menu function of the\n"
309 " Put data into the EPG list. The data entered has to strictly follow the\n"
310 " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n"
311 " by itself terminates the input and starts processing of the data (all\n"
312 " entered data is buffered until the terminating '.' is seen).\n"
313 " If a file name is given, epg data will be read from this file (which\n"
314 " must be accessible under the given name from the machine VDR is running\n"
315 " on). In case of file input, no terminating '.' shall be given.\n",
316 "REMO [ on | off ]\n"
317 " Turns the remote control on or off. Without a parameter, the current\n"
318 " status of the remote control is reported.",
320 " Forces an EPG scan. If this is a single DVB device system, the scan\n"
321 " will be done on the primary device unless it is currently recording.",
323 " Return information about disk usage (total, free, percent).",
325 " Updates a timer. Settings must be in the same format as returned\n"
326 " by the LSTT command. If a timer with the same channel, day, start\n"
327 " and stop time does not yet exists, it will be created.",
329 " Initiates a re-read of the recordings directory, which is the SVDRP\n"
330 " equivalent to 'touch .update'.",
331 "VOLU [ <number> | + | - | mute ]\n"
332 " Set the audio volume to the given number (which is limited to the range\n"
333 " 0...255). If the special options '+' or '-' are given, the volume will\n"
334 " be turned up or down, respectively. The option 'mute' will toggle the\n"
335 " audio muting. If no option is given, the current audio volume level will\n"
338 " Exit vdr (SVDRP).\n"
339 " You can also hit Ctrl-D to exit.",
367 const char *q = HelpPage;
370 uint n = q - HelpPage;
371 if (n >=
sizeof(topic))
372 n =
sizeof(topic) - 1;
373 strncpy(topic, HelpPage, n);
387 if (strcasecmp(Cmd, t) == 0)
405 isyslog(
"SVDRP listening on port %d", Port);
420 gethostname(buffer,
sizeof(buffer));
421 Reply(221,
"%s closing connection%s", buffer, Timeout ?
" (timeout)" :
"");
423 isyslog(
"closing SVDRP connection");
449 const char *s = buffer;
451 const char *n = strchr(s,
'\n');
453 if (Code < 0 || n && *(n + 1))
456 sprintf(number,
"%03d%c", abs(Code), cont);
457 if (!(
Send(number) &&
Send(s, n ? n - s : -1) &&
Send(
"\r\n")))
459 s = n ? n + 1 : NULL;
463 Reply(451,
"Zero return code - looks like a programming error!");
464 esyslog(
"SVDRP: zero return code!");
479 const int TopicsPerLine = 5;
481 for (
int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
484 q += sprintf(q,
" ");
485 for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
486 const char *topic =
GetHelpTopic(hp[(y * TopicsPerLine + x)]);
491 Reply(-214,
"%s", buffer);
501 int o = strtol(Option, NULL, 10);
505 else if (strcmp(Option,
"-") == 0) {
512 else if (strcmp(Option,
"+") == 0) {
526 if (strcasecmp(channel->
Name(), Option) == 0) {
535 Reply(501,
"Undefined channel \"%s\"", Option);
542 Reply(554,
"Error switching to channel \"%d\"", channel->
Number());
547 Reply(550,
"Unable to find channel \"%s\"", Option);
566 int o = strtol(Option, NULL, 10);
574 if (!Channel->GroupSep()) {
575 if (strcasecmp(Channel->Name(), Option) == 0) {
576 ChannelID = Channel->GetChannelID();
590 if (p->ChannelID() == ChannelID) {
597 if (ChannelID == Timer->Channel()->GetChannelID().
ClrRid())
598 Timer->SetEvent(NULL);
602 Reply(250,
"EPG data of channel \"%s\" cleared", Option);
605 Reply(550,
"No EPG data found for channel \"%s\"", Option);
610 Reply(451,
"Can't get EPG data");
613 Reply(501,
"Undefined channel \"%s\"", Option);
618 Reply(250,
"EPG data cleared");
622 Reply(451,
"Error while clearing EPG data");
630 int n = strtol(Option, &tail, 10);
632 if (recording && tail && tail != Option) {
633 char *oldName = strdup(recording->
Name());
637 Reply(250,
"Copying recording \"%s\" to \"%s\"", oldName, tail);
639 Reply(554,
"Can't start file transfer");
642 Reply(554,
"File transfer already active");
646 Reply(550,
"Recording \"%d\" not found%s", n,
Recordings.
Count() ?
"" :
" (use LSTR before copying)");
649 Reply(501,
"Invalid Option \"%s\"", Option);
660 if (timer->Channel() == channel) {
661 Reply(550,
"Channel \"%s\" is in use by timer %d", Option, timer->Index() + 1);
667 if (CurrentChannel && channel == CurrentChannel) {
672 CurrentChannelNr = 0;
677 isyslog(
"channel %s deleted", Option);
678 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
684 Reply(250,
"Channel \"%s\" deleted", Option);
687 Reply(501,
"Channel \"%s\" not defined", Option);
690 Reply(550,
"Channels are being edited - try again later");
693 Reply(501,
"Error in channel number \"%s\"", Option);
696 Reply(501,
"Missing channel number");
708 if (recording->
Delete()) {
709 Reply(250,
"Recording \"%s\" deleted", Option);
713 Reply(554,
"Error while deleting recording!");
716 Reply(550,
"Recording \"%s\" is being edited", Option);
719 Reply(550,
"Recording \"%s\" is in use by timer %d", Option, rc->
Timer()->
Index() + 1);
722 Reply(550,
"Recording \"%s\" not found%s", Option,
recordings.
Count() ?
"" :
" (use LSTR before deleting)");
725 Reply(501,
"Error in recording number \"%s\"", Option);
728 Reply(501,
"Missing recording number");
742 Reply(250,
"Timer \"%s\" deleted", Option);
745 Reply(550,
"Timer \"%s\" is recording", Option);
748 Reply(501,
"Timer \"%s\" not defined", Option);
751 Reply(550,
"Timers are being edited - try again later");
754 Reply(501,
"Error in timer number \"%s\"", Option);
757 Reply(501,
"Missing timer number");
770 Reply(250,
"Editing recording \"%s\" [%s]", Option, recording->
Title());
772 Reply(554,
"Can't start editing process");
775 Reply(554,
"Editing process already active");
778 Reply(554,
"No editing marks defined");
781 Reply(550,
"Recording \"%s\" not found%s", Option,
recordings.
Count() ?
"" :
" (use LSTR before editing)");
784 Reply(501,
"Error in recording number \"%s\"", Option);
787 Reply(501,
"Missing recording number");
792 const char *FileName = NULL;
794 int Quality = -1, SizeX = -1, SizeY = -1;
796 char buf[strlen(Option) + 1];
797 char *p = strcpy(buf, Option);
798 const char *delim =
" \t";
800 FileName = strtok_r(p, delim, &strtok_next);
802 const char *Extension = strrchr(FileName,
'.');
804 if (strcasecmp(Extension,
".jpg") == 0 || strcasecmp(Extension,
".jpeg") == 0)
806 else if (strcasecmp(Extension,
".pnm") == 0)
809 Reply(501,
"Unknown image type \"%s\"", Extension + 1);
812 if (Extension == FileName)
815 else if (strcmp(FileName,
"-") == 0)
818 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
819 if (strcasecmp(p,
"JPEG") == 0 || strcasecmp(p,
"PNM") == 0) {
821 p = strtok_r(NULL, delim, &strtok_next);
827 Reply(501,
"Invalid quality \"%s\"", p);
833 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
837 Reply(501,
"Invalid sizex \"%s\"", p);
840 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
844 Reply(501,
"Invalid sizey \"%s\"", p);
849 Reply(501,
"Missing sizey");
853 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
854 Reply(501,
"Unexpected parameter \"%s\"", p);
858 char RealFileName[PATH_MAX];
863 const char *slash = strrchr(FileName,
'/');
868 slash = strrchr(FileName,
'/');
871 char *r = realpath(t, RealFileName);
874 Reply(501,
"Invalid file name \"%s\"", FileName);
877 strcat(RealFileName, slash);
878 FileName = RealFileName;
880 Reply(501,
"Invalid file name \"%s\"", FileName);
885 Reply(550,
"Grabbing to file not allowed (use \"GRAB -\" instead)");
894 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
896 if (
safe_write(fd, Image, ImageSize) == ImageSize) {
897 dsyslog(
"grabbed image to %s", FileName);
898 Reply(250,
"Grabbed image %s", Option);
902 Reply(451,
"Can't write to '%s'", FileName);
908 Reply(451,
"Can't open '%s'", FileName);
914 while ((s = Base64.
NextLine()) != NULL)
915 Reply(-216,
"%s", s);
916 Reply(216,
"Grabbed image %s", Option);
921 Reply(451,
"Grab image failed");
924 Reply(501,
"Missing filename");
932 Reply(-214,
"%s", hp);
934 Reply(504,
"HELP topic \"%s\" unknown", Option);
940 Reply(-214,
"Topics:");
949 Reply(-214,
"To report bugs in the implementation send email to");
950 Reply(-214,
" vdr-bugs@tvdr.de");
952 Reply(214,
"End of HELP info");
959 Reply(550,
"Remote control currently disabled (key \"%s\" discarded)", Option);
962 char buf[strlen(Option) + 1];
964 const char *delim =
" \t";
966 char *p = strtok_r(buf, delim, &strtok_next);
972 Reply(451,
"Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
977 Reply(504,
"Unknown key: \"%s\"", p);
981 p = strtok_r(NULL, delim, &strtok_next);
983 Reply(250,
"Key%s \"%s\" accepted", NumKeys > 1 ?
"s" :
"", Option);
986 Reply(-214,
"Valid <key> names for the HITK command:");
987 for (
int i = 0; i <
kNone; i++) {
990 Reply(214,
"End of key list");
996 bool WithGroupSeps = strcasecmp(Option,
":groups") == 0;
997 if (*Option && !WithGroupSeps) {
1003 Reply(501,
"Channel \"%s\" not defined", Option);
1009 if (!channel->GroupSep()) {
1010 if (strcasestr(channel->Name(), Option)) {
1021 Reply(501,
"Channel \"%s\" not defined", Option);
1027 Reply(channel->Next() ? -250: 250,
"%d %s", channel->GroupSep() ? 0 : channel->Number(), *channel->ToText());
1028 else if (!channel->GroupSep())
1029 Reply(channel->Number() <
Channels.
MaxNumber() ? -250 : 250,
"%d %s", channel->Number(), *channel->ToText());
1033 Reply(550,
"No channels defined");
1045 char buf[strlen(Option) + 1];
1046 strcpy(buf, Option);
1047 const char *delim =
" \t";
1049 char *p = strtok_r(buf, delim, &strtok_next);
1050 while (p && DumpMode ==
dmAll) {
1051 if (strcasecmp(p,
"NOW") == 0)
1053 else if (strcasecmp(p,
"NEXT") == 0)
1055 else if (strcasecmp(p,
"AT") == 0) {
1057 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1059 AtTime = strtol(p, NULL, 10);
1061 Reply(501,
"Invalid time");
1066 Reply(501,
"Missing time");
1070 else if (!Schedule) {
1079 Reply(550,
"No schedule found");
1084 Reply(550,
"Channel \"%s\" not defined", p);
1089 Reply(501,
"Unknown option: \"%s\"", p);
1092 p = strtok_r(NULL, delim, &strtok_next);
1097 FILE *f = fdopen(fd,
"w");
1100 Schedule->
Dump(f,
"215-", DumpMode, AtTime);
1102 Schedules->
Dump(f,
"215-", DumpMode, AtTime);
1104 Reply(215,
"End of EPG data");
1108 Reply(451,
"Can't open file connection");
1113 Reply(451,
"Can't dup stream descriptor");
1116 Reply(451,
"Can't get EPG data");
1125 char buf[strlen(Option) + 1];
1126 strcpy(buf, Option);
1127 const char *delim =
" \t";
1129 char *p = strtok_r(buf, delim, &strtok_next);
1133 Number = strtol(p, NULL, 10);
1135 Reply(501,
"Error in recording number \"%s\"", Option);
1139 else if (strcasecmp(p,
"PATH") == 0)
1142 Reply(501,
"Unknown option: \"%s\"", p);
1145 p = strtok_r(NULL, delim, &strtok_next);
1150 FILE *f = fdopen(
file,
"w");
1157 Reply(215,
"End of recording information");
1162 Reply(451,
"Can't open file connection");
1165 Reply(550,
"Recording \"%s\" not found", Option);
1176 Reply(550,
"No recordings available");
1184 char buf[strlen(Option) + 1];
1185 strcpy(buf, Option);
1186 const char *delim =
" \t";
1188 char *p = strtok_r(buf, delim, &strtok_next);
1191 Number = strtol(p, NULL, 10);
1192 else if (strcasecmp(p,
"ID") == 0)
1195 Reply(501,
"Unknown option: \"%s\"", p);
1198 p = strtok_r(NULL, delim, &strtok_next);
1206 Reply(501,
"Timer \"%s\" not defined", Option);
1214 Reply(501,
"Timer \"%d\" not found", i + 1);
1218 Reply(550,
"No timers defined");
1224 isyslog(
"SVDRP message: '%s'", Option);
1226 Reply(250,
"Message queued");
1229 Reply(501,
"Missing message");
1236 int n = strtol(Option, &tail, 10);
1237 if (tail && tail != Option) {
1243 if (ch.
Parse(tail)) {
1252 Reply(501,
"Channel settings are not unique");
1255 Reply(501,
"Error in channel settings");
1258 Reply(501,
"Channel \"%d\" not defined", n);
1261 Reply(550,
"Channels are being edited - try again later");
1264 Reply(501,
"Error in channel number");
1267 Reply(501,
"Missing channel settings");
1274 int n = strtol(Option, &tail, 10);
1275 if (tail && tail != Option) {
1281 if (strcasecmp(tail,
"ON") == 0)
1283 else if (strcasecmp(tail,
"OFF") == 0)
1285 else if (!t.
Parse(tail)) {
1286 Reply(501,
"Error in timer settings");
1295 Reply(501,
"Timer \"%d\" not defined", n);
1298 Reply(550,
"Timers are being edited - try again later");
1301 Reply(501,
"Error in timer number");
1304 Reply(501,
"Missing timer settings");
1312 int From = strtol(Option, &tail, 10);
1313 if (tail && tail != Option) {
1315 if (tail && tail != Option) {
1316 int To = strtol(tail, NULL, 10);
1323 int FromNumber = FromChannel->
Number();
1324 int ToNumber = ToChannel->
Number();
1325 if (FromNumber != ToNumber) {
1329 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
1335 isyslog(
"channel %d moved to %d", FromNumber, ToNumber);
1336 Reply(250,
"Channel \"%d\" moved to \"%d\"", From, To);
1339 Reply(501,
"Can't move channel to same position");
1342 Reply(501,
"Channel \"%d\" not defined", To);
1345 Reply(501,
"Channel \"%d\" not defined", From);
1348 Reply(501,
"Error in channel number");
1351 Reply(501,
"Error in channel number");
1354 Reply(550,
"Channels or timers are being edited - try again later");
1357 Reply(501,
"Missing channel number");
1364 int n = strtol(Option, &tail, 10);
1366 if (recording && tail && tail != Option) {
1367 char *oldName = strdup(recording->
Name());
1371 Reply(250,
"Moving recording \"%s\" to \"%s\"", oldName, tail);
1373 Reply(554,
"Can't start file transfer");
1376 Reply(554,
"File transfer already active");
1380 Reply(550,
"Recording \"%d\" not found%s", n,
Recordings.
Count() ?
"" :
" (use LSTR before moving)");
1383 Reply(501,
"Invalid Option \"%s\"", Option);
1390 if (ch.
Parse(Option)) {
1401 Reply(501,
"Channel settings are not unique");
1404 Reply(501,
"Error in channel settings");
1407 Reply(501,
"Missing channel settings");
1414 if (timer->
Parse(Option)) {
1422 Reply(501,
"Error in timer settings");
1426 Reply(501,
"Missing timer settings");
1434 int Number = t->
Index() + 1;
1437 else if (strcasecmp(Option,
"ABS") == 0)
1438 Reply(250,
"%d %ld", Number, Start);
1439 else if (strcasecmp(Option,
"REL") == 0)
1440 Reply(250,
"%d %ld", Number, Start - time(NULL));
1442 Reply(501,
"Unknown option: \"%s\"", Option);
1445 Reply(550,
"No active timers");
1451 char *opt = strdup(Option);
1454 while (*option && !isspace(*option))
1467 if (strcasecmp(option,
"BEGIN") != 0)
1478 Reply(250,
"Playing recording \"%s\" [%s]", num, recording->
Title());
1481 Reply(550,
"Recording \"%s\" not found%s", num,
recordings.
Count() ?
"" :
" (use LSTR before playing)");
1484 Reply(501,
"Error in recording number \"%s\"", num);
1488 Reply(501,
"Missing recording number");
1494 char *opt = strdup(Option);
1496 char *option = name;
1497 while (*option && !isspace(*option))
1506 while (*option && !isspace(*option))
1512 if (!*cmd || strcasecmp(cmd,
"HELP") == 0) {
1513 if (*cmd && *option) {
1516 Reply(-214,
"%s", hp);
1517 Reply(214,
"End of HELP info");
1520 Reply(504,
"HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->
Name());
1526 Reply(-214,
"SVDRP commands:");
1528 Reply(214,
"End of HELP info");
1531 Reply(214,
"This plugin has no SVDRP commands");
1534 else if (strcasecmp(cmd,
"MAIN") == 0) {
1536 Reply(250,
"Initiated call to main menu function of plugin \"%s\"", plugin->
Name());
1538 Reply(550,
"A plugin call is already pending - please try again later");
1541 int ReplyCode = 900;
1544 Reply(abs(ReplyCode),
"%s", *s);
1546 Reply(500,
"Command unrecognized: \"%s\"", cmd);
1550 Reply(550,
"Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
1554 Reply(-214,
"Available plugins:");
1558 Reply(214,
"End of plugin list");
1565 FILE *f = fopen(Option,
"r");
1569 Reply(250,
"EPG data processed from \"%s\"", Option);
1572 Reply(451,
"Error while processing EPG from \"%s\"", Option);
1576 Reply(501,
"Cannot open file \"%s\"", Option);
1590 if (!strcasecmp(Option,
"ON")) {
1592 Reply(250,
"Remote control enabled");
1594 else if (!strcasecmp(Option,
"OFF")) {
1596 Reply(250,
"Remote control disabled");
1599 Reply(501,
"Invalid Option \"%s\"", Option);
1608 Reply(250,
"EPG scan triggered");
1614 if (strcasecmp(Option,
"DISK") == 0) {
1617 Reply(250,
"%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
1620 Reply(501,
"Invalid Option \"%s\"", Option);
1623 Reply(501,
"No option given");
1630 if (timer->
Parse(Option)) {
1648 Reply(550,
"Timers are being edited - try again later");
1651 Reply(501,
"Error in timer settings");
1655 Reply(501,
"Missing timer settings");
1661 Reply(250,
"Re-read of recordings directory triggered");
1669 else if (strcmp(Option,
"+") == 0)
1671 else if (strcmp(Option,
"-") == 0)
1673 else if (strcasecmp(Option,
"MUTE") == 0)
1676 Reply(501,
"Unknown option: \"%s\"", Option);
1681 Reply(250,
"Audio is mute");
1686 #define CMD(c) (strcasecmp(Cmd, c) == 0)
1703 while (*s && !isspace(*s))
1740 else Reply(500,
"Command unrecognized: \"%s\"", Cmd);
1746 bool SendGreeting = NewConnection;
1751 char buffer[BUFSIZ];
1752 gethostname(buffer,
sizeof(buffer));
1753 time_t now = time(NULL);
1762 if (c ==
'\n' || c == 0x00) {
1777 else if (c == 0x04 &&
numChars == 0) {
1781 else if (c == 0x08 || c == 0x7F) {
1786 else if (c <= 0x03 || c == 0x0D) {
1791 int NewLength =
length + BUFSIZ;
1792 if (
char *NewBuffer = (
char *)realloc(
cmdLine, NewLength)) {
1797 esyslog(
"ERROR: out of memory");
1808 isyslog(
"lost connection to SVDRP client");
1813 isyslog(
"timeout on SVDRP connection");
1824 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.
void CmdCPYR(const char *Option)
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)
void DelByName(const char *FileName, bool RemoveRecording=true)
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
static bool Start(const char *FileName, const char *TargetFileName=NULL, bool Overwrite=true)
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.
static bool Start(cRecording *Recording, const char *NewName, bool CopyOnly=false)
void SetFlags(uint Flags)
void CmdMESG(const char *Option)
virtual void Move(int From, int To)
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
static bool ClearAll(void)
void CmdLSTR(const char *Option)
static cRecordControl * GetRecordControl(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
tChannelID & ClrRid(void)
void Del(cTimer *Timer, bool DeleteObject=true)
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.