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));
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);
159 Addr.sin_addr.s_addr =
SVDRPhosts.LocalhostOnly() ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY);
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) {
183 isyslog(
"SVDRP %s listening on port %d/%s",
Setup.SVDRPHostName,
port,
tcp ?
"tcp" :
"udp");
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);
220 isyslog(
"SVDRP %s > %s:%d server connection established",
Setup.SVDRPHostName, 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);
249 dsyslog(
"SVDRP %s > %s:%d send dgram '%s'",
Setup.SVDRPHostName, 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);
265 bool Accepted =
SVDRPhosts.Acceptable(Addr.sin_addr.s_addr);
267 const char *s =
"Access denied!\n";
268 if (write(NewSock, s, strlen(s)) < 0)
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");
289 uint Size =
sizeof(Addr);
290 int NumBytes = recvfrom(
sock, buf,
sizeof(buf), 0, (sockaddr *)&Addr, &Size);
294 if (!
SVDRPhosts.Acceptable(Addr.sin_addr.s_addr)) {
302 if (strcmp(
strgetval(buf,
"name",
':'),
Setup.SVDRPHostName) != 0) {
328 bool Send(
const char *Command);
335 bool HasAddress(
const char *Address,
int Port)
const;
353 timeout = Timeout * 1000 * 9 / 10;
357 if (
socket.Connect(Address)) {
403#define SVDRPResonseTimeout 5000
406 if (
file.Ready(
false)) {
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)
478 return file.IsOpen();
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)
549 if (Params && *Params) {
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";
603 virtual void Action(
void);
608 bool Execute(
const char *ServerName,
const char *Command,
cStringList *Response = NULL);
616:
cThread(
"SVDRP client handler", true)
664 bool TimersModified = Timers->StoreRemoteTimers(Client->
ServerName(), &RemoteTimers);
671 if (*PollTimersCmd) {
672 if (!Client->
Execute(PollTimersCmd))
696 if (ServerParams.
Host() && strcmp(ServerParams.
Host(),
Setup.SVDRPHostName) != 0)
706 if (ServerParams.
Ok())
733 return Client->Execute(Command, Response);
740 ServerNames->
Clear();
746 return ServerNames->
Size() > 0;
776 if ((
f = tmpfile()) != NULL) {
778 message =
"Enter EPG data, end with \".\" on a line by itself";
783 message =
"Error while opening temporary file";
796 if (strcmp(s,
".") != 0) {
806 message =
"EPG data processed";
810 message =
"Error while processing EPG data";
821#define MAXHELPTOPIC 10
822#define EITDISABLETIME 10
826 "AUDI [ <number> ]\n"
827 " Lists the currently available audio tracks in the format 'number language description'.\n"
828 " The number indicates the track type (1..32 = MP2, 33..48 = Dolby).\n"
829 " The currently selected track has its description prefixed with '*'.\n"
830 " If a number is given (which must be one of the track numbers listed)\n"
831 " audio is switched to that track.\n"
832 " Note that the list may not be fully available or current immediately after\n"
833 " switching the channel or starting a replay.",
834 "CHAN [ + | - | <number> | <name> | <id> ]\n"
835 " Switch channel up, down or to the given channel number, name or id.\n"
836 " Without option (or after successfully switching to the channel)\n"
837 " it returns the current channel number and name.",
838 "CLRE [ <number> | <name> | <id> ]\n"
839 " Clear the EPG list of the given channel number, name or id.\n"
840 " Without option it clears the entire EPG list.\n"
841 " After a CLRE command, no further EPG processing is done for 10\n"
842 " seconds, so that data sent with subsequent PUTE commands doesn't\n"
843 " interfere with data from the broadcasters.",
844 "CONN name:<name> port:<port> vdrversion:<vdrversion> apiversion:<apiversion> timeout:<timeout>\n"
845 " Used by peer-to-peer connections between VDRs to tell the other VDR\n"
846 " to establish a connection to this VDR. The name is the SVDRP host name\n"
847 " of this VDR, which may differ from its DNS name.",
848 "CPYR <number> <new name>\n"
849 " Copy the recording with the given number. Before a recording can be\n"
850 " copied, an LSTR command must have been executed in order to retrieve\n"
851 " the recording numbers.\n",
852 "DELC <number> | <id>\n"
853 " Delete the channel with the given number or channel id.",
855 " Delete the recording with the given id. Before a recording can be\n"
856 " deleted, an LSTR command should have been executed in order to retrieve\n"
857 " the recording ids. The ids are unique and don't change while this\n"
858 " instance of VDR is running.\n"
859 " CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n"
860 " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
862 " Delete the timer with the given id. If this timer is currently recording,\n"
863 " the recording will be stopped without any warning.",
865 " Edit the recording with the given id. Before a recording can be\n"
866 " edited, an LSTR command should have been executed in order to retrieve\n"
867 " the recording ids.",
868 "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n"
869 " Grab the current frame and save it to the given file. Images can\n"
870 " be stored as JPEG or PNM, depending on the given file name extension.\n"
871 " The quality of the grabbed image can be in the range 0..100, where 100\n"
872 " (the default) means \"best\" (only applies to JPEG). The size parameters\n"
873 " define the size of the resulting image (default is full screen).\n"
874 " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n"
875 " data will be sent to the SVDRP connection encoded in base64. The same\n"
876 " happens if '-' (a minus sign) is given as file name, in which case the\n"
877 " image format defaults to JPEG.",
879 " The HELP command gives help info.",
880 "HITK [ <key> ... ]\n"
881 " Hit the given remote control key. Without option a list of all\n"
882 " valid key names is given. If more than one key is given, they are\n"
883 " entered into the remote control queue in the given sequence. There\n"
884 " can be up to 31 keys.",
885 "LSTC [ :ids ] [ :groups | <number> | <name> | <id> ]\n"
886 " List channels. Without option, all channels are listed. Otherwise\n"
887 " only the given channel is listed. If a name is given, all channels\n"
888 " containing the given string as part of their name are listed.\n"
889 " If ':groups' is given, all channels are listed including group\n"
890 " separators. The channel number of a group separator is always 0.\n"
891 " With ':ids' the channel ids are listed following the channel numbers.\n"
892 " The special number 0 can be given to list the current channel.",
894 " List all available devices. Each device is listed with its name and\n"
895 " whether it is currently the primary device ('P') or it implements a\n"
896 " decoder ('D') and can be used as output device.",
897 "LSTE [ <channel> ] [ now | next | at <time> ]\n"
898 " List EPG data. Without any parameters all data of all channels is\n"
899 " listed. If a channel is given (either by number or by channel ID),\n"
900 " only data for that channel is listed. 'now', 'next', or 'at <time>'\n"
901 " restricts the returned data to present events, following events, or\n"
902 " events at the given time (which must be in time_t form).",
903 "LSTR [ <id> [ path ] ]\n"
904 " List recordings. Without option, all recordings are listed. Otherwise\n"
905 " the information for the given recording is listed. If a recording\n"
906 " id and the keyword 'path' is given, the actual file name of that\n"
907 " recording's directory is listed.\n"
908 " Note that the ids of the recordings are not necessarily given in\n"
910 "LSTT [ <id> ] [ id ]\n"
911 " List timers. Without option, all timers are listed. Otherwise\n"
912 " only the timer with the given id is listed. If the keyword 'id' is\n"
913 " given, the channels will be listed with their unique channel ids\n"
914 " instead of their numbers. This command lists only the timers that are\n"
915 " defined locally on this VDR, not any remote timers from other VDRs.",
917 " Displays the given message on the OSD. The message will be queued\n"
918 " and displayed whenever this is suitable.\n",
919 "MODC <number> <settings>\n"
920 " Modify a channel. Settings must be in the same format as returned\n"
921 " by the LSTC command.",
922 "MODT <id> on | off | <settings>\n"
923 " Modify a timer. Settings must be in the same format as returned\n"
924 " by the LSTT command. The special keywords 'on' and 'off' can be\n"
925 " used to easily activate or deactivate a timer.",
926 "MOVC <number> <to>\n"
927 " Move a channel to a new position.",
928 "MOVR <id> <new name>\n"
929 " Move the recording with the given id. Before a recording can be\n"
930 " moved, an LSTR command should have been executed in order to retrieve\n"
931 " the recording ids. The ids don't change during subsequent MOVR\n"
934 " Create a new channel. Settings must be in the same format as returned\n"
935 " by the LSTC command.",
937 " Create a new timer. Settings must be in the same format as returned\n"
938 " by the LSTT command.",
939 "NEXT [ abs | rel ]\n"
940 " Show the next timer event. If no option is given, the output will be\n"
941 " in human readable form. With option 'abs' the absolute time of the next\n"
942 " event will be given as the number of seconds since the epoch (time_t\n"
943 " format), while with option 'rel' the relative time will be given as the\n"
944 " number of seconds from now until the event. If the absolute time given\n"
945 " is smaller than the current time, or if the relative time is less than\n"
946 " zero, this means that the timer is currently recording and has started\n"
947 " at the given time. The first value in the resulting line is the id\n"
950 " Used by peer-to-peer connections between VDRs to keep the connection\n"
951 " from timing out. May be used at any time and simply returns a line of\n"
952 " the form '<hostname> is alive'.",
953 "PLAY <id> [ begin | <position> ]\n"
954 " Play the recording with the given id. Before a recording can be\n"
955 " played, an LSTR command should have been executed in order to retrieve\n"
956 " the recording ids.\n"
957 " The keyword 'begin' plays the recording from its very beginning, while\n"
958 " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n"
959 " position. If neither 'begin' nor a <position> are given, replay is resumed\n"
960 " at the position where any previous replay was stopped, or from the beginning\n"
961 " by default. To control or stop the replay session, use the usual remote\n"
962 " control keypresses via the HITK command.",
963 "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n"
964 " Send a command to a plugin.\n"
965 " The PLUG command without any parameters lists all plugins.\n"
966 " If only a name is given, all commands known to that plugin are listed.\n"
967 " If a command is given (optionally followed by parameters), that command\n"
968 " is sent to the plugin, and the result will be displayed.\n"
969 " The keyword 'help' lists all the SVDRP commands known to the named plugin.\n"
970 " If 'help' is followed by a command, the detailed help for that command is\n"
971 " given. The keyword 'main' initiates a call to the main menu function of the\n"
973 "POLL <name> timers\n"
974 " Used by peer-to-peer connections between VDRs to inform other machines\n"
975 " about changes to timers. The receiving VDR shall use LSTT to query the\n"
976 " remote machine with the given name about its timers and update its list\n"
977 " of timers accordingly.\n",
978 "PRIM [ <number> ]\n"
979 " Make the device with the given number the primary device.\n"
980 " Without option it returns the currently active primary device in the same\n"
981 " format as used by the LSTD command.",
983 " Put data into the EPG list. The data entered has to strictly follow the\n"
984 " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n"
985 " by itself terminates the input and starts processing of the data (all\n"
986 " entered data is buffered until the terminating '.' is seen).\n"
987 " If a file name is given, epg data will be read from this file (which\n"
988 " must be accessible under the given name from the machine VDR is running\n"
989 " on). In case of file input, no terminating '.' shall be given.\n",
990 "REMO [ on | off ]\n"
991 " Turns the remote control on or off. Without a parameter, the current\n"
992 " status of the remote control is reported.",
994 " Forces an EPG scan. If this is a single DVB device system, the scan\n"
995 " will be done on the primary device unless it is currently recording.",
997 " Return information about disk usage (total, free, percent).",
999 " Updates a timer. Settings must be in the same format as returned\n"
1000 " by the LSTT command. If a timer with the same channel, day, start\n"
1001 " and stop time does not yet exist, it will be created.",
1003 " Initiates a re-read of the recordings directory, which is the SVDRP\n"
1004 " equivalent to 'touch .update'.",
1005 "VOLU [ <number> | + | - | mute ]\n"
1006 " Set the audio volume to the given number (which is limited to the range\n"
1007 " 0...255). If the special options '+' or '-' are given, the volume will\n"
1008 " be turned up or down, respectively. The option 'mute' will toggle the\n"
1009 " audio muting. If no option is given, the current audio volume level will\n"
1012 " Exit vdr (SVDRP).\n"
1013 " You can also hit Ctrl-D to exit.",
1041 const char *q = HelpPage;
1044 uint n = q - HelpPage;
1045 if (n >=
sizeof(topic))
1046 n =
sizeof(topic) - 1;
1047 strncpy(topic, HelpPage, n);
1061 if (strcasecmp(Cmd, t) == 0)
1082 void Close(
bool SendReply =
false,
bool Timeout =
false);
1083 bool Send(
const char *s);
1086 void CmdAUDI(const
char *Option);
1087 void CmdCHAN(const
char *Option);
1088 void CmdCLRE(const
char *Option);
1089 void CmdCONN(const
char *Option);
1090 void CmdCPYR(const
char *Option);
1091 void CmdDELC(const
char *Option);
1092 void CmdDELR(const
char *Option);
1093 void CmdDELT(const
char *Option);
1094 void CmdEDIT(const
char *Option);
1095 void CmdGRAB(const
char *Option);
1096 void CmdHELP(const
char *Option);
1097 void CmdHITK(const
char *Option);
1098 void CmdLSTC(const
char *Option);
1099 void CmdLSTD(const
char *Option);
1100 void CmdLSTE(const
char *Option);
1101 void CmdLSTR(const
char *Option);
1102 void CmdLSTT(const
char *Option);
1103 void CmdMESG(const
char *Option);
1104 void CmdMODC(const
char *Option);
1105 void CmdMODT(const
char *Option);
1106 void CmdMOVC(const
char *Option);
1107 void CmdMOVR(const
char *Option);
1108 void CmdNEWC(const
char *Option);
1109 void CmdNEWT(const
char *Option);
1110 void CmdNEXT(const
char *Option);
1111 void CmdPING(const
char *Option);
1112 void CmdPLAY(const
char *Option);
1113 void CmdPLUG(const
char *Option);
1114 void CmdPOLL(const
char *Option);
1115 void CmdPRIM(const
char *Option);
1116 void CmdPUTE(const
char *Option);
1117 void CmdREMO(const
char *Option);
1118 void CmdSCAN(const
char *Option);
1119 void CmdSTAT(const
char *Option);
1120 void CmdUPDT(const
char *Option);
1121 void CmdUPDR(const
char *Option);
1122 void CmdVOLU(const
char *Option);
1145 time_t now = time(NULL);
1161 if (
file.IsOpen()) {
1163 Reply(221,
"%s closing connection%s",
Setup.SVDRPHostName, Timeout ?
" (timeout)" :
"");
1186 if (
file.IsOpen()) {
1188 char *buffer = NULL;
1191 if (vasprintf(&buffer, fmt, ap) >= 0) {
1194 char *n = strchr(s,
'\n');
1198 if (Code < 0 || n && *(n + 1))
1202 s = n ? n + 1 : NULL;
1206 Reply(451,
"Bad format - looks like a programming error!");
1213 Reply(451,
"Zero return code - looks like a programming error!");
1229 const int TopicsPerLine = 5;
1231 for (
int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
1234 q += sprintf(q,
" ");
1235 for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
1236 const char *topic =
GetHelpTopic(hp[(y * TopicsPerLine + x)]);
1241 Reply(-214,
"%s", buffer);
1249 int o = strtol(Option, NULL, 10);
1252 if (TrackId && TrackId->
id) {
1257 Reply(501,
"Audio track \"%s\" not available", Option);
1260 Reply(501,
"Invalid audio track \"%s\"", Option);
1263 Reply(501,
"Error in audio track \"%s\"", Option);
1271 if (TrackId && TrackId->
id) {
1273 Reply(-250,
"%s", *s);
1278 Reply(250,
"%s", *s);
1280 Reply(550,
"No audio tracks available");
1291 int o = strtol(Option, NULL, 10);
1295 else if (strcmp(Option,
"-") == 0) {
1302 else if (strcmp(Option,
"+") == 0) {
1310 n = Channel->Number();
1312 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1313 if (!Channel->GroupSep()) {
1314 if (strcasecmp(Channel->Name(), Option) == 0) {
1315 n = Channel->Number();
1322 Reply(501,
"Undefined channel \"%s\"", Option);
1326 if (
const cChannel *Channel = Channels->GetByNumber(n)) {
1328 Reply(554,
"Error switching to channel \"%d\"", Channel->Number());
1333 Reply(550,
"Unable to find channel \"%s\"", Option);
1341 Reply(250,
"%d %s", Channel->Number(), Channel->Name());
1353 int o = strtol(Option, NULL, 10);
1355 if (
const cChannel *Channel = Channels->GetByNumber(o))
1356 ChannelID = Channel->GetChannelID();
1362 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1363 if (!Channel->GroupSep()) {
1364 if (strcasecmp(Channel->Name(), Option) == 0) {
1365 ChannelID = Channel->GetChannelID();
1376 for (
cSchedule *p = Schedules->First(); p; p = Schedules->
Next(p)) {
1377 if (p->ChannelID() == ChannelID) {
1383 for (
cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
1384 if (ChannelID == Timer->Channel()->GetChannelID().
ClrRid())
1385 Timer->SetEvent(NULL);
1389 Reply(250,
"EPG data of channel \"%s\" cleared", Option);
1392 Reply(550,
"No EPG data found for channel \"%s\"", Option);
1397 Reply(501,
"Undefined channel \"%s\"", Option);
1402 for (
cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer))
1403 Timer->SetEvent(NULL);
1404 for (
cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->
Next(Schedule))
1405 Schedule->Cleanup(INT_MAX);
1407 Reply(250,
"EPG data cleared");
1416 if (ServerParams.
Ok()) {
1422 Reply(501,
"Error in server parameters: %s", ServerParams.
Error());
1425 Reply(451,
"No SVDRP client handler");
1428 Reply(501,
"Missing server parameters");
1436 Channels->SetExplicitModify();
1439 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1443 if (
const cTimer *Timer = Timers->UsesChannel(Channel)) {
1444 Reply(550,
"Channel \"%s\" is in use by timer %s", Option, *Timer->ToDescr());
1448 cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
1449 if (CurrentChannel && Channel == CurrentChannel) {
1450 int n = Channels->GetNextNormal(CurrentChannel->
Index());
1452 n = Channels->GetPrevNormal(CurrentChannel->
Index());
1454 Reply(501,
"Can't delete channel \"%s\" - list would be empty", Option);
1457 CurrentChannel = Channels->Get(n);
1458 CurrentChannelNr = 0;
1460 Channels->Del(Channel);
1461 Channels->ReNumber();
1462 Channels->SetModifiedByUser();
1463 Channels->SetModified();
1465 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
1467 Channels->SwitchTo(CurrentChannel->
Number());
1471 Reply(250,
"Channel \"%s\" deleted", Option);
1474 Reply(501,
"Channel \"%s\" not defined", Option);
1477 Reply(501,
"Missing channel number or id");
1486 return cString::sprintf(
"Recording \"%s\" is being replayed", RecordingId);
1487 else if ((Reason &
ruCut) != 0)
1490 return cString::sprintf(
"Recording \"%s\" is being copied/moved", RecordingId);
1499 char *opt = strdup(Option);
1502 while (*option && !isspace(*option))
1508 Recordings->SetExplicitModify();
1509 if (
cRecording *Recording = Recordings->Get(strtol(num, NULL, 10) - 1)) {
1510 if (
int RecordingInUse = Recording->IsInUse())
1518 if (strcmp(newName, Recording->Name())) {
1523 Recordings->AddByName(fileName);
1524 Reply(250,
"Recording \"%s\" copied to \"%s\"", Recording->Name(), *newName);
1527 Reply(554,
"Error while copying recording \"%s\" to \"%s\"!", Recording->Name(), *newName);
1530 Reply(501,
"Identical new recording name");
1533 Reply(501,
"Missing new recording name");
1537 Reply(550,
"Recording \"%s\" not found", num);
1540 Reply(501,
"Error in recording number \"%s\"", num);
1544 Reply(501,
"Missing recording number");
1552 Recordings->SetExplicitModify();
1553 if (
cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1554 if (
int RecordingInUse = Recording->IsInUse())
1557 if (Recording->Delete()) {
1558 Recordings->DelByName(Recording->FileName());
1559 Recordings->SetModified();
1561 Reply(250,
"Recording \"%s\" deleted", Option);
1564 Reply(554,
"Error while deleting recording!");
1568 Reply(550,
"Recording \"%s\" not found", Option);
1571 Reply(501,
"Error in recording id \"%s\"", Option);
1574 Reply(501,
"Missing recording id");
1582 Timers->SetExplicitModify();
1583 if (
cTimer *Timer = Timers->GetById(strtol(Option, NULL, 10))) {
1584 if (Timer->Recording()) {
1588 Timer->TriggerRespawn();
1590 Timers->SetModified();
1592 Reply(250,
"Timer \"%s\" deleted", Option);
1595 Reply(501,
"Timer \"%s\" not defined", Option);
1598 Reply(501,
"Error in timer number \"%s\"", Option);
1601 Reply(501,
"Missing timer number");
1609 if (
const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1611 if (Marks.
Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.
Count()) {
1613 Reply(550,
"Not enough free disk space to start editing process");
1615 Reply(250,
"Editing recording \"%s\" [%s]", Option, Recording->Title());
1617 Reply(554,
"Can't start editing process");
1620 Reply(554,
"No editing marks defined");
1623 Reply(550,
"Recording \"%s\" not found", Option);
1626 Reply(501,
"Error in recording id \"%s\"", Option);
1629 Reply(501,
"Missing recording id");
1634 const char *FileName = NULL;
1636 int Quality = -1, SizeX = -1, SizeY = -1;
1638 char buf[strlen(Option) + 1];
1639 char *p = strcpy(buf, Option);
1640 const char *delim =
" \t";
1642 FileName = strtok_r(p, delim, &strtok_next);
1644 const char *Extension = strrchr(FileName,
'.');
1646 if (strcasecmp(Extension,
".jpg") == 0 || strcasecmp(Extension,
".jpeg") == 0)
1648 else if (strcasecmp(Extension,
".pnm") == 0)
1651 Reply(501,
"Unknown image type \"%s\"", Extension + 1);
1654 if (Extension == FileName)
1657 else if (strcmp(FileName,
"-") == 0)
1660 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1661 if (strcasecmp(p,
"JPEG") == 0 || strcasecmp(p,
"PNM") == 0) {
1663 p = strtok_r(NULL, delim, &strtok_next);
1669 Reply(501,
"Invalid quality \"%s\"", p);
1675 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1679 Reply(501,
"Invalid sizex \"%s\"", p);
1682 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1686 Reply(501,
"Invalid sizey \"%s\"", p);
1691 Reply(501,
"Missing sizey");
1695 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1696 Reply(501,
"Unexpected parameter \"%s\"", p);
1700 char RealFileName[PATH_MAX];
1705 const char *slash = strrchr(FileName,
'/');
1710 slash = strrchr(FileName,
'/');
1713 char *r = realpath(t, RealFileName);
1716 Reply(501,
"Invalid file name \"%s\"", FileName);
1719 strcat(RealFileName, slash);
1720 FileName = RealFileName;
1722 Reply(501,
"Invalid file name \"%s\"", FileName);
1727 Reply(550,
"Grabbing to file not allowed (use \"GRAB -\" instead)");
1736 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
1738 if (
safe_write(fd, Image, ImageSize) == ImageSize) {
1740 Reply(250,
"Grabbed image %s", Option);
1744 Reply(451,
"Can't write to '%s'", FileName);
1750 Reply(451,
"Can't open '%s'", FileName);
1756 while ((s = Base64.
NextLine()) != NULL)
1757 Reply(-216,
"%s", s);
1758 Reply(216,
"Grabbed image %s", Option);
1763 Reply(451,
"Grab image failed");
1766 Reply(501,
"Missing filename");
1774 Reply(-214,
"%s", hp);
1776 Reply(504,
"HELP topic \"%s\" unknown", Option);
1782 Reply(-214,
"Topics:");
1791 Reply(-214,
"To report bugs in the implementation send email to");
1792 Reply(-214,
" vdr-bugs@tvdr.de");
1794 Reply(214,
"End of HELP info");
1801 Reply(550,
"Remote control currently disabled (key \"%s\" discarded)", Option);
1804 char buf[strlen(Option) + 1];
1805 strcpy(buf, Option);
1806 const char *delim =
" \t";
1808 char *p = strtok_r(buf, delim, &strtok_next);
1814 Reply(451,
"Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
1819 Reply(504,
"Unknown key: \"%s\"", p);
1823 p = strtok_r(NULL, delim, &strtok_next);
1825 Reply(250,
"Key%s \"%s\" accepted", NumKeys > 1 ?
"s" :
"", Option);
1828 Reply(-214,
"Valid <key> names for the HITK command:");
1829 for (
int i = 0; i <
kNone; i++) {
1832 Reply(214,
"End of key list");
1839 bool WithChannelIds =
startswith(Option,
":ids") && (Option[4] ==
' ' || Option[4] == 0);
1842 bool WithGroupSeps = strcasecmp(Option,
":groups") == 0;
1843 if (*Option && !WithGroupSeps) {
1845 int n = strtol(Option, NULL, 10);
1848 if (
const cChannel *Channel = Channels->GetByNumber(n))
1849 Reply(250,
"%d%s%s %s", Channel->Number(), WithChannelIds ?
" " :
"", WithChannelIds ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1851 Reply(501,
"Channel \"%s\" not defined", Option);
1856 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1857 if (!Channel->GroupSep()) {
1858 if (strcasestr(Channel->Name(), Option)) {
1869 Reply(501,
"Channel \"%s\" not defined", Option);
1873 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1875 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());
1876 else if (!Channel->GroupSep())
1877 Reply(Channel->Number() <
cChannels::MaxNumber() ? -250 : 250,
"%d%s%s %s", Channel->Number(), WithChannelIds ?
" " :
"", WithChannelIds ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1881 Reply(550,
"No channels defined");
1889 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());
1893 Reply(550,
"No devices found");
1904 char buf[strlen(Option) + 1];
1905 strcpy(buf, Option);
1906 const char *delim =
" \t";
1908 char *p = strtok_r(buf, delim, &strtok_next);
1909 while (p && DumpMode ==
dmAll) {
1910 if (strcasecmp(p,
"NOW") == 0)
1912 else if (strcasecmp(p,
"NEXT") == 0)
1914 else if (strcasecmp(p,
"AT") == 0) {
1916 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1918 AtTime = strtol(p, NULL, 10);
1920 Reply(501,
"Invalid time");
1925 Reply(501,
"Missing time");
1929 else if (!Schedule) {
1932 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1936 Schedule = Schedules->GetSchedule(Channel);
1938 Reply(550,
"No schedule found");
1943 Reply(550,
"Channel \"%s\" not defined", p);
1948 Reply(501,
"Unknown option: \"%s\"", p);
1951 p = strtok_r(NULL, delim, &strtok_next);
1956 FILE *f = fdopen(fd,
"w");
1959 Schedule->
Dump(Channels, f,
"215-", DumpMode, AtTime);
1961 Schedules->Dump(f,
"215-", DumpMode, AtTime);
1963 Reply(215,
"End of EPG data");
1967 Reply(451,
"Can't open file connection");
1972 Reply(451,
"Can't dup stream descriptor");
1981 char buf[strlen(Option) + 1];
1982 strcpy(buf, Option);
1983 const char *delim =
" \t";
1985 char *p = strtok_r(buf, delim, &strtok_next);
1989 Number = strtol(p, NULL, 10);
1991 Reply(501,
"Error in recording id \"%s\"", Option);
1995 else if (strcasecmp(p,
"PATH") == 0)
1998 Reply(501,
"Unknown option: \"%s\"", p);
2001 p = strtok_r(NULL, delim, &strtok_next);
2004 if (
const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
2005 FILE *f = fdopen(
file,
"w");
2008 Reply(250,
"%s", Recording->FileName());
2010 Recording->Info()->Write(f,
"215-");
2012 Reply(215,
"End of recording information");
2017 Reply(451,
"Can't open file connection");
2020 Reply(550,
"Recording \"%s\" not found", Option);
2023 else if (Recordings->Count()) {
2024 const cRecording *Recording = Recordings->First();
2026 Reply(Recording == Recordings->Last() ? 250 : -250,
"%d %s", Recording->
Id(), Recording->
Title(
' ',
true));
2027 Recording = Recordings->
Next(Recording);
2031 Reply(550,
"No recordings available");
2037 bool UseChannelId =
false;
2039 char buf[strlen(Option) + 1];
2040 strcpy(buf, Option);
2041 const char *delim =
" \t";
2043 char *p = strtok_r(buf, delim, &strtok_next);
2046 Id = strtol(p, NULL, 10);
2047 else if (strcasecmp(p,
"ID") == 0)
2048 UseChannelId =
true;
2050 Reply(501,
"Unknown option: \"%s\"", p);
2053 p = strtok_r(NULL, delim, &strtok_next);
2058 for (
const cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
2059 if (!Timer->Remote()) {
2060 if (Timer->Id() == Id) {
2061 Reply(250,
"%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2066 Reply(501,
"Timer \"%s\" not defined", Option);
2070 const cTimer *LastLocalTimer = Timers->Last();
2071 while (LastLocalTimer) {
2072 if (LastLocalTimer->
Remote())
2073 LastLocalTimer = Timers->
Prev(LastLocalTimer);
2077 if (LastLocalTimer) {
2078 for (
const cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
2079 if (!Timer->Remote())
2080 Reply(Timer != LastLocalTimer ? -250 : 250,
"%d %s", Timer->
Id(), *Timer->ToText(UseChannelId));
2081 if (Timer == LastLocalTimer)
2087 Reply(550,
"No timers defined");
2095 Reply(250,
"Message queued");
2098 Reply(501,
"Missing message");
2105 int n = strtol(Option, &tail, 10);
2106 if (tail && tail != Option) {
2109 Channels->SetExplicitModify();
2110 if (
cChannel *Channel = Channels->GetByNumber(n)) {
2112 if (ch.
Parse(tail)) {
2113 if (Channels->HasUniqueChannelID(&ch, Channel)) {
2115 Channels->ReNumber();
2116 Channels->SetModifiedByUser();
2117 Channels->SetModified();
2118 isyslog(
"SVDRP %s < %s modified channel %d %s",
Setup.SVDRPHostName, *
clientName, Channel->Number(), *Channel->ToText());
2119 Reply(250,
"%d %s", Channel->Number(), *Channel->ToText());
2122 Reply(501,
"Channel settings are not unique");
2125 Reply(501,
"Error in channel settings");
2128 Reply(501,
"Channel \"%d\" not defined", n);
2131 Reply(501,
"Error in channel number");
2134 Reply(501,
"Missing channel settings");
2141 int Id = strtol(Option, &tail, 10);
2142 if (tail && tail != Option) {
2145 Timers->SetExplicitModify();
2146 if (
cTimer *Timer = Timers->GetById(Id)) {
2149 if (strcasecmp(tail,
"ON") == 0)
2151 else if (strcasecmp(tail,
"OFF") == 0)
2153 else if (!t.
Parse(tail)) {
2154 Reply(501,
"Error in timer settings");
2158 Reply(550,
"Timer is recording");
2166 Timers->SetModified();
2167 isyslog(
"SVDRP %s < %s modified timer %s (%s)",
Setup.SVDRPHostName, *
clientName, *Timer->ToDescr(), Timer->HasFlags(
tfActive) ?
"active" :
"inactive");
2168 if (Timer->IsPatternTimer())
2169 Timer->SetEvent(NULL);
2170 Timer->TriggerRespawn();
2171 Reply(250,
"%d %s", Timer->Id(), *Timer->ToText(
true));
2174 Reply(501,
"Timer \"%d\" not defined", Id);
2177 Reply(501,
"Error in timer id");
2180 Reply(501,
"Missing timer settings");
2187 int From = strtol(Option, &tail, 10);
2188 if (tail && tail != Option) {
2190 if (tail && tail != Option) {
2193 Channels->SetExplicitModify();
2194 int To = strtol(tail, NULL, 10);
2196 const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
2197 cChannel *FromChannel = Channels->GetByNumber(From);
2199 cChannel *ToChannel = Channels->GetByNumber(To);
2201 int FromNumber = FromChannel->
Number();
2202 int ToNumber = ToChannel->
Number();
2203 if (FromNumber != ToNumber) {
2204 if (Channels->MoveNeedsDecrement(FromChannel, ToChannel))
2205 ToChannel = Channels->
Prev(ToChannel);
2206 Channels->Move(FromChannel, ToChannel);
2207 Channels->ReNumber();
2208 Channels->SetModifiedByUser();
2209 Channels->SetModified();
2210 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
2212 Channels->SwitchTo(CurrentChannel->
Number());
2217 Reply(250,
"Channel \"%d\" moved to \"%d\"", From, To);
2220 Reply(501,
"Can't move channel to same position");
2223 Reply(501,
"Channel \"%d\" not defined", To);
2226 Reply(501,
"Channel \"%d\" not defined", From);
2229 Reply(501,
"Error in channel number");
2232 Reply(501,
"Error in channel number");
2235 Reply(501,
"Missing channel number");
2241 char *opt = strdup(Option);
2244 while (*option && !isspace(*option))
2250 Recordings->SetExplicitModify();
2251 if (
cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2252 if (
int RecordingInUse = Recording->IsInUse())
2258 cString oldName = Recording->Name();
2259 if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) {
2260 Recordings->SetModified();
2261 Recordings->TouchUpdate();
2262 Reply(250,
"Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name());
2265 Reply(554,
"Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
2268 Reply(501,
"Missing new recording name");
2272 Reply(550,
"Recording \"%s\" not found", num);
2275 Reply(501,
"Error in recording id \"%s\"", num);
2279 Reply(501,
"Missing recording id");
2286 if (ch.
Parse(Option)) {
2288 Channels->SetExplicitModify();
2289 if (Channels->HasUniqueChannelID(&ch)) {
2292 Channels->Add(channel);
2293 Channels->ReNumber();
2294 Channels->SetModifiedByUser();
2295 Channels->SetModified();
2300 Reply(501,
"Channel settings are not unique");
2303 Reply(501,
"Error in channel settings");
2306 Reply(501,
"Missing channel settings");
2313 if (Timer->
Parse(Option)) {
2322 Reply(501,
"Error in timer settings");
2326 Reply(501,
"Missing timer settings");
2332 if (
const cTimer *t = Timers->GetNextActiveTimer()) {
2333 time_t Start = t->StartTime();
2337 else if (strcasecmp(Option,
"ABS") == 0)
2338 Reply(250,
"%d %jd", Id, intmax_t(Start));
2339 else if (strcasecmp(Option,
"REL") == 0)
2340 Reply(250,
"%d %jd", Id, intmax_t(Start - time(NULL)));
2342 Reply(501,
"Unknown option: \"%s\"", Option);
2345 Reply(550,
"No active timers");
2350 Reply(250,
"%s is alive",
Setup.SVDRPHostName);
2356 char *opt = strdup(Option);
2359 while (*option && !isspace(*option))
2366 if (
const cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2367 cString FileName = Recording->FileName();
2368 cString Title = Recording->Title();
2369 int FramesPerSecond = Recording->FramesPerSecond();
2370 bool IsPesRecording = Recording->IsPesRecording();
2378 if (strcasecmp(option,
"BEGIN") != 0)
2389 Reply(250,
"Playing recording \"%s\" [%s]", num, *Title);
2393 Reply(550,
"Recording \"%s\" not found", num);
2398 Reply(501,
"Error in recording id \"%s\"", num);
2402 Reply(501,
"Missing recording id");
2408 char *opt = strdup(Option);
2410 char *option = name;
2411 while (*option && !isspace(*option))
2420 while (*option && !isspace(*option))
2426 if (!*cmd || strcasecmp(cmd,
"HELP") == 0) {
2427 if (*cmd && *option) {
2430 Reply(-214,
"%s", hp);
2431 Reply(214,
"End of HELP info");
2434 Reply(504,
"HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->
Name());
2440 Reply(-214,
"SVDRP commands:");
2442 Reply(214,
"End of HELP info");
2445 Reply(214,
"This plugin has no SVDRP commands");
2448 else if (strcasecmp(cmd,
"MAIN") == 0) {
2450 Reply(250,
"Initiated call to main menu function of plugin \"%s\"", plugin->
Name());
2452 Reply(550,
"A plugin call is already pending - please try again later");
2455 int ReplyCode = 900;
2458 Reply(abs(ReplyCode),
"%s", *s);
2460 Reply(500,
"Command unrecognized: \"%s\"", cmd);
2464 Reply(550,
"Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
2468 Reply(-214,
"Available plugins:");
2472 Reply(214,
"End of plugin list");
2479 char buf[strlen(Option) + 1];
2480 char *p = strcpy(buf, Option);
2481 const char *delim =
" \t";
2483 char *RemoteName = strtok_r(p, delim, &strtok_next);
2484 char *ListName = strtok_r(NULL, delim, &strtok_next);
2487 if (strcasecmp(ListName,
"timers") == 0) {
2492 Reply(501,
"Unknown list name: \"%s\"", ListName);
2495 Reply(501,
"Missing list name");
2498 Reply(501,
"No SVDRP client connections");
2501 Reply(501,
"Missing parameters");
2509 int o = strtol(Option, NULL, 10);
2513 Reply(501,
"Invalid device number \"%s\"", Option);
2516 Reply(501,
"Invalid parameter \"%s\"", Option);
2518 Setup.PrimaryDVB = n;
2519 Reply(250,
"Primary device set to %d", n);
2524 Reply(250,
"%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ?
"D" :
"-", d->DeviceNumber() + 1 ==
Setup.PrimaryDVB ?
"P" :
"-", *d->DeviceName());
2526 Reply(501,
"Failed to get primary device");
2533 FILE *f = fopen(Option,
"r");
2537 Reply(250,
"EPG data processed from \"%s\"", Option);
2540 Reply(451,
"Error while processing EPG from \"%s\"", Option);
2544 Reply(501,
"Cannot open file \"%s\"", Option);
2558 if (!strcasecmp(Option,
"ON")) {
2560 Reply(250,
"Remote control enabled");
2562 else if (!strcasecmp(Option,
"OFF")) {
2564 Reply(250,
"Remote control disabled");
2567 Reply(501,
"Invalid Option \"%s\"", Option);
2576 Reply(250,
"EPG scan triggered");
2582 if (strcasecmp(Option,
"DISK") == 0) {
2585 Reply(250,
"%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
2588 Reply(501,
"Invalid Option \"%s\"", Option);
2591 Reply(501,
"No option given");
2598 if (Timer->
Parse(Option)) {
2600 if (
cTimer *t = Timers->GetTimer(Timer)) {
2620 Reply(501,
"Error in timer settings");
2624 Reply(501,
"Missing timer settings");
2630 Recordings->Update(
false);
2631 Reply(250,
"Re-read of recordings directory triggered");
2639 else if (strcmp(Option,
"+") == 0)
2641 else if (strcmp(Option,
"-") == 0)
2643 else if (strcasecmp(Option,
"MUTE") == 0)
2646 Reply(501,
"Unknown option: \"%s\"", Option);
2651 Reply(250,
"Audio is mute");
2656#define CMD(c) (strcasecmp(Cmd, c) == 0)
2673 while (*s && !isspace(*s))
2715 else Reply(500,
"Command unrecognized: \"%s\"", Cmd);
2720 if (
file.IsOpen()) {
2721 while (
file.Ready(
false)) {
2725 if (c ==
'\n' || c == 0x00) {
2741 else if (c == 0x04 &&
numChars == 0) {
2745 else if (c == 0x08 || c == 0x7F) {
2750 else if (c <= 0x03 || c == 0x0D) {
2755 int NewLength =
length + BUFSIZ;
2756 if (
char *NewBuffer = (
char *)realloc(
cmdLine, NewLength)) {
2781 return file.IsOpen();
2805 virtual void Action(
void);
2815:
cThread(
"SVDRP server handler", true)
2899 bool Result =
false;
2908 bool Result =
false;
2921 for (
int i = 0; i < ServerNames.
Size(); i++)
#define LOCK_CHANNELS_READ
#define LOCK_CHANNELS_WRITE
const char * NextLine(void)
Returns the next line of encoded data (terminated by '\0'), or NULL if there is no more encoded data.
bool Parse(const char *s)
static cString ToText(const cChannel *Channel)
tChannelID GetChannelID(void) const
static int MaxNumber(void)
static const char * SystemCharacterTable(void)
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
static void Shutdown(void)
static void Launch(cControl *Control)
virtual uchar * GrabImage(int &Size, bool Jpeg=true, int Quality=-1, int SizeX=-1, int SizeY=-1)
Grabs the currently visible screen image.
static cDevice * PrimaryDevice(void)
Returns the primary device.
static cDevice * GetDevice(int Index)
Gets the device with the given Index.
eTrackType GetCurrentAudioTrack(void) const
bool SwitchChannel(const cChannel *Channel, bool LiveView)
Switches the device to the given Channel, initiating transfer mode if necessary.
static int CurrentChannel(void)
Returns the number of the current channel on the primary device.
const tTrackId * GetTrack(eTrackType Type)
Returns a pointer to the given track id, or NULL if Type is not less than ttMaxTrackTypes.
static void SetCurrentChannel(int ChannelNumber)
Sets the number of the current channel on the primary device, without actually switching to it.
void SetVolume(int Volume, bool Absolute=false)
Sets the volume to the given value, either absolutely or relative to the current volume.
static int NumDevices(void)
Returns the total number of devices.
static int CurrentVolume(void)
bool ToggleMute(void)
Turns the volume off or on and returns the new mute state.
bool SetCurrentAudioTrack(eTrackType Type)
Sets the current audio track to the given Type.
static void SetDisableUntil(time_t Time)
const char * Connection(void) const
const char * Address(void) const
void Set(const char *Address, int Port)
static const char * ToString(eKeys Key, bool Translate=false)
static eKeys FromString(const char *Name)
cListObject * Prev(void) const
cListObject * Next(void) const
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
bool Process(const char *s)
const char * Message(void)
static cPlugin * GetPlugin(int Index)
virtual const char * Version(void)=0
virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
virtual const char * Description(void)=0
virtual const char ** SVDRPHelpPages(void)
static bool Process(cTimers *Timers, time_t t)
static cRecordControl * GetRecordControl(const char *FileName)
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
const char * Title(char Delimiter=' ', bool NewIndicator=false, int Level=-1) const
static const cRecordings * GetRecordingsRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of recordings for read access.
bool Put(uint64_t Code, bool Repeat=false, bool Release=false)
static bool Enabled(void)
static bool CallPlugin(const char *Plugin)
Initiates calling the given plugin's main menu function.
static void SetEnabled(bool Enabled)
static void SetRecording(const char *FileName)
bool Execute(const char *ServerName, const char *Command, cStringList *Response=NULL)
void AddClient(cSVDRPServerParams &ServerParams, const char *IpAddress)
virtual ~cSVDRPClientHandler()
void ProcessConnections(void)
bool GetServerNames(cStringList *ServerNames)
cSVDRPClientHandler(int TcpPort, int UdpPort)
void HandleClientConnection(void)
cSVDRPClient * GetClientForServer(const char *ServerName)
cVector< cSVDRPClient * > clientConnections
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
bool TriggerFetchingTimers(const char *ServerName)
cIpAddress serverIpAddress
bool Connected(void) const
bool Execute(const char *Command, cStringList *Response=NULL)
bool HasAddress(const char *Address, int Port) const
const char * ServerName(void) const
bool Send(const char *Command)
cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout)
bool GetRemoteTimers(cStringList &Response)
bool Process(cStringList *Response=NULL)
void SetFetchFlag(int Flag)
const char * Connection(void) const
bool HasFetchFlag(int Flag)
void HandleServerConnection(void)
void ProcessConnections(void)
cSVDRPServerHandler(int TcpPort)
void WaitUntilReady(void)
virtual ~cSVDRPServerHandler()
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
cVector< cSVDRPServer * > serverConnections
const char * Host(void) const
const int Timeout(void) const
const char * ApiVersion(void) const
cSVDRPServerParams(const char *Params)
const char * VdrVersion(void) const
const char * Name(void) const
const char * Error(void) const
const int Port(void) const
void CmdMESG(const char *Option)
const char * ClientName(void) const
void CmdPOLL(const char *Option)
void CmdLSTT(const char *Option)
void CmdCLRE(const char *Option)
void Reply(int Code, const char *fmt,...) __attribute__((format(printf
void CmdGRAB(const char *Option)
void CmdMODC(const char *Option)
cPUTEhandler * PUTEhandler
void CmdDELC(const char *Option)
void CmdPLUG(const char *Option)
void CmdMODT(const char *Option)
cIpAddress clientIpAddress
void CmdCPYR(const char *Option)
void CmdLSTC(const char *Option)
void CmdSCAN(const char *Option)
void Close(bool SendReply=false, bool Timeout=false)
void CmdPUTE(const char *Option)
void CmdLSTR(const char *Option)
void CmdSTAT(const char *Option)
void CmdCHAN(const char *Option)
void CmdHELP(const char *Option)
void CmdUPDT(const char *Option)
void CmdREMO(const char *Option)
void CmdAUDI(const char *Option)
void CmdLSTE(const char *Option)
void CmdCONN(const char *Option)
void CmdDELR(const char *Option)
void CmdUPDR(const char *Option)
void CmdVOLU(const char *Option)
void CmdNEWT(const char *Option)
void CmdEDIT(const char *Option)
void CmdPLAY(const char *Option)
void CmdDELT(const char *Option)
void CmdLSTD(const char *Option)
cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress)
void CmdNEXT(const char *Option)
void CmdHITK(const char *Option)
void CmdNEWC(const char *Option)
void CmdPRIM(const char *Option)
void CmdMOVR(const char *Option)
void CmdPING(const char *Option)
void CmdMOVC(const char *Option)
void void PrintHelpTopics(const char **hp)
void Cleanup(time_t Time)
void Dump(const cChannels *Channels, FILE *f, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0) const
static void Cleanup(bool Force=false)
static bool Read(FILE *f=NULL)
const cIpAddress * LastIpAddress(void) const
static bool SendDgram(const char *Dgram, int Port)
cSocket(int Port, bool Tcp)
bool Connect(const char *Address)
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
void SortNumerically(void)
cString & CompactChars(char c)
Compact any sequence of characters 'c' to a single character, and strip all of them from the beginnin...
static cString sprintf(const char *fmt,...) __attribute__((format(printf
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 Running(void)
Returns false if a derived cThread object shall leave its Action() function.
cThread(const char *Description=NULL, bool LowPriority=false)
Creates a new thread.
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting 'running' to false, so that the Action() loop can finish in an or...
void Set(int Ms=0)
Sets the timer.
bool TimedOut(void) const
void ClrFlags(uint Flags)
void SetFlags(uint Flags)
bool IsPatternTimer(void) const
cString ToDescr(void) const
const char * Remote(void) const
bool Parse(const char *s)
cString ToText(bool UseChannelID=false) const
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 cTimers * GetTimersWrite(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for write access.
static const cTimers * GetTimersRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for read access.
virtual void Append(T Data)
static int VideoDiskSpace(int *FreeMB=NULL, int *UsedMB=NULL)
#define LOCK_SCHEDULES_READ
#define LOCK_SCHEDULES_WRITE
bool EnoughFreeDiskSpaceForEdit(const char *FileName)
char * ExchangeChars(char *s, bool ToFileSystem)
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
cRecordingsHandler RecordingsHandler
struct __attribute__((packed))
#define LOCK_RECORDINGS_READ
#define LOCK_RECORDINGS_WRITE
tChannelID & ClrRid(void)
static const tChannelID InvalidID
static tChannelID FromString(const char *s)
cString ToString(void) const
char language[MAXLANGCODE2]
void StopSVDRPHandler(void)
static cPoller SVDRPClientPoller
void SetSVDRPGrabImageDir(const char *GrabImageDir)
static cString grabImageDir
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...
static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
static cMutex SVDRPHandlerMutex
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...
static cPoller SVDRPServerPoller
static cSVDRPServerHandler * SVDRPServerHandler
void StartSVDRPHandler(void)
cStateKey StateKeySVDRPRemoteTimersPoll(true)
void BroadcastSVDRPCommand(const char *Command)
Sends the given SVDRP Command string to all remote VDRs.
#define SVDRPResonseTimeout
const char * GetHelpPage(const char *Cmd, const char **p)
static cSVDRPClientHandler * SVDRPClientHandler
static bool DumpSVDRPDataTransfer
const char * GetHelpTopic(const char *HelpPage)
void SetSVDRPPorts(int TcpPort, int UdpPort)
int SVDRPCode(const char *s)
Returns the value of the three digit reply code of the given SVDRP response string.
cStateKey StateKeySVDRPRemoteTimersPoll
Controls whether a change to the local list of timers needs to result in sending a POLL to the remote...
#define LOCK_TIMERS_WRITE