Skip to content

Commit

Permalink
Allow mythfrontend's NetworkControl interface to support multiple
Browse files Browse the repository at this point in the history
simultanious connections.

This is mainly a patch by Xavier Hervy with some changes by me to port it to
current svn trunk, fix a couple bugs, and properly handle RESPONSE messages
going to the right client (which was left as a 'fixme' in the original patch).

Closes #5831.



git-svn-id: http://svn.mythtv.org/svn/trunk@22514 7dbf422c-18fa-0310-86e9-fd20926502f2
  • Loading branch information
cpinkham committed Oct 17, 2009
1 parent 245769d commit 4f40c42
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 102 deletions.
235 changes: 147 additions & 88 deletions mythtv/programs/mythfrontend/networkcontrol.cpp
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
#define LOC_ERR QString("NetworkControl Error: ") #define LOC_ERR QString("NetworkControl Error: ")


const int kNetworkControlDataReadyEvent = 35671; const int kNetworkControlDataReadyEvent = 35671;
const int kNetworkControlCloseEvent = 35672;


/** Is @p test an abbreviation of @p command ? /** Is @p test an abbreviation of @p command ?
* The @p test substring must be at least @p minchars long. * The @p test substring must be at least @p minchars long.
Expand All @@ -46,8 +45,7 @@ NetworkControl::NetworkControl() :
QTcpServer(), QTcpServer(),
prompt("# "), prompt("# "),
gotAnswer(false), answer(""), gotAnswer(false), answer(""),
clientLock(QMutex::Recursive), clientLock(QMutex::Recursive)
client(NULL), clientStream(NULL)
{ {
// Eventually this map should be in the jumppoints table // Eventually this map should be in the jumppoints table
jumpMap["channelpriorities"] = "Channel Recording Priorities"; jumpMap["channelpriorities"] = "Channel Recording Priorities";
Expand Down Expand Up @@ -221,11 +219,18 @@ NetworkControl::NetworkControl() :


NetworkControl::~NetworkControl(void) NetworkControl::~NetworkControl(void)
{ {
deleteClientLater(); clientLock.lock();
while (!clients.isEmpty())
{
NetworkControlClient *ncc = clients.front();
clients.pop_front();
delete ncc;
}
clientLock.unlock();


nrLock.lock(); nrLock.lock();
networkControlReplies.push_back( networkControlReplies.push_back(new NetworkCommand(NULL,
"mythfrontend shutting down, connection closing..."); "mythfrontend shutting down, connection closing..."));
nrLock.unlock(); nrLock.unlock();


notifyDataAvailable(); notifyDataAvailable();
Expand Down Expand Up @@ -258,7 +263,7 @@ void *NetworkControl::CommandThread(void *param)


void NetworkControl::RunCommandThread(void) void NetworkControl::RunCommandThread(void)
{ {
QString command; NetworkCommand *nc;


while (!stopCommandThread) while (!stopCommandThread)
{ {
Expand All @@ -271,124 +276,126 @@ void NetworkControl::RunCommandThread(void)
return; return;
} }
} }
command = networkControlCommands.front(); nc = networkControlCommands.front();
networkControlCommands.pop_front(); networkControlCommands.pop_front();
ncLock.unlock(); ncLock.unlock();


processNetworkControlCommand(command); processNetworkControlCommand(nc);
} }
} }


void NetworkControl::processNetworkControlCommand(QString command) void NetworkControl::processNetworkControlCommand(NetworkCommand *nc)
{ {
QMutexLocker locker(&clientLock); QMutexLocker locker(&clientLock);
QString result; QString result;
QStringList tokens = command.simplified().split(" "); QStringList tokens = nc->getCommand().simplified().split(" ");

int clientID = clients.indexOf(nc->getClient());


if (is_abbrev("jump", tokens[0])) if (is_abbrev("jump", tokens[0]))
result = processJump(tokens); result = processJump(tokens);
else if (is_abbrev("key", tokens[0])) else if (is_abbrev("key", tokens[0]))
result = processKey(tokens); result = processKey(tokens);
else if (is_abbrev("play", tokens[0])) else if (is_abbrev("play", tokens[0]))
result = processPlay(tokens); result = processPlay(tokens, clientID);
else if (is_abbrev("query", tokens[0])) else if (is_abbrev("query", tokens[0]))
result = processQuery(tokens); result = processQuery(tokens);
else if (is_abbrev("set", tokens[0])) else if (is_abbrev("set", tokens[0]))
result = processSet(tokens); result = processSet(tokens);
else if (is_abbrev("help", tokens[0])) else if (is_abbrev("help", tokens[0]))
result = processHelp(tokens); result = processHelp(tokens);
else if ((tokens[0].toLower() == "exit") || (tokens[0].toLower() == "quit")) else if ((tokens[0].toLower() == "exit") || (tokens[0].toLower() == "quit"))
QApplication::postEvent(this, QApplication::postEvent(this,
new QEvent((QEvent::Type)kNetworkControlCloseEvent)); new NetworkControlCloseEvent(nc->getClient()));
else if (! tokens[0].isEmpty()) else if (! tokens[0].isEmpty())
result = QString("INVALID command '%1', try 'help' for more info") result = QString("INVALID command '%1', try 'help' for more info")
.arg(tokens[0]); .arg(tokens[0]);


if (!result.isEmpty()) if (!result.isEmpty())
{ {
nrLock.lock(); nrLock.lock();
networkControlReplies.push_back(result); networkControlReplies.push_back(new NetworkCommand(nc->getClient(),result));
nrLock.unlock(); nrLock.unlock();


notifyDataAvailable(); notifyDataAvailable();
} }
} }


void NetworkControl::deleteClientLater(void) void NetworkControl::deleteClient(void)
{ {
VERBOSE(VB_GENERAL, LOC + "Socket disconnected"); VERBOSE(VB_GENERAL, LOC + "Client Socket disconnected");
QMutexLocker locker(&clientLock); QMutexLocker locker(&clientLock);
if (client)
QList<NetworkControlClient *>::const_iterator it;
for (it = clients.begin(); it != clients.end(); ++it)
{ {
client->disconnect(); NetworkControlClient *ncc = *it;
client->deleteLater(); if (ncc->getSocket()->state() == QTcpSocket::UnconnectedState)
client = NULL; {
deleteClient(ncc);
return;
}
} }
if (clientStream) }

void NetworkControl::deleteClient(NetworkControlClient *ncc)
{
int index = clients.indexOf(ncc);
if (index >= 0)
{ {
delete clientStream; clients.removeAt(index);
clientStream = NULL;
delete ncc;
} }
else
VERBOSE(VB_IMPORTANT, LOC_ERR + QString("deleteClient(%1), unable to "
"locate specified NetworkControlClient").arg((long long)ncc));
} }


void NetworkControl::newConnection() void NetworkControl::newConnection()
{ {
QString welcomeStr; QString welcomeStr;
bool closedOldConn = false;


VERBOSE(VB_GENERAL, LOC + VERBOSE(VB_GENERAL, LOC +
QString("New connection established.")); QString("New connection established."));


QTcpSocket *s = this->nextPendingConnection(); QTcpSocket *client = this->nextPendingConnection();
NetworkControlClient *ncc = new NetworkControlClient(client);


QMutexLocker locker(&clientLock); QMutexLocker locker(&clientLock);
if (clientStream) clients.push_back(ncc);
{
clientStream->setDevice(s);
}
else
{
clientStream = new QTextStream(s);
clientStream->setCodec("UTF-8");
}

if (client)
{
closedOldConn = true;
deleteClientLater();
}
client = s;

connect(client, SIGNAL(readyRead()), this, SLOT(readClient()));
connect(client, SIGNAL(disconnected()), this, SLOT(deleteClientLater()));


ncLock.lock(); connect(ncc, SIGNAL(commandReceived(QString&)), this,
networkControlCommands.clear(); SLOT(receiveCommand(QString&)));
ncLock.unlock(); connect(client, SIGNAL(disconnected()), this, SLOT(deleteClient()));

nrLock.lock();
networkControlReplies.clear();
nrLock.unlock();


welcomeStr = "MythFrontend Network Control\r\n"; welcomeStr = "MythFrontend Network Control\r\n";
if (closedOldConn)
{
welcomeStr +=
"WARNING: mythfrontend was already under network control.\r\n";
welcomeStr +=
" Previous session is being disconnected.\r\n";
}

welcomeStr += "Type 'help' for usage information\r\n" welcomeStr += "Type 'help' for usage information\r\n"
"---------------------------------"; "---------------------------------";
nrLock.lock(); nrLock.lock();
networkControlReplies.push_back(welcomeStr); networkControlReplies.push_back(new NetworkCommand(ncc,welcomeStr));
nrLock.unlock(); nrLock.unlock();


notifyDataAvailable(); notifyDataAvailable();
} }


void NetworkControl::readClient(void) NetworkControlClient::NetworkControlClient(QTcpSocket *s)
{
m_socket = s;
m_textStream = new QTextStream(s);
m_textStream->setCodec("UTF-8");
connect(m_socket, SIGNAL(readyRead()), this, SLOT(readClient()));
}

NetworkControlClient::~NetworkControlClient()
{
m_socket->close();
m_socket->deleteLater();

delete m_textStream;
}

void NetworkControlClient::readClient(void)
{ {
QTcpSocket *socket = (QTcpSocket *)sender(); QTcpSocket *socket = (QTcpSocket *)sender();
if (!socket) if (!socket)
Expand All @@ -407,14 +414,27 @@ void NetworkControl::readClient(void)
continue; continue;


tokens = lineIn.simplified().split(" "); tokens = lineIn.simplified().split(" ");


ncLock.lock(); VERBOSE(VB_NETWORK, LOC +
networkControlCommands.push_back(lineIn); QString("emit commandReceived(%1)").arg(lineIn));
ncCond.wakeOne(); emit commandReceived(lineIn);
ncLock.unlock();
} }
} }


void NetworkControl::receiveCommand(QString &command)
{
VERBOSE(VB_NETWORK, LOC +
QString("NetworkControl::receiveCommand(%1)").arg(command));
NetworkControlClient *ncc = (NetworkControlClient *)sender();
if (!ncc)
return;

ncLock.lock();
networkControlCommands.push_back(new NetworkCommand(ncc,command));
ncCond.wakeOne();
ncLock.unlock();
}

QString NetworkControl::processJump(QStringList tokens) QString NetworkControl::processJump(QStringList tokens)
{ {
QString result = "OK"; QString result = "OK";
Expand Down Expand Up @@ -541,7 +561,7 @@ QString NetworkControl::processKey(QStringList tokens)
return result; return result;
} }


QString NetworkControl::processPlay(QStringList tokens) QString NetworkControl::processPlay(QStringList tokens, int clientID)
{ {
QString result = "OK"; QString result = "OK";
QString message; QString message;
Expand Down Expand Up @@ -609,9 +629,9 @@ QString NetworkControl::processPlay(QStringList tokens)
if (tokens.size() == 5 && tokens[4] == "resume") if (tokens.size() == 5 && tokens[4] == "resume")
action = "RESUME"; action = "RESUME";


QString message = QString("NETWORK_CONTROL %1 PROGRAM %2 %3") QString message = QString("NETWORK_CONTROL %1 PROGRAM %2 %3 %4")
.arg(action).arg(tokens[2]) .arg(action).arg(tokens[2])
.arg(tokens[3].toUpper()); .arg(tokens[3].toUpper()).arg(clientID);
MythEvent me(message); MythEvent me(message);
gContext->dispatch(me); gContext->dispatch(me);


Expand Down Expand Up @@ -1017,6 +1037,28 @@ void NetworkControl::notifyDataAvailable(void)
(QEvent::Type)kNetworkControlDataReadyEvent)); (QEvent::Type)kNetworkControlDataReadyEvent));
} }


void NetworkControl::sendReplyToClient(NetworkControlClient *ncc,
QString &reply)
{
QRegExp crlfRegEx("\r\n$");
QRegExp crlfcrlfRegEx("\r\n.*\r\n");

QTcpSocket *client = ncc->getSocket();
QTextStream *clientStream = ncc->getTextStream();

if (client && clientStream && client->state() == QTcpSocket::ConnectedState)
{
*clientStream << reply;

if ((!reply.contains(crlfRegEx)) ||
( reply.contains(crlfcrlfRegEx)))
*clientStream << "\r\n" << prompt;

clientStream->flush();
client->flush();
}
}

void NetworkControl::customEvent(QEvent *e) void NetworkControl::customEvent(QEvent *e)
{ {
if ((MythEvent::Type)(e->type()) == MythEvent::MythEventMessage) if ((MythEvent::Type)(e->type()) == MythEvent::MythEventMessage)
Expand All @@ -1036,47 +1078,64 @@ void NetworkControl::customEvent(QEvent *e)
answer += QString(" ") + tokens[i]; answer += QString(" ") + tokens[i];
gotAnswer = true; gotAnswer = true;
} }
else if ((tokens.size() >= 3) && else if ((tokens.size() >= 4) &&
(tokens[1] == "RESPONSE")) (tokens[1] == "RESPONSE"))
{ {
QString response = tokens[2]; int clientID = tokens[2].toInt();
for (int i = 3; i < tokens.size(); i++) QString response = tokens[3];
for (int i = 4; i < tokens.size(); i++)
response += QString(" ") + tokens[i]; response += QString(" ") + tokens[i];

clientLock.lock();
NetworkControlClient *ncc = clients.at(clientID);
clientLock.unlock();

nrLock.lock(); nrLock.lock();
networkControlReplies.push_back(response); networkControlReplies.push_back(new NetworkCommand(ncc, response));
nrLock.unlock(); nrLock.unlock();


notifyDataAvailable(); notifyDataAvailable();
} }
} }
else if (e->type() == kNetworkControlDataReadyEvent) else if (e->type() == kNetworkControlDataReadyEvent)
{ {
NetworkCommand *nc;
QString reply; QString reply;
int replies;
QRegExp crlfRegEx("\r\n$");
QRegExp crlfcrlfRegEx("\r\n.*\r\n");


QMutexLocker locker(&clientLock); QMutexLocker locker(&clientLock);
QMutexLocker nrLocker(&nrLock); QMutexLocker nrLocker(&nrLock);


replies = networkControlReplies.size(); while (!networkControlReplies.isEmpty())
while (client && clientStream && replies > 0 &&
client->state() == QTcpSocket::ConnectedState)
{ {
reply = networkControlReplies.front(); nc = networkControlReplies.front();
networkControlReplies.pop_front(); networkControlReplies.pop_front();
*clientStream << reply;
if (!reply.contains(crlfRegEx) || reply.contains(crlfcrlfRegEx))
*clientStream << "\r\n" << prompt;
clientStream->flush();
client->flush();


replies = networkControlReplies.size(); reply = nc->getCommand();

NetworkControlClient * ncc = nc->getClient();
if (ncc)
{
sendReplyToClient(ncc, reply);
}
else //send to all clients
{
QList<NetworkControlClient *>::const_iterator it;
for (it = clients.begin(); it != clients.end(); ++it)
{
NetworkControlClient *ncc = *it;
if (ncc)
sendReplyToClient(ncc, reply);
}
}
delete nc;
} }
} }
else if (e->type() == kNetworkControlCloseEvent) else if (e->type() == kNetworkControlCloseEvent)
{ {
deleteClientLater(); NetworkControlCloseEvent *ncce = (NetworkControlCloseEvent*)e;
NetworkControlClient *ncc = ncce->getClient();

deleteClient(ncc);
} }
} }


Expand Down
Loading

0 comments on commit 4f40c42

Please sign in to comment.