1
0
Fork 0
mirror of https://github.com/HenkKalkwater/harbour-sailfin.git synced 2024-11-22 09:15:18 +00:00

Added basis for WebSocket connection

This commit is contained in:
Chris Josten 2020-10-02 12:20:54 +02:00
parent 5576e53656
commit 97429ff6ec
6 changed files with 203 additions and 6 deletions

View file

@ -12,7 +12,7 @@
# The name of your application # The name of your application
TARGET = harbour-sailfin TARGET = harbour-sailfin
QT += multimedia QT += multimedia websockets
CONFIG += sailfishapp c++11 CONFIG += sailfishapp c++11
@ -31,6 +31,7 @@ SOURCES += \
src/jellyfinapimodel.cpp \ src/jellyfinapimodel.cpp \
src/jellyfindeviceprofile.cpp \ src/jellyfindeviceprofile.cpp \
src/jellyfinmediasource.cpp \ src/jellyfinmediasource.cpp \
src/jellyfinwebsocket.cpp \
src/serverdiscoverymodel.cpp src/serverdiscoverymodel.cpp
DISTFILES += \ DISTFILES += \
@ -90,4 +91,5 @@ HEADERS += \
src/jellyfinapimodel.h \ src/jellyfinapimodel.h \
src/jellyfindeviceprofile.h \ src/jellyfindeviceprofile.h \
src/jellyfinmediasource.h \ src/jellyfinmediasource.h \
src/jellyfinwebsocket.h \
src/serverdiscoverymodel.h src/serverdiscoverymodel.h

View file

@ -22,6 +22,7 @@ PkgConfigBR:
- Qt5Core - Qt5Core
- Qt5Qml - Qt5Qml
- Qt5Quick - Qt5Quick
- Qt5WebSockets
# Build dependencies without a pkgconfig setup can be listed here # Build dependencies without a pkgconfig setup can be listed here
# PkgBR: # PkgBR:
@ -30,6 +31,7 @@ PkgConfigBR:
# Runtime dependencies which are not automatically detected # Runtime dependencies which are not automatically detected
Requires: Requires:
- sailfishsilica-qt5 >= 0.10.9 - sailfishsilica-qt5 >= 0.10.9
- qt5-qtdeclarative-import-xmllistmodel
# All installed files # All installed files
Files: Files:

View file

@ -21,7 +21,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
namespace Jellyfin { namespace Jellyfin {
ApiClient::ApiClient(QObject *parent) ApiClient::ApiClient(QObject *parent)
: QObject(parent) { : QObject(parent),
m_webSocket(new WebSocket(this)) {
m_deviceName = QHostInfo::localHostName(); m_deviceName = QHostInfo::localHostName();
m_deviceId = QUuid::createUuid().toString(); // TODO: make this not random? m_deviceId = QUuid::createUuid().toString(); // TODO: make this not random?
m_credManager = CredentialsManager::newInstance(this); m_credManager = CredentialsManager::newInstance(this);
@ -268,4 +269,10 @@ void ApiClient::defaultNetworkErrorHandler(QNetworkReply::NetworkError error) {
} }
rep->deleteLater(); rep->deleteLater();
} }
void ApiClient::setAuthenticated(bool authenticated) {
this->m_authenticated = authenticated;
if (authenticated) m_webSocket->open();
emit authenticatedChanged(authenticated);
}
} }

View file

