From 756e716886036447915a10992189774991f4dd8d Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 19 Oct 2017 19:26:49 +0900 Subject: Don't log renames This causes enormous traffic in the logs upon every startup when main.debug logs are on. --- user.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/user.cpp b/user.cpp index 171d6d6c..aa1aa447 100644 --- a/user.cpp +++ b/user.cpp @@ -85,8 +85,6 @@ void User::updateName(const QString& newName) const auto oldName = name(); if (d->name != newName) { - qCDebug(MAIN) << "Renaming" << id() - << "from" << oldName << "to" << newName; d->name = newName; emit nameChanged(this, oldName); } -- cgit v1.2.3 From 76438ba95d16c7d007fc16ffc194889f937a19f7 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 19 Oct 2017 19:36:10 +0900 Subject: Introduce device_id and initial_device_name support; switch to generated LoginJob This is _almost_ a backwards-compatible change, except that connect*() and other relevant methods in Connection are no more virtual (that wasn't much useful anyway). Otherwise it's a matter of passing initial_device_name to connectToServer(), saving device_id (along with access_token) from the result of LoginJob and then passing device_id (along with access_token, again) to connectWithToken() upon the next run. --- connection.cpp | 50 ++++++++++++--------------- connection.h | 29 +++++++++------- connectiondata.cpp | 12 +++++++ connectiondata.h | 2 ++ examples/qmc-example.cpp | 2 +- jobs/generated/login.cpp | 89 ++++++++++++++++++++++++++++++++++++++++++++++++ jobs/generated/login.h | 38 +++++++++++++++++++++ settings.cpp | 29 +++++++++++----- settings.h | 13 ++++--- 9 files changed, 209 insertions(+), 55 deletions(-) create mode 100644 jobs/generated/login.cpp create mode 100644 jobs/generated/login.h diff --git a/connection.cpp b/connection.cpp index 84a52149..427118c9 100644 --- a/connection.cpp +++ b/connection.cpp @@ -21,8 +21,8 @@ #include "user.h" #include "events/event.h" #include "room.h" +#include "jobs/generated/login.h" #include "jobs/generated/logout.h" -#include "jobs/passwordlogin.h" #include "jobs/sendeventjob.h" #include "jobs/postreceiptjob.h" #include "jobs/joinroomjob.h" @@ -61,8 +61,6 @@ class Connection::Private // Leave state of the same room. QHash, Room*> roomMap; QHash userMap; - QString username; - QString password; QString userId; SyncJob* syncJob; @@ -113,42 +111,33 @@ void Connection::resolveServer(const QString& domain) }); } -void Connection::connectToServer(const QString& user, const QString& password) +void Connection::connectToServer(const QString& user, const QString& password, + const QString& initialDeviceName, + const QString& deviceId) { - auto loginJob = callApi(user, password); - connect( loginJob, &PasswordLogin::success, [=] () { - connectWithToken(loginJob->id(), loginJob->token()); + auto loginJob = callApi(QStringLiteral("m.login.password"), user, + /*medium*/ "", /*address*/ "", password, /*token*/ "", + deviceId, initialDeviceName); + connect( loginJob, &BaseJob::success, [=] () { + connectWithToken(loginJob->user_id(), loginJob->access_token(), + loginJob->device_id()); }); - connect( loginJob, &PasswordLogin::failure, [=] () { + connect( loginJob, &BaseJob::failure, [=] () { emit loginError(loginJob->errorString()); }); - d->username = user; // to be able to reconnect - d->password = password; } -void Connection::connectWithToken(const QString& userId, const QString& token) +void Connection::connectWithToken(const QString& userId, + const QString& accessToken, const QString& deviceId) { d->userId = userId; - d->data->setToken(token); - qCDebug(MAIN) << "Accessing" << d->data->baseUrl() - << "by user" << userId - << "with the following access token:"; - qCDebug(MAIN) << token; + d->data->setToken(accessToken); + d->data->setDeviceId(deviceId); + qCDebug(MAIN) << "Using server" << d->data->baseUrl() << "by user" << userId + << "from device" << deviceId; emit connected(); } -void Connection::reconnect() -{ - auto loginJob = callApi(d->username, d->password); - connect( loginJob, &PasswordLogin::success, [=] () { - d->userId = loginJob->id(); - emit reconnected(); - }); - connect( loginJob, &PasswordLogin::failure, [=] () { - emit loginError(loginJob->errorString()); - }); -} - void Connection::logout() { auto job = callApi(); @@ -301,6 +290,11 @@ QString Connection::userId() const return d->userId; } +const QString& Connection::deviceId() const +{ + return d->data->deviceId(); +} + QString Connection::token() const { return accessToken(); diff --git a/connection.h b/connection.h index b7d049f1..2a107b43 100644 --- a/connection.h +++ b/connection.h @@ -61,19 +61,6 @@ namespace QMatrixClient QHash, Room*> roomMap() const; - Q_INVOKABLE virtual void resolveServer(const QString& domain); - Q_INVOKABLE virtual void connectToServer(const QString& user, - const QString& password); - Q_INVOKABLE virtual void connectWithToken(const QString& userId, - const QString& token); - Q_INVOKABLE virtual void reconnect(); - /** @deprecated Use stopSync() instead */ - Q_INVOKABLE virtual void disconnectFromServer() { stopSync(); } - Q_INVOKABLE virtual void logout(); - - Q_INVOKABLE void sync(int timeout = -1); - Q_INVOKABLE void stopSync(); - // Old API that will be abolished any time soon. DO NOT USE. /** @deprecated Use callApi() or Room::postMessage() instead */ @@ -113,6 +100,7 @@ namespace QMatrixClient Q_INVOKABLE User* user(const QString& userId); Q_INVOKABLE User* user(); Q_INVOKABLE QString userId() const; + Q_INVOKABLE const QString& deviceId() const; /** @deprecated Use accessToken() instead. */ Q_INVOKABLE QString token() const; Q_INVOKABLE QString accessToken() const; @@ -185,6 +173,21 @@ namespace QMatrixClient [](Connection* c, const QString& id) { return new T(id, c); }; } + public slots: + void resolveServer(const QString& domain); + void connectToServer(const QString& user, const QString& password, + const QString& initialDeviceName, + const QString& deviceId = {}); + void connectWithToken(const QString& userId, const QString& accessToken, + const QString& deviceId); + + /** @deprecated Use stopSync() instead */ + void disconnectFromServer() { stopSync(); } + void logout(); + + void sync(int timeout = -1); + void stopSync(); + signals: void resolved(); void connected(); diff --git a/connectiondata.cpp b/connectiondata.cpp index 6f15577e..9b9b6e04 100644 --- a/connectiondata.cpp +++ b/connectiondata.cpp @@ -35,6 +35,7 @@ struct ConnectionData::Private QUrl baseUrl; QString accessToken; QString lastEvent; + QString deviceId; mutable unsigned int txnCounter = 0; const qint64 id = QDateTime::currentMSecsSinceEpoch(); @@ -83,6 +84,17 @@ void ConnectionData::setPort(int port) qCDebug(MAIN) << "updated baseUrl to" << d->baseUrl; } +const QString& ConnectionData::deviceId() const +{ + return d->deviceId; +} + +void ConnectionData::setDeviceId(const QString& deviceId) +{ + d->deviceId = deviceId; + qCDebug(MAIN) << "updated deviceId to" << d->deviceId; +} + QString ConnectionData::lastEvent() const { return d->lastEvent; diff --git a/connectiondata.h b/connectiondata.h index 7b0097d6..52a7461c 100644 --- a/connectiondata.h +++ b/connectiondata.h @@ -32,11 +32,13 @@ namespace QMatrixClient QString accessToken() const; QUrl baseUrl() const; + const QString& deviceId() const; QNetworkAccessManager* nam() const; void setToken( QString accessToken ); void setHost( QString host ); void setPort( int port ); + void setDeviceId(const QString& deviceId); QString lastEvent() const; void setLastEvent( QString identifier ); diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index a6da6aba..dc0c94e4 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -35,7 +35,7 @@ int main(int argc, char* argv[]) return -1; auto conn = new Connection(QUrl("https://matrix.org")); - conn->connectToServer(argv[1], argv[2]); + conn->connectToServer(argv[1], argv[2], "QMatrixClient example application"); QObject::connect(conn, &Connection::connected, [=] { cout << "Connected" << endl; conn->sync(); diff --git a/jobs/generated/login.cpp b/jobs/generated/login.cpp new file mode 100644 index 00000000..6e8294e7 --- /dev/null +++ b/jobs/generated/login.cpp @@ -0,0 +1,89 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#include "login.h" + +#include "jobs/converters.h" +#include + +using namespace QMatrixClient; + +static const auto basePath = QStringLiteral("/_matrix/client/r0"); + +class LoginJob::Private +{ + public: + QString user_id; + QString access_token; + QString home_server; + QString device_id; + +}; + +LoginJob::LoginJob(QString type, QString user, QString medium, QString address, QString password, QString token, QString device_id, QString initial_device_display_name) + : BaseJob(HttpVerb::Post, "LoginJob", + basePath % "/login", + Query { }, Data { }, false + ), d(new Private) +{ + Data _data; + _data.insert("type", toJson(type)); + if (!user.isEmpty()) + _data.insert("user", toJson(user)); + if (!medium.isEmpty()) + _data.insert("medium", toJson(medium)); + if (!address.isEmpty()) + _data.insert("address", toJson(address)); + if (!password.isEmpty()) + _data.insert("password", toJson(password)); + if (!token.isEmpty()) + _data.insert("token", toJson(token)); + if (!device_id.isEmpty()) + _data.insert("device_id", toJson(device_id)); + if (!initial_device_display_name.isEmpty()) + _data.insert("initial_device_display_name", toJson(initial_device_display_name)); + setRequestData(_data); +} + +LoginJob::~LoginJob() +{ + delete d; +} + +const QString& LoginJob::user_id() const +{ + return d->user_id; +} + +const QString& LoginJob::access_token() const +{ + return d->access_token; +} + +const QString& LoginJob::home_server() const +{ + return d->home_server; +} + +const QString& LoginJob::device_id() const +{ + return d->device_id; +} + +BaseJob::Status LoginJob::parseJson(const QJsonDocument& data) +{ + auto json = data.object(); + + d->user_id = fromJson(json.value("user_id")); + + d->access_token = fromJson(json.value("access_token")); + + d->home_server = fromJson(json.value("home_server")); + + d->device_id = fromJson(json.value("device_id")); + + return Success; +} + diff --git a/jobs/generated/login.h b/jobs/generated/login.h new file mode 100644 index 00000000..dc89206d --- /dev/null +++ b/jobs/generated/login.h @@ -0,0 +1,38 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#pragma once + +#include "../basejob.h" + +#include + + +namespace QMatrixClient +{ + + // Operations + + class LoginJob : public BaseJob + { + public: + explicit LoginJob(QString type, QString user = {}, QString medium = {}, QString address = {}, QString password = {}, QString token = {}, QString device_id = {}, QString initial_device_display_name = {}); + + ~LoginJob() override; + + const QString& user_id() const; + const QString& access_token() const; + const QString& home_server() const; + const QString& device_id() const; + + protected: + Status parseJson(const QJsonDocument& data) override; + + private: + class Private; + Private* d; + }; + +} // namespace QMatrixClient diff --git a/settings.cpp b/settings.cpp index fbcd845f..3a5f4d26 100644 --- a/settings.cpp +++ b/settings.cpp @@ -5,9 +5,6 @@ using namespace QMatrixClient; -Settings::~Settings() -{ } - void Settings::setValue(const QString& key, const QVariant& value) { // qCDebug() << "Setting" << key << "to" << value; @@ -19,9 +16,6 @@ QVariant Settings::value(const QString& key, const QVariant& defaultValue) const return QSettings::value(key, defaultValue); } -SettingsGroup::~SettingsGroup() -{ } - void SettingsGroup::setValue(const QString& key, const QVariant& value) { Settings::setValue(groupPath + "/" + key, value); @@ -58,9 +52,6 @@ void SettingsGroup::remove(const QString& key) Settings::remove(fullKey); } -AccountSettings::~AccountSettings() -{ } - bool AccountSettings::keepLoggedIn() const { return value("keep_logged_in", false).toBool(); @@ -86,6 +77,26 @@ QString AccountSettings::userId() const return group().section('/', -1); } +QString AccountSettings::deviceId() const +{ + return value("device_id").toString(); +} + +void AccountSettings::setDeviceId(const QString& deviceId) +{ + setValue("device_id", deviceId); +} + +QString AccountSettings::deviceName() const +{ + return value("device_name").toString(); +} + +void AccountSettings::setDeviceName(const QString& deviceName) +{ + setValue("device_name", deviceName); +} + QString AccountSettings::accessToken() const { return value("access_token").toString(); diff --git a/settings.h b/settings.h index eab0679a..a6c0420e 100644 --- a/settings.h +++ b/settings.h @@ -36,7 +36,6 @@ namespace QMatrixClient #else using QSettings::QSettings; #endif - virtual ~Settings(); Q_INVOKABLE void setValue(const QString &key, const QVariant &value); @@ -52,7 +51,6 @@ namespace QMatrixClient : Settings(qsettingsArgs...) , groupPath(path) { } - virtual ~SettingsGroup(); Q_INVOKABLE bool contains(const QString& key) const; Q_INVOKABLE QVariant value(const QString &key, @@ -72,6 +70,8 @@ namespace QMatrixClient { Q_OBJECT Q_PROPERTY(QString userId READ userId) + Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId) + Q_PROPERTY(QString deviceName READ deviceName WRITE setDeviceName) Q_PROPERTY(QUrl homeserver READ homeserver WRITE setHomeserver) Q_PROPERTY(bool keepLoggedIn READ keepLoggedIn WRITE setKeepLoggedIn) Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken) @@ -80,10 +80,15 @@ namespace QMatrixClient explicit AccountSettings(const QString& accountId, ArgTs... qsettingsArgs) : SettingsGroup("Accounts/" + accountId, qsettingsArgs...) { } - virtual ~AccountSettings(); QString userId() const; + QString deviceId() const; + void setDeviceId(const QString& deviceId); + + QString deviceName() const; + void setDeviceName(const QString& deviceName); + QUrl homeserver() const; void setHomeserver(const QUrl& url); @@ -94,4 +99,4 @@ namespace QMatrixClient void setAccessToken(const QString& accessToken); Q_INVOKABLE void clearAccessToken(); }; -} +} // namespace QMatrixClient -- cgit v1.2.3 From f2e59227aa325e060319e8ee86b8368bef4f4240 Mon Sep 17 00:00:00 2001 From: Roman Plášil Date: Fri, 20 Oct 2017 16:18:25 +0800 Subject: Try using wildcard for qmake project file --- libqmatrixclient.pri | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index 36f6429c..dcdf1709 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -24,14 +24,11 @@ HEADERS += \ $$PWD/jobs/sendeventjob.h \ $$PWD/jobs/postreceiptjob.h \ $$PWD/jobs/joinroomjob.h \ - $$PWD/jobs/leaveroomjob.h \ $$PWD/jobs/roommessagesjob.h \ $$PWD/jobs/syncjob.h \ $$PWD/jobs/mediathumbnailjob.h \ - $$PWD/jobs/logoutjob.h \ $$PWD/jobs/setroomstatejob.h \ - $$PWD/jobs/generated/inviting.h \ - $$PWD/jobs/generated/kicking.h \ + $$files($$PWD/jobs/generated/*.h, false) \ $$PWD/logging.h \ $$PWD/settings.h @@ -55,13 +52,10 @@ SOURCES += \ $$PWD/jobs/sendeventjob.cpp \ $$PWD/jobs/postreceiptjob.cpp \ $$PWD/jobs/joinroomjob.cpp \ - $$PWD/jobs/leaveroomjob.cpp \ $$PWD/jobs/roommessagesjob.cpp \ $$PWD/jobs/syncjob.cpp \ $$PWD/jobs/mediathumbnailjob.cpp \ - $$PWD/jobs/logoutjob.cpp \ $$PWD/jobs/setroomstatejob.cpp \ - $$PWD/jobs/generated/inviting.cpp \ - $$PWD/jobs/generated/kicking.cpp \ + $$files($$PWD/jobs/generated/*.cpp, false) \ $$PWD/logging.cpp \ $$PWD/settings.cpp -- cgit v1.2.3 From 514fa7512ea23691f7ae7e23257e752a0b758b50 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 20 Oct 2017 19:31:41 +0900 Subject: Cleanup --- events/roommessageevent.h | 2 +- util.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/events/roommessageevent.h b/events/roommessageevent.h index 74e0defb..ad413943 100644 --- a/events/roommessageevent.h +++ b/events/roommessageevent.h @@ -76,7 +76,7 @@ namespace QMatrixClient protected: using Base::Base; - virtual void fillInfoJson(QJsonObject* infoJson) const { } + virtual void fillInfoJson(QJsonObject* /*infoJson*/) const { } }; } // namespace MessageEventContent diff --git a/util.h b/util.h index 0c8f3640..09c58087 100644 --- a/util.h +++ b/util.h @@ -39,8 +39,8 @@ namespace QMatrixClient { public: Owning() = default; - Owning(Owning&) = delete; - Owning(Owning&& other) = default; + Owning(const Owning&) = delete; + Owning(Owning&&) = default; Owning& operator=(Owning&& other) { assign(other.release()); -- cgit v1.2.3 From 34faa56649f04fafd6ace276ba186070f41901be Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 20 Oct 2017 20:16:23 +0900 Subject: Better support of qmake qmc-example.pro added; better CONFIG for libqmatrixclient; refreshed files list. Closes #104 --- .travis.yml | 2 ++ libqmatrixclient.pri | 4 ++-- qmc-example.pro | 7 +++++++ 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 qmc-example.pro diff --git a/.travis.yml b/.travis.yml index 24a182cf..313d1408 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,8 @@ install: - cmake .. script: - cmake --build . --target all + - cd .. + - qmake qmc-example.pro && make all notifications: webhooks: urls: diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index dcdf1709..e974bda2 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -1,5 +1,5 @@ QT += network -CONFIG += c++11 +CONFIG += c++11 warn_on rtti_off INCLUDEPATH += $$PWD @@ -8,7 +8,7 @@ HEADERS += \ $$PWD/connection.h \ $$PWD/room.h \ $$PWD/user.h \ - $$PWD/state.h \ + $$PWD/util.h \ $$PWD/events/event.h \ $$PWD/events/roommessageevent.h \ $$PWD/events/roomnameevent.h \ diff --git a/qmc-example.pro b/qmc-example.pro new file mode 100644 index 00000000..4dc3fed1 --- /dev/null +++ b/qmc-example.pro @@ -0,0 +1,7 @@ +TEMPLATE = app + +windows { CONFIG += console } + +include(libqmatrixclient.pri) + +SOURCES += examples/qmc-example.cpp -- cgit v1.2.3 From 6600905fb0704e0d22eb776167750f341e7f3d98 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 25 Oct 2017 15:55:29 +0900 Subject: Fixed a typo in the logs --- jobs/basejob.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jobs/basejob.cpp b/jobs/basejob.cpp index eb78dec8..aa47f63c 100644 --- a/jobs/basejob.cpp +++ b/jobs/basejob.cpp @@ -84,7 +84,7 @@ QDebug QMatrixClient::operator<<(QDebug dbg, const BaseJob::Status& s) { QRegularExpression filter { "(access_token)=[-_A-Za-z0-9]+" }; return dbg << s.code << ':' - << QString(s.message).replace(filter, "\1=HIDDEN"); + << QString(s.message).replace(filter, "\\1=HIDDEN"); } BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, -- cgit v1.2.3 From 2fb03272a8bc7da4943347ea7ecca6070f667bd6 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 26 Oct 2017 19:40:59 +0300 Subject: Move out common message event content classes ImageContent is usable outside of m.room.message (in particular, m.room.avatar uses the same structure for content. And EventContent::Base is very suitable to derive from even for standard event content structures (such as in room name events), let alone non-standard ones. Also, renamed MessageEventContent to EventContent (for obvious reasons). --- CMakeLists.txt | 1 + events/eventcontent.cpp | 71 +++++++++++++ events/eventcontent.h | 245 ++++++++++++++++++++++++++++++++++++++++++++ events/roommessageevent.cpp | 48 +-------- events/roommessageevent.h | 232 ++--------------------------------------- 5 files changed, 327 insertions(+), 270 deletions(-) create mode 100644 events/eventcontent.cpp create mode 100644 events/eventcontent.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2abf7e69..0395774a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,7 @@ set(libqmatrixclient_SRCS user.cpp settings.cpp events/event.cpp + events/eventcontent.cpp events/roommessageevent.cpp events/roomnameevent.cpp events/roomaliasesevent.cpp diff --git a/events/eventcontent.cpp b/events/eventcontent.cpp new file mode 100644 index 00000000..205d404b --- /dev/null +++ b/events/eventcontent.cpp @@ -0,0 +1,71 @@ +/****************************************************************************** + * Copyright (C) 2017 Kitsune Ral + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "eventcontent.h" + +#include +#include + +using namespace QMatrixClient::EventContent; + +QJsonObject Base::toJson() const +{ + QJsonObject o; + fillJson(&o); + return o; +} + +QJsonObject InfoBase::toInfoJson() const +{ + QJsonObject info; + fillInfoJson(&info); + return info; +} + +FileInfo::FileInfo(const QUrl& u, int payloadSize, const QMimeType& mimeType, + const QString& originalFilename) + : InfoBase(mimeType), url(u), payloadSize(payloadSize) + , originalName(originalFilename) +{ } + +FileInfo::FileInfo(const QUrl& u, const QJsonObject& infoJson, + const QString& originalFilename) + : FileInfo(u, infoJson["size"].toInt(), + QMimeDatabase().mimeTypeForName(infoJson["mimetype"].toString()), + originalFilename) +{ + if (!mimeType.isValid()) + mimeType = QMimeDatabase().mimeTypeForData(QByteArray()); +} + +void FileInfo::fillInfoJson(QJsonObject* infoJson) const +{ + Q_ASSERT(infoJson); + infoJson->insert("size", payloadSize); + infoJson->insert("mimetype", mimeType.name()); +} + +void FileInfo::fillJson(QJsonObject* json) const +{ + Q_ASSERT(json); + json->insert("url", url.toString()); + if (!originalName.isEmpty()) + json->insert("filename", originalName); + json->insert("info", toInfoJson()); +} + diff --git a/events/eventcontent.h b/events/eventcontent.h new file mode 100644 index 00000000..2c8d7f3f --- /dev/null +++ b/events/eventcontent.h @@ -0,0 +1,245 @@ +/****************************************************************************** + * Copyright (C) 2017 Kitsune Ral + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +// This file contains generic event content definitions, applicable to room +// message events as well as other events (e.g., avatars). + +#include +#include +#include +#include + +namespace QMatrixClient +{ + namespace EventContent + { + /** + * A base class for all content types that can be stored + * in a RoomMessageEvent + * + * Each content type class should have a constructor taking + * a QJsonObject and override fillJson() with an implementation + * that will fill the target QJsonObject with stored values. It is + * assumed but not required that a content object can also be created + * from plain data. fillJson() should only fill the main JSON object + * but not the "info" subobject if it exists for a certain content type; + * use \p InfoBase to de/serialize "info" parts with an optional URL + * on the top level. + */ + class Base + { + public: + virtual ~Base() = default; + + QJsonObject toJson() const; + + QMimeType mimeType; + + protected: + Base() = default; + explicit Base(const QMimeType& type) : mimeType(type) { } + + virtual void fillJson(QJsonObject* o) const = 0; + }; + + /** + * A base class for content types that have an "info" object in their + * JSON representation + * + * These include most multimedia types currently in the CS API spec. + * Derived classes should override fillInfoJson() to fill the "info" + * subobject, BUT NOT the main JSON object. Most but not all "info" + * classes (specifically, those deriving from FileInfo) should also + * have a constructor that accepts two parameters, QUrl and QJsonObject, + * in order to load the URL+info part from JSON. + */ + class InfoBase: public Base + { + public: + QJsonObject toInfoJson() const; + + protected: + using Base::Base; + + virtual void fillInfoJson(QJsonObject* /*infoJson*/) const { } + }; + + // The below structures fairly follow CS spec 11.2.1.6. The overall + // set of attributes for each content types is a superset of the spec + // but specific aggregation structure is altered. See doc comments to + // each type for the list of available attributes. + + /** + * Base class for content types that consist of a URL along with + * additional information. Most of message types except textual fall + * under this category. + */ + class FileInfo: public InfoBase + { + public: + explicit FileInfo(const QUrl& u, int payloadSize = -1, + const QMimeType& mimeType = {}, + const QString& originalFilename = {}); + FileInfo(const QUrl& u, const QJsonObject& infoJson, + const QString& originalFilename = {}); + + QUrl url; + int payloadSize; + QString originalName; + + protected: + void fillJson(QJsonObject* json) const override; + void fillInfoJson(QJsonObject* infoJson) const override; + }; + + /** + * A base class for image info types: image, thumbnail, video + * + * \tparam InfoT base info class; should derive from \p InfoBase + */ + template + class ImageInfo : public InfoT + { + public: + explicit ImageInfo(const QUrl& u, int fileSize = -1, + QMimeType mimeType = {}, + const QSize& imageSize = {}) + : InfoT(u, fileSize, mimeType), imageSize(imageSize) + { } + ImageInfo(const QUrl& u, const QJsonObject& infoJson, + const QString& originalFilename = {}) + : InfoT(u, infoJson, originalFilename) + , imageSize(infoJson["w"].toInt(), infoJson["h"].toInt()) + { } + + void fillInfoJson(QJsonObject* infoJson) const override + { + InfoT::fillInfoJson(infoJson); + infoJson->insert("w", imageSize.width()); + infoJson->insert("h", imageSize.height()); + } + + QSize imageSize; + }; + + /** + * A base class for an info type that carries a thumbnail + * + * This class decorates the underlying type, adding ability to save/load + * a thumbnail to/from "info" subobject of the JSON representation of + * event content; namely, "info/thumbnail_url" and "info/thumbnail_info" + * fields are used. + * + * \tparam InfoT base info class; should derive from \p InfoBase + */ + template + class Thumbnailed : public InfoT + { + public: + template + explicit Thumbnailed(const ImageInfo<>& thumbnail, + ArgTs&&... infoArgs) + : InfoT(std::forward(infoArgs)...) + , thumbnail(thumbnail) + { } + + explicit Thumbnailed(const QJsonObject& infoJson) + : thumbnail(infoJson["thumbnail_url"].toString(), + infoJson["thumbnail_info"].toObject()) + { } + + Thumbnailed(const QUrl& u, const QJsonObject& infoJson, + const QString& originalFilename = {}) + : InfoT(u, infoJson, originalFilename) + , thumbnail(infoJson["thumbnail_url"].toString(), + infoJson["thumbnail_info"].toObject()) + { } + + void fillInfoJson(QJsonObject* infoJson) const override + { + InfoT::fillInfoJson(infoJson); + infoJson->insert("thumbnail_url", thumbnail.url.toString()); + infoJson->insert("thumbnail_info", thumbnail.toInfoJson()); + } + + ImageInfo<> thumbnail; + }; + + /** + * One more facility base class for content types that have a URL and + * additional info + * + * Types that derive from UrlWith take "url" and, optionally, + * "filename" values from the top-level JSON object and the rest of + * information from the "info" subobject. + * + * \tparam InfoT base info class; should derive from \p FileInfo or + * provide a constructor with a compatible signature + */ + template // InfoT : public FileInfo + class UrlWith : public InfoT + { + public: + using InfoT::InfoT; + explicit UrlWith(const QJsonObject& json) + : InfoT(json["url"].toString(), json["info"].toObject(), + json["filename"].toString()) + { } + }; + + /** + * Content class for m.image + * + * Available fields: + * - corresponding to the top-level JSON: + * - url + * - filename (extension to the spec) + * - corresponding to the "info" subobject: + * - payloadSize ("size" in JSON) + * - mimeType ("mimetype" in JSON) + * - imageSize (QSize for a combination of "h" and "w" in JSON) + * - thumbnail.url ("thumbnail_url" in JSON) + * - corresponding to the "info/thumbnail_info" subobject: contents of + * thumbnail field, in the same vein as for the main image: + * - payloadSize + * - mimeType + * - imageSize + */ + using ImageContent = UrlWith>>; + + /** + * Content class for m.file + * + * Available fields: + * - corresponding to the top-level JSON: + * - url + * - filename + * - corresponding to the "info" subobject: + * - payloadSize ("size" in JSON) + * - mimeType ("mimetype" in JSON) + * - thumbnail.url ("thumbnail_url" in JSON) + * - corresponding to the "info/thumbnail_info" subobject: + * - thumbnail.payloadSize + * - thumbnail.mimeType + * - thumbnail.imageSize (QSize for "h" and "w" in JSON) + */ + using FileContent = UrlWith>; + } // namespace EventContent +} // namespace QMatrixClient diff --git a/events/roommessageevent.cpp b/events/roommessageevent.cpp index 3fb0226a..82bd07b6 100644 --- a/events/roommessageevent.cpp +++ b/events/roommessageevent.cpp @@ -23,7 +23,7 @@ #include using namespace QMatrixClient; -using namespace MessageEventContent; +using namespace EventContent; using MsgType = RoomMessageEvent::MsgType; @@ -124,20 +124,6 @@ QJsonObject RoomMessageEvent::toJson() const return obj; } -QJsonObject Base::toJson() const -{ - QJsonObject o; - fillJson(&o); - return o; -} - -QJsonObject InfoBase::toInfoJson() const -{ - QJsonObject info; - fillInfoJson(&info); - return info; -} - TextContent::TextContent(const QString& text, const QString& contentType) : Base(QMimeDatabase().mimeTypeForName(contentType)), body(text) { } @@ -167,38 +153,6 @@ void TextContent::fillJson(QJsonObject* json) const json->insert("formatted_body", body); } -FileInfo::FileInfo(const QUrl& u, int payloadSize, const QMimeType& mimeType, - const QString& originalFilename) - : InfoBase(mimeType), url(u), payloadSize(payloadSize) - , originalName(originalFilename) -{ } - -FileInfo::FileInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename) - : FileInfo(u, infoJson["size"].toInt(), - QMimeDatabase().mimeTypeForName(infoJson["mimetype"].toString()), - originalFilename) -{ - if (!mimeType.isValid()) - mimeType = QMimeDatabase().mimeTypeForData(QByteArray()); -} - -void FileInfo::fillInfoJson(QJsonObject* infoJson) const -{ - Q_ASSERT(infoJson); - infoJson->insert("size", payloadSize); - infoJson->insert("mimetype", mimeType.name()); -} - -void FileInfo::fillJson(QJsonObject* json) const -{ - Q_ASSERT(json); - json->insert("url", url.toString()); - if (!originalName.isEmpty()) - json->insert("filename", originalName); - json->insert("info", toInfoJson()); -} - LocationContent::LocationContent(const QString& geoUri, const ImageInfo<>& thumbnail) : Thumbnailed<>(thumbnail), geoUri(geoUri) diff --git a/events/roommessageevent.h b/events/roommessageevent.h index ad413943..b98f12d6 100644 --- a/events/roommessageevent.h +++ b/events/roommessageevent.h @@ -20,65 +20,11 @@ #include "event.h" -#include -#include -#include +#include "eventcontent.h" namespace QMatrixClient { - namespace MessageEventContent - { - /** - * A base class for all content types that can be stored - * in a RoomMessageEvent - * - * Each content type class should have a constructor taking - * a QJsonObject and override fillJson() with an implementation - * that will fill the target QJsonObject with stored values. It is - * assumed but not required that a content object can also be created - * from plain data. fillJson() should only fill the main JSON object - * but not the "info" subobject if it exists for a certain content type; - * use \p InfoBase to de/serialize "info" parts with an optional URL - * on the top level. - */ - class Base - { - public: - virtual ~Base() = default; - - QJsonObject toJson() const; - - QMimeType mimeType; - - protected: - Base() = default; - explicit Base(const QMimeType& type) : mimeType(type) { } - - virtual void fillJson(QJsonObject* o) const = 0; - }; - - /** - * A base class for content types that have an "info" object in their - * JSON representation - * - * These include most multimedia types currently in the CS API spec. - * Derived classes should override fillInfoJson() to fill the "info" - * subobject, BUT NOT the main JSON object. Most but not all "info" - * classes (specifically, those deriving from UrlInfo) should also - * have a constructor that accepts two parameters, QUrl and QJsonObject, - * in order to load the URL+info part from JSON. - */ - class InfoBase: public Base - { - public: - QJsonObject toInfoJson() const; - - protected: - using Base::Base; - - virtual void fillInfoJson(QJsonObject* /*infoJson*/) const { } - }; - } // namespace MessageEventContent + namespace MessageEventContent = EventContent; // Back-compatibility /** * The event class corresponding to m.room.message events @@ -94,19 +40,19 @@ namespace QMatrixClient RoomMessageEvent(const QString& plainBody, const QString& jsonMsgType, - MessageEventContent::Base* content = nullptr) + EventContent::Base* content = nullptr) : RoomEvent(Type::RoomMessage) , _msgtype(jsonMsgType), _plainBody(plainBody), _content(content) { } explicit RoomMessageEvent(const QString& plainBody, MsgType msgType = MsgType::Text, - MessageEventContent::Base* content = nullptr); + EventContent::Base* content = nullptr); explicit RoomMessageEvent(const QJsonObject& obj); MsgType msgtype() const; QString rawMsgtype() const { return _msgtype; } const QString& plainBody() const { return _plainBody; } - const MessageEventContent::Base* content() const + const EventContent::Base* content() const { return _content.data(); } QMimeType mimeType() const; @@ -117,18 +63,15 @@ namespace QMatrixClient private: QString _msgtype; QString _plainBody; - QScopedPointer _content; + QScopedPointer _content; REGISTER_ENUM(MsgType) }; using MessageEventType = RoomMessageEvent::MsgType; - namespace MessageEventContent + namespace EventContent { - // The below structures fairly follow CS spec 11.2.1.6. The overall - // set of attributes for each content types is a superset of the spec - // but specific aggregation structure is altered. See doc comments to - // each type for the list of available attributes. + // Additional event content types /** * Rich text content for m.text, m.emote, m.notice @@ -147,163 +90,6 @@ namespace QMatrixClient QString body; }; - /** - * Base class for content types that consist of a URL along with - * additional information - * - * All message types except the (hyper)text mentioned above and - * m.location fall under this category. - */ - class FileInfo: public InfoBase - { - public: - explicit FileInfo(const QUrl& u, int payloadSize = -1, - const QMimeType& mimeType = {}, - const QString& originalFilename = {}); - FileInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename = {}); - - QUrl url; - int payloadSize; - QString originalName; - - protected: - void fillJson(QJsonObject* json) const override; - void fillInfoJson(QJsonObject* infoJson) const override; - }; - - /** - * A base class for image info types: image, thumbnail, video - * - * \tparam InfoT base info class; should derive from \p InfoBase - */ - template - class ImageInfo : public InfoT - { - public: - explicit ImageInfo(const QUrl& u, int fileSize = -1, - QMimeType mimeType = {}, - const QSize& imageSize = {}) - : InfoT(u, fileSize, mimeType), imageSize(imageSize) - { } - ImageInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename = {}) - : InfoT(u, infoJson, originalFilename) - , imageSize(infoJson["w"].toInt(), infoJson["h"].toInt()) - { } - - void fillInfoJson(QJsonObject* infoJson) const /* override */ - { - InfoT::fillInfoJson(infoJson); - infoJson->insert("w", imageSize.width()); - infoJson->insert("h", imageSize.height()); - } - - QSize imageSize; - }; - - /** - * A base class for an info type that carries a thumbnail - * - * This class provides a means to save/load a thumbnail to/from "info" - * subobject of the JSON representation of a message; namely, - * "info/thumbnail_url" and "info/thumbnail_info" fields are used. - * - * \tparam InfoT base info class; should derive from \p InfoBase - */ - template - class Thumbnailed : public InfoT - { - public: - template - explicit Thumbnailed(const ImageInfo<>& thumbnail, - ArgTs&&... infoArgs) - : InfoT(std::forward(infoArgs)...) - , thumbnail(thumbnail) - { } - - explicit Thumbnailed(const QJsonObject& infoJson) - : thumbnail(infoJson["thumbnail_url"].toString(), - infoJson["thumbnail_info"].toObject()) - { } - - Thumbnailed(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename = {}) - : InfoT(u, infoJson, originalFilename) - , thumbnail(infoJson["thumbnail_url"].toString(), - infoJson["thumbnail_info"].toObject()) - { } - - void fillInfoJson(QJsonObject* infoJson) const /* override */ - { - InfoT::fillInfoJson(infoJson); - infoJson->insert("thumbnail_url", thumbnail.url.toString()); - infoJson->insert("thumbnail_info", thumbnail.toInfoJson()); - } - - ImageInfo<> thumbnail; - }; - - /** - * One more facility base class for content types that have a URL and - * additional info - * - * The assumed layout for types enabled by a combination of UrlInfo and - * UrlWith<> is the following: "url" and, optionally, "filename" in the - * top-level JSON and the rest of information inside the "info" subobject. - * - * \tparam InfoT base info class; should derive from \p UrlInfo or - * provide a constructor with a compatible signature - */ - template // InfoT : public FileInfo - class UrlWith : public InfoT - { - public: - using InfoT::InfoT; - explicit UrlWith(const QJsonObject& json) - : InfoT(json["url"].toString(), json["info"].toObject(), - json["filename"].toString()) - { } - }; - - /** - * Content class for m.image - * - * Available fields: - * - corresponding to the top-level JSON: - * - url - * - filename (extension to the spec) - * - corresponding to the "info" subobject: - * - payloadSize ("size" in JSON) - * - mimeType ("mimetype" in JSON) - * - imageSize (QSize for a combination of "h" and "w" in JSON) - * - thumbnail.url ("thumbnail_url" in JSON) - * - corresponding to the "info/thumbnail_info" subobject: contents of - * thumbnail field, in the same vein as for the main image: - * - payloadSize - * - mimeType - * - imageSize - */ - using ImageContent = UrlWith>>; - - /** - * Content class for m.file - * - * Available fields: - * - corresponding to the top-level JSON: - * - url - * - filename - * - corresponding to the "info" subobject: - * - payloadSize ("size" in JSON) - * - mimeType ("mimetype" in JSON) - * - thumbnail.url ("thumbnail_url" in JSON) - * - corresponding to the "info/thumbnail_info" subobject: - * - thumbnail.payloadSize - * - thumbnail.mimeType - * - thumbnail.imageSize (QSize for "h" and "w" in JSON) - */ - using FileContent = UrlWith>; - /** * Content class for m.location * @@ -380,5 +166,5 @@ namespace QMatrixClient * - duration */ using AudioContent = UrlWith; - } // namespace MessageEventContent + } // namespace EventContent } // namespace QMatrixClient -- cgit v1.2.3 From 0ce284eeca96ac92524a390837b551bebb5431cc Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 26 Oct 2017 19:53:24 +0300 Subject: Move out the avatar code from User Avatars are also a property of rooms, and the supporting code is basically the same. The only thing different will be emitted signals, and the cleanest thing to support that (aside from making Avatar a QObject) seems to be to parameterise the thumbnail-updating logic with a continuation invoked upon completion of the thumbnail job. --- CMakeLists.txt | 1 + avatar.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++ avatar.h | 57 +++++++++++++++++++++++++++++++++++ user.cpp | 93 ++++++++-------------------------------------------------- user.h | 5 ++-- 5 files changed, 152 insertions(+), 84 deletions(-) create mode 100644 avatar.cpp create mode 100644 avatar.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0395774a..fc3276c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ set(libqmatrixclient_SRCS logging.cpp room.cpp user.cpp + avatar.cpp settings.cpp events/event.cpp events/eventcontent.cpp diff --git a/avatar.cpp b/avatar.cpp new file mode 100644 index 00000000..0ce9210b --- /dev/null +++ b/avatar.cpp @@ -0,0 +1,80 @@ +/****************************************************************************** + * Copyright (C) 2017 Kitsune Ral + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avatar.h" + +#include "jobs/mediathumbnailjob.h" +#include "events/eventcontent.h" +#include "connection.h" + +using namespace QMatrixClient; + +QPixmap Avatar::get(int width, int height, std::function continuation) +{ + QSize size(width, height); + + // FIXME: Alternating between longer-width and longer-height requests + // is a sure way to trick the below code into constantly getting another + // image from the server because the existing one is alleged unsatisfactory. + // This is plain abuse by the client, though; so not critical for now. + if( (!_valid && _url.isValid() && !_ongoingRequest) + || width > _requestedSize.width() + || height > _requestedSize.height() ) + { + qCDebug(MAIN) << "Getting avatar from" << _url.toString(); + _requestedSize = size; + _ongoingRequest = _connection->callApi(_url, size); + _ongoingRequest->connect( _ongoingRequest, &MediaThumbnailJob::finished, + _connection, [=]() { + if (_ongoingRequest->status().good()) + { + _valid = true; + _originalPixmap = _ongoingRequest->scaledThumbnail(_requestedSize); + _scaledPixmaps.clear(); + continuation(); + } + _ongoingRequest = nullptr; + }); + } + + if( _originalPixmap.isNull() ) + { + if (_defaultIcon.isNull()) + return _originalPixmap; + + _originalPixmap = _defaultIcon.pixmap(size); + } + + auto& pixmap = _scaledPixmaps[{width, height}]; // Create if needed + if (pixmap.isNull()) + { + pixmap = _originalPixmap.scaled(size, + Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + return pixmap; +} + +bool Avatar::updateUrl(const QUrl& newUrl) +{ + if (newUrl == _url) + return false; + + _url = newUrl; + _valid = false; + return true; +} diff --git a/avatar.h b/avatar.h new file mode 100644 index 00000000..6889e839 --- /dev/null +++ b/avatar.h @@ -0,0 +1,57 @@ +/****************************************************************************** + * Copyright (C) 2017 Kitsune Ral + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include +#include + +#include + +namespace QMatrixClient +{ + class MediaThumbnailJob; + class Connection; + + class Avatar + { + public: + explicit Avatar(Connection* connection, QIcon defaultIcon = {}) + : _defaultIcon(std::move(defaultIcon)), _connection(connection) + { } + + QPixmap get(int w, int h, std::function continuation); + + QUrl url() const { return _url; } + bool updateUrl(const QUrl& newUrl); + + private: + QUrl _url; + QPixmap _originalPixmap; + QIcon _defaultIcon; + + /// Map of requested size to the actual pixmap used for it + /// (it's a shame that QSize has no predefined qHash()). + QHash, QPixmap> _scaledPixmaps; + + QSize _requestedSize; + bool _valid = false; + Connection* _connection; + MediaThumbnailJob* _ongoingRequest = nullptr; + }; +} // namespace QMatrixClient diff --git a/user.cpp b/user.cpp index aa1aa447..b2c0dc1e 100644 --- a/user.cpp +++ b/user.cpp @@ -19,14 +19,12 @@ #include "user.h" #include "connection.h" +#include "avatar.h" #include "events/event.h" #include "events/roommemberevent.h" -#include "jobs/mediathumbnailjob.h" #include "jobs/generated/profile.h" #include -#include -#include #include using namespace QMatrixClient; @@ -35,35 +33,20 @@ class User::Private { public: Private(QString userId, Connection* connection) - : q(nullptr), userId(std::move(userId)), connection(connection) - , defaultIcon(QIcon::fromTheme(QStringLiteral("user-available"))) - , avatarValid(false) , avatarOngoingRequest(false) + : userId(std::move(userId)), connection(connection) + , avatar(connection, QIcon::fromTheme(QStringLiteral("user-available"))) { } - User* q; QString userId; QString name; - QUrl avatarUrl; - Connection* connection; - - QPixmap avatar; - QIcon defaultIcon; - QSize requestedSize; - bool avatarValid; - bool avatarOngoingRequest; - /// Map of requested size to the actual pixmap used for it - /// (it's a shame that QSize has no predefined qHash()). - QHash, QPixmap> scaledAvatars; QString bridged; - - void requestAvatar(); + Connection* connection; + Avatar avatar; }; User::User(QString userId, Connection* connection) - : QObject(connection), d(new Private(userId, connection)) -{ - d->q = this; // Initialization finished -} + : QObject(connection), d(new Private(std::move(userId), connection)) +{ } User::~User() { @@ -109,43 +92,12 @@ QString User::bridged() const { QPixmap User::avatar(int width, int height) { - QSize size(width, height); - - // FIXME: Alternating between longer-width and longer-height requests - // is a sure way to trick the below code into constantly getting another - // image from the server because the existing one is alleged unsatisfactory. - // This is plain abuse by the client, though; so not critical for now. - if( (!d->avatarValid && d->avatarUrl.isValid() && !d->avatarOngoingRequest) - || width > d->requestedSize.width() - || height > d->requestedSize.height() ) - { - qCDebug(MAIN) << "Getting avatar for" << id() - << "from" << d->avatarUrl.toString(); - d->requestedSize = size; - d->avatarOngoingRequest = true; - QTimer::singleShot(0, this, SLOT(requestAvatar())); - } - - if( d->avatar.isNull() ) - { - if (d->defaultIcon.isNull()) - return d->avatar; - - d->avatar = d->defaultIcon.pixmap(size); - } - - auto& pixmap = d->scaledAvatars[{width, height}]; // Create the entry if needed - if (pixmap.isNull()) - { - pixmap = d->avatar.scaled(width, height, - Qt::KeepAspectRatio, Qt::SmoothTransformation); - } - return pixmap; + return d->avatar.get(width, height, [=] { emit avatarChanged(this); }); } -const QUrl& User::avatarUrl() const +QUrl User::avatarUrl() const { - return d->avatarUrl; + return d->avatar.url(); } void User::processEvent(Event* event) @@ -165,28 +117,7 @@ void User::processEvent(Event* event) newName.truncate(match.capturedStart(0)); } updateName(newName); - if( d->avatarUrl != e->avatarUrl() ) - { - d->avatarUrl = e->avatarUrl(); - d->avatarValid = false; - } + if (d->avatar.updateUrl(e->avatarUrl())) + emit avatarChanged(this); } } - -void User::requestAvatar() -{ - d->requestAvatar(); -} - -void User::Private::requestAvatar() -{ - auto* job = connection->callApi(avatarUrl, requestedSize); - connect( job, &MediaThumbnailJob::success, [=]() { - avatarOngoingRequest = false; - avatarValid = true; - avatar = job->scaledThumbnail(requestedSize); - scaledAvatars.clear(); - emit q->avatarChanged(q); - }); -} - diff --git a/user.h b/user.h index 79a6f5db..aee6ec3e 100644 --- a/user.h +++ b/user.h @@ -30,7 +30,7 @@ namespace QMatrixClient Q_OBJECT public: User(QString userId, Connection* connection); - virtual ~User(); + ~User() override; /** * Returns the id of the user @@ -54,12 +54,11 @@ namespace QMatrixClient QPixmap avatar(int requestedWidth, int requestedHeight); - const QUrl& avatarUrl() const; + QUrl avatarUrl() const; void processEvent(Event* event); public slots: - void requestAvatar(); void rename(const QString& newName); signals: -- cgit v1.2.3 From c287d335da0d2ae6609dd090d45a89c6b8974af0 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 27 Oct 2017 14:43:58 +0300 Subject: Fixed building with qmake --- libqmatrixclient.pri | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index e974bda2..9bcb911f 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -8,8 +8,10 @@ HEADERS += \ $$PWD/connection.h \ $$PWD/room.h \ $$PWD/user.h \ + $$PWD/avatar.h \ $$PWD/util.h \ $$PWD/events/event.h \ + $$PWD/events/eventcontent.h \ $$PWD/events/roommessageevent.h \ $$PWD/events/roomnameevent.h \ $$PWD/events/roomaliasesevent.h \ @@ -37,7 +39,9 @@ SOURCES += \ $$PWD/connection.cpp \ $$PWD/room.cpp \ $$PWD/user.cpp \ + $$PWD/avatar.cpp \ $$PWD/events/event.cpp \ + $$PWD/events/eventcontent.cpp \ $$PWD/events/roommessageevent.cpp \ $$PWD/events/roomnameevent.cpp \ $$PWD/events/roomaliasesevent.cpp \ -- cgit v1.2.3 From 0fb7adfcaa5dffee4efd0a34a2a4fd655fe5c709 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 27 Oct 2017 15:19:07 +0300 Subject: Support m.room.avatar events The events are detected in /sync output, and avatars for rooms are loaded from respective URLs. Clients can use Room::avatar() method to request a pixmap of a certain size, and react to avatarChanged() in order to update the UI when new pixmaps/avatars arrive. avatarChanged() signal is overloaded with two tasks - the first firing merely indicates that a new avatar is available (without actual pixmap yet available) while the second firing means that an actual pixmap has arrived (all this is entirely transparent for clients, they just should update their pixmaps from Room::avatar() every time when Room::avatarChanged() is emitted). --- CMakeLists.txt | 1 + events/event.cpp | 2 ++ events/event.h | 3 ++- events/roomavatarevent.cpp | 23 +++++++++++++++++++ events/roomavatarevent.h | 53 ++++++++++++++++++++++++++++++++++++++++++ libqmatrixclient.pri | 2 ++ room.cpp | 57 ++++++++++++++++++++++++++++++++++++++-------- room.h | 10 +++++++- user.cpp | 5 ++++ user.h | 2 ++ 10 files changed, 146 insertions(+), 12 deletions(-) create mode 100644 events/roomavatarevent.cpp create mode 100644 events/roomavatarevent.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fc3276c9..26c7bf46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,6 +70,7 @@ set(libqmatrixclient_SRCS events/roomcanonicalaliasevent.cpp events/roommemberevent.cpp events/roomtopicevent.cpp + events/roomavatarevent.cpp events/typingevent.cpp events/receiptevent.cpp events/encryptedevent.cpp diff --git a/events/event.cpp b/events/event.cpp index 304f2af6..9963a0ef 100644 --- a/events/event.cpp +++ b/events/event.cpp @@ -24,6 +24,7 @@ #include "roomcanonicalaliasevent.h" #include "roommemberevent.h" #include "roomtopicevent.h" +#include "roomavatarevent.h" #include "typingevent.h" #include "receiptevent.h" #include "encryptedevent.h" @@ -136,6 +137,7 @@ RoomEvent* RoomEvent::fromJson(const QJsonObject& obj) "m.room.canonical_alias", make, "m.room.member", make, "m.room.topic", make, + "m.room.avatar", make, "m.room.encryption", make, /* Insert new ROOM event types BEFORE this line */ nullptr diff --git a/events/event.h b/events/event.h index ec993522..c151ac0e 100644 --- a/events/event.h +++ b/events/event.h @@ -34,7 +34,8 @@ namespace QMatrixClient enum class Type { RoomMessage, RoomName, RoomAliases, RoomCanonicalAlias, - RoomMember, RoomTopic, RoomEncryption, RoomEncryptedMessage, + RoomMember, RoomTopic, RoomAvatar, + RoomEncryption, RoomEncryptedMessage, Typing, Receipt, Unknown }; diff --git a/events/roomavatarevent.cpp b/events/roomavatarevent.cpp new file mode 100644 index 00000000..7a5f82a1 --- /dev/null +++ b/events/roomavatarevent.cpp @@ -0,0 +1,23 @@ +/****************************************************************************** + * Copyright (C) 2017 Kitsune Ral + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "roomavatarevent.h" + +using namespace QMatrixClient; + + diff --git a/events/roomavatarevent.h b/events/roomavatarevent.h new file mode 100644 index 00000000..411de4e8 --- /dev/null +++ b/events/roomavatarevent.h @@ -0,0 +1,53 @@ +/****************************************************************************** + * Copyright (C) 2017 Kitsune Ral + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include "event.h" + +#include + +#include "eventcontent.h" + +namespace QMatrixClient +{ + class RoomAvatarEvent: public RoomEvent + { + public: + explicit RoomAvatarEvent(EventContent::ImageContent avatar) + : RoomEvent(Type::RoomAvatar), _avatar(std::move(avatar)) + { } + explicit RoomAvatarEvent(const QJsonObject& obj) + : RoomEvent(Type::RoomAvatar, obj), _avatar(contentJson()) + { } + + const EventContent::ImageContent& content() const { return _avatar; } + + QJsonObject toJson() const { return _avatar.toJson(); } + + static constexpr const char* TypeId = "m.room.avatar"; + + private: + // It's a bit of an overkill to use a full-fledged ImageContent + // because in reality m.room.avatar usually only has a single URL, + // without a thumbnail. But The Spec says there be thumbnails, and + // we follow The Spec. + EventContent::ImageContent _avatar; + }; + +} // namespace QMatrixClient diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index 9bcb911f..9eb6bd16 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -18,6 +18,7 @@ HEADERS += \ $$PWD/events/roomcanonicalaliasevent.h \ $$PWD/events/roommemberevent.h \ $$PWD/events/roomtopicevent.h \ + $$PWD/events/roomavatarevent.h \ $$PWD/events/typingevent.h \ $$PWD/events/receiptevent.h \ $$PWD/jobs/basejob.h \ @@ -48,6 +49,7 @@ SOURCES += \ $$PWD/events/roomcanonicalaliasevent.cpp \ $$PWD/events/roommemberevent.cpp \ $$PWD/events/roomtopicevent.cpp \ + $$PWD/events/roomavatarevent.cpp \ $$PWD/events/typingevent.cpp \ $$PWD/events/receiptevent.cpp \ $$PWD/jobs/basejob.cpp \ diff --git a/room.cpp b/room.cpp index 65cf2d2a..53a6b160 100644 --- a/room.cpp +++ b/room.cpp @@ -27,12 +27,14 @@ #include "events/roomaliasesevent.h" #include "events/roomcanonicalaliasevent.h" #include "events/roomtopicevent.h" +#include "events/roomavatarevent.h" #include "events/roommemberevent.h" #include "events/typingevent.h" #include "events/receiptevent.h" #include "jobs/sendeventjob.h" #include "jobs/roommessagesjob.h" #include "jobs/postreceiptjob.h" +#include "avatar.h" #include "connection.h" #include "user.h" @@ -53,7 +55,7 @@ class Room::Private Private(Connection* c, QString id_, JoinState initialJoinState) : q(nullptr), connection(c), id(std::move(id_)) - , joinState(initialJoinState), unreadMessages(false) + , avatar(c), joinState(initialJoinState), unreadMessages(false) , highlightCount(0), notificationCount(0), roomMessagesJob(nullptr) { } @@ -73,6 +75,7 @@ class Room::Private QString name; QString displayname; QString topic; + Avatar avatar; JoinState joinState; bool unreadMessages; int highlightCount; @@ -189,6 +192,23 @@ QString Room::topic() const return d->topic; } +QPixmap Room::avatar(int width, int height) +{ + if (!d->avatar.url().isEmpty()) + return d->avatar.get(width, height, [=] { emit avatarChanged(); }); + + // Use the other side's avatar for 1:1's + if (d->membersMap.size() == 2) + { + auto theOtherOneIt = d->membersMap.begin(); + if (theOtherOneIt.value() == localUser()) + ++theOtherOneIt; + return theOtherOneIt.value()->avatarObject() + .get(width, height, [=] { emit avatarChanged(); }); + } + return {}; +} + JoinState Room::joinState() const { return d->joinState; @@ -778,6 +798,17 @@ void Room::processStateEvents(const RoomEvents& events) emit topicChanged(); break; } + case EventType::RoomAvatar: { + const auto& avatarEventContent = + static_cast(event)->content(); + if (d->avatar.updateUrl(avatarEventContent.url)) + { + qCDebug(MAIN) << "Room avatar URL updated:" + << avatarEventContent.url.toString(); + emit avatarChanged(); + } + break; + } case EventType::RoomMember: { auto memberEvent = static_cast(event); // Can't use d->member() below because the user may be not a member (yet) @@ -939,9 +970,13 @@ void Room::Private::updateDisplayname() emit q->displaynameChanged(q); } -QJsonObject stateEventToJson(const QString& type, const QString& name, - const QJsonValue& content) +template +void appendEventJson(QJsonArray& events, const QString& type, + const QString& name, const T& content) { + if (content.isEmpty()) + return; + QJsonObject contentObj; contentObj.insert(name, content); @@ -949,7 +984,7 @@ QJsonObject stateEventToJson(const QString& type, const QString& name, eventObj.insert("type", type); eventObj.insert("content", contentObj); - return eventObj; + events.append(eventObj); } QJsonObject Room::Private::toJson() const @@ -958,12 +993,14 @@ QJsonObject Room::Private::toJson() const { QJsonArray stateEvents; - stateEvents.append(stateEventToJson("m.room.name", "name", name)); - stateEvents.append(stateEventToJson("m.room.topic", "topic", topic)); - stateEvents.append(stateEventToJson("m.room.aliases", "aliases", - QJsonArray::fromStringList(aliases))); - stateEvents.append(stateEventToJson("m.room.canonical_alias", "alias", - canonicalAlias)); + appendEventJson(stateEvents, "m.room.name", "name", name); + appendEventJson(stateEvents, "m.room.topic", "topic", topic); + appendEventJson(stateEvents, "m.room.avatar", "avatar_url", + avatar.url().toString()); + appendEventJson(stateEvents, "m.room.aliases", "aliases", + QJsonArray::fromStringList(aliases)); + appendEventJson(stateEvents, "m.room.canonical_alias", "alias", + canonicalAlias); for (const auto &i : membersMap) { diff --git a/room.h b/room.h index 2d0453bc..2fa7e7d5 100644 --- a/room.h +++ b/room.h @@ -25,6 +25,7 @@ #include #include #include +#include #include "jobs/syncjob.h" #include "events/roommessageevent.h" @@ -79,7 +80,7 @@ namespace QMatrixClient using rev_iter_t = Timeline::const_reverse_iterator; Room(Connection* connection, QString id, JoinState initialJoinState); - virtual ~Room(); + ~Room() override; Connection* connection() const; User* localUser() const; @@ -97,6 +98,12 @@ namespace QMatrixClient Q_INVOKABLE QStringList memberNames() const; Q_INVOKABLE int memberCount() const; + /** + * Returns a room avatar and requests it from the network if needed + * @return a pixmap with the avatar or a placeholder if there's none + * available yet + */ + Q_INVOKABLE QPixmap avatar(int width, int height); /** * @brief Produces a disambiguated name for a given user in * the context of the room @@ -181,6 +188,7 @@ namespace QMatrixClient /** @brief The room displayname changed */ void displaynameChanged(Room* room); void topicChanged(); + void avatarChanged(); void userAdded(User* user); void userRemoved(User* user); void memberRenamed(User* user); diff --git a/user.cpp b/user.cpp index b2c0dc1e..8c8a7fdb 100644 --- a/user.cpp +++ b/user.cpp @@ -90,6 +90,11 @@ QString User::bridged() const { return d->bridged; } +Avatar& User::avatarObject() +{ + return d->avatar; +} + QPixmap User::avatar(int width, int height) { return d->avatar.get(width, height, [=] { emit avatarChanged(this); }); diff --git a/user.h b/user.h index aee6ec3e..148ed64d 100644 --- a/user.h +++ b/user.h @@ -20,6 +20,7 @@ #include #include +#include "avatar.h" namespace QMatrixClient { @@ -52,6 +53,7 @@ namespace QMatrixClient */ Q_INVOKABLE QString bridged() const; + Avatar& avatarObject(); QPixmap avatar(int requestedWidth, int requestedHeight); QUrl avatarUrl() const; -- cgit v1.2.3 From 8214e671ec0e0df90f0b7cfa0304f0b6ac99d189 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 27 Oct 2017 16:11:38 +0300 Subject: Fix room avatars getting lost after restarting Quaternion --- room.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/room.cpp b/room.cpp index 53a6b160..4f58e393 100644 --- a/room.cpp +++ b/room.cpp @@ -995,7 +995,7 @@ QJsonObject Room::Private::toJson() const appendEventJson(stateEvents, "m.room.name", "name", name); appendEventJson(stateEvents, "m.room.topic", "topic", topic); - appendEventJson(stateEvents, "m.room.avatar", "avatar_url", + appendEventJson(stateEvents, "m.room.avatar", "url", avatar.url().toString()); appendEventJson(stateEvents, "m.room.aliases", "aliases", QJsonArray::fromStringList(aliases)); -- cgit v1.2.3 From a7f158d1cfdc5c572a62af5a6945e4a6b88e8253 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 27 Oct 2017 21:22:04 +0300 Subject: Put access token to headers instead of query --- connection.cpp | 2 +- connectiondata.cpp | 6 +++--- connectiondata.h | 4 ++-- jobs/basejob.cpp | 11 +++++------ 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/connection.cpp b/connection.cpp index 427118c9..fdcfa680 100644 --- a/connection.cpp +++ b/connection.cpp @@ -131,7 +131,7 @@ void Connection::connectWithToken(const QString& userId, const QString& accessToken, const QString& deviceId) { d->userId = userId; - d->data->setToken(accessToken); + d->data->setToken(accessToken.toLatin1()); d->data->setDeviceId(deviceId); qCDebug(MAIN) << "Using server" << d->data->baseUrl() << "by user" << userId << "from device" << deviceId; diff --git a/connectiondata.cpp b/connectiondata.cpp index 9b9b6e04..6ef293cd 100644 --- a/connectiondata.cpp +++ b/connectiondata.cpp @@ -33,7 +33,7 @@ QNetworkAccessManager* getNam() struct ConnectionData::Private { QUrl baseUrl; - QString accessToken; + QByteArray accessToken; QString lastEvent; QString deviceId; @@ -52,7 +52,7 @@ ConnectionData::~ConnectionData() delete d; } -QString ConnectionData::accessToken() const +QByteArray ConnectionData::accessToken() const { return d->accessToken; } @@ -67,7 +67,7 @@ QNetworkAccessManager* ConnectionData::nam() const return getNam(); } -void ConnectionData::setToken(QString token) +void ConnectionData::setToken(QByteArray token) { d->accessToken = token; } diff --git a/connectiondata.h b/connectiondata.h index 52a7461c..933219ea 100644 --- a/connectiondata.h +++ b/connectiondata.h @@ -30,12 +30,12 @@ namespace QMatrixClient explicit ConnectionData(QUrl baseUrl); virtual ~ConnectionData(); - QString accessToken() const; + QByteArray accessToken() const; QUrl baseUrl() const; const QString& deviceId() const; QNetworkAccessManager* nam() const; - void setToken( QString accessToken ); + void setToken(QByteArray accessToken); void setHost( QString host ); void setPort( int port ); void setDeviceId(const QString& deviceId); diff --git a/jobs/basejob.cpp b/jobs/basejob.cpp index aa47f63c..20fb68f6 100644 --- a/jobs/basejob.cpp +++ b/jobs/basejob.cpp @@ -82,9 +82,9 @@ inline QDebug operator<<(QDebug dbg, const BaseJob* j) QDebug QMatrixClient::operator<<(QDebug dbg, const BaseJob::Status& s) { - QRegularExpression filter { "(access_token)=[-_A-Za-z0-9]+" }; + QRegularExpression filter { "(access_token)(=|: )[-_A-Za-z0-9]+" }; return dbg << s.code << ':' - << QString(s.message).replace(filter, "\\1=HIDDEN"); + << QString(s.message).replace(filter, "\\1 HIDDEN"); } BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, @@ -138,13 +138,12 @@ void BaseJob::Private::sendRequest() { QUrl url = connection->baseUrl(); url.setPath( url.path() + "/" + apiEndpoint ); - QUrlQuery q = requestQuery; - if (needsToken) - q.addQueryItem("access_token", connection->accessToken()); - url.setQuery(q); + url.setQuery(requestQuery); QNetworkRequest req {url}; req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + req.setRawHeader(QByteArray("Authorization"), + QByteArray("Bearer ") + connection->accessToken()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); req.setMaximumRedirectsAllowed(10); -- cgit v1.2.3 From 53e3ef39af62dc44cfa3984db3cc18b0a5d349b6 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 30 Oct 2017 12:13:14 +0300 Subject: Room::timelineSize() convenience method --- room.cpp | 5 +++++ room.h | 1 + 2 files changed, 6 insertions(+) diff --git a/room.cpp b/room.cpp index 4f58e393..1b6afac9 100644 --- a/room.cpp +++ b/room.cpp @@ -414,6 +414,11 @@ int Room::memberCount() const return d->membersMap.size(); } +int Room::timelineSize() const +{ + return int(d->timeline.size()); +} + void Room::Private::insertMemberIntoMap(User *u) { auto namesakes = membersMap.values(u->name()); diff --git a/room.h b/room.h index 2fa7e7d5..f224a8ae 100644 --- a/room.h +++ b/room.h @@ -97,6 +97,7 @@ namespace QMatrixClient Q_INVOKABLE QList users() const; Q_INVOKABLE QStringList memberNames() const; Q_INVOKABLE int memberCount() const; + Q_INVOKABLE int timelineSize() const; /** * Returns a room avatar and requests it from the network if needed -- cgit v1.2.3 From 7387a65d84ac121e59690dcbcd2df947b30ddcaf Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 31 Oct 2017 09:57:44 +0300 Subject: Fix Connection::deviceId(): Q_INVOKABLE is incompatible with references --- connection.cpp | 2 +- connection.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/connection.cpp b/connection.cpp index fdcfa680..41160bd2 100644 --- a/connection.cpp +++ b/connection.cpp @@ -290,7 +290,7 @@ QString Connection::userId() const return d->userId; } -const QString& Connection::deviceId() const +QString Connection::deviceId() const { return d->data->deviceId(); } diff --git a/connection.h b/connection.h index 2a107b43..76e4f3c2 100644 --- a/connection.h +++ b/connection.h @@ -100,7 +100,7 @@ namespace QMatrixClient Q_INVOKABLE User* user(const QString& userId); Q_INVOKABLE User* user(); Q_INVOKABLE QString userId() const; - Q_INVOKABLE const QString& deviceId() const; + Q_INVOKABLE QString deviceId() const; /** @deprecated Use accessToken() instead. */ Q_INVOKABLE QString token() const; Q_INVOKABLE QString accessToken() const; -- cgit v1.2.3 From f4ef8a48fedb77b81465e7ac2f8e124db8cbd754 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 31 Oct 2017 09:58:51 +0300 Subject: More .gitignored files --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index 9e0d4235..e9b0b91c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,10 @@ build .kdev4 + +# Qt Creator project file +*.user + +# qmake derivatives +Makefile* +object_script.* +.qmake* \ No newline at end of file -- cgit v1.2.3 From da8bb556dca628e20be732b4a30895010abcc9bf Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 31 Oct 2017 13:12:33 +0300 Subject: Streamline EventContent hierarchy Two changes to make EventContent hierarchy easier to understand and use: - InfoBase is unbound from Base, and downstream classes use multiple inheritance to work "info" objects - MIME types are separated from Base into a separate TypedBase class because MIME typing is not common to all content kinds. --- events/eventcontent.cpp | 12 ++---------- events/eventcontent.h | 47 +++++++++++++++++++++++++++++++-------------- events/roommessageevent.cpp | 15 ++++++++++----- events/roommessageevent.h | 28 +++++++++++++++++---------- 4 files changed, 63 insertions(+), 39 deletions(-) diff --git a/events/eventcontent.cpp b/events/eventcontent.cpp index 205d404b..dcbccf08 100644 --- a/events/eventcontent.cpp +++ b/events/eventcontent.cpp @@ -37,6 +37,8 @@ QJsonObject InfoBase::toInfoJson() const return info; } +void InfoBase::fillInfoJson(QJsonObject*) const { } + FileInfo::FileInfo(const QUrl& u, int payloadSize, const QMimeType& mimeType, const QString& originalFilename) : InfoBase(mimeType), url(u), payloadSize(payloadSize) @@ -59,13 +61,3 @@ void FileInfo::fillInfoJson(QJsonObject* infoJson) const infoJson->insert("size", payloadSize); infoJson->insert("mimetype", mimeType.name()); } - -void FileInfo::fillJson(QJsonObject* json) const -{ - Q_ASSERT(json); - json->insert("url", url.toString()); - if (!originalName.isEmpty()) - json->insert("filename", originalName); - json->insert("info", toInfoJson()); -} - diff --git a/events/eventcontent.h b/events/eventcontent.h index 2c8d7f3f..f9cdaf11 100644 --- a/events/eventcontent.h +++ b/events/eventcontent.h @@ -50,15 +50,16 @@ namespace QMatrixClient QJsonObject toJson() const; - QMimeType mimeType; - protected: - Base() = default; - explicit Base(const QMimeType& type) : mimeType(type) { } - virtual void fillJson(QJsonObject* o) const = 0; }; + class TypedBase: public Base + { + public: + virtual QMimeType type() const = 0; + }; + /** * A base class for content types that have an "info" object in their * JSON representation @@ -70,15 +71,20 @@ namespace QMatrixClient * have a constructor that accepts two parameters, QUrl and QJsonObject, * in order to load the URL+info part from JSON. */ - class InfoBase: public Base + class InfoBase { public: + virtual ~InfoBase() = default; + QJsonObject toInfoJson() const; + QMimeType mimeType; + protected: - using Base::Base; + InfoBase() = default; + explicit InfoBase(const QMimeType& type) : mimeType(type) { } - virtual void fillInfoJson(QJsonObject* /*infoJson*/) const { } + virtual void fillInfoJson(QJsonObject* /*infoJson*/) const = 0; }; // The below structures fairly follow CS spec 11.2.1.6. The overall @@ -105,7 +111,6 @@ namespace QMatrixClient QString originalName; protected: - void fillJson(QJsonObject* json) const override; void fillInfoJson(QJsonObject* infoJson) const override; }; @@ -129,14 +134,15 @@ namespace QMatrixClient , imageSize(infoJson["w"].toInt(), infoJson["h"].toInt()) { } + QSize imageSize; + + protected: void fillInfoJson(QJsonObject* infoJson) const override { InfoT::fillInfoJson(infoJson); infoJson->insert("w", imageSize.width()); infoJson->insert("h", imageSize.height()); } - - QSize imageSize; }; /** @@ -172,14 +178,15 @@ namespace QMatrixClient infoJson["thumbnail_info"].toObject()) { } + ImageInfo<> thumbnail; + + protected: void fillInfoJson(QJsonObject* infoJson) const override { InfoT::fillInfoJson(infoJson); infoJson->insert("thumbnail_url", thumbnail.url.toString()); infoJson->insert("thumbnail_info", thumbnail.toInfoJson()); } - - ImageInfo<> thumbnail; }; /** @@ -194,7 +201,7 @@ namespace QMatrixClient * provide a constructor with a compatible signature */ template // InfoT : public FileInfo - class UrlWith : public InfoT + class UrlWith : public TypedBase, public InfoT { public: using InfoT::InfoT; @@ -202,6 +209,18 @@ namespace QMatrixClient : InfoT(json["url"].toString(), json["info"].toObject(), json["filename"].toString()) { } + + QMimeType type() const override { return InfoT::mimeType; } + + protected: + void fillJson(QJsonObject* json) const override + { + Q_ASSERT(json); + json->insert("url", InfoT::url.toString()); + if (!InfoT::originalName.isEmpty()) + json->insert("filename", InfoT::originalName); + json->insert("info", InfoT::toInfoJson()); + } }; /** diff --git a/events/roommessageevent.cpp b/events/roommessageevent.cpp index 82bd07b6..f06474e9 100644 --- a/events/roommessageevent.cpp +++ b/events/roommessageevent.cpp @@ -28,7 +28,7 @@ using namespace EventContent; using MsgType = RoomMessageEvent::MsgType; template -Base* make(const QJsonObject& json) +TypedBase* make(const QJsonObject& json) { return new ContentT(json); } @@ -37,7 +37,7 @@ struct MsgTypeDesc { QString jsonType; MsgType enumType; - Base* (*maker)(const QJsonObject&); + TypedBase* (*maker)(const QJsonObject&); }; const std::vector msgTypes = @@ -74,7 +74,7 @@ MsgType jsonToMsgType(const QString& jsonType) } RoomMessageEvent::RoomMessageEvent(const QString& plainBody, - MsgType msgType, Base* content) + MsgType msgType, TypedBase* content) : RoomMessageEvent(plainBody, msgTypeToJson(msgType), content) { } @@ -112,7 +112,7 @@ RoomMessageEvent::MsgType RoomMessageEvent::msgtype() const QMimeType RoomMessageEvent::mimeType() const { - return _content ? _content->mimeType : + return _content ? _content->type() : QMimeDatabase().mimeTypeForName("text/plain"); } @@ -125,7 +125,7 @@ QJsonObject RoomMessageEvent::toJson() const } TextContent::TextContent(const QString& text, const QString& contentType) - : Base(QMimeDatabase().mimeTypeForName(contentType)), body(text) + : mimeType(QMimeDatabase().mimeTypeForName(contentType)), body(text) { } TextContent::TextContent(const QJsonObject& json) @@ -170,6 +170,11 @@ void LocationContent::fillJson(QJsonObject* o) const o->insert("info", Thumbnailed::toInfoJson()); } +QMimeType LocationContent::type() const +{ + return QMimeDatabase().mimeTypeForData(geoUri.toLatin1()); +} + PlayableInfo::PlayableInfo(const QUrl& u, int fileSize, const QMimeType& mimeType, int duration, const QString& originalFilename) diff --git a/events/roommessageevent.h b/events/roommessageevent.h index b98f12d6..eef6b657 100644 --- a/events/roommessageevent.h +++ b/events/roommessageevent.h @@ -40,19 +40,19 @@ namespace QMatrixClient RoomMessageEvent(const QString& plainBody, const QString& jsonMsgType, - EventContent::Base* content = nullptr) + EventContent::TypedBase* content = nullptr) : RoomEvent(Type::RoomMessage) , _msgtype(jsonMsgType), _plainBody(plainBody), _content(content) { } explicit RoomMessageEvent(const QString& plainBody, MsgType msgType = MsgType::Text, - EventContent::Base* content = nullptr); + EventContent::TypedBase* content = nullptr); explicit RoomMessageEvent(const QJsonObject& obj); MsgType msgtype() const; QString rawMsgtype() const { return _msgtype; } const QString& plainBody() const { return _plainBody; } - const EventContent::Base* content() const + const EventContent::TypedBase* content() const { return _content.data(); } QMimeType mimeType() const; @@ -63,7 +63,7 @@ namespace QMatrixClient private: QString _msgtype; QString _plainBody; - QScopedPointer _content; + QScopedPointer _content; REGISTER_ENUM(MsgType) }; @@ -79,15 +79,19 @@ namespace QMatrixClient * Available fields: mimeType, body. The body can be either rich text * or plain text, depending on what mimeType specifies. */ - class TextContent: public Base + class TextContent: public TypedBase { public: TextContent(const QString& text, const QString& contentType); explicit TextContent(const QJsonObject& json); - void fillJson(QJsonObject* json) const override; + QMimeType type() const override { return mimeType; } + QMimeType mimeType; QString body; + + protected: + void fillJson(QJsonObject* json) const override; }; /** @@ -103,16 +107,19 @@ namespace QMatrixClient * - thumbnail.mimeType * - thumbnail.imageSize */ - class LocationContent: public Thumbnailed<> + class LocationContent: public TypedBase, public Thumbnailed<> { public: LocationContent(const QString& geoUri, const ImageInfo<>& thumbnail); explicit LocationContent(const QJsonObject& json); - void fillJson(QJsonObject* o) const override; + QMimeType type() const override; QString geoUri; + + protected: + void fillJson(QJsonObject* o) const override; }; /** @@ -127,9 +134,10 @@ namespace QMatrixClient PlayableInfo(const QUrl& u, const QJsonObject& infoJson, const QString& originalFilename = {}); - void fillInfoJson(QJsonObject* infoJson) const override; - int duration; + + protected: + void fillInfoJson(QJsonObject* infoJson) const override; }; /** -- cgit v1.2.3 From 1cc5d08159936e87f7c1dc791d6ab3c0e46e7035 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 1 Nov 2017 08:38:59 +0300 Subject: Move converters.h out of jobs/ Because they are applicable beyond jobs. --- converters.h | 121 ++++++++++++++++++++++++++++++++++++++++++++ jobs/converters.h | 121 -------------------------------------------- jobs/generated/banning.cpp | 2 +- jobs/generated/inviting.cpp | 2 +- jobs/generated/kicking.cpp | 2 +- jobs/generated/leaving.cpp | 2 +- jobs/generated/login.cpp | 2 +- jobs/generated/logout.cpp | 2 +- jobs/generated/profile.cpp | 2 +- 9 files changed, 128 insertions(+), 128 deletions(-) create mode 100644 converters.h delete mode 100644 jobs/converters.h diff --git a/converters.h b/converters.h new file mode 100644 index 00000000..f6e850c6 --- /dev/null +++ b/converters.h @@ -0,0 +1,121 @@ +/****************************************************************************** +* Copyright (C) 2017 Kitsune Ral +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once + +#include +#include // Includes +#include + +namespace QMatrixClient +{ + template + inline QJsonValue toJson(T val) + { + return QJsonValue(val); + } + + template + inline QJsonValue toJson(const QVector& vals) + { + QJsonArray ar; + for (const auto& v: vals) + ar.push_back(toJson(v)); + return ar; + } + + inline QJsonValue toJson(const QStringList& strings) + { + return QJsonArray::fromStringList(strings); + } + + template + struct FromJson + { + T operator()(QJsonValue jv) const { return static_cast(jv); } + }; + + template + inline T fromJson(const QJsonValue& jv) + { + return FromJson()(jv); + } + + template <> struct FromJson + { + bool operator()(QJsonValue jv) const { return jv.toBool(); } + }; + + template <> struct FromJson + { + int operator()(QJsonValue jv) const { return jv.toInt(); } + }; + + template <> struct FromJson + { + double operator()(QJsonValue jv) const { return jv.toDouble(); } + }; + + template <> struct FromJson + { + qint64 operator()(QJsonValue jv) const { return qint64(jv.toDouble()); } + }; + + template <> struct FromJson + { + QString operator()(QJsonValue jv) const { return jv.toString(); } + }; + + template <> struct FromJson + { + QDateTime operator()(QJsonValue jv) const + { + return QDateTime::fromMSecsSinceEpoch(fromJson(jv), Qt::UTC); + } + }; + + template <> struct FromJson + { + QDate operator()(QJsonValue jv) const + { + return fromJson(jv).date(); + } + }; + + template <> struct FromJson + { + QJsonObject operator()(QJsonValue jv) const { return jv.toObject(); } + }; + + template <> struct FromJson + { + QJsonArray operator()(QJsonValue jv) const { return jv.toArray(); } + }; + + template struct FromJson> + { + QVector operator()(QJsonValue jv) const + { + const auto jsonArray = jv.toArray(); + QVector vect; vect.resize(jsonArray.size()); + std::transform(jsonArray.begin(), jsonArray.end(), + vect.begin(), FromJson()); + return vect; + } + }; +} // namespace QMatrixClient diff --git a/jobs/converters.h b/jobs/converters.h deleted file mode 100644 index f6e850c6..00000000 --- a/jobs/converters.h +++ /dev/null @@ -1,121 +0,0 @@ -/****************************************************************************** -* Copyright (C) 2017 Kitsune Ral -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; either -* version 2.1 of the License, or (at your option) any later version. -* -* This library 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 -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#pragma once - -#include -#include // Includes -#include - -namespace QMatrixClient -{ - template - inline QJsonValue toJson(T val) - { - return QJsonValue(val); - } - - template - inline QJsonValue toJson(const QVector& vals) - { - QJsonArray ar; - for (const auto& v: vals) - ar.push_back(toJson(v)); - return ar; - } - - inline QJsonValue toJson(const QStringList& strings) - { - return QJsonArray::fromStringList(strings); - } - - template - struct FromJson - { - T operator()(QJsonValue jv) const { return static_cast(jv); } - }; - - template - inline T fromJson(const QJsonValue& jv) - { - return FromJson()(jv); - } - - template <> struct FromJson - { - bool operator()(QJsonValue jv) const { return jv.toBool(); } - }; - - template <> struct FromJson - { - int operator()(QJsonValue jv) const { return jv.toInt(); } - }; - - template <> struct FromJson - { - double operator()(QJsonValue jv) const { return jv.toDouble(); } - }; - - template <> struct FromJson - { - qint64 operator()(QJsonValue jv) const { return qint64(jv.toDouble()); } - }; - - template <> struct FromJson - { - QString operator()(QJsonValue jv) const { return jv.toString(); } - }; - - template <> struct FromJson - { - QDateTime operator()(QJsonValue jv) const - { - return QDateTime::fromMSecsSinceEpoch(fromJson(jv), Qt::UTC); - } - }; - - template <> struct FromJson - { - QDate operator()(QJsonValue jv) const - { - return fromJson(jv).date(); - } - }; - - template <> struct FromJson - { - QJsonObject operator()(QJsonValue jv) const { return jv.toObject(); } - }; - - template <> struct FromJson - { - QJsonArray operator()(QJsonValue jv) const { return jv.toArray(); } - }; - - template struct FromJson> - { - QVector operator()(QJsonValue jv) const - { - const auto jsonArray = jv.toArray(); - QVector vect; vect.resize(jsonArray.size()); - std::transform(jsonArray.begin(), jsonArray.end(), - vect.begin(), FromJson()); - return vect; - } - }; -} // namespace QMatrixClient diff --git a/jobs/generated/banning.cpp b/jobs/generated/banning.cpp index 9fc5810a..c47d3419 100644 --- a/jobs/generated/banning.cpp +++ b/jobs/generated/banning.cpp @@ -5,7 +5,7 @@ #include "banning.h" -#include "jobs/converters.h" +#include "converters.h" #include using namespace QMatrixClient; diff --git a/jobs/generated/inviting.cpp b/jobs/generated/inviting.cpp index 95ba658d..11384c5e 100644 --- a/jobs/generated/inviting.cpp +++ b/jobs/generated/inviting.cpp @@ -5,7 +5,7 @@ #include "inviting.h" -#include "jobs/converters.h" +#include "converters.h" #include using namespace QMatrixClient; diff --git a/jobs/generated/kicking.cpp b/jobs/generated/kicking.cpp index 2e6797d6..e75b900a 100644 --- a/jobs/generated/kicking.cpp +++ b/jobs/generated/kicking.cpp @@ -5,7 +5,7 @@ #include "kicking.h" -#include "jobs/converters.h" +#include "converters.h" #include using namespace QMatrixClient; diff --git a/jobs/generated/leaving.cpp b/jobs/generated/leaving.cpp index 7fed347b..e443612e 100644 --- a/jobs/generated/leaving.cpp +++ b/jobs/generated/leaving.cpp @@ -5,7 +5,7 @@ #include "leaving.h" -#include "jobs/converters.h" +#include "converters.h" #include using namespace QMatrixClient; diff --git a/jobs/generated/login.cpp b/jobs/generated/login.cpp index 6e8294e7..0c57c684 100644 --- a/jobs/generated/login.cpp +++ b/jobs/generated/login.cpp @@ -5,7 +5,7 @@ #include "login.h" -#include "jobs/converters.h" +#include "converters.h" #include using namespace QMatrixClient; diff --git a/jobs/generated/logout.cpp b/jobs/generated/logout.cpp index b750efe2..a5848e7c 100644 --- a/jobs/generated/logout.cpp +++ b/jobs/generated/logout.cpp @@ -5,7 +5,7 @@ #include "logout.h" -#include "jobs/converters.h" +#include "converters.h" #include using namespace QMatrixClient; diff --git a/jobs/generated/profile.cpp b/jobs/generated/profile.cpp index 9d20a480..201bca79 100644 --- a/jobs/generated/profile.cpp +++ b/jobs/generated/profile.cpp @@ -5,7 +5,7 @@ #include "profile.h" -#include "jobs/converters.h" +#include "converters.h" #include using namespace QMatrixClient; -- cgit v1.2.3 From f591e02176f494c06c7288f975e8571dda2bc6ad Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 1 Nov 2017 11:11:29 +0300 Subject: Drop UnknownEvent files from the source tree We don't use them for several months already. --- events/unknownevent.cpp | 64 ------------------------------------------------- events/unknownevent.h | 40 ------------------------------- 2 files changed, 104 deletions(-) delete mode 100644 events/unknownevent.cpp delete mode 100644 events/unknownevent.h diff --git a/events/unknownevent.cpp b/events/unknownevent.cpp deleted file mode 100644 index 1670ff1d..00000000 --- a/events/unknownevent.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2015 Felix Rohrbach - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "unknownevent.h" - -#include "logging.h" - -#include - -using namespace QMatrixClient; - -class UnknownEvent::Private -{ - public: - QString type; - QString content; -}; - -UnknownEvent::UnknownEvent() - : Event(EventType::Unknown) - , d(new Private) -{ -} - -UnknownEvent::~UnknownEvent() -{ - delete d; -} - -QString UnknownEvent::typeString() const -{ - return d->type; -} - -QString UnknownEvent::content() const -{ - return d->content; -} - -UnknownEvent* UnknownEvent::fromJson(const QJsonObject& obj) -{ - UnknownEvent* e = new UnknownEvent(); - e->parseJson(obj); - e->d->type = obj.value("type").toString(); - e->d->content = QString::fromUtf8(QJsonDocument(obj).toJson()); - qCDebug(EVENTS) << "UnknownEvent, JSON follows:"; - qCDebug(EVENTS) << formatJson << obj; - return e; -} diff --git a/events/unknownevent.h b/events/unknownevent.h deleted file mode 100644 index 51f2c4be..00000000 --- a/events/unknownevent.h +++ /dev/null @@ -1,40 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2015 Felix Rohrbach - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#pragma once - -#include "event.h" - -namespace QMatrixClient -{ - class UnknownEvent: public Event - { - public: - UnknownEvent(); - virtual ~UnknownEvent(); - - QString typeString() const; - QString content() const; - - static UnknownEvent* fromJson(const QJsonObject& obj); - - private: - class Private; - Private* d; - }; -} -- cgit v1.2.3 From 2931b4d13e1883150b92f80f08aa51d86ac2445f Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 1 Nov 2017 17:40:30 +0300 Subject: ReceiptEvent: Use fromJson<>() from converters.h; add TypeId Event::toTimestamp() duplicates fromJson<>() code, so it should go. --- events/receiptevent.cpp | 6 +++--- events/receiptevent.h | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/events/receiptevent.cpp b/events/receiptevent.cpp index 646bb989..b36ddb23 100644 --- a/events/receiptevent.cpp +++ b/events/receiptevent.cpp @@ -35,10 +35,9 @@ Example of a Receipt Event: #include "receiptevent.h" +#include "converters.h" #include "logging.h" -#include - using namespace QMatrixClient; ReceiptEvent::ReceiptEvent(const QJsonObject& obj) @@ -62,7 +61,8 @@ ReceiptEvent::ReceiptEvent(const QJsonObject& obj) for( auto userIt = reads.begin(); userIt != reads.end(); ++userIt ) { const QJsonObject user = userIt.value().toObject(); - receipts.push_back({userIt.key(), toTimestamp(user["ts"])}); + receipts.push_back({userIt.key(), + QMatrixClient::fromJson(user["ts"])}); } _eventsWithReceipts.push_back({eventIt.key(), receipts}); } diff --git a/events/receiptevent.h b/events/receiptevent.h index cbe36b10..15fdf946 100644 --- a/events/receiptevent.h +++ b/events/receiptevent.h @@ -43,6 +43,8 @@ namespace QMatrixClient { return _eventsWithReceipts; } bool unreadMessages() const { return _unreadMessages; } + static constexpr const char* const TypeId = "m.receipt"; + private: EventsWithReceipts _eventsWithReceipts; bool _unreadMessages; // Spec extension for caching purposes -- cgit v1.2.3 From c4e1e0906558608326210eb333b0beb73636de60 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 1 Nov 2017 17:42:57 +0300 Subject: Add fromJson> and fromJson --- converters.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/converters.h b/converters.h index f6e850c6..733c2c0e 100644 --- a/converters.h +++ b/converters.h @@ -118,4 +118,19 @@ namespace QMatrixClient return vect; } }; + + template struct FromJson> + { + QList operator()(QJsonValue jv) const + { + const auto jsonArray = jv.toArray(); + QList sl; sl.reserve(jsonArray.size()); + std::transform(jsonArray.begin(), jsonArray.end(), + std::back_inserter(sl), FromJson()); + return sl; + } + }; + + template <> struct FromJson : FromJson> { }; + } // namespace QMatrixClient -- cgit v1.2.3 From 64c0b7e045d9e8d58d91f252219a5579d4ba9441 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 1 Nov 2017 17:51:47 +0300 Subject: Add TypeId to more events --- events/roommemberevent.h | 2 ++ events/typingevent.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/events/roommemberevent.h b/events/roommemberevent.h index 9ebb75ee..358af257 100644 --- a/events/roommemberevent.h +++ b/events/roommemberevent.h @@ -28,6 +28,8 @@ namespace QMatrixClient { Q_GADGET public: + static constexpr const char* TypeId = "m.room.member"; + enum MembershipType : int {Invite = 0, Join, Knock, Leave, Ban}; explicit RoomMemberEvent(const QJsonObject& obj); diff --git a/events/typingevent.h b/events/typingevent.h index b12d224e..8c9551a4 100644 --- a/events/typingevent.h +++ b/events/typingevent.h @@ -27,6 +27,8 @@ namespace QMatrixClient class TypingEvent: public Event { public: + static constexpr const char* const TypeId = "m.typing"; + TypingEvent(const QJsonObject& obj); QStringList users() const { return _users; } -- cgit v1.2.3 From a275ef911b3f4d0334df0628324aee0081dd3a65 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 1 Nov 2017 18:19:56 +0300 Subject: StateEvent; EventContent::SimpleContent; event types refactoring * StateEvent<> is a new class template for all state events. It provides a uniform interface to the state content, as well as a means to serialize the content back to JSON. In addition, StateEvent now parses the "prev_content" JSON object, so one can refer to the previous state now (a notable step to proper reflection of state changes in the displayed timeline in clients). * EventContent::SimpleContent, together with StateEvent<>, forms a generalisation for simple state events, such as room name, topic, aliases etc. that boil down to a single key-value pair. DECLARE_SIMPLE_STATE_EVENT is a macro defined to streamline creation of events based on SimpleContent, providing API back-compatibility for events defined so far. As a result, a very concise simplestateevents.h replaces all those room*event.* files. * Event/RoomEvent::fromJson() code is squeezed down to plain type lists passed to makeIfMatches() "chained factory" function template. TypeId is mandatory for an event type to be included into that factory. * Event::toTimestamp() and Event::toStringList are completely superseded by respective fromJson<>() converters. --- CMakeLists.txt | 5 ---- events/encryptedevent.cpp | 5 ---- events/encryptedevent.h | 39 ------------------------ events/event.cpp | 61 ++++++++++++-------------------------- events/event.h | 53 +++++++++++++++++++++++---------- events/eventcontent.h | 31 +++++++++++++++++++ events/roomaliasesevent.cpp | 43 --------------------------- events/roomaliasesevent.h | 37 ----------------------- events/roomavatarevent.h | 22 ++++---------- events/roomcanonicalaliasevent.cpp | 21 ------------- events/roomcanonicalaliasevent.h | 38 ------------------------ events/roomnameevent.cpp | 22 -------------- events/roomnameevent.h | 38 ------------------------ events/roomtopicevent.cpp | 22 -------------- events/roomtopicevent.h | 50 ------------------------------- events/simplestateevents.h | 54 +++++++++++++++++++++++++++++++++ libqmatrixclient.pri | 10 +------ room.cpp | 5 +--- 18 files changed, 150 insertions(+), 406 deletions(-) delete mode 100644 events/encryptedevent.cpp delete mode 100644 events/encryptedevent.h delete mode 100644 events/roomaliasesevent.cpp delete mode 100644 events/roomaliasesevent.h delete mode 100644 events/roomcanonicalaliasevent.cpp delete mode 100644 events/roomcanonicalaliasevent.h delete mode 100644 events/roomnameevent.cpp delete mode 100644 events/roomnameevent.h delete mode 100644 events/roomtopicevent.cpp delete mode 100644 events/roomtopicevent.h create mode 100644 events/simplestateevents.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 26c7bf46..6163b7be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,15 +65,10 @@ set(libqmatrixclient_SRCS events/event.cpp events/eventcontent.cpp events/roommessageevent.cpp - events/roomnameevent.cpp - events/roomaliasesevent.cpp - events/roomcanonicalaliasevent.cpp events/roommemberevent.cpp - events/roomtopicevent.cpp events/roomavatarevent.cpp events/typingevent.cpp events/receiptevent.cpp - events/encryptedevent.cpp jobs/basejob.cpp jobs/checkauthmethods.cpp jobs/passwordlogin.cpp diff --git a/events/encryptedevent.cpp b/events/encryptedevent.cpp deleted file mode 100644 index 90e77c36..00000000 --- a/events/encryptedevent.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by rusakov on 26/09/2017. -// - -#include "encryptedevent.h" diff --git a/events/encryptedevent.h b/events/encryptedevent.h deleted file mode 100644 index 9db462e1..00000000 --- a/events/encryptedevent.h +++ /dev/null @@ -1,39 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2017 Kitsune Ral - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#pragma once - -#include "event.h" - -namespace QMatrixClient -{ - class EncryptionEvent : public RoomEvent - { - public: - explicit EncryptionEvent(const QJsonObject& obj) - : RoomEvent(Type::RoomEncryption, obj) - , _algorithm(contentJson()["algorithm"].toString()) - { } - - QString algorithm() const { return _algorithm; } - - private: - QString _algorithm; - }; -} // namespace QMatrixClient - diff --git a/events/event.cpp b/events/event.cpp index 9963a0ef..44b742c1 100644 --- a/events/event.cpp +++ b/events/event.cpp @@ -19,15 +19,11 @@ #include "event.h" #include "roommessageevent.h" -#include "roomnameevent.h" -#include "roomaliasesevent.h" -#include "roomcanonicalaliasevent.h" +#include "simplestateevents.h" #include "roommemberevent.h" -#include "roomtopicevent.h" #include "roomavatarevent.h" #include "typingevent.h" #include "receiptevent.h" -#include "encryptedevent.h" #include "logging.h" #include @@ -54,32 +50,24 @@ QJsonObject Event::originalJsonObject() const return _originalJson; } -QDateTime Event::toTimestamp(const QJsonValue& v) +const QJsonObject Event::contentJson() const { - Q_ASSERT(v.isDouble() || v.isNull() || v.isUndefined()); - return QDateTime::fromMSecsSinceEpoch( - static_cast(v.toDouble()), Qt::UTC); + return _originalJson["content"].toObject(); } -QStringList Event::toStringList(const QJsonValue& v) +template +inline BaseEventT* makeIfMatches(const QJsonObject&, const QString&) { - Q_ASSERT(v.isArray() || v.isNull() || v.isUndefined()); - - QStringList l; - for( const QJsonValue& e : v.toArray() ) - l.push_back(e.toString()); - return l; + return nullptr; } -const QJsonObject Event::contentJson() const +template +inline BaseEventT* makeIfMatches(const QJsonObject& o, const QString& selector) { - return _originalJson["content"].toObject(); -} + if (selector == EventT::TypeId) + return new EventT(o); -template -EventT* make(const QJsonObject& o) -{ - return new EventT(o); + return makeIfMatches(o, selector); } Event* Event::fromJson(const QJsonObject& obj) @@ -88,17 +76,14 @@ Event* Event::fromJson(const QJsonObject& obj) if (auto e = RoomEvent::fromJson(obj)) return e; - return dispatch(obj).to(obj["type"].toString(), - "m.typing", make, - "m.receipt", make, - /* Insert new event types (except room events) BEFORE this line */ - nullptr - ); + return makeIfMatches(obj, obj["type"].toString()); } RoomEvent::RoomEvent(Type type, const QJsonObject& rep) : Event(type, rep), _id(rep["event_id"].toString()) - , _serverTimestamp(toTimestamp(rep["origin_server_ts"])) + , _serverTimestamp( + QMatrixClient::fromJson(rep["origin_server_ts"])) , _roomId(rep["room_id"].toString()) , _senderId(rep["sender"].toString()) , _txnId(rep["unsigned"].toObject().value("transactionId").toString()) @@ -130,16 +115,8 @@ void RoomEvent::addId(const QString& id) RoomEvent* RoomEvent::fromJson(const QJsonObject& obj) { - return dispatch(obj).to(obj["type"].toString(), - "m.room.message", make, - "m.room.name", make, - "m.room.aliases", make, - "m.room.canonical_alias", make, - "m.room.member", make, - "m.room.topic", make, - "m.room.avatar", make, - "m.room.encryption", make, - /* Insert new ROOM event types BEFORE this line */ - nullptr - ); + return makeIfMatches(obj, obj["type"].toString()); } diff --git a/events/event.h b/events/event.h index c151ac0e..020ef54b 100644 --- a/events/event.h +++ b/events/event.h @@ -56,9 +56,6 @@ namespace QMatrixClient static Event* fromJson(const QJsonObject& obj); protected: - static QDateTime toTimestamp(const QJsonValue& v); - static QStringList toStringList(const QJsonValue& v); - const QJsonObject contentJson() const; private: @@ -74,25 +71,20 @@ namespace QMatrixClient using EventsBatch = std::vector; using Events = EventsBatch; - template - BaseEventT* makeEvent(const QJsonObject& obj) - { - if (auto e = BaseEventT::fromJson(obj)) - return e; - - return new BaseEventT(EventType::Unknown, obj); - } - template > - BatchT makeEvents(const QJsonArray& objs) + inline BatchT makeEvents(const QJsonArray& objs) { BatchT evs; // The below line accommodates the difference in size types of // STL and Qt containers. evs.reserve(static_cast(objs.size())); - for (auto obj: objs) - evs.push_back(makeEvent(obj.toObject())); + for (auto objValue: objs) + { + const auto o = objValue.toObject(); + auto e = BaseEventT::fromJson(o); + evs.push_back(e ? e : new BaseEventT(EventType::Unknown, o)); + } return evs; } @@ -147,6 +139,37 @@ namespace QMatrixClient QString _txnId; }; using RoomEvents = EventsBatch; + + template + class StateEvent: public RoomEvent + { + public: + using content_type = ContentT; + + template + explicit StateEvent(Type type, const QJsonObject& obj, + ContentParamTs&&... contentParams) + : RoomEvent(type, obj) + , _content(contentJson(), + std::forward(contentParams)...) + , _prev(new ContentT(obj["prev_content"].toObject(), + std::forward(contentParams)...)) + { } + template + explicit StateEvent(Type type, ContentParamTs&&... contentParams) + : RoomEvent(type) + , _content(std::forward(contentParams)...) + { } + + QJsonObject toJson() const { return _content.toJson(); } + + ContentT content() const { return _content; } + ContentT* prev_content() const { return _prev.data(); } + + protected: + ContentT _content; + QScopedPointer _prev; + }; } // namespace QMatrixClient Q_DECLARE_OPAQUE_POINTER(QMatrixClient::Event*) Q_DECLARE_METATYPE(QMatrixClient::Event*) diff --git a/events/eventcontent.h b/events/eventcontent.h index f9cdaf11..60437995 100644 --- a/events/eventcontent.h +++ b/events/eventcontent.h @@ -21,6 +21,8 @@ // This file contains generic event content definitions, applicable to room // message events as well as other events (e.g., avatars). +#include "converters.h" + #include #include #include @@ -60,6 +62,35 @@ namespace QMatrixClient virtual QMimeType type() const = 0; }; + template + class SimpleContent: public Base + { + public: + using value_type = T; + + // The constructor is templated to enable perfect forwarding + template + SimpleContent(QString keyName, TT&& value) + : value(std::forward(value)), key(std::move(keyName)) + { } + SimpleContent(const QJsonObject& json, QString keyName) + : value(QMatrixClient::fromJson(json[keyName])) + , key(std::move(keyName)) + { } + + T value; + + protected: + QString key; + + private: + void fillJson(QJsonObject* json) const override + { + Q_ASSERT(json); + json->insert(key, QMatrixClient::toJson(value)); + } + }; + /** * A base class for content types that have an "info" object in their * JSON representation diff --git a/events/roomaliasesevent.cpp b/events/roomaliasesevent.cpp deleted file mode 100644 index 344b4367..00000000 --- a/events/roomaliasesevent.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2015 Felix Rohrbach - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -// Example of a RoomAliases Event: -// -// { -// "age":3758857346, -// "content":{ -// "aliases":["#freenode_#testest376:matrix.org"] -// }, -// "event_id":"$1439989428122drFjY:matrix.org", -// "origin_server_ts":1439989428910, -// "replaces_state":"$143613875199223YYPrN:matrix.org", -// "room_id":"!UoqtanuuSGTMvNRfDG:matrix.org", -// "state_key":"matrix.org", -// "type":"m.room.aliases", -// "user_id":"@appservice-irc:matrix.org" -// } - -#include "roomaliasesevent.h" - -using namespace QMatrixClient; - -RoomAliasesEvent::RoomAliasesEvent(const QJsonObject& obj) - : RoomEvent(Type::RoomAliases, obj) - , _aliases(toStringList(contentJson()["aliases"])) -{ } - diff --git a/events/roomaliasesevent.h b/events/roomaliasesevent.h deleted file mode 100644 index efafcb30..00000000 --- a/events/roomaliasesevent.h +++ /dev/null @@ -1,37 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2015 Felix Rohrbach - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#pragma once - -#include "event.h" - -#include - -namespace QMatrixClient -{ - class RoomAliasesEvent: public RoomEvent - { - public: - explicit RoomAliasesEvent(const QJsonObject& obj); - - QStringList aliases() const { return _aliases; } - - private: - QStringList _aliases; - }; -} // namespace QMatrixClient diff --git a/events/roomavatarevent.h b/events/roomavatarevent.h index 411de4e8..ccfe8fbf 100644 --- a/events/roomavatarevent.h +++ b/events/roomavatarevent.h @@ -26,28 +26,18 @@ namespace QMatrixClient { - class RoomAvatarEvent: public RoomEvent + class RoomAvatarEvent: public StateEvent { + // It's a bit of an overkill to use a full-fledged ImageContent + // because in reality m.room.avatar usually only has a single URL, + // without a thumbnail. But The Spec says there be thumbnails, and + // we follow The Spec. public: - explicit RoomAvatarEvent(EventContent::ImageContent avatar) - : RoomEvent(Type::RoomAvatar), _avatar(std::move(avatar)) - { } explicit RoomAvatarEvent(const QJsonObject& obj) - : RoomEvent(Type::RoomAvatar, obj), _avatar(contentJson()) + : StateEvent(Type::RoomAvatar, obj) { } - const EventContent::ImageContent& content() const { return _avatar; } - - QJsonObject toJson() const { return _avatar.toJson(); } - static constexpr const char* TypeId = "m.room.avatar"; - - private: - // It's a bit of an overkill to use a full-fledged ImageContent - // because in reality m.room.avatar usually only has a single URL, - // without a thumbnail. But The Spec says there be thumbnails, and - // we follow The Spec. - EventContent::ImageContent _avatar; }; } // namespace QMatrixClient diff --git a/events/roomcanonicalaliasevent.cpp b/events/roomcanonicalaliasevent.cpp deleted file mode 100644 index 6884bc15..00000000 --- a/events/roomcanonicalaliasevent.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2016 Felix Rohrbach - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "roomcanonicalaliasevent.h" - -using namespace QMatrixClient; diff --git a/events/roomcanonicalaliasevent.h b/events/roomcanonicalaliasevent.h deleted file mode 100644 index 72620d74..00000000 --- a/events/roomcanonicalaliasevent.h +++ /dev/null @@ -1,38 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2016 Felix Rohrbach - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#pragma once - -#include "event.h" - -namespace QMatrixClient -{ - class RoomCanonicalAliasEvent : public RoomEvent - { - public: - explicit RoomCanonicalAliasEvent(const QJsonObject& obj) - : RoomEvent(Type::RoomCanonicalAlias, obj) - , _canonicalAlias(contentJson()["alias"].toString()) - { } - - QString alias() const { return _canonicalAlias; } - - private: - QString _canonicalAlias; - }; -} // namespace QMatrixClient diff --git a/events/roomnameevent.cpp b/events/roomnameevent.cpp deleted file mode 100644 index c202d17a..00000000 --- a/events/roomnameevent.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2015 Kitsune Ral - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "roomnameevent.h" - -using namespace QMatrixClient; - diff --git a/events/roomnameevent.h b/events/roomnameevent.h deleted file mode 100644 index bb823933..00000000 --- a/events/roomnameevent.h +++ /dev/null @@ -1,38 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2015 Kitsune Ral - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#pragma once - -#include "event.h" - -namespace QMatrixClient -{ - class RoomNameEvent : public RoomEvent - { - public: - explicit RoomNameEvent(const QJsonObject& obj) - : RoomEvent(Type::RoomName, obj) - , _name(contentJson()["name"].toString()) - { } - - QString name() const { return _name; } - - private: - QString _name{}; - }; -} // namespace QMatrixClient diff --git a/events/roomtopicevent.cpp b/events/roomtopicevent.cpp deleted file mode 100644 index 26677e78..00000000 --- a/events/roomtopicevent.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2015 Felix Rohrbach - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "roomtopicevent.h" - -using namespace QMatrixClient; - diff --git a/events/roomtopicevent.h b/events/roomtopicevent.h deleted file mode 100644 index 95ad0e04..00000000 --- a/events/roomtopicevent.h +++ /dev/null @@ -1,50 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2015 Felix Rohrbach - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#pragma once - -#include "event.h" - -namespace QMatrixClient -{ - class RoomTopicEvent: public RoomEvent - { - public: - explicit RoomTopicEvent(const QString& topic) - : RoomEvent(Type::RoomTopic), _topic(topic) - { } - explicit RoomTopicEvent(const QJsonObject& obj) - : RoomEvent(Type::RoomTopic, obj) - , _topic(contentJson()["topic"].toString()) - { } - - QString topic() const { return _topic; } - - QJsonObject toJson() const - { - QJsonObject obj; - obj.insert("topic", _topic); - return obj; - } - - static constexpr const char* TypeId = "m.room.topic"; - - private: - QString _topic; - }; -} // namespace QMatrixClient diff --git a/events/simplestateevents.h b/events/simplestateevents.h new file mode 100644 index 00000000..d5841bdc --- /dev/null +++ b/events/simplestateevents.h @@ -0,0 +1,54 @@ +/****************************************************************************** + * Copyright (C) 2017 Kitsune Ral + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include "event.h" + +#include "eventcontent.h" + +namespace QMatrixClient +{ +#define DECLARE_SIMPLE_STATE_EVENT(_Name, _TypeId, _EnumType, _ContentType, _ContentKey) \ + class _Name \ + : public StateEvent> \ + { \ + public: \ + static constexpr const char* TypeId = _TypeId; \ + explicit _Name(const QJsonObject& obj) \ + : StateEvent(_EnumType, obj, #_ContentKey) \ + { } \ + template \ + explicit _Name(T&& value) \ + : StateEvent(_EnumType, #_ContentKey, \ + std::forward(value)) \ + { } \ + _ContentType _ContentKey() const { return content().value; } \ + }; + + DECLARE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", + Event::Type::RoomName, QString, name) + DECLARE_SIMPLE_STATE_EVENT(RoomAliasesEvent, "m.room.aliases", + Event::Type::RoomAliases, QStringList, aliases) + DECLARE_SIMPLE_STATE_EVENT(RoomCanonicalAliasEvent, "m.room.canonical_alias", + Event::Type::RoomCanonicalAlias, QString, alias) + DECLARE_SIMPLE_STATE_EVENT(RoomTopicEvent, "m.room.topic", + Event::Type::RoomTopic, QString, topic) + DECLARE_SIMPLE_STATE_EVENT(EncryptionEvent, "m.room.encryption", + Event::Type::RoomEncryption, QString, algorithm) +} // namespace QMatrixClient diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index 9eb6bd16..86648860 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -13,11 +13,8 @@ HEADERS += \ $$PWD/events/event.h \ $$PWD/events/eventcontent.h \ $$PWD/events/roommessageevent.h \ - $$PWD/events/roomnameevent.h \ - $$PWD/events/roomaliasesevent.h \ - $$PWD/events/roomcanonicalaliasevent.h \ + $$PWD/events/simplestateevents.h \ $$PWD/events/roommemberevent.h \ - $$PWD/events/roomtopicevent.h \ $$PWD/events/roomavatarevent.h \ $$PWD/events/typingevent.h \ $$PWD/events/receiptevent.h \ @@ -44,12 +41,7 @@ SOURCES += \ $$PWD/events/event.cpp \ $$PWD/events/eventcontent.cpp \ $$PWD/events/roommessageevent.cpp \ - $$PWD/events/roomnameevent.cpp \ - $$PWD/events/roomaliasesevent.cpp \ - $$PWD/events/roomcanonicalaliasevent.cpp \ $$PWD/events/roommemberevent.cpp \ - $$PWD/events/roomtopicevent.cpp \ - $$PWD/events/roomavatarevent.cpp \ $$PWD/events/typingevent.cpp \ $$PWD/events/receiptevent.cpp \ $$PWD/jobs/basejob.cpp \ diff --git a/room.cpp b/room.cpp index 1b6afac9..c4e4c6cd 100644 --- a/room.cpp +++ b/room.cpp @@ -23,10 +23,7 @@ #include "jobs/generated/banning.h" #include "jobs/generated/leaving.h" #include "jobs/setroomstatejob.h" -#include "events/roomnameevent.h" -#include "events/roomaliasesevent.h" -#include "events/roomcanonicalaliasevent.h" -#include "events/roomtopicevent.h" +#include "events/simplestateevents.h" #include "events/roomavatarevent.h" #include "events/roommemberevent.h" #include "events/typingevent.h" -- cgit v1.2.3 From 2390082f7ca264aabeadc8f62bb71c39ce2b96f0 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 1 Nov 2017 20:59:52 +0300 Subject: Event::isStateEvent(); fixed StateEvent::_prev always being initialised Event::isStateEvent() is an easier way to make checking an event kind (instead of enumerating through all types corresponding to state changes). StateEvent::_prev (accessible through prev_content) should only be initialised if there's a previous state in the original JSON. --- events/event.h | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/events/event.h b/events/event.h index 020ef54b..9c915e35 100644 --- a/events/event.h +++ b/events/event.h @@ -31,12 +31,18 @@ namespace QMatrixClient { Q_GADGET public: - enum class Type + enum class Type : quint16 { - RoomMessage, RoomName, RoomAliases, RoomCanonicalAlias, - RoomMember, RoomTopic, RoomAvatar, - RoomEncryption, RoomEncryptedMessage, - Typing, Receipt, Unknown + Unknown = 0, + Typing, Receipt, + RoomEventBase = 0x1000, + RoomMessage = RoomEventBase + 1, + RoomEncryptedMessage, + RoomStateEventBase = 0x1800, + RoomName = RoomStateEventBase + 1, + RoomAliases, RoomCanonicalAlias, RoomMember, RoomTopic, + RoomAvatar, RoomEncryption, + Reserved = 0x2000 }; explicit Event(Type type) : _type(type) { } @@ -44,6 +50,10 @@ namespace QMatrixClient Event(const Event&) = delete; Type type() const { return _type; } + bool isStateEvent() const + { + return (quint16(_type) & 0x1800) == 0x1800; + } QByteArray originalJson() const; QJsonObject originalJsonObject() const; @@ -152,9 +162,12 @@ namespace QMatrixClient : RoomEvent(type, obj) , _content(contentJson(), std::forward(contentParams)...) - , _prev(new ContentT(obj["prev_content"].toObject(), - std::forward(contentParams)...)) - { } + { + if (obj.contains("prev_content")) + _prev.reset(new ContentT( + obj["prev_content"].toObject(), + std::forward(contentParams)...)); + } template explicit StateEvent(Type type, ContentParamTs&&... contentParams) : RoomEvent(type) -- cgit v1.2.3 From 08fda4edfb1753aff148a664d623aa2497621965 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 1 Nov 2017 21:01:25 +0300 Subject: Imbue RoomMemberEvent with EventContent It now allows to check what exactly has happened to the member (display name change, joining, avatar update), fixing #105. --- events/roommemberevent.cpp | 43 ++++++++++++++++++++++++++++++------------- events/roommemberevent.h | 41 +++++++++++++++++++++++++++++------------ 2 files changed, 59 insertions(+), 25 deletions(-) diff --git a/events/roommemberevent.cpp b/events/roommemberevent.cpp index 19f116d2..40e14c39 100644 --- a/events/roommemberevent.cpp +++ b/events/roommemberevent.cpp @@ -22,21 +22,38 @@ using namespace QMatrixClient; -static const auto membershipStrings = - { "invite", "join", "knock", "leave", "ban" }; +static const std::array membershipStrings = + { { "invite", "join", "knock", "leave", "ban" } }; -RoomMemberEvent::RoomMemberEvent(const QJsonObject& obj) - : RoomEvent(Type::RoomMember, obj), _userId(obj["state_key"].toString()) +namespace QMatrixClient { - const auto contentObj = contentJson(); - _displayName = contentObj["displayname"].toString(); - _avatarUrl = contentObj["avatar_url"].toString(); - QString membershipString = contentObj["membership"].toString(); - for (auto it = membershipStrings.begin(); it != membershipStrings.end(); ++it) - if (membershipString == *it) + template <> + struct FromJson + { + MembershipType operator()(const QJsonValue& jv) const { - _membership = MembershipType(it - membershipStrings.begin()); - return; + const auto membershipString = jv.toString(); + for (auto it = membershipStrings.begin(); + it != membershipStrings.end(); ++it) + if (membershipString == *it) + return MembershipType(it - membershipStrings.begin()); + + qCWarning(EVENTS) << "Unknown MembershipType: " << membershipString; + return MembershipType::Join; } - qCWarning(EVENTS) << "Unknown MembershipType: " << membershipString; + }; +} + +MemberEventContent::MemberEventContent(const QJsonObject& json) + : membership(fromJson(json["membership"])) + , displayName(json["displayname"].toString()) + , avatarUrl(json["avatar_url"].toString()) +{ } + +void MemberEventContent::fillJson(QJsonObject* o) const +{ + Q_ASSERT(o); + o->insert("membership", membershipStrings[membership]); + o->insert("displayname", displayName); + o->insert("avatar_url", avatarUrl.toString()); } diff --git a/events/roommemberevent.h b/events/roommemberevent.h index 358af257..d0c63f15 100644 --- a/events/roommemberevent.h +++ b/events/roommemberevent.h @@ -20,32 +20,49 @@ #include "event.h" +#include "eventcontent.h" + #include namespace QMatrixClient { - class RoomMemberEvent: public RoomEvent + class MemberEventContent: public EventContent::Base + { + public: + enum MembershipType : size_t {Invite = 0, Join, Knock, Leave, Ban}; + + MemberEventContent(const QJsonObject& json); + + MembershipType membership; + QString displayName; + QUrl avatarUrl; + + protected: + void fillJson(QJsonObject* o) const override; + }; + + using MembershipType = MemberEventContent::MembershipType; + + class RoomMemberEvent: public StateEvent { Q_GADGET public: static constexpr const char* TypeId = "m.room.member"; - enum MembershipType : int {Invite = 0, Join, Knock, Leave, Ban}; + using MembershipType = MemberEventContent::MembershipType; - explicit RoomMemberEvent(const QJsonObject& obj); + explicit RoomMemberEvent(const QJsonObject& obj) + : StateEvent(Type::RoomMember, obj) + , _userId(obj["state_key"].toString()) + { } - MembershipType membership() const { return _membership; } - const QString& userId() const { return _userId; } - const QString& displayName() const { return _displayName; } - const QUrl& avatarUrl() const { return _avatarUrl; } + MembershipType membership() const { return content().membership; } + QString userId() const { return _userId; } + QString displayName() const { return content().displayName; } + QUrl avatarUrl() const { return content().avatarUrl; } private: - MembershipType _membership; QString _userId; - QString _displayName; - QUrl _avatarUrl; - REGISTER_ENUM(MembershipType) }; - using MembershipType = RoomMemberEvent::MembershipType; } // namespace QMatrixClient -- cgit v1.2.3 From e11404c364056f1ae23a1820a6f458275463599c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 2 Nov 2017 00:05:14 +0300 Subject: Fixed CI --- events/roommemberevent.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/events/roommemberevent.cpp b/events/roommemberevent.cpp index 40e14c39..3e1ff8c1 100644 --- a/events/roommemberevent.cpp +++ b/events/roommemberevent.cpp @@ -22,8 +22,11 @@ using namespace QMatrixClient; -static const std::array membershipStrings = - { { "invite", "join", "knock", "leave", "ban" } }; +static const std::array membershipStrings = { { + QStringLiteral("invite"), QStringLiteral("join"), + QStringLiteral("knock"), QStringLiteral("leave"), + QStringLiteral("ban") +} }; namespace QMatrixClient { @@ -32,7 +35,7 @@ namespace QMatrixClient { MembershipType operator()(const QJsonValue& jv) const { - const auto membershipString = jv.toString(); + const auto& membershipString = jv.toString(); for (auto it = membershipStrings.begin(); it != membershipStrings.end(); ++it) if (membershipString == *it) -- cgit v1.2.3 From 366a69f5c1ced61d270832a76233bda1fcdf0b6c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 2 Nov 2017 00:40:09 +0300 Subject: Fix AppVeyor CI --- events/roommemberevent.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/events/roommemberevent.cpp b/events/roommemberevent.cpp index 3e1ff8c1..76df5f2e 100644 --- a/events/roommemberevent.cpp +++ b/events/roommemberevent.cpp @@ -20,6 +20,8 @@ #include "logging.h" +#include + using namespace QMatrixClient; static const std::array membershipStrings = { { -- cgit v1.2.3 From 7ba7e2072850a59f9797ea97a05c5139c743b88f Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 2 Nov 2017 15:17:55 +0300 Subject: Update the lib version for dynamic linking --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6163b7be..9804835c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,7 +86,7 @@ aux_source_directory(jobs/generated libqmatrixclient_job_SRCS) set(example_SRCS examples/qmc-example.cpp) add_library(qmatrixclient ${libqmatrixclient_SRCS} ${libqmatrixclient_job_SRCS}) -set_property(TARGET qmatrixclient PROPERTY VERSION "0.0.0") +set_property(TARGET qmatrixclient PROPERTY VERSION "0.1.0") set_property(TARGET qmatrixclient PROPERTY SOVERSION 0 ) target_link_libraries(qmatrixclient Qt5::Core Qt5::Network Qt5::Gui) -- cgit v1.2.3 From 414621681a0c1e8bf1f5a01b376b47f24cb95aab Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 2 Nov 2017 15:19:02 +0300 Subject: Room: Move away internal methods to not mislead client authors --- room.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/room.h b/room.h index f224a8ae..f7aa363c 100644 --- a/room.h +++ b/room.h @@ -116,9 +116,6 @@ namespace QMatrixClient */ Q_INVOKABLE QString roomMembername(const QString& userId) const; - void updateData(SyncRoomData&& data ); - Q_INVOKABLE void setJoinState( JoinState state ); - const Timeline& messageEvents() const; /** * A convenience method returning the read marker to the before-oldest @@ -155,6 +152,8 @@ namespace QMatrixClient MemberSorter memberSorter() const; QJsonObject toJson() const; + void updateData(SyncRoomData&& data ); + void setJoinState( JoinState state ); public slots: void postMessage(const QString& plainText, -- cgit v1.2.3 From 352810b5945d4994369379c568eb48dd41071776 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 2 Nov 2017 15:28:41 +0300 Subject: Document room transitions in .h instead of .cpp The original comment got a bit rotten, so refresh it as well. --- connection.cpp | 13 +------------ connection.h | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/connection.cpp b/connection.cpp index 41160bd2..c940c767 100644 --- a/connection.cpp +++ b/connection.cpp @@ -343,22 +343,11 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState) return nullptr; } - // Room transitions: - // 1. none -> Invite: r=createRoom, emit invitedRoom(r,null) - // 2. none -> Join: r=createRoom, emit joinedRoom(r,null) - // 3. none -> Leave: r=createRoom, emit leftRoom(r,null) - // 4. inv=Invite -> Join: r=createRoom, emit joinedRoom(r,inv), delete Invite - // 4a. Leave, inv=Invite -> Join: change state, emit joinedRoom(r,inv), delete Invite - // 5. inv=Invite -> Leave: r=createRoom, emit leftRoom(r,inv), delete Invite - // 5a. r=Leave, inv=Invite -> Leave: emit leftRoom(r,inv), delete Invite - // 6. Join -> Leave: change state - // 7. r=Leave -> Invite: inv=createRoom, emit invitedRoom(inv,r) - // 8. Leave -> (changes to) Join const auto roomKey = qMakePair(id, joinState == JoinState::Invite); auto* room = d->roomMap.value(roomKey, nullptr); if (room) { - // Leave is a special case because in transition (5a) above + // Leave is a special case because in transition (5a) (see the .h file) // joinState == room->joinState but we still have to preempt the Invite // and emit a signal. For Invite and Join, there's no such problem. if (room->joinState() == joinState && joinState != JoinState::Leave) diff --git a/connection.h b/connection.h index 76e4f3c2..adf7a098 100644 --- a/connection.h +++ b/connection.h @@ -191,14 +191,67 @@ namespace QMatrixClient signals: void resolved(); void connected(); - void reconnected(); + void reconnected(); //< Unused; use connected() instead void loggedOut(); void syncDone(); + + /** + * \group Signals emitted on room transitions + * + * Note: Rooms in Invite state are always stored separately from + * rooms in Join/Leave state, because of special treatment of + * invite_state in Matrix CS API (see The Spec on /sync for details). + * Therefore, objects below are: r - room in Join/Leave state; + * i - room in Invite state + * + * 1. none -> Invite: newRoom(r), invitedRoom(r,nullptr) + * 2. none -> Join: newRoom(r), joinedRoom(r,nullptr) + * 3. none -> Leave: newRoom(r), leftRoom(r,nullptr) + * 4. Invite -> Join: + * newRoom(r), joinedRoom(r,i), aboutToDeleteRoom(i) + * 4a. Leave and Invite -> Join: + * joinedRoom(r,i), aboutToDeleteRoom(i) + * 5. Invite -> Leave: + * newRoom(r), leftRoom(r,i), aboutToDeleteRoom(i) + * 5a. Leave and Invite -> Leave: + * leftRoom(r,i), aboutToDeleteRoom(i) + * 6. Join -> Leave: leftRoom(r) + * 7. Leave -> Invite: newRoom(i), invitedRoom(i,r) + * 8. Leave -> Join: joinedRoom(r) + * The following transitions are only possible via forgetRoom() + * so far; if a room gets forgotten externally, sync won't tell + * about it: + * 9. any -> none: as any -> Leave, then aboutToDeleteRoom(r) + */ + + /** A new room object has been created */ void newRoom(Room* room); + + /** Invitation to a room received + * + * If the same room is in Left state, it's passed in prev. + */ void invitedRoom(Room* room, Room* prev); + + /** A room has just been joined + * + * It's not the same as receiving a room in "join" section of sync + * response (rooms will be there even after joining). If this room + * was in Invite state before, the respective object is passed in + * prev (and it will be deleted shortly afterwards). + */ void joinedRoom(Room* room, Room* prev); + + /** A room has just been left + * + * If this room has been in Invite state (as in case of rejecting + * an invitation), the respective object will be passed in prev + * (and will be deleted shortly afterwards). + */ void leftRoom(Room* room, Room* prev); + + /** The room object is about to be deleted */ void aboutToDeleteRoom(Room* room); void loginError(QString error); -- cgit v1.2.3 From 02fa295085ae2297062d10f476a0b40b7fcb2559 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 3 Nov 2017 17:53:23 +0300 Subject: Provide facilities to migrate from legacy settings location This is for a case of renaming an organisation or an application --- settings.cpp | 33 ++++++++++++++++++++++++++++----- settings.h | 22 +++++++++++++++++++++- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/settings.cpp b/settings.cpp index 3a5f4d26..98921869 100644 --- a/settings.cpp +++ b/settings.cpp @@ -5,6 +5,16 @@ using namespace QMatrixClient; +QString Settings::legacyOrganizationName {}; +QString Settings::legacyApplicationName {}; + +void Settings::setLegacyNames(const QString& organizationName, + const QString& applicationName) +{ + legacyOrganizationName = organizationName; + legacyApplicationName = applicationName; +} + void Settings::setValue(const QString& key, const QVariant& value) { // qCDebug() << "Setting" << key << "to" << value; @@ -13,22 +23,33 @@ void Settings::setValue(const QString& key, const QVariant& value) QVariant Settings::value(const QString& key, const QVariant& defaultValue) const { - return QSettings::value(key, defaultValue); + return QSettings::value(key, legacySettings.value(key, defaultValue)); +} + +bool Settings::contains(const QString& key) const +{ + return QSettings::contains(key) || legacySettings.contains(key); +} + +QStringList Settings::childGroups() const +{ + auto l = QSettings::childGroups(); + return !l.isEmpty() ? l : legacySettings.childGroups(); } void SettingsGroup::setValue(const QString& key, const QVariant& value) { - Settings::setValue(groupPath + "/" + key, value); + Settings::setValue(groupPath + '/' + key, value); } bool SettingsGroup::contains(const QString& key) const { - return Settings::contains(groupPath + "/" + key); + return Settings::contains(groupPath + '/' + key); } QVariant SettingsGroup::value(const QString& key, const QVariant& defaultValue) const { - return Settings::value(groupPath + "/" + key, defaultValue); + return Settings::value(groupPath + '/' + key, defaultValue); } QString SettingsGroup::group() const @@ -39,8 +60,10 @@ QString SettingsGroup::group() const QStringList SettingsGroup::childGroups() const { const_cast(this)->beginGroup(groupPath); - QStringList l { Settings::childGroups() }; + const_cast(legacySettings).beginGroup(groupPath); + QStringList l = Settings::childGroups(); const_cast(this)->endGroup(); + const_cast(legacySettings).endGroup(); return l; } diff --git a/settings.h b/settings.h index a6c0420e..ab3aae8b 100644 --- a/settings.h +++ b/settings.h @@ -28,7 +28,17 @@ namespace QMatrixClient { class Settings: public QSettings { + Q_OBJECT public: + /** + * Use this function before creating any Settings objects in order + * to setup a read-only location where configuration has previously + * been stored. This will provide an additional fallback in case of + * renaming the organisation/application. + */ + static void setLegacyNames(const QString& organizationName, + const QString& applicationName = {}); + #if defined(_MSC_VER) && _MSC_VER < 1900 // VS 2013 (and probably older) aren't friends with 'using' statements // that involve private constructors @@ -41,6 +51,16 @@ namespace QMatrixClient const QVariant &value); Q_INVOKABLE QVariant value(const QString &key, const QVariant &defaultValue = {}) const; + Q_INVOKABLE bool contains(const QString& key) const; + Q_INVOKABLE QStringList childGroups() const; + + private: + static QString legacyOrganizationName; + static QString legacyApplicationName; + + protected: + const QSettings legacySettings { legacyOrganizationName, + legacyApplicationName }; }; class SettingsGroup: public Settings @@ -69,7 +89,7 @@ namespace QMatrixClient class AccountSettings: public SettingsGroup { Q_OBJECT - Q_PROPERTY(QString userId READ userId) + Q_PROPERTY(QString userId READ userId CONSTANT) Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId) Q_PROPERTY(QString deviceName READ deviceName WRITE setDeviceName) Q_PROPERTY(QUrl homeserver READ homeserver WRITE setHomeserver) -- cgit v1.2.3 From 5bafd65538a877e8f186fc44d03d4b2c705820f3 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 3 Nov 2017 19:45:41 +0300 Subject: Settings: Catch literal "false" values stored by Qt.labs.Settings QML module Those stand for a boolean false value in Qt.labs.Settings, but plain JavaScript thinks that "false" == true so if you take a value through QMatrixClient::Settings instead of Qt.labs.Settings, you would get screwed. --- settings.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/settings.cpp b/settings.cpp index 98921869..68914642 100644 --- a/settings.cpp +++ b/settings.cpp @@ -23,7 +23,13 @@ void Settings::setValue(const QString& key, const QVariant& value) QVariant Settings::value(const QString& key, const QVariant& defaultValue) const { - return QSettings::value(key, legacySettings.value(key, defaultValue)); + auto value = QSettings::value(key, legacySettings.value(key, defaultValue)); + // QML's Qt.labs.Settings stores boolean values as strings, which, if loaded + // through the usual QSettings interface, confuses QML + // (QVariant("false") == true in JavaScript). Since we have a mixed + // environment where both QSettings and Qt.labs.Settings may potentially + // work with same settings, better ensure compatibility. + return value.toString() == QStringLiteral("false") ? QVariant(false) : value; } bool Settings::contains(const QString& key) const -- cgit v1.2.3 From f467add664c3a562b3a44ada9b2f59ff71a70e27 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 4 Nov 2017 18:22:01 +0300 Subject: Update README.md The documentation is now more independent from Quaternion and includes instructions not only for CMake but also for qmake. It also mentions that we have an example console client! --- README.md | 52 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 089ba492..be78d6c5 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,46 @@ # Libqmatrixclient [![license](https://img.shields.io/github/license/QMatrixClient/libqmatrixclient.svg)](https://github.com/QMatrixClient/libqmatrixclient/blob/master/COPYING) +![status](https://img.shields.io/badge/status-beta-yellow.svg) +[![release](https://img.shields.io/github/release/QMatrixClient/libqmatrixclient/all.svg)](https://github.com/QMatrixClient/Quaternion/releases/latest) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) -libqmatrixclient is a Qt-based library to make IM clients for the [Matrix](https://matrix.org) protocol. It is used by the Quaternion client and is a part of the Quaternion project. The below instructions are the same for Quaternion and libqmatrixclient (the source tree of Quaternion has most up-to-date instructions but this source tree strives to closely follow). +libqmatrixclient is a Qt5-based library to make IM clients for the [Matrix](https://matrix.org) protocol. It is the backbone of [Quaternion](https://github.com/QMatrixClient/Quaternion), [Tensor](https://matrix.org/docs/projects/client/tensor.html) and some other projects. ## Contacts -You can find authors of libqmatrixclient in the Quaternion Matrix room: [#quaternion:matrix.org](https://matrix.to/#/#quaternion:matrix.org). +You can find authors of libqmatrixclient in the Quaternion Matrix room: [#qmatrixclient:matrix.org](https://matrix.to/#/#qmatrixclient:matrix.org). -Issues should be submitted to [the project's issue tracker](https://github.com/Fxrh/libqmatrixclient/issues). We do not guarantee a response but we usually try to at least acknowledge the issue +You can also file issues at [the project's issue tracker](https://github.com/QMatrixClient/libqmatrixclient/issues). If you have what looks like a security issue, please see respective instructions in CONTRIBUTING.md. +## Building and usage +So far the library is typically used as a git submodule of another project (such as Quaternion); however it can be built separately (both as a static and as a dynamic library). There is no specific installation sequence outside of other projects but since it's a CMake-based project, your mileage should be fairly short (in case it's not, issues and PRs are most welcome). + +The source code is hosted at GitHub: https://github.com/QMatrixClient/libqmatrixclient - checking out a certain commit or tag from GitHub (rather than downloading the archive) is the recommended way for packagers. + +There are very few tags so far; there will be more, as new versions are released. ## Pre-requisites a Linux, MacOS or Windows system (desktop versions tried; mobile Linux/Windows might work too) - a Git client (to check out this repo) -- CMake (from your package management system or [the official website](https://cmake.org/download/)) -- Qt 5 (either Open Source or Commercial), version 5.2.1 or higher as of this writing (check the CMakeLists.txt for most up-to-date information). Qt 5.3 or higher recommended on Windows. +- Qt 5 (either Open Source or Commercial), version 5.2.1 or higher as of this writing (check `CMakeLists.txt` for most up-to-date information) +- qmake (from the Qt 5 installation) or CMake (from your package management system or [the official website](https://cmake.org/download/)). - a C++ toolchain supported by your version of Qt (see a link for your platform at [the Qt's platform requirements page](http://doc.qt.io/qt-5/gettingstarted.html#platform-requirements)) - GCC 4.8, Clang 3.5.0, Visual C++ 2015 are the oldest officially supported as of this writing -## Installing pre-requisites -### Linux -Just install things from "Pre-requisites" using your preferred package manager. If your Qt package base is fine-grained you might want to take a look at `CMakeLists.txt` to figure out which specific libraries libqmatrixclient uses (or blindly run cmake and look at error messages). +#### Linux +Just install things from the list above using your preferred package manager. If your Qt package base is fine-grained you might want to take a look at `CMakeLists.txt` to figure out which specific libraries libqmatrixclient uses (or blindly run cmake and look at error messages). The library is entirely offscreen (Qt::Core and Qt::Network are essential) but it also depends on Qt::Gui in order to operate with avatar thumbnails. -### OS X -`brew install qt5` should get you Qt5. You may need to tell CMake about the path to Qt by passing `-DCMAKE_PREFIX_PATH=` +#### OS X +`brew install qt5` should get you Qt5. If you plan to use CMake, you may need to tell it about the path to Qt by passing `-DCMAKE_PREFIX_PATH=` -### Windows -1. Install a Git client and CMake. The commands here imply that git and cmake are in your PATH - otherwise you have to prepend them with your actual paths. -1. Install Qt5, using their official installer. If for some reason you need to use Qt 5.2.1, select its Add-ons component in the installer as well; for later versions, no extras are needed. If you don't have a toolchain and/or IDE, you can easily get one by selecting Qt Creator and at least one toolchain under Qt Creator. -1. Make sure CMake knows about Qt and the toolchain - the easiest way is to run a qtenv2.bat script that can be found in `C:\Qt\\\bin` (assuming you installed Qt to `C:\Qt`). The only thing it does is adding necessary paths to PATH - you might not want to run it on system startup but it's very handy to setup environment before building. Setting CMAKE_PREFIX_PATH, the same way as for OS X (see above), also helps. +#### Windows +1. Install Qt5, using their official installer. If for some reason you need to use Qt 5.2.1, select its Add-ons component in the installer as well; for later versions, no extras are needed. If you don't have a toolchain and/or IDE, you can easily get one by selecting Qt Creator and at least one toolchain under Qt Creator. Qt 5.3 is recommended on Windows; `windeployqt` in Qt 5.2.1 is not functional enough to generate a proper list of files for installing. +1. If you plan to build with CMake, install CMake; if you're ok with qmake, you don't need to install anything on top of Qt. The commands in further sections imply that cmake/qmake is in your PATH - otherwise you have to prepend those commands with actual paths. As an option, it's a good idea to run a `qtenv2.bat` script that can be found in `C:\Qt\\\bin` (assuming you installed Qt to `C:\Qt`); the only thing it does is adding necessary paths to PATH. You might not want to run that script on system startup but it's very handy to setup the environment before building. For CMake, setting `CMAKE_PREFIX_PATH` in the same way as for OS X (see above), also helps. -There are no official MinGW-based 64-bit packages for Qt. If you're determined to build 64-bit libqmatrixclient, either use a Visual Studio toolchain or build Qt5 yourself as described in Qt documentation. +There are no official MinGW-based 64-bit packages for Qt. If you're determined to build a 64-bit library, either use a Visual Studio toolchain or build Qt5 yourself as described in Qt documentation. -## Source code -To get all necessary sources, simply clone the GitHub repo. If you have cloned Quaternion or Tensor sources with `--recursive`, you already have libqmatrixclient in the respective `lib` subdirectory. - -## Building +## Build +### CMake-based In the root directory of the project sources: ``` mkdir build_dir @@ -46,6 +50,14 @@ cmake --build . --target all ``` This will get you the compiled library in `build_dir` inside your project sources. Only static builds of libqmatrixclient are tested at the moment; experiments with dynamic builds are welcome. The two known projects to link with libqmatrixclient are Tensor and Quaternion; you should take a look at their source code before doing anything with libqmatrixclient on your own. +### qmake-based +The library only provides a .pri file with an intention to be included from a bigger project's .pro file. As a starting point you can use `qmc-example.pro` that will build a minimal example of library usage for you. In the root directory of the project sources: +``` +qmake qmc-example.pro +make all +``` +This will get you `debug/qmc-example` and `release/qmc-example` console executables that login to the Matrix server at matrix.org with credentials of your choosing (pass the username and password as arguments) and run a sync long-polling loop, showing some information about received events. + ## Troubleshooting If `cmake` fails with... @@ -69,4 +81,4 @@ where `*` can be used as a wildcard for any part between two dots, and comma is used for a separator. Latter statements override former ones, so if you want to switch on all debug logs except `jobs` you can set ``` QT_LOGGING_RULES="libqmatrixclient.*.debug=true,libqmatrixclient.jobs.debug=false" -``` \ No newline at end of file +``` -- cgit v1.2.3 From fa3bffd340ac512fbab77a05f10d2bf167ca2cc0 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 5 Nov 2017 09:51:02 +0300 Subject: README.md: Replace some leftover references to Quaternion --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index be78d6c5..731b0e72 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ [![license](https://img.shields.io/github/license/QMatrixClient/libqmatrixclient.svg)](https://github.com/QMatrixClient/libqmatrixclient/blob/master/COPYING) ![status](https://img.shields.io/badge/status-beta-yellow.svg) -[![release](https://img.shields.io/github/release/QMatrixClient/libqmatrixclient/all.svg)](https://github.com/QMatrixClient/Quaternion/releases/latest) +[![release](https://img.shields.io/github/release/QMatrixClient/libqmatrixclient/all.svg)](https://github.com/QMatrixClient/libqmatrixclient/releases/latest) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) libqmatrixclient is a Qt5-based library to make IM clients for the [Matrix](https://matrix.org) protocol. It is the backbone of [Quaternion](https://github.com/QMatrixClient/Quaternion), [Tensor](https://matrix.org/docs/projects/client/tensor.html) and some other projects. ## Contacts -You can find authors of libqmatrixclient in the Quaternion Matrix room: [#qmatrixclient:matrix.org](https://matrix.to/#/#qmatrixclient:matrix.org). +You can find authors of libqmatrixclient in the Matrix room: [#qmatrixclient:matrix.org](https://matrix.to/#/#qmatrixclient:matrix.org). You can also file issues at [the project's issue tracker](https://github.com/QMatrixClient/libqmatrixclient/issues). If you have what looks like a security issue, please see respective instructions in CONTRIBUTING.md. -- cgit v1.2.3 From 27bec8d012b6310bf1f23c1c879232a841605f60 Mon Sep 17 00:00:00 2001 From: Roman Plášil Date: Fri, 10 Nov 2017 17:36:54 +0800 Subject: Fix (IRC) bridge detection --- user.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user.cpp b/user.cpp index 8c8a7fdb..dea75360 100644 --- a/user.cpp +++ b/user.cpp @@ -115,7 +115,7 @@ void User::processEvent(Event* event) auto newName = e->displayName(); QRegularExpression reSuffix(" \\((IRC|Gitter)\\)$"); - auto match = reSuffix.match(d->name); + auto match = reSuffix.match(newName); if (match.hasMatch()) { d->bridged = match.captured(1); -- cgit v1.2.3 From 92ede101bbde3b853a3eed20f6c097cd7a137b9c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 10 Nov 2017 19:27:40 +0900 Subject: Add Telegram to the list of bridge suffixes --- user.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user.cpp b/user.cpp index dea75360..faad6231 100644 --- a/user.cpp +++ b/user.cpp @@ -114,7 +114,7 @@ void User::processEvent(Event* event) return; auto newName = e->displayName(); - QRegularExpression reSuffix(" \\((IRC|Gitter)\\)$"); + QRegularExpression reSuffix(" \\((IRC|Gitter|Telegram)\\)$"); auto match = reSuffix.match(newName); if (match.hasMatch()) { -- cgit v1.2.3 From e8ced4bb607084a3771f853bd7dcb6a4396aef59 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 16 Nov 2017 13:01:25 +0900 Subject: Automate cache resets The idea is simple: store a version in the cache; if, upon the next load, the (major) version is too old, the cache is discarded. The currently used version values (0.0) do not discard the cache; but the next commit will bump the (major) version. --- connection.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/connection.cpp b/connection.cpp index c940c767..d15067dd 100644 --- a/connection.cpp +++ b/connection.cpp @@ -402,6 +402,9 @@ QByteArray Connection::generateTxnId() return d->data->generateTxnId(); } +static constexpr int CACHE_VERSION_MAJOR = 0; +static constexpr int CACHE_VERSION_MINOR = 0; + void Connection::saveState(const QUrl &toFile) const { if (!d->cacheState) @@ -447,6 +450,11 @@ void Connection::saveState(const QUrl &toFile) const rootObj.insert("next_batch", d->data->lastEvent()); rootObj.insert("rooms", roomObj); + QJsonObject versionObj; + versionObj.insert("major", CACHE_VERSION_MAJOR); + versionObj.insert("minor", CACHE_VERSION_MINOR); + rootObj.insert("cache_version", versionObj); + QByteArray data = QJsonDocument(rootObj).toJson(QJsonDocument::Compact); qCDebug(MAIN) << "Writing state to file" << outfile.fileName(); @@ -472,8 +480,22 @@ void Connection::loadState(const QUrl &fromFile) file.open(QFile::ReadOnly); QByteArray data = file.readAll(); + auto jsonDoc = QJsonDocument::fromJson(data); + auto actualCacheVersionMajor = + jsonDoc.object() + .value("cache_version").toObject() + .value("major").toInt(); + if (actualCacheVersionMajor < CACHE_VERSION_MAJOR) + { + qCWarning(MAIN) << "Major version of the cache file is" + << actualCacheVersionMajor << "but" + << CACHE_VERSION_MAJOR + << "required; discarding the cache"; + return; + } + SyncData sync; - sync.parseJson(QJsonDocument::fromJson(data)); + sync.parseJson(jsonDoc); onSyncSuccess(std::move(sync)); qCDebug(PROFILER) << "*** Cached state for" << userId() << "loaded in" << et.elapsed() << "ms"; -- cgit v1.2.3 From f12e09ea1b45be1a96533aa83f6227940c70d548 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 16 Nov 2017 14:03:55 +0900 Subject: Require state_key to be present in all state events This impacts the cache as well, as we don't save state_keys for most state events. --- connection.cpp | 2 +- events/event.h | 3 ++- room.cpp | 19 ++++++++++--------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/connection.cpp b/connection.cpp index d15067dd..37d216d7 100644 --- a/connection.cpp +++ b/connection.cpp @@ -402,7 +402,7 @@ QByteArray Connection::generateTxnId() return d->data->generateTxnId(); } -static constexpr int CACHE_VERSION_MAJOR = 0; +static constexpr int CACHE_VERSION_MAJOR = 1; static constexpr int CACHE_VERSION_MINOR = 0; void Connection::saveState(const QUrl &toFile) const diff --git a/events/event.h b/events/event.h index 9c915e35..c0c1b603 100644 --- a/events/event.h +++ b/events/event.h @@ -159,7 +159,8 @@ namespace QMatrixClient template explicit StateEvent(Type type, const QJsonObject& obj, ContentParamTs&&... contentParams) - : RoomEvent(type, obj) + : RoomEvent(obj.contains("state_key") ? type : Type::Unknown, + obj) , _content(contentJson(), std::forward(contentParams)...) { diff --git a/room.cpp b/room.cpp index c4e4c6cd..c5c4e721 100644 --- a/room.cpp +++ b/room.cpp @@ -973,7 +973,7 @@ void Room::Private::updateDisplayname() } template -void appendEventJson(QJsonArray& events, const QString& type, +void appendStateEvent(QJsonArray& events, const QString& type, const QString& name, const T& content) { if (content.isEmpty()) @@ -985,6 +985,7 @@ void appendEventJson(QJsonArray& events, const QString& type, QJsonObject eventObj; eventObj.insert("type", type); eventObj.insert("content", contentObj); + eventObj.insert("state_key", ""); // Mandatory for state events events.append(eventObj); } @@ -995,14 +996,14 @@ QJsonObject Room::Private::toJson() const { QJsonArray stateEvents; - appendEventJson(stateEvents, "m.room.name", "name", name); - appendEventJson(stateEvents, "m.room.topic", "topic", topic); - appendEventJson(stateEvents, "m.room.avatar", "url", - avatar.url().toString()); - appendEventJson(stateEvents, "m.room.aliases", "aliases", - QJsonArray::fromStringList(aliases)); - appendEventJson(stateEvents, "m.room.canonical_alias", "alias", - canonicalAlias); + appendStateEvent(stateEvents, "m.room.name", "name", name); + appendStateEvent(stateEvents, "m.room.topic", "topic", topic); + appendStateEvent(stateEvents, "m.room.avatar", "url", + avatar.url().toString()); + appendStateEvent(stateEvents, "m.room.aliases", "aliases", + QJsonArray::fromStringList(aliases)); + appendStateEvent(stateEvents, "m.room.canonical_alias", "alias", + canonicalAlias); for (const auto &i : membersMap) { -- cgit v1.2.3 From 491e392af73be3ebfd928e80efc0514fd43b8e87 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 16 Nov 2017 14:10:08 +0900 Subject: Simplify code that loads events from JSON arrays --- events/event.h | 41 +++++++++++++++++++++++------------------ jobs/roommessagesjob.cpp | 4 ++-- jobs/syncjob.h | 5 ++--- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/events/event.h b/events/event.h index c0c1b603..cc99b57b 100644 --- a/events/event.h +++ b/events/event.h @@ -63,6 +63,10 @@ namespace QMatrixClient // (and in most cases it will be a combination of other fields // instead of "content" field). + /** Create an event with proper type from a JSON object + * Use this factory to detect the type from the JSON object contents + * and create an event object of that type. + */ static Event* fromJson(const QJsonObject& obj); protected: @@ -77,26 +81,27 @@ namespace QMatrixClient Q_PROPERTY(QJsonObject contentJson READ contentJson CONSTANT) }; using EventType = Event::Type; - template - using EventsBatch = std::vector; - using Events = EventsBatch; - template > - inline BatchT makeEvents(const QJsonArray& objs) + template + class EventsBatch : public std::vector { - BatchT evs; - // The below line accommodates the difference in size types of - // STL and Qt containers. - evs.reserve(static_cast(objs.size())); - for (auto objValue: objs) - { - const auto o = objValue.toObject(); - auto e = BaseEventT::fromJson(o); - evs.push_back(e ? e : new BaseEventT(EventType::Unknown, o)); - } - return evs; - } + public: + void fromJson(const QJsonObject& container, const QString& node) + { + const auto objs = container.value(node).toArray(); + using size_type = typename std::vector::size_type; + // The below line accommodates the difference in size types of + // STL and Qt containers. + this->reserve(static_cast(objs.size())); + for (auto objValue: objs) + { + const auto o = objValue.toObject(); + auto e = EventT::fromJson(o); + this->push_back(e ? e : new EventT(EventType::Unknown, o)); + } + } + }; + using Events = EventsBatch; /** This class corresponds to m.room.* events */ class RoomEvent : public Event diff --git a/jobs/roommessagesjob.cpp b/jobs/roommessagesjob.cpp index 078c692a..c527cc71 100644 --- a/jobs/roommessagesjob.cpp +++ b/jobs/roommessagesjob.cpp @@ -58,8 +58,8 @@ QString RoomMessagesJob::end() const BaseJob::Status RoomMessagesJob::parseJson(const QJsonDocument& data) { - QJsonObject obj = data.object(); - d->events.assign(makeEvents(obj.value("chunk").toArray())); + const auto obj = data.object(); + d->events.fromJson(obj, "chunk"); d->end = obj.value("end").toString(); return Success; } diff --git a/jobs/syncjob.h b/jobs/syncjob.h index b1db914d..08bd773e 100644 --- a/jobs/syncjob.h +++ b/jobs/syncjob.h @@ -36,11 +36,10 @@ namespace QMatrixClient explicit Batch(QString k) : jsonKey(std::move(k)) { } void fromJson(const QJsonObject& roomContents) { - this->assign(makeEvents( - roomContents[jsonKey].toObject()["events"].toArray())); + EventsBatch::fromJson( + roomContents[jsonKey].toObject(), "events"); } - private: QString jsonKey; }; -- cgit v1.2.3 From d8a5a5fb25f3b00fd52a707a8d0e3655cd63a4fe Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 16 Nov 2017 14:20:39 +0900 Subject: Fix CI --- room.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/room.cpp b/room.cpp index c5c4e721..6e6e7e39 100644 --- a/room.cpp +++ b/room.cpp @@ -985,7 +985,7 @@ void appendStateEvent(QJsonArray& events, const QString& type, QJsonObject eventObj; eventObj.insert("type", type); eventObj.insert("content", contentObj); - eventObj.insert("state_key", ""); // Mandatory for state events + eventObj.insert("state_key", {}); // Mandatory for state events events.append(eventObj); } -- cgit v1.2.3 From afbbb6b296e43d7e63c568d326cf8732396da387 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 20 Nov 2017 19:28:56 +0900 Subject: Slightly rearrange code so that the NAM singleton could be configured easier --- connectiondata.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/connectiondata.cpp b/connectiondata.cpp index 6ef293cd..5b6ac71d 100644 --- a/connectiondata.cpp +++ b/connectiondata.cpp @@ -24,10 +24,9 @@ using namespace QMatrixClient; -QNetworkAccessManager* getNam() +QNetworkAccessManager* createNam() { - static QNetworkAccessManager* _nam = new QNetworkAccessManager(); - return _nam; + return new QNetworkAccessManager(); } struct ConnectionData::Private @@ -64,7 +63,8 @@ QUrl ConnectionData::baseUrl() const QNetworkAccessManager* ConnectionData::nam() const { - return getNam(); + static auto nam = createNam(); + return nam; } void ConnectionData::setToken(QByteArray token) -- cgit v1.2.3 From 6fe34a3fc474c298a2df160343b9b3f09caffac2 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 21 Nov 2017 16:40:53 +0900 Subject: Workaround Qt's poor bearer management Closes #109 --- connectiondata.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/connectiondata.cpp b/connectiondata.cpp index 5b6ac71d..a982ed3e 100644 --- a/connectiondata.cpp +++ b/connectiondata.cpp @@ -26,7 +26,14 @@ using namespace QMatrixClient; QNetworkAccessManager* createNam() { - return new QNetworkAccessManager(); + auto nam = new QNetworkAccessManager(); + // See #109. Once Qt bearer management gets better, this workaround + // should become unnecessary. + nam->connect(nam, &QNetworkAccessManager::networkAccessibleChanged, + nam, [=] { + nam->setNetworkAccessible(QNetworkAccessManager::Accessible); + }); + return nam; } struct ConnectionData::Private -- cgit v1.2.3 From fed4b4fe965a9e2788055b602c7217cb298f5fce Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 21 Nov 2017 16:54:39 +0900 Subject: Mitigate the mess with slashes between base URL and endpoint string Closes #125 --- jobs/basejob.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/jobs/basejob.cpp b/jobs/basejob.cpp index 20fb68f6..9d5c5ed6 100644 --- a/jobs/basejob.cpp +++ b/jobs/basejob.cpp @@ -137,7 +137,11 @@ void BaseJob::setRequestData(const BaseJob::Data& data) void BaseJob::Private::sendRequest() { QUrl url = connection->baseUrl(); - url.setPath( url.path() + "/" + apiEndpoint ); + QString path = url.path(); + if (!path.endsWith('/') && !apiEndpoint.startsWith('/')) + path.push_back('/'); + + url.setPath( path + apiEndpoint ); url.setQuery(requestQuery); QNetworkRequest req {url}; @@ -207,6 +211,7 @@ void BaseJob::gotReply() BaseJob::Status BaseJob::checkReply(QNetworkReply* reply) const { + qCDebug(d->logCat) << this << "returned from" << reply->url().toDisplayString(); if (reply->error() != QNetworkReply::NoError) qCDebug(d->logCat) << this << "returned" << reply->error(); switch( reply->error() ) -- cgit v1.2.3 From 8ec38f87990cd5355a4cf0cf0533a6459fa2f6e3 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 21 Nov 2017 17:12:20 +0900 Subject: Eliminate race condition in Avatar::get() The race occurred because _ongoingRequest wasn't properly reinitialized if another request on a bigger size arrives while a request for a smaller size is in the air. The old request is now abandoned and continuations are collected inside the Avatar object rather than in the lambda connected to the job. Closes #124. Along the way, _scaledPixmaps is now std::vector instead of QHash since usually it only contains very few (1 or 2) entries and QHash is a waste of memory for that. --- avatar.cpp | 24 ++++++++++++++---------- avatar.h | 9 +++++---- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/avatar.cpp b/avatar.cpp index 0ce9210b..f5101ddb 100644 --- a/avatar.cpp +++ b/avatar.cpp @@ -24,7 +24,7 @@ using namespace QMatrixClient; -QPixmap Avatar::get(int width, int height, std::function continuation) +QPixmap Avatar::get(int width, int height, Avatar::notifier_t notifier) { QSize size(width, height); @@ -32,12 +32,15 @@ QPixmap Avatar::get(int width, int height, std::function continuation) // is a sure way to trick the below code into constantly getting another // image from the server because the existing one is alleged unsatisfactory. // This is plain abuse by the client, though; so not critical for now. - if( (!_valid && _url.isValid() && !_ongoingRequest) - || width > _requestedSize.width() - || height > _requestedSize.height() ) + if( ( !(_valid || _ongoingRequest) + || width > _requestedSize.width() + || height > _requestedSize.height() ) && _url.isValid() ) { qCDebug(MAIN) << "Getting avatar from" << _url.toString(); _requestedSize = size; + if (_ongoingRequest) + _ongoingRequest->abandon(); + notifiers.emplace_back(std::move(notifier)); _ongoingRequest = _connection->callApi(_url, size); _ongoingRequest->connect( _ongoingRequest, &MediaThumbnailJob::finished, _connection, [=]() { @@ -46,7 +49,8 @@ QPixmap Avatar::get(int width, int height, std::function continuation) _valid = true; _originalPixmap = _ongoingRequest->scaledThumbnail(_requestedSize); _scaledPixmaps.clear(); - continuation(); + for (auto n: notifiers) + n(); } _ongoingRequest = nullptr; }); @@ -60,12 +64,12 @@ QPixmap Avatar::get(int width, int height, std::function continuation) _originalPixmap = _defaultIcon.pixmap(size); } - auto& pixmap = _scaledPixmaps[{width, height}]; // Create if needed - if (pixmap.isNull()) - { - pixmap = _originalPixmap.scaled(size, + for (auto p: _scaledPixmaps) + if (p.first == size) + return p.second; + auto pixmap = _originalPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - } + _scaledPixmaps.emplace_back(size, pixmap); return pixmap; } diff --git a/avatar.h b/avatar.h index 6889e839..60cf3779 100644 --- a/avatar.h +++ b/avatar.h @@ -35,7 +35,9 @@ namespace QMatrixClient : _defaultIcon(std::move(defaultIcon)), _connection(connection) { } - QPixmap get(int w, int h, std::function continuation); + using notifier_t = std::function; + + QPixmap get(int w, int h, notifier_t notifier); QUrl url() const { return _url; } bool updateUrl(const QUrl& newUrl); @@ -45,13 +47,12 @@ namespace QMatrixClient QPixmap _originalPixmap; QIcon _defaultIcon; - /// Map of requested size to the actual pixmap used for it - /// (it's a shame that QSize has no predefined qHash()). - QHash, QPixmap> _scaledPixmaps; + std::vector> _scaledPixmaps; QSize _requestedSize; bool _valid = false; Connection* _connection; MediaThumbnailJob* _ongoingRequest = nullptr; + std::vector notifiers; }; } // namespace QMatrixClient -- cgit v1.2.3 From 128b7259a7f6cc39545711323a074103ea9520f7 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 21 Nov 2017 17:33:26 +0900 Subject: Use QStringLiteral instead of bare QString constructors for string constants For job endpoints, we should eventually move to QLatin1String or QByteArray instead. Maybe later. --- jobs/joinroomjob.cpp | 2 +- jobs/roommessagesjob.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jobs/joinroomjob.cpp b/jobs/joinroomjob.cpp index d465dd42..66a75089 100644 --- a/jobs/joinroomjob.cpp +++ b/jobs/joinroomjob.cpp @@ -29,7 +29,7 @@ class JoinRoomJob::Private JoinRoomJob::JoinRoomJob(const QString& roomAlias) : BaseJob(HttpVerb::Post, "JoinRoomJob", - QString("_matrix/client/r0/join/%1").arg(roomAlias)) + QStringLiteral("_matrix/client/r0/join/%1").arg(roomAlias)) , d(new Private) { } diff --git a/jobs/roommessagesjob.cpp b/jobs/roommessagesjob.cpp index c527cc71..9af1b3a6 100644 --- a/jobs/roommessagesjob.cpp +++ b/jobs/roommessagesjob.cpp @@ -30,7 +30,7 @@ class RoomMessagesJob::Private RoomMessagesJob::RoomMessagesJob(const QString& roomId, const QString& from, int limit, FetchDirection dir) : BaseJob(HttpVerb::Get, "RoomMessagesJob", - QString("/_matrix/client/r0/rooms/%1/messages").arg(roomId), + QStringLiteral("/_matrix/client/r0/rooms/%1/messages").arg(roomId), Query( { { "from", from } , { "dir", dir == FetchDirection::Backward ? "b" : "f" } -- cgit v1.2.3 From 795fa8c45bec3569cc74f02750d831031dbe29d1 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 22 Nov 2017 14:28:09 +0900 Subject: Force-create NAM earlier To somewhat ease a bump at sending the first network request. --- connectiondata.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/connectiondata.cpp b/connectiondata.cpp index a982ed3e..4276fd19 100644 --- a/connectiondata.cpp +++ b/connectiondata.cpp @@ -50,6 +50,7 @@ struct ConnectionData::Private ConnectionData::ConnectionData(QUrl baseUrl) : d(new Private) { + nam(); // Just to ensure NAM is created d->baseUrl = baseUrl; } -- cgit v1.2.3 From f772058ab4d57c9503bb1e1e4354c246bde7cdb6 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 22 Nov 2017 14:29:09 +0900 Subject: Use unique_ptr for connectionData --- connection.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/connection.cpp b/connection.cpp index 37d216d7..7aa2c4eb 100644 --- a/connection.cpp +++ b/connection.cpp @@ -46,15 +46,13 @@ class Connection::Private explicit Private(const QUrl& serverUrl) : q(nullptr) , data(new ConnectionData(serverUrl)) - , syncJob(nullptr) { } Q_DISABLE_COPY(Private) Private(Private&&) = delete; Private operator=(Private&&) = delete; - ~Private() { delete data; } Connection* q; - ConnectionData* data; + std::unique_ptr data; // A complex key below is a pair of room name and whether its // state is Invited. The spec mandates to keep Invited room state // separately so we should, e.g., keep objects for Invite and @@ -63,7 +61,7 @@ class Connection::Private QHash userMap; QString userId; - SyncJob* syncJob; + SyncJob* syncJob = nullptr; bool cacheState = true; }; @@ -331,7 +329,7 @@ QHash< QPair, Room* > Connection::roomMap() const const ConnectionData* Connection::connectionData() const { - return d->data; + return d->data.get(); } Room* Connection::provideRoom(const QString& id, JoinState joinState) -- cgit v1.2.3 From defbad696b924835de9bb8b16e0055a6cc983566 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 22 Nov 2017 14:34:40 +0900 Subject: Undeprecated Connection::getThumbnail(), moved Q_INVOKABLE calls to slots Connection::getThumbnail() should not have been deprecated because it's the only way to request a thumbnail using an event loop (i.e. from QML or via QMetaObject::invokeMethod). --- connection.h | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/connection.h b/connection.h index adf7a098..da211371 100644 --- a/connection.h +++ b/connection.h @@ -61,27 +61,6 @@ namespace QMatrixClient QHash, Room*> roomMap() const; - // Old API that will be abolished any time soon. DO NOT USE. - - /** @deprecated Use callApi() or Room::postMessage() instead */ - Q_INVOKABLE virtual void postMessage(Room* room, const QString& type, - const QString& message) const; - /** @deprecated Use callApi() or Room::postReceipt() instead */ - Q_INVOKABLE virtual PostReceiptJob* postReceipt(Room* room, - RoomEvent* event) const; - /** @deprecated Use callApi() instead */ - Q_INVOKABLE virtual JoinRoomJob* joinRoom(const QString& roomAlias); - /** @deprecated Use callApi() or Room::leaveRoom() instead */ - Q_INVOKABLE virtual void leaveRoom( Room* room ); - /** @deprecated User callApi() or Room::getPreviousContent() instead */ - Q_INVOKABLE virtual RoomMessagesJob* getMessages(Room* room, - const QString& from) const; - /** @deprecated Use callApi() instead */ - virtual MediaThumbnailJob* getThumbnail(const QUrl& url, - QSize requestedSize) const; - /** @deprecated Use callApi() instead */ - MediaThumbnailJob* getThumbnail(const QUrl& url, int requestedWidth, - int requestedHeight) const; /** Sends /forget to the server and also deletes room locally. * This method is in Connection, not in Room, since it's a * room lifecycle operation, and Connection is an acting room manager. @@ -188,6 +167,27 @@ namespace QMatrixClient void sync(int timeout = -1); void stopSync(); + virtual MediaThumbnailJob* getThumbnail(const QUrl& url, + QSize requestedSize) const; + MediaThumbnailJob* getThumbnail(const QUrl& url, + int requestedWidth, + int requestedHeight) const; + + // Old API that will be abolished any time soon. DO NOT USE. + + /** @deprecated Use callApi() or Room::postMessage() instead */ + virtual void postMessage(Room* room, const QString& type, + const QString& message) const; + /** @deprecated Use callApi() or Room::postReceipt() instead */ + virtual PostReceiptJob* postReceipt(Room* room, + RoomEvent* event) const; + /** @deprecated Use callApi() instead */ + virtual JoinRoomJob* joinRoom(const QString& roomAlias); + /** @deprecated Use callApi() or Room::leaveRoom() instead */ + virtual void leaveRoom( Room* room ); + /** @deprecated User callApi() or Room::getPreviousContent() instead */ + virtual RoomMessagesJob* getMessages(Room* room, + const QString& from) const; signals: void resolved(); void connected(); -- cgit v1.2.3 From ca70d70f441d93a1da431a8fa3e927396e2abf70 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 25 Nov 2017 19:52:58 +0900 Subject: ConnectionData::setBaseUrl() --- connectiondata.cpp | 6 ++++++ connectiondata.h | 1 + 2 files changed, 7 insertions(+) diff --git a/connectiondata.cpp b/connectiondata.cpp index 4276fd19..70791952 100644 --- a/connectiondata.cpp +++ b/connectiondata.cpp @@ -75,6 +75,12 @@ QNetworkAccessManager* ConnectionData::nam() const return nam; } +void ConnectionData::setBaseUrl(QUrl baseUrl) +{ + d->baseUrl = baseUrl; + qCDebug(MAIN) << "updated baseUrl to" << d->baseUrl; +} + void ConnectionData::setToken(QByteArray token) { d->accessToken = token; diff --git a/connectiondata.h b/connectiondata.h index 933219ea..530a52ee 100644 --- a/connectiondata.h +++ b/connectiondata.h @@ -35,6 +35,7 @@ namespace QMatrixClient const QString& deviceId() const; QNetworkAccessManager* nam() const; + void setBaseUrl(QUrl baseUrl); void setToken(QByteArray accessToken); void setHost( QString host ); void setPort( int port ); -- cgit v1.2.3 From ad12695aceabc7db7cbdfdd760175c65f4f3d0d1 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 25 Nov 2017 19:59:47 +0900 Subject: Make Connection::resolveServer() work as it should Closes #119; many thanks to @r0kk3rz for prodding me into that. --- connection.cpp | 73 +++++++++++++++++++++++++++++++++++++++++++++++----------- connection.h | 10 +++++++- 2 files changed, 68 insertions(+), 15 deletions(-) diff --git a/connection.cpp b/connection.cpp index 7aa2c4eb..bb9cdd32 100644 --- a/connection.cpp +++ b/connection.cpp @@ -37,6 +37,7 @@ #include #include #include +#include using namespace QMatrixClient; @@ -85,27 +86,61 @@ Connection::~Connection() delete d; } -void Connection::resolveServer(const QString& domain) +void Connection::resolveServer(const QString& mxidOrDomain) { - // Find the Matrix server for the given domain. - QScopedPointer dns { new QDnsLookup() }; + // At this point we may have something as complex as + // @username:[IPv6:address]:port, or as simple as a plain domain name. + + // Try to parse as an FQID; if there's no @ part, assume it's a domain name. + QRegularExpression parser( + "^(@.+?:)?" // Optional username (allow everything for compatibility) + "((\\[[^]]+\\]|[^:@]+)" // Either IPv6 address or hostname/IPv4 address + "(:\\d{1,5})?)$", // Optional port + QRegularExpression::UseUnicodePropertiesOption); // Because asian digits + auto match = parser.match(mxidOrDomain); + + QUrl maybeBaseUrl = QUrl::fromUserInput(match.captured(2)); + if (!match.hasMatch() || !maybeBaseUrl.isValid()) + { + emit resolveError( + tr("%1 is not a valid homeserver address") + .arg(maybeBaseUrl.toString())); + return; + } + + maybeBaseUrl.setScheme("https"); // Instead of the Qt-default "http" + if (maybeBaseUrl.port() != -1) + { + setHomeserver(maybeBaseUrl); + emit resolved(); + return; + } + + auto domain = maybeBaseUrl.host(); + qCDebug(MAIN) << "Resolving server" << domain; + // Check if the Matrix server has a dedicated service record. + QDnsLookup* dns = new QDnsLookup(); dns->setType(QDnsLookup::SRV); dns->setName("_matrix._tcp." + domain); dns->lookup(); - connect(dns.data(), &QDnsLookup::finished, [&]() { - // Check the lookup succeeded. - if (dns->error() != QDnsLookup::NoError || - dns->serviceRecords().isEmpty()) { - emit resolveError("DNS lookup failed"); - return; + connect(dns, &QDnsLookup::finished, [this,dns,maybeBaseUrl]() { + QUrl baseUrl { maybeBaseUrl }; + if (dns->error() == QDnsLookup::NoError && + dns->serviceRecords().isEmpty()) + { + auto record = dns->serviceRecords().front(); + baseUrl.setHost(record.target()); + baseUrl.setPort(record.port()); + qCDebug(MAIN) << "SRV record for" << maybeBaseUrl.host() + << "is" << baseUrl.authority(); + } else { + qCDebug(MAIN) << baseUrl.host() << "doesn't have SRV record" + << dns->name() << "- using the hostname as is"; } - - // Handle the results. - auto record = dns->serviceRecords().front(); - d->data->setHost(record.target()); - d->data->setPort(record.port()); + setHomeserver(baseUrl); emit resolved(); + dns->deleteLater(); }); } @@ -395,11 +430,21 @@ Connection::room_factory_t Connection::createRoom = Connection::user_factory_t Connection::createUser = [](Connection* c, const QString& id) { return new User(id, c); }; + QByteArray Connection::generateTxnId() { return d->data->generateTxnId(); } +void Connection::setHomeserver(const QUrl& url) +{ + if (d->data->baseUrl() == url) + return; + + d->data->setBaseUrl(url); + emit homeserverChanged(url); +} + static constexpr int CACHE_VERSION_MAJOR = 1; static constexpr int CACHE_VERSION_MINOR = 0; diff --git a/connection.h b/connection.h index da211371..cd74b512 100644 --- a/connection.h +++ b/connection.h @@ -75,6 +75,8 @@ namespace QMatrixClient */ ForgetRoomJob* forgetRoom(const QString& id); + // FIXME: Convert Q_INVOKABLEs to Q_PROPERTIES + // (breaks back-compatibility) Q_INVOKABLE QUrl homeserver() const; Q_INVOKABLE User* user(const QString& userId); Q_INVOKABLE User* user(); @@ -153,7 +155,12 @@ namespace QMatrixClient } public slots: - void resolveServer(const QString& domain); + /** Set the homeserver base URL */ + void setHomeserver(const QUrl& baseUrl); + + /** Determine and set the homeserver from domain or MXID */ + void resolveServer(const QString& mxidOrDomain); + void connectToServer(const QString& user, const QString& password, const QString& initialDeviceName, const QString& deviceId = {}); @@ -190,6 +197,7 @@ namespace QMatrixClient const QString& from) const; signals: void resolved(); + void homeserverChanged(QUrl baseUrl); void connected(); void reconnected(); //< Unused; use connected() instead void loggedOut(); -- cgit v1.2.3 From ab3680f61222a1e915cf6fc4e14f6892c9c99341 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 26 Nov 2017 10:15:12 +0900 Subject: Deprecated resolved(); grouped signals from the same operations --- connection.h | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/connection.h b/connection.h index cd74b512..999dcd71 100644 --- a/connection.h +++ b/connection.h @@ -196,13 +196,29 @@ namespace QMatrixClient virtual RoomMessagesJob* getMessages(Room* room, const QString& from) const; signals: + /** + * @deprecated + * This was a signal resulting from a successful resolveServer(). + * Since Connection now provides setHomeserver(), the HS URL + * may change even without resolveServer() invocation. Use + * homeserverChanged() instead of resolved(). You can also use + * connectToServer and connectWithToken without the HS URL set in + * advance (i.e. without calling resolveServer), as they now trigger + * server name resolution from MXID if the server URL is not valid. + */ void resolved(); + void resolveError(QString error); + void homeserverChanged(QUrl baseUrl); + void connected(); void reconnected(); //< Unused; use connected() instead void loggedOut(); + void loginError(QString error); + void networkError(size_t nextAttempt, int inMilliseconds); void syncDone(); + void syncError(QString error); /** * \group Signals emitted on room transitions @@ -262,12 +278,6 @@ namespace QMatrixClient /** The room object is about to be deleted */ void aboutToDeleteRoom(Room* room); - void loginError(QString error); - void networkError(size_t nextAttempt, int inMilliseconds); - void resolveError(QString error); - void syncError(QString error); - //void jobError(BaseJob* job); - void cacheStateChanged(); protected: -- cgit v1.2.3 From ca1d23cf72d4f902dc2da6be0faf93774a6c5583 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 26 Nov 2017 10:21:55 +0900 Subject: Make connectToServer/connectWithToken auto-resolve HS if needed "Needed" means when the current HS URL is invalid even by its outlooks. If it is just inaccessible, no attempt to fix things will be made. This breaks compatibility with previous library behaviour because connectWithToken historically has been fully synchronous and clients depend on that. connectWithToken _may_ work synchronously if HS URL is good enough; but this is no more guaranteed. Moreover, in the future the server will be probed via /versions before working, so connectWithToken will become entirely asynchronous. --- connection.cpp | 83 +++++++++++++++++++++++++++++++++++++++++++++++----------- connection.h | 25 +++++++++++++++--- 2 files changed, 89 insertions(+), 19 deletions(-) diff --git a/connection.cpp b/connection.cpp index bb9cdd32..78ef9754 100644 --- a/connection.cpp +++ b/connection.cpp @@ -65,6 +65,9 @@ class Connection::Private SyncJob* syncJob = nullptr; bool cacheState = true; + + void connectWithToken(const QString& user, const QString& accessToken, + const QString& deviceId); }; Connection::Connection(const QUrl& server, QObject* parent) @@ -123,7 +126,6 @@ void Connection::resolveServer(const QString& mxidOrDomain) dns->setType(QDnsLookup::SRV); dns->setName("_matrix._tcp." + domain); - dns->lookup(); connect(dns, &QDnsLookup::finished, [this,dns,maybeBaseUrl]() { QUrl baseUrl { maybeBaseUrl }; if (dns->error() == QDnsLookup::NoError && @@ -142,33 +144,81 @@ void Connection::resolveServer(const QString& mxidOrDomain) emit resolved(); dns->deleteLater(); }); + dns->lookup(); } void Connection::connectToServer(const QString& user, const QString& password, const QString& initialDeviceName, const QString& deviceId) { - auto loginJob = callApi(QStringLiteral("m.login.password"), user, - /*medium*/ "", /*address*/ "", password, /*token*/ "", + checkAndConnect(user, + [=] { + doConnectToServer(user, password, initialDeviceName, deviceId); + }); +} +void Connection::doConnectToServer(const QString& user, const QString& password, + const QString& initialDeviceName, + const QString& deviceId) +{ + auto loginJob = callApi(QStringLiteral("m.login.password"), + user, /*medium*/ "", /*address*/ "", password, /*token*/ "", deviceId, initialDeviceName); - connect( loginJob, &BaseJob::success, [=] () { - connectWithToken(loginJob->user_id(), loginJob->access_token(), - loginJob->device_id()); - }); - connect( loginJob, &BaseJob::failure, [=] () { - emit loginError(loginJob->errorString()); - }); + connect(loginJob, &BaseJob::success, this, + [=] { + d->connectWithToken(loginJob->user_id(), loginJob->access_token(), + loginJob->device_id()); + }); + connect(loginJob, &BaseJob::failure, this, + [=] { + emit loginError(loginJob->errorString()); + }); } void Connection::connectWithToken(const QString& userId, - const QString& accessToken, const QString& deviceId) + const QString& accessToken, + const QString& deviceId) +{ + checkAndConnect(userId, + [=] { d->connectWithToken(userId, accessToken, deviceId); }); +} + +void Connection::Private::connectWithToken(const QString& user, + const QString& accessToken, + const QString& deviceId) { - d->userId = userId; - d->data->setToken(accessToken.toLatin1()); - d->data->setDeviceId(deviceId); - qCDebug(MAIN) << "Using server" << d->data->baseUrl() << "by user" << userId + userId = user; + data->setToken(accessToken.toLatin1()); + data->setDeviceId(deviceId); + qCDebug(MAIN) << "Using server" << data->baseUrl() << "by user" + << userId << "from device" << deviceId; - emit connected(); + emit q->connected(); + +} + +void Connection::checkAndConnect(const QString& userId, + std::function connectFn) +{ + if (d->data->baseUrl().isValid()) + { + connectFn(); + return; + } + // Not good to go, try to fix the homeserver URL. + if (userId.startsWith('@') && userId.indexOf(':') != -1) + { + // The below construct makes a single-shot connection that triggers + // on the signal and then self-disconnects. + // NB: doResolveServer can emit resolveError, so this is a part of + // checkAndConnect function contract. + QMetaObject::Connection connection; + connection = connect(this, &Connection::homeserverChanged, + this, [=] { connectFn(); disconnect(connection); }); + resolveServer(userId); + } else + emit resolveError( + tr("%1 is an invalid homeserver URL") + .arg(d->data->baseUrl().toString())); } void Connection::logout() @@ -565,3 +615,4 @@ void Connection::setCacheState(bool newValue) emit cacheStateChanged(); } } + diff --git a/connection.h b/connection.h index 999dcd71..256dbd5f 100644 --- a/connection.h +++ b/connection.h @@ -27,6 +27,8 @@ #include +class QDnsLookup; + namespace QMatrixClient { class Room; @@ -162,10 +164,10 @@ namespace QMatrixClient void resolveServer(const QString& mxidOrDomain); void connectToServer(const QString& user, const QString& password, - const QString& initialDeviceName, - const QString& deviceId = {}); + const QString& initialDeviceName, + const QString& deviceId = {}); void connectWithToken(const QString& userId, const QString& accessToken, - const QString& deviceId); + const QString& deviceId); /** @deprecated Use stopSync() instead */ void disconnectFromServer() { stopSync(); } @@ -307,6 +309,23 @@ namespace QMatrixClient class Private; Private* d; + /** + * A single entry for functions that need to check whether the + * homeserver is valid before running. May either execute connectFn + * synchronously or asynchronously (if tryResolve is true and + * a DNS lookup is initiated); in case of errors, emits resolveError + * if the homeserver URL is not valid and cannot be resolved from + * userId. + * + * @param userId - fully-qualified MXID to resolve HS from + * @param connectFn - a function to execute once the HS URL is good + */ + void checkAndConnect(const QString& userId, + std::function connectFn); + void doConnectToServer(const QString& user, const QString& password, + const QString& initialDeviceName, + const QString& deviceId = {}); + static room_factory_t createRoom; static user_factory_t createUser; }; -- cgit v1.2.3 From 8f762a2458db773f6db24b568b2e944427297c2b Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 28 Nov 2017 10:54:07 +0900 Subject: Bumped up the soname version NB: Dynamic library generation requires CMake; no libqmatrixclient.pro yet. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9804835c..29a5c811 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,7 +86,7 @@ aux_source_directory(jobs/generated libqmatrixclient_job_SRCS) set(example_SRCS examples/qmc-example.cpp) add_library(qmatrixclient ${libqmatrixclient_SRCS} ${libqmatrixclient_job_SRCS}) -set_property(TARGET qmatrixclient PROPERTY VERSION "0.1.0") +set_property(TARGET qmatrixclient PROPERTY VERSION "0.2.0") set_property(TARGET qmatrixclient PROPERTY SOVERSION 0 ) target_link_libraries(qmatrixclient Qt5::Core Qt5::Network Qt5::Gui) -- cgit v1.2.3