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;
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)
virtual const char ** SVDRPHelpPages(void)
double FramesPerSecond(void) const
virtual const char * Version(void)=0
void CmdGRAB(const char *Option)
const char * Title(char Delimiter= ' ', bool NewIndicator=false, int Level=-1) const
static const char * SystemCharacterTable(void)
static void SetDisableUntil(time_t Time)
static cString sprintf(const char *fmt,...) __attribute__((format(printf
int QueueMessage(eMessageType Type, const char *s, int Seconds=0, int Timeout=0)
Like Message(), but this function may be called from a background thread.
static eKeys FromString(const char *Name)
cString & Truncate(int Index)
Truncate the string at the given Index (if Index is < 0 it is counted from the end of the string)...
bool Parse(const char *s)
cPUTEhandler * PUTEhandler
void CmdMOVC(const char *Option)
bool GroupSep(void) const
const char * GetHelpTopic(const char *HelpPage)
const char * GetHelpPage(const char *Cmd, const char **p)
time_t StartTime(void) const
static char * grabImageDir
static bool Dump(FILE *f=NULL, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0)
void CmdLSTC(const char *Option)
cTimer * GetNextActiveTimer(void)
void Add(cTimer *Timer, cTimer *After=NULL)
static const cSchedules * Schedules(cSchedulesLock &SchedulesLock)
Caller must provide a cSchedulesLock which has to survive the entire time the returned cSchedules is ...
void CmdNEWT(const char *Option)
bool Send(const char *s, int length=-1)
void CmdHITK(const char *Option)
static void SetRecording(const char *FileName)
static int CurrentVolume(void)
void CmdEDIT(const char *Option)
bool ChangeName(const char *NewName)
Changes the name of this recording to the given value.
virtual const char * Description(void)=0
static cString static cString vsprintf(const char *fmt, va_list &ap)
bool Transferring(void) const
Returns true if we are currently in Transfer Mode.
bool Recording(void) const
cTimer * GetTimer(cTimer *Timer)
cRecording * GetByName(const char *FileName)
static int CurrentChannel(void)
Returns the number of the current channel on the primary device.
const char * Name(void) const
void CmdNEXT(const char *Option)
T * Next(const T *object) const
bool Process(const char *s)
void SetVolume(int Volume, bool Absolute=false)
Sets the volume to the given value, either absolutely or relative to the current volume.
bool Parse(const char *s)
bool SwitchChannel(const cChannel *Channel, bool LiveView)
Switches the device to the given Channel, initiating transfer mode if necessary.
bool Write(FILE *f, const char *Prefix="") const
static void SetEnabled(bool Enabled)
int GetNextNormal(int Idx)
void CmdDELT(const char *Option)
void CmdCHAN(const char *Option)
bool HasFlags(uint Flags) const
void Cleanup(time_t Time)
void CmdPLUG(const char *Option)
int GetPrevNormal(int Idx)
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
static bool Read(FILE *f=NULL)
void CmdUPDR(const char *Option)
bool Delete(void)
Changes the file name so that it will no longer be visible in the "Recordings" menu Returns false in ...
bool Put(uint64_t Code, bool Repeat=false, bool Release=false)
bool Open(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
const char * NextLine(void)
Returns the next line of encoded data (terminated by '\0'), or NULL if there is no more encoded data...
static void Cleanup(bool Force=false)
tChannelID GetChannelID(void) const
virtual uchar * GrabImage(int &Size, bool Jpeg=true, int Quality=-1, int SizeX=-1, int SizeY=-1)
Grabs the currently visible screen image.
void CmdMODC(const char *Option)
void SetModified(bool ByUser=false)
void CmdDELC(const char *Option)
cRecordingsHandler RecordingsHandler
static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
cChannel * GetByChannelID(tChannelID ChannelID, bool TryWithoutRid=false, bool TryWithoutPolarization=false)
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)
cSocket(int Port, int Queue=1)
static bool Enabled(void)
void CmdREMO(const char *Option)
const cSchedule * GetSchedule(tChannelID ChannelID) const
void CmdUPDT(const char *Option)
cString ToDescr(void) const
static int VideoDiskSpace(int *FreeMB=NULL, int *UsedMB=NULL)
void CmdPUTE(const char *Option)
bool HasUniqueChannelID(cChannel *NewChannel, cChannel *OldChannel=NULL)
void CmdVOLU(const char *Option)
void Del(cListObject *Object, bool DeleteObject=true)
void Reply(int Code, const char *fmt,...) __attribute__((format(printf
cChannel * GetByNumber(int Number, int SkipGap=0)
static cDevice * PrimaryDevice(void)
Returns the primary device.
void SetFlags(uint Flags)
void CmdMESG(const char *Option)
virtual void Move(int From, int To)
cRecordings Recordings
Any access to Recordings that loops through the list of recordings needs to hold a thread lock on thi...
static void SetCurrentChannel(const cChannel *Channel)
Sets the number of the current channel on the primary device, without actually switching to it...
static void SetGrabImageDir(const char *GrabImageDir)
const char * Name(void) const
Returns the full name of the recording (without the video directory.
static bool ClearAll(void)
void CmdLSTR(const char *Option)
static cRecordControl * GetRecordControl(const char *FileName)
void DelByName(const char *FileName)
void CmdMOVR(const char *Option)
static cPlugin * GetPlugin(int Index)
bool Acceptable(in_addr_t Address)
void ClrFlags(uint Flags)
static const tChannelID InvalidID
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
bool SwitchTo(int Number)
void Close(bool SendReply=false, bool Timeout=false)
cString ToText(bool UseChannelID=false) const
void CmdDELR(const char *Option)
char * ExchangeChars(char *s, bool ToFileSystem)
void Dump(FILE *f, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0) const
void CmdNEWC(const char *Option)
void CmdSTAT(const char *Option)
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
tChannelID & ClrRid(void)
void Del(cTimer *Timer, bool DeleteObject=true)
void void PrintHelpTopics(const char **hp)
static void Shutdown(void)
void AddByName(const char *FileName, bool TriggerUpdate=true)
bool IsPesRecording(void) const
int IsInUse(void) const
Checks whether this recording is currently in use and therefore shall not be tampered with...
bool Add(int Usage, const char *FileNameSrc, const char *FileNameDst=NULL)
Adds the given FileNameSrc to the recordings handler for (later) processing.
void CmdLSTE(const char *Option)
static const char * ToString(eKeys Key, bool Translate=false)
void CmdSCAN(const char *Option)
static bool CallPlugin(const char *Plugin)
Initiates calling the given plugin's main menu function.