If cantata-dynamic is started in server mode, then communicate status via UDP multicast messages.
This commit is contained in:
@@ -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})
|
||||
|
||||
@@ -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
11
README
@@ -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
|
||||
===========
|
||||
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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
7
dynamic/message-sender
Executable 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: $!";
|
||||
Reference in New Issue
Block a user