@ -37,9 +37,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include "credentialmanager.h" #include "credentialmanager.h"
#include "jellyfindeviceprofile.h" #include "jellyfindeviceprofile.h"
#include "jellyfinwebsocket.h"
namespace Jellyfin { namespace Jellyfin {
class MediaSource; class MediaSource;
class WebSocket;
/** /**
* @brief An Api client for Jellyfin. Handles requests and authentication. * @brief An Api client for Jellyfin. Handles requests and authentication.
* *
@ -67,6 +69,7 @@ class MediaSource;
*/ */
class ApiClient : public QObject { class ApiClient : public QObject {
friend class MediaSource; friend class MediaSource;
friend class WebSocket;
Q_OBJECT Q_OBJECT
public: public:
explicit ApiClient(QObject *parent = nullptr); explicit ApiClient(QObject *parent = nullptr);
@ -194,6 +197,7 @@ private:
/* /*
* State information * State information
*/ */
WebSocket *m_webSocket;
CredentialsManager * m_credManager; CredentialsManager * m_credManager;
QString m_token; QString m_token;
QString m_deviceName; QString m_deviceName;
@ -212,10 +216,8 @@ private:
* Setters * Setters
*/ */
void setAuthenticated(bool authenticated) { void setAuthenticated(bool authenticated);
this->m_authenticated = authenticated;
emit authenticatedChanged(authenticated);
}
void setUserId(QString userId) { void setUserId(QString userId) {
this->m_userId = userId; this->m_userId = userId;
emit userIdChanged(userId); emit userIdChanged(userId);

108
src/jellyfinwebsocket.cpp Normal file
View file

@ -0,0 +1,108 @@
/*
Sailfin: a Jellyfin client written using Qt
Copyright (C) 2020 Chris Josten
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "jellyfinwebsocket.h"
namespace Jellyfin {
WebSocket::WebSocket(ApiClient *client)
: QObject (client), m_apiClient(client){
connect(&m_webSocket, &QWebSocket::connected, this, &WebSocket::onConnected);
connect(&m_webSocket, static_cast<void (QWebSocket::*)(QAbstractSocket::SocketError)>(&QWebSocket::error),
this, [this](QAbstractSocket::SocketError error) {
Q_UNUSED(error)
qDebug() << "Connection error: " << m_webSocket.errorString();
});
}
void WebSocket::open() {
QUrlQuery query;
query.addQueryItem("api_key", m_apiClient->token());
query.addQueryItem("deviceId", m_apiClient->m_deviceId);
QUrl connectionUrl(m_apiClient->baseUrl());
connectionUrl.setScheme(connectionUrl.scheme() == "http" ? "ws" : "wss");
connectionUrl.setPath("/socket");
connectionUrl.setQuery(query);
m_webSocket.open(connectionUrl);
qDebug() << "Opening WebSocket connection to " << m_webSocket.requestUrl();
}
void WebSocket::onConnected() {
connect(&m_webSocket, &QWebSocket::textMessageReceived, this, &WebSocket::textMessageReceived);
}
void WebSocket::textMessageReceived(const QString &message) {
QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8());
if (doc.isNull() || !doc.isObject()) {
qWarning() << "Malformed message received over WebSocket: parse error or root not an object.";
return;
}
QJsonObject messageRoot = doc.object();
if (!messageRoot.contains("MessageType") || !messageRoot.contains("Data")) {
qWarning() << "Malformed message received over WebSocket: no MessageType and Data set.";
return;
}
// Convert the type so we can use it in our enums.
QString messageTypeStr = messageRoot["MessageType"].toString();
bool ok;
MessageType messageType = static_cast<MessageType>(QMetaEnum::fromType<WebSocket::MessageType>().keyToValue(messageTypeStr.toLatin1(), &ok));
if (!ok) {
qWarning() << "Unknown message arrived: " << messageTypeStr;
return;
}
QJsonValue data = messageRoot["Data"];
qDebug() << "Received message: " << messageTypeStr;
switch (messageType) {
case ForceKeepAlive:
setupKeepAlive(data.toInt(-1));
break;
case KeepAlive:
//TODO: do something?
break;
}
}
void WebSocket::sendKeepAlive() {
sendMessage(KeepAlive);
}
void WebSocket::setupKeepAlive(int data) {
// Data is timeout in seconds, we want to send a keepalive at half the timeout
m_keepAliveTimer.setInterval(data * 500);
m_keepAliveTimer.setSingleShot(false);
connect(&m_keepAliveTimer, &QTimer::timeout, this, &WebSocket::sendKeepAlive);
m_keepAliveTimer.start();
sendKeepAlive();
}
QString WebSocket::generateMessageId() {
return QUuid::createUuid().toString();
}
void WebSocket::sendMessage(MessageType type, QJsonValue data) {
QJsonObject root;
root["MessageType"] = QVariant::fromValue(type).toString();
root["Data"] = data;
QString message = QJsonDocument(root).toJson(QJsonDocument::Compact);
m_webSocket.sendTextMessage(message);
qDebug() << "Sent message: " << message;
}
}

76
src/jellyfinwebsocket.h Normal file
View file

@ -0,0 +1,76 @@
/*
Sailfin: a Jellyfin client written using Qt
Copyright (C) 2020 Chris Josten
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef JELLYFIN_WEBSOCKET_H
#define JELLYFIN_WEBSOCKET_H
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <QDebug>
#include <QObject>
#include <QtGlobal>
#include <QTimer>
#include <QUuid>
#include <QtWebSockets/QWebSocket>
#include "jellyfinapiclient.h"
namespace Jellyfin {
class ApiClient;
class WebSocket : public QObject {
Q_OBJECT
public:
/**
* @brief WebSocket creates a webSocket for a Jellyfin server to handle real time updates.
* @param client The client to create the socket for.
*
* The socket will automatically set the ApiClient to its parent.
*/
explicit WebSocket(ApiClient *client);
enum MessageType {
ForceKeepAlive,
KeepAlive
};
Q_ENUM(MessageType)
public slots:
void open();
private slots:
void textMessageReceived(const QString &message);
void onConnected();
void sendKeepAlive();
signals:
void commandReceived(QString arts, QVariantMap args);
protected:
ApiClient *m_apiClient;
QWebSocket m_webSocket;
QTimer m_keepAliveTimer;
void setupKeepAlive(int data);
void sendMessage(MessageType type, QJsonValue data = QJsonValue());
QString generateMessageId();
};
}
#endif // JELLYFIN_WEBSOCKET_H