22#include <netinet/in.h>
27#include <sys/socket.h>
45#define dbgsvdrp(a...) if (DumpSVDRPDataTransfer) fprintf(stderr, a)
70 void Set(
const sockaddr *SockAddr);
93 const sockaddr_in *Addr = (sockaddr_in *)SockAddr;
94 Set(inet_ntoa(Addr->sin_addr), ntohs(Addr->sin_port));
111 bool Connect(
const char *Address);
146 sock =
tcp ? socket(PF_INET, SOCK_STREAM, IPPROTO_IP) : socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
153 setsockopt(
sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr,
sizeof(ReUseAddr));
156 memset(&Addr, 0,
sizeof(Addr));
157 Addr.sin_family = AF_INET;
158 Addr.sin_port = htons(
port);
159 Addr.sin_addr.s_addr =
SVDRPhosts.LocalhostOnly() ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY);
160 if (bind(
sock, (sockaddr *)&Addr,
sizeof(Addr)) < 0) {
166 int Flags = fcntl(
sock, F_GETFL, 0);
172 if (fcntl(
sock, F_SETFL, Flags) < 0) {
178 if (listen(
sock, 1) < 0) {
183 isyslog(
"SVDRP %s listening on port %d/%s",
Setup.SVDRPHostName,
port,
tcp ?
"tcp" :
"udp");
192 sock = socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
199 memset(&Addr, 0,
sizeof(Addr));
200 Addr.sin_family = AF_INET;
201 Addr.sin_port = htons(
port);
202 Addr.sin_addr.s_addr = inet_addr(Address);
203 if (connect(
sock, (sockaddr *)&Addr,
sizeof(Addr)) < 0) {
209 int Flags = fcntl(
sock, F_GETFL, 0);
215 if (fcntl(
sock, F_SETFL, Flags) < 0) {
219 dbgsvdrp(
"> %s:%d server connection established\n", Address,
port);
220 isyslog(
"SVDRP %s > %s:%d server connection established",
Setup.SVDRPHostName, Address,
port);
229 int Socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
236 if (setsockopt(
Socket, SOL_SOCKET, SO_BROADCAST, &One,
sizeof(One)) < 0) {
243 memset(&Addr, 0,
sizeof(Addr));
244 Addr.sin_family = AF_INET;
245 Addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
246 Addr.sin_port = htons(
Port);
248 dbgsvdrp(
"> %s:%d %s\n", inet_ntoa(Addr.sin_addr),
Port, Dgram);
249 int Length = strlen(Dgram);
250 int Sent = sendto(
Socket, Dgram, Length, 0, (sockaddr *)&Addr,
sizeof(Addr));
254 return Sent == Length;
261 uint Size =
sizeof(Addr);
262 int NewSock = accept(
sock, (sockaddr *)&Addr, &Size);
264 bool Accepted =
SVDRPhosts.Acceptable(Addr.sin_addr.s_addr);
266 const char *s =
"Access denied!\n";
267 if (write(NewSock, s, strlen(s)) < 0)
273 dbgsvdrp(
"< %s client connection %s\n",
lastIpAddress.Connection(), Accepted ?
"accepted" :
"DENIED");
274 isyslog(
"SVDRP %s < %s client connection %s",
Setup.SVDRPHostName,
lastIpAddress.Connection(), Accepted ?
"accepted" :
"DENIED");
288 uint Size =
sizeof(Addr);
289 int NumBytes = recvfrom(
sock, buf,
sizeof(buf), 0, (sockaddr *)&Addr, &Size);
293 if (!
SVDRPhosts.Acceptable(Addr.sin_addr.s_addr)) {
301 if (strcmp(
strgetval(buf,
"name",
':'),
Setup.SVDRPHostName) != 0) {
326 bool Send(
const char *Command);
333 bool HasAddress(
const char *Address,
int Port)
const;
351 timeout = Timeout * 1000 * 9 / 10;
355 if (
socket.Connect(Address)) {
401#define SVDRPResonseTimeout 5000
404 if (
file.Ready(
false)) {
408 if (c ==
'\n' || c == 0x00) {
410 while (numChars > 0 && strchr(
" \t\r\n",
input[numChars - 1]))
411 input[--numChars] = 0;
418 switch (atoi(
input)) {
419 case 220:
if (numChars > 4) {
421 if (
char *t = strchr(n,
' ')) {
438 if (numChars >= 4 &&
input[3] !=
'-')
443 if (numChars >=
length - 1) {
444 int NewLength =
length + BUFSIZ;
445 if (
char *NewBuffer = (
char *)realloc(
input, NewLength)) {
455 input[numChars++] = c;
471 else if (!Response && numChars == 0)
477 return file.IsOpen();
504 if (
Execute(
"LSTT ID", &Response)) {
505 for (
int i = 0; i < Response.
Size(); i++) {
506 char *s = Response[i];
510 else if (Code == 550)
548 if (Params && *Params) {
566 error =
"invalid timeout";
569 error =
"missing server timeout";
572 error =
"missing server apiversion";
575 error =
"missing server vdrversion";
578 error =
"missing server port";
581 error =
"missing server name";
584 error =
"missing server parameters";
602 virtual void Action(
void)
override;
608 bool Execute(
const char *ServerName,
const char *Command,
cStringList *Response = NULL);
616:
cThread(
"SVDRP client handler", true)
664 bool TimersModified = Timers->StoreRemoteTimers(Client->
ServerName(), &RemoteTimers);
671 if (*PollTimersCmd) {
672 if (!Client->
Execute(PollTimersCmd))
696 if (ServerParams.
Host() && strcmp(ServerParams.
Host(),
Setup.SVDRPHostName) != 0)
717 if (ServerParams.
Ok())
728 time_t LastDiscover = 0;
729#define SVDRPDiscoverDelta 60
731 time_t Now = time(NULL);
750 return Client->Execute(Command, Response);
757 ServerNames->
Clear();
763 return ServerNames->
Size() > 0;
793 if ((
f = tmpfile()) != NULL) {
795 message =
"Enter EPG data, end with \".\" on a line by itself";
800 message =
"Error while opening temporary file";
813 if (strcmp(s,
".") != 0) {
823 message =
"EPG data processed";
827 message =
"Error while processing EPG data";
838#define MAXHELPTOPIC 10
839#define EITDISABLETIME 10
843 "AUDI [ <number> ]\n"
844 " Lists the currently available audio tracks in the format 'number language description'.\n"
845 " The number indicates the track type (1..32 = MP2, 33..48 = Dolby).\n"
846 " The currently selected track has its description prefixed with '*'.\n"
847 " If a number is given (which must be one of the track numbers listed)\n"
848 " audio is switched to that track.\n"
849 " Note that the list may not be fully available or current immediately after\n"
850 " switching the channel or starting a replay.",
851 "CHAN [ + | - | <number> | <name> | <id> ]\n"
852 " Switch channel up, down or to the given channel number, name or id.\n"
853 " Without option (or after successfully switching to the channel)\n"
854 " it returns the current channel number and name.",
855 "CLRE [ <number> | <name> | <id> ]\n"
856 " Clear the EPG list of the given channel number, name or id.\n"
857 " Without option it clears the entire EPG list.\n"
858 " After a CLRE command, no further EPG processing is done for 10\n"
859 " seconds, so that data sent with subsequent PUTE commands doesn't\n"
860 " interfere with data from the broadcasters.",
861 "CONN name:<name> port:<port> vdrversion:<vdrversion> apiversion:<apiversion> timeout:<timeout>\n"
862 " Used by peer-to-peer connections between VDRs to tell the other VDR\n"
863 " to establish a connection to this VDR. The name is the SVDRP host name\n"
864 " of this VDR, which may differ from its DNS name.",
865 "CPYR <number> <new name>\n"
866 " Copy the recording with the given number. Before a recording can be\n"
867 " copied, an LSTR command must have been executed in order to retrieve\n"
868 " the recording numbers.\n",
869 "DELC <number> | <id>\n"
870 " Delete the channel with the given number or channel id.",
872 " Delete the recording with the given id. Before a recording can be\n"
873 " deleted, an LSTR command should have been executed in order to retrieve\n"
874 " the recording ids. The ids are unique and don't change while this\n"
875 " instance of VDR is running.\n"
876 " CAUTION: THERE IS NO CONFIRMATION PROMPT WHEN DELETING A\n"
877 " RECORDING - BE SURE YOU KNOW WHAT YOU ARE DOING!",
879 " Delete the timer with the given id. If this timer is currently recording,\n"
880 " the recording will be stopped without any warning.",
882 " Edit the recording with the given id. Before a recording can be\n"
883 " edited, an LSTR command should have been executed in order to retrieve\n"
884 " the recording ids.",
885 "GRAB <filename> [ <quality> [ <sizex> <sizey> ] ]\n"
886 " Grab the current frame and save it to the given file. Images can\n"
887 " be stored as JPEG or PNM, depending on the given file name extension.\n"
888 " The quality of the grabbed image can be in the range 0..100, where 100\n"
889 " (the default) means \"best\" (only applies to JPEG). The size parameters\n"
890 " define the size of the resulting image (default is full screen).\n"
891 " If the file name is just an extension (.jpg, .jpeg or .pnm) the image\n"
892 " data will be sent to the SVDRP connection encoded in base64. The same\n"
893 " happens if '-' (a minus sign) is given as file name, in which case the\n"
894 " image format defaults to JPEG.",
896 " The HELP command gives help info.",
897 "HITK [ <key> ... ]\n"
898 " Hit the given remote control key. Without option a list of all\n"
899 " valid key names is given. If more than one key is given, they are\n"
900 " entered into the remote control queue in the given sequence. There\n"
901 " can be up to 31 keys.",
902 "LSTC [ :ids ] [ :groups | <number> | <name> | <id> ]\n"
903 " List channels. Without option, all channels are listed. Otherwise\n"
904 " only the given channel is listed. If a name is given, all channels\n"
905 " containing the given string as part of their name are listed.\n"
906 " If ':groups' is given, all channels are listed including group\n"
907 " separators. The channel number of a group separator is always 0.\n"
908 " With ':ids' the channel ids are listed following the channel numbers.\n"
909 " The special number 0 can be given to list the current channel.",
911 " List all available devices. Each device is listed with its name and\n"
912 " whether it is currently the primary device ('P') or it implements a\n"
913 " decoder ('D') and can be used as output device.",
914 "LSTE [ <channel> ] [ now | next | at <time> ]\n"
915 " List EPG data. Without any parameters all data of all channels is\n"
916 " listed. If a channel is given (either by number or by channel ID),\n"
917 " only data for that channel is listed. 'now', 'next', or 'at <time>'\n"
918 " restricts the returned data to present events, following events, or\n"
919 " events at the given time (which must be in time_t form).",
920 "LSTR [ <id> [ path ] ]\n"
921 " List recordings. Without option, all recordings are listed. Otherwise\n"
922 " the information for the given recording is listed. If a recording\n"
923 " id and the keyword 'path' is given, the actual file name of that\n"
924 " recording's directory is listed.\n"
925 " Note that the ids of the recordings are not necessarily given in\n"
927 "LSTT [ <id> ] [ id ]\n"
928 " List timers. Without option, all timers are listed. Otherwise\n"
929 " only the timer with the given id is listed. If the keyword 'id' is\n"
930 " given, the channels will be listed with their unique channel ids\n"
931 " instead of their numbers. This command lists only the timers that are\n"
932 " defined locally on this VDR, not any remote timers from other VDRs.",
934 " Displays the given message on the OSD. The message will be queued\n"
935 " and displayed whenever this is suitable.\n",
936 "MODC <number> <settings>\n"
937 " Modify a channel. Settings must be in the same format as returned\n"
938 " by the LSTC command.",
939 "MODT <id> on | off | <settings>\n"
940 " Modify a timer. Settings must be in the same format as returned\n"
941 " by the LSTT command. The special keywords 'on' and 'off' can be\n"
942 " used to easily activate or deactivate a timer.",
943 "MOVC <number> <to>\n"
944 " Move a channel to a new position.",
945 "MOVR <id> <new name>\n"
946 " Move the recording with the given id. Before a recording can be\n"
947 " moved, an LSTR command should have been executed in order to retrieve\n"
948 " the recording ids. The ids don't change during subsequent MOVR\n"
951 " Create a new channel. Settings must be in the same format as returned\n"
952 " by the LSTC command.",
954 " Create a new timer. Settings must be in the same format as returned\n"
955 " by the LSTT command.",
956 "NEXT [ abs | rel ]\n"
957 " Show the next timer event. If no option is given, the output will be\n"
958 " in human readable form. With option 'abs' the absolute time of the next\n"
959 " event will be given as the number of seconds since the epoch (time_t\n"
960 " format), while with option 'rel' the relative time will be given as the\n"
961 " number of seconds from now until the event. If the absolute time given\n"
962 " is smaller than the current time, or if the relative time is less than\n"
963 " zero, this means that the timer is currently recording and has started\n"
964 " at the given time. The first value in the resulting line is the id\n"
967 " Used by peer-to-peer connections between VDRs to keep the connection\n"
968 " from timing out. May be used at any time and simply returns a line of\n"
969 " the form '<hostname> is alive'.",
970 "PLAY <id> [ begin | <position> ]\n"
971 " Play the recording with the given id. Before a recording can be\n"
972 " played, an LSTR command should have been executed in order to retrieve\n"
973 " the recording ids.\n"
974 " The keyword 'begin' plays the recording from its very beginning, while\n"
975 " a <position> (given as hh:mm:ss[.ff] or framenumber) starts at that\n"
976 " position. If neither 'begin' nor a <position> are given, replay is resumed\n"
977 " at the position where any previous replay was stopped, or from the beginning\n"
978 " by default. To control or stop the replay session, use the usual remote\n"
979 " control keypresses via the HITK command.",
980 "PLUG <name> [ help | main ] [ <command> [ <options> ]]\n"
981 " Send a command to a plugin.\n"
982 " The PLUG command without any parameters lists all plugins.\n"
983 " If only a name is given, all commands known to that plugin are listed.\n"
984 " If a command is given (optionally followed by parameters), that command\n"
985 " is sent to the plugin, and the result will be displayed.\n"
986 " The keyword 'help' lists all the SVDRP commands known to the named plugin.\n"
987 " If 'help' is followed by a command, the detailed help for that command is\n"
988 " given. The keyword 'main' initiates a call to the main menu function of the\n"
990 "POLL <name> timers\n"
991 " Used by peer-to-peer connections between VDRs to inform other machines\n"
992 " about changes to timers. The receiving VDR shall use LSTT to query the\n"
993 " remote machine with the given name about its timers and update its list\n"
994 " of timers accordingly.\n",
995 "PRIM [ <number> ]\n"
996 " Make the device with the given number the primary device.\n"
997 " Without option it returns the currently active primary device in the same\n"
998 " format as used by the LSTD command.",
1000 " Put data into the EPG list. The data entered has to strictly follow the\n"
1001 " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n"
1002 " by itself terminates the input and starts processing of the data (all\n"
1003 " entered data is buffered until the terminating '.' is seen).\n"
1004 " If a file name is given, epg data will be read from this file (which\n"
1005 " must be accessible under the given name from the machine VDR is running\n"
1006 " on). In case of file input, no terminating '.' shall be given.\n",
1007 "REMO [ on | off ]\n"
1008 " Turns the remote control on or off. Without a parameter, the current\n"
1009 " status of the remote control is reported.",
1011 " Forces an EPG scan. If this is a single DVB device system, the scan\n"
1012 " will be done on the primary device unless it is currently recording.",
1014 " Return information about disk usage (total, free, percent).",
1016 " Updates a timer. Settings must be in the same format as returned\n"
1017 " by the LSTT command. If a timer with the same channel, day, start\n"
1018 " and stop time does not yet exist, it will be created.",
1020 " Initiates a re-read of the recordings directory, which is the SVDRP\n"
1021 " equivalent to 'touch .update'.",
1022 "VOLU [ <number> | + | - | mute ]\n"
1023 " Set the audio volume to the given number (which is limited to the range\n"
1024 " 0...255). If the special options '+' or '-' are given, the volume will\n"
1025 " be turned up or down, respectively. The option 'mute' will toggle the\n"
1026 " audio muting. If no option is given, the current audio volume level will\n"
1029 " Exit vdr (SVDRP).\n"
1030 " You can also hit Ctrl-D to exit.",
1058 const char *q = HelpPage;
1061 uint n = q - HelpPage;
1062 if (n >=
sizeof(topic))
1063 n =
sizeof(topic) - 1;
1064 strncpy(topic, HelpPage, n);
1078 if (strcasecmp(Cmd, t) == 0)
1099 void Close(
bool SendReply =
false,
bool Timeout =
false);
1100 bool Send(
const char *s);
1103 void CmdAUDI(const
char *Option);
1104 void CmdCHAN(const
char *Option);
1105 void CmdCLRE(const
char *Option);
1106 void CmdCONN(const
char *Option);
1107 void CmdCPYR(const
char *Option);
1108 void CmdDELC(const
char *Option);
1109 void CmdDELR(const
char *Option);
1110 void CmdDELT(const
char *Option);
1111 void CmdEDIT(const
char *Option);
1112 void CmdGRAB(const
char *Option);
1113 void CmdHELP(const
char *Option);
1114 void CmdHITK(const
char *Option);
1115 void CmdLSTC(const
char *Option);
1116 void CmdLSTD(const
char *Option);
1117 void CmdLSTE(const
char *Option);
1118 void CmdLSTR(const
char *Option);
1119 void CmdLSTT(const
char *Option);
1120 void CmdMESG(const
char *Option);
1121 void CmdMODC(const
char *Option);
1122 void CmdMODT(const
char *Option);
1123 void CmdMOVC(const
char *Option);
1124 void CmdMOVR(const
char *Option);
1125 void CmdNEWC(const
char *Option);
1126 void CmdNEWT(const
char *Option);
1127 void CmdNEXT(const
char *Option);
1128 void CmdPING(const
char *Option);
1129 void CmdPLAY(const
char *Option);
1130 void CmdPLUG(const
char *Option);
1131 void CmdPOLL(const
char *Option);
1132 void CmdPRIM(const
char *Option);
1133 void CmdPUTE(const
char *Option);
1134 void CmdREMO(const
char *Option);
1135 void CmdSCAN(const
char *Option);
1136 void CmdSTAT(const
char *Option);
1137 void CmdUPDT(const
char *Option);
1138 void CmdUPDR(const
char *Option);
1139 void CmdVOLU(const
char *Option);
1162 time_t now = time(NULL);
1178 if (
file.IsOpen()) {
1180 Reply(221,
"%s closing connection%s",
Setup.SVDRPHostName, Timeout ?
" (timeout)" :
"");
1203 if (
file.IsOpen()) {
1205 char *buffer = NULL;
1208 if (vasprintf(&buffer, fmt, ap) >= 0) {
1211 char *n = strchr(s,
'\n');
1215 if (Code < 0 || n && *(n + 1))
1219 s = n ? n + 1 : NULL;
1223 Reply(451,
"Bad format - looks like a programming error!");
1230 Reply(451,
"Zero return code - looks like a programming error!");
1246 const int TopicsPerLine = 5;
1248 for (
int y = 0; (y * TopicsPerLine + x) < NumPages; y++) {
1251 q += sprintf(q,
" ");
1252 for (x = 0; x < TopicsPerLine && (y * TopicsPerLine + x) < NumPages; x++) {
1253 const char *topic =
GetHelpTopic(hp[(y * TopicsPerLine + x)]);
1258 Reply(-214,
"%s", buffer);
1266 int o = strtol(Option, NULL, 10);
1269 if (TrackId && TrackId->
id) {
1274 Reply(501,
"Audio track \"%s\" not available", Option);
1277 Reply(501,
"Invalid audio track \"%s\"", Option);
1280 Reply(501,
"Error in audio track \"%s\"", Option);
1288 if (TrackId && TrackId->
id) {
1290 Reply(-250,
"%s", *s);
1295 Reply(250,
"%s", *s);
1297 Reply(550,
"No audio tracks available");
1308 int o = strtol(Option, NULL, 10);
1312 else if (strcmp(Option,
"-") == 0) {
1319 else if (strcmp(Option,
"+") == 0) {
1327 n = Channel->Number();
1329 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1330 if (!Channel->GroupSep()) {
1331 if (strcasecmp(Channel->Name(), Option) == 0) {
1332 n = Channel->Number();
1339 Reply(501,
"Undefined channel \"%s\"", Option);
1343 if (
const cChannel *Channel = Channels->GetByNumber(n)) {
1345 Reply(554,
"Error switching to channel \"%d\"", Channel->Number());
1350 Reply(550,
"Unable to find channel \"%s\"", Option);
1358 Reply(250,
"%d %s", Channel->Number(), Channel->Name());
1370 int o = strtol(Option, NULL, 10);
1372 if (
const cChannel *Channel = Channels->GetByNumber(o))
1373 ChannelID = Channel->GetChannelID();
1379 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1380 if (!Channel->GroupSep()) {
1381 if (strcasecmp(Channel->Name(), Option) == 0) {
1382 ChannelID = Channel->GetChannelID();
1393 for (
cSchedule *p = Schedules->First(); p; p = Schedules->
Next(p)) {
1394 if (p->ChannelID() == ChannelID) {
1400 for (
cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
1401 if (ChannelID == Timer->Channel()->GetChannelID().
ClrRid())
1402 Timer->SetEvent(NULL);
1406 Reply(250,
"EPG data of channel \"%s\" cleared", Option);
1409 Reply(550,
"No EPG data found for channel \"%s\"", Option);
1414 Reply(501,
"Undefined channel \"%s\"", Option);
1419 for (
cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer))
1420 Timer->SetEvent(NULL);
1421 for (
cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->
Next(Schedule))
1422 Schedule->Cleanup(INT_MAX);
1424 Reply(250,
"EPG data cleared");
1433 if (ServerParams.
Ok()) {
1439 Reply(501,
"Error in server parameters: %s", ServerParams.
Error());
1442 Reply(451,
"No SVDRP client handler");
1445 Reply(501,
"Missing server parameters");
1453 Channels->SetExplicitModify();
1456 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1460 if (
const cTimer *Timer = Timers->UsesChannel(Channel)) {
1461 Reply(550,
"Channel \"%s\" is in use by timer %s", Option, *Timer->ToDescr());
1465 cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
1466 if (CurrentChannel && Channel == CurrentChannel) {
1467 int n = Channels->GetNextNormal(CurrentChannel->
Index());
1469 n = Channels->GetPrevNormal(CurrentChannel->
Index());
1471 Reply(501,
"Can't delete channel \"%s\" - list would be empty", Option);
1474 CurrentChannel = Channels->Get(n);
1475 CurrentChannelNr = 0;
1477 Channels->Del(Channel);
1478 Channels->ReNumber();
1479 Channels->SetModifiedByUser();
1480 Channels->SetModified();
1482 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
1484 Channels->SwitchTo(CurrentChannel->
Number());
1488 Reply(250,
"Channel \"%s\" deleted", Option);
1491 Reply(501,
"Channel \"%s\" not defined", Option);
1494 Reply(501,
"Missing channel number or id");
1503 return cString::sprintf(
"Recording \"%s\" is being replayed", RecordingId);
1504 else if ((Reason &
ruCut) != 0)
1507 return cString::sprintf(
"Recording \"%s\" is being copied/moved", RecordingId);
1516 char *opt = strdup(Option);
1519 while (*option && !isspace(*option))
1525 Recordings->SetExplicitModify();
1526 if (
cRecording *Recording = Recordings->Get(strtol(num, NULL, 10) - 1)) {
1527 if (
int RecordingInUse = Recording->IsInUse())
1535 if (strcmp(newName, Recording->Name())) {
1540 Recordings->AddByName(fileName);
1541 Reply(250,
"Recording \"%s\" copied to \"%s\"", Recording->Name(), *newName);
1544 Reply(554,
"Error while copying recording \"%s\" to \"%s\"!", Recording->Name(), *newName);
1547 Reply(501,
"Identical new recording name");
1550 Reply(501,
"Missing new recording name");
1554 Reply(550,
"Recording \"%s\" not found", num);
1557 Reply(501,
"Error in recording number \"%s\"", num);
1561 Reply(501,
"Missing recording number");
1569 Recordings->SetExplicitModify();
1570 if (
cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1571 if (
int RecordingInUse = Recording->IsInUse())
1574 if (Recording->Delete()) {
1575 Recordings->DelByName(Recording->FileName());
1576 Recordings->SetModified();
1578 Reply(250,
"Recording \"%s\" deleted", Option);
1581 Reply(554,
"Error while deleting recording!");
1585 Reply(550,
"Recording \"%s\" not found", Option);
1588 Reply(501,
"Error in recording id \"%s\"", Option);
1591 Reply(501,
"Missing recording id");
1599 Timers->SetExplicitModify();
1600 if (
cTimer *Timer = Timers->GetById(strtol(Option, NULL, 10))) {
1601 if (Timer->Recording()) {
1605 Timer->TriggerRespawn();
1607 Timers->SetModified();
1609 Reply(250,
"Timer \"%s\" deleted", Option);
1612 Reply(501,
"Timer \"%s\" not defined", Option);
1615 Reply(501,
"Error in timer number \"%s\"", Option);
1618 Reply(501,
"Missing timer number");
1626 if (
const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
1628 if (Marks.
Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.
Count()) {
1630 Reply(550,
"Not enough free disk space to start editing process");
1632 Reply(250,
"Editing recording \"%s\" [%s]", Option, Recording->Title());
1634 Reply(554,
"Can't start editing process");
1637 Reply(554,
"No editing marks defined");
1640 Reply(550,
"Recording \"%s\" not found", Option);
1643 Reply(501,
"Error in recording id \"%s\"", Option);
1646 Reply(501,
"Missing recording id");
1651 const char *FileName = NULL;
1653 int Quality = -1, SizeX = -1, SizeY = -1;
1655 char buf[strlen(Option) + 1];
1656 char *p = strcpy(buf, Option);
1657 const char *delim =
" \t";
1659 FileName = strtok_r(p, delim, &strtok_next);
1661 const char *Extension = strrchr(FileName,
'.');
1663 if (strcasecmp(Extension,
".jpg") == 0 || strcasecmp(Extension,
".jpeg") == 0)
1665 else if (strcasecmp(Extension,
".pnm") == 0)
1668 Reply(501,
"Unknown image type \"%s\"", Extension + 1);
1671 if (Extension == FileName)
1674 else if (strcmp(FileName,
"-") == 0)
1677 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1678 if (strcasecmp(p,
"JPEG") == 0 || strcasecmp(p,
"PNM") == 0) {
1680 p = strtok_r(NULL, delim, &strtok_next);
1686 Reply(501,
"Invalid quality \"%s\"", p);
1692 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1696 Reply(501,
"Invalid sizex \"%s\"", p);
1699 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1703 Reply(501,
"Invalid sizey \"%s\"", p);
1708 Reply(501,
"Missing sizey");
1712 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1713 Reply(501,
"Unexpected parameter \"%s\"", p);
1717 char RealFileName[PATH_MAX];
1722 const char *slash = strrchr(FileName,
'/');
1727 slash = strrchr(FileName,
'/');
1730 char *r = realpath(t, RealFileName);
1733 Reply(501,
"Invalid file name \"%s\"", FileName);
1736 strcat(RealFileName, slash);
1737 FileName = RealFileName;
1739 Reply(501,
"Invalid file name \"%s\"", FileName);
1744 Reply(550,
"Grabbing to file not allowed (use \"GRAB -\" instead)");
1753 int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
1755 if (
safe_write(fd, Image, ImageSize) == ImageSize) {
1757 Reply(250,
"Grabbed image %s", Option);
1761 Reply(451,
"Can't write to '%s'", FileName);
1767 Reply(451,
"Can't open '%s'", FileName);
1773 while ((s = Base64.
NextLine()) != NULL)
1774 Reply(-216,
"%s", s);
1775 Reply(216,
"Grabbed image %s", Option);
1780 Reply(451,
"Grab image failed");
1783 Reply(501,
"Missing filename");
1791 Reply(-214,
"%s", hp);
1793 Reply(504,
"HELP topic \"%s\" unknown", Option);
1799 Reply(-214,
"Topics:");
1808 Reply(-214,
"To report bugs in the implementation send email to");
1809 Reply(-214,
" vdr-bugs@tvdr.de");
1811 Reply(214,
"End of HELP info");
1818 Reply(550,
"Remote control currently disabled (key \"%s\" discarded)", Option);
1821 char buf[strlen(Option) + 1];
1822 strcpy(buf, Option);
1823 const char *delim =
" \t";
1825 char *p = strtok_r(buf, delim, &strtok_next);
1831 Reply(451,
"Too many keys in \"%s\" (only %d accepted)", Option, NumKeys);
1836 Reply(504,
"Unknown key: \"%s\"", p);
1840 p = strtok_r(NULL, delim, &strtok_next);
1842 Reply(250,
"Key%s \"%s\" accepted", NumKeys > 1 ?
"s" :
"", Option);
1845 Reply(-214,
"Valid <key> names for the HITK command:");
1846 for (
int i = 0; i <
kNone; i++) {
1849 Reply(214,
"End of key list");
1856 bool WithChannelIds =
startswith(Option,
":ids") && (Option[4] ==
' ' || Option[4] == 0);
1859 bool WithGroupSeps = strcasecmp(Option,
":groups") == 0;
1860 if (*Option && !WithGroupSeps) {
1862 int n = strtol(Option, NULL, 10);
1865 if (
const cChannel *Channel = Channels->GetByNumber(n))
1866 Reply(250,
"%d%s%s %s", Channel->Number(), WithChannelIds ?
" " :
"", WithChannelIds ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1868 Reply(501,
"Channel \"%s\" not defined", Option);
1873 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1874 if (!Channel->GroupSep()) {
1875 if (strcasestr(Channel->Name(), Option)) {
1886 Reply(501,
"Channel \"%s\" not defined", Option);
1890 for (
const cChannel *Channel = Channels->First(); Channel; Channel = Channels->
Next(Channel)) {
1892 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());
1893 else if (!Channel->GroupSep())
1894 Reply(Channel->Number() <
cChannels::MaxNumber() ? -250 : 250,
"%d%s%s %s", Channel->Number(), WithChannelIds ?
" " :
"", WithChannelIds ? *Channel->GetChannelID().ToString() :
"", *Channel->ToText());
1898 Reply(550,
"No channels defined");
1906 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());
1910 Reply(550,
"No devices found");
1921 char buf[strlen(Option) + 1];
1922 strcpy(buf, Option);
1923 const char *delim =
" \t";
1925 char *p = strtok_r(buf, delim, &strtok_next);
1926 while (p && DumpMode ==
dmAll) {
1927 if (strcasecmp(p,
"NOW") == 0)
1929 else if (strcasecmp(p,
"NEXT") == 0)
1931 else if (strcasecmp(p,
"AT") == 0) {
1933 if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
1935 AtTime = strtol(p, NULL, 10);
1937 Reply(501,
"Invalid time");
1942 Reply(501,
"Missing time");
1946 else if (!Schedule) {
1949 Channel = Channels->GetByNumber(strtol(Option, NULL, 10));
1953 Schedule = Schedules->GetSchedule(Channel);
1955 Reply(550,
"No schedule found");
1960 Reply(550,
"Channel \"%s\" not defined", p);
1965 Reply(501,
"Unknown option: \"%s\"", p);
1968 p = strtok_r(NULL, delim, &strtok_next);
1973 FILE *f = fdopen(fd,
"w");
1976 Schedule->
Dump(Channels, f,
"215-", DumpMode, AtTime);
1978 Schedules->Dump(f,
"215-", DumpMode, AtTime);
1980 Reply(215,
"End of EPG data");
1984 Reply(451,
"Can't open file connection");
1989 Reply(451,
"Can't dup stream descriptor");
1998 char buf[strlen(Option) + 1];
1999 strcpy(buf, Option);
2000 const char *delim =
" \t";
2002 char *p = strtok_r(buf, delim, &strtok_next);
2006 Number = strtol(p, NULL, 10);
2008 Reply(501,
"Error in recording id \"%s\"", Option);
2012 else if (strcasecmp(p,
"PATH") == 0)
2015 Reply(501,
"Unknown option: \"%s\"", p);
2018 p = strtok_r(NULL, delim, &strtok_next);
2021 if (
const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
2022 FILE *f = fdopen(
file,
"w");
2025 Reply(250,
"%s", Recording->FileName());
2027 Recording->Info()->Write(f,
"215-");
2029 Reply(215,
"End of recording information");
2034 Reply(451,
"Can't open file connection");
2037 Reply(550,
"Recording \"%s\" not found", Option);
2040 else if (Recordings->Count()) {
2041 const cRecording *Recording = Recordings->First();
2043 Reply(Recording == Recordings->Last() ? 250 : -250,
"%d %s", Recording->
Id(), Recording->
Title(
' ',
true));
2044 Recording = Recordings->
Next(Recording);
2048 Reply(550,
"No recordings available");
2054 bool UseChannelId =
false;
2056 char buf[strlen(Option) + 1];
2057 strcpy(buf, Option);
2058 const char *delim =
" \t";
2060 char *p = strtok_r(buf, delim, &strtok_next);
2063 Id = strtol(p, NULL, 10);
2064 else if (strcasecmp(p,
"ID") == 0)
2065 UseChannelId =
true;
2067 Reply(501,
"Unknown option: \"%s\"", p);
2070 p = strtok_r(NULL, delim, &strtok_next);
2075 for (
const cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
2076 if (!Timer->Remote()) {
2077 if (Timer->Id() == Id) {
2078 Reply(250,
"%d %s", Timer->Id(), *Timer->ToText(UseChannelId));
2083 Reply(501,
"Timer \"%s\" not defined", Option);
2087 const cTimer *LastLocalTimer = Timers->Last();
2088 while (LastLocalTimer) {
2089 if (LastLocalTimer->
Remote())
2090 LastLocalTimer = Timers->
Prev(LastLocalTimer);
2094 if (LastLocalTimer) {
2095 for (
const cTimer *Timer = Timers->First(); Timer; Timer = Timers->
Next(Timer)) {
2096 if (!Timer->Remote())
2097 Reply(Timer != LastLocalTimer ? -250 : 250,
"%d %s", Timer->
Id(), *Timer->ToText(UseChannelId));
2098 if (Timer == LastLocalTimer)
2104 Reply(550,
"No timers defined");
2112 Reply(250,
"Message queued");
2115 Reply(501,
"Missing message");
2122 int n = strtol(Option, &tail, 10);
2123 if (tail && tail != Option) {
2126 Channels->SetExplicitModify();
2127 if (
cChannel *Channel = Channels->GetByNumber(n)) {
2129 if (ch.
Parse(tail)) {
2130 if (Channels->HasUniqueChannelID(&ch, Channel)) {
2132 Channels->ReNumber();
2133 Channels->SetModifiedByUser();
2134 Channels->SetModified();
2135 isyslog(
"SVDRP %s < %s modified channel %d %s",
Setup.SVDRPHostName, *
clientName, Channel->Number(), *Channel->ToText());
2136 Reply(250,
"%d %s", Channel->Number(), *Channel->ToText());
2139 Reply(501,
"Channel settings are not unique");
2142 Reply(501,
"Error in channel settings");
2145 Reply(501,
"Channel \"%d\" not defined", n);
2148 Reply(501,
"Error in channel number");
2151 Reply(501,
"Missing channel settings");
2158 int Id = strtol(Option, &tail, 10);
2159 if (tail && tail != Option) {
2162 Timers->SetExplicitModify();
2163 if (
cTimer *Timer = Timers->GetById(Id)) {
2166 if (strcasecmp(tail,
"ON") == 0)
2168 else if (strcasecmp(tail,
"OFF") == 0)
2170 else if (!t.
Parse(tail)) {
2171 Reply(501,
"Error in timer settings");
2175 Reply(550,
"Timer is recording");
2183 Timers->SetModified();
2184 isyslog(
"SVDRP %s < %s modified timer %s (%s)",
Setup.SVDRPHostName, *
clientName, *Timer->ToDescr(), Timer->HasFlags(
tfActive) ?
"active" :
"inactive");
2185 if (Timer->IsPatternTimer())
2186 Timer->SetEvent(NULL);
2187 Timer->TriggerRespawn();
2188 Reply(250,
"%d %s", Timer->Id(), *Timer->ToText(
true));
2191 Reply(501,
"Timer \"%d\" not defined", Id);
2194 Reply(501,
"Error in timer id");
2197 Reply(501,
"Missing timer settings");
2204 int From = strtol(Option, &tail, 10);
2205 if (tail && tail != Option) {
2207 if (tail && tail != Option) {
2210 Channels->SetExplicitModify();
2211 int To = strtol(tail, NULL, 10);
2213 const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr);
2214 cChannel *FromChannel = Channels->GetByNumber(From);
2216 cChannel *ToChannel = Channels->GetByNumber(To);
2218 int FromNumber = FromChannel->
Number();
2219 int ToNumber = ToChannel->
Number();
2220 if (FromNumber != ToNumber) {
2221 if (Channels->MoveNeedsDecrement(FromChannel, ToChannel))
2222 ToChannel = Channels->
Prev(ToChannel);
2223 Channels->Move(FromChannel, ToChannel);
2224 Channels->ReNumber();
2225 Channels->SetModifiedByUser();
2226 Channels->SetModified();
2227 if (CurrentChannel && CurrentChannel->
Number() != CurrentChannelNr) {
2229 Channels->SwitchTo(CurrentChannel->
Number());
2234 Reply(250,
"Channel \"%d\" moved to \"%d\"", From, To);
2237 Reply(501,
"Can't move channel to same position");
2240 Reply(501,
"Channel \"%d\" not defined", To);
2243 Reply(501,
"Channel \"%d\" not defined", From);
2246 Reply(501,
"Error in channel number");
2249 Reply(501,
"Error in channel number");
2252 Reply(501,
"Missing channel number");
2258 char *opt = strdup(Option);
2261 while (*option && !isspace(*option))
2267 Recordings->SetExplicitModify();
2268 if (
cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2269 if (
int RecordingInUse = Recording->IsInUse())
2275 cString oldName = Recording->Name();
2276 if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) {
2277 Recordings->SetModified();
2278 Recordings->TouchUpdate();
2279 Reply(250,
"Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name());
2282 Reply(554,
"Error while moving recording \"%s\" to \"%s\"!", *oldName, option);
2285 Reply(501,
"Missing new recording name");
2289 Reply(550,
"Recording \"%s\" not found", num);
2292 Reply(501,
"Error in recording id \"%s\"", num);
2296 Reply(501,
"Missing recording id");
2303 if (ch.
Parse(Option)) {
2305 Channels->SetExplicitModify();
2306 if (Channels->HasUniqueChannelID(&ch)) {
2309 Channels->Add(channel);
2310 Channels->ReNumber();
2311 Channels->SetModifiedByUser();
2312 Channels->SetModified();
2317 Reply(501,
"Channel settings are not unique");
2320 Reply(501,
"Error in channel settings");
2323 Reply(501,
"Missing channel settings");
2330 if (Timer->
Parse(Option)) {
2339 Reply(501,
"Error in timer settings");
2343 Reply(501,
"Missing timer settings");
2349 if (
const cTimer *t = Timers->GetNextActiveTimer()) {
2350 time_t Start = t->StartTime();
2354 else if (strcasecmp(Option,
"ABS") == 0)
2355 Reply(250,
"%d %jd", Id, intmax_t(Start));
2356 else if (strcasecmp(Option,
"REL") == 0)
2357 Reply(250,
"%d %jd", Id, intmax_t(Start - time(NULL)));
2359 Reply(501,
"Unknown option: \"%s\"", Option);
2362 Reply(550,
"No active timers");
2367 Reply(250,
"%s is alive",
Setup.SVDRPHostName);
2373 char *opt = strdup(Option);
2376 while (*option && !isspace(*option))
2383 if (
const cRecording *Recording = Recordings->GetById(strtol(num, NULL, 10))) {
2384 cString FileName = Recording->FileName();
2385 cString Title = Recording->Title();
2386 int FramesPerSecond = Recording->FramesPerSecond();
2387 bool IsPesRecording = Recording->IsPesRecording();
2395 if (strcasecmp(option,
"BEGIN") != 0)
2406 Reply(250,
"Playing recording \"%s\" [%s]", num, *Title);
2410 Reply(550,
"Recording \"%s\" not found", num);
2415 Reply(501,
"Error in recording id \"%s\"", num);
2419 Reply(501,
"Missing recording id");
2425 char *opt = strdup(Option);
2427 char *option = name;
2428 while (*option && !isspace(*option))
2437 while (*option && !isspace(*option))
2443 if (!*cmd || strcasecmp(cmd,
"HELP") == 0) {
2444 if (*cmd && *option) {
2447 Reply(-214,
"%s", hp);
2448 Reply(214,
"End of HELP info");
2451 Reply(504,
"HELP topic \"%s\" for plugin \"%s\" unknown", option, plugin->
Name());
2457 Reply(-214,
"SVDRP commands:");
2459 Reply(214,
"End of HELP info");
2462 Reply(214,
"This plugin has no SVDRP commands");
2465 else if (strcasecmp(cmd,
"MAIN") == 0) {
2467 Reply(250,
"Initiated call to main menu function of plugin \"%s\"", plugin->
Name());
2469 Reply(550,
"A plugin call is already pending - please try again later");
2472 int ReplyCode = 900;
2475 Reply(abs(ReplyCode),
"%s", *s);
2477 Reply(500,
"Command unrecognized: \"%s\"", cmd);
2481 Reply(550,
"Plugin \"%s\" not found (use PLUG for a list of plugins)", name);
2485 Reply(-214,
"Available plugins:");
2489 Reply(214,
"End of plugin list");
2496 char buf[strlen(Option) + 1];
2497 char *p = strcpy(buf, Option);
2498 const char *delim =
" \t";
2500 char *RemoteName = strtok_r(p, delim, &strtok_next);
2501 char *ListName = strtok_r(NULL, delim, &strtok_next);
2504 if (strcasecmp(ListName,
"timers") == 0) {
2509 Reply(501,
"Unknown list name: \"%s\"", ListName);
2512 Reply(501,
"Missing list name");
2515 Reply(501,
"No SVDRP client connections");
2518 Reply(501,
"Missing parameters");
2526 int o = strtol(Option, NULL, 10);
2530 Reply(501,
"Invalid device number \"%s\"", Option);
2533 Reply(501,
"Invalid parameter \"%s\"", Option);
2535 Setup.PrimaryDVB = n;
2536 Reply(250,
"Primary device set to %d", n);
2541 Reply(250,
"%d [%s%s] %s", d->DeviceNumber() + 1, d->HasDecoder() ?
"D" :
"-", d->DeviceNumber() + 1 ==
Setup.PrimaryDVB ?
"P" :
"-", *d->DeviceName());
2543 Reply(501,
"Failed to get primary device");
2550 FILE *f = fopen(Option,
"r");
2554 Reply(250,
"EPG data processed from \"%s\"", Option);
2557 Reply(451,
"Error while processing EPG from \"%s\"", Option);
2561 Reply(501,
"Cannot open file \"%s\"", Option);
2575 if (!strcasecmp(Option,
"ON")) {
2577 Reply(250,
"Remote control enabled");
2579 else if (!strcasecmp(Option,
"OFF")) {
2581 Reply(250,
"Remote control disabled");
2584 Reply(501,
"Invalid Option \"%s\"", Option);
2593 Reply(250,
"EPG scan triggered");
2599 if (strcasecmp(Option,
"DISK") == 0) {
2602 Reply(250,
"%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
2605 Reply(501,
"Invalid Option \"%s\"", Option);
2608 Reply(501,
"No option given");
2615 if (Timer->
Parse(Option)) {
2617 if (
cTimer *t = Timers->GetTimer(Timer)) {
2637 Reply(501,
"Error in timer settings");
2641 Reply(501,
"Missing timer settings");
2647 Recordings->Update(
false);
2648 Reply(250,
"Re-read of recordings directory triggered");
2656 else if (strcmp(Option,
"+") == 0)
2658 else if (strcmp(Option,
"-") == 0)
2660 else if (strcasecmp(Option,
"MUTE") == 0)
2663 Reply(501,
"Unknown option: \"%s\"", Option);
2668 Reply(250,
"Audio is mute");
2673#define CMD(c) (strcasecmp(Cmd, c) == 0)
2690 while (*s && !isspace(*s))
2732 else Reply(500,
"Command unrecognized: \"%s\"", Cmd);
2737 if (
file.IsOpen()) {
2738 while (
file.Ready(
false)) {
2742 if (c ==
'\n' || c == 0x00) {
2758 else if (c == 0x04 &&
numChars == 0) {
2762 else if (c == 0x08 || c == 0x7F) {
2767 else if (c <= 0x03 || c == 0x0D) {
2772 int NewLength =
length + BUFSIZ;
2773 if (
char *NewBuffer = (
char *)realloc(
cmdLine, NewLength)) {
2798 return file.IsOpen();
2822 virtual void Action(
void)
override;
2832:
cThread(
"SVDRP server handler", true)
2918 bool Result =
false;
2927 bool Result =
false;
2940 for (
int i = 0; i < ServerNames.
Size(); i++)
#define LOCK_CHANNELS_READ
#define LOCK_CHANNELS_WRITE
const char * NextLine(void)
Returns the next line of encoded data (terminated by '\0'), or NULL if there is no more encoded data.
bool Parse(const char *s)
static cString ToText(const cChannel *Channel)
tChannelID GetChannelID(void) const
static int MaxNumber(void)
static const char * SystemCharacterTable(void)
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
static void Shutdown(void)
static void Launch(cControl *Control)
virtual uchar * GrabImage(int &Size, bool Jpeg=true, int Quality=-1, int SizeX=-1, int SizeY=-1)
Grabs the currently visible screen image.
static cDevice * PrimaryDevice(void)
Returns the primary device.
static cDevice * GetDevice(int Index)
Gets the device with the given Index.
eTrackType GetCurrentAudioTrack(void) const
bool SwitchChannel(const cChannel *Channel, bool LiveView)
Switches the device to the given Channel, initiating transfer mode if necessary.
static int CurrentChannel(void)
Returns the number of the current channel on the primary device.
const tTrackId * GetTrack(eTrackType Type)
Returns a pointer to the given track id, or NULL if Type is not less than ttMaxTrackTypes.
static void SetCurrentChannel(int ChannelNumber)
Sets the number of the current channel on the primary device, without actually switching to it.
void SetVolume(int Volume, bool Absolute=false)
Sets the volume to the given value, either absolutely or relative to the current volume.
static int NumDevices(void)
Returns the total number of devices.
static int CurrentVolume(void)
bool ToggleMute(void)
Turns the volume off or on and returns the new mute state.
bool SetCurrentAudioTrack(eTrackType Type)
Sets the current audio track to the given Type.
static void SetDisableUntil(time_t Time)
const char * Connection(void) const
const char * Address(void) const
void Set(const char *Address, int Port)
static const char * ToString(eKeys Key, bool Translate=false)
static eKeys FromString(const char *Name)
cListObject * Prev(void) const
cListObject * Next(void) const
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
bool Process(const char *s)
const char * Message(void)
static cPlugin * GetPlugin(int Index)
virtual const char * Version(void)=0
virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
virtual const char * Description(void)=0
virtual const char ** SVDRPHelpPages(void)
static bool Process(cTimers *Timers, time_t t)
static cRecordControl * GetRecordControl(const char *FileName)
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
const char * Title(char Delimiter=' ', bool NewIndicator=false, int Level=-1) const
static const cRecordings * GetRecordingsRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of recordings for read access.
bool Put(uint64_t Code, bool Repeat=false, bool Release=false)
static bool Enabled(void)
static bool CallPlugin(const char *Plugin)
Initiates calling the given plugin's main menu function.
static void SetEnabled(bool Enabled)
static void SetRecording(const char *FileName)
bool Execute(const char *ServerName, const char *Command, cStringList *Response=NULL)
void AddClient(cSVDRPServerParams &ServerParams, const char *IpAddress)
virtual ~cSVDRPClientHandler() override
void ProcessConnections(void)
bool GetServerNames(cStringList *ServerNames)
void CloseClient(const char *ServerName)
cSVDRPClientHandler(int TcpPort, int UdpPort)
void HandleClientConnection(void)
cSVDRPClient * GetClientForServer(const char *ServerName)
cVector< cSVDRPClient * > clientConnections
bool TriggerFetchingTimers(const char *ServerName)
virtual void Action(void) override
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
cIpAddress serverIpAddress
bool Connected(void) const
bool Execute(const char *Command, cStringList *Response=NULL)
bool HasAddress(const char *Address, int Port) const
const char * ServerName(void) const
bool Send(const char *Command)
cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout)
bool GetRemoteTimers(cStringList &Response)
bool Process(cStringList *Response=NULL)
void SetFetchFlag(int Flag)
const char * Connection(void) const
bool HasFetchFlag(int Flag)
virtual ~cSVDRPServerHandler() override
void HandleServerConnection(void)
virtual void Action(void) override
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
void ProcessConnections(void)
cSVDRPServerHandler(int TcpPort)
void WaitUntilReady(void)
cVector< cSVDRPServer * > serverConnections
const char * Host(void) const
const int Timeout(void) const
const char * ApiVersion(void) const
cSVDRPServerParams(const char *Params)
const char * VdrVersion(void) const
const char * Name(void) const
const char * Error(void) const
const int Port(void) const
void CmdMESG(const char *Option)
const char * ClientName(void) const
void CmdPOLL(const char *Option)
void CmdLSTT(const char *Option)
void CmdCLRE(const char *Option)
void Reply(int Code, const char *fmt,...) __attribute__((format(printf
void CmdGRAB(const char *Option)
void CmdMODC(const char *Option)
cPUTEhandler * PUTEhandler
void CmdDELC(const char *Option)
void CmdPLUG(const char *Option)
void CmdMODT(const char *Option)
cIpAddress clientIpAddress
void CmdCPYR(const char *Option)
void CmdLSTC(const char *Option)
void CmdSCAN(const char *Option)
void Close(bool SendReply=false, bool Timeout=false)
void CmdPUTE(const char *Option)
void CmdLSTR(const char *Option)
void CmdSTAT(const char *Option)
void CmdCHAN(const char *Option)
void CmdHELP(const char *Option)
void CmdUPDT(const char *Option)
void CmdREMO(const char *Option)
void CmdAUDI(const char *Option)
void CmdLSTE(const char *Option)
void CmdCONN(const char *Option)
void CmdDELR(const char *Option)
void CmdUPDR(const char *Option)
void CmdVOLU(const char *Option)
void CmdNEWT(const char *Option)
void CmdEDIT(const char *Option)
void CmdPLAY(const char *Option)
void CmdDELT(const char *Option)
void CmdLSTD(const char *Option)
cSVDRPServer(int Socket, const cIpAddress *ClientIpAddress)
void CmdNEXT(const char *Option)
void CmdHITK(const char *Option)
void CmdNEWC(const char *Option)
void CmdPRIM(const char *Option)
void CmdMOVR(const char *Option)
void CmdPING(const char *Option)
void CmdMOVC(const char *Option)
void void PrintHelpTopics(const char **hp)
void Cleanup(time_t Time)
void Dump(const cChannels *Channels, FILE *f, const char *Prefix="", eDumpMode DumpMode=dmAll, time_t AtTime=0) const
static void Cleanup(bool Force=false)
static bool Read(FILE *f=NULL)
const cIpAddress * LastIpAddress(void) const
static bool SendDgram(const char *Dgram, int Port)
cSocket(int Port, bool Tcp)
bool Connect(const char *Address)
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
virtual void Clear(void) override
void SortNumerically(void)
cString & CompactChars(char c)
Compact any sequence of characters 'c' to a single character, and strip all of them from the beginnin...
static cString sprintf(const char *fmt,...) __attribute__((format(printf
cString & Truncate(int Index)
Truncate the string at the given Index (if Index is < 0 it is counted from the end of the string).
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
cThread(const char *Description=NULL, bool LowPriority=false)
Creates a new thread.
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting 'running' to false, so that the Action() loop can finish in an or...
void Set(int Ms=0)
Sets the timer.
bool TimedOut(void) const
void ClrFlags(uint Flags)
void SetFlags(uint Flags)
bool IsPatternTimer(void) const
cString ToDescr(void) const
const char * Remote(void) const
bool Parse(const char *s)
cString ToText(bool UseChannelID=false) const
bool StoreRemoteTimers(const char *ServerName=NULL, const cStringList *RemoteTimers=NULL)
Stores the given list of RemoteTimers, which come from the VDR ServerName, in this list.
static cTimers * GetTimersWrite(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for write access.
static const cTimers * GetTimersRead(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of timers for read access.
virtual void Append(T Data)
static int VideoDiskSpace(int *FreeMB=NULL, int *UsedMB=NULL)
#define LOCK_SCHEDULES_READ
#define LOCK_SCHEDULES_WRITE
bool EnoughFreeDiskSpaceForEdit(const char *FileName)
char * ExchangeChars(char *s, bool ToFileSystem)
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
cRecordingsHandler RecordingsHandler
struct __attribute__((packed))
#define LOCK_RECORDINGS_READ
#define LOCK_RECORDINGS_WRITE
tChannelID & ClrRid(void)
static const tChannelID InvalidID
static tChannelID FromString(const char *s)
cString ToString(void) const
char language[MAXLANGCODE2]
void StopSVDRPHandler(void)
static cPoller SVDRPClientPoller
void SetSVDRPGrabImageDir(const char *GrabImageDir)
static cString grabImageDir
bool GetSVDRPServerNames(cStringList *ServerNames)
Gets a list of all available VDRs this VDR is connected to via SVDRP, and stores it in the given Serv...
static cString RecordingInUseMessage(int Reason, const char *RecordingId, cRecording *Recording)
static cMutex SVDRPHandlerMutex
bool ExecSVDRPCommand(const char *ServerName, const char *Command, cStringList *Response)
Sends the given SVDRP Command string to the remote VDR identified by ServerName and collects all of t...
static cPoller SVDRPServerPoller
static cSVDRPServerHandler * SVDRPServerHandler
void StartSVDRPHandler(void)
cStateKey StateKeySVDRPRemoteTimersPoll(true)
void BroadcastSVDRPCommand(const char *Command)
Sends the given SVDRP Command string to all remote VDRs.
#define SVDRPResonseTimeout
const char * GetHelpPage(const char *Cmd, const char **p)
static cSVDRPClientHandler * SVDRPClientHandler
static bool DumpSVDRPDataTransfer
const char * GetHelpTopic(const char *HelpPage)
#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