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",
847 "DELC <number> | <id>\n"
848 " Delete the channel with the given number or channel id.",
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 if (
const cChannel *Channel = Channels->GetByNumber(o))
1311 ChannelID = Channel->GetChannelID();
1317 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1318 if (!Channel->GroupSep()) {
1319 if (strcasecmp(Channel->Name(), Option) == 0) {
1320 ChannelID = Channel->GetChannelID();
1331 for (
cSchedule *p = Schedules->First(); p; p = Schedules->
Next(p)) {
1332 if (p->ChannelID() == ChannelID) {
1338 for (
cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
1339 if (ChannelID == Timer->Channel()->GetChannelID().
ClrRid())
1340 Timer->SetEvent(NULL);
1344 Reply(250,
"EPG data of channel \"%s\" cleared", Option);
1347 Reply(550,
"No EPG data found for channel \"%s\"", Option);
1352 Reply(501,
"Undefined channel \"%s\"", Option);
1357 for (
cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer))
1358 Timer->SetEvent(NULL);
1359 for (
cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->
Next(Schedule))
1360 Schedule->Cleanup(INT_MAX);
1362 Reply(250,
"EPG data cleared");
1371 if (ServerParams.
Ok()) {
1377 Reply(501,
"Error in server parameters: %s", ServerParams.
Error());
1380 Reply(451,
"No SVDRP client handler");
1383 Reply(501,
"Missing server parameters");
1391 Channels->SetExplicitModify();
1394 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1398 if (
const cTimer *Timer = Timers->UsesChannel(Channel)) {
1399 Reply(550,
"Channel \"%s\" is in use by timer %s", Option, *Timer->ToDescr());
1403 cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
1404 if (CurrentChannel && Channel == CurrentChannel) {
1405 int n = Channels->GetNextNormal(CurrentChannel->
Index());
1407 n = Channels->GetPrevNormal(CurrentChannel->
Index());
1409 Reply(501,
"Can't delete channel \"%s\" - list would be empty", Option);
1412 CurrentChannel = Channels->Get(n);
1413 CurrentChannelNr = 0;
1415 Channels->Del(Channel);
1416 Channels->ReNumber();
1417 Channels->SetModifiedByUser();
1418 Channels->SetModified();
1420 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
1422 Channels->SwitchTo(CurrentChannel->
Number());
1426 Reply(250,
"Channel \"%s\" deleted", Option);
1429 Reply(501,
"Channel \"%s\" not defined", Option);
1432 Reply(501,
"Missing channel number or id");
1441 return cString::sprintf(
"Recording \"%s\" is being replayed", RecordingId);
1442 else if ((Reason &
ruCut) != 0)
1445 return cString::sprintf(
"Recording \"%s\" is being copied/moved", RecordingId);
1454 char *opt = strdup(Option);
1457 while (*option && !isspace(*option))
1463 Recordings->SetExplicitModify();
1464 if (
cRecording *Recording = Recordings->Get(strtol(num, NULL, 10) - 1)) {
1465 if (
int RecordingInUse = Recording->IsInUse())
1473 if (strcmp(newName, Recording->Name())) {
1478 Recordings->AddByName(fileName);
1479 Reply(250,
"Recording \"%s\" copied to \"%s\"", Recording->Name(), *newName);
1482 Reply(554,
"Error while copying recording \"%s\" to \"%s\"!", Recording->Name(), *newName);
1485 Reply(501,
"Identical new recording name");
1488 Reply(501,
"Missing new recording name");
1492 Reply(550,
"Recording \"%s\" not found", num);
1495 Reply(501,
"Error in recording number \"%s\"", num);
1499 Reply(501,
"Missing recording number");
1507 Recordings->SetExplicitModify();
1508 if (
cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1509 if (
int RecordingInUse = Recording->IsInUse())
1512 if (Recording->Delete()) {
1513 Recordings->DelByName(Recording->FileName());
1514 Recordings->SetModified();
1516 Reply(250,
"Recording \"%s\" deleted", Option);
1519 Reply(554,
"Error while deleting recording!");
1523 Reply(550,
"Recording \"%s\" not found", Option);
1526 Reply(501,
"Error in recording id \"%s\"", Option);
1529 Reply(501,
"Missing recording id");
1537 Timers->SetExplicitModify();
1538 if (
cTimer *Timer = Timers->GetById(strtol(Option, NULL, 10))) {
1539 if (Timer->Recording()) {
1543 Timer->TriggerRespawn();
1545 Timers->SetModified();
1547 Reply(250,
"Timer \"%s\" deleted", Option);
1550 Reply(501,
"Timer \"%s\" not defined", Option);
1553 Reply(501,
"Error in timer number \"%s\"", Option);
1556 Reply(501,
"Missing timer number");
1564 if (
const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1566 if (Marks.
Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.
Count()) {
1568 Reply(250,
"Editing recording \"%s\" [%s]", Option, Recording->Title());
1570 Reply(554,
"Can't start editing process");
1573 Reply(554,
"No editing marks defined");
1576 Reply(550,
"Recording \"%s\" not found", Option);
1579 Reply(501,
"Error in recording id \"%s\"", Option);
1582 Reply(501,
"Missing recording id");
1587 const char *FileName = NULL;
1589 int Quality = -1, SizeX = -1, SizeY = -1;
1591 char buf[strlen(Option) + 1];
1592 char *p = strcpy(buf, Option);
1593 const char *delim =
" \t";
1595 FileName = strtok_r(p, delim, &strtok_next);
1597 const char *Extension = strrchr(FileName,
'.');
1599 if (strcasecmp(Extension,
".jpg") == 0 || strcasecmp(Extension,
".jpeg") == 0)
1601 else if (strcasecmp(Extension,
".pnm") == 0)
1604 Reply(501,
"Unknown image type \"%s\"", Extension + 1);
1607 if (Extension == FileName)
1610 else if (strcmp(FileName,
"-") == 0)
1613 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1614 if (strcasecmp(p,
"JPEG") == 0 || strcasecmp(p,
"PNM") == 0) {
1616 p = strtok_r(NULL, delim, &strtok_next);
1622 Reply(501,
"Invalid quality \"%s\"", p);
1628 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1632 Reply(501,
"Invalid sizex \"%s\"", p);
1635 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1639 Reply(501,
"Invalid sizey \"%s\"", p);
1644 Reply(501,
"Missing sizey");
1648 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1649 Reply(501,
"Unexpected parameter \"%s\"", p);
1653 char RealFileName[PATH_MAX];
1658 const char *slash = strrchr(FileName,
'/');
1663 slash = strrchr(FileName,
'/');
1666 char *r = realpath(t, RealFileName);
1669 Reply(501,
"Invalid file name \"%s\"", FileName);
1672 strcat(RealFileName, slash);
1673 FileName = RealFileName;
1675 Reply(501,
"Invalid file name \"%s\"", FileName);
1680 Reply(550,
"Grabbing to file not allowed (use \"GRAB -\" instead)");
1689 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
1691 if (
safe_write(fd, Image, ImageSize) == ImageSize) {
1693 Reply(250,
"Grabbed image %s", Option);
1697 Reply(451,
"Can't write to '%s'", FileName);
1703 Reply(451,
"Can't open '%s'", FileName);
1709 while ((s = Base64.
NextLine()) != NULL)
1710 Reply(-216,
"%s", s);
1711 Reply(216,
"Grabbed image %s", Option);
1716 Reply(451,
"Grab image failed");
1719 Reply(501,
"Missing filename");
1727 Reply(-214,
"%s", hp);
1729 Reply(504,
"HELP topic \"%s\" unknown", Option);
1735 Reply(-214,
"Topics:");
1744 Reply(-214,
"To report bugs in the implementation send email to");
1745 Reply(-214,
" vdr-bugs@tvdr.de");
1747 Reply(214,
"End of HELP info");
1754 Reply(550,
"Remote control currently disabled (key \"%s\" discarded)", Option);
1757 char buf[strlen(Option) + 1];
1758 strcpy(buf, Option);
1759 const char *delim =
" \t";
1761 char *p = strtok_r(buf, delim, &strtok_next);
1767 Reply(451,
"Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
1772 Reply(504,
"Unknown key: \"%s\"", p);
1776 p = strtok_r(NULL, delim, &strtok_next);
1778 Reply(250,
"Key%s \"%s\" accepted", NumKeys > 1 ?
"s" :
"", Option);
1781 Reply(-214,
"Valid <key> names for the HITK command:");
1782 for (
int i = 0; i <
kNone; i++) {
1785 Reply(214,
"End of key list");
1792 bool WithChannelIds =
startswith(Option,
":ids") && (Option[4] ==
' ' || Option[4] == 0);
1795 bool WithGroupSeps = strcasecmp(Option,
":groups") == 0;
1796 if (*Option && !WithGroupSeps) {
1798 int n = strtol(Option, NULL, 10);
1801 if (
const cChannel *Channel = Channels->GetByNumber(n))
1802 Reply(250,
"%d%s%s %s", Channel->Number(), WithChannelIds ?
" " :
"", WithChannelIds ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1804 Reply(501,
"Channel \"%s\" not defined", Option);
1809 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1810 if (!Channel->GroupSep()) {
1811 if (strcasestr(Channel->Name(), Option)) {
1822 Reply(501,
"Channel \"%s\" not defined", Option);
1826 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1828 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());
1829 else if (!Channel->GroupSep())
1830 Reply(Channel->Number() <
cChannels::MaxNumber() ? -250 : 250,
"%d%s%s %s", Channel->Number(), WithChannelIds ?
" " :
"", WithChannelIds ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1834 Reply(550,
"No channels defined");
1842 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());
1846 Reply(550,
"No devices found");
1857 char buf[strlen(Option) + 1];
1858 strcpy(buf, Option);
1859 const char *delim =
" \t";
1861 char *p = strtok_r(buf, delim, &strtok_next);
1862 while (p && DumpMode ==
dmAll) {
1863 if (strcasecmp(p,
"NOW") == 0)
1865 else if (strcasecmp(p,
"NEXT") == 0)
1867 else if (strcasecmp(p,
"AT") == 0) {
1869 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1871 AtTime = strtol(p, NULL, 10);
1873 Reply(501,
"Invalid time");
1878 Reply(501,
"Missing time");
1882 else if (!Schedule) {
1885 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1889 Schedule = Schedules->GetSchedule(Channel);
1891 Reply(550,
"No schedule found");
1896 Reply(550,
"Channel \"%s\" not defined", p);
1901 Reply(501,
"Unknown option: \"%s\"", p);
1904 p = strtok_r(NULL, delim, &strtok_next);
1909 FILE *f = fdopen(fd,
"w");
1912 Schedule->
Dump(Channels, f,
"215-", DumpMode, AtTime);
1914 Schedules->Dump(f,
"215-", DumpMode, AtTime);
1916 Reply(215,
"End of EPG data");
1920 Reply(451,
"Can't open file connection");
1925 Reply(451,
"Can't dup stream descriptor");
1934 char buf[strlen(Option) + 1];
1935 strcpy(buf, Option);
1936 const char *delim =
" \t";
1938 char *p = strtok_r(buf, delim, &strtok_next);
1942 Number = strtol(p, NULL, 10);
1944 Reply(501,
"Error in recording id \"%s\"", Option);
1948 else if (strcasecmp(p,
"PATH") == 0)
1951 Reply(501,
"Unknown option: \"%s\"", p);
1954 p = strtok_r(NULL, delim, &strtok_next);
1957 if (
const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1958 FILE *f = fdopen(
file,
"w");
1961 Reply(250,
"%s", Recording->FileName());
1963 Recording->Info()->Write(f,
"215-");
1965 Reply(215,
"End of recording information");
1970 Reply(451,
"Can't open file connection");
1973 Reply(550,
"Recording \"%s\" not found", Option);
1976 else if (Recordings->Count()) {
1977 const cRecording *Recording = Recordings->First();
1979 Reply(Recording == Recordings->Last() ? 250 : -250,
"%d %s", Recording->
Id(), Recording->
Title(
' ',
true));
1980 Recording = Recordings->
Next(Recording);
1984 Reply(550,
"No recordings available");
1990 bool UseChannelId =
false;
1992 char buf[strlen(Option) + 1];
1993 strcpy(buf, Option);
1994 const char *delim =
" \t";
1996 char *p = strtok_r(buf, delim, &strtok_next);
1999 Id = strtol(p, NULL, 10);
2000 else if (strcasecmp(p,
"ID") == 0)
2001 UseChannelId =
true;
2003 Reply(501,
"Unknown option: \"%s\"", p);
2006 p = strtok_r(NULL, delim, &strtok_next);
2011 for (
const cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
2012 if (!Timer->Remote()) {
2013 if (Timer->Id() == Id) {
2014 Reply(250,
"%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2019 Reply(501,
"Timer \"%s\" not defined", Option);
2023 const cTimer *LastLocalTimer = Timers->Last();
2024 while (LastLocalTimer) {
2025 if (LastLocalTimer->
Remote())
2026 LastLocalTimer = Timers->
Prev(LastLocalTimer);
2030 if (LastLocalTimer) {
2031 for (
const cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
2032 if (!Timer->Remote())
2033 Reply(Timer != LastLocalTimer ? -250 : 250,
"%d %s", Timer->
Id(), *Timer->ToText(UseChannelId));
2034 if (Timer == LastLocalTimer)
2040 Reply(550,
"No timers defined");
2048 Reply(250,
"Message queued");
2051 Reply(501,
"Missing message");
2058 int n = strtol(Option, &tail, 10);
2059 if (tail && tail != Option) {
2062 Channels->SetExplicitModify();
2063 if (
cChannel *Channel = Channels->GetByNumber(n)) {
2065 if (ch.
Parse(tail)) {
2066 if (Channels->HasUniqueChannelID(&ch, Channel)) {
2068 Channels->ReNumber();
2069 Channels->SetModifiedByUser();
2070 Channels->SetModified();
2072 Reply(250,
"%d %s", Channel->Number(), *Channel->ToText());
2075 Reply(501,
"Channel settings are not unique");
2078 Reply(501,
"Error in channel settings");
2081 Reply(501,
"Channel \"%d\" not defined", n);
2084 Reply(501,
"Error in channel number");
2087 Reply(501,
"Missing channel settings");
2094 int Id = strtol(Option, &tail, 10);
2095 if (tail && tail != Option) {
2098 Timers->SetExplicitModify();
2099 if (
cTimer *Timer = Timers->GetById(Id)) {
2102 if (strcasecmp(tail,
"ON") == 0)
2104 else if (strcasecmp(tail,
"OFF") == 0)
2106 else if (!t.
Parse(tail)) {
2107 Reply(501,
"Error in timer settings");
2111 Reply(550,
"Timer is recording");
2119 Timers->SetModified();
2121 if (Timer->IsPatternTimer())
2122 Timer->SetEvent(NULL);
2123 Timer->TriggerRespawn();
2124 Reply(250,
"%d %s", Timer->Id(), *Timer->ToText(
true));
2127 Reply(501,
"Timer \"%d\" not defined", Id);
2130 Reply(501,
"Error in timer id");
2133 Reply(501,
"Missing timer settings");
2140 int From = strtol(Option, &tail, 10);
2141 if (tail && tail != Option) {
2143 if (tail && tail != Option) {
2146 Channels->SetExplicitModify();
2147 int To = strtol(tail, NULL, 10);
2149 const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
2150 cChannel *FromChannel = Channels->GetByNumber(From);
2152 cChannel *ToChannel = Channels->GetByNumber(To);
2154 int FromNumber = FromChannel->
Number();
2155 int ToNumber = ToChannel->
Number();
2156 if (FromNumber != ToNumber) {
2157 if (Channels->MoveNeedsDecrement(FromChannel, ToChannel))
2158 ToChannel = Channels->
Prev(ToChannel);
2159 Channels->Move(FromChannel, ToChannel);
2160 Channels->ReNumber();
2161 Channels->SetModifiedByUser();
2162 Channels->SetModified();
2163 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
2165 Channels->SwitchTo(CurrentChannel->
Number());
2170 Reply(250,
"Channel \"%d\" moved to \"%d\"", From, To);
2173 Reply(501,
"Can't move channel to same position");
2176 Reply(501,
"Channel \"%d\" not defined", To);
2179 Reply(501,
"Channel \"%d\" not defined", From);
2182 Reply(501,
"Error in channel number");
2185 Reply(501,
"Error in channel number");
2188 Reply(501,
"Missing channel number");
2194 char *opt = strdup(Option);
2197 while (*option && !isspace(*option))
2203 Recordings->SetExplicitModify();
2204 if (
cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2205 if (
int RecordingInUse = Recording->IsInUse())
2211 cString oldName = Recording->Name();
2212 if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) {
2213 Recordings->SetModified();
2214 Recordings->TouchUpdate();
2215 Reply(250,
"Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name());
2218 Reply(554,
"Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
2221 Reply(501,
"Missing new recording name");
2225 Reply(550,
"Recording \"%s\" not found", num);
2228 Reply(501,
"Error in recording id \"%s\"", num);
2232 Reply(501,
"Missing recording id");
2239 if (ch.
Parse(Option)) {
2241 Channels->SetExplicitModify();
2242 if (Channels->HasUniqueChannelID(&ch)) {
2245 Channels->Add(channel);
2246 Channels->ReNumber();
2247 Channels->SetModifiedByUser();
2248 Channels->SetModified();
2253 Reply(501,
"Channel settings are not unique");
2256 Reply(501,
"Error in channel settings");
2259 Reply(501,
"Missing channel settings");
2266 if (Timer->
Parse(Option)) {
2275 Reply(501,
"Error in timer settings");
2279 Reply(501,
"Missing timer settings");
2285 if (
const cTimer *t = Timers->GetNextActiveTimer()) {
2286 time_t Start = t->StartTime();
2290 else if (strcasecmp(Option,
"ABS") == 0)
2291 Reply(250,
"%d %ld", Id, Start);
2292 else if (strcasecmp(Option,
"REL") == 0)
2293 Reply(250,
"%d %ld", Id, Start - time(NULL));
2295 Reply(501,
"Unknown option: \"%s\"", Option);
2298 Reply(550,
"No active timers");
2309 char *opt = strdup(Option);
2312 while (*option && !isspace(*option))
2319 if (
const cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2320 cString FileName = Recording->FileName();
2321 cString Title = Recording->Title();
2322 int FramesPerSecond = Recording->FramesPerSecond();
2323 bool IsPesRecording = Recording->IsPesRecording();
2331 if (strcasecmp(option,
"BEGIN") != 0)
2342 Reply(250,
"Playing recording \"%s\" [%s]", num, *Title);
2346 Reply(550,
"Recording \"%s\" not found", num);
2351 Reply(501,
"Error in recording id \"%s\"", num);
2355 Reply(501,
"Missing recording id");
2361 char *opt = strdup(Option);
2363 char *option = name;
2364 while (*option && !isspace(*option))
2373 while (*option && !isspace(*option))
2379 if (!*cmd || strcasecmp(cmd,
"HELP") == 0) {
2380 if (*cmd && *option) {
2383 Reply(-214,
"%s", hp);
2384 Reply(214,
"End of HELP info");
2387 Reply(504,
"HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->
Name());
2393 Reply(-214,
"SVDRP commands:");
2395 Reply(214,
"End of HELP info");
2398 Reply(214,
"This plugin has no SVDRP commands");
2401 else if (strcasecmp(cmd,
"MAIN") == 0) {
2403 Reply(250,
"Initiated call to main menu function of plugin \"%s\"", plugin->
Name());
2405 Reply(550,
"A plugin call is already pending - please try again later");
2408 int ReplyCode = 900;
2411 Reply(abs(ReplyCode),
"%s", *s);
2413 Reply(500,
"Command unrecognized: \"%s\"", cmd);
2417 Reply(550,
"Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
2421 Reply(-214,
"Available plugins:");
2425 Reply(214,
"End of plugin list");
2432 char buf[strlen(Option) + 1];
2433 char *p = strcpy(buf, Option);
2434 const char *delim =
" \t";
2436 char *RemoteName = strtok_r(p, delim, &strtok_next);
2437 char *ListName = strtok_r(NULL, delim, &strtok_next);
2440 if (strcasecmp(ListName,
"timers") == 0) {
2444 Reply(501,
"No connection to \"%s\"", RemoteName);
2447 Reply(501,
"Unknown list name: \"%s\"", ListName);
2450 Reply(501,
"Missing list name");
2453 Reply(501,
"No SVDRP client connections");
2456 Reply(501,
"Missing parameters");
2464 int o = strtol(Option, NULL, 10);
2468 Reply(501,
"Invalid device number \"%s\"", Option);
2471 Reply(501,
"Invalid parameter \"%s\"", Option);
2474 Reply(250,
"Primary device set to %d", n);
2479 Reply(250,
"%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ?
"D" :
"-", d->DeviceNumber() + 1 ==
Setup.
PrimaryDVB ?
"P" :
"-", *d->DeviceName());
2481 Reply(501,
"Failed to get primary device");
2488 FILE *f = fopen(Option,
"r");
2492 Reply(250,
"EPG data processed from \"%s\"", Option);
2495 Reply(451,
"Error while processing EPG from \"%s\"", Option);
2499 Reply(501,
"Cannot open file \"%s\"", Option);
2513 if (!strcasecmp(Option,
"ON")) {
2515 Reply(250,
"Remote control enabled");
2517 else if (!strcasecmp(Option,
"OFF")) {
2519 Reply(250,
"Remote control disabled");
2522 Reply(501,
"Invalid Option \"%s\"", Option);
2531 Reply(250,
"EPG scan triggered");
2537 if (strcasecmp(Option,
"DISK") == 0) {
2540 Reply(250,
"%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
2543 Reply(501,
"Invalid Option \"%s\"", Option);
2546 Reply(501,
"No option given");
2553 if (Timer->
Parse(Option)) {
2555 if (
cTimer *t = Timers->GetTimer(Timer)) {
2575 Reply(501,
"Error in timer settings");
2579 Reply(501,
"Missing timer settings");
2585 Recordings->Update(
false);
2586 Reply(250,
"Re-read of recordings directory triggered");
2594 else if (strcmp(Option,
"+") == 0)
2596 else if (strcmp(Option,
"-") == 0)
2598 else if (strcasecmp(Option,
"MUTE") == 0)
2601 Reply(501,
"Unknown option: \"%s\"", Option);
2606 Reply(250,
"Audio is mute");
2611 #define CMD(c) (strcasecmp(Cmd, c) == 0)
2628 while (*s && !isspace(*s))
2669 else Reply(500,
"Command unrecognized: \"%s\"", Cmd);
2679 if (c ==
'\n' || c == 0x00) {
2695 else if (c == 0x04 &&
numChars == 0) {
2699 else if (c == 0x08 || c == 0x7F) {
2704 else if (c <= 0x03 || c == 0x0D) {
2709 int NewLength =
length + BUFSIZ;
2710 if (
char *NewBuffer = (
char *)realloc(
cmdLine, NewLength)) {
2759 virtual void Action(
void);
2769 :
cThread(
"SVDRP server handler", true)
2770 ,tcpSocket(TcpPort, true)
2853 bool Result =
false;
2865 bool Result =
false;
2882 for (
int i = 0; i < ServerNames.
Size(); i++)