summaryrefslogtreecommitdiff
path: root/svdrp.c
diff options
context:
space:
mode:
authorKlaus Schmidinger <vdr@tvdr.de>2015-09-01 11:14:27 +0200
committerKlaus Schmidinger <vdr@tvdr.de>2015-09-01 11:14:27 +0200
commit3cd5294d8a337ee5cd2ec894c9fbe04ad3a7690d (patch)
treeda57ce74189de9bfb27e1a747063c37cd62de501 /svdrp.c
parent8a7bc6a0bbf60cae8b6391a630880aad5cba3363 (diff)
downloadvdr-3cd5294d8a337ee5cd2ec894c9fbe04ad3a7690d.tar.gz
vdr-3cd5294d8a337ee5cd2ec894c9fbe04ad3a7690d.tar.bz2
Implemented strict locking of global lists
Diffstat (limited to 'svdrp.c')
-rw-r--r--svdrp.c1335
1 files changed, 697 insertions, 638 deletions
diff --git a/svdrp.c b/svdrp.c
index d621363d..8d7fce7d 100644
--- a/svdrp.c
+++ b/svdrp.c
@@ -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;
}