aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/connection.cpp118
-rw-r--r--lib/connection.h52
-rw-r--r--lib/csapi/capabilities.cpp84
-rw-r--r--lib/csapi/capabilities.h82
-rw-r--r--lib/csapi/definitions/wellknown/full.cpp25
-rw-r--r--lib/csapi/definitions/wellknown/full.h38
-rw-r--r--lib/csapi/gtad.yaml5
-rw-r--r--lib/csapi/login.cpp7
-rw-r--r--lib/csapi/login.h6
-rw-r--r--lib/csapi/presence.cpp46
-rw-r--r--lib/csapi/presence.h53
-rw-r--r--lib/csapi/room_upgrades.cpp49
-rw-r--r--lib/csapi/room_upgrades.h43
-rw-r--r--lib/csapi/sso_login_redirect.cpp38
-rw-r--r--lib/csapi/sso_login_redirect.h39
-rw-r--r--lib/csapi/versions.cpp10
-rw-r--r--lib/csapi/versions.h19
-rw-r--r--lib/csapi/wellknown.cpp19
-rw-r--r--lib/csapi/wellknown.h9
-rw-r--r--lib/events/roomcreateevent.cpp45
-rw-r--r--lib/events/roomcreateevent.h49
-rw-r--r--lib/events/roomtombstoneevent.cpp31
-rw-r--r--lib/events/roomtombstoneevent.h41
-rw-r--r--lib/jobs/basejob.cpp12
-rw-r--r--lib/jobs/basejob.h1
-rw-r--r--lib/room.cpp122
-rw-r--r--lib/room.h37
-rw-r--r--lib/util.h4
28 files changed, 943 insertions, 141 deletions
diff --git a/lib/connection.cpp b/lib/connection.cpp
index c582cf94..4c0fe6b8 100644
--- a/lib/connection.cpp
+++ b/lib/connection.cpp
@@ -24,6 +24,7 @@
#include "room.h"
#include "settings.h"
#include "csapi/login.h"
+#include "csapi/capabilities.h"
#include "csapi/logout.h"
#include "csapi/receipts.h"
#include "csapi/leaving.h"
@@ -90,6 +91,10 @@ class Connection::Private
DirectChatUsersMap directChatUsers;
std::unordered_map<QString, EventPtr> accountData;
QString userId;
+ int syncLoopTimeout = -1;
+
+ GetCapabilitiesJob* capabilitiesJob = nullptr;
+ GetCapabilitiesJob::Capabilities capabilities;
SyncJob* syncJob = nullptr;
@@ -230,6 +235,11 @@ void Connection::doConnectToServer(const QString& user, const QString& password,
});
}
+void Connection::syncLoopIteration()
+{
+ sync(d->syncLoopTimeout);
+}
+
void Connection::connectWithToken(const QString& userId,
const QString& accessToken,
const QString& deviceId)
@@ -238,6 +248,39 @@ void Connection::connectWithToken(const QString& userId,
[=] { d->connectWithToken(userId, accessToken, deviceId); });
}
+void Connection::reloadCapabilities()
+{
+ d->capabilitiesJob = callApi<GetCapabilitiesJob>(BackgroundRequest);
+ connect(d->capabilitiesJob, &BaseJob::finished, this, [this] {
+ if (d->capabilitiesJob->error() == BaseJob::Success)
+ d->capabilities = d->capabilitiesJob->capabilities();
+ else if (d->capabilitiesJob->error() == BaseJob::IncorrectRequestError)
+ qCDebug(MAIN) << "Server doesn't support /capabilities";
+
+ if (d->capabilities.roomVersions.omitted())
+ {
+ qCWarning(MAIN) << "Pinning supported room version to 1";
+ d->capabilities.roomVersions = { "1", {{ "1", "stable" }} };
+ } else {
+ qCDebug(MAIN) << "Room versions:"
+ << defaultRoomVersion() << "is default, full list:"
+ << availableRoomVersions();
+ }
+ Q_ASSERT(!d->capabilities.roomVersions.omitted());
+ emit capabilitiesLoaded();
+ for (auto* r: d->roomMap)
+ if (r->joinState() == JoinState::Join && r->successorId().isEmpty())
+ r->checkVersion();
+ });
+}
+
+bool Connection::loadingCapabilities() const
+{
+ // (Ab)use the fact that room versions cannot be omitted after
+ // the capabilities have been loaded (see reloadCapabilities() above).
+ return d->capabilities.roomVersions.omitted();
+}
+
void Connection::Private::connectWithToken(const QString& user,
const QString& accessToken,
const QString& deviceId)
@@ -250,7 +293,7 @@ void Connection::Private::connectWithToken(const QString& user,
<< "by user" << userId << "from device" << deviceId;
emit q->stateChanged();
emit q->connected();
-
+ q->reloadCapabilities();
}
void Connection::checkAndConnect(const QString& userId,
@@ -319,6 +362,13 @@ void Connection::sync(int timeout)
});
}
+void Connection::syncLoop(int timeout)
+{
+ d->syncLoopTimeout = timeout;
+ connect(this, &Connection::syncDone, this, &Connection::syncLoopIteration);
+ syncLoopIteration(); // initial sync to start the loop
+}
+
void Connection::onSyncSuccess(SyncData &&data, bool fromCache) {
d->data->setLastEvent(data.nextBatch());
for (auto&& roomData: data.takeRoomData())
@@ -343,8 +393,14 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) {
d->pendingStateRoomIds.removeOne(roomData.roomId);
r->updateData(std::move(roomData), fromCache);
if (d->firstTimeRooms.removeOne(r))
+ {
emit loadedRoomState(r);
+ if (!d->capabilities.roomVersions.omitted())
+ r->checkVersion();
+ // Otherwise, the version will be checked in reloadCapabilities()
+ }
}
+ // Let UI update itself after updating each room
QCoreApplication::processEvents();
}
for (auto&& accountEvent: data.takeAccountData())
@@ -537,7 +593,8 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url,
CreateRoomJob* Connection::createRoom(RoomVisibility visibility,
const QString& alias, const QString& name, const QString& topic,
- QStringList invites, const QString& presetName, bool isDirect,
+ QStringList invites, const QString& presetName,
+ const QString& roomVersion, bool isDirect,
const QVector<CreateRoomJob::StateEvent>& initialState,
const QVector<CreateRoomJob::Invite3pid>& invite3pids,
const QJsonObject& creationContent)
@@ -546,7 +603,7 @@ CreateRoomJob* Connection::createRoom(RoomVisibility visibility,
auto job = callApi<CreateRoomJob>(
visibility == PublishRoom ? QStringLiteral("public")
: QStringLiteral("private"),
- alias, name, topic, invites, invite3pids, QString(/*TODO: #233*/),
+ alias, name, topic, invites, invite3pids, roomVersion,
creationContent, initialState, presetName, isDirect);
connect(job, &BaseJob::success, this, [this,job] {
emit createdRoom(provideRoom(job->roomId(), JoinState::Join));
@@ -648,7 +705,7 @@ CreateRoomJob* Connection::createDirectChat(const QString& userId,
const QString& topic, const QString& name)
{
return createRoom(UnpublishRoom, "", name, topic, {userId},
- "trusted_private_chat", true);
+ "trusted_private_chat", {}, true);
}
ForgetRoomJob* Connection::forgetRoom(const QString& id)
@@ -1245,9 +1302,54 @@ void QMatrixClient::Connection::setLazyLoading(bool newValue)
void Connection::getTurnServers()
{
- auto job = callApi<GetTurnServerJob>();
- connect( job, &GetTurnServerJob::success, [=] {
- emit turnServersChanged(job->data());
- });
+ auto job = callApi<GetTurnServerJob>();
+ connect(job, &GetTurnServerJob::success,
+ this, [=] { emit turnServersChanged(job->data()); });
+}
+
+const QString Connection::SupportedRoomVersion::StableTag =
+ QStringLiteral("stable");
+QString Connection::defaultRoomVersion() const
+{
+ Q_ASSERT(!d->capabilities.roomVersions.omitted());
+ return d->capabilities.roomVersions->defaultVersion;
+}
+
+QStringList Connection::stableRoomVersions() const
+{
+ Q_ASSERT(!d->capabilities.roomVersions.omitted());
+ QStringList l;
+ const auto& allVersions = d->capabilities.roomVersions->available;
+ for (auto it = allVersions.begin(); it != allVersions.end(); ++it)
+ if (it.value() == SupportedRoomVersion::StableTag)
+ l.push_back(it.key());
+ return l;
+}
+
+inline bool roomVersionLess(const Connection::SupportedRoomVersion& v1,
+ const Connection::SupportedRoomVersion& v2)
+{
+ bool ok1 = false, ok2 = false;
+ const auto vNum1 = v1.id.toFloat(&ok1);
+ const auto vNum2 = v2.id.toFloat(&ok2);
+ return ok1 && ok2 ? vNum1 < vNum2 : v1.id < v2.id;
+}
+
+QVector<Connection::SupportedRoomVersion> Connection::availableRoomVersions() const
+{
+ Q_ASSERT(!d->capabilities.roomVersions.omitted());
+ QVector<SupportedRoomVersion> result;
+ result.reserve(d->capabilities.roomVersions->available.size());
+ for (auto it = d->capabilities.roomVersions->available.begin();
+ it != d->capabilities.roomVersions->available.end(); ++it)
+ result.push_back({ it.key(), it.value() });
+ // Put stable versions over unstable; within each group,
+ // sort numeric versions as numbers, the rest as strings.
+ const auto mid = std::partition(result.begin(), result.end(),
+ std::mem_fn(&SupportedRoomVersion::isStable));
+ std::sort(result.begin(), mid, roomVersionLess);
+ std::sort(mid, result.end(), roomVersionLess);
+
+ return result;
}
diff --git a/lib/connection.h b/lib/connection.h
index 9e4121f4..1faee255 100644
--- a/lib/connection.h
+++ b/lib/connection.h
@@ -102,6 +102,7 @@ namespace QMatrixClient
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(QString defaultRoomVersion READ defaultRoomVersion NOTIFY capabilitiesLoaded)
Q_PROPERTY(QUrl homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
Q_PROPERTY(bool cacheState READ cacheState WRITE setCacheState NOTIFY cacheStateChanged)
Q_PROPERTY(bool lazyLoading READ lazyLoading WRITE setLazyLoading NOTIFY lazyLoadingChanged)
@@ -257,6 +258,44 @@ namespace QMatrixClient
Q_INVOKABLE QString token() 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; }
+
+ // Pretty-printing
+
+ friend QDebug operator<<(QDebug dbg,
+ const SupportedRoomVersion& v)
+ {
+ QDebugStateSaver _(dbg);
+ return dbg.nospace() << v.id << '/' << v.status;
+ }
+
+ friend QDebug operator<<(QDebug dbg,
+ const QVector<SupportedRoomVersion>& vs)
+ {
+ return QtPrivate::printSequentialContainer(
+ dbg, "", vs);
+ }
+ };
+
+ /// 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;
+
/**
* Call this before first sync to load from previously saved file.
*
@@ -365,12 +404,19 @@ namespace QMatrixClient
const QString& deviceId = {});
void connectWithToken(const QString& userId, 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;
/** @deprecated Use stopSync() instead */
void disconnectFromServer() { stopSync(); }
void logout();
void sync(int timeout = -1);
+ void syncLoop(int timeout = -1);
+
void stopSync();
QString nextBatchToken() const;
@@ -402,7 +448,7 @@ namespace QMatrixClient
CreateRoomJob* createRoom(RoomVisibility visibility,
const QString& alias, const QString& name, const QString& topic,
QStringList invites, const QString& presetName = {},
- bool isDirect = false,
+ const QString& roomVersion = {}, bool isDirect = false,
const QVector<CreateRoomJob::StateEvent>& initialState = {},
const QVector<CreateRoomJob::Invite3pid>& invite3pids = {},
const QJsonObject& creationContent = {});
@@ -499,6 +545,7 @@ namespace QMatrixClient
void resolveError(QString error);
void homeserverChanged(QUrl baseUrl);
+ void capabilitiesLoaded();
void connected();
void reconnected(); //< \deprecated Use connected() instead
@@ -679,6 +726,9 @@ namespace QMatrixClient
*/
void onSyncSuccess(SyncData &&data, bool fromCache = false);
+ protected slots:
+ void syncLoopIteration();
+
private:
class Private;
std::unique_ptr<Private> d;
diff --git a/lib/csapi/capabilities.cpp b/lib/csapi/capabilities.cpp
new file mode 100644
index 00000000..210423f5
--- /dev/null
+++ b/lib/csapi/capabilities.cpp
@@ -0,0 +1,84 @@
+/******************************************************************************
+ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
+ */
+
+#include "capabilities.h"
+
+#include "converters.h"
+
+#include <QtCore/QStringBuilder>
+
+using namespace QMatrixClient;
+
+static const auto basePath = QStringLiteral("/_matrix/client/r0");
+
+namespace QMatrixClient
+{
+ // Converters
+
+ template <> struct JsonObjectConverter<GetCapabilitiesJob::ChangePasswordCapability>
+ {
+ static void fillFrom(const QJsonObject& jo, GetCapabilitiesJob::ChangePasswordCapability& result)
+ {
+ fromJson(jo.value("enabled"_ls), result.enabled);
+ }
+ };
+
+ template <> struct JsonObjectConverter<GetCapabilitiesJob::RoomVersionsCapability>
+ {
+ static void fillFrom(const QJsonObject& jo, GetCapabilitiesJob::RoomVersionsCapability& result)
+ {
+ fromJson(jo.value("default"_ls), result.defaultVersion);
+ fromJson(jo.value("available"_ls), result.available);
+ }
+ };
+
+ template <> struct JsonObjectConverter<GetCapabilitiesJob::Capabilities>
+ {
+ static void fillFrom(QJsonObject jo, GetCapabilitiesJob::Capabilities& result)
+ {
+ fromJson(jo.take("m.change_password"_ls), result.changePassword);
+ fromJson(jo.take("m.room_versions"_ls), result.roomVersions);
+ fromJson(jo, result.additionalProperties);
+ }
+ };
+} // namespace QMatrixClient
+
+class GetCapabilitiesJob::Private
+{
+ public:
+ Capabilities capabilities;
+};
+
+QUrl GetCapabilitiesJob::makeRequestUrl(QUrl baseUrl)
+{
+ return BaseJob::makeRequestUrl(std::move(baseUrl),
+ basePath % "/capabilities");
+}
+
+static const auto GetCapabilitiesJobName = QStringLiteral("GetCapabilitiesJob");
+
+GetCapabilitiesJob::GetCapabilitiesJob()
+ : BaseJob(HttpVerb::Get, GetCapabilitiesJobName,
+ basePath % "/capabilities")
+ , d(new Private)
+{
+}
+
+GetCapabilitiesJob::~GetCapabilitiesJob() = default;
+
+const GetCapabilitiesJob::Capabilities& GetCapabilitiesJob::capabilities() const
+{
+ return d->capabilities;
+}
+
+BaseJob::Status GetCapabilitiesJob::parseJson(const QJsonDocument& data)
+{
+ auto json = data.object();
+ if (!json.contains("capabilities"_ls))
+ return { JsonParseError,
+ "The key 'capabilities' not found in the response" };
+ fromJson(json.value("capabilities"_ls), d->capabilities);
+ return Success;
+}
+
diff --git a/lib/csapi/capabilities.h b/lib/csapi/capabilities.h
new file mode 100644
index 00000000..39e2f4d1
--- /dev/null
+++ b/lib/csapi/capabilities.h
@@ -0,0 +1,82 @@
+/******************************************************************************
+ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
+ */
+
+#pragma once
+
+#include "jobs/basejob.h"
+
+#include <QtCore/QJsonObject>
+#include "converters.h"
+#include <QtCore/QHash>
+
+namespace QMatrixClient
+{
+ // Operations
+
+ /// Gets information about the server's capabilities.
+ ///
+ /// Gets information about the server's supported feature set
+ /// and other relevant capabilities.
+ class GetCapabilitiesJob : public BaseJob
+ {
+ public:
+ // Inner data structures
+
+ /// Capability to indicate if the user can change their password.
+ struct ChangePasswordCapability
+ {
+ /// True if the user can change their password, false otherwise.
+ bool enabled;
+ };
+
+ /// The room versions the server supports.
+ struct RoomVersionsCapability
+ {
+ /// The default room version the server is using for new rooms.
+ QString defaultVersion;
+ /// A detailed description of the room versions the server supports.
+ QHash<QString, QString> available;
+ };
+
+ /// Gets information about the server's supported feature set
+ /// and other relevant capabilities.
+ struct Capabilities
+ {
+ /// Capability to indicate if the user can change their password.
+ Omittable<ChangePasswordCapability> changePassword;
+ /// The room versions the server supports.
+ Omittable<RoomVersionsCapability> roomVersions;
+ /// The custom capabilities the server supports, using the
+ /// Java package naming convention.
+ QHash<QString, QJsonObject> additionalProperties;
+ };
+
+ // Construction/destruction
+
+ explicit GetCapabilitiesJob();
+
+ /*! Construct a URL without creating a full-fledged job object
+ *
+ * This function can be used when a URL for
+ * GetCapabilitiesJob is necessary but the job
+ * itself isn't.
+ */
+ static QUrl makeRequestUrl(QUrl baseUrl);
+
+ ~GetCapabilitiesJob() override;
+
+ // Result properties
+
+ /// Gets information about the server's supported feature set
+ /// and other relevant capabilities.
+ const Capabilities& capabilities() const;
+
+ protected:
+ Status parseJson(const QJsonDocument& data) override;
+
+ private:
+ class Private;
+ QScopedPointer<Private> d;
+ };
+} // namespace QMatrixClient
diff --git a/lib/csapi/definitions/wellknown/full.cpp b/lib/csapi/definitions/wellknown/full.cpp
new file mode 100644
index 00000000..5ecef34f
--- /dev/null
+++ b/lib/csapi/definitions/wellknown/full.cpp
@@ -0,0 +1,25 @@
+/******************************************************************************
+ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
+ */
+
+#include "full.h"
+
+using namespace QMatrixClient;
+
+void JsonObjectConverter<DiscoveryInformation>::dumpTo(
+ QJsonObject& jo, const DiscoveryInformation& pod)
+{
+ fillJson(jo, pod.additionalProperties);
+ addParam<>(jo, QStringLiteral("m.homeserver"), pod.homeserver);
+ addParam<IfNotEmpty>(jo, QStringLiteral("m.identity_server"), pod.identityServer);
+}
+
+void JsonObjectConverter<DiscoveryInformation>::fillFrom(
+ QJsonObject jo, DiscoveryInformation& result)
+{
+ fromJson(jo.take("m.homeserver"_ls), result.homeserver);
+ fromJson(jo.take("m.identity_server"_ls), result.identityServer);
+
+ fromJson(jo, result.additionalProperties);
+}
+
diff --git a/lib/csapi/definitions/wellknown/full.h b/lib/csapi/definitions/wellknown/full.h
new file mode 100644
index 00000000..d9346acb
--- /dev/null
+++ b/lib/csapi/definitions/wellknown/full.h
@@ -0,0 +1,38 @@
+/******************************************************************************
+ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
+ */
+
+#pragma once
+
+#include "converters.h"
+
+#include <QtCore/QJsonObject>
+#include "converters.h"
+#include "csapi/definitions/wellknown/homeserver.h"
+#include "csapi/definitions/wellknown/identity_server.h"
+#include <QtCore/QHash>
+
+namespace QMatrixClient
+{
+ // Data structures
+
+ /// Used by clients to determine the homeserver, identity server, and other
+ /// optional components they should be interacting with.
+ struct DiscoveryInformation
+ {
+ /// Used by clients to determine the homeserver, identity server, and other
+ /// optional components they should be interacting with.
+ HomeserverInformation homeserver;
+ /// Used by clients to determine the homeserver, identity server, and other
+ /// optional components they should be interacting with.
+ Omittable<IdentityServerInformation> identityServer;
+ /// Application-dependent keys using Java package naming convention.
+ QHash<QString, QJsonObject> additionalProperties;
+ };
+ template <> struct JsonObjectConverter<DiscoveryInformation>
+ {
+ static void dumpTo(QJsonObject& jo, const DiscoveryInformation& pod);
+ static void fillFrom(QJsonObject jo, DiscoveryInformation& pod);
+ };
+
+} // namespace QMatrixClient
diff --git a/lib/csapi/gtad.yaml b/lib/csapi/gtad.yaml
index c6ea8a13..a44f803a 100644
--- a/lib/csapi/gtad.yaml
+++ b/lib/csapi/gtad.yaml
@@ -5,12 +5,15 @@ analyzer:
identifiers:
signed: signedData
unsigned: unsignedData
- default: isDefault
+ PushRule/default: isDefault
+ default: defaultVersion # getCapabilities/RoomVersionsCapability
origin_server_ts: originServerTimestamp # Instead of originServerTs
start: begin # Because start() is a method in BaseJob
m.upload.size: uploadSize
m.homeserver: homeserver
m.identity_server: identityServer
+ m.change_password: changePassword
+ m.room_versions: roomVersions
AuthenticationData/additionalProperties: authInfo
# Structure inside `types`:
diff --git a/lib/csapi/login.cpp b/lib/csapi/login.cpp
index ee33dac2..5e369b9a 100644
--- a/lib/csapi/login.cpp
+++ b/lib/csapi/login.cpp
@@ -67,6 +67,7 @@ class LoginJob::Private
QString accessToken;
QString homeServer;
QString deviceId;
+ Omittable<DiscoveryInformation> wellKnown;
};
static const auto LoginJobName = QStringLiteral("LoginJob");
@@ -111,6 +112,11 @@ const QString& LoginJob::deviceId() const
return d->deviceId;
}
+const Omittable<DiscoveryInformation>& LoginJob::wellKnown() const
+{
+ return d->wellKnown;
+}
+
BaseJob::Status LoginJob::parseJson(const QJsonDocument& data)
{
auto json = data.object();
@@ -118,6 +124,7 @@ BaseJob::Status LoginJob::parseJson(const QJsonDocument& data)
fromJson(json.value("access_token"_ls), d->accessToken);
fromJson(json.value("home_server"_ls), d->homeServer);
fromJson(json.value("device_id"_ls), d->deviceId);
+ fromJson(json.value("well_known"_ls), d->wellKnown);
return Success;
}
diff --git a/lib/csapi/login.h b/lib/csapi/login.h
index 957d8881..648316df 100644
--- a/lib/csapi/login.h
+++ b/lib/csapi/login.h
@@ -7,6 +7,7 @@
#include "jobs/basejob.h"
#include <QtCore/QVector>
+#include "csapi/definitions/wellknown/full.h"
#include "csapi/definitions/user_identifier.h"
#include "converters.h"
@@ -118,6 +119,11 @@ namespace QMatrixClient
/// ID of the logged-in device. Will be the same as the
/// corresponding parameter in the request, if one was specified.
const QString& deviceId() const;
+ /// Optional client configuration provided by the server. If present,
+ /// clients SHOULD use the provided object to reconfigure themselves,
+ /// optionally validating the URLs within. This object takes the same
+ /// form as the one returned from .well-known autodiscovery.
+ const Omittable<DiscoveryInformation>& wellKnown() const;
protected:
Status parseJson(const QJsonDocument& data) override;
diff --git a/lib/csapi/presence.cpp b/lib/csapi/presence.cpp
index 8a5510b8..024d7a34 100644
--- a/lib/csapi/presence.cpp
+++ b/lib/csapi/presence.cpp
@@ -83,49 +83,3 @@ BaseJob::Status GetPresenceJob::parseJson(const QJsonDocument& data)
return Success;
}
-static const auto ModifyPresenceListJobName = QStringLiteral("ModifyPresenceListJob");
-
-ModifyPresenceListJob::ModifyPresenceListJob(const QString& userId, const QStringList& invite, const QStringList& drop)
- : BaseJob(HttpVerb::Post, ModifyPresenceListJobName,
- basePath % "/presence/list/" % userId)
-{
- QJsonObject _data;
- addParam<IfNotEmpty>(_data, QStringLiteral("invite"), invite);
- addParam<IfNotEmpty>(_data, QStringLiteral("drop"), drop);
- setRequestData(_data);
-}
-
-class GetPresenceForListJob::Private
-{
- public:
- Events data;
-};
-
-QUrl GetPresenceForListJob::makeRequestUrl(QUrl baseUrl, const QString& userId)
-{
- return BaseJob::makeRequestUrl(std::move(baseUrl),
- basePath % "/presence/list/" % userId);
-}
-
-static const auto GetPresenceForListJobName = QStringLiteral("GetPresenceForListJob");
-
-GetPresenceForListJob::GetPresenceForListJob(const QString& userId)
- : BaseJob(HttpVerb::Get, GetPresenceForListJobName,
- basePath % "/presence/list/" % userId, false)
- , d(new Private)
-{
-}
-
-GetPresenceForListJob::~GetPresenceForListJob() = default;
-
-Events&& GetPresenceForListJob::data()
-{
- return std::move(d->data);
-}
-
-BaseJob::Status GetPresenceForListJob::parseJson(const QJsonDocument& data)
-{
- fromJson(data, d->data);
- return Success;
-}
-
diff --git a/lib/csapi/presence.h b/lib/csapi/presence.h
index c8f80357..5e132d24 100644
--- a/lib/csapi/presence.h
+++ b/lib/csapi/presence.h
@@ -6,7 +6,6 @@
#include "jobs/basejob.h"
-#include "events/eventloader.h"
#include "converters.h"
namespace QMatrixClient
@@ -74,56 +73,4 @@ namespace QMatrixClient
class Private;
QScopedPointer<Private> d;
};
-
- /// Add or remove users from this presence list.
- ///
- /// Adds or removes users from this presence list.
- class ModifyPresenceListJob : public BaseJob
- {
- public:
- /*! Add or remove users from this presence list.
- * \param userId
- * The user whose presence list is being modified.
- * \param invite
- * A list of user IDs to add to the list.
- * \param drop
- * A list of user IDs to remove from the list.
- */
- explicit ModifyPresenceListJob(const QString& userId, const QStringList& invite = {}, const QStringList& drop = {});
- };
-
- /// Get presence events for this presence list.
- ///
- /// Retrieve a list of presence events for every user on this list.
- class GetPresenceForListJob : public BaseJob
- {
- public:
- /*! Get presence events for this presence list.
- * \param userId
- * The user whose presence list should be retrieved.
- */
- explicit GetPresenceForListJob(const QString& userId);
-
- /*! Construct a URL without creating a full-fledged job object
- *
- * This function can be used when a URL for
- * GetPresenceForListJob is necessary but the job
- * itself isn't.
- */
- static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId);
-
- ~GetPresenceForListJob() override;
-
- // Result properties
-
- /// A list of presence events for this list.
- Events&& data();
-
- protected:
- Status parseJson(const QJsonDocument& data) override;
-
- private:
- class Private;
- QScopedPointer<Private> d;
- };
} // namespace QMatrixClient
diff --git a/lib/csapi/room_upgrades.cpp b/lib/csapi/room_upgrades.cpp
new file mode 100644
index 00000000..f58fd675
--- /dev/null
+++ b/lib/csapi/room_upgrades.cpp
@@ -0,0 +1,49 @@
+/******************************************************************************
+ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
+ */
+
+#include "room_upgrades.h"
+
+#include "converters.h"
+
+#include <QtCore/QStringBuilder>
+
+using namespace QMatrixClient;
+
+static const auto basePath = QStringLiteral("/_matrix/client/r0");
+
+class UpgradeRoomJob::Private
+{
+ public:
+ QString replacementRoom;
+};
+
+static const auto UpgradeRoomJobName = QStringLiteral("UpgradeRoomJob");
+
+UpgradeRoomJob::UpgradeRoomJob(const QString& roomId, const QString& newVersion)
+ : BaseJob(HttpVerb::Post, UpgradeRoomJobName,
+ basePath % "/rooms/" % roomId % "/upgrade")
+ , d(new Private)
+{
+ QJsonObject _data;
+ addParam<>(_data, QStringLiteral("new_version"), newVersion);
+ setRequestData(_data);
+}
+
+UpgradeRoomJob::~UpgradeRoomJob() = default;
+
+const QString& UpgradeRoomJob::replacementRoom() const
+{
+ return d->replacementRoom;
+}
+
+BaseJob::Status UpgradeRoomJob::parseJson(const QJsonDocument& data)
+{
+ auto json = data.object();
+ if (!json.contains("replacement_room"_ls))
+ return { JsonParseError,
+ "The key 'replacement_room' not found in the response" };
+ fromJson(json.value("replacement_room"_ls), d->replacementRoom);
+ return Success;
+}
+
diff --git a/lib/csapi/room_upgrades.h b/lib/csapi/room_upgrades.h
new file mode 100644
index 00000000..6f712f10
--- /dev/null
+++ b/lib/csapi/room_upgrades.h
@@ -0,0 +1,43 @@
+/******************************************************************************
+ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
+ */
+
+#pragma once
+
+#include "jobs/basejob.h"
+
+
+namespace QMatrixClient
+{
+ // Operations
+
+ /// Upgrades a room to a new room version.
+ ///
+ /// Upgrades the given room to a particular room version, migrating as much
+ /// data as possible over to the new room. See the `room_upgrades <#room-upgrades>`_
+ /// module for more information on what this entails.
+ class UpgradeRoomJob : public BaseJob
+ {
+ public:
+ /*! Upgrades a room to a new room version.
+ * \param roomId
+ * The ID of the room to upgrade.
+ * \param newVersion
+ * The new version for the room.
+ */
+ explicit UpgradeRoomJob(const QString& roomId, const QString& newVersion);
+ ~UpgradeRoomJob() override;
+
+ // Result properties
+
+ /// The ID of the new room.
+ const QString& replacementRoom() const;
+
+ protected:
+ Status parseJson(const QJsonDocument& data) override;
+
+ private:
+ class Private;
+ QScopedPointer<Private> d;
+ };
+} // namespace QMatrixClient
diff --git a/lib/csapi/sso_login_redirect.cpp b/lib/csapi/sso_login_redirect.cpp
new file mode 100644
index 00000000..7323951c
--- /dev/null
+++ b/lib/csapi/sso_login_redirect.cpp
@@ -0,0 +1,38 @@
+/******************************************************************************
+ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
+ */
+
+#include "sso_login_redirect.h"
+
+#include "converters.h"
+
+#include <QtCore/QStringBuilder>
+
+using namespace QMatrixClient;
+
+static const auto basePath = QStringLiteral("/_matrix/client/r0");
+
+BaseJob::Query queryToRedirectToSSO(const QString& redirectUrl)
+{
+ BaseJob::Query _q;
+ addParam<>(_q, QStringLiteral("redirectUrl"), redirectUrl);
+ return _q;
+}
+
+QUrl RedirectToSSOJob::makeRequestUrl(QUrl baseUrl, const QString& redirectUrl)
+{
+ return BaseJob::makeRequestUrl(std::move(baseUrl),
+ basePath % "/login/sso/redirect",
+ queryToRedirectToSSO(redirectUrl));
+}
+
+static const auto RedirectToSSOJobName = QStringLiteral("RedirectToSSOJob");
+
+RedirectToSSOJob::RedirectToSSOJob(const QString& redirectUrl)
+ : BaseJob(HttpVerb::Get, RedirectToSSOJobName,
+ basePath % "/login/sso/redirect",
+ queryToRedirectToSSO(redirectUrl),
+ {}, false)
+{
+}
+
diff --git a/lib/csapi/sso_login_redirect.h b/lib/csapi/sso_login_redirect.h
new file mode 100644
index 00000000..c09365b0
--- /dev/null
+++ b/lib/csapi/sso_login_redirect.h
@@ -0,0 +1,39 @@
+/******************************************************************************
+ * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
+ */
+
+#pragma once
+
+#include "jobs/basejob.h"
+
+
+namespace QMatrixClient
+{
+ // Operations
+
+ /// Redirect the user's browser to the SSO interface.
+ ///
+ /// A web-based Matrix client should instruct the user's browser to
+ /// navigate to this endpoint in order to log in via SSO.
+ ///
+ /// The server MUST respond with an HTTP redirect to the SSO interface.
+ class RedirectToSSOJob : public BaseJob
+ {
+ public:
+ /*! Redirect the user's browser to the SSO interface.
+ * \param redirectUrl
+ * URI to which the user will be redirected after the homeserver has
+ * authenticated the user with SSO.
+ */
+ explicit RedirectToSSOJob(const QString& redirectUrl);
+
+ /*! Construct a URL without creating a full-fledged job object
+ *
+ * This function can be used when a URL for
+ * RedirectToSSOJob is necessary but the job
+ * itself isn't.
+ */
+ static QUrl makeRequestUrl(QUrl baseUrl, const QString& redirectUrl);
+
+ };
+} // namespace QMatrixClient
diff --git a/lib/csapi/versions.cpp b/lib/csapi/versions.cpp
index c853ec06..6ee6725d 100644
--- a/lib/csapi/versions.cpp
+++ b/lib/csapi/versions.cpp
@@ -16,6 +16,7 @@ class GetVersionsJob::Private
{
public:
QStringList versions;
+ QHash<QString, bool> unstableFeatures;
};
QUrl GetVersionsJob::makeRequestUrl(QUrl baseUrl)
@@ -40,10 +41,19 @@ const QStringList& GetVersionsJob::versions() const
return d->versions;
}
+const QHash<QString, bool>& GetVersionsJob::unstableFeatures() const
+{
+ return d->unstableFeatures;
+}
+
BaseJob::Status GetVersionsJob::parseJson(const QJsonDocument& data)
{
auto json = data.object();
+ if (!json.contains("versions"_ls))
+ return { JsonParseError,
+ "The key 'versions' not found in the response" };
fromJson(json.value("versions"_ls), d->versions);
+ fromJson(json.value("unstable_features"_ls), d->unstableFeatures);
return Success;
}
diff --git a/lib/csapi/versions.h b/lib/csapi/versions.h
index 309de184..b56f293f 100644
--- a/lib/csapi/versions.h
+++ b/lib/csapi/versions.h
@@ -6,6 +6,8 @@
#include "jobs/basejob.h"
+#include <QtCore/QHash>
+#include "converters.h"
namespace QMatrixClient
{
@@ -19,6 +21,19 @@ namespace QMatrixClient
///
/// Only the latest ``Z`` value will be reported for each supported ``X.Y`` value.
/// i.e. if the server implements ``r0.0.0``, ``r0.0.1``, and ``r1.2.0``, it will report ``r0.0.1`` and ``r1.2.0``.
+ ///
+ /// The server may additionally advertise experimental features it supports
+ /// through ``unstable_features``. These features should be namespaced and
+ /// may optionally include version information within their name if desired.
+ /// Features listed here are not for optionally toggling parts of the Matrix
+ /// specification and should only be used to advertise support for a feature
+ /// which has not yet landed in the spec. For example, a feature currently
+ /// undergoing the proposal process may appear here and eventually be taken
+ /// off this list once the feature lands in the spec and the server deems it
+ /// reasonable to do so. Servers may wish to keep advertising features here
+ /// after they've been released into the spec to give clients a chance to
+ /// upgrade appropriately. Additionally, clients should avoid using unstable
+ /// features in their stable releases.
class GetVersionsJob : public BaseJob
{
public:
@@ -38,6 +53,10 @@ namespace QMatrixClient
/// The supported versions.
const QStringList& versions() const;
+ /// Experimental features the server supports. Features not listed here,
+ /// or the lack of this property all together, indicate that a feature is
+ /// not supported.
+ const QHash<QString, bool>& unstableFeatures() const;
protected:
Status parseJson(const QJsonDocument& data) override;
diff --git a/lib/csapi/wellknown.cpp b/lib/csapi/wellknown.cpp
index 97505830..a6107f86 100644
--- a/lib/csapi/wellknown.cpp
+++ b/lib/csapi/wellknown.cpp
@@ -15,8 +15,7 @@ static const auto basePath = QStringLiteral("/.well-known");
class GetWellknownJob::Private
{
public:
- HomeserverInformation homeserver;
- Omittable<IdentityServerInformation> identityServer;
+ DiscoveryInformation data;
};
QUrl GetWellknownJob::makeRequestUrl(QUrl baseUrl)
@@ -36,24 +35,14 @@ GetWellknownJob::GetWellknownJob()
GetWellknownJob::~GetWellknownJob() = default;
-const HomeserverInformation& GetWellknownJob::homeserver() const
+const DiscoveryInformation& GetWellknownJob::data() const
{
- return d->homeserver;
-}
-
-const Omittable<IdentityServerInformation>& GetWellknownJob::identityServer() const
-{
- return d->identityServer;
+ return d->data;
}
BaseJob::Status GetWellknownJob::parseJson(const QJsonDocument& data)
{
- auto json = data.object();
- if (!json.contains("m.homeserver"_ls))
- return { JsonParseError,
- "The key 'm.homeserver' not found in the response" };
- fromJson(json.value("m.homeserver"_ls), d->homeserver);
- fromJson(json.value("m.identity_server"_ls), d->identityServer);
+ fromJson(data, d->data);
return Success;
}
diff --git a/lib/csapi/wellknown.h b/lib/csapi/wellknown.h
index df4c8c6e..8da9ce9f 100644
--- a/lib/csapi/wellknown.h
+++ b/lib/csapi/wellknown.h
@@ -6,9 +6,8 @@
#include "jobs/basejob.h"
+#include "csapi/definitions/wellknown/full.h"
#include "converters.h"
-#include "csapi/definitions/wellknown/identity_server.h"
-#include "csapi/definitions/wellknown/homeserver.h"
namespace QMatrixClient
{
@@ -41,10 +40,8 @@ namespace QMatrixClient
// Result properties
- /// Information about the homeserver to connect to.
- const HomeserverInformation& homeserver() const;
- /// Optional. Information about the identity server to connect to.
- const Omittable<IdentityServerInformation>& identityServer() const;
+ /// Server discovery information.
+ const DiscoveryInformation& data() const;
protected:
Status parseJson(const QJsonDocument& data) override;
diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp
new file mode 100644
index 00000000..8fd0f1de
--- /dev/null
+++ b/lib/events/roomcreateevent.cpp
@@ -0,0 +1,45 @@
+/******************************************************************************
+* Copyright (C) 2019 QMatrixClient project
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation; either
+* version 2.1 of the License, or (at your option) any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "roomcreateevent.h"
+
+using namespace QMatrixClient;
+
+bool RoomCreateEvent::isFederated() const
+{
+ return fromJson<bool>(contentJson()["m.federate"_ls]);
+}
+
+QString RoomCreateEvent::version() const
+{
+ return fromJson<QString>(contentJson()["room_version"_ls]);
+}
+
+RoomCreateEvent::Predecessor RoomCreateEvent::predecessor() const
+{
+ const auto predJson = contentJson()["predecessor"_ls].toObject();
+ return {
+ fromJson<QString>(predJson["room_id"_ls]),
+ fromJson<QString>(predJson["event_id"_ls])
+ };
+}
+
+bool RoomCreateEvent::isUpgrade() const
+{
+ return contentJson().contains("predecessor"_ls);
+}
diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h
new file mode 100644
index 00000000..0a8f27cc
--- /dev/null
+++ b/lib/events/roomcreateevent.h
@@ -0,0 +1,49 @@
+/******************************************************************************
+* Copyright (C) 2019 QMatrixClient project
+*
+* 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 "stateevent.h"
+
+namespace QMatrixClient
+{
+ class RoomCreateEvent : public StateEventBase
+ {
+ public:
+ DEFINE_EVENT_TYPEID("m.room.create", RoomCreateEvent)
+
+ explicit RoomCreateEvent()
+ : StateEventBase(typeId(), matrixTypeId())
+ { }
+ explicit RoomCreateEvent(const QJsonObject& obj)
+ : StateEventBase(typeId(), obj)
+ { }
+
+ struct Predecessor
+ {
+ QString roomId;
+ QString eventId;
+ };
+
+ bool isFederated() const;
+ QString version() const;
+ Predecessor predecessor() const;
+ bool isUpgrade() const;
+ };
+ REGISTER_EVENT_TYPE(RoomCreateEvent)
+}
diff --git a/lib/events/roomtombstoneevent.cpp b/lib/events/roomtombstoneevent.cpp
new file mode 100644
index 00000000..9c3bafd4
--- /dev/null
+++ b/lib/events/roomtombstoneevent.cpp
@@ -0,0 +1,31 @@
+/******************************************************************************
+* Copyright (C) 2019 QMatrixClient project
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation; either
+* version 2.1 of the License, or (at your option) any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "roomtombstoneevent.h"
+
+using namespace QMatrixClient;
+
+QString RoomTombstoneEvent::serverMessage() const
+{
+ return fromJson<QString>(contentJson()["body"_ls]);
+}
+
+QString RoomTombstoneEvent::successorRoomId() const
+{
+ return fromJson<QString>(contentJson()["replacement_room"_ls]);
+}
diff --git a/lib/events/roomtombstoneevent.h b/lib/events/roomtombstoneevent.h
new file mode 100644
index 00000000..c7008ec4
--- /dev/null
+++ b/lib/events/roomtombstoneevent.h
@@ -0,0 +1,41 @@
+/******************************************************************************
+* Copyright (C) 2019 QMatrixClient project
+*
+* 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 "stateevent.h"
+
+namespace QMatrixClient
+{
+ class RoomTombstoneEvent : public StateEventBase
+ {
+ public:
+ DEFINE_EVENT_TYPEID("m.room.tombstone", RoomTombstoneEvent)
+
+ explicit RoomTombstoneEvent()
+ : StateEventBase(typeId(), matrixTypeId())
+ { }
+ explicit RoomTombstoneEvent(const QJsonObject& obj)
+ : StateEventBase(typeId(), obj)
+ { }
+
+ QString serverMessage() const;
+ QString successorRoomId() const;
+ };
+ REGISTER_EVENT_TYPE(RoomTombstoneEvent)
+}
diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp
index 4a7780b1..628d10ec 100644
--- a/lib/jobs/basejob.cpp
+++ b/lib/jobs/basejob.cpp
@@ -325,7 +325,15 @@ void BaseJob::gotReply()
d->status.code = UserConsentRequiredError;
d->errorUrl = json.value("consent_uri"_ls).toString();
}
- else if (!json.isEmpty()) // Not localisable on the client side
+ else if (errCode == "M_UNSUPPORTED_ROOM_VERSION" ||
+ errCode == "M_INCOMPATIBLE_ROOM_VERSION")
+ {
+ d->status.code = UnsupportedRoomVersionError;
+ if (json.contains("room_version"))
+ d->status.message =
+ tr("Requested room version: %1")
+ .arg(json.value("room_version").toString());
+ } else if (!json.isEmpty()) // Not localisable on the client side
setStatus(IncorrectRequestError,
json.value("error"_ls).toString());
}
@@ -568,6 +576,8 @@ QString BaseJob::statusCaption() const
return tr("Network authentication required");
case UserConsentRequiredError:
return tr("User consent required");
+ case UnsupportedRoomVersionError:
+ return tr("The server does not support the needed room version");
default:
return tr("Request failed");
}
diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h
index dd6f9fc8..4c1c7706 100644
--- a/lib/jobs/basejob.h
+++ b/lib/jobs/basejob.h
@@ -64,6 +64,7 @@ namespace QMatrixClient
, IncorrectResponseError
, TooManyRequestsError
, RequestNotImplementedError
+ , UnsupportedRoomVersionError
, NetworkAuthRequiredError
, UserConsentRequiredError
, UserDefinedError = 200
diff --git a/lib/room.cpp b/lib/room.cpp
index d806183f..c6376a26 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -29,7 +29,10 @@
#include "csapi/room_send.h"
#include "csapi/rooms.h"
#include "csapi/tags.h"
+#include "csapi/room_upgrades.h"
#include "events/simplestateevents.h"
+#include "events/roomcreateevent.h"
+#include "events/roomtombstoneevent.h"
#include "events/roomavatarevent.h"
#include "events/roommemberevent.h"
#include "events/typingevent.h"
@@ -53,6 +56,7 @@
#include <QtCore/QPointer>
#include <QtCore/QDir>
#include <QtCore/QTemporaryFile>
+#include <QtCore/QRegularExpression>
#include <QtCore/QMimeDatabase>
#include <array>
@@ -250,11 +254,17 @@ class Room::Private
const QString& txnId, BaseJob* call = nullptr);
template <typename EvT>
- auto requestSetState(const QString& stateKey, const EvT& event)
+ SetRoomStateWithKeyJob* requestSetState(const QString& stateKey,
+ const EvT& event)
{
- // TODO: Queue up state events sending (see #133).
- return connection->callApi<SetRoomStateWithKeyJob>(
+ if (q->successorId().isEmpty())
+ {
+ // TODO: Queue up state events sending (see #133).
+ return connection->callApi<SetRoomStateWithKeyJob>(
id, EvT::matrixTypeId(), stateKey, event.contentJson());
+ }
+ qCWarning(MAIN) << q << "has been upgraded, state won't be set";
+ return nullptr;
}
template <typename EvT>
@@ -296,6 +306,12 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState)
// https://marcmutz.wordpress.com/translated-articles/pimp-my-pimpl-%E2%80%94-reloaded/
d->q = this;
d->displayname = d->calculateDisplayname(); // Set initial "Empty room" name
+ connectUntil(connection, &Connection::loadedRoomState, this,
+ [this] (Room* r) {
+ if (this == r)
+ emit baseStateLoaded();
+ return this == r; // loadedRoomState fires only once per room
+ });
qCDebug(MAIN) << "New" << toCString(initialJoinState) << "Room:" << id;
}
@@ -309,6 +325,28 @@ const QString& Room::id() const
return d->id;
}
+QString Room::version() const
+{
+ const auto v = d->getCurrentState<RoomCreateEvent>()->version();
+ return v.isEmpty() ? "1" : v;
+}
+
+bool Room::isUnstable() const
+{
+ return !connection()->loadingCapabilities() &&
+ !connection()->stableRoomVersions().contains(version());
+}
+
+QString Room::predecessorId() const
+{
+ return d->getCurrentState<RoomCreateEvent>()->predecessor().roomId;
+}
+
+QString Room::successorId() const
+{
+ return d->getCurrentState<RoomTombstoneEvent>()->successorRoomId();
+}
+
const Room::Timeline& Room::messageEvents() const
{
return d->timeline;
@@ -543,6 +581,26 @@ void Room::markAllMessagesAsRead()
d->markMessagesAsRead(d->timeline.crbegin());
}
+bool Room::canSwitchVersions() const
+{
+ // TODO, #276: m.room.power_levels
+ const auto* plEvt =
+ d->currentState.value({"m.room.power_levels", ""});
+ if (!plEvt)
+ return true;
+
+ const auto plJson = plEvt->contentJson();
+ const auto currentUserLevel =
+ plJson.value("users"_ls).toObject()
+ .value(localUser()->id()).toInt(
+ plJson.value("users_default"_ls).toInt());
+ const auto tombstonePowerLevel =
+ plJson.value("events").toObject()
+ .value("m.room.tombstone"_ls).toInt(
+ plJson.value("state_default"_ls).toInt());
+ return currentUserLevel >= tombstonePowerLevel;
+}
+
bool Room::hasUnreadMessages() const
{
return unreadCount() >= 0;
@@ -762,6 +820,14 @@ void Room::resetHighlightCount()
emit highlightCountChanged(this);
}
+void Room::switchVersion(QString newVersion)
+{
+ auto* job = connection()->callApi<UpgradeRoomJob>(id(), newVersion);
+ connect(job, &BaseJob::failure, this, [this,job] {
+ emit upgradeFailed(job->errorString());
+ });
+}
+
bool Room::hasAccountData(const QString& type) const
{
return d->accountData.find(type) != d->accountData.end();
@@ -1300,6 +1366,8 @@ RoomEvent* Room::Private::addAsPending(RoomEventPtr&& event)
event->setTransactionId(connection->generateTxnId());
auto* pEvent = rawPtr(event);
emit q->pendingEventAboutToAdd(pEvent);
+ // FIXME: This sometimes causes a bad read:
+ // https://travis-ci.org/QMatrixClient/libqmatrixclient/jobs/492156899#L2596
unsyncedEvents.emplace_back(move(event));
emit q->pendingEventAdded();
return pEvent;
@@ -1307,7 +1375,11 @@ RoomEvent* Room::Private::addAsPending(RoomEventPtr&& event)
QString Room::Private::sendEvent(RoomEventPtr&& event)
{
- return doSendEvent(addAsPending(std::move(event)));
+ if (q->successorId().isEmpty())
+ return doSendEvent(addAsPending(std::move(event)));
+
+ qCWarning(MAIN) << q << "has been upgraded, event won't be sent";
+ return {};
}
QString Room::Private::doSendEvent(const RoomEvent* pEvent)
@@ -1569,7 +1641,24 @@ bool isEchoEvent(const RoomEventPtr& le, const PendingEventItem& re)
bool Room::supportsCalls() const
{
- return joinedCount() == 2;
+ return joinedCount() == 2;
+}
+
+void Room::checkVersion()
+{
+ const auto defaultVersion = connection()->defaultRoomVersion();
+ const auto stableVersions = connection()->stableRoomVersions();
+ Q_ASSERT(!defaultVersion.isEmpty() && successorId().isEmpty());
+ // This method is only called after the base state has been loaded
+ // or the server capabilities have been loaded.
+ emit stabilityUpdated(defaultVersion, stableVersions);
+ if (!stableVersions.contains(version()))
+ {
+ qCDebug(MAIN) << this << "version is" << version()
+ << "which the server doesn't count as stable";
+ if (canSwitchVersions())
+ qCDebug(MAIN) << "The current user has enough privileges to fix it";
+ }
}
void Room::inviteCall(const QString& callId, const int lifetime,
@@ -1716,7 +1805,8 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename)
{
// Build our own file path, starting with temp directory and eventId.
filePath = eventId;
- filePath = QDir::tempPath() % '/' % filePath.replace(':', '_') %
+ filePath = QDir::tempPath() % '/' %
+ filePath.replace(QRegularExpression("[/\\<>|\"*?:]"), "_") %
'#' % d->fileNameToDownload(event);
}
auto job = connection()->downloadFile(fileUrl, filePath);
@@ -1799,7 +1889,7 @@ RoomEventPtr makeRedacted(const RoomEvent& target,
std::vector<std::pair<Event::Type, QStringList>> keepContentKeysMap
{ { RoomMemberEvent::typeId(), { QStringLiteral("membership") } }
-// , { RoomCreateEvent::typeId(), { QStringLiteral("creator") } }
+ , { RoomCreateEvent::typeId(), { QStringLiteral("creator") } }
// , { RoomJoinRules::typeId(), { QStringLiteral("join_rule") } }
// , { RoomPowerLevels::typeId(),
// { QStringLiteral("ban"), QStringLiteral("events"),
@@ -2123,7 +2213,23 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
}
, [this] (const EncryptionEvent&) {
emit encryption(); // It can only be done once, so emit it here.
- return EncryptionOn;
+ return OtherChange;
+ }
+ , [this] (const RoomTombstoneEvent& evt) {
+ const auto successorId = evt.successorRoomId();
+ if (auto* successor = connection()->room(successorId))
+ emit upgraded(evt.serverMessage(), successor);
+ else
+ connectUntil(connection(), &Connection::loadedRoomState, this,
+ [this,successorId,serverMsg=evt.serverMessage()]
+ (Room* newRoom) {
+ if (newRoom->id() != successorId)
+ return false;
+ emit upgraded(serverMsg, newRoom);
+ return true;
+ });
+
+ return OtherChange;
}
);
}
diff --git a/lib/room.h b/lib/room.h
index 029f87b7..197926e7 100644
--- a/lib/room.h
+++ b/lib/room.h
@@ -80,6 +80,10 @@ namespace QMatrixClient
Q_PROPERTY(Connection* connection READ connection CONSTANT)
Q_PROPERTY(User* localUser READ localUser CONSTANT)
Q_PROPERTY(QString id READ id CONSTANT)
+ Q_PROPERTY(QString version READ version NOTIFY baseStateLoaded)
+ Q_PROPERTY(bool isUnstable READ isUnstable NOTIFY stabilityUpdated)
+ Q_PROPERTY(QString predecessorId READ predecessorId NOTIFY baseStateLoaded)
+ Q_PROPERTY(QString successorId READ successorId NOTIFY upgraded)
Q_PROPERTY(QString name READ name NOTIFY namesChanged)
Q_PROPERTY(QStringList aliases READ aliases NOTIFY namesChanged)
Q_PROPERTY(QString canonicalAlias READ canonicalAlias NOTIFY namesChanged)
@@ -125,7 +129,7 @@ namespace QMatrixClient
JoinStateChange = 0x20,
TagsChange = 0x40,
MembersChange = 0x80,
- EncryptionOn = 0x100,
+ /* = 0x100, */
AccountDataChange = 0x200,
SummaryChange = 0x400,
ReadMarkerChange = 0x800,
@@ -143,6 +147,10 @@ namespace QMatrixClient
Connection* connection() const;
User* localUser() const;
const QString& id() const;
+ QString version() const;
+ bool isUnstable() const;
+ QString predecessorId() const;
+ QString successorId() const;
QString name() const;
QStringList aliases() const;
QString canonicalAlias() const;
@@ -371,6 +379,9 @@ namespace QMatrixClient
Q_INVOKABLE bool supportsCalls() const;
public slots:
+ /** Check whether the room should be upgraded */
+ void checkVersion();
+
QString postMessage(const QString& plainText, MessageEventType type);
QString postPlainText(const QString& plainText);
QString postHtmlMessage(const QString& plainText,
@@ -415,7 +426,22 @@ namespace QMatrixClient
/// Mark all messages in the room as read
void markAllMessagesAsRead();
+ /// Whether the current user is allowed to upgrade the room
+ bool canSwitchVersions() const;
+
+ /// Switch the room's version (aka upgrade)
+ void switchVersion(QString newVersion);
+
signals:
+ /// Initial set of state events has been loaded
+ /**
+ * The initial set is what comes from the initial sync for the room.
+ * This includes all basic things like RoomCreateEvent,
+ * RoomNameEvent, a (lazy-loaded, not full) set of RoomMemberEvents
+ * etc. This is a per-room reflection of Connection::loadedRoomState
+ * \sa Connection::loadedRoomState
+ */
+ void baseStateLoaded();
void eventsHistoryJobChanged();
void aboutToAddHistoricalMessages(RoomEventsRange events);
void aboutToAddNewMessages(RoomEventsRange events);
@@ -513,6 +539,15 @@ namespace QMatrixClient
void fileTransferCancelled(QString id);
void callEvent(Room* room, const RoomEvent* event);
+
+ /// The room's version stability may have changed
+ void stabilityUpdated(QString recommendedDefault,
+ QStringList stableVersions);
+ /// This room has been upgraded and won't receive updates anymore
+ void upgraded(QString serverMessage, Room* successor);
+ /// An attempted room upgrade has failed
+ void upgradeFailed(QString errorMessage);
+
/// The room is about to be deleted
void beforeDestruction(Room*);
diff --git a/lib/util.h b/lib/util.h
index ade6e8c2..596872e2 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -103,6 +103,9 @@ namespace QMatrixClient
}
Omittable<T>& operator=(value_type&& val)
{
+ // For some reason GCC complains about -Wmaybe-uninitialized
+ // in the context of using Omittable<bool> with converters.h;
+ // though the logic looks very much benign (GCC bug???)
_value = std::move(val);
_omitted = false;
return *this;
@@ -156,7 +159,6 @@ namespace QMatrixClient
}
value_type&& release() { _omitted = true; return std::move(_value); }
- operator const value_type&() const & { return value(); }
const value_type* operator->() const & { return &value(); }
value_type* operator->() & { return &editValue(); }
const value_type& operator*() const & { return value(); }