If cantata-dynamic is started in server mode, then communicate status via UDP multicast messages.

This commit is contained in:
craig.p.drummond
2013-06-18 18:35:10 +00:00
parent ce7ef4cdbe
commit aeffbd7db7
7 changed files with 124 additions and 3 deletions

View File

@@ -472,7 +472,7 @@ if (ENABLE_HTTP_STREAM_PLAYBACK AND NOT ENABLE_QT5)
endif (ENABLE_HTTP_STREAM_PLAYBACK AND NOT ENABLE_QT5)
if (NOT WIN32)
install(PROGRAMS dynamic/cantata-dynamic DESTINATION ${CMAKE_INSTALL_PREFIX}/share/${CMAKE_PROJECT_NAME}/scripts)
install(PROGRAMS dynamic/cantata-dynamic dynamic/message-sender DESTINATION ${CMAKE_INSTALL_PREFIX}/share/${CMAKE_PROJECT_NAME}/scripts)
if (MTP_FOUND)
target_link_libraries(cantata ${MTP_LIBRARIES})
include_directories(${MTP_INCLUDE_DIR})

View File

@@ -85,6 +85,8 @@
53. Simpler proxy settings.
54. Delay loading of local devices at atart-up, so that we have time to add
device to view before try to expand it.
55. If cantata-dynamic is started in server mode, then communicate status via
UDP multicast messages.
1.0.3
-----

11
README
View File

@@ -376,6 +376,17 @@ UPDATE_REQUIRED if the client should update its list of playlists (by calling
the 'ListPlaylists' API)
UDP MultiCast Messages
----------------------
When ren in server mode, the helper script will emit a UDP mutlicast message
when the status changes. This will be on httpPort+1 and group 239.123.123.123.
the format of the message is the same as GetStatus response, apart from the
addition of a header - e.g.
{CANTATA}STATUS:STATE:<current status>\nRULES:<name of currently active rule>\nTIME:<epoch time since started, or last rule modification>
Source Code
===========

View File

