From 56d997607c21d52d0ce75067601814e5106a8ef0 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 30 Jan 2018 15:04:02 +0900 Subject: Connection::onSyncSuccess: call processEvents upon each room parsing When there are too many rooms to process, this gives at least some bandwidth for the GUI in the meantime. --- connection.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/connection.cpp b/connection.cpp index 4ed7384f..c515e5a4 100644 --- a/connection.cpp +++ b/connection.cpp @@ -39,6 +39,7 @@ #include #include #include +#include using namespace QMatrixClient; @@ -274,6 +275,7 @@ void Connection::onSyncSuccess(SyncData &&data) { } if ( auto* r = provideRoom(roomData.roomId, roomData.joinState) ) r->updateData(std::move(roomData)); + QCoreApplication::instance()->processEvents(); } } -- cgit v1.2.3 From 89a9656025e938a24a2ce8949e7aaf1aacaae37b Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 30 Jan 2018 15:44:48 +0900 Subject: RequestData: use auto Should improve compatibility with compilers that don't like conversions between different std::unique_ptr<> types. --- jobs/requestdata.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jobs/requestdata.cpp b/jobs/requestdata.cpp index f5516c5f..5cb62221 100644 --- a/jobs/requestdata.cpp +++ b/jobs/requestdata.cpp @@ -8,7 +8,7 @@ using namespace QMatrixClient; -std::unique_ptr fromData(const QByteArray& data) +auto fromData(const QByteArray& data) { auto source = std::make_unique(); source->open(QIODevice::WriteOnly); @@ -18,7 +18,7 @@ std::unique_ptr fromData(const QByteArray& data) } template -inline std::unique_ptr fromJson(const JsonDataT& jdata) +inline auto fromJson(const JsonDataT& jdata) { return fromData(QJsonDocument(jdata).toJson(QJsonDocument::Compact)); } -- cgit v1.2.3 From 20be17094ea2d371e98638f3988dff0d65fae917 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 30 Jan 2018 16:05:56 +0900 Subject: User: fullName, isGuest, better doc comments User::fullName() is used in Room::roomMembername now. That string construction may be further cached now if it ever becomes a bottleneck. --- room.cpp | 4 ++-- user.cpp | 25 +++++++++++++++++++++---- user.h | 35 +++++++++++++++++++++++++++++------ 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/room.cpp b/room.cpp index bc7c083e..d5079711 100644 --- a/room.cpp +++ b/room.cpp @@ -790,8 +790,8 @@ QString Room::roomMembername(const User* u) const // << "is not a member of the room" << id(); // } - // In case of more than one namesake, disambiguate with user id. - return username % " (" % u->id() % ")"; + // In case of more than one namesake, use the full name to disambiguate + return u->fullName(); } QString Room::roomMembername(const QString& userId) const diff --git a/user.cpp b/user.cpp index b0890b61..6cc2e0a5 100644 --- a/user.cpp +++ b/user.cpp @@ -28,6 +28,9 @@ #include #include #include +#include + +#include using namespace QMatrixClient; @@ -65,6 +68,15 @@ QString User::id() const return d->userId; } +bool User::isGuest() const +{ + Q_ASSERT(!d->userId.isEmpty() && d->userId.front() == '@'); + auto it = std::find_if_not(d->userId.begin() + 1, d->userId.end(), + std::mem_fn(&QChar::isDigit)); + Q_ASSERT(it == d->userId.end()); + return *it == ':'; +} + QString User::name() const { return d->name; @@ -121,12 +133,17 @@ void User::Private::setAvatar(UploadContentJob* job, User* q) QString User::displayname() const { - if( !d->name.isEmpty() ) - return d->name; - return d->userId; + return d->name.isEmpty() ? d->userId : d->name; } -QString User::bridged() const { +QString User::fullName() const +{ + return d->name.isEmpty() ? d->userId : + d->name % '(' % d->userId % ')'; +} + +QString User::bridged() const +{ return d->bridged; } diff --git a/user.h b/user.h index 8a2c53d9..37977e08 100644 --- a/user.h +++ b/user.h @@ -30,8 +30,10 @@ namespace QMatrixClient { Q_OBJECT Q_PROPERTY(QString id READ id CONSTANT) + Q_PROPERTY(bool isGuest READ isGuest CONSTANT) Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString displayName READ displayname NOTIFY nameChanged STORED false) + Q_PROPERTY(QString fullName READ fullName NOTIFY nameChanged STORED false) Q_PROPERTY(QString bridgeName READ bridged NOTIFY nameChanged STORED false) Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false) Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY avatarChanged) @@ -39,26 +41,47 @@ namespace QMatrixClient User(QString userId, Connection* connection); ~User() override; - /** - * Returns the id of the user + /** Get unique stable user id + * User id is generated by the server and is not changed ever. */ QString id() const; - /** - * Returns the name chosen by the user + /** Get the name chosen by the user + * This may be empty if the user didn't choose the name or cleared + * it. + * \sa displayName */ QString name() const; - /** - * Returns the name that should be used to display the user. + /** Get the displayed user name + * This method returns the result of name() if its non-empty; + * otherwise it returns user id. This is convenient to show a user + * name outside of a room context. In a room context, user names + * should be disambiguated. + * \sa name, id, fullName Room::roomMembername */ QString displayname() const; + /** Get user name and id in one string + * The constructed string follows the format 'name (id)' + * used for users disambiguation in a room context and in other + * places. + * \sa displayName, Room::roomMembername + */ + QString fullName() const; + /** * Returns the name of bridge the user is connected from or empty. */ QString bridged() const; + /** Whether the user is a guest + * As of now, the function relies on the convention used in Synapse + * that guests and only guests have all-numeric IDs. This may or + * may not work with non-Synapse servers. + */ + bool isGuest() const; + const Avatar& avatarObject() const; Q_INVOKABLE QImage avatar(int dimension); Q_INVOKABLE QImage avatar(int requestedWidth, int requestedHeight); -- cgit v1.2.3 From eca077cfdb9d92a3f2fa29d65e71ae6c213923f4 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 26 Jan 2018 10:37:44 +0900 Subject: CreateRoomJob: Update to the latest Spec version --- jobs/generated/create_room.cpp | 3 ++- jobs/generated/create_room.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/jobs/generated/create_room.cpp b/jobs/generated/create_room.cpp index be06873a..de7807b5 100644 --- a/jobs/generated/create_room.cpp +++ b/jobs/generated/create_room.cpp @@ -74,7 +74,7 @@ class CreateRoomJob::Private QString roomId; }; -CreateRoomJob::CreateRoomJob(const QString& visibility, const QString& roomAliasName, const QString& name, const QString& topic, const QVector& invite, const QVector& invite3pid, const QJsonObject& creationContent, const QVector& initialState, const QString& preset, bool isDirect) +CreateRoomJob::CreateRoomJob(const QString& visibility, const QString& roomAliasName, const QString& name, const QString& topic, const QVector& invite, const QVector& invite3pid, const QJsonObject& creationContent, const QVector& initialState, const QString& preset, bool isDirect, bool guestCanJoin) : BaseJob(HttpVerb::Post, "CreateRoomJob", basePath % "/createRoom") , d(new Private) @@ -95,6 +95,7 @@ CreateRoomJob::CreateRoomJob(const QString& visibility, const QString& roomAlias if (!preset.isEmpty()) _data.insert("preset", toJson(preset)); _data.insert("is_direct", toJson(isDirect)); + _data.insert("guest_can_join", toJson(guestCanJoin)); setRequestData(_data); } diff --git a/jobs/generated/create_room.h b/jobs/generated/create_room.h index 13c9d2c0..b479615a 100644 --- a/jobs/generated/create_room.h +++ b/jobs/generated/create_room.h @@ -40,7 +40,7 @@ namespace QMatrixClient // End of inner data structures - explicit CreateRoomJob(const QString& visibility = {}, const QString& roomAliasName = {}, const QString& name = {}, const QString& topic = {}, const QVector& invite = {}, const QVector& invite3pid = {}, const QJsonObject& creationContent = {}, const QVector& initialState = {}, const QString& preset = {}, bool isDirect = {}); + explicit CreateRoomJob(const QString& visibility = {}, const QString& roomAliasName = {}, const QString& name = {}, const QString& topic = {}, const QVector& invite = {}, const QVector& invite3pid = {}, const QJsonObject& creationContent = {}, const QVector& initialState = {}, const QString& preset = {}, bool isDirect = {}, bool guestCanJoin = {}); ~CreateRoomJob() override; const QString& roomId() const; -- cgit v1.2.3 From 2393ea6a1c43f2e7ac7bcb5d7fb4915cec09f2e3 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 26 Jan 2018 20:53:01 +0900 Subject: Connection class cleanup createRoom and createUser renamed to roomFactory and userFactory (because createRoom will mean a different thing); unneeded #include moved to the cpp file. --- connection.cpp | 9 +++++---- connection.h | 22 +++++++++++----------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/connection.cpp b/connection.cpp index c515e5a4..51a73e8d 100644 --- a/connection.cpp +++ b/connection.cpp @@ -24,6 +24,7 @@ #include "jobs/generated/login.h" #include "jobs/generated/logout.h" #include "jobs/generated/receipts.h" +#include "jobs/generated/leaving.h" #include "jobs/sendeventjob.h" #include "jobs/joinroomjob.h" #include "jobs/roommessagesjob.h" @@ -437,7 +438,7 @@ User* Connection::user(const QString& userId) { if( d->userMap.contains(userId) ) return d->userMap.value(userId); - auto* user = createUser(this, userId); + auto* user = userFactory(this, userId); d->userMap.insert(userId, user); return user; } @@ -515,7 +516,7 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState) } else { - room = createRoom(this, id, joinState); + room = roomFactory(this, id, joinState); if (!room) { qCCritical(MAIN) << "Failed to create a room" << id; @@ -550,11 +551,11 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState) return room; } -Connection::room_factory_t Connection::createRoom = +Connection::room_factory_t Connection::roomFactory = [](Connection* c, const QString& id, JoinState joinState) { return new Room(c, id, joinState); }; -Connection::user_factory_t Connection::createUser = +Connection::user_factory_t Connection::userFactory = [](Connection* c, const QString& id) { return new User(id, c); }; QByteArray Connection::generateTxnId() diff --git a/connection.h b/connection.h index 79d7d658..b9aa328b 100644 --- a/connection.h +++ b/connection.h @@ -18,7 +18,6 @@ #pragma once -#include "jobs/generated/leaving.h" #include "joinstate.h" #include @@ -39,6 +38,7 @@ namespace QMatrixClient class SyncData; class RoomMessagesJob; class PostReceiptJob; + class ForgetRoomJob; class MediaThumbnailJob; class JoinRoomJob; class UploadContentJob; @@ -145,7 +145,7 @@ namespace QMatrixClient template static void setRoomType() { - createRoom = + roomFactory = [](Connection* c, const QString& id, JoinState joinState) { return new T(c, id, joinState); }; } @@ -153,7 +153,7 @@ namespace QMatrixClient template static void setUserType() { - createUser = + userFactory = [](Connection* c, const QString& id) { return new T(id, c); }; } @@ -186,15 +186,15 @@ namespace QMatrixClient int requestedHeight) const; // QIODevice* should already be open - virtual UploadContentJob* uploadContent(QIODevice* contentSource, + UploadContentJob* uploadContent(QIODevice* contentSource, const QString& filename = {}, const QString& contentType = {}) const; - virtual UploadContentJob* uploadFile(const QString& fileName, - const QString& contentType = {}); - virtual GetContentJob* getContent(const QString& mediaId) const; + UploadContentJob* uploadFile(const QString& fileName, + const QString& contentType = {}); + GetContentJob* getContent(const QString& mediaId) const; GetContentJob* getContent(const QUrl& url) const; // If localFilename is empty, a temporary file will be created - virtual DownloadFileJob* downloadFile(const QUrl& url, + DownloadFileJob* downloadFile(const QUrl& url, const QString& localFilename = {}) const; virtual JoinRoomJob* joinRoom(const QString& roomAlias); @@ -310,7 +310,7 @@ namespace QMatrixClient * the server; in particular, does not automatically create rooms * on the server. * @return a pointer to a Room object with the specified id; nullptr - * if roomId is empty if createRoom() failed to create a Room object. + * if roomId is empty if roomFactory() failed to create a Room object. */ Room* provideRoom(const QString& roomId, JoinState joinState); @@ -340,7 +340,7 @@ namespace QMatrixClient const QString& initialDeviceName, const QString& deviceId = {}); - static room_factory_t createRoom; - static user_factory_t createUser; + static room_factory_t roomFactory; + static user_factory_t userFactory; }; } // namespace QMatrixClient -- cgit v1.2.3 From 096fbd4ed4033bbf770769b50ed709862c369281 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 26 Jan 2018 20:54:13 +0900 Subject: Connection::createRoom and Connection::createDirectChat --- connection.cpp | 21 +++++++++++++++++++++ connection.h | 20 ++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/connection.cpp b/connection.cpp index 51a73e8d..00116c5f 100644 --- a/connection.cpp +++ b/connection.cpp @@ -386,6 +386,27 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url, return job; } +CreateRoomJob* Connection::createRoom(RoomVisibility visibility, + const QString& alias, const QString& name, const QString& topic, + const QVector& invites, const QString& presetName, + bool isDirect, bool guestsCanJoin, + const QVector& initialState, + const QVector& invite3pids, + const QJsonObject creationContent) +{ + return callApi( + visibility == PublishRoom ? "public" : "private", alias, name, + topic, invites, invite3pids, creationContent, initialState, + presetName, isDirect, guestsCanJoin); +} + +CreateRoomJob* Connection::createDirectChat(const QString& userId, + const QString& topic, const QString& name) +{ + return createRoom(UnpublishRoom, "", name, topic, {userId}, + "trusted_private_chat", true); +} + ForgetRoomJob* Connection::forgetRoom(const QString& id) { // To forget is hard :) First we should ensure the local user is not diff --git a/connection.h b/connection.h index b9aa328b..47f8e4de 100644 --- a/connection.h +++ b/connection.h @@ -18,6 +18,7 @@ #pragma once +#include "jobs/generated/create_room.h" #include "joinstate.h" #include @@ -58,6 +59,8 @@ namespace QMatrixClient using user_factory_t = std::function; + enum RoomVisibility { PublishRoom, UnpublishRoom }; // FIXME: Should go inside CreateRoomJob + explicit Connection(QObject* parent = nullptr); explicit Connection(const QUrl& server, QObject* parent = nullptr); virtual ~Connection(); @@ -197,6 +200,23 @@ namespace QMatrixClient DownloadFileJob* downloadFile(const QUrl& url, const QString& localFilename = {}) const; + /** + * \brief Create a room (generic method) + * This method allows to customize room entirely to your liking, + * providing all the attributes the original CS API provides. + */ + CreateRoomJob* createRoom(RoomVisibility visibility, + const QString& alias, const QString& name, const QString& topic, + const QVector& invites, const QString& presetName = {}, bool isDirect = false, + bool guestsCanJoin = false, + const QVector& initialState = {}, + const QVector& invite3pids = {}, + const QJsonObject creationContent = {}); + + /** Create a direct chat with a single user, optional name and topic */ + CreateRoomJob* createDirectChat(const QString& userId, + const QString& topic = {}, const QString& name = {}); + virtual JoinRoomJob* joinRoom(const QString& roomAlias); // Old API that will be abolished any time soon. DO NOT USE. -- cgit v1.2.3 From fcb0342410beaacc84c59813a1a7b6195c6abf7f Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 30 Jan 2018 15:13:35 +0900 Subject: Connection: more Q_PROPERTYs, newUser signal --- connection.cpp | 1 + connection.h | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/connection.cpp b/connection.cpp index 00116c5f..01d7461d 100644 --- a/connection.cpp +++ b/connection.cpp @@ -461,6 +461,7 @@ User* Connection::user(const QString& userId) return d->userMap.value(userId); auto* user = userFactory(this, userId); d->userMap.insert(userId, user); + emit newUser(user); return user; } diff --git a/connection.h b/connection.h index 47f8e4de..792ed126 100644 --- a/connection.h +++ b/connection.h @@ -52,6 +52,11 @@ namespace QMatrixClient /** Whether or not the rooms state should be cached locally * \sa loadState(), saveState() */ + Q_PROPERTY(User* localUser READ user CONSTANT) + Q_PROPERTY(QString localUserId READ userId CONSTANT) + Q_PROPERTY(QString deviceId READ deviceId CONSTANT) + Q_PROPERTY(QByteArray accessToken READ accessToken CONSTANT) + Q_PROPERTY(QUrl homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged) Q_PROPERTY(bool cacheState READ cacheState WRITE setCacheState NOTIFY cacheStateChanged) public: using room_factory_t = @@ -83,14 +88,14 @@ namespace QMatrixClient // FIXME: Convert Q_INVOKABLEs to Q_PROPERTIES // (breaks back-compatibility) - Q_INVOKABLE QUrl homeserver() const; + QUrl homeserver() const; Q_INVOKABLE User* user(const QString& userId); - Q_INVOKABLE User* user(); - Q_INVOKABLE QString userId() const; - Q_INVOKABLE QString deviceId() const; + User* user(); + QString userId() const; + QString deviceId() const; /** @deprecated Use accessToken() instead. */ Q_INVOKABLE QString token() const; - Q_INVOKABLE QByteArray accessToken() const; + QByteArray accessToken() const; Q_INVOKABLE SyncJob* syncJob() const; Q_INVOKABLE int millisToReconnect() const; @@ -257,6 +262,8 @@ namespace QMatrixClient void syncDone(); void syncError(QString error); + void newUser(User* user); + /** * \group Signals emitted on room transitions * @@ -364,3 +371,4 @@ namespace QMatrixClient static user_factory_t userFactory; }; } // namespace QMatrixClient +Q_DECLARE_METATYPE(QMatrixClient::Connection*) -- cgit v1.2.3 From 038471e340c51f36f612fbb19d8a2b60a9923973 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 30 Jan 2018 15:35:06 +0900 Subject: Connection: expose the list of users; use an ordered map It's still an open question whether it's better to store a separate sorted index of users, next to an unsorted one; but a sorted list of users is of much more use in GUI than an unsorted one. --- connection.cpp | 7 ++++++- connection.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/connection.cpp b/connection.cpp index 01d7461d..e06baef7 100644 --- a/connection.cpp +++ b/connection.cpp @@ -62,7 +62,7 @@ class Connection::Private // Leave state of the same room. QHash, Room*> roomMap; QVector roomIdsToForget; - QHash userMap; + QMap userMap; QString userId; SyncJob* syncJob = nullptr; @@ -516,6 +516,11 @@ QHash< QPair, Room* > Connection::roomMap() const return roomMap; } +QMap Connection::users() const +{ + return d->userMap; +} + const ConnectionData* Connection::connectionData() const { return d->data.get(); diff --git a/connection.h b/connection.h index 792ed126..2f7c38b3 100644 --- a/connection.h +++ b/connection.h @@ -71,6 +71,7 @@ namespace QMatrixClient virtual ~Connection(); QHash, Room*> roomMap() const; + QMap users() const; /** Sends /forget to the server and also deletes room locally. * This method is in Connection, not in Room, since it's a -- cgit v1.2.3 From db5d99c8b3f68b116fc55a4563bb66db4bf82cf8 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 30 Jan 2018 16:17:03 +0900 Subject: Fix compatibility with Qt before 5.10 --- user.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user.cpp b/user.cpp index 6cc2e0a5..7217413b 100644 --- a/user.cpp +++ b/user.cpp @@ -70,7 +70,7 @@ QString User::id() const bool User::isGuest() const { - Q_ASSERT(!d->userId.isEmpty() && d->userId.front() == '@'); + Q_ASSERT(!d->userId.isEmpty() && d->userId.startsWith('@')); auto it = std::find_if_not(d->userId.begin() + 1, d->userId.end(), std::mem_fn(&QChar::isDigit)); Q_ASSERT(it == d->userId.end()); -- cgit v1.2.3 From eb88190b190e00f808903388052664f0c31d3a42 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 30 Jan 2018 16:17:03 +0900 Subject: Fix compatibility with Qt before 5.10 --- user.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user.cpp b/user.cpp index 6cc2e0a5..7217413b 100644 --- a/user.cpp +++ b/user.cpp @@ -70,7 +70,7 @@ QString User::id() const bool User::isGuest() const { - Q_ASSERT(!d->userId.isEmpty() && d->userId.front() == '@'); + Q_ASSERT(!d->userId.isEmpty() && d->userId.startsWith('@')); auto it = std::find_if_not(d->userId.begin() + 1, d->userId.end(), std::mem_fn(&QChar::isDigit)); Q_ASSERT(it == d->userId.end()); -- cgit v1.2.3 From db398e7134f3a7b6cd7f4281add1decfeb961a55 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 30 Jan 2018 16:47:14 +0900 Subject: Room: setName(), setCanonicalAlias() --- room.cpp | 11 +++++++++++ room.h | 2 ++ 2 files changed, 13 insertions(+) diff --git a/room.cpp b/room.cpp index d5079711..4270875c 100644 --- a/room.cpp +++ b/room.cpp @@ -862,6 +862,17 @@ void Room::postMessage(const RoomMessageEvent& event) connection()->callApi(id(), event); } +void Room::setName(const QString& newName) +{ + connection()->callApi(id(), RoomNameEvent(newName)); +} + +void Room::setCanonicalAlias(const QString& newAlias) +{ + connection()->callApi(id(), + RoomCanonicalAliasEvent(newAlias)); +} + void Room::setTopic(const QString& newTopic) { RoomTopicEvent evt(newTopic); diff --git a/room.h b/room.h index b908a763..0a9bc2b8 100644 --- a/room.h +++ b/room.h @@ -235,6 +235,8 @@ namespace QMatrixClient /** @deprecated If you have a custom event type, construct the event * and pass it as a whole to postMessage() */ void postMessage(const QString& type, const QString& plainText); + void setName(const QString& newName); + void setCanonicalAlias(const QString& newAlias); void setTopic(const QString& newTopic); void getPreviousContent(int limit = 10); -- cgit v1.2.3 From 13bc7b8b784c2095a0187a1787f56f207528867e Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 30 Jan 2018 16:49:05 +0900 Subject: Fix compilation with Clang Did two QChar::isDigit() overloads, one static and one member, confuse it? --- user.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user.cpp b/user.cpp index 7217413b..b07cf688 100644 --- a/user.cpp +++ b/user.cpp @@ -72,7 +72,7 @@ bool User::isGuest() const { Q_ASSERT(!d->userId.isEmpty() && d->userId.startsWith('@')); auto it = std::find_if_not(d->userId.begin() + 1, d->userId.end(), - std::mem_fn(&QChar::isDigit)); + [] (QChar c) { return c.isDigit(); }); Q_ASSERT(it == d->userId.end()); return *it == ':'; } -- cgit v1.2.3 From 0a80e38c1b425322d96dc662c6d47e3ca5fc2c86 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 30 Jan 2018 16:49:05 +0900 Subject: Fix compilation with Clang Did two QChar::isDigit() overloads, one static and one member, confuse it? --- user.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user.cpp b/user.cpp index 7217413b..b07cf688 100644 --- a/user.cpp +++ b/user.cpp @@ -72,7 +72,7 @@ bool User::isGuest() const { Q_ASSERT(!d->userId.isEmpty() && d->userId.startsWith('@')); auto it = std::find_if_not(d->userId.begin() + 1, d->userId.end(), - std::mem_fn(&QChar::isDigit)); + [] (QChar c) { return c.isDigit(); }); Q_ASSERT(it == d->userId.end()); return *it == ':'; } -- cgit v1.2.3 From 86c41b9d233918ebc3312d19fc8f702a0f5da789 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 30 Jan 2018 20:36:52 +0900 Subject: User: Fixes in newly introduced methods --- user.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/user.cpp b/user.cpp index b07cf688..c80ec883 100644 --- a/user.cpp +++ b/user.cpp @@ -73,7 +73,7 @@ bool User::isGuest() const Q_ASSERT(!d->userId.isEmpty() && d->userId.startsWith('@')); auto it = std::find_if_not(d->userId.begin() + 1, d->userId.end(), [] (QChar c) { return c.isDigit(); }); - Q_ASSERT(it == d->userId.end()); + Q_ASSERT(it != d->userId.end()); return *it == ':'; } @@ -139,7 +139,7 @@ QString User::displayname() const QString User::fullName() const { return d->name.isEmpty() ? d->userId : - d->name % '(' % d->userId % ')'; + d->name % " (" % d->userId % ')'; } QString User::bridged() const -- cgit v1.2.3 From 75542386eaf7a6e4ecd064401f2c3373c48f3cd3 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 30 Jan 2018 20:36:52 +0900 Subject: User: Fixes in newly introduced methods --- user.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/user.cpp b/user.cpp index b07cf688..c80ec883 100644 --- a/user.cpp +++ b/user.cpp @@ -73,7 +73,7 @@ bool User::isGuest() const Q_ASSERT(!d->userId.isEmpty() && d->userId.startsWith('@')); auto it = std::find_if_not(d->userId.begin() + 1, d->userId.end(), [] (QChar c) { return c.isDigit(); }); - Q_ASSERT(it == d->userId.end()); + Q_ASSERT(it != d->userId.end()); return *it == ':'; } @@ -139,7 +139,7 @@ QString User::displayname() const QString User::fullName() const { return d->name.isEmpty() ? d->userId : - d->name % '(' % d->userId % ')'; + d->name % " (" % d->userId % ')'; } QString User::bridged() const -- cgit v1.2.3 From 7f7ca55599887af0ad8d2eb9efbf432fc140ffca Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 31 Jan 2018 10:30:12 +0900 Subject: Connection::createdRoom() signal --- connection.cpp | 6 +++++- connection.h | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/connection.cpp b/connection.cpp index e06baef7..4b7d4abb 100644 --- a/connection.cpp +++ b/connection.cpp @@ -394,10 +394,14 @@ CreateRoomJob* Connection::createRoom(RoomVisibility visibility, const QVector& invite3pids, const QJsonObject creationContent) { - return callApi( + auto job = callApi( visibility == PublishRoom ? "public" : "private", alias, name, topic, invites, invite3pids, creationContent, initialState, presetName, isDirect, guestsCanJoin); + connect(job, &BaseJob::success, this, [this,job] { + emit createdRoom(provideRoom(job->roomId(), JoinState::Join)); + }); + return job; } CreateRoomJob* Connection::createDirectChat(const QString& userId, diff --git a/connection.h b/connection.h index 2f7c38b3..c0f013ad 100644 --- a/connection.h +++ b/connection.h @@ -323,6 +323,13 @@ namespace QMatrixClient /** The room object is about to be deleted */ void aboutToDeleteRoom(Room* room); + /** The room has just been created by createRoom or createDirectChat + * This signal is not emitted in usual room state transitions, + * only as an outcome of room creation operations invoked by + * the client. + */ + void createdRoom(Room* room); + void cacheStateChanged(); protected: -- cgit v1.2.3 From 1a58c8382575e39acd5ef438480aff2fe7cf79ad Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 1 Feb 2018 08:39:25 +0900 Subject: AppVeyor CI configuration Compared to the (very similar) Quaternion project configuration, this one adds qmake-based building with MSVC 2017 (MSVC 2015 can be added if anybody needs it). --- .appveyor.yml | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .appveyor.yml diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 00000000..410ad12e --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,46 @@ +image: Visual Studio 2015 + +environment: + #DEPLOY_DIR: libqmatrixclient-%APPVEYOR_BUILD_VERSION% + matrix: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + QTDIR: C:\Qt\5.9\msvc2017_64 + VCVARS: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat" + PLATFORM: + MAKETOOL: cmake + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + QTDIR: C:\Qt\5.9\msvc2017_64 + VCVARS: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat" + PLATFORM: + MAKETOOL: qmake + - QTDIR: C:\Qt\5.9\msvc2015 + VCVARS: "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat" + PLATFORM: x86 + MAKETOOL: cmake + +init: +- call "%QTDIR%\bin\qtenv2.bat" +- set PATH=C:\Qt\Tools\QtCreator\bin;%PATH% +- call "%VCVARS%" %platform% +- cd /D "%APPVEYOR_BUILD_FOLDER%" + +before_build: +- git submodule update --init --recursive +- if %MAKETOOL% == cmake cmake -G "NMake Makefiles JOM" -H. -Bbuild -DCMAKE_CXX_FLAGS="/EHsc /W3" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX="%DEPLOY_DIR%" + +build_script: +- if %MAKETOOL% == cmake cmake --build build +- if %MAKETOOL% == qmake qmake && jom + +#after_build: +#- cmake --build build --target install +#- 7z a libqmatrixclient.zip "%DEPLOY_DIR%\" + +# Uncomment this to connect to the AppVeyor build worker +#on_finish: +# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) + +test: off + +#artifacts: +#- path: libqmatrixclient.zip -- cgit v1.2.3 From f353c7841d9aec6ccc65ae2841f20b420872833f Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 1 Feb 2018 09:42:50 +0900 Subject: Add GCC 4.9.2 to the workaround path for FileTransferInfo code Closes #161. Doesn't make GCC 4.9.2 officially supported but libqmatrixclient can be compiled with it as of now. --- room.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/room.cpp b/room.cpp index 4270875c..79304494 100644 --- a/room.cpp +++ b/room.cpp @@ -99,7 +99,7 @@ class Room::Private struct FileTransferPrivateInfo { -#if defined(_MSC_VER) && _MSC_VER < 1910 +#if (defined(_MSC_VER) && _MSC_VER < 1910) || (defined(__GNUC__) && __GNUC__ <= 4) FileTransferPrivateInfo() = default; FileTransferPrivateInfo(BaseJob* j, QString fileName) : job(j), localFileInfo(fileName) @@ -561,7 +561,7 @@ FileTransferInfo Room::fileTransferInfo(const QString& id) const total = INT_MAX; } -#if defined(_MSC_VER) && _MSC_VER < 1910 +#if (defined(_MSC_VER) && _MSC_VER < 1910) || (defined(__GNUC__) && __GNUC__ <= 4) // A workaround for MSVC 2015 that fails with "error C2440: 'return': // cannot convert from 'initializer list' to 'QMatrixClient::FileTransferInfo'" FileTransferInfo fti; -- cgit v1.2.3 From ff1565e72627715c371aebb70d3d49aa2d153134 Mon Sep 17 00:00:00 2001 From: Roman Plášil Date: Wed, 31 Jan 2018 22:24:07 +0800 Subject: Make qmake warning spec compatible with msvc --- libqmatrixclient.pri | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index 9e4cb279..72637caf 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -1,6 +1,11 @@ QT += network CONFIG += c++14 warn_on rtti_off -QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-parameter + +win32-msvc* { + QMAKE_CXXFLAGS_WARN_ON += -wd4100 +} else { + QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-parameter +} INCLUDEPATH += $$PWD -- cgit v1.2.3 From aa3f747fc8480a8d58b41d447ecb7ca9b43c96e1 Mon Sep 17 00:00:00 2001 From: Roman Plášil Date: Thu, 1 Feb 2018 15:32:45 +0800 Subject: Move forgetRoom over to slots #166 --- connection.h | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/connection.h b/connection.h index c0f013ad..3ec4fd9d 100644 --- a/connection.h +++ b/connection.h @@ -73,20 +73,6 @@ namespace QMatrixClient QHash, Room*> roomMap() const; QMap users() 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. - * It ensures that the local user is not a member of a room (running /leave, - * if necessary) then issues a /forget request and if that one doesn't fail - * deletion of the local Room object is ensured. - * \param id - the room id to forget - * \return - the ongoing /forget request to the server; note that the - * success() signal of this request is connected to deleteLater() - * of a respective room so by the moment this finishes, there might be no - * Room object anymore. - */ - ForgetRoomJob* forgetRoom(const QString& id); - // FIXME: Convert Q_INVOKABLEs to Q_PROPERTIES // (breaks back-compatibility) QUrl homeserver() const; @@ -225,6 +211,20 @@ namespace QMatrixClient virtual JoinRoomJob* joinRoom(const QString& roomAlias); + /** 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. + * It ensures that the local user is not a member of a room (running /leave, + * if necessary) then issues a /forget request and if that one doesn't fail + * deletion of the local Room object is ensured. + * \param id - the room id to forget + * \return - the ongoing /forget request to the server; note that the + * success() signal of this request is connected to deleteLater() + * of a respective room so by the moment this finishes, there might be no + * Room object anymore. + */ + ForgetRoomJob* forgetRoom(const QString& id); + // Old API that will be abolished any time soon. DO NOT USE. /** @deprecated Use callApi() or Room::postMessage() instead */ -- cgit v1.2.3 From 2eb0cace848d7d0415a69be9159cfdf6b1bf1117 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 1 Feb 2018 17:03:14 +0900 Subject: Room::fileNameToDownload: get a sensible initial name for the file picker Works (at least, should work) with both Qt Widgets and QML. --- room.cpp | 23 +++++++++++++++++++++-- room.h | 1 + 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/room.cpp b/room.cpp index 79304494..519f08f5 100644 --- a/room.cpp +++ b/room.cpp @@ -543,6 +543,25 @@ void Room::resetHighlightCount() emit highlightCountChanged(this); } +QString Room::fileNameToDownload(const QString& eventId) +{ + auto evtIt = findInTimeline(eventId); + if (evtIt != timelineEdge() && + evtIt->event()->type() == EventType::RoomMessage) + { + auto* event = static_cast(evtIt->event()); + if (event->hasFileContent()) + { + auto* fileInfo = event->content()->fileInfo(); + return !fileInfo->originalName.isEmpty() ? fileInfo->originalName : + !event->plainBody().isEmpty() ? event->plainBody() : + QString(); + } + } + qWarning() << "No files to download in event" << eventId; + return {}; +} + FileTransferInfo Room::fileTransferInfo(const QString& id) const { auto infoIt = d->fileTransfers.find(id); @@ -981,8 +1000,8 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) auto fileName = !localFilename.isEmpty() ? localFilename.toLocalFile() : !fileInfo->originalName.isEmpty() ? (safeTempPrefix + fileInfo->originalName) : - !event->plainBody().isEmpty() ? - (safeTempPrefix + event->plainBody()) : QString(); + !event->plainBody().isEmpty() ? (safeTempPrefix + event->plainBody()) : + (safeTempPrefix + fileInfo->mimeType.preferredSuffix()); auto job = connection()->downloadFile(fileInfo->url, fileName); if (isJobRunning(job)) { diff --git a/room.h b/room.h index 0a9bc2b8..a8d58f83 100644 --- a/room.h +++ b/room.h @@ -215,6 +215,7 @@ namespace QMatrixClient Q_INVOKABLE int highlightCount() const; Q_INVOKABLE void resetHighlightCount(); + Q_INVOKABLE QString fileNameToDownload(const QString& eventId); Q_INVOKABLE FileTransferInfo fileTransferInfo(const QString& id) const; /** Pretty-prints plain text into HTML -- cgit v1.2.3 From b7fce41348f2c8449e84c3078b67dad7cf78ace5 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 1 Feb 2018 17:15:20 +0900 Subject: Log transferred bytes in PROFILER category --- room.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/room.cpp b/room.cpp index 519f08f5..0e4b1385 100644 --- a/room.cpp +++ b/room.cpp @@ -119,6 +119,9 @@ class Room::Private if (p == 0) p = -1; } + if (p != -1) + qCDebug(PROFILER) << "Transfer progress:" << p << "/" << t + << "=" << llround(double(p) / t * 100) << "%"; progress = p; total = t; } }; -- cgit v1.2.3 From ffb8233d1b6ac54e37391e1949697ba8f6828f47 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 1 Feb 2018 18:16:48 +0900 Subject: On Windows, make sure the downloaded file has a proper extension Closes #287. --- room.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/room.cpp b/room.cpp index 0e4b1385..51da747e 100644 --- a/room.cpp +++ b/room.cpp @@ -1005,6 +1005,14 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) (safeTempPrefix + fileInfo->originalName) : !event->plainBody().isEmpty() ? (safeTempPrefix + event->plainBody()) : (safeTempPrefix + fileInfo->mimeType.preferredSuffix()); + if (QSysInfo::productType() == "windows") + { + const auto& suffixes = fileInfo->mimeType.suffixes(); + if (!suffixes.isEmpty() && + std::none_of(suffixes.begin(), suffixes.end(), + [fileName] (const QString& s) { return fileName.endsWith(s); })) + fileName += '.' + fileInfo->mimeType.preferredSuffix(); + } auto job = connection()->downloadFile(fileInfo->url, fileName); if (isJobRunning(job)) { -- cgit v1.2.3