aboutsummaryrefslogtreecommitdiff
path: root/lib/connection.h
diff options
context:
space:
mode:
Diffstat (limited to 'lib/connection.h')
-rw-r--r--lib/connection.h1565
1 files changed, 890 insertions, 675 deletions
diff --git a/lib/connection.h b/lib/connection.h
index b06fb143..75faf370 100644
--- a/lib/connection.h
+++ b/lib/connection.h
@@ -1,704 +1,919 @@
-/******************************************************************************
- * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de>
- *
- * 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
- */
+// SPDX-FileCopyrightText: 2016 Kitsune Ral <Kitsune-Ral@users.sf.net>
+// SPDX-FileCopyrightText: 2017 Roman Plášil <me@rplasil.name>
+// SPDX-FileCopyrightText: 2019 Alexey Andreyev <aa13q@ya.ru>
+// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
+#include "quotient_common.h"
+#include "ssosession.h"
+#include "util.h"
+
#include "csapi/create_room.h"
-#include "joinstate.h"
+#include "csapi/login.h"
+
#include "events/accountdataevents.h"
+#include <QtCore/QDir>
#include <QtCore/QObject>
-#include <QtCore/QUrl>
#include <QtCore/QSize>
+#include <QtCore/QUrl>
#include <functional>
-#include <memory>
-namespace QMatrixClient
+#ifdef Quotient_E2EE_ENABLED
+#include "e2ee/e2ee.h"
+#include "e2ee/qolmoutboundsession.h"
+#include "keyverificationsession.h"
+#include "events/keyverificationevent.h"
+#endif
+
+Q_DECLARE_METATYPE(Quotient::GetLoginFlowsJob::LoginFlow)
+
+namespace Quotient {
+
+class Room;
+class User;
+class ConnectionData;
+class RoomEvent;
+
+class SyncJob;
+class SyncData;
+class RoomMessagesJob;
+class PostReceiptJob;
+class ForgetRoomJob;
+class MediaThumbnailJob;
+class JoinRoomJob;
+class UploadContentJob;
+class GetContentJob;
+class DownloadFileJob;
+class SendToDeviceJob;
+class SendMessageJob;
+class LeaveRoomJob;
+class Database;
+struct EncryptedFileMetadata;
+
+class QOlmAccount;
+class QOlmInboundGroupSession;
+
+using LoginFlow = GetLoginFlowsJob::LoginFlow;
+
+/// Predefined login flows
+namespace LoginFlows {
+ inline const LoginFlow Password { "m.login.password" };
+ inline const LoginFlow SSO { "m.login.sso" };
+ inline const LoginFlow Token { "m.login.token" };
+}
+
+// To simplify comparisons of LoginFlows
+
+inline bool operator==(const LoginFlow& lhs, const LoginFlow& rhs)
{
- class Room;
- class User;
- class ConnectionData;
- class RoomEvent;
-
- class SyncJob;
- class SyncData;
- class RoomMessagesJob;
- class PostReceiptJob;
- class ForgetRoomJob;
- class MediaThumbnailJob;
- class JoinRoomJob;
- class UploadContentJob;
- class GetContentJob;
- class DownloadFileJob;
- class SendToDeviceJob;
- class SendMessageJob;
-
- /** Create a single-shot connection that triggers on the signal and
- * then self-disconnects
- *
- * Only supports DirectConnection type.
+ return lhs.type == rhs.type;
+}
+
+inline bool operator!=(const LoginFlow& lhs, const LoginFlow& rhs)
+{
+ return !(lhs == rhs);
+}
+
+class Connection;
+
+using room_factory_t =
+ std::function<Room*(Connection*, const QString&, JoinState)>;
+using user_factory_t = std::function<User*(Connection*, const QString&)>;
+
+/** The default factory to create room objects
+ *
+ * Just a wrapper around operator new.
+ * \sa Connection::setRoomFactory, Connection::setRoomType
+ */
+template <typename T = Room>
+auto defaultRoomFactory(Connection* c, const QString& id, JoinState js)
+{
+ return new T(c, id, js);
+}
+
+/** The default factory to create user objects
+ *
+ * Just a wrapper around operator new.
+ * \sa Connection::setUserFactory, Connection::setUserType
+ */
+template <typename T = User>
+auto defaultUserFactory(Connection* c, const QString& id)
+{
+ return new T(id, c);
+}
+
+// Room ids, rather than room pointers, are used in the direct chat
+// map types because the library keeps Invite rooms separate from
+// rooms in Join and Leave state; and direct chats in account data
+// are stored with no regard to their state.
+using DirectChatsMap = QMultiHash<const User*, QString>;
+using DirectChatUsersMap = QMultiHash<QString, User*>;
+using IgnoredUsersList = IgnoredUsersEvent::value_type;
+
+class QUOTIENT_API Connection : public QObject {
+ Q_OBJECT
+
+ Q_PROPERTY(User* localUser READ user NOTIFY stateChanged)
+ Q_PROPERTY(QString localUserId READ userId NOTIFY stateChanged)
+ Q_PROPERTY(QString domain READ domain NOTIFY stateChanged STORED false)
+ Q_PROPERTY(QString deviceId READ deviceId NOTIFY stateChanged)
+ Q_PROPERTY(QByteArray accessToken READ accessToken NOTIFY stateChanged)
+ Q_PROPERTY(bool isLoggedIn READ isLoggedIn NOTIFY stateChanged STORED false)
+ Q_PROPERTY(QString defaultRoomVersion READ defaultRoomVersion NOTIFY capabilitiesLoaded)
+ Q_PROPERTY(QUrl homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
+ Q_PROPERTY(QVector<GetLoginFlowsJob::LoginFlow> loginFlows READ loginFlows NOTIFY loginFlowsChanged)
+ Q_PROPERTY(bool isUsable READ isUsable NOTIFY loginFlowsChanged STORED false)
+ Q_PROPERTY(bool supportsSso READ supportsSso NOTIFY loginFlowsChanged STORED false)
+ Q_PROPERTY(bool supportsPasswordAuth READ supportsPasswordAuth NOTIFY loginFlowsChanged STORED false)
+ Q_PROPERTY(bool cacheState READ cacheState WRITE setCacheState NOTIFY cacheStateChanged)
+ Q_PROPERTY(bool lazyLoading READ lazyLoading WRITE setLazyLoading NOTIFY lazyLoadingChanged)
+ Q_PROPERTY(bool canChangePassword READ canChangePassword NOTIFY capabilitiesLoaded)
+
+public:
+ using UsersToDevicesToContent = QHash<QString, QHash<QString, QJsonObject>>;
+
+ enum RoomVisibility {
+ PublishRoom,
+ UnpublishRoom
+ }; // FIXME: Should go inside CreateRoomJob
+
+ explicit Connection(QObject* parent = nullptr);
+ explicit Connection(const QUrl& server, QObject* parent = nullptr);
+ ~Connection() override;
+
+ /// Get all rooms known within this Connection
+ /*!
+ * This includes Invite, Join and Leave rooms, in no particular order.
+ * \note Leave rooms will only show up in the list if they have been left
+ * in the same running session. The library doesn't cache left rooms
+ * between runs and it doesn't retrieve the full list of left rooms
+ * from the server.
+ * \sa rooms, room, roomsWithTag
+ */
+ Q_INVOKABLE QVector<Quotient::Room*> allRooms() const;
+
+ /// Get rooms that have either of the given join state(s)
+ /*!
+ * This method returns, in no particular order, rooms which join state
+ * matches the mask passed in \p joinStates.
+ * \note Similar to allRooms(), this won't retrieve the full list of
+ * Leave rooms from the server.
+ * \sa allRooms, room, roomsWithTag
+ */
+ Q_INVOKABLE QVector<Quotient::Room*>
+ rooms(Quotient::JoinStates joinStates) const;
+
+ /// Get the total number of rooms in the given join state(s)
+ Q_INVOKABLE int roomsCount(Quotient::JoinStates joinStates) const;
+
+ /** Check whether the account has data of the given type
+ * Direct chats map is not supported by this method _yet_.
*/
- template <typename SenderT1, typename SignalT,
- typename ReceiverT2, typename SlotT>
- inline auto connectSingleShot(SenderT1* sender, SignalT signal,
- ReceiverT2* receiver, SlotT slot)
+ bool hasAccountData(const QString& type) const;
+
+ //! \brief Get a generic account data event of the given type
+ //!
+ //! \return an account data event of the given type stored on the server,
+ //! or nullptr if there's none of that type.
+ //! \note Direct chats map cannot be retrieved using this method _yet_;
+ //! use directChats() instead.
+ const EventPtr& accountData(const QString& type) const;
+
+ //! \brief Get an account data event of the given type
+ //!
+ //! \return the account data content for the given event type stored
+ //! on the server, or a default-constructed object if there's none
+ //! of that type.
+ //! \note Direct chats map cannot be retrieved using this method _yet_;
+ //! use directChats() instead.
+ template <EventClass EventT>
+ const EventT* accountData() const
{
- QMetaObject::Connection connection;
- connection = QObject::connect(sender, signal, receiver, slot,
- Qt::DirectConnection);
- Q_ASSERT(connection);
- QObject::connect(sender, signal, receiver,
- [connection] { QObject::disconnect(connection); },
- Qt::DirectConnection);
- return connection;
+ return eventCast<EventT>(accountData(EventT::TypeId));
}
- class Connection;
+ /** Get account data as a JSON object
+ * This returns the content part of the account data event
+ * of the given type. Direct chats map cannot be retrieved using
+ * this method _yet_; use directChats() instead.
+ */
+ Q_INVOKABLE QJsonObject accountDataJson(const QString& type) const;
+
+ /** Set a generic account data event of the given type */
+ void setAccountData(EventPtr&& event);
+
+ Q_INVOKABLE void setAccountData(const QString& type,
+ const QJsonObject& content);
+
+ /** Get all Invited and Joined rooms grouped by tag
+ * \return a hashmap from tag name to a vector of room pointers,
+ * sorted by their order in the tag - details are at
+ * https://matrix.org/speculator/spec/drafts%2Fe2e/client_server/unstable.html#id95
+ */
+ QHash<QString, QVector<Room*>> tagsToRooms() const;
+
+ /** Get all room tags known on this connection */
+ QStringList tagNames() const;
+
+ /** Get the list of rooms with the specified tag */
+ QVector<Room*> roomsWithTag(const QString& tagName) const;
+
+ /*! \brief Mark the room as a direct chat with the user
+ *
+ * This function marks \p room as a direct chat with \p user.
+ * Emits the signal synchronously, without waiting to complete
+ * synchronisation with the server.
+ *
+ * \sa directChatsListChanged
+ */
+ void addToDirectChats(const Room* room, User* user);
+
+ /*! \brief Unmark the room from direct chats
+ *
+ * This function removes the room id from direct chats either for
+ * a specific \p user or for all users if \p user in nullptr.
+ * The room id is used to allow removal of, e.g., ids of forgotten
+ * rooms; a Room object need not exist. Emits the signal
+ * immediately, without waiting to complete synchronisation with
+ * the server.
+ *
+ * \sa directChatsListChanged
+ */
+ void removeFromDirectChats(const QString& roomId, User* user = nullptr);
+
+ /** Check whether the room id corresponds to a direct chat */
+ bool isDirectChat(const QString& roomId) const;
+
+ /** Get the whole map from users to direct chat rooms */
+ DirectChatsMap directChats() const;
+
+ /** Retrieve the list of users the room is a direct chat with
+ * @return The list of users for which this room is marked as
+ * a direct chat; an empty list if the room is not a direct chat
+ */
+ QList<User*> directChatUsers(const Room* room) const;
+
+ /** Check whether a particular user is in the ignore list */
+ Q_INVOKABLE bool isIgnored(const Quotient::User* user) const;
+
+ /** Get the whole list of ignored users */
+ Q_INVOKABLE Quotient::IgnoredUsersList ignoredUsers() const;
+
+ /** Add the user to the ignore list
+ * The change signal is emitted synchronously, without waiting
+ * to complete synchronisation with the server.
+ *
+ * \sa ignoredUsersListChanged
+ */
+ Q_INVOKABLE void addToIgnoredUsers(const Quotient::User* user);
+
+ /** Remove the user from the ignore list */
+ /** Similar to adding, the change signal is emitted synchronously.
+ *
+ * \sa ignoredUsersListChanged
+ */
+ Q_INVOKABLE void removeFromIgnoredUsers(const Quotient::User* user);
+
+ /** Get the full list of users known to this account */
+ QMap<QString, User*> users() const;
+
+ /** Get the base URL of the homeserver to connect to */
+ QUrl homeserver() const;
+ /** Get the domain name used for ids/aliases on the server */
+ QString domain() const;
+ /** Check if the homeserver is known to be reachable and working */
+ bool isUsable() const;
+ /** Get the list of supported login flows */
+ QVector<GetLoginFlowsJob::LoginFlow> loginFlows() const;
+ /** Check whether the current homeserver supports password auth */
+ bool supportsPasswordAuth() const;
+ /** Check whether the current homeserver supports SSO */
+ bool supportsSso() const;
+ /** Find a room by its id and a mask of applicable states */
+ Q_INVOKABLE Quotient::Room*
+ room(const QString& roomId,
+ Quotient::JoinStates states = JoinState::Invite | JoinState::Join) const;
+ /** Find a room by its alias and a mask of applicable states */
+ Q_INVOKABLE Quotient::Room*
+ roomByAlias(const QString& roomAlias,
+ Quotient::JoinStates states = JoinState::Invite
+ | JoinState::Join) const;
+ /** Update the internal map of room aliases to IDs */
+ /// This is used to maintain the internal index of room aliases.
+ /// It does NOT change aliases on the server,
+ /// \sa Room::setLocalAliases
+ void updateRoomAliases(const QString& roomId,
+ const QStringList& previousRoomAliases,
+ const QStringList& roomAliases);
+ Q_INVOKABLE Quotient::Room* invitation(const QString& roomId) const;
+ Q_INVOKABLE Quotient::User* user(const QString& uId);
+ const User* user() const;
+ User* user();
+ QString userId() const;
+ QString deviceId() const;
+ QByteArray accessToken() const;
+ bool isLoggedIn() const;
+#ifdef Quotient_E2EE_ENABLED
+ QOlmAccount* olmAccount() const;
+ Database* database() const;
+ PicklingMode picklingMode() const;
+
+ UnorderedMap<QString, QOlmInboundGroupSessionPtr> loadRoomMegolmSessions(
+ const Room* room) const;
+ void saveMegolmSession(const Room* room,
+ const QOlmInboundGroupSession& session) const;
+ QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession(
+ const QString& roomId) const;
+ void saveCurrentOutboundMegolmSession(
+ const QString& roomId, const QOlmOutboundGroupSession& session) const;
+
+ QString edKeyForUserDevice(const QString& userId,
+ const QString& deviceId) const;
+ bool hasOlmSession(const QString& user, const QString& deviceId) const;
+
+ // This assumes that an olm session already exists. If it doesn't, no message is sent.
+ void sendToDevice(const QString& targetUserId, const QString& targetDeviceId,
+ const Event& event, bool encrypted);
+
+ /// Returns true if this megolm session comes from a verified device
+ bool isVerifiedSession(const QString& megolmSessionId) const;
+
+ void sendSessionKeyToDevices(const QString& roomId,
+ const QByteArray& sessionId,
+ const QByteArray& sessionKey,
+ const QMultiHash<QString, QString>& devices,
+ int index);
+
+ QJsonObject decryptNotification(const QJsonObject &notification);
+ QStringList devicesForUser(const QString& userId) const;
+#endif // Quotient_E2EE_ENABLED
+ Q_INVOKABLE Quotient::SyncJob* syncJob() const;
+ Q_INVOKABLE int millisToReconnect() const;
+
+ Q_INVOKABLE void getTurnServers();
+
+ struct SupportedRoomVersion {
+ QString id;
+ QString status;
+
+ static const QString StableTag; // "stable", as of CS API 0.5
+ bool isStable() const { return status == StableTag; }
+
+ friend QDebug operator<<(QDebug dbg, const SupportedRoomVersion& v)
+ {
+ QDebugStateSaver _(dbg);
+ return dbg.nospace() << v.id << '/' << v.status;
+ }
+ };
+
+ /// Get the room version recommended by the server
+ /** Only works after server capabilities have been loaded.
+ * \sa loadingCapabilities */
+ QString defaultRoomVersion() const;
+ /// Get the room version considered stable by the server
+ /** Only works after server capabilities have been loaded.
+ * \sa loadingCapabilities */
+ QStringList stableRoomVersions() const;
+ /// Get all room versions supported by the server
+ /** Only works after server capabilities have been loaded.
+ * \sa loadingCapabilities */
+ QVector<SupportedRoomVersion> availableRoomVersions() const;
+
+ /// Indicate if the user can change its password from the client.
+ /// This is often not the case when SSO is enabled.
+ /// \sa loadingCapabilities
+ bool canChangePassword() const;
+
+ /**
+ * Call this before first sync to load from previously saved file.
+ */
+ Q_INVOKABLE void loadState();
+ /**
+ * This method saves the current state of rooms (but not messages
+ * in them) to a local cache file, so that it could be loaded by
+ * loadState() on a next run of the client.
+ */
+ Q_INVOKABLE void saveState() const;
+
+ /// This method saves the current state of a single room.
+ void saveRoomState(Room* r) const;
+
+ /// Get the default directory path to save the room state to
+ /** \sa stateCacheDir */
+ Q_INVOKABLE QString stateCachePath() const;
+
+ /// Get the default directory to save the room state to
+ /**
+ * This function returns the default directory to store the cached
+ * room state, defined as follows:
+ * \code
+ * QStandardPaths::writeableLocation(QStandardPaths::CacheLocation) +
+ * _safeUserId + "_state.json" \endcode where `_safeUserId` is userId() with
+ * `:` (colon) replaced by
+ * `_` (underscore), as colons are reserved characters on Windows.
+ * \sa loadState, saveState, stateCachePath
+ */
+ QDir stateCacheDir() const;
+
+ /** Whether or not the rooms state should be cached locally
+ * \sa loadState(), saveState()
+ */
+ bool cacheState() const;
+ void setCacheState(bool newValue);
+
+ bool lazyLoading() const;
+ void setLazyLoading(bool newValue);
- using room_factory_t = std::function<Room*(Connection*, const QString&,
- JoinState)>;
- using user_factory_t = std::function<User*(Connection*, const QString&)>;
+ /*! Start a pre-created job object on this connection */
+ Q_INVOKABLE BaseJob* run(BaseJob* job,
+ RunningPolicy runningPolicy = ForegroundRequest);
- /** The default factory to create room objects
+ /*! Start a job of a specified type with specified arguments and policy
+ *
+ * This is a universal method to create and start a job of a type passed
+ * as a template parameter. The policy allows to fine-tune the way
+ * the job is executed - as of this writing it means a choice
+ * between "foreground" and "background".
+ *
+ * \param runningPolicy controls how the job is executed
+ * \param jobArgs arguments to the job constructor
*
- * Just a wrapper around operator new.
- * \sa Connection::setRoomFactory, Connection::setRoomType
+ * \sa BaseJob::isBackground. QNetworkRequest::BackgroundRequestAttribute
*/
- template <typename T = Room>
- static inline room_factory_t defaultRoomFactory()
+ template <typename JobT, typename... JobArgTs>
+ JobT* callApi(RunningPolicy runningPolicy, JobArgTs&&... jobArgs)
{
- return [](Connection* c, const QString& id, JoinState js)
- {
- return new T(c, id, js);
- };
+ auto job = new JobT(std::forward<JobArgTs>(jobArgs)...);
+ run(job, runningPolicy);
+ return job;
}
- /** The default factory to create user objects
+ /*! Start a job of a specified type with specified arguments
*
- * Just a wrapper around operator new.
- * \sa Connection::setUserFactory, Connection::setUserType
+ * This is an overload that runs the job with "foreground" policy.
*/
- template <typename T = User>
- static inline user_factory_t defaultUserFactory()
+ template <typename JobT, typename... JobArgTs>
+ JobT* callApi(JobArgTs&&... jobArgs)
{
- return [](Connection* c, const QString& id)
- {
- return new T(id, c);
- };
+ return callApi<JobT>(ForegroundRequest,
+ std::forward<JobArgTs>(jobArgs)...);
}
- /** Enumeration with flags defining the network job running policy
- * So far only background/foreground flags are available.
+ /*! Get a request URL for a job with specified type and arguments
*
- * \sa Connection::callApi
- */
- enum RunningPolicy { ForegroundRequest = 0x0, BackgroundRequest = 0x1 };
-
- class Connection: public QObject {
- Q_OBJECT
-
- /** Whether or not the rooms state should be cached locally
- * \sa loadState(), saveState()
- */
- Q_PROPERTY(User* localUser READ user NOTIFY stateChanged)
- Q_PROPERTY(QString localUserId READ userId NOTIFY stateChanged)
- Q_PROPERTY(QString deviceId READ deviceId NOTIFY stateChanged)
- Q_PROPERTY(QByteArray accessToken READ accessToken NOTIFY stateChanged)
- Q_PROPERTY(QUrl homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
- Q_PROPERTY(bool cacheState READ cacheState WRITE setCacheState NOTIFY cacheStateChanged)
- public:
- // Room ids, rather than room pointers, are used in the direct chat
- // map types because the library keeps Invite rooms separate from
- // rooms in Join and Leave state; and direct chats in account data
- // are stored with no regard to their state.
- using DirectChatsMap = QMultiHash<const User*, QString>;
- using DirectChatUsersMap = QMultiHash<QString, User*>;
- using IgnoredUsersList = IgnoredUsersEvent::content_type;
-
- using UsersToDevicesToEvents =
- std::unordered_map<QString,
- std::unordered_map<QString, const Event&>>;
-
- enum RoomVisibility { PublishRoom, UnpublishRoom }; // FIXME: Should go inside CreateRoomJob
-
- explicit Connection(QObject* parent = nullptr);
- explicit Connection(const QUrl& server, QObject* parent = nullptr);
- virtual ~Connection();
-
- /** Get all Invited and Joined rooms
- * \return a hashmap from a composite key - room name and whether
- * it's an Invite rather than Join - to room pointers
- */
- QHash<QPair<QString, bool>, Room*> roomMap() const;
-
- /** Check whether the account has data of the given type
- * Direct chats map is not supported by this method _yet_.
- */
- bool hasAccountData(const QString& type) const;
-
- /** Get a generic account data event of the given type
- * This returns an account data event of the given type
- * stored on the server. Direct chats map cannot be retrieved
- * using this method _yet_; use directChats() instead.
- */
- const EventPtr& accountData(const QString& type) const;
-
- /** Get a generic account data event of the given type
- * This returns an account data event of the given type
- * stored on the server. Direct chats map cannot be retrieved
- * using this method _yet_; use directChats() instead.
- */
- template <typename EventT>
- const typename EventT::content_type accountData() const
- {
- if (const auto& eventPtr = accountData(EventT::matrixTypeId()))
- return eventPtr->content();
- return {};
- }
-
- /** Get account data as a JSON object
- * This returns the content part of the account data event
- * of the given type. Direct chats map cannot be retrieved using
- * this method _yet_; use directChats() instead.
- */
- Q_INVOKABLE QJsonObject accountDataJson(const QString& type) const;
-
- /** Set a generic account data event of the given type */
- void setAccountData(EventPtr&& event);
-
- Q_INVOKABLE void setAccountData(const QString& type,
- const QJsonObject& content);
-
- /** Get all Invited and Joined rooms grouped by tag
- * \return a hashmap from tag name to a vector of room pointers,
- * sorted by their order in the tag - details are at
- * https://matrix.org/speculator/spec/drafts%2Fe2e/client_server/unstable.html#id95
- */
- QHash<QString, QVector<Room*>> tagsToRooms() const;
-
- /** Get all room tags known on this connection */
- QStringList tagNames() const;
-
- /** Get the list of rooms with the specified tag */
- QVector<Room*> roomsWithTag(const QString& tagName) const;
-
- /** Mark the room as a direct chat with the user
- * This function marks \p room as a direct chat with \p user.
- * Emits the signal synchronously, without waiting to complete
- * synchronisation with the server.
- *
- * \sa directChatsListChanged
- */
- void addToDirectChats(const Room* room, User* user);
-
- /** Unmark the room from direct chats
- * This function removes the room id from direct chats either for
- * a specific \p user or for all users if \p user in nullptr.
- * The room id is used to allow removal of, e.g., ids of forgotten
- * rooms; a Room object need not exist. Emits the signal
- * immediately, without waiting to complete synchronisation with
- * the server.
- *
- * \sa directChatsListChanged
- */
- void removeFromDirectChats(const QString& roomId,
- User* user = nullptr);
-
- /** Check whether the room id corresponds to a direct chat */
- bool isDirectChat(const QString& roomId) const;
-
- /** Get the whole map from users to direct chat rooms */
- DirectChatsMap directChats() const;
-
- /** Retrieve the list of users the room is a direct chat with
- * @return The list of users for which this room is marked as
- * a direct chat; an empty list if the room is not a direct chat
- */
- QList<User*> directChatUsers(const Room* room) const;
-
- /** Check whether a particular user is in the ignore list */
- bool isIgnored(const User* user) const;
-
- /** Get the whole list of ignored users */
- IgnoredUsersList ignoredUsers() const;
-
- /** Add the user to the ignore list
- * The change signal is emitted synchronously, without waiting
- * to complete synchronisation with the server.
- *
- * \sa ignoredUsersListChanged
- */
- void addToIgnoredUsers(const User* user);
-
- /** Remove the user from the ignore list
- * Similar to adding, the change signal is emitted synchronously.
- *
- * \sa ignoredUsersListChanged
- */
- void removeFromIgnoredUsers(const User* user);
-
- /** Get the full list of users known to this account */
- QMap<QString, User*> users() const;
-
- QUrl homeserver() const;
- Q_INVOKABLE Room* room(const QString& roomId,
- JoinStates states = JoinState::Invite|JoinState::Join) const;
- Q_INVOKABLE Room* invitation(const QString& roomId) const;
- Q_INVOKABLE User* user(const QString& userId);
- const User* user() const;
- User* user();
- QString userId() const;
- QString deviceId() const;
- QByteArray accessToken() const;
- Q_INVOKABLE SyncJob* syncJob() const;
- Q_INVOKABLE int millisToReconnect() const;
-
- [[deprecated("Use accessToken() instead")]]
- Q_INVOKABLE QString token() const;
- Q_INVOKABLE void getTurnServers();
-
- /**
- * Call this before first sync to load from previously saved file.
- *
- * \param fromFile A local path to read the state from. Uses QUrl
- * to be QML-friendly. Empty parameter means using a path
- * defined by stateCachePath().
- */
- Q_INVOKABLE void loadState(const QUrl &fromFile = {});
- /**
- * This method saves the current state of rooms (but not messages
- * in them) to a local cache file, so that it could be loaded by
- * loadState() on a next run of the client.
- *
- * \param toFile A local path to save the state to. Uses QUrl to be
- * QML-friendly. Empty parameter means using a path defined by
- * stateCachePath().
- */
- Q_INVOKABLE void saveState(const QUrl &toFile = {}) const;
-
- /**
- * The default path to store the cached room state, defined as
- * follows:
- * QStandardPaths::writeableLocation(QStandardPaths::CacheLocation) + _safeUserId + "_state.json"
- * where `_safeUserId` is userId() with `:` (colon) replaced with
- * `_` (underscore)
- * /see loadState(), saveState()
- */
- Q_INVOKABLE QString stateCachePath() const;
-
- bool cacheState() const;
- void setCacheState(bool newValue);
-
- /** Start a job of a specified type with specified arguments and policy
- *
- * This is a universal method to start a job of a type passed
- * as a template parameter. The policy allows to fine-tune the way
- * the job is executed - as of this writing it means a choice
- * between "foreground" and "background".
- *
- * \param runningPolicy controls how the job is executed
- * \param jobArgs arguments to the job constructor
- *
- * \sa BaseJob::isBackground. QNetworkRequest::BackgroundRequestAttribute
- */
- template <typename JobT, typename... JobArgTs>
- JobT* callApi(RunningPolicy runningPolicy,
- JobArgTs&&... jobArgs) const
- {
- auto job = new JobT(std::forward<JobArgTs>(jobArgs)...);
- connect(job, &BaseJob::failure, this, &Connection::requestFailed);
- job->start(connectionData(), runningPolicy&BackgroundRequest);
- return job;
- }
-
- /** Start a job of a specified type with specified arguments
- *
- * This is an overload that calls the job with "foreground" policy.
- */
- template <typename JobT, typename... JobArgTs>
- JobT* callApi(JobArgTs&&... jobArgs) const
- {
- return callApi<JobT>(ForegroundRequest,
- std::forward<JobArgTs>(jobArgs)...);
- }
-
- /** Generate a new transaction id. Transaction id's are unique within
- * a single Connection object
- */
- Q_INVOKABLE QByteArray generateTxnId() const;
-
- /// Set a room factory function
- static void setRoomFactory(room_factory_t f);
-
- /// Set a user factory function
- static void setUserFactory(user_factory_t f);
-
- /// Get a room factory function
- static room_factory_t roomFactory();
-
- /// Get a user factory function
- static user_factory_t userFactory();
-
- /// Set the room factory to default with the overriden room type
- template <typename T>
- static void setRoomType() { setRoomFactory(defaultRoomFactory<T>()); }
-
- /// Set the user factory to default with the overriden user type
- template <typename T>
- static void setUserType() { setUserFactory(defaultUserFactory<T>()); }
-
- public slots:
- /** 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 = {});
- 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();
-
- virtual MediaThumbnailJob* getThumbnail(const QString& mediaId,
- QSize requestedSize, RunningPolicy policy = BackgroundRequest) const;
- MediaThumbnailJob* getThumbnail(const QUrl& url,
- QSize requestedSize, RunningPolicy policy = BackgroundRequest) const;
- MediaThumbnailJob* getThumbnail(const QUrl& url,
- int requestedWidth, int requestedHeight,
- RunningPolicy policy = BackgroundRequest) const;
-
- // QIODevice* should already be open
- UploadContentJob* uploadContent(QIODevice* contentSource,
- const QString& filename = {},
- const QString& contentType = {}) 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
- 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,
- QStringList invites, const QString& presetName = {},
- bool isDirect = false,
- const QVector<CreateRoomJob::StateEvent>& initialState = {},
- const QVector<CreateRoomJob::Invite3pid>& invite3pids = {},
- const QJsonObject& creationContent = {});
-
- /** Get a direct chat with a single user
- * This method may return synchronously or asynchoronously depending
- * on whether a direct chat room with the respective person exists
- * already.
- *
- * \sa directChatAvailable
- */
- void requestDirectChat(const QString& userId);
-
- /** Get a direct chat with a single user
- * This method may return synchronously or asynchoronously depending
- * on whether a direct chat room with the respective person exists
- * already.
- *
- * \sa directChatAvailable
- */
- void requestDirectChat(User* u);
-
- /** Run an operation in a direct chat with the user
- * This method may return synchronously or asynchoronously depending
- * on whether a direct chat room with the respective person exists
- * already. Instead of emitting a signal it executes the passed
- * function object with the direct chat room as its parameter.
- */
- void doInDirectChat(const QString& userId,
- const std::function<void(Room*)>& operation);
-
- /** Run an operation in a direct chat with the user
- * This method may return synchronously or asynchoronously depending
- * on whether a direct chat room with the respective person exists
- * already. Instead of emitting a signal it executes the passed
- * function object with the direct chat room as its parameter.
- */
- void doInDirectChat(User* u,
- const std::function<void(Room*)>& operation);
-
- /** Create a direct chat with a single user, optional name and topic
- * A room will always be created, unlike in requestDirectChat.
- * It is advised to use requestDirectChat as a default way of getting
- * one-on-one with a person, and only use createDirectChat when
- * a new creation is explicitly desired.
- */
- CreateRoomJob* createDirectChat(const QString& userId,
- const QString& topic = {}, const QString& name = {});
-
- virtual JoinRoomJob* joinRoom(const QString& roomAlias,
- const QStringList& serverNames = {});
-
- /** 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);
-
- SendToDeviceJob* sendToDevices(const QString& eventType,
- const UsersToDevicesToEvents& eventsMap) const;
-
- /** \deprecated This method is experimental and may be removed any time */
- SendMessageJob* sendMessage(const QString& roomId,
- const RoomEvent& event) const;
-
- // Old API that will be abolished any time soon. DO NOT USE.
-
- /** @deprecated Use callApi<PostReceiptJob>() or Room::postReceipt() instead */
- virtual PostReceiptJob* postReceipt(Room* room,
- RoomEvent* event) const;
- /** @deprecated Use callApi<LeaveRoomJob>() or Room::leaveRoom() instead */
- virtual void leaveRoom( Room* room );
-
- 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(); //< \deprecated Use connected() instead
- void loggedOut();
- /** Login data or state have changed
- *
- * This is a common change signal for userId, deviceId and
- * accessToken - these properties normally only change at
- * a successful login and logout and are constant at other times.
- */
- void stateChanged();
- void loginError(QString message, QByteArray details);
-
- /** A network request (job) failed
- *
- * @param request - the pointer to the failed job
- */
- void requestFailed(BaseJob* request);
-
- /** A network request (job) failed due to network problems
- *
- * This is _only_ emitted when the job will retry on its own;
- * once it gives up, requestFailed() will be emitted.
- *
- * @param message - message about the network problem
- * @param details - raw error details, if any available
- * @param retriesTaken - how many retries have already been taken
- * @param nextRetryInMilliseconds - when the job will retry again
- */
- void networkError(QString message, QByteArray details,
- int retriesTaken, int nextRetryInMilliseconds);
-
- void syncDone();
- void syncError(QString message, QByteArray details);
-
- void newUser(User* user);
-
- /**
- * \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);
-
- /** A room invitation is seen for the first time
- *
- * If the same room is in Left state, it's passed in prev. Beware
- * that initial sync will trigger this signal for all rooms in
- * Invite state.
- */
- void invitedRoom(Room* room, Room* prev);
-
- /** A joined room is seen for the first time
- *
- * It's not the same as receiving a room in "join" section of sync
- * response (rooms will be there even after joining); it's also
- * not (exactly) the same as actual joining action of a user (all
- * rooms coming in initial sync will trigger this signal too). 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). Note that, similar
- * to invitedRoom and joinedRoom, this signal is triggered for all
- * Left rooms upon initial sync (not only those that were left
- * right before the sync).
- */
- void leftRoom(Room* room, Room* prev);
-
- /** The room object is about to be deleted */
- void aboutToDeleteRoom(Room* room);
-
- /** The room has just been created by createRoom or requestDirectChat
- *
- * This signal is not emitted in usual room state transitions,
- * only as an outcome of room creation operations invoked by
- * the client.
- * \note requestDirectChat doesn't necessarily create a new chat;
- * use directChatAvailable signal if you just need to obtain
- * a direct chat room.
- */
- void createdRoom(Room* room);
-
- /** The first sync for the room has been completed
- *
- * This signal is emitted after the room has been synced the first
- * time. This is the right signal to connect to if you need to
- * access the room state (name, aliases, members); state transition
- * signals (newRoom, joinedRoom etc.) come earlier, when the room
- * has just been created.
- */
- void loadedRoomState(Room* room);
-
- /** Account data (except direct chats) have changed */
- void accountDataChanged(QString type);
-
- /** The direct chat room is ready for using
- * This signal is emitted upon any successful outcome from
- * requestDirectChat.
- */
- void directChatAvailable(Room* directChat);
-
- /** The list of direct chats has changed
- * This signal is emitted every time when the mapping of users
- * to direct chat rooms is changed (because of either local updates
- * or a different list arrived from the server).
- */
- void directChatsListChanged(DirectChatsMap additions,
- DirectChatsMap removals);
-
- void ignoredUsersListChanged(IgnoredUsersList additions,
- IgnoredUsersList removals);
-
- void cacheStateChanged();
- void turnServersChanged(const QJsonObject& servers);
-
- protected:
- /**
- * @brief Access the underlying ConnectionData class
- */
- const ConnectionData* connectionData() const;
-
- /**
- * @brief Find a (possibly new) Room object for the specified id
- * Use this method whenever you need to find a Room object in
- * the local list of rooms. Note that this does not interact with
- * 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 or roomFactory() failed to create a Room object.
- */
- Room* provideRoom(const QString& roomId, JoinState joinState);
-
- /**
- * Completes loading sync data.
- */
- void onSyncSuccess(SyncData &&data);
-
- private:
- class Private;
- std::unique_ptr<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<void()> connectFn);
- void doConnectToServer(const QString& user, const QString& password,
- const QString& initialDeviceName,
- const QString& deviceId = {});
-
- static room_factory_t _roomFactory;
- static user_factory_t _userFactory;
- };
-} // namespace QMatrixClient
-Q_DECLARE_METATYPE(QMatrixClient::Connection*)
+ * This calls JobT::makeRequestUrl() prepending the connection's homeserver
+ * to the list of arguments.
+ */
+ template <typename JobT, typename... JobArgTs>
+ QUrl getUrlForApi(JobArgTs&&... jobArgs) const
+ {
+ return JobT::makeRequestUrl(homeserver(),
+ std::forward<JobArgTs>(jobArgs)...);
+ }
+
+ //! \brief Start a local HTTP server and generate a single sign-on URL
+ //!
+ //! This call does the preparatory steps to carry out single sign-on
+ //! sequence
+ //! \sa https://matrix.org/docs/guides/sso-for-client-developers
+ //! \return A proxy object holding two URLs: one for SSO on the chosen
+ //! homeserver and another for the local callback address. Normally
+ //! you won't need the callback URL unless you proxy the response
+ //! with a custom UI. You do not need to delete the SsoSession
+ //! object; the Connection that issued it will dispose of it once
+ //! the login sequence completes (with any outcome).
+ Q_INVOKABLE SsoSession* prepareForSso(const QString& initialDeviceName,
+ const QString& deviceId = {});
+
+ /** Generate a new transaction id. Transaction id's are unique within
+ * a single Connection object
+ */
+ Q_INVOKABLE QByteArray generateTxnId() const;
+
+ /// Set a room factory function
+ static void setRoomFactory(room_factory_t f);
+
+ /// Set a user factory function
+ static void setUserFactory(user_factory_t f);
+
+ /// Get a room factory function
+ static room_factory_t roomFactory();
+
+ /// Get a user factory function
+ static user_factory_t userFactory();
+
+ /// Set the room factory to default with the overriden room type
+ template <typename T>
+ static void setRoomType()
+ {
+ setRoomFactory(defaultRoomFactory<T>);
+ }
+
+ /// Set the user factory to default with the overriden user type
+ template <typename T>
+ static void setUserType()
+ {
+ setUserFactory(defaultUserFactory<T>);
+ }
+
+ /// Saves the olm account data to disk. Usually doesn't need to be called manually.
+ void saveOlmAccount();
+
+public Q_SLOTS:
+ /// \brief Set the homeserver base URL and retrieve its login flows
+ ///
+ /// \sa LoginFlowsJob, loginFlows, loginFlowsChanged, homeserverChanged
+ void setHomeserver(const QUrl& baseUrl);
+
+ /// \brief Determine and set the homeserver from MXID
+ ///
+ /// This attempts to resolve the homeserver by requesting
+ /// .well-known/matrix/client record from the server taken from the MXID
+ /// serverpart. If there is no record found, the serverpart itself is
+ /// attempted as the homeserver base URL; if the record is there but
+ /// is malformed (e.g., the homeserver base URL cannot be found in it)
+ /// resolveError() is emitted and further processing stops. Otherwise,
+ /// setHomeserver is called, preparing the Connection object for the login
+ /// attempt.
+ /// \param mxid user Matrix ID, such as @someone:example.org
+ /// \sa setHomeserver, homeserverChanged, loginFlowsChanged, resolveError
+ void resolveServer(const QString& mxid);
+
+ /** \brief Log in using a username and password pair
+ *
+ * Before logging in, this method checks if the homeserver is valid and
+ * supports the password login flow. If the homeserver is invalid but
+ * a full user MXID is provided, this method calls resolveServer() using
+ * this MXID.
+ *
+ * \sa resolveServer, resolveError, loginError
+ */
+ void loginWithPassword(const QString& userId, const QString& password,
+ const QString& initialDeviceName,
+ const QString& deviceId = {});
+ /** \brief Log in using a login token
+ *
+ * One usual case for this method is the final stage of logging in via SSO.
+ * Unlike loginWithPassword() and assumeIdentity(), this method cannot
+ * resolve the server from the user name because the full user MXID is
+ * encoded in the login token. Callers should ensure the homeserver
+ * sanity in advance.
+ */
+ void loginWithToken(const QByteArray& loginToken,
+ const QString& initialDeviceName,
+ const QString& deviceId = {});
+ /** \brief Use an existing access token to connect to the homeserver
+ *
+ * Similar to loginWithPassword(), this method checks that the homeserver
+ * URL is valid and tries to resolve it from the MXID in case it is not.
+ */
+ void assumeIdentity(const QString& mxId, const QString& accessToken,
+ const QString& deviceId);
+ /// Explicitly request capabilities from the server
+ void reloadCapabilities();
+
+ /// Find out if capabilites are still loading from the server
+ bool loadingCapabilities() const;
+
+ void logout();
+
+ void sync(int timeout = -1);
+ void syncLoop(int timeout = 30000);
+
+ void stopSync();
+ QString nextBatchToken() const;
+
+ Q_INVOKABLE QUrl makeMediaUrl(QUrl mxcUrl) const;
+
+ virtual MediaThumbnailJob*
+ getThumbnail(const QString& mediaId, QSize requestedSize,
+ RunningPolicy policy = BackgroundRequest);
+ MediaThumbnailJob* getThumbnail(const QUrl& url, QSize requestedSize,
+ RunningPolicy policy = BackgroundRequest);
+ MediaThumbnailJob* getThumbnail(const QUrl& url, int requestedWidth,
+ int requestedHeight,
+ RunningPolicy policy = BackgroundRequest);
+
+ // QIODevice* should already be open
+ UploadContentJob* uploadContent(QIODevice* contentSource,
+ const QString& filename = {},
+ const QString& overrideContentType = {});
+ UploadContentJob* uploadFile(const QString& fileName,
+ const QString& overrideContentType = {});
+ GetContentJob* getContent(const QString& mediaId);
+ GetContentJob* getContent(const QUrl& url);
+ // If localFilename is empty, a temporary file will be created
+ DownloadFileJob* downloadFile(const QUrl& url,
+ const QString& localFilename = {});
+
+#ifdef Quotient_E2EE_ENABLED
+ DownloadFileJob* downloadFile(const QUrl& url,
+ const EncryptedFileMetadata& fileMetadata,
+ const QString& localFilename = {});
+#endif
+ /**
+ * \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, QStringList invites,
+ const QString& presetName = {}, const QString& roomVersion = {},
+ bool isDirect = false,
+ const QVector<CreateRoomJob::StateEvent>& initialState = {},
+ const QVector<CreateRoomJob::Invite3pid>& invite3pids = {},
+ const QJsonObject& creationContent = {});
+
+ /** Get a direct chat with a single user
+ * This method may return synchronously or asynchoronously depending
+ * on whether a direct chat room with the respective person exists
+ * already.
+ *
+ * \sa directChatAvailable
+ */
+ void requestDirectChat(const QString& userId);
+
+ /** Get a direct chat with a single user
+ * This method may return synchronously or asynchoronously depending
+ * on whether a direct chat room with the respective person exists
+ * already.
+ *
+ * \sa directChatAvailable
+ */
+ void requestDirectChat(User* u);
+
+ /** Run an operation in a direct chat with the user
+ * This method may return synchronously or asynchoronously depending
+ * on whether a direct chat room with the respective person exists
+ * already. Instead of emitting a signal it executes the passed
+ * function object with the direct chat room as its parameter.
+ */
+ void doInDirectChat(const QString& userId,
+ const std::function<void(Room*)>& operation);
+
+ /** Run an operation in a direct chat with the user
+ * This method may return synchronously or asynchoronously depending
+ * on whether a direct chat room with the respective person exists
+ * already. Instead of emitting a signal it executes the passed
+ * function object with the direct chat room as its parameter.
+ */
+ void doInDirectChat(User* u, const std::function<void(Room*)>& operation);
+
+ /** Create a direct chat with a single user, optional name and topic
+ * A room will always be created, unlike in requestDirectChat.
+ * It is advised to use requestDirectChat as a default way of getting
+ * one-on-one with a person, and only use createDirectChat when
+ * a new creation is explicitly desired.
+ */
+ CreateRoomJob* createDirectChat(const QString& userId,
+ const QString& topic = {},
+ const QString& name = {});
+
+ virtual JoinRoomJob* joinRoom(const QString& roomAlias,
+ const QStringList& serverNames = {});
+
+ /** 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);
+
+ SendToDeviceJob* sendToDevices(const QString& eventType,
+ const UsersToDevicesToContent& contents);
+
+ /** \deprecated This method is experimental and may be removed any time */
+ SendMessageJob* sendMessage(const QString& roomId, const RoomEvent& event);
+
+ /** \deprecated Do not use this directly, use Room::leaveRoom() instead */
+ virtual LeaveRoomJob* leaveRoom(Room* room);
+
+#ifdef Quotient_E2EE_ENABLED
+ void startKeyVerificationSession(const QString& deviceId);
+
+ void encryptionUpdate(Room *room);
+#endif
+
+Q_SIGNALS:
+ /// \brief Initial server resolution has failed
+ ///
+ /// This signal is emitted when resolveServer() did not manage to resolve
+ /// the homeserver using its .well-known/client record or otherwise.
+ /// \sa resolveServer
+ void resolveError(QString error);
+
+ void homeserverChanged(QUrl baseUrl);
+ void loginFlowsChanged();
+ void capabilitiesLoaded();
+
+ void connected();
+ void loggedOut();
+ /** Login data or state have changed
+ *
+ * This is a common change signal for userId, deviceId and
+ * accessToken - these properties normally only change at
+ * a successful login and logout and are constant at other times.
+ */
+ void stateChanged();
+ void loginError(QString message, QString details);
+
+ /** A network request (job) failed
+ *
+ * @param request - the pointer to the failed job
+ */
+ void requestFailed(Quotient::BaseJob* request);
+
+ /** A network request (job) failed due to network problems
+ *
+ * This is _only_ emitted when the job will retry on its own;
+ * once it gives up, requestFailed() will be emitted.
+ *
+ * @param message - message about the network problem
+ * @param details - raw error details, if any available
+ * @param retriesTaken - how many retries have already been taken
+ * @param nextRetryInMilliseconds - when the job will retry again
+ */
+ void networkError(QString message, QString details, int retriesTaken,
+ int nextRetryInMilliseconds);
+
+ void syncDone();
+ void syncError(QString message, QString details);
+
+ void newUser(Quotient::User* user);
+
+ /**
+ * \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(Quotient::Room* room);
+
+ /** A room invitation is seen for the first time
+ *
+ * If the same room is in Left state, it's passed in prev. Beware
+ * that initial sync will trigger this signal for all rooms in
+ * Invite state.
+ */
+ void invitedRoom(Quotient::Room* room, Quotient::Room* prev);
+
+ /** A joined room is seen for the first time
+ *
+ * It's not the same as receiving a room in "join" section of sync
+ * response (rooms will be there even after joining); it's also
+ * not (exactly) the same as actual joining action of a user (all
+ * rooms coming in initial sync will trigger this signal too). If
+ * this room was in Invite state before, the respective object is
+ * passed in prev (and it will be deleted shortly afterwards).
+ */
+ void joinedRoom(Quotient::Room* room, Quotient::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). Note that, similar
+ * to invitedRoom and joinedRoom, this signal is triggered for all
+ * Left rooms upon initial sync (not only those that were left
+ * right before the sync).
+ */
+ void leftRoom(Quotient::Room* room, Quotient::Room* prev);
+
+ /** The room object is about to be deleted */
+ void aboutToDeleteRoom(Quotient::Room* room);
+
+ /** The room has just been created by createRoom or requestDirectChat
+ *
+ * This signal is not emitted in usual room state transitions,
+ * only as an outcome of room creation operations invoked by
+ * the client.
+ * \note requestDirectChat doesn't necessarily create a new chat;
+ * use directChatAvailable signal if you just need to obtain
+ * a direct chat room.
+ */
+ void createdRoom(Quotient::Room* room);
+
+ /** The first sync for the room has been completed
+ *
+ * This signal is emitted after the room has been synced the first
+ * time. This is the right signal to connect to if you need to
+ * access the room state (name, aliases, members); state transition
+ * signals (newRoom, joinedRoom etc.) come earlier, when the room
+ * has just been created.
+ */
+ void loadedRoomState(Quotient::Room* room);
+
+ /** Account data (except direct chats) have changed */
+ void accountDataChanged(QString type);
+
+ /** The direct chat room is ready for using
+ * This signal is emitted upon any successful outcome from
+ * requestDirectChat.
+ */
+ void directChatAvailable(Quotient::Room* directChat);
+
+ /** The list of direct chats has changed
+ * This signal is emitted every time when the mapping of users
+ * to direct chat rooms is changed (because of either local updates
+ * or a different list arrived from the server).
+ */
+ void directChatsListChanged(Quotient::DirectChatsMap additions,
+ Quotient::DirectChatsMap removals);
+
+ void ignoredUsersListChanged(Quotient::IgnoredUsersList additions,
+ Quotient::IgnoredUsersList removals);
+
+ void cacheStateChanged();
+ void lazyLoadingChanged();
+ void turnServersChanged(const QJsonObject& servers);
+ void devicesListLoaded();
+
+#ifdef Quotient_E2EE_ENABLED
+ void newKeyVerificationSession(KeyVerificationSession* session);
+ void keyVerificationStateChanged(
+ const KeyVerificationSession* session,
+ Quotient::KeyVerificationSession::State state);
+ void sessionVerified(const QString& userId, const QString& deviceId);
+#endif
+
+protected:
+ /**
+ * @brief Access the underlying ConnectionData class
+ */
+ const ConnectionData* connectionData() const;
+
+ /** Get a Room object for the given id in the given state
+ *
+ * Use this method when you need a Room object in the local list
+ * of rooms, with the given state. Note that this does not interact
+ * with the server; in particular, does not automatically create
+ * rooms on the server. This call performs necessary join state
+ * transitions; e.g., if it finds a room in Invite but
+ * `joinState == JoinState::Join` then the Invite room object
+ * will be deleted and a new room object with Join state created.
+ * In contrast, switching between Join and Leave happens within
+ * the same object.
+ * \param roomId room id (not alias!)
+ * \param joinState desired (target) join state of the room; if
+ * omitted, any state will be found and return unchanged, or a
+ * new Join room created.
+ * @return a pointer to a Room object with the specified id and the
+ * specified state; nullptr if roomId is empty or if roomFactory()
+ * failed to create a Room object.
+ */
+ Room* provideRoom(const QString& roomId,
+ Omittable<JoinState> joinState = none);
+
+ /**
+ * Completes loading sync data.
+ */
+ void onSyncSuccess(SyncData&& data, bool fromCache = false);
+
+protected Q_SLOTS:
+ void syncLoopIteration();
+
+private:
+ class Private;
+ ImplPtr<Private> d;
+
+ static room_factory_t _roomFactory;
+ static user_factory_t _userFactory;
+};
+} // namespace Quotient
+Q_DECLARE_METATYPE(Quotient::DirectChatsMap)
+Q_DECLARE_METATYPE(Quotient::IgnoredUsersList)