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 char buf[INET_ADDRSTRLEN];
95 Set(inet_ntop(AF_INET, &Addr->sin_addr, buf,
sizeof(buf)), ntohs(Addr->sin_port));
100#define MAXUDPBUF 1024
112 bool Connect(
const char *Address);
147 sock =
tcp ? socket(PF_INET, SOCK_STREAM, IPPROTO_IP) : socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
154 setsockopt(
sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr,
sizeof(ReUseAddr));
157 memset(&Addr, 0,
sizeof(Addr));
158 Addr.sin_family = AF_INET;
159 Addr.sin_port = htons(
port);
161 if (bind(
sock, (sockaddr *)&Addr,
sizeof(Addr)) < 0) {
167 int Flags = fcntl(
sock, F_GETFL, 0);
174 if (fcntl(
sock, F_SETFL, Flags) < 0) {
181 if (listen(
sock, 1) < 0) {
196 sock = socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
203 memset(&Addr, 0,
sizeof(Addr));
204 Addr.sin_family = AF_INET;
205 Addr.sin_port = htons(
port);
206 if (inet_pton(AF_INET, Address, &Addr.sin_addr) != 1) {
207 esyslog(
"ERROR: invalid IP address '%s'", Address);
211 if (connect(
sock, (sockaddr *)&Addr,
sizeof(Addr)) < 0) {
217 int Flags = fcntl(
sock, F_GETFL, 0);
224 if (fcntl(
sock, F_SETFL, Flags) < 0) {
229 dbgsvdrp(
"> %s:%d server connection established\n", Address,
port);
239 int Socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
246 if (setsockopt(
Socket, SOL_SOCKET, SO_BROADCAST, &One,
sizeof(One)) < 0) {
253 memset(&Addr, 0,
sizeof(Addr));
254 Addr.sin_family = AF_INET;
255 Addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
256 Addr.sin_port = htons(
Port);
258 char buf[INET_ADDRSTRLEN];
259 dbgsvdrp(
"> %s:%d %s\n", inet_ntop(AF_INET, &Addr.sin_addr, buf,
sizeof(buf)),
Port, Dgram);
260 int Length = strlen(Dgram);
261 int Sent = sendto(
Socket, Dgram, Length, 0, (sockaddr *)&Addr,
sizeof(Addr));
265 return Sent == Length;
272 uint Size =
sizeof(Addr);
273 int NewSock = accept(
sock, (sockaddr *)&Addr, &Size);
277 const char *s =
"Access denied!\n";
278 if (write(NewSock, s, strlen(s)) < 0)
299 uint Size =
sizeof(Addr);
300 int NumBytes = recvfrom(
sock, buf,
sizeof(buf), 0, (sockaddr *)&Addr, &Size);
337 bool Send(
const char *Command);
344 bool HasAddress(
const char *Address,
int Port)
const;
362 timeout = Timeout * 1000 * 9 / 10;
412#define SVDRPResonseTimeout 5000
419 if (c ==
'\n' || c == 0x00) {
421 while (numChars > 0 && strchr(
" \t\r\n",
input[numChars - 1]))
422 input[--numChars] = 0;
429 switch (atoi(
input)) {
430 case 220:
if (numChars > 4) {
432 if (
char *t = strchr(n,
' ')) {
449 if (numChars >= 4 &&
input[3] !=
'-')
454 if (numChars >=
length - 1) {
455 int NewLength =
length + BUFSIZ;
456 if (
char *NewBuffer = (
char *)realloc(
input, NewLength)) {
466 input[numChars++] = c;
482 else if (!Response && numChars == 0)
515 if (
Execute(
"LSTT ID", &Response)) {
516 for (
int i = 0; i < Response.
Size(); i++) {
517 char *s = Response[i];
521 else if (Code == 550)
559 if (Params && *Params) {
577 error =
"invalid timeout";
580 error =
"missing server timeout";
583 error =
"missing server apiversion";
586 error =
"missing server vdrversion";
589 error =
"missing server port";
592 error =
"missing server name";
595 error =
"missing server parameters";
613 virtual void Action(
void)
override;
619 bool Execute(
const char *ServerName,
const char *Command,
cStringList *Response = NULL);
627:
cThread(
"SVDRP client handler", true)
675 bool TimersModified = Timers->StoreRemoteTimers(Client->
ServerName(), &RemoteTimers);
682 if (*PollTimersCmd) {
683 if (!Client->
Execute(PollTimersCmd))
728 if (ServerParams.
Ok())
739 time_t LastDiscover = 0;
740#define SVDRPDiscoverDelta 60
742 time_t Now = time(NULL);
761 return Client->Execute(Command, Response);
768 ServerNames->
Clear();
774 return ServerNames->
Size() > 0;
804 if ((
f = tmpfile()) != NULL) {
806 message =
"Enter EPG data, end with \".\" on a line by itself";
811 message =
"Error while opening temporary file";
824 if (strcmp(s,
".") != 0) {
834 message =
"EPG data processed";
838 message =
"Error while processing EPG data";
849#define MAXHELPTOPIC 10
850#define EITDISABLETIME 10
854 "AUDI [ <number> ]\n"
855 " Lists the currently available audio tracks in the format 'number language description'.\n"
856 " The number indicates the track type (1..32 = MP2, 33..48 = Dolby).\n"
857 " The currently selected track has its description prefixed with '*'.\n"
858 " If a number is given (which must be one of the track numbers listed)\n"
859 " audio is switched to that track.\n"
860 " Note that the list may not be fully available or current immediately after\n"
861 " switching the channel or starting a replay.",
862 "CHAN [ + | - | <number> | <name> | <id> ]\n"
863 " Switch channel up, down or to the given channel number, name or id.\n"
864 " Without option (or after successfully switching to the channel)\n"
865 " it returns the current channel number and name.",
866 "CLRE [ <number> | <name> | <id> ]\n"
867 " Clear the EPG list of the given channel number, name or id.\n"
868 " Without option it clears the entire EPG list.\n"
869 " After a CLRE command, no further EPG processing is done for 10\n"
870 " seconds, so that data sent with subsequent PUTE commands doesn't\n"
871 " interfere with data from the broadcasters.",
872 "CONN name:<name> port:<port> vdrversion:<vdrversion> apiversion:<apiversion> timeout:<timeout>\n"
873 " Used by peer-to-peer connections between VDRs to tell the other VDR\n"
874 " to establish a connection to this VDR. The name is the SVDRP host name\n"
875 " of this VDR, which may differ from its DNS name.",
876 "DELC <number> | <id>\n"
877 " Delete the channel with the given number or channel id.",
879 " Delete the recording with the given id. Before a recording can be\n"
880 " deleted, an LSTR command should have been executed in order to retrieve\n"
881 " the recording ids. The ids are unique and don't change while this\n"
882 " instance of VDR is running.\n"
883 " CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n"
884 " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
886 " Delete the timer with the given id. If this timer is currently recording,\n"
887 " the recording will be stopped without any warning.",
889 " Edit the recording with the given id. Before a recording can be\n"
890 " edited, an LSTR command should have been executed in order to retrieve\n"
891 " the recording ids.",
892 "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n"
893 " Grab the current frame and save it to the given file. Images can\n"
894 " be stored as JPEG or PNM, depending on the given file name extension.\n"
895 " The quality of the grabbed image can be in the range 0..100, where 100\n"
896 " (the default) means \"best\" (only applies to JPEG). The size parameters\n"
897 " define the size of the resulting image (default is full screen).\n"
898 " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n"
899 " data will be sent to the SVDRP connection encoded in base64. The same\n"
900 " happens if '-' (a minus sign) is given as file name, in which case the\n"
901 " image format defaults to JPEG.",
903 " The HELP command gives help info.",
904 "HITK [ <key> ... ]\n"
905 " Hit the given remote control key. Without option a list of all\n"
906 " valid key names is given. If more than one key is given, they are\n"
907 " entered into the remote control queue in the given sequence. There\n"
908 " can be up to 31 keys.",
909 "LSTC [ :ids ] [ :groups | <number> | <name> | <id> ]\n"
910 " List channels. Without option, all channels are listed. Otherwise\n"
911 " only the given channel is listed. If a name is given, all channels\n"
912 " containing the given string as part of their name are listed.\n"
913 " If ':groups' is given, all channels are listed including group\n"
914 " separators. The channel number of a group separator is always 0.\n"
915 " With ':ids' the channel ids are listed following the channel numbers.\n"
916 " The special number 0 can be given to list the current channel.",
918 " List all available devices. Each device is listed with its name and\n"
919 " whether it is currently the primary device ('P') or it implements a\n"
920 " decoder ('D') and can be used as output device.",
921 "LSTE [ <channel> ] [ now | next | at <time> ]\n"
922 " List EPG data. Without any parameters all data of all channels is\n"
923 " listed. If a channel is given (either by number or by channel ID),\n"
924 " only data for that channel is listed. 'now', 'next', or 'at <time>'\n"
925 " restricts the returned data to present events, following events, or\n"
926 " events at the given time (which must be in time_t form).",
927 "LSTR [ <id> [ path ] ]\n"
928 " List recordings. Without option, all recordings are listed. Otherwise\n"
929 " the information for the given recording is listed. If a recording\n"
930 " id and the keyword 'path' is given, the actual file name of that\n"
931 " recording's directory is listed.\n"
932 " Note that the ids of the recordings are not necessarily given in\n"
934 "LSTT [ <id> ] [ id ]\n"
935 " List timers. Without option, all timers are listed. Otherwise\n"
936 " only the timer with the given id is listed. If the keyword 'id' is\n"
937 " given, the channels will be listed with their unique channel ids\n"
938 " instead of their numbers. This command lists only the timers that are\n"
939 " defined locally on this VDR, not any remote timers from other VDRs.",
941 " Displays the given message on the OSD. The message will be queued\n"
942 " and displayed whenever this is suitable.\n",
943 "MODC <number> <settings>\n"
944 " Modify a channel. Settings must be in the same format as returned\n"
945 " by the LSTC command.",
946 "MODT <id> on | off | <settings>\n"
947 " Modify a timer. Settings must be in the same format as returned\n"
948 " by the LSTT command. The special keywords 'on' and 'off' can be\n"
949 " used to easily activate or deactivate a timer.",
950 "MOVC <number> <to>\n"
951 " Move a channel to a new position.",
952 "MOVR <id> <new name>\n"
953 " Move the recording with the given id. Before a recording can be\n"
954 " moved, an LSTR command should have been executed in order to retrieve\n"
955 " the recording ids. The ids don't change during subsequent MOVR\n"
958 " Create a new channel. Settings must be in the same format as returned\n"
959 " by the LSTC command.",
961 " Create a new timer. Settings must be in the same format as returned\n"
962 " by the LSTT command. If a timer with the same channel, day, start\n"
963 " and stop time already exists, the data of the existing timer is returned\n"
965 "NEXT [ abs | rel ]\n"
966 " Show the next timer event. If no option is given, the output will be\n"
967 " in human readable form. With option 'abs' the absolute time of the next\n"
968 " event will be given as the number of seconds since the epoch (time_t\n"
969 " format), while with option 'rel' the relative time will be given as the\n"
970 " number of seconds from now until the event. If the absolute time given\n"
971 " is smaller than the current time, or if the relative time is less than\n"
972 " zero, this means that the timer is currently recording and has started\n"
973 " at the given time. The first value in the resulting line is the id\n"
976 " Used by peer-to-peer connections between VDRs to keep the connection\n"
977 " from timing out. May be used at any time and simply returns a line of\n"
978 " the form '<hostname> is alive'.",
979 "PLAY [ <id> [ begin | <position> ] ]\n"
980 " Play the recording with the given id. Before a recording can be\n"
981 " played, an LSTR command should have been executed in order to retrieve\n"
982 " the recording ids.\n"
983 " The keyword 'begin' plays the recording from its very beginning, while\n"
984 " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n"
985 " position. If neither 'begin' nor a <position> are given, replay is resumed\n"
986 " at the position where any previous replay was stopped, or from the beginning\n"
987 " by default. To control or stop the replay session, use the usual remote\n"
988 " control keypresses via the HITK command.\n"
989 " Without any parameters PLAY returns the id and title of the recording that\n"
990 " is currently being played (if any).",
991 "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n"
992 " Send a command to a plugin.\n"
993 " The PLUG command without any parameters lists all plugins.\n"
994 " If only a name is given, all commands known to that plugin are listed.\n"
995 " If a command is given (optionally followed by parameters), that command\n"
996 " is sent to the plugin, and the result will be displayed.\n"
997 " The keyword 'help' lists all the SVDRP commands known to the named plugin.\n"
998 " If 'help' is followed by a command, the detailed help for that command is\n"
999 " given. The keyword 'main' initiates a call to the main menu function of the\n"
1001 "POLL <name> timers\n"
1002 " Used by peer-to-peer connections between VDRs to inform other machines\n"
1003 " about changes to timers. The receiving VDR shall use LSTT to query the\n"
1004 " remote machine with the given name about its timers and update its list\n"
1005 " of timers accordingly.\n",
1006 "PRIM [ <number> ]\n"
1007 " Make the device with the given number the primary device.\n"
1008 " Without option it returns the currently active primary device in the same\n"
1009 " format as used by the LSTD command.",
1011 " Put data into the EPG list. The data entered has to strictly follow the\n"
1012 " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n"
1013 " by itself terminates the input and starts processing of the data (all\n"
1014 " entered data is buffered until the terminating '.' is seen).\n"
1015 " If a file name is given, epg data will be read from this file (which\n"
1016 " must be accessible under the given name from the machine VDR is running\n"
1017 " on). In case of file input, no terminating '.' shall be given.\n",
1018 "REMO [ on | off ]\n"
1019 " Turns the remote control on or off. Without a parameter, the current\n"
1020 " status of the remote control is reported.",
1022 " Forces an EPG scan. If this is a single DVB device system, the scan\n"
1023 " will be done on the primary device unless it is currently recording.",
1025 " Return information about disk usage (total, free, percent).",
1027 " Updates a timer. Settings must be in the same format as returned\n"
1028 " by the LSTT command. If a timer with the same channel, day, start\n"
1029 " and stop time does not yet exist, it will be created.",
1031 " Initiates a re-read of the recordings directory, which is the SVDRP\n"
1032 " equivalent to 'touch .update'.",
1033 "VOLU [ <number> | + | - | mute ]\n"
1034 " Set the audio volume to the given number (which is limited to the range\n"
1035 " 0...255). If the special options '+' or '-' are given, the volume will\n"
1036 " be turned up or down, respectively. The option 'mute' will toggle the\n"
1037 " audio muting. If no option is given, the current audio volume level will\n"
1040 " Exit vdr (SVDRP).\n"
1041 " You can also hit Ctrl-D to exit.",
1069 const char *q = HelpPage;
1072 uint n = q - HelpPage;
1073 if (n >=
sizeof(topic))
1074 n =
sizeof(topic) - 1;
1075 strncpy(topic, HelpPage, n);
1089 if (strcasecmp(Cmd, t) == 0)
1109 void Close(
bool SendReply =
false,
bool Timeout =
false);
1110 bool Send(
const char *s);
1113 void CmdAUDI(const
char *Option);
1114 void CmdCHAN(const
char *Option);
1115 void CmdCLRE(const
char *Option);
1116 void CmdCONN(const
char *Option);
1117 void CmdDELC(const
char *Option);
1118 void CmdDELR(const
char *Option);
1119 void CmdDELT(const
char *Option);
1120 void CmdEDIT(const
char *Option);
1121 void CmdGRAB(const
char *Option);
1122 void CmdHELP(const
char *Option);
1123 void CmdHITK(const
char *Option);
1124 void CmdLSTC(const
char *Option);
1125 void CmdLSTD(const
char *Option);
1126 void CmdLSTE(const
char *Option);
1127 void CmdLSTR(const
char *Option);
1128 void CmdLSTT(const
char *Option);
1129 void CmdMESG(const
char *Option);
1130 void CmdMODC(const
char *Option);
1131 void CmdMODT(const
char *Option);
1132 void CmdMOVC(const
char *Option);
1133 void CmdMOVR(const
char *Option);
1134 void CmdNEWC(const
char *Option);
1135 void CmdNEWT(const
char *Option);
1136 void CmdNEXT(const
char *Option);
1137 void CmdPING(const
char *Option);
1138 void CmdPLAY(const
char *Option);
1139 void CmdPLUG(const
char *Option);
1140 void CmdPOLL(const
char *Option);
1141 void CmdPRIM(const
char *Option);
1142 void CmdPUTE(const
char *Option);
1143 void CmdREMO(const
char *Option);
1144 void CmdSCAN(const
char *Option);
1145 void CmdSTAT(const
char *Option);
1146 void CmdUPDT(const
char *Option);
1147 void CmdUPDR(const
char *Option);
1148 void CmdVOLU(const
char *Option);
1170 time_t now = time(NULL);
1212 char *buffer = NULL;
1215 if (vasprintf(&buffer, fmt, ap) >= 0) {
1218 char *n = strchr(s,
'\n');
1222 if (Code < 0 || n && *(n + 1))
1226 s = n ? n + 1 : NULL;
1230 Reply(451,
"Bad format - looks like a programming error!");
1237 Reply(451,
"Zero return code - looks like a programming error!");
1253 const int TopicsPerLine = 5;
1255 for (
int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
1258 q += sprintf(q,
" ");
1259 for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
1260 const char *topic =
GetHelpTopic(hp[(y * TopicsPerLine + x)]);
1265 Reply(-214,
"%s", buffer);
1273 int o = strtol(Option, NULL, 10);
1276 if (TrackId && TrackId->
id) {
1281 Reply(501,
"Audio track \"%s\" not available", Option);
1284 Reply(501,
"Invalid audio track \"%s\"", Option);
1287 Reply(501,
"Error in audio track \"%s\"", Option);
1295 if (TrackId && TrackId->
id) {
1297 Reply(-250,
"%s", *s);
1302 Reply(250,
"%s", *s);
1304 Reply(550,
"No audio tracks available");
1315 int o = strtol(Option, NULL, 10);
1319 else if (strcmp(Option,
"-") == 0) {
1326 else if (strcmp(Option,
"+") == 0) {
1334 n = Channel->Number();
1336 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1337 if (!Channel->GroupSep()) {
1338 if (strcasecmp(Channel->Name(), Option) == 0) {
1339 n = Channel->Number();
1346 Reply(501,
"Undefined channel \"%s\"", Option);
1350 if (
const cChannel *Channel = Channels->GetByNumber(n)) {
1352 Reply(554,
"Error switching to channel \"%d\"", Channel->Number());
1357 Reply(550,
"Unable to find channel \"%s\"", Option);
1365 Reply(250,
"%d %s", Channel->Number(), Channel->Name());
1377 int o = strtol(Option, NULL, 10);
1379 if (
const cChannel *Channel = Channels->GetByNumber(o))
1380 ChannelID = Channel->GetChannelID();
1386 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1387 if (!Channel->GroupSep()) {
1388 if (strcasecmp(Channel->Name(), Option) == 0) {
1389 ChannelID = Channel->GetChannelID();
1400 for (
cSchedule *p = Schedules->First(); p; p = Schedules->
Next(p)) {
1401 if (p->ChannelID() == ChannelID) {
1407 for (
cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
1408 if (ChannelID == Timer->Channel()->GetChannelID().
ClrRid())
1409 Timer->SetEvent(NULL);
1413 Reply(250,
"EPG data of channel \"%s\" cleared", Option);
1416 Reply(550,
"No EPG data found for channel \"%s\"", Option);
1421 Reply(501,
"Undefined channel \"%s\"", Option);
1426 for (
cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer))
1427 Timer->SetEvent(NULL);
1428 for (
cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->
Next(Schedule))
1429 Schedule->Cleanup(INT_MAX);
1431 Reply(250,
"EPG data cleared");
1440 if (ServerParams.
Ok()) {
1446 Reply(501,
"Error in server parameters: %s", ServerParams.
Error());
1449 Reply(451,
"No SVDRP client handler");
1452 Reply(501,
"Missing server parameters");
1460 Channels->SetExplicitModify();
1463 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1467 if (
const cTimer *Timer = Timers->UsesChannel(Channel)) {
1468 Reply(550,
"Channel \"%s\" is in use by timer %s", Option, *Timer->ToDescr());
1472 cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
1473 if (CurrentChannel && Channel == CurrentChannel) {
1474 int n = Channels->GetNextNormal(CurrentChannel->
Index());
1476 n = Channels->GetPrevNormal(CurrentChannel->
Index());
1478 Reply(501,
"Can't delete channel \"%s\" - list would be empty", Option);
1481 CurrentChannel = Channels->Get(n);
1482 CurrentChannelNr = 0;
1484 Channels->Del(Channel);
1485 Channels->ReNumber();
1486 Channels->SetModifiedByUser();
1487 Channels->SetModified();
1489 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
1491 Channels->SwitchTo(CurrentChannel->
Number());
1495 Reply(250,
"Channel \"%s\" deleted", Option);
1498 Reply(501,
"Channel \"%s\" not defined", Option);
1501 Reply(501,
"Missing channel number or id");
1510 return cString::sprintf(
"Recording \"%s\" is being replayed", RecordingId);
1511 else if ((Reason &
ruCut) != 0)
1514 return cString::sprintf(
"Recording \"%s\" is being copied/moved", RecordingId);
1525 Recordings->SetExplicitModify();
1526 if (
cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1527 if (
int RecordingInUse = Recording->IsInUse())
1530 if (Recording->Delete()) {
1532 Recordings->Del(Recording,
false);
1533 DeletedRecordings->Add(Recording);
1534 Recordings->SetModified();
1536 Reply(250,
"Recording \"%s\" deleted", Option);
1539 Reply(554,
"Error while deleting recording!");
1543 Reply(550,
"Recording \"%s\" not found", Option);
1546 Reply(501,
"Error in recording id \"%s\"", Option);
1549 Reply(501,
"Missing recording id");
1557 Timers->SetExplicitModify();
1558 if (
cTimer *Timer = Timers->GetById(strtol(Option, NULL, 10))) {
1559 if (Timer->Recording())
1561 Timer->TriggerRespawn();
1563 Timers->SetModified();
1565 Reply(250,
"Timer \"%s\" deleted", Option);
1568 Reply(501,
"Timer \"%s\" not defined", Option);
1571 Reply(501,
"Error in timer number \"%s\"", Option);
1574 Reply(501,
"Missing timer number");
1582 if (
const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1584 if (Marks.
Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.
Count()) {
1586 Reply(550,
"Not enough free disk space to start editing process");
1588 Reply(250,
"Editing recording \"%s\" [%s]", Option, Recording->Title());
1590 Reply(554,
"Can't start editing process");
1593 Reply(554,
"No editing marks defined");
1596 Reply(550,
"Recording \"%s\" not found", Option);
1599 Reply(501,
"Error in recording id \"%s\"", Option);
1602 Reply(501,
"Missing recording id");
1607 const char *FileName = NULL;
1609 int Quality = -1, SizeX = -1, SizeY = -1;
1611 char buf[strlen(Option) + 1];
1612 char *p = strcpy(buf, Option);
1613 const char *delim =
" \t";
1615 FileName = strtok_r(p, delim, &strtok_next);
1617 const char *Extension = strrchr(FileName,
'.');
1619 if (strcasecmp(Extension,
".jpg") == 0 || strcasecmp(Extension,
".jpeg") == 0)
1621 else if (strcasecmp(Extension,
".pnm") == 0)
1624 Reply(501,
"Unknown image type \"%s\"", Extension + 1);
1627 if (Extension == FileName)
1630 else if (strcmp(FileName,
"-") == 0)
1633 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1634 if (strcasecmp(p,
"JPEG") == 0 || strcasecmp(p,
"PNM") == 0) {
1636 p = strtok_r(NULL, delim, &strtok_next);
1642 Reply(501,
"Invalid quality \"%s\"", p);
1648 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1652 Reply(501,
"Invalid sizex \"%s\"", p);
1655 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1659 Reply(501,
"Invalid sizey \"%s\"", p);
1664 Reply(501,
"Missing sizey");
1668 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1669 Reply(501,
"Unexpected parameter \"%s\"", p);
1673 char RealFileName[PATH_MAX];
1678 const char *slash = strrchr(FileName,
'/');
1683 slash = strrchr(FileName,
'/');
1686 char *r = realpath(t, RealFileName);
1689 Reply(501,
"Invalid file name \"%s\"", FileName);
1692 strcat(RealFileName, slash);
1693 FileName = RealFileName;
1695 Reply(501,
"Invalid file name \"%s\"", FileName);
1700 Reply(550,
"Grabbing to file not allowed (use \"GRAB -\" instead)");
1709 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
1711 if (
safe_write(fd, Image, ImageSize) == ImageSize) {
1713 Reply(250,
"Grabbed image %s", Option);
1717 Reply(451,
"Can't write to '%s'", FileName);
1723 Reply(451,
"Can't open '%s'", FileName);
1729 while ((s = Base64.
NextLine()) != NULL)
1730 Reply(-216,
"%s", s);
1731 Reply(216,
"Grabbed image %s", Option);
1736 Reply(451,
"Grab image failed");
1739 Reply(501,
"Missing filename");
1747 Reply(-214,
"%s", hp);
1749 Reply(504,
"HELP topic \"%s\" unknown", Option);
1755 Reply(-214,
"Topics:");
1764 Reply(-214,
"To report bugs in the implementation send email to");
1765 Reply(-214,
" vdr-bugs@tvdr.de");
1767 Reply(214,
"End of HELP info");
1774 Reply(550,
"Remote control currently disabled (key \"%s\" discarded)", Option);
1777 char buf[strlen(Option) + 1];
1778 strcpy(buf, Option);
1779 const char *delim =
" \t";
1781 char *p = strtok_r(buf, delim, &strtok_next);
1787 Reply(451,
"Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
1792 Reply(504,
"Unknown key: \"%s\"", p);
1796 p = strtok_r(NULL, delim, &strtok_next);
1798 Reply(250,
"Key%s \"%s\" accepted", NumKeys > 1 ?
"s" :
"", Option);
1801 Reply(-214,
"Valid <key> names for the HITK command:");
1802 for (
int i = 0; i <
kNone; i++) {
1805 Reply(214,
"End of key list");
1812 bool WithChannelIds =
startswith(Option,
":ids") && (Option[4] ==
' ' || Option[4] == 0);
1815 bool WithGroupSeps = strcasecmp(Option,
":groups") == 0;
1816 if (*Option && !WithGroupSeps) {
1818 int n = strtol(Option, NULL, 10);
1821 if (
const cChannel *Channel = Channels->GetByNumber(n))
1822 Reply(250,
"%d%s%s %s", Channel->Number(), WithChannelIds ?
" " :
"", WithChannelIds ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1824 Reply(501,
"Channel \"%s\" not defined", Option);
1829 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1830 if (!Channel->GroupSep()) {
1831 if (strcasestr(Channel->Name(), Option)) {
1842 Reply(501,
"Channel \"%s\" not defined", Option);
1846 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1848 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());
1849 else if (!Channel->GroupSep())
1850 Reply(Channel->Number() <
cChannels::MaxNumber() ? -250 : 250,
"%d%s%s %s", Channel->Number(), WithChannelIds ?
" " :
"", WithChannelIds ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1854 Reply(550,
"No channels defined");
1862 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());
1866 Reply(550,
"No devices found");
1877 char buf[strlen(Option) + 1];
1878 strcpy(buf, Option);
1879 const char *delim =
" \t";
1881 char *p = strtok_r(buf, delim, &strtok_next);
1882 while (p && DumpMode ==
dmAll) {
1883 if (strcasecmp(p,
"NOW") == 0)
1885 else if (strcasecmp(p,
"NEXT") == 0)
1887 else if (strcasecmp(p,
"AT") == 0) {
1889 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1891 AtTime = strtol(p, NULL, 10);
1893 Reply(501,
"Invalid time");
1898 Reply(501,
"Missing time");
1902 else if (!Schedule) {
1905 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1909 Schedule = Schedules->GetSchedule(Channel);
1911 Reply(550,
"No schedule found");
1916 Reply(550,
"Channel \"%s\" not defined", p);
1921 Reply(501,
"Unknown option: \"%s\"", p);
1924 p = strtok_r(NULL, delim, &strtok_next);
1929 FILE *f = fdopen(fd,
"w");
1932 Schedule->
Dump(Channels, f,
"215-", DumpMode, AtTime);
1934 Schedules->Dump(f,
"215-", DumpMode, AtTime);
1936 Reply(215,
"End of EPG data");
1940 Reply(451,
"Can't open file connection");
1945 Reply(451,
"Can't dup stream descriptor");
1954 char buf[strlen(Option) + 1];
1955 strcpy(buf, Option);
1956 const char *delim =
" \t";
1958 char *p = strtok_r(buf, delim, &strtok_next);
1962 Number = strtol(p, NULL, 10);
1964 Reply(501,
"Error in recording id \"%s\"", Option);
1968 else if (strcasecmp(p,
"PATH") == 0)
1971 Reply(501,
"Unknown option: \"%s\"", p);
1974 p = strtok_r(NULL, delim, &strtok_next);
1977 if (
const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1978 FILE *f = fdopen(
file,
"w");
1981 Reply(250,
"%s", Recording->FileName());
1983 Recording->Info()->Write(f,
"215-");
1985 Reply(215,
"End of recording information");
1990 Reply(451,
"Can't open file connection");
1993 Reply(550,
"Recording \"%s\" not found", Option);
1996 else if (Recordings->Count()) {
1997 const cRecording *Recording = Recordings->First();
1999 Reply(Recording == Recordings->Last() ? 250 : -250,
"%d %s", Recording->
Id(), Recording->
Title(
' ',
true));
2000 Recording = Recordings->
Next(Recording);
2004 Reply(550,
"No recordings available");
2010 bool UseChannelId =
false;
2012 char buf[strlen(Option) + 1];
2013 strcpy(buf, Option);
2014 const char *delim =
" \t";
2016 char *p = strtok_r(buf, delim, &strtok_next);
2019 Id = strtol(p, NULL, 10);
2020 else if (strcasecmp(p,
"ID") == 0)
2021 UseChannelId =
true;
2023 Reply(501,
"Unknown option: \"%s\"", p);
2026 p = strtok_r(NULL, delim, &strtok_next);
2031 for (
const cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
2032 if (!Timer->Remote()) {
2033 if (Timer->Id() == Id) {
2034 Reply(250,
"%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2039 Reply(501,
"Timer \"%s\" not defined", Option);
2043 const cTimer *LastLocalTimer = Timers->Last();
2044 while (LastLocalTimer) {
2045 if (LastLocalTimer->
Remote())
2046 LastLocalTimer = Timers->
Prev(LastLocalTimer);
2050 if (LastLocalTimer) {
2051 for (
const cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
2052 if (!Timer->Remote())
2053 Reply(Timer != LastLocalTimer ? -250 : 250,
"%d %s", Timer->
Id(), *Timer->ToText(UseChannelId));
2054 if (Timer == LastLocalTimer)
2060 Reply(550,
"No timers defined");
2068 Reply(250,
"Message queued");
2071 Reply(501,
"Missing message");
2078 int n = strtol(Option, &tail, 10);
2079 if (tail && tail != Option) {
2082 Channels->SetExplicitModify();
2083 if (
cChannel *Channel = Channels->GetByNumber(n)) {
2085 if (ch.
Parse(tail)) {
2086 if (Channels->HasUniqueChannelID(&ch, Channel)) {
2088 Channels->ReNumber();
2089 Channels->SetModifiedByUser();
2090 Channels->SetModified();
2092 Reply(250,
"%d %s", Channel->Number(), *Channel->ToText());
2095 Reply(501,
"Channel settings are not unique");
2098 Reply(501,
"Error in channel settings");
2101 Reply(501,
"Channel \"%d\" not defined", n);
2104 Reply(501,
"Error in channel number");
2107 Reply(501,
"Missing channel settings");
2114 int Id = strtol(Option, &tail, 10);
2115 if (tail && tail != Option) {
2118 Timers->SetExplicitModify();
2119 if (
cTimer *Timer = Timers->GetById(Id)) {
2122 if (strcasecmp(tail,
"ON") == 0)
2124 else if (strcasecmp(tail,
"OFF") == 0)
2126 else if (!t.
Parse(tail)) {
2127 Reply(501,
"Error in timer settings");
2131 Reply(550,
"Timer is recording");
2139 Timers->SetModified();
2141 if (Timer->IsPatternTimer())
2142 Timer->SetEvent(NULL);
2143 Timer->TriggerRespawn();
2144 Reply(250,
"%d %s", Timer->Id(), *Timer->ToText(
true));
2147 Reply(501,
"Timer \"%d\" not defined", Id);
2150 Reply(501,
"Error in timer id");
2153 Reply(501,
"Missing timer settings");
2160 int From = strtol(Option, &tail, 10);
2161 if (tail && tail != Option) {
2163 if (tail && tail != Option) {
2166 Channels->SetExplicitModify();
2167 int To = strtol(tail, NULL, 10);
2169 const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
2170 cChannel *FromChannel = Channels->GetByNumber(From);
2172 cChannel *ToChannel = Channels->GetByNumber(To);
2174 int FromNumber = FromChannel->
Number();
2175 int ToNumber = ToChannel->
Number();
2176 if (FromNumber != ToNumber) {
2177 if (Channels->MoveNeedsDecrement(FromChannel, ToChannel))
2178 ToChannel = Channels->
Prev(ToChannel);
2179 Channels->Move(FromChannel, ToChannel);
2180 Channels->ReNumber();
2181 Channels->SetModifiedByUser();
2182 Channels->SetModified();
2183 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
2185 Channels->SwitchTo(CurrentChannel->
Number());
2190 Reply(250,
"Channel \"%d\" moved to \"%d\"", From, To);
2193 Reply(501,
"Can't move channel to same position");
2196 Reply(501,
"Channel \"%d\" not defined", To);
2199 Reply(501,
"Channel \"%d\" not defined", From);
2202 Reply(501,
"Error in channel number");
2205 Reply(501,
"Error in channel number");
2208 Reply(501,
"Missing channel number");
2214 char *opt = strdup(Option);
2217 while (*option && !isspace(*option))
2223 Recordings->SetExplicitModify();
2224 if (
cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2225 if (
int RecordingInUse = Recording->IsInUse())
2231 cString oldName = Recording->Name();
2232 if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) {
2233 Recordings->SetModified();
2234 Recordings->TouchUpdate();
2235 Reply(250,
"Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name());
2238 Reply(554,
"Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
2241 Reply(501,
"Missing new recording name");
2245 Reply(550,
"Recording \"%s\" not found", num);
2248 Reply(501,
"Error in recording id \"%s\"", num);
2252 Reply(501,
"Missing recording id");
2259 if (ch.
Parse(Option)) {
2261 Channels->SetExplicitModify();
2262 if (Channels->HasUniqueChannelID(&ch)) {
2265 Channels->Add(channel);
2266 Channels->ReNumber();
2267 Channels->SetModifiedByUser();
2268 Channels->SetModified();
2273 Reply(501,
"Channel settings are not unique");
2276 Reply(501,
"Error in channel settings");
2279 Reply(501,
"Missing channel settings");
2286 if (Timer->
Parse(Option)) {
2288 const cTimer *t = Timers->GetTimer(Timer);
2304 Reply(501,
"Error in timer settings");
2308 Reply(501,
"Missing timer settings");
2314 if (
const cTimer *t = Timers->GetNextActiveTimer()) {
2315 time_t Start = t->StartTime();
2319 else if (strcasecmp(Option,
"ABS") == 0)
2320 Reply(250,
"%d %jd", Id, intmax_t(Start));
2321 else if (strcasecmp(Option,
"REL") == 0)
2322 Reply(250,
"%d %jd", Id, intmax_t(Start - time(NULL)));
2324 Reply(501,
"Unknown option: \"%s\"", Option);
2327 Reply(550,
"No active timers");
2338 char *opt = strdup(Option);
2341 while (*option && !isspace(*option))
2348 if (
const cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2349 cString FileName = Recording->FileName();
2350 cString Title = Recording->Title();
2351 int FramesPerSecond = Recording->FramesPerSecond();
2352 bool IsPesRecording = Recording->IsPesRecording();
2360 if (strcasecmp(option,
"BEGIN") != 0)
2371 Reply(250,
"Playing recording \"%s\" [%s]", num, *Title);
2375 Reply(550,
"Recording \"%s\" not found", num);
2380 Reply(501,
"Error in recording id \"%s\"", num);
2385 if (
const cRecording *Recording = Recordings->GetByName(FileName))
2386 Reply(250,
"%d %s", Recording->Id(), Recording->Title(
' ',
true));
2388 Reply(550,
"Recording \"%s\" not found", FileName);
2391 Reply(550,
"Not playing");
2397 char *opt = strdup(Option);
2399 char *option = name;
2400 while (*option && !isspace(*option))
2409 while (*option && !isspace(*option))
2415 if (!*cmd || strcasecmp(cmd,
"HELP") == 0) {
2416 if (*cmd && *option) {
2419 Reply(-214,
"%s", hp);
2420 Reply(214,
"End of HELP info");
2423 Reply(504,
"HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->
Name());
2429 Reply(-214,
"SVDRP commands:");
2431 Reply(214,
"End of HELP info");
2434 Reply(214,
"This plugin has no SVDRP commands");
2437 else if (strcasecmp(cmd,
"MAIN") == 0) {
2439 Reply(250,
"Initiated call to main menu function of plugin \"%s\"", plugin->
Name());
2441 Reply(550,
"A plugin call is already pending - please try again later");
2444 int ReplyCode = 900;
2447 Reply(abs(ReplyCode),
"%s", *s);
2449 Reply(500,
"Command unrecognized: \"%s\"", cmd);
2453 Reply(550,
"Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
2457 Reply(-214,
"Available plugins:");
2461 Reply(214,
"End of plugin list");
2468 char buf[strlen(Option) + 1];
2469 char *p = strcpy(buf, Option);
2470 const char *delim =
" \t";
2472 char *RemoteName = strtok_r(p, delim, &strtok_next);
2473 char *ListName = strtok_r(NULL, delim, &strtok_next);
2476 if (strcasecmp(ListName,
"timers") == 0) {
2481 Reply(501,
"Unknown list name: \"%s\"", ListName);
2484 Reply(501,
"Missing list name");
2487 Reply(501,
"No SVDRP client connections");
2490 Reply(501,
"Missing parameters");
2498 int o = strtol(Option, NULL, 10);
2502 Reply(501,
"Invalid device number \"%s\"", Option);
2505 Reply(501,
"Invalid parameter \"%s\"", Option);
2508 Reply(250,
"Primary device set to %d", n);
2513 Reply(250,
"%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ?
"D" :
"-", d->DeviceNumber() + 1 ==
Setup.
PrimaryDVB ?
"P" :
"-", *d->DeviceName());
2515 Reply(501,
"Failed to get primary device");
2522 FILE *f = fopen(Option,
"r");
2526 Reply(250,
"EPG data processed from \"%s\"", Option);
2529 Reply(451,
"Error while processing EPG from \"%s\"", Option);
2533 Reply(501,
"Cannot open file \"%s\"", Option);
2547 if (!strcasecmp(Option,
"ON")) {
2549 Reply(250,
"Remote control enabled");
2551 else if (!strcasecmp(Option,
"OFF")) {
2553 Reply(250,
"Remote control disabled");
2556 Reply(501,
"Invalid Option \"%s\"", Option);
2565 Reply(250,
"EPG scan triggered");
2571 if (strcasecmp(Option,
"DISK") == 0) {
2574 Reply(250,
"%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
2577 Reply(501,
"Invalid Option \"%s\"", Option);
2580 Reply(501,
"No option given");
2587 if (Timer->
Parse(Option)) {
2589 if (
cTimer *t = Timers->GetTimer(Timer)) {
2609 Reply(501,
"Error in timer settings");
2613 Reply(501,
"Missing timer settings");
2619 Recordings->Update(
false);
2620 Reply(250,
"Re-read of recordings directory triggered");
2628 else if (strcmp(Option,
"+") == 0)
2630 else if (strcmp(Option,
"-") == 0)
2632 else if (strcasecmp(Option,
"MUTE") == 0)
2635 Reply(501,
"Unknown option: \"%s\"", Option);
2640 Reply(250,
"Audio is mute");
2645#define CMD(c) (strcasecmp(Cmd, c) == 0)
2662 while (*s && !isspace(*s))
2704 else Reply(500,
"Command unrecognized: \"%s\"", Cmd);
2714 if (c ==
'\n' || c == 0x00) {
2730 else if (c == 0x04 &&
numChars == 0) {
2734 else if (c == 0x08 || c == 0x7F) {
2739 else if (c <= 0x03 || c == 0x0D) {
2744 int NewLength =
length + BUFSIZ;
2745 if (
char *NewBuffer = (
char *)realloc(
cmdLine, NewLength)) {
2794 virtual void Action(
void)
override;
2804:
cThread(
"SVDRP server handler", true)
2892 bool Result =
false;
2901 bool Result =
false;
2914 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