diff --git a/Desktop.qml b/Desktop.qml index 93518071..d90a14e3 100644 --- a/Desktop.qml +++ b/Desktop.qml @@ -1,231 +1,222 @@ // 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 string selectedRoomID; property bool ready; -// Settings { -// id: settings -// property alias authToken: UserData.authToken; -// } - - - id: appid title: qsTr("Ruqola") width: 640 height: 480 visible: true - - menuBar: MenuBar { Menu { title: qsTr("&Main") MenuItem { - text: qsTr("&Login") + text: qsTr("&Log out") onTriggered: { - - loginTab.visible = true; - mainWidget.visible = false; + UserData.logOut(); +// loginTab.visible = true; +// mainWidget.visible = false; // messageDialog.show(qsTr("Reconnect action triggered")); } } MenuItem { text: qsTr("E&xit") onTriggered: Qt.quit(); shortcut: StandardKey.Quit; } } } // Component.onCompleted : {UserData.tryLogin()}//.log(UserData.loggedIn);} Login { id: loginTab - visible: (UserData.loginStatus == DDPClient.LoginFailed) + visible: (UserData.loginStatus == DDPClient.LoginFailed || UserData.loginStatus == DDPClient.LoggedOut) anchors.fill:parent z: 10 serverURL: UserData.serverURL username: UserData.userName onAccepted: { UserData.password = loginTab.password; UserData.userName = loginTab.username; UserData.serverURL = loginTab.serverURL; // console.log("") UserData.tryLogin(); } } // statusBar: StatusView { // RowLayout { // Label { text: statusText } // } // } BusyIndicator { id: busy anchors.centerIn: parent visible: UserData.loginStatus == DDPClient.LoggingIn } Item { id: mainWidget anchors.fill: parent - visible: UserData.loginStatus != DDPClient.LoginFailed + visible: !loginTab.visible // 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 = ""; } } } } onClosing: { console.log("Minimizing to systray..."); hide(); } function toggleShow(reason) { // console.log ("Showing"); if (visible) { hide(); } else { show(); raise(); requestActivate(); } } Component.onCompleted: { systrayIcon.activated.connect(toggleShow); // systrayIcon.showMessage("Connected", "We are CONNECTED!"); } Timer { id: timer interval: 3000 onTriggered: statusText = ""; repeat: true } onStatusTextChanged: timer.restart(); } diff --git a/src/ddpclient.cpp b/src/ddpclient.cpp index 1e9e318d..26134c9b 100644 --- a/src/ddpclient.cpp +++ b/src/ddpclient.cpp @@ -1,272 +1,294 @@ /* * * 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 "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()); + UserData::self()->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()); + UserData::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_doingTokenLogin(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(UserData::self(), &UserData::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 (UserData::instance()->serverURL() != m_url || !m_webSocket.isValid()) { + if (UserData::self()->serverURL() != m_url || !m_webSocket.isValid()) { if (m_webSocket.isValid()) { m_webSocket.flush(); m_webSocket.close(); } - m_url = UserData::instance()->serverURL(); + m_url = UserData::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) { 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.sendTextMessage(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)"; 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 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") { qDebug() << "got a result" << root; 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"; - // 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); +// // 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); + + login(); // Let's keep trying to log in } else { - UserData::instance()->setAuthToken(root.value("result").toObject().value("token").toString()); + UserData::self()->setAuthToken(root.value("result").toObject().value("token").toString()); setLoginStatus(DDPClient::LoggedIn); } // emit loggedInChanged(); } } else if (messageType == "connected") { qDebug() << "Connected"; m_connected = true; -// emit connected(); emit connectedChanged(); - if (!UserData::instance()->authToken().isEmpty()) { - setLoginStatus(DDPClient::LoggingIn); - login();// Try to resume auth token login - } + 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) { + qDebug() << "Setting login status to" << l; m_loginStatus = l; emit loginStatusChanged(); + + // reset flags + if (l == LoginFailed) { + m_attemptedPasswordLogin = false; + m_attemptedTokenLogin = false; + } } 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()) { + if (!UserData::self()->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; QString json = "{\"password\":\"%1\", \"user\": {\"username\":\"%2\"}}"; - json = json.arg(UserData::instance()->password()).arg(UserData::instance()->userName()); + json = json.arg(UserData::self()->password()).arg(UserData::self()->userName()); + m_loginJob = method("login", QJsonDocument::fromJson(json.toUtf8())); + + } else if (!UserData::self()->authToken().isEmpty() && !m_attemptedTokenLogin) { + m_attemptedPasswordLogin = true; + QString json = "{\"resume\":\"%1\"}"; + json = json.arg(UserData::self()->authToken()); m_loginJob = method("login", QJsonDocument::fromJson(json.toUtf8())); } else { setLoginStatus(LoginFailed); } } +void DDPClient::logOut() +{ +// setLoginStatus(NotConnected); + m_webSocket.close(); +} + void DDPClient::onWSConnected() { qDebug() << "Websocket connected at URL" << m_url; QString json("{\"msg\":\"connect\", \"version\": \"1\", \"support\": [\"1\"]}"); qint64 bytes = m_webSocket.sendTextMessage(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() { qDebug() << "WebSocket CLOSED" << m_webSocket.closeReason() << m_webSocket.error() << m_webSocket.closeCode(); setLoginStatus(NotConnected); // m_connected = false; } diff --git a/src/ddpclient.h b/src/ddpclient.h index f4e9d6ad..ac91c266 100644 --- a/src/ddpclient.h +++ b/src/ddpclient.h @@ -1,124 +1,129 @@ /* * * 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 + LoginFailed, + LoggedOut }; Q_ENUM(LoginStatus) 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); // unsigned method(const QString &method, const QJsonObject ¶ms); void subscribe(const QString &collection, const QJsonDocument ¶ms); Q_INVOKABLE void login(); + void logOut(); + // Q_INVOKABLE void loginWithPassword(); bool isConnected() const; bool isLoggedIn() const; - LoginStatus loginStatus() const; void onServerURLChange(); signals: // void connected(); void connectedChanged(); 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: + 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_doingTokenLogin; + bool m_attemptedPasswordLogin; + bool m_attemptedTokenLogin; + + friend class UserData; }; // #include "ddpclient.moc" #endif // DDPCLIENT_H diff --git a/src/messagemodel.cpp b/src/messagemodel.cpp index 217a36cf..bc0e4e7c 100644 --- a/src/messagemodel.cpp +++ b/src/messagemodel.cpp @@ -1,176 +1,179 @@ #include #include #include #include #include "messagemodel.h" +#include "userdata.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(); 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; d.setObject(o); return d.toBinaryData(); } MessageModel::MessageModel(const QString &roomID, QObject* parent) : QAbstractListModel(parent), m_roomID(roomID) { - QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + qDebug() << "Creating message Model"; + QDir cacheDir(UserData::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()); m_allMessages[m.timestamp] = m; // qDebug() << m.message; } } } } MessageModel::~MessageModel() { - QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)+'/rooms_cache'); + QDir cacheDir(UserData::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; } 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/rocketchatbackend.cpp b/src/rocketchatbackend.cpp index 1b48dee3..5efbe76e 100644 --- a/src/rocketchatbackend.cpp +++ b/src/rocketchatbackend.cpp @@ -1,160 +1,172 @@ /* * * 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) { // qDebug() << doc; - RoomModel *model = UserData::instance()->roomModel(); + RoomModel *model = UserData::self()->roomModel(); +// qDebug() << model; +// model->reset(); + 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() == "c") { QString roomID = room.value("rid").toString(); qDebug() << "Adding" << roomID<< room.value("name").toString(); - MessageModel *roomModel = UserData::instance()->getModelForRoom(roomID); + MessageModel *roomModel = UserData::self()->getModelForRoom(roomID); model->addRoom(roomID, room.value("name").toString()); QString params = QString("[\"%1\"]").arg(roomID); - UserData::instance()->ddp()->subscribe("stream-room-messages", QJsonDocument::fromJson(params.toLatin1())); + UserData::self()->ddp()->subscribe("stream-room-messages", QJsonDocument::fromJson(params.toLatin1())); // Load history QByteArray json = "[\""+roomID.toLatin1() + "\", null, 50, {\"$date\": "+ QString::number(roomModel->lastTimestamp()).toLatin1()+ "}]"; qDebug() << json; - UserData::instance()->ddp()->method("loadHistory", QJsonDocument::fromJson(json), process_backlog); + UserData::self()->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); + UserData::self()->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::loginStatusChanged, this, &RocketChatBackend::onLoggedIn); +// UserData::self()->ddp() = new DDPClient(, this); + connect(UserData::self(), &UserData::loginStatusChanged, this, &RocketChatBackend::onLoginStatusChanged); + } RocketChatBackend::~RocketChatBackend() { // delete m_rooms; -// delete UserData::instance()->ddp(); +// delete UserData::self()->ddp(); } -void RocketChatBackend::onLoggedIn() +void RocketChatBackend::onLoginStatusChanged() { - if (UserData::instance()->ddp()->loginStatus() != DDPClient::LoggedIn) { - qDebug() << "not yet logged in:" << UserData::instance()->ddp()->loginStatus(); - return; + if (UserData::self()->loginStatus() == DDPClient::LoggedIn) { + connect(UserData::self()->ddp(), &DDPClient::changed, this, &RocketChatBackend::onChanged); + connect(UserData::self()->ddp(), &DDPClient::added, this, &RocketChatBackend::onAdded); + + qDebug() << "GETTING LIST OF ROOMS"; + UserData::self()->ddp()->method("subscriptions/get", QJsonDocument::fromJson("{\"$date\": 0}"), rooms_callback); } - qDebug() << "GETTING LIST OF ROOMS"; +} + +void RocketChatBackend::onLoggedIn() +{ +// if (UserData::self()->loginStatus() != DDPClient::LoggedIn) { +// qDebug() << "not yet logged in:" << UserData::self()->loginStatus(); +// return; +// } + // // get list of rooms -// UserData::instance()->ddp()->method("rooms/get", QJsonDocument::fromJson("{\"$date\": 0}"), rooms_callback); - UserData::instance()->ddp()->method("subscriptions/get", QJsonDocument::fromJson("{\"$date\": 0}"), rooms_callback); +// UserData::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") { qDebug() << "NEW USER"; } else if (collection == "rooms") { } } // {"_id":"RhiggypZiepy9M4qL","_updatedAt":{"$date":1482704143952},"alert":false,"ls":{"$date":1482704143952},"name":"tech","open":true,"rid":"tKHaLfB35vo4qAnBp","t":"c","ts":{"$date":1479497659929},"u":{"_id":"po2bcKwPMdtnDCXhX","username":"ruphy"},"unread":0} 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/rocketchatbackend.h b/src/rocketchatbackend.h index 4ef83bd2..f492ffab 100644 --- a/src/rocketchatbackend.h +++ b/src/rocketchatbackend.h @@ -1,52 +1,52 @@ /* * * 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 ROCKETCHATBACKEND_H #define ROCKETCHATBACKEND_H #include #include #include "roommodel.h" // #include "ddpclient.h" class DDPClient; // class QJsonObject; class RocketChatBackend : public QObject { Q_OBJECT public: RocketChatBackend(QObject *parent = 0); ~RocketChatBackend(); static void processIncomingMessages(QJsonArray messages); private slots: void onAdded(QJsonObject object); void onChanged(QJsonObject object); void onLoggedIn(); - + void onLoginStatusChanged(); private: // RoomModel *m_rooms; }; #endif // ROCKETCHATBACKEND_H diff --git a/src/roommodel.cpp b/src/roommodel.cpp index e64a3079..c8662643 100644 --- a/src/roommodel.cpp +++ b/src/roommodel.cpp @@ -1,133 +1,165 @@ /* * * 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 +#include "userdata.h" + 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; - } - } - } + reset(); } RoomModel::~RoomModel() { - QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + QDir cacheDir(UserData::self()->cacheBasePath()); 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()); } } } +void RoomModel::reset() +{ + if (UserData::self()->serverURL().isEmpty()) { + return; + } + + beginResetModel(); + m_roomsList.clear(); + endResetModel(); + + QDir cacheDir(UserData::self()->cacheBasePath()); + // 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()); +// qDebug() << m.id << m.name << m.selected; + m_roomsList[m.name] = m; +// addRoom(m.id, m.name, m.selected); + } + } + } +} + 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.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(); +// qDebug() << m_roomsList.size(); +// return; + qDebug() << "Adding room" << roomID << roomName << m_roomsList.keys(); if (roomID.isEmpty()) { return; } + bool updating = false; + + if (m_roomsList.contains(roomName)) { + // we are doing an update + updating = true; + } + int size = m_roomsList.size(); - beginInsertRows(index(size), size, (size+1)); + if (!updating) { + beginInsertRows(index(size), size, (size+1)); + } Room r; r.id = roomID; r.name = roomName; r.selected = selected; m_roomsList[roomName] = r; - endInsertRows(); + + if (updating) { + //Figure out a better way to update just the really changed message, not EVERYTHING + emit dataChanged(createIndex(1, 1), createIndex(rowCount(), 1)); + } else { + endInsertRows(); + } } // #include "roommodel.moc" diff --git a/src/roommodel.h b/src/roommodel.h index d2547730..2ae2aa66 100644 --- a/src/roommodel.h +++ b/src/roommodel.h @@ -1,66 +1,68 @@ /* * * 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 { QString name; 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); + void reset(); protected: virtual QHash roleNames() const; private: + QHash< QString, Room > m_roomsList; }; #endif // ROOMMODEL_H diff --git a/src/userdata.cpp b/src/userdata.cpp index 3f680388..9b5d8d75 100644 --- a/src/userdata.cpp +++ b/src/userdata.cpp @@ -1,150 +1,180 @@ /* * * 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 +RoomModel * UserData::roomModel() { + if (!m_roomModel) { + qDebug() << "creating new RoomModel"; + m_roomModel = new RoomModel; + qDebug() << m_roomModel; +// m_roomModel->reset(); + } return m_roomModel; } -DDPClient * UserData::ddp() const +DDPClient * UserData::ddp() { + if (!m_ddp) { + m_ddp = new DDPClient(serverURL()); + connect(m_ddp, &DDPClient::loginStatusChanged, this, &UserData::loginStatusChanged); +// connect(m_ddp, &DDPClient::loginStatusChanged, this, [=](){qDebug() << "Signal received";}); + } 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(roomID, this); return m_messageModels[roomID]; } } QString UserData::serverURL() const { return m_serverURL; } void UserData::setServerURL(const QString& serverURL) { + if (m_serverURL == serverURL) { + return; + } + QSettings s; s.setValue("serverURL", serverURL); m_serverURL = serverURL; + m_roomModel->reset(); emit serverURLChanged(); } -bool UserData::connected() const +DDPClient::LoginStatus UserData::loginStatus() { - return ddp()->isConnected(); + if (m_ddp) { + return ddp()->loginStatus(); + } else { + return DDPClient::LoggedOut; + } } -DDPClient::LoginStatus UserData::loginStatus() const +void UserData::tryLogin() { - return ddp()->loginStatus(); + qDebug() << "Attempting login" << userName() << "on" << serverURL(); +// ddp()->login(); + ddp(); // This creates a new ddp() object. DDP will automatically try to connect and login. } -void UserData::tryLogin() +void UserData::logOut() { - qDebug() << "Attempting login" << userName() << "on" << serverURL(); - ddp()->login(); + setAuthToken(QString()); + setPassword(QString()); +// m_ddp->logOut(); + 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->reset(); + // RoomModel -> reset(); +} + +QString UserData::cacheBasePath() const +{ + return QStandardPaths::writableLocation(QStandardPaths::CacheLocation)+'/'+m_serverURL; } UserData::UserData(QObject* parent) : QObject(parent), - m_roomModel(new RoomModel) + m_ddp(0), + m_roomModel(0) { QSettings s; m_serverURL = s.value("serverURL", "demo.rocket.chat").toString(); - m_ddp = new DDPClient(m_serverURL); m_userName = s.value("username").toString(); m_authToken = s.value("authToken").toString(); - connect(m_ddp, &DDPClient::loginStatusChanged, this, &UserData::loginStatusChanged); - connect(this, &UserData::serverURLChanged, m_ddp, &DDPClient::onServerURLChange); - -} - -UserData::~UserData() -{ -// delete m_roomModel; -// delete m_ddp; +// roomModel()->reset(); } -UserData * UserData::instance() +UserData * UserData::self() { if (!m_self) { m_self = new UserData; + m_self->ddp(); // Create DDP object so we try to connect at startup } return m_self; } diff --git a/src/userdata.h b/src/userdata.h index 32bb8902..8b653d52 100644 --- a/src/userdata.h +++ b/src/userdata.h @@ -1,104 +1,107 @@ /* * * 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 connected READ connected NOTIFY connectedChanged) Q_PROPERTY (DDPClient::LoginStatus loginStatus READ loginStatus NOTIFY loginStatusChanged) public: - static UserData* instance(); + static UserData* self(); 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; - DDPClient::LoginStatus loginStatus() const; + bool connected(); + DDPClient::LoginStatus loginStatus(); QString serverURL() const; void setServerURL(const QString &serverURL); - DDPClient *ddp() const; - Q_INVOKABLE RoomModel *roomModel() const; + DDPClient *ddp(); + Q_INVOKABLE RoomModel *roomModel(); Q_INVOKABLE void sendMessage(const QString &roomID, const QString &message); Q_INVOKABLE MessageModel* getModelForRoom(const QString &roomID); Q_INVOKABLE void tryLogin(); + Q_INVOKABLE void logOut(); // void setRoomModel(); + QString cacheBasePath() const; signals: void userNameChanged(); - void connectedChanged(); +// void connectedChanged(); void serverURLChanged(); void loginStatusChanged(); +// void DDPChanged(); private: UserData(QObject *parent = 0); - ~UserData(); +// ~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(); + UserData *userData = UserData::self(); return userData; } #endif // USERDATA_H