diff --git a/Desktop.qml b/Desktop.qml index ac0b23ac..15d17e02 100644 --- a/Desktop.qml +++ b/Desktop.qml @@ -1,349 +1,352 @@ // Skeleton from https://github.com/achipa/outqross_blog.git // Almost everything has been re-adapted import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.2 import QtQuick.Window 2.2 import QtQuick.Dialogs 1.2 import QtQuick.Layouts 1.1 import Qt.labs.settings 1.0 import QtGraphicalEffects 1.0 import KDE.Ruqola.Ruqola 1.0 import KDE.Ruqola.DDPClient 1.0 import KDE.Ruqola.Notification 1.0 // import "Log.js" as Log // import "Data.js" as Data ApplicationWindow { property int margin: 11 property string statusText property string lightGreen: "#6ab141"; property string darkGreen: "#00613a"; property string selectedRoomID: ""; id: appid title: qsTr("Ruqola") width: 800 height: 600 visible: true Shortcut { sequence: StandardKey.Quit context: Qt.ApplicationShortcut onActivated: Qt.quit() } Login { id: loginTab visible: (Ruqola.loginStatus == DDPClient.LoginFailed || Ruqola.loginStatus == DDPClient.LoggedOut) // visible: (Ruqola.loginStatus != DDPClient.LoggedIn) anchors.fill:parent z: 10 serverURL: Ruqola.serverURL username: Ruqola.userName onAccepted: { Ruqola.password = loginTab.password; Ruqola.userName = loginTab.username; Ruqola.serverURL = loginTab.serverURL; Ruqola.tryLogin(); - } + } + onOauthAccepted: { + Ruqola.tryOAuthLogin(); + } } BusyIndicator { id: busy anchors.centerIn: parent visible: Ruqola.loginStatus == DDPClient.LoggingIn } Item { id: mainWidget anchors.fill: parent visible: !loginTab.visible Rectangle { id: userBox anchors.top: parent.top width: parent.width anchors.left: parent.left anchors.right: roomsList.right height: 40 color: darkGreen Text { verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignRight anchors.rightMargin: 10 anchors.fill: parent font.pointSize: 12 color: "white" text: "Hello, " + Ruqola.userName } } RoomsView { anchors.top: userBox.bottom anchors.left: parent.left anchors.bottom: parent.bottom anchors.margins: 0 width: 200 height: appid.height id: roomsList model: Ruqola.roomModel() visible: parent.visible selectedRoomID: appid.selectedRoomID; onRoomSelected: { if (roomID == selectedRoomID) { return; } console.log("Choosing room", roomID); appid.selectedRoomID = roomID; activeChat.model = Ruqola.getModelForRoom(roomID) topicWidget.selectedRoom = Ruqola.getRoom(roomID) } onCountChanged: { // console.log("We have", roomsList.count, "rooms") } LinearGradient { id: greenGradient anchors.fill: parent start: Qt.point(0, 0) end: Qt.point(roomsList.width, 0) gradient: Gradient { GradientStop { position: 0.0; color: "#6ab141" } GradientStop { position: 1.0; color: "#00613a" } } z: -1; } } //RoomsView Item { anchors.right: parent.right anchors.left: roomsList.right anchors.top: parent.top anchors.bottom: input.top id: chatView Rectangle { id: topicWidget color: "#fff" anchors.top: parent.top anchors.right: parent.right anchors.left: parent.left height: nameLabel.height + topicLabel.height property var selectedRoom; Text { id: nameLabel text: "#" + parent.selectedRoom.name font.pointSize: 18 verticalAlignment: Text.AlignVCenter anchors.leftMargin: 20 height: 40 // height: font.pixelSize + 10 anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right } Text { id: topicLabel text: topicWidget.selectedRoom.topic anchors.top: nameLabel.bottom anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right horizontalAlignment: Text.AlignHCenter height: font.pixelSize + 10 } } ScrollView { anchors.right: parent.right anchors.left: parent.left anchors.top: topicWidget.bottom anchors.bottom: parent.bottom verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn // visible: parent.visible && (Ruqola.loginStatus != DDPClient.LoggingIn) // visible: !greeter.visible ListView { id: activeChat // model: Ruqola.getModelForRoom(selectedRoomID) onCountChanged: { // console.log("changed") // var newIndex = count - 1 // last index // positionViewAtEnd() positionViewAtIndex(count - 1, ListView.Beginning) // currentIndex = newIndex } // Component.onCompleted: positionViewAtEnd() Component.onCompleted: positionViewAtIndex(count - 1, ListView.Beginning) // onSelectedRoomIDChanged: { console.log("CHANGED"); activeChat.positionViewAtEnd(); } // model: myModel anchors.fill:parent visible : count > 0 z: -1 // ScrollBar.vertical: ScrollBar { } delegate: Message { i_messageText: messageText i_username: username i_systemMessage: systemMessage i_systemMessageType: type //width: parent.width } } } } //Item chatView Item { anchors.bottom: parent.bottom anchors.left: roomsList.right anchors.right: parent.right id: input height: 40 TextField { id: messageLine anchors.left: parent.left anchors.bottom: parent.bottom anchors.top: parent.top anchors.right: emoticonsButton.left placeholderText: if (Ruqola.loginStatus != DDPClient.LoggedIn || (selectedRoomID=="")){ qsTr("Please Select a room") } else{ qsTr("Enter message") } // height: 2.7*font.pixelSize property string type: "text"; onAccepted: { if (text != "" && Ruqola.loginStatus == DDPClient.LoggedIn && !(selectedRoomID=="")) { Ruqola.sendMessage(selectedRoomID, text, type); text = ""; } } } Button { anchors.bottom: parent.bottom anchors.top: parent.top anchors.right: attachmentsButton.left width: 50 id : emoticonsButton iconName: "emoticonsButton" iconSource: "qrc:/Emoticon.png" visible: true } Button { anchors.bottom: parent.bottom anchors.top: parent.top anchors.right: parent.right width: 50 id : attachmentsButton iconName: "attachmentsButton" iconSource: "qrc:/attach-button.jpg" visible: true onClicked: Ruqola.attachmentButtonClicked(); } }//Item input }// mainWidget Item Image { id: receivedImage source:" " width: 60 height: 80 fillMode: Image.PreserveAspectFit // visible: //only when an image is recieved from server sourceSize.width: 1024 sourceSize.height: 1024 } Rectangle { z: -10 anchors.fill: parent color: "white" } onClosing: { console.log("Minimizing to systray..."); hide(); } function toggleShow() { if (visible) { hide(); } else { show(); raise(); requestActivate(); } } Component.onCompleted: { systrayIcon.activated.connect(toggleShow); systrayIcon.messageClicked.connect(toggleShow); // roomsList.model = Ruqola.roomModel(); // timer.start(); // timer.fire(); } /* Timer { id: timer interval: 1000 onTriggered: { // console.log("FIRE"); switch (Ruqola.loginStatus) { case Ruqola.NotConnected: statusText = qsTr("Not connected."); break; case Ruqola.LoggedIn: statusText = qsTr("Connected to " + Ruqola.serverURL); break; } } repeat: true }*/ // onStatusTextChanged: timer.restart(); } diff --git a/Login.qml b/Login.qml index 5add9217..b331ae24 100644 --- a/Login.qml +++ b/Login.qml @@ -1,107 +1,116 @@ import QtQuick 2.7 import QtQuick.Controls 1.3 Item { property alias username: usernameField.text; property alias password: passField.text; property alias serverURL: urlField.text; signal accepted() - + signal oauthAccepted() + Keys.onPressed: { if (event.key === Qt.Key_Enter) { acceptingButton.clicked(); } else if (event.key === StandardKey.Escape) { } } id: loginForm // color: "#eeeeee" implicitHeight: 400 implicitWidth: 300 Column { id: form anchors.centerIn: parent width: 0.8*parent.width spacing: 3 Text { text: "Ruqola Log in" color: "#555" id: loginLabel - font.pixelSize: 20 + font.pixelSize: 40 horizontalAlignment: Text.AlignHCenter width: parent.width } Item { id: spacer width: 30 height: 30 } Text { width: parent.width text:"Rocket Chat Server" } TextField { id: urlField // text: loginForm.serverURL width: parent.width placeholderText: qsTr("Enter address of the server") } Text { id:username width: parent.width text:"Enter your username" } TextField { width: parent.width id: usernameField placeholderText: qsTr("Enter username") } Text { id: passLabel width: parent.width text:"Enter your password" } TextField { width: parent.width id:passField echoMode: TextInput.Password inputMethodHints: Qt.ImhHiddenText placeholderText: qsTr("Enter password") } Item { id: spacer2 width: 30 height: 30 } Button { id: acceptingButton width: parent.width text: qsTr("Log in") enabled: (passField.text && urlField.text && usernameField.text) onClicked: loginForm.accepted() isDefault: true } + + Button { + id: authButton + width: parent.width + text: qsTr("Log in with Google Account") +// enabled: (passField.text && urlField.text && usernameField.text) + onClicked: loginForm.oauthAccepted() + } } // Component.onCompleted: { // acceptingButton.clicked.connect(loginForm.accepted) // } } diff --git a/Ruqola.pro b/Ruqola.pro index 867b1d81..8675924f 100644 --- a/Ruqola.pro +++ b/Ruqola.pro @@ -1,20 +1,23 @@ TEMPLATE = app -QT += widgets gui core qml quick websockets +QT += widgets gui core qml quick websockets network networkauth HEADERS += src/messagemodel.h src/roommodel.h src/ddpclient.h src/ruqola.h src/rocketchatbackend.h \ src/notification.h \ - src/messagequeue.h + src/messagequeue.h \ + src/authentication.h SOURCES += main.cpp src/messagemodel.cpp src/roommodel.cpp src/ddpclient.cpp src/ruqola.cpp src/rocketchatbackend.cpp \ src/notification.cpp \ - src/messagequeue.cpp + src/messagequeue.cpp \ + src/authentication.cpp RESOURCES += qml.qrc # Additional import path used to resolve QML modules in Qt Creator's code model # QML_IMPORT_PATH = # Default rules for deployment. include(deployment.pri) -DISTFILES += +DISTFILES += \ + src/client_secret.json diff --git a/src/authentication.cpp b/src/authentication.cpp index ce46e7e1..fe27a3fe 100644 --- a/src/authentication.cpp +++ b/src/authentication.cpp @@ -1,122 +1,111 @@ +/* + * + * 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 "authentication.h" - -#include #include -#include #include -Authentication::Authentication(){ - - m_google->setScope("email"); -// connect(m_google, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &QDesktopServices::openUrl); -// connect(&m_google, &QOAuth2AuthorizationCodeFlow::granted, this, &Authentication::onGranted()); - - +Authentication::Authentication() +{ + getDataFromJson(); } - - void Authentication::OAuthLogin() { QJsonObject authKeys; authKeys["credentialToken"] = m_client_id; authKeys["credentialSecret"] = m_client_secret; Ruqola::self()->ddp()->method("login", QJsonDocument(authKeys)); + QJsonArray requestPermissions; + requestPermissions.append("email"); + + bool requestOfflineToken = true; + + QString scope = QString("openID profile email"); + + QSettings s; + s.setValue("stateHexNumber", QString("{67C8770B-44F1-410A-AB9A-F9B5446F13EE}")); + QUuid state(s.value("stateHexNumber").toString()); + + QJsonObject loginUrlParameters; + loginUrlParameters["client_id"] = m_client_id; + loginUrlParameters["response_type"] = QString("code"); + loginUrlParameters["scope"] = scope; + loginUrlParameters["state"] = state.toString(); + + QString username = s.value("username").toString(); + QString loginHint = username; + + QString loginStyle = QString("redirect"); + QString redirectUrl = s.value("redirectUrl").toString(); + + QJsonObject json; + json["requestPermissions"] = requestPermissions; + json["requestOfflineToken"] = requestOfflineToken; + json["loginUrlParameters"] = loginUrlParameters; + json["loginHint"] = loginHint; + json["loginStyle"] = loginStyle; + json["redirectUrl"] = redirectUrl; + +// Ruqola::self()->ddp()->method("login", QJsonDocument(json)); + } void Authentication::getDataFromJson(){ QDir cacheDir(":/src/client_secret.json"); if (!cacheDir.exists(cacheDir.path())) { cacheDir.mkpath(cacheDir.path()); } QFile f(cacheDir.absoluteFilePath("client_secret.json")); QString val; if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { val = f.readAll(); } QJsonDocument document = QJsonDocument::fromJson(val.toUtf8()); QJsonObject object = document.object(); const auto settingsObject = object["web"].toObject(); const QUrl authUri(settingsObject["auth_uri"].toString()); const auto clientId = settingsObject["client_id"].toString(); const QUrl tokenUri(settingsObject["token_uri"].toString()); const auto clientSecret(settingsObject["client_secret"].toString()); - const auto redirectUris = settingsObject["redirect_uris"].toArray(); - const QUrl redirectUri(redirectUris[0].toString()); - const auto port = static_cast(redirectUri.port()); - - m_google->setAuthorizationUrl(authUri); - m_google->setClientIdentifier(clientId); - m_google->setAccessTokenUrl(tokenUri); - m_google->setClientIdentifierSharedKey(clientSecret); + const auto redirectUrls = settingsObject["redirect_uris"].toArray(); + const QUrl redirectUrl(redirectUrls[0].toString()); - auto replyHandler = new QOAuthHttpServerReplyHandler(port, this); - m_google->setReplyHandler(replyHandler); - - // If user grants the permissions, client receives a QOAuth2AuthorizationCodeFlow::granted signal - // and can then start sending authorized requests. - m_google->grant(); - -} - -void Authentication::onGranted() -{ - m_authGranted = true; -} - -void Authentication::sendApiRequest() -{ - if(!m_authGranted) return; - //Try sending a request using https://www.googleapis.com/plus/v1/people/me - auto reply = m_google->get(QUrl("https://www.googleapis.com/plus/v1/people/me")); + QSettings s; + s.setValue("clientID", clientId); + m_client_id = clientId; + s.setValue("clientSecret", clientSecret); + m_client_secret = clientSecret; + s.setValue("redirectUrl", redirectUrl); } - -/* - * Handle the OAuth 2.0 server response - * Exchange authorization code for refresh and access tokens - * - * - * - * REQUEST - * ------------------------ - * POST /oauth2/v4/token HTTP/1.1 - Host: www.googleapis.com - Content-Type: application/x-www-form-urlencoded - - code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7& - client_id=your_client_id& - client_secret=your_client_secret& - redirect_uri=https://oauth2.example.com/code& - grant_type=authorization_code - - -Google responds to this request by returning a JSON object that contains a short-lived access token and a refresh token. - - *RESPONSE - * ---------------------------- - * The following snippet shows a sample response: - - { - "access_token":"1/fFAGRNJru1FTz70BzhT3Zg", - "expires_in":3920, - "token_type":"Bearer", - "refresh_token":"1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI" - } - - - *CALLING GOOGLE APIs - * ------------------------------- - GET https://www.googleapis.com/drive/v2/files?access_token= - - -*/ +//#include "authentication.moc" diff --git a/src/authentication.h b/src/authentication.h index 8406ab6c..36320c1a 100644 --- a/src/authentication.h +++ b/src/authentication.h @@ -1,32 +1,62 @@ +/* + * + * 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 AUTHENTICATION_H #define AUTHENTICATION_H - -#include +#include #include -class Authentication : public QObject +class Authentication { - Q_OBJECT + public: Authentication(); + + /** + * @brief Extract info from Google Json API + */ void getDataFromJson(); + + /** + * @brief Call DDPClient's @method method with OAuth params + */ void OAuthLogin(); + + /** + * @brief Make requests to Google on behalf of user using access token + */ void sendApiRequest(); private slots: void onGranted(); - private: bool m_authGranted; QString m_client_id; QString m_client_secret; QOAuth2AuthorizationCodeFlow * m_google; }; - - - #endif // AUTHENTICATION_H diff --git a/src/client_secret.json b/src/client_secret.json index dd0e3d15..b8eca407 100644 --- a/src/client_secret.json +++ b/src/client_secret.json @@ -1 +1 @@ -{"web":{"client_id":"143580046552-s4rmnq5mg008u76id0d3rl63od985hc6.apps.googleusercontent.com","project_id":"ruqola-161705","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"nyVm19iOjjtldcCZJ-7003xg","redirect_uris":["http://localhost:8080/cb","http://127.0.0.1:3000"]}} \ No newline at end of file +{"web":{"client_id":"143580046552-s4rmnq5mg008u76id0d3rl63od985hc6.apps.googleusercontent.com","project_id":"ruqola-161705","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"nyVm19iOjjtldcCZJ-7003xg","redirect_uris":["http://localhost:8080/cb/_oauth/google?close","http://localhost:8080/cb"]}} \ No newline at end of file diff --git a/src/ddpclient.cpp b/src/ddpclient.cpp index e27d00c1..b061f6a3 100644 --- a/src/ddpclient.cpp +++ b/src/ddpclient.cpp @@ -1,355 +1,308 @@ /* * * 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 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); } QQueue> DDPClient::messageQueue() { return m_messageQueue; } unsigned int DDPClient::method(const QString& m, const QJsonDocument& params, DDPClient::MessageType messageType) { return method(m, params, empty_callback, messageType); } unsigned int DDPClient::method(const QString& method, const QJsonDocument& params, std::function callback, DDPClient::MessageType messageType) { 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(); if(messageType==DDPClient::Persistent){ m_messageQueue.enqueue(qMakePair(method,params)); Ruqola::self()->messageQueue()->processQueue(); } } else { qDebug() << "Successfully sent " << json; } - //callback(QJsonDocument::fromJson(json.toUtf8())); m_callbackHash[m_uid] = callback; m_uid++; return m_uid - 1 ; } void DDPClient::subscribe(const QString& collection, const QJsonArray& params) { QJsonObject json; json["msg"] = "sub"; json["id"] = QString::number(m_uid); json["name"] = collection; json["params"] = params; qint64 bytes = m_webSocket.sendTextMessage(QJsonDocument(json).toJson(QJsonDocument::Compact)); if (bytes < json.length()) { qDebug() << "ERROR! I couldn't send all of my message. This is a bug! (try again)"; } m_uid++; } void DDPClient::onTextMessageReceived(QString message) { 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); - - /* - *Handle attachments in a separate class - * - * - 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 = 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 + 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 67391156..69414999 100644 --- a/src/ddpclient.h +++ b/src/ddpclient.h @@ -1,136 +1,198 @@ /* * * 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 MessageType { Persistent, Ephemeral }; DDPClient(const QString &url = QString(), QObject *parent = 0); ~DDPClient(); /** - * @brief Call a method with name @param method and parameters @param params + * @brief Call a method with name @param method and parameters @param params and @param messageType with an empty callback * - * @param method The name of the method + * @param method The name of the method to call Rocket.Chat API for * @param params The parameters - * @return unsigned int, the ID of the called method. Watch for it + * @param messageType The type of message + * @return unsigned int, the ID of the called method */ - unsigned method(const QString &method, const QJsonDocument ¶ms, DDPClient::MessageType messageStatus = DDPClient::Ephemeral); - unsigned method(const QString &method, const QJsonDocument ¶ms, std::function callback, DDPClient::MessageType messageStatus = DDPClient::Ephemeral); + unsigned method(const QString &method, const QJsonDocument ¶ms, DDPClient::MessageType messageType = DDPClient::Ephemeral); + /** + * @brief Send message over network + * + * @param method The name of the method to call Rocket.Chat API for + * @param params The parameters + * @param callback The pointer to callback function + * @param messageType The type of message + * @return unsigned int, the ID of the called method + */ + unsigned method(const QString &method, const QJsonDocument ¶ms, std::function callback, DDPClient::MessageType messageType = DDPClient::Ephemeral); + + + /** + * @brief Subscribes to a collection with name @param collection and parameters @param params + * + * @param collection The name of the collection + * @param params The parameters + */ void subscribe(const QString &collection, const QJsonArray ¶ms); + /** + * @brief Calls method to log in the user with valid username and password + */ Q_INVOKABLE void login(); + + /** + * @brief Closes the websocket connection + */ void logOut(); + /** + * @brief Check whether websocket is connected at url + * + * @return true if connected, else false + */ bool isConnected() const; + + /** + * @brief Check whether user is logged in + * + * @return true if user is logged in, else false + */ bool isLoggedIn() const; + + /** + * @brief Reconnects the websocket to new url + */ void onServerURLChange(); + /** + * @brief Returns the queue used to cache unsent messages + * + *@return QQueue>, The m_messageQueue object + */ QQueue> messageQueue(); + + /** + * @brief Returns standard cache path + * + *@def QString path + */ QString cachePath() const; signals: void connectedChanged(); void loginStatusChanged(); void disconnected(); + void added(QJsonObject item); + void changed(QJsonObject item); /** - * @brief Emitted whenever a result is received. The parameter is the expected ID. + * @brief Emitted whenever a result is received * - * @param id the ID received in the method() call + * @param id The ID received in the method() call + * @param result The response sent by server */ 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; + /** + * @brief Unique message ID for each message sent over network + */ unsigned m_uid; + /** + * @brief Stores callback function associated with each message + * + * @def QHash unsigned messageID and std::function pointer to callback + */ 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, QJsonDocument params + /** + * @brief Abstract queue for all requests regarding network management + * + * @def QPair QString method and QJsonDocument params + */ QQueue> m_messageQueue; friend class Ruqola; }; // #include "ddpclient.moc" #endif // DDPCLIENT_H diff --git a/src/messagemodel.h b/src/messagemodel.h index 164c8f07..3723eac9 100644 --- a/src/messagemodel.h +++ b/src/messagemodel.h @@ -1,178 +1,207 @@ /* * * 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 MESSAGEMODEL_H #define MESSAGEMODEL_H #include #include #include #include #include #include -class Message +class Message //: public QObject { - Q_OBJECT +// Q_OBJECT public: - enum MessageStatus { - Unsent, - Sending, - Sent, - SendFailed - }; - Q_ENUM(MessageStatus) +// enum MessageStatus { +// Unsent, +// Sending, +// Sent, +// SendFailed +// }; +// Q_ENUM(MessageStatus) // To be used in ID find: message ID inline bool operator==(const Message &other) const { return other.messageID == messageID; } // To be used in sorted insert: timestamp inline bool operator<(const Message &other) const { return timestamp < other.timestamp; } - MessageStatus messageStatus() const; - void setMessageStatus(MessageStatus m); +// MessageStatus messageStatus() const; +// void setMessageStatus(MessageStatus m); //Message Object Fields // _id QString messageID; // rid QString roomID; // msg QString message; // ts qint64 timestamp; // u QString username; QString userID; // _updatedAt qint64 updatedAt; // editedAt qint64 editedAt; // editedBy QString editedByUsername; QString editedByUserID; // urls QString url; QString meta; QString headers; QString parsedUrl; // attachments QString imageUrl; QString color; // alias QString alias; // avatar QString avatar; // groupable bool groupable; // parseUrls bool parseUrls; bool systemMessage = false; QString systemMessageType; - MessageStatus m_messageStatus; +// MessageStatus m_messageStatus; signals: - void messageStatusChanged(); +// void messageStatusChanged(); }; class MessageModel : public QAbstractListModel { Q_OBJECT public: enum MessageRoles { Username = Qt::UserRole + 1, MessageText, Timestamp, UserID, SystemMessage, SystemMessageType, MessageID, RoomID, UpdatedAt, EditedAt, EditedByUserName, EditedByUserID, Url, Meta, Headers, ParsedUrl, ImageUrl, Color, Alias, Avatar, Groupable, ParseUrls }; Q_ENUM(MessageRoles) MessageModel(const QString &roomID = "no_room", QObject *parent = 0); virtual ~MessageModel(); + /** + * @brief Adds a message to QVector m_allMessages + * + * @param message The message to be added + */ void addMessage(const Message& message); + /** + * @brief returns number of messages in QVector m_allMessages + * + * @param parent, it is void + * @return int, The number of messages in QVector m_allMessages + */ virtual int rowCount(const QModelIndex & parent = QModelIndex()) const; + + virtual QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; + /** + * @brief Returns last timestamp of last message in QVector m_allMessages + * + * @return qint64 The last timestamp + */ qint64 lastTimestamp() const; + /** + * @brief Constructs Message object from QJsonObject + * + * @param source The Json containing message attributes + * @return Message object, The message constructed from Json + */ static Message fromJSon(const QJsonObject &source); + + /** + * @brief Constructs QBytearray from Message object + * + * @param message The Message object + * @return QByteArray, The Json containing message attributes + */ static QByteArray serialize(const Message &message); protected: virtual QHash roleNames() const; private: const QString m_roomID; QVector m_allMessages; -// QMap m_allMessages; -// QMap m_allMessages; QString m_writableLocation; QFile *cacheWriter; }; #endif diff --git a/src/messagequeue.cpp b/src/messagequeue.cpp index 8d4ca444..1e6e2e7c 100644 --- a/src/messagequeue.cpp +++ b/src/messagequeue.cpp @@ -1,123 +1,124 @@ /* * * 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 "ddpclient.h" -QPair MessageQueue::fromJson(const QJsonObject &o) +QPair MessageQueue::fromJson(const QJsonObject &object) { QPair pair; - pair.first = o["method"].toString(); - QJsonArray arr = o["params"].toArray(); + pair.first = object["method"].toString(); + QJsonArray arr = object["params"].toArray(); pair.second = QJsonDocument(arr); return pair; } QByteArray MessageQueue::serialize(const QPair pair) { QJsonDocument d; QJsonObject o; o["method"] = QJsonValue(pair.first); QJsonArray arr; if ( pair.second.isArray() ){ arr.append(pair.second.array()); } else if ( pair.second.isObject() ) { arr.append(pair.second.object()); } o["params"] = QJsonValue(arr); d.setObject(o); return d.toBinaryData(); } MessageQueue::MessageQueue() { connect(Ruqola::self()->ddp(), &DDPClient::loginStatusChanged, this, &MessageQueue::onLoginStatusChanged); QDir cacheDir(Ruqola::self()->ddp()->cachePath()); // load unsent messages cache 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); QPair pair = MessageQueue::fromJson(QJsonDocument::fromBinaryData(ba).object()); QString method = pair.first; QJsonDocument params = pair.second; Ruqola::self()->ddp()->messageQueue().enqueue(qMakePair(method,params)); } } } } MessageQueue::~MessageQueue() { 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; QQueue> queue = Ruqola::self()->ddp()->messageQueue(); for ( it = queue.begin(); it != queue.end(); it++ ) { QPair pair = *it; QByteArray ba = serialize(pair); out.writeBytes(ba, ba.size()); } } } void MessageQueue::onLoginStatusChanged() { if (Ruqola::self()->loginStatus() == DDPClient::LoggedIn && !Ruqola::self()->ddp()->messageQueue().empty()){ //retry sending messages processQueue(); } } void MessageQueue::processQueue() { + //can be optimized using single shot timer while ( Ruqola::self()->loginStatus() == DDPClient::LoggedIn && !Ruqola::self()->ddp()->messageQueue().empty() ){ QPair pair = Ruqola::self()->ddp()->messageQueue().head(); QString method = pair.first; QJsonDocument params = pair.second; - Ruqola::self()->ddp()->method(method, params); //can be optimized using single shot timer + Ruqola::self()->ddp()->method(method, params); } } diff --git a/src/messagequeue.h b/src/messagequeue.h index d18154cc..110b8105 100644 --- a/src/messagequeue.h +++ b/src/messagequeue.h @@ -1,48 +1,65 @@ /* * * 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: MessageQueue(); ~MessageQueue(); + /** + * @brief Retry to send unsent messages in DDPClient's abtract message queue + */ void processQueue(); - static QPair fromJson(const QJsonObject &o); + + /** + * @brief Constructs QPair object from QJsonObject + * + * @param object The Json containing message attributes + * @return QPair, The pair containing the method and params + */ + static QPair fromJson(const QJsonObject &object); + + /** + * @brief Constructs QBytearray from QPair object + * + * @param pair The pair containing method and params + * @return QByteArray, The Json containing message attributes + */ static QByteArray serialize(const QPair pair); public slots: void onLoginStatusChanged(); }; #endif // MESSAGEQUEUE_H diff --git a/src/notification.h b/src/notification.h index a4ffefa5..de31c493 100644 --- a/src/notification.h +++ b/src/notification.h @@ -1,46 +1,55 @@ /* * * 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 NOTIFICATION_H #define NOTIFICATION_H #include #include #include class Notification: public QSystemTrayIcon { Q_OBJECT public: Notification(); private: + + + /** + * @brief Create actions to be displayed in tray icon menu + */ void createActions(); + + /** + * @brief Creates tray icon consisting of actions + */ void createTrayIcon(); QAction *m_quitAction; QMenu *m_trayIconMenu; }; #endif // NOTIFICATION_H diff --git a/src/rocketchatbackend.cpp b/src/rocketchatbackend.cpp index 4e6e4c8d..8edbf8a0 100644 --- a/src/rocketchatbackend.cpp +++ b/src/rocketchatbackend.cpp @@ -1,277 +1,250 @@ /* * * Copyright 2016 Riccardo Iaconelli * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "rocketchatbackend.h" #include #include #include #include "ruqola.h" #include "ddpclient.h" void debug_callback(QJsonDocument doc) { qDebug() << "DEBUG:" << doc; } void process_backlog(QJsonDocument messages) { qDebug() << messages.object().value("messages").toArray().size(); RocketChatBackend::processIncomingMessages(messages.object().value("messages").toArray()); } void rooms_callback(QJsonDocument doc) { RoomModel *model = Ruqola::self()->roomModel(); QJsonArray removed = doc.object().value("remove").toArray(); QJsonArray updated = doc.object().value("update").toArray(); for (int i = 0; i < updated.size(); i++) { QJsonObject room = updated.at(i).toObject(); if (room.value("t").toString() != "d") { QString roomID = room.value("_id").toString(); MessageModel *roomModel = Ruqola::self()->getModelForRoom(roomID); // let's be extra safe around crashes if (Ruqola::self()->loginStatus() == DDPClient::LoggedIn) { Room r; r.id = roomID; r.name = room["name"].toString(); r.topic = room["topic"].toString(); qDebug() << "Adding room" << r.name << r.id << r.topic; model->addRoom(r); } QJsonArray params; params.append(QJsonValue(roomID)); Ruqola::self()->ddp()->subscribe("stream-room-messages", params); // Load history params.append(QJsonValue(QJsonValue::Null)); params.append(QJsonValue(50)); // Max number of messages to load; QJsonObject dateObject; dateObject["$date"] = QJsonValue(roomModel->lastTimestamp()); params.append(dateObject); Ruqola::self()->ddp()->method("loadHistory", QJsonDocument(params), process_backlog); } } } void subs_callback(QJsonDocument doc) { RoomModel *model = Ruqola::self()->roomModel(); QJsonArray removed = doc.object().value("remove").toArray(); QJsonArray updated = doc.object().value("update").toArray(); for (int i = 0; i < updated.size(); i++) { QJsonObject room = updated.at(i).toObject(); if (room.value("t").toString() != "d") { QString roomID = room.value("rid").toString(); MessageModel *roomModel = Ruqola::self()->getModelForRoom(roomID); // let's be extra safe around crashes if (Ruqola::self()->loginStatus() == DDPClient::LoggedIn) { Room r; r.id = roomID; r.name = room["name"].toString(); r.topic = room["topic"].toString(); qDebug() << "Adding room" << r.name << r.id << r.topic; model->addRoom(r); } QJsonArray params; params.append(QJsonValue(roomID)); Ruqola::self()->ddp()->subscribe("stream-room-messages", params); // Load history params.append(QJsonValue(QJsonValue::Null)); params.append(QJsonValue(50)); // Max number of messages to load; QJsonObject dateObject; dateObject["$date"] = QJsonValue(roomModel->lastTimestamp()); params.append(dateObject); Ruqola::self()->ddp()->method("loadHistory", QJsonDocument(params), process_backlog); } } } void RocketChatBackend::processIncomingMessages(QJsonArray messages) { foreach (const QJsonValue v, messages) { QJsonObject o = v.toObject(); Message m; QString roomId = o.value("rid").toString(); QString type = o.value("t").toString(); m.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); } diff --git a/src/rocketchatbackend.h b/src/rocketchatbackend.h index 795f7b0c..923da37b 100644 --- a/src/rocketchatbackend.h +++ b/src/rocketchatbackend.h @@ -1,53 +1,58 @@ /* * * 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(); + + /** + * @brief Adds incoming message from server to appropriate room + * + * @param messages The Json containing the message + */ static void processIncomingMessages(QJsonArray messages); private slots: void onAdded(QJsonObject object); void onChanged(QJsonObject object); void onLoggedIn(); void onLoginStatusChanged(); void onUserIDChanged(); private: // RoomModel *m_rooms; }; #endif // ROCKETCHATBACKEND_H diff --git a/src/roommodel.cpp b/src/roommodel.cpp index 567f7be1..6710057e 100644 --- a/src/roommodel.cpp +++ b/src/roommodel.cpp @@ -1,249 +1,236 @@ /* * * 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 "ruqola.h" #include #include Room RoomModel::fromJSon(const QJsonObject& o) { Room r; r.id = o["id"].toString(); r.type = o["t"].toString(); r.name = o["name"].toString(); r.userName = o["userName"].toString(); r.userID = o["userID"].toString(); r.topic = o["topic"].toString(); r.mutedUsers = o["muted"].toString(); r.jitsiTimeout = o["jitsiTimeout"].toDouble(); r.ro = o["ro"].toBool(); r.unread = o["unread"].toInt(0); return r; } QByteArray RoomModel::serialize(const Room& r) { QJsonDocument d; QJsonObject o; o["id"] = r.id; o["t"] = r.type; o["name"] = r.name; o["userName"] = r.userName; o["userID"] = r.userID; o["topic"] = r.topic; o["muted"] = r.mutedUsers; o["jitsiTimeout"] = r.jitsiTimeout; o["ro"] = r.ro; o["unread"] = r.unread; d.setObject(o); return d.toBinaryData(); } RoomWrapper::RoomWrapper(QObject *parent) : QObject(parent) {} RoomWrapper::RoomWrapper(const Room &r, QObject *parent) : QObject(parent) { m_name = r.name; m_topic = r.topic; m_unread = r.unread; m_id = r.id; m_selected = r.selected; } RoomModel::RoomModel(QObject* parent) : QAbstractListModel(parent) -{ -// connect(Ruqola::self(), &Ruqola::loginStatusChanged, this, &RoomModel::onLoginStatusChanged); -} +{} RoomModel::~RoomModel() { QDir cacheDir(Ruqola::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::clear() { if (m_roomsList.size()) { beginRemoveRows(QModelIndex(), 0, rowCount()-1); m_roomsList.clear(); QAbstractItemModel::endRemoveRows(); } } RoomWrapper* RoomModel::findRoom(const QString& roomID) const { foreach (const Room r, m_roomsList) { if (r.id == roomID) { return new RoomWrapper(r); } } Room r; return new RoomWrapper(r); } // Clear data and refill it with data in the cache, if there is void RoomModel::reset() { if (Ruqola::self()->cacheBasePath().isEmpty()) { return; } clear(); QDir cacheDir(Ruqola::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()); addRoom(m.id, m.name, m.selected); } } qDebug() << "Cache Loaded"; } } QHash RoomModel::roleNames() const { QHash roles; roles[RoomName] = "name"; roles[RoomID] = "room_id"; roles[RoomSelected] = "selected"; roles[RoomUnread] = "unread"; return roles; } int RoomModel::rowCount(const QModelIndex & parent) const { Q_UNUSED(parent); -// if (m_roomsHash.size() > 4) {return 4;} -// qDebug() << m_roomsList.size() << "ROOMS"; return m_roomsList.size(); } QVariant RoomModel::data(const QModelIndex & index, int role) const { Room r = m_roomsList.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::setActiveRoom(const QString& activeRoom) -// { -// foreach (const QString &id, m_roomsHash.keys()) { -// qDebug() << id; -// m_roomsHash[id].selected = (id == activeRoom); -// } -// // emit dataChanged(createIndex(1, 1), createIndex(rowCount(), 1)); -// } - void RoomModel::addRoom(const QString& roomID, const QString& roomName, bool selected) { if (roomID.isEmpty() || roomName.isEmpty()) { return; } qDebug() << "Adding room" << roomID << roomName; Room r; r.id = roomID; r.name = roomName; r.selected = selected; addRoom(r); } void RoomModel::addRoom(const Room &room) { auto existingRoom = qFind(m_roomsList.begin(), m_roomsList.end(), room); bool present = (existingRoom != m_roomsList.end()); auto i = qUpperBound(m_roomsList.begin(), m_roomsList.end(), room); int pos = i-m_roomsList.begin(); bool roomChanged = false; qDebug() << pos; // if (qFind(m_roomsList.begin(), m_roomsList.end(), room) != m_roomsList.end() && pos > 0) { if (present) { // qDebug() << (qFind(m_roomsList.begin(), m_roomsList.end(), room) - m_roomsList.begin()); // if (pos != m_roomsList.size()) { // we're at the end qDebug() << "Room changed!"; roomChanged = true; //Figure out a better way to update just the really changed message } else { beginInsertRows(QModelIndex(), pos, pos); } if (roomChanged) { m_roomsList.replace(pos-1, room); } else { qDebug() << "Inserting room at position" <getModelForRoom(room.id); } // #include "roommodel.moc" diff --git a/src/roommodel.h b/src/roommodel.h index c79f99dd..a212f752 100644 --- a/src/roommodel.h +++ b/src/roommodel.h @@ -1,177 +1,219 @@ /* * * 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 #include class Room { public: // Room(const Room &room) // { // // this->parent = room.parent(); // } // To be used in ID find: message ID inline bool operator==(const Room &other) const { return other.id == id; } // To be used in sorted insert: timestamp inline bool operator<(const Room &other) const { return name < other.name; } + /** + * @brief Return room name + * + * @return QString, The name of the room + */ QString getName() const { return name; } + + /** + * @brief Return topic name + * + * @return QString, The name of the topic of room + */ QString getTopic() const { return topic; } // private: // friend class RoomModel; // friend class RoomWrapper; // When you add a field, please remember to also add relevant code // to the enum declaration, roleNames, fromJSon and serialize //Room Object Fields // _id QString id; // t (can take values "d" , "c" or "p") QString type; // name QString name; // u QString userName; QString userID; // topic QString topic; // muted - collection of muted users by its usernames QString mutedUsers; //QStringList // jitsiTimeout qint64 jitsiTimeout; // ro - read-only chat or not bool ro; int unread; bool selected = false; }; class RoomWrapper : public QObject { Q_PROPERTY(QString name READ getName NOTIFY nameChanged) Q_PROPERTY(QString topic READ getTopic NOTIFY topicChanged) Q_OBJECT public: RoomWrapper(QObject *parent = 0); RoomWrapper(const Room &r, QObject *parent = 0); QString getName() { return m_name; } QString getTopic() { return m_topic; } signals: void nameChanged(); void topicChanged(); private: QString m_name, m_topic, m_id; int m_unread; bool m_selected; }; class RoomModel : public QAbstractListModel { Q_OBJECT public: enum RoomRoles { RoomName = Qt::UserRole + 1, RoomSelected, RoomID, RoomUnread, RoomType, RoomUserName, //created by UserName RoomUserID, RoomTopic, RoomMuted, RoomJitsiTimeout, RoomRO }; 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; + /** + * @brief Constructs room object from @param roomID and @param roomName and @param selected, then calls @method addRoom + * + * @param roomID The unique room ID + * @param roomName The name of the room + * @param selected True if room if @param roomID is selected, else false + */ Q_INVOKABLE void addRoom(const QString& roomID, const QString& roomName, bool selected = false); + /** + * @brief Adds a room to m_roomsList with @param room + * + * @param room The room to be added + */ void addRoom(const Room& room); - RoomWrapper* findRoom(const QString &roomID) const; + /** + * @brief Finds a room with @param roomID in m_roomsList + * + * @param roomID The ID of the room to find + * @return RoomWrapper Pointer, The pointer to room with @param roomID in m_roomsList, if exists. Else return a new RoomWrapper object + */ + RoomWrapper* findRoom(const QString &roomID) const; + /** + * @brief Constructs Message object from QJsonObject + * + * @param source The Json containing room attributes + * @return Room object, The room constructed from Json + */ static Room fromJSon(const QJsonObject &source); + + /** + * @brief Constructs QBytearray from Message object + * + * @param message The Room object + * @return QByteArray, The Json containing room attributes + */ static QByteArray serialize(const Room &r); // void setActiveRoom(const QString &activeRoom); - +//Clear data and refill it with data in the cache, if there is void reset(); void clear(); protected: virtual QHash roleNames() const; private: QVector m_roomsList; // QHash< QString, Room > m_roomsHash; }; #endif // ROOMMODEL_H diff --git a/src/ruqola.cpp b/src/ruqola.cpp index 876116e3..85750a95 100644 --- a/src/ruqola.cpp +++ b/src/ruqola.cpp @@ -1,284 +1,304 @@ /* * * 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()->processQueue(); } return m_messageQueue; } Notification * Ruqola::notification() { - if (m_notification == NULL) { + if (!m_notification) { m_notification = new Notification(); m_notification->show(); } return m_notification; } +Authentication * Ruqola::authentication() +{ + if (!m_authentication) { + m_authentication = new Authentication(); + } + + return m_authentication; +} + 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), 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::tryOAuthLogin() +{ + m_authentication->OAuthLogin(); + + // 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(); + +} + void Ruqola::logOut() { setAuthToken(QString()); setPassword(QString()); foreach (const QString key, m_messageModels.keys()) { MessageModel *m = m_messageModels.take(key); delete m; } + + m_roomModel->clear(); 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) +Ruqola::Ruqola(QObject* parent): + QObject(parent), + m_ddp(0), + m_messageQueue(0), + m_roomModel(0), + m_notification(0), + m_authentication(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(); + + m_self->authentication(); + } return m_self; } diff --git a/src/ruqola.h b/src/ruqola.h index 8e888956..c7d9c753 100644 --- a/src/ruqola.h +++ b/src/ruqola.h @@ -1,124 +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 . * */ #ifndef USERDATA_H #define USERDATA_H #include #include #include #include "ddpclient.h" #include "roommodel.h" #include "messagemodel.h" #include "notification.h" #include "messagequeue.h" - +#include "authentication.h" class QString; class Ruqola: public QObject { Q_OBJECT Q_PROPERTY(QString userName READ userName WRITE setUserName NOTIFY userNameChanged) Q_PROPERTY(QString userID READ userID WRITE setUserID NOTIFY userIDChanged) Q_PROPERTY(QString serverURL READ serverURL WRITE setServerURL NOTIFY serverURLChanged) Q_PROPERTY(QString password WRITE setPassword) // Q_PROPERTY (bool connected READ connected NOTIFY connectedChanged) Q_PROPERTY(DDPClient::LoginStatus loginStatus READ loginStatus NOTIFY loginStatusChanged) // Q_PROPERTY(QString activeRoom READ activeRoom WRITE setActiveRoom NOTIFY activeRoomChanged) public: + + /** + * @brief Singleton provider + * + * @return Returns the singleton object m_self + */ static Ruqola* self(); void setUserName(const QString &username); QString userName() const; void setUserID(const QString &userID); QString userID() const; void setPassword(const QString &password); QString password() const; void setAuthToken(const QString &token); QString authToken() const; bool connected(); DDPClient::LoginStatus loginStatus(); QString serverURL() const; void setServerURL(const QString &serverURL); // QString activeRoom() const; // void setActiveRoom(const QString &activeRoom); DDPClient *ddp(); Notification *notification(); MessageQueue *messageQueue(); - + Authentication *authentication(); Q_INVOKABLE RoomModel *roomModel(); + + + /** + * @brief Constructs a Json with @param roomID and @param message and @param type, then calls DDPClient's method to send text message over the network + */ Q_INVOKABLE void sendMessage(const QString &roomID, const QString &message, const QString &type); + + /** + * @brief Returns a model for room with ID @param roomID + * + * @return MessageModel Pointer, model for room + */ Q_INVOKABLE MessageModel* getModelForRoom(const QString &roomID); + /** + * @brief Reset models, load cache and call DDPClient's object to automatically try to connect and log in via username and password + */ Q_INVOKABLE void tryLogin(); + + /** + * @brief Clear models, stores cache and logs out the user + */ Q_INVOKABLE void logOut(); + + /** + * @brief Reset models, load cache and call DDPClient's object to automatically try to connect and log in via Google account + */ + Q_INVOKABLE void tryOAuthLogin(); + + /** + * @brief Finds room with @param roomID + * + * @return RoomWrapper Pointer, The room model for @param roomID + */ Q_INVOKABLE RoomWrapper* getRoom(const QString &roomID); Q_INVOKABLE void attachmentButtonClicked(); + /** + * @brief Returns standard cache path + * + * @return QString, The standard cache path + */ QString cacheBasePath() const; signals: void userNameChanged(); void userIDChanged(); void serverURLChanged(); void loginStatusChanged(); private: Ruqola(QObject *parent = 0); static Ruqola *m_self; QString m_password; QString m_userName; QString m_userID; QString m_authToken; QString m_serverURL; DDPClient *m_ddp; MessageQueue *m_messageQueue; RoomModel *m_roomModel; Notification *m_notification; + Authentication *m_authentication; QHash< QString, MessageModel * > m_messageModels; }; inline static QObject *ruqola_singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) Ruqola *userData = Ruqola::self(); return userData; } #endif // USERDATA_H