Files
cantata/web/app/statusapi.cpp
Craig Drummond 605af484f7 Initial import of *VERY* incomplete, and not fully functional Cantata
webapp. Mainly used to test SQLite backend for storing MPD db.
2015-06-01 22:57:49 +01:00

229 lines
8.1 KiB
C++

/*
* Cantata Web
*
* Copyright (c) 2015 Craig Drummond <craig.p.drummond@gmail.com>
*
* ----
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "statusapi.h"
#include "apihandler.h"
#include "http/httprequest.h"
#include "http/httpresponse.h"
#include "support/globalstatic.h"
#include "mpd-interface/mpdconnection.h"
#include "mpd-interface/mpdparseutils.h"
#include "mpd-interface/song.h"
#include "support/localize.h"
#include <QByteArray>
#include <QWebSocket>
#include <QJsonDocument>
#include <QDebug>
#define DBUG if (ApiHandler::debugEnabled()) qWarning() << metaObject()->className() << (void *)this << __FUNCTION__
GLOBAL_STATIC(StatusApi, instance)
static QByteArray encode(const char *key, int val, bool last=false)
{
return QByteArray("\"")+key+QByteArray("\":")+QByteArray::number(val)+QByteArray(last ? "" : ",");
}
static QByteArray encode(const char *key, bool val, bool last=false)
{
return QByteArray("\"")+key+QByteArray("\":")+QByteArray(val ? "true" : "false")+QByteArray(last ? "" : ",");
}
static QByteArray encode(const char *key, MPDState val, bool last=false)
{
QByteArray rv=QByteArray("\"")+key+QByteArray("\":\"");
switch (val) {
case MPDState_Inactive: rv+="inactive"; break;
case MPDState_Playing: rv+="playing"; break;
case MPDState_Stopped: rv+="stopped"; break;
case MPDState_Paused: rv+="paused"; break;
}
rv+=QByteArray(last ? "\"" : "\",");
return rv;
}
static bool diff(const MPDStatusValues &a, const MPDStatusValues &b)
{
return a.volume!=b.volume || a.consume!=b.consume || a.repeat!=b.repeat || a.single!=b.single || a.random!=b.random ||
a.playlist!=b.playlist || a.playlistLength!=b.playlistLength || a.state!=b.state ||
a.timeElapsed!=b.timeElapsed || a.timeTotal!=b.timeTotal;
}
StatusApi::StatusApi()
: QWebSocketServer("Cantata", NonSecureMode)
{
connect(this, SIGNAL(newConnection()), this, SLOT(slotNewConnection()));
connect(MPDConnection::self(), SIGNAL(statusUpdated(MPDStatusValues)), this, SLOT(statusUpdated(MPDStatusValues)));
connect(MPDConnection::self(), SIGNAL(currentSongUpdated(Song)), this, SLOT(currentSongUpdated(Song)));
connect(this, SIGNAL(currentSong()), MPDConnection::self(), SLOT(currentSong()));
connect(this, SIGNAL(setConsume(bool)), MPDConnection::self(), SLOT(setConsume(bool)));
connect(this, SIGNAL(setRandom(bool)), MPDConnection::self(), SLOT(setRandom(bool)));
connect(this, SIGNAL(setRepeat(bool)), MPDConnection::self(), SLOT(setRepeat(bool)));
connect(this, SIGNAL(setSingle(bool)), MPDConnection::self(), SLOT(setSingle(bool)));
listen();
}
static inline bool isSet(const QString &v) { return QLatin1String("1")==v || QLatin1String("true")==v; }
HttpRequestHandler::HandleStatus StatusApi::handle(HttpRequest *request, HttpResponse *response)
{
if (HttpRequest::Method_Get==request->method()) {
if (request->path()=="/api/v1/status/socket") {
response->write("{\"port\":"+QByteArray::number(serverPort())+"}");
return Status_Handled;
} else if (request->path()=="/api/v1/status") {
response->write(statusMessage());
return Status_Handled;
} else if (request->path()=="/api/v1/status/current") {
response->write(currentSongMessage());
return Status_Handled;
}
} else if(HttpRequest::Method_Post==request->method() && request->path()=="/api/v1/status") {
QString repeat=request->parameter("repeat");
QString random=request->parameter("random");
QString single=request->parameter("single");
QString consume=request->parameter("consume");
DBUG << repeat << random << single << consume;
if (!repeat.isEmpty()) {
emit setRepeat(isSet(repeat));
}
if (!random.isEmpty()) {
emit setRandom(isSet(random));
}
if (!single.isEmpty()) {
emit setSingle(isSet(single));
}
if (!consume.isEmpty()) {
emit setConsume(isSet(consume));
}
return Status_Handled;
}
return Status_BadRequest;
}
void StatusApi::slotNewConnection()
{
while (hasPendingConnections()) {
QWebSocket *sock=nextPendingConnection();
DBUG << (void *)sock;
connect(sock, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
clients.append(sock);
sock->sendTextMessage(statusMessage());
sock->sendTextMessage(currentSongMessage());
}
}
void StatusApi::clientDisconnected()
{
QWebSocket *sock=qobject_cast<QWebSocket *>(sender());
if (!sock) {
return;
}
DBUG << (void *)sock;
sock->deleteLater();
clients.removeAll(sock);
}
void StatusApi::statusUpdated(const MPDStatusValues &updatedStatus)
{
DBUG << clients.count();
if (diff(status, updatedStatus)) {
if (status.playlist!=updatedStatus.playlist) {
// Clear playqueue album keys
Song::clearKeyStore(MPDParseUtils::Loc_PlayQueue);
}
if (status.state!=updatedStatus.state || status.songId!=updatedStatus.songId) {
DBUG << "Get current song";
emit currentSong();
}
status=updatedStatus;
QString message=statusMessage();
foreach (QWebSocket *client, clients) {
client->sendTextMessage(message);
}
}
}
void StatusApi::currentSongUpdated(const Song &s)
{
QString name=s.name();
if (s.isEmpty()) {
song.track=QString();
song.artist=QString();
} else if (s.isStream() && !s.isCantataStream() && !s.isCdda()) {
song.track=name.isEmpty() ? Song::unknown() : name;
if (s.artist.isEmpty() && s.title.isEmpty() && !name.isEmpty()) {
song.artist=i18n("(Stream)");
} else {
song.artist=s.artist.isEmpty() ? s.title : s.artistSong();
}
} else {
if (s.title.isEmpty() && s.artist.isEmpty() && (!name.isEmpty() || !s.file.isEmpty())) {
song.track=name.isEmpty() ? s.file : name;
} else {
song.track=s.title;
}
if (s.album.isEmpty() && s.artist.isEmpty()) {
song.artist=song.track.isEmpty() ? QString() : Song::unknown();
} else if (s.album.isEmpty()) {
song.artist=(s.artist);
} else {
song.artist=s.artist+QLatin1String(" - ")+s.displayAlbum();
}
}
song.id=s.id;
DBUG << song.track << song.artist << song.duration << song.id;
QString message=currentSongMessage();
foreach (QWebSocket *client, clients) {
client->sendTextMessage(message);
}
}
QByteArray StatusApi::statusMessage() const
{
return QByteArray("{"+
encode("volume", (int)status.volume)+
encode("consume", status.consume)+
encode("repeat", status.repeat)+
encode("single", status.single)+
encode("random", status.random)+
encode("playlist", (int)status.playlist)+
encode("playlistLength", (int)status.playlistLength)+
encode("state", status.state)+
encode("timeElapsed", (int)status.timeElapsed)+
encode("timeTotal", (int)status.timeTotal, true)+"}");
}
QByteArray StatusApi::currentSongMessage() const
{
QVariantMap map;
map["track"]=song.track;
map["artist"]=song.artist;
map["duration"]=song.duration;
map["id"]=song.id;
return QJsonDocument::fromVariant(map).toJson();
}