@@ -35,6 +35,11 @@ use threads::shared;
use URI::Escape;
use Encode;
# UDP multicast message sender - used to communicate with Cantata when cantata-dynamic is run in server mode!
my $serverSender : shared = dirname(__FILE__) . '/message-sender';
my $MESSAGE_GROUP : shared = '239.123.123.123';
my $MESSAGE_HEADER : shared = '{CANTATA}';
my $isServerMode : shared =0;
my $dynamicIsActive : shared =1;
my $currentStatus : shared ="IDLE";
@@ -477,6 +482,16 @@ sub uniq {
return keys %{{ map { $_ => 1 } @_ }};
}
# Send message to Cantata application - ued when run in server mode
sub sendServerMessage() {
my $message=shift;
if (length($message)<=0) {
$message="STATUS:STATE:${currentStatus}\nRULES:${activeRules}\nTIME:${currentStatusTime}";
}
my $port=$httpPort+1;
system("${serverSender} ${MESSAGE_GROUP}:${port} \"${MESSAGE_HEADER}${message}\"");
}
# Send message to Cantata application...
sub sendMessage() {
my $method=shift;
@@ -560,12 +575,14 @@ sub getSongs() {
if (scalar(@mpdSongs)<1) {
if (1==$isServerMode) {
$currentStatus="NO_SONGS";
&sendServerMessage();
} else {
&sendMessage("showError", "NO_SONGS");
exit(0);
}
} elsif (1==$isServerMode) {
$currentStatus="HAVE_SONGS";
&sendServerMessage();
}
if (1==$testMode) {
@@ -961,6 +978,7 @@ sub saveRulesToFile() {
print $fileHandle $content;
close($fileHandle);
$currentStatusTime = time;
&sendServerMessage();
return "OK";
} else {
return "ERROR: Failed to create file";
@@ -980,6 +998,7 @@ sub deleteRules() {
if ($name eq $active) {
&control("stop");
}
&sendServerMessage();
return "OK";
}
@@ -989,10 +1008,12 @@ sub control() {
$dynamicIsActive=1;
$currentStatus="STARTING";
&sendCommand("clear");
&sendServerMessage();
return "OK";
} elsif ($command eq "stop") {
$dynamicIsActive=0;
$currentStatus="IDLE";
&sendServerMessage();
return "OK";
}
return "ERROR: Invalid command";
@@ -1022,6 +1043,7 @@ sub setActiveRules() {
if (0!=$?) {
return "ERROR: Failed to create 'rules' symlink";
}
&sendServerMessage();
return "OK";
} else {
return "ERROR: Could not file ${name}";
@@ -1208,6 +1230,7 @@ sub serverMode() {
$dynamicIsActive=0;
threads->create(\&populatePlayQueue, 1);
&httpServer();
&sendServerMessage();
}
sub stopServer() {
@@ -1217,6 +1240,8 @@ sub stopServer() {
system("kill", $pid);
system("pkill", "-P", $pid);
}
$currentStatus="TERMINATED";
&sendServerMessage();
}
if ($ARGV[0] eq "start") {

View File

@@ -42,6 +42,8 @@ K_GLOBAL_STATIC(Dynamic, instance)
#include <QTimer>
#include <QIcon>
#include <QUrl>
#include <QUdpSocket>
#include <QNetworkProxy>
#if QT_VERSION >= 0x050000
#include <QUrlQuery>
#endif
@@ -88,10 +90,45 @@ const QString Dynamic::constDateKey=QLatin1String("Date");
const QString Dynamic::constExactKey=QLatin1String("Exact");
const QString Dynamic::constExcludeKey=QLatin1String("Exclude");
static QString constMulticastGroup=QLatin1String("239.123.123.123");
static const char * constMulticastMsgHeader="{CANTATA}";
const QString constStatusMsg(QLatin1String("STATUS:"));
MulticastReceiver::MulticastReceiver(QObject *parent, quint16 port)
: QObject(parent)
{
socket = new QUdpSocket(this);
socket->setProxy(QNetworkProxy(QNetworkProxy::NoProxy));
#if QT_VERSION < 0x050000
socket->bind(port+1, QUdpSocket::ShareAddress);
#else
socket->bind(port+1, QAbstractSocket::ShareAddress);
#endif
socket->joinMulticastGroup(QHostAddress(constMulticastGroup));
connect(socket, SIGNAL(readyRead()), this, SLOT(processMessages()));
}
void MulticastReceiver::processMessages()
{
static int headerLen=strlen(constMulticastMsgHeader);
while (socket->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(socket->pendingDatagramSize());
socket->readDatagram(datagram.data(), datagram.size());
if (datagram.length()>headerLen && datagram.startsWith(constMulticastMsgHeader)) {
QString message=QString::fromUtf8(&(datagram.constData()[headerLen]));
if (message.startsWith(constStatusMsg)) {
emit status(message.mid(constStatusMsg.length()));
}
}
}
}
Dynamic::Dynamic()
: timer(0)
, statusTime(1)
, currentJob(0)
, receiver(0)
{
loadLocal();
connect(this, SIGNAL(clear()), MPDConnection::self(), SLOT(clear()));
@@ -586,7 +623,7 @@ void Dynamic::parseStatus(const QString &response)
}
} else if (QLatin1String("HAVE_SONGS")==state || QLatin1String("STARTING")==state) {
emit running(true);
} else if (QLatin1String("IDLE")==state) {
} else if (QLatin1String("IDLE")==state || QLatin1String("TERMINATED")==state) {
currentEntry.clear();
emit running(false);
}
@@ -857,12 +894,30 @@ void Dynamic::dynamicUrlChanged(const QString &url)
bool wasRemote=isRemote();
dynamicUrl=url;
if (wasRemote && !isRemote()) {
stopReceiver();
loadLocal();
} else {
if (timer) {
timer->stop();
}
startReceiver();
loadRemote();
}
}
}
void Dynamic::stopReceiver()
{
if (receiver) {
disconnect(receiver, SIGNAL(status(QString)), this, SLOT(parseStatus(QString)));
receiver->deleteLater();
receiver=0;
}
}
void Dynamic::startReceiver()
{
stopReceiver();
receiver=new MulticastReceiver(this, QUrl(dynamicUrl).port());
connect(receiver, SIGNAL(status(QString)), this, SLOT(parseStatus(QString)));
}

View File

@@ -33,6 +33,23 @@
class QTimer;
class QNetworkReply;
class QUdpSocket;
class MulticastReceiver : public QObject
{
Q_OBJECT
public:
MulticastReceiver(QObject *parent, quint16 port);
Q_SIGNALS:
void status(const QString &st);
private Q_SLOTS:
void processMessages();
private:
QUdpSocket *socket;
};
class Dynamic : public ActionModel
{
@@ -61,6 +78,7 @@ public:
static const QString constExcludeKey;
Dynamic();
virtual ~Dynamic() { stopReceiver(); }
bool isRemote() const { return !dynamicUrl.isEmpty(); }
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
@@ -105,6 +123,7 @@ private Q_SLOTS:
void checkRemoteHelper();
void jobFinished();
void dynamicUrlChanged(const QString &url);
void parseStatus(const QString &response);
private:
int getPid() const;
@@ -114,9 +133,10 @@ private:
void loadLocal();
void loadRemote();
void parseRemote(const QString &response);
void parseStatus(const QString &response);
void checkResponse(const QString &response);
void updateEntry(const Entry &e);
void stopReceiver();
void startReceiver();
private:
QTimer *timer;
@@ -133,6 +153,7 @@ private:
QString currentCommand;
QStringList currentArgs;
Entry currentSave;
MulticastReceiver *receiver;
};
#endif

7
dynamic/message-sender Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env perl
use strict;
use IO::Socket::Multicast;
my $sock = IO::Socket::Multicast->new(Proto=>'udp', PeerAddr=>$ARGV[0]);
$sock->send($ARGV[1]) || die "Couldn't send: $!";