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);
173 if (fcntl(
sock, F_SETFL, Flags) < 0) {
180 if (listen(
sock, 1) < 0) {
186 isyslog(
"SVDRP %s listening on port %d/%s",
Setup.SVDRPHostName,
port,
tcp ?
"tcp" :
"udp");
195 sock = socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
202 memset(&Addr, 0,
sizeof(Addr));
203 Addr.sin_family = AF_INET;
204 Addr.sin_port = htons(
port);
205 Addr.sin_addr.s_addr = inet_addr(Address);
206 if (connect(
sock, (sockaddr *)&Addr,
sizeof(Addr)) < 0) {
212 int Flags = fcntl(
sock, F_GETFL, 0);
219 if (fcntl(
sock, F_SETFL, Flags) < 0) {
224 dbgsvdrp(
"> %s:%d server connection established\n", Address,
port);
225 isyslog(
"SVDRP %s > %s:%d server connection established",
Setup.SVDRPHostName, Address,
port);
234 int Socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
241 if (setsockopt(
Socket, SOL_SOCKET, SO_BROADCAST, &One,
sizeof(One)) < 0) {
248 memset(&Addr, 0,
sizeof(Addr));
249 Addr.sin_family = AF_INET;
250 Addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
251 Addr.sin_port = htons(
Port);
253 dbgsvdrp(
"> %s:%d %s\n", inet_ntoa(Addr.sin_addr),
Port, Dgram);
254 int Length = strlen(Dgram);
255 int Sent = sendto(
Socket, Dgram, Length, 0, (sockaddr *)&Addr,
sizeof(Addr));
259 return Sent == Length;
266 uint Size =
sizeof(Addr);
267 int NewSock = accept(
sock, (sockaddr *)&Addr, &Size);
269 bool Accepted =
SVDRPhosts.Acceptable(Addr.sin_addr.s_addr);
271 const char *s =
"Access denied!\n";
272 if (write(NewSock, s, strlen(s)) < 0)
278 dbgsvdrp(
"< %s client connection %s\n",
lastIpAddress.Connection(), Accepted ?
"accepted" :
"DENIED");
279 isyslog(
"SVDRP %s < %s client connection %s",
Setup.SVDRPHostName,
lastIpAddress.Connection(), Accepted ?
"accepted" :
"DENIED");
293 uint Size =
sizeof(Addr);
294 int NumBytes = recvfrom(
sock, buf,
sizeof(buf), 0, (sockaddr *)&Addr, &Size);
298 if (!
SVDRPhosts.Acceptable(Addr.sin_addr.s_addr)) {
306 if (strcmp(
strgetval(buf,
"name",
':'),
Setup.SVDRPHostName) != 0) {
331 bool Send(
const char *Command);
338 bool HasAddress(
const char *Address,
int Port)
const;
356 timeout = Timeout * 1000 * 9 / 10;
360 if (
socket.Connect(Address)) {
406#define SVDRPResonseTimeout 5000
409 if (
file.Ready(
false)) {
413 if (c ==
'\n' || c == 0x00) {
415 while (numChars > 0 && strchr(
" \t\r\n",
input[numChars - 1]))
416 input[--numChars] = 0;
423 switch (atoi(
input)) {
424 case 220:
if (numChars > 4) {
426 if (
char *t = strchr(n,
' ')) {
443 if (numChars >= 4 &&
input[3] !=
'-')
448 if (numChars >=
length - 1) {
449 int NewLength =
length + BUFSIZ;
450 if (
char *NewBuffer = (
char *)realloc(
input, NewLength)) {
460 input[numChars++] = c;
476 else if (!Response && numChars == 0)
482 return file.IsOpen();
509 if (
Execute(
"LSTT ID", &Response)) {
510 for (
int i = 0; i < Response.
Size(); i++) {
511 char *s = Response[i];
515 else if (Code == 550)
553 if (Params && *Params) {
571 error =
"invalid timeout";
574 error =
"missing server timeout";
577 error =
"missing server apiversion";
580 error =
"missing server vdrversion";
583 error =
"missing server port";
586 error =
"missing server name";
589 error =
"missing server parameters";
607 virtual void Action(
void)
override;
613 bool Execute(
const char *ServerName,
const char *Command,
cStringList *Response = NULL);
621:
cThread(
"SVDRP client handler", true)
669 bool TimersModified = Timers->StoreRemoteTimers(Client->
ServerName(), &RemoteTimers);
676 if (*PollTimersCmd) {
677 if (!Client->
Execute(PollTimersCmd))
701 if (ServerParams.
Host() && strcmp(ServerParams.
Host(),
Setup.SVDRPHostName) != 0)
722 if (ServerParams.
Ok())
733 time_t LastDiscover = 0;
734#define SVDRPDiscoverDelta 60
736 time_t Now = time(NULL);
755 return Client->Execute(Command, Response);
762 ServerNames->
Clear();
768 return ServerNames->
Size() > 0;
798 if ((
f = tmpfile()) != NULL) {
800 message =
"Enter EPG data, end with \".\" on a line by itself";
805 message =
"Error while opening temporary file";
818 if (strcmp(s,
".") != 0) {
828 message =
"EPG data processed";
832 message =
"Error while processing EPG data";
843#define MAXHELPTOPIC 10
844#define EITDISABLETIME 10
848 "AUDI [ <number> ]\n"
849 " Lists the currently available audio tracks in the format 'number language description'.\n"
850 " The number indicates the track type (1..32 = MP2, 33..48 = Dolby).\n"
851 " The currently selected track has its description prefixed with '*'.\n"
852 " If a number is given (which must be one of the track numbers listed)\n"
853 " audio is switched to that track.\n"
854 " Note that the list may not be fully available or current immediately after\n"
855 " switching the channel or starting a replay.",
856 "CHAN [ + | - | <number> | <name> | <id> ]\n"
857 " Switch channel up, down or to the given channel number, name or id.\n"
858 " Without option (or after successfully switching to the channel)\n"
859 " it returns the current channel number and name.",
860 "CLRE [ <number> | <name> | <id> ]\n"
861 " Clear the EPG list of the given channel number, name or id.\n"
862 " Without option it clears the entire EPG list.\n"
863 " After a CLRE command, no further EPG processing is done for 10\n"
864 " seconds, so that data sent with subsequent PUTE commands doesn't\n"
865 " interfere with data from the broadcasters.",
866 "CONN name:<name> port:<port> vdrversion:<vdrversion> apiversion:<apiversion> timeout:<timeout>\n"
867 " Used by peer-to-peer connections between VDRs to tell the other VDR\n"
868 " to establish a connection to this VDR. The name is the SVDRP host name\n"
869 " of this VDR, which may differ from its DNS name.",
870 "DELC <number> | <id>\n"
871 " Delete the channel with the given number or channel id.",
873 " Delete the recording with the given id. Before a recording can be\n"
874 " deleted, an LSTR command should have been executed in order to retrieve\n"
875 " the recording ids. The ids are unique and don't change while this\n"
876 " instance of VDR is running.\n"
877 " CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n"
878 " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
880 " Delete the timer with the given id. If this timer is currently recording,\n"
881 " the recording will be stopped without any warning.",
883 " Edit the recording with the given id. Before a recording can be\n"
884 " edited, an LSTR command should have been executed in order to retrieve\n"
885 " the recording ids.",
886 "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n"
887 " Grab the current frame and save it to the given file. Images can\n"
888 " be stored as JPEG or PNM, depending on the given file name extension.\n"
889 " The quality of the grabbed image can be in the range 0..100, where 100\n"
890 " (the default) means \"best\" (only applies to JPEG). The size parameters\n"
891 " define the size of the resulting image (default is full screen).\n"
892 " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n"
893 " data will be sent to the SVDRP connection encoded in base64. The same\n"
894 " happens if '-' (a minus sign) is given as file name, in which case the\n"
895 " image format defaults to JPEG.",
897 " The HELP command gives help info.",
898 "HITK [ <key> ... ]\n"
899 " Hit the given remote control key. Without option a list of all\n"
900 " valid key names is given. If more than one key is given, they are\n"
901 " entered into the remote control queue in the given sequence. There\n"
902 " can be up to 31 keys.",
903 "LSTC [ :ids ] [ :groups | <number> | <name> | <id> ]\n"
904 " List channels. Without option, all channels are listed. Otherwise\n"
905 " only the given channel is listed. If a name is given, all channels\n"
906 " containing the given string as part of their name are listed.\n"
907 " If ':groups' is given, all channels are listed including group\n"
908 " separators. The channel number of a group separator is always 0.\n"
909 " With ':ids' the channel ids are listed following the channel numbers.\n"
910 " The special number 0 can be given to list the current channel.",
912 " List all available devices. Each device is listed with its name and\n"
913 " whether it is currently the primary device ('P') or it implements a\n"
914 " decoder ('D') and can be used as output device.",
915 "LSTE [ <channel> ] [ now | next | at <time> ]\n"
916 " List EPG data. Without any parameters all data of all channels is\n"
917 " listed. If a channel is given (either by number or by channel ID),\n"
918 " only data for that channel is listed. 'now', 'next', or 'at <time>'\n"
919 " restricts the returned data to present events, following events, or\n"
920 " events at the given time (which must be in time_t form).",
921 "LSTR [ <id> [ path ] ]\n"
922 " List recordings. Without option, all recordings are listed. Otherwise\n"
923 " the information for the given recording is listed. If a recording\n"
924 " id and the keyword 'path' is given, the actual file name of that\n"
925 " recording's directory is listed.\n"
926 " Note that the ids of the recordings are not necessarily given in\n"
928 "LSTT [ <id> ] [ id ]\n"
929 " List timers. Without option, all timers are listed. Otherwise\n"
930 " only the timer with the given id is listed. If the keyword 'id' is\n"
931 " given, the channels will be listed with their unique channel ids\n"
932 " instead of their numbers. This command lists only the timers that are\n"
933 " defined locally on this VDR, not any remote timers from other VDRs.",
935 " Displays the given message on the OSD. The message will be queued\n"
936 " and displayed whenever this is suitable.\n",
937 "MODC <number> <settings>\n"
938 " Modify a channel. Settings must be in the same format as returned\n"
939 " by the LSTC command.",
940 "MODT <id> on | off | <settings>\n"
941 " Modify a timer. Settings must be in the same format as returned\n"
942 " by the LSTT command. The special keywords 'on' and 'off' can be\n"
943 " used to easily activate or deactivate a timer.",
944 "MOVC <number> <to>\n"
945 " Move a channel to a new position.",
946 "MOVR <id> <new name>\n"
947 " Move the recording with the given id. Before a recording can be\n"
948 " moved, an LSTR command should have been executed in order to retrieve\n"
949 " the recording ids. The ids don't change during subsequent MOVR\n"
952 " Create a new channel. Settings must be in the same format as returned\n"
953 " by the LSTC command.",
955 " Create a new timer. Settings must be in the same format as returned\n"
956 " by the LSTT command. If a timer with the same channel, day, start\n"
957 " and stop time already exists, the data of the existing timer is returned\n"
959 "NEXT [ abs | rel ]\n"
960 " Show the next timer event. If no option is given, the output will be\n"
961 " in human readable form. With option 'abs' the absolute time of the next\n"
962 " event will be given as the number of seconds since the epoch (time_t\n"
963 " format), while with option 'rel' the relative time will be given as the\n"
964 " number of seconds from now until the event. If the absolute time given\n"
965 " is smaller than the current time, or if the relative time is less than\n"
966 " zero, this means that the timer is currently recording and has started\n"
967 " at the given time. The first value in the resulting line is the id\n"
970 " Used by peer-to-peer connections between VDRs to keep the connection\n"
971 " from timing out. May be used at any time and simply returns a line of\n"
972 " the form '<hostname> is alive'.",
973 "PLAY [ <id> [ begin | <position> ] ]\n"
974 " Play the recording with the given id. Before a recording can be\n"
975 " played, an LSTR command should have been executed in order to retrieve\n"
976 " the recording ids.\n"
977 " The keyword 'begin' plays the recording from its very beginning, while\n"
978 " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n"
979 " position. If neither 'begin' nor a <position> are given, replay is resumed\n"
980 " at the position where any previous replay was stopped, or from the beginning\n"
981 " by default. To control or stop the replay session, use the usual remote\n"
982 " control keypresses via the HITK command.\n"
983 " Without any parameters PLAY returns the id and title of the recording that\n"
984 " is currently being played (if any).",
985 "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n"
986 " Send a command to a plugin.\n"
987 " The PLUG command without any parameters lists all plugins.\n"
988 " If only a name is given, all commands known to that plugin are listed.\n"
989 " If a command is given (optionally followed by parameters), that command\n"
990 " is sent to the plugin, and the result will be displayed.\n"
991 " The keyword 'help' lists all the SVDRP commands known to the named plugin.\n"
992 " If 'help' is followed by a command, the detailed help for that command is\n"
993 " given. The keyword 'main' initiates a call to the main menu function of the\n"
995 "POLL <name> timers\n"
996 " Used by peer-to-peer connections between VDRs to inform other machines\n"
997 " about changes to timers. The receiving VDR shall use LSTT to query the\n"
998 " remote machine with the given name about its timers and update its list\n"
999 " of timers accordingly.\n",
1000 "PRIM [ <number> ]\n"
1001 " Make the device with the given number the primary device.\n"
1002 " Without option it returns the currently active primary device in the same\n"
1003 " format as used by the LSTD command.",
1005 " Put data into the EPG list. The data entered has to strictly follow the\n"
1006 " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n"
1007 " by itself terminates the input and starts processing of the data (all\n"
1008 " entered data is buffered until the terminating '.' is seen).\n"
1009 " If a file name is given, epg data will be read from this file (which\n"
1010 " must be accessible under the given name from the machine VDR is running\n"
1011 " on). In case of file input, no terminating '.' shall be given.\n",
1012 "REMO [ on | off ]\n"
1013 " Turns the remote control on or off. Without a parameter, the current\n"
1014 " status of the remote control is reported.",
1016 " Forces an EPG scan. If this is a single DVB device system, the scan\n"
1017 " will be done on the primary device unless it is currently recording.",
1019 " Return information about disk usage (total, free, percent).",
1021 " Updates a timer. Settings must be in the same format as returned\n"
1022 " by the LSTT command. If a timer with the same channel, day, start\n"
1023 " and stop time does not yet exist, it will be created.",
1025 " Initiates a re-read of the recordings directory, which is the SVDRP\n"
1026 " equivalent to 'touch .update'.",
1027 "VOLU [ <number> | + | - | mute ]\n"
1028 " Set the audio volume to the given number (which is limited to the range\n"
1029 " 0...255). If the special options '+' or '-' are given, the volume will\n"
1030 " be turned up or down, respectively. The option 'mute' will toggle the\n"
1031 " audio muting. If no option is given, the current audio volume level will\n"
1034 " Exit vdr (SVDRP).\n"
1035 " You can also hit Ctrl-D to exit.",
1063 const char *q = HelpPage;
1066 uint n = q - HelpPage;
1067 if (n >=
sizeof(topic))
1068 n =
sizeof(topic) - 1;
1069 strncpy(topic, HelpPage, n);
1083 if (strcasecmp(Cmd, t) == 0)
1104 void Close(
bool SendReply =
false,
bool Timeout =
false);
1105 bool Send(
const char *s);
1108 void CmdAUDI(const
char *Option);
1109 void CmdCHAN(const
char *Option);
1110 void CmdCLRE(const
char *Option);
1111 void CmdCONN(const
char *Option);
1112 void CmdDELC(const
char *Option);
1113 void CmdDELR(const
char *Option);
1114 void CmdDELT(const
char *Option);
1115 void CmdEDIT(const
char *Option);
1116 void CmdGRAB(const
char *Option);
1117 void CmdHELP(const
char *Option);
1118 void CmdHITK(const
char *Option);
1119 void CmdLSTC(const
char *Option);
1120 void CmdLSTD(const
char *Option);
1121 void CmdLSTE(const
char *Option);
1122 void CmdLSTR(const
char *Option);
1123 void CmdLSTT(const
char *Option);
1124 void CmdMESG(const
char *Option);
1125 void CmdMODC(const
char *Option);
1126 void CmdMODT(const
char *Option);
1127 void CmdMOVC(const
char *Option);
1128 void CmdMOVR(const
char *Option);
1129 void CmdNEWC(const
char *Option);
1130 void CmdNEWT(const
char *Option);
1131 void CmdNEXT(const
char *Option);
1132 void CmdPING(const
char *Option);
1133 void CmdPLAY(const
char *Option);
1134 void CmdPLUG(const
char *Option);
1135 void CmdPOLL(const
char *Option);
1136 void CmdPRIM(const
char *Option);
1137 void CmdPUTE(const
char *Option);
1138 void CmdREMO(const
char *Option);
1139 void CmdSCAN(const
char *Option);
1140 void CmdSTAT(const
char *Option);
1141 void CmdUPDT(const
char *Option);
1142 void CmdUPDR(const
char *Option);
1143 void CmdVOLU(const
char *Option);
1166 time_t now = time(NULL);
1182 if (
file.IsOpen()) {
1184 Reply(221,
"%s closing connection%s",
Setup.SVDRPHostName, Timeout ?
" (timeout)" :
"");
1207 if (
file.IsOpen()) {
1209 char *buffer = NULL;
1212 if (vasprintf(&buffer, fmt, ap) >= 0) {
1215 char *n = strchr(s,
'\n');
1219 if (Code < 0 || n && *(n + 1))
1223 s = n ? n + 1 : NULL;
1227 Reply(451,
"Bad format - looks like a programming error!");
1234 Reply(451,
"Zero return code - looks like a programming error!");
1250 const int TopicsPerLine = 5;
1252 for (
int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
1255 q += sprintf(q,
" ");
1256 for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
1257 const char *topic =
GetHelpTopic(hp[(y * TopicsPerLine + x)]);
1262 Reply(-214,
"%s", buffer);
1270 int o = strtol(Option, NULL, 10);
1273 if (TrackId && TrackId->
id) {
1278 Reply(501,
"Audio track \"%s\" not available", Option);
1281 Reply(501,
"Invalid audio track \"%s\"", Option);
1284 Reply(501,
"Error in audio track \"%s\"", Option);
1292 if (TrackId && TrackId->
id) {
1294 Reply(-250,
"%s", *s);
1299 Reply(250,
"%s", *s);
1301 Reply(550,
"No audio tracks available");
1312 int o = strtol(Option, NULL, 10);
1316 else if (strcmp(Option,
"-") == 0) {
1323 else if (strcmp(Option,
"+") == 0) {
1331 n = Channel->Number();
1333 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1334 if (!Channel->GroupSep()) {
1335 if (strcasecmp(Channel->Name(), Option) == 0) {
1336 n = Channel->Number();
1343 Reply(501,
"Undefined channel \"%s\"", Option);
1347 if (
const cChannel *Channel = Channels->GetByNumber(n)) {
1349 Reply(554,
"Error switching to channel \"%d\"", Channel->Number());
1354 Reply(550,
"Unable to find channel \"%s\"", Option);
1362 Reply(250,
"%d %s", Channel->Number(), Channel->Name());
1374 int o = strtol(Option, NULL, 10);
1376 if (
const cChannel *Channel = Channels->GetByNumber(o))
1377 ChannelID = Channel->GetChannelID();
1383 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1384 if (!Channel->GroupSep()) {
1385 if (strcasecmp(Channel->Name(), Option) == 0) {
1386 ChannelID = Channel->GetChannelID();
1397 for (
cSchedule *p = Schedules->First(); p; p = Schedules->
Next(p)) {
1398 if (p->ChannelID() == ChannelID) {
1404 for (
cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
1405 if (ChannelID == Timer->Channel()->GetChannelID().
ClrRid())
1406 Timer->SetEvent(NULL);
1410 Reply(250,
"EPG data of channel \"%s\" cleared", Option);
1413 Reply(550,
"No EPG data found for channel \"%s\"", Option);
1418 Reply(501,
"Undefined channel \"%s\"", Option);
1423 for (
cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer))
1424 Timer->SetEvent(NULL);
1425 for (
cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->
Next(Schedule))
1426 Schedule->Cleanup(INT_MAX);
1428 Reply(250,
"EPG data cleared");
1437 if (ServerParams.
Ok()) {
1443 Reply(501,
"Error in server parameters: %s", ServerParams.
Error());
1446 Reply(451,
"No SVDRP client handler");
1449 Reply(501,
"Missing server parameters");
1457 Channels->SetExplicitModify();
1460 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1464 if (
const cTimer *Timer = Timers->UsesChannel(Channel)) {
1465 Reply(550,
"Channel \"%s\" is in use by timer %s", Option, *Timer->ToDescr());
1469 cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
1470 if (CurrentChannel && Channel == CurrentChannel) {
1471 int n = Channels->GetNextNormal(CurrentChannel->
Index());
1473 n = Channels->GetPrevNormal(CurrentChannel->
Index());
1475 Reply(501,
"Can't delete channel \"%s\" - list would be empty", Option);
1478 CurrentChannel = Channels->Get(n);
1479 CurrentChannelNr = 0;
1481 Channels->Del(Channel);
1482 Channels->ReNumber();
1483 Channels->SetModifiedByUser();
1484 Channels->SetModified();
1486 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
1488 Channels->SwitchTo(CurrentChannel->
Number());
1492 Reply(250,
"Channel \"%s\" deleted", Option);
1495 Reply(501,
"Channel \"%s\" not defined", Option);
1498 Reply(501,
"Missing channel number or id");
1507 return cString::sprintf(
"Recording \"%s\" is being replayed", RecordingId);
1508 else if ((Reason &
ruCut) != 0)
1511 return cString::sprintf(
"Recording \"%s\" is being copied/moved", RecordingId);
1522 Recordings->SetExplicitModify();
1523 if (
cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1524 if (
int RecordingInUse = Recording->IsInUse())
1527 if (Recording->Delete()) {
1529 Recordings->Del(Recording,
false);
1530 DeletedRecordings->Add(Recording);
1531 Recordings->SetModified();
1533 Reply(250,
"Recording \"%s\" deleted", Option);
1536 Reply(554,
"Error while deleting recording!");
1540 Reply(550,
"Recording \"%s\" not found", Option);
1543 Reply(501,
"Error in recording id \"%s\"", Option);
1546 Reply(501,
"Missing recording id");
1554 Timers->SetExplicitModify();
1555 if (
cTimer *Timer = Timers->GetById(strtol(Option, NULL, 10))) {
1556 if (Timer->Recording())
1558 Timer->TriggerRespawn();
1560 Timers->SetModified();
1562 Reply(250,
"Timer \"%s\" deleted", Option);
1565 Reply(501,
"Timer \"%s\" not defined", Option);
1568 Reply(501,
"Error in timer number \"%s\"", Option);
1571 Reply(501,
"Missing timer number");
1579 if (
const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1581 if (Marks.
Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.
Count()) {
1583 Reply(550,
"Not enough free disk space to start editing process");
1585 Reply(250,
"Editing recording \"%s\" [%s]", Option, Recording->Title());
1587 Reply(554,
"Can't start editing process");
1590 Reply(554,
"No editing marks defined");
1593 Reply(550,
"Recording \"%s\" not found", Option);
1596 Reply(501,
"Error in recording id \"%s\"", Option);
1599 Reply(501,
"Missing recording id");
1604 const char *FileName = NULL;
1606 int Quality = -1, SizeX = -1, SizeY = -1;
1608 char buf[strlen(Option) + 1];
1609 char *p = strcpy(buf, Option);
1610 const char *delim =
" \t";
1612 FileName = strtok_r(p, delim, &strtok_next);
1614 const char *Extension = strrchr(FileName,
'.');
1616 if (strcasecmp(Extension,
".jpg") == 0 || strcasecmp(Extension,
".jpeg") == 0)
1618 else if (strcasecmp(Extension,
".pnm") == 0)
1621 Reply(501,
"Unknown image type \"%s\"", Extension + 1);
1624 if (Extension == FileName)
1627 else if (strcmp(FileName,
"-") == 0)
1630 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1631 if (strcasecmp(p,
"JPEG") == 0 || strcasecmp(p,
"PNM") == 0) {
1633 p = strtok_r(NULL, delim, &strtok_next);
1639 Reply(501,
"Invalid quality \"%s\"", p);
1645 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1649 Reply(501,
"Invalid sizex \"%s\"", p);
1652 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1656 Reply(501,
"Invalid sizey \"%s\"", p);
1661 Reply(501,
"Missing sizey");
1665 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1666 Reply(501,
"Unexpected parameter \"%s\"", p);
1670 char RealFileName[PATH_MAX];
1675 const char *slash = strrchr(FileName,
'/');
1680 slash = strrchr(FileName,
'/');
1683 char *r = realpath(t, RealFileName);
1686 Reply(501,
"Invalid file name \"%s\"", FileName);
1689 strcat(RealFileName, slash);
1690 FileName = RealFileName;
1692 Reply(501,
"Invalid file name \"%s\"", FileName);
1697 Reply(550,
"Grabbing to file not allowed (use \"GRAB -\" instead)");
1706 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
1708 if (
safe_write(fd, Image, ImageSize) == ImageSize) {
1710 Reply(250,
"Grabbed image %s", Option);
1714 Reply(451,
"Can't write to '%s'", FileName);
1720 Reply(451,
"Can't open '%s'", FileName);
1726 while ((s = Base64.
NextLine()) != NULL)
1727 Reply(-216,
"%s", s);
1728 Reply(216,
"Grabbed image %s", Option);
1733 Reply(451,
"Grab image failed");
1736 Reply(501,
"Missing filename");
1744 Reply(-214,
"%s", hp);
1746 Reply(504,
"HELP topic \"%s\" unknown", Option);
1752 Reply(-214,
"Topics:");
1761 Reply(-214,
"To report bugs in the implementation send email to");
1762 Reply(-214,
" vdr-bugs@tvdr.de");
1764 Reply(214,
"End of HELP info");
1771 Reply(550,
"Remote control currently disabled (key \"%s\" discarded)", Option);
1774 char buf[strlen(Option) + 1];
1775 strcpy(buf, Option);
1776 const char *delim =
" \t";
1778 char *p = strtok_r(buf, delim, &strtok_next);
1784 Reply(451,
"Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
1789 Reply(504,
"Unknown key: \"%s\"", p);
1793 p = strtok_r(NULL, delim, &strtok_next);
1795 Reply(250,
"Key%s \"%s\" accepted", NumKeys > 1 ?
"s" :
"", Option);
1798 Reply(-214,
"Valid <key> names for the HITK command:");
1799 for (
int i = 0; i <
kNone; i++) {
1802 Reply(214,
"End of key list");
1809 bool WithChannelIds =
startswith(Option,
":ids") && (Option[4] ==
' ' || Option[4] == 0);
1812 bool WithGroupSeps = strcasecmp(Option,
":groups") == 0;
1813 if (*Option && !WithGroupSeps) {
1815 int n = strtol(Option, NULL, 10);
1818 if (
const cChannel *Channel = Channels->GetByNumber(n))
1819 Reply(250,
"%d%s%s %s", Channel->Number(), WithChannelIds ?
" " :
"", WithChannelIds ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1821 Reply(501,
"Channel \"%s\" not defined", Option);
1826 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1827 if (!Channel->GroupSep()) {
1828 if (strcasestr(Channel->Name(), Option)) {
1839 Reply(501,
"Channel \"%s\" not defined", Option);
1843 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1845 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());
1846 else if (!Channel->GroupSep())
1847 Reply(Channel->Number() <
cChannels::MaxNumber() ? -250 : 250,
"%d%s%s %s", Channel->Number(), WithChannelIds ?
" " :
"", WithChannelIds ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1851 Reply(550,
"No channels defined");
1859 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());
1863 Reply(550,
"No devices found");
1874 char buf[strlen(Option) + 1];
1875 strcpy(buf, Option);
1876 const char *delim =
" \t";
1878 char *p = strtok_r(buf, delim, &strtok_next);
1879 while (p && DumpMode ==
dmAll) {
1880 if (strcasecmp(p,
"NOW") == 0)
1882 else if (strcasecmp(p,
"NEXT") == 0)
1884 else if (strcasecmp(p,
"AT") == 0) {
1886 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1888 AtTime = strtol(p, NULL, 10);
1890 Reply(501,
"Invalid time");
1895 Reply(501,
"Missing time");
1899 else if (!Schedule) {
1902 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1906 Schedule = Schedules->GetSchedule(Channel);
1908 Reply(550,
"No schedule found");
1913 Reply(550,
"Channel \"%s\" not defined", p);
1918 Reply(501,
"Unknown option: \"%s\"", p);
1921 p = strtok_r(NULL, delim, &strtok_next);
1926 FILE *f = fdopen(fd,
"w");
1929 Schedule->
Dump(Channels, f,
"215-", DumpMode, AtTime);
1931 Schedules->Dump(f,
"215-", DumpMode, AtTime);
1933 Reply(215,
"End of EPG data");
1937 Reply(451,
"Can't open file connection");
1942 Reply(451,
"Can't dup stream descriptor");
1951 char buf[strlen(Option) + 1];
1952 strcpy(buf, Option);
1953 const char *delim =
" \t";
1955 char *p = strtok_r(buf, delim, &strtok_next);
1959 Number = strtol(p, NULL, 10);
1961 Reply(501,
"Error in recording id \"%s\"", Option);
1965 else if (strcasecmp(p,
"PATH") == 0)
1968 Reply(501,
"Unknown option: \"%s\"", p);
1971 p = strtok_r(NULL, delim, &strtok_next);
1974 if (
const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1975 FILE *f = fdopen(
file,
"w");
1978 Reply(250,
"%s", Recording->FileName());
1980 Recording->Info()->Write(f,
"215-");
1982 Reply(215,
"End of recording information");
1987 Reply(451,
"Can't open file connection");
1990 Reply(550,
"Recording \"%s\" not found", Option);
1993 else if (Recordings->Count()) {
1994 const cRecording *Recording = Recordings->First();
1996 Reply(Recording == Recordings->Last() ? 250 : -250,
"%d %s", Recording->
Id(), Recording->
Title(
' ',
true));
1997 Recording = Recordings->
Next(Recording);
2001 Reply(550,
"No recordings available");
2007 bool UseChannelId =
false;
2009 char buf[strlen(Option) + 1];
2010 strcpy(buf, Option);
2011 const char *delim =
" \t";
2013 char *p = strtok_r(buf, delim, &strtok_next);
2016 Id = strtol(p, NULL, 10);
2017 else if (strcasecmp(p,
"ID") == 0)
2018 UseChannelId =
true;
2020 Reply(501,
"Unknown option: \"%s\"", p);
2023 p = strtok_r(NULL, delim, &strtok_next);
2028 for (
const cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
2029 if (!Timer->Remote()) {
2030 if (Timer->Id() == Id) {
2031 Reply(250,
"%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2036 Reply(501,
"Timer \"%s\" not defined", Option);
2040 const cTimer *LastLocalTimer = Timers->Last();
2041 while (LastLocalTimer) {
2042 if (LastLocalTimer->
Remote())
2043 LastLocalTimer = Timers->
Prev(LastLocalTimer);
2047 if (LastLocalTimer) {
2048 for (
const cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
2049 if (!Timer->Remote())
2050 Reply(Timer != LastLocalTimer ? -250 : 250,
"%d %s", Timer->
Id(), *Timer->ToText(UseChannelId));
2051 if (Timer == LastLocalTimer)
2057 Reply(550,
"No timers defined");
2065 Reply(250,
"Message queued");
2068 Reply(501,
"Missing message");
2075 int n = strtol(Option, &tail, 10);
2076 if (tail && tail != Option) {
2079 Channels->SetExplicitModify();
2080 if (
cChannel *Channel = Channels->GetByNumber(n)) {
2082 if (ch.
Parse(tail)) {
2083 if (Channels->HasUniqueChannelID(&ch, Channel)) {
2085 Channels->ReNumber();
2086 Channels->SetModifiedByUser();
2087 Channels->SetModified();
2088 isyslog(
"SVDRP %s < %s modified channel %d %s",
Setup.SVDRPHostName, *
clientName, Channel->Number(), *Channel->ToText());
2089 Reply(250,
"%d %s", Channel->Number(), *Channel->ToText());
2092 Reply(501,
"Channel settings are not unique");
2095 Reply(501,
"Error in channel settings");
2098 Reply(501,
"Channel \"%d\" not defined", n);
2101 Reply(501,
"Error in channel number");
2104 Reply(501,
"Missing channel settings");
2111 int Id = strtol(Option, &tail, 10);
2112 if (tail && tail != Option) {
2115 Timers->SetExplicitModify();
2116 if (
cTimer *Timer = Timers->GetById(Id)) {
2119 if (strcasecmp(tail,
"ON") == 0)
2121 else if (strcasecmp(tail,
"OFF") == 0)
2123 else if (!t.
Parse(tail)) {
2124 Reply(501,
"Error in timer settings");
2128 Reply(550,
"Timer is recording");
2136 Timers->SetModified();
2137 isyslog(
"SVDRP %s < %s modified timer %s (%s)",
Setup.SVDRPHostName, *
clientName, *Timer->ToDescr(), Timer->HasFlags(
tfActive) ?
"active" :
"inactive");
2138 if (Timer->IsPatternTimer())
2139 Timer->SetEvent(NULL);
2140 Timer->TriggerRespawn();
2141 Reply(250,
"%d %s", Timer->Id(), *Timer->ToText(
true));
2144 Reply(501,
"Timer \"%d\" not defined", Id);
2147 Reply(501,
"Error in timer id");
2150 Reply(501,
"Missing timer settings");
2157 int From = strtol(Option, &tail, 10);
2158 if (tail && tail != Option) {
2160 if (tail && tail != Option) {
2163 Channels->SetExplicitModify();
2164 int To = strtol(tail, NULL, 10);
2166 const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
2167 cChannel *FromChannel = Channels->GetByNumber(From);
2169 cChannel *ToChannel = Channels->GetByNumber(To);
2171 int FromNumber = FromChannel->
Number();
2172 int ToNumber = ToChannel->
Number();
2173 if (FromNumber != ToNumber) {
2174 if (Channels->MoveNeedsDecrement(FromChannel, ToChannel))
2175 ToChannel = Channels->
Prev(ToChannel);
2176 Channels->Move(FromChannel, ToChannel);
2177 Channels->ReNumber();
2178 Channels->SetModifiedByUser();
2179 Channels->SetModified();
2180 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
2182 Channels->SwitchTo(CurrentChannel->
Number());
2187 Reply(250,
"Channel \"%d\" moved to \"%d\"", From, To);
2190 Reply(501,
"Can't move channel to same position");
2193 Reply(501,
"Channel \"%d\" not defined", To);
2196 Reply(501,
"Channel \"%d\" not defined", From);
2199 Reply(501,
"Error in channel number");
2202 Reply(501,
"Error in channel number");
2205 Reply(501,
"Missing channel number");
2211 char *opt = strdup(Option);
2214 while (*option && !isspace(*option))
2220 Recordings->SetExplicitModify();
2221 if (
cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2222 if (
int RecordingInUse = Recording->IsInUse())
2228 cString oldName = Recording->Name();
2229 if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) {
2230 Recordings->SetModified();
2231 Recordings->TouchUpdate();
2232 Reply(250,
"Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name());
2235 Reply(554,
"Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
2238 Reply(501,
"Missing new recording name");
2242 Reply(550,
"Recording \"%s\" not found", num);
2245 Reply(501,
"Error in recording id \"%s\"", num);
2249 Reply(501,
"Missing recording id");
2256 if (ch.
Parse(Option)) {
2258 Channels->SetExplicitModify();
2259 if (Channels->HasUniqueChannelID(&ch)) {
2262 Channels->Add(channel);
2263 Channels->ReNumber();
2264 Channels->SetModifiedByUser();
2265 Channels->SetModified();
2270 Reply(501,
"Channel settings are not unique");
2273 Reply(501,
"Error in channel settings");
2276 Reply(501,
"Missing channel settings");
2283 if (Timer->
Parse(Option)) {
2285 const cTimer *t = Timers->GetTimer(Timer);
2301 Reply(501,
"Error in timer settings");
2305 Reply(501,
"Missing timer settings");
2311 if (
const cTimer *t = Timers->GetNextActiveTimer()) {
2312 time_t Start = t->StartTime();
2316 else if (strcasecmp(Option,
"ABS") == 0)
2317 Reply(250,
"%d %jd", Id, intmax_t(Start));
2318 else if (strcasecmp(Option,
"REL") == 0)
2319 Reply(250,
"%d %jd", Id, intmax_t(Start - time(NULL)));
2321 Reply(501,
"Unknown option: \"%s\"", Option);
2324 Reply(550,
"No active timers");
2329 Reply(250,
"%s is alive",
Setup.SVDRPHostName);
2335 char *opt = strdup(Option);
2338 while (*option && !isspace(*option))
2345 if (
const cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2346 cString FileName = Recording->FileName();
2347 cString Title = Recording->Title();
2348 int FramesPerSecond = Recording->FramesPerSecond();
2349 bool IsPesRecording = Recording->IsPesRecording();
2357 if (strcasecmp(option,
"BEGIN") != 0)
2368 Reply(250,
"Playing recording \"%s\" [%s]", num, *Title);
2372 Reply(550,
"Recording \"%s\" not found", num);
2377 Reply(501,
"Error in recording id \"%s\"", num);
2382 if (
const cRecording *Recording = Recordings->GetByName(FileName))
2383 Reply(250,
"%d %s", Recording->Id(), Recording->Title(
' ',
true));
2385 Reply(550,
"Recording \"%s\" not found", FileName);
2388 Reply(550,
"Not playing");
2394 char *opt = strdup(Option);
2396 char *option = name;
2397 while (*option && !isspace(*option))
2406 while (*option && !isspace(*option))
2412 if (!*cmd || strcasecmp(cmd,
"HELP") == 0) {
2413 if (*cmd && *option) {
2416 Reply(-214,
"%s", hp);
2417 Reply(214,
"End of HELP info");
2420 Reply(504,
"HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->
Name());
2426 Reply(-214,
"SVDRP commands:");
2428 Reply(214,
"End of HELP info");
2431 Reply(214,
"This plugin has no SVDRP commands");
2434 else if (strcasecmp(cmd,
"MAIN") == 0) {
2436 Reply(250,
"Initiated call to main menu function of plugin \"%s\"", plugin->
Name());
2438 Reply(550,
"A plugin call is already pending - please try again later");
2441 int ReplyCode = 900;
2444 Reply(abs(ReplyCode),
"%s", *s);
2446 Reply(500,
"Command unrecognized: \"%s\"", cmd);
2450 Reply(550,
"Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
2454 Reply(-214,
"Available plugins:");
2458 Reply(214,
"End of plugin list");
2465 char buf[strlen(Option) + 1];
2466 char *p = strcpy(buf, Option);
2467 const char *delim =
" \t";
2469 char *RemoteName = strtok_r(p, delim, &strtok_next);
2470 char *ListName = strtok_r(NULL, delim, &strtok_next);
2473 if (strcasecmp(ListName,
"timers") == 0) {
2478 Reply(501,
"Unknown list name: \"%s\"", ListName);
2481 Reply(501,
"Missing list name");
2484 Reply(501,
"No SVDRP client connections");
2487 Reply(501,
"Missing parameters");
2495 int o = strtol(Option, NULL, 10);
2499 Reply(501,
"Invalid device number \"%s\"", Option);
2502 Reply(501,
"Invalid parameter \"%s\"", Option);
2504 Setup.PrimaryDVB = n;
2505 Reply(250,
"Primary device set to %d", n);
2510 Reply(250,
"%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ?
"D" :
"-", d->DeviceNumber() + 1 ==
Setup.PrimaryDVB ?
"P" :
"-", *d->DeviceName());
2512 Reply(501,
"Failed to get primary device");
2519 FILE *f = fopen(Option,
"r");
2523 Reply(250,
"EPG data processed from \"%s\"", Option);
2526 Reply(451,
"Error while processing EPG from \"%s\"", Option);
2530 Reply(501,
"Cannot open file \"%s\"", Option);
2544 if (!strcasecmp(Option,
"ON")) {
2546 Reply(250,
"Remote control enabled");
2548 else if (!strcasecmp(Option,
"OFF")) {
2550 Reply(250,
"Remote control disabled");
2553 Reply(501,
"Invalid Option \"%s\"", Option);
2562 Reply(250,
"EPG scan triggered");
2568 if (strcasecmp(Option,
"DISK") == 0) {
2571 Reply(250,
"%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
2574 Reply(501,
"Invalid Option \"%s\"", Option);
2577 Reply(501,
"No option given");
2584 if (Timer->
Parse(Option)) {
2586 if (
cTimer *t = Timers->GetTimer(Timer)) {
2606 Reply(501,
"Error in timer settings");
2610 Reply(501,
"Missing timer settings");
2616 Recordings->Update(
false);
2617 Reply(250,
"Re-read of recordings directory triggered");
2625 else if (strcmp(Option,
"+") == 0)
2627 else if (strcmp(Option,
"-") == 0)
2629 else if (strcasecmp(Option,
"MUTE") == 0)
2632 Reply(501,
"Unknown option: \"%s\"", Option);
2637 Reply(250,
"Audio is mute");
2642#define CMD(c) (strcasecmp(Cmd, c) == 0)
2659 while (*s && !isspace(*s))
2701 else Reply(500,
"Command unrecognized: \"%s\"", Cmd);
2706 if (
file.IsOpen()) {
2707 while (
file.Ready(
false)) {
2711 if (c ==
'\n' || c == 0x00) {
2727 else if (c == 0x04 &&
numChars == 0) {
2731 else if (c == 0x08 || c == 0x7F) {
2736 else if (c <= 0x03 || c == 0x0D) {
2741 int NewLength =
length + BUFSIZ;
2742 if (
char *NewBuffer = (
char *)realloc(
cmdLine, NewLength)) {
2767 return file.IsOpen();
2791 virtual void Action(
void)
override;
2801:
cThread(
"SVDRP server handler", true)
2887 bool Result =
false;
2896 bool Result =
false;
2909 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 void Stop(const char *InstantId)
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)
static const char * NowReplaying(void)
bool Execute(const char *ServerName, const char *Command, cStringList *Response=NULL)
void AddClient(cSVDRPServerParams &ServerParams, const char *IpAddress)
virtual ~cSVDRPClientHandler() override
void ProcessConnections(void)
bool GetServerNames(cStringList *ServerNames)
void CloseClient(const char *ServerName)
cSVDRPClientHandler(int TcpPort, int UdpPort)
void HandleClientConnection(void)
cSVDRPClient * GetClientForServer(const char *ServerName)
cVector< cSVDRPClient * > clientConnections
bool TriggerFetchingTimers(const char *ServerName)
virtual void Action(void) override
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
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)
virtual ~cSVDRPServerHandler() override
void HandleServerConnection(void)
virtual void Action(void) override
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
void ProcessConnections(void)
cSVDRPServerHandler(int TcpPort)
void WaitUntilReady(void)
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 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.
virtual void Clear(void) override
void SortNumerically(void)
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
Returns true if the number of milliseconds given in the last call to Set() have passed.
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)
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
cRecordingsHandler RecordingsHandler
struct __attribute__((packed))
#define LOCK_RECORDINGS_READ
#define LOCK_DELETEDRECORDINGS_WRITE
#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)
#define SVDRPDiscoverDelta
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