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);
115 static bool SendDgram(
const char *Dgram,
int Port);
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) {
190 if (sock < 0 && tcp) {
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;
260 if (sock >= 0 && tcp) {
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)
273 lastIpAddress.Set((sockaddr *)&Addr);
274 dbgsvdrp(
"< %s client connection %s\n", lastIpAddress.Connection(), Accepted ?
"accepted" :
"DENIED");
275 isyslog(
"SVDRP %s < %s client connection %s",
Setup.
SVDRPHostName, lastIpAddress.Connection(), Accepted ?
"accepted" :
"DENIED");
286 if (sock >= 0 && !tcp) {
289 uint Size =
sizeof(Addr);
290 int NumBytes = recvfrom(sock, buf,
sizeof(buf), 0, (sockaddr *)&Addr, &Size);
293 lastIpAddress.Set((sockaddr *)&Addr);
303 dbgsvdrp(
"< %s discovery received (%s)\n", lastIpAddress.Connection(), buf);
328 bool Send(
const char *Command);
335 bool HasAddress(
const char *
Address,
int Port)
const;
337 bool Execute(
const char *Command,
cStringList *Response = NULL);
339 void SetFetchFlag(
int Flag);
340 bool HasFetchFlag(
int Flag);
347 :serverIpAddress(Address, Port)
353 timeout = Timeout * 1000 * 9 / 10;
359 SVDRPClientPoller.
Add(
file,
false);
377 SVDRPClientPoller.
Del(
file,
false);
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];
537 const char *
Name(
void)
const {
return name; }
538 const int Port(
void)
const {
return port; }
542 const char *
Host(
void)
const {
return host; }
543 bool Ok(
void)
const {
return !*error; }
544 const char *
Error(
void)
const {
return error; }
549 if (Params && *Params) {
555 vdrversion =
strgetval(Params,
"vdrversion",
':');
557 apiversion =
strgetval(Params,
"apiversion",
':');
567 error =
"invalid timeout";
570 error =
"missing server timeout";
573 error =
"missing server apiversion";
576 error =
"missing server vdrversion";
579 error =
"missing server port";
582 error =
"missing server name";
585 error =
"missing server parameters";
598 void SendDiscover(
void);
599 void HandleClientConnection(
void);
600 void ProcessConnections(
void);
603 virtual void Action(
void);
612 bool TriggerFetchingTimers(
const char *ServerName);
618 :
cThread(
"SVDRP client handler", true)
619 ,udpSocket(UdpPort, false)
651 StateKeySVDRPRemoteTimersPoll.
Remove();
653 else if (StateKeySVDRPRemoteTimersPoll.
TimedOut())
666 bool TimersModified = Timers->StoreRemoteTimers(Client->
ServerName(), &RemoteTimers);
667 StateKeySVDRPRemoteTimersPoll.
Remove(TimersModified);
673 if (*PollTimersCmd) {
674 if (!Client->
Execute(PollTimersCmd))
681 StateKeySVDRPRemoteTimersPoll.
Remove(TimersModified);
708 if (ServerParams.
Ok())
721 SVDRPClientPoller.
Poll(1000);
735 return Client->Execute(Command, Response);
742 ServerNames->
Clear();
748 return ServerNames->
Size() > 0;
771 bool Process(
const char *s);
778 if ((f = tmpfile()) != NULL) {
780 message =
"Enter EPG data, end with \".\" on a line by itself";
785 message =
"Error while opening temporary file";
798 if (strcmp(s,
".") != 0) {
808 message =
"EPG data processed";
812 message =
"Error while processing EPG data";
823 #define MAXHELPTOPIC 10 824 #define EITDISABLETIME 10 // seconds until EIT processing is enabled again after a CLRE command 828 "CHAN [ + | - | <number> | <name> | <id> ]\n" 829 " Switch channel up, down or to the given channel number, name or id.\n" 830 " Without option (or after successfully switching to the channel)\n" 831 " it returns the current channel number and name.",
832 "CLRE [ <number> | <name> | <id> ]\n" 833 " Clear the EPG list of the given channel number, name or id.\n" 834 " Without option it clears the entire EPG list.\n" 835 " After a CLRE command, no further EPG processing is done for 10\n" 836 " seconds, so that data sent with subsequent PUTE commands doesn't\n" 837 " interfere with data from the broadcasters.",
838 "CONN name:<name> port:<port> vdrversion:<vdrversion> apiversion:<apiversion> timeout:<timeout>\n" 839 " Used by peer-to-peer connections between VDRs to tell the other VDR\n" 840 " to establish a connection to this VDR. The name is the SVDRP host name\n" 841 " of this VDR, which may differ from its DNS name.",
845 " Delete the recording with the given id. Before a recording can be\n" 846 " deleted, an LSTR command should have been executed in order to retrieve\n" 847 " the recording ids. The ids are unique and don't change while this\n" 848 " instance of VDR is running.\n" 849 " CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n" 850 " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
852 " Delete the timer with the given id. If this timer is currently recording,\n" 853 " the recording will be stopped without any warning.",
855 " Edit the recording with the given id. Before a recording can be\n" 856 " edited, an LSTR command should have been executed in order to retrieve\n" 857 " the recording ids.",
858 "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n" 859 " Grab the current frame and save it to the given file. Images can\n" 860 " be stored as JPEG or PNM, depending on the given file name extension.\n" 861 " The quality of the grabbed image can be in the range 0..100, where 100\n" 862 " (the default) means \"best\" (only applies to JPEG). The size parameters\n" 863 " define the size of the resulting image (default is full screen).\n" 864 " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n" 865 " data will be sent to the SVDRP connection encoded in base64. The same\n" 866 " happens if '-' (a minus sign) is given as file name, in which case the\n" 867 " image format defaults to JPEG.",
869 " The HELP command gives help info.",
870 "HITK [ <key> ... ]\n" 871 " Hit the given remote control key. Without option a list of all\n" 872 " valid key names is given. If more than one key is given, they are\n" 873 " entered into the remote control queue in the given sequence. There\n" 874 " can be up to 31 keys.",
875 "LSTC [ :ids ] [ :groups | <number> | <name> | <id> ]\n" 876 " List channels. Without option, all channels are listed. Otherwise\n" 877 " only the given channel is listed. If a name is given, all channels\n" 878 " containing the given string as part of their name are listed.\n" 879 " If ':groups' is given, all channels are listed including group\n" 880 " separators. The channel number of a group separator is always 0.\n" 881 " With ':ids' the channel ids are listed following the channel numbers.\n" 882 " The special number 0 can be given to list the current channel.",
884 " List all available devices. Each device is listed with its name and\n" 885 " whether it is currently the primary device ('P') or it implements a\n" 886 " decoder ('D') and can be used as output device.",
887 "LSTE [ <channel> ] [ now | next | at <time> ]\n" 888 " List EPG data. Without any parameters all data of all channels is\n" 889 " listed. If a channel is given (either by number or by channel ID),\n" 890 " only data for that channel is listed. 'now', 'next', or 'at <time>'\n" 891 " restricts the returned data to present events, following events, or\n" 892 " events at the given time (which must be in time_t form).",
893 "LSTR [ <id> [ path ] ]\n" 894 " List recordings. Without option, all recordings are listed. Otherwise\n" 895 " the information for the given recording is listed. If a recording\n" 896 " id and the keyword 'path' is given, the actual file name of that\n" 897 " recording's directory is listed.\n" 898 " Note that the ids of the recordings are not necessarily given in\n" 900 "LSTT [ <id> ] [ id ]\n" 901 " List timers. Without option, all timers are listed. Otherwise\n" 902 " only the timer with the given id is listed. If the keyword 'id' is\n" 903 " given, the channels will be listed with their unique channel ids\n" 904 " instead of their numbers. This command lists only the timers that are\n" 905 " defined locally on this VDR, not any remote timers from other VDRs.",
907 " Displays the given message on the OSD. The message will be queued\n" 908 " and displayed whenever this is suitable.\n",
909 "MODC <number> <settings>\n" 910 " Modify a channel. Settings must be in the same format as returned\n" 911 " by the LSTC command.",
912 "MODT <id> on | off | <settings>\n" 913 " Modify a timer. Settings must be in the same format as returned\n" 914 " by the LSTT command. The special keywords 'on' and 'off' can be\n" 915 " used to easily activate or deactivate a timer.",
916 "MOVC <number> <to>\n" 917 " Move a channel to a new position.",
918 "MOVR <id> <new name>\n" 919 " Move the recording with the given id. Before a recording can be\n" 920 " moved, an LSTR command should have been executed in order to retrieve\n" 921 " the recording ids. The ids don't change during subsequent MOVR\n" 924 " Create a new channel. Settings must be in the same format as returned\n" 925 " by the LSTC command.",
927 " Create a new timer. Settings must be in the same format as returned\n" 928 " by the LSTT command.",
929 "NEXT [ abs | rel ]\n" 930 " Show the next timer event. If no option is given, the output will be\n" 931 " in human readable form. With option 'abs' the absolute time of the next\n" 932 " event will be given as the number of seconds since the epoch (time_t\n" 933 " format), while with option 'rel' the relative time will be given as the\n" 934 " number of seconds from now until the event. If the absolute time given\n" 935 " is smaller than the current time, or if the relative time is less than\n" 936 " zero, this means that the timer is currently recording and has started\n" 937 " at the given time. The first value in the resulting line is the id\n" 940 " Used by peer-to-peer connections between VDRs to keep the connection\n" 941 " from timing out. May be used at any time and simply returns a line of\n" 942 " the form '<hostname> is alive'.",
943 "PLAY <id> [ begin | <position> ]\n" 944 " Play the recording with the given id. Before a recording can be\n" 945 " played, an LSTR command should have been executed in order to retrieve\n" 946 " the recording ids.\n" 947 " The keyword 'begin' plays the recording from its very beginning, while\n" 948 " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n" 949 " position. If neither 'begin' nor a <position> are given, replay is resumed\n" 950 " at the position where any previous replay was stopped, or from the beginning\n" 951 " by default. To control or stop the replay session, use the usual remote\n" 952 " control keypresses via the HITK command.",
953 "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n" 954 " Send a command to a plugin.\n" 955 " The PLUG command without any parameters lists all plugins.\n" 956 " If only a name is given, all commands known to that plugin are listed.\n" 957 " If a command is given (optionally followed by parameters), that command\n" 958 " is sent to the plugin, and the result will be displayed.\n" 959 " The keyword 'help' lists all the SVDRP commands known to the named plugin.\n" 960 " If 'help' is followed by a command, the detailed help for that command is\n" 961 " given. The keyword 'main' initiates a call to the main menu function of the\n" 963 "POLL <name> timers\n" 964 " Used by peer-to-peer connections between VDRs to inform other machines\n" 965 " about changes to timers. The receiving VDR shall use LSTT to query the\n" 966 " remote machine with the given name about its timers and update its list\n" 967 " of timers accordingly.\n",
968 "PRIM [ <number> ]\n" 969 " Make the device with the given number the primary device.\n" 970 " Without option it returns the currently active primary device in the same\n" 971 " format as used by the LSTD command.",
973 " Put data into the EPG list. The data entered has to strictly follow the\n" 974 " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n" 975 " by itself terminates the input and starts processing of the data (all\n" 976 " entered data is buffered until the terminating '.' is seen).\n" 977 " If a file name is given, epg data will be read from this file (which\n" 978 " must be accessible under the given name from the machine VDR is running\n" 979 " on). In case of file input, no terminating '.' shall be given.\n",
980 "REMO [ on | off ]\n" 981 " Turns the remote control on or off. Without a parameter, the current\n" 982 " status of the remote control is reported.",
984 " Forces an EPG scan. If this is a single DVB device system, the scan\n" 985 " will be done on the primary device unless it is currently recording.",
987 " Return information about disk usage (total, free, percent).",
989 " Updates a timer. Settings must be in the same format as returned\n" 990 " by the LSTT command. If a timer with the same channel, day, start\n" 991 " and stop time does not yet exist, it will be created.",
993 " Initiates a re-read of the recordings directory, which is the SVDRP\n" 994 " equivalent to 'touch .update'.",
995 "VOLU [ <number> | + | - | mute ]\n" 996 " Set the audio volume to the given number (which is limited to the range\n" 997 " 0...255). If the special options '+' or '-' are given, the volume will\n" 998 " be turned up or down, respectively. The option 'mute' will toggle the\n" 999 " audio muting. If no option is given, the current audio volume level will\n" 1002 " Exit vdr (SVDRP).\n" 1003 " You can also hit Ctrl-D to exit.",
1031 const char *q = HelpPage;
1034 uint n = q - HelpPage;
1035 if (n >=
sizeof(topic))
1036 n =
sizeof(topic) - 1;
1037 strncpy(topic, HelpPage, n);
1051 if (strcasecmp(Cmd, t) == 0)
1072 void Close(
bool SendReply =
false,
bool Timeout =
false);
1073 bool Send(
const char *s);
1074 void Reply(
int Code,
const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
1075 void PrintHelpTopics(const
char **hp);
1076 void CmdCHAN(const
char *Option);
1077 void CmdCLRE(const
char *Option);
1078 void CmdCONN(const
char *Option);
1079 void CmdDELC(const
char *Option);
1080 void CmdDELR(const
char *Option);
1081 void CmdDELT(const
char *Option);
1082 void CmdEDIT(const
char *Option);
1083 void CmdGRAB(const
char *Option);
1084 void CmdHELP(const
char *Option);
1085 void CmdHITK(const
char *Option);
1086 void CmdLSTC(const
char *Option);
1087 void CmdLSTD(const
char *Option);
1088 void CmdLSTE(const
char *Option);
1089 void CmdLSTR(const
char *Option);
1090 void CmdLSTT(const
char *Option);
1091 void CmdMESG(const
char *Option);
1092 void CmdMODC(const
char *Option);
1093 void CmdMODT(const
char *Option);
1094 void CmdMOVC(const
char *Option);
1095 void CmdMOVR(const
char *Option);
1096 void CmdNEWC(const
char *Option);
1097 void CmdNEWT(const
char *Option);
1098 void CmdNEXT(const
char *Option);
1099 void CmdPING(const
char *Option);
1100 void CmdPLAY(const
char *Option);
1101 void CmdPLUG(const
char *Option);
1102 void CmdPOLL(const
char *Option);
1103 void CmdPRIM(const
char *Option);
1104 void CmdPUTE(const
char *Option);
1105 void CmdREMO(const
char *Option);
1106 void CmdSCAN(const
char *Option);
1107 void CmdSTAT(const
char *Option);
1108 void CmdUPDT(const
char *Option);
1109 void CmdUPDR(const
char *Option);
1110 void CmdVOLU(const
char *Option);
1115 const
char *ClientName(
void)
const {
return clientName; }
1125 clientIpAddress = *ClientIpAddress;
1130 cmdLine =
MALLOC(
char, length);
1131 lastActivity = time(NULL);
1132 if (file.Open(socket)) {
1133 time_t now = time(NULL);
1135 SVDRPServerPoller.
Add(file,
false);
1149 if (file.IsOpen()) {
1151 Reply(221,
"%s closing connection%s",
Setup.
SVDRPHostName, Timeout ?
" (timeout)" :
"");
1154 SVDRPServerPoller.
Del(file,
false);
1163 dbgsvdrp(
"> S %s: %s", *clientName, s);
1174 if (file.IsOpen()) {
1176 char *buffer = NULL;
1179 if (vasprintf(&buffer, fmt, ap) >= 0) {
1182 char *n = strchr(s,
'\n');
1186 if (Code < 0 || n && *(n + 1))
1190 s = n ? n + 1 : NULL;
1194 Reply(451,
"Bad format - looks like a programming error!");
1201 Reply(451,
"Zero return code - looks like a programming error!");
1217 const int TopicsPerLine = 5;
1219 for (
int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
1222 q += sprintf(q,
" ");
1223 for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
1224 const char *topic =
GetHelpTopic(hp[(y * TopicsPerLine + x)]);
1229 Reply(-214,
"%s", buffer);
1240 int o = strtol(Option, NULL, 10);
1244 else if (strcmp(Option,
"-") == 0) {
1251 else if (strcmp(Option,
"+") == 0) {
1259 n = Channel->Number();
1261 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1262 if (!Channel->GroupSep()) {
1263 if (strcasecmp(Channel->Name(), Option) == 0) {
1264 n = Channel->Number();
1271 Reply(501,
"Undefined channel \"%s\"", Option);
1275 if (
const cChannel *Channel = Channels->GetByNumber(n)) {
1277 Reply(554,
"Error switching to channel \"%d\"", Channel->Number());
1282 Reply(550,
"Unable to find channel \"%s\"", Option);
1290 Reply(250,
"%d %s", Channel->Number(), Channel->Name());
1302 int o = strtol(Option, NULL, 10);
1304 ChannelID = Channels->GetByNumber(o)->GetChannelID();
1309 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1310 if (!Channel->GroupSep()) {
1311 if (strcasecmp(Channel->Name(), Option) == 0) {
1312 ChannelID = Channel->GetChannelID();
1323 for (
cSchedule *p = Schedules->First(); p; p = Schedules->
Next(p)) {
1324 if (p->ChannelID() == ChannelID) {
1330 for (
cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
1331 if (ChannelID == Timer->Channel()->GetChannelID().
ClrRid())
1332 Timer->SetEvent(NULL);
1336 Reply(250,
"EPG data of channel \"%s\" cleared", Option);
1339 Reply(550,
"No EPG data found for channel \"%s\"", Option);
1344 Reply(501,
"Undefined channel \"%s\"", Option);
1349 for (
cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer))
1350 Timer->SetEvent(NULL);
1351 for (
cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->
Next(Schedule))
1352 Schedule->Cleanup(INT_MAX);
1354 Reply(250,
"EPG data cleared");
1361 if (SVDRPClientHandler) {
1363 if (ServerParams.
Ok()) {
1364 clientName = ServerParams.
Name();
1366 SVDRPClientHandler->
AddClient(ServerParams, clientIpAddress.Address());
1369 Reply(501,
"Error in server parameters: %s", ServerParams.
Error());
1372 Reply(451,
"No SVDRP client handler");
1375 Reply(501,
"Missing server parameters");
1384 Channels->SetExplicitModify();
1385 if (
cChannel *Channel = Channels->GetByNumber(strtol(Option, NULL, 10))) {
1386 if (
const cTimer *Timer = Timers->UsesChannel(Channel)) {
1387 Reply(550,
"Channel \"%s\" is in use by timer %s", Option, *Timer->ToDescr());
1391 cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
1392 if (CurrentChannel && Channel == CurrentChannel) {
1393 int n = Channels->GetNextNormal(CurrentChannel->
Index());
1395 n = Channels->GetPrevNormal(CurrentChannel->
Index());
1397 Reply(501,
"Can't delete channel \"%s\" - list would be empty", Option);
1400 CurrentChannel = Channels->Get(n);
1401 CurrentChannelNr = 0;
1403 Channels->Del(Channel);
1404 Channels->ReNumber();
1405 Channels->SetModifiedByUser();
1406 Channels->SetModified();
1408 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
1410 Channels->SwitchTo(CurrentChannel->
Number());
1414 Reply(250,
"Channel \"%s\" deleted", Option);
1417 Reply(501,
"Channel \"%s\" not defined", Option);
1420 Reply(501,
"Error in channel number \"%s\"", Option);
1423 Reply(501,
"Missing channel number");
1432 return cString::sprintf(
"Recording \"%s\" is being replayed", RecordingId);
1433 else if ((Reason &
ruCut) != 0)
1436 return cString::sprintf(
"Recording \"%s\" is being copied/moved", RecordingId);
1447 Recordings->SetExplicitModify();
1448 if (
cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1449 if (
int RecordingInUse = Recording->IsInUse())
1452 if (Recording->Delete()) {
1453 Recordings->DelByName(Recording->FileName());
1454 Recordings->SetModified();
1456 Reply(250,
"Recording \"%s\" deleted", Option);
1459 Reply(554,
"Error while deleting recording!");
1463 Reply(550,
"Recording \"%s\" not found", Option);
1466 Reply(501,
"Error in recording id \"%s\"", Option);
1469 Reply(501,
"Missing recording id");
1477 Timers->SetExplicitModify();
1478 if (
cTimer *Timer = Timers->GetById(strtol(Option, NULL, 10))) {
1479 if (Timer->Recording()) {
1484 Timers->SetModified();
1486 Reply(250,
"Timer \"%s\" deleted", Option);
1489 Reply(501,
"Timer \"%s\" not defined", Option);
1492 Reply(501,
"Error in timer number \"%s\"", Option);
1495 Reply(501,
"Missing timer number");
1503 if (
const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1505 if (Marks.
Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.
Count()) {
1507 Reply(250,
"Editing recording \"%s\" [%s]", Option, Recording->Title());
1509 Reply(554,
"Can't start editing process");
1512 Reply(554,
"No editing marks defined");
1515 Reply(550,
"Recording \"%s\" not found", Option);
1518 Reply(501,
"Error in recording id \"%s\"", Option);
1521 Reply(501,
"Missing recording id");
1526 const char *FileName = NULL;
1528 int Quality = -1, SizeX = -1, SizeY = -1;
1530 char buf[strlen(Option) + 1];
1531 char *p = strcpy(buf, Option);
1532 const char *delim =
" \t";
1534 FileName = strtok_r(p, delim, &strtok_next);
1536 const char *Extension = strrchr(FileName,
'.');
1538 if (strcasecmp(Extension,
".jpg") == 0 || strcasecmp(Extension,
".jpeg") == 0)
1540 else if (strcasecmp(Extension,
".pnm") == 0)
1543 Reply(501,
"Unknown image type \"%s\"", Extension + 1);
1546 if (Extension == FileName)
1549 else if (strcmp(FileName,
"-") == 0)
1552 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1553 if (strcasecmp(p,
"JPEG") == 0 || strcasecmp(p,
"PNM") == 0) {
1555 p = strtok_r(NULL, delim, &strtok_next);
1561 Reply(501,
"Invalid quality \"%s\"", p);
1567 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1571 Reply(501,
"Invalid sizex \"%s\"", p);
1574 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1578 Reply(501,
"Invalid sizey \"%s\"", p);
1583 Reply(501,
"Missing sizey");
1587 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1588 Reply(501,
"Unexpected parameter \"%s\"", p);
1592 char RealFileName[PATH_MAX];
1594 if (*grabImageDir) {
1597 const char *slash = strrchr(FileName,
'/');
1602 slash = strrchr(FileName,
'/');
1605 char *r = realpath(t, RealFileName);
1608 Reply(501,
"Invalid file name \"%s\"", FileName);
1611 strcat(RealFileName, slash);
1612 FileName = RealFileName;
1613 if (strncmp(FileName, grabImageDir, strlen(grabImageDir)) != 0) {
1614 Reply(501,
"Invalid file name \"%s\"", FileName);
1619 Reply(550,
"Grabbing to file not allowed (use \"GRAB -\" instead)");
1628 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
1630 if (
safe_write(fd, Image, ImageSize) == ImageSize) {
1632 Reply(250,
"Grabbed image %s", Option);
1636 Reply(451,
"Can't write to '%s'", FileName);
1642 Reply(451,
"Can't open '%s'", FileName);
1648 while ((s = Base64.
NextLine()) != NULL)
1649 Reply(-216,
"%s", s);
1650 Reply(216,
"Grabbed image %s", Option);
1655 Reply(451,
"Grab image failed");
1658 Reply(501,
"Missing filename");
1666 Reply(-214,
"%s", hp);
1668 Reply(504,
"HELP topic \"%s\" unknown", Option);
1673 Reply(-214,
"This is VDR version %s",
VDRVERSION);
1674 Reply(-214,
"Topics:");
1681 PrintHelpTopics(hp);
1683 Reply(-214,
"To report bugs in the implementation send email to");
1684 Reply(-214,
" vdr-bugs@tvdr.de");
1686 Reply(214,
"End of HELP info");
1693 Reply(550,
"Remote control currently disabled (key \"%s\" discarded)", Option);
1696 char buf[strlen(Option) + 1];
1697 strcpy(buf, Option);
1698 const char *delim =
" \t";
1700 char *p = strtok_r(buf, delim, &strtok_next);
1706 Reply(451,
"Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
1711 Reply(504,
"Unknown key: \"%s\"", p);
1715 p = strtok_r(NULL, delim, &strtok_next);
1717 Reply(250,
"Key%s \"%s\" accepted", NumKeys > 1 ?
"s" :
"", Option);
1720 Reply(-214,
"Valid <key> names for the HITK command:");
1721 for (
int i = 0; i <
kNone; i++) {
1724 Reply(214,
"End of key list");
1731 bool WithChannelIds =
startswith(Option,
":ids") && (Option[4] ==
' ' || Option[4] == 0);
1734 bool WithGroupSeps = strcasecmp(Option,
":groups") == 0;
1735 if (*Option && !WithGroupSeps) {
1737 int n = strtol(Option, NULL, 10);
1740 if (
const cChannel *Channel = Channels->GetByNumber(n))
1741 Reply(250,
"%d%s%s %s", Channel->Number(), WithChannelIds ?
" " :
"", WithChannelIds ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1743 Reply(501,
"Channel \"%s\" not defined", Option);
1748 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1749 if (!Channel->GroupSep()) {
1750 if (strcasestr(Channel->Name(), Option)) {
1761 Reply(501,
"Channel \"%s\" not defined", Option);
1765 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1767 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());
1768 else if (!Channel->GroupSep())
1769 Reply(Channel->Number() <
cChannels::MaxNumber() ? -250 : 250,
"%d%s%s %s", Channel->Number(), WithChannelIds ?
" " :
"", WithChannelIds ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1773 Reply(550,
"No channels defined");
1781 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());
1785 Reply(550,
"No devices found");
1796 char buf[strlen(Option) + 1];
1797 strcpy(buf, Option);
1798 const char *delim =
" \t";
1800 char *p = strtok_r(buf, delim, &strtok_next);
1801 while (p && DumpMode ==
dmAll) {
1802 if (strcasecmp(p,
"NOW") == 0)
1804 else if (strcasecmp(p,
"NEXT") == 0)
1806 else if (strcasecmp(p,
"AT") == 0) {
1808 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1810 AtTime = strtol(p, NULL, 10);
1812 Reply(501,
"Invalid time");
1817 Reply(501,
"Missing time");
1821 else if (!Schedule) {
1824 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1828 Schedule = Schedules->GetSchedule(Channel);
1830 Reply(550,
"No schedule found");
1835 Reply(550,
"Channel \"%s\" not defined", p);
1840 Reply(501,
"Unknown option: \"%s\"", p);
1843 p = strtok_r(NULL, delim, &strtok_next);
1848 FILE *f = fdopen(fd,
"w");
1851 Schedule->
Dump(Channels, f,
"215-", DumpMode, AtTime);
1853 Schedules->Dump(f,
"215-", DumpMode, AtTime);
1855 Reply(215,
"End of EPG data");
1859 Reply(451,
"Can't open file connection");
1864 Reply(451,
"Can't dup stream descriptor");
1873 char buf[strlen(Option) + 1];
1874 strcpy(buf, Option);
1875 const char *delim =
" \t";
1877 char *p = strtok_r(buf, delim, &strtok_next);
1881 Number = strtol(p, NULL, 10);
1883 Reply(501,
"Error in recording id \"%s\"", Option);
1887 else if (strcasecmp(p,
"PATH") == 0)
1890 Reply(501,
"Unknown option: \"%s\"", p);
1893 p = strtok_r(NULL, delim, &strtok_next);
1896 if (
const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1897 FILE *f = fdopen(file,
"w");
1900 Reply(250,
"%s", Recording->FileName());
1902 Recording->Info()->Write(f,
"215-");
1904 Reply(215,
"End of recording information");
1909 Reply(451,
"Can't open file connection");
1912 Reply(550,
"Recording \"%s\" not found", Option);
1915 else if (Recordings->Count()) {
1916 const cRecording *Recording = Recordings->First();
1918 Reply(Recording == Recordings->Last() ? 250 : -250,
"%d %s", Recording->
Id(), Recording->
Title(
' ',
true));
1919 Recording = Recordings->
Next(Recording);
1923 Reply(550,
"No recordings available");
1929 bool UseChannelId =
false;
1931 char buf[strlen(Option) + 1];
1932 strcpy(buf, Option);
1933 const char *delim =
" \t";
1935 char *p = strtok_r(buf, delim, &strtok_next);
1938 Id = strtol(p, NULL, 10);
1939 else if (strcasecmp(p,
"ID") == 0)
1940 UseChannelId =
true;
1942 Reply(501,
"Unknown option: \"%s\"", p);
1945 p = strtok_r(NULL, delim, &strtok_next);
1950 for (
const cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
1951 if (!Timer->Remote()) {
1952 if (Timer->Id() == Id) {
1953 Reply(250,
"%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
1958 Reply(501,
"Timer \"%s\" not defined", Option);
1962 const cTimer *LastLocalTimer = Timers->Last();
1963 while (LastLocalTimer) {
1964 if (LastLocalTimer->
Remote())
1965 LastLocalTimer = Timers->
Prev(LastLocalTimer);
1969 if (LastLocalTimer) {
1970 for (
const cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
1971 if (!Timer->Remote())
1972 Reply(Timer != LastLocalTimer ? -250 : 250,
"%d %s", Timer->
Id(), *Timer->ToText(UseChannelId));
1973 if (Timer == LastLocalTimer)
1979 Reply(550,
"No timers defined");
1987 Reply(250,
"Message queued");
1990 Reply(501,
"Missing message");
1997 int n = strtol(Option, &tail, 10);
1998 if (tail && tail != Option) {
2001 Channels->SetExplicitModify();
2002 if (
cChannel *Channel = Channels->GetByNumber(n)) {
2004 if (ch.
Parse(tail)) {
2005 if (Channels->HasUniqueChannelID(&ch, Channel)) {
2007 Channels->ReNumber();
2008 Channels->SetModifiedByUser();
2009 Channels->SetModified();
2010 isyslog(
"SVDRP %s < %s modifed channel %d %s",
Setup.
SVDRPHostName, *clientName, Channel->Number(), *Channel->ToText());
2011 Reply(250,
"%d %s", Channel->Number(), *Channel->ToText());
2014 Reply(501,
"Channel settings are not unique");
2017 Reply(501,
"Error in channel settings");
2020 Reply(501,
"Channel \"%d\" not defined", n);
2023 Reply(501,
"Error in channel number");
2026 Reply(501,
"Missing channel settings");
2033 int Id = strtol(Option, &tail, 10);
2034 if (tail && tail != Option) {
2037 Timers->SetExplicitModify();
2038 if (
cTimer *Timer = Timers->GetById(Id)) {
2041 if (strcasecmp(tail,
"ON") == 0)
2043 else if (strcasecmp(tail,
"OFF") == 0)
2045 else if (!t.
Parse(tail)) {
2046 Reply(501,
"Error in timer settings");
2054 Timers->SetModified();
2056 Reply(250,
"%d %s", Timer->Id(), *Timer->ToText(
true));
2059 Reply(501,
"Timer \"%d\" not defined", Id);
2062 Reply(501,
"Error in timer id");
2065 Reply(501,
"Missing timer settings");
2072 int From = strtol(Option, &tail, 10);
2073 if (tail && tail != Option) {
2075 if (tail && tail != Option) {
2078 Channels->SetExplicitModify();
2079 int To = strtol(tail, NULL, 10);
2081 const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
2082 cChannel *FromChannel = Channels->GetByNumber(From);
2084 cChannel *ToChannel = Channels->GetByNumber(To);
2086 int FromNumber = FromChannel->
Number();
2087 int ToNumber = ToChannel->
Number();
2088 if (FromNumber != ToNumber) {
2089 Channels->Move(FromChannel, ToChannel);
2090 Channels->ReNumber();
2091 Channels->SetModifiedByUser();
2092 Channels->SetModified();
2093 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
2095 Channels->SwitchTo(CurrentChannel->
Number());
2100 Reply(250,
"Channel \"%d\" moved to \"%d\"", From, To);
2103 Reply(501,
"Can't move channel to same position");
2106 Reply(501,
"Channel \"%d\" not defined", To);
2109 Reply(501,
"Channel \"%d\" not defined", From);
2112 Reply(501,
"Error in channel number");
2115 Reply(501,
"Error in channel number");
2118 Reply(501,
"Missing channel number");
2124 char *opt = strdup(Option);
2127 while (*option && !isspace(*option))
2133 Recordings->SetExplicitModify();
2134 if (
cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2135 if (
int RecordingInUse = Recording->IsInUse())
2141 cString oldName = Recording->Name();
2142 if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) {
2143 Recordings->SetModified();
2144 Recordings->TouchUpdate();
2145 Reply(250,
"Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name());
2148 Reply(554,
"Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
2151 Reply(501,
"Missing new recording name");
2155 Reply(550,
"Recording \"%s\" not found", num);
2158 Reply(501,
"Error in recording id \"%s\"", num);
2162 Reply(501,
"Missing recording id");
2169 if (ch.
Parse(Option)) {
2171 Channels->SetExplicitModify();
2172 if (Channels->HasUniqueChannelID(&ch)) {
2175 Channels->Add(channel);
2176 Channels->ReNumber();
2177 Channels->SetModifiedByUser();
2178 Channels->SetModified();
2180 Reply(250,
"%d %s", channel->
Number(), *channel->
ToText());
2183 Reply(501,
"Channel settings are not unique");
2186 Reply(501,
"Error in channel settings");
2189 Reply(501,
"Missing channel settings");
2196 if (Timer->
Parse(Option)) {
2201 Reply(250,
"%d %s", Timer->
Id(), *Timer->
ToText(
true));
2205 Reply(501,
"Error in timer settings");
2209 Reply(501,
"Missing timer settings");
2215 if (
const cTimer *t = Timers->GetNextActiveTimer()) {
2216 time_t
Start = t->StartTime();
2220 else if (strcasecmp(Option,
"ABS") == 0)
2221 Reply(250,
"%d %ld", Id, Start);
2222 else if (strcasecmp(Option,
"REL") == 0)
2223 Reply(250,
"%d %ld", Id, Start - time(NULL));
2225 Reply(501,
"Unknown option: \"%s\"", Option);
2228 Reply(550,
"No active timers");
2239 char *opt = strdup(Option);
2242 while (*option && !isspace(*option))
2249 if (
const cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2250 cString FileName = Recording->FileName();
2251 cString Title = Recording->Title();
2252 int FramesPerSecond = Recording->FramesPerSecond();
2253 bool IsPesRecording = Recording->IsPesRecording();
2261 if (strcasecmp(option,
"BEGIN") != 0)
2272 Reply(250,
"Playing recording \"%s\" [%s]", num, *Title);
2276 Reply(550,
"Recording \"%s\" not found", num);
2281 Reply(501,
"Error in recording id \"%s\"", num);
2285 Reply(501,
"Missing recording id");
2291 char *opt = strdup(Option);
2293 char *option = name;
2294 while (*option && !isspace(*option))
2303 while (*option && !isspace(*option))
2309 if (!*cmd || strcasecmp(cmd,
"HELP") == 0) {
2310 if (*cmd && *option) {
2313 Reply(-214,
"%s", hp);
2314 Reply(214,
"End of HELP info");
2317 Reply(504,
"HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->
Name());
2323 Reply(-214,
"SVDRP commands:");
2324 PrintHelpTopics(hp);
2325 Reply(214,
"End of HELP info");
2328 Reply(214,
"This plugin has no SVDRP commands");
2331 else if (strcasecmp(cmd,
"MAIN") == 0) {
2333 Reply(250,
"Initiated call to main menu function of plugin \"%s\"", plugin->
Name());
2335 Reply(550,
"A plugin call is already pending - please try again later");
2338 int ReplyCode = 900;
2341 Reply(abs(ReplyCode),
"%s", *s);
2343 Reply(500,
"Command unrecognized: \"%s\"", cmd);
2347 Reply(550,
"Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
2351 Reply(-214,
"Available plugins:");
2355 Reply(214,
"End of plugin list");
2362 char buf[strlen(Option) + 1];
2363 char *p = strcpy(buf, Option);
2364 const char *delim =
" \t";
2366 char *RemoteName = strtok_r(p, delim, &strtok_next);
2367 char *ListName = strtok_r(NULL, delim, &strtok_next);
2368 if (SVDRPClientHandler) {
2370 if (strcasecmp(ListName,
"timers") == 0) {
2374 Reply(501,
"No connection to \"%s\"", RemoteName);
2377 Reply(501,
"Unknown list name: \"%s\"", ListName);
2380 Reply(501,
"Missing list name");
2383 Reply(501,
"No SVDRP client connections");
2386 Reply(501,
"Missing parameters");
2394 int o = strtol(Option, NULL, 10);
2398 Reply(501,
"Invalid device number \"%s\"", Option);
2401 Reply(501,
"Invalid parameter \"%s\"", Option);
2404 Reply(250,
"Primary device set to %d", n);
2409 Reply(250,
"%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ?
"D" :
"-", d->DeviceNumber() + 1 ==
Setup.
PrimaryDVB ?
"P" :
"-", *d->DeviceName());
2411 Reply(501,
"Failed to get primary device");
2418 FILE *f = fopen(Option,
"r");
2422 Reply(250,
"EPG data processed from \"%s\"", Option);
2425 Reply(451,
"Error while processing EPG from \"%s\"", Option);
2429 Reply(501,
"Cannot open file \"%s\"", Option);
2434 Reply(PUTEhandler->Status(),
"%s", PUTEhandler->Message());
2435 if (PUTEhandler->Status() != 354)
2443 if (!strcasecmp(Option,
"ON")) {
2445 Reply(250,
"Remote control enabled");
2447 else if (!strcasecmp(Option,
"OFF")) {
2449 Reply(250,
"Remote control disabled");
2452 Reply(501,
"Invalid Option \"%s\"", Option);
2455 Reply(250,
"Remote control is %s",
cRemote::Enabled() ?
"enabled" :
"disabled");
2461 Reply(250,
"EPG scan triggered");
2467 if (strcasecmp(Option,
"DISK") == 0) {
2470 Reply(250,
"%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
2473 Reply(501,
"Invalid Option \"%s\"", Option);
2476 Reply(501,
"No option given");
2483 if (Timer->
Parse(Option)) {
2485 if (
cTimer *t = Timers->GetTimer(Timer)) {
2501 Reply(250,
"%d %s", Timer->
Id(), *Timer->
ToText(
true));
2505 Reply(501,
"Error in timer settings");
2509 Reply(501,
"Missing timer settings");
2515 Recordings->Update(
false);
2516 Reply(250,
"Re-read of recordings directory triggered");
2524 else if (strcmp(Option,
"+") == 0)
2526 else if (strcmp(Option,
"-") == 0)
2528 else if (strcasecmp(Option,
"MUTE") == 0)
2531 Reply(501,
"Unknown option: \"%s\"", Option);
2536 Reply(250,
"Audio is mute");
2541 #define CMD(c) (strcasecmp(Cmd, c) == 0) 2547 if (!PUTEhandler->Process(Cmd)) {
2548 Reply(PUTEhandler->Status(),
"%s", PUTEhandler->Message());
2558 while (*s && !isspace(*s))
2563 if (
CMD(
"CHAN")) CmdCHAN(s);
2564 else if (
CMD(
"CLRE")) CmdCLRE(s);
2565 else if (
CMD(
"CONN")) CmdCONN(s);
2566 else if (
CMD(
"DELC")) CmdDELC(s);
2567 else if (
CMD(
"DELR")) CmdDELR(s);
2568 else if (
CMD(
"DELT")) CmdDELT(s);
2569 else if (
CMD(
"EDIT")) CmdEDIT(s);
2570 else if (
CMD(
"GRAB")) CmdGRAB(s);
2571 else if (
CMD(
"HELP")) CmdHELP(s);
2572 else if (
CMD(
"HITK")) CmdHITK(s);
2573 else if (
CMD(
"LSTC")) CmdLSTC(s);
2574 else if (
CMD(
"LSTD")) CmdLSTD(s);
2575 else if (
CMD(
"LSTE")) CmdLSTE(s);
2576 else if (
CMD(
"LSTR")) CmdLSTR(s);
2577 else if (
CMD(
"LSTT")) CmdLSTT(s);
2578 else if (
CMD(
"MESG")) CmdMESG(s);
2579 else if (
CMD(
"MODC")) CmdMODC(s);
2580 else if (
CMD(
"MODT")) CmdMODT(s);
2581 else if (
CMD(
"MOVC")) CmdMOVC(s);
2582 else if (
CMD(
"MOVR")) CmdMOVR(s);
2583 else if (
CMD(
"NEWC")) CmdNEWC(s);
2584 else if (
CMD(
"NEWT")) CmdNEWT(s);
2585 else if (
CMD(
"NEXT")) CmdNEXT(s);
2586 else if (
CMD(
"PING")) CmdPING(s);
2587 else if (
CMD(
"PLAY")) CmdPLAY(s);
2588 else if (
CMD(
"PLUG")) CmdPLUG(s);
2589 else if (
CMD(
"POLL")) CmdPOLL(s);
2590 else if (
CMD(
"PRIM")) CmdPRIM(s);
2591 else if (
CMD(
"PUTE")) CmdPUTE(s);
2592 else if (
CMD(
"REMO")) CmdREMO(s);
2593 else if (
CMD(
"SCAN")) CmdSCAN(s);
2594 else if (
CMD(
"STAT")) CmdSTAT(s);
2595 else if (
CMD(
"UPDR")) CmdUPDR(s);
2596 else if (
CMD(
"UPDT")) CmdUPDT(s);
2597 else if (
CMD(
"VOLU")) CmdVOLU(s);
2598 else if (
CMD(
"QUIT")) Close(
true);
2599 else Reply(500,
"Command unrecognized: \"%s\"", Cmd);
2604 if (file.IsOpen()) {
2605 while (file.Ready(
false)) {
2609 if (c ==
'\n' || c == 0x00) {
2611 while (numChars > 0 && strchr(
" \t\r\n", cmdLine[numChars - 1]))
2612 cmdLine[--numChars] = 0;
2614 cmdLine[numChars] = 0;
2616 dbgsvdrp(
"< S %s: %s\n", *clientName, cmdLine);
2619 if (length > BUFSIZ) {
2622 cmdLine =
MALLOC(
char, length);
2625 else if (c == 0x04 && numChars == 0) {
2629 else if (c == 0x08 || c == 0x7F) {
2634 else if (c <= 0x03 || c == 0x0D) {
2638 if (numChars >= length - 1) {
2639 int NewLength = length + BUFSIZ;
2640 if (
char *NewBuffer = (
char *)realloc(cmdLine, NewLength)) {
2642 cmdLine = NewBuffer;
2650 cmdLine[numChars++] = c;
2651 cmdLine[numChars] = 0;
2653 lastActivity = time(NULL);
2665 return file.IsOpen();
2676 grabImageDir = GrabImageDir;
2686 void HandleServerConnection(
void);
2689 virtual void Action(
void);
2693 void WaitUntilReady(
void);
2699 :
cThread(
"SVDRP server handler", true)
2700 ,tcpSocket(TcpPort, true)
2743 SVDRPServerPoller.
Poll(1000);
2760 if (!SVDRPServerHandler) {
2762 SVDRPServerHandler->
Start();
2767 SVDRPClientHandler->
Start();
2776 SVDRPClientHandler = NULL;
2778 SVDRPServerHandler = NULL;
2783 bool Result =
false;
2785 if (SVDRPClientHandler) {
2786 SVDRPClientHandler->
Lock();
2788 SVDRPClientHandler->
Unlock();
2795 bool Result =
false;
2797 if (SVDRPClientHandler) {
2798 SVDRPClientHandler->
Lock();
2799 Result = SVDRPClientHandler->
Execute(ServerName, Command, Response);
2800 SVDRPClientHandler->
Unlock();
2809 if (SVDRPClientHandler) {
2810 SVDRPClientHandler->
Lock();
2812 for (
int i = 0; i < ServerNames.
Size(); i++)
2815 SVDRPClientHandler->
Unlock();
void CmdLSTT(const char *Option)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
bool Replaying(void) const
Returns true if we are currently replaying.
virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
void CmdCONN(const char *Option)
void SetSVDRPPorts(int TcpPort, int UdpPort)
bool HasAddress(const char *Address, int Port) const
void CmdNEWT(const char *Option)
const char * Message(void)
void Close(bool SendReply=false, bool Timeout=false)
static tChannelID FromString(const char *s)
bool ToggleMute(void)
Turns the volume off or on and returns the new mute state.
bool Ready(bool Wait=true)
void CmdNEWC(const char *Option)
cSVDRPServerParams(const char *Params)
bool GetSVDRPServerNames(cStringList *ServerNames)
Gets a list of all available VDRs this VDR is connected to via SVDRP, and stores it in the given Serv...
const char * Host(void) const
static cString ToText(const cChannel *Channel)
bool TimedOut(void) const
Returns true if the last lock attempt this key was used with failed due to a timeout.
void CmdSCAN(const char *Option)
virtual const char ** SVDRPHelpPages(void)
const char * ServerName(void) const
virtual const char * Version(void)=0
void CmdMESG(const char *Option)
void CmdMODC(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
void CmdCLRE(const char *Option)
void CmdPRIM(const char *Option)
virtual void Append(T Data)
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 cMutex SVDRPHandlerMutex
void CmdVOLU(const char *Option)
void Reply(int Code, const char *fmt,...) __attribute__((format(printf
char SVDRPDefaultHost[HOST_NAME_MAX]
static eKeys FromString(const char *Name)
bool Add(int FileHandle, bool Out)
void Dump(const cChannels *Channels, FILE *f, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0) const
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
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 TriggerFetchingTimers(const char *ServerName)
static const cRecordings * GetRecordingsRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of recordings for read access.
static cDevice * GetDevice(int Index)
Gets the device with the given Index.
bool Parse(const char *s)
const char * GetHelpTopic(const char *HelpPage)
const char * GetHelpPage(const char *Cmd, const char **p)
#define SVDRPResonseTimeout
static int NumDevices(void)
Returns the total number of devices.
static cString grabImageDir
bool GetRemoteTimers(cStringList &Response)
const int Port(void) const
static bool SendDgram(const char *Dgram, int Port)
void CmdMOVR(const char *Option)
void CmdSTAT(const char *Option)
void Del(int FileHandle, bool Out)
void CmdNEXT(const char *Option)
#define LOCK_CHANNELS_WRITE
static int MaxNumber(void)
void SetFetchFlag(int Flag)
bool Poll(int TimeoutMs=0)
void CmdUPDR(const char *Option)
bool Execute(const char *ServerName, const char *Command, cStringList *Response=NULL)
void StopSVDRPHandler(void)
static cTimers * GetTimersWrite(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for write access.
static void SetRecording(const char *FileName)
static int CurrentVolume(void)
void CmdLSTE(const char *Option)
void CmdCHAN(const char *Option)
void HandleClientConnection(void)
virtual void Remove(int Index)
virtual const char * Description(void)=0
void CmdLSTD(const char *Option)
bool Transferring(void) const
Returns true if we are currently in Transfer Mode.
void CmdPING(const char *Option)
cPUTEhandler * PUTEhandler
static int CurrentChannel(void)
Returns the number of the current channel on the primary device.
bool GetServerNames(cStringList *ServerNames)
void CmdDELC(const char *Option)
void CmdGRAB(const char *Option)
void void PrintHelpTopics(const char **hp)
const char * VdrVersion(void) const
cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress)
void HandleServerConnection(void)
cSVDRPServerHandler(int TcpPort)
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)
void StartSVDRPHandler(void)
bool Execute(const char *Command, cStringList *Response=NULL)
cListObject * Next(void) const
bool ExecSVDRPCommand(const char *ServerName, const char *Command, cStringList *Response)
Sends the given SVDRP Command string to the remote VDR identified by ServerName and collects all of t...
cIpAddress serverIpAddress
bool SwitchChannel(const cChannel *Channel, bool LiveView)
Switches the device to the given Channel, initiating transfer mode if necessary.
static void SetEnabled(bool Enabled)
#define LOCK_CHANNELS_READ
const int Timeout(void) const
const char * Name(void) const
bool TimedOut(void) const
#define LOCK_RECORDINGS_WRITE
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
void CmdUPDT(const char *Option)
void Cleanup(time_t Time)
const char * Connection(void) const
void SetSVDRPGrabImageDir(const char *GrabImageDir)
void CmdPUTE(const char *Option)
void CmdHELP(const char *Option)
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
static bool Read(FILE *f=NULL)
void WaitUntilReady(void)
#define LOCK_TIMERS_WRITE
static bool Process(cTimers *Timers, time_t t)
const char * Remote(void) const
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.
bool HasFetchFlag(int Flag)
void ProcessConnections(void)
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
cRecordingsHandler RecordingsHandler
bool Connect(const char *Address)
static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
cStateKey StateKeySVDRPRemoteTimersPoll(true)
static void Launch(cControl *Control)
cSVDRPClientHandler(int TcpPort, int UdpPort)
const char * Error(void) const
void CmdPLAY(const char *Option)
static bool Enabled(void)
cString ToDescr(void) const
char SVDRPHostName[HOST_NAME_MAX]
void CmdREMO(const char *Option)
void CmdPOLL(const char *Option)
static int VideoDiskSpace(int *FreeMB=NULL, int *UsedMB=NULL)
void CmdHITK(const char *Option)
void ProcessConnections(void)
cSocket(int Port, bool Tcp)
const char * ApiVersion(void) const
cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout)
cListObject * Prev(void) const
static cPoller SVDRPServerPoller
cString ToString(void) const
void CmdLSTR(const char *Option)
const char * Address(void) const
static cDevice * PrimaryDevice(void)
Returns the primary device.
void CmdPLUG(const char *Option)
#define LOCK_RECORDINGS_READ
void SetFlags(uint Flags)
cSVDRPClient * GetClientForServer(const char *ServerName)
void CmdMODT(const char *Option)
virtual ~cSVDRPServerHandler()
static void SetCurrentChannel(const cChannel *Channel)
bool StoreRemoteTimers(const char *ServerName=NULL, const cStringList *RemoteTimers=NULL)
Stores the given list of RemoteTimers, which come from the VDR ServerName, in this list...
static cRecordControl * GetRecordControl(const char *FileName)
cVector< cSVDRPServer * > serverConnections
static cPlugin * GetPlugin(int Index)
void AddClient(cSVDRPServerParams &ServerParams, const char *IpAddress)
bool Acceptable(in_addr_t Address)
void ClrFlags(uint Flags)
static const tChannelID InvalidID
bool Send(const char *Command)
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
#define LOCK_SCHEDULES_WRITE
bool Connected(void) const
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
const cIpAddress * LastIpAddress(void) const
void BroadcastSVDRPCommand(const char *Command)
Sends the given SVDRP Command string to all remote VDRs.
cIpAddress clientIpAddress
cString ToText(bool UseChannelID=false) const
virtual ~cSVDRPClientHandler()
static cSVDRPServerHandler * SVDRPServerHandler
#define LOCK_SCHEDULES_READ
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
cVector< cSVDRPClient * > clientConnections
void CmdDELR(const char *Option)
tChannelID & ClrRid(void)
static const cTimers * GetTimersRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for read access.
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting 'running' to false, so that the Action() loop can finish in an or...
static bool DumpSVDRPDataTransfer
void CmdEDIT(const char *Option)
void Set(const char *Address, int Port)
static void Shutdown(void)
static cSVDRPClientHandler * SVDRPClientHandler
void CmdDELT(const char *Option)
void CmdLSTC(const char *Option)
bool Add(int Usage, const char *FileNameSrc, const char *FileNameDst=NULL)
Adds the given FileNameSrc to the recordings handler for (later) processing.
void SortNumerically(void)
static const char * ToString(eKeys Key, bool Translate=false)
void CmdMOVC(const char *Option)
static cPoller SVDRPClientPoller
const char * Connection(void) const
bool Process(cStringList *Response=NULL)
static bool CallPlugin(const char *Plugin)
Initiates calling the given plugin's main menu function.
int SVDRPCode(const char *s)
Returns the value of the three digit reply code of the given SVDRP response string.