From d57485bb3a7d980642e7f68edd5785c144acc742 Mon Sep 17 00:00:00 2001 From: Kitsune Ral <Kitsune-Ral@users.sf.net> Date: Fri, 8 Sep 2017 18:36:44 +0900 Subject: converters.h: Facility methods for generated jobs A cherry-pick from the kitsune-apigen branch; a family of toJson() and fromJson<>() functions to unify conversion of data back and forth. --- jobs/converters.h | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 jobs/converters.h (limited to 'jobs') diff --git a/jobs/converters.h b/jobs/converters.h new file mode 100644 index 00000000..376dfeab --- /dev/null +++ b/jobs/converters.h @@ -0,0 +1,89 @@ +/****************************************************************************** +* Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net> +* +* 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 <QtCore/QJsonValue> +#include <QtCore/QJsonArray> +#include <QtCore/QDate> + +namespace QMatrixClient +{ + template <typename T> + inline QJsonValue toJson(T val) + { + return QJsonValue(val); + } + + template <typename T> + inline QJsonValue toJson(const QVector<T>& 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 <typename T> + inline T fromJson(const QJsonValue& jv) + { + return QVariant(jv).value<T>(); + } + + template <> + inline int fromJson<int>(const QJsonValue& jv) + { + return jv.toInt(); + } + + template <> + inline qint64 fromJson<qint64>(const QJsonValue& jv) + { + return static_cast<qint64>(jv.toDouble()); + } + + template <> + inline double fromJson<double>(const QJsonValue& jv) + { + return jv.toDouble(); + } + + template <> + inline QString fromJson<QString>(const QJsonValue& jv) + { + return jv.toString(); + } + + template <> + inline QDateTime fromJson<QDateTime>(const QJsonValue& jv) + { + return QDateTime::fromMSecsSinceEpoch(fromJson<qint64>(jv), Qt::UTC); + } + + template <> + inline QDate fromJson<QDate>(const QJsonValue& jv) + { + return QDateTime::fromMSecsSinceEpoch( + fromJson<qint64>(jv), Qt::UTC).date(); + } +} // namespace QMatrixClient \ No newline at end of file -- cgit v1.2.3 From 8ae1da50f8474a698ce83f61233b3a8975faeaee Mon Sep 17 00:00:00 2001 From: Kitsune Ral <Kitsune-Ral@users.sf.net> Date: Sat, 9 Sep 2017 20:14:08 +0900 Subject: First files made by api-generator Actual usage will come with the next commit. --- jobs/generated/inviting.cpp | 37 +++++++++++++++++++++++++++++++++++++ jobs/generated/inviting.h | 42 ++++++++++++++++++++++++++++++++++++++++++ jobs/generated/kicking.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ jobs/generated/kicking.h | 45 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 jobs/generated/inviting.cpp create mode 100644 jobs/generated/inviting.h create mode 100644 jobs/generated/kicking.cpp create mode 100644 jobs/generated/kicking.h (limited to 'jobs') diff --git a/jobs/generated/inviting.cpp b/jobs/generated/inviting.cpp new file mode 100644 index 00000000..e5e7f410 --- /dev/null +++ b/jobs/generated/inviting.cpp @@ -0,0 +1,37 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#include "inviting.h" + + +#include "../converters.h" + +#include <QtCore/QStringBuilder> + +using namespace QMatrixClient; + + + +static const auto basePath = QStringLiteral("/_matrix/client/r0"); + + +InviteUserJob::InviteUserJob(const ConnectionData* connection, + QString roomId + , + QString user_id + ) + : BaseJob(connection, HttpVerb::Post, "InviteUserJob" + , basePath % "/rooms/" % roomId % "/invite" + , Query { } + , Data { + { "user_id", toJson(user_id) } + } + + ) +{ } + + + + diff --git a/jobs/generated/inviting.h b/jobs/generated/inviting.h new file mode 100644 index 00000000..af5a426d --- /dev/null +++ b/jobs/generated/inviting.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#pragma once + + +#include "../basejob.h" + + + +#include <QtCore/QString> + + + + +namespace QMatrixClient +{ + + + // Operations + + /** + + */ + class InviteUserJob : public BaseJob + { + public: + InviteUserJob(const ConnectionData* connection + + , + QString roomId + + , + QString user_id + ); + + }; + + +} // namespace QMatrixClient diff --git a/jobs/generated/kicking.cpp b/jobs/generated/kicking.cpp new file mode 100644 index 00000000..726f6fb0 --- /dev/null +++ b/jobs/generated/kicking.cpp @@ -0,0 +1,41 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#include "kicking.h" + + +#include "../converters.h" + +#include <QtCore/QStringBuilder> + +using namespace QMatrixClient; + + + +static const auto basePath = QStringLiteral("/_matrix/client/r0"); + + +KickJob::KickJob(const ConnectionData* connection, + QString roomId + , + QString user_id + , + QString reason + ) + : BaseJob(connection, HttpVerb::Post, "KickJob" + , basePath % "/rooms/" % roomId % "/kick" + , Query { } + , Data { + { "user_id", toJson(user_id) }, + + { "reason", toJson(reason) } + } + + ) +{ } + + + + diff --git a/jobs/generated/kicking.h b/jobs/generated/kicking.h new file mode 100644 index 00000000..7b183b08 --- /dev/null +++ b/jobs/generated/kicking.h @@ -0,0 +1,45 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#pragma once + + +#include "../basejob.h" + + + +#include <QtCore/QString> + + + + +namespace QMatrixClient +{ + + + // Operations + + /** + + */ + class KickJob : public BaseJob + { + public: + KickJob(const ConnectionData* connection + + , + QString roomId + + , + QString user_id + + , + QString reason + ); + + }; + + +} // namespace QMatrixClient -- cgit v1.2.3 From 24e3d967d7c9029147202e10385b0b8e8881e4d9 Mon Sep 17 00:00:00 2001 From: Kitsune Ral <Kitsune-Ral@users.sf.net> Date: Sat, 9 Sep 2017 20:17:42 +0900 Subject: Kicking, inviting, exposing rooms in Invite state Kicking and inviting use generated job classes. Rooms in Invite state are stored separately in the hash from those in Join/Leave state because The Spec says so. For clients, this means that the same room may appear twice in the rooms map if it's been left and then the user was again invited to it. The code in Quaternion that properly processes this will arrive shortly. --- connection.cpp | 69 ++++++++++++++++++++++++++++++++++---------------------- connection.h | 18 +++++++++++---- jobs/syncjob.cpp | 5 ++-- jobs/syncjob.h | 1 - room.cpp | 23 +++++++++++++++---- room.h | 5 +++- 6 files changed, 80 insertions(+), 41 deletions(-) (limited to 'jobs') diff --git a/connection.cpp b/connection.cpp index 2c9ee88a..5d8a42e3 100644 --- a/connection.cpp +++ b/connection.cpp @@ -50,7 +50,11 @@ class Connection::Private Connection* q; ConnectionData* data; - QHash<QString, Room*> roomMap; + // 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 + // Leave state of the same room. + QHash<QPair<QString, bool>, Room*> roomMap; QHash<QString, User*> userMap; QString username; QString password; @@ -160,7 +164,7 @@ void Connection::sync(int timeout) d->data->setLastEvent(job->nextBatch()); for( auto&& roomData: job->takeRoomData() ) { - if ( auto* r = provideRoom(roomData.roomId) ) + if ( auto* r = provideRoom(roomData.roomId, roomData.joinState) ) r->updateData(std::move(roomData)); } d->syncJob = nullptr; @@ -197,20 +201,12 @@ PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) const JoinRoomJob* Connection::joinRoom(const QString& roomAlias) { - auto job = callApi<JoinRoomJob>(roomAlias); - connect( job, &BaseJob::success, [=] () { - if ( Room* r = provideRoom(job->roomId()) ) - emit joinedRoom(r); - }); - return job; + return callApi<JoinRoomJob>(roomAlias); } void Connection::leaveRoom(Room* room) { - auto job = callApi<LeaveRoomJob>(room->id()); - connect( job, &BaseJob::success, [=] () { - emit leftRoom(room); - }); + callApi<LeaveRoomJob>(room->id()); } RoomMessagesJob* Connection::getMessages(Room* room, const QString& from) const @@ -275,7 +271,7 @@ int Connection::millisToReconnect() const return d->syncJob ? d->syncJob->millisToRetry() : 0; } -QHash< QString, Room* > Connection::roomMap() const +const QHash< QPair<QString, bool>, Room* >& Connection::roomMap() const { return d->roomMap; } @@ -285,36 +281,55 @@ const ConnectionData* Connection::connectionData() const return d->data; } -Room* Connection::provideRoom(const QString& id) +Room* Connection::provideRoom(const QString& id, JoinState joinState) { + // TODO: This whole function is a strong case for a RoomManager class. if (id.isEmpty()) { qCDebug(MAIN) << "Connection::provideRoom() with empty id, doing nothing"; return nullptr; } - if (d->roomMap.contains(id)) - return d->roomMap.value(id); - - // Not yet in the map, create a new one. - auto* room = createRoom(this, id); - if (room) + const auto roomKey = qMakePair(id, joinState == JoinState::Invite); + auto* room = d->roomMap.value(roomKey, nullptr); + if (!room) { - d->roomMap.insert( id, room ); + room = createRoom(this, id, joinState); + if (!room) + { + qCritical() << "Failed to create a room!!!" << id; + return nullptr; + } + qCDebug(MAIN) << "Created Room" << id << ", invited:" << roomKey.second; + + d->roomMap.insert(roomKey, room); emit newRoom(room); - } else { - qCritical() << "Failed to create a room!!!" << id; + } + else if (room->joinState() != joinState) + { + room->setJoinState(joinState); + if (joinState == JoinState::Leave) + emit leftRoom(room); + else if (joinState == JoinState::Join) + emit joinedRoom(room); + } + + if (joinState != JoinState::Invite && d->roomMap.contains({id, true})) + { + // Preempt the Invite room after it's been acted upon (joined or left). + qCDebug(MAIN) << "Deleting invited state"; + delete d->roomMap.take({id, true}); } return room; } -std::function<Room*(Connection*, const QString&)> Connection::createRoom = - [](Connection* c, const QString& id) { return new Room(c, id); }; +Connection::room_factory_t Connection::createRoom = + [](Connection* c, const QString& id, JoinState joinState) + { return new Room(c, id, joinState); }; -std::function<User*(Connection*, const QString&)> Connection::createUser = +Connection::user_factory_t Connection::createUser = [](Connection* c, const QString& id) { return new User(id, c); }; - QByteArray Connection::generateTxnId() { return d->data->generateTxnId(); diff --git a/connection.h b/connection.h index 4b0413e3..b118ffb0 100644 --- a/connection.h +++ b/connection.h @@ -18,6 +18,8 @@ #pragma once +#include "joinstate.h" + #include <QtCore/QObject> #include <QtCore/QUrl> #include <QtCore/QSize> @@ -41,11 +43,16 @@ namespace QMatrixClient class Connection: public QObject { Q_OBJECT public: + using room_factory_t = + std::function<Room*(Connection*, const QString&, JoinState joinState)>; + using user_factory_t = + std::function<User*(Connection*, const QString&)>; + explicit Connection(const QUrl& server, QObject* parent = nullptr); Connection(); virtual ~Connection(); - QHash<QString, Room*> roomMap() const; + const QHash<QPair<QString, bool>, Room*>& roomMap() const; Q_INVOKABLE virtual void resolveServer(const QString& domain); Q_INVOKABLE virtual void connectToServer(const QString& user, @@ -102,7 +109,8 @@ namespace QMatrixClient static void setRoomType() { createRoom = - [](Connection* c, const QString& id) { return new T(c, id); }; + [](Connection* c, const QString& id, JoinState joinState) + { return new T(c, id, joinState); }; } template <typename T = User> @@ -144,13 +152,13 @@ namespace QMatrixClient * @return a pointer to a Room object with the specified id; nullptr * if roomId is empty if createRoom() failed to create a Room object. */ - Room* provideRoom(const QString& roomId); + Room* provideRoom(const QString& roomId, JoinState joinState); private: class Private; Private* d; - static std::function<Room*(Connection*, const QString&)> createRoom; - static std::function<User*(Connection*, const QString&)> createUser; + static room_factory_t createRoom; + static user_factory_t createUser; }; } // namespace QMatrixClient diff --git a/jobs/syncjob.cpp b/jobs/syncjob.cpp index 29ddc2e6..38cfcb2a 100644 --- a/jobs/syncjob.cpp +++ b/jobs/syncjob.cpp @@ -99,15 +99,14 @@ SyncRoomData::SyncRoomData(const QString& roomId_, JoinState joinState_, const QJsonObject& room_) : roomId(roomId_) , joinState(joinState_) - , state("state") + , state(joinState == JoinState::Invite ? "invite_state" : "state") , timeline("timeline") , ephemeral("ephemeral") , accountData("account_data") - , inviteState("invite_state") { switch (joinState) { case JoinState::Invite: - inviteState.fromJson(room_); + state.fromJson(room_); break; case JoinState::Join: state.fromJson(room_); diff --git a/jobs/syncjob.h b/jobs/syncjob.h index 07824e23..57a87c9f 100644 --- a/jobs/syncjob.h +++ b/jobs/syncjob.h @@ -51,7 +51,6 @@ namespace QMatrixClient Batch<RoomEvent> timeline; Batch<Event> ephemeral; Batch<Event> accountData; - Batch<Event> inviteState; bool timelineLimited; QString timelinePrevBatch; diff --git a/room.cpp b/room.cpp index 547b74c4..78e5b80d 100644 --- a/room.cpp +++ b/room.cpp @@ -18,6 +18,9 @@ #include "room.h" +#include "jobs/generated/kicking.h" +#include "jobs/generated/inviting.h" + #include <array> #include <QtCore/QHash> @@ -48,9 +51,9 @@ class Room::Private typedef QMultiHash<QString, User*> members_map_t; typedef std::pair<rev_iter_t, rev_iter_t> rev_iter_pair_t; - Private(Connection* c, QString id_) + Private(Connection* c, QString id_, JoinState initialJoinState) : q(nullptr), connection(c), id(std::move(id_)) - , joinState(JoinState::Join), unreadMessages(false) + , joinState(initialJoinState), unreadMessages(false) , highlightCount(0), notificationCount(0), roomMessagesJob(nullptr) { } @@ -134,8 +137,8 @@ class Room::Private } }; -Room::Room(Connection* connection, QString id) - : QObject(connection), d(new Private(connection, id)) +Room::Room(Connection* connection, QString id, JoinState initialJoinState) + : QObject(connection), d(new Private(connection, id, initialJoinState)) { // See "Accessing the Public Class" section in // https://marcmutz.wordpress.com/translated-articles/pimp-my-pimpl-%E2%80%94-reloaded/ @@ -194,6 +197,8 @@ void Room::setJoinState(JoinState state) if( state == oldState ) return; d->joinState = state; + qCDebug(MAIN) << "Room" << id() << "changed state: " + << int(oldState) << "->" << int(state); emit joinStateChanged(oldState, state); } @@ -601,11 +606,21 @@ void Room::Private::getPreviousContent(int limit) } } +void Room::inviteToRoom(const QString& memberId) const +{ + connection()->callApi<InviteUserJob>(id(), memberId); +} + void Room::leaveRoom() const { connection()->callApi<LeaveRoomJob>(id()); } +void Room::kickMember(const QString& memberId, const QString& reason) const +{ + connection()->callApi<KickJob>(id(), memberId, reason); +} + void Room::Private::dropDuplicateEvents(RoomEvents* events) const { // Collect all duplicate events at the end of the container diff --git a/room.h b/room.h index 23a1412d..9465a960 100644 --- a/room.h +++ b/room.h @@ -77,7 +77,7 @@ namespace QMatrixClient using Timeline = std::deque<TimelineItem>; using rev_iter_t = Timeline::const_reverse_iterator; - Room(Connection* connection, QString id); + Room(Connection* connection, QString id, JoinState initialJoinState); virtual ~Room(); Connection* connection() const; @@ -154,7 +154,10 @@ namespace QMatrixClient void getPreviousContent(int limit = 10); + void inviteToRoom(const QString& memberId) const; void leaveRoom() const; + void kickMember(const QString& memberId, const QString& reason) const; + void userRenamed(User* user, QString oldName); /** Mark all messages in the room as read */ -- cgit v1.2.3 From 5ddfbf25f2657f232649a9fdd2ad481127f6c349 Mon Sep 17 00:00:00 2001 From: Kitsune Ral <Kitsune-Ral@users.sf.net> Date: Wed, 13 Sep 2017 21:09:20 +0900 Subject: Add a missing #include --- jobs/converters.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'jobs') diff --git a/jobs/converters.h b/jobs/converters.h index 376dfeab..f9ab0269 100644 --- a/jobs/converters.h +++ b/jobs/converters.h @@ -21,6 +21,7 @@ #include <QtCore/QJsonValue> #include <QtCore/QJsonArray> #include <QtCore/QDate> +#include <QtCore/QVariant> namespace QMatrixClient { @@ -83,7 +84,6 @@ namespace QMatrixClient template <> inline QDate fromJson<QDate>(const QJsonValue& jv) { - return QDateTime::fromMSecsSinceEpoch( - fromJson<qint64>(jv), Qt::UTC).date(); + return fromJson<QDateTime>(jv).date(); } -} // namespace QMatrixClient \ No newline at end of file +} // namespace QMatrixClient -- cgit v1.2.3