diff options
Diffstat (limited to 'svdrp.c')
-rw-r--r-- | svdrp.c | 1335 |
1 files changed, 697 insertions, 638 deletions
@@ -10,7 +10,7 @@ * and interact with the Video Disk Recorder - or write a full featured * graphical interface that sits on top of an SVDRP connection. * - * $Id: svdrp.c 4.2 2015/05/22 11:01:33 kls Exp $ + * $Id: svdrp.c 4.3 2015/09/01 10:34:34 kls Exp $ */ #include "svdrp.h" @@ -45,7 +45,7 @@ static bool DumpSVDRPDataTransfer = false; #define dbgsvdrp(a...) if (DumpSVDRPDataTransfer) fprintf(stderr, a) -static const char *HostName(void) +const char *SVDRPHostName(void) { static char buffer[HOST_NAME_MAX] = ""; if (!*buffer) { @@ -243,7 +243,7 @@ bool cSocket::Connect(const char *Address) LOG_ERROR; return false; } - isyslog("SVDRP > %s:%d connection established", Address, port); + isyslog("SVDRP > %s:%d server connection established", Address, port); return true; } return false; @@ -298,7 +298,7 @@ int cSocket::Accept(void) NewSock = -1; } lastIpAddress.Set((sockaddr *)&Addr); - isyslog("SVDRP < %s connection %s", lastIpAddress.Connection(), Accepted ? "accepted" : "DENIED"); + isyslog("SVDRP < %s client connection %s", lastIpAddress.Connection(), Accepted ? "accepted" : "DENIED"); } else if (FATALERRNO) LOG_ERROR; @@ -336,6 +336,321 @@ cString cSocket::Discover(void) return NULL; } +// --- cSVDRPClient ---------------------------------------------------------- + +class cSVDRPClient { +private: + cIpAddress ipAddress; + cSocket socket; + cString serverName; + int timeout; + cTimeMs pingTime; + cFile file; + int fetchFlags; + void Close(void); +public: + cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout); + ~cSVDRPClient(); + const char *ServerName(void) const { return serverName; } + const char *Connection(void) const { return ipAddress.Connection(); } + bool HasAddress(const char *Address, int Port) const; + bool Send(const char *Command); + bool Process(cStringList *Response = NULL); + bool Execute(const char *Command, cStringList *Response = NULL); + void SetFetchFlag(eSvdrpFetchFlags Flag); + bool HasFetchFlag(eSvdrpFetchFlags Flag); + }; + +static cPoller SVDRPClientPoller; + +cSVDRPClient::cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout) +:ipAddress(Address, Port) +,socket(Port, true) +{ + serverName = ServerName; + timeout = Timeout * 1000 * 9 / 10; // ping after 90% of timeout + pingTime.Set(timeout); + fetchFlags = sffTimers; + if (socket.Connect(Address)) { + if (file.Open(socket.Socket())) { + SVDRPClientPoller.Add(file, false); + dsyslog("SVDRP > %s client created for '%s'", ipAddress.Connection(), *serverName); + SendSVDRPDiscover(Address); + return; + } + } + esyslog("SVDRP > %s ERROR: failed to create client for '%s'", ipAddress.Connection(), *serverName); +} + +cSVDRPClient::~cSVDRPClient() +{ + Close(); + dsyslog("SVDRP > %s client destroyed for '%s'", ipAddress.Connection(), *serverName); +} + +void cSVDRPClient::Close(void) +{ + if (file.IsOpen()) { + SVDRPClientPoller.Del(file, false); + file.Close(); + socket.Close(); + } +} + +bool cSVDRPClient::HasAddress(const char *Address, int Port) const +{ + return strcmp(ipAddress.Address(), Address) == 0 && ipAddress.Port() == Port; +} + +bool cSVDRPClient::Send(const char *Command) +{ + pingTime.Set(timeout); + dbgsvdrp("> %s: %s\n", *serverName, Command); + if (safe_write(file, Command, strlen(Command) + 1) < 0) { + LOG_ERROR; + return false; + } + return true; +} + +bool cSVDRPClient::Process(cStringList *Response) +{ + if (file.IsOpen()) { + char input[BUFSIZ]; + int numChars = 0; +#define SVDRPResonseTimeout 5000 // ms + cTimeMs Timeout(SVDRPResonseTimeout); + for (;;) { + if (file.Ready(false)) { + unsigned char c; + int r = safe_read(file, &c, 1); + if (r > 0) { + if (c == '\n' || c == 0x00) { + // strip trailing whitespace: + while (numChars > 0 && strchr(" \t\r\n", input[numChars - 1])) + input[--numChars] = 0; + // make sure the string is terminated: + input[numChars] = 0; + dbgsvdrp("< %s: %s\n", *serverName, input); + if (Response) { + Response->Append(strdup(input)); + if (numChars >= 4 && input[3] != '-') // no more lines will follow + break; + } + else { + switch (atoi(input)) { + case 220: if (numChars > 4) { + char *n = input + 4; + if (char *t = strchr(n, ' ')) { + *t = 0; + if (strcmp(n, serverName) != 0) { + serverName = n; + dsyslog("SVDRP < %s remote server name is '%s'", ipAddress.Connection(), *serverName); + } + } + } + break; + case 221: dsyslog("SVDRP < %s remote server closed connection to '%s'", ipAddress.Connection(), *serverName); + Close(); + break; + } + } + numChars = 0; + } + else { + if (numChars >= int(sizeof(input))) { + esyslog("SVDRP < %s ERROR: out of memory", ipAddress.Connection()); + Close(); + break; + } + input[numChars++] = c; + input[numChars] = 0; + } + Timeout.Set(SVDRPResonseTimeout); + } + else if (r <= 0) { + isyslog("SVDRP < %s lost connection to remote server '%s'", ipAddress.Connection(), *serverName); + Close(); + } + } + else if (!Response) + break; + else if (Timeout.TimedOut()) { + esyslog("SVDRP < %s timeout while waiting for response from '%s'", ipAddress.Connection(), *serverName); + return false; + } + } + if (pingTime.TimedOut()) + Execute("PING"); + } + return file.IsOpen(); +} + +bool cSVDRPClient::Execute(const char *Command, cStringList *Response) +{ + if (Response) + Response->Clear(); + return Send(Command) && Process(Response); +} + +void cSVDRPClient::SetFetchFlag(eSvdrpFetchFlags Flags) +{ + fetchFlags |= Flags; +} + +bool cSVDRPClient::HasFetchFlag(eSvdrpFetchFlags Flag) +{ + bool Result = (fetchFlags & Flag); + fetchFlags &= ~Flag; + return Result; +} + +// --- cSVDRPClientHandler --------------------------------------------------- + +class cSVDRPClientHandler : public cThread { +private: + cMutex mutex; + cSocket udpSocket; + int tcpPort; + cVector<cSVDRPClient *> clientConnections; + void HandleClientConnection(void); + void ProcessConnections(void); + cSVDRPClient *GetClientForServer(const char *ServerName); +protected: + virtual void Action(void); +public: + cSVDRPClientHandler(int UdpPort, int TcpPort); + virtual ~cSVDRPClientHandler(); + void SendDiscover(const char *Address = NULL); + bool Execute(const char *ServerName, const char *Command, cStringList *Response); + bool GetServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlags = sffNone); + bool TriggerFetchingTimers(const char *ServerName); + }; + +static cSVDRPClientHandler *SVDRPClientHandler = NULL; + +cSVDRPClientHandler::cSVDRPClientHandler(int UdpPort, int TcpPort) +:cThread("SVDRP client handler", true) +,udpSocket(UdpPort, false) +{ + tcpPort = TcpPort; +} + +cSVDRPClientHandler::~cSVDRPClientHandler() +{ + Cancel(3); + for (int i = 0; i < clientConnections.Size(); i++) + delete clientConnections[i]; +} + +cSVDRPClient *cSVDRPClientHandler::GetClientForServer(const char *ServerName) +{ + for (int i = 0; i < clientConnections.Size(); i++) { + if (strcmp(clientConnections[i]->ServerName(), ServerName) == 0) + return clientConnections[i]; + } + return NULL; +} + +void cSVDRPClientHandler::SendDiscover(const char *Address) +{ + cMutexLock MutexLock(&mutex); + cString Dgram = cString::sprintf("SVDRP:discover name:%s port:%d vdrversion:%d apiversion:%d timeout:%d", SVDRPHostName(), tcpPort, VDRVERSNUM, APIVERSNUM, Setup.SVDRPTimeout); + udpSocket.SendDgram(Dgram, udpSocket.Port(), Address); +} + +void cSVDRPClientHandler::ProcessConnections(void) +{ + cMutexLock MutexLock(&mutex); + for (int i = 0; i < clientConnections.Size(); i++) { + if (!clientConnections[i]->Process()) { + delete clientConnections[i]; + clientConnections.Remove(i); + i--; + } + } +} + +void cSVDRPClientHandler::HandleClientConnection(void) +{ + cString NewDiscover = udpSocket.Discover(); + if (*NewDiscover) { + cString p = strgetval(NewDiscover, "port", ':'); + if (*p) { + int Port = atoi(p); + for (int i = 0; i < clientConnections.Size(); i++) { + if (clientConnections[i]->HasAddress(udpSocket.LastIpAddress()->Address(), Port)) { + dsyslog("SVDRP < %s connection to '%s' confirmed", clientConnections[i]->Connection(), clientConnections[i]->ServerName()); + return; + } + } + cString ServerName = strgetval(NewDiscover, "name", ':'); + if (*ServerName) { + cString t = strgetval(NewDiscover, "timeout", ':'); + if (*t) { + int Timeout = atoi(t); + if (Timeout > 10) // don't let it get too small + clientConnections.Append(new cSVDRPClient(udpSocket.LastIpAddress()->Address(), Port, ServerName, Timeout)); + else + esyslog("SVDRP < %s ERROR: invalid timeout (%d)", udpSocket.LastIpAddress()->Connection(), Timeout); + } + else + esyslog("SVDRP < %s ERROR: missing timeout", udpSocket.LastIpAddress()->Connection()); + } + else + esyslog("SVDRP < %s ERROR: missing server name", udpSocket.LastIpAddress()->Connection()); + } + else + esyslog("SVDRP < %s ERROR: missing port number", udpSocket.LastIpAddress()->Connection()); + } +} + +void cSVDRPClientHandler::Action(void) +{ + if (udpSocket.Listen()) { + SVDRPClientPoller.Add(udpSocket.Socket(), false); + SendDiscover(); + while (Running()) { + SVDRPClientPoller.Poll(1000); + cMutexLock MutexLock(&mutex); + HandleClientConnection(); + ProcessConnections(); + } + SVDRPClientPoller.Del(udpSocket.Socket(), false); + udpSocket.Close(); + } +} + +bool cSVDRPClientHandler::Execute(const char *ServerName, const char *Command, cStringList *Response) +{ + cMutexLock MutexLock(&mutex); + if (cSVDRPClient *Client = GetClientForServer(ServerName)) + return Client->Execute(Command, Response); + return false; +} + +bool cSVDRPClientHandler::GetServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlag) +{ + cMutexLock MutexLock(&mutex); + ServerNames->Clear(); + for (int i = 0; i < clientConnections.Size(); i++) { + cSVDRPClient *Client = clientConnections[i]; + if (FetchFlag == sffNone || Client->HasFetchFlag(FetchFlag)) + ServerNames->Append(strdup(Client->ServerName())); + } + return ServerNames->Size() > 0; +} + +bool cSVDRPClientHandler::TriggerFetchingTimers(const char *ServerName) +{ + cMutexLock MutexLock(&mutex); + if (cSVDRPClient *Client = GetClientForServer(ServerName)) { + Client->SetFetchFlag(sffTimers); + return true; + } + return false; +} + // --- cPUTEhandler ---------------------------------------------------------- class cPUTEhandler { @@ -465,7 +780,8 @@ const char *HelpPages[] = { " List timers. Without option, all timers are listed. Otherwise\n" " only the given timer is listed. If the keyword 'id' is given, the\n" " channels will be listed with their unique channel ids instead of\n" - " their numbers.", + " their numbers. This command lists only the timers that are defined\n" + " locally on this VDR, not any remote timers from other VDRs.", "MESG <message>\n" " Displays the given message on the OSD. The message will be queued\n" " and displayed whenever this is suitable.\n", @@ -523,6 +839,10 @@ const char *HelpPages[] = { " If 'help' is followed by a command, the detailed help for that command is\n" " given. The keyword 'main' initiates a call to the main menu function of the\n" " given plugin.\n", + "POLL timers\n" + " Used by peer-to-peer connections between VDRs to inform other machines\n" + " about changes to timers. The receiving VDR shall use LSTT to query the\n" + " remote machine's timers and update its list of timers accordingly.\n", "PUTE [ file ]\n" " Put data into the EPG list. The data entered has to strictly follow the\n" " format defined in vdr(5) for the 'epg.data' file. A '.' on a line\n" @@ -542,7 +862,7 @@ const char *HelpPages[] = { "UPDT <settings>\n" " Updates a timer. Settings must be in the same format as returned\n" " by the LSTT command. If a timer with the same channel, day, start\n" - " and stop time does not yet exists, it will be created.", + " and stop time does not yet exist, it will be created.", "UPDR\n" " Initiates a re-read of the recordings directory, which is the SVDRP\n" " equivalent to 'touch .update'.", @@ -617,7 +937,6 @@ private: int socket; cString connection; cFile file; - cRecordings recordings; cPUTEhandler *PUTEhandler; int numChars; int length; @@ -651,6 +970,7 @@ private: void CmdPING(const char *Option); void CmdPLAY(const char *Option); void CmdPLUG(const char *Option); + void CmdPOLL(const char *Option); void CmdPUTE(const char *Option); void CmdREMO(const char *Option); void CmdSCAN(const char *Option); @@ -667,7 +987,6 @@ public: }; static cPoller SVDRPServerPoller; -static cPoller SVDRPClientPoller; cSVDRPServer::cSVDRPServer(int Socket, const char *Connection) { @@ -680,7 +999,7 @@ cSVDRPServer::cSVDRPServer(int Socket, const char *Connection) lastActivity = time(NULL); if (file.Open(socket)) { time_t now = time(NULL); - Reply(220, "%s SVDRP VideoDiskRecorder %s; %s; %s", HostName(), VDRVERSION, *TimeToString(now), cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8"); + Reply(220, "%s SVDRP VideoDiskRecorder %s; %s; %s", SVDRPHostName(), VDRVERSION, *TimeToString(now), cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8"); SVDRPServerPoller.Add(file, false); } dsyslog("SVDRP < %s server created", *connection); @@ -697,7 +1016,7 @@ void cSVDRPServer::Close(bool SendReply, bool Timeout) { if (file.IsOpen()) { if (SendReply) { - Reply(221, "%s closing connection%s", HostName(), Timeout ? " (timeout)" : ""); + Reply(221, "%s closing connection%s", SVDRPHostName(), Timeout ? " (timeout)" : ""); } isyslog("SVDRP < %s connection closed", *connection); SVDRPServerPoller.Del(file, false); @@ -775,12 +1094,13 @@ void cSVDRPServer::PrintHelpTopics(const char **hp) void cSVDRPServer::CmdCHAN(const char *Option) { + LOCK_CHANNELS_READ; if (*Option) { int n = -1; int d = 0; if (isnumber(Option)) { int o = strtol(Option, NULL, 10); - if (o >= 1 && o <= Channels.MaxNumber()) + if (o >= 1 && o <= cChannels::MaxNumber()) n = o; } else if (strcmp(Option, "-") == 0) { @@ -792,35 +1112,31 @@ void cSVDRPServer::CmdCHAN(const char *Option) } else if (strcmp(Option, "+") == 0) { n = cDevice::CurrentChannel(); - if (n < Channels.MaxNumber()) { + if (n < cChannels::MaxNumber()) { n++; d = 1; } } + else if (const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(Option))) + n = Channel->Number(); else { - cChannel *channel = Channels.GetByChannelID(tChannelID::FromString(Option)); - if (channel) - n = channel->Number(); - else { - for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) { - if (!channel->GroupSep()) { - if (strcasecmp(channel->Name(), Option) == 0) { - n = channel->Number(); - break; - } + for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { + if (!Channel->GroupSep()) { + if (strcasecmp(Channel->Name(), Option) == 0) { + n = Channel->Number(); + break; } } - } + } } if (n < 0) { Reply(501, "Undefined channel \"%s\"", Option); return; } if (!d) { - cChannel *channel = Channels.GetByNumber(n); - if (channel) { - if (!cDevice::PrimaryDevice()->SwitchChannel(channel, true)) { - Reply(554, "Error switching to channel \"%d\"", channel->Number()); + if (const cChannel *Channel = Channels->GetByNumber(n)) { + if (!cDevice::PrimaryDevice()->SwitchChannel(Channel, true)) { + Reply(554, "Error switching to channel \"%d\"", Channel->Number()); return; } } @@ -832,9 +1148,8 @@ void cSVDRPServer::CmdCHAN(const char *Option) else cDevice::SwitchChannel(d); } - cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel()); - if (channel) - Reply(250, "%d %s", channel->Number(), channel->Name()); + if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel())) + Reply(250, "%d %s", Channel->Number(), Channel->Name()); else Reply(550, "Unable to find channel \"%d\"", cDevice::CurrentChannel()); } @@ -842,16 +1157,18 @@ void cSVDRPServer::CmdCHAN(const char *Option) void cSVDRPServer::CmdCLRE(const char *Option) { if (*Option) { + LOCK_TIMERS_WRITE; + LOCK_CHANNELS_READ; tChannelID ChannelID = tChannelID::InvalidID; if (isnumber(Option)) { int o = strtol(Option, NULL, 10); - if (o >= 1 && o <= Channels.MaxNumber()) - ChannelID = Channels.GetByNumber(o)->GetChannelID(); + if (o >= 1 && o <= cChannels::MaxNumber()) + ChannelID = Channels->GetByNumber(o)->GetChannelID(); } else { ChannelID = tChannelID::FromString(Option); if (ChannelID == tChannelID::InvalidID) { - for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) { + for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { if (!Channel->GroupSep()) { if (strcasecmp(Channel->Name(), Option) == 0) { ChannelID = Channel->GetChannelID(); @@ -862,45 +1179,41 @@ void cSVDRPServer::CmdCLRE(const char *Option) } } if (!(ChannelID == tChannelID::InvalidID)) { - cSchedulesLock SchedulesLock(true, 1000); - cSchedules *s = (cSchedules *)cSchedules::Schedules(SchedulesLock); - if (s) { - cSchedule *Schedule = NULL; - ChannelID.ClrRid(); - for (cSchedule *p = s->First(); p; p = s->Next(p)) { - if (p->ChannelID() == ChannelID) { - Schedule = p; - break; - } + LOCK_SCHEDULES_WRITE; + cSchedule *Schedule = NULL; + ChannelID.ClrRid(); + for (cSchedule *p = Schedules->First(); p; p = Schedules->Next(p)) { + if (p->ChannelID() == ChannelID) { + Schedule = p; + break; } - if (Schedule) { - for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) { - if (ChannelID == Timer->Channel()->GetChannelID().ClrRid()) - Timer->SetEvent(NULL); - } - Schedule->Cleanup(INT_MAX); - cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME); - Reply(250, "EPG data of channel \"%s\" cleared", Option); - } - else { - Reply(550, "No EPG data found for channel \"%s\"", Option); - return; - } + } + if (Schedule) { + for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) { + if (ChannelID == Timer->Channel()->GetChannelID().ClrRid()) + Timer->SetEvent(NULL); + } + Schedule->Cleanup(INT_MAX); + cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME); + Reply(250, "EPG data of channel \"%s\" cleared", Option); + } + else { + Reply(550, "No EPG data found for channel \"%s\"", Option); + return; } - else - Reply(451, "Can't get EPG data"); } else Reply(501, "Undefined channel \"%s\"", Option); } else { + LOCK_TIMERS_WRITE; + LOCK_SCHEDULES_WRITE; + for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) + Timer->SetEvent(NULL); // processing all timers here (local *and* remote) + for (cSchedule *Schedule = Schedules->First(); Schedule; Schedule = Schedules->Next(Schedule)) + Schedule->Cleanup(INT_MAX); cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME); - if (cSchedules::ClearAll()) { - Reply(250, "EPG data cleared"); - cEitFilter::SetDisableUntil(time(NULL) + EITDISABLETIME); - } - else - Reply(451, "Error while clearing EPG data"); + Reply(250, "EPG data cleared"); } } @@ -908,41 +1221,38 @@ void cSVDRPServer::CmdDELC(const char *Option) { if (*Option) { if (isnumber(Option)) { - if (!Channels.BeingEdited()) { - cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10)); - if (channel) { - for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) { - if (timer->Channel() == channel) { - Reply(550, "Channel \"%s\" is in use by timer %d", Option, timer->Index() + 1); - return; - } - } - int CurrentChannelNr = cDevice::CurrentChannel(); - cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr); - if (CurrentChannel && channel == CurrentChannel) { - int n = Channels.GetNextNormal(CurrentChannel->Index()); - if (n < 0) - n = Channels.GetPrevNormal(CurrentChannel->Index()); - CurrentChannel = Channels.Get(n); - CurrentChannelNr = 0; // triggers channel switch below - } - Channels.Del(channel); - Channels.ReNumber(); - Channels.SetModified(true); - isyslog("SVDRP < %s channel %s deleted", *connection, Option); - if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { - if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) - Channels.SwitchTo(CurrentChannel->Number()); - else - cDevice::SetCurrentChannel(CurrentChannel); - } - Reply(250, "Channel \"%s\" deleted", Option); + LOCK_TIMERS_READ; + LOCK_CHANNELS_WRITE; + Channels->SetExplicitModify(); + if (cChannel *Channel = Channels->GetByNumber(strtol(Option, NULL, 10))) { + if (const cTimer *Timer = Timers->UsesChannel(Channel)) { + Reply(550, "Channel \"%s\" is in use by timer %d", Option, Timer->Index() + 1); + return; } - else - Reply(501, "Channel \"%s\" not defined", Option); + int CurrentChannelNr = cDevice::CurrentChannel(); + cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr); + if (CurrentChannel && Channel == CurrentChannel) { + int n = Channels->GetNextNormal(CurrentChannel->Index()); + if (n < 0) + n = Channels->GetPrevNormal(CurrentChannel->Index()); + CurrentChannel = Channels->Get(n); + CurrentChannelNr = 0; // triggers channel switch below + } + Channels->Del(Channel); + Channels->ReNumber(); + Channels->SetModifiedByUser(); + Channels->SetModified(); + isyslog("SVDRP < %s channel %s deleted", *connection, Option); + if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { + if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) + Channels->SwitchTo(CurrentChannel->Number()); + else + cDevice::SetCurrentChannel(CurrentChannel); + } + Reply(250, "Channel \"%s\" deleted", Option); } else - Reply(550, "Channels are being edited - try again later"); + Reply(501, "Channel \"%s\" not defined", Option); } else Reply(501, "Error in channel number \"%s\"", Option); @@ -971,21 +1281,23 @@ void cSVDRPServer::CmdDELR(const char *Option) { if (*Option) { if (isnumber(Option)) { - cRecording *recording = recordings.Get(strtol(Option, NULL, 10) - 1); - if (recording) { - if (int RecordingInUse = recording->IsInUse()) - Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, recording)); + LOCK_RECORDINGS_WRITE; + Recordings->SetExplicitModify(); + if (cRecording *Recording = Recordings->Get(strtol(Option, NULL, 10) - 1)) { + if (int RecordingInUse = Recording->IsInUse()) + Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording)); else { - if (recording->Delete()) { + if (Recording->Delete()) { + Recordings->DelByName(Recording->FileName()); + Recordings->SetModified(); Reply(250, "Recording \"%s\" deleted", Option); - Recordings.DelByName(recording->FileName()); } else Reply(554, "Error while deleting recording!"); } } else - Reply(550, "Recording \"%s\" not found%s", Option, recordings.Count() ? "" : " (use LSTR before deleting)"); + Reply(550, "Recording \"%s\" not found", Option); } else Reply(501, "Error in recording number \"%s\"", Option); @@ -998,23 +1310,21 @@ void cSVDRPServer::CmdDELT(const char *Option) { if (*Option) { if (isnumber(Option)) { - if (!Timers.BeingEdited()) { - cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1); - if (timer) { - if (!timer->Recording()) { - isyslog("SVDRP < %s deleting timer %s", *connection, *timer->ToDescr()); - Timers.Del(timer); - Timers.SetModified(); - Reply(250, "Timer \"%s\" deleted", Option); - } - else - Reply(550, "Timer \"%s\" is recording", Option); + LOCK_TIMERS_WRITE; + Timers->SetExplicitModify(); + cTimer *Timer = Timers->Get(strtol(Option, NULL, 10) - 1); + if (Timer && !Timer->Remote()) { + if (!Timer->Recording()) { + isyslog("SVDRP < %s deleting timer %s", *connection, *Timer->ToDescr()); + Timers->Del(Timer); + Timers->SetModified(); + Reply(250, "Timer \"%s\" deleted", Option); } else - Reply(501, "Timer \"%s\" not defined", Option); + Reply(550, "Timer \"%s\" is recording", Option); } else - Reply(550, "Timers are being edited - try again later"); + Reply(501, "Timer \"%s\" not defined", Option); } else Reply(501, "Error in timer number \"%s\"", Option); @@ -1027,12 +1337,12 @@ void cSVDRPServer::CmdEDIT(const char *Option) { if (*Option) { if (isnumber(Option)) { - cRecording *recording = recordings.Get(strtol(Option, NULL, 10) - 1); - if (recording) { + LOCK_RECORDINGS_READ; + if (const cRecording *Recording = Recordings->Get(strtol(Option, NULL, 10) - 1)) { cMarks Marks; - if (Marks.Load(recording->FileName(), recording->FramesPerSecond(), recording->IsPesRecording()) && Marks.Count()) { - if (RecordingsHandler.Add(ruCut, recording->FileName())) - Reply(250, "Editing recording \"%s\" [%s]", Option, recording->Title()); + if (Marks.Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.Count()) { + if (RecordingsHandler.Add(ruCut, Recording->FileName())) + Reply(250, "Editing recording \"%s\" [%s]", Option, Recording->Title()); else Reply(554, "Can't start editing process"); } @@ -1040,7 +1350,7 @@ void cSVDRPServer::CmdEDIT(const char *Option) Reply(554, "No editing marks defined"); } else - Reply(550, "Recording \"%s\" not found%s", Option, recordings.Count() ? "" : " (use LSTR before editing)"); + Reply(550, "Recording \"%s\" not found", Option); } else Reply(501, "Error in recording number \"%s\"", Option); @@ -1255,40 +1565,40 @@ void cSVDRPServer::CmdHITK(const char *Option) void cSVDRPServer::CmdLSTC(const char *Option) { + LOCK_CHANNELS_READ; bool WithGroupSeps = strcasecmp(Option, ":groups") == 0; if (*Option && !WithGroupSeps) { if (isnumber(Option)) { - cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10)); - if (channel) - Reply(250, "%d %s", channel->Number(), *channel->ToText()); + if (const cChannel *Channel = Channels->GetByNumber(strtol(Option, NULL, 10))) + Reply(250, "%d %s", Channel->Number(), *Channel->ToText()); else Reply(501, "Channel \"%s\" not defined", Option); } else { - cChannel *next = Channels.GetByChannelID(tChannelID::FromString(Option)); - if (!next) { - for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) { - if (!channel->GroupSep()) { - if (strcasestr(channel->Name(), Option)) { - if (next) - Reply(-250, "%d %s", next->Number(), *next->ToText()); - next = channel; + const cChannel *Next = Channels->GetByChannelID(tChannelID::FromString(Option)); + if (!Next) { + for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { + if (!Channel->GroupSep()) { + if (strcasestr(Channel->Name(), Option)) { + if (Next) + Reply(-250, "%d %s", Next->Number(), *Next->ToText()); + Next = Channel; } } } } - if (next) - Reply(250, "%d %s", next->Number(), *next->ToText()); + if (Next) + Reply(250, "%d %s", Next->Number(), *Next->ToText()); else Reply(501, "Channel \"%s\" not defined", Option); } } - else if (Channels.MaxNumber() >= 1) { - for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) { + else if (cChannels::MaxNumber() >= 1) { + for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { if (WithGroupSeps) - Reply(channel->Next() ? -250: 250, "%d %s", channel->GroupSep() ? 0 : channel->Number(), *channel->ToText()); - else if (!channel->GroupSep()) - Reply(channel->Number() < Channels.MaxNumber() ? -250 : 250, "%d %s", channel->Number(), *channel->ToText()); + Reply(Channel->Next() ? -250: 250, "%d %s", Channel->GroupSep() ? 0 : Channel->Number(), *Channel->ToText()); + else if (!Channel->GroupSep()) + Reply(Channel->Number() < cChannels::MaxNumber() ? -250 : 250, "%d %s", Channel->Number(), *Channel->ToText()); } } else @@ -1297,92 +1607,88 @@ void cSVDRPServer::CmdLSTC(const char *Option) void cSVDRPServer::CmdLSTE(const char *Option) { - cSchedulesLock SchedulesLock; - const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock); - if (Schedules) { - const cSchedule* Schedule = NULL; - eDumpMode DumpMode = dmAll; - time_t AtTime = 0; - if (*Option) { - char buf[strlen(Option) + 1]; - strcpy(buf, Option); - const char *delim = " \t"; - char *strtok_next; - char *p = strtok_r(buf, delim, &strtok_next); - while (p && DumpMode == dmAll) { - if (strcasecmp(p, "NOW") == 0) - DumpMode = dmPresent; - else if (strcasecmp(p, "NEXT") == 0) - DumpMode = dmFollowing; - else if (strcasecmp(p, "AT") == 0) { - DumpMode = dmAtTime; - if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) { - if (isnumber(p)) - AtTime = strtol(p, NULL, 10); - else { - Reply(501, "Invalid time"); - return; - } - } + LOCK_SCHEDULES_READ; + const cSchedule* Schedule = NULL; + eDumpMode DumpMode = dmAll; + time_t AtTime = 0; + if (*Option) { + char buf[strlen(Option) + 1]; + strcpy(buf, Option); + const char *delim = " \t"; + char *strtok_next; + char *p = strtok_r(buf, delim, &strtok_next); + while (p && DumpMode == dmAll) { + if (strcasecmp(p, "NOW") == 0) + DumpMode = dmPresent; + else if (strcasecmp(p, "NEXT") == 0) + DumpMode = dmFollowing; + else if (strcasecmp(p, "AT") == 0) { + DumpMode = dmAtTime; + if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) { + if (isnumber(p)) + AtTime = strtol(p, NULL, 10); else { - Reply(501, "Missing time"); + Reply(501, "Invalid time"); return; } } - else if (!Schedule) { - cChannel* Channel = NULL; - if (isnumber(p)) - Channel = Channels.GetByNumber(strtol(Option, NULL, 10)); - else - Channel = Channels.GetByChannelID(tChannelID::FromString(Option)); - if (Channel) { - Schedule = Schedules->GetSchedule(Channel); - if (!Schedule) { - Reply(550, "No schedule found"); - return; - } - } - else { - Reply(550, "Channel \"%s\" not defined", p); + else { + Reply(501, "Missing time"); + return; + } + } + else if (!Schedule) { + LOCK_CHANNELS_READ; + const cChannel* Channel = NULL; + if (isnumber(p)) + Channel = Channels->GetByNumber(strtol(Option, NULL, 10)); + else + Channel = Channels->GetByChannelID(tChannelID::FromString(Option)); + if (Channel) { + Schedule = Schedules->GetSchedule(Channel); + if (!Schedule) { + Reply(550, "No schedule found"); return; } } else { - Reply(501, "Unknown option: \"%s\"", p); + Reply(550, "Channel \"%s\" not defined", p); return; } - p = strtok_r(NULL, delim, &strtok_next); } - } - int fd = dup(file); - if (fd) { - FILE *f = fdopen(fd, "w"); - if (f) { - if (Schedule) - Schedule->Dump(f, "215-", DumpMode, AtTime); - else - Schedules->Dump(f, "215-", DumpMode, AtTime); - fflush(f); - Reply(215, "End of EPG data"); - fclose(f); - } - else { - Reply(451, "Can't open file connection"); - close(fd); + else { + Reply(501, "Unknown option: \"%s\"", p); + return; + } + p = strtok_r(NULL, delim, &strtok_next); } + } + int fd = dup(file); + if (fd) { + FILE *f = fdopen(fd, "w"); + if (f) { + if (Schedule) + Schedule->Dump(f, "215-", DumpMode, AtTime); + else + Schedules->Dump(f, "215-", DumpMode, AtTime); + fflush(f); + Reply(215, "End of EPG data"); + fclose(f); + } + else { + Reply(451, "Can't open file connection"); + close(fd); } - else - Reply(451, "Can't dup stream descriptor"); } else - Reply(451, "Can't get EPG data"); + Reply(451, "Can't dup stream descriptor"); } void cSVDRPServer::CmdLSTR(const char *Option) { int Number = 0; bool Path = false; - recordings.Update(true); + LOCK_RECORDINGS_READ; if (*Option) { char buf[strlen(Option) + 1]; strcpy(buf, Option); @@ -1407,14 +1713,13 @@ void cSVDRPServer::CmdLSTR(const char *Option) p = strtok_r(NULL, delim, &strtok_next); } if (Number) { - cRecording *recording = recordings.Get(strtol(Option, NULL, 10) - 1); - if (recording) { + if (const cRecording *Recording = Recordings->Get(strtol(Option, NULL, 10) - 1)) { FILE *f = fdopen(file, "w"); if (f) { if (Path) - Reply(250, "%s", recording->FileName()); + Reply(250, "%s", Recording->FileName()); else { - recording->Info()->Write(f, "215-"); + Recording->Info()->Write(f, "215-"); fflush(f); Reply(215, "End of recording information"); } @@ -1427,11 +1732,11 @@ void cSVDRPServer::CmdLSTR(const char *Option) Reply(550, "Recording \"%s\" not found", Option); } } - else if (recordings.Count()) { - cRecording *recording = recordings.First(); - while (recording) { - Reply(recording == recordings.Last() ? 250 : -250, "%d %s", recording->Index() + 1, recording->Title(' ', true)); - recording = recordings.Next(recording); + else if (Recordings->Count()) { + const cRecording *Recording = Recordings->First(); + while (Recording) { + Reply(Recording == Recordings->Last() ? 250 : -250, "%d %s", Recording->Index() + 1, Recording->Title(' ', true)); + Recording = Recordings->Next(Recording); } } else @@ -1460,24 +1765,34 @@ void cSVDRPServer::CmdLSTT(const char *Option) p = strtok_r(NULL, delim, &strtok_next); } } + LOCK_TIMERS_READ; if (Number) { - cTimer *timer = Timers.Get(Number - 1); - if (timer) - Reply(250, "%d %s", timer->Index() + 1, *timer->ToText(Id)); - else - Reply(501, "Timer \"%s\" not defined", Option); + for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) { + if (!Timer->Remote()) { + if (Timer->Index() + 1 == Number) { + Reply(250, "%d %s", Timer->Index() + 1, *Timer->ToText(Id)); + return; + } + } + } + Reply(501, "Timer \"%s\" not defined", Option); + return; } - else if (Timers.Count()) { - for (int i = 0; i < Timers.Count(); i++) { - cTimer *timer = Timers.Get(i); - if (timer) - Reply(i < Timers.Count() - 1 ? -250 : 250, "%d %s", timer->Index() + 1, *timer->ToText(Id)); - else - Reply(501, "Timer \"%d\" not found", i + 1); + else { + cVector<const cTimer *> LocalTimers; + for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) { + if (!Timer->Remote()) + LocalTimers.Append(Timer); } + if (LocalTimers.Size()) { + for (int i = 0; i < LocalTimers.Size(); i++) { + const cTimer *Timer = LocalTimers[i]; + Reply(i < LocalTimers.Size() - 1 ? -250 : 250, "%d %s", Timer->Index() + 1, *Timer->ToText(Id)); + } + return; + } } - else - Reply(550, "No timers defined"); + Reply(550, "No timers defined"); } void cSVDRPServer::CmdMESG(const char *Option) @@ -1498,29 +1813,27 @@ void cSVDRPServer::CmdMODC(const char *Option) int n = strtol(Option, &tail, 10); if (tail && tail != Option) { tail = skipspace(tail); - if (!Channels.BeingEdited()) { - cChannel *channel = Channels.GetByNumber(n); - if (channel) { - cChannel ch; - if (ch.Parse(tail)) { - if (Channels.HasUniqueChannelID(&ch, channel)) { - *channel = ch; - Channels.ReNumber(); - Channels.SetModified(true); - isyslog("SVDRP < %s modifed channel %d %s", *connection, channel->Number(), *channel->ToText()); - Reply(250, "%d %s", channel->Number(), *channel->ToText()); - } - else - Reply(501, "Channel settings are not unique"); + LOCK_CHANNELS_WRITE; + Channels->SetExplicitModify(); + if (cChannel *Channel = Channels->GetByNumber(n)) { + cChannel ch; + if (ch.Parse(tail)) { + if (Channels->HasUniqueChannelID(&ch, Channel)) { + *Channel = ch; + Channels->ReNumber(); + Channels->SetModifiedByUser(); + Channels->SetModified(); + isyslog("SVDRP < %s modifed channel %d %s", *connection, Channel->Number(), *Channel->ToText()); + Reply(250, "%d %s", Channel->Number(), *Channel->ToText()); } else - Reply(501, "Error in channel settings"); + Reply(501, "Channel settings are not unique"); } else - Reply(501, "Channel \"%d\" not defined", n); + Reply(501, "Error in channel settings"); } else - Reply(550, "Channels are being edited - try again later"); + Reply(501, "Channel \"%d\" not defined", n); } else Reply(501, "Error in channel number"); @@ -1536,28 +1849,25 @@ void cSVDRPServer::CmdMODT(const char *Option) int n = strtol(Option, &tail, 10); if (tail && tail != Option) { tail = skipspace(tail); - if (!Timers.BeingEdited()) { - cTimer *timer = Timers.Get(n - 1); - if (timer) { - cTimer t = *timer; - if (strcasecmp(tail, "ON") == 0) - t.SetFlags(tfActive); - else if (strcasecmp(tail, "OFF") == 0) - t.ClrFlags(tfActive); - else if (!t.Parse(tail)) { - Reply(501, "Error in timer settings"); - return; - } - *timer = t; - Timers.SetModified(); - isyslog("SVDRP < %s timer %s modified (%s)", *connection, *timer->ToDescr(), timer->HasFlags(tfActive) ? "active" : "inactive"); - Reply(250, "%d %s", timer->Index() + 1, *timer->ToText()); + LOCK_TIMERS_WRITE; + Timers->SetExplicitModify(); + if (cTimer *Timer = Timers->Get(n - 1)) { + cTimer t = *Timer; + if (strcasecmp(tail, "ON") == 0) + t.SetFlags(tfActive); + else if (strcasecmp(tail, "OFF") == 0) + t.ClrFlags(tfActive); + else if (!t.Parse(tail)) { + Reply(501, "Error in timer settings"); + return; } - else - Reply(501, "Timer \"%d\" not defined", n); + *Timer = t; + Timers->SetModified(); + isyslog("SVDRP < %s timer %s modified (%s)", *connection, *Timer->ToDescr(), Timer->HasFlags(tfActive) ? "active" : "inactive"); + Reply(250, "%d %s", Timer->Index() + 1, *Timer->ToText()); } else - Reply(550, "Timers are being edited - try again later"); + Reply(501, "Timer \"%d\" not defined", n); } else Reply(501, "Error in timer number"); @@ -1569,51 +1879,51 @@ void cSVDRPServer::CmdMODT(const char *Option) void cSVDRPServer::CmdMOVC(const char *Option) { if (*Option) { - if (!Channels.BeingEdited() && !Timers.BeingEdited()) { - char *tail; - int From = strtol(Option, &tail, 10); + char *tail; + int From = strtol(Option, &tail, 10); + if (tail && tail != Option) { + tail = skipspace(tail); if (tail && tail != Option) { - tail = skipspace(tail); - if (tail && tail != Option) { - int To = strtol(tail, NULL, 10); - int CurrentChannelNr = cDevice::CurrentChannel(); - cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr); - cChannel *FromChannel = Channels.GetByNumber(From); - if (FromChannel) { - cChannel *ToChannel = Channels.GetByNumber(To); - if (ToChannel) { - int FromNumber = FromChannel->Number(); - int ToNumber = ToChannel->Number(); - if (FromNumber != ToNumber) { - Channels.Move(FromChannel, ToChannel); - Channels.ReNumber(); - Channels.SetModified(true); - if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { - if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) - Channels.SwitchTo(CurrentChannel->Number()); - else - cDevice::SetCurrentChannel(CurrentChannel); - } - isyslog("SVDRP < %s channel %d moved to %d", *connection, FromNumber, ToNumber); - Reply(250,"Channel \"%d\" moved to \"%d\"", From, To); + LOCK_TIMERS_READ; // necessary to keep timers and channels in sync! + LOCK_CHANNELS_WRITE; + Channels->SetExplicitModify(); + int To = strtol(tail, NULL, 10); + int CurrentChannelNr = cDevice::CurrentChannel(); + const cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr); + cChannel *FromChannel = Channels->GetByNumber(From); + if (FromChannel) { + cChannel *ToChannel = Channels->GetByNumber(To); + if (ToChannel) { + int FromNumber = FromChannel->Number(); + int ToNumber = ToChannel->Number(); + if (FromNumber != ToNumber) { + Channels->Move(FromChannel, ToChannel); + Channels->ReNumber(); + Channels->SetModifiedByUser(); + Channels->SetModified(); + if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { + if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) + Channels->SwitchTo(CurrentChannel->Number()); + else + cDevice::SetCurrentChannel(CurrentChannel); } - else - Reply(501, "Can't move channel to same position"); + isyslog("SVDRP < %s channel %d moved to %d", *connection, FromNumber, ToNumber); + Reply(250,"Channel \"%d\" moved to \"%d\"", From, To); } else - Reply(501, "Channel \"%d\" not defined", To); + Reply(501, "Can't move channel to same position"); } else - Reply(501, "Channel \"%d\" not defined", From); + Reply(501, "Channel \"%d\" not defined", To); } else - Reply(501, "Error in channel number"); + Reply(501, "Channel \"%d\" not defined", From); } else Reply(501, "Error in channel number"); } else - Reply(550, "Channels or timers are being edited - try again later"); + Reply(501, "Error in channel number"); } else Reply(501, "Missing channel number"); @@ -1630,17 +1940,21 @@ void cSVDRPServer::CmdMOVR(const char *Option) char c = *option; *option = 0; if (isnumber(num)) { - cRecording *recording = recordings.Get(strtol(num, NULL, 10) - 1); - if (recording) { - if (int RecordingInUse = recording->IsInUse()) - Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, recording)); + LOCK_RECORDINGS_WRITE; + Recordings->SetExplicitModify(); + if (cRecording *Recording = Recordings->Get(strtol(num, NULL, 10) - 1)) { + if (int RecordingInUse = Recording->IsInUse()) + Reply(550, "%s", *RecordingInUseMessage(RecordingInUse, Option, Recording)); else { if (c) option = skipspace(++option); if (*option) { - cString oldName = recording->Name(); - if ((recording = Recordings.GetByName(recording->FileName())) != NULL && recording->ChangeName(option)) - Reply(250, "Recording \"%s\" moved to \"%s\"", *oldName, recording->Name()); + cString oldName = Recording->Name(); + if ((Recording = Recordings->GetByName(Recording->FileName())) != NULL && Recording->ChangeName(option)) { + Recordings->SetModified(); + Recordings->TouchUpdate(); + Reply(250, "Recording \"%s\" moved to \"%s\"", *oldName, Recording->Name()); + } else Reply(554, "Error while moving recording \"%s\" to \"%s\"!", *oldName, option); } @@ -1649,7 +1963,7 @@ void cSVDRPServer::CmdMOVR(const char *Option) } } else - Reply(550, "Recording \"%s\" not found%s", num, recordings.Count() ? "" : " (use LSTR before moving)"); + Reply(550, "Recording \"%s\" not found", num); } else Reply(501, "Error in recording number \"%s\"", num); @@ -1664,12 +1978,15 @@ void cSVDRPServer::CmdNEWC(const char *Option) if (*Option) { cChannel ch; if (ch.Parse(Option)) { - if (Channels.HasUniqueChannelID(&ch)) { + LOCK_CHANNELS_WRITE; + Channels->SetExplicitModify(); + if (Channels->HasUniqueChannelID(&ch)) { cChannel *channel = new cChannel; *channel = ch; - Channels.Add(channel); - Channels.ReNumber(); - Channels.SetModified(true); + Channels->Add(channel); + Channels->ReNumber(); + Channels->SetModifiedByUser(); + Channels->SetModified(); isyslog("SVDRP < %s new channel %d %s", *connection, channel->Number(), *channel->ToText()); Reply(250, "%d %s", channel->Number(), *channel->ToText()); } @@ -1686,17 +2003,17 @@ void cSVDRPServer::CmdNEWC(const char *Option) void cSVDRPServer::CmdNEWT(const char *Option) { if (*Option) { - cTimer *timer = new cTimer; - if (timer->Parse(Option)) { - Timers.Add(timer); - Timers.SetModified(); - isyslog("SVDRP < %s timer %s added", *connection, *timer->ToDescr()); - Reply(250, "%d %s", timer->Index() + 1, *timer->ToText()); + cTimer *Timer = new cTimer; + if (Timer->Parse(Option)) { + LOCK_TIMERS_WRITE; + Timers->Add(Timer); + isyslog("SVDRP < %s timer %s added", *connection, *Timer->ToDescr()); + Reply(250, "%d %s", Timer->Index() + 1, *Timer->ToText()); return; } else Reply(501, "Error in timer settings"); - delete timer; + delete Timer; } else Reply(501, "Missing timer settings"); @@ -1704,8 +2021,8 @@ void cSVDRPServer::CmdNEWT(const char *Option) void cSVDRPServer::CmdNEXT(const char *Option) { - cTimer *t = Timers.GetNextActiveTimer(); - if (t) { + LOCK_TIMERS_READ; + if (const cTimer *t = Timers->GetNextActiveTimer()) { time_t Start = t->StartTime(); int Number = t->Index() + 1; if (!*Option) @@ -1723,7 +2040,7 @@ void cSVDRPServer::CmdNEXT(const char *Option) void cSVDRPServer::CmdPING(const char *Option) { - Reply(250, "%s is alive", HostName()); + Reply(250, "%s is alive", SVDRPHostName()); } void cSVDRPServer::CmdPLAY(const char *Option) @@ -1737,8 +2054,8 @@ void cSVDRPServer::CmdPLAY(const char *Option) char c = *option; *option = 0; if (isnumber(num)) { - cRecording *recording = recordings.Get(strtol(num, NULL, 10) - 1); - if (recording) { + LOCK_RECORDINGS_READ; + if (const cRecording *Recording = Recordings->Get(strtol(num, NULL, 10) - 1)) { if (c) option = skipspace(++option); cReplayControl::SetRecording(NULL); @@ -1746,20 +2063,20 @@ void cSVDRPServer::CmdPLAY(const char *Option) if (*option) { int pos = 0; if (strcasecmp(option, "BEGIN") != 0) - pos = HMSFToIndex(option, recording->FramesPerSecond()); - cResumeFile resume(recording->FileName(), recording->IsPesRecording()); + pos = HMSFToIndex(option, Recording->FramesPerSecond()); + cResumeFile Resume(Recording->FileName(), Recording->IsPesRecording()); if (pos <= 0) - resume.Delete(); + Resume.Delete(); else - resume.Save(pos); + Resume.Save(pos); } - cReplayControl::SetRecording(recording->FileName()); + cReplayControl::SetRecording(Recording->FileName()); cControl::Launch(new cReplayControl); cControl::Attach(); - Reply(250, "Playing recording \"%s\" [%s]", num, recording->Title()); + Reply(250, "Playing recording \"%s\" [%s]", num, Recording->Title()); } else - Reply(550, "Recording \"%s\" not found%s", num, recordings.Count() ? "" : " (use LSTR before playing)"); + Reply(550, "Recording \"%s\" not found", num); } else Reply(501, "Error in recording number \"%s\"", num); @@ -1840,6 +2157,36 @@ void cSVDRPServer::CmdPLUG(const char *Option) } } +void cSVDRPServer::CmdPOLL(const char *Option) +{ + if (*Option) { + char buf[strlen(Option) + 1]; + char *p = strcpy(buf, Option); + const char *delim = " \t"; + char *strtok_next; + char *RemoteName = strtok_r(p, delim, &strtok_next); + char *ListName = strtok_r(NULL, delim, &strtok_next); + if (SVDRPClientHandler) { + if (ListName) { + if (strcasecmp(ListName, "timers") == 0) { + if (SVDRPClientHandler->TriggerFetchingTimers(RemoteName)) + Reply(250, "OK"); + else + Reply(501, "No connection to \"%s\"", RemoteName); + } + else + Reply(501, "Unknown list name: \"%s\"", ListName); + } + else + Reply(501, "Missing list name"); + } + else + Reply(501, "No SVDRP client connections"); + } + else + Reply(501, "Missing parameters"); +} + void cSVDRPServer::CmdPUTE(const char *Option) { if (*Option) { @@ -1907,30 +2254,25 @@ void cSVDRPServer::CmdSTAT(const char *Option) void cSVDRPServer::CmdUPDT(const char *Option) { if (*Option) { - cTimer *timer = new cTimer; - if (timer->Parse(Option)) { - if (!Timers.BeingEdited()) { - cTimer *t = Timers.GetTimer(timer); - if (t) { - t->Parse(Option); - delete timer; - timer = t; - isyslog("SVDRP < %s timer %s updated", *connection, *timer->ToDescr()); - } - else { - Timers.Add(timer); - isyslog("SVDRP < %s timer %s added", *connection, *timer->ToDescr()); - } - Timers.SetModified(); - Reply(250, "%d %s", timer->Index() + 1, *timer->ToText()); - return; + cTimer *Timer = new cTimer; + if (Timer->Parse(Option)) { + LOCK_TIMERS_WRITE; + if (cTimer *t = Timers->GetTimer(Timer)) { + t->Parse(Option); + delete Timer; + Timer = t; + isyslog("SVDRP < %s timer %s updated", *connection, *Timer->ToDescr()); } - else - Reply(550, "Timers are being edited - try again later"); + else { + Timers->Add(Timer); + isyslog("SVDRP < %s timer %s added", *connection, *Timer->ToDescr()); + } + Reply(250, "%d %s", Timer->Index() + 1, *Timer->ToText()); + return; } else Reply(501, "Error in timer settings"); - delete timer; + delete Timer; } else Reply(501, "Missing timer settings"); @@ -1938,7 +2280,8 @@ void cSVDRPServer::CmdUPDT(const char *Option) void cSVDRPServer::CmdUPDR(const char *Option) { - Recordings.Update(false); + LOCK_RECORDINGS_WRITE; + Recordings->Update(false); Reply(250, "Re-read of recordings directory triggered"); } @@ -2010,6 +2353,7 @@ void cSVDRPServer::Execute(char *Cmd) else if (CMD("PING")) CmdPING(s); else if (CMD("PLAY")) CmdPLAY(s); else if (CMD("PLUG")) CmdPLUG(s); + else if (CMD("POLL")) CmdPOLL(s); else if (CMD("PUTE")) CmdPUTE(s); else if (CMD("REMO")) CmdREMO(s); else if (CMD("SCAN")) CmdSCAN(s); @@ -2034,6 +2378,7 @@ bool cSVDRPServer::Process(void) cmdLine[--numChars] = 0; // make sure the string is terminated: cmdLine[numChars] = 0; + dbgsvdrp("< %s: %s\n", *connection, cmdLine); // showtime! Execute(cmdLine); numChars = 0; @@ -2091,162 +2436,6 @@ void SetSVDRPGrabImageDir(const char *GrabImageDir) grabImageDir = GrabImageDir; } -// --- cSVDRPClient ---------------------------------------------------------- - -class cSVDRPClient { -private: - cIpAddress ipAddress; - cSocket socket; - cString serverName; - int timeout; - cTimeMs pingTime; - cFile file; - void Close(void); -public: - cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout); - ~cSVDRPClient(); - const char *ServerName(void) const { return serverName; } - const char *Connection(void) const { return ipAddress.Connection(); } - bool HasAddress(const char *Address, int Port) const; - bool Send(const char *Command); - bool Ping(void); - bool Process(cStringList *Response = NULL); - bool Execute(const char *Command, cStringList *Response); - }; - -cSVDRPClient::cSVDRPClient(const char *Address, int Port, const char *ServerName, int Timeout) -:ipAddress(Address, Port) -,socket(Port, true) -{ - serverName = ServerName; - timeout = Timeout * 1000 * 9 / 10; // ping after 90% of timeout - pingTime.Set(timeout); - if (socket.Connect(Address)) { - if (file.Open(socket.Socket())) - SVDRPClientPoller.Add(file, false); - } - dsyslog("SVDRP > %s client created for '%s'", ipAddress.Connection(), *serverName); - SendSVDRPDiscover(Address); -} - -cSVDRPClient::~cSVDRPClient() -{ - Close(); - dsyslog("SVDRP > %s client destroyed for '%s'", ipAddress.Connection(), *serverName); -} - -void cSVDRPClient::Close(void) -{ - if (file.IsOpen()) { - SVDRPClientPoller.Del(file, false); - file.Close(); - socket.Close(); - } -} - -bool cSVDRPClient::HasAddress(const char *Address, int Port) const -{ - return strcmp(ipAddress.Address(), Address) == 0 && ipAddress.Port() == Port; -} - -bool cSVDRPClient::Send(const char *Command) -{ - pingTime.Set(timeout); - dbgsvdrp("> %s: %s\n", *serverName, Command); - if (safe_write(file, Command, strlen(Command) + 1) < 0) { - LOG_ERROR; - return false; - } - return true; -} - -bool cSVDRPClient::Ping(void) -{ - cSVDRPCommand Cmd(serverName, "PING"); - if (Cmd.Execute()) - return Cmd.Response(0) && atoi(Cmd.Response(0)) == 250; - return false; -} - -bool cSVDRPClient::Process(cStringList *Response) -{ - if (file.IsOpen()) { - char input[BUFSIZ]; - int numChars = 0; -#define SVDRPResonseTimeout 5000 // ms - cTimeMs Timeout(SVDRPResonseTimeout); - for (;;) { - if (file.Ready(false)) { - unsigned char c; - int r = safe_read(file, &c, 1); - if (r > 0) { - if (c == '\n' || c == 0x00) { - // strip trailing whitespace: - while (numChars > 0 && strchr(" \t\r\n", input[numChars - 1])) - input[--numChars] = 0; - // make sure the string is terminated: - input[numChars] = 0; - dbgsvdrp("< %s: %s\n", *serverName, input); - if (Response) { - Response->Append(strdup(input)); - if (input[3] != '-') // no more lines will follow - break; - } - else { - switch (atoi(input)) { - case 220: if (numChars > 4) { - char *n = input + 4; - if (char *t = strchr(n, ' ')) { - *t = 0; - if (strcmp(n, serverName) != 0) { - serverName = n; - dsyslog("SVDRP < %s remote server name is '%s'", ipAddress.Connection(), *serverName); - } - } - } - break; - case 221: dsyslog("SVDRP < %s remote server closed connection to '%s'", ipAddress.Connection(), *serverName); - Close(); - break; - } - } - numChars = 0; - } - else { - if (numChars >= int(sizeof(input))) { - esyslog("SVDRP < %s ERROR: out of memory", ipAddress.Connection()); - Close(); - break; - } - input[numChars++] = c; - input[numChars] = 0; - } - Timeout.Set(SVDRPResonseTimeout); - } - else if (r <= 0) { - isyslog("SVDRP < %s lost connection to remote server '%s'", ipAddress.Connection(), *serverName); - Close(); - } - } - else if (!Response) - break; - else if (Timeout.TimedOut()) { - esyslog("SVDRP < %s timeout while waiting for response from '%s'", ipAddress.Connection(), *serverName); - return false; - } - } - if (pingTime.TimedOut()) - Ping(); - } - return file.IsOpen(); -} - -bool cSVDRPClient::Execute(const char *Command, cStringList *Response) -{ - Response->Clear(); - return Send(Command) && Process(Response); -} - // --- cSVDRPServerHandler --------------------------------------------------- class cSVDRPServerHandler : public cThread { @@ -2265,6 +2454,8 @@ public: void WaitUntilReady(void); }; +static cSVDRPServerHandler *SVDRPServerHandler = NULL; + cSVDRPServerHandler::cSVDRPServerHandler(int TcpPort) :cThread("SVDRP server handler", true) ,tcpSocket(TcpPort, true) @@ -2321,141 +2512,9 @@ void cSVDRPServerHandler::Action(void) } } -// --- cSVDRPClientHandler --------------------------------------------------- - -class cSVDRPClientHandler : public cThread { -private: - cMutex mutex; - cSocket udpSocket; - int tcpPort; - cVector<cSVDRPClient *> clientConnections; - void HandleClientConnection(void); - void ProcessConnections(void); - cSVDRPClient *GetClientForServer(const char *ServerName); -protected: - virtual void Action(void); -public: - cSVDRPClientHandler(int UdpPort, int TcpPort); - virtual ~cSVDRPClientHandler(); - void SendDiscover(const char *Address = NULL); - bool Execute(const char *ServerName, const char *Command, cStringList *Response); - bool GetServerNames(cStringList *ServerNames); - }; - -cSVDRPClientHandler::cSVDRPClientHandler(int UdpPort, int TcpPort) -:cThread("SVDRP client handler", true) -,udpSocket(UdpPort, false) -{ - tcpPort = TcpPort; -} - -cSVDRPClientHandler::~cSVDRPClientHandler() -{ - Cancel(3); - for (int i = 0; i < clientConnections.Size(); i++) - delete clientConnections[i]; -} - -cSVDRPClient *cSVDRPClientHandler::GetClientForServer(const char *ServerName) -{ - for (int i = 0; i < clientConnections.Size(); i++) { - if (strcmp(clientConnections[i]->ServerName(), ServerName) == 0) - return clientConnections[i]; - } - return NULL; -} - -void cSVDRPClientHandler::SendDiscover(const char *Address) -{ - cMutexLock MutexLock(&mutex); - cString Dgram = cString::sprintf("SVDRP:discover name:%s port:%d vdrversion:%d apiversion:%d timeout:%d", HostName(), tcpPort, VDRVERSNUM, APIVERSNUM, Setup.SVDRPTimeout); - udpSocket.SendDgram(Dgram, udpSocket.Port(), Address); -} - -void cSVDRPClientHandler::ProcessConnections(void) -{ - cMutexLock MutexLock(&mutex); - for (int i = 0; i < clientConnections.Size(); i++) { - if (!clientConnections[i]->Process()) { - delete clientConnections[i]; - clientConnections.Remove(i); - i--; - } - } -} - -void cSVDRPClientHandler::HandleClientConnection(void) -{ - cString NewDiscover = udpSocket.Discover(); - if (*NewDiscover) { - cString p = strgetval(NewDiscover, "port", ':'); - if (*p) { - int Port = atoi(p); - for (int i = 0; i < clientConnections.Size(); i++) { - if (clientConnections[i]->HasAddress(udpSocket.LastIpAddress()->Address(), Port)) { - dsyslog("SVDRP < %s connection to '%s' confirmed", clientConnections[i]->Connection(), clientConnections[i]->ServerName()); - return; - } - } - cString ServerName = strgetval(NewDiscover, "name", ':'); - if (*ServerName) { - cString t = strgetval(NewDiscover, "timeout", ':'); - if (*t) { - int Timeout = atoi(t); - if (Timeout > 10) // don't let it get too small - clientConnections.Append(new cSVDRPClient(udpSocket.LastIpAddress()->Address(), Port, ServerName, Timeout)); - else - esyslog("SVDRP < %s ERROR: invalid timeout (%d)", udpSocket.LastIpAddress()->Connection(), Timeout); - } - else - esyslog("SVDRP < %s ERROR: missing timeout", udpSocket.LastIpAddress()->Connection()); - } - else - esyslog("SVDRP < %s ERROR: missing server name", udpSocket.LastIpAddress()->Connection()); - } - else - esyslog("SVDRP < %s ERROR: missing port number", udpSocket.LastIpAddress()->Connection()); - } -} - -void cSVDRPClientHandler::Action(void) -{ - if (udpSocket.Listen()) { - SVDRPClientPoller.Add(udpSocket.Socket(), false); - SendDiscover(); - while (Running()) { - SVDRPClientPoller.Poll(1000); - cMutexLock MutexLock(&mutex); - HandleClientConnection(); - ProcessConnections(); - } - SVDRPClientPoller.Del(udpSocket.Socket(), false); - udpSocket.Close(); - } -} - -bool cSVDRPClientHandler::Execute(const char *ServerName, const char *Command, cStringList *Response) -{ - cMutexLock MutexLock(&mutex); - if (cSVDRPClient *Client = GetClientForServer(ServerName)) - return Client->Execute(Command, Response); - return false; -} - -bool cSVDRPClientHandler::GetServerNames(cStringList *ServerNames) -{ - cMutexLock MutexLock(&mutex); - ServerNames->Clear(); - for (int i = 0; i < clientConnections.Size(); i++) - ServerNames->Append(strdup(clientConnections[i]->ServerName())); - return ServerNames->Size() > 0; -} - // --- SVDRP Handler --------------------------------------------------------- static cMutex SVDRPHandlerMutex; -static cSVDRPServerHandler *SVDRPServerHandler = NULL; -static cSVDRPClientHandler *SVDRPClientHandler = NULL; void StartSVDRPHandler(int TcpPort, int UdpPort) { @@ -2487,11 +2546,11 @@ void SendSVDRPDiscover(const char *Address) SVDRPClientHandler->SendDiscover(Address); } -bool GetSVDRPServerNames(cStringList *ServerNames) +bool GetSVDRPServerNames(cStringList *ServerNames, eSvdrpFetchFlags FetchFlag) { cMutexLock MutexLock(&SVDRPHandlerMutex); if (SVDRPClientHandler) - return SVDRPClientHandler->GetServerNames(ServerNames); + return SVDRPClientHandler->GetServerNames(ServerNames, FetchFlag); return false; } |