17 #include <arpa/inet.h>
22 #include <netinet/in.h>
27 #include <sys/socket.h>
45 #define dbgsvdrp(a...) if (DumpSVDRPDataTransfer) fprintf(stderr, a)
70 void Set(
const sockaddr *SockAddr);
93 const sockaddr_in *Addr = (sockaddr_in *)SockAddr;
94 Set(inet_ntoa(Addr->sin_addr), ntohs(Addr->sin_port));
99 #define MAXUDPBUF 1024
111 bool Connect(
const char *Address);
146 sock =
tcp ? socket(PF_INET, SOCK_STREAM, IPPROTO_IP) : socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
153 setsockopt(
sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr,
sizeof(ReUseAddr));
156 memset(&Addr, 0,
sizeof(Addr));
157 Addr.sin_family = AF_INET;
158 Addr.sin_port = htons(
port);
160 if (bind(
sock, (sockaddr *)&Addr,
sizeof(Addr)) < 0) {
166 int Flags = fcntl(
sock, F_GETFL, 0);
172 if (fcntl(
sock, F_SETFL, Flags) < 0) {
178 if (listen(
sock, 1) < 0) {
192 sock = socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
199 memset(&Addr, 0,
sizeof(Addr));
200 Addr.sin_family = AF_INET;
201 Addr.sin_port = htons(
port);
202 Addr.sin_addr.s_addr = inet_addr(Address);
203 if (connect(
sock, (sockaddr *)&Addr,
sizeof(Addr)) < 0) {
209 int Flags = fcntl(
sock, F_GETFL, 0);
215 if (fcntl(
sock, F_SETFL, Flags) < 0) {
219 dbgsvdrp(
"> %s:%d server connection established\n", Address,
port);
229 int Socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
236 if (setsockopt(
Socket, SOL_SOCKET, SO_BROADCAST, &One,
sizeof(One)) < 0) {
243 memset(&Addr, 0,
sizeof(Addr));
244 Addr.sin_family = AF_INET;
245 Addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
246 Addr.sin_port = htons(
Port);
248 dbgsvdrp(
"> %s:%d %s\n", inet_ntoa(Addr.sin_addr),
Port, Dgram);
250 int Length = strlen(Dgram);
251 int Sent = sendto(
Socket, Dgram, Length, 0, (sockaddr *)&Addr,
sizeof(Addr));
255 return Sent == Length;
262 uint Size =
sizeof(Addr);
263 int NewSock = accept(
sock, (sockaddr *)&Addr, &Size);
267 const char *s =
"Access denied!\n";
268 if (write(NewSock, s, strlen(s)) < 0)
289 uint Size =
sizeof(Addr);
290 int NumBytes = recvfrom(
sock, buf,
sizeof(buf), 0, (sockaddr *)&Addr, &Size);
328 bool Send(
const char *Command);
335 bool HasAddress(
const char *Address,
int Port)
const;
347 :serverIpAddress(Address, Port)
353 timeout = Timeout * 1000 * 9 / 10;
403 #define SVDRPResonseTimeout 5000 // ms
410 if (c ==
'\n' || c == 0x00) {
412 while (numChars > 0 && strchr(
" \t\r\n",
input[numChars - 1]))
413 input[--numChars] = 0;
420 switch (atoi(
input)) {
421 case 220:
if (numChars > 4) {
423 if (
char *t = strchr(n,
' ')) {
440 if (numChars >= 4 &&
input[3] !=
'-')
445 if (numChars >=
length - 1) {
446 int NewLength =
length + BUFSIZ;
447 if (
char *NewBuffer = (
char *)realloc(
input, NewLength)) {
457 input[numChars++] = c;
472 else if (!Response && numChars == 0)
505 if (
Execute(
"LSTT ID", &Response)) {
506 for (
int i = 0; i < Response.
Size(); i++) {
507 char *s = Response[i];
511 else if (Code == 550)
550 if (Params && *Params) {
568 error =
"invalid timeout";
571 error =
"missing server timeout";
574 error =
"missing server apiversion";
577 error =
"missing server vdrversion";
580 error =
"missing server port";
583 error =
"missing server name";
586 error =
"missing server parameters";
604 virtual void Action(
void);
611 bool Execute(
const char *ServerName,
const char *Command,
cStringList *Response = NULL);
619 :
cThread(
"SVDRP client handler", true)
620 ,udpSocket(UdpPort, false)
667 bool TimersModified = Timers->StoreRemoteTimers(Client->
ServerName(), &RemoteTimers);
674 if (*PollTimersCmd) {
675 if (!Client->
Execute(PollTimersCmd))
709 if (ServerParams.
Ok())
736 return Client->Execute(Command, Response);
743 ServerNames->
Clear();
749 return ServerNames->
Size() > 0;
779 if ((
f = tmpfile()) != NULL) {
781 message =
"Enter EPG data, end with \".\" on a line by itself";
786 message =
"Error while opening temporary file";
799 if (strcmp(s,
".") != 0) {
809 message =
"EPG data processed";
813 message =
"Error while processing EPG data";
824 #define MAXHELPTOPIC 10
825 #define EITDISABLETIME 10 // seconds until EIT processing is enabled again after a CLRE command
829 "CHAN [ + | - | <number> | <name> | <id> ]\n"
830 " Switch channel up, down or to the given channel number, name or id.\n"
831 " Without option (or after successfully switching to the channel)\n"
832 " it returns the current channel number and name.",
833 "CLRE [ <number> | <name> | <id> ]\n"
834 " Clear the EPG list of the given channel number, name or id.\n"
835 " Without option it clears the entire EPG list.\n"
836 " After a CLRE command, no further EPG processing is done for 10\n"
837 " seconds, so that data sent with subsequent PUTE commands doesn't\n"
838 " interfere with data from the broadcasters.",
839 "CONN name:<name> port:<port> vdrversion:<vdrversion> apiversion:<apiversion> timeout:<timeout>\n"
840 " Used by peer-to-peer connections between VDRs to tell the other VDR\n"
841 " to establish a connection to this VDR. The name is the SVDRP host name\n"
842 " of this VDR, which may differ from its DNS name.",
843 "CPYR <number> <new name>\n"
844 " Copy the recording with the given number. Before a recording can be\n"
845 " copied, an LSTR command must have been executed in order to retrieve\n"
846 " the recording numbers.\n",
850 " Delete the recording with the given id. Before a recording can be\n"
851 " deleted, an LSTR command should have been executed in order to retrieve\n"
852 " the recording ids. The ids are unique and don't change while this\n"
853 " instance of VDR is running.\n"
854 " CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n"
855 " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
857 " Delete the timer with the given id. If this timer is currently recording,\n"
858 " the recording will be stopped without any warning.",
860 " Edit the recording with the given id. Before a recording can be\n"
861 " edited, an LSTR command should have been executed in order to retrieve\n"
862 " the recording ids.",
863 "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n"
864 " Grab the current frame and save it to the given file. Images can\n"
865 " be stored as JPEG or PNM, depending on the given file name extension.\n"
866 " The quality of the grabbed image can be in the range 0..100, where 100\n"
867 " (the default) means \"best\" (only applies to JPEG). The size parameters\n"
868 " define the size of the resulting image (default is full screen).\n"
869 " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n"
870 " data will be sent to the SVDRP connection encoded in base64. The same\n"
871 " happens if '-' (a minus sign) is given as file name, in which case the\n"
872 " image format defaults to JPEG.",
874 " The HELP command gives help info.",
875 "HITK [ <key> ... ]\n"
876 " Hit the given remote control key. Without option a list of all\n"
877 " valid key names is given. If more than one key is given, they are\n"
878 " entered into the remote control queue in the given sequence. There\n"
879 " can be up to 31 keys.",
880 "LSTC [ :ids ] [ :groups | <number> | <name> | <id> ]\n"
881 " List channels. Without option, all channels are listed. Otherwise\n"
882 " only the given channel is listed. If a name is given, all channels\n"
883 " containing the given string as part of their name are listed.\n"
884 " If ':groups' is given, all channels are listed including group\n"
885 " separators. The channel number of a group separator is always 0.\n"
886 " With ':ids' the channel ids are listed following the channel numbers.\n"
887 " The special number 0 can be given to list the current channel.",
889 " List all available devices. Each device is listed with its name and\n"
890 " whether it is currently the primary device ('P') or it implements a\n"
891 " decoder ('D') and can be used as output device.",
892 "LSTE [ <channel> ] [ now | next | at <time> ]\n"
893 " List EPG data. Without any parameters all data of all channels is\n"
894 " listed. If a channel is given (either by number or by channel ID),\n"
895 " only data for that channel is listed. 'now', 'next', or 'at <time>'\n"
896 " restricts the returned data to present events, following events, or\n"
897 " events at the given time (which must be in time_t form).",
898 "LSTR [ <id> [ path ] ]\n"
899 " List recordings. Without option, all recordings are listed. Otherwise\n"
900 " the information for the given recording is listed. If a recording\n"
901 " id and the keyword 'path' is given, the actual file name of that\n"
902 " recording's directory is listed.\n"
903 " Note that the ids of the recordings are not necessarily given in\n"
905 "LSTT [ <id> ] [ id ]\n"
906 " List timers. Without option, all timers are listed. Otherwise\n"
907 " only the timer with the given id is listed. If the keyword 'id' is\n"
908 " given, the channels will be listed with their unique channel ids\n"
909 " instead of their numbers. This command lists only the timers that are\n"
910 " defined locally on this VDR, not any remote timers from other VDRs.",
912 " Displays the given message on the OSD. The message will be queued\n"
913 " and displayed whenever this is suitable.\n",
914 "MODC <number> <settings>\n"
915 " Modify a channel. Settings must be in the same format as returned\n"
916 " by the LSTC command.",
917 "MODT <id> on | off | <settings>\n"
918 " Modify a timer. Settings must be in the same format as returned\n"
919 " by the LSTT command. The special keywords 'on' and 'off' can be\n"
920 " used to easily activate or deactivate a timer.",
921 "MOVC <number> <to>\n"
922 " Move a channel to a new position.",
923 "MOVR <id> <new name>\n"
924 " Move the recording with the given id. Before a recording can be\n"
925 " moved, an LSTR command should have been executed in order to retrieve\n"
926 " the recording ids. The ids don't change during subsequent MOVR\n"
929 " Create a new channel. Settings must be in the same format as returned\n"
930 " by the LSTC command.",
932 " Create a new timer. Settings must be in the same format as returned\n"
933 " by the LSTT command.",
934 "NEXT [ abs | rel ]\n"
935 " Show the next timer event. If no option is given, the output will be\n"
936 " in human readable form. With option 'abs' the absolute time of the next\n"
937 " event will be given as the number of seconds since the epoch (time_t\n"
938 " format), while with option 'rel' the relative time will be given as the\n"
939 " number of seconds from now until the event. If the absolute time given\n"
940 " is smaller than the current time, or if the relative time is less than\n"
941 " zero, this means that the timer is currently recording and has started\n"
942 " at the given time. The first value in the resulting line is the id\n"
945 " Used by peer-to-peer connections between VDRs to keep the connection\n"
946 " from timing out. May be used at any time and simply returns a line of\n"
947 " the form '<hostname> is alive'.",
948 "PLAY <id> [ begin | <position> ]\n"
949 " Play the recording with the given id. Before a recording can be\n"
950 " played, an LSTR command should have been executed in order to retrieve\n"
951 " the recording ids.\n"
952 " The keyword 'begin' plays the recording from its very beginning, while\n"
953 " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n"
954 " position. If neither 'begin' nor a <position> are given, replay is resumed\n"
955 " at the position where any previous replay was stopped, or from the beginning\n"
956 " by default. To control or stop the replay session, use the usual remote\n"
957 " control keypresses via the HITK command.",
958 "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n"
959 " Send a command to a plugin.\n"
960 " The PLUG command without any parameters lists all plugins.\n"
961 " If only a name is given, all commands known to that plugin are listed.\n"
962 " If a command is given (optionally followed by parameters), that command\n"
963 " is sent to the plugin, and the result will be displayed.\n"
964 " The keyword 'help' lists all the SVDRP commands known to the named plugin.\n"
965 " If 'help' is followed by a command, the detailed help for that command is\n"
966 " given. The keyword 'main' initiates a call to the main menu function of the\n"
968 "POLL <name> timers\n"
969 " Used by peer-to-peer connections between VDRs to inform other machines\n"
970 " about changes to timers. The receiving VDR shall use LSTT to query the\n"
971 " remote machine with the given name about its timers and update its list\n"
972 " of timers accordingly.\n",
973 "PRIM [ <number> ]\n"
974 " Make the device with the given number the primary device.\n"
975 " Without option it returns the currently active primary device in the same\n"
976 " format as used by the LSTD command.",
978 " Put data into the EPG list. The data entered has to strictly follow the\n"
979 " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n"
980 " by itself terminates the input and starts processing of the data (all\n"
981 " entered data is buffered until the terminating '.' is seen).\n"
982 " If a file name is given, epg data will be read from this file (which\n"
983 " must be accessible under the given name from the machine VDR is running\n"
984 " on). In case of file input, no terminating '.' shall be given.\n",
985 "REMO [ on | off ]\n"
986 " Turns the remote control on or off. Without a parameter, the current\n"
987 " status of the remote control is reported.",
989 " Forces an EPG scan. If this is a single DVB device system, the scan\n"
990 " will be done on the primary device unless it is currently recording.",
992 " Return information about disk usage (total, free, percent).",
994 " Updates a timer. Settings must be in the same format as returned\n"
995 " by the LSTT command. If a timer with the same channel, day, start\n"
996 " and stop time does not yet exist, it will be created.",
998 " Initiates a re-read of the recordings directory, which is the SVDRP\n"
999 " equivalent to 'touch .update'.",
1000 "VOLU [ <number> | + | - | mute ]\n"
1001 " Set the audio volume to the given number (which is limited to the range\n"
1002 " 0...255). If the special options '+' or '-' are given, the volume will\n"
1003 " be turned up or down, respectively. The option 'mute' will toggle the\n"
1004 " audio muting. If no option is given, the current audio volume level will\n"
1007 " Exit vdr (SVDRP).\n"
1008 " You can also hit Ctrl-D to exit.",
1036 const char *q = HelpPage;
1039 uint n = q - HelpPage;
1040 if (n >=
sizeof(topic))
1041 n =
sizeof(topic) - 1;
1042 strncpy(topic, HelpPage, n);
1056 if (strcasecmp(Cmd, t) == 0)
1077 void Close(
bool SendReply =
false,
bool Timeout =
false);
1078 bool Send(
const char *s);
1081 void CmdCHAN(const
char *Option);
1082 void CmdCLRE(const
char *Option);
1083 void CmdCONN(const
char *Option);
1084 void CmdCPYR(const
char *Option);
1085 void CmdDELC(const
char *Option);
1086 void CmdDELR(const
char *Option);
1087 void CmdDELT(const
char *Option);
1088 void CmdEDIT(const
char *Option);
1089 void CmdGRAB(const
char *Option);
1090 void CmdHELP(const
char *Option);
1091 void CmdHITK(const
char *Option);
1092 void CmdLSTC(const
char *Option);
1093 void CmdLSTD(const
char *Option);
1094 void CmdLSTE(const
char *Option);
1095 void CmdLSTR(const
char *Option);
1096 void CmdLSTT(const
char *Option);
1097 void CmdMESG(const
char *Option);
1098 void CmdMODC(const
char *Option);
1099 void CmdMODT(const
char *Option);
1100 void CmdMOVC(const
char *Option);
1101 void CmdMOVR(const
char *Option);
1102 void CmdNEWC(const
char *Option);
1103 void CmdNEWT(const
char *Option);
1104 void CmdNEXT(const
char *Option);
1105 void CmdPING(const
char *Option);
1106 void CmdPLAY(const
char *Option);
1107 void CmdPLUG(const
char *Option);
1108 void CmdPOLL(const
char *Option);
1109 void CmdPRIM(const
char *Option);
1110 void CmdPUTE(const
char *Option);
1111 void CmdREMO(const
char *Option);
1112 void CmdSCAN(const
char *Option);
1113 void CmdSTAT(const
char *Option);
1114 void CmdUPDT(const
char *Option);
1115 void CmdUPDR(const
char *Option);
1116 void CmdVOLU(const
char *Option);
1139 time_t now = time(NULL);
1182 char *buffer = NULL;
1185 if (vasprintf(&buffer, fmt, ap) >= 0) {
1188 char *n = strchr(s,
'\n');
1192 if (Code < 0 || n && *(n + 1))
1196 s = n ? n + 1 : NULL;
1200 Reply(451,
"Bad format - looks like a programming error!");
1207 Reply(451,
"Zero return code - looks like a programming error!");
1223 const int TopicsPerLine = 5;
1225 for (
int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
1228 q += sprintf(q,
" ");
1229 for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
1230 const char *topic =
GetHelpTopic(hp[(y * TopicsPerLine + x)]);
1235 Reply(-214,
"%s", buffer);
1246 int o = strtol(Option, NULL, 10);
1250 else if (strcmp(Option,
"-") == 0) {
1257 else if (strcmp(Option,
"+") == 0) {
1265 n = Channel->Number();
1267 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1268 if (!Channel->GroupSep()) {
1269 if (strcasecmp(Channel->Name(), Option) == 0) {
1270 n = Channel->Number();
1277 Reply(501,
"Undefined channel \"%s\"", Option);
1281 if (
const cChannel *Channel = Channels->GetByNumber(n)) {
1283 Reply(554,
"Error switching to channel \"%d\"", Channel->Number());
1288 Reply(550,
"Unable to find channel \"%s\"", Option);
1296 Reply(250,
"%d %s", Channel->Number(), Channel->Name());
1308 int o = strtol(Option, NULL, 10);
1310 ChannelID = Channels->GetByNumber(o)->GetChannelID();
1315 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1316 if (!Channel->GroupSep()) {
1317 if (strcasecmp(Channel->Name(), Option) == 0) {
1318 ChannelID = Channel->GetChannelID();
1329 for (
cSchedule *p = Schedules->First(); p; p = Schedules->
Next(p)) {
1330 if (p->ChannelID() == ChannelID) {
1336 for (
cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
1337 if (ChannelID == Timer->Channel()->GetChannelID().
ClrRid())
1338 Timer->SetEvent(NULL);
1342 Reply(250,
"EPG data of channel \"%s\" cleared", Option);
1345 Reply(550,
"No EPG data found for channel \"%s\"", Option);
1350 Reply(501,
"Undefined channel \"%s\"", Option);
1355 for (
cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer))
1356 Timer->SetEvent(NULL);
1357 for (
cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->
Next(Schedule))
1358 Schedule->Cleanup(INT_MAX);
1360 Reply(250,
"EPG data cleared");
1369 if (ServerParams.
Ok()) {
1375 Reply(501,
"Error in server parameters: %s", ServerParams.
Error());
1378 Reply(451,
"No SVDRP client handler");
1381 Reply(501,
"Missing server parameters");
1390 Channels->SetExplicitModify();
1391 if (
cChannel *Channel = Channels->GetByNumber(strtol(Option, NULL, 10))) {
1392 if (
const cTimer *Timer = Timers->UsesChannel(Channel)) {
1393 Reply(550,
"Channel \"%s\" is in use by timer %s", Option, *Timer->ToDescr());
1397 cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
1398 if (CurrentChannel && Channel == CurrentChannel) {
1399 int n = Channels->GetNextNormal(CurrentChannel->
Index());
1401 n = Channels->GetPrevNormal(CurrentChannel->
Index());
1403 Reply(501,
"Can't delete channel \"%s\" - list would be empty", Option);
1406 CurrentChannel = Channels->Get(n);
1407 CurrentChannelNr = 0;
1409 Channels->Del(Channel);
1410 Channels->ReNumber();
1411 Channels->SetModifiedByUser();
1412 Channels->SetModified();
1414 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
1416 Channels->SwitchTo(CurrentChannel->
Number());
1420 Reply(250,
"Channel \"%s\" deleted", Option);
1423 Reply(501,
"Channel \"%s\" not defined", Option);
1426 Reply(501,
"Error in channel number \"%s\"", Option);
1429 Reply(501,
"Missing channel number");
1438 return cString::sprintf(
"Recording \"%s\" is being replayed", RecordingId);
1439 else if ((Reason &
ruCut) != 0)
1442 return cString::sprintf(
"Recording \"%s\" is being copied/moved", RecordingId);
1451 char *opt = strdup(Option);
1454 while (*option && !isspace(*option))
1460 Recordings->SetExplicitModify();
1461 if (
cRecording *Recording = Recordings->Get(strtol(num, NULL, 10) - 1)) {
1462 if (
int RecordingInUse = Recording->IsInUse())
1470 if (strcmp(newName, Recording->Name())) {
1475 Recordings->AddByName(fileName);
1476 Reply(250,
"Recording \"%s\" copied to \"%s\"", Recording->Name(), *newName);
1479 Reply(554,
"Error while copying recording \"%s\" to \"%s\"!", Recording->Name(), *newName);
1482 Reply(501,
"Identical new recording name");
1485 Reply(501,
"Missing new recording name");
1489 Reply(550,
"Recording \"%s\" not found", num);
1492 Reply(501,
"Error in recording number \"%s\"", num);
1496 Reply(501,
"Missing recording number");
1504 Recordings->SetExplicitModify();
1505 if (
cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1506 if (
int RecordingInUse = Recording->IsInUse())
1509 if (Recording->Delete()) {
1510 Recordings->DelByName(Recording->FileName());
1511 Recordings->SetModified();
1513 Reply(250,
"Recording \"%s\" deleted", Option);
1516 Reply(554,
"Error while deleting recording!");
1520 Reply(550,
"Recording \"%s\" not found", Option);
1523 Reply(501,
"Error in recording id \"%s\"", Option);
1526 Reply(501,
"Missing recording id");
1534 Timers->SetExplicitModify();
1535 if (
cTimer *Timer = Timers->GetById(strtol(Option, NULL, 10))) {
1536 if (Timer->Recording()) {
1541 Timers->SetModified();
1543 Reply(250,
"Timer \"%s\" deleted", Option);
1546 Reply(501,
"Timer \"%s\" not defined", Option);
1549 Reply(501,
"Error in timer number \"%s\"", Option);
1552 Reply(501,
"Missing timer number");
1560 if (
const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1562 if (Marks.
Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.
Count()) {
1564 Reply(250,
"Editing recording \"%s\" [%s]", Option, Recording->Title());
1566 Reply(554,
"Can't start editing process");
1569 Reply(554,
"No editing marks defined");
1572 Reply(550,
"Recording \"%s\" not found", Option);
1575 Reply(501,
"Error in recording id \"%s\"", Option);
1578 Reply(501,
"Missing recording id");
1583 const char *FileName = NULL;
1585 int Quality = -1, SizeX = -1, SizeY = -1;
1587 char buf[strlen(Option) + 1];
1588 char *p = strcpy(buf, Option);
1589 const char *delim =
" \t";
1591 FileName = strtok_r(p, delim, &strtok_next);
1593 const char *Extension = strrchr(FileName,
'.');
1595 if (strcasecmp(Extension,
".jpg") == 0 || strcasecmp(Extension,
".jpeg") == 0)
1597 else if (strcasecmp(Extension,
".pnm") == 0)
1600 Reply(501,
"Unknown image type \"%s\"", Extension + 1);
1603 if (Extension == FileName)
1606 else if (strcmp(FileName,
"-") == 0)
1609 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1610 if (strcasecmp(p,
"JPEG") == 0 || strcasecmp(p,
"PNM") == 0) {
1612 p = strtok_r(NULL, delim, &strtok_next);
1618 Reply(501,
"Invalid quality \"%s\"", p);
1624 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1628 Reply(501,
"Invalid sizex \"%s\"", p);
1631 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1635 Reply(501,
"Invalid sizey \"%s\"", p);
1640 Reply(501,
"Missing sizey");
1644 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1645 Reply(501,
"Unexpected parameter \"%s\"", p);
1649 char RealFileName[PATH_MAX];
1654 const char *slash = strrchr(FileName,
'/');
1659 slash = strrchr(FileName,
'/');
1662 char *r = realpath(t, RealFileName);
1665 Reply(501,
"Invalid file name \"%s\"", FileName);
1668 strcat(RealFileName, slash);
1669 FileName = RealFileName;
1671 Reply(501,
"Invalid file name \"%s\"", FileName);
1676 Reply(550,
"Grabbing to file not allowed (use \"GRAB -\" instead)");
1685 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
1687 if (
safe_write(fd, Image, ImageSize) == ImageSize) {
1689 Reply(250,
"Grabbed image %s", Option);
1693 Reply(451,
"Can't write to '%s'", FileName);
1699 Reply(451,
"Can't open '%s'", FileName);
1705 while ((s = Base64.
NextLine()) != NULL)
1706 Reply(-216,
"%s", s);
1707 Reply(216,
"Grabbed image %s", Option);
1712 Reply(451,
"Grab image failed");
1715 Reply(501,
"Missing filename");
1723 Reply(-214,
"%s", hp);
1725 Reply(504,
"HELP topic \"%s\" unknown", Option);
1731 Reply(-214,
"Topics:");
1740 Reply(-214,
"To report bugs in the implementation send email to");
1741 Reply(-214,
" vdr-bugs@tvdr.de");
1743 Reply(214,
"End of HELP info");
1750 Reply(550,
"Remote control currently disabled (key \"%s\" discarded)", Option);
1753 char buf[strlen(Option) + 1];
1754 strcpy(buf, Option);
1755 const char *delim =
" \t";
1757 char *p = strtok_r(buf, delim, &strtok_next);
1763 Reply(451,
"Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
1768 Reply(504,
"Unknown key: \"%s\"", p);
1772 p = strtok_r(NULL, delim, &strtok_next);
1774 Reply(250,
"Key%s \"%s\" accepted", NumKeys > 1 ?
"s" :
"", Option);
1777 Reply(-214,
"Valid <key> names for the HITK command:");
1778 for (
int i = 0; i <
kNone; i++) {
1781 Reply(214,
"End of key list");
1788 bool WithChannelIds =
startswith(Option,
":ids") && (Option[4] ==
' ' || Option[4] == 0);
1791 bool WithGroupSeps = strcasecmp(Option,
":groups") == 0;
1792 if (*Option && !WithGroupSeps) {
1794 int n = strtol(Option, NULL, 10);
1797 if (
const cChannel *Channel = Channels->GetByNumber(n))
1798 Reply(250,
"%d%s%s %s", Channel->Number(), WithChannelIds ?
" " :
"", WithChannelIds ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1800 Reply(501,
"Channel \"%s\" not defined", Option);
1805 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1806 if (!Channel->GroupSep()) {
1807 if (strcasestr(Channel->Name(), Option)) {
1818 Reply(501,
"Channel \"%s\" not defined", Option);
1822 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1824 Reply(Channel->Next() ? -250: 250,
"%d%s%s %s", Channel->GroupSep() ? 0 : Channel->Number(), (WithChannelIds && !Channel->GroupSep()) ?
" " :
"", (WithChannelIds && !Channel->GroupSep()) ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1825 else if (!Channel->GroupSep())
1826 Reply(Channel->Number() <
cChannels::MaxNumber() ? -250 : 250,
"%d%s%s %s", Channel->Number(), WithChannelIds ?
" " :
"", WithChannelIds ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1830 Reply(550,
"No channels defined");
1838 Reply(d->DeviceNumber() + 1 ==
cDevice::NumDevices() ? 250 : -250,
"%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ?
"D" :
"-", d->DeviceNumber() + 1 ==
Setup.
PrimaryDVB ?
"P" :
"-", *d->DeviceName());
1842 Reply(550,
"No devices found");
1853 char buf[strlen(Option) + 1];
1854 strcpy(buf, Option);
1855 const char *delim =
" \t";
1857 char *p = strtok_r(buf, delim, &strtok_next);
1858 while (p && DumpMode ==
dmAll) {
1859 if (strcasecmp(p,
"NOW") == 0)
1861 else if (strcasecmp(p,
"NEXT") == 0)
1863 else if (strcasecmp(p,
"AT") == 0) {
1865 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1867 AtTime = strtol(p, NULL, 10);
1869 Reply(501,
"Invalid time");
1874 Reply(501,
"Missing time");
1878 else if (!Schedule) {
1881 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1885 Schedule = Schedules->GetSchedule(Channel);
1887 Reply(550,
"No schedule found");
1892 Reply(550,
"Channel \"%s\" not defined", p);
1897 Reply(501,
"Unknown option: \"%s\"", p);
1900 p = strtok_r(NULL, delim, &strtok_next);
1905 FILE *f = fdopen(fd,
"w");
1908 Schedule->
Dump(Channels, f,
"215-", DumpMode, AtTime);
1910 Schedules->Dump(f,
"215-", DumpMode, AtTime);
1912 Reply(215,
"End of EPG data");
1916 Reply(451,
"Can't open file connection");
1921 Reply(451,
"Can't dup stream descriptor");
1930 char buf[strlen(Option) + 1];
1931 strcpy(buf, Option);
1932 const char *delim =
" \t";
1934 char *p = strtok_r(buf, delim, &strtok_next);
1938 Number = strtol(p, NULL, 10);
1940 Reply(501,
"Error in recording id \"%s\"", Option);
1944 else if (strcasecmp(p,
"PATH") == 0)
1947 Reply(501,
"Unknown option: \"%s\"", p);
1950 p = strtok_r(NULL, delim, &strtok_next);
1953 if (
const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1954 FILE *f = fdopen(
file,
"w");
1957 Reply(250,
"%s", Recording->FileName());
1959 Recording->Info()->Write(f,
"215-");
1961 Reply(215,
"End of recording information");
1966 Reply(451,
"Can't open file connection");
1969 Reply(550,
"Recording \"%s\" not found", Option);
1972 else if (Recordings->Count()) {
1973 const cRecording *Recording = Recordings->First();
1975 Reply(Recording == Recordings->Last() ? 250 : -250,
"%d %s", Recording->
Id(), Recording->
Title(
' ',
true));
1976 Recording = Recordings->
Next(Recording);
1980 Reply(550,
"No recordings available");
1986 bool UseChannelId =
false;
1988 char buf[strlen(Option) + 1];
1989 strcpy(buf, Option);
1990 const char *delim =
" \t";
1992 char *p = strtok_r(buf, delim, &strtok_next);
1995 Id = strtol(p, NULL, 10);
1996 else if (strcasecmp(p,
"ID") == 0)
1997 UseChannelId =
true;
1999 Reply(501,
"Unknown option: \"%s\"", p);
2002 p = strtok_r(NULL, delim, &strtok_next);
2007 for (
const cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
2008 if (!Timer->Remote()) {
2009 if (Timer->Id() == Id) {
2010 Reply(250,
"%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2015 Reply(501,
"Timer \"%s\" not defined", Option);
2019 const cTimer *LastLocalTimer = Timers->Last();
2020 while (LastLocalTimer) {
2021 if (LastLocalTimer->
Remote())
2022 LastLocalTimer = Timers->
Prev(LastLocalTimer);
2026 if (LastLocalTimer) {
2027 for (
const cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
2028 if (!Timer->Remote())
2029 Reply(Timer != LastLocalTimer ? -250 : 250,
"%d %s", Timer->
Id(), *Timer->ToText(UseChannelId));
2030 if (Timer == LastLocalTimer)
2036 Reply(550,
"No timers defined");
2044 Reply(250,
"Message queued");
2047 Reply(501,
"Missing message");
2054 int n = strtol(Option, &tail, 10);
2055 if (tail && tail != Option) {
2058 Channels->SetExplicitModify();
2059 if (
cChannel *Channel = Channels->GetByNumber(n)) {
2061 if (ch.
Parse(tail)) {
2062 if (Channels->HasUniqueChannelID(&ch, Channel)) {
2064 Channels->ReNumber();
2065 Channels->SetModifiedByUser();
2066 Channels->SetModified();
2068 Reply(250,
"%d %s", Channel->Number(), *Channel->ToText());
2071 Reply(501,
"Channel settings are not unique");
2074 Reply(501,
"Error in channel settings");
2077 Reply(501,
"Channel \"%d\" not defined", n);
2080 Reply(501,
"Error in channel number");
2083 Reply(501,
"Missing channel settings");
2090 int Id = strtol(Option, &tail, 10);
2091 if (tail && tail != Option) {
2094 Timers->SetExplicitModify();
2095 if (
cTimer *Timer = Timers->GetById(Id)) {
2098 if (strcasecmp(tail,
"ON") == 0)
2100 else if (strcasecmp(tail,
"OFF") == 0)
2102 else if (!t.
Parse(tail)) {
2103 Reply(501,
"Error in timer settings");
2111 Timers->SetModified();
2113 Reply(250,
"%d %s", Timer->Id(), *Timer->ToText(
true));
2116 Reply(501,
"Timer \"%d\" not defined", Id);
2119 Reply(501,
"Error in timer id");
2122 Reply(501,
"Missing timer settings");
2129 int From = strtol(Option, &tail, 10);
2130 if (tail && tail != Option) {
2132 if (tail && tail != Option) {
2135 Channels->SetExplicitModify();
2136 int To = strtol(tail, NULL, 10);
2138 const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
2139 cChannel *FromChannel = Channels->GetByNumber(From);
2141 cChannel *ToChannel = Channels->GetByNumber(To);
2143 int FromNumber = FromChannel->
Number();
2144 int ToNumber = ToChannel->
Number();
2145 if (FromNumber != ToNumber) {
2146 Channels->Move(FromChannel, ToChannel);
2147 Channels->ReNumber();
2148 Channels->SetModifiedByUser();
2149 Channels->SetModified();
2150 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
2152 Channels->SwitchTo(CurrentChannel->
Number());
2157 Reply(250,
"Channel \"%d\" moved to \"%d\"", From, To);
2160 Reply(501,
"Can't move channel to same position");
2163 Reply(501,
"Channel \"%d\" not defined", To);
2166 Reply(501,
"Channel \"%d\" not defined", From);
2169 Reply(501,
"Error in channel number");
2172 Reply(501,
"Error in channel number");
2175 Reply(501,
"Missing channel number");
2181 char *opt = strdup(Option);
2184 while (*option && !isspace(*option))
2190 Recordings->SetExplicitModify();
2191 if (
cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2192 if (
int RecordingInUse = Recording->IsInUse())
2198 cString oldName = Recording->Name();
2199 if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) {
2200 Recordings->SetModified();
2201 Recordings->TouchUpdate();
2202 Reply(250,
"Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name());
2205 Reply(554,
"Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
2208 Reply(501,
"Missing new recording name");
2212 Reply(550,
"Recording \"%s\" not found", num);
2215 Reply(501,
"Error in recording id \"%s\"", num);
2219 Reply(501,
"Missing recording id");
2226 if (ch.
Parse(Option)) {
2228 Channels->SetExplicitModify();
2229 if (Channels->HasUniqueChannelID(&ch)) {
2232 Channels->Add(channel);
2233 Channels->ReNumber();
2234 Channels->SetModifiedByUser();
2235 Channels->SetModified();
2240 Reply(501,
"Channel settings are not unique");
2243 Reply(501,
"Error in channel settings");
2246 Reply(501,
"Missing channel settings");
2253 if (Timer->
Parse(Option)) {
2262 Reply(501,
"Error in timer settings");
2266 Reply(501,
"Missing timer settings");
2272 if (
const cTimer *t = Timers->GetNextActiveTimer()) {
2273 time_t Start = t->StartTime();
2277 else if (strcasecmp(Option,
"ABS") == 0)
2278 Reply(250,
"%d %ld", Id, Start);
2279 else if (strcasecmp(Option,
"REL") == 0)
2280 Reply(250,
"%d %ld", Id, Start - time(NULL));
2282 Reply(501,
"Unknown option: \"%s\"", Option);
2285 Reply(550,
"No active timers");
2296 char *opt = strdup(Option);
2299 while (*option && !isspace(*option))
2306 if (
const cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2307 cString FileName = Recording->FileName();
2308 cString Title = Recording->Title();
2309 int FramesPerSecond = Recording->FramesPerSecond();
2310 bool IsPesRecording = Recording->IsPesRecording();
2318 if (strcasecmp(option,
"BEGIN") != 0)
2329 Reply(250,
"Playing recording \"%s\" [%s]", num, *Title);
2333 Reply(550,
"Recording \"%s\" not found", num);
2338 Reply(501,
"Error in recording id \"%s\"", num);
2342 Reply(501,
"Missing recording id");
2348 char *opt = strdup(Option);
2350 char *option = name;
2351 while (*option && !isspace(*option))
2360 while (*option && !isspace(*option))
2366 if (!*cmd || strcasecmp(cmd,
"HELP") == 0) {
2367 if (*cmd && *option) {
2370 Reply(-214,
"%s", hp);
2371 Reply(214,
"End of HELP info");
2374 Reply(504,
"HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->
Name());
2380 Reply(-214,
"SVDRP commands:");
2382 Reply(214,
"End of HELP info");
2385 Reply(214,
"This plugin has no SVDRP commands");
2388 else if (strcasecmp(cmd,
"MAIN") == 0) {
2390 Reply(250,
"Initiated call to main menu function of plugin \"%s\"", plugin->
Name());
2392 Reply(550,
"A plugin call is already pending - please try again later");
2395 int ReplyCode = 900;
2398 Reply(abs(ReplyCode),
"%s", *s);
2400 Reply(500,
"Command unrecognized: \"%s\"", cmd);
2404 Reply(550,
"Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
2408 Reply(-214,
"Available plugins:");
2412 Reply(214,
"End of plugin list");
2419 char buf[strlen(Option) + 1];
2420 char *p = strcpy(buf, Option);
2421 const char *delim =
" \t";
2423 char *RemoteName = strtok_r(p, delim, &strtok_next);
2424 char *ListName = strtok_r(NULL, delim, &strtok_next);
2427 if (strcasecmp(ListName,
"timers") == 0) {
2431 Reply(501,
"No connection to \"%s\"", RemoteName);
2434 Reply(501,
"Unknown list name: \"%s\"", ListName);
2437 Reply(501,
"Missing list name");
2440 Reply(501,
"No SVDRP client connections");
2443 Reply(501,
"Missing parameters");
2451 int o = strtol(Option, NULL, 10);
2455 Reply(501,
"Invalid device number \"%s\"", Option);
2458 Reply(501,
"Invalid parameter \"%s\"", Option);
2461 Reply(250,
"Primary device set to %d", n);
2466 Reply(250,
"%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ?
"D" :
"-", d->DeviceNumber() + 1 ==
Setup.
PrimaryDVB ?
"P" :
"-", *d->DeviceName());
2468 Reply(501,
"Failed to get primary device");
2475 FILE *f = fopen(Option,
"r");
2479 Reply(250,
"EPG data processed from \"%s\"", Option);
2482 Reply(451,
"Error while processing EPG from \"%s\"", Option);
2486 Reply(501,
"Cannot open file \"%s\"", Option);
2500 if (!strcasecmp(Option,
"ON")) {
2502 Reply(250,
"Remote control enabled");
2504 else if (!strcasecmp(Option,
"OFF")) {
2506 Reply(250,
"Remote control disabled");
2509 Reply(501,
"Invalid Option \"%s\"", Option);
2518 Reply(250,
"EPG scan triggered");
2524 if (strcasecmp(Option,
"DISK") == 0) {
2527 Reply(250,
"%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
2530 Reply(501,
"Invalid Option \"%s\"", Option);
2533 Reply(501,
"No option given");
2540 if (Timer->
Parse(Option)) {
2542 if (
cTimer *t = Timers->GetTimer(Timer)) {
2562 Reply(501,
"Error in timer settings");
2566 Reply(501,
"Missing timer settings");
2572 Recordings->Update(
false);
2573 Reply(250,
"Re-read of recordings directory triggered");
2581 else if (strcmp(Option,
"+") == 0)
2583 else if (strcmp(Option,
"-") == 0)
2585 else if (strcasecmp(Option,
"MUTE") == 0)
2588 Reply(501,
"Unknown option: \"%s\"", Option);
2593 Reply(250,
"Audio is mute");
2598 #define CMD(c) (strcasecmp(Cmd, c) == 0)
2615 while (*s && !isspace(*s))
2656 else Reply(500,
"Command unrecognized: \"%s\"", Cmd);
2666 if (c ==
'\n' || c == 0x00) {
2682 else if (c == 0x04 &&
numChars == 0) {
2686 else if (c == 0x08 || c == 0x7F) {
2691 else if (c <= 0x03 || c == 0x0D) {
2696 int NewLength =
length + BUFSIZ;
2697 if (
char *NewBuffer = (
char *)realloc(
cmdLine, NewLength)) {
2746 virtual void Action(
void);
2756 :
cThread(
"SVDRP server handler", true)
2757 ,tcpSocket(TcpPort, true)
2840 bool Result =
false;
2852 bool Result =
false;
2869 for (
int i = 0; i < ServerNames.
Size(); i++)