aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/room.cpp150
-rw-r--r--lib/room.h54
-rw-r--r--lib/roomstateview.cpp35
-rw-r--r--lib/roomstateview.h127
-rw-r--r--lib/user.cpp17
5 files changed, 267 insertions, 116 deletions
diff --git a/lib/room.cpp b/lib/room.cpp
index ba63f50d..1450eb3b 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -16,6 +16,7 @@
#include "syncdata.h"
#include "user.h"
#include "eventstats.h"
+#include "roomstateview.h"
// NB: since Qt 6, moc_room.cpp needs User fully defined
#include "moc_room.cpp"
@@ -103,7 +104,7 @@ public:
static decltype(baseState) stubbedState;
/// The state of the room at syncEdge()
/// \sa syncEdge
- QHash<StateEventKey, const StateEventBase*> currentState;
+ RoomStateView currentState;
/// Servers with aliases for this room except the one of the local user
/// \sa Room::remoteAliases
QSet<QString> aliasServers;
@@ -227,34 +228,6 @@ public:
return evt;
}
- QVector<const StateEventBase*> stateEventsOfType(const QString& evtType) const
- {
- auto vals = QVector<const StateEventBase*>();
- for (auto it = currentState.cbegin(); it != currentState.cend(); ++it)
- if (it.key().first == evtType)
- vals.append(it.value());
-
- return vals;
- }
-
- template <typename EventT>
- const EventT* getCurrentState(const QString& stateKey = {}) const
- {
- const auto* evt = getCurrentState({ EventT::matrixTypeId(), stateKey });
- Q_ASSERT(evt->type() == EventT::typeId()
- && evt->matrixType() == EventT::matrixTypeId());
- return static_cast<const EventT*>(evt);
- }
-
-// template <typename EventT>
-// const auto& getCurrentStateContent(const QString& stateKey = {}) const
-// {
-// if (const auto* evt =
-// currentState.value({ EventT::matrixTypeId(), stateKey }, nullptr))
-// return evt->content();
-// return EventT::content_type()
-// }
-
template <typename EventArrayT>
Changes updateStateFrom(EventArrayT&& events)
{
@@ -480,8 +453,8 @@ const QString& Room::id() const { return d->id; }
QString Room::version() const
{
- const auto v = d->getCurrentState<RoomCreateEvent>()->version();
- return v.isEmpty() ? QStringLiteral("1") : v;
+ const auto v = currentState().query(&RoomCreateEvent::version);
+ return v && !v->isEmpty() ? *v : QStringLiteral("1");
}
bool Room::isUnstable() const
@@ -492,7 +465,10 @@ bool Room::isUnstable() const
QString Room::predecessorId() const
{
- return d->getCurrentState<RoomCreateEvent>()->predecessor().roomId;
+ if (const auto* evt = currentState().get<RoomCreateEvent>())
+ return evt->predecessor().roomId;
+
+ return {};
}
Room* Room::predecessor(JoinStates statesFilter) const
@@ -507,7 +483,8 @@ Room* Room::predecessor(JoinStates statesFilter) const
QString Room::successorId() const
{
- return d->getCurrentState<RoomTombstoneEvent>()->successorRoomId();
+ return currentState().queryOr(&RoomTombstoneEvent::successorRoomId,
+ QString());
}
Room* Room::successor(JoinStates statesFilter) const
@@ -534,39 +511,41 @@ bool Room::allHistoryLoaded() const
QString Room::name() const
{
- return d->getCurrentState<RoomNameEvent>()->name();
+ return currentState().queryOr(&RoomNameEvent::name, QString());
}
QStringList Room::aliases() const
{
- const auto* evt = d->getCurrentState<RoomCanonicalAliasEvent>();
- auto result = evt->altAliases();
- if (!evt->alias().isEmpty())
- result << evt->alias();
- return result;
+ if (const auto* evt = currentState().get<RoomCanonicalAliasEvent>()) {
+ auto result = evt->altAliases();
+ if (!evt->alias().isEmpty())
+ result << evt->alias();
+ return result;
+ }
+ return {};
}
QStringList Room::altAliases() const
{
- return d->getCurrentState<RoomCanonicalAliasEvent>()->altAliases();
+ return currentState().queryOr(&RoomCanonicalAliasEvent::altAliases,
+ QStringList());
}
QString Room::canonicalAlias() const
{
- return d->getCurrentState<RoomCanonicalAliasEvent>()->alias();
+ return currentState().queryOr(&RoomCanonicalAliasEvent::alias, QString());
}
QString Room::displayName() const { return d->displayname; }
QStringList Room::pinnedEventIds() const {
- return d->getCurrentState<RoomPinnedEvent>()->pinnedEvents();
+ return currentState().queryOr(&RoomPinnedEvent::pinnedEvents, QStringList());
}
QVector<const Quotient::RoomEvent*> Quotient::Room::pinnedEvents() const
{
- const auto& pinnedIds = d->getCurrentState<RoomPinnedEvent>()->pinnedEvents();
QVector<const RoomEvent*> pinnedEvents;
- for (auto&& evtId: pinnedIds)
+ for (const auto& evtId : pinnedEventIds())
if (const auto& it = findInTimeline(evtId); it != historyEdge())
pinnedEvents.append(it->event());
@@ -582,7 +561,7 @@ void Room::refreshDisplayName() { d->updateDisplayname(); }
QString Room::topic() const
{
- return d->getCurrentState<RoomTopicEvent>()->topic();
+ return currentState().queryOr(&RoomTopicEvent::topic, QString());
}
QString Room::avatarMediaId() const { return d->avatar.mediaId(); }
@@ -621,7 +600,8 @@ JoinState Room::memberJoinState(User* user) const
Membership Room::memberState(const QString& userId) const
{
- return d->getCurrentState<RoomMemberEvent>(userId)->membership();
+ return currentState().queryOr(userId, &RoomMemberEvent::membership,
+ Membership::Leave);
}
bool Room::isMember(const QString& userId) const
@@ -899,8 +879,9 @@ bool Room::canSwitchVersions() const
if (!successorId().isEmpty())
return false; // No one can upgrade a room that's already upgraded
- if (const auto* plEvt = d->getCurrentState<RoomPowerLevelsEvent>()) {
- const auto currentUserLevel = plEvt->powerLevelForUser(localUser()->id());
+ if (const auto* plEvt = currentState().get<RoomPowerLevelsEvent>()) {
+ const auto currentUserLevel =
+ plEvt->powerLevelForUser(localUser()->id());
const auto tombstonePowerLevel =
plEvt->powerLevelForState("m.room.tombstone"_ls);
return currentUserLevel >= tombstonePowerLevel;
@@ -1008,6 +989,16 @@ const Room::RelatedEvents Room::relatedEvents(
return relatedEvents(evt.id(), relType);
}
+const RoomCreateEvent* Room::creation() const
+{
+ return currentState().get<RoomCreateEvent>();
+}
+
+const RoomTombstoneEvent *Room::tombstone() const
+{
+ return currentState().get<RoomTombstoneEvent>();
+}
+
void Room::Private::getAllMembers()
{
// If already loaded or already loading, there's nothing to do here.
@@ -1472,7 +1463,9 @@ int Room::timelineSize() const { return int(d->timeline.size()); }
bool Room::usesEncryption() const
{
- return !d->getCurrentState<EncryptionEvent>()->algorithm().isEmpty();
+ return !currentState()
+ .queryOr(&EncryptionEvent::algorithm, QString())
+ .isEmpty();
}
const StateEventBase* Room::getCurrentState(const QString& evtType,
@@ -1481,13 +1474,7 @@ const StateEventBase* Room::getCurrentState(const QString& evtType,
return d->getCurrentState({ evtType, stateKey });
}
-const QVector<const StateEventBase*>
-Room::stateEventsOfType(const QString& evtType) const
-{
- return d->stateEventsOfType(evtType);
-}
-
-const QHash<StateEventKey, const StateEventBase*>& Room::currentState() const
+RoomStateView Room::currentState() const
{
return d->currentState;
}
@@ -1564,7 +1551,7 @@ Room::Changes Room::Private::setSummary(RoomSummary&& newSummary)
void Room::Private::insertMemberIntoMap(User* u)
{
const auto maybeUserName =
- getCurrentState<RoomMemberEvent>(u->id())->newDisplayName();
+ currentState.query(u->id(), &RoomMemberEvent::newDisplayName);
if (!maybeUserName)
qCWarning(MEMBERS) << "insertMemberIntoMap():" << u->id()
<< "has no name (even empty)";
@@ -1594,9 +1581,9 @@ void Room::Private::insertMemberIntoMap(User* u)
void Room::Private::removeMemberFromMap(User* u)
{
- const auto userName =
- getCurrentState<RoomMemberEvent>(
- u->id())->newDisplayName().value_or(QString());
+ const auto userName = currentState.queryOr(u->id(),
+ &RoomMemberEvent::newDisplayName,
+ QString());
qCDebug(MEMBERS) << "removeMemberFromMap(), username" << userName
<< "for user" << u->id();
@@ -1679,10 +1666,13 @@ Room::Private::moveEventsToTimeline(RoomEventsRange events,
QString Room::memberName(const QString& mxId) const
{
// See https://github.com/matrix-org/matrix-doc/issues/1375
- const auto rme = getCurrentState<RoomMemberEvent>(mxId);
- return rme->newDisplayName() ? *rme->newDisplayName()
- : rme->prevContent() ? rme->prevContent()->displayName.value_or(QString())
- : QString();
+ if (const auto rme = currentState().get<RoomMemberEvent>(mxId)) {
+ if (rme->newDisplayName())
+ return *rme->newDisplayName();
+ if (rme->prevContent() && rme->prevContent()->displayName)
+ return *rme->prevContent()->displayName;
+ }
+ return {};
}
QString Room::roomMembername(const User* u) const
@@ -1737,10 +1727,13 @@ QString Room::htmlSafeMemberName(const QString& userId) const
QUrl Room::memberAvatarUrl(const QString &mxId) const
{
// See https://github.com/matrix-org/matrix-doc/issues/1375
- const auto rme = getCurrentState<RoomMemberEvent>(mxId);
- return rme->newAvatarUrl() ? *rme->newAvatarUrl()
- : rme->prevContent() ? rme->prevContent()->avatarUrl.value_or(QUrl())
- : QUrl();
+ if (const auto rme = currentState().get<RoomMemberEvent>(mxId)) {
+ if (rme->newAvatarUrl())
+ return *rme->newAvatarUrl();
+ if (rme->prevContent() && rme->prevContent()->avatarUrl)
+ return *rme->prevContent()->avatarUrl;
+ }
+ return {};
}
Room::Changes Room::Private::updateStatsFromSyncData(const SyncRoomData& data,
@@ -2489,12 +2482,14 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction)
auto oldEvent = ti.replaceEvent(makeRedacted(*ti, redaction));
qCDebug(EVENTS) << "Redacted" << oldEvent->id() << "with" << redaction.id();
if (oldEvent->isStateEvent()) {
- const StateEventKey evtKey { oldEvent->matrixType(),
- oldEvent->stateKey() };
- Q_ASSERT(currentState.contains(evtKey));
- if (currentState.value(evtKey) == oldEvent.get()) {
- Q_ASSERT(ti.index() >= 0); // Historical states can't be in
- // currentState
+ // Check whether the old event was a part of current state; if it was,
+ // update the current state to the redacted event object.
+ const auto currentStateEvt =
+ currentState.get(oldEvent->matrixType(), oldEvent->stateKey());
+ Q_ASSERT(currentStateEvt);
+ if (currentStateEvt == oldEvent.get()) {
+ // Historical states can't be in currentState
+ Q_ASSERT(ti.index() >= 0);
qCDebug(STATE).nospace()
<< "Redacting state " << oldEvent->matrixType() << "/"
<< oldEvent->stateKey();
@@ -2514,6 +2509,7 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction)
}
q->onRedaction(*oldEvent, *ti);
emit q->replacedEvent(ti.event(), rawPtr(oldEvent));
+ // By now, all references to oldEvent must have been updated to ti.event()
return true;
}
@@ -2754,7 +2750,7 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events)
for (const auto& eptr : events) {
const auto& e = *eptr;
if (e.isStateEvent()
- && !currentState.contains({ e.matrixType(), e.stateKey() })) {
+ && !currentState.contains(e.matrixType(), e.stateKey())) {
changes |= q->processStateEvent(e);
}
}
@@ -2791,9 +2787,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
return Change::None;
// Find a value (create an empty one if necessary) and get a reference
- // to it. Can't use getCurrentState<>() because it (creates and) returns
- // a stub if a value is not found, and what's needed here is a "real" event
- // or nullptr.
+ // to it, anticipating a change further in the function.
auto& curStateEvent = d->currentState[{ e.matrixType(), e.stateKey() }];
// Prepare for the state change
// clang-format off
diff --git a/lib/room.h b/lib/room.h
index 15bc7648..d49cfb55 100644
--- a/lib/room.h
+++ b/lib/room.h
@@ -10,6 +10,7 @@
#pragma once
#include "connection.h"
+#include "roomstateview.h"
#include "eventitem.h"
#include "quotient_common.h"
@@ -399,14 +400,8 @@ public:
const RelatedEvents relatedEvents(const RoomEvent& evt,
EventRelation::reltypeid_t relType) const;
- const RoomCreateEvent* creation() const
- {
- return getCurrentState<RoomCreateEvent>();
- }
- const RoomTombstoneEvent* tombstone() const
- {
- return getCurrentState<RoomTombstoneEvent>();
- }
+ const RoomCreateEvent* creation() const;
+ const RoomTombstoneEvent* tombstone() const;
bool displayed() const;
/// Mark the room as currently displayed to the user
@@ -760,45 +755,40 @@ public:
/*! This method returns a (potentially empty) state event corresponding
* to the pair of event type \p evtType and state key \p stateKey.
*/
- Q_INVOKABLE const Quotient::StateEventBase*
+ [[deprecated("Use currentState().get() instead; "
+ "make sure to check its result for nullptrs")]] //
+ const Quotient::StateEventBase*
getCurrentState(const QString& evtType, const QString& stateKey = {}) const;
- /// Get all state events in the room.
- /*! This method returns all known state events that have occured in
- * the room, as a mapping from the event type and state key to value.
- */
- const QHash<StateEventKey, const StateEventBase*>& currentState() const;
-
- /// Get all state events in the room of a certain type.
- /*! This method returns all known state events that have occured in
- * the room of the given type.
- */
- Q_INVOKABLE const QVector<const StateEventBase*>
- stateEventsOfType(const QString& evtType) const;
-
/// Get a state event with the given event type and state key
/*! This is a typesafe overload that accepts a C++ event type instead of
* its Matrix name.
*/
template <typename EvT>
+ [[deprecated("Use currentState().get() instead; "
+ "make sure to check its result for nullptrs")]] //
const EvT* getCurrentState(const QString& stateKey = {}) const
{
- const auto* evt =
- eventCast<const EvT>(getCurrentState(EvT::matrixTypeId(), stateKey));
+ QT_IGNORE_DEPRECATIONS(
+ const auto* evt = eventCast<const EvT>(
+ getCurrentState(EvT::matrixTypeId(), stateKey));)
Q_ASSERT(evt);
Q_ASSERT(evt->matrixTypeId() == EvT::matrixTypeId()
&& evt->stateKey() == stateKey);
return evt;
}
- /// Set a state event of the given type with the given arguments
- /*! This typesafe overload attempts to send a state event with the type
- * \p EvT and the content defined by \p args. Specifically, the function
- * creates a temporary object of type \p EvT passing \p args to
- * the constructor, and sends a request to the homeserver using
- * the Matrix event type defined by \p EvT and the event content produced
- * via EvT::contentJson().
- */
+ /// \brief Get the current room state
+ RoomStateView currentState() const;
+
+ //! \brief Set a state event of the given type with the given arguments
+ //!
+ //! This typesafe overload attempts to send a state event with the type
+ //! \p EvT and the content defined by \p args. Specifically, the function
+ //! creates a temporary object of type \p EvT passing \p args to
+ //! the constructor, and sends a request to the homeserver using
+ //! the Matrix event type defined by \p EvT and the event content produced
+ //! via EvT::contentJson().
template <typename EvT, typename... ArgTs>
auto setState(ArgTs&&... args) const
{
diff --git a/lib/roomstateview.cpp b/lib/roomstateview.cpp
new file mode 100644
index 00000000..94c88eee
--- /dev/null
+++ b/lib/roomstateview.cpp
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: 2021 Kitsune Ral <kitsune-ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "roomstateview.h"
+
+using namespace Quotient;
+
+const StateEventBase* RoomStateView::get(const QString& evtType,
+ const QString& stateKey) const
+{
+ return value({ evtType, stateKey });
+}
+
+bool RoomStateView::contains(const QString& evtType,
+ const QString& stateKey) const
+{
+ return contains({ evtType, stateKey });
+}
+
+QJsonObject RoomStateView::contentJson(const QString& evtType,
+ const QString& stateKey) const
+{
+ return queryOr(evtType, stateKey, &Event::contentJson, QJsonObject());
+}
+
+const QVector<const StateEventBase*>
+RoomStateView::eventsOfType(const QString& evtType) const
+{
+ auto vals = QVector<const StateEventBase*>();
+ for (auto it = cbegin(); it != cend(); ++it)
+ if (it.key().first == evtType)
+ vals.append(it.value());
+
+ return vals;
+}
diff --git a/lib/roomstateview.h b/lib/roomstateview.h
new file mode 100644
index 00000000..cab69ae3
--- /dev/null
+++ b/lib/roomstateview.h
@@ -0,0 +1,127 @@
+// SPDX-FileCopyrightText: 2021 Kitsune Ral <kitsune-ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include "events/stateevent.h"
+
+#include <QtCore/QHash>
+
+namespace Quotient {
+
+class Room;
+
+class RoomStateView : private QHash<StateEventKey, const StateEventBase*> {
+ Q_GADGET
+public:
+ const QHash<StateEventKey, const StateEventBase*>& events() const
+ {
+ return *this;
+ }
+
+ //! \brief Get a state event with the given event type and state key
+ //! \return A state event corresponding to the pair of event type
+ //! \p evtType and state key \p stateKey, or nullptr if there's
+ //! no such \p evtType / \p stateKey combination in the current
+ //! state.
+ //! \warning In libQuotient 0.7 the return type changed to an OmittableCref
+ //! which is effectively a nullable const reference wrapper. You
+ //! have to check that it has_value() before using. Alternatively
+ //! you can now use queryCurrentState() to access state safely.
+ //! \sa getCurrentStateContentJson
+ const StateEventBase* get(const QString& evtType,
+ const QString& stateKey = {}) const;
+
+ //! \brief Get a state event with the given event type and state key
+ //!
+ //! This is a typesafe overload that accepts a C++ event type instead of
+ //! its Matrix name.
+ //! \warning In libQuotient 0.7 the return type changed to an Omittable with
+ //! a reference wrapper inside - you have to check that it
+ //! has_value() before using. Alternatively you can now use
+ //! queryCurrentState() to access state safely.
+ template <typename EvT>
+ const EvT* get(const QString& stateKey = {}) const
+ {
+ static_assert(std::is_base_of_v<StateEventBase, EvT>);
+ if (const auto* evt = get(EvT::matrixTypeId(), stateKey)) {
+ Q_ASSERT(evt->matrixType() == EvT::matrixTypeId()
+ && evt->stateKey() == stateKey);
+ return eventCast<const EvT>(evt);
+ }
+ return nullptr;
+ }
+
+ using QHash::contains;
+
+ bool contains(const QString& evtType, const QString& stateKey = {}) const;
+
+ template <typename EvT>
+ bool contains(const QString& stateKey = {}) const
+ {
+ return contains(EvT::matrixTypeId(), stateKey);
+ }
+
+ //! \brief Get the content of the current state event with the given
+ //! event type and state key
+ //! \return An empty object if there's no event in the current state with
+ //! this event type and state key; the contents of the event
+ //! <tt>'content'</tt> object otherwise
+ Q_INVOKABLE QJsonObject contentJson(const QString& evtType,
+ const QString& stateKey = {}) const;
+
+ //! \brief Get all state events in the room of a certain type.
+ //!
+ //! This method returns all known state events that have occured in
+ //! the room of the given type.
+ const QVector<const StateEventBase*>
+ eventsOfType(const QString& evtType) const;
+
+ template <typename FnT>
+ auto query(const QString& evtType, const QString& stateKey, FnT&& fn) const
+ {
+ return lift(std::forward<FnT>(fn), get(evtType, stateKey));
+ }
+
+ template <typename FnT>
+ auto query(const QString& stateKey, FnT&& fn) const
+ {
+ using EventT = std::decay_t<fn_arg_t<FnT>>;
+ static_assert(std::is_base_of_v<StateEventBase, EventT>);
+ return lift(std::forward<FnT>(fn), get<EventT>(stateKey));
+ }
+
+ template <typename FnT, typename FallbackT>
+ auto queryOr(const QString& evtType, const QString& stateKey, FnT&& fn,
+ FallbackT&& fallback) const
+ {
+ return lift(std::forward<FnT>(fn), get(evtType, stateKey))
+ .value_or(std::forward<FallbackT>(fallback));
+ }
+
+ template <typename FnT>
+ auto query(FnT&& fn) const
+ {
+ return query({}, std::forward<FnT>(fn));
+ }
+
+ template <typename FnT, typename FallbackT>
+ auto queryOr(const QString& stateKey, FnT&& fn, FallbackT&& fallback) const
+ {
+ using EventT = std::decay_t<fn_arg_t<FnT>>;
+ static_assert(std::is_base_of_v<StateEventBase, EventT>);
+ return lift(std::forward<FnT>(fn), get<EventT>(stateKey))
+ .value_or(std::forward<FallbackT>(fallback));
+ }
+
+ template <typename FnT, typename FallbackT>
+ auto queryOr(FnT&& fn, FallbackT&& fallback) const
+ {
+ return queryOr({}, std::forward<FnT>(fn),
+ std::forward<FallbackT>(fallback));
+ }
+
+private:
+ friend class Room;
+};
+} // namespace Quotient
diff --git a/lib/user.cpp b/lib/user.cpp
index 0dbc444a..f7840c40 100644
--- a/lib/user.cpp
+++ b/lib/user.cpp
@@ -119,12 +119,17 @@ void User::rename(const QString& newName, const Room* r)
return;
}
// #481: take the current state and update it with the new name
- auto evtC = r->getCurrentState<RoomMemberEvent>(id())->content();
- Q_ASSERT_X(evtC.membership == Membership::Join, __FUNCTION__,
- "Attempt to rename a user that's not a room member");
- evtC.displayName = sanitized(newName);
- r->setState<RoomMemberEvent>(id(), move(evtC));
- // The state will be updated locally after it arrives with sync
+ if (const auto& maybeEvt = r->currentState().get<RoomMemberEvent>(id())) {
+ auto content = maybeEvt->content();
+ if (content.membership == Membership::Join) {
+ content.displayName = sanitized(newName);
+ r->setState<RoomMemberEvent>(id(), move(content));
+ // The state will be updated locally after it arrives with sync
+ return;
+ }
+ }
+ qCCritical(MEMBERS)
+ << "Attempt to rename a non-member in a room context - ignored";
}
template <typename SourceT>