diff --git a/Desktop.qml b/Desktop.qml
index f2aa83f3..00a41f56 100644
--- a/Desktop.qml
+++ b/Desktop.qml
@@ -1,329 +1,330 @@
// Skeleton from https://github.com/achipa/outqross_blog.git
// Almost everything has been re-adapted
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.2
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import Qt.labs.settings 1.0
import QtGraphicalEffects 1.0
import KDE.Ruqola.Ruqola 1.0
import KDE.Ruqola.DDPClient 1.0
import KDE.Ruqola.Notification 1.0
// import "Log.js" as Log
// import "Data.js" as Data
ApplicationWindow {
property int margin: 11
property string statusText
property string lightGreen: "#6ab141";
property string darkGreen: "#00613a";
property string selectedRoomID: "";
id: appid
title: qsTr("Ruqola")
width: 800
height: 600
visible: true
Shortcut {
sequence: StandardKey.Quit
context: Qt.ApplicationShortcut
onActivated: Qt.quit()
}
Login {
id: loginTab
visible: (Ruqola.loginStatus == DDPClient.LoginFailed || Ruqola.loginStatus == DDPClient.LoggedOut)
// visible: (Ruqola.loginStatus != DDPClient.LoggedIn)
anchors.fill:parent
z: 10
serverURL: Ruqola.serverURL
username: Ruqola.userName
onAccepted: {
Ruqola.password = loginTab.password;
Ruqola.userName = loginTab.username;
Ruqola.serverURL = loginTab.serverURL;
Ruqola.tryLogin();
}
}
BusyIndicator {
id: busy
anchors.centerIn: parent
visible: Ruqola.loginStatus == DDPClient.LoggingIn
}
Item {
id: mainWidget
anchors.fill: parent
visible: !loginTab.visible
Rectangle {
id: userBox
anchors.top: parent.top
width: parent.width
anchors.left: parent.left
anchors.right: roomsList.right
height: 40
color: darkGreen
Text {
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignRight
anchors.rightMargin: 10
anchors.fill: parent
font.pointSize: 12
color: "white"
text: "Hello, " + Ruqola.userName
}
}
RoomsView {
anchors.top: userBox.bottom
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.margins: 0
width: 200
height: appid.height
id: roomsList
model: Ruqola.roomModel()
visible: parent.visible
selectedRoomID: appid.selectedRoomID;
onRoomSelected: {
if (roomID == selectedRoomID) {
return;
}
console.log("Choosing room", roomID);
appid.selectedRoomID = roomID;
activeChat.model = Ruqola.getModelForRoom(roomID)
topicWidget.selectedRoom = Ruqola.getRoom(roomID)
}
onCountChanged: {
// console.log("We have", roomsList.count, "rooms")
}
LinearGradient {
id: greenGradient
anchors.fill: parent
start: Qt.point(0, 0)
end: Qt.point(roomsList.width, 0)
gradient: Gradient {
GradientStop { position: 0.0; color: "#6ab141" }
GradientStop { position: 1.0; color: "#00613a" }
}
z: -1;
}
} //RoomsView
Item {
anchors.right: parent.right
anchors.left: roomsList.right
anchors.top: parent.top
anchors.bottom: input.top
id: chatView
Rectangle {
id: topicWidget
color: "#fff"
anchors.top: parent.top
anchors.right: parent.right
anchors.left: parent.left
height: nameLabel.height + topicLabel.height
property var selectedRoom;
Text {
id: nameLabel
text: "#" + parent.selectedRoom.name
font.pointSize: 18
verticalAlignment: Text.AlignVCenter
anchors.leftMargin: 20
height: 40
// height: font.pixelSize + 10
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
}
Text {
id: topicLabel
text: topicWidget.selectedRoom.topic
anchors.top: nameLabel.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
horizontalAlignment: Text.AlignHCenter
height: font.pixelSize + 10
}
}
ScrollView {
anchors.right: parent.right
anchors.left: parent.left
anchors.top: topicWidget.bottom
anchors.bottom: parent.bottom
verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn
// visible: parent.visible && (Ruqola.loginStatus != DDPClient.LoggingIn)
// visible: !greeter.visible
ListView {
id: activeChat
// model: Ruqola.getModelForRoom(selectedRoomID)
onCountChanged: {
// console.log("changed")
// var newIndex = count - 1 // last index
// positionViewAtEnd()
positionViewAtIndex(count - 1, ListView.Beginning)
// currentIndex = newIndex
}
// Component.onCompleted: positionViewAtEnd()
Component.onCompleted: positionViewAtIndex(count - 1, ListView.Beginning)
// onSelectedRoomIDChanged: { console.log("CHANGED"); activeChat.positionViewAtEnd(); }
// model: myModel
anchors.fill:parent
visible : count > 0
z: -1
// ScrollBar.vertical: ScrollBar { }
delegate: Message {
i_messageText: messageText
i_username: username
i_systemMessage: systemMessage
i_systemMessageType: type
//width: parent.width
}
}
}
} //Item chatView
Item {
anchors.bottom: parent.bottom
anchors.left: roomsList.right
anchors.right: parent.right
id: input
height: 40
TextField {
id: messageLine
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.top: parent.top
anchors.right: attachmentsButton.left
placeholderText: if (Ruqola.loginStatus != DDPClient.LoggedIn || (selectedRoomID=="")){
qsTr("Please Select a room")
}
else{
qsTr("Enter message")
}
// height: 2.7*font.pixelSize
+ property string type: "text";
onAccepted: {
if (text != "" && Ruqola.loginStatus == DDPClient.LoggedIn && !(selectedRoomID=="")) {
- Ruqola.sendMessage(selectedRoomID, text);
+ Ruqola.sendMessage(selectedRoomID, text, type);
text = "";
}
}
}
Button {
anchors.bottom: parent.bottom
anchors.top: parent.top
anchors.right: parent.right
width: 50
id : attachmentsButton
iconName: "Button"
text: "Click"
visible: true
onClicked: Ruqola.attachmentButtonClicked();
}
}//Item input
}// mainWidget Item
Rectangle {
z: -10
anchors.fill: parent
color: "white"
}
onClosing: {
console.log("Minimizing to systray...");
hide();
}
function toggleShow() {
if (visible) {
hide();
} else {
show();
raise();
requestActivate();
}
}
Component.onCompleted: {
systrayIcon.activated.connect(toggleShow);
systrayIcon.messageClicked.connect(toggleShow);
// roomsList.model = Ruqola.roomModel();
// timer.start();
// timer.fire();
}
/*
Timer {
id: timer
interval: 1000
onTriggered: {
// console.log("FIRE");
switch (Ruqola.loginStatus) {
case Ruqola.NotConnected:
statusText = qsTr("Not connected.");
break;
case Ruqola.LoggedIn:
statusText = qsTr("Connected to " + Ruqola.serverURL);
break;
}
}
repeat: true
}*/
// onStatusTextChanged: timer.restart();
}
diff --git a/Ruqola.pro.user b/Ruqola.pro.user
index 594c0f7a..c50c3aec 100644
--- a/Ruqola.pro.user
+++ b/Ruqola.pro.user
@@ -1,656 +1,656 @@
-
+
EnvironmentId
{ab19c5d8-e149-4a7a-9ecf-d7074f8c4302}
ProjectExplorer.Project.ActiveTarget
0
ProjectExplorer.Project.EditorSettings
true
false
true
Cpp
CppGlobal
QmlJS
QmlJSGlobal
2
UTF-8
false
4
false
80
true
true
1
true
false
0
true
true
0
8
true
1
true
true
true
false
ProjectExplorer.Project.PluginSettings
ProjectExplorer.Project.Target.0
Desktop Qt 5.8.0 GCC 64bit
Desktop Qt 5.8.0 GCC 64bit
qt.58.gcc_64_kit
0
0
0
/home/vasudha/Qt/build-Ruqola-Desktop_Qt_5_8_0_GCC_64bit-Debug
true
qmake
QtProjectManager.QMakeBuildStep
true
false
false
false
true
Make
Qt4ProjectManager.MakeStep
-w
-r
false
2
Build
ProjectExplorer.BuildSteps.Build
true
Make
Qt4ProjectManager.MakeStep
-w
-r
true
clean
1
Clean
ProjectExplorer.BuildSteps.Clean
2
false
Debug
Qt4ProjectManager.Qt4BuildConfiguration
2
true
/home/vasudha/Qt/build-Ruqola-Desktop_Qt_5_8_0_GCC_64bit-Release
true
qmake
QtProjectManager.QMakeBuildStep
false
false
false
false
true
Make
Qt4ProjectManager.MakeStep
-w
-r
false
2
Build
ProjectExplorer.BuildSteps.Build
true
Make
Qt4ProjectManager.MakeStep
-w
-r
true
clean
1
Clean
ProjectExplorer.BuildSteps.Clean
2
false
Release
Qt4ProjectManager.Qt4BuildConfiguration
0
true
/home/vasudha/Qt/build-Ruqola-Desktop_Qt_5_8_0_GCC_64bit-Profile
true
qmake
QtProjectManager.QMakeBuildStep
true
false
true
false
true
Make
Qt4ProjectManager.MakeStep
-w
-r
false
2
Build
ProjectExplorer.BuildSteps.Build
true
Make
Qt4ProjectManager.MakeStep
-w
-r
true
clean
1
Clean
ProjectExplorer.BuildSteps.Clean
2
false
Profile
Qt4ProjectManager.Qt4BuildConfiguration
0
true
3
0
Deploy
ProjectExplorer.BuildSteps.Deploy
1
Deploy locally
ProjectExplorer.DefaultDeployConfiguration
1
false
false
1000
true
false
false
false
false
true
0.01
10
true
1
25
1
true
false
true
valgrind
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
Ruqola
Ruqola2
Qt4ProjectManager.Qt4RunConfiguration:/home/vasudha/Qt/Ruqola-desktop/Ruqola/Ruqola.pro
true
Ruqola.pro
false
-
+ /home/vasudha/Qt/build-Ruqola-Desktop_Qt_5_8_0_GCC_64bit-Debug
3768
false
true
false
false
true
1
ProjectExplorer.Project.Target.1
Android for armeabi-v7a (GCC 4.9, Qt 5.8.0)
Android for armeabi-v7a (GCC 4.9, Qt 5.8.0)
{6065b6c8-337b-478a-8e04-5b38fdcd0119}
0
0
0
/home/vasudha/Qt/build-Ruqola-Android_for_armeabi_v7a_GCC_4_9_Qt_5_8_0_6065b6-Debug
true
qmake
QtProjectManager.QMakeBuildStep
true
false
false
false
true
Make
Qt4ProjectManager.MakeStep
-w
-r
false
true
Copy application data
Qt4ProjectManager.AndroidPackageInstallationStep
android-25
true
Build Android APK
QmakeProjectManager.AndroidBuildApkStep
2
false
false
4
Build
ProjectExplorer.BuildSteps.Build
true
Make
Qt4ProjectManager.MakeStep
-w
-r
true
clean
1
Clean
ProjectExplorer.BuildSteps.Clean
2
false
Debug
Qt4ProjectManager.Qt4BuildConfiguration
2
true
/home/vasudha/Qt/build-Ruqola-Android_for_armeabi_v7a_GCC_4_9_Qt_5_8_0_6065b6-Release
true
qmake
QtProjectManager.QMakeBuildStep
false
false
false
false
true
Make
Qt4ProjectManager.MakeStep
-w
-r
false
true
Copy application data
Qt4ProjectManager.AndroidPackageInstallationStep
android-25
true
Build Android APK
QmakeProjectManager.AndroidBuildApkStep
2
false
false
4
Build
ProjectExplorer.BuildSteps.Build
true
Make
Qt4ProjectManager.MakeStep
-w
-r
true
clean
1
Clean
ProjectExplorer.BuildSteps.Clean
2
false
Release
Qt4ProjectManager.Qt4BuildConfiguration
0
true
/home/vasudha/Qt/build-Ruqola-Android_for_armeabi_v7a_GCC_4_9_Qt_5_8_0_6065b6-Profile
true
qmake
QtProjectManager.QMakeBuildStep
true
false
true
false
true
Make
Qt4ProjectManager.MakeStep
-w
-r
false
true
Copy application data
Qt4ProjectManager.AndroidPackageInstallationStep
android-25
true
Build Android APK
QmakeProjectManager.AndroidBuildApkStep
2
false
false
4
Build
ProjectExplorer.BuildSteps.Build
true
Make
Qt4ProjectManager.MakeStep
-w
-r
true
clean
1
Clean
ProjectExplorer.BuildSteps.Clean
2
false
Profile
Qt4ProjectManager.Qt4BuildConfiguration
0
true
3
true
Deploy to Android device
Qt4ProjectManager.AndroidDeployQtStep
false
1
Deploy
ProjectExplorer.BuildSteps.Deploy
1
Deploy to Android device
Deploy to Android device
Qt4ProjectManager.AndroidDeployConfiguration2
1
false
false
1000
true
false
false
false
false
true
0.01
10
true
1
25
1
true
false
true
valgrind
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-1
%{buildDir}
Custom Executable
ProjectExplorer.CustomExecutableRunConfiguration
3768
false
true
false
false
true
1
ProjectExplorer.Project.TargetCount
2
ProjectExplorer.Project.Updater.FileVersion
18
Version
18
diff --git a/src/ddpclient.cpp b/src/ddpclient.cpp
index 331e95a1..a0149769 100644
--- a/src/ddpclient.cpp
+++ b/src/ddpclient.cpp
@@ -1,323 +1,336 @@
/*
*
* Copyright 2016 Riccardo Iaconelli
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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. If not, see .
*
*/
#include "ddpclient.h"
#include
#include
#include
#include
#include "ruqola.h"
void process_test(QJsonDocument doc)
{
qDebug() << "Callback test:" << doc;
qDebug() << "End callback";
}
void login_callback(QJsonDocument doc)
{
qDebug() << "LOGIN:" << doc;
Ruqola::self()->setAuthToken(doc.object().value("token").toString());
qDebug() << "End callback";
}
void DDPClient::resume_login_callback(QJsonDocument doc)
{
qDebug() << "LOGIN:" << doc;
Ruqola::self()->setAuthToken(doc.object().value("token").toString());
qDebug() << "End callback";
}
void empty_callback(QJsonDocument doc)
{
Q_UNUSED(doc);
}
DDPClient::DDPClient(const QString& url, QObject* parent)
: QObject(parent),
m_url(url),
m_uid(1),
m_loginJob(0),
m_loginStatus(NotConnected),
m_connected(false),
m_attemptedPasswordLogin(false),
m_attemptedTokenLogin(false)
{
m_webSocket.ignoreSslErrors();
connect(&m_webSocket, &QWebSocket::connected, this, &DDPClient::onWSConnected);
connect(&m_webSocket, &QWebSocket::textMessageReceived, this, &DDPClient::onTextMessageReceived);
connect(&m_webSocket, &QWebSocket::disconnected, this, &DDPClient::WSclosed);
connect(Ruqola::self(), &Ruqola::serverURLChanged, this, &DDPClient::onServerURLChange);
if (!url.isEmpty()) {
m_webSocket.open(QUrl("wss://"+url+"/websocket"));
}
qDebug() << "Trying to connect to URL" << url;
}
DDPClient::~DDPClient()
{
m_webSocket.close();
}
void DDPClient::onServerURLChange()
{
if (Ruqola::self()->serverURL() != m_url || !m_webSocket.isValid()) {
if (m_webSocket.isValid()) {
m_webSocket.flush();
m_webSocket.close();
}
m_url = Ruqola::self()->serverURL();
m_webSocket.open(QUrl("wss://"+m_url+"/websocket"));
connect(&m_webSocket, &QWebSocket::connected, this, &DDPClient::onWSConnected);
qDebug() << "Reconnecting" << m_url; //<< m_webSocket.st;
}
}
DDPClient::LoginStatus DDPClient::loginStatus() const
{
return m_loginStatus;
}
bool DDPClient::isConnected() const
{
return m_connected;
}
bool DDPClient::isLoggedIn() const
{
return m_loginStatus == LoggedIn;
}
unsigned int DDPClient::method(const QString& m, const QJsonDocument& params)
{
return method(m, params, empty_callback);
}
unsigned int DDPClient::method(const QString& method, const QJsonDocument& params, std::function callback)
{
QJsonObject json;
json["msg"] = "method";
json["method"] = method;
json["id"] = QString::number(m_uid);
if (params.isArray()){
json["params"] = params.array();
} else if (params.isObject()) {
QJsonArray arr;
arr.append(params.object());
json["params"] = arr;
// params.object();
}
qint64 bytes = m_webSocket.sendTextMessage(QJsonDocument(json).toJson(QJsonDocument::Compact));
if (bytes < json.length()) {
qDebug() << "ERROR! I couldn't send all of my message. This is a bug! (try again)";
qDebug() << m_webSocket.isValid() << m_webSocket.error() << m_webSocket.requestUrl();
} else {
qDebug() << "Successfully sent " << json;
}
//callback(QJsonDocument::fromJson(json.toUtf8()));
m_callbackHash[m_uid] = callback;
m_uid++;
return m_uid - 1 ;
}
void DDPClient::subscribe(const QString& collection, const QJsonArray& params)
{
QJsonObject json;
json["msg"] = "sub";
json["id"] = QString::number(m_uid);
json["name"] = collection;
json["params"] = params;
qint64 bytes = m_webSocket.sendTextMessage(QJsonDocument(json).toJson(QJsonDocument::Compact));
if (bytes < json.length()) {
qDebug() << "ERROR! I couldn't send all of my message. This is a bug! (try again)";
}
m_uid++;
}
void DDPClient::onTextMessageReceived(QString message)
{
// qDebug() << "DDPClient::onTextMessageReceived" << message;
QJsonDocument response = QJsonDocument::fromJson(message.toUtf8());
if (!response.isNull() && response.isObject()) {
QJsonObject root = response.object();
QString messageType = root.value("msg").toString();
if (messageType == "updated") {
} else if (messageType == "result") {
// qDebug() << "Got a result" << root;
unsigned id = root.value("id").toString().toInt();
if (m_callbackHash.contains(id)) {
std::function callback = m_callbackHash.take(id);
//----------------------------------------------------
//Receiving image message
QByteArray ba;
ba.append(root.value("result").toString());
- int imageWidth = 400;
- int imageHeight = 300;
- int bytesPerPixel = 3;
- QImage image((uchar *)ba.data(), imageWidth, imageHeight, imageWidth * bytesPerPixel, QImage::Format_RGB32);
- //image.loadFromData(ba);
- if (image.isNull() || image.byteCount() != ba.size()) {
+ //check message type from params
+ QJsonArray params = root.value("result").toArray();
+ QString type = params.at(3);
+ if (type == "image"){
+ QDataStream in;
+ QByteArray ba;
+ in >> ba;
+ file.open(QIODevice::WriteOnly);
+ file.write(ba);
+ file.close();
+ } else { //text message
+
+ }
+ */
+ /*
+ int imageFlag = 0;
+ if (image.isNull() ){
+ qDebug() << "Text Message Received";
+ } else if ( image.byteCount() != ba.size()) {
qDebug() << "Error: Image not loaded correctly";
+ } else {
+ qDebug() << "Image received";
+ imageFlag = 1;
}
-// QLabel label;
-// label.setPixmap(QPixmap::fromImage(image));
-// label.show();
+ */
//----------------------------------------------------------------
callback( QJsonDocument(root.value("result").toObject()) );
-
}
emit result(id, QJsonDocument(root.value("result").toObject()));
-
+
if (id == m_loginJob) {
if (root.value("error").toObject().value("error").toInt() == 403) {
qDebug() << "Wrong password or token expired";
login(); // Let's keep trying to log in
} else {
Ruqola::self()->setAuthToken(root.value("result").toObject().value("token").toString());
setLoginStatus(DDPClient::LoggedIn);
}
// emit loggedInChanged();
}
} else if (messageType == "connected") {
qDebug() << "Connected";
m_connected = true;
emit connectedChanged();
setLoginStatus(DDPClient::LoggingIn);
login(); // Try to resume auth token login
} else if (messageType == "error") {
qDebug() << "ERROR!!" << message;
} else if (messageType == "ping") {
qDebug() << "Ping - Pong";
QJsonObject pong;
pong["msg"] = "pong";
m_webSocket.sendBinaryMessage(QJsonDocument(pong).toJson(QJsonDocument::Compact));
} else if (messageType == "added"){
qDebug() << "ADDING" <password().isEmpty()) {
// If we have a password and we couldn't log in, let's stop here
if (m_attemptedPasswordLogin) {
setLoginStatus(LoginFailed);
return;
}
m_attemptedPasswordLogin = true;
QJsonObject user;
user["username"] = Ruqola::self()->userName();
QJsonObject json;
json["password"] = Ruqola::self()->password();
json["user"] = user;
m_loginJob = method("login", QJsonDocument(json));
} else if (!Ruqola::self()->authToken().isEmpty() && !m_attemptedTokenLogin) {
m_attemptedPasswordLogin = true;
QJsonObject json;
json["resume"] = Ruqola::self()->authToken();
m_loginJob = method("login", QJsonDocument(json));
} else {
setLoginStatus(LoginFailed);
}
}
void DDPClient::logOut()
{
// setLoginStatus(NotConnected);
m_webSocket.close();
}
void DDPClient::onWSConnected()
{
qDebug() << "Websocket connected at URL" << m_url;
QJsonArray supportedVersions;
supportedVersions.append("1");
QJsonObject protocol;
protocol["msg"] = "connect";
protocol["version"] = "1";
protocol["support"] = supportedVersions;
// QString json("{\"msg\":\"connect\", \"version\": \"1\", \"support\": [\"1\"]}");
QByteArray serialize = QJsonDocument(protocol).toJson(QJsonDocument::Compact);
qint64 bytes = m_webSocket.sendTextMessage(serialize);
if (bytes < serialize.length()) {
qDebug() << "ERROR! I couldn't send all of my message. This is a bug! (try again)";
} else {
qDebug() << "Successfully sent " << serialize;
}
}
void DDPClient::WSclosed()
{
qDebug() << "WebSocket CLOSED" << m_webSocket.closeReason() << m_webSocket.error() << m_webSocket.closeCode();
setLoginStatus(NotConnected);
// m_connected = false;
}
diff --git a/src/messagemodel.cpp b/src/messagemodel.cpp
index f3820512..f9743b16 100644
--- a/src/messagemodel.cpp
+++ b/src/messagemodel.cpp
@@ -1,211 +1,205 @@
/*
*
* Copyright 2016 Riccardo Iaconelli
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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. If not, see .
*
*/
#include
#include
#include
#include
#include
// #include
#include
#include
#include "messagemodel.h"
#include "ruqola.h"
Message MessageModel::fromJSon(const QJsonObject& o)
{
Message message;
message.username = o["username"].toString();
message.message = o["message"].toString();
message.userID = o["userID"].toString();
message.timestamp = (qint64) o["timestamp"].toDouble();
message.systemMessage = o["systemMessage"].toBool();
message.systemMessageType = o["type"].toString();
message.roomID = o["roomID"].toString();
message.messageID = o["messageID"].toString();
return message;
}
QByteArray MessageModel::serialize(const Message& message)
{
QJsonDocument d;
QJsonObject o;
o["username"] = message.username;
o["message"] = message.message;
o["userID"] = message.userID;
o["timestamp"] = message.timestamp;
o["systemMessage"] = message.systemMessage;
o["type"] = message.systemMessageType;
o["roomID"] = message.roomID;
o["messageID"] = message.messageID;
d.setObject(o);
return d.toBinaryData();
}
MessageModel::MessageModel(const QString &roomID, QObject* parent)
: QAbstractListModel(parent),
m_roomID(roomID)
{
qDebug() << "Creating message Model";
QDir cacheDir(Ruqola::self()->cacheBasePath()+"/rooms_cache");
// load cache
if (QFile::exists(cacheDir.absoluteFilePath(roomID)) && !roomID.isEmpty()) {
QFile f(cacheDir.absoluteFilePath(roomID));
if (f.open(QIODevice::ReadOnly)) {
QDataStream in(&f);
while (!f.atEnd()) {
char * byteArray;
quint32 length;
in.readBytes(byteArray, length);
QByteArray arr = QByteArray::fromRawData(byteArray, length);
Message m = MessageModel::fromJSon(QJsonDocument::fromBinaryData(arr).object());
addMessage(m);
// m_allMessages[m.timestamp] = m;
// qDebug() << m.message;
}
}
}
}
MessageModel::~MessageModel()
{
QDir cacheDir(Ruqola::self()->cacheBasePath()+"/rooms_cache");
qDebug() << "Caching to..." << cacheDir.path();
if (!cacheDir.exists(cacheDir.path())) {
cacheDir.mkpath(cacheDir.path());
}
QFile f(cacheDir.absoluteFilePath(m_roomID));
if (f.open(QIODevice::WriteOnly)) {
QDataStream out(&f);
foreach (const Message m, m_allMessages) {
QByteArray ms = MessageModel::serialize(m);
out.writeBytes(ms, ms.size());
}
}
}
QHash MessageModel::roleNames() const
{
QHash roles;
roles[MessageText] = "messageText";
roles[Username] = "username";
roles[Timestamp] = "timestamp";
roles[UserID] = "userID";
roles[SystemMessage] = "systemMessage";
roles[SystemMessageType] = "type";
return roles;
}
qint64 MessageModel::lastTimestamp() const
{
if (m_allMessages.size()) {
qDebug() << "returning timestamp" << m_allMessages.last().timestamp;
return m_allMessages.last().timestamp;
} else {
return 0;
}
}
int MessageModel::rowCount(const QModelIndex& parent) const
{
// qDebug() << "C++ asked for rowcount " << m_allMessages.size();
// if (m_allMessages.contains(m_currentRoom)) {
return m_allMessages.size();
(void)parent;
}
void MessageModel::addMessage(const Message& message)
{
// Don't add empty messages?
if (message.message.isEmpty()) {
return;
}
-
-// qDebug() << "MessageModel::addMessage called";
-
+ // qDebug() << "MessageModel::addMessage called for msg: " << message.message;
auto existingMessage = qFind(m_allMessages.begin(), m_allMessages.end(), message);
bool present = (existingMessage != m_allMessages.end());
-
- auto i = qUpperBound(m_allMessages.begin(), m_allMessages.end(),
- message);
-
+ auto i = qUpperBound(m_allMessages.begin(), m_allMessages.end(), message);
int pos = i-m_allMessages.begin();
-
bool messageChanged = false;
// if (qFind(m_allMessages.begin(), m_allMessages.end(), message) != m_allMessages.end()) {
if (present){
// if (pos != m_allMessages.size()) { // we're at the end
// qDebug() << "detecting a message change";
messageChanged = true;
//Figure out a better way to update just the really changed message
} else {
beginInsertRows(QModelIndex(), pos, pos);
}
if (messageChanged) {
m_allMessages.replace(pos-1, message);
} else {
m_allMessages.insert(i, message);
}
if (messageChanged) {
emit dataChanged(createIndex(1, 1), createIndex(pos, 1));
} else {
endInsertRows();
}
}
QVariant MessageModel::data(const QModelIndex& index, int role) const
{
int idx = index.row();//-1;
if (role == MessageModel::Username) {
// qDebug() << "C++ returning username" <<
// m_allMessages[m_currentRoom].values().at(idx).username();
return m_allMessages.at(idx).username;
} else if (role == MessageModel::MessageText) {
return m_allMessages.at(idx).message;
} else if (role == MessageModel::Timestamp) {
return QVariant(m_allMessages.at(idx).timestamp);
} else if (role == MessageModel::UserID) {
return QVariant(m_allMessages.at(idx).userID);
} else if (role == MessageModel::SystemMessage) {
// qDebug() << "System message?" << m_allMessages.at(idx).systemMessage;
return QVariant(m_allMessages.at(idx).systemMessage);
} else if (role == MessageModel::SystemMessageType) {
return QVariant(m_allMessages.at(idx).systemMessageType);
} else {
return QVariant("");
}
}
// #include "messagelist.moc"
diff --git a/src/rocketchatbackend.cpp b/src/rocketchatbackend.cpp
index 0c90c16b..7992196c 100644
--- a/src/rocketchatbackend.cpp
+++ b/src/rocketchatbackend.cpp
@@ -1,265 +1,263 @@
/*
*
* Copyright 2016 Riccardo Iaconelli
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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. If not, see .
*
*/
#include "rocketchatbackend.h"
#include
#include
#include
#include "ruqola.h"
#include "ddpclient.h"
void debug_callback(QJsonDocument doc)
{
qDebug() << "DEBUG:" << doc;
}
void process_backlog(QJsonDocument messages)
{
qDebug() << messages.object().value("messages").toArray().size();
RocketChatBackend::processIncomingMessages(messages.object().value("messages").toArray());
}
void rooms_callback(QJsonDocument doc)
{
RoomModel *model = Ruqola::self()->roomModel();
QJsonArray removed = doc.object().value("remove").toArray();
QJsonArray updated = doc.object().value("update").toArray();
for (int i = 0; i < updated.size(); i++) {
QJsonObject room = updated.at(i).toObject();
if (room.value("t").toString() != "d") {
QString roomID = room.value("_id").toString();
MessageModel *roomModel = Ruqola::self()->getModelForRoom(roomID);
// let's be extra safe around crashes
if (Ruqola::self()->loginStatus() == DDPClient::LoggedIn) {
Room r;
r.id = roomID;
r.name = room["name"].toString();
r.topic = room["topic"].toString();
qDebug() << "Adding room" << r.name << r.id << r.topic;
model->addRoom(r);
}
QJsonArray params;
params.append(QJsonValue(roomID));
Ruqola::self()->ddp()->subscribe("stream-room-messages", params);
// Load history
params.append(QJsonValue(QJsonValue::Null));
params.append(QJsonValue(50)); // Max number of messages to load;
QJsonObject dateObject;
dateObject["$date"] = QJsonValue(roomModel->lastTimestamp());
params.append(dateObject);
Ruqola::self()->ddp()->method("loadHistory", QJsonDocument(params), process_backlog);
}
}
}
void subs_callback(QJsonDocument doc)
{
RoomModel *model = Ruqola::self()->roomModel();
QJsonArray removed = doc.object().value("remove").toArray();
QJsonArray updated = doc.object().value("update").toArray();
for (int i = 0; i < updated.size(); i++) {
QJsonObject room = updated.at(i).toObject();
if (room.value("t").toString() != "d") {
QString roomID = room.value("rid").toString();
MessageModel *roomModel = Ruqola::self()->getModelForRoom(roomID);
// let's be extra safe around crashes
if (Ruqola::self()->loginStatus() == DDPClient::LoggedIn) {
Room r;
r.id = roomID;
r.name = room["name"].toString();
r.topic = room["topic"].toString();
qDebug() << "Adding room" << r.name << r.id << r.topic;
model->addRoom(r);
}
QJsonArray params;
params.append(QJsonValue(roomID));
Ruqola::self()->ddp()->subscribe("stream-room-messages", params);
// Load history
params.append(QJsonValue(QJsonValue::Null));
params.append(QJsonValue(50)); // Max number of messages to load;
QJsonObject dateObject;
dateObject["$date"] = QJsonValue(roomModel->lastTimestamp());
params.append(dateObject);
Ruqola::self()->ddp()->method("loadHistory", QJsonDocument(params), process_backlog);
}
}
}
void RocketChatBackend::processIncomingMessages(QJsonArray messages)
{
foreach (const QJsonValue v, messages) {
QJsonObject o = v.toObject();
Message m;
QString roomId = o.value("rid").toString();
QString type = o.value("t").toString();
m.username = o.value("u").toObject().value("username").toString();
m.userID = o.value("u").toObject().value("_id").toString();
m.message = o.value("msg").toString();
m.messageID = o.value("_id").toString();
m.roomID = roomId;
m.timestamp = (qint64)o.value("ts").toObject().value("$date").toDouble();
if (!type.isEmpty()) {
m.systemMessage = true;
m.systemMessageType = type;
} else {
m.systemMessage = false;
}
Ruqola::self()->getModelForRoom(roomId)->addMessage(m);
// qDebug() << "RocketChatBackend::processIncomingMessages sending notification";
// //Send notifications only when user is logged in
// if ( Ruqola::self()->loginStatus() == DDPClient::LoggedIn) {
// QString userName = m.username;
// QString message = m.message;
// QString param = QString("%1 \n %2").arg(userName).arg(message);
// Ruqola::self()->notification()->setMessage(param);
// } else {
// qDebug() << m.username << " recieved message: " << m.message;
// }
}
}
RocketChatBackend::RocketChatBackend(QObject* parent)
: QObject(parent)
{
connect(Ruqola::self(), &Ruqola::loginStatusChanged, this, &RocketChatBackend::onLoginStatusChanged);
connect(Ruqola::self(), &Ruqola::userIDChanged, this, &RocketChatBackend::onUserIDChanged);
connect(Ruqola::self()->ddp(), &DDPClient::changed, this, &RocketChatBackend::onChanged);
connect(Ruqola::self()->ddp(), &DDPClient::added, this, &RocketChatBackend::onAdded);
}
RocketChatBackend::~RocketChatBackend()
{
}
void RocketChatBackend::onLoginStatusChanged()
{
if (Ruqola::self()->loginStatus() == DDPClient::LoggedIn) {
qDebug() << "GETTING LIST OF ROOMS";
// Ruqola::self()->ddp()->method("subscriptions/get", QJsonDocument::fromJson("{\"$date\": 0}"), rooms_callback);
QJsonObject params;
params["$date"] = QJsonValue(0); // get ALL rooms we've ever seen
Ruqola::self()->ddp()->method("rooms/get", QJsonDocument(params), rooms_callback);
// Ruqola::self()->ddp()->subscribe("stream-room-messages", QJsonDocument::fromJson(params.toLatin1()));
}
}
void RocketChatBackend::onLoggedIn()
{
// if (Ruqola::self()->loginStatus() != DDPClient::LoggedIn) {
// qDebug() << "not yet logged in:" << Ruqola::self()->loginStatus();
// return;
// }
// // get list of rooms
// Ruqola::self()->ddp()->method("rooms/get", QJsonDocument::fromJson("{\"$date\": 0}"), rooms_callback);
}
void RocketChatBackend::onAdded(QJsonObject object)
{
QString collection = object.value("collection").toString();
// qDebug() << "ROCKET BACK" << object << collection;
if (collection == "stream-room-messages") {
} else if (collection == "users") {
if (object["username"].isNull()) {
// it's us! get ID
Ruqola::self()->setUserID(object["id"].toString());
}
qDebug() << "NEW USER ADDED: " << object.value("userName").toString();
} else if (collection == "rooms") {
}
else if (collection == "stream-notify-user"){
}
}
void RocketChatBackend::onChanged(QJsonObject object)
{
QString collection = object["collection"].toString();
// qDebug() << "ROCKET CHAT BACK onChanged" << object << collection;
if (collection == "stream-room-messages") {
QJsonObject fields = object.value("fields").toObject();
QString roomId = fields.value("eventName").toString();
QJsonArray contents = fields.value("args").toArray();
processIncomingMessages(contents);
} else if (collection == "users") {
qDebug() << "USER CHANGED";
} else if (collection == "rooms") {
} else if (collection == "stream-notify-user") {
QJsonObject fields = object.value("fields").toObject();
QJsonArray contents = fields.value("args").toArray();
-// Ruqola::self()->notification()->setMessage(contents.at(0).toObject()["text"].toString());
QString message = contents.at(0).toObject()["text"].toString();
Ruqola::self()->notification()->showMessage("New message", message, QSystemTrayIcon::Information, 5000 );
-
qDebug() << "New notification" << object.value("fields").toObject();
}
}
void RocketChatBackend::onUserIDChanged()
{
qDebug() << "subscribing to notification feed";
QJsonArray params;
params.append(QJsonValue(QString("%1/%2").arg(Ruqola::self()->userID()).arg(QString("notification"))));
Ruqola::self()->ddp()->subscribe("stream-notify-user", params);
}
diff --git a/src/ruqola.cpp b/src/ruqola.cpp
index 8a65bc52..5f69c892 100644
--- a/src/ruqola.cpp
+++ b/src/ruqola.cpp
@@ -1,277 +1,275 @@
/*
*
* Copyright 2016 Riccardo Iaconelli
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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. If not, see .
*
*/
#include "ruqola.h"
#include "roommodel.h"
#include "ddpclient.h"
#include "notification.h"
#include
#include
#include
+#include
Ruqola *Ruqola::m_self = 0;
QString Ruqola::authToken() const
{
return m_authToken;
}
QString Ruqola::userName() const
{
return m_userName;
}
QString Ruqola::userID() const
{
return m_userID;
}
QString Ruqola::password() const
{
return m_password;
}
void Ruqola::setAuthToken(const QString& token)
{
qDebug() << "Setting token to" << token;
QSettings s;
m_authToken = token;
s.setValue("authToken", token);
}
void Ruqola::setPassword(const QString& password)
{
m_password = password;
}
void Ruqola::setUserName(const QString& username)
{
m_userName = username;
QSettings s;
s.setValue("username", username);
emit userNameChanged();
}
void Ruqola::setUserID(const QString& userID)
{
m_userName = userID;
QSettings s;
s.setValue("userID", userID);
emit userIDChanged();
}
RoomModel * Ruqola::roomModel()
{
if (!m_roomModel) {
qDebug() << "creating new RoomModel";
m_roomModel = new RoomModel(this);
qDebug() << m_roomModel;
}
return m_roomModel;
}
DDPClient * Ruqola::ddp()
{
if (!m_ddp) {
m_ddp = new DDPClient(serverURL());
connect(m_ddp, &DDPClient::loginStatusChanged, this, &Ruqola::loginStatusChanged);
// connect(m_ddp, &DDPClient::loginStatusChanged, this, [=](){qDebug() << "Signal received";});
}
return m_ddp;
}
Notification * Ruqola::notification()
{
if (m_notification == NULL) {
m_notification = new Notification();
m_notification->show();
}
return m_notification;
}
void Ruqola::attachmentButtonClicked()
{
- //open fileDialogBox, select an image, convert it to bytearray and send as message
-// QFileDialog dialog(NULL);
-// dialog.setFileMode(QFileDialog::ExistingFile);
-// dialog.setViewMode(QFileDialog::List);
+ //open fileDialogBox, select an image, encode and send to server
QString fileName = QFileDialog::getOpenFileName(Q_NULLPTR,
"Select one or more files to open",
QDir::homePath(),
"Images (*.png *.jpeg *.jpg)");
- qDebug() << "Selected Image" << fileName;
- sendImage(fileName);
-}
-void Ruqola::sendImage(QString fileName)
-{
- QImage image(fileName);
- QByteArray ba;
+ qDebug() << "Selected Image " << fileName;
- qDebug() << "Sending Image of size " << image.byteCount();
+ QFile file(fileName);
+ if (!file.open(QFile::ReadOnly)) {
+ qDebug() << "Cannot open the selected file";
+ return;
+ }
- ba.append((char *)image.bits(),image.byteCount());
- if (image.byteCount() != ba.size())
- qDebug() << "Image not encoded correctly";
+ QByteArray block; // Data that will be sent
+ block = file.readAll();
+ block.toBase64();
- QString message(ba);
- //hard code roomID for now
- QString roomID("3cGRyFLWgnPL7B79n");
- sendMessage(roomID,message);
-}
+ QString message(block);
+ QString roomID("3cGRyFLWgnPL7B79n"); //hard code roomID for now
+ QString type("image");
+ qDebug() << "base64 image- " << message;
+ sendMessage(roomID,message,type);
+}
-void Ruqola::sendMessage(const QString &roomID, const QString &message)
+void Ruqola::sendMessage(const QString &roomID, const QString &message, const QString type)
{
- QString json = "{\"rid\": \"%1\", \"msg\": \"%2\"}";
- json = json.arg(roomID, message);
- // qDebug() << "Sending json: " << json;
+ QString json = "{\"rid\": \"%1\", \"msg\": \"%2\", \"type\": \"%3\"}";
+ json = json.arg(roomID, message, type);
+ qDebug() << "Sending json: " << json;
+
ddp()->method("sendMessage", QJsonDocument::fromJson(json.toUtf8()));
}
MessageModel * Ruqola::getModelForRoom(const QString& roomID)
{
if (m_messageModels.contains(roomID)) {
// qDebug() << "Returning old model for " << roomID;
return m_messageModels.value(roomID);
} else {
// qDebug() << "Creating a new model";
m_messageModels[roomID] = new MessageModel(roomID, this);
return m_messageModels[roomID];
}
}
QString Ruqola::serverURL() const
{
return m_serverURL;
}
void Ruqola::setServerURL(const QString& serverURL)
{
if (m_serverURL == serverURL) {
return;
}
QSettings s;
s.setValue("serverURL", serverURL);
m_serverURL = serverURL;
// m_roomModel->reset();
emit serverURLChanged();
}
DDPClient::LoginStatus Ruqola::loginStatus()
{
if (m_ddp) {
return ddp()->loginStatus();
} else {
return DDPClient::LoggedOut;
}
}
void Ruqola::tryLogin()
{
qDebug() << "Attempting login" << userName() << "on" << serverURL();
// Reset model views
foreach (const QString key, m_messageModels.keys()) {
MessageModel *m = m_messageModels.take(key);
delete m;
}
delete m_ddp;
m_ddp = 0;
// In the meantime, load cache...
m_roomModel->reset();
// This creates a new ddp() object.
// DDP will automatically try to connect and login.
ddp();
}
void Ruqola::logOut()
{
setAuthToken(QString());
setPassword(QString());
foreach (const QString key, m_messageModels.keys()) {
MessageModel *m = m_messageModels.take(key);
delete m;
}
delete m_ddp;
m_ddp = 0;
emit loginStatusChanged();
m_roomModel->clear();
}
QString Ruqola::cacheBasePath() const
{
if (m_serverURL.isEmpty()) {
return QString();
}
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation)+'/'+m_serverURL;
}
// QString Ruqola::activeRoom() const
// {
// return m_activeRoom;
// }
// void Ruqola::setActiveRoom(const QString& activeRoom)
// {
// m_activeRoom = activeRoom;
// // roomModel()->setActiveRoom(activeRoom);
// emit activeRoomChanged();
// }
RoomWrapper * Ruqola::getRoom(const QString& roomID)
{
return roomModel()->findRoom(roomID);
}
Ruqola::Ruqola(QObject* parent): QObject(parent), m_ddp(0), m_roomModel(0), m_notification(0)
{
QSettings s;
m_serverURL = s.value("serverURL", "demo.rocket.chat").toString();
m_userName = s.value("username").toString();
m_userID = s.value("userID").toString();
m_authToken = s.value("authToken").toString();
}
Ruqola * Ruqola::self()
{
if (!m_self) {
m_self = new Ruqola;
// Create DDP object so we try to connect at startup
m_self->ddp();
// Clear rooms data and refill it with data in the cache, if there is
m_self->roomModel()->reset();
// Create systray to show notifications
m_self->notification();
}
return m_self;
}
diff --git a/src/ruqola.h b/src/ruqola.h
index 5bb27ec4..536ad6b4 100644
--- a/src/ruqola.h
+++ b/src/ruqola.h
@@ -1,122 +1,121 @@
/*
*
* Copyright 2016 Riccardo Iaconelli
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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. If not, see .
*
*/
#ifndef USERDATA_H
#define USERDATA_H
#include
#include
#include
#include
#include "ddpclient.h"
#include "roommodel.h"
#include "messagemodel.h"
#include "notification.h"
class QString;
class Ruqola: public QObject
{
Q_OBJECT
Q_PROPERTY(QString userName READ userName WRITE setUserName NOTIFY userNameChanged)
Q_PROPERTY(QString userID READ userID WRITE setUserID NOTIFY userIDChanged)
Q_PROPERTY(QString serverURL READ serverURL WRITE setServerURL NOTIFY serverURLChanged)
Q_PROPERTY(QString password WRITE setPassword)
// Q_PROPERTY (bool connected READ connected NOTIFY connectedChanged)
Q_PROPERTY(DDPClient::LoginStatus loginStatus READ loginStatus NOTIFY loginStatusChanged)
// Q_PROPERTY(QString activeRoom READ activeRoom WRITE setActiveRoom NOTIFY activeRoomChanged)
public:
static Ruqola* self();
void setUserName(const QString &username);
QString userName() const;
void setUserID(const QString &userID);
QString userID() const;
void setPassword(const QString &password);
QString password() const;
void setAuthToken(const QString &token);
QString authToken() const;
bool connected();
DDPClient::LoginStatus loginStatus();
QString serverURL() const;
void setServerURL(const QString &serverURL);
// QString activeRoom() const;
// void setActiveRoom(const QString &activeRoom);
DDPClient *ddp();
Notification * notification();
Q_INVOKABLE RoomModel *roomModel();
- Q_INVOKABLE void sendMessage(const QString &roomID, const QString &message);
+ Q_INVOKABLE void sendMessage(const QString &roomID, const QString &message, const QString type);
Q_INVOKABLE MessageModel* getModelForRoom(const QString &roomID);
Q_INVOKABLE void tryLogin();
Q_INVOKABLE void logOut();
Q_INVOKABLE RoomWrapper* getRoom(const QString &roomID);
Q_INVOKABLE void attachmentButtonClicked();
- void sendImage(QString fileName);
QString cacheBasePath() const;
signals:
void userNameChanged();
void userIDChanged();
void serverURLChanged();
void loginStatusChanged();
private:
Ruqola(QObject *parent = 0);
static Ruqola *m_self;
QString m_password;
QString m_userName;
QString m_userID;
QString m_authToken;
QString m_serverURL;
DDPClient *m_ddp;
RoomModel *m_roomModel;
Notification *m_notification;
QHash< QString, MessageModel * > m_messageModels;
};
inline static QObject *ruqola_singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
Ruqola *userData = Ruqola::self();
return userData;
}
#endif // USERDATA_H