aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKitsune Ral <Kitsune-Ral@users.sf.net>2019-02-18 08:15:04 +0900
committerKitsune Ral <Kitsune-Ral@users.sf.net>2019-02-18 08:15:04 +0900
commit23008cac0c351d2e30583c53456394bbd4d14c5a (patch)
treeb69e672238703a20c03052e7dbad9e789e0cc78c
parent3285850731022773c45621509205b9e8cd790282 (diff)
parent9ef28a3b43dc576716ace005e300b43c3af74b9f (diff)
downloadlibquotient-23008cac0c351d2e30583c53456394bbd4d14c5a.tar.gz
libquotient-23008cac0c351d2e30583c53456394bbd4d14c5a.zip
Merge branch 'kitsune-room-versions'
-rw-r--r--CMakeLists.txt2
-rw-r--r--lib/connection.cpp105
-rw-r--r--lib/connection.h47
-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/room.cpp118
-rw-r--r--lib/room.h37
-rw-r--r--libqmatrixclient.pri4
10 files changed, 462 insertions, 17 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9729811b..d2d8c218 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -90,6 +90,8 @@ set(libqmatrixclient_SRCS
lib/events/roomevent.cpp
lib/events/stateevent.cpp
lib/events/eventcontent.cpp
+ lib/events/roomcreateevent.cpp
+ lib/events/roomtombstoneevent.cpp
lib/events/roommessageevent.cpp
lib/events/roommemberevent.cpp
lib/events/typingevent.cpp
diff --git a/lib/connection.cpp b/lib/connection.cpp
index 63b0a31d..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"
@@ -92,6 +93,9 @@ class Connection::Private
QString userId;
int syncLoopTimeout = -1;
+ GetCapabilitiesJob* capabilitiesJob = nullptr;
+ GetCapabilitiesJob::Capabilities capabilities;
+
SyncJob* syncJob = nullptr;
bool cacheState = true;
@@ -244,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)
@@ -256,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,
@@ -356,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())
@@ -550,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)
@@ -559,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));
@@ -661,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)
@@ -1258,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 45b691e1..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,6 +404,11 @@ 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(); }
@@ -404,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 = {});
@@ -501,6 +545,7 @@ namespace QMatrixClient
void resolveError(QString error);
void homeserverChanged(QUrl baseUrl);
+ void capabilitiesLoaded();
void connected();
void reconnected(); //< \deprecated Use connected() instead
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/room.cpp b/lib/room.cpp
index d806183f..f6956d82 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"
@@ -250,11 +253,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 +305,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 +324,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 +580,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 +819,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 +1365,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 +1374,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 +1640,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,
@@ -1799,7 +1887,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 +2211,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/libqmatrixclient.pri b/libqmatrixclient.pri
index f523f3a2..be568bd2 100644
--- a/libqmatrixclient.pri
+++ b/libqmatrixclient.pri
@@ -26,6 +26,8 @@ HEADERS += \
$$SRCPATH/events/eventcontent.h \
$$SRCPATH/events/roommessageevent.h \
$$SRCPATH/events/simplestateevents.h \
+ $$SRCPATH/events/roomcreateevent.h \
+ $$SRCPATH/events/roomtombstoneevent.h \
$$SRCPATH/events/roommemberevent.h \
$$SRCPATH/events/roomavatarevent.h \
$$SRCPATH/events/typingevent.h \
@@ -68,6 +70,8 @@ SOURCES += \
$$SRCPATH/events/roomevent.cpp \
$$SRCPATH/events/stateevent.cpp \
$$SRCPATH/events/eventcontent.cpp \
+ $$SRCPATH/events/roomcreateevent.cpp \
+ $$SRCPATH/events/roomtombstoneevent.cpp \
$$SRCPATH/events/roommessageevent.cpp \
$$SRCPATH/events/roommemberevent.cpp \
$$SRCPATH/events/typingevent.cpp \