diff --git a/src/ddpclient.cpp b/src/ddpclient.cpp index e8e86c1f..b97d1713 100644 --- a/src/ddpclient.cpp +++ b/src/ddpclient.cpp @@ -1,354 +1,358 @@ /* * * 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 "ruqola.h" #include #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; } +QString DDPClient::cachePath() const +{ + return QStandardPaths::writableLocation(QStandardPaths::CacheLocation); +} - -unsigned int DDPClient::method(const QString& m, const QJsonDocument& params) +unsigned int DDPClient::method(const QString& m, const QJsonDocument& params, DDPClient::MessageStatus messageStatus) { - return method(m, params, empty_callback); + return method(m, params, empty_callback, messageStatus); } -unsigned int DDPClient::method(const QString& method, const QJsonDocument& params, std::function callback) +unsigned int DDPClient::method(const QString& method, const QJsonDocument& params, std::function callback, DDPClient::MessageStatus messageStatus) { 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; } 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(); - //enqueue unsent messages - Ruqola::self()->messageQueue()->messageQueue().enqueue(qMakePair(m_uid-1, params)); - Ruqola::self()->messageQueue()->messageStatus().insert(m_uid-1,false); - //and retry - Ruqola::self()->messageQueue()->retry(); + if(messageStatus==DDPClient::Persistent){ + QJsonObject jsonObject = params.object(); + QString message = jsonObject["msg"].toString(); //Find out how to generalize this + m_messageQueue.enqueue(qMakePair(method,message)); + + Ruqola::self()->messageQueue()->processQueue(); + } + } else { qDebug() << "Successfully sent " << json; - QHash::iterator it = Ruqola::self()->messageQueue()->messageStatus().find(m_uid-1); - if (it.value() == false){ - it.value() = true; - } } //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) +void DDPClient::subscribe(const QString& collection, const QJsonArray& params, DDPClient::MessageStatus messageStatus) { 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)"; - //enqueue unsent messages - Ruqola::self()->messageQueue()->messageQueue().enqueue(qMakePair(m_uid-1, params)); - Ruqola::self()->messageQueue()->messageStatus().insert(m_uid-1,false); - //and retry - Ruqola::self()->messageQueue()->retry(); + if(messageStatus==DDPClient::Persistent){ +// QJsonObject jsonObject = params; +// QString message = jsonObject["msg"].toString(); +// m_messageQueue.enqueue(qMakePair(message,method)); + + Ruqola::self()->messageQueue()->processQueue(); + } + } m_uid++; } void DDPClient::onTextMessageReceived(QString message) { QJsonDocument response = QJsonDocument::fromJson(message.toUtf8()); if (!response.isNull() && response.isObject()) { QJsonObject root = response.object(); QString messageType = root.value("msg").toString(); // qDebug() << "Root is- " << root; if (messageType == "updated") { } else if (messageType == "result") { unsigned id = root.value("id").toString().toInt(); if (m_callbackHash.contains(id)) { std::function callback = m_callbackHash.take(id); QJsonDocument res = QJsonDocument(root.value("result").toObject()); QJsonObject result = res.object(); QString type = result.value("type").toString(); QString msg = result.value("msg").toString(); QByteArray base64Image; QImage image; - QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation)+"/Images"; + QString path = DDPClient::cachePath()+"/Images"; QDir dir(path); if (!dir.exists()){ dir.mkdir(path); qDebug() << "Directory created at " << path; } QDir::setCurrent(path); const QDateTime currentTime = QDateTime::currentDateTime(); const QString timestamp = currentTime.toString(QLatin1String("yyyyMMdd-hhmmsszzz")); const QString filename = QString::fromLatin1("%1.jpg").arg(timestamp); if (type == "image"){ base64Image.append(msg); image.loadFromData(QByteArray::fromBase64(base64Image), "JPG"); if ( !image.isNull() ){ qDebug() << "Saving Image to " << path; if (image.save(filename, "JPEG") ){ qDebug() << "Image saved successfully"; } else { qDebug() << "Image NOT saved"; } } else{ qDebug() << "Image is NULL"; } } else if (type == "text"){ } 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); } } } 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); } diff --git a/src/ddpclient.h b/src/ddpclient.h index 33e2df1d..23f05743 100644 --- a/src/ddpclient.h +++ b/src/ddpclient.h @@ -1,128 +1,136 @@ /* * * 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 DDPCLIENT_H #define DDPCLIENT_H // #include // #include // #include #include #include #include class QJsonObject; class QJsonDocument; class QUrl; class QWebSocket; class DDPClient : public QObject { Q_OBJECT public: enum LoginStatus { NotConnected, LoggingIn, LoggedIn, LoginFailed, LoggedOut }; Q_ENUM(LoginStatus) + enum MessageStatus { + Persistent, + Ephemeral + }; + DDPClient(const QString &url = QString(), QObject *parent = 0); ~DDPClient(); /** * @brief Call a method with name @param method and parameters @param params * * @param method The name of the method * @param params The parameters * @return unsigned int, the ID of the called method. Watch for it */ - unsigned method(const QString &method, const QJsonDocument ¶ms); - unsigned method(const QString &method, const QJsonDocument ¶ms, std::function callback); - - void subscribe(const QString &collection, const QJsonArray ¶ms); + unsigned method(const QString &method, const QJsonDocument ¶ms, DDPClient::MessageStatus messageStatus = DDPClient::Ephemeral); + unsigned method(const QString &method, const QJsonDocument ¶ms, std::function callback, DDPClient::MessageStatus messageStatus = DDPClient::Ephemeral); + void subscribe(const QString &collection, const QJsonArray ¶ms, MessageStatus messageStatus = DDPClient::Ephemeral); Q_INVOKABLE void login(); void logOut(); bool isConnected() const; bool isLoggedIn() const; void onServerURLChange(); - //Again try to send unsent message; returns true if message was sent successfully - bool unsentMessages(); + QQueue> messageQueue(); + QString cachePath() const; signals: void connectedChanged(); void loginStatusChanged(); void disconnected(); + /** * @brief Emitted whenever a result is received. The parameter is the expected ID. * * @param id the ID received in the method() call */ void result(unsigned id, QJsonDocument result); void added(QJsonObject item); void changed(QJsonObject item); private slots: void onWSConnected(); void onTextMessageReceived(QString message); void WSclosed(); private: LoginStatus loginStatus() const; void setLoginStatus(LoginStatus l); void resume_login_callback(QJsonDocument doc); QString m_url; QWebSocket m_webSocket; unsigned m_uid; QHash > m_callbackHash; unsigned m_loginJob; LoginStatus m_loginStatus; bool m_connected; bool m_attemptedPasswordLogin; bool m_attemptedTokenLogin; + //Abstract queue for all requests + //QPair- QString method, QString message + QQueue> m_messageQueue; + friend class Ruqola; - friend class MessageQueue; }; // #include "ddpclient.moc" #endif // DDPCLIENT_H diff --git a/src/messagequeue.cpp b/src/messagequeue.cpp index 678813b9..a0ada21e 100644 --- a/src/messagequeue.cpp +++ b/src/messagequeue.cpp @@ -1,110 +1,141 @@ /* * * 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 "ruqola.h" +#include "ddpclient.h" +#include "messagemodel.h" +#include +#include -QQueue> MessageQueue::messageQueue() { - return m_messageQueue; -} - -QHash MessageQueue::messageStatus() { - return m_messageStatus; -} MessageQueue::MessageQueue() { connect(Ruqola::self()->ddp(), &DDPClient::loginStatusChanged, this, &MessageQueue::onLoginStatusChanged); - qDebug() << "Creating message Model"; - QDir cacheDir(Ruqola::self()->cacheBasePath()); + QDir cacheDir(Ruqola::self()->ddp()->cachePath()); // load unsent messages cache - if (QFile::exists(cacheDir.absoluteFilePath("UnsentMessagesCache"))) { - QFile f(cacheDir.absoluteFilePath("UnsentMessagesCache")); + if (QFile::exists(cacheDir.absoluteFilePath("QueueCache"))) { + QFile f(cacheDir.absoluteFilePath("QueueCache")); if (f.open(QIODevice::ReadOnly)) { QDataStream in(&f); while (!f.atEnd()) { char * byteArray; quint32 length; in.readBytes(byteArray, length); QByteArray ba = QByteArray::fromRawData(byteArray, length); - QByteArray mid; - mid.append(ba.at(ba.size()-1)).append(ba.at(ba.size())); - int m_uid = mid.toInt(NULL,10); - QJsonDocument params = QJsonDocument::fromBinaryData(ba); - m_messageQueue.enqueue(qMakePair(m_uid,params)); + //Find out what to store and retrieve + QByteArray method; + QString message = QString(ba); + Ruqola::self()->ddp()->messageQueue().enqueue(qMakePair(method,message)); } } } } +void cacheQueue() +{ + QDir cacheDir(Ruqola::self()->ddp()->cachePath()); + qDebug() << "Caching Unsent messages to..." << cacheDir.path(); + if (!cacheDir.exists(cacheDir.path())) { + cacheDir.mkpath(cacheDir.path()); + } + QFile f(cacheDir.absoluteFilePath("QueueCache")); + if (f.open(QIODevice::WriteOnly)) { + QDataStream out(&f); + QQueue>::iterator it; + //Find out what to store and retrieve + QQueue> queue = Ruqola::self()->ddp()->messageQueue(); + for ( it = queue.begin(); it != queue.end(); it++ ) { + QPair pair = *it; + QByteArray ba; + ba.append(pair.first.toLatin1()); + ba.append(pair.second.toLatin1()); + out.writeBytes(ba, ba.size()); + } + } +} + + +MessageQueue::~MessageQueue() +{ + cacheQueue(); +} + + +bool internetConnection() +{ + QNetworkAccessManager nam; + QNetworkRequest req(QUrl("http://www.google.com")); + QNetworkReply *reply = nam.get(req); + QEventLoop loop; +// connect(&reply, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + if(reply->bytesAvailable()){ + return true; + } else { + return false; + } +} + void MessageQueue::onLoginStatusChanged() { - if (Ruqola::self()->ddp()->loginStatus() == DDPClient::LoggedIn && !m_messageQueue.empty()){ - //retry sending messages - retry(); - } else if (Ruqola::self()->ddp()->loginStatus() != DDPClient::LoggedIn && !m_messageQueue.empty()) { - //save messages in messageQueue in local cache and retry after client is loggedIn + if (Ruqola::self()->loginStatus() == DDPClient::LoggedIn + && !Ruqola::self()->ddp()->messageQueue().empty()){ - QDir cacheDir(Ruqola::self()->cacheBasePath()); - qDebug() << "Caching Unsent messages to..." << cacheDir.path(); - if (!cacheDir.exists(cacheDir.path())) { - cacheDir.mkpath(cacheDir.path()); - } - QFile f(cacheDir.absoluteFilePath("UnsentMessagesCache")); - if (f.open(QIODevice::WriteOnly)) { - QDataStream out(&f); - QQueue>::iterator it; - for ( it = m_messageQueue.begin(); it != m_messageQueue.end(); it++ ) { - QPair pair = *it; - QByteArray ba; - ba = pair.second.toBinaryData(); - if ( pair.first < 10 ) { //so that we can take last 2 digits as m_uid - ba.append(QString("0"+pair.first).toInt(NULL,10)); - } else { - ba.append(QString(pair.first).toInt(NULL,10)); - } - out.writeBytes(ba, ba.size()); - } - } + processQueue(); //retry sending messages + + } else if (Ruqola::self()->loginStatus() == DDPClient::LoggedIn + && !internetConnection() && !Ruqola::self()->ddp()->messageQueue().empty()) { + + QMessageBox msgBox; + msgBox.setText("You are not connected to the internet"); + msgBox.exec(); + + cacheQueue(); + + } else if (Ruqola::self()->loginStatus() != DDPClient::LoggedIn + && !Ruqola::self()->ddp()->messageQueue().empty()) { + + //save messages in messageQueue in local cache and retry after client is loggedIn + cacheQueue(); } } -void MessageQueue::retry() +void MessageQueue::processQueue() { - if ( Ruqola::self()->loginStatus() == DDPClient::LoggedIn && !m_messageQueue.empty() ){ - while ( Ruqola::self()->loginStatus() == DDPClient::LoggedIn && !m_messageQueue.empty() ){ - QPair pair = m_messageQueue.head(); - int id = pair.first; - QJsonDocument params = pair.second; - Ruqola::self()->ddp()->method("sendMessage", params); - - //if it is sent successfully, dequeue it - //else it'll stay at head in queue for sending again - QHash::iterator it = m_messageStatus.find(id); - if ( it!= m_messageStatus.end() && it.value() == true ){ - m_messageQueue.dequeue(); + while ( Ruqola::self()->loginStatus() == DDPClient::LoggedIn + && !Ruqola::self()->ddp()->messageQueue().empty() ){ + + //If in between client goes offline, break + if(!internetConnection()){ + break; } - } + + //Find out what to store and retrieve + QPair pair = Ruqola::self()->ddp()->messageQueue().head(); + QString method = pair.first; + QString message = pair.second; + QJsonObject params; +// Ruqola::self()->ddp()->method(method, params); } } diff --git a/src/messagequeue.h b/src/messagequeue.h index 79b01755..d0fb07c8 100644 --- a/src/messagequeue.h +++ b/src/messagequeue.h @@ -1,54 +1,48 @@ /* * * 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 MESSAGEQUEUE_H #define MESSAGEQUEUE_H #include +#include + class MessageQueue : public QObject { Q_OBJECT public: - // method which tries to resend unsuccessful messages again - void retry(); - MessageQueue(); + ~MessageQueue(); - QQueue> messageQueue(); - QHash messageStatus(); + bool internetConnection(); + void cacheQueue(); + void processQueue(); public slots: void onLoginStatusChanged(); -private: - //pair- int (m_uid), QJsonDocument (params) - QQueue> m_messageQueue; - - //message with m_uid sent succussfully or not - QHash m_messageStatus; - }; #endif // MESSAGEQUEUE_H diff --git a/src/rocketchatbackend.cpp b/src/rocketchatbackend.cpp index d39ba43e..575865d2 100644 --- a/src/rocketchatbackend.cpp +++ b/src/rocketchatbackend.cpp @@ -1,278 +1,278 @@ /* * * 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); + Ruqola::self()->ddp()->method("loadHistory", QJsonDocument(params), process_backlog, DDPClient::Persistent); } } } 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); + Ruqola::self()->ddp()->method("loadHistory", QJsonDocument(params), process_backlog, DDPClient::Persistent); } } } 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.messageID = o.value("_id").toString(); m.roomID = roomId; m.message = o.value("msg").toString(); m.timestamp = (qint64)o.value("ts").toObject().value("$date").toDouble(); m.username = o.value("u").toObject().value("username").toString(); m.userID = o.value("u").toObject().value("_id").toString(); m.updatedAt = o.value("_updatedAt").toObject().value("$date").toDouble(); m.editedAt = o.value("editedAt").toObject().value("$date").toDouble(); m.editedByUsername = o.value("editedBy").toObject().value("username").toString(); m.editedByUserID = o.value("editedBy").toObject().value("userID").toString(); m.url = o.value("urls").toObject().value("url").toString(); m.meta = o.value("urls").toObject().value("meta").toString(); m.headers = o.value("urls").toObject().value("headers").toString(); m.parsedUrl = o.value("urls").toObject().value("parsedUrl").toString(); m.imageUrl = o.value("attachments").toObject().value("imageUrl").toString(); m.color = o.value("attachments").toObject().value("color").toString(); m.alias = o.value("alias").toString(); m.avatar = o.value("avatar").toString(); m.groupable = o.value("groupable").toBool(); m.parseUrls = o.value("parseUrls").toBool(); 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(); 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); + Ruqola::self()->ddp()->subscribe("stream-notify-user", params, DDPClient::Persistent); } diff --git a/src/ruqola.cpp b/src/ruqola.cpp index 1200b78b..876116e3 100644 --- a/src/ruqola.cpp +++ b/src/ruqola.cpp @@ -1,284 +1,284 @@ /* * * 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 "messagequeue.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; } MessageQueue * Ruqola::messageQueue() { if (!m_messageQueue) { m_messageQueue = new MessageQueue(); // retry to send any unsent messages - Ruqola::self()->messageQueue()->retry(); -// connect(m_messageQueue, &DDPClient::loginStatusChanged, this, &MessageQueue::loginStatusChanged); + Ruqola::self()->messageQueue()->processQueue(); } return m_messageQueue; } Notification * Ruqola::notification() { if (m_notification == NULL) { m_notification = new Notification(); m_notification->show(); } return m_notification; } void Ruqola::attachmentButtonClicked() { QString fileName = QFileDialog::getOpenFileName(Q_NULLPTR, "Select one or more files to open", QDir::homePath(), "Images (*.png *.jpeg *.jpg)"); qDebug() << "Selected Image " << fileName; QFile file(fileName); if (!file.open(QFile::ReadOnly)) { qDebug() << "Cannot open the selected file"; return; } const QString message = QString::fromLatin1(file.readAll().toBase64()); const QString roomID("3cGRyFLWgnPL7B79n"); //hard code roomID for now const QString type("image"); sendMessage(roomID, message, type); } void Ruqola::sendMessage(const QString &roomID, const QString &message, const QString &type) { QJsonObject json; json["rid"] = roomID; json["msg"] = message; json["type"] = type; - ddp()->method("sendMessage", QJsonDocument(json)); + ddp()->method("sendMessage", QJsonDocument(json), DDPClient::Persistent); } 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_messageQueue(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(); //Initialize the messageQueue object m_self->messageQueue(); } return m_self; }