diff --git a/Desktop.qml b/Desktop.qml index c2d577d2..5d6edd13 100644 --- a/Desktop.qml +++ b/Desktop.qml @@ -1,210 +1,217 @@ // Skeleton from https://github.com/achipa/outqross_blog.git // Almost everything has been re-adapted import QtQuick 2.7 import QtQuick.Controls 1.3 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 KDE.Ruqola.UserData 1.0 +import KDE.Ruqola.DDPClient 1.0 // import "Log.js" as Log // import "Data.js" as Data ApplicationWindow { property int margin: 11 property string statusText property list todos property JSONListModel lists: JSONListModel { } property JSONListModel activeRoom: JSONListModel {} property JSONListModel userRooms: JSONListModel {} // // property var rooms: new Map() property string selectedRoomID; property bool ready; // Settings { // id: settings // property alias authToken: UserData.authToken; // } // // Component.onCompleted: // // Component.onCompleted // UserData.onConnected: { // UserData.tryLogin() // }- id: appid title: qsTr("Ruqola") // title: UserData.userName // title: "test" width: 640 height: 480 visible: true menuBar: MenuBar { Menu { title: qsTr("&Main") MenuItem { text: qsTr("&Login") onTriggered: { // loginTab.visible = true-; // mainWidget.visible = false; // messageDialog.show(qsTr("Reconnect action triggered")); } } MenuItem { text: qsTr("E&xit") onTriggered: Qt.quit(); } } } // Component.onCompleted : {UserData.tryLogin()}//.log(UserData.loggedIn);} Login { id: loginTab - visible: !UserData.loggedIn + visible: (UserData.loginStatus == DDPClient.LoginFailed) anchors.fill:parent z: 10 serverURL: UserData.serverURL username: UserData.userName onAccepted: { UserData.password = loginTab.password; UserData.userName = loginTab.username; UserData.serverURL = loginTab.serverURL; UserData.tryLogin(); } } // statusBar: StatusView { // RowLayout { // Label { text: statusText } // } // } BusyIndicator { id: busy anchors.centerIn: parent - visible: false + visible: UserData.loginStatus == DDPClient.LoggingIn } Item { id: mainWidget anchors.fill: parent - visible: UserData.loggedIn - + visible: UserData.loginStatus != DDPClient.LoginFailed +// visible:true +// Component.onCompleted :{ +// console.log("debug"); +// console.log(UserData.loginStatus); +// console.log( DDPClient.LoginFailed); +// console.log(UserData.loginStatus != DDPClient.LoginFailed); +// } ListView { id: roomsList model: UserData.roomModel() width: 100 visible: true anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom anchors.margins: 10; delegate: Text { property variant internal_id: id text: name font.bold: (selectedRoomID == id) id: room_chooser MouseArea { anchors.fill: parent onClicked: { console.log("Choosing room", room_chooser.internal_id); selectedRoomID = room_chooser.internal_id; // myModel.currentRoom = selectedRoomID; activeChat.model = UserData.getModelForRoom(selectedRoomID); console.log(activeChat.count); } } } } ScrollView { anchors.right: parent.right anchors.left: roomsList.right anchors.top: parent.top anchors.bottom: messageLine.top verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn ListView { id: activeChat // property string activeRoom: selectedRoomID // visibleArea.yPosition: 1.0-heightRatio 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 } } } TextField { id: messageLine anchors.right: parent.right anchors.left: roomsList.right anchors.bottom: parent.bottom placeholderText: qsTr("Enter message") onAccepted: { if (text != "") { UserData.sendMessage(selectedRoomID, text); text = ""; } } } } Timer { id: timer interval: 3000 onTriggered: statusText = ""; repeat: true } onStatusTextChanged: timer.restart(); } diff --git a/main.cpp b/main.cpp index cc68ba8a..3ce58cda 100644 --- a/main.cpp +++ b/main.cpp @@ -1,37 +1,38 @@ #include #include // #include // #include #include "src/roommodel.h" #include "src/rocketchatbackend.h" #include "src/userdata.h" #include #include int main(int argc, char *argv[]) { QApplication app(argc, argv); QCoreApplication::setOrganizationName("KDE"); QCoreApplication::setOrganizationDomain("kde.org"); QCoreApplication::setApplicationName("Ruqola"); // DDPClient client(); qmlRegisterSingletonType("KDE.Ruqola.UserData", 1, 0, "UserData", userdata_singletontype_provider); qmlRegisterType("KDE.Ruqola.Models", 1, 0, "MessageModel"); + qmlRegisterType("KDE.Ruqola.DDPClient", 1, 0, "DDPClient"); qmlRegisterType("KDE.Ruqola.Models", 1, 0, "RoomModel"); RocketChatBackend c; QQmlApplicationEngine engine; // QQmlContext *ctxt = engine.rootContext(); // // ctxt->setContextProperty("myModel", UserData::instance()->messageList()); // ctxt->setContextProperty("roomModel", UserData::instance()->roomModel()); engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); } diff --git a/src/ddpclient.cpp b/src/ddpclient.cpp index 1c78e4ca..fae77154 100644 --- a/src/ddpclient.cpp +++ b/src/ddpclient.cpp @@ -1,241 +1,253 @@ /* * * 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 #include "userdata.h" void process_test(QJsonDocument doc) { qDebug() << "Callback test:" << doc; qDebug() << "End callback"; } void login_callback(QJsonDocument doc) { qDebug() << "LOGIN:" << doc; UserData::instance()->setAuthToken(doc.object().value("token").toString()); qDebug() << "End callback"; } void DDPClient::resume_login_callback(QJsonDocument doc) { qDebug() << "LOGIN:" << doc; UserData::instance()->setAuthToken(doc.object().value("token").toString()); qDebug() << "End callback"; } void empty_callback(QJsonDocument doc) { Q_UNUSED(doc); } DDPClient::DDPClient(const QUrl& url, QObject* parent) : QObject(parent), m_url(url), m_uid(1), m_loginJob(0), + m_loginStatus(NotConnected), m_connected(false), - m_loggedIn(false), m_doingTokenLogin(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); m_webSocket.open(QUrl(url)); // qDebug() << "Trying to connect to URL" << m_url; } DDPClient::~DDPClient() { m_webSocket.close(); } +DDPClient::LoginStatus DDPClient::loginStatus() const +{ + return m_loginStatus; +} + bool DDPClient::isConnected() const { return m_connected; } bool DDPClient::isLoggedIn() const { - return m_loggedIn; + 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) { QString json; if (params.isArray()){ json = "{\"msg\":\"method\", \"method\": \"%1\", \"id\": \"%2\", \"params\": %3}"; } else { json = "{\"msg\":\"method\", \"method\": \"%1\", \"id\": \"%2\", \"params\": [%3]}"; } json = json.arg(method).arg(m_uid).arg(QString(params.toJson(QJsonDocument::Compact))); qint64 bytes = m_webSocket.sendBinaryMessage(json.toUtf8()); // FIXME : text? maybe binary will be better - check if it keeps working if (bytes < json.length()) { qDebug() << "ERROR! I couldn't send all of my message. This is a bug! (try again)"; } 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 QJsonDocument& params) { QString json("{\"msg\":\"sub\",\"id\": \"%1\",\"name\":\"%2\", \"params\": %3}"); json = json.arg(m_uid).arg(collection).arg(QString(params.toJson(QJsonDocument::Compact))); qint64 bytes = m_webSocket.sendBinaryMessage(json.toUtf8()); // FIXME : text? maybe binary will be better - check if it keeps working if (bytes < json.length()) { qDebug() << "ERROR! I couldn't send all of my message. This is a bug! (try again)"; } else { qDebug() << "Successfully sent " << json; } 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(); 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); 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"; - m_loggedIn = false; // Kill wrong credentials, so we don't try to use them again if (!UserData::instance()->authToken().isEmpty()) { UserData::instance()->setAuthToken(QString()); } else if (!UserData::instance()->password().isEmpty()) { UserData::instance()->setPassword(QString()); } + setLoginStatus(DDPClient::LoginFailed); } else { UserData::instance()->setAuthToken(root.value("result").toObject().value("token").toString()); - m_loggedIn = true; + + setLoginStatus(DDPClient::LoggedIn); } - emit loggedInChanged(); +// emit loggedInChanged(); } } else if (messageType == "connected") { m_connected = true; // emit connected(); emit connectedChanged(); if (!UserData::instance()->authToken().isEmpty()) { + setLoginStatus(DDPClient::LoggingIn); login();// Try to resume auth token login } } else if (messageType == "error") { qDebug() << "ERROR!!" << message; } else if (messageType == "ping") { qDebug() << "Ping - Pong"; m_webSocket.sendBinaryMessage("{\"msg\":\"pong\"}"); } else if (messageType == "added"){ emit added(root); } else if (messageType == "changed") { emit changed(root); } else { qDebug() << "received something unhandled:" << message; } } } +void DDPClient::setLoginStatus(DDPClient::LoginStatus l) +{ + m_loginStatus = l; + emit loginStatusChanged(); +} + void DDPClient::login() { if (!UserData::instance()->authToken().isEmpty()) { m_doingTokenLogin = true; QString json = "{\"resume\":\"%1\"}"; json = json.arg(UserData::instance()->authToken()); m_loginJob = method("login", QJsonDocument::fromJson(json.toUtf8())); } else if (!UserData::instance()->password().isEmpty()) { QString json = "{\"password\":\"%1\", \"user\": {\"username\":\"%2\"}}"; json = json.arg(UserData::instance()->password()).arg(UserData::instance()->userName()); m_loginJob = method("login", QJsonDocument::fromJson(json.toUtf8())); } else { - m_loggedIn = false; // FIXME this is an enum - emit loggedInChanged(); + setLoginStatus(LoginFailed); } } void DDPClient::onWSConnected() { qDebug() << "Websocket connected at URL" << m_url; QString json("{\"msg\":\"connect\", \"version\": \"1\", \"support\": [\"1\"]}"); qint64 bytes = m_webSocket.sendBinaryMessage(json.toUtf8()); if (bytes < json.length()) { qDebug() << "ERROR! I couldn't send all of my message. This is a bug! (try again)"; } else { qDebug() << "Successfully sent " << json; } } void DDPClient::WSclosed() { m_connected = false; } diff --git a/src/ddpclient.h b/src/ddpclient.h index 2abecaf2..84644423 100644 --- a/src/ddpclient.h +++ b/src/ddpclient.h @@ -1,107 +1,123 @@ /* * * 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: - DDPClient(const QUrl &url, QObject *parent = 0); + enum LoginStatus { + NotConnected, + LoggingIn, + LoggedIn, + LoginFailed + }; + Q_ENUM(LoginStatus) + + + DDPClient(const QUrl &url = QUrl(), 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); // unsigned method(const QString &method, const QJsonObject ¶ms); void subscribe(const QString &collection, const QJsonDocument ¶ms); Q_INVOKABLE void login(); // Q_INVOKABLE void loginWithPassword(); bool isConnected() const; bool isLoggedIn() const; + LoginStatus loginStatus() const; + signals: // void connected(); void connectedChanged(); - void loggedInChanged(); + + void loginStatusChanged(); +// void loggedInChanged(); 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: + void setLoginStatus(LoginStatus l); + void resume_login_callback(QJsonDocument doc); QUrl m_url; QWebSocket m_webSocket; unsigned m_uid; QHash > m_callbackHash; unsigned m_loginJob; - bool m_loggedIn; + LoginStatus m_loginStatus; + bool m_connected; bool m_doingTokenLogin; }; // #include "ddpclient.moc" #endif // DDPCLIENT_H diff --git a/src/messagemodel.cpp b/src/messagemodel.cpp index 3be66823..7ffe5ea5 100644 --- a/src/messagemodel.cpp +++ b/src/messagemodel.cpp @@ -1,129 +1,176 @@ +#include #include #include #include #include "messagemodel.h" -/* -Message::Message(const QString& username, const QString& message, qulonglong timestamp) - : m_username(username), m_message(message), m_timestamp(timestamp) + +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(); + + return message; } -QString Message::message() const +QByteArray MessageModel::serialize(const Message& message) { - return m_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; + d.setObject(o); + return d.toBinaryData(); } -QString Message::username() const +MessageModel::MessageModel(const QString &roomID, QObject* parent) + : QAbstractListModel(parent), + m_roomID(roomID) { - return m_username; + QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + + // 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()); + m_allMessages[m.timestamp] = m; +// qDebug() << m.message; + } + } + } + + } -qulonglong Message::timestamp() const +MessageModel::~MessageModel() { - return m_timestamp; -}*/ + QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)+'/rooms_cache'); + if (!cacheDir.exists(cacheDir.path())) { + cacheDir.mkpath(cacheDir.path()); + } + + QFile f(cacheDir.absoluteFilePath(m_roomID)); -MessageModel::MessageModel(QObject* parent) - : QAbstractListModel(parent), - m_currentRoom("no_room") -{ + 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; } -/* -void MessageModel::addMessage(qulonglong timestamp, const QString& user, const QString& message) + +qint64 MessageModel::lastTimestamp() const { -// qDebug() << roomID << user << message; - Message m; - m.username = user; - m.message = message; - m.timestamp = timestamp; - addMessage(m); -}*/ + 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() << "PROTECTED ADD MESSAGE" << roomID << m_currentRoom; int size = m_allMessages.size(); // qDebug() << "calling begin insert rows:" << size << size+1; bool messageChanged = false; if (m_allMessages.contains(message.timestamp)) { messageChanged = true; //Figure out a better way to update just the really changed message } else { beginInsertRows(index(size), size, (size)); } m_allMessages[message.timestamp] = message; if (messageChanged) { qDebug() << "Data changed"; //Figure out a better way to update just the really changed message, not EVERYTHING emit dataChanged(createIndex(1, 1), createIndex(rowCount(), 1), QVector(MessageModel::MessageText)); } else { endInsertRows(); } } QVariant MessageModel::data(const QModelIndex& index, int role) const { // return QVariant("Hey baby"); // qDebug() << "C++ got asked item at index" << index // << "room contains" << m_allMessages[m_currentRoom].size() << "messages"; // foreach (Message m, m_allMessages[m_currentRoom].values()) { // qDebug() << m.username(); // } int idx = index.row();//-1; if (role == MessageModel::Username) { // qDebug() << "C++ returning username" << // m_allMessages[m_currentRoom].values().at(idx).username(); return m_allMessages.values().at(idx).username; } else if (role == MessageModel::MessageText) { return m_allMessages.values().at(idx).message; } else if (role == MessageModel::Timestamp) { return QVariant(m_allMessages.values().at(idx).timestamp); } else if (role == MessageModel::UserID) { return QVariant(m_allMessages.values().at(idx).userID); } else if (role == MessageModel::SystemMessage) { // qDebug() << "System message?" << m_allMessages.values().at(idx).systemMessage; return QVariant(m_allMessages.values().at(idx).systemMessage); } else if (role == MessageModel::SystemMessageType) { return QVariant(m_allMessages.values().at(idx).systemMessageType); } else { return QVariant(""); } } // #include "messagelist.moc" diff --git a/src/messagemodel.h b/src/messagemodel.h index a2a0d7b7..96146534 100644 --- a/src/messagemodel.h +++ b/src/messagemodel.h @@ -1,69 +1,58 @@ #ifndef MESSAGEMODEL_H #define MESSAGEMODEL_H #include #include #include - -// class Message -// { -// public: -// Message(const QString &username = QString(), -// const QString &message = QString(), -// qulonglong timestamp = 0); -// -// QString username() const; -// QString message() const; -// qulonglong timestamp() const; -// -// private: -// QString m_username; -// QString m_message; -// qulonglong m_timestamp; -// }; +#include +#include +#include struct Message { QString username; QString userID; QString message; qint64 timestamp; bool systemMessage = false; QString roomID; QString systemMessageType; }; class MessageModel : public QAbstractListModel { Q_OBJECT public: enum MessageRoles { Username = Qt::UserRole + 1, MessageText, Timestamp, UserID, SystemMessage, SystemMessageType }; - MessageModel(QObject *parent = 0); + MessageModel(const QString &roomID = "no_room", QObject *parent = 0); + virtual ~MessageModel(); void addMessage(const Message& message); virtual int rowCount(const QModelIndex & parent = QModelIndex()) const; virtual QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; -// static Message fromJSon(const QJSonDocument &source); -// static QByteArray serialize(const Message &message); + qint64 lastTimestamp() const; + + static Message fromJSon(const QJsonObject &source); + static QByteArray serialize(const Message &message); protected: virtual QHash roleNames() const; private: - QString m_currentRoom; - - // > + const QString m_roomID; QMap m_allMessages; -// QMap m_roomMessages; + QString m_writableLocation; + + QFile *cacheWriter; }; #endif diff --git a/src/rocketchatbackend.cpp b/src/rocketchatbackend.cpp index 869a8743..4754392f 100644 --- a/src/rocketchatbackend.cpp +++ b/src/rocketchatbackend.cpp @@ -1,153 +1,156 @@ /* * * 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 "userdata.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 = UserData::instance()->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(); -// qDebug() << "Room " << room.toObject().value("_id").toString(); if (room.value("t").toString() == "c") { + qDebug() << "Adding" << room.value("_id").toString()<< room.value("name").toString(); + MessageModel *roomModel = UserData::instance()->getModelForRoom(room.value("_id").toString()); + model->addRoom(room.value("_id").toString(), room.value("name").toString()); QString params = QString("[\"%1\"]").arg(room.value("_id").toString()); UserData::instance()->ddp()->subscribe("stream-room-messages", QJsonDocument::fromJson(params.toLatin1())); // Load history QByteArray json = "[\""+room.value("_id").toString().toLatin1() + - "\", null, 50,{\"$date\": "+ - QString::number(QDateTime::currentMSecsSinceEpoch()).toLatin1()+ + "\", null, 50, {\"$date\": "+ + QString::number(roomModel->lastTimestamp()).toLatin1()+ "}]"; qDebug() << json; UserData::instance()->ddp()->method("loadHistory", QJsonDocument::fromJson(json), process_backlog); } } // qDebug() << "DEBUG:" << doc; } 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.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; } UserData::instance()->getModelForRoom(roomId)->addMessage(m); } } RocketChatBackend::RocketChatBackend(QObject* parent) : QObject(parent) { // UserData::instance()->ddp() = new DDPClient(, this); connect(UserData::instance()->ddp(), &DDPClient::changed, this, &RocketChatBackend::onChanged); connect(UserData::instance()->ddp(), &DDPClient::added, this, &RocketChatBackend::onAdded); - connect(UserData::instance()->ddp(), &DDPClient::loggedInChanged, this, &RocketChatBackend::onLoggedIn); + connect(UserData::instance()->ddp(), &DDPClient::loginStatusChanged, this, &RocketChatBackend::onLoggedIn); } RocketChatBackend::~RocketChatBackend() { // delete m_rooms; // delete UserData::instance()->ddp(); } void RocketChatBackend::onLoggedIn() { - if (!UserData::instance()->ddp()->isLoggedIn()) { + if (UserData::instance()->ddp()->loginStatus() != DDPClient::LoggedIn) { + qDebug() << "not yet logged in:" << UserData::instance()->ddp()->loginStatus(); return; } qDebug() << "GETTING LIST OF ROOMS"; // get list of rooms UserData::instance()->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") { qDebug() << "NEW USER"; } else if (collection == "rooms") { } } void RocketChatBackend::onChanged(QJsonObject object) { QString collection = object.value("collection").toString(); qDebug() << "ROCKET BACK" << 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(); RocketChatBackend::processIncomingMessages(contents); } else if (collection == "users") { qDebug() << "NEW USER"; } else if (collection == "rooms") { } } diff --git a/src/roommodel.cpp b/src/roommodel.cpp index 3eaf6639..087e7150 100644 --- a/src/roommodel.cpp +++ b/src/roommodel.cpp @@ -1,79 +1,133 @@ /* * * 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 "roommodel.h" #include +Room RoomModel::fromJSon(const QJsonObject& o) +{ + Room r; + + r.name = o["name"].toString(); + r.id = o["id"].toString(); + return r; +} + +QByteArray RoomModel::serialize(const Room& r) +{ + QJsonDocument d; + QJsonObject o; + o["name"] = r.name; + o["id"] = r.id; + d.setObject(o); + return d.toBinaryData(); +} + RoomModel::RoomModel(QObject* parent) : QAbstractListModel(parent) { + QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + + // load cache + if (cacheDir.exists(cacheDir.path())) { + QFile f(cacheDir.absoluteFilePath("rooms")); + 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); + Room m = RoomModel::fromJSon(QJsonDocument::fromBinaryData(arr).object()); + m_roomsList[m.name] = m; +// qDebug() << m.message; + } + } + } +} + +RoomModel::~RoomModel() +{ + QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + if (!cacheDir.exists(cacheDir.path())) { + cacheDir.mkpath(cacheDir.path()); + } + + QFile f(cacheDir.absoluteFilePath("rooms")); + + if (f.open(QIODevice::WriteOnly)) { + QDataStream out(&f); + foreach (const Room m, m_roomsList) { + QByteArray ms = RoomModel::serialize(m); + out.writeBytes(ms, ms.size()); + } + } } QHash RoomModel::roleNames() const { QHash roles; roles[RoomName] = "name"; roles[RoomID] = "id"; roles[RoomSelected] = "selected"; return roles; } int RoomModel::rowCount(const QModelIndex & parent) const { return m_roomsList.size(); } QVariant RoomModel::data(const QModelIndex & index, int role) const { - Room r = m_roomsList.at(index.row()); + Room r = m_roomsList.values().at(index.row()); if (role == RoomModel::RoomName) { return r.name; } else if (role == RoomModel::RoomID) { return r.id; } else if (role == RoomModel::RoomSelected) { return r.selected; } else { return QVariant("0"); } } void RoomModel::addRoom(const QString& roomID, const QString& roomName, bool selected) { // qDebug() << "Adding room" << roomID << rowCount(); if (roomID.isEmpty()) { return; } int size = m_roomsList.size(); beginInsertRows(index(size), size, (size+1)); Room r; r.id = roomID; r.name = roomName; r.selected = selected; - m_roomsList.append(r); + m_roomsList[roomName] = r; endInsertRows(); } // #include "roommodel.moc" diff --git a/src/roommodel.h b/src/roommodel.h index acf1e583..d2547730 100644 --- a/src/roommodel.h +++ b/src/roommodel.h @@ -1,63 +1,66 @@ /* * * 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 ROOMMODEL_H #define ROOMMODEL_H #include struct Room { -public: QString name; - bool selected; + bool selected = false; QString id; }; class RoomModel : public QAbstractListModel { Q_OBJECT public: enum RoomRoles { RoomName = Qt::UserRole + 1, RoomSelected, RoomID }; RoomModel (QObject *parent = 0); - + virtual ~RoomModel(); + virtual int rowCount(const QModelIndex & parent = QModelIndex()) const; virtual QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; // void setCurrentRoom(const QString &newRoom); // QString getCurrentRoom() const; Q_INVOKABLE void addRoom(const QString& roomID, const QString& roomName, bool selected = false); + static Room fromJSon(const QJsonObject &source); + static QByteArray serialize(const Room &r); + protected: virtual QHash roleNames() const; private: - QList< Room > m_roomsList; + QHash< QString, Room > m_roomsList; }; #endif // ROOMMODEL_H diff --git a/src/userdata.cpp b/src/userdata.cpp index 0f77233d..d8cd6e8d 100644 --- a/src/userdata.cpp +++ b/src/userdata.cpp @@ -1,147 +1,148 @@ /* * * 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 "userdata.h" #include "roommodel.h" #include "ddpclient.h" UserData *UserData::m_self = 0; QString UserData::authToken() const { return m_authToken; } QString UserData::userName() const { return m_userName; } QString UserData::password() const { return m_password; } void UserData::setAuthToken(const QString& token) { qDebug() << "Setting token to" << token; QSettings s; m_authToken = token; s.setValue("authToken", token); } void UserData::setPassword(const QString& password) { m_password = password; } void UserData::setUserName(const QString& username) { m_userName = username; QSettings s; s.setValue("username", username); emit userNameChanged(); } RoomModel * UserData::roomModel() const { return m_roomModel; } DDPClient * UserData::ddp() const { return m_ddp; } void UserData::sendMessage(const QString &roomID, const QString &message) { QString json = "{\"rid\": \"%1\", \"msg\": \"%2\"}"; json = json.arg(roomID, message); ddp()->method("sendMessage", QJsonDocument::fromJson(json.toUtf8())); } MessageModel * UserData::getModelForRoom(const QString& roomID) { if (m_messageModels.contains(roomID)) { return m_messageModels.value(roomID); } else { - qDebug() << "Creating a new model"; - m_messageModels[roomID] = new MessageModel(this); +// qDebug() << "Creating a new model"; + m_messageModels[roomID] = new MessageModel(roomID, this); + return m_messageModels[roomID]; } } QString UserData::serverURL() const { return m_serverURL; } void UserData::setServerURL(const QString& serverURL) { QSettings s; s.setValue("serverURL", serverURL); m_serverURL = serverURL; emit serverURLChanged(); } bool UserData::connected() const { return ddp()->isConnected(); } -bool UserData::loggedIn() const +DDPClient::LoginStatus UserData::loginStatus() const { - return ddp()->isLoggedIn(); + return ddp()->loginStatus(); } void UserData::tryLogin() { qDebug() << "Attempting login" << password() << userName(); ddp()->login(); } UserData::UserData(QObject* parent) : QObject(parent), m_roomModel(new RoomModel) { QSettings s; m_serverURL = s.value("serverURL", "wss://demo.rocket.chat/websocket").toString(); m_ddp = new DDPClient(QUrl(m_serverURL)); m_userName = s.value("username").toString(); m_authToken = s.value("authToken").toString(); - connect(m_ddp, &DDPClient::loggedInChanged, this, &UserData::loggedInChanged); + connect(m_ddp, &DDPClient::loginStatusChanged, this, &UserData::loginStatusChanged); } UserData::~UserData() { - delete m_roomModel; - delete m_ddp; +// delete m_roomModel; +// delete m_ddp; } UserData * UserData::instance() { if (!m_self) { m_self = new UserData; } return m_self; } diff --git a/src/userdata.h b/src/userdata.h index cfc30350..32bb8902 100644 --- a/src/userdata.h +++ b/src/userdata.h @@ -1,104 +1,104 @@ /* * * 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 "ddpclient.h" #include "roommodel.h" #include "messagemodel.h" class QString; class UserData: public QObject { Q_OBJECT Q_PROPERTY (QString userName READ userName WRITE setUserName NOTIFY userNameChanged) 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 (bool loggedIn READ loggedIn NOTIFY loggedInChanged) + Q_PROPERTY (DDPClient::LoginStatus loginStatus READ loginStatus NOTIFY loginStatusChanged) public: static UserData* instance(); void setUserName(const QString &username); QString userName() const; void setPassword(const QString &password); QString password() const; void setAuthToken(const QString &token); QString authToken() const; bool connected() const; - bool loggedIn() const; + DDPClient::LoginStatus loginStatus() const; QString serverURL() const; void setServerURL(const QString &serverURL); DDPClient *ddp() const; Q_INVOKABLE RoomModel *roomModel() const; Q_INVOKABLE void sendMessage(const QString &roomID, const QString &message); Q_INVOKABLE MessageModel* getModelForRoom(const QString &roomID); Q_INVOKABLE void tryLogin(); // void setRoomModel(); signals: void userNameChanged(); void connectedChanged(); void serverURLChanged(); - void loggedInChanged(); + void loginStatusChanged(); private: UserData(QObject *parent = 0); ~UserData(); static UserData *m_self; QString m_password; QString m_userName; QString m_authToken; QString m_serverURL; RoomModel *m_roomModel; DDPClient *m_ddp; QHash< QString, MessageModel * > m_messageModels; }; inline static QObject *userdata_singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) UserData *userData = UserData::instance(); return userData; } #endif // USERDATA_H