23#include <netinet/in.h>
28#include <sys/socket.h>
46#define dbgsvdrp(a...) if (DumpSVDRPDataTransfer) fprintf(stderr, a)
71 void Set(
const sockaddr *SockAddr);
94 const sockaddr_in *Addr = (sockaddr_in *)SockAddr;
95 char buf[INET_ADDRSTRLEN];
96 Set(inet_ntop(AF_INET, &Addr->sin_addr, buf,
sizeof(buf)), ntohs(Addr->sin_port));
101#define MAXUDPBUF 1024
113 bool Connect(
const char *Address);
148 sock =
tcp ? socket(PF_INET, SOCK_STREAM, IPPROTO_IP) : socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
155 setsockopt(
sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr,
sizeof(ReUseAddr));
158 memset(&Addr, 0,
sizeof(Addr));
159 Addr.sin_family = AF_INET;
160 Addr.sin_port = htons(
port);
162 if (bind(
sock, (sockaddr *)&Addr,
sizeof(Addr)) < 0) {
168 int Flags = fcntl(
sock, F_GETFL, 0);
175 if (fcntl(
sock, F_SETFL, Flags) < 0) {
182 if (listen(
sock, 1) < 0) {
197 sock = socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
204 memset(&Addr, 0,
sizeof(Addr));
205 Addr.sin_family = AF_INET;
206 Addr.sin_port = htons(
port);
207 if (inet_pton(AF_INET, Address, &Addr.sin_addr) != 1) {
208 esyslog(
"ERROR: invalid IP address '%s'", Address);
212 if (connect(
sock, (sockaddr *)&Addr,
sizeof(Addr)) < 0) {
218 int Flags = fcntl(
sock, F_GETFL, 0);
225 if (fcntl(
sock, F_SETFL, Flags) < 0) {
230 dbgsvdrp(
"> %s:%d server connection established\n", Address,
port);
240 int Socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
247 if (setsockopt(
Socket, SOL_SOCKET, SO_BROADCAST, &One,
sizeof(One)) < 0) {
254 memset(&Addr, 0,
sizeof(Addr));
255 Addr.sin_family = AF_INET;
256 Addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
257 Addr.sin_port = htons(
Port);
259 char buf[INET_ADDRSTRLEN];
260 dbgsvdrp(
"> %s:%d %s\n", inet_ntop(AF_INET, &Addr.sin_addr, buf,
sizeof(buf)),
Port, Dgram);
261 int Length = strlen(Dgram);
262 int Sent = sendto(
Socket, Dgram, Length, 0, (sockaddr *)&Addr,
sizeof(Addr));
266 return Sent == Length;
273 uint Size =
sizeof(Addr);
274 int NewSock = accept(
sock, (sockaddr *)&Addr, &Size);
278 const char *s =
"Access denied!\n";
279 if (write(NewSock, s, strlen(s)) < 0)
300 uint Size =
sizeof(Addr);
301 int NumBytes = recvfrom(
sock, buf,
sizeof(buf), 0, (sockaddr *)&Addr, &Size);
338 bool Send(
const char *Command);
345 bool HasAddress(
const char *Address,
int Port)
const;
363 timeout = Timeout * 1000 * 9 / 10;
413#define SVDRPResonseTimeout 5000
420 if (c ==
'\n' || c == 0x00) {
422 while (numChars > 0 && strchr(
" \t\r\n",
input[numChars - 1]))
423 input[--numChars] = 0;
430 switch (atoi(
input)) {
431 case 220:
if (numChars > 4) {
433 if (
char *t = strchr(n,
' ')) {
450 if (numChars >= 4 &&
input[3] !=
'-')
455 if (numChars >=
length - 1) {
456 int NewLength =
length + BUFSIZ;
457 if (
char *NewBuffer = (
char *)realloc(
input, NewLength)) {
467 input[numChars++] = c;
483 else if (!Response && numChars == 0)
516 if (
Execute(
"LSTT ID", &Response)) {
517 for (
int i = 0; i < Response.
Size(); i++) {
518 char *s = Response[i];
522 else if (Code == 550)
560 if (Params && *Params) {
578 error =
"invalid timeout";
581 error =
"missing server timeout";
584 error =
"missing server apiversion";
587 error =
"missing server vdrversion";
590 error =
"missing server port";
593 error =
"missing server name";
596 error =
"missing server parameters";
614 virtual void Action(
void)
override;
620 bool Execute(
const char *ServerName,
const char *Command,
cStringList *Response = NULL);
628:
cThread(
"SVDRP client handler", true)
676 bool TimersModified = Timers->StoreRemoteTimers(Client->
ServerName(), &RemoteTimers);
683 if (*PollTimersCmd) {
684 if (!Client->
Execute(PollTimersCmd))
729 if (ServerParams.
Ok())
740 time_t LastDiscover = 0;
741#define SVDRPDiscoverDelta 60
743 time_t Now = time(NULL);
762 return Client->Execute(Command, Response);
769 ServerNames->
Clear();
775 return ServerNames->
Size() > 0;
805 if ((
f = tmpfile()) != NULL) {
807 message =
"Enter EPG data, end with \".\" on a line by itself";
812 message =
"Error while opening temporary file";
825 if (strcmp(s,
".") != 0) {
835 message =
"EPG data processed";
839 message =
"Error while processing EPG data";
850#define MAXHELPTOPIC 10
851#define EITDISABLETIME 10
855 "AUDI [ <number> ]\n"
856 " Lists the currently available audio tracks in the format 'number language description'.\n"
857 " The number indicates the track type (1..32 = MP2, 33..48 = Dolby).\n"
858 " The currently selected track has its description prefixed with '*'.\n"
859 " If a number is given (which must be one of the track numbers listed)\n"
860 " audio is switched to that track.\n"
861 " Note that the list may not be fully available or current immediately after\n"
862 " switching the channel or starting a replay.",
863 "CHAN [ + | - | <number> | <name> | <id> ]\n"
864 " Switch channel up, down or to the given channel number, name or id.\n"
865 " Without option (or after successfully switching to the channel)\n"
866 " it returns the current channel number and name.",
867 "CLRE [ <number> | <name> | <id> ]\n"
868 " Clear the EPG list of the given channel number, name or id.\n"
869 " Without option it clears the entire EPG list.\n"
870 " After a CLRE command, no further EPG processing is done for 10\n"
871 " seconds, so that data sent with subsequent PUTE commands doesn't\n"
872 " interfere with data from the broadcasters.",
873 "CONN name:<name> port:<port> vdrversion:<vdrversion> apiversion:<apiversion> timeout:<timeout>\n"
874 " Used by peer-to-peer connections between VDRs to tell the other VDR\n"
875 " to establish a connection to this VDR. The name is the SVDRP host name\n"
876 " of this VDR, which may differ from its DNS name.",
877 "DELC <number> | <id>\n"
878 " Delete the channel with the given number or channel id.",
880 " Delete the recording with the given id. Before a recording can be\n"
881 " deleted, an LSTR command should have been executed in order to retrieve\n"
882 " the recording ids. The ids are unique and don't change while this\n"
883 " instance of VDR is running.\n"
884 " CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n"
885 " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
887 " Delete the timer with the given id. If this timer is currently recording,\n"
888 " the recording will be stopped without any warning.",
890 " Edit the recording with the given id. Before a recording can be\n"
891 " edited, an LSTR command should have been executed in order to retrieve\n"
892 " the recording ids.",
893 "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n"
894 " Grab the current frame and save it to the given file. Images can\n"
895 " be stored as JPEG or PNM, depending on the given file name extension.\n"
896 " The quality of the grabbed image can be in the range 0..100, where 100\n"
897 " (the default) means \"best\" (only applies to JPEG). The size parameters\n"
898 " define the size of the resulting image (default is full screen).\n"
899 " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n"
900 " data will be sent to the SVDRP connection encoded in base64. The same\n"
901 " happens if '-' (a minus sign) is given as file name, in which case the\n"
902 " image format defaults to JPEG.",
904 " The HELP command gives help info.",
905 "HITK [ <key> ... ]\n"
906 " Hit the given remote control key. Without option a list of all\n"
907 " valid key names is given. If more than one key is given, they are\n"
908 " entered into the remote control queue in the given sequence. There\n"
909 " can be up to 31 keys.",
910 "LSTC [ :ids ] [ :groups | <number> | <name> | <id> ]\n"
911 " List channels. Without option, all channels are listed. Otherwise\n"
912 " only the given channel is listed. If a name is given, all channels\n"
913 " containing the given string as part of their name are listed.\n"
914 " If ':groups' is given, all channels are listed including group\n"
915 " separators. The channel number of a group separator is always 0.\n"
916 " With ':ids' the channel ids are listed following the channel numbers.\n"
917 " The special number 0 can be given to list the current channel.",
919 " List all available devices. Each device is listed with its name and\n"
920 " whether it is currently the primary device ('P') or it implements a\n"
921 " decoder ('D') and can be used as output device.",
922 "LSTE [ <channel> ] [ now | next | at <time> ]\n"
923 " List EPG data. Without any parameters all data of all channels is\n"
924 " listed. If a channel is given (either by number or by channel ID),\n"
925 " only data for that channel is listed. 'now', 'next', or 'at <time>'\n"
926 " restricts the returned data to present events, following events, or\n"
927 " events at the given time (which must be in time_t form).",
928 "LSTR [ <id> [ path ] ]\n"
929 " List recordings. Without option, all recordings are listed. Otherwise\n"
930 " the information for the given recording is listed. If a recording\n"
931 " id and the keyword 'path' is given, the actual file name of that\n"
932 " recording's directory is listed.\n"
933 " Note that the ids of the recordings are not necessarily given in\n"
935 "LSTT [ <id> ] [ id ]\n"
936 " List timers. Without option, all timers are listed. Otherwise\n"
937 " only the timer with the given id is listed. If the keyword 'id' is\n"
938 " given, the channels will be listed with their unique channel ids\n"
939 " instead of their numbers. This command lists only the timers that are\n"
940 " defined locally on this VDR, not any remote timers from other VDRs.",
942 " Displays the given message on the OSD. The message will be queued\n"
943 " and displayed whenever this is suitable.\n",
944 "MODC <number> <settings>\n"
945 " Modify a channel. Settings must be in the same format as returned\n"
946 " by the LSTC command.",
947 "MODT <id> on | off | <settings>\n"
948 " Modify a timer. Settings must be in the same format as returned\n"
949 " by the LSTT command. The special keywords 'on' and 'off' can be\n"
950 " used to easily activate or deactivate a timer.",
951 "MOVC <number> <to>\n"
952 " Move a channel to a new position.",
953 "MOVR <id> <new name>\n"
954 " Move the recording with the given id. Before a recording can be\n"
955 " moved, an LSTR command should have been executed in order to retrieve\n"
956 " the recording ids. The ids don't change during subsequent MOVR\n"
959 " Create a new channel. Settings must be in the same format as returned\n"
960 " by the LSTC command.",
962 " Create a new timer. Settings must be in the same format as returned\n"
963 " by the LSTT command. If a timer with the same channel, day, start\n"
964 " and stop time already exists, the data of the existing timer is returned\n"
966 "NEXT [ abs | rel ]\n"
967 " Show the next timer event. If no option is given, the output will be\n"
968 " in human readable form. With option 'abs' the absolute time of the next\n"
969 " event will be given as the number of seconds since the epoch (time_t\n"
970 " format), while with option 'rel' the relative time will be given as the\n"
971 " number of seconds from now until the event. If the absolute time given\n"
972 " is smaller than the current time, or if the relative time is less than\n"
973 " zero, this means that the timer is currently recording and has started\n"
974 " at the given time. The first value in the resulting line is the id\n"
977 " Used by peer-to-peer connections between VDRs to keep the connection\n"
978 " from timing out. May be used at any time and simply returns a line of\n"
979 " the form '<hostname> is alive'.",
980 "PLAY [ <id> [ begin | <position> ] ]\n"
981 " Play the recording with the given id. Before a recording can be\n"
982 " played, an LSTR command should have been executed in order to retrieve\n"
983 " the recording ids.\n"
984 " The keyword 'begin' plays the recording from its very beginning, while\n"
985 " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n"
986 " position. If neither 'begin' nor a <position> are given, replay is resumed\n"
987 " at the position where any previous replay was stopped, or from the beginning\n"
988 " by default. To control or stop the replay session, use the usual remote\n"
989 " control keypresses via the HITK command.\n"
990 " Without any parameters PLAY returns the id and title of the recording that\n"
991 " is currently being played (if any).",
992 "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n"
993 " Send a command to a plugin.\n"
994 " The PLUG command without any parameters lists all plugins.\n"
995 " If only a name is given, all commands known to that plugin are listed.\n"
996 " If a command is given (optionally followed by parameters), that command\n"
997 " is sent to the plugin, and the result will be displayed.\n"
998 " The keyword 'help' lists all the SVDRP commands known to the named plugin.\n"
999 " If 'help' is followed by a command, the detailed help for that command is\n"
1000 " given. The keyword 'main' initiates a call to the main menu function of the\n"
1002 "POLL <name> timers\n"
1003 " Used by peer-to-peer connections between VDRs to inform other machines\n"
1004 " about changes to timers. The receiving VDR shall use LSTT to query the\n"
1005 " remote machine with the given name about its timers and update its list\n"
1006 " of timers accordingly.\n",
1007 "PRIM [ <number> ]\n"
1008 " Make the device with the given number the primary device.\n"
1009 " Without option it returns the currently active primary device in the same\n"
1010 " format as used by the LSTD command.",
1012 " Put data into the EPG list. The data entered has to strictly follow the\n"
1013 " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n"
1014 " by itself terminates the input and starts processing of the data (all\n"
1015 " entered data is buffered until the terminating '.' is seen).\n"
1016 " If a file name is given, epg data will be read from this file (which\n"
1017 " must be accessible under the given name from the machine VDR is running\n"
1018 " on). In case of file input, no terminating '.' shall be given.\n",
1019 "REMO [ on | off ]\n"
1020 " Turns the remote control on or off. Without a parameter, the current\n"
1021 " status of the remote control is reported.",
1023 " Forces an EPG scan. If this is a single DVB device system, the scan\n"
1024 " will be done on the primary device unless it is currently recording.",
1026 " Return information about disk usage (total, free, percent).",
1028 " Updates a timer. Settings must be in the same format as returned\n"
1029 " by the LSTT command. If a timer with the same channel, day, start\n"
1030 " and stop time does not yet exist, it will be created.",
1032 " Initiates a re-read of the recordings directory, which is the SVDRP\n"
1033 " equivalent to 'touch .update'.",
1034 "VOLU [ <number> | + | - | mute ]\n"
1035 " Set the audio volume to the given number (which is limited to the range\n"
1036 " 0...255). If the special options '+' or '-' are given, the volume will\n"
1037 " be turned up or down, respectively. The option 'mute' will toggle the\n"
1038 " audio muting. If no option is given, the current audio volume level will\n"
1041 " Exit vdr (SVDRP).\n"
1042 " You can also hit Ctrl-D to exit.",
1070 const char *q = HelpPage;
1073 uint n = q - HelpPage;
1074 if (n >=
sizeof(topic))
1075 n =
sizeof(topic) - 1;
1076 strncpy(topic, HelpPage, n);
1090 if (strcasecmp(Cmd, t) == 0)
1110 void Close(
bool SendReply =
false,
bool Timeout =
false);
1111 bool Send(
const char *s);
1114 void CmdAUDI(const
char *Option);
1115 void CmdCHAN(const
char *Option);
1116 void CmdCLRE(const
char *Option);
1117 void CmdCONN(const
char *Option);
1118 void CmdDELC(const
char *Option);
1119 void CmdDELR(const
char *Option);
1120 void CmdDELT(const
char *Option);
1121 void CmdEDIT(const
char *Option);
1122 void CmdGRAB(const
char *Option);
1123 void CmdHELP(const
char *Option);
1124 void CmdHITK(const
char *Option);
1125 void CmdLSTC(const
char *Option);
1126 void CmdLSTD(const
char *Option);
1127 void CmdLSTE(const
char *Option);
1128 void CmdLSTR(const
char *Option);
1129 void CmdLSTT(const
char *Option);
1130 void CmdMESG(const
char *Option);
1131 void CmdMODC(const
char *Option);
1132 void CmdMODT(const
char *Option);
1133 void CmdMOVC(const
char *Option);
1134 void CmdMOVR(const
char *Option);
1135 void CmdNEWC(const
char *Option);
1136 void CmdNEWT(const
char *Option);
1137 void CmdNEXT(const
char *Option);
1138 void CmdPING(const
char *Option);
1139 void CmdPLAY(const
char *Option);
1140 void CmdPLUG(const
char *Option);
1141 void CmdPOLL(const
char *Option);
1142 void CmdPRIM(const
char *Option);
1143 void CmdPUTE(const
char *Option);
1144 void CmdREMO(const
char *Option);
1145 void CmdSCAN(const
char *Option);
1146 void CmdSTAT(const
char *Option);
1147 void CmdUPDT(const
char *Option);
1148 void CmdUPDR(const
char *Option);
1149 void CmdVOLU(const
char *Option);
1171 time_t now = time(NULL);
1213 char *buffer = NULL;
1216 if (vasprintf(&buffer, fmt, ap) >= 0) {
1219 char *n = strchr(s,
'\n');
1223 if (Code < 0 || n && *(n + 1))
1227 s = n ? n + 1 : NULL;
1231 Reply(451,
"Bad format - looks like a programming error!");
1238 Reply(451,
"Zero return code - looks like a programming error!");
1254 const int TopicsPerLine = 5;
1256 for (
int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
1259 q += sprintf(q,
" ");
1260 for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
1261 const char *topic =
GetHelpTopic(hp[(y * TopicsPerLine + x)]);
1266 Reply(-214,
"%s", buffer);
1274 int o = strtol(Option, NULL, 10);
1277 if (TrackId && TrackId->
id) {
1282 Reply(501,
"Audio track \"%s\" not available", Option);
1285 Reply(501,
"Invalid audio track \"%s\"", Option);
1288 Reply(501,
"Error in audio track \"%s\"", Option);
1296 if (TrackId && TrackId->
id) {
1298 Reply(-250,
"%s", *s);
1303 Reply(250,
"%s", *s);
1305 Reply(550,
"No audio tracks available");
1316 int o = strtol(Option, NULL, 10);
1320 else if (strcmp(Option,
"-") == 0) {
1327 else if (strcmp(Option,
"+") == 0) {
1335 n = Channel->Number();
1337 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1338 if (!Channel->GroupSep()) {
1339 if (strcasecmp(Channel->Name(), Option) == 0) {
1340 n = Channel->Number();
1347 Reply(501,
"Undefined channel \"%s\"", Option);
1351 if (
const cChannel *Channel = Channels->GetByNumber(n)) {
1353 Reply(554,
"Error switching to channel \"%d\"", Channel->Number());
1358 Reply(550,
"Unable to find channel \"%s\"", Option);
1366 Reply(250,
"%d %s", Channel->Number(), Channel->Name());
1378 int o = strtol(Option, NULL, 10);
1380 if (
const cChannel *Channel = Channels->GetByNumber(o))
1381 ChannelID = Channel->GetChannelID();
1387 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1388 if (!Channel->GroupSep()) {
1389 if (strcasecmp(Channel->Name(), Option) == 0) {
1390 ChannelID = Channel->GetChannelID();
1401 for (
cSchedule *p = Schedules->First(); p; p = Schedules->
Next(p)) {
1402 if (p->ChannelID() == ChannelID) {
1408 for (
cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
1409 if (ChannelID == Timer->Channel()->GetChannelID().
ClrRid())
1410 Timer->SetEvent(NULL);
1414 Reply(250,
"EPG data of channel \"%s\" cleared", Option);
1417 Reply(550,
"No EPG data found for channel \"%s\"", Option);
1422 Reply(501,
"Undefined channel \"%s\"", Option);
1427 for (
cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer))
1428 Timer->SetEvent(NULL);
1429 for (
cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->
Next(Schedule))
1430 Schedule->Cleanup(INT_MAX);
1432 Reply(250,
"EPG data cleared");
1441 if (ServerParams.
Ok()) {
1447 Reply(501,
"Error in server parameters: %s", ServerParams.
Error());
1450 Reply(451,
"No SVDRP client handler");
1453 Reply(501,
"Missing server parameters");
1461 Channels->SetExplicitModify();
1464 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1468 if (
const cTimer *Timer = Timers->UsesChannel(Channel)) {
1469 Reply(550,
"Channel \"%s\" is in use by timer %s", Option, *Timer->ToDescr());
1473 cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
1474 if (CurrentChannel && Channel == CurrentChannel) {
1475 int n = Channels->GetNextNormal(CurrentChannel->
Index());
1477 n = Channels->GetPrevNormal(CurrentChannel->
Index());
1479 Reply(501,
"Can't delete channel \"%s\" - list would be empty", Option);
1482 CurrentChannel = Channels->Get(n);
1483 CurrentChannelNr = 0;
1485 Channels->Del(Channel);
1486 Channels->ReNumber();
1487 Channels->SetModifiedByUser();
1488 Channels->SetModified();
1490 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
1492 Channels->SwitchTo(CurrentChannel->
Number());
1496 Reply(250,
"Channel \"%s\" deleted", Option);
1499 Reply(501,
"Channel \"%s\" not defined", Option);
1502 Reply(501,
"Missing channel number or id");
1511 return cString::sprintf(
"Recording \"%s\" is being replayed", RecordingId);
1512 else if ((Reason &
ruCut) != 0)
1515 return cString::sprintf(
"Recording \"%s\" is being copied/moved", RecordingId);
1526 Recordings->SetExplicitModify();
1527 if (
cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1528 if (
int RecordingInUse = Recording->IsInUse())
1531 if (Recording->Delete()) {
1533 Recordings->Del(Recording,
false);
1534 DeletedRecordings->Add(Recording);
1535 Recordings->SetModified();
1537 Reply(250,
"Recording \"%s\" deleted", Option);
1540 Reply(554,
"Error while deleting recording!");
1544 Reply(550,
"Recording \"%s\" not found", Option);
1547 Reply(501,
"Error in recording id \"%s\"", Option);
1550 Reply(501,
"Missing recording id");
1558 Timers->SetExplicitModify();
1559 if (
cTimer *Timer = Timers->GetById(strtol(Option, NULL, 10))) {
1560 if (Timer->Recording())
1562 Timer->TriggerRespawn();
1564 Timers->SetModified();
1566 Reply(250,
"Timer \"%s\" deleted", Option);
1569 Reply(501,
"Timer \"%s\" not defined", Option);
1572 Reply(501,
"Error in timer number \"%s\"", Option);
1575 Reply(501,
"Missing timer number");
1583 if (
const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1585 if (Marks.
Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.
Count()) {
1587 Reply(550,
"Not enough free disk space to start editing process");
1589 Reply(250,
"Editing recording \"%s\" [%s]", Option, Recording->Title());
1591 Reply(554,
"Can't start editing process");
1594 Reply(554,
"No editing marks defined");
1597 Reply(550,
"Recording \"%s\" not found", Option);
1600 Reply(501,
"Error in recording id \"%s\"", Option);
1603 Reply(501,
"Missing recording id");
1608 const char *FileName = NULL;
1610 int Quality = -1, SizeX = -1, SizeY = -1;
1612 char buf[strlen(Option) + 1];
1613 char *p = strcpy(buf, Option);
1614 const char *delim =
" \t";
1616 FileName = strtok_r(p, delim, &strtok_next);
1618 const char *Extension = strrchr(FileName,
'.');
1620 if (strcasecmp(Extension,
".jpg") == 0 || strcasecmp(Extension,
".jpeg") == 0)
1622 else if (strcasecmp(Extension,
".pnm") == 0)
1625 Reply(501,
"Unknown image type \"%s\"", Extension + 1);
1628 if (Extension == FileName)
1631 else if (strcmp(FileName,
"-") == 0)
1634 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1635 if (strcasecmp(p,
"JPEG") == 0 || strcasecmp(p,
"PNM") == 0) {
1637 p = strtok_r(NULL, delim, &strtok_next);
1643 Reply(501,
"Invalid quality \"%s\"", p);
1649 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1653 Reply(501,
"Invalid sizex \"%s\"", p);
1656 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1660 Reply(501,
"Invalid sizey \"%s\"", p);
1665 Reply(501,
"Missing sizey");
1669 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1670 Reply(501,
"Unexpected parameter \"%s\"", p);
1674 char RealFileName[PATH_MAX];
1679 const char *slash = strrchr(FileName,
'/');
1684 slash = strrchr(FileName,
'/');
1687 char *r = realpath(t, RealFileName);
1690 Reply(501,
"Invalid file name \"%s\"", FileName);
1693 strcat(RealFileName, slash);
1694 FileName = RealFileName;
1696 Reply(501,
"Invalid file name \"%s\"", FileName);
1701 Reply(550,
"Grabbing to file not allowed (use \"GRAB -\" instead)");
1710 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
1712 if (
safe_write(fd, Image, ImageSize) == ImageSize) {
1714 Reply(250,
"Grabbed image %s", Option);
1718 Reply(451,
"Can't write to '%s'", FileName);
1724 Reply(451,
"Can't open '%s'", FileName);
1730 while ((s = Base64.
NextLine()) != NULL)
1731 Reply(-216,
"%s", s);
1732 Reply(216,
"Grabbed image %s", Option);
1737 Reply(451,
"Grab image failed");
1740 Reply(501,
"Missing filename");
1748 Reply(-214,
"%s", hp);
1750 Reply(504,
"HELP topic \"%s\" unknown", Option);
1756 Reply(-214,
"Topics:");
1765 Reply(-214,
"To report bugs in the implementation send email to");
1766 Reply(-214,
" vdr-bugs@tvdr.de");
1768 Reply(214,
"End of HELP info");
1775 Reply(550,
"Remote control currently disabled (key \"%s\" discarded)", Option);
1778 char buf[strlen(Option) + 1];
1779 strcpy(buf, Option);
1780 const char *delim =
" \t";
1782 char *p = strtok_r(buf, delim, &strtok_next);
1788 Reply(451,
"Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
1793 Reply(504,
"Unknown key: \"%s\"", p);
1797 p = strtok_r(NULL, delim, &strtok_next);
1799 Reply(250,
"Key%s \"%s\" accepted", NumKeys > 1 ?
"s" :
"", Option);
1802 Reply(-214,
"Valid <key> names for the HITK command:");
1803 for (
int i = 0; i <
kNone; i++) {
1806 Reply(214,
"End of key list");
1813 bool WithChannelIds =
startswith(Option,
":ids") && (Option[4] ==
' ' || Option[4] == 0);
1816 bool WithGroupSeps = strcasecmp(Option,
":groups") == 0;
1817 if (*Option && !WithGroupSeps) {
1819 int n = strtol(Option, NULL, 10);
1822 if (
const cChannel *Channel = Channels->GetByNumber(n))
1823 Reply(250,
"%d%s%s %s", Channel->Number(), WithChannelIds ?
" " :
"", WithChannelIds ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1825 Reply(501,
"Channel \"%s\" not defined", Option);
1830 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1831 if (!Channel->GroupSep()) {
1832 if (strcasestr(Channel->Name(), Option)) {
1843 Reply(501,
"Channel \"%s\" not defined", Option);
1847 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1849 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());
1850 else if (!Channel->GroupSep())
1851 Reply(Channel->Number() <
cChannels::MaxNumber() ? -250 : 250,
"%d%s%s %s", Channel->Number(), WithChannelIds ?
" " :
"", WithChannelIds ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1855 Reply(550,
"No channels defined");
1863 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());
1867 Reply(550,
"No devices found");
1878 char buf[strlen(Option) + 1];
1879 strcpy(buf, Option);
1880 const char *delim =
" \t";
1882 char *p = strtok_r(buf, delim, &strtok_next);
1883 while (p && DumpMode ==
dmAll) {
1884 if (strcasecmp(p,
"NOW") == 0)
1886 else if (strcasecmp(p,
"NEXT") == 0)
1888 else if (strcasecmp(p,
"AT") == 0) {
1890 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1892 AtTime = strtol(p, NULL, 10);
1894 Reply(501,
"Invalid time");
1899 Reply(501,
"Missing time");
1903 else if (!Schedule) {
1906 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1910 Schedule = Schedules->GetSchedule(Channel);
1912 Reply(550,
"No schedule found");
1917 Reply(550,
"Channel \"%s\" not defined", p);
1922 Reply(501,
"Unknown option: \"%s\"", p);
1925 p = strtok_r(NULL, delim, &strtok_next);
1930 FILE *f = fdopen(fd,
"w");
1933 Schedule->
Dump(Channels, f,
"215-", DumpMode, AtTime);
1935 Schedules->Dump(f,
"215-", DumpMode, AtTime);
1937 Reply(215,
"End of EPG data");
1941 Reply(451,
"Can't open file connection");
1946 Reply(451,
"Can't dup stream descriptor");
1955 char buf[strlen(Option) + 1];
1956 strcpy(buf, Option);
1957 const char *delim =
" \t";
1959 char *p = strtok_r(buf, delim, &strtok_next);
1963 Number = strtol(p, NULL, 10);
1965 Reply(501,
"Error in recording id \"%s\"", Option);
1969 else if (strcasecmp(p,
"PATH") == 0)
1972 Reply(501,
"Unknown option: \"%s\"", p);
1975 p = strtok_r(NULL, delim, &strtok_next);
1978 if (
const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1979 FILE *f = fdopen(
file,
"w");
1982 Reply(250,
"%s", Recording->FileName());
1984 Recording->Info()->Write(f,
"215-");
1986 Reply(215,
"End of recording information");
1991 Reply(451,
"Can't open file connection");
1994 Reply(550,
"Recording \"%s\" not found", Option);
1997 else if (Recordings->Count()) {
1998 const cRecording *Recording = Recordings->First();
2000 Reply(Recording == Recordings->Last() ? 250 : -250,
"%d %s", Recording->
Id(), Recording->
Title(
' ',
true));
2001 Recording = Recordings->
Next(Recording);
2005 Reply(550,
"No recordings available");
2011 bool UseChannelId =
false;
2013 char buf[strlen(Option) + 1];
2014 strcpy(buf, Option);
2015 const char *delim =
" \t";
2017 char *p = strtok_r(buf, delim, &strtok_next);
2020 Id = strtol(p, NULL, 10);
2021 else if (strcasecmp(p,
"ID") == 0)
2022 UseChannelId =
true;
2024 Reply(501,
"Unknown option: \"%s\"", p);
2027 p = strtok_r(NULL, delim, &strtok_next);
2032 for (
const cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
2033 if (!Timer->Remote()) {
2034 if (Timer->Id() == Id) {
2035 Reply(250,
"%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2040 Reply(501,
"Timer \"%s\" not defined", Option);
2044 const cTimer *LastLocalTimer = Timers->Last();
2045 while (LastLocalTimer) {
2046 if (LastLocalTimer->
Remote())
2047 LastLocalTimer = Timers->
Prev(LastLocalTimer);
2051 if (LastLocalTimer) {
2052 for (
const cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
2053 if (!Timer->Remote())
2054 Reply(Timer != LastLocalTimer ? -250 : 250,
"%d %s", Timer->
Id(), *Timer->ToText(UseChannelId));
2055 if (Timer == LastLocalTimer)
2061 Reply(550,
"No timers defined");
2069 Reply(250,
"Message queued");
2072 Reply(501,
"Missing message");
2079 int n = strtol(Option, &tail, 10);
2080 if (tail && tail != Option) {
2083 Channels->SetExplicitModify();
2084 if (
cChannel *Channel = Channels->GetByNumber(n)) {
2086 if (ch.
Parse(tail)) {
2087 if (Channels->HasUniqueChannelID(&ch, Channel)) {
2089 Channels->ReNumber();
2090 Channels->SetModifiedByUser();
2091 Channels->SetModified();
2093 Reply(250,
"%d %s", Channel->Number(), *Channel->ToText());
2096 Reply(501,
"Channel settings are not unique");
2099 Reply(501,
"Error in channel settings");
2102 Reply(501,
"Channel \"%d\" not defined", n);
2105 Reply(501,
"Error in channel number");
2108 Reply(501,
"Missing channel settings");
2115 int Id = strtol(Option, &tail, 10);
2116 if (tail && tail != Option) {
2119 Timers->SetExplicitModify();
2120 if (
cTimer *Timer = Timers->GetById(Id)) {
2123 if (strcasecmp(tail,
"ON") == 0)
2125 else if (strcasecmp(tail,
"OFF") == 0)
2127 else if (!t.
Parse(tail)) {
2128 Reply(501,
"Error in timer settings");
2132 Reply(550,
"Timer is recording");
2140 Timers->SetModified();
2142 if (Timer->IsPatternTimer())
2143 Timer->SetEvent(NULL);
2144 Timer->TriggerRespawn();
2145 Reply(250,
"%d %s", Timer->Id(), *Timer->ToText(
true));
2148 Reply(501,
"Timer \"%d\" not defined", Id);
2151 Reply(501,
"Error in timer id");
2154 Reply(501,
"Missing timer settings");
2161 int From = strtol(Option, &tail, 10);
2162 if (tail && tail != Option) {
2164 if (tail && tail != Option) {
2167 Channels->SetExplicitModify();
2168 int To = strtol(tail, NULL, 10);
2170 const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
2171 cChannel *FromChannel = Channels->GetByNumber(From);
2173 cChannel *ToChannel = Channels->GetByNumber(To);
2175 int FromNumber = FromChannel->
Number();
2176 int ToNumber = ToChannel->
Number();
2177 if (FromNumber != ToNumber) {
2178 if (Channels->MoveNeedsDecrement(FromChannel, ToChannel))
2179 ToChannel = Channels->
Prev(ToChannel);
2180 Channels->Move(FromChannel, ToChannel);
2181 Channels->ReNumber();
2182 Channels->SetModifiedByUser();
2183 Channels->SetModified();
2184 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
2186 Channels->SwitchTo(CurrentChannel->
Number());
2191 Reply(250,
"Channel \"%d\" moved to \"%d\"", From, To);
2194 Reply(501,
"Can't move channel to same position");
2197 Reply(501,
"Channel \"%d\" not defined", To);
2200 Reply(501,
"Channel \"%d\" not defined", From);
2203 Reply(501,
"Error in channel number");
2206 Reply(501,
"Error in channel number");
2209 Reply(501,
"Missing channel number");
2215 char *opt = strdup(Option);
2218 while (*option && !isspace(*option))
2224 Recordings->SetExplicitModify();
2225 if (
cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2226 if (
int RecordingInUse = Recording->IsInUse())
2232 cString oldName = Recording->Name();
2233 if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) {
2234 Recordings->SetModified();
2235 Recordings->TouchUpdate();
2236 Reply(250,
"Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name());
2239 Reply(554,
"Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
2242 Reply(501,
"Missing new recording name");
2246 Reply(550,
"Recording \"%s\" not found", num);
2249 Reply(501,
"Error in recording id \"%s\"", num);
2253 Reply(501,
"Missing recording id");
2260 if (ch.
Parse(Option)) {
2262 Channels->SetExplicitModify();
2263 if (Channels->HasUniqueChannelID(&ch)) {
2266 Channels->Add(channel);
2267 Channels->ReNumber();
2268 Channels->SetModifiedByUser();
2269 Channels->SetModified();
2274 Reply(501,
"Channel settings are not unique");
2277 Reply(501,
"Error in channel settings");
2280 Reply(501,
"Missing channel settings");
2287 if (Timer->
Parse(Option)) {
2289 const cTimer *t = Timers->GetTimer(Timer);
2305 Reply(501,
"Error in timer settings");
2309 Reply(501,
"Missing timer settings");
2315 if (
const cTimer *t = Timers->GetNextActiveTimer()) {
2316 time_t Start = t->StartTime();
2320 else if (strcasecmp(Option,
"ABS") == 0)
2321 Reply(250,
"%d %jd", Id, intmax_t(Start));
2322 else if (strcasecmp(Option,
"REL") == 0)
2323 Reply(250,
"%d %jd", Id, intmax_t(Start - time(NULL)));
2325 Reply(501,
"Unknown option: \"%s\"", Option);
2328 Reply(550,
"No active timers");
2339 char *opt = strdup(Option);
2342 while (*option && !isspace(*option))
2349 if (
const cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2350 cString FileName = Recording->FileName();
2351 cString Title = Recording->Title();
2352 int FramesPerSecond = Recording->FramesPerSecond();
2353 bool IsPesRecording = Recording->IsPesRecording();
2361 if (strcasecmp(option,
"BEGIN") != 0)
2372 Reply(250,
"Playing recording \"%s\" [%s]", num, *Title);
2376 Reply(550,
"Recording \"%s\" not found", num);
2381 Reply(501,
"Error in recording id \"%s\"", num);
2386 if (
const cRecording *Recording = Recordings->GetByName(FileName))
2387 Reply(250,
"%d %s", Recording->Id(), Recording->Title(
' ',
true));
2389 Reply(550,
"Recording \"%s\" not found", FileName);
2392 Reply(550,
"Not playing");
2398 char *opt = strdup(Option);
2400 char *option = name;
2401 while (*option && !isspace(*option))
2410 while (*option && !isspace(*option))
2416 if (!*cmd || strcasecmp(cmd,
"HELP") == 0) {
2417 if (*cmd && *option) {
2420 Reply(-214,
"%s", hp);
2421 Reply(214,
"End of HELP info");
2424 Reply(504,
"HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->
Name());
2430 Reply(-214,
"SVDRP commands:");
2432 Reply(214,
"End of HELP info");
2435 Reply(214,
"This plugin has no SVDRP commands");
2438 else if (strcasecmp(cmd,
"MAIN") == 0) {
2440 Reply(250,
"Initiated call to main menu function of plugin \"%s\"", plugin->
Name());
2442 Reply(550,
"A plugin call is already pending - please try again later");
2445 int ReplyCode = 900;
2448 Reply(abs(ReplyCode),
"%s", *s);
2450 Reply(500,
"Command unrecognized: \"%s\"", cmd);
2454 Reply(550,
"Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
2458 Reply(-214,
"Available plugins:");
2462 Reply(214,
"End of plugin list");
2469 char buf[strlen(Option) + 1];
2470 char *p = strcpy(buf, Option);
2471 const char *delim =
" \t";
2473 char *RemoteName = strtok_r(p, delim, &strtok_next);
2474 char *ListName = strtok_r(NULL, delim, &strtok_next);
2477 if (strcasecmp(ListName,
"timers") == 0) {
2482 Reply(501,
"Unknown list name: \"%s\"", ListName);
2485 Reply(501,
"Missing list name");
2488 Reply(501,
"No SVDRP client connections");
2491 Reply(501,
"Missing parameters");
2499 int o = strtol(Option, NULL, 10);
2503 Reply(501,
"Invalid device number \"%s\"", Option);
2506 Reply(501,
"Invalid parameter \"%s\"", Option);
2509 Reply(250,
"Primary device set to %d", n);
2514 Reply(250,
"%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ?
"D" :
"-", d->DeviceNumber() + 1 ==
Setup.
PrimaryDVB ?
"P" :
"-", *d->DeviceName());
2516 Reply(501,
"Failed to get primary device");
2523 FILE *f = fopen(Option,
"r");
2527 Reply(250,
"EPG data processed from \"%s\"", Option);
2530 Reply(451,
"Error while processing EPG from \"%s\"", Option);
2534 Reply(501,
"Cannot open file \"%s\"", Option);
2548 if (!strcasecmp(Option,
"ON")) {
2550 Reply(250,
"Remote control enabled");
2552 else if (!strcasecmp(Option,
"OFF")) {
2554 Reply(250,
"Remote control disabled");
2557 Reply(501,
"Invalid Option \"%s\"", Option);
2566 Reply(250,
"EPG scan triggered");
2572 if (strcasecmp(Option,
"DISK") == 0) {
2575 Reply(250,
"%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
2578 Reply(501,
"Invalid Option \"%s\"", Option);
2581 Reply(501,
"No option given");
2588 if (Timer->
Parse(Option)) {
2590 if (
cTimer *t = Timers->GetTimer(Timer)) {
2610 Reply(501,
"Error in timer settings");
2614 Reply(501,
"Missing timer settings");
2620 Recordings->Update(
false);
2621 Reply(250,
"Re-read of recordings directory triggered");
2629 else if (strcmp(Option,
"+") == 0)
2631 else if (strcmp(Option,
"-") == 0)
2633 else if (strcasecmp(Option,
"MUTE") == 0)
2636 Reply(501,
"Unknown option: \"%s\"", Option);
2641 Reply(250,
"Audio is mute");
2646#define CMD(c) (strcasecmp(Cmd, c) == 0)
2663 while (*s && !isspace(*s))
2705 else Reply(500,
"Command unrecognized: \"%s\"", Cmd);
2715 if (c ==
'\n' || c == 0x00) {
2731 else if (c == 0x04 &&
numChars == 0) {
2735 else if (c == 0x08 || c == 0x7F) {
2740 else if (c <= 0x03 || c == 0x0D) {
2745 int NewLength =
length + BUFSIZ;
2746 if (
char *NewBuffer = (
char *)realloc(
cmdLine, NewLength)) {
2795 virtual void Action(
void)
override;
2805:
cThread(
"SVDRP server handler", true)
2893 bool Result =
false;
2902 bool Result =
false;
2915 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)
bool Ready(bool Wait=true)
bool Open(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
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)
bool Add(int FileHandle, bool Out)
bool Poll(int TimeoutMs=0)
void Del(int FileHandle, bool Out)
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
bool Add(int Usage, const char *FileNameSrc, const char *FileNameDst=NULL)
Adds the given FileNameSrc to the recordings handler for (later) processing.
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...
std::atomic< bool > ready
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)
bool Acceptable(in_addr_t Address)
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)
char SVDRPDefaultHost[HOST_NAME_MAX]
char SVDRPHostName[HOST_NAME_MAX]
int QueueMessage(eMessageType Type, const char *s, int Seconds=0, int Timeout=0)
Like Message(), but this function may be called from a background thread.
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.
bool TimedOut(void) const
Returns true if the last lock attempt this key was used with failed due to a timeout.
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).
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
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 Remove(int Index, int Count=1)
virtual void Append(T Data)
static int VideoDiskSpace(int *FreeMB=NULL, int *UsedMB=NULL)
#define LOCK_SCHEDULES_READ
#define LOCK_SCHEDULES_WRITE
struct __attribute__((packed))
bool EnoughFreeDiskSpaceForEdit(const char *FileName)
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
cRecordingsHandler RecordingsHandler
#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