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.",
195 "CPYR <number> <new name>\n" 196 " Copy the recording with the given number. Before a recording can be\n" 197 " copied, an LSTR command must have been executed in order to retrieve\n" 198 " the recording numbers.\n" 202 " Delete the recording with the given number. Before a recording can be\n" 203 " deleted, an LSTR command must have been executed in order to retrieve\n" 204 " the recording numbers. The numbers don't change during subsequent DELR\n" 205 " commands. CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n" 206 " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
210 " Edit the recording with the given number. Before a recording can be\n" 211 " edited, an LSTR command must have been executed in order to retrieve\n" 212 " the recording numbers.",
213 "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n" 214 " Grab the current frame and save it to the given file. Images can\n" 215 " be stored as JPEG or PNM, depending on the given file name extension.\n" 216 " The quality of the grabbed image can be in the range 0..100, where 100\n" 217 " (the default) means \"best\" (only applies to JPEG). The size parameters\n" 218 " define the size of the resulting image (default is full screen).\n" 219 " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n" 220 " data will be sent to the SVDRP connection encoded in base64. The same\n" 221 " happens if '-' (a minus sign) is given as file name, in which case the\n" 222 " image format defaults to JPEG.",
224 " The HELP command gives help info.",
225 "HITK [ <key> ... ]\n" 226 " Hit the given remote control key. Without option a list of all\n" 227 " valid key names is given. If more than one key is given, they are\n" 228 " entered into the remote control queue in the given sequence. There\n" 229 " can be up to 31 keys.",
230 "LSTC [ :groups | <number> | <name> | <id> ]\n" 231 " List channels. Without option, all channels are listed. Otherwise\n" 232 " only the given channel is listed. If a name is given, all channels\n" 233 " containing the given string as part of their name are listed.\n" 234 " If ':groups' is given, all channels are listed including group\n" 235 " separators. The channel number of a group separator is always 0.",
236 "LSTE [ <channel> ] [ now | next | at <time> ]\n" 237 " List EPG data. Without any parameters all data of all channels is\n" 238 " listed. If a channel is given (either by number or by channel ID),\n" 239 " only data for that channel is listed. 'now', 'next', or 'at <time>'\n" 240 " restricts the returned data to present events, following events, or\n" 241 " events at the given time (which must be in time_t form).",
242 "LSTR [ <number> [ path ] ]\n" 243 " List recordings. Without option, all recordings are listed. Otherwise\n" 244 " the information for the given recording is listed. If a recording\n" 245 " number and the keyword 'path' is given, the actual file name of that\n" 246 " recording's directory is listed.",
247 "LSTT [ <number> ] [ id ]\n" 248 " List timers. Without option, all timers are listed. Otherwise\n" 249 " only the given timer is listed. If the keyword 'id' is given, the\n" 250 " channels will be listed with their unique channel ids instead of\n" 253 " Displays the given message on the OSD. The message will be queued\n" 254 " and displayed whenever this is suitable.\n",
255 "MODC <number> <settings>\n" 256 " Modify a channel. Settings must be in the same format as returned\n" 257 " by the LSTC command.",
258 "MODT <number> on | off | <settings>\n" 259 " Modify a timer. Settings must be in the same format as returned\n" 260 " by the LSTT command. The special keywords 'on' and 'off' can be\n" 261 " used to easily activate or deactivate a timer.",
262 "MOVC <number> <to>\n" 263 " Move a channel to a new position.",
264 "MOVR <number> <new name>\n" 265 " Move the recording with the given number. Before a recording can be\n" 266 " moved, an LSTR command must have been executed in order to retrieve\n" 267 " the recording numbers. The numbers don't change during subsequent MOVR\n" 270 " Create a new channel. Settings must be in the same format as returned\n" 271 " by the LSTC command.",
273 " Create a new timer. Settings must be in the same format as returned\n" 274 " by the LSTT command.",
275 "NEXT [ abs | rel ]\n" 276 " Show the next timer event. If no option is given, the output will be\n" 277 " in human readable form. With option 'abs' the absolute time of the next\n" 278 " event will be given as the number of seconds since the epoch (time_t\n" 279 " format), while with option 'rel' the relative time will be given as the\n" 280 " number of seconds from now until the event. If the absolute time given\n" 281 " is smaller than the current time, or if the relative time is less than\n" 282 " zero, this means that the timer is currently recording and has started\n" 283 " at the given time. The first value in the resulting line is the number\n" 285 "PLAY <number> [ begin | <position> ]\n" 286 " Play the recording with the given number. Before a recording can be\n" 287 " played, an LSTR command must have been executed in order to retrieve\n" 288 " the recording numbers.\n" 289 " The keyword 'begin' plays the recording from its very beginning, while\n" 290 " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n" 291 " position. If neither 'begin' nor a <position> are given, replay is resumed\n" 292 " at the position where any previous replay was stopped, or from the beginning\n" 293 " by default. To control or stop the replay session, use the usual remote\n" 294 " control keypresses via the HITK command.",
295 "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n" 296 " Send a command to a plugin.\n" 297 " The PLUG command without any parameters lists all plugins.\n" 298 " If only a name is given, all commands known to that plugin are listed.\n" 299 " If a command is given (optionally followed by parameters), that command\n" 300 " is sent to the plugin, and the result will be displayed.\n" 301 " The keyword 'help' lists all the SVDRP commands known to the named plugin.\n" 302 " If 'help' is followed by a command, the detailed help for that command is\n" 303 " given. The keyword 'main' initiates a call to the main menu function of the\n" 306 " Put data into the EPG list. The data entered has to strictly follow the\n" 307 " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n" 308 " by itself terminates the input and starts processing of the data (all\n" 309 " entered data is buffered until the terminating '.' is seen).\n" 310 " If a file name is given, epg data will be read from this file (which\n" 311 " must be accessible under the given name from the machine VDR is running\n" 312 " on). In case of file input, no terminating '.' shall be given.\n",
313 "REMO [ on | off ]\n" 314 " Turns the remote control on or off. Without a parameter, the current\n" 315 " status of the remote control is reported.",
317 " Forces an EPG scan. If this is a single DVB device system, the scan\n" 318 " will be done on the primary device unless it is currently recording.",
320 " Return information about disk usage (total, free, percent).",
322 " Updates a timer. Settings must be in the same format as returned\n" 323 " by the LSTT command. If a timer with the same channel, day, start\n" 324 " and stop time does not yet exists, it will be created.",
326 " Initiates a re-read of the recordings directory, which is the SVDRP\n" 327 " equivalent to 'touch .update'.",
328 "VOLU [ <number> | + | - | mute ]\n" 329 " Set the audio volume to the given number (which is limited to the range\n" 330 " 0...255). If the special options '+' or '-' are given, the volume will\n" 331 " be turned up or down, respectively. The option 'mute' will toggle the\n" 332 " audio muting. If no option is given, the current audio volume level will\n" 335 " Exit vdr (SVDRP).\n" 336 " You can also hit Ctrl-D to exit.",
364 const char *q = HelpPage;
367 uint n = q - HelpPage;
368 if (n >=
sizeof(topic))
369 n =
sizeof(topic) - 1;
370 strncpy(topic, HelpPage, n);
384 if (strcasecmp(Cmd, t) == 0)
402 isyslog(
"SVDRP listening on port %d", Port);
417 gethostname(buffer,
sizeof(buffer));
418 Reply(221,
"%s closing connection%s", buffer, Timeout ?
" (timeout)" :
"");
420 isyslog(
"closing SVDRP connection");
446 const char *s = buffer;
448 const char *n = strchr(s,
'\n');
450 if (Code < 0 || n && *(n + 1))
453 sprintf(number,
"%03d%c", abs(Code), cont);
454 if (!(
Send(number) &&
Send(s, n ? n - s : -1) &&
Send(
"\r\n")))
456 s = n ? n + 1 : NULL;
460 Reply(451,
"Zero return code - looks like a programming error!");
461 esyslog(
"SVDRP: zero return code!");
476 const int TopicsPerLine = 5;
478 for (
int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
481 q += sprintf(q,
" ");
482 for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
483 const char *topic =
GetHelpTopic(hp[(y * TopicsPerLine + x)]);
488 Reply(-214,
"%s", buffer);
498 int o = strtol(Option, NULL, 10);
502 else if (strcmp(Option,
"-") == 0) {
509 else if (strcmp(Option,
"+") == 0) {
523 if (strcasecmp(channel->
Name(), Option) == 0) {
532 Reply(501,
"Undefined channel \"%s\"", Option);
539 Reply(554,
"Error switching to channel \"%d\"", channel->
Number());
544 Reply(550,
"Unable to find channel \"%s\"", Option);
563 int o = strtol(Option, NULL, 10);
571 if (!Channel->GroupSep()) {
572 if (strcasecmp(Channel->Name(), Option) == 0) {
573 ChannelID = Channel->GetChannelID();
587 if (p->ChannelID() == ChannelID) {
594 if (ChannelID == Timer->Channel()->GetChannelID().
ClrRid())
595 Timer->SetEvent(NULL);
599 Reply(250,
"EPG data of channel \"%s\" cleared", Option);
602 Reply(550,
"No EPG data found for channel \"%s\"", Option);
607 Reply(451,
"Can't get EPG data");
610 Reply(501,
"Undefined channel \"%s\"", Option);
615 Reply(250,
"EPG data cleared");
619 Reply(451,
"Error while clearing EPG data");
631 if (timer->Channel() == channel) {
632 Reply(550,
"Channel \"%s\" is in use by timer %d", Option, timer->Index() + 1);
638 if (CurrentChannel && channel == CurrentChannel) {
643 CurrentChannelNr = 0;
648 isyslog(
"channel %s deleted", Option);
649 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
655 Reply(250,
"Channel \"%s\" deleted", Option);
658 Reply(501,
"Channel \"%s\" not defined", Option);
661 Reply(550,
"Channels are being edited - try again later");
664 Reply(501,
"Error in channel number \"%s\"", Option);
667 Reply(501,
"Missing channel number");
677 else if ((Reason &
ruCut) != 0)
680 return cString::sprintf(
"Recording \"%s\" is being copied/moved", RecordingId);
689 char *opt = strdup(Option);
692 while (*option && !isspace(*option))
699 if (
int RecordingInUse = recording->
IsInUse())
707 if (strcmp(newName, recording->
Name())) {
713 Reply(250,
"Recording \"%s\" copied to \"%s\"", recording->
Name(), *newName);
716 Reply(554,
"Error while copying recording \"%s\" to \"%s\"!", recording->
Name(), *newName);
719 Reply(501,
"Identical new recording name");
722 Reply(501,
"Missing new recording name");
726 Reply(550,
"Recording \"%s\" not found%s", num,
recordings.
Count() ?
"" :
" (use LSTR before moving)");
729 Reply(501,
"Error in recording number \"%s\"", num);
733 Reply(501,
"Missing recording number");
742 if (
int RecordingInUse = recording->
IsInUse())
745 if (recording->
Delete()) {
746 Reply(250,
"Recording \"%s\" deleted", Option);
750 Reply(554,
"Error while deleting recording!");
754 Reply(550,
"Recording \"%s\" not found%s", Option,
recordings.
Count() ?
"" :
" (use LSTR before deleting)");
757 Reply(501,
"Error in recording number \"%s\"", Option);
760 Reply(501,
"Missing recording number");
774 Reply(250,
"Timer \"%s\" deleted", Option);
777 Reply(550,
"Timer \"%s\" is recording", Option);
780 Reply(501,
"Timer \"%s\" not defined", Option);
783 Reply(550,
"Timers are being edited - try again later");
786 Reply(501,
"Error in timer number \"%s\"", Option);
789 Reply(501,
"Missing timer number");
801 Reply(250,
"Editing recording \"%s\" [%s]", Option, recording->
Title());
803 Reply(554,
"Can't start editing process");
806 Reply(554,
"No editing marks defined");
809 Reply(550,
"Recording \"%s\" not found%s", Option,
recordings.
Count() ?
"" :
" (use LSTR before editing)");
812 Reply(501,
"Error in recording number \"%s\"", Option);
815 Reply(501,
"Missing recording number");
820 const char *FileName = NULL;
822 int Quality = -1, SizeX = -1, SizeY = -1;
824 char buf[strlen(Option) + 1];
825 char *p = strcpy(buf, Option);
826 const char *delim =
" \t";
828 FileName = strtok_r(p, delim, &strtok_next);
830 const char *Extension = strrchr(FileName,
'.');
832 if (strcasecmp(Extension,
".jpg") == 0 || strcasecmp(Extension,
".jpeg") == 0)
834 else if (strcasecmp(Extension,
".pnm") == 0)
837 Reply(501,
"Unknown image type \"%s\"", Extension + 1);
840 if (Extension == FileName)
843 else if (strcmp(FileName,
"-") == 0)
846 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
847 if (strcasecmp(p,
"JPEG") == 0 || strcasecmp(p,
"PNM") == 0) {
849 p = strtok_r(NULL, delim, &strtok_next);
855 Reply(501,
"Invalid quality \"%s\"", p);
861 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
865 Reply(501,
"Invalid sizex \"%s\"", p);
868 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
872 Reply(501,
"Invalid sizey \"%s\"", p);
877 Reply(501,
"Missing sizey");
881 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
882 Reply(501,
"Unexpected parameter \"%s\"", p);
886 char RealFileName[PATH_MAX];
891 const char *slash = strrchr(FileName,
'/');
896 slash = strrchr(FileName,
'/');
899 char *r = realpath(t, RealFileName);
902 Reply(501,
"Invalid file name \"%s\"", FileName);
905 strcat(RealFileName, slash);
906 FileName = RealFileName;
908 Reply(501,
"Invalid file name \"%s\"", FileName);
913 Reply(550,
"Grabbing to file not allowed (use \"GRAB -\" instead)");
922 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
924 if (
safe_write(fd, Image, ImageSize) == ImageSize) {
925 dsyslog(
"grabbed image to %s", FileName);
926 Reply(250,
"Grabbed image %s", Option);
930 Reply(451,
"Can't write to '%s'", FileName);
936 Reply(451,
"Can't open '%s'", FileName);
942 while ((s = Base64.
NextLine()) != NULL)
943 Reply(-216,
"%s", s);
944 Reply(216,
"Grabbed image %s", Option);
949 Reply(451,
"Grab image failed");
952 Reply(501,
"Missing filename");
960 Reply(-214,
"%s", hp);
962 Reply(504,
"HELP topic \"%s\" unknown", Option);
968 Reply(-214,
"Topics:");
977 Reply(-214,
"To report bugs in the implementation send email to");
978 Reply(-214,
" vdr-bugs@tvdr.de");
980 Reply(214,
"End of HELP info");
987 Reply(550,
"Remote control currently disabled (key \"%s\" discarded)", Option);
990 char buf[strlen(Option) + 1];
992 const char *delim =
" \t";
994 char *p = strtok_r(buf, delim, &strtok_next);
1000 Reply(451,
"Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
1005 Reply(504,
"Unknown key: \"%s\"", p);
1009 p = strtok_r(NULL, delim, &strtok_next);
1011 Reply(250,
"Key%s \"%s\" accepted", NumKeys > 1 ?
"s" :
"", Option);
1014 Reply(-214,
"Valid <key> names for the HITK command:");
1015 for (
int i = 0; i <
kNone; i++) {
1018 Reply(214,
"End of key list");
1024 bool WithGroupSeps = strcasecmp(Option,
":groups") == 0;
1025 if (*Option && !WithGroupSeps) {
1031 Reply(501,
"Channel \"%s\" not defined", Option);
1037 if (!channel->GroupSep()) {
1038 if (strcasestr(channel->Name(), Option)) {
1049 Reply(501,
"Channel \"%s\" not defined", Option);
1055 Reply(channel->Next() ? -250: 250,
"%d %s", channel->GroupSep() ? 0 : channel->Number(), *channel->ToText());
1056 else if (!channel->GroupSep())
1057 Reply(channel->Number() <
Channels.
MaxNumber() ? -250 : 250,
"%d %s", channel->Number(), *channel->ToText());
1061 Reply(550,
"No channels defined");
1073 char buf[strlen(Option) + 1];
1074 strcpy(buf, Option);
1075 const char *delim =
" \t";
1077 char *p = strtok_r(buf, delim, &strtok_next);
1078 while (p && DumpMode ==
dmAll) {
1079 if (strcasecmp(p,
"NOW") == 0)
1081 else if (strcasecmp(p,
"NEXT") == 0)
1083 else if (strcasecmp(p,
"AT") == 0) {
1085 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1087 AtTime = strtol(p, NULL, 10);
1089 Reply(501,
"Invalid time");
1094 Reply(501,
"Missing time");
1098 else if (!Schedule) {
1107 Reply(550,
"No schedule found");
1112 Reply(550,
"Channel \"%s\" not defined", p);
1117 Reply(501,
"Unknown option: \"%s\"", p);
1120 p = strtok_r(NULL, delim, &strtok_next);
1125 FILE *f = fdopen(fd,
"w");
1128 Schedule->
Dump(f,
"215-", DumpMode, AtTime);
1130 Schedules->
Dump(f,
"215-", DumpMode, AtTime);
1132 Reply(215,
"End of EPG data");
1136 Reply(451,
"Can't open file connection");
1141 Reply(451,
"Can't dup stream descriptor");
1144 Reply(451,
"Can't get EPG data");
1153 char buf[strlen(Option) + 1];
1154 strcpy(buf, Option);
1155 const char *delim =
" \t";
1157 char *p = strtok_r(buf, delim, &strtok_next);
1161 Number = strtol(p, NULL, 10);
1163 Reply(501,
"Error in recording number \"%s\"", Option);
1167 else if (strcasecmp(p,
"PATH") == 0)
1170 Reply(501,
"Unknown option: \"%s\"", p);
1173 p = strtok_r(NULL, delim, &strtok_next);
1178 FILE *f = fdopen(
file,
"w");
1185 Reply(215,
"End of recording information");
1190 Reply(451,
"Can't open file connection");
1193 Reply(550,
"Recording \"%s\" not found", Option);
1204 Reply(550,
"No recordings available");
1212 char buf[strlen(Option) + 1];
1213 strcpy(buf, Option);
1214 const char *delim =
" \t";
1216 char *p = strtok_r(buf, delim, &strtok_next);
1219 Number = strtol(p, NULL, 10);
1220 else if (strcasecmp(p,
"ID") == 0)
1223 Reply(501,
"Unknown option: \"%s\"", p);
1226 p = strtok_r(NULL, delim, &strtok_next);
1234 Reply(501,
"Timer \"%s\" not defined", Option);
1242 Reply(501,
"Timer \"%d\" not found", i + 1);
1246 Reply(550,
"No timers defined");
1252 isyslog(
"SVDRP message: '%s'", Option);
1254 Reply(250,
"Message queued");
1257 Reply(501,
"Missing message");
1264 int n = strtol(Option, &tail, 10);
1265 if (tail && tail != Option) {
1271 if (ch.
Parse(tail)) {
1280 Reply(501,
"Channel settings are not unique");
1283 Reply(501,
"Error in channel settings");
1286 Reply(501,
"Channel \"%d\" not defined", n);
1289 Reply(550,
"Channels are being edited - try again later");
1292 Reply(501,
"Error in channel number");
1295 Reply(501,
"Missing channel settings");
1302 int n = strtol(Option, &tail, 10);
1303 if (tail && tail != Option) {
1309 if (strcasecmp(tail,
"ON") == 0)
1311 else if (strcasecmp(tail,
"OFF") == 0)
1313 else if (!t.
Parse(tail)) {
1314 Reply(501,
"Error in timer settings");
1323 Reply(501,
"Timer \"%d\" not defined", n);
1326 Reply(550,
"Timers are being edited - try again later");
1329 Reply(501,
"Error in timer number");
1332 Reply(501,
"Missing timer settings");
1340 int From = strtol(Option, &tail, 10);
1341 if (tail && tail != Option) {
1343 if (tail && tail != Option) {
1344 int To = strtol(tail, NULL, 10);
1351 int FromNumber = FromChannel->
Number();
1352 int ToNumber = ToChannel->
Number();
1353 if (FromNumber != ToNumber) {
1357 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
1363 isyslog(
"channel %d moved to %d", FromNumber, ToNumber);
1364 Reply(250,
"Channel \"%d\" moved to \"%d\"", From, To);
1367 Reply(501,
"Can't move channel to same position");
1370 Reply(501,
"Channel \"%d\" not defined", To);
1373 Reply(501,
"Channel \"%d\" not defined", From);
1376 Reply(501,
"Error in channel number");
1379 Reply(501,
"Error in channel number");
1382 Reply(550,
"Channels or timers are being edited - try again later");
1385 Reply(501,
"Missing channel number");
1391 char *opt = strdup(Option);
1394 while (*option && !isspace(*option))
1401 if (
int RecordingInUse = recording->
IsInUse())
1409 Reply(250,
"Recording \"%s\" moved to \"%s\"", *oldName, recording->
Name());
1411 Reply(554,
"Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
1414 Reply(501,
"Missing new recording name");
1418 Reply(550,
"Recording \"%s\" not found%s", num,
recordings.
Count() ?
"" :
" (use LSTR before moving)");
1421 Reply(501,
"Error in recording number \"%s\"", num);
1425 Reply(501,
"Missing recording number");
1432 if (ch.
Parse(Option)) {
1443 Reply(501,
"Channel settings are not unique");
1446 Reply(501,
"Error in channel settings");
1449 Reply(501,
"Missing channel settings");
1456 if (timer->
Parse(Option)) {
1464 Reply(501,
"Error in timer settings");
1468 Reply(501,
"Missing timer settings");
1476 int Number = t->
Index() + 1;
1479 else if (strcasecmp(Option,
"ABS") == 0)
1480 Reply(250,
"%d %ld", Number, Start);
1481 else if (strcasecmp(Option,
"REL") == 0)
1482 Reply(250,
"%d %ld", Number, Start - time(NULL));
1484 Reply(501,
"Unknown option: \"%s\"", Option);
1487 Reply(550,
"No active timers");
1493 char *opt = strdup(Option);
1496 while (*option && !isspace(*option))
1509 if (strcasecmp(option,
"BEGIN") != 0)
1520 Reply(250,
"Playing recording \"%s\" [%s]", num, recording->
Title());
1523 Reply(550,
"Recording \"%s\" not found%s", num,
recordings.
Count() ?
"" :
" (use LSTR before playing)");
1526 Reply(501,
"Error in recording number \"%s\"", num);
1530 Reply(501,
"Missing recording number");
1536 char *opt = strdup(Option);
1538 char *option = name;
1539 while (*option && !isspace(*option))
1548 while (*option && !isspace(*option))
1554 if (!*cmd || strcasecmp(cmd,
"HELP") == 0) {
1555 if (*cmd && *option) {
1558 Reply(-214,
"%s", hp);
1559 Reply(214,
"End of HELP info");
1562 Reply(504,
"HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->
Name());
1568 Reply(-214,
"SVDRP commands:");
1570 Reply(214,
"End of HELP info");
1573 Reply(214,
"This plugin has no SVDRP commands");
1576 else if (strcasecmp(cmd,
"MAIN") == 0) {
1578 Reply(250,
"Initiated call to main menu function of plugin \"%s\"", plugin->
Name());
1580 Reply(550,
"A plugin call is already pending - please try again later");
1583 int ReplyCode = 900;
1586 Reply(abs(ReplyCode),
"%s", *s);
1588 Reply(500,
"Command unrecognized: \"%s\"", cmd);
1592 Reply(550,
"Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
1596 Reply(-214,
"Available plugins:");
1600 Reply(214,
"End of plugin list");
1607 FILE *f = fopen(Option,
"r");
1611 Reply(250,
"EPG data processed from \"%s\"", Option);
1614 Reply(451,
"Error while processing EPG from \"%s\"", Option);
1618 Reply(501,
"Cannot open file \"%s\"", Option);
1632 if (!strcasecmp(Option,
"ON")) {
1634 Reply(250,
"Remote control enabled");
1636 else if (!strcasecmp(Option,
"OFF")) {
1638 Reply(250,
"Remote control disabled");
1641 Reply(501,
"Invalid Option \"%s\"", Option);
1650 Reply(250,
"EPG scan triggered");
1656 if (strcasecmp(Option,
"DISK") == 0) {
1659 Reply(250,
"%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
1662 Reply(501,
"Invalid Option \"%s\"", Option);
1665 Reply(501,
"No option given");
1672 if (timer->
Parse(Option)) {
1690 Reply(550,
"Timers are being edited - try again later");
1693 Reply(501,
"Error in timer settings");
1697 Reply(501,
"Missing timer settings");
1703 Reply(250,
"Re-read of recordings directory triggered");
1711 else if (strcmp(Option,
"+") == 0)
1713 else if (strcmp(Option,
"-") == 0)
1715 else if (strcasecmp(Option,
"MUTE") == 0)
1718 Reply(501,
"Unknown option: \"%s\"", Option);
1723 Reply(250,
"Audio is mute");
1728 #define CMD(c) (strcasecmp(Cmd, c) == 0) 1745 while (*s && !isspace(*s))
1781 else Reply(500,
"Command unrecognized: \"%s\"", Cmd);
1787 bool SendGreeting = NewConnection;
1792 char buffer[BUFSIZ];
1793 gethostname(buffer,
sizeof(buffer));
1794 time_t now = time(NULL);
1803 if (c ==
'\n' || c == 0x00) {
1818 else if (c == 0x04 &&
numChars == 0) {
1822 else if (c == 0x08 || c == 0x7F) {
1827 else if (c <= 0x03 || c == 0x0D) {
1832 int NewLength =
length + BUFSIZ;
1833 if (
char *NewBuffer = (
char *)realloc(
cmdLine, NewLength)) {
1838 esyslog(
"ERROR: out of memory");
1849 isyslog(
"lost connection to SVDRP client");
1854 isyslog(
"timeout on SVDRP connection");
1865 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.
void CmdCPYR(const char *Option)
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)
cString & CompactChars(char c)
Compact any sequence of characters 'c' to a single character, and strip all of them from the beginnin...
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)
char * ExchangeChars(char *s, bool ToFileSystem)
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)
void AddByName(const char *FileName, bool TriggerUpdate=true)
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.