aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--autotests/CMakeLists.txt1
-rw-r--r--autotests/utiltests.cpp45
-rw-r--r--gtad/gtad.yaml2
-rw-r--r--lib/connection.h1
-rw-r--r--lib/converters.h11
-rw-r--r--lib/events/encryptionevent.h21
-rw-r--r--lib/events/eventcontent.cpp1
-rw-r--r--lib/events/receiptevent.cpp1
-rw-r--r--lib/events/roomcreateevent.h1
-rw-r--r--lib/events/roomevent.cpp1
-rw-r--r--lib/events/roommemberevent.cpp1
-rw-r--r--lib/events/roommemberevent.h10
-rw-r--r--lib/events/roompowerlevelsevent.h4
-rw-r--r--lib/events/roomtombstoneevent.h1
-rw-r--r--lib/events/simplestateevents.h1
-rw-r--r--lib/jobs/basejob.cpp2
-rw-r--r--lib/jobs/basejob.h6
-rw-r--r--lib/omittable.h223
-rw-r--r--lib/quotient_common.h7
-rw-r--r--lib/room.cpp186
-rw-r--r--lib/room.h65
-rw-r--r--lib/roomstateview.cpp35
-rw-r--r--lib/roomstateview.h127
-rw-r--r--lib/user.cpp19
-rw-r--r--lib/user.h2
-rw-r--r--lib/util.h141
27 files changed, 605 insertions, 312 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 599424ab..89eb996a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -127,12 +127,14 @@ list(APPEND lib_SRCS
lib/quotient_common.h
lib/quotient_export.h
lib/function_traits.h lib/function_traits.cpp
+ lib/omittable.h
lib/networkaccessmanager.h lib/networkaccessmanager.cpp
lib/connectiondata.h lib/connectiondata.cpp
lib/connection.h lib/connection.cpp
lib/ssosession.h lib/ssosession.cpp
lib/logging.h lib/logging.cpp
lib/room.h lib/room.cpp
+ lib/roomstateview.h lib/roomstateview.cpp
lib/user.h lib/user.cpp
lib/avatar.h lib/avatar.cpp
lib/uri.h lib/uri.cpp
diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index 282ab036..9efab0d1 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -12,3 +12,4 @@ function(QUOTIENT_ADD_TEST)
endfunction()
quotient_add_test(NAME callcandidateseventtest)
+quotient_add_test(NAME utiltests)
diff --git a/autotests/utiltests.cpp b/autotests/utiltests.cpp
new file mode 100644
index 00000000..e3ec63d0
--- /dev/null
+++ b/autotests/utiltests.cpp
@@ -0,0 +1,45 @@
+// SPDX-FileCopyrightText: 2021 Kitsune Ral <kitsune-ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "omittable.h"
+
+#include <QtTest/QtTest>
+
+// compile-time Omittable<> tests
+using namespace Quotient;
+
+Omittable<int> testFn(bool) { return 0; }
+bool testFn2(int) { return false; }
+static_assert(
+ std::is_same_v<decltype(std::declval<Omittable<bool>>().then(testFn)),
+ Omittable<int>>);
+static_assert(
+ std::is_same_v<
+ decltype(std::declval<Omittable<bool>>().then_or(testFn, 0)), int>);
+static_assert(
+ std::is_same_v<decltype(std::declval<Omittable<bool>>().then(testFn)),
+ Omittable<int>>);
+static_assert(std::is_same_v<decltype(std::declval<Omittable<int>>()
+ .then(testFn2)
+ .then(testFn)),
+ Omittable<int>>);
+static_assert(std::is_same_v<decltype(std::declval<Omittable<bool>>()
+ .then(testFn)
+ .then_or(testFn2, false)),
+ bool>);
+
+constexpr auto visitTestFn(int, bool) { return false; }
+static_assert(
+ std::is_same_v<Omittable<bool>, decltype(lift(testFn2, Omittable<int>()))>);
+static_assert(std::is_same_v<Omittable<bool>,
+ decltype(lift(visitTestFn, Omittable<int>(),
+ Omittable<bool>()))>);
+
+class TestUtils : public QObject {
+ Q_OBJECT
+private Q_SLOTS:
+ // TODO
+};
+
+QTEST_APPLESS_MAIN(TestUtils)
+#include "utiltests.moc"
diff --git a/gtad/gtad.yaml b/gtad/gtad.yaml
index 943ac013..03c23886 100644
--- a/gtad/gtad.yaml
+++ b/gtad/gtad.yaml
@@ -45,7 +45,7 @@ analyzer:
types:
- +set: &UseOmittable
useOmittable:
- omittedValue: 'none' # Quotient::none in lib/util.h
+ omittedValue: 'none' # Quotient::none in lib/omittable.h
+on:
- integer:
- int64: qint64
diff --git a/lib/connection.h b/lib/connection.h
index 28688cc1..dc2eaad1 100644
--- a/lib/connection.h
+++ b/lib/connection.h
@@ -8,6 +8,7 @@
#include "ssosession.h"
#include "qt_connection_util.h"
#include "quotient_common.h"
+#include "util.h"
#include "csapi/login.h"
#include "csapi/create_room.h"
diff --git a/lib/converters.h b/lib/converters.h
index 8eea1cd3..8ddf6e45 100644
--- a/lib/converters.h
+++ b/lib/converters.h
@@ -3,6 +3,7 @@
#pragma once
+#include "omittable.h"
#include "util.h"
#include <QtCore/QDate>
@@ -287,6 +288,8 @@ QVariantHash QUOTIENT_API fromJson(const QJsonValue& jv);
// Conditional insertion into a QJsonObject
+constexpr bool IfNotEmpty = false;
+
namespace _impl {
template <typename ValT>
inline void addTo(QJsonObject& o, const QString& k, ValT&& v)
@@ -332,7 +335,7 @@ namespace _impl {
// This one is for types that have isEmpty() when Force is false
template <typename ValT>
- struct AddNode<ValT, false, decltype(std::declval<ValT>().isEmpty())> {
+ struct AddNode<ValT, IfNotEmpty, decltype(std::declval<ValT>().isEmpty())> {
template <typename ContT, typename ForwardedT>
static void impl(ContT& container, const QString& key,
ForwardedT&& value)
@@ -342,9 +345,9 @@ namespace _impl {
}
};
- // This one unfolds Omittable<> (also only when Force is false)
+ // This one unfolds Omittable<> (also only when IfNotEmpty is requested)
template <typename ValT>
- struct AddNode<Omittable<ValT>, false> {
+ struct AddNode<Omittable<ValT>, IfNotEmpty> {
template <typename ContT, typename OmittableT>
static void impl(ContT& container, const QString& key,
const OmittableT& value)
@@ -355,8 +358,6 @@ namespace _impl {
};
} // namespace _impl
-constexpr bool IfNotEmpty = false;
-
/*! Add a key-value pair to QJsonObject or QUrlQuery
*
* Adds a key-value pair(s) specified by \p key and \p value to
diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h
index dfb28b2f..124ced33 100644
--- a/lib/events/encryptionevent.h
+++ b/lib/events/encryptionevent.h
@@ -6,13 +6,18 @@
#include "eventcontent.h"
#include "stateevent.h"
+#include "quotient_common.h"
namespace Quotient {
class QUOTIENT_API EncryptionEventContent : public EventContent::Base {
public:
enum EncryptionType : size_t { MegolmV1AesSha2 = 0, Undefined };
- explicit EncryptionEventContent(EncryptionType et = Undefined);
+ QUO_IMPLICIT EncryptionEventContent(EncryptionType et);
+ [[deprecated("This constructor will require explicit EncryptionType soon")]] //
+ explicit EncryptionEventContent()
+ : EncryptionEventContent(Undefined)
+ {}
explicit EncryptionEventContent(const QJsonObject& json);
EncryptionType encryption;
@@ -34,15 +39,15 @@ public:
using EncryptionType = EncryptionEventContent::EncryptionType;
Q_ENUM(EncryptionType)
- explicit EncryptionEvent(const QJsonObject& obj = {}) // TODO: apropriate
- // default value
+ explicit EncryptionEvent(const QJsonObject& obj)
: StateEvent(typeId(), obj)
{}
- EncryptionEvent(EncryptionEvent&&) = delete;
- template <typename... ArgTs>
- EncryptionEvent(ArgTs&&... contentArgs)
- : StateEvent(typeId(), matrixTypeId(), QString(),
- std::forward<ArgTs>(contentArgs)...)
+ [[deprecated("This constructor will require an explicit parameter soon")]] //
+// explicit EncryptionEvent()
+// : EncryptionEvent(QJsonObject())
+// {}
+ explicit EncryptionEvent(EncryptionEventContent&& content)
+ : StateEvent(typeId(), matrixTypeId(), QString(), std::move(content))
{}
EncryptionType encryption() const { return content().encryption; }
diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp
index 22878d4c..4ce130a6 100644
--- a/lib/events/eventcontent.cpp
+++ b/lib/events/eventcontent.cpp
@@ -4,7 +4,6 @@
#include "eventcontent.h"
#include "converters.h"
-#include "util.h"
#include "logging.h"
#include <QtCore/QMimeDatabase>
diff --git a/lib/events/receiptevent.cpp b/lib/events/receiptevent.cpp
index 72dbf2e3..7f06d99f 100644
--- a/lib/events/receiptevent.cpp
+++ b/lib/events/receiptevent.cpp
@@ -20,7 +20,6 @@ Example of a Receipt Event:
#include "receiptevent.h"
-#include "converters.h"
#include "logging.h"
using namespace Quotient;
diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h
index 016855b9..989030ac 100644
--- a/lib/events/roomcreateevent.h
+++ b/lib/events/roomcreateevent.h
@@ -11,7 +11,6 @@ class QUOTIENT_API RoomCreateEvent : public StateEventBase {
public:
DEFINE_EVENT_TYPEID("m.room.create", RoomCreateEvent)
- explicit RoomCreateEvent() : StateEventBase(typeId(), matrixTypeId()) {}
explicit RoomCreateEvent(const QJsonObject& obj)
: StateEventBase(typeId(), obj)
{}
diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp
index b728e0bf..3502e3f7 100644
--- a/lib/events/roomevent.cpp
+++ b/lib/events/roomevent.cpp
@@ -3,7 +3,6 @@
#include "roomevent.h"
-#include "converters.h"
#include "logging.h"
#include "redactionevent.h"
diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp
index 3141f6b5..b4770224 100644
--- a/lib/events/roommemberevent.cpp
+++ b/lib/events/roommemberevent.cpp
@@ -4,7 +4,6 @@
#include "roommemberevent.h"
-#include "converters.h"
#include "logging.h"
#include <QtCore/QtAlgorithms>
diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h
index 5e446dbe..ceb7826b 100644
--- a/lib/events/roommemberevent.h
+++ b/lib/events/roommemberevent.h
@@ -15,9 +15,7 @@ public:
using MembershipType
[[deprecated("Use Quotient::Membership instead")]] = Membership;
- explicit MemberEventContent(Membership ms = Membership::Join)
- : membership(ms)
- {}
+ QUO_IMPLICIT MemberEventContent(Membership ms) : membership(ms) {}
explicit MemberEventContent(const QJsonObject& json);
Membership membership;
@@ -43,10 +41,8 @@ public:
explicit RoomMemberEvent(const QJsonObject& obj) : StateEvent(typeId(), obj)
{}
- template <typename... ArgTs>
- RoomMemberEvent(const QString& userId, ArgTs&&... contentArgs)
- : StateEvent(typeId(), matrixTypeId(), userId,
- std::forward<ArgTs>(contentArgs)...)
+ RoomMemberEvent(const QString& userId, MemberEventContent&& content)
+ : StateEvent(typeId(), matrixTypeId(), userId, std::move(content))
{}
//! \brief A special constructor to create unknown RoomMemberEvents
diff --git a/lib/events/roompowerlevelsevent.h b/lib/events/roompowerlevelsevent.h
index 80e27048..415cc814 100644
--- a/lib/events/roompowerlevelsevent.h
+++ b/lib/events/roompowerlevelsevent.h
@@ -36,10 +36,12 @@ protected:
class QUOTIENT_API RoomPowerLevelsEvent
: public StateEvent<PowerLevelsEventContent> {
- Q_GADGET
public:
DEFINE_EVENT_TYPEID("m.room.power_levels", RoomPowerLevelsEvent)
+ explicit RoomPowerLevelsEvent(PowerLevelsEventContent&& content)
+ : StateEvent(typeId(), matrixTypeId(), QString(), std::move(content))
+ {}
explicit RoomPowerLevelsEvent(const QJsonObject& obj)
: StateEvent(typeId(), obj)
{}
diff --git a/lib/events/roomtombstoneevent.h b/lib/events/roomtombstoneevent.h
index e336c448..15d26923 100644
--- a/lib/events/roomtombstoneevent.h
+++ b/lib/events/roomtombstoneevent.h
@@ -10,7 +10,6 @@ class QUOTIENT_API RoomTombstoneEvent : public StateEventBase {
public:
DEFINE_EVENT_TYPEID("m.room.tombstone", RoomTombstoneEvent)
- explicit RoomTombstoneEvent() : StateEventBase(typeId(), matrixTypeId()) {}
explicit RoomTombstoneEvent(const QJsonObject& obj)
: StateEventBase(typeId(), obj)
{}
diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h
index e6c05880..9610574b 100644
--- a/lib/events/simplestateevents.h
+++ b/lib/events/simplestateevents.h
@@ -35,7 +35,6 @@ namespace EventContent {
public: \
using value_type = content_type::value_type; \
DEFINE_EVENT_TYPEID(_TypeId, _Name) \
- explicit _Name() : _Name(value_type()) {} \
template <typename T> \
explicit _Name(T&& value) \
: StateEvent(typeId(), matrixTypeId(), QString(), \
diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp
index f518a1b0..b6858b5a 100644
--- a/lib/jobs/basejob.cpp
+++ b/lib/jobs/basejob.cpp
@@ -5,11 +5,9 @@
#include "basejob.h"
#include "connectiondata.h"
-#include "quotient_common.h"
#include <QtCore/QRegularExpression>
#include <QtCore/QTimer>
-#include <QtCore/QStringBuilder>
#include <QtCore/QMetaEnum>
#include <QtCore/QPointer>
#include <QtNetwork/QNetworkAccessManager>
diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h
index 9ed58ba8..555c602b 100644
--- a/lib/jobs/basejob.h
+++ b/lib/jobs/basejob.h
@@ -5,9 +5,9 @@
#pragma once
#include "requestdata.h"
-#include "../logging.h"
-#include "../converters.h"
-#include "../quotient_common.h"
+#include "logging.h"
+#include "converters.h" // Common for csapi/ headers even though not used here
+#include "quotient_common.h" // For DECL_DEPRECATED_ENUMERATOR
#include <QtCore/QObject>
#include <QtCore/QStringBuilder>
diff --git a/lib/omittable.h b/lib/omittable.h
new file mode 100644
index 00000000..b5efecf5
--- /dev/null
+++ b/lib/omittable.h
@@ -0,0 +1,223 @@
+// SPDX-FileCopyrightText: 2018 Kitsune Ral <kitsune-ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include <optional>
+#include <functional>
+
+namespace Quotient {
+
+template <typename T>
+class Omittable;
+
+constexpr auto none = std::nullopt;
+
+//! \brief Lift an operation into dereferenceable types (Omittables or pointers)
+//!
+//! This is a more generic version of Omittable::then() that extends to
+//! an arbitrary number of arguments of any type that is dereferenceable (unary
+//! operator*() can be applied to it) and (explicitly or implicitly) convertible
+//! to bool. This allows to streamline checking for nullptr/none before applying
+//! the operation on the underlying types. \p fn is only invoked if all \p args
+//! are "truthy" (i.e. <tt>(... && bool(args)) == true</tt>).
+//! \param fn A callable that should accept the types stored inside
+//! Omittables/pointers passed in \p args
+//! \return Always an Omittable: if \p fn returns another type, lift() wraps
+//! it in an Omittable; if \p fn returns an Omittable, that return value
+//! (or none) is returned as is.
+template <typename FnT, typename... MaybeTs>
+inline auto lift(FnT&& fn, MaybeTs&&... args)
+{
+ return (... && bool(args))
+ ? Omittable(std::invoke(std::forward<FnT>(fn), *args...))
+ : none;
+}
+
+/** `std::optional` with tweaks
+ *
+ * The tweaks are:
+ * - streamlined assignment (operator=)/emplace()ment of values that can be
+ * used to implicitly construct the underlying type, including
+ * direct-list-initialisation, e.g.:
+ * \code
+ * struct S { int a; char b; }
+ * Omittable<S> o;
+ * o = { 1, 'a' }; // std::optional would require o = S { 1, 'a' }
+ * \endcode
+ * - entirely deleted value(). The technical reason is that Xcode 10 doesn't
+ * have it; but besides that, value_or() or (after explicit checking)
+ * `operator*()`/`operator->()` are better alternatives within Quotient
+ * that doesn't practice throwing exceptions (as doesn't most of Qt).
+ * - disabled non-const lvalue operator*() and operator->(), as it's too easy
+ * to inadvertently cause a value change through them.
+ * - ensure() to provide a safe and explicit lvalue accessor instead of
+ * those above. Allows chained initialisation of nested Omittables:
+ * \code
+ * struct Inner { int member = 10; Omittable<int> innermost; };
+ * struct Outer { int anotherMember = 10; Omittable<Inner> inner; };
+ * Omittable<Outer> o; // = { 10, std::nullopt };
+ * o.ensure().inner.ensure().innermost.emplace(42);
+ * \endcode
+ * - merge() - a soft version of operator= that only overwrites its first
+ * operand with the second one if the second one is not empty.
+ * - then() and then_or() to streamline read-only interrogation in a "monadic"
+ * interface.
+ */
+template <typename T>
+class Omittable : public std::optional<T> {
+public:
+ using base_type = std::optional<T>;
+ using value_type = std::decay_t<T>;
+
+ using std::optional<T>::optional;
+
+ // Overload emplace() and operator=() to allow passing braced-init-lists
+ // (the standard emplace() does direct-initialisation but
+ // not direct-list-initialisation).
+ using base_type::operator=;
+ Omittable& operator=(const value_type& v)
+ {
+ base_type::operator=(v);
+ return *this;
+ }
+ Omittable& operator=(value_type&& v)
+ {
+ base_type::operator=(std::move(v));
+ return *this;
+ }
+
+ using base_type::emplace;
+ T& emplace(const T& val) { return base_type::emplace(val); }
+ T& emplace(T&& val) { return base_type::emplace(std::move(val)); }
+
+ // Use value_or() or check (with operator! or has_value) before accessing
+ // with operator-> or operator*
+ // The technical reason is that Xcode 10 has incomplete std::optional
+ // that has no value(); but using value() may also mean that you rely
+ // on the optional throwing an exception (which is not assumed practice
+ // throughout Quotient) or that you spend unnecessary CPU cycles on
+ // an extraneous has_value() check.
+ auto& value() = delete;
+ const auto& value() const = delete;
+
+ template <typename U>
+ value_type& ensure(U&& defaultValue = value_type {})
+ {
+ return this->has_value() ? this->operator*()
+ : this->emplace(std::forward<U>(defaultValue));
+ }
+ value_type& ensure(const value_type& defaultValue)
+ {
+ return ensure<>(defaultValue);
+ }
+ value_type& ensure(value_type&& defaultValue)
+ {
+ return ensure<>(std::move(defaultValue));
+ }
+
+ //! Merge the value from another Omittable
+ //! \return true if \p other is not omitted and the value of
+ //! the current Omittable was different (or omitted),
+ //! in other words, if the current Omittable has changed;
+ //! false otherwise
+ template <typename T1>
+ auto merge(const std::optional<T1>& other)
+ -> std::enable_if_t<std::is_convertible_v<T1, T>, bool>
+ {
+ if (!other || (this->has_value() && **this == *other))
+ return false;
+ this->emplace(*other);
+ return true;
+ }
+
+ // Hide non-const lvalue operator-> and operator* as these are
+ // a bit too surprising: value() & doesn't lazy-create an object;
+ // and it's too easy to inadvertently change the underlying value.
+
+ const value_type* operator->() const& { return base_type::operator->(); }
+ value_type* operator->() && { return base_type::operator->(); }
+ const value_type& operator*() const& { return base_type::operator*(); }
+ value_type& operator*() && { return base_type::operator*(); }
+
+ // The below is inspired by the proposed std::optional monadic operations
+ // (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0798r6.html).
+
+ //! \brief Lift a callable into the Omittable
+ //!
+ //! 'Lifting', as used in functional programming, means here invoking
+ //! a callable (e.g., a function) on the contents of the Omittable if it has
+ //! any and wrapping the returned value (that may be of a different type T2)
+ //! into a new Omittable\<T2>. If the current Omittable is empty,
+ //! the invocation is skipped altogether and Omittable\<T2>{none} is
+ //! returned instead.
+ //! \note if \p fn already returns an Omittable (i.e., it is a 'functor',
+ //! in functional programming terms), then() will not wrap another
+ //! Omittable around but will just return what \p fn returns. The
+ //! same doesn't hold for the parameter: if \p fn accepts an Omittable
+ //! you have to wrap it in another Omittable before calling then().
+ //! \return `none` if the current Omittable has `none`;
+ //! otherwise, the Omittable returned from a call to \p fn
+ //! \tparam FnT a callable with \p T (or <tt>const T&</tt>)
+ //! returning Omittable<T2>, T2 is any supported type
+ //! \sa then_or, transform
+ template <typename FnT>
+ auto then(FnT&& fn) const&
+ {
+ return lift(std::forward<FnT>(fn), *this);
+ }
+
+ //! \brief Lift a callable into the rvalue Omittable
+ //!
+ //! This is an rvalue overload for then().
+ template <typename FnT>
+ auto then(FnT&& fn) &&
+ {
+ return lift(std::forward<FnT>(fn), *this);
+ }
+
+ //! \brief Lift a callable into the const lvalue Omittable, with a fallback
+ //!
+ //! This effectively does the same what then() does, except that it returns
+ //! a value of type returned by the callable, or the provided fallback value
+ //! if the current Omittable is empty. This is a typesafe version to apply
+ //! an operation on an Omittable without having to deal with another
+ //! Omittable afterwards.
+ template <typename FnT, typename FallbackT>
+ auto then_or(FnT&& fn, FallbackT&& fallback) const&
+ {
+ return then(std::forward<FnT>(fn))
+ .value_or(std::forward<FallbackT>(fallback));
+ }
+
+ //! \brief Lift a callable into the rvalue Omittable, with a fallback
+ //!
+ //! This is an overload for functions that accept rvalue
+ template <typename FnT, typename FallbackT>
+ auto then_or(FnT&& fn, FallbackT&& fallback) &&
+ {
+ return then(std::forward<FnT>(fn))
+ .value_or(std::forward<FallbackT>(fallback));
+ }
+};
+
+template <typename T>
+Omittable(T&&) -> Omittable<T>;
+
+//! \brief Merge the value from an optional
+//! This is an adaptation of Omittable::merge() to the case when the value
+//! on the left hand side is not an Omittable.
+//! \return true if \p rhs is not omitted and the \p lhs value was different,
+//! in other words, if \p lhs has changed;
+//! false otherwise
+template <typename T1, typename T2>
+inline auto merge(T1& lhs, const std::optional<T2>& rhs)
+ -> std::enable_if_t<std::is_assignable_v<T1&, const T2&>, bool>
+{
+ if (!rhs || lhs == *rhs)
+ return false;
+ lhs = *rhs;
+ return true;
+}
+
+} // namespace Quotient
diff --git a/lib/quotient_common.h b/lib/quotient_common.h
index 02a9f0cd..b3fb3efa 100644
--- a/lib/quotient_common.h
+++ b/lib/quotient_common.h
@@ -25,6 +25,13 @@
Q_ENUM_NS_IMPL(Enum) \
Q_FLAG_NS(Flags)
+// Apple Clang hasn't caught up with explicit(bool) yet
+#if __cpp_conditional_explicit >= 201806L
+#define QUO_IMPLICIT explicit(false)
+#else
+#define QUO_IMPLICIT
+#endif
+
#define DECL_DEPRECATED_ENUMERATOR(Deprecated, Recommended) \
Deprecated Q_DECL_ENUMERATOR_DEPRECATED_X("Use " #Recommended) = Recommended
diff --git a/lib/room.cpp b/lib/room.cpp
index ba63f50d..abd6110c 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)
{
@@ -324,7 +297,9 @@ public:
QString doSendEvent(const RoomEvent* pEvent);
void onEventSendingFailure(const QString& txnId, BaseJob* call = nullptr);
- SetRoomStateWithKeyJob* requestSetState(const StateEventBase& event)
+ SetRoomStateWithKeyJob* requestSetState(const QString& evtType,
+ const QString& stateKey,
+ const QJsonObject& contentJson)
{
// if (event.roomId().isEmpty())
// event.setRoomId(id);
@@ -332,14 +307,8 @@ public:
// event.setSender(connection->userId());
// TODO: Queue up state events sending (see #133).
// TODO: Maybe addAsPending() as well, despite having no txnId
- return connection->callApi<SetRoomStateWithKeyJob>(
- id, event.matrixType(), event.stateKey(), event.contentJson());
- }
-
- template <typename EvT, typename... ArgTs>
- auto requestSetState(ArgTs&&... args)
- {
- return requestSetState(EvT(std::forward<ArgTs>(args)...));
+ return connection->callApi<SetRoomStateWithKeyJob>(id, evtType, stateKey,
+ contentJson);
}
/*! Apply redaction to the timeline
@@ -480,8 +449,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 +461,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 +479,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 +507,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 +557,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 +596,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 +875,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 +985,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 +1459,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 +1470,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 +1547,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 +1577,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 +1662,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 +1723,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,
@@ -2133,33 +2122,41 @@ QString Room::postJson(const QString& matrixType,
return d->sendEvent(loadEvent<RoomEvent>(matrixType, eventContent));
}
-SetRoomStateWithKeyJob* Room::setState(const StateEventBase& evt) const
+SetRoomStateWithKeyJob* Room::setState(const StateEventBase& evt)
+{
+ return d->requestSetState(evt.matrixType(), evt.stateKey(),
+ evt.contentJson());
+}
+
+SetRoomStateWithKeyJob* Room::setState(const QString& evtType,
+ const QString& stateKey,
+ const QJsonObject& contentJson)
{
- return d->requestSetState(evt);
+ return d->requestSetState(evtType, stateKey, contentJson);
}
void Room::setName(const QString& newName)
{
- d->requestSetState<RoomNameEvent>(newName);
+ setState<RoomNameEvent>(newName);
}
void Room::setCanonicalAlias(const QString& newAlias)
{
- d->requestSetState<RoomCanonicalAliasEvent>(newAlias, altAliases());
+ setState<RoomCanonicalAliasEvent>(newAlias, altAliases());
}
void Room::setPinnedEvents(const QStringList& events)
{
- d->requestSetState<RoomPinnedEvent>(events);
+ setState<RoomPinnedEvent>(events);
}
void Room::setLocalAliases(const QStringList& aliases)
{
- d->requestSetState<RoomCanonicalAliasEvent>(canonicalAlias(), aliases);
+ setState<RoomCanonicalAliasEvent>(canonicalAlias(), aliases);
}
void Room::setTopic(const QString& newTopic)
{
- d->requestSetState<RoomTopicEvent>(newTopic);
+ setState<RoomTopicEvent>(newTopic);
}
bool isEchoEvent(const RoomEventPtr& le, const PendingEventItem& re)
@@ -2489,12 +2486,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 +2513,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 +2754,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 +2791,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..9f70d77a 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,47 +755,45 @@ 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;
+
+ //! Send a request to update the room state with the given event
+ SetRoomStateWithKeyJob* setState(const StateEventBase& evt);
+
+ //! \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
+ auto setState(ArgTs&&... args)
{
return setState(EvT(std::forward<ArgTs>(args)...));
}
@@ -834,8 +827,10 @@ public Q_SLOTS:
QString retryMessage(const QString& txnId);
void discardMessage(const QString& txnId);
- /// Send a request to update the room state with the given event
- SetRoomStateWithKeyJob* setState(const StateEventBase& evt) const;
+ //! Send a request to update the room state based on freeform inputs
+ SetRoomStateWithKeyJob* setState(const QString& evtType,
+ const QString& stateKey,
+ const QJsonObject& contentJson);
void setName(const QString& newName);
void setCanonicalAlias(const QString& newAlias);
void setPinnedEvents(const QStringList& events);
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..4c3fc9e2 100644
--- a/lib/user.cpp
+++ b/lib/user.cpp
@@ -110,7 +110,7 @@ void User::rename(const QString& newName)
});
}
-void User::rename(const QString& newName, const Room* r)
+void User::rename(const QString& newName, Room* r)
{
if (!r) {
qCWarning(MAIN) << "Passing a null room to two-argument User::rename()"
@@ -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>
diff --git a/lib/user.h b/lib/user.h
index 8412b7fd..dfbff4a0 100644
--- a/lib/user.h
+++ b/lib/user.h
@@ -96,7 +96,7 @@ public Q_SLOTS:
/// Set a new name in the global user profile
void rename(const QString& newName);
/// Set a new name for the user in one room
- void rename(const QString& newName, const Room* r);
+ void rename(const QString& newName, Room* r);
/// Upload the file and use it as an avatar
bool setAvatar(const QString& fileName);
/// Upload contents of the QIODevice and set that as an avatar
diff --git a/lib/util.h b/lib/util.h
index 3505b62f..753eb1ea 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -9,10 +9,8 @@
#include <QtCore/QLatin1String>
#include <QtCore/QHashFunctions>
-#include <functional>
#include <memory>
#include <unordered_map>
-#include <optional>
#ifndef Q_DISABLE_MOVE
// Q_DISABLE_MOVE was introduced in Q_VERSION_CHECK(5,13,0)
@@ -52,145 +50,6 @@ struct HashQ {
template <typename KeyT, typename ValT>
using UnorderedMap = std::unordered_map<KeyT, ValT, HashQ<KeyT>>;
-namespace _impl {
- template <typename TT>
- constexpr auto IsOmittableValue = false;
- template <typename TT>
- constexpr auto IsOmittable = IsOmittableValue<std::decay_t<TT>>;
-}
-
-constexpr auto none = std::nullopt;
-
-/** `std::optional` with tweaks
- *
- * The tweaks are:
- * - streamlined assignment (operator=)/emplace()ment of values that can be
- * used to implicitly construct the underlying type, including
- * direct-list-initialisation, e.g.:
- * \code
- * struct S { int a; char b; }
- * Omittable<S> o;
- * o = { 1, 'a' }; // std::optional would require o = S { 1, 'a' }
- * \endcode
- * - entirely deleted value(). The technical reason is that Xcode 10 doesn't
- * have it; but besides that, value_or() or (after explicit checking)
- * `operator*()`/`operator->()` are better alternatives within Quotient
- * that doesn't practice throwing exceptions (as doesn't most of Qt).
- * - disabled non-const lvalue operator*() and operator->(), as it's too easy
- * to inadvertently cause a value change through them.
- * - edit() to provide a safe and explicit lvalue accessor instead of those
- * above. Requires the underlying type to be default-constructible.
- * Allows chained initialisation of nested Omittables:
- * \code
- * struct Inner { int member = 10; Omittable<int> innermost; };
- * struct Outer { int anotherMember = 10; Omittable<Inner> inner; };
- * Omittable<Outer> o; // = { 10, std::nullopt };
- * o.edit().inner.edit().innermost.emplace(42);
- * \endcode
- * - merge() - a soft version of operator= that only overwrites its first
- * operand with the second one if the second one is not empty.
- */
-template <typename T>
-class Omittable : public std::optional<T> {
-public:
- using base_type = std::optional<T>;
- using value_type = std::decay_t<T>;
-
- using std::optional<T>::optional;
-
- // Overload emplace() and operator=() to allow passing braced-init-lists
- // (the standard emplace() does direct-initialisation but
- // not direct-list-initialisation).
- using base_type::operator=;
- Omittable& operator=(const value_type& v)
- {
- base_type::operator=(v);
- return *this;
- }
- Omittable& operator=(value_type&& v)
- {
- base_type::operator=(v);
- return *this;
- }
- using base_type::emplace;
- T& emplace(const T& val) { return base_type::emplace(val); }
- T& emplace(T&& val) { return base_type::emplace(std::move(val)); }
-
- // use value_or() or check (with operator! or has_value) before accessing
- // with operator-> or operator*
- // The technical reason is that Xcode 10 has incomplete std::optional
- // that has no value(); but using value() may also mean that you rely
- // on the optional throwing an exception (which is not assumed practice
- // throughout Quotient) or that you spend unnecessary CPU cycles on
- // an extraneous has_value() check.
- value_type& value() = delete;
- const value_type& value() const = delete;
- value_type& edit()
- {
- return this->has_value() ? base_type::operator*() : this->emplace();
- }
-
- [[deprecated("Use '!o' or '!o.has_value()' instead of 'o.omitted()'")]]
- bool omitted() const
- {
- return !this->has_value();
- }
-
- //! Merge the value from another Omittable
- //! \return true if \p other is not omitted and the value of
- //! the current Omittable was different (or omitted),
- //! in other words, if the current Omittable has changed;
- //! false otherwise
- template <typename T1>
- auto merge(const Omittable<T1>& other)
- -> std::enable_if_t<std::is_convertible_v<T1, T>, bool>
- {
- if (!other || (this->has_value() && **this == *other))
- return false;
- emplace(*other);
- return true;
- }
-
- // Hide non-const lvalue operator-> and operator* as these are
- // a bit too surprising: value() & doesn't lazy-create an object;
- // and it's too easy to inadvertently change the underlying value.
-
- const value_type* operator->() const& { return base_type::operator->(); }
- value_type* operator->() && { return base_type::operator->(); }
- const value_type& operator*() const& { return base_type::operator*(); }
- value_type& operator*() && { return base_type::operator*(); }
-};
-template <typename T>
-Omittable(T&&) -> Omittable<T>;
-
-namespace _impl {
- template <typename T>
- constexpr auto IsOmittableValue<Omittable<T>> = true;
-}
-
-template <typename T1, typename T2>
-inline auto merge(Omittable<T1>& lhs, T2&& rhs)
-{
- return lhs.merge(std::forward<T2>(rhs));
-}
-
-//! \brief Merge the value from an Omittable
-//! This is an adaptation of Omittable::merge() to the case when the value
-//! on the left hand side is not an Omittable.
-//! \return true if \p rhs is not omitted and the \p lhs value was different,
-//! in other words, if \p lhs has changed;
-//! false otherwise
-template <typename T1, typename T2>
-inline auto merge(T1& lhs, const Omittable<T2>& rhs)
- -> std::enable_if_t<!_impl::IsOmittable<T1>
- && std::is_convertible_v<T2, T1>, bool>
-{
- if (!rhs || lhs == *rhs)
- return false;
- lhs = *rhs;
- return true;
-}
-
constexpr auto operator"" _ls(const char* s, std::size_t size)
{
return QLatin1String(s, int(size));